+ if (nodes.length > maxLabelCount) {
+ const label = `+ ${nodes.length - maxLabelCount} ...`
+ const key = nodes.slice(maxLabelCount).map(node => node.rawNode)
+ items.push({ isMax: true, key, label } as unknown as MergedNode & { isMax?: boolean })
+ }
+ return items
+ })
+
+ const showItems = computed(() => {
+ return treeSelectProps.multiple || (selectedValue.value.length > 0 && !searchValue.value)
+ })
+
+ const showPlaceholder = computed(() => {
+ return selectedValue.value.length === 0 && (!searchValue.value || treeSelectProps.searchable === 'overlay')
+ })
+
+ return () => {
+ const { clearable, suffix } = props
+ const { multiple, readonly } = treeSelectProps
+ const disabled = isDisabled.value
+ const prefixCls = `${mergedPrefixCls.value}-selector`
+
+ const itemPrefixCls = `${prefixCls}-item`
+ const itemNodes = selectedItems.value.map(item => {
+ const { key, isMax, label } = item
+ const itemProps = {
+ key,
+ disabled: disabled || item.selectDisabled,
+ prefixCls: itemPrefixCls,
+ removable: multiple && !isMax,
+ readonly,
+ title: label,
+ handleItemRemove,
+ }
+ let labelNode: VNodeTypes | undefined
+ if (isMax) {
+ labelNode = slots.maxLabel?.(item.key) ?? label
+ } else {
+ labelNode = slots.label?.(item.rawNode) ?? label
+ }
+ return - {labelNode}
+ })
+
+ return (
+
+ )
+ }
+ },
+})
diff --git a/packages/components/tree-select/src/trigger/Trigger.tsx b/packages/components/tree-select/src/trigger/Trigger.tsx
new file mode 100644
index 000000000..fb9ce148f
--- /dev/null
+++ b/packages/components/tree-select/src/trigger/Trigger.tsx
@@ -0,0 +1,107 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+import { computed, defineComponent, inject, onBeforeUnmount, onMounted, watch } from 'vue'
+
+import { FORM_TOKEN } from '@idux/components/form'
+
+import { treeSelectToken } from '../token'
+import Selector from './Selector'
+
+const hiddenBoxStyle = { width: 0, height: 0, display: 'flex', overflow: 'hidden', opacity: 0 }
+
+export default defineComponent({
+ setup() {
+ const {
+ props,
+ slots,
+ config,
+ focusMonitor,
+ mergedPrefixCls,
+ triggerRef,
+ isDisabled,
+ selectedValue,
+ isFocused,
+ overlayOpened,
+ handleFocus,
+ handleBlur,
+ setOverlayOpened,
+ } = inject(treeSelectToken)!
+ const formContext = inject(FORM_TOKEN, null)
+
+ const clearable = computed(() => {
+ return !isDisabled.value && props.clearable && selectedValue.value.length > 0
+ })
+ const searchable = computed(() => {
+ return !isDisabled.value && props.searchable
+ })
+
+ const suffix = computed(() => {
+ const { suffix } = props
+ if (suffix) {
+ return suffix
+ }
+ return props.searchable === true && isFocused.value ? 'search' : config.suffix
+ })
+
+ const classes = computed(() => {
+ const { multiple, readonly, size = formContext?.size.value ?? config.size } = props
+ const disabled = isDisabled.value
+ const prefixCls = mergedPrefixCls.value
+ return {
+ [prefixCls]: true,
+ [`${prefixCls}-clearable`]: clearable.value,
+ [`${prefixCls}-disabled`]: disabled,
+ [`${prefixCls}-readonly`]: readonly,
+ [`${prefixCls}-focused`]: isFocused.value,
+ [`${prefixCls}-opened`]: overlayOpened.value,
+ [`${prefixCls}-multiple`]: multiple,
+ [`${prefixCls}-single`]: !multiple,
+ [`${prefixCls}-searchable`]: searchable.value === true,
+ [`${prefixCls}-with-suffix`]: slots.suffix || suffix.value,
+ [`${prefixCls}-${size}`]: true,
+ }
+ })
+
+ const handleClick = () => {
+ const currOpened = overlayOpened.value
+ if ((currOpened && searchable.value === true) || isDisabled.value) {
+ return
+ }
+
+ setOverlayOpened(!currOpened)
+ }
+
+ onMounted(() => {
+ watch(focusMonitor.monitor(triggerRef.value!, true), evt => {
+ const { origin, event } = evt
+ if (event) {
+ if (origin) {
+ handleFocus(event)
+ } else {
+ handleBlur(event)
+ }
+ }
+ })
+ })
+
+ onBeforeUnmount(() => focusMonitor.stopMonitoring(triggerRef.value!))
+
+ return () => {
+ return (
+
+ {isFocused.value && !overlayOpened.value && (
+
+ {selectedValue.value.join(', ')}
+
+ )}
+
+
+ )
+ }
+ },
+})
diff --git a/packages/components/tree-select/src/types.ts b/packages/components/tree-select/src/types.ts
new file mode 100644
index 000000000..10285beb4
--- /dev/null
+++ b/packages/components/tree-select/src/types.ts
@@ -0,0 +1,117 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import type { VirtualScrollToFn } from '@idux/cdk/scroll'
+import type { IxInnerPropTypes, IxPublicPropTypes, VKey } from '@idux/cdk/utils'
+import type { EmptyProps } from '@idux/components/empty'
+import type { FormSize } from '@idux/components/form'
+import type { TreeCheckStrategy, TreeDragDropOptions, TreeDroppable, TreeNode } from '@idux/components/tree'
+import type { DefineComponent, HTMLAttributes, VNode, VNodeTypes } from 'vue'
+
+import { controlPropDef } from '@idux/cdk/forms'
+import { ɵPortalTargetDef } from '@idux/cdk/portal'
+import { IxPropTypes } from '@idux/cdk/utils'
+
+export const treeSelectProps = {
+ value: IxPropTypes.any,
+ expandedKeys: IxPropTypes.array(),
+ loadedKeys: IxPropTypes.array(),
+ control: controlPropDef,
+ open: IxPropTypes.bool,
+
+ autofocus: IxPropTypes.bool.def(false),
+ childrenKey: IxPropTypes.string,
+ cascade: IxPropTypes.bool.def(false),
+ checkable: IxPropTypes.bool.def(false),
+ clearable: IxPropTypes.bool.def(false),
+ checkStrategy: IxPropTypes.oneOf(['all', 'parent', 'child']).def('all'),
+ dataSource: IxPropTypes.array().def(() => []),
+ disabled: IxPropTypes.bool.def(false),
+ draggable: IxPropTypes.bool.def(false),
+ droppable: IxPropTypes.func(),
+ empty: IxPropTypes.oneOfType([String, IxPropTypes.object()]),
+ expandIcon: IxPropTypes.string,
+ maxLabelCount: IxPropTypes.number.def(Number.MAX_SAFE_INTEGER),
+ multiple: IxPropTypes.bool.def(false),
+ labelKey: IxPropTypes.string,
+ leafLineIcon: IxPropTypes.string,
+ loadChildren: IxPropTypes.func<(node: TreeSelectNode) => Promise>(),
+ nodeKey: IxPropTypes.oneOfType([String, IxPropTypes.func<(node: TreeSelectNode) => VKey>()]),
+ overlayClassName: IxPropTypes.string,
+ overlayRender: IxPropTypes.func<(children: VNode[]) => VNodeTypes>(),
+ placeholder: IxPropTypes.string,
+ readonly: IxPropTypes.bool.def(false),
+ searchable: IxPropTypes.oneOfType([Boolean, IxPropTypes.oneOf(['overlay'])]).def(false),
+ searchFn: IxPropTypes.func<(node: TreeSelectNode, searchValue?: string) => boolean>(),
+ size: IxPropTypes.oneOf(['sm', 'md', 'lg']),
+ suffix: IxPropTypes.string,
+ showLine: IxPropTypes.bool,
+ target: ɵPortalTargetDef,
+ treeDisabled: IxPropTypes.func<(node: TreeSelectNode) => boolean | TreeSelectNodeDisabled>(),
+ virtual: IxPropTypes.bool.def(false),
+
+ // events
+ 'onUpdate:value': IxPropTypes.emit<(value: any) => void>(),
+ 'onUpdate:expandedKeys': IxPropTypes.emit<(keys: VKey[]) => void>(),
+ 'onUpdate:loadedKeys': IxPropTypes.emit<(keys: VKey[]) => void>(),
+ onCheck: IxPropTypes.emit<(checked: boolean, node: TreeSelectNode) => void>(),
+ onChange: IxPropTypes.emit<(value: any, oldValue: any, node: TreeSelectNode | TreeSelectNode[]) => void>(),
+ onClear: IxPropTypes.emit<(evt: Event) => void>(),
+ onDragstart: IxPropTypes.emit<(options: TreeDragDropOptions) => void>(),
+ onDragend: IxPropTypes.emit<(options: TreeDragDropOptions) => void>(),
+ onDragenter: IxPropTypes.emit<(options: TreeDragDropOptions) => void>(),
+ onDragleave: IxPropTypes.emit<(options: TreeDragDropOptions) => void>(),
+ onDragover: IxPropTypes.emit<(options: TreeDragDropOptions) => void>(),
+ onDrop: IxPropTypes.emit<(options: TreeDragDropOptions) => void>(),
+ onExpand: IxPropTypes.emit<(expanded: boolean, node: TreeSelectNode) => void>(),
+ onExpandedChange: IxPropTypes.emit<(expendedKeys: VKey[], expendedNodes: TreeSelectNode[]) => void>(),
+ onLoaded: IxPropTypes.emit<(loadedKeys: VKey[], node: TreeSelectNode) => void>(),
+ onSelect: IxPropTypes.emit<(selected: boolean, node: TreeSelectNode) => void>(),
+ onBlur: IxPropTypes.emit<(evt: FocusEvent) => void>(),
+ onFocus: IxPropTypes.emit<(evt: FocusEvent) => void>(),
+ onNodeClick: IxPropTypes.emit<(evt: Event, node: TreeSelectNode) => void>(),
+ onNodeContextmenu: IxPropTypes.emit<(evt: Event, node: TreeSelectNode) => void>(),
+ onSearchedChange: IxPropTypes.emit<(searchedKeys: VKey[], searchedNodes: TreeSelectNode[]) => void>(),
+ onScroll: IxPropTypes.emit<(evt: Event) => void>(),
+ onScrolledChange:
+ IxPropTypes.emit<(startIndex: number, endIndex: number, visibleOptions: TreeSelectNode[]) => void>(),
+ onScrolledBottom: IxPropTypes.emit<() => void>(),
+
+ // private
+ overlayHeight: IxPropTypes.number.def(256),
+}
+
+export type TreeSelectProps = IxInnerPropTypes
+export type TreeSelectPublicProps = IxPublicPropTypes
+export interface TreeSelectBindings {
+ focus: (options?: FocusOptions) => void
+ blur: () => void
+ scrollTo: VirtualScrollToFn
+ setExpandAll: (isAll: boolean) => void
+}
+export type TreeSelectComponent = DefineComponent<
+ Omit & TreeSelectPublicProps,
+ TreeSelectBindings
+>
+export type TreeSelectInstance = InstanceType>
+
+export type TreeSelectNode = TreeNode
+
+export interface TreeSelectNodeDisabled {
+ select?: boolean
+ drag?: boolean
+ drop?: boolean
+}
+
+// private
+export const treeSelectorProps = {
+ clearable: IxPropTypes.bool,
+ suffix: IxPropTypes.string,
+}
+export type TreeSelectorProps = IxInnerPropTypes
diff --git a/packages/components/tree-select/style/index.less b/packages/components/tree-select/style/index.less
new file mode 100644
index 000000000..e6fd20878
--- /dev/null
+++ b/packages/components/tree-select/style/index.less
@@ -0,0 +1,161 @@
+@import '../../style/mixins/borderless.less';
+@import '../../style/mixins/ellipsis.less';
+@import '../../style/mixins/reset.less';
+@import './mixin.less';
+@import './single.less';
+@import './multiple.less';
+
+.@{tree-select-prefix} {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+
+ &-selector {
+ position: relative;
+ display: flex;
+ color: @tree-select-color;
+ background-color: @tree-select-background-color;
+ border: @tree-select-border-width @tree-select-border-style @tree-select-border-color;
+ border-radius: @tree-select-border-radius;
+ transition: all @tree-select-transition-duration @tree-select-transition-function;
+ cursor: pointer;
+
+ &-item {
+ flex: 1;
+ user-select: none;
+ .ellipsis();
+ }
+
+ &-input {
+
+ &-inner {
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ background: transparent;
+ border: none;
+ outline: none;
+ appearance: none;
+ cursor: pointer;
+ z-index: @zindex-l1-1;
+ }
+ }
+
+ &-placeholder {
+ flex: 1;
+ overflow: hidden;
+ color: @tree-select-placeholder-color;
+ transition: all @tree-select-transition-duration @tree-select-transition-function;
+ .ellipsis();
+ }
+
+ &-suffix {
+ .tree-select-icon();
+
+ pointer-events: none;
+ }
+
+ &-clear {
+ .tree-select-icon();
+
+ z-index: 1;
+ opacity: 0;
+ background-color: @tree-select-icon-background-color;
+
+ &:hover {
+ color: @tree-select-icon-hover-color;
+ }
+
+ .@{tree-select-prefix}:hover & {
+ opacity: 1;
+ }
+ }
+ }
+
+ &:hover:not(&-disabled) &-selector {
+ border-color: @tree-select-hover-color;
+ }
+
+ &-active:not(&-disabled):not(&-borderless) &-selector {
+ border-color: @tree-select-active-color;
+ box-shadow: @tree-select-active-box-shadow;
+ }
+
+ &-disabled &-selector {
+ color: @tree-select-disabled-color;
+ background-color: @tree-select-disabled-background-color;
+ cursor: not-allowed;
+
+ &-input-inner {
+ cursor: not-allowed;
+ }
+ }
+
+ &-borderless &-selector {
+ .borderless();
+ }
+
+ &-searchable &-selector {
+ cursor: text;
+
+ &-input-inner {
+ cursor: auto;
+ }
+ }
+
+ &-overlay {
+ z-index: @tree-select-option-container-zindex;
+ padding: @tree-select-option-container-padding;
+ background-color: @tree-select-option-container-background-color;
+ border-radius: @tree-select-option-container-border-radius;
+ box-shadow: @tree-select-option-container-box-shadow;
+ overflow: auto;
+
+ .@{tree-select-option-prefix}-group:not(:first-child) {
+ border-top: @tree-select-option-group-border;
+ }
+
+ &-search-wrapper {
+ display: flex;
+ gap: 4px;
+ margin-bottom: 8px;
+ }
+ }
+}
+
+.@{tree-select-option-prefix} {
+ .tree-select-option(@tree-select-option-font-size, @tree-select-option-color);
+
+ &-disabled {
+ color: @tree-select-option-disabled-color;
+ cursor: not-allowed;
+ }
+
+ &-active:not(&-disabled) {
+ background-color: @tree-select-option-active-background-color;
+ }
+
+ &-selected:not(&-disabled) {
+ color: @tree-select-option-selected-color;
+ background-color: @tree-select-option-selected-background-color;
+ font-weight: @tree-select-option-selected-font-weight;
+ }
+
+ &-checkbox {
+ margin-left: @tree-select-option-margin-left;
+ }
+
+ &-label {
+ margin-left: @tree-select-option-margin-left;
+ .ellipsis();
+ }
+
+ &-group {
+ .tree-select-option(@tree-select-option-font-size - 2px, @tree-select-option-disabled-color);
+
+ &-label {
+ margin-left: @tree-select-option-margin-left;
+ .ellipsis();
+ }
+ }
+}
diff --git a/packages/components/tree-select/style/mixin.less b/packages/components/tree-select/style/mixin.less
new file mode 100644
index 000000000..0a9683b3e
--- /dev/null
+++ b/packages/components/tree-select/style/mixin.less
@@ -0,0 +1,21 @@
+.tree-select-option(@font-size; @text-color) {
+ display: block;
+ position: relative;
+ height: @tree-select-option-height;
+ padding: @tree-select-option-padding;
+ font-size: @font-size;
+ color: @text-color;
+ transition: @tree-select-option-transition;
+ cursor: pointer;
+}
+
+.tree-select-icon() {
+ position: absolute;
+ top: 50%;
+ margin-top: -(@tree-select-icon-font-size / 2);
+ right: @tree-select-icon-margin-right;
+ line-height: 1;
+ font-size: @tree-select-icon-font-size;
+ color: @tree-select-icon-color;
+ transition: color @tree-select-transition-duration @tree-select-transition-function;
+}
diff --git a/packages/components/tree-select/style/multiple.less b/packages/components/tree-select/style/multiple.less
new file mode 100644
index 000000000..2e85524e2
--- /dev/null
+++ b/packages/components/tree-select/style/multiple.less
@@ -0,0 +1,110 @@
+.select-size(@tree-select-height; @tree-select-font-size) {
+ @tree-select-margin-half: ceil((@tree-select-multiple-padding / 2));
+ @tree-select-padding-vertical: max(@tree-select-multiple-padding - @tree-select-multiple-item-border-width - @tree-select-margin-half, 0);
+ @tree-select-item-height: @tree-select-height - @tree-select-multiple-padding * 2;
+ @tree-select-item-line-height: @tree-select-item-height - @tree-select-border-width * 2;
+
+ .@{tree-select-prefix}-selector {
+ padding: @tree-select-padding-vertical @tree-select-multiple-padding;
+ font-size: @tree-select-font-size;
+
+ &-item {
+ height: @tree-select-item-height;
+ line-height: @tree-select-item-line-height;
+ margin: @tree-select-margin-half 0;
+ margin-inline-end: @tree-select-multiple-padding;
+ }
+
+ &-input {
+ margin: @tree-select-margin-half 0;
+ margin-inline-start: @tree-select-multiple-padding;
+
+ &-inner,
+ &-mirror {
+ height: @tree-select-item-height;
+ line-height: @tree-select-item-line-height;
+ }
+ }
+ }
+
+ &.@{tree-select-prefix}-with-suffix .@{tree-select-prefix}-selector,
+ &.@{tree-select-prefix}-clearable .@{tree-select-prefix}-selector {
+ padding-right: @tree-select-icon-font-size + @tree-select-multiple-padding;
+ }
+}
+
+.@{tree-select-prefix}-multiple {
+ .@{tree-select-prefix}-selector {
+ flex-wrap: wrap;
+ align-items: center;
+
+ &-item {
+ display: flex;
+ flex: none;
+ max-width: 100%;
+ padding: @tree-select-multiple-item-padding;
+ background: @tree-select-multiple-item-background-color;
+ border: @tree-select-multiple-item-border;
+ border-radius: @tree-select-multiple-item-border-radius;
+ cursor: default;
+
+ &-disabled {
+ color: @tree-select-multiple-item-disabled-color;
+ border-color: @tree-select-multiple-item-disabled-border-color;
+ cursor: not-allowed;
+ }
+
+ &-label {
+ display: inline-block;
+ .ellipsis();
+ }
+
+ &-remove {
+ margin-left: @tree-select-multiple-item-label-margin-left;
+ color: @tree-select-icon-color;
+ font-size: @tree-select-multiple-item-remove-icon-font-size;
+ line-height: inherit;
+ cursor: pointer;
+
+ &:hover {
+ color: @tree-select-icon-hover-color;
+ }
+ }
+ }
+
+ &-input {
+ position: relative;
+ max-width: 100%;
+
+ &-mirror {
+ position: absolute;
+ top: 0;
+ left: 0;
+ white-space: pre;
+ visibility: hidden;
+ }
+ }
+
+ &-placeholder {
+ position: absolute;
+ right: @tree-select-padding-horizontal-md;
+ left: @tree-select-padding-horizontal-md;
+ }
+ }
+
+ &.@{tree-select-prefix}-sm {
+ .select-size(@tree-select-height-sm, @tree-select-font-size-sm);
+
+ .@{tree-select-prefix}-selector-input {
+ margin: 0;
+ }
+ }
+
+ &.@{tree-select-prefix}-md {
+ .select-size(@tree-select-height-md, @tree-select-font-size-md);
+ }
+
+ &.@{tree-select-prefix}-lg {
+ .select-size(@tree-select-height-lg, @tree-select-font-size-lg);
+ }
+}
diff --git a/packages/components/tree-select/style/single.less b/packages/components/tree-select/style/single.less
new file mode 100644
index 000000000..74d025372
--- /dev/null
+++ b/packages/components/tree-select/style/single.less
@@ -0,0 +1,57 @@
+.tree-select-size(@tree-select-height; @tree-select-padding-horizontal; @tree-select-font-size) {
+ .@{tree-select-prefix}-selector {
+ height: @tree-select-height;
+ padding: 0 @tree-select-padding-horizontal;
+ font-size: @tree-select-font-size;
+
+ &-item,
+ &-placeholder,
+ &-input-inner {
+ line-height: @tree-select-height - 2 * @tree-select-border-width;
+ }
+
+ &-input {
+ right: @tree-select-padding-horizontal;
+ left: @tree-select-padding-horizontal;
+ }
+ }
+
+ &.@{tree-select-prefix}-show-suffix &-selector-input {
+ right: @tree-select-padding-horizontal + @tree-select-font-size;
+ }
+}
+
+.@{tree-select-prefix}-single {
+ .@{tree-select-prefix}-selector {
+ width: 100%;
+
+ &-input {
+ position: absolute;
+ }
+
+ &-item {
+ position: relative;
+ }
+ }
+
+ &.@{tree-select-prefix}-with-suffix .@{tree-select-prefix}-selector-item,
+ &.@{tree-select-prefix}-with-suffix .@{tree-select-prefix}-selector-placeholder {
+ padding-right: @tree-select-icon-font-size;
+ }
+
+ &.@{tree-select-prefix}-opened .@{tree-select-prefix}-selector-item {
+ color: @tree-select-placeholder-color;
+ }
+
+ &.@{tree-select-prefix}-sm {
+ .tree-select-size(@tree-select-height-sm, @tree-select-padding-horizontal-sm, @tree-select-font-size-sm);
+ }
+
+ &.@{tree-select-prefix}-md {
+ .tree-select-size(@tree-select-height-md, @tree-select-padding-horizontal-md, @tree-select-font-size-md);
+ }
+
+ &.@{tree-select-prefix}-lg {
+ .tree-select-size(@tree-select-height-lg, @tree-select-padding-horizontal-lg, @tree-select-font-size-lg);
+ }
+}
diff --git a/packages/components/tree-select/style/themes/default.less b/packages/components/tree-select/style/themes/default.less
new file mode 100644
index 000000000..830f5793b
--- /dev/null
+++ b/packages/components/tree-select/style/themes/default.less
@@ -0,0 +1,72 @@
+@import '../../../style/themes/default.less';
+@import '../../../form/style/themes/default.less';
+@import '../index.less';
+
+@tree-select-font-size-sm: @form-font-size-sm;
+@tree-select-font-size-md: @form-font-size-md;
+@tree-select-font-size-lg: @form-font-size-lg;
+@tree-select-line-height: @form-line-height;
+@tree-select-height-sm: @form-height-sm;
+@tree-select-height-md: @form-height-md;
+@tree-select-height-lg: @form-height-lg;
+@tree-select-padding-horizontal-sm: @form-padding-horizontal-sm;
+@tree-select-padding-horizontal-md: @form-padding-horizontal-md;
+@tree-select-padding-horizontal-lg: @form-padding-horizontal-lg;
+@tree-select-padding-vertical-sm: @form-padding-vertical-sm;
+@tree-select-padding-vertical-md: @form-padding-vertical-md;
+@tree-select-padding-vertical-lg: @form-padding-vertical-lg;
+
+@tree-select-border-width: @form-border-width;
+@tree-select-border-style: @form-border-style;
+@tree-select-border-color: @form-border-color;
+@tree-select-border-radius: @border-radius-md;
+
+@tree-select-color: @form-color;
+@tree-select-color-secondary: @form-color-secondary;
+@tree-select-background-color: @form-background-color;
+@tree-select-placeholder-color: @form-placeholder-color;
+@tree-select-hover-color: @form-hover-color;
+@tree-select-active-color: @form-active-color;
+@tree-select-active-box-shadow: @form-active-box-shadow;
+@tree-select-disabled-color: @form-disabled-color;
+@tree-select-disabled-background-color: @form-disabled-background-color;
+
+@tree-select-transition-duration: @form-transition-duration;
+@tree-select-transition-function: @form-transition-function;
+
+@tree-select-option-font-size: @font-size-md;
+@tree-select-option-height: @height-md;
+@tree-select-option-margin-left: @spacing-md;
+@tree-select-option-padding: @spacing-sm;
+@tree-select-option-color: @text-color;
+@tree-select-option-disabled-color: @text-color-disabled;
+@tree-select-option-active-background-color: @color-grey-l50;
+@tree-select-option-selected-color: @color-primary;
+@tree-select-option-selected-background-color: tint(@color-primary, 90%);
+@tree-select-option-selected-font-weight: @font-weight-xl;
+@tree-select-option-transition: background @transition-duration-base @ease-in-out;
+
+@tree-select-option-group-border: @border-width-sm @border-style @border-color;
+
+@tree-select-option-container-zindex: @zindex-l4-3;
+@tree-select-option-container-padding: @spacing-sm;
+@tree-select-option-container-background-color: @background-color-component;
+@tree-select-option-container-border-radius: @border-radius-sm;
+@tree-select-option-container-box-shadow: @shadow-bottom-md;
+
+@tree-select-icon-font-size: @font-size-sm;
+@tree-select-icon-margin-right: @spacing-xs;
+@tree-select-icon-color: @tree-select-placeholder-color;
+@tree-select-icon-hover-color: @tree-select-color-secondary;
+@tree-select-icon-background-color: @tree-select-background-color;
+
+@tree-select-multiple-padding: @tree-select-padding-vertical-md;
+@tree-select-multiple-item-padding: 0 @spacing-xs;
+@tree-select-multiple-item-background-color: @background-color-base;
+@tree-select-multiple-item-disabled-color: @tree-select-disabled-color;
+@tree-select-multiple-item-disabled-border-color: @tree-select-border-color;
+@tree-select-multiple-item-border-width: @border-width-sm;
+@tree-select-multiple-item-border: @tree-select-multiple-item-border-width @border-style @border-color-split;
+@tree-select-multiple-item-border-radius: @tree-select-border-radius;
+@tree-select-multiple-item-label-margin-left: @spacing-xs;
+@tree-select-multiple-item-remove-icon-font-size: @font-size-xs;
diff --git a/packages/components/tree-select/style/themes/default.ts b/packages/components/tree-select/style/themes/default.ts
new file mode 100644
index 000000000..b3734601b
--- /dev/null
+++ b/packages/components/tree-select/style/themes/default.ts
@@ -0,0 +1,6 @@
+// style dependencies
+import '@idux/components/style/core/default'
+import '@idux/components/icon/style/themes/default'
+import '@idux/components/tree/style/themes/default'
+
+import './default.less'
diff --git a/packages/components/tree/src/Tree.tsx b/packages/components/tree/src/Tree.tsx
index e7bd54b98..16d7b1963 100644
--- a/packages/components/tree/src/Tree.tsx
+++ b/packages/components/tree/src/Tree.tsx
@@ -126,8 +126,8 @@ export default defineComponent({
const scrollTo = (option?: number | VirtualScrollToOptions) => {
virtualScrollRef?.value?.scrollTo(option)
}
-
- expose({ focus, blur, scrollTo })
+ const { setExpandAll } = expandableContext
+ expose({ focus, blur, scrollTo, setExpandAll })
const handleScrolledChange = (startIndex: number, endIndex: number, visibleNodes: FlattedNode[]) => {
callEmit(
diff --git a/packages/components/tree/src/node/Indent.tsx b/packages/components/tree/src/node/Indent.tsx
index 6e5b6701a..41f45fe13 100644
--- a/packages/components/tree/src/node/Indent.tsx
+++ b/packages/components/tree/src/node/Indent.tsx
@@ -7,9 +7,9 @@
import type { FunctionalComponent, VNodeTypes } from 'vue'
-const Indent: FunctionalComponent<{ level: number; noopIdentUnit: number; prefixCls: string }> = ({
+const Indent: FunctionalComponent<{ level: number; noopIdentUnitArr: number[]; prefixCls: string }> = ({
level,
- noopIdentUnit,
+ noopIdentUnitArr,
prefixCls,
}) => {
const children: VNodeTypes[] = []
@@ -17,7 +17,9 @@ const Indent: FunctionalComponent<{ level: number; noopIdentUnit: number; prefix
children.push(
,
)
}
diff --git a/packages/components/tree/src/node/TreeNode.tsx b/packages/components/tree/src/node/TreeNode.tsx
index 5aacfee4e..a8df8d291 100644
--- a/packages/components/tree/src/node/TreeNode.tsx
+++ b/packages/components/tree/src/node/TreeNode.tsx
@@ -117,13 +117,15 @@ export default defineComponent({
const { showLine, checkable, draggable } = treeProps
const mergedDraggable = draggable && !dragDisabled
const currNode = nodeMap.get(key)
- let noopIdentUnit = 0
+ const noopIdentUnitArr: number[] = []
if (treeProps.showLine) {
- getParentKeys(nodeMap, currNode).forEach(parentKey => {
- if (nodeMap.get(parentKey)?.isLast) {
- noopIdentUnit++
- }
- })
+ getParentKeys(nodeMap, currNode)
+ .reverse()
+ .forEach((parentKey, index) => {
+ if (nodeMap.get(parentKey)?.isLast) {
+ noopIdentUnitArr.push(index)
+ }
+ })
}
return (
@@ -139,7 +141,7 @@ export default defineComponent({
onDragleave={mergedDraggable ? onDragleave : undefined}
onDrop={mergedDraggable && !dropDisabled ? onDrop : undefined}
>
-
+
{isLeaf && showLine ? (
) : (
diff --git a/packages/components/tree/src/types.ts b/packages/components/tree/src/types.ts
index db6cb8015..d38f7603d 100644
--- a/packages/components/tree/src/types.ts
+++ b/packages/components/tree/src/types.ts
@@ -81,6 +81,7 @@ export interface TreeBindings {
focus: (options?: FocusOptions) => void
blur: () => void
scrollTo: VirtualScrollToFn
+ setExpandAll: (isAll: boolean) => void
}
export type TreeComponent = DefineComponent & TreePublicProps, TreeBindings>
export type TreeInstance = InstanceType>
diff --git a/scripts/gulp/icons/assets/tree-expand.svg b/scripts/gulp/icons/assets/tree-expand.svg
new file mode 100644
index 000000000..a2ca476a1
--- /dev/null
+++ b/scripts/gulp/icons/assets/tree-expand.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/scripts/gulp/icons/assets/tree-unexpand.svg b/scripts/gulp/icons/assets/tree-unexpand.svg
new file mode 100644
index 000000000..f39cccced
--- /dev/null
+++ b/scripts/gulp/icons/assets/tree-unexpand.svg
@@ -0,0 +1 @@
+
\ No newline at end of file