From 9f5aae64ea5089c18533c91181e299925adb634c Mon Sep 17 00:00:00 2001 From: Jeremy <15975785+jw-foss@users.noreply.github.com> Date: Tue, 7 Jun 2022 17:23:12 +0800 Subject: [PATCH] fix(components): [select-v2] remove private API (#8145) * fix(components): [select-v2] remove private API * Refactor `select-menu` to `tsx`. * Remove usage for `h` renderer * Fix write operation for readonly computed. * chore: make select-dropdown as tsx Co-authored-by: JeremyWuuuuu <15975785+JeremyWuuuuu@users.noreply.github.com> --- .../select-v2/src/select-dropdown.tsx | 246 ++++++++++++++++ .../select-v2/src/select-dropdown.vue | 275 ------------------ packages/components/select-v2/src/select.vue | 2 +- packages/components/select-v2/src/token.ts | 1 + .../components/select-v2/src/useSelect.ts | 9 +- 5 files changed, 255 insertions(+), 278 deletions(-) create mode 100644 packages/components/select-v2/src/select-dropdown.tsx delete mode 100644 packages/components/select-v2/src/select-dropdown.vue diff --git a/packages/components/select-v2/src/select-dropdown.tsx b/packages/components/select-v2/src/select-dropdown.tsx new file mode 100644 index 0000000000..c4cda26a75 --- /dev/null +++ b/packages/components/select-v2/src/select-dropdown.tsx @@ -0,0 +1,246 @@ +import { computed, defineComponent, inject, ref, unref } from 'vue' +import { get } from 'lodash-unified' +import { isObject, isUndefined } from '@element-plus/utils' +import { + DynamicSizeList, + FixedSizeList, +} from '@element-plus/components/virtual-list' +import { useNamespace } from '@element-plus/hooks' +import { EVENT_CODE } from '@element-plus/constants' +import GroupItem from './group-item.vue' +import OptionItem from './option-item.vue' + +import { selectV2InjectionKey } from './token' + +import type { ItemProps } from '@element-plus/components/virtual-list' +import type { Option, OptionItemProps } from './select.types' + +export default defineComponent({ + name: 'ElSelectDropdown', + + props: { + data: { + type: Array, + required: true, + }, + hoveringIndex: Number, + width: Number, + }, + setup(props, { slots, expose }) { + const select = inject(selectV2InjectionKey)! + const ns = useNamespace('select') + const cachedHeights = ref>([]) + + const listRef = ref() + + const isSized = computed(() => + isUndefined(select.props.estimatedOptionHeight) + ) + const listProps = computed(() => { + if (isSized.value) { + return { + itemSize: select.props.itemHeight, + } + } + + return { + estimatedSize: select.props.estimatedOptionHeight, + itemSize: (idx: number) => cachedHeights.value[idx], + } + }) + + const contains = (arr: Array = [], target: any) => { + const { + props: { valueKey }, + } = select + + if (!isObject(target)) { + return arr.includes(target) + } + + return ( + arr && + arr.some((item) => { + return get(item, valueKey) === get(target, valueKey) + }) + ) + } + const isEqual = (selected: unknown, target: unknown) => { + if (!isObject(target)) { + return selected === target + } else { + const { valueKey } = select.props + return get(selected, valueKey) === get(target, valueKey) + } + } + + const isItemSelected = (modelValue: any[] | any, target: Option) => { + const { valueKey } = select.props + if (select.props.multiple) { + return contains(modelValue, get(target, valueKey)) + } + return isEqual(modelValue, get(target, valueKey)) + } + + const isItemDisabled = (modelValue: any[] | any, selected: boolean) => { + const { disabled, multiple, multipleLimit } = select.props + return ( + disabled || + (!selected && + (multiple + ? multipleLimit > 0 && modelValue.length >= multipleLimit + : false)) + ) + } + + const isItemHovering = (target: number) => props.hoveringIndex === target + + const scrollToItem = (index: number) => { + const list = listRef.value as any + if (list) { + list.scrollToItem(index) + } + } + + const resetScrollTop = () => { + const list = listRef.value as any + if (list) { + list.resetScrollTop() + } + } + + expose({ + listRef, + isSized, + + isItemDisabled, + isItemHovering, + isItemSelected, + scrollToItem, + resetScrollTop, + }) + + const Item = (itemProps: ItemProps) => { + const { index, data, style } = itemProps + const sized = unref(isSized) + const { itemSize, estimatedSize } = unref(listProps) + const { modelValue } = select.props + const { onSelect, onHover } = select + const item = data[index] + if (item.type === 'Group') { + return ( + + ) + } + + const isSelected = isItemSelected(modelValue, item) + const isDisabled = isItemDisabled(modelValue, isSelected) + const isHovering = isItemHovering(index) + return ( + + {{ + default: (props: OptionItemProps) => + slots.default?.(props) || {item.label}, + }} + + ) + } + + // computed + const { onKeyboardNavigate, onKeyboardSelect } = select + + const onForward = () => { + onKeyboardNavigate('forward') + } + + const onBackward = () => { + onKeyboardNavigate('backward') + } + + const onEscOrTab = () => { + select.expanded = false + } + + const onKeydown = (e: KeyboardEvent) => { + const { code } = e + const { tab, esc, down, up, enter } = EVENT_CODE + if (code !== tab) { + e.preventDefault() + e.stopPropagation() + } + + switch (code) { + case tab: + case esc: { + onEscOrTab() + break + } + case down: { + onForward() + break + } + case up: { + onBackward() + break + } + case enter: { + onKeyboardSelect() + break + } + } + } + + return () => { + const { data, width } = props + const { height, multiple, scrollbarAlwaysOn } = select.props + + if (data.length === 0) { + return ( +
+ {slots.empty?.()} +
+ ) + } + + const List = unref(isSized) ? FixedSizeList : DynamicSizeList + + return ( +
+ + {{ + default: (props: ItemProps) => , + }} + +
+ ) + } + }, +}) diff --git a/packages/components/select-v2/src/select-dropdown.vue b/packages/components/select-v2/src/select-dropdown.vue deleted file mode 100644 index f7bc003f4e..0000000000 --- a/packages/components/select-v2/src/select-dropdown.vue +++ /dev/null @@ -1,275 +0,0 @@ - diff --git a/packages/components/select-v2/src/select.vue b/packages/components/select-v2/src/select.vue index a9b24338b9..0a230963fc 100644 --- a/packages/components/select-v2/src/select.vue +++ b/packages/components/select-v2/src/select.vue @@ -314,7 +314,7 @@ import ElTooltip from '@element-plus/components/tooltip' import ElTag from '@element-plus/components/tag' import ElIcon from '@element-plus/components/icon' import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants' -import ElSelectMenu from './select-dropdown.vue' +import ElSelectMenu from './select-dropdown' import useSelect from './useSelect' import { selectV2InjectionKey } from './token' import { SelectProps } from './defaults' diff --git a/packages/components/select-v2/src/token.ts b/packages/components/select-v2/src/token.ts index ce59a352e7..456fc6b5de 100644 --- a/packages/components/select-v2/src/token.ts +++ b/packages/components/select-v2/src/token.ts @@ -6,6 +6,7 @@ export interface SelectV2Context { props: ExtractPropTypes expanded: boolean onSelect: (option: Option, index: number, byClick?: boolean) => void + onHover: (idx: number) => void onKeyboardNavigate: (direction: 'forward' | 'backward') => void onKeyboardSelect: () => void } diff --git a/packages/components/select-v2/src/useSelect.ts b/packages/components/select-v2/src/useSelect.ts index 7f4eeec6b0..d643beb6a4 100644 --- a/packages/components/select-v2/src/useSelect.ts +++ b/packages/components/select-v2/src/useSelect.ts @@ -261,8 +261,13 @@ const useSelect = (props: ExtractPropTypes, emit) => { return -1 }) - const dropdownMenuVisible = computed(() => { - return expanded.value && emptyText.value !== false + const dropdownMenuVisible = computed({ + get() { + return expanded.value && emptyText.value !== false + }, + set(val: boolean) { + expanded.value = val + }, }) // hooks