refactor: tree-select

pull/4577/head
tangjinzhou 2021-08-22 16:59:13 +08:00
parent 81c16aba93
commit 8bf14f40ac
18 changed files with 139 additions and 159 deletions

View File

@ -1,6 +1,6 @@
import type { App, Plugin, VNode, ExtractPropTypes } from 'vue';
import { defineComponent, inject, provide } from 'vue';
import Select, { SelectProps } from '../select';
import Select, { selectProps } from '../select';
import Input from '../input';
import PropTypes from '../_util/vue-types';
import { defaultConfigProvider } from '../config-provider';
@ -15,7 +15,7 @@ function isSelectOptionOrSelectOptGroup(child: any): boolean {
}
const autoCompleteProps = {
...SelectProps(),
...selectProps(),
dataSource: PropTypes.array,
dropdownMenuStyle: PropTypes.style,
optionLabelProp: PropTypes.string,

View File

@ -1,10 +1,10 @@
import { defineComponent } from 'vue';
import VcSelect, { SelectProps } from '../select';
import VcSelect, { selectProps } from '../select';
import { getOptionProps, getSlot } from '../_util/props-util';
export default defineComponent({
inheritAttrs: false,
props: SelectProps(),
props: selectProps(),
Option: VcSelect.Option,
render() {
const selectOptionsProps = getOptionProps(this);

View File

@ -1,8 +1,7 @@
import type { App, PropType, Plugin, ExtractPropTypes } from 'vue';
import { computed, defineComponent, ref } from 'vue';
import omit from 'omit.js';
import classNames from '../_util/classNames';
import type { SelectProps as RcSelectProps } from '../vc-select';
import { selectProps as vcSelectProps } from '../vc-select';
import RcSelect, { Option, OptGroup, selectBaseProps } from '../vc-select';
import type { OptionProps as OptionPropsType } from '../vc-select/Option';
import getIcons from './utils/iconUtil';
@ -10,6 +9,7 @@ import PropTypes from '../_util/vue-types';
import { tuple } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
import type { SizeType } from '../config-provider';
import omit from '../_util/omit';
type RawValue = string | number;
@ -24,24 +24,8 @@ export interface LabeledValue {
}
export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[] | undefined;
interface InternalSelectProps<VT> extends Omit<RcSelectProps<VT>, 'mode'> {
suffixIcon?: any;
itemIcon?: any;
size?: SizeType;
mode?: 'multiple' | 'tags' | 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
bordered?: boolean;
}
interface SelectPropsTypes<VT>
extends Omit<InternalSelectProps<VT>, 'inputIcon' | 'mode' | 'getInputElement' | 'backfill'> {
mode?: 'multiple' | 'tags';
}
export type SelectProps = Partial<ExtractPropTypes<SelectPropsTypes<SelectValue>>>;
export const selectProps = () => ({
...(omit(selectBaseProps(), ['inputIcon', 'mode', 'getInputElement', 'backfill']) as Omit<
SelectPropsTypes<SelectValue>,
'inputIcon' | 'mode' | 'getInputElement' | 'backfill' | 'class' | 'style'
>),
...omit(vcSelectProps<SelectValue>(), ['inputIcon', 'mode', 'getInputElement', 'backfill']),
value: {
type: [Array, Object, String, Number] as PropType<SelectValue>,
},
@ -58,6 +42,8 @@ export const selectProps = () => ({
choiceTransitionName: PropTypes.string.def(''),
});
export type SelectProps = Partial<ExtractPropTypes<ReturnType<typeof selectProps>>>;
const Select = defineComponent({
name: 'ASelect',
Option,

View File

@ -11,8 +11,6 @@ import VcTreeSelect, {
import classNames from '../_util/classNames';
import initDefaultProps from '../_util/props-util/initDefaultProps';
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 type { DefaultValueType, FieldNames } from '../vc-tree-select/interface';
@ -23,6 +21,7 @@ import devWarning from '../vc-util/devWarning';
import getIcons from '../select/utils/iconUtil';
import renderSwitcherIcon from '../tree/utils/iconUtil';
import type { AntTreeNodeProps } from '../tree/Tree';
import { warning } from '../vc-util/warning';
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
if (transitionName !== undefined) {
@ -53,6 +52,7 @@ export const treeSelectProps = {
replaceFields: { type: Object as PropType<FieldNames> },
};
export type TreeSelectProps = Partial<ExtractPropTypes<typeof treeSelectProps>>;
const TreeSelect = defineComponent({
TreeNode,
SHOW_ALL,
@ -68,8 +68,19 @@ const TreeSelect = defineComponent({
listItemHeight: 26,
bordered: true,
}),
slots: ['placeholder', 'maxTagPlaceholder', 'treeIcon', 'switcherIcon', 'notFoundContent'],
slots: [
'title',
'placeholder',
'maxTagPlaceholder',
'treeIcon',
'switcherIcon',
'notFoundContent',
],
setup(props, { attrs, slots, expose, emit }) {
warning(
!(props.treeData === undefined && slots.default),
'`children` of Tree is deprecated. Please use `treeData` instead.',
);
watchEffect(() => {
devWarning(
props.multiple !== false || !props.treeCheckable,
@ -212,6 +223,7 @@ const TreeSelect = defineComponent({
onSearch={handleSearch}
onTreeExpand={handleTreeExpand}
v-slots={{
...slots,
treeCheckable: () => <span class={`${prefixCls.value}-tree-checkbox-inner`} />,
}}
children={slots.default?.()}

View File

@ -1,65 +0,0 @@
import PropTypes, { withUndefined } from '../_util/vue-types';
import { SelectProps } from '../select';
import { tuple } from '../_util/type';
export const TreeData = PropTypes.shape({
key: PropTypes.string,
value: PropTypes.string,
label: PropTypes.VNodeChild,
slots: PropTypes.object,
children: PropTypes.array,
}).loose;
export const TreeSelectProps = () => ({
...SelectProps(),
autofocus: PropTypes.looseBool,
dropdownStyle: PropTypes.object,
filterTreeNode: withUndefined(PropTypes.oneOfType([Function, Boolean])),
getPopupContainer: PropTypes.func,
labelInValue: PropTypes.looseBool,
loadData: PropTypes.func,
maxTagCount: PropTypes.number,
maxTagPlaceholder: PropTypes.VNodeChild,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.array,
PropTypes.number,
]),
defaultValue: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.array,
PropTypes.number,
]),
multiple: PropTypes.looseBool,
notFoundContent: PropTypes.VNodeChild,
searchPlaceholder: PropTypes.string,
searchValue: PropTypes.string,
showCheckedStrategy: PropTypes.oneOf(tuple('SHOW_ALL', 'SHOW_PARENT', 'SHOW_CHILD')),
suffixIcon: PropTypes.VNodeChild,
treeCheckable: PropTypes.looseBool,
treeCheckStrictly: PropTypes.looseBool,
treeData: PropTypes.arrayOf(Object),
treeDataSimpleMode: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, Object])),
dropdownClassName: PropTypes.string,
dropdownMatchSelectWidth: PropTypes.looseBool,
treeDefaultExpandAll: PropTypes.looseBool,
treeExpandedKeys: PropTypes.array,
treeIcon: PropTypes.looseBool,
treeDefaultExpandedKeys: PropTypes.array,
treeNodeFilterProp: PropTypes.string,
treeNodeLabelProp: PropTypes.string,
replaceFields: PropTypes.object.def({}),
clearIcon: PropTypes.VNodeChild,
removeIcon: PropTypes.VNodeChild,
onSelect: PropTypes.func,
onChange: PropTypes.func,
onSearch: PropTypes.func,
onTreeExpand: PropTypes.func,
'onUpdate:treeExpandedKeys': PropTypes.func,
'onUpdate:searchValue': PropTypes.func,
'onUpdate:value': PropTypes.func,
});

View File

@ -13,6 +13,7 @@ import useConfigInject from '../_util/hooks/useConfigInject';
import renderSwitcherIcon from './utils/iconUtil';
import dropIndicatorRender from './utils/dropIndicator';
import devWarning from '../vc-util/devWarning';
import { warning } from '../vc-util/warning';
export interface AntdTreeNodeAttribute {
eventKey: string;
@ -153,6 +154,10 @@ export default defineComponent({
],
TreeNode,
setup(props, { attrs, expose, emit, slots }) {
warning(
!(props.treeData === undefined && slots.default),
'`children` of Tree is deprecated. Please use `treeData` instead.',
);
const { prefixCls, direction, virtual } = useConfigInject('tree', props);
const treeRef = ref();
expose({

View File

@ -42,7 +42,7 @@ import {
flattenOptions,
fillOptionsWithMissingValue,
} from './utils/valueUtil';
import type { SelectProps } from './generate';
import { selectBaseProps, SelectProps } from './generate';
import generateSelector from './generate';
import type { DefaultValueType } from './interface/generator';
import warningProps from './utils/warningPropsUtil';
@ -68,6 +68,10 @@ export type ExportedSelectProps<T extends DefaultValueType = DefaultValueType> =
T
>;
export function selectProps<T>() {
return selectBaseProps<SelectOptionsType[number], T>();
}
const Select = defineComponent({
name: 'Select',
inheritAttrs: false,

View File

@ -2,8 +2,9 @@ import pickAttrs from '../../_util/pickAttrs';
import Input from './Input';
import type { InnerSelectorProps } from './interface';
import type { VNodeChild } from 'vue';
import { computed, defineComponent, Fragment, ref, watch } from 'vue';
import { computed, defineComponent, ref, watch } from 'vue';
import PropTypes from '../../_util/vue-types';
import { useInjectTreeSelectContext } from 'ant-design-vue/es/vc-tree-select/Context';
interface SelectorProps extends InnerSelectorProps {
inputElement: VNodeChild;
@ -50,6 +51,7 @@ const SingleSelector = defineComponent<SelectorProps>({
}
return inputValue;
});
const treeSelectContext = useInjectTreeSelectContext();
watch(
[combobox, () => props.activeValue],
() => {
@ -94,6 +96,12 @@ const SingleSelector = defineComponent<SelectorProps>({
onInputCompositionEnd,
} = props;
const item = values[0];
let slotTitle = null;
if (treeSelectContext.value.slots) {
slotTitle =
treeSelectContext.value.slots[item?.option?.data?.slots?.title] ||
treeSelectContext.value.slots.title;
}
return (
<>
<span class={`${prefixCls}-selection-search`}>
@ -126,7 +134,8 @@ const SingleSelector = defineComponent<SelectorProps>({
{/* Display value */}
{!combobox.value && item && !hasTextInput.value && (
<span class={`${prefixCls}-selection-item`} title={title.value}>
<Fragment key={item.key || item.value}>{item.label}</Fragment>
{/* <Fragment key={item.key || item.value}>{item.label}</Fragment> */}
{slotTitle?.(item.option?.data) || item.label}
</span>
)}

View File

@ -119,7 +119,7 @@ export function selectBaseProps<OptionType, ValueType>() {
defaultOpen: { type: Boolean, default: undefined },
listHeight: Number,
listItemHeight: Number,
dropdownStyle: { type: Function as PropType<CSSProperties> },
dropdownStyle: { type: Object as PropType<CSSProperties> },
dropdownClassName: String,
dropdownMatchSelectWidth: {
type: [Boolean, Number] as PropType<boolean | number>,
@ -275,7 +275,7 @@ export default function generateSelector<
slots: ['option'],
inheritAttrs: false,
props: selectBaseProps<OptionType, DefaultValueType>(),
setup(props) {
setup(props, { expose }) {
const useInternalProps = computed(
() => props.internalProps && props.internalProps.mark === INTERNAL_PROPS_MARK,
);
@ -455,10 +455,10 @@ export default function generateSelector<
labelInValue: mergedLabelInValue.value,
optionLabelProp: mergedOptionLabelProp.value,
});
return {
...displayValue,
disabled: isValueDisabled(val, valueOptions),
option: valueOptions[0],
};
});
@ -947,10 +947,12 @@ export default function generateSelector<
const blur = () => {
selectorRef.value.blur();
};
return {
expose({
focus,
blur,
scrollTo: listRef.value?.scrollTo,
scrollTo: (...args: any[]) => listRef.value?.scrollTo(...args),
});
return {
tokenWithEnter,
mockFocused,
mergedId,
@ -1139,7 +1141,7 @@ export default function generateSelector<
menuItemSelectedIcon={menuItemSelectedIcon}
virtual={virtual !== false && dropdownMatchSelectWidth !== false}
onMouseenter={onPopupMouseEnter}
v-slots={{ option: slots.option }}
v-slots={{ ...slots, option: slots.option }}
/>
);
@ -1212,7 +1214,6 @@ export default function generateSelector<
[`${prefixCls}-customize-input`]: customizeInputElement,
[`${prefixCls}-show-search`]: mergedShowSearch,
});
return (
<div
{...this.$attrs}

View File

@ -1,11 +1,11 @@
import type { ExportedSelectProps } from './Select';
import Select from './Select';
import Select, { selectProps } from './Select';
import Option from './Option';
import OptGroup from './OptGroup';
import { selectBaseProps } from './generate';
import type { ExtractPropTypes } from 'vue';
export type SelectProps<T = any> = Partial<ExtractPropTypes<ExportedSelectProps<T>>>;
export { Option, OptGroup, selectBaseProps };
export { Option, OptGroup, selectBaseProps, selectProps };
export default Select;

View File

@ -50,7 +50,7 @@ export const SelectContext = defineComponent({
},
});
export const useInjectSelectContext = () => {
export const useInjectTreeSelectContext = () => {
return inject(
SelectContextKey,
computed(() => ({} as ContextProps)),

View File

@ -1,5 +1,5 @@
import type { DataNode, TreeDataNode, Key } from './interface';
import { useInjectSelectContext } from './Context';
import { useInjectTreeSelectContext } from './Context';
import type { RefOptionListProps } from '../vc-select/OptionList';
import type { ScrollTo } from '../vc-virtual-list/List';
import { computed, defineComponent, nextTick, ref, watch } from 'vue';
@ -36,7 +36,7 @@ export default defineComponent({
slots: ['notFoundContent', 'menuItemSelectedIcon'],
expose: ['scrollTo', 'onKeydown', 'onKeyup'],
setup(props, { slots, expose }) {
const context = useInjectSelectContext();
const context = useInjectTreeSelectContext();
const treeRef = ref();
const memoOptions = useMemo(
@ -144,7 +144,7 @@ export default defineComponent({
activeKey.value = key;
};
expose({
scrollTo: treeRef.value?.scrollTo as ScrollTo,
scrollTo: (...args: any[]) => treeRef.value.scrollTo?.(...args),
onKeydown: (event: KeyboardEvent) => {
const { which } = event;
switch (which) {
@ -217,7 +217,6 @@ export default defineComponent({
if (mergedExpandedKeys.value) {
treeProps.expandedKeys = mergedExpandedKeys.value;
}
return (
<div onMousedown={onListMouseDown} onMouseenter={onMouseenter}>
{activeEntity.value && open && (
@ -255,7 +254,7 @@ export default defineComponent({
onExpand={onInternalExpand}
onLoad={onTreeLoad}
filterTreeNode={filterTreeNode}
v-slots={{ checkable: context.value.customCheckable }}
v-slots={{ ...slots, checkable: context.value.customCheckable }}
/>
</div>
);

View File

@ -91,7 +91,15 @@ export default function generate(config: {
return defineComponent({
name: 'TreeSelect',
props: treeSelectProps(),
slots: ['placeholder', 'maxTagPlaceholder', 'treeIcon', 'switcherIcon', 'notFoundContent'],
slots: [
'title',
'placeholder',
'maxTagPlaceholder',
'treeIcon',
'switcherIcon',
'notFoundContent',
'treeCheckable',
],
TreeNode,
SHOW_ALL,
SHOW_PARENT,
@ -105,7 +113,6 @@ export default function generate(config: {
// ======================= Tree Data =======================
// FieldNames
const mergedFieldNames = computed(() => fillFieldNames(props.fieldNames, true));
// Legacy both support `label` or `title` if not set.
// We have to fallback to function to handle this
const getTreeNodeTitle = (node: DataNode) => {
@ -157,9 +164,9 @@ export default function generate(config: {
const selectRef = ref(null);
expose({
scrollTo: selectRef.value.scrollTo,
focus: selectRef.value.focus,
blur: selectRef.value.blur,
scrollTo: (...args: any[]) => selectRef.value.scrollTo?.(...args),
focus: () => selectRef.value.focus?.(),
blur: () => selectRef.value?.blur(),
/** @private Internal usage. It's save to remove if `rc-cascader` not use it any longer */
getEntityByValue,
@ -477,7 +484,7 @@ export default function generate(config: {
treeNodeFilterProp,
getEntityByKey,
getEntityByValue,
customCheckable: slots.checkable,
customCheckable: slots.treeCheckable,
slots,
};
return (
@ -496,6 +503,7 @@ export default function generate(config: {
onSelect={null}
onDeselect={null}
onDropdownVisibleChange={onInternalDropdownVisibleChange}
v-slots={slots}
/>
</SelectContext>
);

View File

@ -78,6 +78,10 @@ function formatTreeData(
node,
};
if (node.slots) {
dataNode.slots = node.slots;
}
// Check `key` & `value` and warning user
if (process.env.NODE_ENV !== 'production') {
if (

View File

@ -38,6 +38,8 @@ export interface InternalDataEntity {
/** Origin DataNode */
node: DataNode;
slots?: Record<string, string>; // 兼容 V2
}
export interface LegacyDataNode extends DataNode {

View File

@ -84,10 +84,6 @@ export default defineComponent({
// abstract-drag-over-node is the top node
dragOverNodeKey: null,
});
warning(
!(props.treeData === undefined && props.children),
'`children` of Tree is deprecated. Please use `treeData` instead.',
);
const treeData = computed(() => {
return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children);
});
@ -953,6 +949,7 @@ export default defineComponent({
};
expose({
onNodeExpand,
scrollTo,
});
onUnmounted(() => {
window.removeEventListener('dragend', onWindowDragEnd);

View File

@ -22,7 +22,9 @@ export default defineComponent({
setup(props, { attrs, slots, expose }) {
warning(
!('slots' in props.data),
'treeData slots is deprecated, please use `v-slot:icon` or `v-slot:title`, `v-slot:switcherIcon` instead',
`treeData slots is deprecated, please use ${Object.keys(props.data.slots || {}).map(
key => '`v-slot:' + key + '` ',
)}instead`,
);
const dragNodeHighlight = ref(false);
const context = useInjectTreeContext();
@ -346,7 +348,9 @@ export default defineComponent({
// Icon + Title
const renderSelector = () => {
const {
title = slots.title || context.value.slots?.[props.data?.slots?.title],
title = slots.title ||
context.value.slots?.[props.data?.slots?.title] ||
context.value.slots?.title,
selected,
icon = slots.icon,
loading,

View File

@ -1,49 +1,63 @@
<template>
<a-directory-tree
v-model:expandedKeys="expandedKeys"
v-model:selectedKeys="selectedKeys"
multiple
:tree-data1="treeData"
<a-tree-select
v-model:value="value"
style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:tree-data="treeData"
placeholder="Please select"
tree-default-expand-all
>
<a-tree-node key="0-0" title="ddd" class="test" style="color: red">
<template #title="{ title }">{{ title }}</template>
<a-tree-node key="0-0-0" title="leaf 0-0" is-leaf />
<a-tree-node key="0-0-1" title="leaf 0-1" is-leaf />
</a-tree-node>
<a-tree-node key="0-1" title="parent 1">
<a-tree-node key="0-1-0" title="leaf 1-0" is-leaf />
<a-tree-node key="0-1-1" title="leaf 1-1" is-leaf />
</a-tree-node>
</a-directory-tree>
<template #title1="{ key, value }">
<span v-if="key === '0-0-1'" style="color: #08c">Child Node1 {{ value }}</span>
</template>
</a-tree-select>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
interface TreeDataItem {
value: string;
key: string;
title?: string;
slots?: Record<string, string>;
children?: TreeDataItem[];
}
const treeData: TreeDataItem[] = [
{
title: 'Node1',
value: '0-0',
key: '0-0',
children: [
{
value: '0-0-1',
key: '0-0-1',
slots: {
title: 'title1',
},
},
{
title: 'Child Node2',
value: '0-0-2',
key: '0-0-2',
},
],
},
{
title: 'Node2',
value: '0-1',
key: '0-1',
},
];
export default defineComponent({
setup() {
const expandedKeys = ref<string[]>([]);
const selectedKeys = ref<string[]>([]);
const treeData = [
{
title: 'parent 0',
key: '0-0',
children: [
{ title: 'leaf 0-0', key: '0-0-0', isLeaf: true },
// { title: 'leaf 0-1', key: '0-0-1', isLeaf: true },
],
},
{
title: 'parent 1',
key: '0-1',
children: [
{ title: 'leaf 1-0', key: '0-1-0', isLeaf: true },
{ title: 'leaf 1-1', key: '0-1-1', isLeaf: true },
],
},
];
const value = ref<string>();
watch(value, () => {
console.log(value.value);
});
return {
expandedKeys,
selectedKeys,
onTest: () => {},
value,
treeData,
};
},