mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [select/select-v2/cascader] expose more tag options (#21157)
* feat(components): [select/select-v2] bind `data` for tag slot * chore: remove console.log * feat: expose `deleteTag` aswell --------- Co-authored-by: warmthsea <2586244885@qq.com>
This commit is contained in:
@@ -197,13 +197,13 @@ cascader/custom-tag
|
||||
|
||||
### 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. | — |
|
||||
| prefix ^(2.9.4) | content as Input prefix | — |
|
||||
| suggestion-item ^(2.9.5) | custom content for suggestion item when searching | ^[object]`{ item: CascaderNode }` |
|
||||
| tag ^(2.10.3) | custom tags style | ^[object]`{ data: Tag[] }` |
|
||||
| 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. | — |
|
||||
| prefix ^(2.9.4) | content as Input prefix | — |
|
||||
| suggestion-item ^(2.9.5) | custom content for suggestion item when searching | ^[object]`{ item: CascaderNode }` |
|
||||
| tag ^(2.10.3) | custom tags style | ^[object]`{ data: Tag[], deleteTag: (tag: Tag) => void }` |
|
||||
|
||||
### Cascader Exposes
|
||||
|
||||
|
||||
@@ -303,16 +303,16 @@ select-v2/custom-width
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description | Subtags |
|
||||
| ---------------- | ----------------------------------------------------------------- | -------------------------------------- |
|
||||
| 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 | — |
|
||||
| empty | content when options is empty | — |
|
||||
| prefix | prefix content of input | — |
|
||||
| tag ^(2.5.0) | content as Select tag, selectDisabled tag introduced in ^(2.10.3) | ^[object]`{ selectDisabled: boolean }` |
|
||||
| loading ^(2.5.2) | content as Select loading | — |
|
||||
| label ^(2.7.4) | content as Select label | — |
|
||||
| Name | Description | Subtags |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| 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 | — |
|
||||
| empty | content when options is empty | — |
|
||||
| prefix | prefix content of input | — |
|
||||
| tag ^(2.5.0) | content as Select tag, subTags `data`, `selectDisabled` and `deleteTag` introduced in ^(2.10.3) | ^[object]`{ data: Option[], selectDisabled: boolean, deleteTag: (event: MouseEvent, option: Option) => void }` |
|
||||
| loading ^(2.5.2) | content as Select loading | — |
|
||||
| label ^(2.7.4) | content as Select label | — |
|
||||
|
||||
### Exposes
|
||||
|
||||
|
||||
@@ -255,16 +255,16 @@ select/custom-label
|
||||
|
||||
### 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 | — |
|
||||
| prefix | content as Select prefix | — |
|
||||
| empty | content when there is no options | — |
|
||||
| tag ^(2.5.0) | content as Select tag, selectDisabled tag introduced in ^(2.10.3) | ^[object]`{ selectDisabled: boolean }` |
|
||||
| loading ^(2.5.2) | content as Select loading | — |
|
||||
| label ^(2.7.4) | content as Select label | — |
|
||||
| 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 | — |
|
||||
| prefix | content as Select prefix | — |
|
||||
| empty | content when there is no options | — |
|
||||
| tag ^(2.5.0) | content as Select tag, subTags `data`, `selectDisabled` and `deleteTag` introduced in ^(2.10.3) | ^[object]`{ data: OptionBasic[], selectDisabled: boolean, deleteTag: (event: MouseEvent, tag: OptionPublicInstance \| OptionBasic) => void }` |
|
||||
| loading ^(2.5.2) | content as Select loading | — |
|
||||
| label ^(2.7.4) | content as Select label | — |
|
||||
|
||||
### Select Exposes
|
||||
|
||||
|
||||
@@ -421,6 +421,52 @@ describe('Cascader.vue', () => {
|
||||
expect(wrapper.find('.el-tag').classes()).toContain('el-tag--dark')
|
||||
})
|
||||
|
||||
test('should expose delete-tag through slot & be able to delete a value', async () => {
|
||||
const value = ref(['hangzhou', 'wenzhou'])
|
||||
const wrapper = _mount(() => (
|
||||
<Cascader
|
||||
v-model={value.value}
|
||||
options={[
|
||||
{
|
||||
value: 'zhejiang',
|
||||
label: 'Zhejiang',
|
||||
},
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
},
|
||||
{
|
||||
value: 'ningbo',
|
||||
label: 'Ningbo',
|
||||
},
|
||||
{
|
||||
value: 'wenzhou',
|
||||
label: 'Wenzhou',
|
||||
},
|
||||
]}
|
||||
props={{ multiple: true }}
|
||||
>
|
||||
{{
|
||||
tag: ({ data, deleteTag }: any) =>
|
||||
data.map((option: any) => (
|
||||
<div class="no-tag" onClick={() => deleteTag(option)}>
|
||||
{option.text}
|
||||
</div>
|
||||
)),
|
||||
}}
|
||||
</Cascader>
|
||||
))
|
||||
await nextTick()
|
||||
const cascader = wrapper.findComponent(Cascader)
|
||||
const tags = wrapper.findAll('.no-tag')
|
||||
expect(tags).toHaveLength(2)
|
||||
expect(cascader.vm.modelValue).toEqual(['hangzhou', 'wenzhou'])
|
||||
|
||||
await tags[0].trigger('click')
|
||||
expect(wrapper.findAll('.no-tag')).toHaveLength(1)
|
||||
expect(cascader.vm.modelValue).toEqual([['wenzhou']])
|
||||
})
|
||||
|
||||
test('filterable', async () => {
|
||||
const value = ref([])
|
||||
const wrapper = _mount(() => (
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
nsCascader.is('validate', Boolean(validateState)),
|
||||
]"
|
||||
>
|
||||
<slot name="tag" :data="allPresentTags">
|
||||
<slot name="tag" :data="allPresentTags" :delete-tag="deleteTag">
|
||||
<el-tag
|
||||
v-for="tag in presentTags"
|
||||
:key="tag.key"
|
||||
|
||||
@@ -87,6 +87,7 @@ const createSelect = (
|
||||
slots?: {
|
||||
empty?: string
|
||||
default?: string
|
||||
tag?: string
|
||||
}
|
||||
} = {}
|
||||
) => {
|
||||
@@ -100,6 +101,12 @@ const createSelect = (
|
||||
options.slots.default &&
|
||||
`<template #default="{item}">${options.slots.default}</template>`) ||
|
||||
''
|
||||
const tagSlot =
|
||||
(options.slots &&
|
||||
options.slots.tag &&
|
||||
`<template #tag="{ data, deleteTag }">${options.slots.tag}</template>`) ||
|
||||
''
|
||||
|
||||
return _mount(
|
||||
`
|
||||
<el-select
|
||||
@@ -140,6 +147,7 @@ const createSelect = (
|
||||
v-model="value">
|
||||
${defaultSlot}
|
||||
${emptySlot}
|
||||
${tagSlot}
|
||||
</el-select>
|
||||
`,
|
||||
{
|
||||
@@ -2148,6 +2156,73 @@ describe('Select', () => {
|
||||
expect(wrapper.findAll('.el-tag').length).toBe(1)
|
||||
})
|
||||
|
||||
it('should return slot tag data correctly & dont have tag component', async () => {
|
||||
const value = [1, 3, 5]
|
||||
const wrapper = createSelect({
|
||||
data() {
|
||||
return {
|
||||
multiple: true,
|
||||
options: [
|
||||
{ label: 'Test 1', value: 1 },
|
||||
{ label: 'Test 2', value: 2 },
|
||||
{ label: 'Test 3', value: 3 },
|
||||
{ label: 'Test 4', value: 4 },
|
||||
{ label: 'Test 5', value: 5 },
|
||||
],
|
||||
value,
|
||||
}
|
||||
},
|
||||
slots: {
|
||||
tag: `
|
||||
<span v-for="option in data" class="no-tag" :key="option.value">
|
||||
{{ option.value }} - {{ option.label }}
|
||||
</span>
|
||||
`,
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
const slotTagEls = wrapper.findAll('.no-tag')
|
||||
expect(slotTagEls).toHaveLength(3)
|
||||
expect(wrapper.find('.el-tag').exists()).toBe(false)
|
||||
slotTagEls.forEach((el, idx) => {
|
||||
expect(el.text()).toBe(`${value[idx]} - Test ${value[idx]}`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should expose delete-tag through slot & be able to delete a value', async () => {
|
||||
const wrapper = createSelect({
|
||||
data() {
|
||||
return {
|
||||
multiple: true,
|
||||
options: [
|
||||
{ label: 'Test 1', value: 1 },
|
||||
{ label: 'Test 2', value: 2 },
|
||||
{ label: 'Test 3', value: 3 },
|
||||
{ label: 'Test 4', value: 4 },
|
||||
{ label: 'Test 5', value: 5 },
|
||||
],
|
||||
value: [2, 3, 5],
|
||||
}
|
||||
},
|
||||
slots: {
|
||||
tag: `
|
||||
<span v-for="option in data" class="no-tag" :key="option.value" @click="deleteTag($event, option)">
|
||||
{{ option.value }} - {{ option.label }}
|
||||
</span>
|
||||
`,
|
||||
},
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
const slotTagEls = wrapper.findAll('.no-tag')
|
||||
expect(slotTagEls).toHaveLength(3)
|
||||
expect(wrapper.vm.value).toEqual([2, 3, 5])
|
||||
|
||||
await slotTagEls[0].trigger('click')
|
||||
expect(wrapper.findAll('.no-tag')).toHaveLength(2)
|
||||
expect(wrapper.vm.value).toEqual([3, 5])
|
||||
})
|
||||
|
||||
it('should be trigger the click event', async () => {
|
||||
const handleClick = vi.fn()
|
||||
const wrapper = _mount(`<el-select :options="[]" @click="handleClick" />`, {
|
||||
|
||||
@@ -56,7 +56,13 @@
|
||||
),
|
||||
]"
|
||||
>
|
||||
<slot v-if="multiple" name="tag" :select-disabled="selectDisabled">
|
||||
<slot
|
||||
v-if="multiple"
|
||||
name="tag"
|
||||
:data="states.cachedOptions"
|
||||
:delete-tag="deleteTag"
|
||||
:select-disabled="selectDisabled"
|
||||
>
|
||||
<div
|
||||
v-for="item in showTagList"
|
||||
:key="getValueKey(getValue(item))"
|
||||
|
||||
@@ -2902,6 +2902,75 @@ describe('Select', () => {
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.el-tag').length).toBe(1)
|
||||
})
|
||||
|
||||
it('should return slot tag data correctly & dont have tag component', async () => {
|
||||
const value = [1, 3, 5]
|
||||
const wrapper = _mount(
|
||||
`
|
||||
<el-select v-model="value" multiple>
|
||||
<el-option v-for="option in options" :value="option.value" :label="option.label"></el-option>
|
||||
<template #tag="{ data }">
|
||||
<span v-for="option in data" class="no-tag" :key="option.value">
|
||||
{{ option.value }} - {{ option.currentLabel }}
|
||||
</span>
|
||||
</template>
|
||||
</el-select>
|
||||
`,
|
||||
() => ({
|
||||
value,
|
||||
multiple: true,
|
||||
options: [
|
||||
{ label: 'Test 1', value: 1 },
|
||||
{ label: 'Test 2', value: 2 },
|
||||
{ label: 'Test 3', value: 3 },
|
||||
{ label: 'Test 4', value: 4 },
|
||||
{ label: 'Test 5', value: 5 },
|
||||
],
|
||||
})
|
||||
)
|
||||
await nextTick()
|
||||
const slotTagEls = wrapper.findAll('.no-tag')
|
||||
expect(slotTagEls).toHaveLength(3)
|
||||
expect(wrapper.find('.el-tag').exists()).toBe(false)
|
||||
slotTagEls.forEach((el, idx) => {
|
||||
expect(el.text()).toBe(`${value[idx]} - Test ${value[idx]}`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should expose delete-tag through slot & be able to delete a value', async () => {
|
||||
const wrapper = _mount(
|
||||
`
|
||||
<el-select v-model="value" multiple>
|
||||
<el-option v-for="option in options" :value="option.value" :label="option.label"></el-option>
|
||||
<template #tag="{ data, deleteTag }">
|
||||
<span v-for="option in data" class="no-tag" :key="option.value" @click="deleteTag($event, option)">
|
||||
{{ option.value }} - {{ option.currentLabel }}
|
||||
</span>
|
||||
</template>
|
||||
</el-select>
|
||||
`,
|
||||
() => ({
|
||||
value: [2, 3, 5],
|
||||
multiple: true,
|
||||
options: [
|
||||
{ label: 'Test 1', value: 1 },
|
||||
{ label: 'Test 2', value: 2 },
|
||||
{ label: 'Test 3', value: 3 },
|
||||
{ label: 'Test 4', value: 4 },
|
||||
{ label: 'Test 5', value: 5 },
|
||||
],
|
||||
})
|
||||
)
|
||||
await nextTick()
|
||||
const slotTagEls = wrapper.findAll('.no-tag')
|
||||
expect(slotTagEls).toHaveLength(3)
|
||||
expect(wrapper.vm.value).toEqual([2, 3, 5])
|
||||
|
||||
await slotTagEls[0].trigger('click')
|
||||
expect(wrapper.findAll('.no-tag')).toHaveLength(2)
|
||||
expect(wrapper.vm.value).toEqual([3, 5])
|
||||
})
|
||||
|
||||
it('It should generate accessible attributes', async () => {
|
||||
wrapper = _mount(
|
||||
`<el-select v-model="value">
|
||||
|
||||
@@ -56,7 +56,13 @@
|
||||
),
|
||||
]"
|
||||
>
|
||||
<slot v-if="multiple" name="tag" :select-disabled="selectDisabled">
|
||||
<slot
|
||||
v-if="multiple"
|
||||
name="tag"
|
||||
:data="states.selected"
|
||||
:delete-tag="deleteTag"
|
||||
:select-disabled="selectDisabled"
|
||||
>
|
||||
<div
|
||||
v-for="item in showTagList"
|
||||
:key="getValueKey(item)"
|
||||
|
||||
@@ -51,7 +51,12 @@ import {
|
||||
import type { TooltipInstance } from '@element-plus/components/tooltip'
|
||||
import type { ScrollbarInstance } from '@element-plus/components/scrollbar'
|
||||
import type { SelectEmits, SelectProps } from './select'
|
||||
import type { OptionPublicInstance, OptionValue, SelectStates } from './type'
|
||||
import type {
|
||||
OptionBasic,
|
||||
OptionPublicInstance,
|
||||
OptionValue,
|
||||
SelectStates,
|
||||
} from './type'
|
||||
|
||||
export const useSelect = (props: SelectProps, emit: SelectEmits) => {
|
||||
const { t } = useLocale()
|
||||
@@ -521,7 +526,7 @@ export const useSelect = (props: SelectProps, emit: SelectEmits) => {
|
||||
|
||||
const deleteTag = (
|
||||
event: MouseEvent,
|
||||
tag: OptionPublicInstance | SelectStates['selected'][0]
|
||||
tag: OptionPublicInstance | OptionBasic
|
||||
) => {
|
||||
const index = states.selected.indexOf(tag)
|
||||
if (index > -1 && !selectDisabled.value) {
|
||||
|
||||
@@ -157,8 +157,8 @@
|
||||
border-bottom: solid 1px getCssVar('border-color-lighter');
|
||||
|
||||
&.#{$namespace}-date-table__week-header {
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user