diff --git a/components/_util/hooks/useConfigInject.ts b/components/_util/hooks/useConfigInject.ts index d7b64dbbb..26df1b29b 100644 --- a/components/_util/hooks/useConfigInject.ts +++ b/components/_util/hooks/useConfigInject.ts @@ -18,24 +18,34 @@ export default ( form?: ComputedRef<{ requiredMark?: RequiredMark; }>; - autoInsertSpaceInButton: ComputedRef; + autoInsertSpaceInButton: ComputedRef; renderEmpty?: ComputedRef<(componentName?: string) => VNodeChild | JSX.Element>; - virtual: ComputedRef; + virtual: ComputedRef; + dropdownMatchSelectWidth: ComputedRef; + getPopupContainer: ComputedRef; } => { const configProvider = inject>( 'configProvider', defaultConfigProvider, ); const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls)); - const direction = computed(() => configProvider.direction); + const direction = computed(() => props.direction ?? configProvider.direction); const autoInsertSpaceInButton = computed(() => configProvider.autoInsertSpaceInButton); const renderEmpty = computed(() => configProvider.renderEmpty); const space = computed(() => configProvider.space); const pageHeader = computed(() => configProvider.pageHeader); const form = computed(() => configProvider.form); - const size = computed(() => props.size || configProvider.componentSize); - const getTargetContainer = computed(() => props.getTargetContainer); - const virtual = computed(() => props.virtual); + const size = computed(() => props.size ?? configProvider.componentSize); + const getTargetContainer = computed( + () => props.getTargetContainer || configProvider.getTargetContainer, + ); + const getPopupContainer = computed( + () => props.getPopupContainer || configProvider.getPopupContainer, + ); + const virtual = computed(() => props.virtual ?? configProvider.virtual); + const dropdownMatchSelectWidth = computed( + () => props.dropdownMatchSelectWidth ?? configProvider.dropdownMatchSelectWidth, + ); return { configProvider, prefixCls, @@ -48,5 +58,7 @@ export default ( autoInsertSpaceInButton, renderEmpty, virtual, + dropdownMatchSelectWidth, + getPopupContainer, }; }; diff --git a/components/_util/omit.ts b/components/_util/omit.ts index 7375f6fa9..3388fd122 100644 --- a/components/_util/omit.ts +++ b/components/_util/omit.ts @@ -1,9 +1,4 @@ -function omit( - obj: T, - fields: K, -): { - [K2 in Exclude]: T[K2]; -} { +function omit(obj: T, fields: K[]): Omit { // eslint-disable-next-line prefer-object-spread const shallowCopy = Object.assign({}, obj); for (let i = 0; i < fields.length; i += 1) { diff --git a/components/tree-select/index.tsx b/components/tree-select/index.tsx index 3c834933a..9201f99f7 100644 --- a/components/tree-select/index.tsx +++ b/components/tree-select/index.tsx @@ -1,22 +1,58 @@ -import type { App, Plugin } from 'vue'; -import { defineComponent, inject } from 'vue'; -import VcTreeSelect, { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from '../vc-tree-select'; +import type { App, ExtractPropTypes, Plugin, PropType } from 'vue'; +import { computed, ref, watchEffect } from 'vue'; +import { defineComponent } from 'vue'; +import VcTreeSelect, { + TreeNode, + SHOW_ALL, + SHOW_PARENT, + SHOW_CHILD, + treeSelectProps as vcTreeSelectProps, +} from '../vc-tree-select'; import classNames from '../_util/classNames'; -import { TreeSelectProps } from './interface'; -import warning from '../_util/warning'; -import { getOptionProps, getComponent, getSlot } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { defaultConfigProvider } from '../config-provider'; +import type { SizeType } from '../config-provider'; export { TreeData, TreeSelectProps } from './interface'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; import CaretDownOutlined from '@ant-design/icons-vue/CaretDownOutlined'; -import DownOutlined from '@ant-design/icons-vue/DownOutlined'; -import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; -import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; -import omit from 'omit.js'; -import { convertChildrenToData } from './utils'; +import type { DefaultValueType, FieldNames } from '../vc-tree-select/interface'; +import omit from '../_util/omit'; +import PropTypes from '../_util/vue-types'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import devWarning from '../vc-util/devWarning'; +import getIcons from '../select/utils/iconUtil'; +import renderSwitcherIcon from '../tree/utils/iconUtil'; +import type { AntTreeNodeProps } from '../tree/Tree'; +const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => { + if (transitionName !== undefined) { + return transitionName; + } + return `${rootPrefixCls}-${motion}`; +}; + +type RawValue = string | number; + +export interface LabeledValue { + key?: string; + value: RawValue; + label: any; +} + +export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[]; + +export interface RefTreeSelectProps { + focus: () => void; + blur: () => void; +} +export const treeSelectProps = { + ...omit(vcTreeSelectProps(), ['showTreeIcon', 'treeMotion', 'inputIcon']), + suffixIcon: PropTypes.any, + size: { type: String as PropType }, + bordered: { type: Boolean, default: undefined }, + replaceFields: { type: Object as PropType }, +}; +export type TreeSelectProps = Partial>; const TreeSelect = defineComponent({ TreeNode, SHOW_ALL, @@ -24,23 +60,166 @@ const TreeSelect = defineComponent({ SHOW_CHILD, name: 'ATreeSelect', inheritAttrs: false, - props: initDefaultProps(TreeSelectProps(), { + props: initDefaultProps(treeSelectProps, { transitionName: 'slide-up', choiceTransitionName: '', + listHeight: 256, + treeIcon: false, + listItemHeight: 26, + bordered: true, }), - setup() { - return { - vcTreeSelect: null, - configProvider: inject('configProvider', defaultConfigProvider), + slots: ['placeholder', 'maxTagPlaceholder', 'treeIcon', 'switcherIcon', 'notFoundContent'], + setup(props, { attrs, slots, expose, emit }) { + watchEffect(() => { + devWarning( + props.multiple !== false || !props.treeCheckable, + 'TreeSelect', + '`multiple` will alway be `true` when `treeCheckable` is true', + ); + devWarning( + props.replaceFields === undefined, + 'TreeSelect', + '`replaceFields` is deprecated, please use fieldNames instead', + ); + }); + const { + configProvider, + prefixCls, + renderEmpty, + direction, + virtual, + dropdownMatchSelectWidth, + size, + getPopupContainer, + } = useConfigInject('select', props); + const treePrefixCls = computed(() => + configProvider.getPrefixCls('select-tree', props.prefixCls), + ); + const treeSelectPrefixCls = computed(() => + configProvider.getPrefixCls('tree-select', props.prefixCls), + ); + + const mergedDropdownClassName = computed(() => + classNames(props.dropdownClassName, `${treeSelectPrefixCls.value}-dropdown`, { + [`${treeSelectPrefixCls.value}-dropdown-rtl`]: direction.value === 'rtl', + }), + ); + + const isMultiple = computed(() => !!(props.treeCheckable || props.multiple)); + + const treeSelectRef = ref(); + expose({ + focus() { + treeSelectRef.value.focus?.(); + }, + + blur() { + treeSelectRef.value.blur?.(); + }, + }); + + const handleChange = (...args: any[]) => { + emit('update:value', args[0]); + emit('change', ...args); + }; + const handleTreeExpand = (...args: any[]) => { + emit('update:treeExpandedKeys', args[0]); + emit('treeExpand', ...args); + }; + const handleSearch = (...args: any[]) => { + emit('update:searchValue', args[0]); + emit('search', ...args); + }; + return () => { + const { + notFoundContent = slots.notFoundContent?.(), + prefixCls: customizePrefixCls, + bordered, + listHeight, + listItemHeight, + multiple, + treeIcon, + transitionName, + choiceTransitionName, + treeLine, + switcherIcon = slots.switcherIcon?.(), + fieldNames = props.replaceFields, + } = props; + // ===================== Icons ===================== + const { suffixIcon, removeIcon, clearIcon } = getIcons( + { + ...props, + multiple: isMultiple.value, + prefixCls: prefixCls.value, + }, + slots, + ); + + // ===================== Empty ===================== + let mergedNotFound; + if (notFoundContent !== undefined) { + mergedNotFound = notFoundContent; + } else { + mergedNotFound = renderEmpty.value('Select'); + } + // ==================== Render ===================== + const selectProps = omit(props as typeof props & { itemIcon: any; switcherIcon: any }, [ + 'suffixIcon', + 'itemIcon', + 'removeIcon', + 'clearIcon', + 'switcherIcon', + ]); + + const mergedClassName = classNames( + !customizePrefixCls && treeSelectPrefixCls.value, + { + [`${prefixCls.value}-lg`]: size.value === 'large', + [`${prefixCls.value}-sm`]: size.value === 'small', + [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [`${prefixCls.value}-borderless`]: !bordered, + }, + attrs.class, + ); + const rootPrefixCls = configProvider.getPrefixCls(); + return ( + + renderSwitcherIcon(treePrefixCls.value, switcherIcon, treeLine, nodeProps) + } + showTreeIcon={treeIcon as any} + notFoundContent={mergedNotFound} + getPopupContainer={getPopupContainer.value} + treeMotion={null} + dropdownClassName={mergedDropdownClassName.value} + choiceTransitionName={getTransitionName(rootPrefixCls, '', choiceTransitionName)} + transitionName={getTransitionName(rootPrefixCls, 'slide-up', transitionName)} + onChange={handleChange} + onSearch={handleSearch} + onTreeExpand={handleTreeExpand} + v-slots={{ + treeCheckable: () => , + }} + children={slots.default?.()} + /> + ); }; }, - created() { - warning( - this.multiple !== false || !this.treeCheckable, - 'TreeSelect', - '`multiple` will alway be `true` when `treeCheckable` is true', - ); - }, + methods: { saveTreeSelect(node: any) { this.vcTreeSelect = node; @@ -112,89 +291,6 @@ const TreeSelect = defineComponent({ }); }, }, - - render() { - const props: any = getOptionProps(this); - const { - prefixCls: customizePrefixCls, - size, - dropdownStyle, - dropdownClassName, - getPopupContainer, - ...restProps - } = props; - const { class: className } = this.$attrs; - const { renderEmpty, getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('select', customizePrefixCls); - - const notFoundContent = getComponent(this, 'notFoundContent'); - const removeIcon = getComponent(this, 'removeIcon'); - const clearIcon = getComponent(this, 'clearIcon'); - const { getPopupContainer: getContextPopupContainer } = this.configProvider; - const rest = omit(restProps, [ - 'inputIcon', - 'removeIcon', - 'clearIcon', - 'switcherIcon', - 'suffixIcon', - ]); - let suffixIcon = getComponent(this, 'suffixIcon'); - suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon; - let treeData = props.treeData; - if (treeData) { - treeData = this.updateTreeData(treeData); - } - const cls = { - [`${prefixCls}-lg`]: size === 'large', - [`${prefixCls}-sm`]: size === 'small', - [className as string]: className, - }; - - // showSearch: single - false, multiple - true - let { showSearch } = restProps; - if (!('showSearch' in restProps)) { - showSearch = !!(restProps.multiple || restProps.treeCheckable); - } - - let checkable = getComponent(this, 'treeCheckable'); - if (checkable) { - checkable = ; - } - - const inputIcon = suffixIcon || ; - - const finalRemoveIcon = removeIcon || ; - - const finalClearIcon = clearIcon || ; - const VcTreeSelectProps = { - ...this.$attrs, - switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, nodeProps), - inputIcon, - removeIcon: finalRemoveIcon, - clearIcon: finalClearIcon, - ...rest, - showSearch, - getPopupContainer: getPopupContainer || getContextPopupContainer, - dropdownClassName: classNames(dropdownClassName, `${prefixCls}-tree-dropdown`), - prefixCls, - dropdownStyle: { maxHeight: '100vh', overflow: 'auto', ...dropdownStyle }, - treeCheckable: checkable, - notFoundContent: notFoundContent || renderEmpty('Select'), - class: cls, - onChange: this.handleChange, - onSearch: this.handleSearch, - onTreeExpand: this.handleTreeExpand, - ref: this.saveTreeSelect, - treeData: treeData ? treeData : convertChildrenToData(getSlot(this)), - }; - return ( - - ); - }, }); /* istanbul ignore next */ diff --git a/components/tree/Tree.tsx b/components/tree/Tree.tsx index 8a668500c..09f38491a 100644 --- a/components/tree/Tree.tsx +++ b/components/tree/Tree.tsx @@ -1,4 +1,5 @@ import type { PropType, ExtractPropTypes } from 'vue'; +import { watchEffect } from 'vue'; import { ref } from 'vue'; import { defineComponent } from 'vue'; import classNames from '../_util/classNames'; @@ -11,6 +12,7 @@ import { treeProps as vcTreeProps } from '../vc-tree/props'; import useConfigInject from '../_util/hooks/useConfigInject'; import renderSwitcherIcon from './utils/iconUtil'; import dropIndicatorRender from './utils/dropIndicator'; +import devWarning from '../vc-util/devWarning'; export interface AntdTreeNodeAttribute { eventKey: string; @@ -160,6 +162,14 @@ export default defineComponent({ }, }); + watchEffect(() => { + devWarning( + props.replaceFields === undefined, + 'Tree', + '`replaceFields` is deprecated, please use fieldNames instead', + ); + }); + const handleCheck: TreeProps['onCheck'] = (checkedObjOrKeys, eventObj) => { emit('update:checkedKeys', checkedObjOrKeys); emit('check', checkedObjOrKeys, eventObj); @@ -181,8 +191,7 @@ export default defineComponent({ blockNode, checkable, selectable, - fieldNames, - replaceFields, + fieldNames = props.replaceFields, motion = props.openAnimation, } = props; const newProps = { @@ -190,7 +199,7 @@ export default defineComponent({ ...props, showLine: Boolean(showLine), dropIndicatorRender, - fieldNames: fieldNames || (replaceFields as FieldNames), + fieldNames, icon, }; diff --git a/components/vc-select/generate.tsx b/components/vc-select/generate.tsx index 58ea04f79..822ea5434 100644 --- a/components/vc-select/generate.tsx +++ b/components/vc-select/generate.tsx @@ -75,8 +75,14 @@ export function selectBaseProps() { mode: { type: String as PropType }, // Value - value: { type: [String, Number, Object, Array] as PropType }, - defaultValue: { type: [String, Number, Object, Array] as PropType }, + value: { + type: [String, Number, Object, Array] as PropType, + default: undefined as ValueType, + }, + defaultValue: { + type: [String, Number, Object, Array] as PropType, + default: undefined as ValueType, + }, labelInValue: { type: Boolean, default: undefined }, // Search @@ -194,7 +200,7 @@ export function selectBaseProps() { onRawDeselect?: (value: RawValueType, option: OptionType, source: SelectSource) => void; }, }, - children: Array, + children: { type: Array as PropType }, }; } diff --git a/components/vc-select/index.ts b/components/vc-select/index.ts index c9bc52070..eb10427ff 100644 --- a/components/vc-select/index.ts +++ b/components/vc-select/index.ts @@ -3,8 +3,9 @@ import Select from './Select'; import Option from './Option'; import OptGroup from './OptGroup'; import { selectBaseProps } from './generate'; +import type { ExtractPropTypes } from 'vue'; -export type SelectProps = ExportedSelectProps; +export type SelectProps = Partial>>; export { Option, OptGroup, selectBaseProps }; export default Select; diff --git a/components/vc-tree-select/TreeSelect.tsx b/components/vc-tree-select/TreeSelect.tsx index 21e0d534c..1f2d1684c 100644 --- a/components/vc-tree-select/TreeSelect.tsx +++ b/components/vc-tree-select/TreeSelect.tsx @@ -1,8 +1,6 @@ -import generate, { TreeSelectProps } from './generate'; +import generate from './generate'; import OptionList from './OptionList'; const TreeSelect = generate({ prefixCls: 'vc-tree-select', optionList: OptionList as any }); -export { TreeSelectProps }; - export default TreeSelect; diff --git a/components/vc-tree-select/generate.tsx b/components/vc-tree-select/generate.tsx index c1bdb4cff..bdaefc986 100644 --- a/components/vc-tree-select/generate.tsx +++ b/components/vc-tree-select/generate.tsx @@ -91,7 +91,7 @@ export default function generate(config: { return defineComponent({ name: 'TreeSelect', props: treeSelectProps(), - slots: ['placeholder', 'maxTagPlaceholder', 'treeIcon', 'switcherIcon'], + slots: ['placeholder', 'maxTagPlaceholder', 'treeIcon', 'switcherIcon', 'notFoundContent'], TreeNode, SHOW_ALL, SHOW_PARENT, @@ -488,7 +488,7 @@ export default function generate(config: { mode={mergedMultiple.value ? 'multiple' : null} {...props} {...selectProps} - value={selectValues} + value={selectValues.value} // We will handle this ourself since we need calculate conduction labelInValue options={mergedTreeData.value} diff --git a/components/vc-tree-select/index.tsx b/components/vc-tree-select/index.tsx index 1c52ac633..59b815d7c 100644 --- a/components/vc-tree-select/index.tsx +++ b/components/vc-tree-select/index.tsx @@ -1,7 +1,8 @@ -import TreeSelect, { TreeSelectProps } from './TreeSelect'; +import TreeSelect from './TreeSelect'; import TreeNode from './TreeNode'; import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil'; +import { TreeSelectProps, treeSelectProps } from './props'; -export { TreeNode, SHOW_ALL, SHOW_CHILD, SHOW_PARENT, TreeSelectProps }; +export { TreeNode, SHOW_ALL, SHOW_CHILD, SHOW_PARENT, TreeSelectProps, treeSelectProps }; export default TreeSelect;