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>
This commit is contained in:
Jeremy 2022-06-07 17:23:12 +08:00 committed by GitHub
parent 676f27a768
commit 9f5aae64ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 255 additions and 278 deletions

View File

@ -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<Array<number>>([])
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<any> = [], 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<any>) => {
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 (
<GroupItem
item={item}
style={style}
height={(sized ? itemSize : estimatedSize) as number}
/>
)
}
const isSelected = isItemSelected(modelValue, item)
const isDisabled = isItemDisabled(modelValue, isSelected)
const isHovering = isItemHovering(index)
return (
<OptionItem
{...itemProps}
selected={isSelected}
disabled={item.disabled || isDisabled}
created={!!item.created}
hovering={isHovering}
item={item}
onSelect={onSelect}
onHover={onHover}
>
{{
default: (props: OptionItemProps) =>
slots.default?.(props) || <span>{item.label}</span>,
}}
</OptionItem>
)
}
// 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 (
<div
class={ns.b('dropdown')}
style={{
width: `${width}px`,
}}
>
{slots.empty?.()}
</div>
)
}
const List = unref(isSized) ? FixedSizeList : DynamicSizeList
return (
<div class={[ns.b('dropdown'), ns.is('multiple', multiple)]}>
<List
ref={listRef}
{...unref(listProps)}
className={ns.be('dropdown', 'list')}
scrollbarAlwaysOn={scrollbarAlwaysOn}
data={data}
height={height}
width={width}
total={data.length}
onKeydown={onKeydown}
>
{{
default: (props: ItemProps<any>) => <Item {...props} />,
}}
</List>
</div>
)
}
},
})

View File

@ -1,275 +0,0 @@
<script lang="ts">
import {
computed,
defineComponent,
h,
inject,
ref,
renderSlot,
withCtx,
withKeys,
withModifiers,
} 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 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: Array,
hoveringIndex: Number,
width: Number,
},
setup(props) {
const select = inject(selectV2InjectionKey) as any
const ns = useNamespace('select')
const cachedHeights = ref<Array<number>>([])
const listRef = ref(null)
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<any> = [], 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()
}
}
// computed
return {
ns,
select,
listProps,
listRef,
isSized,
isItemDisabled,
isItemHovering,
isItemSelected,
scrollToItem,
resetScrollTop,
}
},
render(_ctx, _cache) {
const {
$slots,
data,
listProps,
select,
isSized,
width,
ns,
// methods
isItemDisabled,
isItemHovering,
isItemSelected,
} = _ctx
const Comp = isSized ? FixedSizeList : DynamicSizeList
const {
props: selectProps,
onSelect,
onHover,
onKeyboardNavigate,
onKeyboardSelect,
} = select
const { height, modelValue, multiple } = selectProps
if (data.length === 0) {
return h(
'div',
{
class: ns.b('dropdown'),
style: {
width: `${width}px`,
},
},
$slots.empty?.()
)
}
const ListItem = withCtx((scoped: ItemProps<any>) => {
const { index, data } = scoped
const item = data[index]
// render group item which is not selectable.
if (data[index].type === 'Group') {
return h(GroupItem, {
item,
style: scoped.style,
height: isSized ? listProps.itemSize : listProps.estimatedSize,
})
}
const selected = isItemSelected(modelValue, item)
const itemDisabled = isItemDisabled(modelValue, selected)
// render option item which is selectable
return h(
OptionItem,
{
...scoped,
selected,
disabled: item.disabled || itemDisabled,
created: !!item.created,
hovering: isItemHovering(index),
item,
onSelect,
onHover,
},
{
default: withCtx((props: OptionItemProps) => {
return renderSlot($slots, 'default', props, () => [
h('span', item.label),
])
}),
}
)
})
const List = h(
Comp,
{
ref: 'listRef', // forwarded ref so that select can access the list directly
className: ns.be('dropdown', 'list'),
data,
height,
width,
total: data.length,
scrollbarAlwaysOn: selectProps.scrollbarAlwaysOn,
onKeydown: [
_cache[1] ||
(_cache[1] = withKeys(
withModifiers(
() => onKeyboardNavigate('forward'),
['stop', 'prevent']
),
['down']
)),
_cache[2] ||
(_cache[2] = withKeys(
withModifiers(
() => onKeyboardNavigate('backward'),
['stop', 'prevent']
),
['up']
)),
_cache[3] ||
(_cache[3] = withKeys(
withModifiers(onKeyboardSelect, ['stop', 'prevent']),
['enter']
)),
_cache[4] ||
(_cache[4] = withKeys(
withModifiers(
() => (select.expanded = false),
['stop', 'prevent']
),
['esc']
)),
_cache[5] ||
(_cache[5] = withKeys(() => (select.expanded = false), ['tab'])),
// _cache[6] || (_cache[6] = () => {
// console.log(11)
// }),
],
...listProps,
},
{
default: ListItem,
}
)
return h(
'div',
{
class: [ns.b('dropdown'), ns.is('multiple', multiple)],
},
[List]
)
},
})
</script>

View File

@ -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'

View File

@ -6,6 +6,7 @@ export interface SelectV2Context {
props: ExtractPropTypes<typeof SelectProps>
expanded: boolean
onSelect: (option: Option<any>, index: number, byClick?: boolean) => void
onHover: (idx: number) => void
onKeyboardNavigate: (direction: 'forward' | 'backward') => void
onKeyboardSelect: () => void
}

View File

@ -261,8 +261,13 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, 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