feat(components): [select] support options prop rendering (#21203)

* feat(components): [select] support options prop rendering

* feat(components):[select] format

* feat(components):[select] format

* Update packages/components/select/src/select.vue

Thanks for the guidance!

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>

* Update packages/components/select/src/select.vue

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>

* Merge remote-tracking branch 'upstream/dev' into feat/autocomplete-size

* feat(components): [select] format

* feat(components): [select] Add test case

* feat(components): [select] Add doc

* Update packages/components/select/src/select.ts

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>

* feat(components): [select] Code optimization

* feat(components): [select] Code optimization

* feat(components): [select] format

* feat(components): [select] format

* docs: refact

* refactor: simplify

* chore: useless diff change

* Update shared.ts

* Update message.ts

* Update popper.ts

* Update result.ts

* Update form-item.ts

* Update cascader-panel.test.tsx

* chore: useless change

* Update select.vue

* Update option.vue

* Update select.vue

---------

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
Co-authored-by: Dsaquel <291874700n@gmail.com>
This commit is contained in:
snowbitx
2025-07-29 09:54:30 +08:00
committed by GitHub
parent 54cb51f2d9
commit dbbacae7d1
5 changed files with 233 additions and 51 deletions

View File

@@ -21,6 +21,14 @@ select/basic-usage
:::
## Options attribute ^(2.10.5)
:::demo Shortcut from basic `el-option` usage. You can customize the alias of the `options` through the `props` attribute.
select/options
:::
## Disabled option
:::demo Set the value of `disabled` in `el-option` to `true` to disable this option.
@@ -193,56 +201,58 @@ select/custom-label
### Select Attributes
| Name | Description | Type | Default |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| model-value / v-model | binding value | ^[string] / ^[number] / ^[boolean] / ^[object] / ^[array] | — |
| multiple | whether multiple-select is activated | ^[boolean] | false |
| disabled | whether Select is disabled | ^[boolean] | false |
| value-key | unique identity key name for value, required when value is an object | ^[string] | value |
| size | size of Input | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | |
| clearable | whether select can be cleared | ^[boolean] | false |
| collapse-tags | whether to collapse tags to a text when multiple selecting | ^[boolean] | false |
| collapse-tags-tooltip ^(2.3.0) | whether show all selected tags when mouse hover text of collapse-tags. To use this, `collapse-tags` must be true | ^[boolean] | false |
| multiple-limit | maximum number of options user can select when `multiple` is `true`. No limit when set to 0 | ^[number] | 0 |
| id | native input id input | ^[string] | |
| name | the name attribute of select input | ^[string] | |
| effect | tooltip theme, built-in theme: `dark` / `light` | ^[enum]`'dark' \| 'light'` / ^[string] | light |
| autocomplete | the autocomplete attribute of select input | ^[string] | off |
| placeholder | placeholder, default is 'Select' | ^[string] | — |
| filterable | whether Select is filterable | ^[boolean] | false |
| allow-create | whether creating new items is allowed. To use this, `filterable` must be true | ^[boolean] | false |
| filter-method | custom filter method, the first parameter is the current input value. To use this, `filterable` must be true | ^[Function]`(query: string) => void` | |
| remote | whether options are loaded from server | ^[boolean] | false |
| remote-method | function that gets called when the input value changes. Its parameter is the current input value. To use this, `filterable` must be true | ^[Function]`(query: string) => void` | — |
| remote-show-suffix | in remote search method show suffix icon | ^[boolean] | false |
| loading | whether Select is loading data from server | ^[boolean] | false |
| loading-text | displayed text while loading data from server, default is 'Loading' | ^[string] | |
| no-match-text | displayed text when no data matches the filtering query, you can also use slot `empty`, default is 'No matching data' | ^[string] | |
| no-data-text | displayed text when there is no options, you can also use slot `empty`, default is 'No data' | ^[string] | — |
| popper-class | custom class name for Select's dropdown | ^[string] | '' |
| reserve-keyword | when `multiple` and `filterable` is true, whether to reserve current keyword after selecting an option | ^[boolean] | true |
| default-first-option | select first matching option on enter key. Use with `filterable` or `remote` | ^[boolean] | false |
| teleported | whether select dropdown is teleported, if `true` it will be teleported to where `append-to` sets | ^[boolean] | true |
| append-to ^(2.8.4) | which element the select dropdown appends to | ^[CSSSelector] / ^[HTMLElement] | |
| persistent | when select dropdown is inactive and `persistent` is `false`, select dropdown will be destroyed | ^[boolean] | true |
| automatic-dropdown | for non-filterable Select, this prop decides if the option menu pops up when the input is focused | ^[boolean] | false |
| clear-icon | custom clear icon component | ^[string] / ^[object]`Component` | CircleClose |
| fit-input-width | whether the width of the dropdown is the same as the input | ^[boolean] | false |
| suffix-icon | custom suffix icon component | ^[string] / ^[object]`Component` | ArrowDown |
| tag-type | tag type | ^[enum]`'' \| 'success' \| 'info' \| 'warning' \| 'danger'` | info |
| tag-effect ^(2.7.7) | tag effect | ^[enum]`'' \| 'light' \| 'dark' \| 'plain'` | light |
| validate-event | whether to trigger form validation | ^[boolean] | true |
| offset ^(2.8.8) | offset of the dropdown | ^[number] | 12 |
| show-arrow ^(2.8.8) | whether the dropdown has an arrow | ^[boolean] | true |
| placement ^(2.2.17) | position of dropdown | ^[enum]`'top' \| 'top-start' \| 'top-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'left' \| 'left-start' \| 'left-end' \| 'right' \| 'right-start' \| 'right-end'` | bottom-start |
| fallback-placements ^(2.5.6) | list of possible positions for dropdown [popper.js](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) | ^[array]`Placement[]` | ['bottom-start', 'top-start', 'right', 'left'] |
| max-collapse-tags ^(2.3.0) | the max tags number to be shown. To use this, `collapse-tags` must be true | ^[number] | 1 |
| popper-options | [popper.js](https://popper.js.org/docs/v2/) parameters | ^[object]refer to [popper.js](https://popper.js.org/docs/v2/) doc | {} |
| aria-label ^(a11y) | same as `aria-label` in native input | ^[string] | |
| empty-values ^(2.7.0) | empty values of component, [see config-provider](/en-US/component/config-provider#empty-values-configurations) | ^[array] | |
| value-on-clear ^(2.7.0) | clear return value, [see config-provider](/en-US/component/config-provider#empty-values-configurations) | ^[string] / ^[number] / ^[boolean] / ^[Function] | — |
| suffix-transition ^(deprecated) | animation when dropdown appears/disappears icon | ^[boolean] | true |
| tabindex ^(2.9.0) | tabindex for input | ^[string] / ^[number] | — |
| Name | Description | Type | Default |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| model-value / v-model | binding value | ^[string] / ^[number] / ^[boolean] / ^[object] / ^[array] | — |
| multiple | whether multiple-select is activated | ^[boolean] | false |
| options ^(2.10.5) | data of the options, the key of `value` and `label` and `disabled` can be customize by `props` | ^[array]`Array<{[key: string]: any}>` | |
| props ^(2.10.5) | configuration options | ^[object]`{ value?: string, label?: string, disabled?: boolean}` | `{value: 'value', label: 'label', disabled: 'disabled'}` |
| disabled | whether Select is disabled | ^[boolean] | false |
| value-key | unique identity key name for value, required when value is an object | ^[string] | value |
| size | size of Input | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | |
| clearable | whether select can be cleared | ^[boolean] | false |
| collapse-tags | whether to collapse tags to a text when multiple selecting | ^[boolean] | false |
| collapse-tags-tooltip ^(2.3.0) | whether show all selected tags when mouse hover text of collapse-tags. To use this, `collapse-tags` must be true | ^[boolean] | false |
| multiple-limit | maximum number of options user can select when `multiple` is `true`. No limit when set to 0 | ^[number] | 0 |
| id | native input id input | ^[string] | — |
| name | the name attribute of select input | ^[string] | |
| effect | tooltip theme, built-in theme: `dark` / `light` | ^[enum]`'dark' \| 'light'` / ^[string] | light |
| autocomplete | the autocomplete attribute of select input | ^[string] | off |
| placeholder | placeholder, default is 'Select' | ^[string] | |
| filterable | whether Select is filterable | ^[boolean] | false |
| allow-create | whether creating new items is allowed. To use this, `filterable` must be true | ^[boolean] | false |
| filter-method | custom filter method, the first parameter is the current input value. To use this, `filterable` must be true | ^[Function]`(query: string) => void` | — |
| remote | whether options are loaded from server | ^[boolean] | false |
| remote-method | function that gets called when the input value changes. Its parameter is the current input value. To use this, `filterable` must be true | ^[Function]`(query: string) => void` | |
| remote-show-suffix | in remote search method show suffix icon | ^[boolean] | false |
| loading | whether Select is loading data from server | ^[boolean] | false |
| loading-text | displayed text while loading data from server, default is 'Loading' | ^[string] | — |
| no-match-text | displayed text when no data matches the filtering query, you can also use slot `empty`, default is 'No matching data' | ^[string] | |
| no-data-text | displayed text when there is no options, you can also use slot `empty`, default is 'No data' | ^[string] | |
| popper-class | custom class name for Select's dropdown | ^[string] | '' |
| reserve-keyword | when `multiple` and `filterable` is true, whether to reserve current keyword after selecting an option | ^[boolean] | true |
| default-first-option | select first matching option on enter key. Use with `filterable` or `remote` | ^[boolean] | false |
| teleported | whether select dropdown is teleported, if `true` it will be teleported to where `append-to` sets | ^[boolean] | true |
| append-to ^(2.8.4) | which element the select dropdown appends to | ^[CSSSelector] / ^[HTMLElement] | |
| persistent | when select dropdown is inactive and `persistent` is `false`, select dropdown will be destroyed | ^[boolean] | true |
| automatic-dropdown | for non-filterable Select, this prop decides if the option menu pops up when the input is focused | ^[boolean] | false |
| clear-icon | custom clear icon component | ^[string] / ^[object]`Component` | CircleClose |
| fit-input-width | whether the width of the dropdown is the same as the input | ^[boolean] | false |
| suffix-icon | custom suffix icon component | ^[string] / ^[object]`Component` | ArrowDown |
| tag-type | tag type | ^[enum]`'' \| 'success' \| 'info' \| 'warning' \| 'danger'` | info |
| tag-effect ^(2.7.7) | tag effect | ^[enum]`'' \| 'light' \| 'dark' \| 'plain'` | light |
| validate-event | whether to trigger form validation | ^[boolean] | true |
| offset ^(2.8.8) | offset of the dropdown | ^[number] | 12 |
| show-arrow ^(2.8.8) | whether the dropdown has an arrow | ^[boolean] | true |
| placement ^(2.2.17) | position of dropdown | ^[enum]`'top' \| 'top-start' \| 'top-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'left' \| 'left-start' \| 'left-end' \| 'right' \| 'right-start' \| 'right-end'` | bottom-start |
| fallback-placements ^(2.5.6) | list of possible positions for dropdown [popper.js](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) | ^[array]`Placement[]` | ['bottom-start', 'top-start', 'right', 'left'] |
| max-collapse-tags ^(2.3.0) | the max tags number to be shown. To use this, `collapse-tags` must be true | ^[number] | 1 |
| popper-options | [popper.js](https://popper.js.org/docs/v2/) parameters | ^[object]refer to [popper.js](https://popper.js.org/docs/v2/) doc | {} |
| aria-label ^(a11y) | same as `aria-label` in native input | ^[string] | — |
| empty-values ^(2.7.0) | empty values of component, [see config-provider](/en-US/component/config-provider#empty-values-configurations) | ^[array] | |
| value-on-clear ^(2.7.0) | clear return value, [see config-provider](/en-US/component/config-provider#empty-values-configurations) | ^[string] / ^[number] / ^[boolean] / ^[Function] | — |
| suffix-transition ^(deprecated) | animation when dropdown appears/disappears icon | ^[boolean] | true |
| tabindex ^(2.9.0) | tabindex for input | ^[string] / ^[number] | — |
:::warning

View File

@@ -0,0 +1,37 @@
<template>
<el-select
v-model="value"
:options="options"
placeholder="Select"
style="width: 240px"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref('')
const options = [
{
value: 'Option1',
label: 'Option1',
},
{
value: 'Option2',
label: 'Option2',
},
{
value: 'Option3',
label: 'Option3',
},
{
value: 'Option4',
label: 'Option4',
},
{
value: 'Option5',
label: 'Option5',
},
]
</script>

View File

@@ -3430,4 +3430,120 @@ describe('Select', () => {
await wrapper.setData({ formDisabled: false })
expect(wrapper.find('.custom-tag').text()).toBe('enabled')
})
test('renders options via props', async () => {
wrapper = _mount(
`<el-select v-model="value" @change="handleChange" :options="options"/>`,
() => ({
options: [
{
value: '选项1',
label: '黄金糕',
},
{
value: '选项2',
label: '双皮奶',
},
{
value: '选项3',
label: '蚵仔煎',
},
{
value: '选项4',
label: '龙须面',
},
{
value: '选项5',
label: '北京烤鸭',
},
],
value: '',
count: 0,
}),
{
methods: {
handleChange() {
this.count++
},
},
}
)
await wrapper.find(`.${WRAPPER_CLASS_NAME}`).trigger('click')
const options = getOptions()
const vm = wrapper.vm as any
expect(vm.value).toBe('')
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
DEFAULT_PLACEHOLDER
)
options[2].click()
await nextTick()
expect(vm.value).toBe('选项3')
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('蚵仔煎')
expect(vm.count).toBe(1)
options[4].click()
await nextTick()
expect(vm.value).toBe('选项5')
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('北京烤鸭')
expect(vm.count).toBe(2)
})
test('renders options with custom field names', async () => {
wrapper = _mount(
`<el-select v-model="value" @change="handleChange" :options="options" :props="{
value:'id'
}"/>`,
() => ({
options: [
{
id: 1,
label: '黄金糕',
},
{
id: 2,
label: '双皮奶',
},
{
id: 3,
label: '蚵仔煎',
},
{
id: 4,
label: '龙须面',
},
{
id: 5,
label: '北京烤鸭',
},
],
value: '',
count: 0,
}),
{
methods: {
handleChange() {
this.count++
},
},
}
)
await wrapper.find(`.${WRAPPER_CLASS_NAME}`).trigger('click')
const options = getOptions()
const vm = wrapper.vm as any
expect(vm.value).toBe('')
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
DEFAULT_PLACEHOLDER
)
options[2].click()
await nextTick()
expect(vm.value).toBe(3)
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('蚵仔煎')
expect(vm.count).toBe(1)
options[4].click()
await nextTick()
expect(vm.value).toBe(5)
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('北京烤鸭')
expect(vm.count).toBe(2)
})
})

View File

@@ -270,6 +270,12 @@ export const selectProps = buildProps({
* @description which element the selection dropdown appends to
*/
appendTo: useTooltipContentProps.appendTo,
options: {
type: definePropType<Record<string, any>[]>(Array),
},
props: {
type: definePropType<SelectOptionProps>(Object),
},
...useEmptyValuesProps,
...useAriaProps(['ariaLabel']),
})
@@ -290,3 +296,8 @@ export type SelectProps = ExtractPropTypes<typeof selectProps>
export type SelectPropsPublic = __ExtractPublicPropTypes<typeof selectProps>
export type SelectEmits = EmitFn<typeof selectEmits>
export type SelectInstance = InstanceType<typeof Select> & unknown
export type SelectOptionProps = {
value?: string
label?: string
disabled?: string
}

View File

@@ -276,7 +276,15 @@
:created="true"
/>
<el-options>
<slot />
<slot>
<el-option
v-for="(item, index) in options"
:key="index"
:label="item[props?.label ?? 'label']"
:value="item[props?.value ?? 'value']"
:disabled="item[props?.disabled ?? 'disabled']"
/>
</slot>
</el-options>
</el-scrollbar>
<div