refactor: tree-select

pull/4577/head
tangjinzhou 2021-08-23 10:54:55 +08:00
parent 8e38ed883c
commit 1581943eb0
15 changed files with 389 additions and 465 deletions

View File

@ -2,13 +2,12 @@ import type { App, PropType, Plugin, ExtractPropTypes } from 'vue';
import { computed, defineComponent, ref } from 'vue'; import { computed, defineComponent, ref } from 'vue';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import { selectProps as vcSelectProps } from '../vc-select'; import { selectProps as vcSelectProps } from '../vc-select';
import RcSelect, { Option, OptGroup, selectBaseProps } from '../vc-select'; import RcSelect, { Option, OptGroup } from '../vc-select';
import type { OptionProps as OptionPropsType } from '../vc-select/Option'; import type { OptionProps as OptionPropsType } from '../vc-select/Option';
import getIcons from './utils/iconUtil'; import getIcons from './utils/iconUtil';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { tuple } from '../_util/type'; import { tuple } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject'; import useConfigInject from '../_util/hooks/useConfigInject';
import type { SizeType } from '../config-provider';
import omit from '../_util/omit'; import omit from '../_util/omit';
type RawValue = string | number; type RawValue = string | number;
@ -62,7 +61,7 @@ const Select = defineComponent({
'option', 'option',
], ],
setup(props, { attrs, emit, slots, expose }) { setup(props, { attrs, emit, slots, expose }) {
const selectRef = ref(null); const selectRef = ref();
const focus = () => { const focus = () => {
if (selectRef.value) { if (selectRef.value) {

View File

@ -22,6 +22,7 @@ import getIcons from '../select/utils/iconUtil';
import renderSwitcherIcon from '../tree/utils/iconUtil'; import renderSwitcherIcon from '../tree/utils/iconUtil';
import type { AntTreeNodeProps } from '../tree/Tree'; import type { AntTreeNodeProps } from '../tree/Tree';
import { warning } from '../vc-util/warning'; import { warning } from '../vc-util/warning';
import { flattenChildren } from '../_util/props-util';
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => { const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
if (transitionName !== undefined) { if (transitionName !== undefined) {
@ -80,7 +81,7 @@ const TreeSelect = defineComponent({
setup(props, { attrs, slots, expose, emit }) { setup(props, { attrs, slots, expose, emit }) {
warning( warning(
!(props.treeData === undefined && slots.default), !(props.treeData === undefined && slots.default),
'`children` of Tree is deprecated. Please use `treeData` instead.', '`children` of TreeSelect is deprecated. Please use `treeData` instead.',
); );
watchEffect(() => { watchEffect(() => {
devWarning( devWarning(
@ -194,6 +195,10 @@ const TreeSelect = defineComponent({
attrs.class, attrs.class,
); );
const rootPrefixCls = configProvider.getPrefixCls(); const rootPrefixCls = configProvider.getPrefixCls();
const otherProps: any = {};
if (props.treeData === undefined && slots.default) {
otherProps.children = flattenChildren(slots.default());
}
return ( return (
<VcTreeSelect <VcTreeSelect
{...attrs} {...attrs}
@ -227,7 +232,7 @@ const TreeSelect = defineComponent({
...slots, ...slots,
treeCheckable: () => <span class={`${prefixCls.value}-tree-checkbox-inner`} />, treeCheckable: () => <span class={`${prefixCls.value}-tree-checkbox-inner`} />,
}} }}
children={slots.default?.()} {...otherProps}
/> />
); );
}; };

View File

@ -42,7 +42,8 @@ import {
flattenOptions, flattenOptions,
fillOptionsWithMissingValue, fillOptionsWithMissingValue,
} from './utils/valueUtil'; } from './utils/valueUtil';
import { selectBaseProps, SelectProps } from './generate'; import type { SelectProps } from './generate';
import { selectBaseProps } from './generate';
import generateSelector from './generate'; import generateSelector from './generate';
import type { DefaultValueType } from './interface/generator'; import type { DefaultValueType } from './interface/generator';
import warningProps from './utils/warningPropsUtil'; import warningProps from './utils/warningPropsUtil';
@ -79,7 +80,7 @@ const Select = defineComponent({
OptGroup, OptGroup,
props: RefSelect.props, props: RefSelect.props,
setup(props, { attrs, expose, slots }) { setup(props, { attrs, expose, slots }) {
const selectRef = ref(null); const selectRef = ref();
expose({ expose({
focus: () => { focus: () => {
selectRef.value?.focus(); selectRef.value?.focus();

View File

@ -1,7 +1,8 @@
import pickAttrs from '../../_util/pickAttrs'; import pickAttrs from '../../_util/pickAttrs';
import Input from './Input'; import Input from './Input';
import type { InnerSelectorProps } from './interface'; import type { InnerSelectorProps } from './interface';
import { Fragment, Suspense, VNodeChild } from 'vue'; import type { VNodeChild } from 'vue';
import { Fragment } from 'vue';
import { computed, defineComponent, ref, watch } from 'vue'; import { computed, defineComponent, ref, watch } from 'vue';
import PropTypes from '../../_util/vue-types'; import PropTypes from '../../_util/vue-types';
import { useInjectTreeSelectContext } from 'ant-design-vue/es/vc-tree-select/Context'; import { useInjectTreeSelectContext } from 'ant-design-vue/es/vc-tree-select/Context';

View File

@ -52,7 +52,7 @@ export interface SelectorProps {
// Motion // Motion
choiceTransitionName?: string; choiceTransitionName?: string;
onToggleOpen: (open?: boolean) => void; onToggleOpen: (open?: boolean) => void | any;
/** `onSearch` returns go next step boolean to check if need do toggle open */ /** `onSearch` returns go next step boolean to check if need do toggle open */
onSearch: (searchText: string, fromTyping: boolean, isCompositing: boolean) => boolean; onSearch: (searchText: string, fromTyping: boolean, isCompositing: boolean) => boolean;
onSearchSubmit: (searchText: string) => void; onSearchSubmit: (searchText: string) => void;

View File

@ -37,6 +37,7 @@ import useSelectTriggerControl from './hooks/useSelectTriggerControl';
import useCacheDisplayValue from './hooks/useCacheDisplayValue'; import useCacheDisplayValue from './hooks/useCacheDisplayValue';
import useCacheOptions from './hooks/useCacheOptions'; import useCacheOptions from './hooks/useCacheOptions';
import type { CSSProperties, DefineComponent, PropType, VNode, VNodeChild } from 'vue'; import type { CSSProperties, DefineComponent, PropType, VNode, VNodeChild } from 'vue';
import { getCurrentInstance } from 'vue';
import { import {
computed, computed,
defineComponent, defineComponent,
@ -275,7 +276,7 @@ export default function generateSelector<
slots: ['option'], slots: ['option'],
inheritAttrs: false, inheritAttrs: false,
props: selectBaseProps<OptionType, DefaultValueType>(), props: selectBaseProps<OptionType, DefaultValueType>(),
setup(props, { expose }) { setup(props, { expose, attrs, slots }) {
const useInternalProps = computed( const useInternalProps = computed(
() => props.internalProps && props.internalProps.mark === INTERNAL_PROPS_MARK, () => props.internalProps && props.internalProps.mark === INTERNAL_PROPS_MARK,
); );
@ -284,10 +285,10 @@ export default function generateSelector<
'Select', 'Select',
'optionFilterProp not support children, please use label instead', 'optionFilterProp not support children, please use label instead',
); );
const containerRef = ref(null); const containerRef = ref();
const triggerRef = ref(null); const triggerRef = ref();
const selectorRef = ref(null); const selectorRef = ref();
const listRef = ref(null); const listRef = ref();
const tokenWithEnter = computed(() => const tokenWithEnter = computed(() =>
(props.tokenSeparators || []).some(tokenSeparator => (props.tokenSeparators || []).some(tokenSeparator =>
['\n', '\r\n'].includes(tokenSeparator), ['\n', '\r\n'].includes(tokenSeparator),
@ -353,7 +354,7 @@ export default function generateSelector<
// ============================= Option ============================= // ============================= Option =============================
// Set by option list active, it will merge into search input when mode is `combobox` // Set by option list active, it will merge into search input when mode is `combobox`
const activeValue = ref(null); const activeValue = ref();
const setActiveValue = (val: string) => { const setActiveValue = (val: string) => {
activeValue.value = val; activeValue.value = val;
}; };
@ -925,7 +926,7 @@ export default function generateSelector<
}; };
// ============================= Popup ============================== // ============================= Popup ==============================
const containerWidth = ref(null); const containerWidth = ref<number>(null);
onMounted(() => { onMounted(() => {
watch( watch(
triggerOpen, triggerOpen,
@ -952,86 +953,12 @@ export default function generateSelector<
blur, blur,
scrollTo: (...args: any[]) => listRef.value?.scrollTo(...args), scrollTo: (...args: any[]) => listRef.value?.scrollTo(...args),
}); });
return { const instance = getCurrentInstance();
tokenWithEnter, const onPopupMouseEnter = () => {
mockFocused,
mergedId,
containerWidth,
onActiveValue,
accessibilityIndex,
mergedDefaultActiveFirstOption,
onInternalMouseDown,
onContainerFocus,
onContainerBlur,
onInternalKeyDown,
isMultiple,
mergedOpen,
displayOptions,
displayFlattenOptions,
rawValues,
onInternalOptionSelect,
onToggleOpen,
mergedSearchValue,
useInternalProps,
triggerChange,
triggerSearch,
mergedRawValue,
mergedShowSearch,
onInternalKeyUp,
triggerOpen,
mergedOptions,
onInternalSelectionSelect,
selectorDomRef,
displayValues,
activeValue,
onSearchSubmit,
containerRef,
listRef,
triggerRef,
selectorRef,
};
},
methods: {
// We need force update here since popup dom is render async // We need force update here since popup dom is render async
onPopupMouseEnter() { instance.update();
(this as any).$forceUpdate(); };
}, return () => {
},
render() {
const {
tokenWithEnter,
mockFocused,
mergedId,
containerWidth,
onActiveValue,
accessibilityIndex,
mergedDefaultActiveFirstOption,
onInternalMouseDown,
onInternalKeyDown,
isMultiple,
mergedOpen,
displayOptions,
displayFlattenOptions,
rawValues,
onInternalOptionSelect,
onToggleOpen,
mergedSearchValue,
onPopupMouseEnter,
useInternalProps,
triggerChange,
triggerSearch,
mergedRawValue,
mergedShowSearch,
onInternalKeyUp,
triggerOpen,
mergedOptions,
onInternalSelectionSelect,
selectorDomRef,
displayValues,
activeValue,
onSearchSubmit,
$slots: slots,
} = this as any;
const { const {
prefixCls = defaultPrefixCls, prefixCls = defaultPrefixCls,
id, id,
@ -1107,8 +1034,7 @@ export default function generateSelector<
internalProps = {}, internalProps = {},
...restProps ...restProps
} = this.$props; //as SelectProps<OptionType[], ValueType>; } = props; //as SelectProps<OptionType[], ValueType>;
// ============================= Input ============================== // ============================= Input ==============================
// Only works in `combobox` // Only works in `combobox`
const customizeInputElement: VNodeChild | JSX.Element = const customizeInputElement: VNodeChild | JSX.Element =
@ -1120,24 +1046,24 @@ export default function generateSelector<
}); });
const popupNode = ( const popupNode = (
<OptionList <OptionList
ref="listRef" ref={listRef}
prefixCls={prefixCls} prefixCls={prefixCls}
id={mergedId} id={mergedId.value}
open={mergedOpen} open={mergedOpen.value}
childrenAsData={!options} childrenAsData={!options}
options={displayOptions} options={displayOptions.value}
flattenOptions={displayFlattenOptions} flattenOptions={displayFlattenOptions.value}
multiple={isMultiple} multiple={isMultiple.value}
values={rawValues} values={rawValues.value}
height={listHeight} height={listHeight}
itemHeight={listItemHeight} itemHeight={listItemHeight}
onSelect={onInternalOptionSelect} onSelect={onInternalOptionSelect}
onToggleOpen={onToggleOpen} onToggleOpen={onToggleOpen}
onActiveValue={onActiveValue} onActiveValue={onActiveValue}
defaultActiveFirstOption={mergedDefaultActiveFirstOption} defaultActiveFirstOption={mergedDefaultActiveFirstOption.value}
notFoundContent={notFoundContent} notFoundContent={notFoundContent}
onScroll={onPopupScroll} onScroll={onPopupScroll}
searchValue={mergedSearchValue} searchValue={mergedSearchValue.value}
menuItemSelectedIcon={menuItemSelectedIcon} menuItemSelectedIcon={menuItemSelectedIcon}
virtual={virtual !== false && dropdownMatchSelectWidth !== false} virtual={virtual !== false && dropdownMatchSelectWidth !== false}
onMouseenter={onPopupMouseEnter} onMouseenter={onPopupMouseEnter}
@ -1149,7 +1075,7 @@ export default function generateSelector<
let clearNode: VNode | JSX.Element; let clearNode: VNode | JSX.Element;
const onClearMouseDown = () => { const onClearMouseDown = () => {
// Trigger internal `onClear` event // Trigger internal `onClear` event
if (useInternalProps && internalProps.onClear) { if (useInternalProps.value && internalProps.onClear) {
internalProps.onClear(); internalProps.onClear();
} }
@ -1161,7 +1087,7 @@ export default function generateSelector<
triggerSearch('', false, false); triggerSearch('', false, false);
}; };
if (!disabled && allowClear && (mergedRawValue.length || mergedSearchValue)) { if (!disabled && allowClear && (mergedRawValue.value.length || mergedSearchValue.value)) {
clearNode = ( clearNode = (
<TransBtn <TransBtn
class={`${prefixCls}-clear`} class={`${prefixCls}-clear`}
@ -1175,7 +1101,9 @@ export default function generateSelector<
// ============================= Arrow ============================== // ============================= Arrow ==============================
const mergedShowArrow = const mergedShowArrow =
showArrow !== undefined ? showArrow : loading || (!isMultiple && mode !== 'combobox'); showArrow !== undefined
? showArrow
: loading || (!isMultiple.value && mode !== 'combobox');
let arrowNode: VNode | JSX.Element; let arrowNode: VNode | JSX.Element;
if (mergedShowArrow) { if (mergedShowArrow) {
@ -1187,10 +1115,10 @@ export default function generateSelector<
customizeIcon={inputIcon} customizeIcon={inputIcon}
customizeIconProps={{ customizeIconProps={{
loading, loading,
searchValue: mergedSearchValue, searchValue: mergedSearchValue.value,
open: mergedOpen, open: mergedOpen.value,
focused: mockFocused, focused: mockFocused.value,
showSearch: mergedShowSearch, showSearch: mergedShowSearch.value,
}} }}
/> />
); );
@ -1198,35 +1126,35 @@ export default function generateSelector<
// ============================ Warning ============================= // ============================ Warning =============================
if (process.env.NODE_ENV !== 'production' && warningProps) { if (process.env.NODE_ENV !== 'production' && warningProps) {
warningProps(this.$props); warningProps(props);
} }
// ============================= Render ============================= // ============================= Render =============================
const mergedClassName = classNames(prefixCls, this.$attrs.class, { const mergedClassName = classNames(prefixCls, attrs.class, {
[`${prefixCls}-focused`]: mockFocused, [`${prefixCls}-focused`]: mockFocused.value,
[`${prefixCls}-multiple`]: isMultiple, [`${prefixCls}-multiple`]: isMultiple.value,
[`${prefixCls}-single`]: !isMultiple, [`${prefixCls}-single`]: !isMultiple.value,
[`${prefixCls}-allow-clear`]: allowClear, [`${prefixCls}-allow-clear`]: allowClear,
[`${prefixCls}-show-arrow`]: mergedShowArrow, [`${prefixCls}-show-arrow`]: mergedShowArrow,
[`${prefixCls}-disabled`]: disabled, [`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-loading`]: loading, [`${prefixCls}-loading`]: loading,
[`${prefixCls}-open`]: mergedOpen, [`${prefixCls}-open`]: mergedOpen.value,
[`${prefixCls}-customize-input`]: customizeInputElement, [`${prefixCls}-customize-input`]: customizeInputElement,
[`${prefixCls}-show-search`]: mergedShowSearch, [`${prefixCls}-show-search`]: mergedShowSearch.value,
}); });
return ( return (
<div <div
{...this.$attrs} {...attrs}
class={mergedClassName} class={mergedClassName}
{...domProps} {...domProps}
ref="containerRef" ref={containerRef}
onMousedown={onInternalMouseDown} onMousedown={onInternalMouseDown}
onKeydown={onInternalKeyDown} onKeydown={onInternalKeyDown}
onKeyup={onInternalKeyUp} onKeyup={onInternalKeyUp}
// onFocus={onContainerFocus} // trigger by input // onFocus={onContainerFocus} // trigger by input
// onBlur={onContainerBlur} // trigger by input // onBlur={onContainerBlur} // trigger by input
> >
{mockFocused && !mergedOpen && ( {mockFocused.value && !mergedOpen.value && (
<span <span
style={{ style={{
width: 0, width: 0,
@ -1238,16 +1166,16 @@ export default function generateSelector<
aria-live="polite" aria-live="polite"
> >
{/* Merge into one string to make screen reader work as expect */} {/* Merge into one string to make screen reader work as expect */}
{`${mergedRawValue.join(', ')}`} {`${mergedRawValue.value.join(', ')}`}
</span> </span>
)} )}
<SelectTrigger <SelectTrigger
ref="triggerRef" ref={triggerRef}
disabled={disabled} disabled={disabled}
prefixCls={prefixCls} prefixCls={prefixCls}
visible={triggerOpen} visible={triggerOpen.value}
popupElement={popupNode} popupElement={popupNode}
containerWidth={containerWidth} containerWidth={containerWidth.value}
animation={animation} animation={animation}
transitionName={transitionName} transitionName={transitionName}
dropdownStyle={dropdownStyle} dropdownStyle={dropdownStyle}
@ -1257,30 +1185,30 @@ export default function generateSelector<
dropdownRender={dropdownRender as any} dropdownRender={dropdownRender as any}
dropdownAlign={dropdownAlign} dropdownAlign={dropdownAlign}
getPopupContainer={getPopupContainer} getPopupContainer={getPopupContainer}
empty={!mergedOptions.length} empty={!mergedOptions.value.length}
getTriggerDOMNode={() => selectorDomRef.current} getTriggerDOMNode={() => selectorDomRef.current}
> >
<Selector <Selector
{...this.$props} {...props}
domRef={selectorDomRef} domRef={selectorDomRef}
prefixCls={prefixCls} prefixCls={prefixCls}
inputElement={customizeInputElement} inputElement={customizeInputElement}
ref="selectorRef" ref={selectorRef}
id={mergedId} id={mergedId.value}
showSearch={mergedShowSearch} showSearch={mergedShowSearch.value}
mode={mode} mode={mode}
accessibilityIndex={accessibilityIndex} accessibilityIndex={accessibilityIndex.value}
multiple={isMultiple} multiple={isMultiple.value}
tagRender={tagRender} tagRender={tagRender}
values={displayValues} values={displayValues.value}
open={mergedOpen} open={mergedOpen.value}
onToggleOpen={onToggleOpen} onToggleOpen={onToggleOpen}
searchValue={mergedSearchValue} searchValue={mergedSearchValue.value}
activeValue={activeValue} activeValue={activeValue.value}
onSearch={triggerSearch} onSearch={triggerSearch}
onSearchSubmit={onSearchSubmit} onSearchSubmit={onSearchSubmit}
onSelect={onInternalSelectionSelect} onSelect={onInternalSelectionSelect}
tokenWithEnter={tokenWithEnter} tokenWithEnter={tokenWithEnter.value}
/> />
</SelectTrigger> </SelectTrigger>
@ -1288,6 +1216,7 @@ export default function generateSelector<
{clearNode} {clearNode}
</div> </div>
); );
};
}, },
}); });
return Select; return Select;

View File

@ -34,7 +34,6 @@ export default defineComponent({
inheritAttrs: false, inheritAttrs: false,
props: optionListProps<DataNode>(), props: optionListProps<DataNode>(),
slots: ['notFoundContent', 'menuItemSelectedIcon'], slots: ['notFoundContent', 'menuItemSelectedIcon'],
expose: ['scrollTo', 'onKeydown', 'onKeyup'],
setup(props, { slots, expose }) { setup(props, { slots, expose }) {
const context = useInjectTreeSelectContext(); const context = useInjectTreeSelectContext();
@ -153,7 +152,7 @@ export default defineComponent({
case KeyCode.DOWN: case KeyCode.DOWN:
case KeyCode.LEFT: case KeyCode.LEFT:
case KeyCode.RIGHT: case KeyCode.RIGHT:
treeRef.value?.onKeyDown(event); treeRef.value?.onKeydown(event);
break; break;
// >>> Select item // >>> Select item

View File

@ -8,7 +8,8 @@ export interface TreeNodeProps extends Omit<DataNode, 'children'> {
} }
/** This is a placeholder, not real render in dom */ /** This is a placeholder, not real render in dom */
const TreeNode: FunctionalComponent<TreeNodeProps> = () => null; const TreeNode: FunctionalComponent<TreeNodeProps> & { isTreeSelectNode: boolean } = () => null;
TreeNode.inheritAttrs = false; TreeNode.inheritAttrs = false;
TreeNode.displayName = 'ATreeSelectNode'; TreeNode.displayName = 'ATreeSelectNode';
TreeNode.isTreeSelectNode = true;
export default TreeNode; export default TreeNode;

View File

@ -161,7 +161,7 @@ export default function generate(config: {
}); });
// ========================== Ref ========================== // ========================== Ref ==========================
const selectRef = ref(null); const selectRef = ref();
expose({ expose({
scrollTo: (...args: any[]) => selectRef.value.scrollTo?.(...args), scrollTo: (...args: any[]) => selectRef.value.scrollTo?.(...args),

View File

@ -1,5 +1,6 @@
import type { VNodeChild } from 'vue';
import { camelize } from 'vue';
import { warning } from '../../vc-util/warning'; import { warning } from '../../vc-util/warning';
import { isValidElement } from '../../_util/props-util';
import type { import type {
DataNode, DataNode,
LegacyDataNode, LegacyDataNode,
@ -10,32 +11,58 @@ import type {
} from '../interface'; } from '../interface';
import TreeNode from '../TreeNode'; import TreeNode from '../TreeNode';
export function convertChildrenToData(nodes): DataNode[] { function isTreeSelectNode(node: any) {
return nodes return node && node.type && (node.type as any).isTreeSelectNode;
.map(node => { }
if (!isValidElement(node) || !node.type) { export function convertChildrenToData(rootNodes: VNodeChild): DataNode[] {
function dig(treeNodes: any[] = []): DataNode[] {
return treeNodes.map(treeNode => {
// Filter invalidate node
if (!isTreeSelectNode(treeNode)) {
warning(!treeNode, 'TreeSelect/TreeSelectNode can only accept TreeSelectNode as children.');
return null; return null;
} }
const slots = (treeNode.children as any) || {};
const key = treeNode.key as string | number;
const props: any = {};
for (const [k, v] of Object.entries(treeNode.props)) {
props[camelize(k)] = v;
}
const { isLeaf, checkable, selectable, disabled, disableCheckbox } = props;
// undefined
const newProps = {
isLeaf: isLeaf || isLeaf === '' || undefined,
checkable: checkable || checkable === '' || undefined,
selectable: selectable || selectable === '' || undefined,
disabled: disabled || disabled === '' || undefined,
disableCheckbox: disableCheckbox || disableCheckbox === '' || undefined,
};
const slotsProps = { ...props, ...newProps };
const { const {
title = slots.title?.(slotsProps),
switcherIcon = slots.switcherIcon?.(slotsProps),
...rest
} = props;
const children = slots.default?.();
const dataNode: DataNode = {
...rest,
title,
switcherIcon,
key, key,
props: { children, value, ...restProps }, isLeaf,
} = node; ...newProps,
const data = {
key,
value,
...restProps,
}; };
const childData = convertChildrenToData(children); const parsedChildren = dig(children);
if (childData.length) { if (parsedChildren.length) {
data.children = childData; dataNode.children = parsedChildren;
} }
return data; return dataNode;
}) });
.filter(data => data); }
return dig(rootNodes as any[]);
} }
export function fillLegacyProps(dataNode: DataNode): LegacyDataNode { export function fillLegacyProps(dataNode: DataNode): LegacyDataNode {

View File

@ -96,8 +96,8 @@ export default defineComponent({
props: nodeListProps, props: nodeListProps,
setup(props, { expose, attrs }) { setup(props, { expose, attrs }) {
// =============================== Ref ================================ // =============================== Ref ================================
const listRef = ref(null); const listRef = ref();
const indentMeasurerRef = ref(null); const indentMeasurerRef = ref();
expose({ expose({
scrollTo: scroll => { scrollTo: scroll => {
listRef.value.scrollTo(scroll); listRef.value.scrollTo(scroll);

View File

@ -870,8 +870,8 @@ export default defineComponent({
active: true, active: true,
}); });
}); });
const onKeyDown = event => { const onKeydown = event => {
const { onKeyDown, checkable, selectable } = props; const { onKeydown, checkable, selectable } = props;
// >>>>>>>>>> Direction // >>>>>>>>>> Direction
switch (event.which) { switch (event.which) {
@ -943,13 +943,14 @@ export default defineComponent({
} }
} }
if (onKeyDown) { if (onKeydown) {
onKeyDown(event); onKeydown(event);
} }
}; };
expose({ expose({
onNodeExpand, onNodeExpand,
scrollTo, scrollTo,
onKeydown,
}); });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('dragend', onWindowDragEnd); window.removeEventListener('dragend', onWindowDragEnd);
@ -1068,7 +1069,7 @@ export default defineComponent({
activeItem={activeItem.value} activeItem={activeItem.value}
onFocus={onFocus} onFocus={onFocus}
onBlur={onBlur} onBlur={onBlur}
onKeydown={onKeyDown} onKeydown={onKeydown}
onActiveChange={onActiveChange} onActiveChange={onActiveChange}
onListChangeStart={onListChangeStart} onListChangeStart={onListChangeStart}
onListChangeEnd={onListChangeEnd} onListChangeEnd={onListChangeEnd}

View File

@ -146,7 +146,7 @@ export const treeProps = () => ({
}, },
onFocus: { type: Function as PropType<(e: FocusEvent) => void> }, onFocus: { type: Function as PropType<(e: FocusEvent) => void> },
onBlur: { type: Function as PropType<(e: FocusEvent) => void> }, onBlur: { type: Function as PropType<(e: FocusEvent) => void> },
onKeyDown: { type: Function as PropType<EventHandlerNonNull> }, onKeydown: { type: Function as PropType<EventHandlerNonNull> },
onContextmenu: { type: Function as PropType<EventHandlerNonNull> }, onContextmenu: { type: Function as PropType<EventHandlerNonNull> },
onClick: { type: Function as PropType<NodeMouseEventHandler> }, onClick: { type: Function as PropType<NodeMouseEventHandler> },
onDblclick: { type: Function as PropType<NodeMouseEventHandler> }, onDblclick: { type: Function as PropType<NodeMouseEventHandler> },

View File

@ -12,6 +12,7 @@ import { getPosition, isTreeNode } from '../util';
import { warning } from '../../vc-util/warning'; import { warning } from '../../vc-util/warning';
import Omit from 'omit.js'; import Omit from 'omit.js';
import type { VNodeChild } from 'vue'; import type { VNodeChild } from 'vue';
import { camelize } from 'vue';
import type { TreeNodeProps } from '../props'; import type { TreeNodeProps } from '../props';
export function getKey(key: Key, pos: string) { export function getKey(key: Key, pos: string) {
@ -60,28 +61,13 @@ export function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames)
dig(treeData); dig(treeData);
} }
const cacheStringFunction = (fn: (s: string) => string) => {
const cache = Object.create(null);
return (str: string) => {
const hit = cache[str];
return hit || (cache[str] = fn(str));
};
};
const camelizeRE = /-(\w)/g;
const camelize = cacheStringFunction((str: string) => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});
/** /**
* Convert `children` of Tree into `treeData` structure. * Convert `children` of Tree into `treeData` structure.
*/ */
export function convertTreeToData(rootNodes: VNodeChild): DataNode[] { export function convertTreeToData(rootNodes: VNodeChild): DataNode[] {
function dig(node: VNodeChild = []): DataNode[] { function dig(node: VNodeChild = []): DataNode[] {
const treeNodes = node as NodeElement[]; const treeNodes = node as NodeElement[];
return treeNodes return treeNodes.map(treeNode => {
.map(treeNode => {
// Filter invalidate node // Filter invalidate node
if (!isTreeNode(treeNode)) { if (!isTreeNode(treeNode)) {
warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.'); warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
@ -126,8 +112,7 @@ export function convertTreeToData(rootNodes: VNodeChild): DataNode[] {
} }
return dataNode; return dataNode;
}) });
.filter((dataNode: DataNode) => dataNode);
} }
return dig(rootNodes); return dig(rootNodes);

View File

@ -1,64 +1,40 @@
<template> <template>
<a-tree-select <a-tree-select
v-model:value="value" v-model:value="value"
show-search
style="width: 100%" style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:tree-data="treeData"
placeholder="Please select" placeholder="Please select"
allow-clear
multiple
tree-default-expand-all tree-default-expand-all
> >
<template #title="{ key, value }"> <a-tree-select-node value="parent 1" title="parent 1">
<span v-if="key === '0-0-1'" style="color: #08c">Child Node1 {{ value }}</span> <a-tree-select-node value="parent 1-0" title="parent 1-0">
</template> <a-tree-select-node value="leaf1" title="my leaf" />
<a-tree-select-node value="leaf2" title="your leaf" />
</a-tree-select-node>
<a-tree-select-node value="parent 1-1" title="parent 1-1">
<a-tree-select-node value="sss">
<template #title><b style="color: #08c">sss</b></template>
</a-tree-select-node>
</a-tree-select-node>
</a-tree-select-node>
</a-tree-select> </a-tree-select>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watch } from 'vue'; 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({ export default defineComponent({
setup() { setup() {
const value = ref<string>(); const value = ref<string[]>([]);
watch(value, () => { watch(value, () => {
console.log(value.value); console.log('select', value.value);
}); });
return { return {
value, value,
treeData,
}; };
}, },
}); });