From dbbacae7d1133afc2f1312fda4f82ff90d1815db Mon Sep 17 00:00:00 2001 From: snowbitx <109521682+snowbitx@users.noreply.github.com> Date: Tue, 29 Jul 2025 09:54:30 +0800 Subject: [PATCH] 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> --- docs/en-US/component/select.md | 110 +++++++++-------- docs/examples/select/options.vue | 37 ++++++ .../select/__tests__/select.test.ts | 116 ++++++++++++++++++ packages/components/select/src/select.ts | 11 ++ packages/components/select/src/select.vue | 10 +- 5 files changed, 233 insertions(+), 51 deletions(-) create mode 100644 docs/examples/select/options.vue diff --git a/docs/en-US/component/select.md b/docs/en-US/component/select.md index 25cb751ac2..3e7921a814 100644 --- a/docs/en-US/component/select.md +++ b/docs/en-US/component/select.md @@ -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 diff --git a/docs/examples/select/options.vue b/docs/examples/select/options.vue new file mode 100644 index 0000000000..09415b6871 --- /dev/null +++ b/docs/examples/select/options.vue @@ -0,0 +1,37 @@ + + + diff --git a/packages/components/select/__tests__/select.test.ts b/packages/components/select/__tests__/select.test.ts index 7468c2a6bb..1fa6daff01 100644 --- a/packages/components/select/__tests__/select.test.ts +++ b/packages/components/select/__tests__/select.test.ts @@ -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( + ``, + () => ({ + 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( + ``, + () => ({ + 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) + }) }) diff --git a/packages/components/select/src/select.ts b/packages/components/select/src/select.ts index 2b037c4a4b..92cd04ca95 100644 --- a/packages/components/select/src/select.ts +++ b/packages/components/select/src/select.ts @@ -270,6 +270,12 @@ export const selectProps = buildProps({ * @description which element the selection dropdown appends to */ appendTo: useTooltipContentProps.appendTo, + options: { + type: definePropType[]>(Array), + }, + props: { + type: definePropType(Object), + }, ...useEmptyValuesProps, ...useAriaProps(['ariaLabel']), }) @@ -290,3 +296,8 @@ export type SelectProps = ExtractPropTypes export type SelectPropsPublic = __ExtractPublicPropTypes export type SelectEmits = EmitFn export type SelectInstance = InstanceType & unknown +export type SelectOptionProps = { + value?: string + label?: string + disabled?: string +} diff --git a/packages/components/select/src/select.vue b/packages/components/select/src/select.vue index 5abeef68cf..4e3b5c64cf 100644 --- a/packages/components/select/src/select.vue +++ b/packages/components/select/src/select.vue @@ -276,7 +276,15 @@ :created="true" /> - + + +