chore: wip

This commit is contained in:
Dsaquel
2025-07-07 01:22:45 +02:00
parent 45b429c116
commit b8ca4687a7
5 changed files with 166 additions and 116 deletions

View File

@@ -54,26 +54,16 @@
</template>
<script lang="ts">
import {
computed,
defineComponent,
getCurrentInstance,
inject,
reactive,
toRefs,
watch,
} from 'vue'
import { computed, defineComponent, inject, reactive, toRefs } from 'vue'
import ElScrollbar from '@element-plus/components/scrollbar'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { flattedChildren, isArray, isObject } from '@element-plus/utils'
import { isArray } from '@element-plus/utils'
import ElOption from './option.vue'
import ElSelectMenu from './select-dropdown.vue'
import ElOptions from './options'
import { selectProps } from './select'
import { useFlatSelect } from './useFlatSelect'
import type { VNode } from 'vue'
const COMPONENT_NAME = 'ElSelect'
export default defineComponent({
name: COMPONENT_NAME,
@@ -87,22 +77,7 @@ export default defineComponent({
props: selectProps,
emits: [UPDATE_MODEL_EVENT, CHANGE_EVENT, 'popup-scroll'],
setup(props, { emit, slots }) {
const instance = getCurrentInstance()!
instance.appContext.config.warnHandler = (...args) => {
// Overrides warnings about slots not being executable outside of a render function.
// We call slot below just to simulate data when persist is false, this warning message should be ignored
if (
!args[0] ||
args[0].includes(
'Slot "default" invoked outside of the render function'
)
) {
return
}
// eslint-disable-next-line no-console
console.warn(...args)
}
setup(props, { emit }) {
const modelValue = computed(() => {
const { modelValue: rawModelValue, multiple } = props
const fallback = multiple ? [] : undefined
@@ -121,67 +96,6 @@ export default defineComponent({
})
const API = inject('flat-select', () => useFlatSelect(_props, emit), true)
const flatTreeSelectData = (data: any[]) => {
return data.reduce((acc, item) => {
acc.push(item)
if (item.children && item.children.length > 0) {
acc.push(...flatTreeSelectData(item.children))
}
return acc
}, [])
}
const manuallyRenderSlots = (vnodes: VNode[] | undefined) => {
// After option rendering is completed, the useSelect internal state can collect the value of each option.
// If the persistent value is false, option will not be rendered by default, so in this case,
// manually render and load option data here.
const children = flattedChildren(vnodes || []) as VNode[]
children.forEach((item) => {
if (
isObject(item) &&
// @ts-expect-error
(item.type.name === 'ElOption' || item.type.name === 'ElTree')
) {
// @ts-expect-error
const _name = item.type.name
if (_name === 'ElTree') {
// tree-select component is a special case.
// So we need to handle it separately.
const treeData = item.props?.data || []
const flatData = flatTreeSelectData(treeData)
flatData.forEach((treeItem: any) => {
treeItem.currentLabel =
treeItem.label ||
(isObject(treeItem.value) ? '' : treeItem.value)
API.onOptionCreate(treeItem)
})
} else if (_name === 'ElOption') {
const obj = { ...item.props } as any
obj.currentLabel =
obj.label || (isObject(obj.value) ? '' : obj.value)
API.onOptionCreate(obj)
}
}
})
}
watch(
() => {
const slotsContent = slots.default?.()
return slotsContent
},
(newSlot) => {
if (props.persistent) {
// If persistent is true, we don't need to manually render slots.
return
}
manuallyRenderSlots(newSlot)
},
{
immediate: true,
}
)
return {
...API,
modelValue,

View File

@@ -17,7 +17,7 @@
:effect="effect"
pure
trigger="click"
:transition="`${nsSelect.namespace.value}-zoom-in-top`"
:transition="`${nsSelect.namespace}-zoom-in-top`"
:stop-popper-mouse-event="false"
:gpu-acceleration="false"
:persistent="persistent"
@@ -250,7 +250,21 @@
</template>
<template #content>
<el-flat-select v-bind="$props">
<template v-if="$slots.header">
<slot name="header" />
</template>
<slot />
<template v-if="$slots.footer">
<slot name="footer" />
</template>
<template v-if="$slots.loading">
<slot name="loading" />
</template>
<template v-if="$slots.empty">
<slot name="empty" />
</template>
</el-flat-select>
</template>
</el-tooltip>
@@ -258,7 +272,15 @@
</template>
<script lang="ts">
import { computed, defineComponent, reactive, toRefs } from 'vue'
import {
computed,
defineComponent,
getCurrentInstance,
reactive,
toRefs,
useSlots,
watch,
} from 'vue'
import { ClickOutside } from '@element-plus/directives'
import ElTooltip from '@element-plus/components/tooltip'
import ElScrollbar from '@element-plus/components/scrollbar'
@@ -270,6 +292,10 @@ import ElFlatSelect from './flat-select.vue'
import { useSelect } from './useSelect'
import ElOptions from './options'
import { selectProps } from './select'
import { flattedChildren, isArray, isObject } from '@element-plus/utils'
import { useFlatSelect } from './useFlatSelect'
import type { VNode } from 'vue'
const COMPONENT_NAME = 'ElSelect'
export default defineComponent({
@@ -301,9 +327,86 @@ export default defineComponent({
...toRefs(props),
})
const API = useSelect(_props, emit)
const FLAT_SELECT_API = reactive(useFlatSelect(props, emit))
const API = useSelect(_props, emit, FLAT_SELECT_API)
const { calculatorRef, inputStyle } = useCalcInputWidth()
const instance = getCurrentInstance()!
instance.appContext.config.warnHandler = (...args) => {
// Overrides warnings about slots not being executable outside of a render function.
// We call slot below just to simulate data when persist is false, this warning message should be ignored
if (
!args[0] ||
args[0].includes(
'Slot "default" invoked outside of the render function'
)
) {
return
}
// eslint-disable-next-line no-console
console.warn(...args)
}
const flatTreeSelectData = (data: any[]) => {
return data.reduce((acc, item) => {
acc.push(item)
if (item.children && item.children.length > 0) {
acc.push(...flatTreeSelectData(item.children))
}
return acc
}, [])
}
const manuallyRenderSlots = (vnodes: VNode[] | undefined) => {
// After option rendering is completed, the useSelect internal state can collect the value of each option.
// If the persistent value is false, option will not be rendered by default, so in this case,
// manually render and load option data here.
const children = flattedChildren(vnodes || []) as VNode[]
children.forEach((item) => {
if (
isObject(item) &&
// @ts-expect-error
(item.type.name === 'ElOption' || item.type.name === 'ElTree')
) {
// @ts-expect-error
const _name = item.type.name
if (_name === 'ElTree') {
// tree-select component is a special case.
// So we need to handle it separately.
const treeData = item.props?.data || []
const flatData = flatTreeSelectData(treeData)
flatData.forEach((treeItem: any) => {
treeItem.currentLabel =
treeItem.label ||
(isObject(treeItem.value) ? '' : treeItem.value)
FLAT_SELECT_API.onOptionCreate(treeItem)
})
} else if (_name === 'ElOption') {
const obj = { ...item.props } as any
obj.currentLabel =
obj.label || (isObject(obj.value) ? '' : obj.value)
FLAT_SELECT_API.onOptionCreate(obj)
}
}
})
}
const slots = useSlots()
watch(
() => {
const slotsContent = slots.default?.()
return slotsContent
},
(newSlot) => {
if (props.persistent) {
// If persistent is true, we don't need to manually render slots.
return
}
manuallyRenderSlots(newSlot)
},
{
immediate: true,
}
)
const selectedLabel = computed(() => {
if (!props.multiple) {
return API.states.selectedLabel
@@ -311,8 +414,21 @@ export default defineComponent({
return API.states.selected.map((i) => i.currentLabel as string)
})
const modelValue = computed(() => {
const { modelValue: rawModelValue, multiple } = props
const fallback = multiple ? [] : undefined
// When it is array, we check if this is multi-select.
// Based on the result we get
if (isArray(rawModelValue)) {
return multiple ? rawModelValue : fallback
}
return multiple ? fallback : rawModelValue
})
return {
...API,
modelValue,
calculatorRef,
inputStyle,
selectedLabel,

View File

@@ -4,7 +4,11 @@ import type {
ComputedRef,
ExtractPropTypes,
Ref,
UnwrapNestedRefs,
} from 'vue'
import type { ScrollbarInstance } from '@element-plus/components/scrollbar'
import type { Translator } from '@element-plus/hooks/use-locale'
import type { UseNamespaceReturn } from '@element-plus/hooks/use-namespace'
import type { SelectProps } from './select'
import type { optionProps } from './option'
@@ -19,6 +23,28 @@ export interface SelectContext extends FlatSelectContext {
export interface FlatSelectContext {
props: SelectProps
states: SelectStates
t: Translator
contentId: string
nsSelect: UnwrapNestedRefs<UseNamespaceReturn>
filteredOptionsCount: number
scrollToOption: (
option:
| OptionPublicInstance
| OptionPublicInstance[]
| SelectStates['selected']
) => void
hasModelValue: boolean
updateOptions: () => void
showNewOption: boolean
emptyText: string | null
selectOption: () => void
getValueKey: (
item: OptionPublicInstance | SelectStates['selected'][number]
) => any
popupScroll: (data: { scrollTop: number; scrollLeft: number }) => void
scrollbarRef: ScrollbarInstance | undefined
menuRef: HTMLElement | undefined
handleQueryChange: (val: string) => void
optionsArray: OptionPublicInstance[]
setSelected(): void
onOptionCreate(vm: OptionPublicInstance): void

View File

@@ -380,20 +380,10 @@ export const useFlatSelect = (props: SelectProps, emit: FlatSelectEmits) => {
// DOM ref
scrollbarRef,
menuRef,
props,
}
provide(
flatSelectKey,
reactive({
props,
states,
optionsArray,
setSelected,
handleOptionSelect,
onOptionCreate,
onOptionDestroy,
})
)
provide(flatSelectKey, reactive(FLAT_SELECT_API))
provide('flat-select', FLAT_SELECT_API)

View File

@@ -44,19 +44,22 @@ import {
useFormSize,
} from '@element-plus/components/form'
import { selectKey } from './token'
import { useFlatSelect } from './useFlatSelect'
import type { TooltipInstance } from '@element-plus/components/tooltip'
import type { SelectEmits, SelectProps } from './select'
import type {
FlatSelectContext,
OptionBasic,
OptionPublicInstance,
OptionValue,
SelectStates,
} from './type'
export const useSelect = (props: SelectProps, emit: SelectEmits) => {
const FLAT_SELECT_API = useFlatSelect(props, emit)
export const useSelect = (
props: SelectProps,
emit: SelectEmits,
FLAT_SELECT_API: FlatSelectContext
) => {
const {
t,
contentId,
@@ -471,7 +474,7 @@ export const useSelect = (props: SelectProps, emit: SelectEmits) => {
scrollIntoView(menu as HTMLElement, target)
}
}
scrollbarRef.value?.handleScroll()
FLAT_SELECT_API.scrollbarRef?.handleScroll()
}
const popperRef = computed(() => {
@@ -481,7 +484,7 @@ export const useSelect = (props: SelectProps, emit: SelectEmits) => {
const handleMenuEnter = () => {
states.isBeforeHide = false
nextTick(() => {
scrollbarRef.value?.update()
FLAT_SELECT_API.scrollbarRef?.update()
scrollToOption(states.selected)
})
}
@@ -654,14 +657,15 @@ export const useSelect = (props: SelectProps, emit: SelectEmits) => {
provide(
selectKey,
reactive({
props,
states: FLAT_SELECT_API.states,
//props,
//states: FLAT_SELECT_API.states,
selectRef,
optionsArray: FLAT_SELECT_API.optionsArray,
setSelected: FLAT_SELECT_API.setSelected,
handleOptionSelect: FLAT_SELECT_API.handleOptionSelect,
onOptionCreate: FLAT_SELECT_API.onOptionCreate,
onOptionDestroy: FLAT_SELECT_API.onOptionDestroy,
//optionsArray: FLAT_SELECT_API.optionsArray,
//setSelected: FLAT_SELECT_API.setSelected,
//handleOptionSelect: FLAT_SELECT_API.handleOptionSelect,
//onOptionCreate: FLAT_SELECT_API.onOptionCreate,
//onOptionDestroy: FLAT_SELECT_API.onOptionDestroy,
...FLAT_SELECT_API,
})
)