feat(components): add empty-values and value-on-clear props (#16361)

* feat(components): add empty values

* feat(hooks): update

* feat(components): update

* feat(components): update

* feat: update

* feat(components): update

* feat(components): update

* feat(components): update

* feat: update doc

* feat: add doc
This commit is contained in:
kooriookami 2024-04-12 13:33:21 +08:00 committed by GitHub
parent 18fa408391
commit 1163d27f71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 564 additions and 188 deletions

View File

@ -138,7 +138,7 @@ cascader/panel
### Cascader Attributes
| Name | Description | Type | Default |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------- |
|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|---------|
| model-value / v-model | binding value | ^[string]/^[number]/^[object]`string[] \| number[] \| any` | — |
| options | data of the options, the key of `value` and `label` can be customize by `CascaderProps`. | ^[object]`Record<string, unknown>[]` | — |
| props | configuration options, see the following `CascaderProps` table. | ^[object]`CascaderProps` | — |
@ -160,11 +160,13 @@ cascader/panel
| tag-type | tag type | ^[enum]`'success' \| 'info' \| 'warning' \| 'danger'` | info |
| validate-event | whether to trigger form validation | ^[boolean] | true |
| max-collapse-tags ^(2.3.10) | The max tags number to be shown. To use this, `collpase-tags` must be true | ^[number] | 1 |
| 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] | — |
### Cascader Events
| Name | Description | Type |
| -------------- | --------------------------------------------------- | ----------------------------------------------------------- |
|----------------|-----------------------------------------------------|-------------------------------------------------------------|
| change | triggers when the binding value changes | ^[Function]`(value: CascaderValue) => void` |
| expand-change | triggers when expand option changes | ^[Function]`(value: CascaderValue) => void` |
| blur | triggers when Cascader blurs | ^[Function]`(event: FocusEvent) => void` |
@ -175,14 +177,14 @@ cascader/panel
### Cascader Slots
| Name | Description | Scope |
| ------- | ---------------------------------------------------------------------------------------------- | ----------------------------------- |
|---------|------------------------------------------------------------------------------------------------|-------------------------------------|
| default | the custom content of cascader node, which are current Node object and node data respectively. | ^[object]`{ node: any, data: any }` |
| empty | content when there is no matched options. | — |
### Cascader Exposes
| Name | Description | Type |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|-------------------------------|-------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
| getCheckedNodes | get an array of currently selected node,(leafOnly) whether only return the leaf checked nodes, default is `false` | ^[Function]`(leafOnly: boolean) => CascaderNode[] \| undefined` |
| cascaderPanelRef | cascader panel ref | ^[object]`ComputedRef<any>` |
| togglePopperVisible ^(2.2.31) | toggle the visible type of popper | ^[Function]`(visible?: boolean) => void` |
@ -193,7 +195,7 @@ cascader/panel
### CascaderPanel Attributes
| Name | Description | Type | Default |
| --------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- |
|-----------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------|---------|
| model-value / v-model | binding value | ^[string]/^[number]/^[object]`string[] \| number[] \| any` | — |
| options | data of the options, the key of `value` and `label` can be customize by `CascaderProps`. | ^[object]`Record<string, unknown>[]` | — |
| props | configuration options, see the following `CascaderProps` table. | ^[object]`CascaderProps` | — |
@ -201,7 +203,7 @@ cascader/panel
### CascaderPanel Events
| Name | Description | Type |
| ------------- | ----------------------------------------------------------------------- | --------------------------------------------------- |
|---------------|-------------------------------------------------------------------------|-----------------------------------------------------|
| change | triggers when the binding value changes | ^[Function]`(value: CascaderValue) => void` |
| expand-change | triggers when expand option changes | ^[Function]`(value: CascaderNodePathValue) => void` |
| close | close panel event, provided to Cascader to put away the panel judgment. | ^[Function]`() => void` |
@ -209,20 +211,20 @@ cascader/panel
### CascaderPanel Slots
| Name | Description | Scope |
| ------- | ---------------------------------------------------------------------------------------------- | ----------------------------------- |
|---------|------------------------------------------------------------------------------------------------|-------------------------------------|
| default | the custom content of cascader node, which are current Node object and node data respectively. | ^[object]`{ node: any, data: any }` |
### CascaderPanel Exposes
| Name | Description | Type |
| ----------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|-------------------|-------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
| getCheckedNodes | get an array of currently selected node,(leafOnly) whether only return the leaf checked nodes, default is `false` | ^[Function]`(leafOnly: boolean) => CascaderNode[] \| undefined` |
| clearCheckedNodes | clear checked nodes | ^[Function]`() => void` |
## CascaderProps
| Attribute | Description | Type | Default |
| -------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------- |
|----------------|------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|----------|
| expandTrigger | trigger mode of expanding options | ^[enum]`'click' \| 'hover'` | click |
| multiple | whether multiple selection is enabled | ^[boolean] | false |
| checkStrictly | whether checked state of a node not affects its parent and child nodes | ^[boolean] | false |

View File

