import type { TreeDataNode, Key } from './interface'; import type { RefOptionListProps } from '../vc-select/OptionList'; import type { ScrollTo } from '../vc-virtual-list/List'; import { computed, defineComponent, nextTick, ref, shallowRef, watch } from 'vue'; import useMemo from '../_util/hooks/useMemo'; import type { EventDataNode } from '../tree'; import KeyCode from '../_util/KeyCode'; import Tree from '../vc-tree/Tree'; import type { TreeProps } from '../vc-tree/props'; import { getAllKeys, isCheckDisabled } from './utils/valueUtil'; import { useBaseProps } from '../vc-select'; import useInjectLegacySelectContext from './LegacyContext'; import useInjectSelectContext from './TreeSelectContext'; const HIDDEN_STYLE = { width: 0, height: 0, display: 'flex', overflow: 'hidden', opacity: 0, border: 0, padding: 0, margin: 0, }; interface TreeEventInfo { node: { key: Key }; selected?: boolean; checked?: boolean; } type ReviseRefOptionListProps = Omit & { scrollTo: ScrollTo }; export default defineComponent({ name: 'OptionList', inheritAttrs: false, slots: ['notFoundContent', 'menuItemSelectedIcon'], setup(_, { slots, expose }) { const baseProps = useBaseProps(); const legacyContext = useInjectLegacySelectContext(); const context = useInjectSelectContext(); const treeRef = ref(); const memoTreeData = useMemo( () => context.treeData, [() => baseProps.open, () => context.treeData], next => next[0], ); const mergedCheckedKeys = computed(() => { const { checkable, halfCheckedKeys, checkedKeys } = legacyContext; if (!checkable) { return null; } return { checked: checkedKeys, halfChecked: halfCheckedKeys, }; }); watch( () => baseProps.open, () => { nextTick(() => { if (baseProps.open && !baseProps.multiple && legacyContext.checkedKeys.length) { treeRef.value?.scrollTo({ key: legacyContext.checkedKeys[0] }); } }); }, { immediate: true, flush: 'post' }, ); // ========================== Search ========================== const lowerSearchValue = computed(() => String(baseProps.searchValue).toLowerCase()); const filterTreeNode = (treeNode: EventDataNode) => { if (!lowerSearchValue.value) { return false; } return String(treeNode[legacyContext.treeNodeFilterProp]) .toLowerCase() .includes(lowerSearchValue.value); }; // =========================== Keys =========================== const expandedKeys = shallowRef(legacyContext.treeDefaultExpandedKeys); const searchExpandedKeys = shallowRef(null); watch( () => baseProps.searchValue, () => { if (baseProps.searchValue) { searchExpandedKeys.value = getAllKeys(context.treeData, context.fieldNames); } }, { immediate: true, }, ); const mergedExpandedKeys = computed(() => { if (legacyContext.treeExpandedKeys) { return [...legacyContext.treeExpandedKeys]; } return baseProps.searchValue ? searchExpandedKeys.value : expandedKeys.value; }); const onInternalExpand = (keys: Key[]) => { expandedKeys.value = keys; searchExpandedKeys.value = keys; legacyContext.onTreeExpand?.(keys); }; // ========================== Events ========================== const onListMouseDown = (event: MouseEvent) => { event.preventDefault(); }; const onInternalSelect = (_: Key[], { node }: TreeEventInfo) => { const { checkable, checkedKeys } = legacyContext; if (checkable && isCheckDisabled(node)) { return; } context.onSelect?.(node.key, { selected: !checkedKeys.includes(node.key), }); if (!baseProps.multiple) { baseProps.toggleOpen?.(false); } }; // ========================= Keyboard ========================= const activeKey = ref(null); const activeEntity = computed(() => legacyContext.keyEntities[activeKey.value]); const setActiveKey = (key: Key) => { activeKey.value = key; }; expose({ scrollTo: (...args: any[]) => treeRef.value?.scrollTo?.(...args), onKeydown: (event: KeyboardEvent) => { const { which } = event; switch (which) { // >>> Arrow keys case KeyCode.UP: case KeyCode.DOWN: case KeyCode.LEFT: case KeyCode.RIGHT: treeRef.value?.onKeydown(event); break; // >>> Select item case KeyCode.ENTER: { const { selectable, value } = activeEntity.value?.node || {}; if (activeEntity.value && selectable !== false) { onInternalSelect(null, { node: { key: activeKey.value }, selected: !legacyContext.checkedKeys.includes(value), }); } break; } // >>> Close case KeyCode.ESC: { baseProps.toggleOpen(false); } } }, onKeyup: () => {}, } as ReviseRefOptionListProps); return () => { const { prefixCls, multiple, searchValue, open, notFoundContent = slots.notFoundContent?.(), } = baseProps; const { listHeight, listItemHeight, virtual } = context; const { checkable, treeDefaultExpandAll, treeIcon, showTreeIcon, switcherIcon, treeLine, loadData, treeLoadedKeys, treeMotion, onTreeLoad, checkedKeys, } = legacyContext; // ========================== Render ========================== if (memoTreeData.value.length === 0) { return (
{notFoundContent}
); } const treeProps: Partial = { fieldNames: context.fieldNames, }; if (treeLoadedKeys) { treeProps.loadedKeys = treeLoadedKeys; } if (mergedExpandedKeys.value) { treeProps.expandedKeys = mergedExpandedKeys.value; } return (
{activeEntity.value && open && ( {activeEntity.value.node.value} )}
); }; }, });