import type { DataNode, TreeDataNode, Key } from './interface'; 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'; import { optionListProps } from './props'; 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'; 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, props: optionListProps(), slots: ['notFoundContent', 'menuItemSelectedIcon'], setup(props, { slots, expose }) { const context = useInjectTreeSelectContext(); const treeRef = ref(); const memoOptions = useMemo( () => props.options, [() => props.open, () => props.options], (next, prev) => next[0] && prev[1] !== next[1], ); const valueKeys = computed(() => { const { checkedKeys, getEntityByValue } = context.value; return checkedKeys.map(val => { const entity = getEntityByValue(val); return entity ? entity.key : null; }); }); const mergedCheckedKeys = computed(() => { const { checkable, halfCheckedKeys } = context.value; if (!checkable) { return null; } return { checked: valueKeys.value, halfChecked: halfCheckedKeys, }; }); watch( () => props.open, () => { nextTick(() => { if (props.open && !props.multiple && valueKeys.value.length) { treeRef.value?.scrollTo({ key: valueKeys.value[0] }); } }); }, { immediate: true, flush: 'post' }, ); // ========================== Search ========================== const lowerSearchValue = computed(() => String(props.searchValue).toLowerCase()); const filterTreeNode = (treeNode: EventDataNode) => { if (!lowerSearchValue.value) { return false; } return String(treeNode[context.value.treeNodeFilterProp]) .toLowerCase() .includes(lowerSearchValue.value); }; // =========================== Keys =========================== const expandedKeys = ref(context.value.treeDefaultExpandedKeys); const searchExpandedKeys = ref(null); watch( () => props.searchValue, () => { if (props.searchValue) { searchExpandedKeys.value = props.flattenOptions.map(o => o.key); } }, { immediate: true, }, ); const mergedExpandedKeys = computed(() => { if (context.value.treeExpandedKeys) { return [...context.value.treeExpandedKeys]; } return props.searchValue ? searchExpandedKeys.value : expandedKeys.value; }); const onInternalExpand = (keys: Key[]) => { expandedKeys.value = keys; searchExpandedKeys.value = keys; context.value.onTreeExpand?.(keys); }; // ========================== Events ========================== const onListMouseDown = (event: MouseEvent) => { event.preventDefault(); }; const onInternalSelect = (_: Key[], { node: { key } }: TreeEventInfo) => { const { getEntityByKey, checkable, checkedKeys } = context.value; const entity = getEntityByKey(key, checkable ? 'checkbox' : 'select'); if (entity !== null) { props.onSelect?.(entity.data.value, { selected: !checkedKeys.includes(entity.data.value), }); } if (!props.multiple) { props.onToggleOpen?.(false); } }; // ========================= Keyboard ========================= const activeKey = ref(null); const activeEntity = computed(() => context.value.getEntityByKey(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?.data.node || {}; if (selectable !== false) { onInternalSelect(null, { node: { key: activeKey.value }, selected: !context.value.checkedKeys.includes(value), }); } break; } // >>> Close case KeyCode.ESC: { props.onToggleOpen(false); } } }, onKeyup: () => {}, } as ReviseRefOptionListProps); return () => { const { prefixCls, height, itemHeight, virtual, multiple, searchValue, open, notFoundContent = slots.notFoundContent?.(), onMouseenter, } = props; const { checkable, treeDefaultExpandAll, treeIcon, showTreeIcon, switcherIcon, treeLine, loadData, treeLoadedKeys, treeMotion, onTreeLoad, } = context.value; // ========================== Render ========================== if (memoOptions.value.length === 0) { return (
{notFoundContent}
); } const treeProps: Partial = {}; if (treeLoadedKeys) { treeProps.loadedKeys = treeLoadedKeys; } if (mergedExpandedKeys.value) { treeProps.expandedKeys = mergedExpandedKeys.value; } return (
{activeEntity.value && open && ( {activeEntity.value.data.value} )}
); }; }, });