@ -33,6 +33,31 @@ config-provider/message
:::
## Empty Values Configurations ^(2.7.0)
<details>
<summary>Supported components list</summary>
- Cascader
- DatePicker
- Select
- SelectV2
- TimePicker
- TimeSelect
- TreeSelect
</details>
Set `empty-values` to support empty values of components. The fallback value is `['', null, undefined]`. If you think the empty string is meaningful, write `[undefined, null]`.
Set `value-on-clear` to set the return value when cleared. The fallback value is `undefined`. In the date component is `null`. If you want to set `undefined`, use `() => undefined`.
:::demo
config-provider/empty-values
:::
## Experimental features
In this section, you can learn how to use Config Provider to provide experimental features. For now, we haven't added any experimental features, but in the feature roadmap, we will add some experimental features. You can use this config to manage the features you want or not.
@ -43,30 +68,32 @@ In this section, you can learn how to use Config Provider to provide experimenta
### Config Provider Attributes
| Name | Description | Type | Default |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| locale | Locale Object | ^[object]`{name: string, el: TranslatePair}`[](https://github.com/element-plus/element-plus/blob/a98ff9b40c0c3d2b9959f99919bd8363e3e3c25a/packages/locale/index.ts#L5) [languages](https://github.com/element-plus/element-plus/tree/dev/packages/locale/lang) | [en](https://github.com/element-plus/element-plus/blob/dev/packages/locale/lang/en.ts) |
| size | global component size | ^[enum]`'large' \| 'default' \| 'small'` | default |
| zIndex | global Initial zIndex | ^[number] | — |
| namespace | global component className prefix (cooperated with [$namespace](https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/mixins/config.scss#L1)) | ^[string] | el |
| button | button related configuration, [see the following table](#button-attribute) | ^[object]`{autoInsertSpace?: boolean}` | see the following table |
| message | message related configuration, [see the following table](#message-attribute) | ^[object]`{max?: number}` | see the following table |
| experimental-features | features at experimental stage to be added, all features are default to be set to false | ^[object] | — |
| Name | Description | Type | Default |
|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
| locale | Locale Object | ^[object]`{name: string, el: TranslatePair}`[](https://github.com/element-plus/element-plus/blob/a98ff9b40c0c3d2b9959f99919bd8363e3e3c25a/packages/locale/index.ts#L5) [languages](https://github.com/element-plus/element-plus/tree/dev/packages/locale/lang) | [en](https://github.com/element-plus/element-plus/blob/dev/packages/locale/lang/en.ts) |
| size | global component size | ^[enum]`'large' \| 'default' \| 'small'` | default |
| zIndex | global Initial zIndex | ^[number] | — |
| namespace | global component className prefix (cooperated with [$namespace](https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/mixins/config.scss#L1)) | ^[string] | el |
| button | button related configuration, [see the following table](#button-attribute) | ^[object]`{autoInsertSpace?: boolean}` | see the following table |
| message | message related configuration, [see the following table](#message-attribute) | ^[object]`{max?: number}` | see the following table |
| experimental-features | features at experimental stage to be added, all features are default to be set to false | ^[object] | — |
| empty-values ^(2.7.0) | global empty values of components | ^[array] | — |
| value-on-clear ^(2.7.0) | global clear return value | ^[string] / ^[number] / ^[boolean] / ^[Function] | — |
### Button Attribute
| Attribute | Description | Type | Default |
| --------------- | ----------------------------------------------------------- | ---------- | ------- |
|-----------------|-------------------------------------------------------------|------------|---------|
| autoInsertSpace | automatically insert a space between two chinese characters | ^[boolean] | false |
### Message Attribute
| Attribute | Description | Type | Default |
| --------- | --------------------------------------------------------------------- | --------- | ------- |
|-----------|-----------------------------------------------------------------------|-----------|---------|
| max | the maximum number of messages that can be displayed at the same time | ^[number] | — |
### Config Provider Slots
| Name | Description | Scope |
| ------- | ------------------------- | ------------------------------------------------------- |
|---------|---------------------------|---------------------------------------------------------|
| default | customize default content | config: provided global config (inherited from the top) |

View File

@ -147,40 +147,42 @@ Note, date time locale (month name, first day of the week ...) are also configur
### Attributes
| Name | Description | Type | Default |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| model-value / v-model | binding value, if it is an array, the length should be 2 | ^[number] / ^[string] / ^[object]`Date \| [Date, Date] \| [string, string]` | '' |
| readonly | whether DatePicker is read only | ^[boolean] | false |
| disabled | whether DatePicker is disabled | ^[boolean] | false |
| size | size of Input | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | — |
| editable | whether the input is editable | ^[boolean] | true |
| clearable | whether to show clear button | ^[boolean] | true |
| placeholder | placeholder in non-range mode | ^[string] | '' |
| start-placeholder | placeholder for the start date in range mode | ^[string] | — |
| end-placeholder | placeholder for the end date in range mode | ^[string] | — |
| type | type of the picker | ^[enum]`'year' \| 'years' \|'month' \| 'date' \| 'dates' \| 'datetime' \| 'week' \| 'datetimerange' \| 'daterange' \| 'monthrange'` | date |
| format | format of the displayed value in the input box | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | YYYY-MM-DD |
| popper-class | custom class name for DatePicker's dropdown | ^[string] | — |
| popper-options | Customized popper option see more at [popper.js](https://popper.js.org/docs/v2/) | ^[object]`Partial<PopperOptions>` | {} |
| range-separator | range separator | ^[string] | '-' |
| default-value | optional, default date of the calendar | ^[object]`Date \| [Date, Date]` | — |
| default-time | optional, the time value to use when selecting date range | ^[object]`Date \| [Date, Date]` | — |
| value-format | optional, format of binding value. If not specified, the binding value will be a Date object | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | — |
| id | same as `id` in native input | ^[string] / ^[object]`[string, string]` | — |
| name | same as `name` in native input | ^[string] / ^[object]`[string, string]` | '' |
| unlink-panels | unlink two date-panels in range-picker | ^[boolean] | false |
| prefix-icon | custom prefix icon component. By default, if the value of `type` is `TimeLikeType`, the value is `Clock`, else is `Calendar` | ^[string] / ^[object]`Component` | '' |
| clear-icon | custom clear icon component | ^[string] / ^[object]`Component` | `CircleClose` |
| validate-event | whether to trigger form validation | ^[boolean] | true |
| disabled-date | a function determining if a date is disabled with that date as its parameter. Should return a Boolean | ^[Function]`(data: Date) => boolean` | — |
| shortcuts | an object array to set shortcut options | ^[object]`Array<{ text: string, value: Date \| Function }>` | [] |
| cell-class-name | set custom className | ^[Function]`(data: Date) => string` | — |
| teleported | whether date-picker dropdown is teleported to the body | ^[boolean] | true |
| Name | Description | Type | Default |
|-------------------------|------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|---------------|
| model-value / v-model | binding value, if it is an array, the length should be 2 | ^[number] / ^[string] / ^[object]`Date \| [Date, Date] \| [string, string]` | '' |
| readonly | whether DatePicker is read only | ^[boolean] | false |
| disabled | whether DatePicker is disabled | ^[boolean] | false |
| size | size of Input | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | — |
| editable | whether the input is editable | ^[boolean] | true |
| clearable | whether to show clear button | ^[boolean] | true |
| placeholder | placeholder in non-range mode | ^[string] | '' |
| start-placeholder | placeholder for the start date in range mode | ^[string] | — |
| end-placeholder | placeholder for the end date in range mode | ^[string] | — |
| type | type of the picker | ^[enum]`'year' \| 'years' \|'month' \| 'date' \| 'dates' \| 'datetime' \| 'week' \| 'datetimerange' \| 'daterange' \| 'monthrange'` | date |
| format | format of the displayed value in the input box | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | YYYY-MM-DD |
| popper-class | custom class name for DatePicker's dropdown | ^[string] | — |
| popper-options | Customized popper option see more at [popper.js](https://popper.js.org/docs/v2/) | ^[object]`Partial<PopperOptions>` | {} |
| range-separator | range separator | ^[string] | '-' |
| default-value | optional, default date of the calendar | ^[object]`Date \| [Date, Date]` | — |
| default-time | optional, the time value to use when selecting date range | ^[object]`Date \| [Date, Date]` | — |
| value-format | optional, format of binding value. If not specified, the binding value will be a Date object | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | — |
| id | same as `id` in native input | ^[string] / ^[object]`[string, string]` | — |
| name | same as `name` in native input | ^[string] / ^[object]`[string, string]` | '' |
| unlink-panels | unlink two date-panels in range-picker | ^[boolean] | false |
| prefix-icon | custom prefix icon component. By default, if the value of `type` is `TimeLikeType`, the value is `Clock`, else is `Calendar` | ^[string] / ^[object]`Component` | '' |
| clear-icon | custom clear icon component | ^[string] / ^[object]`Component` | `CircleClose` |
| validate-event | whether to trigger form validation | ^[boolean] | true |
| disabled-date | a function determining if a date is disabled with that date as its parameter. Should return a Boolean | ^[Function]`(data: Date) => boolean` | — |
| shortcuts | an object array to set shortcut options | ^[object]`Array<{ text: string, value: Date \| Function }>` | [] |
| cell-class-name | set custom className | ^[Function]`(data: Date) => string` | — |
| teleported | whether date-picker dropdown is teleported to the body | ^[boolean] | true |
| 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] | — |
### Events
| Name | Description | Type |
| --------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|-----------------|------------------------------------------------------------|-------------------------------------------------------------------------------------------|
| change | triggers when user confirms the value | ^[Function]`(val: typeof v-model) => void` |
| blur | triggers when Input blurs | ^[Function]`(e: FocusEvent) => void` |
| focus | triggers when Input focuses | ^[Function]`(e: FocusEvent) => void` |
@ -191,14 +193,14 @@ Note, date time locale (month name, first day of the week ...) are also configur
### Slots
| Name | Description |
| --------------- | ------------------------------ |
|-----------------|--------------------------------|
| default | custom cell content |
| range-separator | custom range separator content |
### Exposes
| Name | Description | Type |
| --------------------- | --------------------------- | ------------------------------------------------------------------------------ |
|-----------------------|-----------------------------|--------------------------------------------------------------------------------|
| focus | focus the Input component | ^[Function]`(focusStartInput?: boolean, isIgnoreFocusEvent?: boolean) => void` |
| handleOpen ^(2.2.16) | open the DatePicker popper | ^[Function]`() => void` |
| handleClose ^(2.2.16) | close the DatePicker popper | ^[Function]`() => void` |

View File

@ -75,41 +75,43 @@ datetime-picker/default-time
## Attributes
| Name | Description | Type | Accepted Values | Default |
| --------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------- | ------------------- |
| model-value / v-model | binding value, if it is an array, the length should be 2 | Date / number / string / Array | — | — |
| readonly | whether DatePicker is read only | boolean | — | false |
| disabled | whether DatePicker is disabled | boolean | — | false |
| editable | whether the input is editable | boolean | — | true |
| clearable | whether to show clear button | boolean | — | true |
| size | size of Input | string | large/default/small | default |
| placeholder | placeholder in non-range mode | string | — | — |
| start-placeholder | placeholder for the start date in range mode | string | — | — |
| end-placeholder | placeholder for the end date in range mode | string | — | — |
| arrow-control | whether to pick time using arrow buttons | boolean | — | false |
| type | type of the picker | string | year/month/date/datetime/ week/datetimerange/daterange | date |
| format | format of the displayed value in the input box | string | see [date formats](/en-US/component/date-picker#date-formats) | YYYY-MM-DD HH:mm:ss |
| popper-class | custom class name for DateTimePicker's dropdown | string | — | — |
| range-separator | range separator | string | — | '-' |
| default-value | optional, default date of the calendar | Date / [Date, Date] | | — |
| default-time | the default time value after picking a date. Time `00:00:00` will be used if not specified | Date / [Date, Date] | — | — |
| value-format | optional, format of binding value. If not specified, the binding value will be a Date object | string | see [date formats](https://day.js.org/docs/en/display/format) | — |
| date-format ^(2.4.0) | optional, format of the date displayed value in TimePicker's dropdown | string | see [date formats](https://day.js.org/docs/en/display/format) | — |
| time-format ^(2.4.0) | optional, format of the time displayed value in TimePicker's dropdown | string | see [date formats](https://day.js.org/docs/en/display/format) | — |
| id | same as `id` in native input | string / [string, string] | — | — |
| name | same as `name` in native input | string | — | — |
| unlink-panels | unlink two date-panels in range-picker | boolean | — | false |
| prefix-icon | Custom prefix icon component | `string \| Component` | — | Date |
| clear-icon | Custom clear icon component | `string \| Component` | — | CircleClose |
| shortcuts | an object array to set shortcut options | object[{ text: string, value: date / function }] | — | — |
| disabled-date | a function determining if a date is disabled with that date as its parameter. Should return a Boolean | function(Date) | — | — |
| cell-class-name | set custom className | Function(Date) | — | — |
| teleported | whether datetime-picker dropdown is teleported to the body | boolean | true / false | true |
| Name | Description | Type | Accepted Values | Default |
|-------------------------|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------|---------------------------------------------------------------|---------------------|
| model-value / v-model | binding value, if it is an array, the length should be 2 | Date / number / string / Array | — | — |
| readonly | whether DatePicker is read only | boolean | — | false |
| disabled | whether DatePicker is disabled | boolean | — | false |
| editable | whether the input is editable | boolean | — | true |
| clearable | whether to show clear button | boolean | — | true |
| size | size of Input | string | large/default/small | default |
| placeholder | placeholder in non-range mode | string | — | — |
| start-placeholder | placeholder for the start date in range mode | string | — | — |
| end-placeholder | placeholder for the end date in range mode | string | — | — |
| arrow-control | whether to pick time using arrow buttons | boolean | — | false |
| type | type of the picker | string | year/month/date/datetime/ week/datetimerange/daterange | date |
| format | format of the displayed value in the input box | string | see [date formats](/en-US/component/date-picker#date-formats) | YYYY-MM-DD HH:mm:ss |
| popper-class | custom class name for DateTimePicker's dropdown | string | — | — |
| range-separator | range separator | string | — | '-' |
| default-value | optional, default date of the calendar | Date / [Date, Date] | | — |
| default-time | the default time value after picking a date. Time `00:00:00` will be used if not specified | Date / [Date, Date] | — | — |
| value-format | optional, format of binding value. If not specified, the binding value will be a Date object | string | see [date formats](https://day.js.org/docs/en/display/format) | — |
| date-format ^(2.4.0) | optional, format of the date displayed value in TimePicker's dropdown | string | see [date formats](https://day.js.org/docs/en/display/format) | — |
| time-format ^(2.4.0) | optional, format of the time displayed value in TimePicker's dropdown | string | see [date formats](https://day.js.org/docs/en/display/format) | — |
| id | same as `id` in native input | string / [string, string] | — | — |
| name | same as `name` in native input | string | — | — |
| unlink-panels | unlink two date-panels in range-picker | boolean | — | false |
| prefix-icon | Custom prefix icon component | `string \| Component` | — | Date |
| clear-icon | Custom clear icon component | `string \| Component` | — | CircleClose |
| shortcuts | an object array to set shortcut options | object[{ text: string, value: date / function }] | — | — |
| disabled-date | a function determining if a date is disabled with that date as its parameter. Should return a Boolean | function(Date) | — | — |
| cell-class-name | set custom className | Function(Date) | — | — |
| teleported | whether datetime-picker dropdown is teleported to the body | boolean | true / false | true |
| 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] | — |
## Events
| Name | Description | Parameters |
| --------------- | ----------------------------------------------------------------------------- | ----------------------------------------- |
|-----------------|-------------------------------------------------------------------------------|-------------------------------------------|
| change | triggers when user confirms the value | component's binding value |
| blur | triggers when Input blurs | `(e: FocusEvent)` |
| focus | triggers when Input focuses | `(e: FocusEvent)` |
@ -119,12 +121,12 @@ datetime-picker/default-time
## Methods
| Method | Description | Parameters |
| ------ | ------------------------- | ---------- |
|--------|---------------------------|------------|
| focus | focus the Input component | — |
## Slots
| Name | Description |
| --------------- | ------------------------------ |
|-----------------|--------------------------------|
| default | custom cell content |
| range-separator | custom range separator content |

View File

@ -197,12 +197,24 @@ select-v2/custom-loading
:::
## Empty Values ^(2.7.0)
If you want to support empty string, please set `empty-values` to `[null, undefined]`.
If you want to change the clear value to `null`, please set `value-on-clear` to `null`.
:::demo
select-v2/empty-values
:::
## API
### Attributes
| Name | Description | Type | Default |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------|
| model-value / v-model | binding value | ^[string] / ^[number] / ^[boolean] / ^[object] / ^[array] | — |
| options | data of the options, the key of `value` and `label` can be customize by `props` | ^[array] | — |
| props ^(2.4.2) | configuration options, see the following table | ^[object] | — |
@ -244,11 +256,13 @@ select-v2/custom-loading
| max-collapse-tags ^(2.3.0) | The max tags number to be shown. To use this, `collapse-tags` must be true | ^[number] | 1 |
| tag-type ^(2.5.0) | tag type | ^[enum]`'' \| 'success' \| 'info' \| 'warning' \| 'danger'` | info |
| aria-label ^(a11y) ^(2.5.0) | 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] | — |
### props
| Attribute | Description | Type | Default |
| --------- | --------------------------------------------------------------- | --------- | -------- |
|-----------|-----------------------------------------------------------------|-----------|----------|
| value | specify which key of node object is used as the node's value | ^[string] | value |
| label | specify which key of node object is used as the node's label | ^[string] | label |
| options | specify which key of node object is used as the node's children | ^[string] | options |
@ -257,7 +271,7 @@ select-v2/custom-loading
### Events
| Name | Description | Type |
| -------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|----------------|------------------------------------------------------------------------------------------------------------|------------------------------------------|
| change | triggers when the selected value changes, the param is current selected value | ^[Function]`(val: any) => void` |
| visible-change | triggers when the dropdown appears/disappears, the param will be true when it appears, and false otherwise | ^[Function]`(visible: boolean) => void` |
| remove-tag | triggers when a tag is removed in multiple mode, the param is removed tag value | ^[Function]`(tagValue: any) => void` |
@ -268,7 +282,7 @@ select-v2/custom-loading
### Slots
| Name | Description |
| ---------------- | ------------------------------------- |
|------------------|---------------------------------------|
| default | Option renderer |
| header ^(2.5.2) | content at the top of the dropdown |
| footer ^(2.5.2) | content at the bottom of the dropdown |
@ -280,6 +294,6 @@ select-v2/custom-loading
### Exposes
| Method | Description | Type |
| ------ | ----------------------------------------------- | ----------------------- |
|--------|-------------------------------------------------|-------------------------|
| focus | focus the Input component | ^[Function]`() => void` |
| blur | blur the Input component, and hide the dropdown | ^[Function]`() => void` |

View File

@ -165,12 +165,24 @@ select/custom-loading
:::
## Empty Values ^(2.7.0)
If you want to support empty string, please set `empty-values` to `[null, undefined]`.
If you want to change the clear value to `null`, please set `value-on-clear` to `null`.
:::demo
select/empty-values
:::
## Select API
### 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 |
@ -211,6 +223,8 @@ select/custom-loading
| 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] | — |
:::warning
@ -221,7 +235,7 @@ select/custom-loading
### Select Events
| Name | Description | Type |
| -------------- | ------------------------------------------------------------- | ---------------------------------------- |
|----------------|---------------------------------------------------------------|------------------------------------------|
| change | triggers when the selected value changes | ^[Function]`(value: any) => void` |
| visible-change | triggers when the dropdown appears/disappears | ^[Function]`(visible: boolean) => void` |
| remove-tag | triggers when a tag is removed in multiple mode | ^[Function]`(tagValue: any) => void` |
@ -232,7 +246,7 @@ select/custom-loading
### Select Slots
| Name | Description | Subtags |
| ---------------- | ------------------------------------- | --------------------- |
|------------------|---------------------------------------|-----------------------|
| default | option component list | Option Group / Option |
| header ^(2.4.3) | content at the top of the dropdown | — |
| footer ^(2.4.3) | content at the bottom of the dropdown | — |
@ -244,7 +258,7 @@ select/custom-loading
### Select Exposes
| Method | Description | Type |
| ------ | ----------------------------------------------- | ----------------------- |
|--------|-------------------------------------------------|-------------------------|
| focus | focus the Input component | ^[Function]`() => void` |
| blur | blur the Input component, and hide the dropdown | ^[Function]`() => void` |
@ -253,14 +267,14 @@ select/custom-loading
### Option Group Attributes
| Name | Description | Type | Default |
| -------- | -------------------------------------------- | ---------- | ------- |
|----------|----------------------------------------------|------------|---------|
| label | name of the group | ^[string] | — |
| disabled | whether to disable all options in this group | ^[boolean] | false |
### Option Group Slots
| Name | Description | Subtags |
| ------- | ------------------------- | ------- |
|---------|---------------------------|---------|
| default | customize default content | Option |
## Option API
@ -268,7 +282,7 @@ select/custom-loading
### Option Attributes
| Name | Description | Type | Default |
| -------- | ------------------------------------------- | ---------------------------------------------- | ------- |
|----------|---------------------------------------------|------------------------------------------------|---------|
| value | value of option | ^[string] / ^[number] / ^[boolean] / ^[object] | — |
| label | label of option, same as `value` if omitted | ^[string] / ^[number] | — |
| disabled | whether option is disabled | ^[boolean] | false |
@ -276,5 +290,5 @@ select/custom-loading
### Option Slots
| Name | Description |
| ------- | ------------------------- |
|---------|---------------------------|
| default | customize default content |

View File

@ -47,39 +47,41 @@ time-picker/range
### Attributes
| Name | Description | Type | Default |
| --------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------- |
| model-value / v-model | binding value, if it is an array, the length should be 2 | ^[number] / ^[string] / ^[object]`Date \| [Date, Date] \| [number, number] \| [string, string]` | '' |
| readonly | whether TimePicker is read only | ^[boolean] | false |
| disabled | whether TimePicker is disabled | ^[boolean] | false |
| editable | whether the input is editable | ^[boolean] | true |
| clearable | whether to show clear button | ^[boolean] | true |
| size | size of Input | ^[enum]`'large' \| 'default' \| 'small'` | — |
| placeholder | placeholder in non-range mode | ^[string] | '' |
| start-placeholder | placeholder for the start time in range mode | ^[string] | — |
| end-placeholder | placeholder for the end time in range mode | ^[string] | — |
| is-range | whether to pick a time range | ^[boolean] | false |
| arrow-control | whether to pick time using arrow buttons | ^[boolean] | false |
| popper-class | custom class name for TimePicker's dropdown | ^[string] | '' |
| range-separator | range separator | ^[string] | '-' |
| format | format of the displayed value in the input box | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | — |
| default-value | optional, default date of the calendar | ^[Date] / ^[object]`[Date, Date]` | — |
| value-format | optional, format of binding value. If not specified, the binding value will be a Date object | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | — |
| id | same as `id` in native input | ^[string] / ^[object]`[string, string]` | — |
| name | same as `name` in native input | ^[string] | '' |
| label ^(a11y) | same as `aria-label` in native input | ^[string] | — |
| prefix-icon | Custom prefix icon component | ^[string] / ^[Component] | Clock |
| clear-icon | Custom clear icon component | ^[string] / ^[Component] | CircleClose |
| disabled-hours | To specify the array of hours that cannot be selected | ^[Function]`(role: string, comparingDate?: Dayjs) => number[]` | — |
| disabled-minutes | To specify the array of minutes that cannot be selected | ^[Function]`(hour: number, role: string, comparingDate?: Dayjs) => number[]` | — |
| disabled-seconds | To specify the array of seconds that cannot be selected | ^[Function]`(hour: number, minute: number, role: string, comparingDate?: Dayjs) => number[]` | — |
| teleported | whether time-picker dropdown is teleported to the body | ^[boolean] | true |
| tabindex | input tabindex | ^[string] / ^[number] | 0 |
| Name | Description | Type | Default |
|-------------------------|----------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|-------------|
| model-value / v-model | binding value, if it is an array, the length should be 2 | ^[number] / ^[string] / ^[object]`Date \| [Date, Date] \| [number, number] \| [string, string]` | '' |
| readonly | whether TimePicker is read only | ^[boolean] | false |
| disabled | whether TimePicker is disabled | ^[boolean] | false |
| editable | whether the input is editable | ^[boolean] | true |
| clearable | whether to show clear button | ^[boolean] | true |
| size | size of Input | ^[enum]`'large' \| 'default' \| 'small'` | — |
| placeholder | placeholder in non-range mode | ^[string] | '' |
| start-placeholder | placeholder for the start time in range mode | ^[string] | — |
| end-placeholder | placeholder for the end time in range mode | ^[string] | — |
| is-range | whether to pick a time range | ^[boolean] | false |
| arrow-control | whether to pick time using arrow buttons | ^[boolean] | false |
| popper-class | custom class name for TimePicker's dropdown | ^[string] | '' |
| range-separator | range separator | ^[string] | '-' |
| format | format of the displayed value in the input box | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | — |
| default-value | optional, default date of the calendar | ^[Date] / ^[object]`[Date, Date]` | — |
| value-format | optional, format of binding value. If not specified, the binding value will be a Date object | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | — |
| id | same as `id` in native input | ^[string] / ^[object]`[string, string]` | — |
| name | same as `name` in native input | ^[string] | '' |
| label ^(a11y) | same as `aria-label` in native input | ^[string] | — |
| prefix-icon | Custom prefix icon component | ^[string] / ^[Component] | Clock |
| clear-icon | Custom clear icon component | ^[string] / ^[Component] | CircleClose |
| disabled-hours | To specify the array of hours that cannot be selected | ^[Function]`(role: string, comparingDate?: Dayjs) => number[]` | — |
| disabled-minutes | To specify the array of minutes that cannot be selected | ^[Function]`(hour: number, role: string, comparingDate?: Dayjs) => number[]` | — |
| disabled-seconds | To specify the array of seconds that cannot be selected | ^[Function]`(hour: number, minute: number, role: string, comparingDate?: Dayjs) => number[]` | — |
| teleported | whether time-picker dropdown is teleported to the body | ^[boolean] | true |
| tabindex | input tabindex | ^[string] / ^[number] | 0 |
| 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] | — |
### Events
| Name | Description | Type |
| -------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|----------------|------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| change | triggers when user confirms the value | ^[Function]`(val: number \| string \| Date \| [number, number] \| [string, string] \| [Date, Date]) => void` |
| blur | triggers when Input blurs | ^[Function]`(e: FocusEvent) => void` |
| focus | triggers when Input focuses | ^[Function]`(e: FocusEvent) => void` |
@ -88,7 +90,7 @@ time-picker/range
### Exposes
| Name | Description | Type |
| --------------------- | --------------------------- | ------------------------------------------------- |
|-----------------------|-----------------------------|---------------------------------------------------|
| focus | focus the Input component | ^[Function]`(e: FocusEvent \| undefined) => void` |
| blur | blur the Input component | ^[Function]`(e: FocusEvent \| undefined) => void` |
| handleOpen ^(2.2.16) | open the TimePicker popper | ^[Function]`() => void` |

View File

@ -57,29 +57,31 @@ time-select/time-range
### Attributes
| Name | Description | Type | Default |
| --------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------- |
| model-value / v-model | binding value | ^[string] | — |
| disabled | whether TimeSelect is disabled | ^[boolean] | false |
| editable | whether the input is editable | ^[boolean] | true |
| clearable | whether to show clear button | ^[boolean] | true |
| size | size of Input | ^[enum]`'large' \| 'default' \| 'small'` | default |
| placeholder | placeholder in non-range mode | ^[string] | — |
| name | same as `name` in native input | ^[string] | — |
| effect | Tooltip theme, built-in theme: `dark` / `light` | ^[string] / ^[enum]`'dark' \| 'light'` | light |
| prefix-icon | custom prefix icon component | ^[string] / ^[Component] | Clock |
| clear-icon | custom clear icon component | ^[string] / ^[Component] | CircleClose |
| start | start time | ^[string] | 09:00 |
| end | end time | ^[string] | 18:00 |
| step | time step | ^[string] | 00:30 |
| min-time | minimum time, any time before this time will be disabled | ^[string] | — |
| max-time | maximum time, any time after this time will be disabled | ^[string] | — |
| format | set format of time | ^[string] see [formats](https://day.js.org/docs/en/display/format#list-of-all-available-formats) | HH:mm |
| Name | Description | Type | Default |
|-------------------------|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|-------------|
| model-value / v-model | binding value | ^[string] | — |
| disabled | whether TimeSelect is disabled | ^[boolean] | false |
| editable | whether the input is editable | ^[boolean] | true |
| clearable | whether to show clear button | ^[boolean] | true |
| size | size of Input | ^[enum]`'large' \| 'default' \| 'small'` | default |
| placeholder | placeholder in non-range mode | ^[string] | — |
| name | same as `name` in native input | ^[string] | — |
| effect | Tooltip theme, built-in theme: `dark` / `light` | ^[string] / ^[enum]`'dark' \| 'light'` | light |
| prefix-icon | custom prefix icon component | ^[string] / ^[Component] | Clock |
| clear-icon | custom clear icon component | ^[string] / ^[Component] | CircleClose |
| start | start time | ^[string] | 09:00 |
| end | end time | ^[string] | 18:00 |
| step | time step | ^[string] | 00:30 |
| min-time | minimum time, any time before this time will be disabled | ^[string] | — |
| max-time | maximum time, any time after this time will be disabled | ^[string] | — |
| format | set format of time | ^[string] see [formats](https://day.js.org/docs/en/display/format#list-of-all-available-formats) | HH:mm |
| 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] | — |
### Events
| Name | Description | Type |
| ------ | ------------------------------------- | ---------------------------------------- |
|--------|---------------------------------------|------------------------------------------|
| change | triggers when user confirms the value | ^[Function]`(value: string) => void` |
| blur | triggers when Input blurs | ^[Function]`(event: FocusEvent) => void` |
| focus | triggers when Input focuses | ^[Function]`(event: FocusEvent) => void` |
@ -87,6 +89,6 @@ time-select/time-range
### Exposes
| Method | Description | Type |
| ------ | ------------------------- | ----------------------- |
|--------|---------------------------|-------------------------|
| focus | focus the Input component | ^[Function]`() => void` |
| blur | blur the Input component | ^[Function]`() => void` |

View File

@ -0,0 +1,69 @@
<template>
<el-config-provider :value-on-clear="null" :empty-values="[undefined, null]">
<div class="flex flex-wrap gap-4 items-center">
<el-select
v-model="value1"
clearable
placeholder="Select"
style="width: 240px"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-select-v2
v-model="value2"
clearable
placeholder="Select"
style="width: 240px"
:options="options"
:value-on-clear="() => undefined"
@change="handleChange"
/>
</div>
</el-config-provider>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const value1 = ref('')
const value2 = ref('')
const options = [
{
value: '',
label: 'All',
},
{
value: 'Option1',
label: 'Option1',
},
{
value: 'Option2',
label: 'Option2',
},
{
value: 'Option3',
label: 'Option3',
},
{
value: 'Option4',
label: 'Option4',
},
{
value: 'Option5',
label: 'Option5',
},
]
const handleChange = (value) => {
if ([undefined, null].includes(value)) {
ElMessage.info(`The clear value is: ${value}`)
}
}
</script>

View File

@ -0,0 +1,35 @@
<template>
<el-select-v2
v-model="value"
:options="options"
:empty-values="[null, undefined]"
:value-on-clear="null"
clearable
placeholder="Select"
style="width: 240px"
@clear="handleClear"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const value = ref('')
const initials = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
const options = Array.from({ length: 1000 }).map((_, idx) => ({
value: `Option ${idx + 1}`,
label: `${initials[idx % 10]}${idx}`,
}))
options.unshift({
value: '',
label: 'All',
})
const handleClear = () => {
ElMessage.info(`The clear value is: ${value.value}`)
}
</script>

View File

@ -0,0 +1,56 @@
<template>
<el-select
v-model="value"
:empty-values="[null, undefined]"
:value-on-clear="null"
clearable
placeholder="Select"
style="width: 240px"
@clear="handleClear"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const value = ref('')
const options = [
{
value: '',
label: 'All',
},
{
value: 'Option1',
label: 'Option1',
},
{
value: 'Option2',
label: 'Option2',
},
{
value: 'Option3',
label: 'Option3',
},
{
value: 'Option4',
label: 'Option4',
},
{
value: 'Option5',
label: 'Option5',
},
]
const handleClear = () => {
ElMessage.info(`The clear value is: ${value.value}`)
}
</script>

View File

@ -1,6 +1,6 @@
import { CommonProps } from '@element-plus/components/cascader-panel'
import { buildProps, definePropType, isBoolean } from '@element-plus/utils'
import { useSizeProp } from '@element-plus/hooks'
import { useEmptyValuesProps, useSizeProp } from '@element-plus/hooks'
import { useTooltipContentProps } from '@element-plus/components/tooltip'
import { tagProps } from '@element-plus/components/tag'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
@ -110,11 +110,14 @@ export const cascaderProps = buildProps({
type: Boolean,
default: true,
},
...useEmptyValuesProps,
})
export const cascaderEmits = {
[UPDATE_MODEL_EVENT]: (val: CascaderValue) => !!val || val === null,
[CHANGE_EVENT]: (val: CascaderValue) => !!val || val === null,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[UPDATE_MODEL_EVENT]: (_: CascaderValue) => true,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[CHANGE_EVENT]: (_: CascaderValue) => true,
focus: (evt: FocusEvent) => evt instanceof FocusEvent,
blur: (evt: FocusEvent) => evt instanceof FocusEvent,
visibleChange: (val: boolean) => isBoolean(val),

View File

@ -207,7 +207,7 @@ import ElTag from '@element-plus/components/tag'
import ElIcon from '@element-plus/components/icon'
import { useFormItem, useFormSize } from '@element-plus/components/form'
import { ClickOutside as vClickoutside } from '@element-plus/directives'
import { useLocale, useNamespace } from '@element-plus/hooks'
import { useEmptyValues, useLocale, useNamespace } from '@element-plus/hooks'
import {
debugWarn,
focusNode,
@ -268,6 +268,7 @@ const nsInput = useNamespace('input')
const { t } = useLocale()
const { form, formItem } = useFormItem()
const { valueOnClear } = useEmptyValues(props)
const tooltipRef: Ref<TooltipInstance | null> = ref(null)
const input: Ref<InputInstance | null> = ref(null)
@ -340,8 +341,9 @@ const checkedValue = computed<CascaderValue>({
return cloneDeep(props.modelValue) as CascaderValue
},
set(val) {
emit(UPDATE_MODEL_EVENT, val)
emit(CHANGE_EVENT, val)
const value = val || valueOnClear.value
emit(UPDATE_MODEL_EVENT, value)
emit(CHANGE_EVENT, value)
if (props.validateEvent) {
formItem?.validate('change').catch((err) => debugWarn(err))
}

View File

@ -1,5 +1,5 @@
import { buildProps, definePropType } from '@element-plus/utils'
import { useSizeProp } from '@element-plus/hooks'
import { useEmptyValuesProps, useSizeProp } from '@element-plus/hooks'
import type { ExtractPropTypes } from 'vue'
import type { Language } from '@element-plus/locale'
@ -64,5 +64,6 @@ export const configProviderProps = buildProps({
type: String,
default: 'el',
},
...useEmptyValuesProps,
} as const)
export type ConfigProviderProps = ExtractPropTypes<typeof configProviderProps>

View File

@ -124,7 +124,7 @@ const mergeConfig = (
const keys = [...new Set([...keysOf(a), ...keysOf(b)])]
const obj: Record<string, any> = {}
for (const key of keys) {
obj[key] = b[key] ?? a[key]
obj[key] = b[key] !== undefined ? b[key] : a[key]
}
return obj
}

View File

@ -178,7 +178,7 @@ describe('DatePicker', () => {
;(picker.vm as any).showClose = true
await nextTick()
;(document.querySelector('.clear-icon') as HTMLElement).click()
expect(vm.value).toBeNull()
expect(vm.value).toBe(null)
})
it('defaultValue', async () => {
@ -207,7 +207,7 @@ describe('DatePicker', () => {
;(picker.vm as any).showClose = true
await nextTick()
document.querySelector<HTMLElement>('.clear-icon').click()
expect(vm.value).toBeNull()
expect(vm.value).toBe(null)
vm.defaultValue = new Date(2031, 5, 1)
input.trigger('blur')

View File

@ -1,5 +1,5 @@
import { placements } from '@popperjs/core'
import { useSizeProp } from '@element-plus/hooks'
import { useEmptyValuesProps, useSizeProp } from '@element-plus/hooks'
import { buildProps, definePropType, iconPropType } from '@element-plus/utils'
import { useTooltipContentProps } from '@element-plus/components/tooltip'
import { CircleClose } from '@element-plus/icons-vue'
@ -247,6 +247,7 @@ export const SelectProps = buildProps({
type: String,
default: undefined,
},
...useEmptyValuesProps,
} as const)
export const OptionProps = buildProps({

View File

@ -13,11 +13,11 @@ import {
findLastIndex,
get,
isEqual,
isNil,
debounce as lodashDebounce,
} from 'lodash-unified'
import { useResizeObserver } from '@vueuse/core'
import {
useEmptyValues,
useFocusController,
useLocale,
useNamespace,
@ -59,6 +59,7 @@ const useSelect = (props: ISelectV2Props, emit) => {
formItemContext: elFormItem,
})
const { getLabel, getValue, getDisabled, getOptions } = useProps(props)
const { valueOnClear, isEmptyValue } = useEmptyValues(props)
const states = reactive({
inputValue: '',
@ -127,24 +128,19 @@ const useSelect = (props: ISelectV2Props, emit) => {
return totalHeight > props.height ? props.height : totalHeight
})
const hasEmptyStringOption = computed(() =>
allOptions.value.some((option) => getValue(option) === '')
)
const hasModelValue = computed(() => {
return props.multiple
? isArray(props.modelValue) && props.modelValue.length > 0
: !isNil(props.modelValue) &&
(props.modelValue !== '' || hasEmptyStringOption.value)
: !isEmptyValue(props.modelValue)
})
const showClearBtn = computed(() => {
const criteria =
return (
props.clearable &&
!selectDisabled.value &&
states.inputHovering &&
hasModelValue.value
return criteria
)
})
const iconComponent = computed(() =>
@ -600,7 +596,7 @@ const useSelect = (props: ISelectV2Props, emit) => {
if (isArray(props.modelValue)) {
emptyValue = []
} else {
emptyValue = undefined
emptyValue = valueOnClear.value
}
if (props.multiple) {

View File

@ -1,5 +1,5 @@
import { placements } from '@popperjs/core'
import { useSizeProp } from '@element-plus/hooks'
import { useEmptyValuesProps, useSizeProp } from '@element-plus/hooks'
import { buildProps, definePropType, iconPropType } from '@element-plus/utils'
import { useTooltipContentProps } from '@element-plus/components/tooltip'
import { ArrowDown, CircleClose } from '@element-plus/icons-vue'
@ -219,4 +219,5 @@ export const SelectProps = buildProps({
type: String,
default: undefined,
},
...useEmptyValuesProps,
})

View File

@ -14,7 +14,6 @@ import {
findLastIndex,
get,
isEqual,
isNil,
debounce as lodashDebounce,
} from 'lodash-unified'
import { useResizeObserver } from '@vueuse/core'
@ -33,6 +32,7 @@ import {
scrollIntoView,
} from '@element-plus/utils'
import {
useEmptyValues,
useFocusController,
useId,
useLocale,
@ -120,27 +120,23 @@ export const useSelect = (props: ISelectProps, emit) => {
const { inputId } = useFormItemInputId(props, {
formItemContext: formItem,
})
const { valueOnClear, isEmptyValue } = useEmptyValues(props)
const selectDisabled = computed(() => props.disabled || form?.disabled)
const hasEmptyStringOption = computed(() =>
optionsArray.value.some((option) => option.value === '')
)
const hasModelValue = computed(() => {
return props.multiple
? isArray(props.modelValue) && props.modelValue.length > 0
: !isNil(props.modelValue) &&
(props.modelValue !== '' || hasEmptyStringOption.value)
: !isEmptyValue(props.modelValue)
})
const showClose = computed(() => {
const criteria =
return (
props.clearable &&
!selectDisabled.value &&
states.inputHovering &&
hasModelValue.value
return criteria
)
})
const iconComponent = computed(() =>
props.remote && props.filterable && !props.remoteShowSuffix
@ -527,7 +523,7 @@ export const useSelect = (props: ISelectProps, emit) => {
const deleteSelected = (event) => {
event.stopPropagation()
const value: string | any[] = props.multiple ? [] : undefined
const value: string | any[] = props.multiple ? [] : valueOnClear.value
if (props.multiple) {
for (const item of states.selected) {
if (item.isDisabled) value.push(item.value)

View File

@ -173,7 +173,7 @@ import {
} from 'vue'
import { isEqual } from 'lodash-unified'
import { onClickOutside } from '@vueuse/core'
import { useLocale, useNamespace } from '@element-plus/hooks'
import { useEmptyValues, useLocale, useNamespace } from '@element-plus/hooks'
import { useFormItem, useFormSize } from '@element-plus/components/form'
import ElInput from '@element-plus/components/input'
import ElIcon from '@element-plus/components/icon'
@ -225,6 +225,7 @@ const nsRange = useNamespace('range')
const { form, formItem } = useFormItem()
const elPopperOptions = inject('ElPopperOptions', {} as Options)
const { valueOnClear } = useEmptyValues(props, null)
const refPopper = ref<TooltipInstance>()
const inputRef = ref<HTMLElement | ComponentPublicInstance>()
@ -502,8 +503,8 @@ const onClearIconClick = (event: MouseEvent) => {
if (showClose.value) {
event.stopPropagation()
focusOnInputBox()
emitInput(null)
emitChange(null, true)
emitInput(valueOnClear.value)
emitChange(valueOnClear.value, true)
showClose.value = false
pickerVisible.value = false
pickerOptions.value.handleClear && pickerOptions.value.handleClear()
@ -590,8 +591,8 @@ const handleChange = () => {
}
}
if (userInput.value === '') {
emitInput(null)
emitChange(null)
emitInput(valueOnClear.value)
emitChange(valueOnClear.value)
userInput.value = null
}
}

View File

@ -1,5 +1,5 @@
import { buildProps, definePropType } from '@element-plus/utils'
import { useSizeProp } from '@element-plus/hooks'
import { useEmptyValuesProps, useSizeProp } from '@element-plus/hooks'
import { CircleClose } from '@element-plus/icons-vue'
import { disabledTimeListsProps } from '../props/shared'
@ -211,6 +211,7 @@ export const timePickerDefaultProps = buildProps({
* @description unlink two date-panels in range-picker
*/
unlinkPanels: Boolean,
...useEmptyValuesProps,
} as const)
export type TimePickerDefaultProps = ExtractPropTypes<

View File

@ -1,6 +1,6 @@
import { buildProps, definePropType } from '@element-plus/utils'
import { CircleClose, Clock } from '@element-plus/icons-vue'
import { useSizeProp } from '@element-plus/hooks'
import { useEmptyValuesProps, useSizeProp } from '@element-plus/hooks'
import type TimeSelect from './time-select.vue'
import type { Component, ExtractPropTypes, PropType } from 'vue'
@ -96,6 +96,7 @@ export const timeSelectProps = buildProps({
type: definePropType<string | Component>([String, Object]),
default: () => CircleClose,
},
...useEmptyValuesProps,
} as const)
export type TimeSelectProps = ExtractPropTypes<typeof timeSelectProps>

View File

@ -10,6 +10,8 @@
:placeholder="placeholder"
default-first-option
:filterable="editable"
:empty-values="emptyValues"
:value-on-clear="valueOnClear"
@update:model-value="(event) => $emit('update:modelValue', event)"
@change="(event) => $emit('change', event)"
@blur="(event) => $emit('blur', event)"

View File

@ -0,0 +1,81 @@
import { reactive } from 'vue'
import { describe, expect, it } from 'vitest'
import {
DEFAULT_EMPTY_VALUES,
DEFAULT_VALUE_ON_CLEAR,
useEmptyValues,
} from '../use-empty-values'
describe('useEmptyValues', () => {
it('should return default value', async () => {
const props = reactive({}) as any
const { emptyValues, valueOnClear } = useEmptyValues(props)
expect(emptyValues.value).toEqual(DEFAULT_EMPTY_VALUES)
expect(valueOnClear.value).toEqual(DEFAULT_VALUE_ON_CLEAR)
})
it('should return default value prop', async () => {
const props = reactive({}) as any
const { valueOnClear } = useEmptyValues(props, null)
expect(valueOnClear.value).toEqual(null)
})
it('should return props value', async () => {
const props = reactive({
emptyValues: [null, undefined],
valueOnClear: null,
}) as any
const { emptyValues, valueOnClear } = useEmptyValues(props)
expect(emptyValues.value).toEqual([null, undefined])
expect(valueOnClear.value).toEqual(null)
props.emptyValues = ['']
props.valueOnClear = ''
expect(emptyValues.value).toEqual([''])
expect(valueOnClear.value).toEqual('')
props.emptyValues = [null]
props.valueOnClear = null
expect(emptyValues.value).toEqual([null])
expect(valueOnClear.value).toEqual(null)
props.emptyValues = [undefined]
props.valueOnClear = () => undefined
expect(emptyValues.value).toEqual([undefined])
expect(valueOnClear.value).toEqual(undefined)
})
it('should judge empty value', async () => {
const props = reactive({}) as any
const { isEmptyValue } = useEmptyValues(props)
expect(isEmptyValue('')).toBe(true)
expect(isEmptyValue(undefined)).toBe(true)
expect(isEmptyValue(null)).toBe(true)
expect(isEmptyValue('null')).toBe(false)
expect(isEmptyValue(Number.NaN)).toBe(false)
expect(isEmptyValue(false)).toBe(false)
expect(isEmptyValue(0)).toBe(false)
props.emptyValues = ['', Number.NaN, 'null', false, 0]
props.valueOnClear = ''
expect(isEmptyValue('')).toBe(true)
expect(isEmptyValue(undefined)).toBe(false)
expect(isEmptyValue(null)).toBe(false)
expect(isEmptyValue('null')).toBe(true)
expect(isEmptyValue(Number.NaN)).toBe(true)
expect(isEmptyValue(false)).toBe(true)
expect(isEmptyValue(0)).toBe(true)
})
})

View File

@ -27,3 +27,4 @@ export * from './use-cursor'
export * from './use-ordered-children'
export * from './use-size'
export * from './use-focus-controller'
export * from './use-empty-values'

View File

@ -0,0 +1,64 @@
import { computed } from 'vue'
import { useGlobalConfig } from '@element-plus/components/config-provider'
import { buildProps, debugWarn, isFunction } from '@element-plus/utils'
import type { ExtractPropTypes } from 'vue'
export const SCOPE = 'use-empty-values'
export const DEFAULT_EMPTY_VALUES = ['', undefined, null]
export const DEFAULT_VALUE_ON_CLEAR = undefined
export const useEmptyValuesProps = buildProps({
/**
* @description empty values supported by the component
*/
emptyValues: Array,
/**
* @description return value when cleared, if you want to set `undefined`, use `() => undefined`
*/
valueOnClear: {
type: [String, Number, Boolean, Function],
default: undefined,
validator: (val: any) => (isFunction(val) ? !val() : !val),
},
} as const)
export const useEmptyValues = (
props: ExtractPropTypes<typeof useEmptyValuesProps>,
defaultValue?: null | undefined
) => {
const config = useGlobalConfig()
config.value = config.value || {}
const emptyValues = computed(
() => props.emptyValues || config.value.emptyValues || DEFAULT_EMPTY_VALUES
)
const valueOnClear = computed(() => {
// function is used for undefined cause undefined can't be a value of prop
if (isFunction(props.valueOnClear)) {
return props.valueOnClear()
} else if (props.valueOnClear !== undefined) {
return props.valueOnClear
} else if (isFunction(config.value.valueOnClear)) {
return config.value.valueOnClear()
} else if (config.value.valueOnClear !== undefined) {
return config.value.valueOnClear
}
return defaultValue !== undefined ? defaultValue : DEFAULT_VALUE_ON_CLEAR
})
const isEmptyValue = (value: any) => {
return emptyValues.value.includes(value)
}
if (!emptyValues.value.includes(valueOnClear.value)) {
debugWarn(SCOPE, 'value-on-clear should be a value of empty-values')
}
return {
emptyValues,
valueOnClear,
isEmptyValue,
}
}