mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-05 04:37:47 +08:00
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:
parent
676f27a768
commit
9f5aae64ea
246
packages/components/select-v2/src/select-dropdown.tsx
Normal file
246
packages/components/select-v2/src/select-dropdown.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
@ -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>
|
@ -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'
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user