From 6f3fca48a1f5c0abbd41e4cdb3c99c2a9bd41309 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 10 Aug 2021 14:36:28 +0800 Subject: [PATCH 01/53] feat: support vue@3.2 --- components/_util/transition.tsx | 6 +- components/auto-complete/InputElement.tsx | 9 -- components/auto-complete/index.tsx | 3 +- .../__snapshots__/index.test.js.snap | 31 ++++- components/drawer/index.tsx | 2 +- components/input/Input.tsx | 2 +- components/input/TextArea.tsx | 2 +- components/menu/src/MenuItem.tsx | 11 +- components/menu/src/SubMenu.tsx | 17 ++- components/table/filterDropdown.tsx | 2 +- components/upload/UploadList.tsx | 2 +- components/vc-select/OptionList.tsx | 4 +- components/vc-select/Selector/Input.tsx | 2 +- .../vc-select/Selector/MultipleSelector.tsx | 18 +-- .../vc-select/Selector/SingleSelector.tsx | 2 +- components/vc-select/Selector/index.tsx | 121 +++++++----------- components/vc-select/Selector/interface.ts | 27 ++++ components/vc-select/generate.tsx | 2 +- v2-doc | 2 +- 19 files changed, 151 insertions(+), 114 deletions(-) delete mode 100644 components/auto-complete/InputElement.tsx create mode 100644 components/vc-select/Selector/interface.ts diff --git a/components/_util/transition.tsx b/components/_util/transition.tsx index 1894aff8b..6caea7504 100644 --- a/components/_util/transition.tsx +++ b/components/_util/transition.tsx @@ -1,5 +1,5 @@ -import type { BaseTransitionProps, CSSProperties, Ref } from 'vue'; -import { getCurrentInstance, onUpdated } from 'vue'; +import { BaseTransitionProps, CSSProperties, onBeforeUpdate, Ref } from 'vue'; +import { getCurrentInstance } from 'vue'; import { defineComponent, nextTick, Transition as T, TransitionGroup as TG } from 'vue'; export const getTransitionProps = (transitionName: string, opt: object = {}) => { @@ -51,7 +51,7 @@ if (process.env.NODE_ENV === 'test') { inheritAttrs: false, setup(_props, { slots, attrs }) { const instance = getCurrentInstance(); - onUpdated(() => { + onBeforeUpdate(() => { const child = instance.subTree.children[0]; if (child && child.dirs && child.dirs[0]) { const value = child.dirs[0].value; diff --git a/components/auto-complete/InputElement.tsx b/components/auto-complete/InputElement.tsx deleted file mode 100644 index c3623cfec..000000000 --- a/components/auto-complete/InputElement.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { cloneElement } from '../_util/vnode'; -import { flattenChildren } from '../_util/props-util'; - -const InputElement = (_: any, { attrs, slots }) => { - const children = flattenChildren(slots.default?.())[0]; - return cloneElement(children, { ...attrs }); -}; -InputElement.inheritAttrs = false; -export default InputElement; diff --git a/components/auto-complete/index.tsx b/components/auto-complete/index.tsx index dac4d43db..d9e43f753 100644 --- a/components/auto-complete/index.tsx +++ b/components/auto-complete/index.tsx @@ -2,7 +2,6 @@ import type { App, Plugin, VNode, ExtractPropTypes } from 'vue'; import { defineComponent, inject, provide } from 'vue'; import Select, { SelectProps } from '../select'; import Input from '../input'; -import InputElement from './InputElement'; import PropTypes from '../_util/vue-types'; import { defaultConfigProvider } from '../config-provider'; import { getComponent, getOptionProps, isValidElement, getSlot } from '../_util/props-util'; @@ -72,7 +71,7 @@ const AutoComplete = defineComponent({ getInputElement() { const children = getSlot(this); const element = children.length ? children[0] : ; - return {element}; + return element; }, focus() { diff --git a/components/cascader/__tests__/__snapshots__/index.test.js.snap b/components/cascader/__tests__/__snapshots__/index.test.js.snap index 708351c16..c2a63580d 100644 --- a/components/cascader/__tests__/__snapshots__/index.test.js.snap +++ b/components/cascader/__tests__/__snapshots__/index.test.js.snap @@ -113,4 +113,33 @@ exports[`Cascader popup correctly with defaultValue 1`] = ` `; -exports[`Cascader support controlled mode 1`] = `Zhejiang / Hangzhou / West Lake`; +exports[`Cascader popup correctly with defaultValue 2`] = ` +
+ +
+
+
    + + +
+
    + +
+
    + +
+
+
+
+`; + +exports[`Cascader support controlled mode 1`] = `Zhejiang / Hangzhou / West Lake`; diff --git a/components/drawer/index.tsx b/components/drawer/index.tsx index cd4ababc7..bb53f4dab 100644 --- a/components/drawer/index.tsx +++ b/components/drawer/index.tsx @@ -123,7 +123,7 @@ const Drawer = defineComponent({ } if (!this.visible) { this.destroyClose = true; - this.$forceUpdate(); + (this as any).$forceUpdate(); } }, diff --git a/components/input/Input.tsx b/components/input/Input.tsx index df07a8bf1..46cb7ce0e 100644 --- a/components/input/Input.tsx +++ b/components/input/Input.tsx @@ -128,7 +128,7 @@ export default defineComponent({ if (!hasProp(this, 'value')) { this.stateValue = value; } else { - this.$forceUpdate(); + (this as any).$forceUpdate(); } nextTick(() => { callback && callback(); diff --git a/components/input/TextArea.tsx b/components/input/TextArea.tsx index 1fb195432..f654bc56d 100644 --- a/components/input/TextArea.tsx +++ b/components/input/TextArea.tsx @@ -55,7 +55,7 @@ export default defineComponent({ if (!hasProp(this, 'value')) { this.stateValue = value; } else { - this.$forceUpdate(); + (this as any).$forceUpdate(); } nextTick(() => { callback && callback(); diff --git a/components/menu/src/MenuItem.tsx b/components/menu/src/MenuItem.tsx index 49f7f7405..df483b93d 100644 --- a/components/menu/src/MenuItem.tsx +++ b/components/menu/src/MenuItem.tsx @@ -10,6 +10,7 @@ import type { MenuInfo } from './interface'; import KeyCode from '../../_util/KeyCode'; import useDirectionStyle from './hooks/useDirectionStyle'; import Overflow from '../../vc-overflow'; +import devWarning from '../../vc-util/devWarning'; let indexGuid = 0; const menuItemProps = { @@ -30,7 +31,15 @@ export default defineComponent({ slots: ['icon', 'title'], setup(props, { slots, emit, attrs }) { const instance = getCurrentInstance(); - const key = instance.vnode.key; + + const key = + typeof instance.vnode.key === 'symbol' ? String(instance.vnode.key) : instance.vnode.key; + devWarning( + typeof instance.vnode.key !== 'symbol', + 'MenuItem', + `MenuItem \`:key="${String(key)}"\` not support Symbol type`, + ); + const eventKey = `menu_item_${++indexGuid}_$$_${key}`; const { parentEventKeys, parentKeys } = useInjectKeyPath(); const { diff --git a/components/menu/src/SubMenu.tsx b/components/menu/src/SubMenu.tsx index 24fde493f..57fea3003 100644 --- a/components/menu/src/SubMenu.tsx +++ b/components/menu/src/SubMenu.tsx @@ -12,6 +12,8 @@ import InlineSubMenuList from './InlineSubMenuList'; import Transition, { getTransitionProps } from '../../_util/transition'; import { cloneElement } from '../../_util/vnode'; import Overflow from '../../vc-overflow'; +import devWarning from '../../vc-util/devWarning'; +import isValid from '../../_util/isValid'; let indexGuid = 0; @@ -39,14 +41,17 @@ export default defineComponent({ useProvideFirstLevel(false); const instance = getCurrentInstance(); - const key = - instance.vnode.key !== null ? instance.vnode.key : `sub_menu_${++indexGuid}_$$_not_set_key`; - + const vnodeKey = + typeof instance.vnode.key === 'symbol' ? String(instance.vnode.key) : instance.vnode.key; + devWarning( + typeof instance.vnode.key !== 'symbol', + 'SubMenu', + `SubMenu \`:key="${String(vnodeKey)}"\` not support Symbol type`, + ); + const key = isValid(vnodeKey) ? vnodeKey : `sub_menu_${++indexGuid}_$$_not_set_key`; const eventKey = props.eventKey ?? - (instance.vnode.key !== null - ? `sub_menu_${++indexGuid}_$$_${instance.vnode.key}` - : (key as string)); + (isValid(vnodeKey) ? `sub_menu_${++indexGuid}_$$_${vnodeKey}` : (key as string)); const { parentEventKeys, parentInfo, parentKeys } = useInjectKeyPath(); const keysPath = computed(() => [...parentKeys.value, key]); const eventKeysPath = computed(() => [...parentEventKeys.value, eventKey]); diff --git a/components/table/filterDropdown.tsx b/components/table/filterDropdown.tsx index 8f3b8ba27..12380eb11 100755 --- a/components/table/filterDropdown.tsx +++ b/components/table/filterDropdown.tsx @@ -98,7 +98,7 @@ export default defineComponent({ this.setVisible(false); // Call `setSelectedKeys` & `confirm` in the same time will make filter data not up to date // https://github.com/ant-design/ant-design/issues/12284 - this.$forceUpdate(); + (this as any).$forceUpdate(); nextTick(this.confirmFilter2); }, diff --git a/components/upload/UploadList.tsx b/components/upload/UploadList.tsx index 4137f876a..05f5fdc9b 100644 --- a/components/upload/UploadList.tsx +++ b/components/upload/UploadList.tsx @@ -64,7 +64,7 @@ export default defineComponent({ previewFile(file.originFileObj).then(previewDataUrl => { // Need append '' to avoid dead loop file.thumbUrl = previewDataUrl || ''; - this.$forceUpdate(); + (this as any).$forceUpdate(); }); } }); diff --git a/components/vc-select/OptionList.tsx b/components/vc-select/OptionList.tsx index fbeb9744b..a24735d76 100644 --- a/components/vc-select/OptionList.tsx +++ b/components/vc-select/OptionList.tsx @@ -5,7 +5,7 @@ import classNames from '../_util/classNames'; import pickAttrs from '../_util/pickAttrs'; import { isValidElement } from '../_util/props-util'; import createRef from '../_util/createRef'; -import type { VNodeChild } from 'vue'; +import type { PropType, VNodeChild } from 'vue'; import { computed, defineComponent, nextTick, reactive, watch } from 'vue'; import List from '../vc-virtual-list/List'; import type { @@ -61,7 +61,7 @@ const OptionListProps = { virtual: PropTypes.looseBool, onSelect: PropTypes.func, - onToggleOpen: PropTypes.func, + onToggleOpen: { type: Function as PropType<(open?: boolean) => void> }, /** Tell Select that some value is now active to make accessibility work */ onActiveValue: PropTypes.func, onScroll: PropTypes.func, diff --git a/components/vc-select/Selector/Input.tsx b/components/vc-select/Selector/Input.tsx index c8f72a71d..b29c6edf0 100644 --- a/components/vc-select/Selector/Input.tsx +++ b/components/vc-select/Selector/Input.tsx @@ -74,7 +74,7 @@ const Input = defineComponent) as VNode, [[antInput]]); + let inputNode: any = inputElement || withDirectives(() as VNode, [[antInput]]); const inputProps = inputNode.props || {}; const { diff --git a/components/vc-select/Selector/MultipleSelector.tsx b/components/vc-select/Selector/MultipleSelector.tsx index dc9805a97..950378854 100644 --- a/components/vc-select/Selector/MultipleSelector.tsx +++ b/components/vc-select/Selector/MultipleSelector.tsx @@ -7,9 +7,9 @@ import type { DisplayLabelValueType, } from '../interface/generator'; import type { RenderNode } from '../interface'; -import type { InnerSelectorProps } from '.'; +import type { InnerSelectorProps } from './interface'; import Input from './Input'; -import type { VNodeChild, Ref } from 'vue'; +import type { VNodeChild, Ref, PropType } from 'vue'; import { computed, defineComponent, onMounted, ref, watch } from 'vue'; import classNames from '../../_util/classNames'; import pickAttrs from '../../_util/pickAttrs'; @@ -17,24 +17,24 @@ import PropTypes from '../../_util/vue-types'; import type { VueNode } from '../../_util/type'; import Overflow from '../../vc-overflow'; -interface SelectorProps extends InnerSelectorProps { +type SelectorProps = InnerSelectorProps & { // Icon removeIcon?: RenderNode; // Tags maxTagCount?: number | 'responsive'; maxTagTextLength?: number; - maxTagPlaceholder?: VNodeChild; + maxTagPlaceholder?: VNodeChild | ((omittedValues: LabelValueType[]) => VNodeChild); tokenSeparators?: string[]; tagRender?: (props: CustomTagProps) => VNodeChild; - onToggleOpen: (open?: boolean) => void; + onToggleOpen: any; // Motion choiceTransitionName?: string; // Event onSelect: (value: RawValueType, option: { selected: boolean }) => void; -} +}; const props = { id: PropTypes.string, @@ -62,6 +62,7 @@ const props = { ), tagRender: PropTypes.func, + onToggleOpen: { type: Function as PropType<(open?: boolean) => void> }, onSelect: PropTypes.func, onInputChange: PropTypes.func, onInputPaste: PropTypes.func, @@ -78,6 +79,8 @@ const onPreventMouseDown = (event: MouseEvent) => { const SelectSelector = defineComponent({ name: 'MultipleSelectSelector', + inheritAttrs: false, + props: props as any, setup(props) { const measureRef = ref(); const inputWidth = ref(0); @@ -278,6 +281,5 @@ const SelectSelector = defineComponent({ }; }, }); -SelectSelector.inheritAttrs = false; -SelectSelector.props = props; + export default SelectSelector; diff --git a/components/vc-select/Selector/SingleSelector.tsx b/components/vc-select/Selector/SingleSelector.tsx index be58e784f..fdbff431a 100644 --- a/components/vc-select/Selector/SingleSelector.tsx +++ b/components/vc-select/Selector/SingleSelector.tsx @@ -1,6 +1,6 @@ import pickAttrs from '../../_util/pickAttrs'; import Input from './Input'; -import type { InnerSelectorProps } from '.'; +import type { InnerSelectorProps } from './interface'; import type { VNodeChild } from 'vue'; import { computed, defineComponent, Fragment, ref, watch } from 'vue'; import PropTypes from '../../_util/vue-types'; diff --git a/components/vc-select/Selector/index.tsx b/components/vc-select/Selector/index.tsx index d4430c35a..e6dd0f9a1 100644 --- a/components/vc-select/Selector/index.tsx +++ b/components/vc-select/Selector/index.tsx @@ -14,36 +14,12 @@ import SingleSelector from './SingleSelector'; import type { LabelValueType, RawValueType, CustomTagProps } from '../interface/generator'; import type { RenderNode, Mode } from '../interface'; import useLock from '../hooks/useLock'; -import type { VNodeChild } from 'vue'; +import type { VNodeChild, PropType } from 'vue'; import { defineComponent } from 'vue'; -import type { RefObject } from '../../_util/createRef'; import createRef from '../../_util/createRef'; import PropTypes from '../../_util/vue-types'; import type { VueNode } from '../../_util/type'; -export interface InnerSelectorProps { - prefixCls: string; - id: string; - mode: Mode; - inputRef: RefObject; - placeholder?: VNodeChild; - disabled?: boolean; - autofocus?: boolean; - autocomplete?: string; - values: LabelValueType[]; - showSearch?: boolean; - searchValue: string; - accessibilityIndex: number; - open: boolean; - tabindex?: number | string; - onInputKeyDown: EventHandlerNonNull; - onInputMouseDown: EventHandlerNonNull; - onInputChange: EventHandlerNonNull; - onInputPaste: EventHandlerNonNull; - onInputCompositionStart: EventHandlerNonNull; - onInputCompositionEnd: EventHandlerNonNull; -} - export interface SelectorProps { id: string; prefixCls: string; @@ -67,7 +43,7 @@ export interface SelectorProps { // Tags maxTagCount?: number | 'responsive'; maxTagTextLength?: number; - maxTagPlaceholder?: VNodeChild; + maxTagPlaceholder?: VNodeChild | ((omittedValues: LabelValueType[]) => VNodeChild); tagRender?: (props: CustomTagProps) => VNodeChild; /** Check if `tokenSeparators` contains `\n` or `\r\n` */ @@ -92,6 +68,52 @@ export interface SelectorProps { const Selector = defineComponent({ name: 'Selector', + inheritAttrs: false, + props: { + id: PropTypes.string, + prefixCls: PropTypes.string, + showSearch: PropTypes.looseBool, + open: PropTypes.looseBool, + /** Display in the Selector value, it's not same as `value` prop */ + values: PropTypes.array, + multiple: PropTypes.looseBool, + mode: PropTypes.string, + searchValue: PropTypes.string, + activeValue: PropTypes.string, + inputElement: PropTypes.any, + + autofocus: PropTypes.looseBool, + accessibilityIndex: PropTypes.number, + tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + disabled: PropTypes.looseBool, + placeholder: PropTypes.any, + removeIcon: PropTypes.any, + + // Tags + maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + maxTagTextLength: PropTypes.number, + maxTagPlaceholder: PropTypes.any, + tagRender: PropTypes.func, + + /** Check if `tokenSeparators` contains `\n` or `\r\n` */ + tokenWithEnter: PropTypes.looseBool, + + // Motion + choiceTransitionName: PropTypes.string, + + onToggleOpen: { type: Function as PropType<(open?: boolean) => void> }, + /** `onSearch` returns go next step boolean to check if need do toggle open */ + onSearch: PropTypes.func, + onSearchSubmit: PropTypes.func, + onSelect: PropTypes.func, + onInputKeyDown: PropTypes.func, + + /** + * @private get real dom for trigger align. + * This may be removed after React provides replacement of `findDOMNode` + */ + domRef: PropTypes.func, + } as any, setup(props) { const inputRef = createRef(); let compositionStatus = false; @@ -258,51 +280,4 @@ const Selector = defineComponent({ }, }); -Selector.inheritAttrs = false; -Selector.props = { - id: PropTypes.string, - prefixCls: PropTypes.string, - showSearch: PropTypes.looseBool, - open: PropTypes.looseBool, - /** Display in the Selector value, it's not same as `value` prop */ - values: PropTypes.array, - multiple: PropTypes.looseBool, - mode: PropTypes.string, - searchValue: PropTypes.string, - activeValue: PropTypes.string, - inputElement: PropTypes.any, - - autofocus: PropTypes.looseBool, - accessibilityIndex: PropTypes.number, - tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - disabled: PropTypes.looseBool, - placeholder: PropTypes.any, - removeIcon: PropTypes.any, - - // Tags - maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - maxTagTextLength: PropTypes.number, - maxTagPlaceholder: PropTypes.any, - tagRender: PropTypes.func, - - /** Check if `tokenSeparators` contains `\n` or `\r\n` */ - tokenWithEnter: PropTypes.looseBool, - - // Motion - choiceTransitionName: PropTypes.string, - - onToggleOpen: PropTypes.func, - /** `onSearch` returns go next step boolean to check if need do toggle open */ - onSearch: PropTypes.func, - onSearchSubmit: PropTypes.func, - onSelect: PropTypes.func, - onInputKeyDown: PropTypes.func, - - /** - * @private get real dom for trigger align. - * This may be removed after React provides replacement of `findDOMNode` - */ - domRef: PropTypes.func, -}; - export default Selector; diff --git a/components/vc-select/Selector/interface.ts b/components/vc-select/Selector/interface.ts new file mode 100644 index 000000000..2aa8b1e2c --- /dev/null +++ b/components/vc-select/Selector/interface.ts @@ -0,0 +1,27 @@ +import type { RefObject } from '../../_util/createRef'; +import type { VNodeChild } from 'vue'; +import type { Mode } from '../interface'; +import type { LabelValueType } from '../interface/generator'; + +export interface InnerSelectorProps { + prefixCls: string; + id: string; + mode: Mode; + inputRef: RefObject; + placeholder?: VNodeChild; + disabled?: boolean; + autofocus?: boolean; + autocomplete?: string; + values: LabelValueType[]; + showSearch?: boolean; + searchValue: string; + accessibilityIndex: number; + open: boolean; + tabindex?: number | string; + onInputKeyDown: EventHandlerNonNull; + onInputMouseDown: EventHandlerNonNull; + onInputChange: EventHandlerNonNull; + onInputPaste: EventHandlerNonNull; + onInputCompositionStart: EventHandlerNonNull; + onInputCompositionEnd: EventHandlerNonNull; +} diff --git a/components/vc-select/generate.tsx b/components/vc-select/generate.tsx index 09b2591ce..eb6571923 100644 --- a/components/vc-select/generate.tsx +++ b/components/vc-select/generate.tsx @@ -1057,7 +1057,7 @@ export default function generateSelector< methods: { // We need force update here since popup dom is render async onPopupMouseEnter() { - this.$forceUpdate(); + (this as any).$forceUpdate(); }, }, render() { diff --git a/v2-doc b/v2-doc index 157cce105..6b53258cc 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit 157cce105e1f0a369658dfb29cc802ebc09d0d93 +Subproject commit 6b53258cc2b3709e070d340714e992760e660e67 From f5849fad68aefc9bba60d5c62af8bf3a73fd4c50 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 10 Aug 2021 14:39:47 +0800 Subject: [PATCH 02/53] test: update snap --- .../__snapshots__/index.test.js.snap | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/components/cascader/__tests__/__snapshots__/index.test.js.snap b/components/cascader/__tests__/__snapshots__/index.test.js.snap index c2a63580d..358aacf65 100644 --- a/components/cascader/__tests__/__snapshots__/index.test.js.snap +++ b/components/cascader/__tests__/__snapshots__/index.test.js.snap @@ -113,33 +113,4 @@ exports[`Cascader popup correctly with defaultValue 1`] = ` `; -exports[`Cascader popup correctly with defaultValue 2`] = ` -
- -
-
-
    - - -
-
    - -
-
    - -
-
-
-
-`; - exports[`Cascader support controlled mode 1`] = `Zhejiang / Hangzhou / West Lake`; From 08c3c9cc3bc6970f0d2a2f4759b093d70192d337 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 10 Aug 2021 15:21:13 +0800 Subject: [PATCH 03/53] fix: symbol error --- components/descriptions/Row.tsx | 6 +++--- components/vc-select/utils/legacyUtil.ts | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/components/descriptions/Row.tsx b/components/descriptions/Row.tsx index 556d1d19d..0f0e7055c 100644 --- a/components/descriptions/Row.tsx +++ b/components/descriptions/Row.tsx @@ -50,7 +50,7 @@ const Row: FunctionalComponent = props => { if (typeof component === 'string') { return ( = props => { return [ = props => { label={label} />, & { children: { default?: () => any }; + key: string | number; }; const child = children && children.default ? children.default() : undefined; return { @@ -16,7 +17,7 @@ function convertNodeToOption(node: VNode): OptionData { value: value !== undefined ? value : key, children: child, disabled: disabled || disabled === '', // support - ...restProps, + ...(restProps as Omit), }; } @@ -46,7 +47,7 @@ export function convertChildrenToData( const child = children && children.default ? children.default() : undefined; const label = props?.label || children.label?.() || key; return { - key: `__RC_SELECT_GRP__${key === null ? index : key}__`, + key: `__RC_SELECT_GRP__${key === null ? index : String(key)}__`, ...props, label, options: convertChildrenToData(child || []), From 0cd3db0bb0d4aa616cce504234fc84060edba54b Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 10 Aug 2021 16:09:24 +0800 Subject: [PATCH 04/53] fix: auto hide table scrollbar #4484 close #4484 --- components/vc-table/src/BaseTable.jsx | 7 +--- components/vc-table/src/BodyTable.jsx | 59 ++++++--------------------- components/vc-table/src/Table.jsx | 31 +------------- 3 files changed, 16 insertions(+), 81 deletions(-) diff --git a/components/vc-table/src/BaseTable.jsx b/components/vc-table/src/BaseTable.jsx index 36bf0da97..2e5a5dc8a 100644 --- a/components/vc-table/src/BaseTable.jsx +++ b/components/vc-table/src/BaseTable.jsx @@ -132,16 +132,13 @@ const BaseTable = { render() { const { sComponents: components, prefixCls, scroll, data } = this.table; - const { expander, tableClassName, hasHead, hasBody, fixed, isAnyColumnsFixed } = this.$props; + const { expander, tableClassName, hasHead, hasBody, fixed } = this.$props; const columns = this.getColumns(); const tableStyle = {}; if (!fixed && scroll.x) { - // 当有固定列时,width auto 会导致 body table 的宽度撑不开,从而固定列无法对齐 - // 详情见:https://github.com/ant-design/ant-design/issues/22160 - const tableWidthScrollX = isAnyColumnsFixed ? 'max-content' : 'auto'; // not set width, then use content fixed width - tableStyle.width = scroll.x === true ? tableWidthScrollX : scroll.x; + tableStyle.width = scroll.x === true ? 'auto' : scroll.x; tableStyle.width = typeof tableStyle.width === 'number' ? `${tableStyle.width}px` : tableStyle.width; } diff --git a/components/vc-table/src/BodyTable.jsx b/components/vc-table/src/BodyTable.jsx index ea5ba86de..b0352dab0 100644 --- a/components/vc-table/src/BodyTable.jsx +++ b/components/vc-table/src/BodyTable.jsx @@ -1,13 +1,11 @@ import { inject } from 'vue'; -import PropTypes, { withUndefined } from '../../_util/vue-types'; -import { measureScrollbar } from './utils'; +import PropTypes from '../../_util/vue-types'; import BaseTable from './BaseTable'; export default { name: 'BodyTable', inheritAttrs: false, props: { - fixed: withUndefined(PropTypes.oneOfType([PropTypes.string, PropTypes.looseBool])), columns: PropTypes.array.isRequired, tableClassName: PropTypes.string.isRequired, handleBodyScroll: PropTypes.func.isRequired, @@ -25,7 +23,6 @@ export default { const { prefixCls, scroll } = this.table; const { columns, - fixed, tableClassName, getRowKey, handleBodyScroll, @@ -35,34 +32,26 @@ export default { } = this; let { useFixedHeader, saveRef } = this.table; const bodyStyle = { ...this.table.bodyStyle }; - const innerBodyStyle = {}; - - if (scroll.x || fixed) { - bodyStyle.overflowX = bodyStyle.overflowX || 'scroll'; - // Fix weired webkit render bug - // https://github.com/ant-design/ant-design/issues/7783 - bodyStyle.WebkitTransform = 'translate3d (0, 0, 0)'; - } if (scroll.y) { // maxHeight will make fixed-Table scrolling not working // so we only set maxHeight to body-Table here let maxHeight = bodyStyle.maxHeight || scroll.y; maxHeight = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight; - if (fixed) { - innerBodyStyle.maxHeight = maxHeight; - innerBodyStyle.overflowY = bodyStyle.overflowY || 'scroll'; - } else { - bodyStyle.maxHeight = maxHeight; - } + + bodyStyle.maxHeight = maxHeight; bodyStyle.overflowY = bodyStyle.overflowY || 'scroll'; useFixedHeader = true; + } - // Add negative margin bottom for scroll bar overflow bug - const scrollbarWidth = measureScrollbar({ direction: 'vertical' }); - if (scrollbarWidth > 0 && fixed) { - bodyStyle.marginBottom = `-${scrollbarWidth}px`; - bodyStyle.paddingBottom = '0px'; + if (scroll.x) { + bodyStyle.overflowX = bodyStyle.overflowX || 'auto'; + // Fix weired webkit render bug + // https://github.com/ant-design/ant-design/issues/7783 + bodyStyle.WebkitTransform = 'translate3d (0, 0, 0)'; + + if (!scroll.y) { + bodyStyle.overflowY = 'hidden'; } } @@ -71,7 +60,6 @@ export default { tableClassName={tableClassName} hasHead={!useFixedHeader} hasBody - fixed={fixed} columns={columns} expander={expander} getRowKey={getRowKey} @@ -79,29 +67,6 @@ export default { /> ); - if (fixed && columns.length) { - let refName; - if (columns[0].fixed === 'left' || columns[0].fixed === true) { - refName = 'fixedColumnsBodyLeft'; - } else if (columns[0].fixed === 'right') { - refName = 'fixedColumnsBodyRight'; - } - delete bodyStyle.overflowX; - delete bodyStyle.overflowY; - return ( -
-
- {baseTable} -
-
- ); - } // Should provides `tabindex` if use scroll to enable keyboard scroll const useTabIndex = scroll && (scroll.x || scroll.y); diff --git a/components/vc-table/src/Table.jsx b/components/vc-table/src/Table.jsx index c05c4a379..19d0d6acc 100644 --- a/components/vc-table/src/Table.jsx +++ b/components/vc-table/src/Table.jsx @@ -501,41 +501,15 @@ export default defineComponent({ ); }, - renderLeftFixedTable() { - const { prefixCls } = this; - - return ( -
- {this.renderTable({ - columns: this.columnManager.leftColumns.value, - fixed: 'left', - })} -
- ); - }, - renderRightFixedTable() { - const { prefixCls } = this; - - return ( -
- {this.renderTable({ - columns: this.columnManager.rightColumns.value, - fixed: 'right', - })} -
- ); - }, - renderTable(options) { - const { columns, fixed, isAnyColumnsFixed } = options; + const { columns, isAnyColumnsFixed } = options; const { prefixCls, scroll = {} } = this; - const tableClassName = scroll.x || fixed ? `${prefixCls}-fixed` : ''; + const tableClassName = scroll.x ? `${prefixCls}-fixed` : ''; const headTable = ( Date: Tue, 10 Aug 2021 16:25:04 +0800 Subject: [PATCH 05/53] release 2.2.4 --- CHANGELOG.en-US.md | 7 +++++++ CHANGELOG.zh-CN.md | 7 +++++++ components/_util/transition.tsx | 3 ++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 24e3cdb21..eea6e05ce 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -10,6 +10,13 @@ --- +## 2.2.4 + +`2021-08-10` + +- 🌟 Support Vue@3.2 [#4490](https://github.com/vueComponent/ant-design-vue/issues/4490) +- 🌟 Automatically hide the horizontal scroll bar of `Table` [#4484](https://github.com/vueComponent/ant-design-vue/issues/4484) + ## 2.2.3 `2021-08-07` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index d93a8bbe8..efddaed1a 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -10,6 +10,13 @@ --- +## 2.2.4 + +`2021-08-10` + +- 🌟 支持 Vue@3.2 [#4490](https://github.com/vueComponent/ant-design-vue/issues/4490) +- 🌟 自动隐藏 `Table` 横向滚动条 [#4484](https://github.com/vueComponent/ant-design-vue/issues/4484) + ## 2.2.3 `2021-08-07` diff --git a/components/_util/transition.tsx b/components/_util/transition.tsx index 6caea7504..18c1733d3 100644 --- a/components/_util/transition.tsx +++ b/components/_util/transition.tsx @@ -1,4 +1,5 @@ -import { BaseTransitionProps, CSSProperties, onBeforeUpdate, Ref } from 'vue'; +import type { BaseTransitionProps, CSSProperties, Ref } from 'vue'; +import { onBeforeUpdate } from 'vue'; import { getCurrentInstance } from 'vue'; import { defineComponent, nextTick, Transition as T, TransitionGroup as TG } from 'vue'; From 1da679de343898a018b37aee4e404c51c5919948 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 10 Aug 2021 16:34:09 +0800 Subject: [PATCH 06/53] release 2.2.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad6fb3666..83703b2cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ant-design-vue", - "version": "2.2.3", + "version": "2.2.4", "title": "Ant Design Vue", "description": "An enterprise-class UI design language and Vue-based implementation", "keywords": [ From 07f6d9edf0508a206dfdd7e4adc64049e7f7fbe0 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 10 Aug 2021 16:51:21 +0800 Subject: [PATCH 07/53] docs: udapte changelog --- CHANGELOG.en-US.md | 1 + CHANGELOG.zh-CN.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index eea6e05ce..a1fd75d75 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -16,6 +16,7 @@ - 🌟 Support Vue@3.2 [#4490](https://github.com/vueComponent/ant-design-vue/issues/4490) - 🌟 Automatically hide the horizontal scroll bar of `Table` [#4484](https://github.com/vueComponent/ant-design-vue/issues/4484) +- 🐞 Fix the issue of `Progress` trailColor not taking effect [#4483](https://github.com/vueComponent/ant-design-vue/issues/4483) ## 2.2.3 diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index efddaed1a..81ecfbb83 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -16,6 +16,7 @@ - 🌟 支持 Vue@3.2 [#4490](https://github.com/vueComponent/ant-design-vue/issues/4490) - 🌟 自动隐藏 `Table` 横向滚动条 [#4484](https://github.com/vueComponent/ant-design-vue/issues/4484) +- 🐞 修复 `Progress` trailColor 不生效问题 [#4483](https://github.com/vueComponent/ant-design-vue/issues/4483) ## 2.2.3 From 68c1f4550108a3a6bbe4f1b2c5c168523fd6c84a Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 11 Aug 2021 15:22:49 +0800 Subject: [PATCH 08/53] feat: select support custom option by option slot --- components/select/index.tsx | 11 ++++++++++- components/vc-select/OptionList.tsx | 10 +++++++--- components/vc-select/Select.tsx | 1 + components/vc-select/generate.tsx | 3 +++ v2-doc | 2 +- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/components/select/index.tsx b/components/select/index.tsx index 5cce05d0d..17b278961 100644 --- a/components/select/index.tsx +++ b/components/select/index.tsx @@ -76,7 +76,15 @@ const Select = defineComponent({ props: SelectProps(), SECRET_COMBOBOX_MODE_DO_NOT_USE: 'SECRET_COMBOBOX_MODE_DO_NOT_USE', emits: ['change', 'update:value'], - slots: ['notFoundContent', 'suffixIcon', 'itemIcon', 'removeIcon', 'clearIcon', 'dropdownRender'], + slots: [ + 'notFoundContent', + 'suffixIcon', + 'itemIcon', + 'removeIcon', + 'clearIcon', + 'dropdownRender', + 'option', + ], setup(props, { attrs, emit, slots, expose }) { const selectRef = ref(null); @@ -194,6 +202,7 @@ const Select = defineComponent({ dropdownClassName={rcSelectRtlDropDownClassName} onChange={triggerChange} dropdownRender={selectProps.dropdownRender || slots.dropdownRender} + v-slots={{ option: slots.option }} > {slots.default?.()} diff --git a/components/vc-select/OptionList.tsx b/components/vc-select/OptionList.tsx index a24735d76..0f9330910 100644 --- a/components/vc-select/OptionList.tsx +++ b/components/vc-select/OptionList.tsx @@ -77,6 +77,7 @@ const OptionListProps = { const OptionList = defineComponent({ name: 'OptionList', inheritAttrs: false, + slots: ['option'], setup(props) { const itemPrefixCls = computed(() => `${props.prefixCls}-item`); @@ -268,6 +269,7 @@ const OptionList = defineComponent({ setActive, onSelectValue, memoFlattenOptions, + $slots, } = this as any; const { id, @@ -281,6 +283,7 @@ const OptionList = defineComponent({ onScroll, onMouseenter, } = this.$props as OptionListProps; + const renderOption = $slots.option; const { activeIndex } = this.state; // ========================== Render ========================== if (memoFlattenOptions.length === 0) { @@ -315,12 +318,11 @@ const OptionList = defineComponent({ onMouseenter={onMouseenter} children={({ group, groupOption, data }, itemIndex) => { const { label, key } = data; - // Group if (group) { return (
- {label !== undefined ? label : key} + {renderOption ? renderOption(data) : label !== undefined ? label : key}
); } @@ -387,7 +389,9 @@ const OptionList = defineComponent({ }} style={style} > -
{content}
+
+ {renderOption ? renderOption(data) : content} +
{isValidElement(menuItemSelectedIcon) || selected} {iconVisible && ( >({ ref={selectRef} {...(props as any)} {...attrs} + v-slots={slots} children={slots.default?.() || []} /> ); diff --git a/components/vc-select/generate.tsx b/components/vc-select/generate.tsx index eb6571923..f0049771e 100644 --- a/components/vc-select/generate.tsx +++ b/components/vc-select/generate.tsx @@ -340,6 +340,7 @@ export default function generateSelector< } = config as any; const Select = defineComponent>({ name: 'Select', + slots: ['option'], setup(props: SelectProps) { const useInternalProps = computed( () => props.internalProps && props.internalProps.mark === INTERNAL_PROPS_MARK, @@ -1093,6 +1094,7 @@ export default function generateSelector< displayValues, activeValue, onSearchSubmit, + $slots: slots, } = this as any; const { prefixCls = defaultPrefixCls, @@ -1204,6 +1206,7 @@ export default function generateSelector< menuItemSelectedIcon={menuItemSelectedIcon} virtual={virtual !== false && dropdownMatchSelectWidth !== false} onMouseenter={onPopupMouseEnter} + v-slots={{ option: slots.option }} /> ); diff --git a/v2-doc b/v2-doc index 6b53258cc..157cce105 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit 6b53258cc2b3709e070d340714e992760e660e67 +Subproject commit 157cce105e1f0a369658dfb29cc802ebc09d0d93 From 01718e35badb12ec2b44d2a9584aafe036f53978 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 11 Aug 2021 16:41:23 +0800 Subject: [PATCH 09/53] fix: transition remove appearFromClass #4409 --- components/_util/transition.tsx | 4 ++-- v2-doc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/_util/transition.tsx b/components/_util/transition.tsx index 18c1733d3..140e31713 100644 --- a/components/_util/transition.tsx +++ b/components/_util/transition.tsx @@ -10,7 +10,7 @@ export const getTransitionProps = (transitionName: string, opt: object = {}) => const transitionProps = transitionName ? { appear: true, - appearFromClass: `${transitionName}-appear ${transitionName}-appear-prepare`, + // appearFromClass: `${transitionName}-appear ${transitionName}-appear-prepare`, // appearActiveClass: `antdv-base-transtion`, appearToClass: `${transitionName}-appear ${transitionName}-appear-active`, enterFromClass: `${transitionName}-enter ${transitionName}-enter-prepare`, @@ -29,7 +29,7 @@ export const getTransitionGroupProps = (transitionName: string, opt: object = {} const transitionProps = transitionName ? { appear: true, - appearFromClass: `${transitionName}-appear ${transitionName}-appear-prepare`, + // appearFromClass: `${transitionName}-appear ${transitionName}-appear-prepare`, appearActiveClass: `${transitionName}`, appearToClass: `${transitionName}-appear ${transitionName}-appear-active`, enterFromClass: `${transitionName}-appear ${transitionName}-enter ${transitionName}-appear-prepare ${transitionName}-enter-prepare`, diff --git a/v2-doc b/v2-doc index 157cce105..2924233b6 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit 157cce105e1f0a369658dfb29cc802ebc09d0d93 +Subproject commit 2924233b60d3645d3df5e86e8bec912d3a13496d From ccb24016c07632f49550646c971060c402586c67 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 11 Aug 2021 16:59:39 +0800 Subject: [PATCH 10/53] fix: select not scrollTo active position --- components/vc-select/OptionList.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/vc-select/OptionList.tsx b/components/vc-select/OptionList.tsx index 0f9330910..a61876512 100644 --- a/components/vc-select/OptionList.tsx +++ b/components/vc-select/OptionList.tsx @@ -151,7 +151,9 @@ const OptionList = defineComponent({ const value = Array.from(props.values)[0]; const index = memoFlattenOptions.value.findIndex(({ data }) => data.value === value); setActive(index); - scrollIntoView(index); + nextTick(() => { + scrollIntoView(index); + }); } // Force trigger scrollbar visible when open if (props.open) { From 665e5919af5548d3700f0142345f98a1149386a1 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 11 Aug 2021 17:13:51 +0800 Subject: [PATCH 11/53] release 2.2.5 --- CHANGELOG.en-US.md | 8 ++++++++ CHANGELOG.zh-CN.md | 8 ++++++++ package.json | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index a1fd75d75..4ff30a9a6 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -10,6 +10,14 @@ --- +## 2.2.5 + +`2021-08-11` + +- 🌟 `Select` supports customizing nodes through option slots [68c1f4](https://github.com/vueComponent/ant-design-vue/commit/68c1f4550108a3a6bbe4f1b2c5c168523fd6c84a) +- 🐞 Fix the problem that the pop-up window component in the development environment does not display in the lower version of chrome, and avoid the pop-up window flashing [#4409](https://github.com/vueComponent/ant-design-vue/issues/4409) +- 🐞 Fix the problem of not scrolling to the active position when `Select` is opened [ccb240](https://github.com/vueComponent/ant-design-vue/commit/ccb24016c07632f49550646c971060c402586c67) + ## 2.2.4 `2021-08-10` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 81ecfbb83..bf7755094 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -10,6 +10,14 @@ --- +## 2.2.5 + +`2021-08-11` + +- 🌟 `Select` 支持通过 option 插槽定制化节点 [68c1f4](https://github.com/vueComponent/ant-design-vue/commit/68c1f4550108a3a6bbe4f1b2c5c168523fd6c84a) +- 🐞 修复开发环境下弹窗类组件在低版本 chrome 下,不显示问题,并避免弹窗闪动 [#4409](https://github.com/vueComponent/ant-design-vue/issues/4409) +- 🐞 修复 `Select` 打开时没有滚动到激活位置问题 [ccb240](https://github.com/vueComponent/ant-design-vue/commit/ccb24016c07632f49550646c971060c402586c67) + ## 2.2.4 `2021-08-10` diff --git a/package.json b/package.json index 83703b2cf..f339f3115 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ant-design-vue", - "version": "2.2.4", + "version": "2.2.5", "title": "Ant Design Vue", "description": "An enterprise-class UI design language and Vue-based implementation", "keywords": [ From 4e14656ef1adf1b56ab39f22b8aa43c86540b5c6 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Thu, 12 Aug 2021 15:11:40 +0800 Subject: [PATCH 12/53] fix: table expanded icon render error #4507 --- components/vc-table/src/TableHeader.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/vc-table/src/TableHeader.jsx b/components/vc-table/src/TableHeader.jsx index aee123792..154356cc2 100644 --- a/components/vc-table/src/TableHeader.jsx +++ b/components/vc-table/src/TableHeader.jsx @@ -1,4 +1,4 @@ -import { computed, inject } from 'vue'; +import { inject } from 'vue'; import PropTypes from '../../_util/vue-types'; import TableHeaderRow from './TableHeaderRow'; @@ -75,21 +75,20 @@ export default { columns: PropTypes.array.isRequired, expander: PropTypes.object.isRequired, }, - setup(props) { + setup() { return { table: inject('table', {}), - rows: computed(() => parseHeaderRows(props.columns)), }; }, render() { const { sComponents: components, prefixCls, showHeader, customHeaderRow } = this.table; - const { expander, columns, fixed, rows } = this; + const { expander, columns, fixed } = this; if (!showHeader) { return null; } - + const rows = parseHeaderRows(this.columns); expander.renderExpandIndentCell(rows, fixed); const HeaderWrapper = components.header.wrapper; From 8187d3a73959612ad777213a2890bd006b297f6d Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Thu, 12 Aug 2021 15:33:01 +0800 Subject: [PATCH 13/53] fix: rate slot character not work #4509 close #4509 --- components/rate/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/rate/index.tsx b/components/rate/index.tsx index 312a3dac0..fb690bab8 100644 --- a/components/rate/index.tsx +++ b/components/rate/index.tsx @@ -40,7 +40,6 @@ const Rate = defineComponent({ allowClear: true, prefixCls: 'ant-rate', tabindex: 0, - character: '★', direction: 'ltr', }), emits: ['hoverChange', 'update:value', 'change', 'focus', 'blur', 'keydown'], From 9f15fe9fdf5492d058419d48769516839b4a79a5 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Thu, 12 Aug 2021 15:42:33 +0800 Subject: [PATCH 14/53] fix: add resize-observer-polyfill #4508 close #4508 --- components/vc-align/util.ts | 1 + components/vc-resize-observer/index.tsx | 7 +++---- components/vc-slick/src/inner-slider.js | 1 + components/vc-tabs/src/ScrollableTabBarNode.jsx | 1 + package.json | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/vc-align/util.ts b/components/vc-align/util.ts index ecff1cdfc..585828c1b 100644 --- a/components/vc-align/util.ts +++ b/components/vc-align/util.ts @@ -1,5 +1,6 @@ import contains from '../vc-util/Dom/contains'; import type { TargetPoint } from './interface'; +import ResizeObserver from 'resize-observer-polyfill'; export function isSamePoint(prev: TargetPoint, next: TargetPoint) { if (prev === next) return true; diff --git a/components/vc-resize-observer/index.tsx b/components/vc-resize-observer/index.tsx index 850f27e6b..86613fc4a 100644 --- a/components/vc-resize-observer/index.tsx +++ b/components/vc-resize-observer/index.tsx @@ -1,5 +1,6 @@ // based on rc-resize-observer 1.0.0 import type { PropType } from 'vue'; +import ResizeObserver from 'resize-observer-polyfill'; import { defineComponent, getCurrentInstance, @@ -18,7 +19,7 @@ interface ResizeObserverState { offsetWidth: number; } -const ResizeObserver = defineComponent({ +export default defineComponent({ name: 'ResizeObserver', props: { disabled: Boolean, @@ -110,7 +111,7 @@ const ResizeObserver = defineComponent({ } if (!resizeObserver && element) { - resizeObserver = new window.ResizeObserver(onResize); + resizeObserver = new ResizeObserver(onResize); resizeObserver.observe(element); } }; @@ -135,5 +136,3 @@ const ResizeObserver = defineComponent({ }; }, }); - -export default ResizeObserver; diff --git a/components/vc-slick/src/inner-slider.js b/components/vc-slick/src/inner-slider.js index 421ef43d9..828e79bad 100644 --- a/components/vc-slick/src/inner-slider.js +++ b/components/vc-slick/src/inner-slider.js @@ -1,4 +1,5 @@ import debounce from 'lodash-es/debounce'; +import ResizeObserver from 'resize-observer-polyfill'; import classnames from '../../_util/classNames'; import BaseMixin from '../../_util/BaseMixin'; import defaultProps from './default-props'; diff --git a/components/vc-tabs/src/ScrollableTabBarNode.jsx b/components/vc-tabs/src/ScrollableTabBarNode.jsx index 806bc9a32..2e1650e47 100644 --- a/components/vc-tabs/src/ScrollableTabBarNode.jsx +++ b/components/vc-tabs/src/ScrollableTabBarNode.jsx @@ -1,4 +1,5 @@ import debounce from 'lodash-es/debounce'; +import ResizeObserver from 'resize-observer-polyfill'; import PropTypes from '../../_util/vue-types'; import BaseMixin from '../../_util/BaseMixin'; import { getComponent, getSlot } from '../../_util/props-util'; diff --git a/package.json b/package.json index f339f3115..d67796e3b 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,6 @@ "querystring": "^0.2.0", "raw-loader": "^4.0.2", "reqwest": "^2.0.5", - "resize-observer-polyfill": "^1.5.1", "rimraf": "^3.0.0", "rucksack-css": "^1.0.2", "selenium-server": "^3.0.1", @@ -214,6 +213,7 @@ "lodash-es": "^4.17.15", "moment": "^2.27.0", "omit.js": "^2.0.0", + "resize-observer-polyfill": "^1.5.1", "scroll-into-view-if-needed": "^2.2.25", "shallow-equal": "^1.0.0", "vue-types": "^3.0.0", From d0f03cae2aa6b21f08fb87f85f8e1cc1279dc7d4 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 12 Aug 2021 15:47:15 +0800 Subject: [PATCH 15/53] fix(modal): allow Prop `getContainer` to be type of string & boolean (#4494) --- components/modal/Modal.tsx | 4 ++-- components/modal/__tests__/Modal.test.js | 13 +++++++++++ .../__snapshots__/Modal.test.js.snap | 23 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/components/modal/Modal.tsx b/components/modal/Modal.tsx index bc28b952c..1b3fbc02e 100644 --- a/components/modal/Modal.tsx +++ b/components/modal/Modal.tsx @@ -79,7 +79,7 @@ const modalProps = { wrapClassName: PropTypes.string, maskTransitionName: PropTypes.string, transitionName: PropTypes.string, - getContainer: PropTypes.func, + getContainer: PropTypes.any, zIndex: PropTypes.number, bodyStyle: PropTypes.style, maskStyle: PropTypes.style, @@ -119,7 +119,7 @@ export interface ModalFuncProps { maskStyle?: CSSProperties; type?: string; keyboard?: boolean; - getContainer?: getContainerFunc; + getContainer?: getContainerFunc | boolean | string; autoFocusButton?: null | 'ok' | 'cancel'; transitionName?: string; maskTransitionName?: string; diff --git a/components/modal/__tests__/Modal.test.js b/components/modal/__tests__/Modal.test.js index fb8a6bc7e..f9f7adca3 100644 --- a/components/modal/__tests__/Modal.test.js +++ b/components/modal/__tests__/Modal.test.js @@ -65,4 +65,17 @@ describe('Modal', () => { expect(wrapper.html()).toMatchSnapshot(); }); }); + + it('should work with getContainer=false', async () => { + const wrapper1 = mount(Modal, { + sync: false, + props: { + getContainer: false, + visible: true, + }, + }); + await asyncExpect(() => { + expect(wrapper1.html()).toMatchSnapshot(); + }); + }); }); diff --git a/components/modal/__tests__/__snapshots__/Modal.test.js.snap b/components/modal/__tests__/__snapshots__/Modal.test.js.snap index 1d3bc0ccc..ede42e3f3 100644 --- a/components/modal/__tests__/__snapshots__/Modal.test.js.snap +++ b/components/modal/__tests__/__snapshots__/Modal.test.js.snap @@ -86,3 +86,26 @@ exports[`Modal render without footer 1`] = ` `; + +exports[`Modal should work with getContainer=false 1`] = ` +
+
+ +
+`; From 6479864a6f02cc6940d8473259960ccb141c3526 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Thu, 12 Aug 2021 16:04:33 +0800 Subject: [PATCH 16/53] release 2.2.6 --- CHANGELOG.en-US.md | 8 ++++++++ CHANGELOG.zh-CN.md | 8 ++++++++ package.json | 2 +- v2-doc | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 4ff30a9a6..5865a859a 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -10,6 +10,14 @@ --- +## 2.2.6 + +`2021-08-12` + +- 🐞 Fix `Table` expanded list rendering problem [#4507](https://github.com/vueComponent/ant-design-vue/issues/4507) +- 🐞 Fix `Rate` custom `character` slot not taking effect [#4509](https://github.com/vueComponent/ant-design-vue/issues/4509) +- 🐞 Add resize-observer-polyfill to fix the problem of reporting errors in low versions of Chrome [#4508](https://github.com/vueComponent/ant-design-vue/issues/4508) + ## 2.2.5 `2021-08-11` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index bf7755094..662b07486 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -10,6 +10,14 @@ --- +## 2.2.6 + +`2021-08-12` + +- 🐞 修复 `Table` 展开列表渲染错位问题 [#4507](https://github.com/vueComponent/ant-design-vue/issues/4507) +- 🐞 修复 `Rate` 自定义 `character` 插槽未生效问题 [#4509](https://github.com/vueComponent/ant-design-vue/issues/4509) +- 🐞 添加 resize-observer-polyfill, 修复在低版本 Chrome 下报错问题 [#4508](https://github.com/vueComponent/ant-design-vue/issues/4508) + ## 2.2.5 `2021-08-11` diff --git a/package.json b/package.json index d67796e3b..f9a85d4f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ant-design-vue", - "version": "2.2.5", + "version": "2.2.6", "title": "Ant Design Vue", "description": "An enterprise-class UI design language and Vue-based implementation", "keywords": [ diff --git a/v2-doc b/v2-doc index 2924233b6..e5fb2accb 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit 2924233b60d3645d3df5e86e8bec912d3a13496d +Subproject commit e5fb2accb9cf5e02e2fd0011310a70041b5ff7a1 From af0620d14e3f02cface68017df682d0bd14a3328 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Fri, 13 Aug 2021 22:12:29 +0800 Subject: [PATCH 17/53] feat: support overflowedIndicator slot #4515 --- components/menu/src/Menu.tsx | 4 ++-- v2-doc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/menu/src/Menu.tsx b/components/menu/src/Menu.tsx index 58ccd6013..407287680 100644 --- a/components/menu/src/Menu.tsx +++ b/components/menu/src/Menu.tsx @@ -68,7 +68,7 @@ export default defineComponent({ 'click', 'update:activeKey', ], - slots: ['expandIcon'], + slots: ['expandIcon', 'overflowedIndicator'], setup(props, { slots, emit }) { const { prefixCls, direction } = useConfigInject('menu', props); const store = ref>({}); @@ -396,7 +396,7 @@ export default defineComponent({ {child} )); - const overflowedIndicator = ; + const overflowedIndicator = slots.overflowedIndicator?.() || ; return ( Date: Tue, 17 Aug 2021 14:36:46 +0800 Subject: [PATCH 18/53] refactor: tree --- components/vc-tree/DropIndicator.tsx | 34 + components/vc-tree/Indent.tsx | 31 + components/vc-tree/MotionTreeNode.tsx | 101 +++ components/vc-tree/NodeList.tsx | 321 +++++++ components/vc-tree/Tree.tsx | 1087 +++++++++++++++++++++++ components/vc-tree/TreeNode.tsx | 465 ++++++++++ components/vc-tree/assets/icons.png | Bin 11173 -> 0 bytes components/vc-tree/assets/index.less | 194 ---- components/vc-tree/assets/line.gif | Bin 45 -> 0 bytes components/vc-tree/assets/loading.gif | Bin 381 -> 0 bytes components/vc-tree/contextTypes.ts | 104 +++ components/vc-tree/index.js | 4 - components/vc-tree/index.ts | 8 + components/vc-tree/interface.tsx | 87 ++ components/vc-tree/props.ts | 233 +++++ components/vc-tree/src/Tree.jsx | 686 -------------- components/vc-tree/src/TreeNode.jsx | 578 ------------ components/vc-tree/src/index.js | 5 - components/vc-tree/src/util.js | 426 --------- components/vc-tree/util.tsx | 353 ++++++++ components/vc-tree/utils/conductUtil.ts | 252 ++++++ components/vc-tree/utils/diffUtil.ts | 45 + components/vc-tree/utils/treeUtil.ts | 411 +++++++++ components/vc-virtual-list/List.tsx | 23 +- v2-doc | 2 +- 25 files changed, 3553 insertions(+), 1897 deletions(-) create mode 100644 components/vc-tree/DropIndicator.tsx create mode 100644 components/vc-tree/Indent.tsx create mode 100644 components/vc-tree/MotionTreeNode.tsx create mode 100644 components/vc-tree/NodeList.tsx create mode 100644 components/vc-tree/Tree.tsx create mode 100644 components/vc-tree/TreeNode.tsx delete mode 100644 components/vc-tree/assets/icons.png delete mode 100644 components/vc-tree/assets/index.less delete mode 100644 components/vc-tree/assets/line.gif delete mode 100644 components/vc-tree/assets/loading.gif create mode 100644 components/vc-tree/contextTypes.ts delete mode 100644 components/vc-tree/index.js create mode 100644 components/vc-tree/index.ts create mode 100644 components/vc-tree/interface.tsx create mode 100644 components/vc-tree/props.ts delete mode 100644 components/vc-tree/src/Tree.jsx delete mode 100644 components/vc-tree/src/TreeNode.jsx delete mode 100644 components/vc-tree/src/index.js delete mode 100644 components/vc-tree/src/util.js create mode 100644 components/vc-tree/util.tsx create mode 100644 components/vc-tree/utils/conductUtil.ts create mode 100644 components/vc-tree/utils/diffUtil.ts create mode 100644 components/vc-tree/utils/treeUtil.ts diff --git a/components/vc-tree/DropIndicator.tsx b/components/vc-tree/DropIndicator.tsx new file mode 100644 index 000000000..e3d836243 --- /dev/null +++ b/components/vc-tree/DropIndicator.tsx @@ -0,0 +1,34 @@ +import { CSSProperties } from 'vue'; + +export default function DropIndicator({ + dropPosition, + dropLevelOffset, + indent, +}: { + dropPosition: -1 | 0 | 1; + dropLevelOffset: number; + indent: number; +}) { + const style: CSSProperties = { + pointerEvents: 'none', + position: 'absolute', + right: 0, + backgroundColor: 'red', + height: `${2}px`, + }; + switch (dropPosition) { + case -1: + style.top = 0; + style.left = `${-dropLevelOffset * indent}px`; + break; + case 1: + style.bottom = 0; + style.left = `${-dropLevelOffset * indent}px`; + break; + case 0: + style.bottom = 0; + style.left = `${indent}`; + break; + } + return
; +} diff --git a/components/vc-tree/Indent.tsx b/components/vc-tree/Indent.tsx new file mode 100644 index 000000000..de353cd89 --- /dev/null +++ b/components/vc-tree/Indent.tsx @@ -0,0 +1,31 @@ +interface IndentProps { + prefixCls: string; + level: number; + isStart: boolean[]; + isEnd: boolean[]; +} + +const Indent = ({ prefixCls, level, isStart, isEnd }: IndentProps) => { + const baseClassName = `${prefixCls}-indent-unit`; + const list = []; + for (let i = 0; i < level; i += 1) { + list.push( + , + ); + } + + return ( + + ); +}; + +export default Indent; diff --git a/components/vc-tree/MotionTreeNode.tsx b/components/vc-tree/MotionTreeNode.tsx new file mode 100644 index 000000000..568cdc9d9 --- /dev/null +++ b/components/vc-tree/MotionTreeNode.tsx @@ -0,0 +1,101 @@ +import TreeNode from './TreeNode'; +import { FlattenNode } from './interface'; +import { getTreeNodeProps, TreeNodeRequiredProps } from './utils/treeUtil'; +import { useInjectTreeContext } from './contextTypes'; +import { defineComponent, onBeforeUnmount, onMounted, PropType, ref, Transition, watch } from 'vue'; +import { treeNodeProps } from './props'; + +export default defineComponent({ + name: 'MotionTreeNode', + inheritAttrs: false, + props: { + ...treeNodeProps, + active: Boolean, + motion: Object, + motionNodes: { type: Array as PropType }, + onMotionStart: Function, + onMotionEnd: Function, + motionType: String, + treeNodeRequiredProps: { type: Object as PropType }, + }, + slots: ['title', 'icon', 'switcherIcon'], + setup(props, { attrs, slots }) { + const visible = ref(true); + const context = useInjectTreeContext(); + const motionedRef = ref(false); + const onMotionEnd = () => { + if (!motionedRef.value) { + props.onMotionEnd(); + } + motionedRef.value = true; + }; + + watch( + () => props.motionNodes, + () => { + if (props.motionNodes && props.motionType === 'hide' && visible.value) { + visible.value = false; + } + }, + ); + onMounted(() => { + props.motionNodes && props.onMotionStart(); + }); + onBeforeUnmount(() => { + props.motionNodes && onMotionEnd(); + }); + return () => { + const { motion, motionNodes, motionType, active, treeNodeRequiredProps, ...otherProps } = + props; + if (motionNodes) { + return ( + +
+ {motionNodes.map((treeNode: FlattenNode) => { + const { + data: { ...restProps }, + title, + key, + isStart, + isEnd, + } = treeNode; + delete restProps.children; + + const treeNodeProps = getTreeNodeProps(key, treeNodeRequiredProps); + + return ( + + ); + })} +
+
+ ); + } + return ( + + ); + }; + }, +}); diff --git a/components/vc-tree/NodeList.tsx b/components/vc-tree/NodeList.tsx new file mode 100644 index 000000000..01c3a2cce --- /dev/null +++ b/components/vc-tree/NodeList.tsx @@ -0,0 +1,321 @@ +/** + * Handle virtual list of the TreeNodes. + */ + +import { computed, defineComponent, ref, watch } from 'vue'; +import VirtualList from '../vc-virtual-list'; +import { FlattenNode, DataEntity, DataNode, ScrollTo } from './interface'; +import MotionTreeNode from './MotionTreeNode'; +import { nodeListProps } from './props'; +import { findExpandedKeys, getExpandRange } from './utils/diffUtil'; +import { getTreeNodeProps, getKey } from './utils/treeUtil'; + +const HIDDEN_STYLE = { + width: 0, + height: 0, + display: 'flex', + overflow: 'hidden', + opacity: 0, + border: 0, + padding: 0, + margin: 0, +}; + +const noop = () => {}; + +export const MOTION_KEY = `RC_TREE_MOTION_${Math.random()}`; + +const MotionNode: DataNode = { + key: MOTION_KEY, +}; + +export const MotionEntity: DataEntity = { + key: MOTION_KEY, + level: 0, + index: 0, + pos: '0', + node: MotionNode, +}; + +const MotionFlattenData: FlattenNode = { + parent: null, + children: [], + pos: MotionEntity.pos, + data: MotionNode, + title: null, + key: MOTION_KEY, + /** Hold empty list here since we do not use it */ + isStart: [], + isEnd: [], +}; + +export interface NodeListRef { + scrollTo: ScrollTo; + getIndentWidth: () => number; +} + +/** + * We only need get visible content items to play the animation. + */ +export function getMinimumRangeTransitionRange( + list: FlattenNode[], + virtual: boolean, + height: number, + itemHeight: number, +) { + if (virtual === false || !height) { + return list; + } + + return list.slice(0, Math.ceil(height / itemHeight) + 1); +} + +function itemKey(item: FlattenNode) { + const { + data: { key }, + pos, + } = item; + return getKey(key, pos); +} + +function getAccessibilityPath(item: FlattenNode): string { + let path = String(item.data.key); + let current = item; + + while (current.parent) { + current = current.parent; + path = `${current.data.key} > ${path}`; + } + + return path; +} + +export default defineComponent({ + name: 'NodeList', + inheritAttrs: false, + props: nodeListProps, + setup(props, { expose, attrs, slots }) { + // =============================== Ref ================================ + const listRef = ref(null); + const indentMeasurerRef = ref(null); + expose({ + scrollTo: scroll => { + listRef.value.scrollTo(scroll); + }, + getIndentWidth: () => indentMeasurerRef.value.offsetWidth, + }); + + // ============================== Motion ============================== + const transitionData = ref(props.data); + const transitionRange = ref([]); + const motionType = ref<'show' | 'hide' | null>(null); + + function onMotionEnd() { + transitionData.value = props.data; + transitionRange.value = []; + motionType.value = null; + + props.onListChangeEnd(); + } + watch( + [() => ({ ...props.expandedKeys }), () => props.data], + ([expandedKeys, data], [prevExpandedKeys, prevData]) => { + const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys); + + if (diffExpanded.key !== null) { + const { virtual, height, itemHeight } = props; + if (diffExpanded.add) { + const keyIndex = prevData.findIndex(({ data: { key } }) => key === diffExpanded.key); + + const rangeNodes = getMinimumRangeTransitionRange( + getExpandRange(prevData, data, diffExpanded.key), + virtual, + height, + itemHeight, + ); + + const newTransitionData: FlattenNode[] = prevData.slice(); + newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData); + + transitionData.value = newTransitionData; + transitionRange.value = rangeNodes; + motionType.value = 'show'; + } else { + const keyIndex = data.findIndex(({ data: { key } }) => key === diffExpanded.key); + + const rangeNodes = getMinimumRangeTransitionRange( + getExpandRange(data, prevData, diffExpanded.key), + virtual, + height, + itemHeight, + ); + + const newTransitionData: FlattenNode[] = data.slice(); + newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData); + + transitionData.value = newTransitionData; + transitionRange.value = rangeNodes; + motionType.value = 'hide'; + } + } else if (prevData !== data) { + transitionData.value = data; + } + }, + { immediate: true }, + ); + + // We should clean up motion if is changed by dragging + watch( + () => props.dragging, + dragging => { + if (!dragging) { + onMotionEnd(); + } + }, + ); + + const mergedData = computed(() => (props.motion ? transitionData.value : props.data)); + + return () => { + const { + prefixCls, + data, + selectable, + checkable, + expandedKeys, + selectedKeys, + checkedKeys, + loadedKeys, + loadingKeys, + halfCheckedKeys, + keyEntities, + disabled, + + dragging, + dragOverNodeKey, + dropPosition, + motion, + + height, + itemHeight, + virtual, + + focusable, + activeItem, + focused, + tabindex, + + onKeydown, + onFocus, + onBlur, + onActiveChange, + + onListChangeStart, + onListChangeEnd, + + ...domProps + } = { ...props, ...attrs }; + + const treeNodeRequiredProps = { + expandedKeys, + selectedKeys, + loadedKeys, + loadingKeys, + checkedKeys, + halfCheckedKeys, + dragOverNodeKey, + dropPosition, + keyEntities, + }; + return ( + <> + {focused && activeItem && ( + + {getAccessibilityPath(activeItem)} + + )} + +
+ +
+ +
+
+
+
+
+ + + {(treeNode: FlattenNode) => { + const { + pos, + data: { ...restProps }, + title, + key, + isStart, + isEnd, + } = treeNode; + const mergedKey = getKey(key, pos); + delete restProps.key; + delete restProps.children; + + const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps); + + return ( + { + onActiveChange(null); + }} + /> + ); + }} + + + ); + }; + }, +}); diff --git a/components/vc-tree/Tree.tsx b/components/vc-tree/Tree.tsx new file mode 100644 index 000000000..654c59d7f --- /dev/null +++ b/components/vc-tree/Tree.tsx @@ -0,0 +1,1087 @@ +import { TreeContext, NodeMouseEventHandler, NodeDragEventHandler } from './contextTypes'; +import { + getDataAndAria, + getDragChildrenKeys, + parseCheckedKeys, + conductExpandParent, + calcSelectedKeys, + calcDropPosition, + arrAdd, + arrDel, + posToArr, +} from './util'; +import { Key, FlattenNode, EventDataNode, NodeInstance, ScrollTo } from './interface'; +import { + flattenTreeData, + convertTreeToData, + convertDataToEntities, + convertNodePropsToEventData, + getTreeNodeProps, + fillFieldNames, +} from './utils/treeUtil'; +import NodeList, { MOTION_KEY, MotionEntity } from './NodeList'; +import { conductCheck } from './utils/conductUtil'; +import DropIndicator from './DropIndicator'; +import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, watchEffect } from 'vue'; +import initDefaultProps from '../_util/props-util/initDefaultProps'; +import { CheckInfo, treeProps } from './props'; +import { warning } from '../vc-util/warning'; +import KeyCode from '../_util/KeyCode'; +import classNames from '../_util/classNames'; + +export default defineComponent({ + name: 'Tree', + inheritAttrs: false, + props: initDefaultProps(treeProps(), { + prefixCls: 'vc-tree', + showLine: false, + showIcon: true, + selectable: true, + multiple: false, + checkable: false, + disabled: false, + checkStrictly: false, + draggable: false, + defaultExpandParent: true, + autoExpandParent: false, + defaultExpandAll: false, + defaultExpandedKeys: [], + defaultCheckedKeys: [], + defaultSelectedKeys: [], + dropIndicatorRender: DropIndicator, + allowDrop: () => true, + }), + + setup(props, { attrs }) { + const destroyed = ref(false); + let delayedDragEnterLogic: Record = {}; + + const indent = ref(); + const selectedKeys = ref([]); + const checkedKeys = ref([]); + const halfCheckedKeys = ref([]); + const loadedKeys = ref([]); + const loadingKeys = ref([]); + const expandedKeys = ref([]); + + const dragState = reactive({ + dragging: false, + dragChildrenKeys: [], + + // dropTargetKey is the key of abstract-drop-node + // the abstract-drop-node is the real drop node when drag and drop + // not the DOM drag over node + dropTargetKey: null, + dropPosition: null, // the drop position of abstract-drop-node, inside 0, top -1, bottom 1 + dropContainerKey: null, // the container key of abstract-drop-node if dropPosition is -1 or 1 + dropLevelOffset: null, // the drop level offset of abstract-drag-over-node + dropTargetPos: null, // the pos of abstract-drop-node + dropAllowed: true, // if drop to abstract-drop-node is allowed + // the abstract-drag-over-node + // if mouse is on the bottom of top dom node or no the top of the bottom dom node + // abstract-drag-over-node is the top node + dragOverNodeKey: null, + }); + + const treeData = computed(() => { + warning( + !(props.treeData === undefined && props.children), + '`children` of Tree is deprecated. Please use `treeData` instead.', + ); + return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children); + }); + const keyEntities = ref({}); + watchEffect(() => { + if (treeData.value) { + const entitiesMap = convertDataToEntities(treeData.value, { fieldNames: fieldNames.value }); + keyEntities.value = { + [MOTION_KEY]: MotionEntity, + ...entitiesMap.keyEntities, + }; + } + }); + let init = false; // 处理 defaultXxxx api, 仅仅首次有效 + + onMounted(() => { + init = true; + }); + + // ================ expandedKeys ================= + watchEffect(() => { + let keys = expandedKeys.value; + // ================ expandedKeys ================= + if (props.expandedKeys !== undefined || (init && props.autoExpandParent)) { + keys = + props.autoExpandParent || (!init && props.defaultExpandParent) + ? conductExpandParent(props.expandedKeys, keyEntities.value) + : props.expandedKeys; + } else if (!init && props.defaultExpandAll) { + const cloneKeyEntities = { ...keyEntities }; + delete cloneKeyEntities[MOTION_KEY]; + keys = Object.keys(cloneKeyEntities).map(key => cloneKeyEntities[key].key); + } else if (!init && props.defaultExpandedKeys) { + keys = + props.autoExpandParent || props.defaultExpandParent + ? conductExpandParent(props.defaultExpandedKeys, keyEntities.value) + : props.defaultExpandedKeys; + } + + if (keys) { + expandedKeys.value = keys; + } + }); + + // ================ flattenNodes ================= + const flattenNodes = computed(() => { + return flattenTreeData(treeData.value, expandedKeys.value, fieldNames.value); + }); + + // ================ selectedKeys ================= + watchEffect(() => { + if (props.selectable) { + if (props.selectedKeys !== undefined) { + selectedKeys.value = calcSelectedKeys(props.selectedKeys, props); + } else if (!init && props.defaultSelectedKeys) { + selectedKeys.value = calcSelectedKeys(props.defaultSelectedKeys, props); + } + } + }); + + // ================= checkedKeys ================= + watchEffect(() => { + if (props.checkable) { + let checkedKeyEntity; + + if (props.checkedKeys !== undefined) { + checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || {}; + } else if (!init && props.defaultCheckedKeys) { + checkedKeyEntity = parseCheckedKeys(props.defaultCheckedKeys) || {}; + } else if (treeData) { + // If `treeData` changed, we also need check it + checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || { + checkedKeys: checkedKeys.value, + halfCheckedKeys: halfCheckedKeys.value, + }; + } + + if (checkedKeyEntity) { + let { checkedKeys: newCheckedKeys = [], halfCheckedKeys: newHalfCheckedKeys = [] } = + checkedKeyEntity; + + if (!props.checkStrictly) { + const conductKeys = conductCheck(newCheckedKeys, true, keyEntities.value); + ({ checkedKeys: newCheckedKeys, halfCheckedKeys: newHalfCheckedKeys } = conductKeys); + } + + checkedKeys.value = newCheckedKeys; + halfCheckedKeys.value = newHalfCheckedKeys; + } + } + }); + + // ================= loadedKeys ================== + watchEffect(() => { + if (props.loadedKeys) { + loadedKeys.value = props.loadedKeys; + } + }); + + const focused = ref(false); + const activeKey = ref(null); + + const listChanging = ref(false); + + const fieldNames = computed(() => fillFieldNames(props.fieldNames)); + + const listRef = ref(); + + let dragStartMousePosition = null; + + let dragNode = null; + + const treeNodeRequiredProps = computed(() => { + return { + expandedKeys: expandedKeys.value || [], + selectedKeys: selectedKeys.value || [], + loadedKeys: loadedKeys.value || [], + loadingKeys: loadingKeys.value || [], + checkedKeys: checkedKeys.value || [], + halfCheckedKeys: halfCheckedKeys.value || [], + dragOverNodeKey: dragState.dragOverNodeKey, + dropPosition: dragState.dropPosition, + keyEntities: keyEntities.value, + }; + }); + const scrollTo: ScrollTo = scroll => { + listRef.value.scrollTo(scroll); + }; + // =========================== Expanded =========================== + /** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */ + const setExpandedKeys = (keys: Key[]) => { + if (props.expandedKeys !== undefined) { + expandedKeys.value = keys; + } + }; + + const cleanDragState = () => { + if (dragState.dragging) { + Object.assign(dragState, { + dragging: false, + dropPosition: null, + dropContainerKey: null, + dropTargetKey: null, + dropLevelOffset: null, + dropAllowed: true, + dragOverNodeKey: null, + }); + } + dragStartMousePosition = null; + }; + // if onNodeDragEnd is called, onWindowDragEnd won't be called since stopPropagation() is called + const onNodeDragEnd: NodeDragEventHandler = (event, node, outsideTree = false) => { + const { onDragend } = props; + + dragState.dragOverNodeKey = null; + + cleanDragState(); + + if (onDragend && !outsideTree) { + onDragend({ event, node: convertNodePropsToEventData(node.props) }); + } + + dragNode = null; + }; + + // since stopPropagation() is called in treeNode + // if onWindowDrag is called, whice means state is keeped, drag state should be cleared + const onWindowDragEnd = event => { + onNodeDragEnd(event, null, true); + window.removeEventListener('dragend', onWindowDragEnd); + }; + + const onNodeDragStart: NodeDragEventHandler = (event, node) => { + const { onDragstart } = props; + const { eventKey } = node.props; + + dragNode = node; + dragStartMousePosition = { + x: event.clientX, + y: event.clientY, + }; + + const newExpandedKeys = arrDel(expandedKeys.value, eventKey); + + dragState.dragging = true; + dragState.dragChildrenKeys = getDragChildrenKeys(eventKey, keyEntities.value); + indent.value = listRef.value.getIndentWidth(); + + setExpandedKeys(newExpandedKeys); + window.addEventListener('dragend', onWindowDragEnd); + + if (onDragstart) { + onDragstart({ event, node: convertNodePropsToEventData(node.props) }); + } + }; + + /** + * [Legacy] Select handler is smaller than node, + * so that this will trigger when drag enter node or select handler. + * This is a little tricky if customize css without padding. + * Better for use mouse move event to refresh drag state. + * But let's just keep it to avoid event trigger logic change. + */ + const onNodeDragEnter = (event: MouseEvent, node: NodeInstance) => { + const { onDragenter, onExpand, allowDrop, direction } = props; + const { pos } = node.props; + + const { + dropPosition, + dropLevelOffset, + dropTargetKey, + dropContainerKey, + dropTargetPos, + dropAllowed, + dragOverNodeKey, + } = calcDropPosition( + event, + dragNode, + node, + indent.value, + dragStartMousePosition, + allowDrop, + flattenNodes.value, + keyEntities.value, + expandedKeys.value, + direction, + ); + + if ( + !dragNode || + // don't allow drop inside its children + dragState.dragChildrenKeys.indexOf(dropTargetKey) !== -1 || + // don't allow drop when drop is not allowed caculated by calcDropPosition + !dropAllowed + ) { + Object.assign(dragState, { + dragOverNodeKey: null, + dropPosition: null, + dropLevelOffset: null, + dropTargetKey: null, + dropContainerKey: null, + dropTargetPos: null, + dropAllowed: false, + }); + return; + } + + // Side effect for delay drag + if (!delayedDragEnterLogic) { + delayedDragEnterLogic = {}; + } + Object.keys(delayedDragEnterLogic).forEach(key => { + clearTimeout(delayedDragEnterLogic[key]); + }); + + if (dragNode.props.eventKey !== node.props.eventKey) { + // hoist expand logic here + // since if logic is on the bottom + // it will be blocked by abstract dragover node check + // => if you dragenter from top, you mouse will still be consider as in the top node + (event as any).persist(); + delayedDragEnterLogic[pos] = window.setTimeout(() => { + if (!dragState.dragging) return; + + let newExpandedKeys = [...expandedKeys.value]; + const entity = keyEntities[node.props.eventKey]; + + if (entity && (entity.children || []).length) { + newExpandedKeys = arrAdd(expandedKeys.value, node.props.eventKey); + } + + setExpandedKeys(newExpandedKeys); + + if (onExpand) { + onExpand(newExpandedKeys, { + node: convertNodePropsToEventData(node.props), + expanded: true, + nativeEvent: (event as any).nativeEvent, + }); + } + }, 800); + } + + // Skip if drag node is self + if (dragNode.props.eventKey === dropTargetKey && dropLevelOffset === 0) { + Object.assign(dragState, { + dragOverNodeKey: null, + dropPosition: null, + dropLevelOffset: null, + dropTargetKey: null, + dropContainerKey: null, + dropTargetPos: null, + dropAllowed: false, + }); + return; + } + + // Update drag over node and drag state + Object.assign(dragState, { + dragOverNodeKey, + dropPosition, + dropLevelOffset, + dropTargetKey, + dropContainerKey, + dropTargetPos, + dropAllowed, + }); + + if (onDragenter) { + onDragenter({ + event, + node: convertNodePropsToEventData(node.props), + expandedKeys: expandedKeys.value, + }); + } + }; + + const onNodeDragOver = (event: MouseEvent, node: NodeInstance) => { + const { onDragover, allowDrop, direction } = props; + + const { + dropPosition, + dropLevelOffset, + dropTargetKey, + dropContainerKey, + dropAllowed, + dropTargetPos, + dragOverNodeKey, + } = calcDropPosition( + event, + dragNode, + node, + indent.value, + dragStartMousePosition, + allowDrop, + flattenNodes.value, + keyEntities.value, + expandedKeys.value, + direction, + ); + + if (!dragNode || dragState.dragChildrenKeys.indexOf(dropTargetKey) !== -1 || !dropAllowed) { + // don't allow drop inside its children + // don't allow drop when drop is not allowed caculated by calcDropPosition + return; + } + + // Update drag position + + if (dragNode.props.eventKey === dropTargetKey && dropLevelOffset === 0) { + if ( + !( + dragState.dropPosition === null && + dragState.dropLevelOffset === null && + dragState.dropTargetKey === null && + dragState.dropContainerKey === null && + dragState.dropTargetPos === null && + dragState.dropAllowed === false && + dragState.dragOverNodeKey === null + ) + ) { + Object.assign(dragState, { + dropPosition: null, + dropLevelOffset: null, + dropTargetKey: null, + dropContainerKey: null, + dropTargetPos: null, + dropAllowed: false, + dragOverNodeKey: null, + }); + } + } else if ( + !( + dropPosition === dragState.dropPosition && + dropLevelOffset === dragState.dropLevelOffset && + dropTargetKey === dragState.dropTargetKey && + dropContainerKey === dragState.dropContainerKey && + dropTargetPos === dragState.dropTargetPos && + dropAllowed === dragState.dropAllowed && + dragOverNodeKey === dragState.dragOverNodeKey + ) + ) { + Object.assign(dragState, { + dropPosition, + dropLevelOffset, + dropTargetKey, + dropContainerKey, + dropTargetPos, + dropAllowed, + dragOverNodeKey, + }); + } + + if (onDragover) { + onDragover({ event, node: convertNodePropsToEventData(node.props) }); + } + }; + + const onNodeDragLeave: NodeDragEventHandler = (event, node) => { + const { onDragleave } = props; + + if (onDragleave) { + onDragleave({ event, node: convertNodePropsToEventData(node.props) }); + } + }; + const onNodeDrop = (event: MouseEvent, _node, outsideTree: boolean = false) => { + const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } = + dragState; + + if (!dropAllowed) return; + + const { onDrop } = props; + + dragState.dragOverNodeKey = null; + cleanDragState(); + + if (dropTargetKey === null) return; + + const abstractDropNodeProps = { + ...getTreeNodeProps(dropTargetKey, treeNodeRequiredProps.value), + active: activeItem.value?.data.key === dropTargetKey, + data: keyEntities.value[dropTargetKey].node, + }; + const dropToChild = dragChildrenKeys.indexOf(dropTargetKey) !== -1; + + warning( + !dropToChild, + "Can not drop to dragNode's children node. Maybe this is a bug of ant-design-vue. Please report an issue.", + ); + + const posArr = posToArr(dropTargetPos); + + const dropResult = { + event, + node: convertNodePropsToEventData(abstractDropNodeProps), + dragNode: dragNode ? convertNodePropsToEventData(dragNode.props) : null, + dragNodesKeys: [dragNode.props.eventKey].concat(dragChildrenKeys), + dropToGap: dropPosition !== 0, + dropPosition: dropPosition + Number(posArr[posArr.length - 1]), + }; + + if (onDrop && !outsideTree) { + onDrop(dropResult); + } + + dragNode = null; + }; + + const onNodeClick: NodeMouseEventHandler = (e, treeNode) => { + const { onClick } = props; + if (onClick) { + onClick(e, treeNode); + } + }; + + const onNodeDoubleClick: NodeMouseEventHandler = (e, treeNode) => { + const { onDblClick } = props; + if (onDblClick) { + onDblClick(e, treeNode); + } + }; + + const onNodeSelect: NodeMouseEventHandler = (e, treeNode) => { + let newSelectedKeys = selectedKeys.value; + const { onSelect, multiple } = props; + const { selected } = treeNode; + const key = treeNode[fieldNames.value.key]; + const targetSelected = !selected; + + // Update selected keys + if (!targetSelected) { + newSelectedKeys = arrDel(newSelectedKeys, key); + } else if (!multiple) { + newSelectedKeys = [key]; + } else { + newSelectedKeys = arrAdd(newSelectedKeys, key); + } + + // [Legacy] Not found related usage in doc or upper libs + const selectedNodes = newSelectedKeys + .map(selectedKey => { + const entity = keyEntities.value[selectedKey]; + if (!entity) return null; + + return entity.node; + }) + .filter(node => node); + + if (props.selectedKeys !== undefined) { + selectedKeys.value = newSelectedKeys; + } + + if (onSelect) { + onSelect(newSelectedKeys, { + event: 'select', + selected: targetSelected, + node: treeNode, + selectedNodes, + nativeEvent: (e as any).nativeEvent, + }); + } + }; + + const onNodeCheck = (e: MouseEvent, treeNode: EventDataNode, checked: boolean) => { + const { checkStrictly, onCheck } = props; + const { key } = treeNode; + + // Prepare trigger arguments + let checkedObj; + const eventObj: Partial = { + event: 'check', + node: treeNode, + checked, + nativeEvent: (e as any).nativeEvent, + }; + + if (checkStrictly) { + const newCheckedKeys = checked + ? arrAdd(checkedKeys.value, key) + : arrDel(checkedKeys.value, key); + const newHalfCheckedKeys = arrDel(halfCheckedKeys.value, key); + checkedObj = { checked: newCheckedKeys, halfChecked: newHalfCheckedKeys }; + + eventObj.checkedNodes = newCheckedKeys + .map(checkedKey => keyEntities[checkedKey]) + .filter(entity => entity) + .map(entity => entity.node); + + if (props.checkedKeys !== undefined) { + checkedKeys.value = newCheckedKeys; + } + } else { + // Always fill first + let { checkedKeys: newCheckedKeys, halfCheckedKeys: newHalfCheckedKeys } = conductCheck( + [...checkedKeys.value, key], + true, + keyEntities.value, + ); + + // If remove, we do it again to correction + if (!checked) { + const keySet = new Set(newCheckedKeys); + keySet.delete(key); + ({ checkedKeys: newCheckedKeys, halfCheckedKeys: newHalfCheckedKeys } = conductCheck( + Array.from(keySet), + { checked: false, halfCheckedKeys: newHalfCheckedKeys }, + keyEntities.value, + )); + } + + checkedObj = newCheckedKeys; + + // [Legacy] This is used for `rc-tree-select` + eventObj.checkedNodes = []; + eventObj.checkedNodesPositions = []; + eventObj.halfCheckedKeys = newHalfCheckedKeys; + + newCheckedKeys.forEach(checkedKey => { + const entity = keyEntities.value[checkedKey]; + if (!entity) return; + + const { node, pos } = entity; + + eventObj.checkedNodes.push(node); + eventObj.checkedNodesPositions.push({ node, pos }); + }); + if (props.checkedKeys !== undefined) { + checkedKeys.value = newCheckedKeys; + halfCheckedKeys.value = newHalfCheckedKeys; + } + } + + if (onCheck) { + onCheck(checkedObj, eventObj as CheckInfo); + } + }; + + const onNodeLoad = (treeNode: EventDataNode) => + new Promise((resolve, reject) => { + // We need to get the latest state of loading/loaded keys + const { loadData, onLoad } = props; + const { key } = treeNode; + + if ( + !loadData || + loadedKeys.value.indexOf(key) !== -1 || + loadingKeys.value.indexOf(key) !== -1 + ) { + return null; + } + + // Process load data + const promise = loadData(treeNode); + promise + .then(() => { + const newLoadedKeys = arrAdd(loadedKeys.value, key); + const newLoadingKeys = arrDel(loadingKeys.value, key); + + // onLoad should trigger before internal setState to avoid `loadData` trigger twice. + // https://github.com/ant-design/ant-design/issues/12464 + if (onLoad) { + onLoad(newLoadedKeys, { + event: 'load', + node: treeNode, + }); + } + + if (props.loadedKeys !== undefined) { + loadedKeys.value = newLoadedKeys; + } + loadingKeys.value = newLoadingKeys; + resolve(); + }) + .catch(e => { + const newLoadingKeys = arrDel(loadingKeys.value, key); + loadingKeys.value = newLoadingKeys; + reject(e); + }); + + loadingKeys.value = arrAdd(loadingKeys.value, key); + }); + + const onNodeMouseEnter: NodeMouseEventHandler = (event, node) => { + const { onMouseenter } = props; + if (onMouseenter) { + onMouseenter({ event, node }); + } + }; + + const onNodeMouseLeave: NodeMouseEventHandler = (event, node) => { + const { onMouseleave } = props; + if (onMouseleave) { + onMouseleave({ event, node }); + } + }; + + const onNodeContextMenu: NodeMouseEventHandler = (event, node) => { + const { onRightClick } = props; + if (onRightClick) { + event.preventDefault(); + onRightClick({ event, node }); + } + }; + + const onFocus = (e: FocusEvent) => { + const { onFocus } = props; + focused.value = true; + if (onFocus) { + onFocus(e); + } + }; + + const onBlur = (e: FocusEvent) => { + const { onBlur } = props; + focused.value = false; + onActiveChange(null); + + if (onBlur) { + onBlur(e); + } + }; + + const onNodeExpand = (e: MouseEvent, treeNode: EventDataNode) => { + let newExpandedKeys = expandedKeys.value; + const { onExpand, loadData } = props; + const { expanded } = treeNode; + const key = treeNode[fieldNames.value.key]; + + // Do nothing when motion is in progress + if (listChanging.value) { + return; + } + + // Update selected keys + const index = newExpandedKeys.indexOf(key); + const targetExpanded = !expanded; + + warning( + (expanded && index !== -1) || (!expanded && index === -1), + 'Expand state not sync with index check', + ); + + if (targetExpanded) { + newExpandedKeys = arrAdd(newExpandedKeys, key); + } else { + newExpandedKeys = arrDel(newExpandedKeys, key); + } + + setExpandedKeys(newExpandedKeys); + + if (onExpand) { + onExpand(newExpandedKeys, { + node: treeNode, + expanded: targetExpanded, + nativeEvent: (e as any).nativeEvent, + }); + } + + // Async Load data + if (targetExpanded && loadData) { + const loadPromise = onNodeLoad(treeNode); + if (loadPromise) { + loadPromise + .then(() => { + // [Legacy] Refresh logic + // const newFlattenTreeData = flattenTreeData( + // treeData.value, + // newExpandedKeys, + // fieldNames.value, + // ); + // flattenNodes.value = newFlattenTreeData; + }) + .catch(() => { + const expandedKeysToRestore = arrDel(expandedKeys.value, key); + setExpandedKeys(expandedKeysToRestore); + }); + } + } + }; + + const onListChangeStart = () => { + listChanging.value = true; + }; + + const onListChangeEnd = () => { + setTimeout(() => { + listChanging.value = false; + }); + }; + + // =========================== Keyboard =========================== + const onActiveChange = (newActiveKey: Key) => { + const { onActiveChange } = props; + + if (activeKey.value === newActiveKey) { + return; + } + + activeKey.value = newActiveKey; + if (newActiveKey !== null) { + scrollTo({ key: newActiveKey }); + } + + if (onActiveChange) { + onActiveChange(newActiveKey); + } + }; + + // const getActiveItem = () => { + // if (activeKey.value === null) { + // return null; + // } + + // return flattenNodes.value.find(({ data: { key } }) => key === activeKey.value) || null; + // }; + + const activeItem = computed(() => { + if (activeKey.value === null) { + return null; + } + + return flattenNodes.value.find(({ data: { key } }) => key === activeKey.value) || null; + }); + + const offsetActiveKey = (offset: number) => { + let index = flattenNodes.value.findIndex(({ data: { key } }) => key === activeKey.value); + + // Align with index + if (index === -1 && offset < 0) { + index = flattenNodes.value.length; + } + + index = (index + offset + flattenNodes.value.length) % flattenNodes.value.length; + + const item = flattenNodes.value[index]; + if (item) { + const { key } = item.data; + onActiveChange(key); + } else { + onActiveChange(null); + } + }; + + const onKeyDown = event => { + const { onKeyDown, checkable, selectable } = props; + + // >>>>>>>>>> Direction + switch (event.which) { + case KeyCode.UP: { + offsetActiveKey(-1); + event.preventDefault(); + break; + } + case KeyCode.DOWN: { + offsetActiveKey(1); + event.preventDefault(); + break; + } + } + + // >>>>>>>>>> Expand & Selection + const item = activeItem.value; + if (item && item.data) { + const expandable = item.data.isLeaf === false || !!(item.data.children || []).length; + const eventNode = convertNodePropsToEventData({ + ...getTreeNodeProps(activeKey.value, treeNodeRequiredProps.value), + data: item.data, + active: true, + }); + + switch (event.which) { + // >>> Expand + case KeyCode.LEFT: { + // Collapse if possible + if (expandable && expandedKeys.value.includes(activeKey.value)) { + onNodeExpand({} as MouseEvent, eventNode); + } else if (item.parent) { + onActiveChange(item.parent.data.key); + } + event.preventDefault(); + break; + } + case KeyCode.RIGHT: { + // Expand if possible + if (expandable && !expandedKeys.value.includes(activeKey.value)) { + onNodeExpand({} as MouseEvent, eventNode); + } else if (item.children && item.children.length) { + onActiveChange(item.children[0].data.key); + } + event.preventDefault(); + break; + } + + // Selection + case KeyCode.ENTER: + case KeyCode.SPACE: { + if ( + checkable && + !eventNode.disabled && + eventNode.checkable !== false && + !eventNode.disableCheckbox + ) { + onNodeCheck( + {} as MouseEvent, + eventNode, + !checkedKeys.value.includes(activeKey.value), + ); + } else if ( + !checkable && + selectable && + !eventNode.disabled && + eventNode.selectable !== false + ) { + onNodeSelect({} as MouseEvent, eventNode); + } + break; + } + } + } + + if (onKeyDown) { + onKeyDown(event); + } + }; + + onUnmounted(() => { + window.removeEventListener('dragend', onWindowDragEnd); + destroyed.value = true; + }); + return () => { + const { + // focused, + // flattenNodes, + // keyEntities, + dragging, + // activeKey, + dropLevelOffset, + dropContainerKey, + dropTargetKey, + dropPosition, + dragOverNodeKey, + // indent, + } = dragState; + const { + prefixCls, + showLine, + focusable, + tabindex = 0, + selectable, + showIcon, + icon, + switcherIcon, + draggable, + checkable, + checkStrictly, + disabled, + motion, + loadData, + filterTreeNode, + height, + itemHeight, + virtual, + titleRender, + dropIndicatorRender, + onContextmenu, + onScroll, + direction, + } = props; + + const { class: className, style } = attrs; + const domProps = getDataAndAria({ ...props, ...attrs }); + + return ( + +
+ +
+
+ ); + }; + }, +}); diff --git a/components/vc-tree/TreeNode.tsx b/components/vc-tree/TreeNode.tsx new file mode 100644 index 000000000..da4650072 --- /dev/null +++ b/components/vc-tree/TreeNode.tsx @@ -0,0 +1,465 @@ +import { useInjectTreeContext } from './contextTypes'; +import { getDataAndAria } from './util'; +import Indent from './Indent'; +import { convertNodePropsToEventData } from './utils/treeUtil'; +import { computed, defineComponent, getCurrentInstance, onMounted, onUpdated, ref } from 'vue'; +import { treeNodeProps } from './props'; +import classNames from '../_util/classNames'; + +const ICON_OPEN = 'open'; +const ICON_CLOSE = 'close'; + +const defaultTitle = '---'; + +export default defineComponent({ + name: 'TreeNode', + inheritAttrs: false, + props: treeNodeProps, + isTreeNode: 1, + slots: ['title', 'icon', 'switcherIcon'], + setup(props, { attrs, expose, slots }) { + const dragNodeHighlight = ref(false); + const context = useInjectTreeContext(); + const instance = getCurrentInstance(); + const selectHandle = ref(); + + const hasChildren = computed(() => { + const { eventKey } = props; + const { keyEntities } = context.value; + const { children } = keyEntities[eventKey] || {}; + + return !!(children || []).length; + }); + + const isLeaf = computed(() => { + const { isLeaf, loaded } = props; + const { loadData } = context.value; + + const has = hasChildren.value; + + if (isLeaf === false) { + return false; + } + + return isLeaf || (!loadData && !has) || (loadData && loaded && !has); + }); + const nodeState = computed(() => { + const { expanded } = props; + + if (isLeaf.value) { + return null; + } + + return expanded ? ICON_OPEN : ICON_CLOSE; + }); + + const isDisabled = computed(() => { + const { disabled } = props; + const { disabled: treeDisabled } = context.value; + + return !!(treeDisabled || disabled); + }); + + const isCheckable = computed(() => { + const { checkable } = props; + const { checkable: treeCheckable } = context.value; + + // Return false if tree or treeNode is not checkable + if (!treeCheckable || checkable === false) return false; + return treeCheckable; + }); + + const isSelectable = computed(() => { + const { selectable } = props; + const { selectable: treeSelectable } = context.value; + + // Ignore when selectable is undefined or null + if (typeof selectable === 'boolean') { + return selectable; + } + + return treeSelectable; + }); + + const onSelectorDoubleClick = (e: MouseEvent) => { + const { onNodeDoubleClick } = context.value; + onNodeDoubleClick(e, convertNodePropsToEventData(props)); + }; + + const onSelect = (e: MouseEvent) => { + if (isDisabled.value) return; + + const { onNodeSelect } = context.value; + e.preventDefault(); + onNodeSelect(e, convertNodePropsToEventData(props)); + }; + + const onCheck = (e: MouseEvent) => { + if (isDisabled.value) return; + + const { disableCheckbox, checked } = props; + const { onNodeCheck } = context.value; + + if (!isCheckable.value || disableCheckbox) return; + + e.preventDefault(); + const targetChecked = !checked; + onNodeCheck(e, convertNodePropsToEventData(props), targetChecked); + }; + + const onSelectorClick = (e: MouseEvent) => { + // Click trigger before select/check operation + const { onNodeClick } = context.value; + onNodeClick(e, convertNodePropsToEventData(props)); + + if (isSelectable.value) { + onSelect(e); + } else { + onCheck(e); + } + }; + + const onMouseEnter = (e: MouseEvent) => { + const { onNodeMouseEnter } = context.value; + onNodeMouseEnter(e, convertNodePropsToEventData(props)); + }; + + const onMouseLeave = (e: MouseEvent) => { + const { onNodeMouseLeave } = context.value; + onNodeMouseLeave(e, convertNodePropsToEventData(props)); + }; + + const onContextmenu = (e: MouseEvent) => { + const { onNodeContextMenu } = context.value; + onNodeContextMenu(e, convertNodePropsToEventData(props)); + }; + + const onDragStart = (e: DragEvent) => { + const { onNodeDragStart } = context.value; + + e.stopPropagation(); + dragNodeHighlight.value = true; + onNodeDragStart(e, instance.vnode); + + try { + // ie throw error + // firefox-need-it + e.dataTransfer.setData('text/plain', ''); + } catch (error) { + // empty + } + }; + + const onDragEnter = (e: DragEvent) => { + const { onNodeDragEnter } = context.value; + + e.preventDefault(); + e.stopPropagation(); + onNodeDragEnter(e, instance.vnode); + }; + + const onDragOver = (e: DragEvent) => { + const { onNodeDragOver } = context.value; + + e.preventDefault(); + e.stopPropagation(); + onNodeDragOver(e, instance.vnode); + }; + + const onDragLeave = (e: DragEvent) => { + const { onNodeDragLeave } = context.value; + + e.stopPropagation(); + onNodeDragLeave(e, instance.vnode); + }; + + const onDragEnd = (e: DragEvent) => { + const { onNodeDragEnd } = context.value; + + e.stopPropagation(); + dragNodeHighlight.value = false; + onNodeDragEnd(e, instance.vnode); + }; + + const onDrop = (e: DragEvent) => { + const { onNodeDrop } = context.value; + + e.preventDefault(); + e.stopPropagation(); + dragNodeHighlight.value = false; + onNodeDrop(e, instance.vnode); + }; + + // Disabled item still can be switch + const onExpand = e => { + const { onNodeExpand } = context.value; + if (props.loading) return; + onNodeExpand(e, convertNodePropsToEventData(props)); + }; + + const renderSwitcherIconDom = (isLeaf: boolean) => { + const { switcherIcon: switcherIconFromProps = slots.switcherIcon } = props; + const { switcherIcon: switcherIconFromCtx } = context.value; + + const switcherIcon = switcherIconFromProps || switcherIconFromCtx; + // if switcherIconDom is null, no render switcher span + if (typeof switcherIcon === 'function') { + return switcherIcon({ ...props, isLeaf }); + } + return switcherIcon; + }; + + // Load data to avoid default expanded tree without data + const syncLoadData = () => { + const { expanded, loading, loaded } = props; + const { loadData, onNodeLoad } = context.value; + + if (loading) { + return; + } + + // read from state to avoid loadData at same time + if (loadData && expanded && !isLeaf.value) { + // We needn't reload data when has children in sync logic + // It's only needed in node expanded + if (!hasChildren.value && !loaded) { + onNodeLoad(convertNodePropsToEventData(props)); + } + } + }; + + onMounted(() => { + syncLoadData(); + }); + onUpdated(() => { + syncLoadData(); + }); + + // Switcher + const renderSwitcher = () => { + const { expanded } = props; + const { prefixCls } = context.value; + + if (isLeaf.value) { + // if switcherIconDom is null, no render switcher span + const switcherIconDom = renderSwitcherIconDom(true); + + return switcherIconDom !== false ? ( + + {switcherIconDom} + + ) : null; + } + + const switcherCls = classNames( + `${prefixCls}-switcher`, + `${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`, + ); + + const switcherIconDom = renderSwitcherIconDom(false); + + return switcherIconDom !== false ? ( + + {switcherIconDom} + + ) : null; + }; + + // Checkbox + const renderCheckbox = () => { + const { checked, halfChecked, disableCheckbox } = props; + const { prefixCls } = context.value; + + const disabled = isDisabled.value; + const checkable = isCheckable.value; + + if (!checkable) return null; + + // [Legacy] Custom element should be separate with `checkable` in future + const $custom = typeof checkable !== 'boolean' ? checkable : null; + + return ( + + {$custom} + + ); + }; + + const renderIcon = () => { + const { loading } = props; + const { prefixCls } = context.value; + + return ( + + ); + }; + + const renderDropIndicator = () => { + const { disabled, eventKey } = props; + const { + draggable, + dropLevelOffset, + dropPosition, + prefixCls, + indent, + dropIndicatorRender, + dragOverNodeKey, + direction, + } = context.value; + const mergedDraggable = draggable !== false; + // allowDrop is calculated in Tree.tsx, there is no need for calc it here + const showIndicator = !disabled && mergedDraggable && dragOverNodeKey === eventKey; + return showIndicator + ? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls, direction }) + : null; + }; + + // Icon + Title + const renderSelector = () => { + const { title = slots.title, selected, icon = slots.icon, loading, data } = props; + const { + prefixCls, + showIcon, + icon: treeIcon, + draggable, + loadData, + titleRender, + } = context.value; + const disabled = isDisabled.value; + const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable; + + const wrapClass = `${prefixCls}-node-content-wrapper`; + + // Icon - Still show loading icon when loading without showIcon + let $icon; + + if (showIcon) { + const currentIcon = icon || treeIcon; + + $icon = currentIcon ? ( + + {typeof currentIcon === 'function' ? currentIcon(props) : currentIcon} + + ) : ( + renderIcon() + ); + } else if (loadData && loading) { + $icon = renderIcon(); + } + + // Title + let titleNode: any; + if (typeof title === 'function') { + titleNode = title(data); + } else if (titleRender) { + titleNode = titleRender(data); + } else { + titleNode = title === undefined ? defaultTitle : title; + } + + const $title = {titleNode}; + + return ( + + {$icon} + {$title} + {renderDropIndicator()} + + ); + }; + return () => { + const { + eventKey, + dragOver, + dragOverGapTop, + dragOverGapBottom, + isLeaf, + isStart, + isEnd, + expanded, + selected, + checked, + halfChecked, + loading, + domRef, + active, + data, + onMousemove, + ...otherProps + } = { ...props, ...attrs }; + const { prefixCls, filterTreeNode, draggable, keyEntities, dropContainerKey, dropTargetKey } = + context.value; + const disabled = isDisabled.value; + const dataOrAriaAttributeProps = getDataAndAria(otherProps); + const { level } = keyEntities[eventKey] || {}; + const isEndNode = isEnd[isEnd.length - 1]; + const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable; + return ( +
+ + {renderSwitcher()} + {renderCheckbox()} + {renderSelector()} +
+ ); + }; + }, +}); diff --git a/components/vc-tree/assets/icons.png b/components/vc-tree/assets/icons.png deleted file mode 100644 index ffda01ef1cccc398ee4e2327f4093ba1130a4961..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11173 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 X3wf1`XyoU;TL_tNiW1dgpn(4aGkUu% diff --git a/components/vc-tree/assets/index.less b/components/vc-tree/assets/index.less deleted file mode 100644 index 4dcb641db..000000000 --- a/components/vc-tree/assets/index.less +++ /dev/null @@ -1,194 +0,0 @@ -@treePrefixCls: rc-tree; -.@{treePrefixCls} { - margin: 0; - padding: 5px; - li { - padding: 0; - margin: 0; - list-style: none; - white-space: nowrap; - outline: 0; - .draggable { - color: #333; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; - user-select: none; - /* Required to make elements draggable in old WebKit */ - -khtml-user-drag: element; - -webkit-user-drag: element; - } - &.drag-over { - > .draggable { - background-color: #316ac5; - color: white; - border: 1px #316ac5 solid; - opacity: 0.8; - } - } - &.drag-over-gap-top { - > .draggable { - border-top: 2px blue solid; - } - } - &.drag-over-gap-bottom { - > .draggable { - border-bottom: 2px blue solid; - } - } - &.filter-node { - > .@{treePrefixCls}-node-content-wrapper { - color: #a60000 !important; - font-weight: bold !important; - } - } - ul { - margin: 0; - padding: 0 0 0 18px; - } - .@{treePrefixCls}-node-content-wrapper { - display: inline-block; - padding: 1px 3px 0 0; - margin: 0; - cursor: pointer; - height: 17px; - text-decoration: none; - vertical-align: top; - } - span { - &.@{treePrefixCls}-switcher, - &.@{treePrefixCls}-checkbox, - &.@{treePrefixCls}-iconEle { - line-height: 16px; - margin-right: 2px; - width: 16px; - height: 16px; - display: inline-block; - vertical-align: middle; - border: 0 none; - cursor: pointer; - outline: none; - background-color: transparent; - background-repeat: no-repeat; - background-attachment: scroll; - background-image: url('data:image/png;base64,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'); - - &.@{treePrefixCls}-icon__customize { - background-image: none; - } - } - &.@{treePrefixCls}-icon_loading { - margin-right: 2px; - vertical-align: top; - background: url('data:image/gif;base64,R0lGODlhEAAQAKIGAMLY8YSx5HOm4Mjc88/g9Ofw+v///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAGACwAAAAAEAAQAAADMGi6RbUwGjKIXCAA016PgRBElAVlG/RdLOO0X9nK61W39qvqiwz5Ls/rRqrggsdkAgAh+QQFCgAGACwCAAAABwAFAAADD2hqELAmiFBIYY4MAutdCQAh+QQFCgAGACwGAAAABwAFAAADD1hU1kaDOKMYCGAGEeYFCQAh+QQFCgAGACwKAAIABQAHAAADEFhUZjSkKdZqBQG0IELDQAIAIfkEBQoABgAsCgAGAAUABwAAAxBoVlRKgyjmlAIBqCDCzUoCACH5BAUKAAYALAYACgAHAAUAAAMPaGpFtYYMAgJgLogA610JACH5BAUKAAYALAIACgAHAAUAAAMPCAHWFiI4o1ghZZJB5i0JACH5BAUKAAYALAAABgAFAAcAAAMQCAFmIaEp1motpDQySMNFAgA7') - no-repeat scroll 0 0 transparent; - } - &.@{treePrefixCls}-switcher { - &.@{treePrefixCls}-switcher-noop { - cursor: auto; - } - &.@{treePrefixCls}-switcher_open { - background-position: -93px -56px; - } - &.@{treePrefixCls}-switcher_close { - background-position: -75px -56px; - } - } - &.@{treePrefixCls}-checkbox { - width: 13px; - height: 13px; - margin: 0 3px; - background-position: 0 0; - &-checked { - background-position: -14px 0; - } - &-indeterminate { - background-position: -14px -28px; - } - &-disabled { - background-position: 0 -56px; - } - &.@{treePrefixCls}-checkbox-checked.@{treePrefixCls}-checkbox-disabled { - background-position: -14px -56px; - } - &.@{treePrefixCls}-checkbox-indeterminate.@{treePrefixCls}-checkbox-disabled { - position: relative; - background: #ccc; - border-radius: 3px; - &::after { - content: ' '; - -webkit-transform: scale(1); - transform: scale(1); - position: absolute; - left: 3px; - top: 5px; - width: 5px; - height: 0; - border: 2px solid #fff; - border-top: 0; - border-left: 0; - } - } - } - } - } - &:not(.@{treePrefixCls}-show-line) { - .@{treePrefixCls}-switcher-noop { - background: none; - } - } - &.@{treePrefixCls}-show-line { - li:not(:last-child) { - > ul { - background: url('data:image/gif;base64,R0lGODlhCQACAIAAAMzMzP///yH5BAEAAAEALAAAAAAJAAIAAAIEjI9pUAA7') - 0 0 repeat-y; - } - > .@{treePrefixCls}-switcher-noop { - background-position: -56px -18px; - } - } - li:last-child { - > .@{treePrefixCls}-switcher-noop { - background-position: -56px -36px; - } - } - } - &-child-tree { - display: none; - &-open { - display: block; - } - } - &-treenode-disabled { - > span:not(.@{treePrefixCls}-switcher), - > a, - > a span { - color: #767676; - cursor: not-allowed; - } - } - &-node-selected { - background-color: #ffe6b0; - border: 1px #ffb951 solid; - opacity: 0.8; - } - &-icon__open { - margin-right: 2px; - background-position: -110px -16px; - vertical-align: top; - } - &-icon__close { - margin-right: 2px; - background-position: -110px 0; - vertical-align: top; - } - &-icon__docu { - margin-right: 2px; - background-position: -110px -32px; - vertical-align: top; - } - &-icon__customize { - margin-right: 2px; - vertical-align: top; - } -} diff --git a/components/vc-tree/assets/line.gif b/components/vc-tree/assets/line.gif deleted file mode 100644 index d561d36a915776730eb3069cee4c949f027667ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 xcmZ?wbhEHbT_)p@)n z{^qIIq1(T$e$~zMQ}6t1w^a|Cj;Amo3}FHq!p^`7G=x7ROJIXqM}S9S9}m;(SWbi* zHiVjpkZW$u7K%QH zEX1o> void; +export type NodeDragEventHandler = ( + e: MouseEvent, + node: NodeInstance, + outsideTree?: boolean, +) => void; + +export interface TreeContextProps { + prefixCls: string; + selectable: boolean; + showIcon: boolean; + icon: IconType; + switcherIcon: IconType; + draggable: ((node: DataNode) => boolean) | boolean; + checkable: boolean | VueNode; + checkStrictly: boolean; + disabled: boolean; + keyEntities: Record; + // for details see comment in Tree.state (Tree.tsx) + dropLevelOffset?: number; + dropContainerKey: Key | null; + dropTargetKey: Key | null; + dropPosition: -1 | 0 | 1 | null; + indent: number | null; + dropIndicatorRender: (props: { + dropPosition: -1 | 0 | 1; + dropLevelOffset: number; + indent: number | null; + prefixCls: string; + direction: Direction; + }) => VueNode; + dragOverNodeKey: Key | null; + direction: Direction; + + loadData: (treeNode: EventDataNode) => Promise; + filterTreeNode: (treeNode: EventDataNode) => boolean; + titleRender?: (node: DataNode) => VueNode; + + onNodeClick: NodeMouseEventHandler; + onNodeDoubleClick: NodeMouseEventHandler; + onNodeExpand: NodeMouseEventHandler; + onNodeSelect: NodeMouseEventHandler; + onNodeCheck: (e: MouseEvent, treeNode: EventDataNode, checked: boolean) => void; + onNodeLoad: (treeNode: EventDataNode) => void; + onNodeMouseEnter: NodeMouseEventHandler; + onNodeMouseLeave: NodeMouseEventHandler; + onNodeContextMenu: NodeMouseEventHandler; + onNodeDragStart: NodeDragEventHandler; + onNodeDragEnter: NodeDragEventHandler; + onNodeDragOver: NodeDragEventHandler; + onNodeDragLeave: NodeDragEventHandler; + onNodeDragEnd: NodeDragEventHandler; + onNodeDrop: NodeDragEventHandler; +} +const TreeContextKey: InjectionKey> = Symbol('TreeContextKey'); + +export const TreeContext = defineComponent({ + props: { + value: { type: Object as PropType }, + }, + setup(props, { slots }) { + provide( + TreeContextKey, + computed(() => props.value), + ); + return slots.default?.(); + }, +}); + +export const useInjectTreeContext = () => { + return inject( + TreeContextKey, + computed(() => ({} as TreeContextProps)), + ); +}; diff --git a/components/vc-tree/index.js b/components/vc-tree/index.js deleted file mode 100644 index 384396194..000000000 --- a/components/vc-tree/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// based on rc-tree 2.1.3 -import Tree from './src'; - -export default Tree; diff --git a/components/vc-tree/index.ts b/components/vc-tree/index.ts new file mode 100644 index 000000000..6df172093 --- /dev/null +++ b/components/vc-tree/index.ts @@ -0,0 +1,8 @@ +import Tree from './Tree'; +import TreeNode from './TreeNode'; +import type { TreeProps } from './Tree'; +import type { TreeNodeProps } from './TreeNode'; + +export { TreeNode }; +export type { TreeProps, TreeNodeProps }; +export default Tree; diff --git a/components/vc-tree/interface.tsx b/components/vc-tree/interface.tsx new file mode 100644 index 000000000..1d277f9d0 --- /dev/null +++ b/components/vc-tree/interface.tsx @@ -0,0 +1,87 @@ +import { VNode } from 'vue'; +export type { ScrollTo } from '../vc-virtual-list/List'; + +export interface DataNode { + checkable?: boolean; + children?: DataNode[]; + disabled?: boolean; + disableCheckbox?: boolean; + icon?: IconType; + isLeaf?: boolean; + key: string | number; + title?: any; + selectable?: boolean; + switcherIcon?: IconType; + + /** Set style of TreeNode. This is not recommend if you don't have any force requirement */ + // className?: string; + // style?: CSSProperties; +} + +export interface EventDataNode extends DataNode { + expanded: boolean; + selected: boolean; + checked: boolean; + loaded: boolean; + loading: boolean; + halfChecked: boolean; + dragOver: boolean; + dragOverGapTop: boolean; + dragOverGapBottom: boolean; + pos: string; + active: boolean; +} + +export type IconType = any; + +export type Key = string | number; + +export type NodeElement = VNode & { + selectHandle?: HTMLSpanElement; + type: { + isTreeNode: boolean; + }; +}; + +export type NodeInstance = VNode & { + selectHandle?: HTMLSpanElement; +}; + +export interface Entity { + node: NodeElement; + index: number; + key: Key; + pos: string; + parent?: Entity; + children?: Entity[]; +} + +export interface DataEntity extends Omit { + node: DataNode; + parent?: DataEntity; + children?: DataEntity[]; + level: number; +} + +export interface FlattenNode { + parent: FlattenNode | null; + children: FlattenNode[]; + pos: string; + data: DataNode; + title: any; + key: Key; + isStart: boolean[]; + isEnd: boolean[]; +} + +export type GetKey = (record: RecordType, index?: number) => Key; + +export type GetCheckDisabled = (record: RecordType) => boolean; + +export type Direction = 'ltr' | 'rtl' | undefined; + +export interface FieldNames { + title?: string; + key?: string; + children?: string; +} diff --git a/components/vc-tree/props.ts b/components/vc-tree/props.ts new file mode 100644 index 000000000..32513b4d0 --- /dev/null +++ b/components/vc-tree/props.ts @@ -0,0 +1,233 @@ +import type { ExtractPropTypes, PropType } from 'vue'; +import PropTypes from '../_util/vue-types'; +import type { + NodeDragEventParams, + NodeMouseEventHandler, + NodeMouseEventParams, +} from './contextTypes'; +import type { DataNode, Key, FlattenNode, DataEntity, EventDataNode, Direction } from './interface'; +import { fillFieldNames } from './utils/treeUtil'; + +export interface CheckInfo { + event: 'check'; + node: EventDataNode; + checked: boolean; + nativeEvent: MouseEvent; + checkedNodes: DataNode[]; + checkedNodesPositions?: { node: DataNode; pos: string }[]; + halfCheckedKeys?: Key[]; +} + +export const treeNodeProps = { + eventKey: [String, Number], // Pass by parent `cloneElement` + prefixCls: String, + + // By parent + expanded: { type: Boolean, default: undefined }, + selected: { type: Boolean, default: undefined }, + checked: { type: Boolean, default: undefined }, + loaded: { type: Boolean, default: undefined }, + loading: { type: Boolean, default: undefined }, + halfChecked: { type: Boolean, default: undefined }, + title: PropTypes.any, + dragOver: { type: Boolean, default: undefined }, + dragOverGapTop: { type: Boolean, default: undefined }, + dragOverGapBottom: { type: Boolean, default: undefined }, + pos: String, + // domRef: React.Ref, + /** New added in Tree for easy data access */ + data: { type: Object as PropType }, + isStart: { type: Array as PropType }, + isEnd: { type: Array as PropType }, + active: { type: Boolean, default: undefined }, + onMousemove: { type: Function as PropType }, + + // By user + isLeaf: { type: Boolean, default: undefined }, + checkable: { type: Boolean, default: undefined }, + selectable: { type: Boolean, default: undefined }, + disabled: { type: Boolean, default: undefined }, + disableCheckbox: { type: Boolean, default: undefined }, + icon: PropTypes.any, + switcherIcon: PropTypes.any, + domRef: { type: Function as PropType<(arg: any) => void> }, +}; + +export type TreeNodeProps = Partial>; + +export const nodeListProps = { + prefixCls: { type: String as PropType }, + data: { type: Array as PropType }, + motion: { type: Object as PropType }, + focusable: { type: Boolean as PropType }, + activeItem: { type: Object as PropType }, + focused: { type: Boolean as PropType }, + tabindex: { type: Number as PropType }, + checkable: { type: Boolean as PropType }, + selectable: { type: Boolean as PropType }, + disabled: { type: Boolean as PropType }, + + expandedKeys: { type: Array as PropType }, + selectedKeys: { type: Array as PropType }, + checkedKeys: { type: Array as PropType }, + loadedKeys: { type: Array as PropType }, + loadingKeys: { type: Array as PropType }, + halfCheckedKeys: { type: Array as PropType }, + keyEntities: { type: Object as PropType> }, + + dragging: { type: Boolean as PropType }, + dragOverNodeKey: { type: [String, Number] as PropType }, + dropPosition: { type: Number as PropType }, + + // Virtual list + height: { type: Number as PropType }, + itemHeight: { type: Number as PropType }, + virtual: { type: Boolean as PropType }, + + onKeydown: { type: Function as PropType }, + onFocus: { type: Function as PropType<(e: FocusEvent) => void> }, + onBlur: { type: Function as PropType<(e: FocusEvent) => void> }, + onActiveChange: { type: Function as PropType<(key: Key) => void> }, + onContextmenu: { type: Function as PropType }, + + onListChangeStart: { type: Function as PropType<() => void> }, + onListChangeEnd: { type: Function as PropType<() => void> }, +}; + +export type NodeListProps = Partial>; +export type AllowDrop = (options: { dropNode: DataNode; dropPosition: -1 | 0 | 1 }) => boolean; + +export const treeProps = () => ({ + prefixCls: String, + focusable: { type: Boolean, default: undefined }, + tabindex: Number, + children: PropTypes.VNodeChild, + treeData: { type: Array as PropType }, // Generate treeNode by children + fieldNames: fillFieldNames, + showLine: { type: Boolean, default: undefined }, + showIcon: { type: Boolean, default: undefined }, + icon: PropTypes.any, + selectable: { type: Boolean, default: undefined }, + disabled: { type: Boolean, default: undefined }, + multiple: { type: Boolean, default: undefined }, + checkable: { type: Boolean, default: undefined }, + checkStrictly: { type: Boolean, default: undefined }, + draggable: { type: [Function, Boolean] as PropType<((node: DataNode) => boolean) | boolean> }, + defaultExpandParent: { type: Boolean, default: undefined }, + autoExpandParent: { type: Boolean, default: undefined }, + defaultExpandAll: { type: Boolean, default: undefined }, + defaultExpandedKeys: { type: Array as PropType }, + expandedKeys: { type: Array as PropType }, + defaultCheckedKeys: { type: Array as PropType }, + checkedKeys: { + type: [Object, Array] as PropType, + }, + defaultSelectedKeys: { type: Array as PropType }, + selectedKeys: { type: Array as PropType }, + allowDrop: { type: Function as PropType }, + titleRender: { type: Function as PropType<(node: DataNode) => any> }, + dropIndicatorRender: { + type: Function as PropType< + (props: { + dropPosition: -1 | 0 | 1; + dropLevelOffset: number; + indent: number; + prefixCls: string; + direction: Direction; + }) => any + >, + }, + onFocus: { type: Function as PropType<(e: FocusEvent) => void> }, + onBlur: { type: Function as PropType<(e: FocusEvent) => void> }, + onKeyDown: { type: Function as PropType }, + onContextmenu: { type: Function as PropType }, + onClick: { type: Function as PropType }, + onDblClick: { type: Function as PropType }, + onScroll: { type: Function as PropType }, + onExpand: { + type: Function as PropType< + ( + expandedKeys: Key[], + info: { + node: EventDataNode; + expanded: boolean; + nativeEvent: MouseEvent; + }, + ) => void + >, + }, + onCheck: { + type: Function as PropType< + (checked: { checked: Key[]; halfChecked: Key[] } | Key[], info: CheckInfo) => void + >, + }, + onSelect: { + type: Function as PropType< + ( + selectedKeys: Key[], + info: { + event: 'select'; + selected: boolean; + node: EventDataNode; + selectedNodes: DataNode[]; + nativeEvent: MouseEvent; + }, + ) => void + >, + }, + onLoad: { + type: Function as PropType< + ( + loadedKeys: Key[], + info: { + event: 'load'; + node: EventDataNode; + }, + ) => void + >, + }, + loadData: { type: Function as PropType<(treeNode: EventDataNode) => Promise> }, + loadedKeys: { type: Array as PropType }, + onMouseenter: { type: Function as PropType<(info: NodeMouseEventParams) => void> }, + onMouseleave: { type: Function as PropType<(info: NodeMouseEventParams) => void> }, + onRightClick: { + type: Function as PropType<(info: { event: MouseEvent; node: EventDataNode }) => void>, + }, + onDragstart: { type: Function as PropType<(info: NodeDragEventParams) => void> }, + onDragenter: { + type: Function as PropType<(info: NodeDragEventParams & { expandedKeys: Key[] }) => void>, + }, + onDragover: { type: Function as PropType<(info: NodeDragEventParams) => void> }, + onDragleave: { type: Function as PropType<(info: NodeDragEventParams) => void> }, + onDragend: { type: Function as PropType<(info: NodeDragEventParams) => void> }, + onDrop: { + type: Function as PropType< + ( + info: NodeDragEventParams & { + dragNode: EventDataNode; + dragNodesKeys: Key[]; + dropPosition: number; + dropToGap: boolean; + }, + ) => void + >, + }, + /** + * Used for `rc-tree-select` only. + * Do not use in your production code directly since this will be refactor. + */ + onActiveChange: { type: Function as PropType<(key: Key) => void> }, + filterTreeNode: { type: Function as PropType<(treeNode: EventDataNode) => boolean> }, + motion: PropTypes.any, + switcherIcon: PropTypes.any, + + // Virtual List + height: Number, + itemHeight: Number, + virtual: { type: Boolean, default: undefined }, + + // direction for drag logic + direction: { type: String as PropType }, +}); + +export type TreeProps = Partial>>; diff --git a/components/vc-tree/src/Tree.jsx b/components/vc-tree/src/Tree.jsx deleted file mode 100644 index 2085c138a..000000000 --- a/components/vc-tree/src/Tree.jsx +++ /dev/null @@ -1,686 +0,0 @@ -import PropTypes, { withUndefined } from '../../_util/vue-types'; -import classNames from '../../_util/classNames'; -import warning from 'warning'; -import { hasProp, initDefaultProps, getOptionProps, getSlot } from '../../_util/props-util'; -import { cloneElement } from '../../_util/vnode'; -import BaseMixin from '../../_util/BaseMixin'; -import { - convertTreeToEntities, - convertDataToTree, - getPosition, - getDragNodesKeys, - parseCheckedKeys, - conductExpandParent, - calcSelectedKeys, - calcDropPosition, - arrAdd, - arrDel, - posToArr, - mapChildren, - conductCheck, - warnOnlyTreeNode, - getDataAndAria, -} from './util'; -import { defineComponent } from 'vue'; - -/** - * Thought we still use `cloneElement` to pass `key`, - * other props can pass with context for future refactor. - */ - -function getWatch(keys = []) { - const watch = {}; - keys.forEach(k => { - watch[k] = { - handler() { - this.needSyncKeys[k] = true; - }, - flush: 'sync', - }; - }); - return watch; -} - -const Tree = defineComponent({ - name: 'Tree', - mixins: [BaseMixin], - provide() { - return { - vcTree: this, - }; - }, - inheritAttrs: false, - props: initDefaultProps( - { - prefixCls: PropTypes.string, - tabindex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - children: PropTypes.any, - treeData: PropTypes.array, // Generate treeNode by children - showLine: PropTypes.looseBool, - showIcon: PropTypes.looseBool, - icon: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - focusable: PropTypes.looseBool, - selectable: PropTypes.looseBool, - disabled: PropTypes.looseBool, - multiple: PropTypes.looseBool, - checkable: withUndefined(PropTypes.oneOfType([PropTypes.object, PropTypes.looseBool])), - checkStrictly: PropTypes.looseBool, - draggable: PropTypes.looseBool, - defaultExpandParent: PropTypes.looseBool, - autoExpandParent: PropTypes.looseBool, - defaultExpandAll: PropTypes.looseBool, - defaultExpandedKeys: PropTypes.array, - expandedKeys: PropTypes.array, - defaultCheckedKeys: PropTypes.array, - checkedKeys: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - defaultSelectedKeys: PropTypes.array, - selectedKeys: PropTypes.array, - // onClick: PropTypes.func, - // onDoubleClick: PropTypes.func, - // onExpand: PropTypes.func, - // onCheck: PropTypes.func, - // onSelect: PropTypes.func, - loadData: PropTypes.func, - loadedKeys: PropTypes.array, - // onMouseEnter: PropTypes.func, - // onMouseLeave: PropTypes.func, - // onRightClick: PropTypes.func, - // onDragStart: PropTypes.func, - // onDragEnter: PropTypes.func, - // onDragOver: PropTypes.func, - // onDragLeave: PropTypes.func, - // onDragEnd: PropTypes.func, - // onDrop: PropTypes.func, - filterTreeNode: PropTypes.func, - openTransitionName: PropTypes.string, - openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - switcherIcon: PropTypes.any, - __propsSymbol__: PropTypes.any, - }, - { - prefixCls: 'rc-tree', - showLine: false, - showIcon: true, - selectable: true, - multiple: false, - checkable: false, - disabled: false, - checkStrictly: false, - draggable: false, - defaultExpandParent: true, - autoExpandParent: false, - defaultExpandAll: false, - defaultExpandedKeys: [], - defaultCheckedKeys: [], - defaultSelectedKeys: [], - }, - ), - - data() { - warning(this.$props.__propsSymbol__, 'must pass __propsSymbol__'); - warning(this.$props.children, 'please use children prop replace slots.default'); - this.needSyncKeys = {}; - this.domTreeNodes = {}; - const state = { - _posEntities: new Map(), - _keyEntities: new Map(), - _expandedKeys: [], - _selectedKeys: [], - _checkedKeys: [], - _halfCheckedKeys: [], - _loadedKeys: [], - _loadingKeys: [], - _treeNode: [], - _prevProps: null, - _dragOverNodeKey: '', - _dropPosition: null, - _dragNodesKeys: [], - }; - return { - ...state, - ...this.getDerivedState(getOptionProps(this), state), - }; - }, - - watch: { - // watch 引用类型的改变 - ...getWatch([ - 'treeData', - 'children', - 'expandedKeys', - 'autoExpandParent', - 'selectedKeys', - 'checkedKeys', - 'loadedKeys', - ]), - __propsSymbol__() { - this.setState(this.getDerivedState(getOptionProps(this), this.$data)); - this.needSyncKeys = {}; - }, - }, - - methods: { - getDerivedState(props, prevState) { - const { _prevProps } = prevState; - const newState = { - _prevProps: { ...props }, - }; - const self = this; - function needSync(name) { - return (!_prevProps && name in props) || (_prevProps && self.needSyncKeys[name]); - } - - // ================== Tree Node ================== - let treeNode = null; - - // Check if `treeData` or `children` changed and save into the state. - if (needSync('treeData')) { - treeNode = convertDataToTree(props.treeData); - } else if (needSync('children')) { - treeNode = props.children; - } - - // Tree support filter function which will break the tree structure in the vdm. - // We cache the treeNodes in state so that we can return the treeNode in event trigger. - if (treeNode) { - newState._treeNode = treeNode; - - // Calculate the entities data for quick match - const entitiesMap = convertTreeToEntities(treeNode); - newState._keyEntities = entitiesMap.keyEntities; - } - - const keyEntities = newState._keyEntities || prevState._keyEntities; - - // ================ expandedKeys ================= - if (needSync('expandedKeys') || (_prevProps && needSync('autoExpandParent'))) { - newState._expandedKeys = - props.autoExpandParent || (!_prevProps && props.defaultExpandParent) - ? conductExpandParent(props.expandedKeys, keyEntities) - : props.expandedKeys; - } else if (!_prevProps && props.defaultExpandAll) { - newState._expandedKeys = [...keyEntities.keys()]; - } else if (!_prevProps && props.defaultExpandedKeys) { - newState._expandedKeys = - props.autoExpandParent || props.defaultExpandParent - ? conductExpandParent(props.defaultExpandedKeys, keyEntities) - : props.defaultExpandedKeys; - } - - // ================ selectedKeys ================= - if (props.selectable) { - if (needSync('selectedKeys')) { - newState._selectedKeys = calcSelectedKeys(props.selectedKeys, props); - } else if (!_prevProps && props.defaultSelectedKeys) { - newState._selectedKeys = calcSelectedKeys(props.defaultSelectedKeys, props); - } - } - - // ================= checkedKeys ================= - if (props.checkable) { - let checkedKeyEntity; - - if (needSync('checkedKeys')) { - checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || {}; - } else if (!_prevProps && props.defaultCheckedKeys) { - checkedKeyEntity = parseCheckedKeys(props.defaultCheckedKeys) || {}; - } else if (treeNode) { - // If treeNode changed, we also need check it - checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || { - checkedKeys: prevState._checkedKeys, - halfCheckedKeys: prevState._halfCheckedKeys, - }; - } - - if (checkedKeyEntity) { - let { checkedKeys = [], halfCheckedKeys = [] } = checkedKeyEntity; - - if (!props.checkStrictly) { - const conductKeys = conductCheck(checkedKeys, true, keyEntities); - ({ checkedKeys, halfCheckedKeys } = conductKeys); - } - - newState._checkedKeys = checkedKeys; - newState._halfCheckedKeys = halfCheckedKeys; - } - } - // ================= loadedKeys ================== - if (needSync('loadedKeys')) { - newState._loadedKeys = props.loadedKeys; - } - - return newState; - }, - onNodeDragStart(event, node) { - const { _expandedKeys } = this.$data; - const { eventKey } = node; - const children = getSlot(node); - this.dragNode = node; - - this.setState({ - _dragNodesKeys: getDragNodesKeys( - typeof children === 'function' ? children() : children, - node, - ), - _expandedKeys: arrDel(_expandedKeys, eventKey), - }); - this.__emit('dragstart', { event, node }); - }, - - /** - * [Legacy] Select handler is less small than node, - * so that this will trigger when drag enter node or select handler. - * This is a little tricky if customize css without padding. - * Better for use mouse move event to refresh drag state. - * But let's just keep it to avoid event trigger logic change. - */ - onNodeDragEnter(event, node) { - const { _expandedKeys: expandedKeys } = this.$data; - const { pos, eventKey } = node; - - if (!this.dragNode || !node.selectHandle) return; - - const dropPosition = calcDropPosition(event, node); - - // Skip if drag node is self - if (this.dragNode.eventKey === eventKey && dropPosition === 0) { - this.setState({ - _dragOverNodeKey: '', - _dropPosition: null, - }); - return; - } - - // Ref: https://github.com/react-component/tree/issues/132 - // Add timeout to let onDragLevel fire before onDragEnter, - // so that we can clean drag props for onDragLeave node. - // Macro task for this: - // https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script - setTimeout(() => { - // Update drag over node - this.setState({ - _dragOverNodeKey: eventKey, - _dropPosition: dropPosition, - }); - - // Side effect for delay drag - if (!this.delayedDragEnterLogic) { - this.delayedDragEnterLogic = {}; - } - Object.keys(this.delayedDragEnterLogic).forEach(key => { - clearTimeout(this.delayedDragEnterLogic[key]); - }); - this.delayedDragEnterLogic[pos] = setTimeout(() => { - const newExpandedKeys = arrAdd(expandedKeys, eventKey); - if (!hasProp(this, 'expandedKeys')) { - this.setState({ - _expandedKeys: newExpandedKeys, - }); - } - this.__emit('dragenter', { event, node, expandedKeys: newExpandedKeys }); - }, 400); - }, 0); - }, - onNodeDragOver(event, node) { - const { eventKey } = node; - const { _dragOverNodeKey, _dropPosition } = this.$data; - // Update drag position - if (this.dragNode && eventKey === _dragOverNodeKey && node.selectHandle) { - const dropPosition = calcDropPosition(event, node); - - if (dropPosition === _dropPosition) return; - - this.setState({ - _dropPosition: dropPosition, - }); - } - this.__emit('dragover', { event, node }); - }, - onNodeDragLeave(event, node) { - this.setState({ - _dragOverNodeKey: '', - }); - this.__emit('dragleave', { event, node }); - }, - onNodeDragEnd(event, node) { - this.setState({ - _dragOverNodeKey: '', - }); - this.__emit('dragend', { event, node }); - this.dragNode = null; - }, - onNodeDrop(event, node) { - const { _dragNodesKeys = [], _dropPosition } = this.$data; - - const { eventKey, pos } = node; - - this.setState({ - _dragOverNodeKey: '', - }); - - if (_dragNodesKeys.indexOf(eventKey) !== -1) { - warning(false, "Can not drop to dragNode(include it's children node)"); - return; - } - - const posArr = posToArr(pos); - - const dropResult = { - event, - node, - dragNode: this.dragNode, - dragNodesKeys: _dragNodesKeys.slice(), - dropPosition: _dropPosition + Number(posArr[posArr.length - 1]), - dropToGap: false, - }; - - if (_dropPosition !== 0) { - dropResult.dropToGap = true; - } - this.__emit('drop', dropResult); - this.dragNode = null; - }, - - onNodeClick(e, treeNode) { - this.__emit('click', e, treeNode); - }, - - onNodeDoubleClick(e, treeNode) { - this.__emit('dblclick', e, treeNode); - }, - - onNodeSelect(e, treeNode) { - let { _selectedKeys: selectedKeys } = this.$data; - const { _keyEntities: keyEntities } = this.$data; - const { multiple } = this.$props; - const { selected, eventKey } = getOptionProps(treeNode); - const targetSelected = !selected; - // Update selected keys - if (!targetSelected) { - selectedKeys = arrDel(selectedKeys, eventKey); - } else if (!multiple) { - selectedKeys = [eventKey]; - } else { - selectedKeys = arrAdd(selectedKeys, eventKey); - } - - // [Legacy] Not found related usage in doc or upper libs - const selectedNodes = selectedKeys - .map(key => { - const entity = keyEntities.get(key); - if (!entity) return null; - - return entity.node; - }) - .filter(node => node); - - this.setUncontrolledState({ _selectedKeys: selectedKeys }); - - const eventObj = { - event: 'select', - selected: targetSelected, - node: treeNode, - selectedNodes, - nativeEvent: e, - }; - this.__emit('select', selectedKeys, eventObj); - }, - onNodeCheck(e, treeNode, checked) { - const { - _keyEntities: keyEntities, - _checkedKeys: oriCheckedKeys, - _halfCheckedKeys: oriHalfCheckedKeys, - } = this.$data; - const { checkStrictly } = this.$props; - const { eventKey } = getOptionProps(treeNode); - - // Prepare trigger arguments - let checkedObj; - const eventObj = { - event: 'check', - node: treeNode, - checked, - nativeEvent: e, - }; - - if (checkStrictly) { - const checkedKeys = checked - ? arrAdd(oriCheckedKeys, eventKey) - : arrDel(oriCheckedKeys, eventKey); - const halfCheckedKeys = arrDel(oriHalfCheckedKeys, eventKey); - checkedObj = { checked: checkedKeys, halfChecked: halfCheckedKeys }; - - eventObj.checkedNodes = checkedKeys - .map(key => keyEntities.get(key)) - .filter(entity => entity) - .map(entity => entity.node); - - this.setUncontrolledState({ _checkedKeys: checkedKeys }); - } else { - const { checkedKeys, halfCheckedKeys } = conductCheck([eventKey], checked, keyEntities, { - checkedKeys: oriCheckedKeys, - halfCheckedKeys: oriHalfCheckedKeys, - }); - - checkedObj = checkedKeys; - - // [Legacy] This is used for `rc-tree-select` - eventObj.checkedNodes = []; - eventObj.checkedNodesPositions = []; - eventObj.halfCheckedKeys = halfCheckedKeys; - - checkedKeys.forEach(key => { - const entity = keyEntities.get(key); - if (!entity) return; - - const { node, pos } = entity; - - eventObj.checkedNodes.push(node); - eventObj.checkedNodesPositions.push({ node, pos }); - }); - - this.setUncontrolledState({ - _checkedKeys: checkedKeys, - _halfCheckedKeys: halfCheckedKeys, - }); - } - this.__emit('check', checkedObj, eventObj); - }, - onNodeLoad(treeNode) { - return new Promise(resolve => { - // We need to get the latest state of loading/loaded keys - this.setState(({ _loadedKeys: loadedKeys = [], _loadingKeys: loadingKeys = [] }) => { - const { loadData } = this.$props; - const { eventKey } = getOptionProps(treeNode); - - if ( - !loadData || - loadedKeys.indexOf(eventKey) !== -1 || - loadingKeys.indexOf(eventKey) !== -1 - ) { - return {}; - } - - // Process load data - const promise = loadData(treeNode); - promise.then(() => { - const { _loadedKeys: currentLoadedKeys, _loadingKeys: currentLoadingKeys } = this.$data; - const newLoadedKeys = arrAdd(currentLoadedKeys, eventKey); - const newLoadingKeys = arrDel(currentLoadingKeys, eventKey); - - // onLoad should trigger before internal setState to avoid `loadData` trigger twice. - // https://github.com/ant-design/ant-design/issues/12464 - this.__emit('load', newLoadedKeys, { - event: 'load', - node: treeNode, - }); - this.setUncontrolledState({ - _loadedKeys: newLoadedKeys, - }); - this.setState({ - _loadingKeys: newLoadingKeys, - }); - resolve(); - }); - - return { - _loadingKeys: arrAdd(loadingKeys, eventKey), - }; - }); - }); - }, - - onNodeExpand(e, treeNode) { - let { _expandedKeys: expandedKeys } = this.$data; - const { loadData } = this.$props; - const { eventKey, expanded } = getOptionProps(treeNode); - - // Update selected keys - const index = expandedKeys.indexOf(eventKey); - const targetExpanded = !expanded; - - warning( - (expanded && index !== -1) || (!expanded && index === -1), - 'Expand state not sync with index check', - ); - - if (targetExpanded) { - expandedKeys = arrAdd(expandedKeys, eventKey); - } else { - expandedKeys = arrDel(expandedKeys, eventKey); - } - - this.setUncontrolledState({ _expandedKeys: expandedKeys }); - this.__emit('expand', expandedKeys, { - node: treeNode, - expanded: targetExpanded, - nativeEvent: e, - }); - - // Async Load data - if (targetExpanded && loadData) { - const loadPromise = this.onNodeLoad(treeNode); - return loadPromise - ? loadPromise.then(() => { - // [Legacy] Refresh logic - this.setUncontrolledState({ _expandedKeys: expandedKeys }); - }) - : null; - } - - return null; - }, - - onNodeMouseEnter(event, node) { - this.__emit('mouseenter', { event, node }); - }, - - onNodeMouseLeave(event, node) { - this.__emit('mouseleave', { event, node }); - }, - - onNodeContextMenu(event, node) { - event.preventDefault(); - this.__emit('rightClick', { event, node }); - }, - - /** - * Only update the value which is not in props - */ - setUncontrolledState(state) { - let needSync = false; - const newState = {}; - const props = getOptionProps(this); - Object.keys(state).forEach(name => { - if (name.replace('_', '') in props) return; - needSync = true; - newState[name] = state[name]; - }); - - if (needSync) { - this.setState(newState); - } - }, - - registerTreeNode(key, node) { - if (node) { - this.domTreeNodes[key] = node; - } else { - delete this.domTreeNodes[key]; - } - }, - - isKeyChecked(key) { - const { _checkedKeys: checkedKeys = [] } = this.$data; - return checkedKeys.indexOf(key) !== -1; - }, - - /** - * [Legacy] Original logic use `key` as tracking clue. - * We have to use `cloneElement` to pass `key`. - */ - renderTreeNode(child, index, level = 0) { - const { - _keyEntities: keyEntities, - _expandedKeys: expandedKeys = [], - _selectedKeys: selectedKeys = [], - _halfCheckedKeys: halfCheckedKeys = [], - _loadedKeys: loadedKeys = [], - _loadingKeys: loadingKeys = [], - _dragOverNodeKey: dragOverNodeKey, - _dropPosition: dropPosition, - } = this.$data; - const pos = getPosition(level, index); - let key = child.key; - if (!key && (key === undefined || key === null)) { - key = pos; - } - if (!keyEntities.get(key)) { - warnOnlyTreeNode(); - return null; - } - - return cloneElement(child, { - eventKey: key, - expanded: expandedKeys.indexOf(key) !== -1, - selected: selectedKeys.indexOf(key) !== -1, - loaded: loadedKeys.indexOf(key) !== -1, - loading: loadingKeys.indexOf(key) !== -1, - checked: this.isKeyChecked(key), - halfChecked: halfCheckedKeys.indexOf(key) !== -1, - pos, - - // [Legacy] Drag props - dragOver: dragOverNodeKey === key && dropPosition === 0, - dragOverGapTop: dragOverNodeKey === key && dropPosition === -1, - dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1, - key, - }); - }, - }, - - render() { - const { _treeNode: treeNode } = this.$data; - const { prefixCls, focusable, showLine, tabindex = 0 } = this.$props; - const domProps = getDataAndAria({ ...this.$props, ...this.$attrs }); - const { class: className, style } = this.$attrs; - return ( -
    - {mapChildren(treeNode, (node, index) => this.renderTreeNode(node, index))} -
- ); - }, -}); - -export { Tree }; - -export default Tree; diff --git a/components/vc-tree/src/TreeNode.jsx b/components/vc-tree/src/TreeNode.jsx deleted file mode 100644 index aee09368e..000000000 --- a/components/vc-tree/src/TreeNode.jsx +++ /dev/null @@ -1,578 +0,0 @@ -import { defineComponent, inject, provide } from 'vue'; -import PropTypes from '../../_util/vue-types'; -import classNames from '../../_util/classNames'; -import { getNodeChildren, mapChildren, warnOnlyTreeNode, getDataAndAria } from './util'; -import { initDefaultProps, getComponent, getSlot } from '../../_util/props-util'; -import BaseMixin from '../../_util/BaseMixin'; -import { getTransitionProps, Transition } from '../../_util/transition'; - -function noop() {} -const ICON_OPEN = 'open'; -const ICON_CLOSE = 'close'; - -const defaultTitle = '---'; - -const TreeNode = defineComponent({ - name: 'TreeNode', - mixins: [BaseMixin], - inheritAttrs: false, - __ANT_TREE_NODE: true, - props: initDefaultProps( - { - eventKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // Pass by parent `cloneElement` - prefixCls: PropTypes.string, - // className: PropTypes.string, - root: PropTypes.object, - // onSelect: PropTypes.func, - - // By parent - expanded: PropTypes.looseBool, - selected: PropTypes.looseBool, - checked: PropTypes.looseBool, - loaded: PropTypes.looseBool, - loading: PropTypes.looseBool, - halfChecked: PropTypes.looseBool, - title: PropTypes.any, - pos: PropTypes.string, - dragOver: PropTypes.looseBool, - dragOverGapTop: PropTypes.looseBool, - dragOverGapBottom: PropTypes.looseBool, - - // By user - isLeaf: PropTypes.looseBool, - checkable: PropTypes.looseBool, - selectable: PropTypes.looseBool, - disabled: PropTypes.looseBool, - disableCheckbox: PropTypes.looseBool, - icon: PropTypes.any, - dataRef: PropTypes.object, - switcherIcon: PropTypes.any, - label: PropTypes.any, - value: PropTypes.any, - }, - {}, - ), - setup() { - return { - vcTree: inject('vcTree', {}), - vcTreeNode: inject('vcTreeNode', {}), - }; - }, - - data() { - this.children = null; - return { - dragNodeHighlight: false, - }; - }, - created() { - provide('vcTreeNode', this); - }, - // Isomorphic needn't load data in server side - mounted() { - const { - eventKey, - vcTree: { registerTreeNode }, - } = this; - this.syncLoadData(this.$props); - registerTreeNode && registerTreeNode(eventKey, this); - }, - updated() { - this.syncLoadData(this.$props); - }, - beforeUnmount() { - const { - eventKey, - vcTree: { registerTreeNode }, - } = this; - registerTreeNode && registerTreeNode(eventKey, null); - }, - - methods: { - onSelectorClick(e) { - // Click trigger before select/check operation - const { - vcTree: { onNodeClick }, - } = this; - onNodeClick(e, this); - if (this.isSelectable()) { - this.onSelect(e); - } else { - this.onCheck(e); - } - }, - - onSelectorDoubleClick(e) { - const { - vcTree: { onNodeDoubleClick }, - } = this; - onNodeDoubleClick(e, this); - }, - - onSelect(e) { - if (this.isDisabled()) return; - - const { - vcTree: { onNodeSelect }, - } = this; - e.preventDefault(); - onNodeSelect(e, this); - }, - - onCheck(e) { - if (this.isDisabled()) return; - - const { disableCheckbox, checked } = this; - const { - vcTree: { onNodeCheck }, - } = this; - - if (!this.isCheckable() || disableCheckbox) return; - - e.preventDefault(); - const targetChecked = !checked; - onNodeCheck(e, this, targetChecked); - }, - - onMouseEnter(e) { - const { - vcTree: { onNodeMouseEnter }, - } = this; - onNodeMouseEnter(e, this); - }, - - onMouseLeave(e) { - const { - vcTree: { onNodeMouseLeave }, - } = this; - onNodeMouseLeave(e, this); - }, - - onContextMenu(e) { - const { - vcTree: { onNodeContextMenu }, - } = this; - onNodeContextMenu(e, this); - }, - - onDragStart(e) { - const { - vcTree: { onNodeDragStart }, - } = this; - - e.stopPropagation(); - this.setState({ - dragNodeHighlight: true, - }); - onNodeDragStart(e, this); - - try { - // ie throw error - // firefox-need-it - e.dataTransfer.setData('text/plain', ''); - } catch (error) { - // empty - } - }, - - onDragEnter(e) { - const { - vcTree: { onNodeDragEnter }, - } = this; - - e.preventDefault(); - e.stopPropagation(); - onNodeDragEnter(e, this); - }, - - onDragOver(e) { - const { - vcTree: { onNodeDragOver }, - } = this; - - e.preventDefault(); - e.stopPropagation(); - onNodeDragOver(e, this); - }, - - onDragLeave(e) { - const { - vcTree: { onNodeDragLeave }, - } = this; - - e.stopPropagation(); - onNodeDragLeave(e, this); - }, - - onDragEnd(e) { - const { - vcTree: { onNodeDragEnd }, - } = this; - - e.stopPropagation(); - this.setState({ - dragNodeHighlight: false, - }); - onNodeDragEnd(e, this); - }, - - onDrop(e) { - const { - vcTree: { onNodeDrop }, - } = this; - - e.preventDefault(); - e.stopPropagation(); - this.setState({ - dragNodeHighlight: false, - }); - onNodeDrop(e, this); - }, - - // Disabled item still can be switch - onExpand(e) { - const { - vcTree: { onNodeExpand }, - } = this; - onNodeExpand(e, this); - }, - // Drag usage - setSelectHandle(node) { - this.selectHandle = node; - }, - - getNodeChildren() { - const originList = this.children; - const targetList = getNodeChildren(originList); - - if (originList.length !== targetList.length) { - warnOnlyTreeNode(); - } - - return targetList; - }, - - getNodeState() { - const { expanded } = this; - - if (this.isLeaf2()) { - return null; - } - - return expanded ? ICON_OPEN : ICON_CLOSE; - }, - - isLeaf2() { - const { isLeaf, loaded } = this; - const { - vcTree: { loadData }, - } = this; - - const hasChildren = this.getNodeChildren().length !== 0; - if (isLeaf === false) { - return false; - } - return isLeaf || (!loadData && !hasChildren) || (loadData && loaded && !hasChildren); - }, - - isDisabled() { - const { disabled } = this; - const { - vcTree: { disabled: treeDisabled }, - } = this; - - // Follow the logic of Selectable - if (disabled === false) { - return false; - } - - return !!(treeDisabled || disabled); - }, - - isCheckable() { - const { checkable } = this.$props; - const { - vcTree: { checkable: treeCheckable }, - } = this; - - // Return false if tree or treeNode is not checkable - if (!treeCheckable || checkable === false) return false; - return treeCheckable; - }, - - // Load data to avoid default expanded tree without data - syncLoadData(props) { - const { expanded, loading, loaded } = props; - const { - vcTree: { loadData, onNodeLoad }, - } = this; - if (loading) return; - // read from state to avoid loadData at same time - if (loadData && expanded && !this.isLeaf2()) { - // We needn't reload data when has children in sync logic - // It's only needed in node expanded - const hasChildren = this.getNodeChildren().length !== 0; - if (!hasChildren && !loaded) { - onNodeLoad(this); - } - } - }, - - isSelectable() { - const { selectable } = this; - const { - vcTree: { selectable: treeSelectable }, - } = this; - - // Ignore when selectable is undefined or null - if (typeof selectable === 'boolean') { - return selectable; - } - - return treeSelectable; - }, - - // Switcher - renderSwitcher() { - const { expanded } = this; - const { - vcTree: { prefixCls }, - } = this; - const switcherIcon = - getComponent(this, 'switcherIcon', {}, false) || - getComponent(this.vcTree, 'switcherIcon', {}, false); - if (this.isLeaf2()) { - return ( - - {typeof switcherIcon === 'function' - ? switcherIcon({ ...this.$props, ...this.$props.dataRef, isLeaf: true }) - : switcherIcon} - - ); - } - - const switcherCls = classNames( - `${prefixCls}-switcher`, - `${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`, - ); - return ( - - {typeof switcherIcon === 'function' - ? switcherIcon({ ...this.$props, ...this.$props.dataRef, isLeaf: false }) - : switcherIcon} - - ); - }, - - // Checkbox - renderCheckbox() { - const { checked, halfChecked, disableCheckbox } = this; - const { - vcTree: { prefixCls }, - } = this; - const disabled = this.isDisabled(); - const checkable = this.isCheckable(); - - if (!checkable) return null; - - // [Legacy] Custom element should be separate with `checkable` in future - const $custom = typeof checkable !== 'boolean' ? checkable : null; - - return ( - - {$custom} - - ); - }, - - renderIcon() { - const { loading } = this; - const { - vcTree: { prefixCls }, - } = this; - - return ( - - ); - }, - - // Icon + Title - renderSelector() { - const { selected, loading, dragNodeHighlight } = this; - const icon = getComponent(this, 'icon', {}, false); - const { - vcTree: { prefixCls, showIcon, icon: treeIcon, draggable, loadData }, - } = this; - const disabled = this.isDisabled(); - const title = getComponent(this, 'title', {}, false); - const wrapClass = `${prefixCls}-node-content-wrapper`; - - // Icon - Still show loading icon when loading without showIcon - let $icon; - - if (showIcon) { - const currentIcon = icon || treeIcon; - $icon = currentIcon ? ( - - {typeof currentIcon === 'function' - ? currentIcon({ ...this.$props, ...this.$props.dataRef }) - : currentIcon} - - ) : ( - this.renderIcon() - ); - } else if (loadData && loading) { - $icon = this.renderIcon(); - } - - const currentTitle = title; - let $title = currentTitle ? ( - - {typeof currentTitle === 'function' - ? currentTitle({ ...this.$props, ...this.$props.dataRef }) - : currentTitle} - - ) : ( - {defaultTitle} - ); - return ( - - {$icon} - {$title} - - ); - }, - - // Children list wrapped with `Animation` - renderChildren() { - const { expanded, pos } = this; - const { - vcTree: { prefixCls, openTransitionName, openAnimation, renderTreeNode }, - } = this; - - let animProps = {}; - if (openTransitionName) { - animProps = getTransitionProps(openTransitionName); - } else if (typeof openAnimation === 'object') { - animProps = { ...openAnimation, css: false, ...animProps }; - } - - // Children TreeNode - const nodeList = this.getNodeChildren(); - - if (nodeList.length === 0) { - return null; - } - - let $children; - if (expanded) { - $children = ( -
    - {mapChildren(nodeList, (node, index) => renderTreeNode(node, index, pos))} -
- ); - } - - return {$children}; - }, - }, - - render() { - this.children = getSlot(this); - const { - dragOver, - dragOverGapTop, - dragOverGapBottom, - isLeaf, - expanded, - selected, - checked, - halfChecked, - loading, - } = this.$props; - const { - vcTree: { prefixCls, filterTreeNode, draggable }, - } = this; - const disabled = this.isDisabled(); - const dataOrAriaAttributeProps = getDataAndAria({ ...this.$props, ...this.$attrs }); - const { class: className, style } = this.$attrs; - return ( -
  • - {this.renderSwitcher()} - {this.renderCheckbox()} - {this.renderSelector()} - {this.renderChildren()} -
  • - ); - }, -}); - -TreeNode.isTreeNode = 1; - -export default TreeNode; diff --git a/components/vc-tree/src/index.js b/components/vc-tree/src/index.js deleted file mode 100644 index a37c0d201..000000000 --- a/components/vc-tree/src/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import Tree from './Tree'; -import TreeNode from './TreeNode'; -Tree.TreeNode = TreeNode; - -export default Tree; diff --git a/components/vc-tree/src/util.js b/components/vc-tree/src/util.js deleted file mode 100644 index ed5a5e307..000000000 --- a/components/vc-tree/src/util.js +++ /dev/null @@ -1,426 +0,0 @@ -/* eslint no-loop-func: 0*/ -import warning from 'warning'; -import TreeNode from './TreeNode'; -import { getOptionProps, getSlot } from '../../_util/props-util'; -const DRAG_SIDE_RANGE = 0.25; -const DRAG_MIN_GAP = 2; - -let onlyTreeNodeWarned = false; - -export function warnOnlyTreeNode() { - if (onlyTreeNodeWarned) return; - - onlyTreeNodeWarned = true; - warning(false, 'Tree only accept TreeNode as children.'); -} - -export function arrDel(list, value) { - const clone = list.slice(); - const index = clone.indexOf(value); - if (index >= 0) { - clone.splice(index, 1); - } - return clone; -} - -export function arrAdd(list, value) { - const clone = list.slice(); - if (clone.indexOf(value) === -1) { - clone.push(value); - } - return clone; -} - -export function posToArr(pos) { - return pos.split('-'); -} - -export function getPosition(level, index) { - return `${level}-${index}`; -} - -export function isTreeNode(node) { - return node.type && node.type.isTreeNode; -} - -export function getNodeChildren(children = []) { - return children.filter(isTreeNode); -} - -export function isCheckDisabled(node) { - const { disabled, disableCheckbox, checkable } = getOptionProps(node) || {}; - return !!(disabled || disableCheckbox) || checkable === false; -} - -export function traverseTreeNodes(treeNodes, callback) { - function processNode(node, index, parent) { - const children = node ? getSlot(node) : treeNodes; - const pos = node ? getPosition(parent.pos, index) : 0; - - // Filter children - const childList = getNodeChildren(children); - - // Process node if is not root - if (node) { - let key = node.key; - if (!key && (key === undefined || key === null)) { - key = pos; - } - const data = { - node, - index, - pos, - key, - parentPos: parent.node ? parent.pos : null, - }; - callback(data); - } - - // Process children node - childList.forEach((subNode, subIndex) => { - processNode(subNode, subIndex, { node, pos }); - }); - } - - processNode(null); -} - -/** - * Use `rc-util` `toArray` to get the children list which keeps the key. - * And return single node if children is only one(This can avoid `key` missing check). - */ -export function mapChildren(children = [], func) { - const list = children.map(func); - if (list.length === 1) { - return list[0]; - } - return list; -} - -export function getDragNodesKeys(treeNodes, node) { - const { eventKey, pos } = getOptionProps(node); - const dragNodesKeys = []; - - traverseTreeNodes(treeNodes, ({ key }) => { - dragNodesKeys.push(key); - }); - dragNodesKeys.push(eventKey || pos); - return dragNodesKeys; -} - -export function calcDropPosition(event, treeNode) { - const { clientY } = event; - const { top, bottom, height } = treeNode.selectHandle.getBoundingClientRect(); - const des = Math.max(height * DRAG_SIDE_RANGE, DRAG_MIN_GAP); - - if (clientY <= top + des) { - return -1; - } - if (clientY >= bottom - des) { - return 1; - } - return 0; -} - -/** - * Return selectedKeys according with multiple prop - * @param selectedKeys - * @param props - * @returns [string] - */ -export function calcSelectedKeys(selectedKeys, props) { - if (!selectedKeys) { - return undefined; - } - - const { multiple } = props; - if (multiple) { - return selectedKeys.slice(); - } - - if (selectedKeys.length) { - return [selectedKeys[0]]; - } - return selectedKeys; -} - -/** - * Since React internal will convert key to string, - * we need do this to avoid `checkStrictly` use number match - */ -// function keyListToString (keyList) { -// if (!keyList) return keyList -// return keyList.map(key => String(key)) -// } - -const internalProcessProps = (props = {}) => { - return { - ...props, - class: props.class || props.className, - style: props.style, - key: props.key, - }; -}; -export function convertDataToTree(treeData, processor) { - if (!treeData) return []; - - const { processProps = internalProcessProps } = processor || {}; - const list = Array.isArray(treeData) ? treeData : [treeData]; - return list.map(({ children, ...props }) => { - const childrenNodes = convertDataToTree(children, processor); - return {childrenNodes}; - }); -} - -// TODO: ========================= NEW LOGIC ========================= -/** - * Calculate treeNodes entities. `processTreeEntity` is used for `rc-tree-select` - * @param treeNodes - * @param processTreeEntity User can customize the entity - */ -export function convertTreeToEntities( - treeNodes, - { initWrapper, processEntity, onProcessFinished } = {}, -) { - const posEntities = new Map(); - const keyEntities = new Map(); - let wrapper = { - posEntities, - keyEntities, - }; - - if (initWrapper) { - wrapper = initWrapper(wrapper) || wrapper; - } - - traverseTreeNodes(treeNodes, item => { - const { node, index, pos, key, parentPos } = item; - const entity = { node, index, key, pos }; - - posEntities.set(pos, entity); - keyEntities.set(key, entity); - - // Fill children - entity.parent = posEntities.get(parentPos); - if (entity.parent) { - entity.parent.children = entity.parent.children || []; - entity.parent.children.push(entity); - } - - if (processEntity) { - processEntity(entity, wrapper); - } - }); - - if (onProcessFinished) { - onProcessFinished(wrapper); - } - - return wrapper; -} - -/** - * Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style - */ -export function parseCheckedKeys(keys) { - if (!keys) { - return null; - } - - // Convert keys to object format - let keyProps; - if (Array.isArray(keys)) { - // [Legacy] Follow the api doc - keyProps = { - checkedKeys: keys, - halfCheckedKeys: undefined, - }; - } else if (typeof keys === 'object') { - keyProps = { - checkedKeys: keys.checked || undefined, - halfCheckedKeys: keys.halfChecked || undefined, - }; - } else { - warning(false, '`checkedKeys` is not an array or an object'); - return null; - } - - // keyProps.checkedKeys = keyListToString(keyProps.checkedKeys) - // keyProps.halfCheckedKeys = keyListToString(keyProps.halfCheckedKeys) - - return keyProps; -} - -/** - * Conduct check state by the keyList. It will conduct up & from the provided key. - * If the conduct path reach the disabled or already checked / unchecked node will stop conduct. - * @param keyList list of keys - * @param isCheck is check the node or not - * @param keyEntities parsed by `convertTreeToEntities` function in Tree - * @param checkStatus Can pass current checked status for process (usually for uncheck operation) - * @returns {{checkedKeys: [], halfCheckedKeys: []}} - */ -export function conductCheck(keyList, isCheck, keyEntities, checkStatus = {}) { - const checkedKeys = new Map(); - const halfCheckedKeys = new Map(); // Record the key has some child checked (include child half checked) - - (checkStatus.checkedKeys || []).forEach(key => { - checkedKeys.set(key, true); - }); - - (checkStatus.halfCheckedKeys || []).forEach(key => { - halfCheckedKeys.set(key, true); - }); - - // Conduct up - function conductUp(key) { - if (checkedKeys.get(key) === isCheck) return; - - const entity = keyEntities.get(key); - if (!entity) return; - - const { children, parent, node } = entity; - - if (isCheckDisabled(node)) return; - - // Check child node checked status - let everyChildChecked = true; - let someChildChecked = false; // Child checked or half checked - - (children || []) - .filter(child => !isCheckDisabled(child.node)) - .forEach(({ key: childKey }) => { - const childChecked = checkedKeys.get(childKey); - const childHalfChecked = halfCheckedKeys.get(childKey); - - if (childChecked || childHalfChecked) someChildChecked = true; - if (!childChecked) everyChildChecked = false; - }); - - // Update checked status - if (isCheck) { - checkedKeys.set(key, everyChildChecked); - } else { - checkedKeys.set(key, false); - } - halfCheckedKeys.set(key, someChildChecked); - - if (parent) { - conductUp(parent.key); - } - } - - // Conduct down - function conductDown(key) { - if (checkedKeys.get(key) === isCheck) return; - - const entity = keyEntities.get(key); - if (!entity) return; - - const { children, node } = entity; - - if (isCheckDisabled(node)) return; - - checkedKeys.set(key, isCheck); - - (children || []).forEach(child => { - conductDown(child.key); - }); - } - - function conduct(key) { - const entity = keyEntities.get(key); - - if (!entity) { - warning(false, `'${key}' does not exist in the tree.`); - return; - } - const { children, parent, node } = entity; - checkedKeys.set(key, isCheck); - - if (isCheckDisabled(node)) return; - - // Conduct down - (children || []) - .filter(child => !isCheckDisabled(child.node)) - .forEach(child => { - conductDown(child.key); - }); - - // Conduct up - if (parent) { - conductUp(parent.key); - } - } - - (keyList || []).forEach(key => { - conduct(key); - }); - - const checkedKeyList = []; - const halfCheckedKeyList = []; - - // Fill checked list - for (const [key, value] of checkedKeys) { - if (value) { - checkedKeyList.push(key); - } - } - - // Fill half checked list - for (const [key, value] of halfCheckedKeys) { - if (!checkedKeys.get(key) && value) { - halfCheckedKeyList.push(key); - } - } - - return { - checkedKeys: checkedKeyList, - halfCheckedKeys: halfCheckedKeyList, - }; -} - -/** - * If user use `autoExpandParent` we should get the list of parent node - * @param keyList - * @param keyEntities - */ -export function conductExpandParent(keyList, keyEntities) { - const expandedKeys = new Map(); - - function conductUp(key) { - if (expandedKeys.get(key)) return; - - const entity = keyEntities.get(key); - if (!entity) return; - - expandedKeys.set(key, true); - - const { parent, node } = entity; - const props = getOptionProps(node); - if (props && props.disabled) return; - - if (parent) { - conductUp(parent.key); - } - } - - (keyList || []).forEach(key => { - conductUp(key); - }); - - return [...expandedKeys.keys()]; -} - -/** - * Returns only the data- and aria- key/value pairs - * @param {object} props - */ -export function getDataAndAria(props) { - return Object.keys(props).reduce((prev, key) => { - if (key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-') { - prev[key] = props[key]; - } - return prev; - }, {}); -} diff --git a/components/vc-tree/util.tsx b/components/vc-tree/util.tsx new file mode 100644 index 000000000..5fb761803 --- /dev/null +++ b/components/vc-tree/util.tsx @@ -0,0 +1,353 @@ +/* eslint-disable no-lonely-if */ +/** + * Legacy code. Should avoid to use if you are new to import these code. + */ + +import TreeNode from './TreeNode'; +import { + NodeElement, + Key, + DataNode, + DataEntity, + NodeInstance, + FlattenNode, + Direction, +} from './interface'; +import { warning } from '../vc-util/warning'; +import { AllowDrop, TreeNodeProps, TreeProps } from './props'; + +export function arrDel(list: Key[], value: Key) { + const clone = list.slice(); + const index = clone.indexOf(value); + if (index >= 0) { + clone.splice(index, 1); + } + return clone; +} + +export function arrAdd(list: Key[], value: Key) { + const clone = list.slice(); + if (clone.indexOf(value) === -1) { + clone.push(value); + } + return clone; +} + +export function posToArr(pos: string) { + return pos.split('-'); +} + +export function getPosition(level: string | number, index: number) { + return `${level}-${index}`; +} + +export function isTreeNode(node: NodeElement) { + return node && node.type && node.type.isTreeNode; +} + +export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record): Key[] { + // not contains self + // self for left or right drag + const dragChildrenKeys = []; + + const entity = keyEntities[dragNodeKey]; + function dig(list: DataEntity[] = []) { + list.forEach(({ key, children }) => { + dragChildrenKeys.push(key); + dig(children); + }); + } + + dig(entity.children); + + return dragChildrenKeys; +} + +export function isLastChild(treeNodeEntity: DataEntity) { + if (treeNodeEntity.parent) { + const posArr = posToArr(treeNodeEntity.pos); + return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1; + } + return false; +} + +export function isFirstChild(treeNodeEntity: DataEntity) { + const posArr = posToArr(treeNodeEntity.pos); + return Number(posArr[posArr.length - 1]) === 0; +} + +// Only used when drag, not affect SSR. +export function calcDropPosition( + event: MouseEvent, + _dragNode: NodeInstance, + targetNode: NodeInstance, + indent: number, + startMousePosition: { + x: number; + y: number; + }, + allowDrop: AllowDrop, + flattenedNodes: FlattenNode[], + keyEntities: Record, + expandKeys: Key[], + direction: Direction, +): { + dropPosition: -1 | 0 | 1; + dropLevelOffset: number; + dropTargetKey: Key; + dropTargetPos: string; + dropContainerKey: Key; + dragOverNodeKey: Key; + dropAllowed: boolean; +} { + const { clientX, clientY } = event; + const { top, height } = (event.target as HTMLElement).getBoundingClientRect(); + // optional chain for testing + const horizontalMouseOffset = + (direction === 'rtl' ? -1 : 1) * ((startMousePosition?.x || 0) - clientX); + const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent; + + // find abstract drop node by horizontal offset + let abstractDropNodeEntity: DataEntity = keyEntities[targetNode.props.eventKey]; + + if (clientY < top + height / 2) { + // first half, set abstract drop node to previous node + const nodeIndex = flattenedNodes.findIndex( + flattenedNode => flattenedNode.data.key === abstractDropNodeEntity.key, + ); + const prevNodeIndex = nodeIndex <= 0 ? 0 : nodeIndex - 1; + const prevNodeKey = flattenedNodes[prevNodeIndex].data.key; + abstractDropNodeEntity = keyEntities[prevNodeKey]; + } + + const initialAbstractDropNodeKey = abstractDropNodeEntity.key; + + const abstractDragOverEntity = abstractDropNodeEntity; + const dragOverNodeKey = abstractDropNodeEntity.key; + + let dropPosition: -1 | 0 | 1 = 0; + let dropLevelOffset = 0; + + // Only allow cross level drop when dragging on a non-expanded node + if (!expandKeys.includes(initialAbstractDropNodeKey)) { + for (let i = 0; i < rawDropLevelOffset; i += 1) { + if (isLastChild(abstractDropNodeEntity)) { + abstractDropNodeEntity = abstractDropNodeEntity.parent; + dropLevelOffset += 1; + } else { + break; + } + } + } + + const abstractDropDataNode = abstractDropNodeEntity.node; + let dropAllowed = true; + if ( + isFirstChild(abstractDropNodeEntity) && + abstractDropNodeEntity.level === 0 && + clientY < top + height / 2 && + allowDrop({ + dropNode: abstractDropDataNode, + dropPosition: -1, + }) && + abstractDropNodeEntity.key === targetNode.props.eventKey + ) { + // first half of first node in first level + dropPosition = -1; + } else if ( + (abstractDragOverEntity.children || []).length && + expandKeys.includes(dragOverNodeKey) + ) { + // drop on expanded node + // only allow drop inside + if ( + allowDrop({ + dropNode: abstractDropDataNode, + dropPosition: 0, + }) + ) { + dropPosition = 0; + } else { + dropAllowed = false; + } + } else if (dropLevelOffset === 0) { + if (rawDropLevelOffset > -1.5) { + // | Node | <- abstractDropNode + // | -^-===== | <- mousePosition + // 1. try drop after + // 2. do not allow drop + if ( + allowDrop({ + dropNode: abstractDropDataNode, + dropPosition: 1, + }) + ) { + dropPosition = 1; + } else { + dropAllowed = false; + } + } else { + // | Node | <- abstractDropNode + // | ---==^== | <- mousePosition + // whether it has children or doesn't has children + // always + // 1. try drop inside + // 2. try drop after + // 3. do not allow drop + if ( + allowDrop({ + dropNode: abstractDropDataNode, + dropPosition: 0, + }) + ) { + dropPosition = 0; + } else if ( + allowDrop({ + dropNode: abstractDropDataNode, + dropPosition: 1, + }) + ) { + dropPosition = 1; + } else { + dropAllowed = false; + } + } + } else { + // | Node1 | <- abstractDropNode + // | Node2 | + // --^--|----=====| <- mousePosition + // 1. try insert after Node1 + // 2. do not allow drop + if ( + allowDrop({ + dropNode: abstractDropDataNode, + dropPosition: 1, + }) + ) { + dropPosition = 1; + } else { + dropAllowed = false; + } + } + + return { + dropPosition, + dropLevelOffset, + dropTargetKey: abstractDropNodeEntity.key, + dropTargetPos: abstractDropNodeEntity.pos, + dragOverNodeKey, + dropContainerKey: dropPosition === 0 ? null : abstractDropNodeEntity.parent?.key || null, + dropAllowed, + }; +} + +/** + * Return selectedKeys according with multiple prop + * @param selectedKeys + * @param props + * @returns [string] + */ +export function calcSelectedKeys(selectedKeys: Key[], props: TreeProps) { + if (!selectedKeys) return undefined; + + const { multiple } = props; + if (multiple) { + return selectedKeys.slice(); + } + + if (selectedKeys.length) { + return [selectedKeys[0]]; + } + return selectedKeys; +} + +const internalProcessProps = (props: DataNode): Partial => props; +export function convertDataToTree( + treeData: DataNode[], + processor?: { processProps: (prop: DataNode) => any }, +): NodeElement[] { + if (!treeData) return []; + + const { processProps = internalProcessProps } = processor || {}; + const list = Array.isArray(treeData) ? treeData : [treeData]; + return list.map(({ children, ...props }): NodeElement => { + const childrenNodes = convertDataToTree(children, processor); + + return {childrenNodes}; + }); +} + +/** + * Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style + */ +export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Key[] }) { + if (!keys) { + return null; + } + + // Convert keys to object format + let keyProps; + if (Array.isArray(keys)) { + // [Legacy] Follow the api doc + keyProps = { + checkedKeys: keys, + halfCheckedKeys: undefined, + }; + } else if (typeof keys === 'object') { + keyProps = { + checkedKeys: keys.checked || undefined, + halfCheckedKeys: keys.halfChecked || undefined, + }; + } else { + warning(false, '`checkedKeys` is not an array or an object'); + return null; + } + + return keyProps; +} + +/** + * If user use `autoExpandParent` we should get the list of parent node + * @param keyList + * @param keyEntities + */ +export function conductExpandParent(keyList: Key[], keyEntities: Record): Key[] { + const expandedKeys = new Set(); + + function conductUp(key: Key) { + if (expandedKeys.has(key)) return; + + const entity = keyEntities[key]; + if (!entity) return; + + expandedKeys.add(key); + + const { parent, node } = entity; + + if (node.disabled) return; + + if (parent) { + conductUp(parent.key); + } + } + + (keyList || []).forEach(key => { + conductUp(key); + }); + + return [...expandedKeys]; +} + +/** + * Returns only the data- and aria- key/value pairs + */ +export function getDataAndAria(props: Partial) { + const omitProps: Record = {}; + Object.keys(props).forEach(key => { + if (key.startsWith('data-') || key.startsWith('aria-')) { + omitProps[key] = props[key]; + } + }); + + return omitProps; +} diff --git a/components/vc-tree/utils/conductUtil.ts b/components/vc-tree/utils/conductUtil.ts new file mode 100644 index 000000000..1eff1685e --- /dev/null +++ b/components/vc-tree/utils/conductUtil.ts @@ -0,0 +1,252 @@ +import { warning } from '../../vc-util/warning'; +import type { Key, DataEntity, DataNode, GetCheckDisabled } from '../interface'; + +interface ConductReturnType { + checkedKeys: Key[]; + halfCheckedKeys: Key[]; +} + +function removeFromCheckedKeys(halfCheckedKeys: Set, checkedKeys: Set) { + const filteredKeys = new Set(); + halfCheckedKeys.forEach(key => { + if (!checkedKeys.has(key)) { + filteredKeys.add(key); + } + }); + return filteredKeys; +} + +export function isCheckDisabled(node: DataNode) { + const { disabled, disableCheckbox, checkable } = (node || {}) as DataNode; + return !!(disabled || disableCheckbox) || checkable === false; +} + +// Fill miss keys +function fillConductCheck( + keys: Set, + levelEntities: Map>, + maxLevel: number, + syntheticGetCheckDisabled: GetCheckDisabled, +): ConductReturnType { + const checkedKeys = new Set(keys); + const halfCheckedKeys = new Set(); + + // Add checked keys top to bottom + for (let level = 0; level <= maxLevel; level += 1) { + const entities = levelEntities.get(level) || new Set(); + entities.forEach(entity => { + const { key, node, children = [] } = entity; + + if (checkedKeys.has(key) && !syntheticGetCheckDisabled(node)) { + children + .filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)) + .forEach(childEntity => { + checkedKeys.add(childEntity.key); + }); + } + }); + } + + // Add checked keys from bottom to top + const visitedKeys = new Set(); + for (let level = maxLevel; level >= 0; level -= 1) { + const entities = levelEntities.get(level) || new Set(); + entities.forEach(entity => { + const { parent, node } = entity; + + // Skip if no need to check + if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) { + return; + } + + // Skip if parent is disabled + if (syntheticGetCheckDisabled(entity.parent.node)) { + visitedKeys.add(parent.key); + return; + } + + let allChecked = true; + let partialChecked = false; + + (parent.children || []) + .filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)) + .forEach(({ key }) => { + const checked = checkedKeys.has(key); + if (allChecked && !checked) { + allChecked = false; + } + if (!partialChecked && (checked || halfCheckedKeys.has(key))) { + partialChecked = true; + } + }); + + if (allChecked) { + checkedKeys.add(parent.key); + } + if (partialChecked) { + halfCheckedKeys.add(parent.key); + } + + visitedKeys.add(parent.key); + }); + } + + return { + checkedKeys: Array.from(checkedKeys), + halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys)), + }; +} + +// Remove useless key +function cleanConductCheck( + keys: Set, + halfKeys: Key[], + levelEntities: Map>, + maxLevel: number, + syntheticGetCheckDisabled: GetCheckDisabled, +): ConductReturnType { + const checkedKeys = new Set(keys); + let halfCheckedKeys = new Set(halfKeys); + + // Remove checked keys from top to bottom + for (let level = 0; level <= maxLevel; level += 1) { + const entities = levelEntities.get(level) || new Set(); + entities.forEach(entity => { + const { key, node, children = [] } = entity; + + if (!checkedKeys.has(key) && !halfCheckedKeys.has(key) && !syntheticGetCheckDisabled(node)) { + children + .filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)) + .forEach(childEntity => { + checkedKeys.delete(childEntity.key); + }); + } + }); + } + + // Remove checked keys form bottom to top + halfCheckedKeys = new Set(); + const visitedKeys = new Set(); + for (let level = maxLevel; level >= 0; level -= 1) { + const entities = levelEntities.get(level) || new Set(); + + entities.forEach(entity => { + const { parent, node } = entity; + + // Skip if no need to check + if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) { + return; + } + + // Skip if parent is disabled + if (syntheticGetCheckDisabled(entity.parent.node)) { + visitedKeys.add(parent.key); + return; + } + + let allChecked = true; + let partialChecked = false; + + (parent.children || []) + .filter(childEntity => !syntheticGetCheckDisabled(childEntity.node)) + .forEach(({ key }) => { + const checked = checkedKeys.has(key); + if (allChecked && !checked) { + allChecked = false; + } + if (!partialChecked && (checked || halfCheckedKeys.has(key))) { + partialChecked = true; + } + }); + + if (!allChecked) { + checkedKeys.delete(parent.key); + } + if (partialChecked) { + halfCheckedKeys.add(parent.key); + } + + visitedKeys.add(parent.key); + }); + } + + return { + checkedKeys: Array.from(checkedKeys), + halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys)), + }; +} + +/** + * Conduct with keys. + * @param keyList current key list + * @param keyEntities key - dataEntity map + * @param mode `fill` to fill missing key, `clean` to remove useless key + */ +export function conductCheck( + keyList: Key[], + checked: true | { checked: false; halfCheckedKeys: Key[] }, + keyEntities: Record, + getCheckDisabled?: GetCheckDisabled, +): ConductReturnType { + const warningMissKeys: Key[] = []; + + let syntheticGetCheckDisabled: GetCheckDisabled; + if (getCheckDisabled) { + syntheticGetCheckDisabled = getCheckDisabled; + } else { + syntheticGetCheckDisabled = isCheckDisabled; + } + + // We only handle exist keys + const keys = new Set( + keyList.filter(key => { + const hasEntity = !!keyEntities[key]; + if (!hasEntity) { + warningMissKeys.push(key); + } + + return hasEntity; + }), + ); + const levelEntities = new Map>(); + let maxLevel = 0; + + // Convert entities by level for calculation + Object.keys(keyEntities).forEach(key => { + const entity = keyEntities[key]; + const { level } = entity; + + let levelSet: Set = levelEntities.get(level); + if (!levelSet) { + levelSet = new Set(); + levelEntities.set(level, levelSet); + } + + levelSet.add(entity); + + maxLevel = Math.max(maxLevel, level); + }); + + warning( + !warningMissKeys.length, + `Tree missing follow keys: ${warningMissKeys + .slice(0, 100) + .map(key => `'${key}'`) + .join(', ')}`, + ); + + let result: ConductReturnType; + if (checked === true) { + result = fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled); + } else { + result = cleanConductCheck( + keys, + checked.halfCheckedKeys, + levelEntities, + maxLevel, + syntheticGetCheckDisabled, + ); + } + + return result; +} diff --git a/components/vc-tree/utils/diffUtil.ts b/components/vc-tree/utils/diffUtil.ts new file mode 100644 index 000000000..1c4f5e891 --- /dev/null +++ b/components/vc-tree/utils/diffUtil.ts @@ -0,0 +1,45 @@ +import type { Key, FlattenNode } from '../interface'; + +export function findExpandedKeys(prev: Key[] = [], next: Key[] = []) { + const prevLen = prev.length; + const nextLen = next.length; + + if (Math.abs(prevLen - nextLen) !== 1) { + return { add: false, key: null }; + } + + function find(shorter: Key[], longer: Key[]) { + const cache: Map = new Map(); + shorter.forEach(key => { + cache.set(key, true); + }); + + const keys = longer.filter(key => !cache.has(key)); + + return keys.length === 1 ? keys[0] : null; + } + + if (prevLen < nextLen) { + return { + add: true, + key: find(prev, next), + }; + } + + return { + add: false, + key: find(next, prev), + }; +} + +export function getExpandRange(shorter: FlattenNode[], longer: FlattenNode[], key: Key) { + const shorterStartIndex = shorter.findIndex(({ data }) => data.key === key); + const shorterEndNode = shorter[shorterStartIndex + 1]; + const longerStartIndex = longer.findIndex(({ data }) => data.key === key); + + if (shorterEndNode) { + const longerEndIndex = longer.findIndex(({ data }) => data.key === shorterEndNode.data.key); + return longer.slice(longerStartIndex + 1, longerEndIndex); + } + return longer.slice(longerStartIndex + 1); +} diff --git a/components/vc-tree/utils/treeUtil.ts b/components/vc-tree/utils/treeUtil.ts new file mode 100644 index 000000000..a077d2a59 --- /dev/null +++ b/components/vc-tree/utils/treeUtil.ts @@ -0,0 +1,411 @@ +import type { + DataNode, + FlattenNode, + NodeElement, + DataEntity, + Key, + EventDataNode, + GetKey, + FieldNames, +} from '../interface'; +import { getPosition, isTreeNode } from '../util'; +import { warning } from '../../vc-util/warning'; +import Omit from 'omit.js'; +import type { VNodeChild } from 'vue'; +import type { TreeNodeProps } from '../props'; + +export function getKey(key: Key, pos: string) { + if (key !== null && key !== undefined) { + return key; + } + return pos; +} + +export function fillFieldNames(fieldNames?: FieldNames) { + const { title, key, children } = fieldNames || {}; + + return { + title: title || 'title', + key: key || 'key', + children: children || 'children', + }; +} + +/** + * Warning if TreeNode do not provides key + */ +export function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames) { + const keys: Map = new Map(); + + function dig(list: DataNode[], path = '') { + (list || []).forEach(treeNode => { + const key = treeNode[fieldNames.key]; + const children = treeNode[fieldNames.children]; + warning( + key !== null && key !== undefined, + `Tree node must have a certain key: [${path}${key}]`, + ); + + const recordKey = String(key); + warning( + !keys.has(recordKey) || key === null || key === undefined, + `Same 'key' exist in the Tree: ${recordKey}`, + ); + keys.set(recordKey, true); + + dig(children, `${path}${recordKey} > `); + }); + } + + dig(treeData); +} + +/** + * Convert `children` of Tree into `treeData` structure. + */ +export function convertTreeToData(rootNodes: VNodeChild): DataNode[] { + function dig(node: VNodeChild): DataNode[] { + const treeNodes = node as NodeElement[]; + return treeNodes + .map(treeNode => { + // Filter invalidate node + if (!isTreeNode(treeNode)) { + warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.'); + return null; + } + + const key = treeNode.key as string | number; + const { children, ...rest } = treeNode.props; + + const dataNode: DataNode = { + ...rest, + key, + }; + + const parsedChildren = dig(children); + if (parsedChildren.length) { + dataNode.children = parsedChildren; + } + + return dataNode; + }) + .filter((dataNode: DataNode) => dataNode); + } + + return dig(rootNodes); +} + +/** + * Flat nest tree data into flatten list. This is used for virtual list render. + * @param treeNodeList Origin data node list + * @param expandedKeys + * need expanded keys, provides `true` means all expanded (used in `rc-tree-select`). + */ +export function flattenTreeData( + treeNodeList: DataNode[], + expandedKeys: Key[] | true, + fieldNames: FieldNames, +): FlattenNode[] { + const { title: fieldTitle, key: fieldKey, children: fieldChildren } = fillFieldNames(fieldNames); + + const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys); + const flattenList: FlattenNode[] = []; + + function dig(list: DataNode[], parent: FlattenNode = null): FlattenNode[] { + return list.map((treeNode, index) => { + const pos: string = getPosition(parent ? parent.pos : '0', index); + const mergedKey = getKey(treeNode[fieldKey], pos); + + // Add FlattenDataNode into list + const flattenNode: FlattenNode = { + ...Omit(treeNode, [fieldTitle, fieldKey, fieldChildren] as any), + title: treeNode[fieldTitle], + key: mergedKey, + parent, + pos, + children: null, + data: treeNode, + isStart: [...(parent ? parent.isStart : []), index === 0], + isEnd: [...(parent ? parent.isEnd : []), index === list.length - 1], + }; + + flattenList.push(flattenNode); + + // Loop treeNode children + if (expandedKeys === true || expandedKeySet.has(mergedKey)) { + flattenNode.children = dig(treeNode[fieldChildren] || [], flattenNode); + } else { + flattenNode.children = []; + } + + return flattenNode; + }); + } + + dig(treeNodeList); + + return flattenList; +} + +type ExternalGetKey = GetKey | string; + +interface TraverseDataNodesConfig { + childrenPropName?: string; + externalGetKey?: ExternalGetKey; + fieldNames?: FieldNames; +} + +/** + * Traverse all the data by `treeData`. + * Please not use it out of the `rc-tree` since we may refactor this code. + */ +export function traverseDataNodes( + dataNodes: DataNode[], + callback: (data: { + node: DataNode; + index: number; + pos: string; + key: Key; + parentPos: string | number; + level: number; + }) => void, + // To avoid too many params, let use config instead of origin param + config?: TraverseDataNodesConfig | string, +) { + let mergedConfig: TraverseDataNodesConfig = {}; + if (typeof config === 'object') { + mergedConfig = config; + } else { + mergedConfig = { externalGetKey: config }; + } + mergedConfig = mergedConfig || {}; + + // Init config + const { childrenPropName, externalGetKey, fieldNames } = mergedConfig; + + const { key: fieldKey, children: fieldChildren } = fillFieldNames(fieldNames); + + const mergeChildrenPropName = childrenPropName || fieldChildren; + + // Get keys + let syntheticGetKey: (node: DataNode, pos?: string) => Key; + if (externalGetKey) { + if (typeof externalGetKey === 'string') { + syntheticGetKey = (node: DataNode) => (node as any)[externalGetKey as string]; + } else if (typeof externalGetKey === 'function') { + syntheticGetKey = (node: DataNode) => (externalGetKey as GetKey)(node); + } + } else { + syntheticGetKey = (node, pos) => getKey(node[fieldKey], pos); + } + + // Process + function processNode( + node: DataNode, + index?: number, + parent?: { node: DataNode; pos: string; level: number }, + ) { + const children = node ? node[mergeChildrenPropName] : dataNodes; + const pos = node ? getPosition(parent.pos, index) : '0'; + + // Process node if is not root + if (node) { + const key: Key = syntheticGetKey(node, pos); + const data = { + node, + index, + pos, + key, + parentPos: parent.node ? parent.pos : null, + level: parent.level + 1, + }; + + callback(data); + } + + // Process children node + if (children) { + children.forEach((subNode, subIndex) => { + processNode(subNode, subIndex, { + node, + pos, + level: parent ? parent.level + 1 : -1, + }); + }); + } + } + + processNode(null); +} + +interface Wrapper { + posEntities: Record; + keyEntities: Record; +} + +/** + * Convert `treeData` into entity records. + */ +export function convertDataToEntities( + dataNodes: DataNode[], + { + initWrapper, + processEntity, + onProcessFinished, + externalGetKey, + childrenPropName, + fieldNames, + }: { + initWrapper?: (wrapper: Wrapper) => Wrapper; + processEntity?: (entity: DataEntity, wrapper: Wrapper) => void; + onProcessFinished?: (wrapper: Wrapper) => void; + externalGetKey?: ExternalGetKey; + childrenPropName?: string; + fieldNames?: FieldNames; + } = {}, + /** @deprecated Use `config.externalGetKey` instead */ + legacyExternalGetKey?: ExternalGetKey, +) { + // Init config + const mergedExternalGetKey = externalGetKey || legacyExternalGetKey; + + const posEntities = {}; + const keyEntities = {}; + let wrapper = { + posEntities, + keyEntities, + }; + + if (initWrapper) { + wrapper = initWrapper(wrapper) || wrapper; + } + + traverseDataNodes( + dataNodes, + item => { + const { node, index, pos, key, parentPos, level } = item; + const entity: DataEntity = { node, index, key, pos, level }; + + const mergedKey = getKey(key, pos); + + posEntities[pos] = entity; + keyEntities[mergedKey] = entity; + + // Fill children + entity.parent = posEntities[parentPos]; + if (entity.parent) { + entity.parent.children = entity.parent.children || []; + entity.parent.children.push(entity); + } + + if (processEntity) { + processEntity(entity, wrapper); + } + }, + { externalGetKey: mergedExternalGetKey, childrenPropName, fieldNames }, + ); + + if (onProcessFinished) { + onProcessFinished(wrapper); + } + + return wrapper; +} + +export interface TreeNodeRequiredProps { + expandedKeys: Key[]; + selectedKeys: Key[]; + loadedKeys: Key[]; + loadingKeys: Key[]; + checkedKeys: Key[]; + halfCheckedKeys: Key[]; + dragOverNodeKey: Key; + dropPosition: number; + keyEntities: Record; +} + +/** + * Get TreeNode props with Tree props. + */ +export function getTreeNodeProps( + key: Key, + { + expandedKeys, + selectedKeys, + loadedKeys, + loadingKeys, + checkedKeys, + halfCheckedKeys, + dragOverNodeKey, + dropPosition, + keyEntities, + }: TreeNodeRequiredProps, +) { + const entity = keyEntities[key]; + + const treeNodeProps = { + eventKey: key, + expanded: expandedKeys.indexOf(key) !== -1, + selected: selectedKeys.indexOf(key) !== -1, + loaded: loadedKeys.indexOf(key) !== -1, + loading: loadingKeys.indexOf(key) !== -1, + checked: checkedKeys.indexOf(key) !== -1, + halfChecked: halfCheckedKeys.indexOf(key) !== -1, + pos: String(entity ? entity.pos : ''), + + // [Legacy] Drag props + // Since the interaction of drag is changed, the semantic of the props are + // not accuracy, I think it should be finally removed + dragOver: dragOverNodeKey === key && dropPosition === 0, + dragOverGapTop: dragOverNodeKey === key && dropPosition === -1, + dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1, + }; + + return treeNodeProps; +} + +export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode { + const { + data, + expanded, + selected, + checked, + loaded, + loading, + halfChecked, + dragOver, + dragOverGapTop, + dragOverGapBottom, + pos, + active, + } = props; + + const eventData = { + ...data, + expanded, + selected, + checked, + loaded, + loading, + halfChecked, + dragOver, + dragOverGapTop, + dragOverGapBottom, + pos, + active, + }; + + if (!('props' in eventData)) { + Object.defineProperty(eventData, 'props', { + get() { + warning( + false, + 'Second param return from event is node data instead of TreeNode instance. Please read value directly instead of reading from `props`.', + ); + return props; + }, + }); + } + + return eventData; +} diff --git a/components/vc-virtual-list/List.tsx b/components/vc-virtual-list/List.tsx index ea9d788cf..0644aba21 100644 --- a/components/vc-virtual-list/List.tsx +++ b/components/vc-virtual-list/List.tsx @@ -30,6 +30,20 @@ const ScrollStyle: CSSProperties = { overflowAnchor: 'none', }; +export type ScrollAlign = 'top' | 'bottom' | 'auto'; +export type ScrollConfig = + | { + index: number; + align?: ScrollAlign; + offset?: number; + } + | { + key: Key; + align?: ScrollAlign; + offset?: number; + }; +export type ScrollTo = (arg: number | ScrollConfig) => void; + function renderChildren( list: T[], startIndex: number, @@ -68,7 +82,7 @@ const List = defineComponent({ /** If not match virtual scroll condition, Set List still use height of container. */ fullHeight: PropTypes.looseBool, itemKey: { - type: [String, Number, Function] as PropType Key)>, + type: [String, Number, Function] as PropType) => Key)>, required: true, }, component: { @@ -81,7 +95,7 @@ const List = defineComponent({ onMousedown: PropTypes.func, onMouseenter: PropTypes.func, }, - setup(props) { + setup(props, { expose }) { // ================================= MISC ================================= const useVirtual = computed(() => { const { height, itemHeight, virtual } = props; @@ -323,6 +337,10 @@ const List = defineComponent({ }, ); + expose({ + scrollTo, + }); + const componentStyle = computed(() => { let cs: CSSProperties | null = null; if (props.height) { @@ -343,7 +361,6 @@ const List = defineComponent({ state, mergedData, componentStyle, - scrollTo, onFallbackScroll, onScrollBar, componentRef, diff --git a/v2-doc b/v2-doc index 7a7b52df8..d571ad4bf 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit 7a7b52df8b3b69d8b1a8b8dcd96e1b0f7bb3f8c9 +Subproject commit d571ad4bf772cfc372511dc1dedf07981dc56ae8 From a0f7e8d21f6d7c337551a82f6682ba46dffb8633 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 17 Aug 2021 22:03:49 +0800 Subject: [PATCH 19/53] refactor: tree --- components/_util/hooks/useConfigInject.ts | 3 + components/style/themes/default.less | 1 + components/tree/DirectoryTree copy.tsx | 251 +++++++++ components/tree/DirectoryTree.tsx | 232 +------- components/tree/Tree copy.tsx | 289 ++++++++++ components/tree/Tree.tsx | 529 ++++++++++-------- components/tree/index.tsx | 15 + components/tree/style/directory.less | 127 ++--- components/tree/style/index.less | 272 +-------- components/tree/style/{index.ts => index.tsx} | 0 components/tree/style/mixin.less | 271 ++++++++- components/tree/style/rtl.less | 72 +++ components/tree/utils/dictUtil.ts | 92 +++ components/tree/utils/dropIndicator.tsx | 33 ++ components/tree/utils/iconUtil.tsx | 52 ++ components/upload/Upload.tsx | 4 +- components/vc-tree-select/src/util.js | 2 +- components/vc-tree/MotionTreeNode.tsx | 2 +- components/vc-tree/NodeList.tsx | 7 +- components/vc-tree/Tree.tsx | 62 +- components/vc-tree/TreeNode.tsx | 7 +- components/vc-tree/contextTypes.ts | 3 +- components/vc-tree/index.ts | 4 +- components/vc-tree/interface.tsx | 6 +- components/vc-tree/props.ts | 15 +- examples/App.vue | 85 ++- 26 files changed, 1545 insertions(+), 891 deletions(-) create mode 100644 components/tree/DirectoryTree copy.tsx create mode 100644 components/tree/Tree copy.tsx rename components/tree/style/{index.ts => index.tsx} (100%) create mode 100644 components/tree/style/rtl.less create mode 100644 components/tree/utils/dictUtil.ts create mode 100644 components/tree/utils/dropIndicator.tsx create mode 100644 components/tree/utils/iconUtil.tsx diff --git a/components/_util/hooks/useConfigInject.ts b/components/_util/hooks/useConfigInject.ts index 93f18e1f8..d7b64dbbb 100644 --- a/components/_util/hooks/useConfigInject.ts +++ b/components/_util/hooks/useConfigInject.ts @@ -20,6 +20,7 @@ export default ( }>; autoInsertSpaceInButton: ComputedRef; renderEmpty?: ComputedRef<(componentName?: string) => VNodeChild | JSX.Element>; + virtual: ComputedRef; } => { const configProvider = inject>( 'configProvider', @@ -34,6 +35,7 @@ export default ( const form = computed(() => configProvider.form); const size = computed(() => props.size || configProvider.componentSize); const getTargetContainer = computed(() => props.getTargetContainer); + const virtual = computed(() => props.virtual); return { configProvider, prefixCls, @@ -45,5 +47,6 @@ export default ( form, autoInsertSpaceInButton, renderEmpty, + virtual, }; }; diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 1b9b0c301..3808946a6 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -747,6 +747,7 @@ // Tree // --- +@tree-bg: @component-background; @tree-title-height: 24px; @tree-child-padding: 18px; @tree-directory-selected-color: #fff; diff --git a/components/tree/DirectoryTree copy.tsx b/components/tree/DirectoryTree copy.tsx new file mode 100644 index 000000000..3e096b36e --- /dev/null +++ b/components/tree/DirectoryTree copy.tsx @@ -0,0 +1,251 @@ +import type { VNode } from 'vue'; +import { defineComponent, inject } from 'vue'; +import omit from 'omit.js'; +import debounce from 'lodash-es/debounce'; +import FolderOpenOutlined from '@ant-design/icons-vue/FolderOpenOutlined'; +import FolderOutlined from '@ant-design/icons-vue/FolderOutlined'; +import FileOutlined from '@ant-design/icons-vue/FileOutlined'; +import PropTypes from '../_util/vue-types'; +import classNames from '../_util/classNames'; +import { conductExpandParent, convertTreeToEntities } from '../vc-tree/src/util'; +import type { CheckEvent, ExpendEvent, SelectEvent } from './Tree'; +import Tree, { TreeProps } from './Tree'; +import { + calcRangeKeys, + getFullKeyList, + convertDirectoryKeysToNodes, + getFullKeyListByTreeData, +} from './util'; +import BaseMixin from '../_util/BaseMixin'; +import { getOptionProps, getComponent, getSlot } from '../_util/props-util'; +import initDefaultProps from '../_util/props-util/initDefaultProps'; +import { defaultConfigProvider } from '../config-provider'; + +// export type ExpandAction = false | 'click' | 'dblclick'; export interface +// DirectoryTreeProps extends TreeProps { expandAction?: ExpandAction; } +// export interface DirectoryTreeState { expandedKeys?: string[]; +// selectedKeys?: string[]; } + +export interface DirectoryTreeState { + _expandedKeys?: (string | number)[]; + _selectedKeys?: (string | number)[]; +} + +function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) { + const { isLeaf, expanded } = props; + if (isLeaf) { + return ; + } + return expanded ? : ; +} + +export default defineComponent({ + name: 'ADirectoryTree', + mixins: [BaseMixin], + inheritAttrs: false, + props: initDefaultProps( + { + ...TreeProps(), + expandAction: PropTypes.oneOf([false, 'click', 'doubleclick', 'dblclick']), + }, + { + showIcon: true, + expandAction: 'click', + }, + ), + setup() { + return { + children: null, + onDebounceExpand: null, + tree: null, + lastSelectedKey: '', + cachedSelectedKeys: [], + configProvider: inject('configProvider', defaultConfigProvider), + }; + }, + data() { + const props = getOptionProps(this); + const { defaultExpandAll, defaultExpandParent, expandedKeys, defaultExpandedKeys } = props; + const children = getSlot(this); + const { keyEntities } = convertTreeToEntities(children); + const state: DirectoryTreeState = {}; + // Selected keys + state._selectedKeys = props.selectedKeys || props.defaultSelectedKeys || []; + + // Expanded keys + if (defaultExpandAll) { + if (props.treeData) { + state._expandedKeys = getFullKeyListByTreeData(props.treeData, props.replaceFields); + } else { + state._expandedKeys = getFullKeyList(children); + } + } else if (defaultExpandParent) { + state._expandedKeys = conductExpandParent(expandedKeys || defaultExpandedKeys, keyEntities); + } else { + state._expandedKeys = expandedKeys || defaultExpandedKeys; + } + return { + _selectedKeys: [], + _expandedKeys: [], + ...state, + }; + }, + watch: { + expandedKeys(val) { + this.setState({ _expandedKeys: val }); + }, + selectedKeys(val) { + this.setState({ _selectedKeys: val }); + }, + }, + created() { + this.onDebounceExpand = debounce(this.expandFolderNode, 200, { leading: true }); + }, + methods: { + handleExpand(expandedKeys: (string | number)[], info: ExpendEvent) { + this.setUncontrolledState({ _expandedKeys: expandedKeys }); + this.$emit('update:expandedKeys', expandedKeys); + this.$emit('expand', expandedKeys, info); + + return undefined; + }, + + handleClick(event: MouseEvent, node: VNode) { + const { expandAction } = this.$props; + + // Expand the tree + if (expandAction === 'click') { + this.onDebounceExpand(event, node); + } + this.$emit('click', event, node); + }, + + handleDoubleClick(event: MouseEvent, node: VNode) { + const { expandAction } = this.$props; + + // Expand the tree + if (expandAction === 'dblclick' || expandAction === 'doubleclick') { + this.onDebounceExpand(event, node); + } + + this.$emit('doubleclick', event, node); + this.$emit('dblclick', event, node); + }, + + hanldeSelect(keys: (string | number)[], event: SelectEvent) { + const { multiple } = this.$props; + const children = this.children || []; + const { _expandedKeys: expandedKeys = [] } = this.$data; + const { node, nativeEvent } = event; + const { eventKey = '' } = node; + + const newState: DirectoryTreeState = {}; + + // We need wrap this event since some value is not same + const newEvent = { + ...event, + selected: true, // Directory selected always true + }; + + // Windows / Mac single pick + const ctrlPick = nativeEvent.ctrlKey || nativeEvent.metaKey; + const shiftPick = nativeEvent.shiftKey; + + // Generate new selected keys + let newSelectedKeys: (string | number)[]; + if (multiple && ctrlPick) { + // Control click + newSelectedKeys = keys; + this.lastSelectedKey = eventKey; + this.cachedSelectedKeys = newSelectedKeys; + newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys); + } else if (multiple && shiftPick) { + // Shift click + newSelectedKeys = Array.from( + new Set([ + ...(this.cachedSelectedKeys || []), + ...calcRangeKeys(children, expandedKeys, eventKey, this.lastSelectedKey), + ]), + ); + newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys); + } else { + // Single click + newSelectedKeys = [eventKey]; + this.lastSelectedKey = eventKey; + this.cachedSelectedKeys = newSelectedKeys; + newEvent.selectedNodes = [event.node]; + } + newState._selectedKeys = newSelectedKeys; + + this.$emit('update:selectedKeys', newSelectedKeys); + this.$emit('select', newSelectedKeys, newEvent); + + this.setUncontrolledState(newState); + }, + setTreeRef(node: VNode) { + this.tree = node; + }, + + expandFolderNode(event: MouseEvent, node: { isLeaf: boolean } & VNode) { + const { isLeaf } = node; + + if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) { + return; + } + + if (this.tree.tree) { + // Get internal vc-tree + const internalTree = this.tree.tree; + + // Call internal rc-tree expand function + // https://github.com/ant-design/ant-design/issues/12567 + internalTree.onNodeExpand(event, node); + } + }, + + setUncontrolledState(state: unknown) { + const newState = omit( + state, + Object.keys(getOptionProps(this)).map(p => `_${p}`), + ); + if (Object.keys(newState).length) { + this.setState(newState); + } + }, + handleCheck(checkedObj: (string | number)[], eventObj: CheckEvent) { + this.$emit('update:checkedKeys', checkedObj); + this.$emit('check', checkedObj, eventObj); + }, + }, + + render() { + this.children = getSlot(this); + const { prefixCls: customizePrefixCls, ...props } = getOptionProps(this); + const getPrefixCls = this.configProvider.getPrefixCls; + const prefixCls = getPrefixCls('tree', customizePrefixCls); + const { _expandedKeys: expandedKeys, _selectedKeys: selectedKeys } = this.$data; + const { class: className, ...restAttrs } = this.$attrs; + const connectClassName = classNames(`${prefixCls}-directory`, className); + const treeProps = { + icon: getIcon, + ...restAttrs, + ...omit(props, ['onUpdate:selectedKeys', 'onUpdate:checkedKeys', 'onUpdate:expandedKeys']), + prefixCls, + expandedKeys, + selectedKeys, + switcherIcon: getComponent(this, 'switcherIcon'), + ref: this.setTreeRef, + class: connectClassName, + onSelect: this.hanldeSelect, + onClick: this.handleClick, + onDblclick: this.handleDoubleClick, + onExpand: this.handleExpand, + onCheck: this.handleCheck, + }; + return ( + + {this.children} + + ); + }, +}); diff --git a/components/tree/DirectoryTree.tsx b/components/tree/DirectoryTree.tsx index 3e096b36e..55c958b9c 100644 --- a/components/tree/DirectoryTree.tsx +++ b/components/tree/DirectoryTree.tsx @@ -1,4 +1,4 @@ -import type { VNode } from 'vue'; +import type { ExtractPropTypes, PropType, VNode } from 'vue'; import { defineComponent, inject } from 'vue'; import omit from 'omit.js'; import debounce from 'lodash-es/debounce'; @@ -7,8 +7,7 @@ import FolderOutlined from '@ant-design/icons-vue/FolderOutlined'; import FileOutlined from '@ant-design/icons-vue/FileOutlined'; import PropTypes from '../_util/vue-types'; import classNames from '../_util/classNames'; -import { conductExpandParent, convertTreeToEntities } from '../vc-tree/src/util'; -import type { CheckEvent, ExpendEvent, SelectEvent } from './Tree'; +import { treeProps } from './Tree'; import Tree, { TreeProps } from './Tree'; import { calcRangeKeys, @@ -16,20 +15,11 @@ import { convertDirectoryKeysToNodes, getFullKeyListByTreeData, } from './util'; -import BaseMixin from '../_util/BaseMixin'; import { getOptionProps, getComponent, getSlot } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; import { defaultConfigProvider } from '../config-provider'; -// export type ExpandAction = false | 'click' | 'dblclick'; export interface -// DirectoryTreeProps extends TreeProps { expandAction?: ExpandAction; } -// export interface DirectoryTreeState { expandedKeys?: string[]; -// selectedKeys?: string[]; } - -export interface DirectoryTreeState { - _expandedKeys?: (string | number)[]; - _selectedKeys?: (string | number)[]; -} +export type ExpandAction = false | 'click' | 'doubleClick' | 'dblclick'; function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) { const { isLeaf, expanded } = props; @@ -39,213 +29,23 @@ function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) { return expanded ? : ; } +const directoryTreeProps = { + ...treeProps(), + expandAction: { type: [Boolean, String] as PropType }, +}; + +export type DirectoryTreeProps = Partial>; + export default defineComponent({ name: 'ADirectoryTree', - mixins: [BaseMixin], inheritAttrs: false, - props: initDefaultProps( - { - ...TreeProps(), - expandAction: PropTypes.oneOf([false, 'click', 'doubleclick', 'dblclick']), - }, - { - showIcon: true, - expandAction: 'click', - }, - ), + props: initDefaultProps(directoryTreeProps, { + showIcon: true, + expandAction: 'click', + }), setup() { - return { - children: null, - onDebounceExpand: null, - tree: null, - lastSelectedKey: '', - cachedSelectedKeys: [], - configProvider: inject('configProvider', defaultConfigProvider), + return () => { + return null; }; }, - data() { - const props = getOptionProps(this); - const { defaultExpandAll, defaultExpandParent, expandedKeys, defaultExpandedKeys } = props; - const children = getSlot(this); - const { keyEntities } = convertTreeToEntities(children); - const state: DirectoryTreeState = {}; - // Selected keys - state._selectedKeys = props.selectedKeys || props.defaultSelectedKeys || []; - - // Expanded keys - if (defaultExpandAll) { - if (props.treeData) { - state._expandedKeys = getFullKeyListByTreeData(props.treeData, props.replaceFields); - } else { - state._expandedKeys = getFullKeyList(children); - } - } else if (defaultExpandParent) { - state._expandedKeys = conductExpandParent(expandedKeys || defaultExpandedKeys, keyEntities); - } else { - state._expandedKeys = expandedKeys || defaultExpandedKeys; - } - return { - _selectedKeys: [], - _expandedKeys: [], - ...state, - }; - }, - watch: { - expandedKeys(val) { - this.setState({ _expandedKeys: val }); - }, - selectedKeys(val) { - this.setState({ _selectedKeys: val }); - }, - }, - created() { - this.onDebounceExpand = debounce(this.expandFolderNode, 200, { leading: true }); - }, - methods: { - handleExpand(expandedKeys: (string | number)[], info: ExpendEvent) { - this.setUncontrolledState({ _expandedKeys: expandedKeys }); - this.$emit('update:expandedKeys', expandedKeys); - this.$emit('expand', expandedKeys, info); - - return undefined; - }, - - handleClick(event: MouseEvent, node: VNode) { - const { expandAction } = this.$props; - - // Expand the tree - if (expandAction === 'click') { - this.onDebounceExpand(event, node); - } - this.$emit('click', event, node); - }, - - handleDoubleClick(event: MouseEvent, node: VNode) { - const { expandAction } = this.$props; - - // Expand the tree - if (expandAction === 'dblclick' || expandAction === 'doubleclick') { - this.onDebounceExpand(event, node); - } - - this.$emit('doubleclick', event, node); - this.$emit('dblclick', event, node); - }, - - hanldeSelect(keys: (string | number)[], event: SelectEvent) { - const { multiple } = this.$props; - const children = this.children || []; - const { _expandedKeys: expandedKeys = [] } = this.$data; - const { node, nativeEvent } = event; - const { eventKey = '' } = node; - - const newState: DirectoryTreeState = {}; - - // We need wrap this event since some value is not same - const newEvent = { - ...event, - selected: true, // Directory selected always true - }; - - // Windows / Mac single pick - const ctrlPick = nativeEvent.ctrlKey || nativeEvent.metaKey; - const shiftPick = nativeEvent.shiftKey; - - // Generate new selected keys - let newSelectedKeys: (string | number)[]; - if (multiple && ctrlPick) { - // Control click - newSelectedKeys = keys; - this.lastSelectedKey = eventKey; - this.cachedSelectedKeys = newSelectedKeys; - newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys); - } else if (multiple && shiftPick) { - // Shift click - newSelectedKeys = Array.from( - new Set([ - ...(this.cachedSelectedKeys || []), - ...calcRangeKeys(children, expandedKeys, eventKey, this.lastSelectedKey), - ]), - ); - newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys); - } else { - // Single click - newSelectedKeys = [eventKey]; - this.lastSelectedKey = eventKey; - this.cachedSelectedKeys = newSelectedKeys; - newEvent.selectedNodes = [event.node]; - } - newState._selectedKeys = newSelectedKeys; - - this.$emit('update:selectedKeys', newSelectedKeys); - this.$emit('select', newSelectedKeys, newEvent); - - this.setUncontrolledState(newState); - }, - setTreeRef(node: VNode) { - this.tree = node; - }, - - expandFolderNode(event: MouseEvent, node: { isLeaf: boolean } & VNode) { - const { isLeaf } = node; - - if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) { - return; - } - - if (this.tree.tree) { - // Get internal vc-tree - const internalTree = this.tree.tree; - - // Call internal rc-tree expand function - // https://github.com/ant-design/ant-design/issues/12567 - internalTree.onNodeExpand(event, node); - } - }, - - setUncontrolledState(state: unknown) { - const newState = omit( - state, - Object.keys(getOptionProps(this)).map(p => `_${p}`), - ); - if (Object.keys(newState).length) { - this.setState(newState); - } - }, - handleCheck(checkedObj: (string | number)[], eventObj: CheckEvent) { - this.$emit('update:checkedKeys', checkedObj); - this.$emit('check', checkedObj, eventObj); - }, - }, - - render() { - this.children = getSlot(this); - const { prefixCls: customizePrefixCls, ...props } = getOptionProps(this); - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('tree', customizePrefixCls); - const { _expandedKeys: expandedKeys, _selectedKeys: selectedKeys } = this.$data; - const { class: className, ...restAttrs } = this.$attrs; - const connectClassName = classNames(`${prefixCls}-directory`, className); - const treeProps = { - icon: getIcon, - ...restAttrs, - ...omit(props, ['onUpdate:selectedKeys', 'onUpdate:checkedKeys', 'onUpdate:expandedKeys']), - prefixCls, - expandedKeys, - selectedKeys, - switcherIcon: getComponent(this, 'switcherIcon'), - ref: this.setTreeRef, - class: connectClassName, - onSelect: this.hanldeSelect, - onClick: this.handleClick, - onDblclick: this.handleDoubleClick, - onExpand: this.handleExpand, - onCheck: this.handleCheck, - }; - return ( - - {this.children} - - ); - }, }); diff --git a/components/tree/Tree copy.tsx b/components/tree/Tree copy.tsx new file mode 100644 index 000000000..dc4230ffd --- /dev/null +++ b/components/tree/Tree copy.tsx @@ -0,0 +1,289 @@ +import type { VNode, PropType, CSSProperties } from 'vue'; +import { defineComponent, inject } from 'vue'; +import classNames from '../_util/classNames'; +import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; +import FileOutlined from '@ant-design/icons-vue/FileOutlined'; +import CaretDownFilled from '@ant-design/icons-vue/CaretDownFilled'; +import MinusSquareOutlined from '@ant-design/icons-vue/MinusSquareOutlined'; +import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined'; +import VcTree, { TreeNode } from '../vc-tree'; +import animation from '../_util/openAnimation'; +import PropTypes from '../_util/vue-types'; +import { getOptionProps, getComponent, getSlot } from '../_util/props-util'; +import initDefaultProps from '../_util/props-util/initDefaultProps'; +import { cloneElement } from '../_util/vnode'; +import { defaultConfigProvider } from '../config-provider'; + +export interface TreeDataItem { + key?: string | number; + title?: string; + isLeaf?: boolean; + selectable?: boolean; + children?: TreeDataItem[]; + disableCheckbox?: boolean; + disabled?: boolean; + class?: string; + style?: CSSProperties; + checkable?: boolean; + icon?: VNode; + + slots?: Record; + switcherIcon?: VNode; + // support custom field + [key: string]: any; +} + +interface DefaultEvent { + nativeEvent: MouseEvent; + node: Record; +} + +export interface CheckEvent extends DefaultEvent { + checked: boolean; + checkedNodes: Array>; + checkedNodesPositions: { node: Record; pos: string | number }[]; + event: string; + halfCheckedKeys: (string | number)[]; +} + +export interface ExpendEvent extends DefaultEvent { + expanded: boolean; +} + +export interface SelectEvent extends DefaultEvent { + event: string; + selected: boolean; + selectedNodes: Array>; +} + +export interface TreeDragEvent { + event: DragEvent; + expandedKeys: (string | number)[]; + node: Record; +} + +export interface DropEvent { + dragNode: Record; + dragNodesKeys: (string | number)[]; + dropPosition: number; + dropToGap: boolean; + event: DragEvent; + node: Record; +} + +function TreeProps() { + return { + showLine: PropTypes.looseBool, + /** 是否支持多选 */ + multiple: PropTypes.looseBool, + /** 是否自动展开父节点 */ + autoExpandParent: PropTypes.looseBool, + /** checkable状态下节点选择完全受控(父子节点选中状态不再关联)*/ + checkStrictly: PropTypes.looseBool, + /** 是否支持选中 */ + checkable: PropTypes.looseBool, + /** 是否禁用树 */ + disabled: PropTypes.looseBool, + /** 默认展开所有树节点 */ + defaultExpandAll: PropTypes.looseBool, + /** 默认展开对应树节点 */ + defaultExpandParent: PropTypes.looseBool, + /** 默认展开指定的树节点 */ + defaultExpandedKeys: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + ), + /** (受控)展开指定的树节点 */ + expandedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + /** (受控)选中复选框的树节点 */ + checkedKeys: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + PropTypes.shape({ + checked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + halfChecked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + }).loose, + ]), + /** 默认选中复选框的树节点 */ + defaultCheckedKeys: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + ), + /** (受控)设置选中的树节点 */ + selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + /** 默认选中的树节点 */ + defaultSelectedKeys: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + ), + selectable: PropTypes.looseBool, + + /** filter some AntTreeNodes as you need. it should return true */ + filterAntTreeNode: PropTypes.func, + /** 异步加载数据 */ + loadData: PropTypes.func, + loadedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + // onLoaded: (loadedKeys: string[], info: { event: 'load', node: AntTreeNode; }) => void, + /** 响应右键点击 */ + // onRightClick: (options: AntTreeNodeMouseEvent) => void, + /** 设置节点可拖拽(IE>8)*/ + draggable: PropTypes.looseBool, + // /** 开始拖拽时调用 */ + // onDragStart: (options: AntTreeNodeMouseEvent) => void, + // /** dragenter 触发时调用 */ + // onDragEnter: (options: AntTreeNodeMouseEvent) => void, + // /** dragover 触发时调用 */ + // onDragOver: (options: AntTreeNodeMouseEvent) => void, + // /** dragleave 触发时调用 */ + // onDragLeave: (options: AntTreeNodeMouseEvent) => void, + // /** drop 触发时调用 */ + // onDrop: (options: AntTreeNodeMouseEvent) => void, + showIcon: PropTypes.looseBool, + icon: PropTypes.func, + switcherIcon: PropTypes.any, + prefixCls: PropTypes.string, + filterTreeNode: PropTypes.func, + openAnimation: PropTypes.any, + treeData: { + type: Array as PropType, + }, + /** + * @default{title,key,children} + * 替换treeNode中 title,key,children字段为treeData中对应的字段 + */ + replaceFields: PropTypes.object, + blockNode: PropTypes.looseBool, + /** 展开/收起节点时触发 */ + onExpand: PropTypes.func, + /** 点击复选框触发 */ + onCheck: PropTypes.func, + /** 点击树节点触发 */ + onSelect: PropTypes.func, + /** 单击树节点触发 */ + onClick: PropTypes.func, + /** 双击树节点触发 */ + onDoubleclick: PropTypes.func, + onDblclick: PropTypes.func, + 'onUpdate:selectedKeys': PropTypes.func, + 'onUpdate:checkedKeys': PropTypes.func, + 'onUpdate:expandedKeys': PropTypes.func, + }; +} + +export { TreeProps }; + +export default defineComponent({ + name: 'ATree', + inheritAttrs: false, + props: initDefaultProps(TreeProps(), { + checkable: false, + showIcon: false, + openAnimation: { + ...animation, + appear: null, + }, + blockNode: false, + }), + setup() { + return { + tree: null, + configProvider: inject('configProvider', defaultConfigProvider), + }; + }, + TreeNode, + methods: { + renderSwitcherIcon(prefixCls: string, switcherIcon: VNode, { isLeaf, loading, expanded }) { + const { showLine } = this.$props; + if (loading) { + return ; + } + + if (isLeaf) { + return showLine ? : null; + } + const switcherCls = `${prefixCls}-switcher-icon`; + if (switcherIcon) { + return cloneElement(switcherIcon, { + class: switcherCls, + }); + } + return showLine ? ( + expanded ? ( + + ) : ( + + ) + ) : ( + + ); + }, + updateTreeData(treeData: TreeDataItem[]) { + const { $slots } = this; + const defaultFields = { children: 'children', title: 'title', key: 'key' }; + const replaceFields = { ...defaultFields, ...this.$props.replaceFields }; + return treeData.map(item => { + const key = item[replaceFields.key]; + const children = item[replaceFields.children]; + const { slots = {}, class: cls, style, ...restProps } = item; + const treeNodeProps = { + ...restProps, + icon: $slots[slots.icon] || restProps.icon, + switcherIcon: $slots[slots.switcherIcon] || restProps.switcherIcon, + title: $slots[slots.title] || $slots.title || restProps[replaceFields.title], + dataRef: item, + key, + class: cls, + style, + }; + if (children) { + return { ...treeNodeProps, children: this.updateTreeData(children) }; + } + return treeNodeProps; + }); + }, + setTreeRef(node: VNode) { + this.tree = node; + }, + handleCheck(checkedObj: (number | string)[], eventObj: CheckEvent) { + this.$emit('update:checkedKeys', checkedObj); + this.$emit('check', checkedObj, eventObj); + }, + handleExpand(expandedKeys: (number | string)[], eventObj: ExpendEvent) { + this.$emit('update:expandedKeys', expandedKeys); + this.$emit('expand', expandedKeys, eventObj); + }, + handleSelect(selectedKeys: (number | string)[], eventObj: SelectEvent) { + this.$emit('update:selectedKeys', selectedKeys); + this.$emit('select', selectedKeys, eventObj); + }, + }, + render() { + const props = getOptionProps(this); + const { prefixCls: customizePrefixCls, showIcon, treeNodes, blockNode } = props; + const getPrefixCls = this.configProvider.getPrefixCls; + const prefixCls = getPrefixCls('tree', customizePrefixCls); + const switcherIcon = getComponent(this, 'switcherIcon'); + const checkable = props.checkable; + let treeData = props.treeData || treeNodes; + if (treeData) { + treeData = this.updateTreeData(treeData); + } + const { class: className, ...restAttrs } = this.$attrs; + const vcTreeProps = { + ...props, + prefixCls, + checkable: checkable ? : checkable, + children: getSlot(this), + switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps), + ref: this.setTreeRef, + ...restAttrs, + class: classNames(className, { + [`${prefixCls}-icon-hide`]: !showIcon, + [`${prefixCls}-block-node`]: blockNode, + }), + onCheck: this.handleCheck, + onExpand: this.handleExpand, + onSelect: this.handleSelect, + } as Record; + if (treeData) { + vcTreeProps.treeData = treeData; + } + return ; + }, +}); diff --git a/components/tree/Tree.tsx b/components/tree/Tree.tsx index b01c4fcd8..a1426969e 100644 --- a/components/tree/Tree.tsx +++ b/components/tree/Tree.tsx @@ -1,290 +1,347 @@ -import type { VNode, PropType, CSSProperties } from 'vue'; -import { defineComponent, inject } from 'vue'; +import { VNode, PropType, DefineComponent, ExtractPropTypes, ref } from 'vue'; +import { defineComponent } from 'vue'; import classNames from '../_util/classNames'; -import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; -import FileOutlined from '@ant-design/icons-vue/FileOutlined'; -import CaretDownFilled from '@ant-design/icons-vue/CaretDownFilled'; -import MinusSquareOutlined from '@ant-design/icons-vue/MinusSquareOutlined'; -import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined'; -import VcTree from '../vc-tree'; +import VcTree, { TreeNode } from '../vc-tree'; import animation from '../_util/openAnimation'; import PropTypes from '../_util/vue-types'; -import { getOptionProps, getComponent, getSlot } from '../_util/props-util'; +import { filterEmpty } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { cloneElement } from '../_util/vnode'; -import { defaultConfigProvider } from '../config-provider'; +import { DataNode, FieldNames, Key } from '../vc-tree/interface'; +import { treeProps as vcTreeProps } from '../vc-tree/props'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import renderSwitcherIcon from './utils/iconUtil'; +import dropIndicatorRender from './utils/dropIndicator'; -const TreeNode = VcTree.TreeNode; - -export interface TreeDataItem { - key?: string | number; - title?: string; - isLeaf?: boolean; - selectable?: boolean; - children?: TreeDataItem[]; - disableCheckbox?: boolean; - disabled?: boolean; - class?: string; - style?: CSSProperties; - checkable?: boolean; - icon?: VNode; - slots?: Record; - switcherIcon?: VNode; - // support custom field - [key: string]: any; -} - -interface DefaultEvent { - nativeEvent: MouseEvent; - node: Record; -} - -export interface CheckEvent extends DefaultEvent { - checked: boolean; - checkedNodes: Array>; - checkedNodesPositions: { node: Record; pos: string | number }[]; - event: string; - halfCheckedKeys: (string | number)[]; -} - -export interface ExpendEvent extends DefaultEvent { +export interface AntdTreeNodeAttribute { + eventKey: string; + prefixCls: string; + className: string; expanded: boolean; -} - -export interface SelectEvent extends DefaultEvent { - event: string; selected: boolean; - selectedNodes: Array>; + checked: boolean; + halfChecked: boolean; + children: any; + title: any; + pos: string; + dragOver: boolean; + dragOverGapTop: boolean; + dragOverGapBottom: boolean; + isLeaf: boolean; + selectable: boolean; + disabled: boolean; + disableCheckbox: boolean; } -export interface TreeDragEvent { +export interface AntTreeNodeProps { + className?: string; + checkable?: boolean; + disabled?: boolean; + disableCheckbox?: boolean; + title?: string | any; + key?: Key; + eventKey?: string; + isLeaf?: boolean; + checked?: boolean; + expanded?: boolean; + loading?: boolean; + selected?: boolean; + selectable?: boolean; + icon?: ((treeNode: AntdTreeNodeAttribute) => any) | VNode; + children?: any; + [customProp: string]: any; +} + +export interface AntTreeNode extends DefineComponent {} + +export interface AntTreeNodeBaseEvent { + node: AntTreeNode; + nativeEvent: MouseEvent; +} + +export interface AntTreeNodeCheckedEvent extends AntTreeNodeBaseEvent { + event: 'check'; + checked?: boolean; + checkedNodes?: AntTreeNode[]; +} + +export interface AntTreeNodeSelectedEvent extends AntTreeNodeBaseEvent { + event: 'select'; + selected?: boolean; + selectedNodes?: DataNode[]; +} + +export interface AntTreeNodeExpandedEvent extends AntTreeNodeBaseEvent { + expanded?: boolean; +} + +export interface AntTreeNodeMouseEvent { + node: AntTreeNode; event: DragEvent; - expandedKeys: (string | number)[]; - node: Record; } -export interface DropEvent { - dragNode: Record; - dragNodesKeys: (string | number)[]; +export interface AntTreeNodeDragEnterEvent extends AntTreeNodeMouseEvent { + expandedKeys: Key[]; +} + +export interface AntTreeNodeDropEvent { + node: AntTreeNode; + dragNode: AntTreeNode; + dragNodesKeys: Key[]; dropPosition: number; - dropToGap: boolean; - event: DragEvent; - node: Record; + dropToGap?: boolean; + event: MouseEvent; } -function TreeProps() { +// [Legacy] Compatible for v2 +export type TreeDataItem = DataNode; + +export const treeProps = () => { return { - showLine: PropTypes.looseBool, + ...vcTreeProps(), + showLine: { type: Boolean, default: undefined }, /** 是否支持多选 */ - multiple: PropTypes.looseBool, + multiple: { type: Boolean, default: undefined }, /** 是否自动展开父节点 */ - autoExpandParent: PropTypes.looseBool, + autoExpandParent: { type: Boolean, default: undefined }, /** checkable状态下节点选择完全受控(父子节点选中状态不再关联)*/ - checkStrictly: PropTypes.looseBool, + checkStrictly: { type: Boolean, default: undefined }, /** 是否支持选中 */ - checkable: PropTypes.looseBool, + checkable: { type: Boolean, default: undefined }, /** 是否禁用树 */ - disabled: PropTypes.looseBool, + disabled: { type: Boolean, default: undefined }, /** 默认展开所有树节点 */ - defaultExpandAll: PropTypes.looseBool, + defaultExpandAll: { type: Boolean, default: undefined }, /** 默认展开对应树节点 */ - defaultExpandParent: PropTypes.looseBool, + defaultExpandParent: { type: Boolean, default: undefined }, /** 默认展开指定的树节点 */ - defaultExpandedKeys: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - ), + defaultExpandedKeys: { type: Array as PropType }, /** (受控)展开指定的树节点 */ - expandedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + expandedKeys: { type: Array as PropType }, /** (受控)选中复选框的树节点 */ - checkedKeys: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), - PropTypes.shape({ - checked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), - halfChecked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), - }).loose, - ]), + checkedKeys: { + type: [Array, Object] as PropType, + }, /** 默认选中复选框的树节点 */ - defaultCheckedKeys: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - ), + defaultCheckedKeys: { type: Array as PropType }, /** (受控)设置选中的树节点 */ - selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + selectedKeys: { type: Array as PropType }, /** 默认选中的树节点 */ - defaultSelectedKeys: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - ), - selectable: PropTypes.looseBool, + defaultSelectedKeys: { type: Array as PropType }, + selectable: { type: Boolean, default: undefined }, /** filter some AntTreeNodes as you need. it should return true */ - filterAntTreeNode: PropTypes.func, - /** 异步加载数据 */ - loadData: PropTypes.func, - loadedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), - // onLoaded: (loadedKeys: string[], info: { event: 'load', node: AntTreeNode; }) => void, - /** 响应右键点击 */ - // onRightClick: (options: AntTreeNodeMouseEvent) => void, - /** 设置节点可拖拽(IE>8)*/ - draggable: PropTypes.looseBool, - // /** 开始拖拽时调用 */ - // onDragStart: (options: AntTreeNodeMouseEvent) => void, - // /** dragenter 触发时调用 */ - // onDragEnter: (options: AntTreeNodeMouseEvent) => void, - // /** dragover 触发时调用 */ - // onDragOver: (options: AntTreeNodeMouseEvent) => void, - // /** dragleave 触发时调用 */ - // onDragLeave: (options: AntTreeNodeMouseEvent) => void, - // /** drop 触发时调用 */ - // onDrop: (options: AntTreeNodeMouseEvent) => void, - showIcon: PropTypes.looseBool, - icon: PropTypes.func, + filterAntTreeNode: { type: Function as PropType<(node: AntTreeNode) => boolean> }, + loadedKeys: { type: Array as PropType }, + draggable: { type: Boolean, default: undefined }, + showIcon: { type: Boolean, default: undefined }, + icon: { type: Function as PropType<(nodeProps: AntdTreeNodeAttribute) => any> }, switcherIcon: PropTypes.any, prefixCls: PropTypes.string, - filterTreeNode: PropTypes.func, - openAnimation: PropTypes.any, - treeData: { - type: Array as PropType, - }, /** * @default{title,key,children} + * deprecated, please use `fieldNames` instead * 替换treeNode中 title,key,children字段为treeData中对应的字段 */ - replaceFields: PropTypes.object, - blockNode: PropTypes.looseBool, - /** 展开/收起节点时触发 */ - onExpand: PropTypes.func, - /** 点击复选框触发 */ - onCheck: PropTypes.func, - /** 点击树节点触发 */ - onSelect: PropTypes.func, - /** 单击树节点触发 */ - onClick: PropTypes.func, - /** 双击树节点触发 */ - onDoubleclick: PropTypes.func, - onDblclick: PropTypes.func, - 'onUpdate:selectedKeys': PropTypes.func, - 'onUpdate:checkedKeys': PropTypes.func, - 'onUpdate:expandedKeys': PropTypes.func, + replaceFields: { type: Object as PropType }, + blockNode: { type: Boolean, default: undefined }, }; -} +}; -export { TreeProps }; +export type TreeProps = Partial>>; export default defineComponent({ name: 'ATree', inheritAttrs: false, - props: initDefaultProps(TreeProps(), { + props: initDefaultProps(treeProps(), { checkable: false, + selectable: true, showIcon: false, openAnimation: { ...animation, - appear: null, + appear: false, }, blockNode: false, }), - setup() { - return { - tree: null, - configProvider: inject('configProvider', defaultConfigProvider), + slots: ['icon', 'title', 'switcherIcon'], + emits: [ + 'update:selectedKeys', + 'update:checkedKeys', + 'update:expandedKeys', + 'expand', + 'select', + 'check', + ], + TreeNode, + setup(props, { attrs, expose, emit, slots }) { + const { prefixCls, direction, virtual } = useConfigInject('tree', props); + const tree = ref(); + expose({ + tree, + }); + + const handleCheck: TreeProps['onCheck'] = (checkedObjOrKeys, eventObj) => { + emit('update:checkedKeys', checkedObjOrKeys); + emit('check', checkedObjOrKeys, eventObj); + }; + const handleExpand: TreeProps['onExpand'] = (expandedKeys, eventObj) => { + emit('update:expandedKeys', expandedKeys); + emit('expand', expandedKeys, eventObj); + }; + const handleSelect: TreeProps['onSelect'] = (selectedKeys, eventObj) => { + emit('update:selectedKeys', selectedKeys); + emit('select', selectedKeys, eventObj); + }; + return () => { + const { + showIcon, + showLine, + switcherIcon = slots.switcherIcon?.(), + icon = slots.icon, + blockNode, + checkable, + selectable, + fieldNames, + replaceFields, + } = props; + const newProps = { + ...attrs, + ...props, + showLine: Boolean(showLine), + dropIndicatorRender, + fieldNames: fieldNames || (replaceFields as FieldNames), + icon, + }; + + return ( + + renderSwitcherIcon(prefixCls.value, switcherIcon, showLine, nodeProps) + } + onCheck={handleCheck} + onExpand={handleExpand} + onSelect={handleSelect} + v-slots={{ + checkable: () => , + }} + children={filterEmpty(slots.default?.())} + > + ); }; }, - TreeNode, - methods: { - renderSwitcherIcon(prefixCls: string, switcherIcon: VNode, { isLeaf, loading, expanded }) { - const { showLine } = this.$props; - if (loading) { - return ; - } + // methods: { + // renderSwitcherIcon(prefixCls: string, switcherIcon: VNode, { isLeaf, loading, expanded }) { + // const { showLine } = this.$props; + // if (loading) { + // return ; + // } - if (isLeaf) { - return showLine ? : null; - } - const switcherCls = `${prefixCls}-switcher-icon`; - if (switcherIcon) { - return cloneElement(switcherIcon, { - class: switcherCls, - }); - } - return showLine ? ( - expanded ? ( - - ) : ( - - ) - ) : ( - - ); - }, - updateTreeData(treeData: TreeDataItem[]) { - const { $slots } = this; - const defaultFields = { children: 'children', title: 'title', key: 'key' }; - const replaceFields = { ...defaultFields, ...this.$props.replaceFields }; - return treeData.map(item => { - const key = item[replaceFields.key]; - const children = item[replaceFields.children]; - const { slots = {}, class: cls, style, ...restProps } = item; - const treeNodeProps = { - ...restProps, - icon: $slots[slots.icon] || restProps.icon, - switcherIcon: $slots[slots.switcherIcon] || restProps.switcherIcon, - title: $slots[slots.title] || $slots.title || restProps[replaceFields.title], - dataRef: item, - key, - class: cls, - style, - }; - if (children) { - return { ...treeNodeProps, children: this.updateTreeData(children) }; - } - return treeNodeProps; - }); - }, - setTreeRef(node: VNode) { - this.tree = node; - }, - handleCheck(checkedObj: (number | string)[], eventObj: CheckEvent) { - this.$emit('update:checkedKeys', checkedObj); - this.$emit('check', checkedObj, eventObj); - }, - handleExpand(expandedKeys: (number | string)[], eventObj: ExpendEvent) { - this.$emit('update:expandedKeys', expandedKeys); - this.$emit('expand', expandedKeys, eventObj); - }, - handleSelect(selectedKeys: (number | string)[], eventObj: SelectEvent) { - this.$emit('update:selectedKeys', selectedKeys); - this.$emit('select', selectedKeys, eventObj); - }, - }, - render() { - const props = getOptionProps(this); - const { prefixCls: customizePrefixCls, showIcon, treeNodes, blockNode } = props; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('tree', customizePrefixCls); - const switcherIcon = getComponent(this, 'switcherIcon'); - const checkable = props.checkable; - let treeData = props.treeData || treeNodes; - if (treeData) { - treeData = this.updateTreeData(treeData); - } - const { class: className, ...restAttrs } = this.$attrs; - const vcTreeProps = { - ...props, - prefixCls, - checkable: checkable ? : checkable, - children: getSlot(this), - switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps), - ref: this.setTreeRef, - ...restAttrs, - class: classNames(className, { - [`${prefixCls}-icon-hide`]: !showIcon, - [`${prefixCls}-block-node`]: blockNode, - }), - onCheck: this.handleCheck, - onExpand: this.handleExpand, - onSelect: this.handleSelect, - } as Record; - if (treeData) { - vcTreeProps.treeData = treeData; - } - return ; - }, + // if (isLeaf) { + // return showLine ? : null; + // } + // const switcherCls = `${prefixCls}-switcher-icon`; + // if (switcherIcon) { + // return cloneElement(switcherIcon, { + // class: switcherCls, + // }); + // } + // return showLine ? ( + // expanded ? ( + // + // ) : ( + // + // ) + // ) : ( + // + // ); + // }, + // updateTreeData(treeData: TreeDataItem[]) { + // const { $slots } = this; + // const defaultFields = { children: 'children', title: 'title', key: 'key' }; + // const replaceFields = { ...defaultFields, ...this.$props.replaceFields }; + // return treeData.map(item => { + // const key = item[replaceFields.key]; + // const children = item[replaceFields.children]; + // const { slots = {}, class: cls, style, ...restProps } = item; + // const treeNodeProps = { + // ...restProps, + // icon: $slots[slots.icon] || restProps.icon, + // switcherIcon: $slots[slots.switcherIcon] || restProps.switcherIcon, + // title: $slots[slots.title] || $slots.title || restProps[replaceFields.title], + // dataRef: item, + // key, + // class: cls, + // style, + // }; + // if (children) { + // return { ...treeNodeProps, children: this.updateTreeData(children) }; + // } + // return treeNodeProps; + // }); + // }, + // setTreeRef(node: VNode) { + // this.tree = node; + // }, + // handleCheck(checkedObj: (number | string)[], eventObj: CheckEvent) { + // this.$emit('update:checkedKeys', checkedObj); + // this.$emit('check', checkedObj, eventObj); + // }, + // handleExpand(expandedKeys: (number | string)[], eventObj: ExpendEvent) { + // this.$emit('update:expandedKeys', expandedKeys); + // this.$emit('expand', expandedKeys, eventObj); + // }, + // handleSelect(selectedKeys: (number | string)[], eventObj: SelectEvent) { + // this.$emit('update:selectedKeys', selectedKeys); + // this.$emit('select', selectedKeys, eventObj); + // }, + // }, + // render() { + // const props = getOptionProps(this); + // const { prefixCls: customizePrefixCls, showIcon, treeNodes, blockNode } = props; + // const getPrefixCls = this.configProvider.getPrefixCls; + // const prefixCls = getPrefixCls('tree', customizePrefixCls); + // const switcherIcon = getComponent(this, 'switcherIcon'); + // const checkable = props.checkable; + // let treeData = props.treeData || treeNodes; + // if (treeData) { + // treeData = this.updateTreeData(treeData); + // } + // const { class: className, ...restAttrs } = this.$attrs; + // const vcTreeProps = { + // ...props, + // prefixCls, + // checkable: checkable ? : checkable, + // children: getSlot(this), + // switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps), + // ref: this.setTreeRef, + // ...restAttrs, + // class: classNames(className, { + // [`${prefixCls}-icon-hide`]: !showIcon, + // [`${prefixCls}-block-node`]: blockNode, + // }), + // onCheck: this.handleCheck, + // onExpand: this.handleExpand, + // onSelect: this.handleSelect, + // } as Record; + // if (treeData) { + // vcTreeProps.treeData = treeData; + // } + // return ; + // }, }); diff --git a/components/tree/index.tsx b/components/tree/index.tsx index 446ceb119..54e4e9940 100644 --- a/components/tree/index.tsx +++ b/components/tree/index.tsx @@ -2,6 +2,21 @@ import type { App, Plugin } from 'vue'; import Tree from './Tree'; import DirectoryTree from './DirectoryTree'; +export { EventDataNode, DataNode } from '../vc-tree/interface'; + +export { + TreeProps, + AntTreeNode, + AntTreeNodeMouseEvent, + AntTreeNodeExpandedEvent, + AntTreeNodeCheckedEvent, + AntTreeNodeSelectedEvent, + AntdTreeNodeAttribute, + AntTreeNodeProps, +} from './Tree'; + +export { ExpandAction as DirectoryTreeExpandAction, DirectoryTreeProps } from './DirectoryTree'; + Tree.TreeNode.name = 'ATreeNode'; Tree.DirectoryTree = DirectoryTree; /* istanbul ignore next */ diff --git a/components/tree/style/directory.less b/components/tree/style/directory.less index 91caf33c0..51baf617c 100644 --- a/components/tree/style/directory.less +++ b/components/tree/style/directory.less @@ -2,93 +2,70 @@ @tree-prefix-cls: ~'@{ant-prefix}-tree'; -.@{tree-prefix-cls} { - &.@{tree-prefix-cls}-directory { +.@{tree-prefix-cls}.@{tree-prefix-cls}-directory { + // ================== TreeNode ================== + .@{tree-prefix-cls}-treenode { position: relative; - // Stretch selector width - > li, - .@{tree-prefix-cls}-child-tree > li { - span { - &.@{tree-prefix-cls}-switcher { - position: relative; - z-index: 1; + // Hover color + &::before { + position: absolute; + top: 0; + right: 0; + bottom: 4px; + left: 0; + transition: background-color 0.3s; + content: ''; + pointer-events: none; + } - &.@{tree-prefix-cls}-switcher-noop { - pointer-events: none; - } - } + &:hover { + &::before { + background: @item-hover-bg; + } + } - &.@{tree-prefix-cls}-checkbox { - position: relative; - z-index: 1; - } + // Elements + > * { + z-index: 1; + } - &.@{tree-prefix-cls}-node-content-wrapper { - border-radius: 0; - user-select: none; + // >>> Switcher + .@{tree-prefix-cls}-switcher { + transition: color 0.3s; + } - &:hover { - background: transparent; + // >>> Title + .@{tree-prefix-cls}-node-content-wrapper { + border-radius: 0; + user-select: none; - &::before { - background: @item-hover-bg; - } - } - - &.@{tree-prefix-cls}-node-selected { - color: @tree-directory-selected-color; - background: transparent; - } - - &::before { - position: absolute; - right: 0; - left: 0; - height: @tree-title-height; - transition: all 0.3s; - content: ''; - } - - > span { - position: relative; - z-index: 1; - } - } + &:hover { + background: transparent; } - &.@{tree-prefix-cls}-treenode-selected { - > span { - &.@{tree-prefix-cls}-switcher { - color: @tree-directory-selected-color; - } + &.@{tree-prefix-cls}-node-selected { + color: @tree-directory-selected-color; + background: transparent; + } + } - &.@{tree-prefix-cls}-checkbox { - .@{tree-prefix-cls}-checkbox-inner { - border-color: @primary-color; - } + // ============= Selected ============= + &-selected { + &:hover::before, + &::before { + background: @tree-directory-selected-bg; + } - &.@{tree-prefix-cls}-checkbox-checked { - &::after { - border-color: @checkbox-check-color; - } + // >>> Switcher + .@{tree-prefix-cls}-switcher { + color: @tree-directory-selected-color; + } - .@{tree-prefix-cls}-checkbox-inner { - background: @checkbox-check-color; - - &::after { - border-color: @primary-color; - } - } - } - } - - &.@{tree-prefix-cls}-node-content-wrapper { - &::before { - background: @tree-directory-selected-bg; - } - } - } + // >>> Title + .@{tree-prefix-cls}-node-content-wrapper { + color: @tree-directory-selected-color; + background: transparent; } } } diff --git a/components/tree/style/index.less b/components/tree/style/index.less index 31f57bf09..a2c0067f2 100644 --- a/components/tree/style/index.less +++ b/components/tree/style/index.less @@ -5,276 +5,12 @@ @import './directory'; @tree-prefix-cls: ~'@{ant-prefix}-tree'; -@tree-showline-icon-color: @text-color-secondary; -@tree-node-padding: 4px; +@tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode'; .antCheckboxFn(@checkbox-prefix-cls: ~'@{ant-prefix}-tree-checkbox'); .@{tree-prefix-cls} { - /* see https://github.com/ant-design/ant-design/issues/16259 */ - &-checkbox-checked::after { - position: absolute; - top: 16.67%; - left: 0; - width: 100%; - height: 66.67%; - } - - .reset-component(); - - margin: 0; - padding: 0; - - ol, - ul { - margin: 0; - padding: 0; - list-style: none; - } - - li { - margin: 0; - padding: @tree-node-padding 0; - white-space: nowrap; - list-style: none; - outline: 0; - span[draggable], - span[draggable='true'] { - line-height: @tree-title-height - 4px; - border-top: 2px transparent solid; - border-bottom: 2px transparent solid; - user-select: none; - /* Required to make elements draggable in old WebKit */ - -khtml-user-drag: element; - -webkit-user-drag: element; - } - &.drag-over { - > span[draggable] { - color: white; - background-color: @primary-color; - opacity: 0.8; - } - } - &.drag-over-gap-top { - > span[draggable] { - border-top-color: @primary-color; - } - } - &.drag-over-gap-bottom { - > span[draggable] { - border-bottom-color: @primary-color; - } - } - &.filter-node { - > span { - color: @highlight-color !important; - font-weight: 500 !important; - } - } - - // When node is loading - &.@{tree-prefix-cls}-treenode-loading { - span { - &.@{tree-prefix-cls}-switcher { - &.@{tree-prefix-cls}-switcher_open, - &.@{tree-prefix-cls}-switcher_close { - .@{tree-prefix-cls}-switcher-loading-icon { - position: absolute; - left: 0; - display: inline-block; - width: 24px; - height: @tree-title-height; - color: @primary-color; - font-size: 14px; - transform: none; - svg { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - margin: auto; - } - } - - :root &::after { - opacity: 0; - } - } - } - } - } - - ul { - margin: 0; - padding: 0 0 0 @tree-child-padding; - } - .@{tree-prefix-cls}-node-content-wrapper { - display: inline-block; - height: @tree-title-height; - margin: 0; - padding: 0 5px; - color: @text-color; - line-height: @tree-title-height; - text-decoration: none; - vertical-align: top; - border-radius: @border-radius-sm; - cursor: pointer; - transition: all 0.3s; - &:hover { - background-color: @tree-node-hover-bg; - } - &.@{tree-prefix-cls}-node-selected { - background-color: @tree-node-selected-bg; - } - } - span { - &.@{tree-prefix-cls}-checkbox { - top: initial; - height: @tree-title-height; - margin: 0 4px 0 2px; - padding: ((@tree-title-height - 16px) / 2) 0; - } - &.@{tree-prefix-cls}-switcher, - &.@{tree-prefix-cls}-iconEle { - display: inline-block; - width: 24px; - height: @tree-title-height; - margin: 0; - line-height: @tree-title-height; - text-align: center; - vertical-align: top; - border: 0 none; - outline: none; - cursor: pointer; - } - - &.@{tree-prefix-cls}-iconEle:empty { - display: none; - } - - &.@{tree-prefix-cls}-switcher { - position: relative; - - &.@{tree-prefix-cls}-switcher-noop { - cursor: default; - } - &.@{tree-prefix-cls}-switcher_open { - .antTreeSwitcherIcon(); - } - &.@{tree-prefix-cls}-switcher_close { - .antTreeSwitcherIcon(); - .@{tree-prefix-cls}-switcher-icon { - svg { - transform: rotate(-90deg); - } - } - } - } - } - &:last-child > span { - &.@{tree-prefix-cls}-switcher, - &.@{tree-prefix-cls}-iconEle { - &::before { - display: none; - } - } - } - } - - > li { - &:first-child { - padding-top: 7px; - } - &:last-child { - padding-bottom: 7px; - } - } - &-child-tree { - // https://github.com/ant-design/ant-design/issues/14958 - > li { - // Provide additional padding between top child node and parent node - &:first-child { - padding-top: 2 * @tree-node-padding; - } - - // Hide additional padding between last child node and next parent node - &:last-child { - padding-bottom: 0; - } - } - } - li&-treenode-disabled { - > span:not(.@{tree-prefix-cls}-switcher), - > .@{tree-prefix-cls}-node-content-wrapper, - > .@{tree-prefix-cls}-node-content-wrapper span { - color: @disabled-color; - cursor: not-allowed; - } - > .@{tree-prefix-cls}-node-content-wrapper:hover { - background: transparent; - } - } - &-icon__open { - margin-right: 2px; - vertical-align: top; - } - &-icon__close { - margin-right: 2px; - vertical-align: top; - } - // Tree with line - &&-show-line { - li { - position: relative; - span { - &.@{tree-prefix-cls}-switcher { - color: @tree-showline-icon-color; - background: @component-background; - &.@{tree-prefix-cls}-switcher-noop { - .antTreeShowLineIcon('tree-doc-icon'); - } - &.@{tree-prefix-cls}-switcher_open { - .antTreeShowLineIcon('tree-showline-open-icon'); - } - &.@{tree-prefix-cls}-switcher_close { - .antTreeShowLineIcon('tree-showline-close-icon'); - } - } - } - } - li:not(:last-child)::before { - position: absolute; - left: 12px; - width: 1px; - height: 100%; - height: calc(100% - 22px); // Remove additional height if support - margin: 22px 0 0; - border-left: 1px solid @border-color-base; - content: ' '; - } - } - - &.@{tree-prefix-cls}-icon-hide { - .@{tree-prefix-cls}-treenode-loading { - .@{tree-prefix-cls}-iconEle { - display: none; - } - } - } - - &.@{tree-prefix-cls}-block-node { - li { - .@{tree-prefix-cls}-node-content-wrapper { - width: ~'calc(100% - 24px)'; - } - span { - &.@{tree-prefix-cls}-checkbox { - + .@{tree-prefix-cls}-node-content-wrapper { - width: ~'calc(100% - 46px)'; - } - } - } - } - } + .antTreeFn(@tree-prefix-cls); } + +@import './rtl'; diff --git a/components/tree/style/index.ts b/components/tree/style/index.tsx similarity index 100% rename from components/tree/style/index.ts rename to components/tree/style/index.tsx diff --git a/components/tree/style/mixin.less b/components/tree/style/mixin.less index 929cecdee..197c20662 100644 --- a/components/tree/style/mixin.less +++ b/components/tree/style/mixin.less @@ -1,29 +1,274 @@ @import '../../style/mixins/index'; @tree-prefix-cls: ~'@{ant-prefix}-tree'; -@tree-select-prefix-cls: ~'@{ant-prefix}-select'; +@tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode'; +@select-tree-prefix-cls: ~'@{ant-prefix}-select-tree'; +@tree-motion: ~'@{ant-prefix}-motion-collapse'; +@tree-node-padding: (@padding-xs / 2); +@tree-node-hightlight-color: inherit; .antTreeSwitcherIcon(@type: 'tree-default-open-icon') { .@{tree-prefix-cls}-switcher-icon, - .@{tree-select-prefix-cls}-switcher-icon { - .iconfont-size-under-12px(10px); - + .@{select-tree-prefix-cls}-switcher-icon { display: inline-block; - font-weight: bold; + font-size: 10px; + vertical-align: baseline; svg { transition: transform 0.3s; } } } -.antTreeShowLineIcon(@type) { - .@{tree-prefix-cls}-switcher-icon, - .@{tree-select-prefix-cls}-switcher-icon { - display: inline-block; - font-weight: normal; - font-size: 12px; - svg { - transition: transform 0.3s; +.drop-indicator() { + .@{tree-prefix-cls}-drop-indicator { + position: absolute; + // it should displayed over the following node + z-index: 1; + height: 2px; + background-color: @primary-color; + border-radius: 1px; + pointer-events: none; + &::after { + position: absolute; + top: -3px; + left: -6px; + width: 8px; + height: 8px; + background-color: transparent; + border: 2px solid @primary-color; + border-radius: 50%; + content: ''; + } + } +} + +.antTreeFn(@custom-tree-prefix-cls) { + @custom-tree-node-prefix-cls: ~'@{custom-tree-prefix-cls}-treenode'; + .reset-component(); + background: @tree-bg; + border-radius: @border-radius-base; + transition: background-color 0.3s; + + &-focused:not(:hover):not(&-active-focused) { + background: @primary-1; + } + + // =================== Virtual List =================== + &-list-holder-inner { + align-items: flex-start; + } + + &.@{custom-tree-prefix-cls}-block-node { + .@{custom-tree-prefix-cls}-list-holder-inner { + align-items: stretch; + + // >>> Title + .@{custom-tree-prefix-cls}-node-content-wrapper { + flex: auto; + } + } + } + + // ===================== TreeNode ===================== + .@{custom-tree-node-prefix-cls} { + display: flex; + align-items: flex-start; + padding: 0 0 @tree-node-padding 0; + outline: none; + // Disabled + &-disabled { + // >>> Title + .@{custom-tree-prefix-cls}-node-content-wrapper { + color: @disabled-color; + cursor: not-allowed; + + &:hover { + background: transparent; + } + } + } + + &-active .@{custom-tree-prefix-cls}-node-content-wrapper { + background: @tree-node-hover-bg; + } + + &:not(&-disabled).filter-node .@{custom-tree-prefix-cls}-title { + color: @tree-node-hightlight-color; + font-weight: 500; + } + } + + // >>> Indent + &-indent { + align-self: stretch; + white-space: nowrap; + user-select: none; + + &-unit { + display: inline-block; + width: @tree-title-height; + } + } + + // >>> Switcher + &-switcher { + .antTreeSwitcherIcon(); + position: relative; + flex: none; + align-self: stretch; + width: @tree-title-height; + margin: 0; + line-height: @tree-title-height; + text-align: center; + cursor: pointer; + user-select: none; + + &-noop { + cursor: default; + } + + &_close { + .@{custom-tree-prefix-cls}-switcher-icon { + svg { + transform: rotate(-90deg); + } + } + } + + &-loading-icon { + color: @primary-color; + } + + &-leaf-line { + position: relative; + z-index: 1; + display: inline-block; + width: 100%; + height: 100%; + &::before { + position: absolute; + top: 0; + bottom: -@tree-node-padding; + margin-left: -1px; + border-left: 1px solid @normal-color; + content: ' '; + } + &::after { + position: absolute; + width: @tree-title-height - 14px; + height: @tree-title-height - 10px; + margin-left: -1px; + border-bottom: 1px solid @normal-color; + content: ' '; + } + } + } + + // >>> Checkbox + &-checkbox { + top: initial; + margin: ((@tree-title-height - @checkbox-size) / 2) 8px 0 0; + } + + // >>> Title + & &-node-content-wrapper { + position: relative; + z-index: auto; + min-height: @tree-title-height; + margin: 0; + padding: 0 4px; + color: inherit; + line-height: @tree-title-height; + background: transparent; + border-radius: @border-radius-base; + cursor: pointer; + transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s; + + &:hover { + background-color: @tree-node-hover-bg; + } + + &.@{custom-tree-prefix-cls}-node-selected { + background-color: @tree-node-selected-bg; + } + + // Icon + .@{custom-tree-prefix-cls}-iconEle { + display: inline-block; + width: @tree-title-height; + height: @tree-title-height; + line-height: @tree-title-height; + text-align: center; + vertical-align: top; + &:empty { + display: none; + } + } + } + + // https://github.com/ant-design/ant-design/issues/28217 + &-unselectable &-node-content-wrapper:hover { + background-color: transparent; + } + + // ==================== Draggable ===================== + &-node-content-wrapper[draggable='true'] { + line-height: @tree-title-height; + user-select: none; + + .drop-indicator(); + } + + .@{custom-tree-node-prefix-cls}.drop-container { + > [draggable] { + box-shadow: 0 0 0 2px @primary-color; + } + } + + // ==================== Show Line ===================== + &-show-line { + // ================ Indent lines ================ + .@{custom-tree-prefix-cls}-indent { + &-unit { + position: relative; + height: 100%; + + &::before { + position: absolute; + top: 0; + right: (@tree-title-height / 2); + bottom: -@tree-node-padding; + border-right: 1px solid @border-color-base; + content: ''; + } + + &-end { + &::before { + display: none; + } + } + } + } + + // ============== Cover Background ============== + .@{custom-tree-prefix-cls}-switcher { + background: @component-background; + + &-line-icon { + vertical-align: -0.225em; + } + } + } +} + +.@{tree-node-prefix-cls}-leaf-last { + .@{tree-prefix-cls}-switcher { + &-leaf-line { + &::before { + top: auto !important; + bottom: auto !important; + height: @tree-title-height - 10px !important; + } } } } diff --git a/components/tree/style/rtl.less b/components/tree/style/rtl.less new file mode 100644 index 000000000..bc985c956 --- /dev/null +++ b/components/tree/style/rtl.less @@ -0,0 +1,72 @@ +@import '../../style/themes/index'; +@import '../../style/mixins/index'; +@import '../../checkbox/style/mixin'; + +@tree-prefix-cls: ~'@{ant-prefix}-tree'; +@select-tree-prefix-cls: ~'@{ant-prefix}-select-tree'; +@tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode'; + +.@{tree-prefix-cls} { + &-rtl { + direction: rtl; + .@{tree-prefix-cls}-node-content-wrapper[draggable='true'] { + .@{tree-prefix-cls}-drop-indicator { + &::after { + right: -6px; + left: unset; + } + } + } + } + + // ===================== TreeNode ===================== + .@{tree-node-prefix-cls} { + &-rtl { + direction: rtl; + } + } + + // >>> Switcher + &-switcher { + &_close { + .@{tree-prefix-cls}-switcher-icon { + svg { + .@{tree-prefix-cls}-rtl & { + transform: rotate(90deg); + } + } + } + } + } + // ==================== Show Line ===================== + &-show-line { + // ================ Indent lines ================ + .@{tree-prefix-cls}-indent { + &-unit { + &::before { + .@{tree-prefix-cls}-rtl& { + right: auto; + left: -(@tree-title-height / 2) - 1px; + border-right: none; + border-left: 1px solid @border-color-base; + } + } + } + } + } + // >>> Checkbox + &-checkbox { + .@{tree-prefix-cls}-rtl& { + margin: ((@tree-title-height - @checkbox-size) / 2) 0 0 8px; + } + } +} + +.@{select-tree-prefix-cls} { + // >>> Checkbox + &-checkbox { + .@{tree-prefix-cls}-select-dropdown-rtl & { + margin: ((@tree-title-height - @checkbox-size) / 2) 0 0 8px; + } + } +} diff --git a/components/tree/utils/dictUtil.ts b/components/tree/utils/dictUtil.ts new file mode 100644 index 000000000..bd1f0a339 --- /dev/null +++ b/components/tree/utils/dictUtil.ts @@ -0,0 +1,92 @@ +import type { DataNode, Key } from 'ant-design-vue/es/vc-tree/interface'; + +enum Record { + None, + Start, + End, +} + +function traverseNodesKey( + treeData: DataNode[], + callback: (key: Key | number | null, node: DataNode) => boolean, +) { + function processNode(dataNode: DataNode) { + const { key, children } = dataNode; + if (callback(key, dataNode) !== false) { + traverseNodesKey(children || [], callback); + } + } + + treeData.forEach(processNode); +} + +/** 计算选中范围,只考虑expanded情况以优化性能 */ +export function calcRangeKeys({ + treeData, + expandedKeys, + startKey, + endKey, +}: { + treeData: DataNode[]; + expandedKeys: Key[]; + startKey?: Key; + endKey?: Key; +}): Key[] { + const keys: Key[] = []; + let record: Record = Record.None; + + if (startKey && startKey === endKey) { + return [startKey]; + } + if (!startKey || !endKey) { + return []; + } + + function matchKey(key: Key) { + return key === startKey || key === endKey; + } + + traverseNodesKey(treeData, (key: Key) => { + if (record === Record.End) { + return false; + } + + if (matchKey(key)) { + // Match test + keys.push(key); + + if (record === Record.None) { + record = Record.Start; + } else if (record === Record.Start) { + record = Record.End; + return false; + } + } else if (record === Record.Start) { + // Append selection + keys.push(key); + } + + if (expandedKeys.indexOf(key) === -1) { + return false; + } + + return true; + }); + + return keys; +} + +export function convertDirectoryKeysToNodes(treeData: DataNode[], keys: Key[]) { + const restKeys: Key[] = [...keys]; + const nodes: DataNode[] = []; + traverseNodesKey(treeData, (key: Key, node: DataNode) => { + const index = restKeys.indexOf(key); + if (index !== -1) { + nodes.push(node); + restKeys.splice(index, 1); + } + + return !!restKeys.length; + }); + return nodes; +} diff --git a/components/tree/utils/dropIndicator.tsx b/components/tree/utils/dropIndicator.tsx new file mode 100644 index 000000000..a69569908 --- /dev/null +++ b/components/tree/utils/dropIndicator.tsx @@ -0,0 +1,33 @@ +import { CSSProperties } from 'vue'; + +export const offset = 4; + +export default function dropIndicatorRender(props: { + dropPosition: -1 | 0 | 1; + dropLevelOffset: number; + indent: number; + prefixCls: string; + direction: 'ltr' | 'rtl'; +}) { + const { dropPosition, dropLevelOffset, prefixCls, indent, direction = 'ltr' } = props; + const startPosition = direction === 'ltr' ? 'left' : 'right'; + const endPosition = direction === 'ltr' ? 'right' : 'left'; + const style: CSSProperties = { + [startPosition]: `${-dropLevelOffset * indent + offset}px`, + [endPosition]: 0, + }; + switch (dropPosition) { + case -1: + style.top = `${-3}px`; + break; + case 1: + style.bottom = `${-3}px`; + break; + default: + // dropPosition === 0 + style.bottom = `${-3}px`; + style[startPosition] = `${indent + offset}px`; + break; + } + return
    ; +} diff --git a/components/tree/utils/iconUtil.tsx b/components/tree/utils/iconUtil.tsx new file mode 100644 index 000000000..59dc2f8cb --- /dev/null +++ b/components/tree/utils/iconUtil.tsx @@ -0,0 +1,52 @@ +import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; +import FileOutlined from '@ant-design/icons-vue/FileOutlined'; +import MinusSquareOutlined from '@ant-design/icons-vue/MinusSquareOutlined'; +import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined'; +import CaretDownFilled from '@ant-design/icons-vue/CaretDownFilled'; +import { AntTreeNodeProps } from '../Tree'; +import { isValidElement } from 'ant-design-vue/es/_util/props-util'; + +import { cloneVNode } from 'vue'; + +export default function renderSwitcherIcon( + prefixCls: string, + switcherIcon: any, + showLine: boolean | { showLeafIcon: boolean } | undefined, + { isLeaf, expanded, loading }: AntTreeNodeProps, +) { + if (loading) { + return ; + } + let showLeafIcon; + if (showLine && typeof showLine === 'object') { + showLeafIcon = showLine.showLeafIcon; + } + if (isLeaf) { + if (showLine) { + if (typeof showLine === 'object' && !showLeafIcon) { + return ; + } + return ; + } + return null; + } + const switcherCls = `${prefixCls}-switcher-icon`; + if (isValidElement(switcherIcon)) { + return cloneVNode(switcherIcon, { + class: switcherCls, + }); + } + + if (switcherIcon) { + return switcherIcon; + } + + if (showLine) { + return expanded ? ( + + ) : ( + + ); + } + return ; +} diff --git a/components/upload/Upload.tsx b/components/upload/Upload.tsx index 4e1041d73..686b01653 100644 --- a/components/upload/Upload.tsx +++ b/components/upload/Upload.tsx @@ -13,7 +13,7 @@ import UploadList from './UploadList'; import { UploadProps } from './interface'; import { T, fileToObject, genPercentAdd, getFileItem, removeFileItem } from './utils'; import { defineComponent, inject } from 'vue'; -import { getDataAndAria } from '../vc-tree/src/util'; +import { getDataAndAriaProps } from '../_util/util'; export default defineComponent({ name: 'AUpload', @@ -280,7 +280,7 @@ export default defineComponent({ [`${prefixCls}-disabled`]: disabled, }); return ( - +
    }, }, - slots: ['title', 'icon', 'switcherIcon'], + slots: ['title', 'icon', 'switcherIcon', 'checkable'], setup(props, { attrs, slots }) { const visible = ref(true); const context = useInjectTreeContext(); diff --git a/components/vc-tree/NodeList.tsx b/components/vc-tree/NodeList.tsx index 01c3a2cce..912c3a721 100644 --- a/components/vc-tree/NodeList.tsx +++ b/components/vc-tree/NodeList.tsx @@ -94,6 +94,7 @@ export default defineComponent({ name: 'NodeList', inheritAttrs: false, props: nodeListProps, + slots: ['checkable'], setup(props, { expose, attrs, slots }) { // =============================== Ref ================================ const listRef = ref(null); @@ -275,8 +276,7 @@ export default defineComponent({ itemHeight={itemHeight} prefixCls={`${prefixCls}-list`} ref={listRef} - > - {(treeNode: FlattenNode) => { + children={(treeNode: FlattenNode) => { const { pos, data: { ...restProps }, @@ -310,10 +310,11 @@ export default defineComponent({ onMousemove={() => { onActiveChange(null); }} + v-slots={slots} /> ); }} - + > ); }; diff --git a/components/vc-tree/Tree.tsx b/components/vc-tree/Tree.tsx index 654c59d7f..83de46dd3 100644 --- a/components/vc-tree/Tree.tsx +++ b/components/vc-tree/Tree.tsx @@ -32,6 +32,7 @@ import classNames from '../_util/classNames'; export default defineComponent({ name: 'Tree', inheritAttrs: false, + slots: ['checkable'], props: initDefaultProps(treeProps(), { prefixCls: 'vc-tree', showLine: false, @@ -52,7 +53,7 @@ export default defineComponent({ allowDrop: () => true, }), - setup(props, { attrs }) { + setup(props, { attrs, slots }) { const destroyed = ref(false); let delayedDragEnterLogic: Record = {}; @@ -91,6 +92,34 @@ export default defineComponent({ return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children); }); const keyEntities = ref({}); + + const focused = ref(false); + const activeKey = ref(null); + + const listChanging = ref(false); + + const fieldNames = computed(() => fillFieldNames(props.fieldNames)); + + const listRef = ref(); + + let dragStartMousePosition = null; + + let dragNode = null; + + const treeNodeRequiredProps = computed(() => { + return { + expandedKeys: expandedKeys.value || [], + selectedKeys: selectedKeys.value || [], + loadedKeys: loadedKeys.value || [], + loadingKeys: loadingKeys.value || [], + checkedKeys: checkedKeys.value || [], + halfCheckedKeys: halfCheckedKeys.value || [], + dragOverNodeKey: dragState.dragOverNodeKey, + dropPosition: dragState.dropPosition, + keyEntities: keyEntities.value, + }; + }); + watchEffect(() => { if (treeData.value) { const entitiesMap = convertDataToEntities(treeData.value, { fieldNames: fieldNames.value }); @@ -186,32 +215,6 @@ export default defineComponent({ } }); - const focused = ref(false); - const activeKey = ref(null); - - const listChanging = ref(false); - - const fieldNames = computed(() => fillFieldNames(props.fieldNames)); - - const listRef = ref(); - - let dragStartMousePosition = null; - - let dragNode = null; - - const treeNodeRequiredProps = computed(() => { - return { - expandedKeys: expandedKeys.value || [], - selectedKeys: selectedKeys.value || [], - loadedKeys: loadedKeys.value || [], - loadingKeys: loadingKeys.value || [], - checkedKeys: checkedKeys.value || [], - halfCheckedKeys: halfCheckedKeys.value || [], - dragOverNodeKey: dragState.dragOverNodeKey, - dropPosition: dragState.dropPosition, - keyEntities: keyEntities.value, - }; - }); const scrollTo: ScrollTo = scroll => { listRef.value.scrollTo(scroll); }; @@ -983,7 +986,7 @@ export default defineComponent({ checkable, checkStrictly, disabled, - motion, + openAnimation, loadData, filterTreeNode, height, @@ -1059,7 +1062,7 @@ export default defineComponent({ disabled={disabled} selectable={selectable} checkable={!!checkable} - motion={motion} + motion={openAnimation} dragging={dragging} height={height} itemHeight={itemHeight} @@ -1078,6 +1081,7 @@ export default defineComponent({ onScroll={onScroll} {...treeNodeRequiredProps.value} {...domProps} + v-slots={slots} />
    diff --git a/components/vc-tree/TreeNode.tsx b/components/vc-tree/TreeNode.tsx index da4650072..1a03081c3 100644 --- a/components/vc-tree/TreeNode.tsx +++ b/components/vc-tree/TreeNode.tsx @@ -16,7 +16,7 @@ export default defineComponent({ inheritAttrs: false, props: treeNodeProps, isTreeNode: 1, - slots: ['title', 'icon', 'switcherIcon'], + slots: ['title', 'icon', 'switcherIcon', 'checkable'], setup(props, { attrs, expose, slots }) { const dragNodeHighlight = ref(false); const context = useInjectTreeContext(); @@ -275,9 +275,6 @@ export default defineComponent({ if (!checkable) return null; - // [Legacy] Custom element should be separate with `checkable` in future - const $custom = typeof checkable !== 'boolean' ? checkable : null; - return ( - {$custom} + {slots.checkable?.()} ); }; diff --git a/components/vc-tree/contextTypes.ts b/components/vc-tree/contextTypes.ts index 36d61e174..995bf16c8 100644 --- a/components/vc-tree/contextTypes.ts +++ b/components/vc-tree/contextTypes.ts @@ -84,6 +84,7 @@ export interface TreeContextProps { const TreeContextKey: InjectionKey> = Symbol('TreeContextKey'); export const TreeContext = defineComponent({ + name: 'TreeContext', props: { value: { type: Object as PropType }, }, @@ -92,7 +93,7 @@ export const TreeContext = defineComponent({ TreeContextKey, computed(() => props.value), ); - return slots.default?.(); + return () => slots.default?.(); }, }); diff --git a/components/vc-tree/index.ts b/components/vc-tree/index.ts index 6df172093..94a53c555 100644 --- a/components/vc-tree/index.ts +++ b/components/vc-tree/index.ts @@ -1,8 +1,6 @@ +import type { TreeProps, TreeNodeProps } from './props'; import Tree from './Tree'; import TreeNode from './TreeNode'; -import type { TreeProps } from './Tree'; -import type { TreeNodeProps } from './TreeNode'; - export { TreeNode }; export type { TreeProps, TreeNodeProps }; export default Tree; diff --git a/components/vc-tree/interface.tsx b/components/vc-tree/interface.tsx index 1d277f9d0..0f3e9db93 100644 --- a/components/vc-tree/interface.tsx +++ b/components/vc-tree/interface.tsx @@ -1,4 +1,4 @@ -import { VNode } from 'vue'; +import { CSSProperties, VNode } from 'vue'; export type { ScrollTo } from '../vc-virtual-list/List'; export interface DataNode { @@ -14,8 +14,8 @@ export interface DataNode { switcherIcon?: IconType; /** Set style of TreeNode. This is not recommend if you don't have any force requirement */ - // className?: string; - // style?: CSSProperties; + class?: string; + style?: CSSProperties; } export interface EventDataNode extends DataNode { diff --git a/components/vc-tree/props.ts b/components/vc-tree/props.ts index 32513b4d0..9ac8eb0fc 100644 --- a/components/vc-tree/props.ts +++ b/components/vc-tree/props.ts @@ -5,8 +5,15 @@ import type { NodeMouseEventHandler, NodeMouseEventParams, } from './contextTypes'; -import type { DataNode, Key, FlattenNode, DataEntity, EventDataNode, Direction } from './interface'; -import { fillFieldNames } from './utils/treeUtil'; +import type { + DataNode, + Key, + FlattenNode, + DataEntity, + EventDataNode, + Direction, + FieldNames, +} from './interface'; export interface CheckInfo { event: 'check'; @@ -103,7 +110,7 @@ export const treeProps = () => ({ tabindex: Number, children: PropTypes.VNodeChild, treeData: { type: Array as PropType }, // Generate treeNode by children - fieldNames: fillFieldNames, + fieldNames: { type: Object as PropType }, showLine: { type: Boolean, default: undefined }, showIcon: { type: Boolean, default: undefined }, icon: PropTypes.any, @@ -218,7 +225,7 @@ export const treeProps = () => ({ */ onActiveChange: { type: Function as PropType<(key: Key) => void> }, filterTreeNode: { type: Function as PropType<(treeNode: EventDataNode) => boolean> }, - motion: PropTypes.any, + openAnimation: PropTypes.any, switcherIcon: PropTypes.any, // Virtual List diff --git a/examples/App.vue b/examples/App.vue index 65b1ba433..ed81c908f 100644 --- a/examples/App.vue +++ b/examples/App.vue @@ -1,39 +1,62 @@ - From 448788eaa5fbeed2380c34ef27e25a1712bdcbc4 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 17 Aug 2021 22:05:24 +0800 Subject: [PATCH 20/53] style: lint --- components/tree/Tree.tsx | 7 +++-- components/tree/utils/dropIndicator.tsx | 2 +- components/tree/utils/iconUtil.tsx | 2 +- components/vc-tree/DropIndicator.tsx | 2 +- components/vc-tree/MotionTreeNode.tsx | 8 +++-- components/vc-tree/NodeList.tsx | 2 +- components/vc-tree/Tree.tsx | 42 +++++++++++++------------ components/vc-tree/interface.tsx | 2 +- components/vc-tree/util.tsx | 4 +-- 9 files changed, 38 insertions(+), 33 deletions(-) diff --git a/components/tree/Tree.tsx b/components/tree/Tree.tsx index a1426969e..aa0f9a54b 100644 --- a/components/tree/Tree.tsx +++ b/components/tree/Tree.tsx @@ -1,4 +1,5 @@ -import { VNode, PropType, DefineComponent, ExtractPropTypes, ref } from 'vue'; +import type { VNode, PropType, DefineComponent, ExtractPropTypes } from 'vue'; +import { ref } from 'vue'; import { defineComponent } from 'vue'; import classNames from '../_util/classNames'; import VcTree, { TreeNode } from '../vc-tree'; @@ -6,7 +7,7 @@ import animation from '../_util/openAnimation'; import PropTypes from '../_util/vue-types'; import { filterEmpty } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { DataNode, FieldNames, Key } from '../vc-tree/interface'; +import type { DataNode, FieldNames, Key } from '../vc-tree/interface'; import { treeProps as vcTreeProps } from '../vc-tree/props'; import useConfigInject from '../_util/hooks/useConfigInject'; import renderSwitcherIcon from './utils/iconUtil'; @@ -51,7 +52,7 @@ export interface AntTreeNodeProps { [customProp: string]: any; } -export interface AntTreeNode extends DefineComponent {} +export type AntTreeNode = DefineComponent; export interface AntTreeNodeBaseEvent { node: AntTreeNode; diff --git a/components/tree/utils/dropIndicator.tsx b/components/tree/utils/dropIndicator.tsx index a69569908..848b6a8a7 100644 --- a/components/tree/utils/dropIndicator.tsx +++ b/components/tree/utils/dropIndicator.tsx @@ -1,4 +1,4 @@ -import { CSSProperties } from 'vue'; +import type { CSSProperties } from 'vue'; export const offset = 4; diff --git a/components/tree/utils/iconUtil.tsx b/components/tree/utils/iconUtil.tsx index 59dc2f8cb..e14f6df63 100644 --- a/components/tree/utils/iconUtil.tsx +++ b/components/tree/utils/iconUtil.tsx @@ -3,7 +3,7 @@ import FileOutlined from '@ant-design/icons-vue/FileOutlined'; import MinusSquareOutlined from '@ant-design/icons-vue/MinusSquareOutlined'; import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined'; import CaretDownFilled from '@ant-design/icons-vue/CaretDownFilled'; -import { AntTreeNodeProps } from '../Tree'; +import type { AntTreeNodeProps } from '../Tree'; import { isValidElement } from 'ant-design-vue/es/_util/props-util'; import { cloneVNode } from 'vue'; diff --git a/components/vc-tree/DropIndicator.tsx b/components/vc-tree/DropIndicator.tsx index e3d836243..df37c9c83 100644 --- a/components/vc-tree/DropIndicator.tsx +++ b/components/vc-tree/DropIndicator.tsx @@ -1,4 +1,4 @@ -import { CSSProperties } from 'vue'; +import type { CSSProperties } from 'vue'; export default function DropIndicator({ dropPosition, diff --git a/components/vc-tree/MotionTreeNode.tsx b/components/vc-tree/MotionTreeNode.tsx index 9d896eafd..a57897773 100644 --- a/components/vc-tree/MotionTreeNode.tsx +++ b/components/vc-tree/MotionTreeNode.tsx @@ -1,8 +1,10 @@ import TreeNode from './TreeNode'; -import { FlattenNode } from './interface'; -import { getTreeNodeProps, TreeNodeRequiredProps } from './utils/treeUtil'; +import type { FlattenNode } from './interface'; +import type { TreeNodeRequiredProps } from './utils/treeUtil'; +import { getTreeNodeProps } from './utils/treeUtil'; import { useInjectTreeContext } from './contextTypes'; -import { defineComponent, onBeforeUnmount, onMounted, PropType, ref, Transition, watch } from 'vue'; +import type { PropType } from 'vue'; +import { defineComponent, onBeforeUnmount, onMounted, ref, Transition, watch } from 'vue'; import { treeNodeProps } from './props'; export default defineComponent({ diff --git a/components/vc-tree/NodeList.tsx b/components/vc-tree/NodeList.tsx index 912c3a721..1ef1a433b 100644 --- a/components/vc-tree/NodeList.tsx +++ b/components/vc-tree/NodeList.tsx @@ -4,7 +4,7 @@ import { computed, defineComponent, ref, watch } from 'vue'; import VirtualList from '../vc-virtual-list'; -import { FlattenNode, DataEntity, DataNode, ScrollTo } from './interface'; +import type { FlattenNode, DataEntity, DataNode, ScrollTo } from './interface'; import MotionTreeNode from './MotionTreeNode'; import { nodeListProps } from './props'; import { findExpandedKeys, getExpandRange } from './utils/diffUtil'; diff --git a/components/vc-tree/Tree.tsx b/components/vc-tree/Tree.tsx index 83de46dd3..182ed78fd 100644 --- a/components/vc-tree/Tree.tsx +++ b/components/vc-tree/Tree.tsx @@ -1,4 +1,5 @@ -import { TreeContext, NodeMouseEventHandler, NodeDragEventHandler } from './contextTypes'; +import type { NodeMouseEventHandler, NodeDragEventHandler } from './contextTypes'; +import { TreeContext } from './contextTypes'; import { getDataAndAria, getDragChildrenKeys, @@ -10,7 +11,7 @@ import { arrDel, posToArr, } from './util'; -import { Key, FlattenNode, EventDataNode, NodeInstance, ScrollTo } from './interface'; +import type { Key, FlattenNode, EventDataNode, NodeInstance, ScrollTo } from './interface'; import { flattenTreeData, convertTreeToData, @@ -24,7 +25,8 @@ import { conductCheck } from './utils/conductUtil'; import DropIndicator from './DropIndicator'; import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, watchEffect } from 'vue'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { CheckInfo, treeProps } from './props'; +import type { CheckInfo } from './props'; +import { treeProps } from './props'; import { warning } from '../vc-util/warning'; import KeyCode from '../_util/KeyCode'; import classNames from '../_util/classNames'; @@ -185,7 +187,7 @@ export default defineComponent({ checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || {}; } else if (!init && props.defaultCheckedKeys) { checkedKeyEntity = parseCheckedKeys(props.defaultCheckedKeys) || {}; - } else if (treeData) { + } else if (treeData.value) { // If `treeData` changed, we also need check it checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || { checkedKeys: checkedKeys.value, @@ -495,7 +497,7 @@ export default defineComponent({ onDragleave({ event, node: convertNodePropsToEventData(node.props) }); } }; - const onNodeDrop = (event: MouseEvent, _node, outsideTree: boolean = false) => { + const onNodeDrop = (event: MouseEvent, _node, outsideTree = false) => { const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } = dragState; @@ -1029,21 +1031,21 @@ export default defineComponent({ titleRender, - onNodeClick: onNodeClick, - onNodeDoubleClick: onNodeDoubleClick, - onNodeExpand: onNodeExpand, - onNodeSelect: onNodeSelect, - onNodeCheck: onNodeCheck, - onNodeLoad: onNodeLoad, - onNodeMouseEnter: onNodeMouseEnter, - onNodeMouseLeave: onNodeMouseLeave, - onNodeContextMenu: onNodeContextMenu, - onNodeDragStart: onNodeDragStart, - onNodeDragEnter: onNodeDragEnter, - onNodeDragOver: onNodeDragOver, - onNodeDragLeave: onNodeDragLeave, - onNodeDragEnd: onNodeDragEnd, - onNodeDrop: onNodeDrop, + onNodeClick, + onNodeDoubleClick, + onNodeExpand, + onNodeSelect, + onNodeCheck, + onNodeLoad, + onNodeMouseEnter, + onNodeMouseLeave, + onNodeContextMenu, + onNodeDragStart, + onNodeDragEnter, + onNodeDragOver, + onNodeDragLeave, + onNodeDragEnd, + onNodeDrop, }} >
    Date: Tue, 17 Aug 2021 22:52:28 +0800 Subject: [PATCH 21/53] refactor: tree --- components/tree/Tree.tsx | 5 +++-- components/vc-tree/Tree.tsx | 3 ++- components/vc-tree/TreeNode.tsx | 24 +++++++++++++++++++----- components/vc-tree/contextTypes.ts | 1 + components/vc-tree/interface.tsx | 1 + 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/components/tree/Tree.tsx b/components/tree/Tree.tsx index aa0f9a54b..850e0f386 100644 --- a/components/tree/Tree.tsx +++ b/components/tree/Tree.tsx @@ -1,4 +1,4 @@ -import type { VNode, PropType, DefineComponent, ExtractPropTypes } from 'vue'; +import type { VNode, PropType, DefineComponent, ExtractPropTypes, CSSProperties } from 'vue'; import { ref } from 'vue'; import { defineComponent } from 'vue'; import classNames from '../_util/classNames'; @@ -16,7 +16,7 @@ import dropIndicatorRender from './utils/dropIndicator'; export interface AntdTreeNodeAttribute { eventKey: string; prefixCls: string; - className: string; + class: string; expanded: boolean; selected: boolean; checked: boolean; @@ -239,6 +239,7 @@ export default defineComponent({ onExpand={handleExpand} onSelect={handleSelect} v-slots={{ + ...slots, checkable: () => , }} children={filterEmpty(slots.default?.())} diff --git a/components/vc-tree/Tree.tsx b/components/vc-tree/Tree.tsx index 182ed78fd..3f71b860c 100644 --- a/components/vc-tree/Tree.tsx +++ b/components/vc-tree/Tree.tsx @@ -1046,13 +1046,14 @@ export default defineComponent({ onNodeDragLeave, onNodeDragEnd, onNodeDrop, + slots: slots, }} >
    diff --git a/components/vc-tree/TreeNode.tsx b/components/vc-tree/TreeNode.tsx index 1a03081c3..83a70d556 100644 --- a/components/vc-tree/TreeNode.tsx +++ b/components/vc-tree/TreeNode.tsx @@ -5,6 +5,7 @@ import { convertNodePropsToEventData } from './utils/treeUtil'; import { computed, defineComponent, getCurrentInstance, onMounted, onUpdated, ref } from 'vue'; import { treeNodeProps } from './props'; import classNames from '../_util/classNames'; +import { warning } from '../vc-util/warning'; const ICON_OPEN = 'open'; const ICON_CLOSE = 'close'; @@ -17,7 +18,11 @@ export default defineComponent({ props: treeNodeProps, isTreeNode: 1, slots: ['title', 'icon', 'switcherIcon', 'checkable'], - setup(props, { attrs, expose, slots }) { + setup(props, { attrs, slots }) { + warning( + !('slots' in props.data), + 'treeData slots is deprecated, please use `v-slot:icon` or `v-slot:title`, `v-slot:switcherIcon` instead', + ); const dragNodeHighlight = ref(false); const context = useInjectTreeContext(); const instance = getCurrentInstance(); @@ -198,7 +203,10 @@ export default defineComponent({ }; const renderSwitcherIconDom = (isLeaf: boolean) => { - const { switcherIcon: switcherIconFromProps = slots.switcherIcon } = props; + const { + switcherIcon: switcherIconFromProps = slots.switcherIcon || + context.value.slots?.[props.data?.slots?.switcherIcon], + } = props; const { switcherIcon: switcherIconFromCtx } = context.value; const switcherIcon = switcherIconFromProps || switcherIconFromCtx; @@ -327,7 +335,13 @@ export default defineComponent({ // Icon + Title const renderSelector = () => { - const { title = slots.title, selected, icon = slots.icon, loading, data } = props; + const { + title = slots.title || context.value.slots?.[props.data?.slots?.title], + selected, + icon = slots.icon, + loading, + data, + } = props; const { prefixCls, showIcon, @@ -345,7 +359,7 @@ export default defineComponent({ let $icon; if (showIcon) { - const currentIcon = icon || treeIcon; + const currentIcon = icon || context.value.slots?.[data?.slots?.icon] || treeIcon; $icon = currentIcon ? ( @@ -377,7 +391,7 @@ export default defineComponent({ class={classNames( `${wrapClass}`, `${wrapClass}-${nodeState.value || 'normal'}`, - !disabled && (selected || dragNodeHighlight) && `${prefixCls}-node-selected`, + !disabled && (selected || dragNodeHighlight.value) && `${prefixCls}-node-selected`, !disabled && mergedDraggable && 'draggable', )} draggable={(!disabled && mergedDraggable) || undefined} diff --git a/components/vc-tree/contextTypes.ts b/components/vc-tree/contextTypes.ts index 995bf16c8..0ec4d032f 100644 --- a/components/vc-tree/contextTypes.ts +++ b/components/vc-tree/contextTypes.ts @@ -80,6 +80,7 @@ export interface TreeContextProps { onNodeDragLeave: NodeDragEventHandler; onNodeDragEnd: NodeDragEventHandler; onNodeDrop: NodeDragEventHandler; + slots: Record; } const TreeContextKey: InjectionKey> = Symbol('TreeContextKey'); diff --git a/components/vc-tree/interface.tsx b/components/vc-tree/interface.tsx index 6685424b0..89fbb8904 100644 --- a/components/vc-tree/interface.tsx +++ b/components/vc-tree/interface.tsx @@ -16,6 +16,7 @@ export interface DataNode { /** Set style of TreeNode. This is not recommend if you don't have any force requirement */ class?: string; style?: CSSProperties; + slots?: Record; } export interface EventDataNode extends DataNode { From c7abe76939d1db1011de3c4a38ac632ee211b337 Mon Sep 17 00:00:00 2001 From: mehunk Date: Wed, 18 Aug 2021 08:36:28 +0800 Subject: [PATCH 22/53] fix(mentions): chinese or japanese input error (#4524) --- components/vc-mentions/src/Mentions.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/vc-mentions/src/Mentions.jsx b/components/vc-mentions/src/Mentions.jsx index 91a5d9a66..a3c5b3514 100644 --- a/components/vc-mentions/src/Mentions.jsx +++ b/components/vc-mentions/src/Mentions.jsx @@ -115,6 +115,9 @@ const Mentions = { const { measureText: prevMeasureText, measuring } = this.$data; const { prefix = '', validateSearch } = this.$props; const target = event.target; + if (target.composing) { + return; + } const selectionStartText = getBeforeSelectionText(target); const { location: measureIndex, prefix: measurePrefix } = getLastMeasureIndex( selectionStartText, From 106b2609be67aa8a60b940356e3ac2cb54a63015 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 18 Aug 2021 09:14:01 +0800 Subject: [PATCH 23/53] =?UTF-8?q?=E7=83=ADfactor:=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/vc-tree/NodeList.tsx | 4 +- components/vc-tree/Tree.tsx | 9 ++-- components/vc-tree/TreeNode.tsx | 2 +- components/vc-tree/contextTypes.ts | 3 +- components/vc-tree/props.ts | 2 +- examples/App.vue | 72 ++++++++++++------------------ 6 files changed, 37 insertions(+), 55 deletions(-) diff --git a/components/vc-tree/NodeList.tsx b/components/vc-tree/NodeList.tsx index 1ef1a433b..6dbe01dd8 100644 --- a/components/vc-tree/NodeList.tsx +++ b/components/vc-tree/NodeList.tsx @@ -94,8 +94,7 @@ export default defineComponent({ name: 'NodeList', inheritAttrs: false, props: nodeListProps, - slots: ['checkable'], - setup(props, { expose, attrs, slots }) { + setup(props, { expose, attrs }) { // =============================== Ref ================================ const listRef = ref(null); const indentMeasurerRef = ref(null); @@ -310,7 +309,6 @@ export default defineComponent({ onMousemove={() => { onActiveChange(null); }} - v-slots={slots} /> ); }} diff --git a/components/vc-tree/Tree.tsx b/components/vc-tree/Tree.tsx index 3f71b860c..e6a7b023f 100644 --- a/components/vc-tree/Tree.tsx +++ b/components/vc-tree/Tree.tsx @@ -34,7 +34,7 @@ import classNames from '../_util/classNames'; export default defineComponent({ name: 'Tree', inheritAttrs: false, - slots: ['checkable'], + slots: ['checkable', 'title', 'icon'], props: initDefaultProps(treeProps(), { prefixCls: 'vc-tree', showLine: false, @@ -982,7 +982,7 @@ export default defineComponent({ tabindex = 0, selectable, showIcon, - icon, + icon = slots.icon, switcherIcon, draggable, checkable, @@ -994,7 +994,6 @@ export default defineComponent({ height, itemHeight, virtual, - titleRender, dropIndicatorRender, onContextmenu, onScroll, @@ -1014,6 +1013,7 @@ export default defineComponent({ switcherIcon, draggable, checkable, + customCheckable: slots.checkable, checkStrictly, disabled, keyEntities: keyEntities.value, @@ -1029,7 +1029,7 @@ export default defineComponent({ loadData, filterTreeNode, - titleRender, + titleRender: slots.title, onNodeClick, onNodeDoubleClick, @@ -1084,7 +1084,6 @@ export default defineComponent({ onScroll={onScroll} {...treeNodeRequiredProps.value} {...domProps} - v-slots={slots} />
    diff --git a/components/vc-tree/TreeNode.tsx b/components/vc-tree/TreeNode.tsx index 83a70d556..0326bdd8d 100644 --- a/components/vc-tree/TreeNode.tsx +++ b/components/vc-tree/TreeNode.tsx @@ -293,7 +293,7 @@ export default defineComponent({ )} onClick={onCheck} > - {slots.checkable?.()} + {context.value.customCheckable?.()}
    ); }; diff --git a/components/vc-tree/contextTypes.ts b/components/vc-tree/contextTypes.ts index 0ec4d032f..29fcd75c3 100644 --- a/components/vc-tree/contextTypes.ts +++ b/components/vc-tree/contextTypes.ts @@ -41,7 +41,8 @@ export interface TreeContextProps { icon: IconType; switcherIcon: IconType; draggable: ((node: DataNode) => boolean) | boolean; - checkable: boolean | VueNode; + checkable: boolean; + customCheckable: () => any; checkStrictly: boolean; disabled: boolean; keyEntities: Record; diff --git a/components/vc-tree/props.ts b/components/vc-tree/props.ts index 9ac8eb0fc..73ff94ffa 100644 --- a/components/vc-tree/props.ts +++ b/components/vc-tree/props.ts @@ -132,7 +132,7 @@ export const treeProps = () => ({ defaultSelectedKeys: { type: Array as PropType }, selectedKeys: { type: Array as PropType }, allowDrop: { type: Function as PropType }, - titleRender: { type: Function as PropType<(node: DataNode) => any> }, + // titleRender: { type: Function as PropType<(node: DataNode) => any> }, dropIndicatorRender: { type: Function as PropType< (props: { diff --git a/examples/App.vue b/examples/App.vue index ed81c908f..cb7db35dd 100644 --- a/examples/App.vue +++ b/examples/App.vue @@ -2,60 +2,44 @@ - - + />