Merge branch 'dev' into fix/upload-photo-wall

This commit is contained in:
云游君
2022-03-11 22:13:41 +08:00
committed by GitHub
36 changed files with 624 additions and 94 deletions

25
.github/workflows/welcome.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: PR Open Greeting
on:
pull_request:
types: [opened]
jobs:
pr-open-greeting:
runs-on: ubuntu-latest
steps:
- uses: actions-cool/check-user-permission@v2
id: isTeamMember
with:
require: 'write'
- uses: actions-awesome/pr-helper@1.0.15
if: steps.isTeamMember.outputs.require-result == 'false'
with:
actions: 'add-assignees, add-reviewers, greeting, add-labels'
assignees: 'element-plus/backers'
reviewers: 'element-plus/backers'
labels: 'Contribution::Community'
token: ${{ github.token }}
pr-number: ${{ github.event.pull_request.number }}
greeting-message: 'Hello %user%, thank you for contributing to %repo%, please see our [guideline](%guideline%) to see how to make contribution'
greeting-guideline-address: 'https://github.com/element-plus/element-plus/blob/dev/CONTRIBUTING.md'

View File

@@ -1,4 +1,58 @@
## Changelog
### 2.0.6
_2022-03-11_
#### Features
- [el-table] support always show scrollbar & get selection rows (#6469 by @msidolphin)
- Extract eslint config to separate package (#6495 by @sxzz)
- Export dayjs instance, closes #6498 (#6517 by @sxzz)
- Components [select]add collapse-tags-tooltip (#6245 by @Alanscut)
- Components [select-v2]add collapse-tags-tooltip (#6532 by @Alanscut)
- Components [cascader]add collapse-tags-tooltip (#6331 by @Alanscut)
#### Bug fixes
- Deps update all non-major dependencies (#6448 by @renovate[bot])
- Components [el-select] optimize prefix size & selected style (#6267 by @msidolphin)
- Components [el-table] defaultSort not working (#6322 by @msidolphin)
- [el-table] fixed columns display when horizontal cannot scroll (#6320 by @msidolphin)
- Components [el-checkbox] modelValue (#6168 by @gjfei) (#6169)
- Components[select] (#6446 by @gjfei) (#6474)
- Components [el-collapse] collapse item key pressing jumping (#6462 by @JeremyWuuuuu)
- Components [message] offset error (#6497 by @Alanscut)
- Components[select] namespace (#6486 by @gjfei)
- Components el-select-allow-dynamically-update-options (#6473 by @gjfei)
- Components [el-tooltip] close the dropdown after set disabled (#6467 by @Alanscut)
- Docs improve component typings (#6524 by @sxzz)
- Workflow coverage (#6518 by @sxzz)
- Components [upload] fileList prop should sync uploadFiles (#6492 by @exherb)
- Components [rate] fix disbaledVoidIcon typo (#6456 by @Shana-AE)
- Components [select] display error when setting persistent to false (#6522 by @Alanscut)
- Components [el-input] pass input-style props to input-wrapper (#6506 by @zt123123)
- Components [el-table] escape special html characters (#6520 by @msidolphin)
- Components [autocomplete] fix shaking at first open (#6342 by @freedomlang)
- Components [el-upload] jitter when remove file in the list (#6505 by @msidolphin)
- Components [el-menu] icon is not to the right of the text (#6268 by @Alanscut)
- Test(components): [select] click icon twice (#6471 by @Alanscut)
- Chore(project): add escape-html as dependency for main entry (#6534 by @JeremyWuuuuu)
#### Refactors
- Components!: refactor form (#5401 by @sxzz)
- Components [row] refactor (#6354 by @sxzz)
- Docs [form] improve docs and typings (#6447 by @sxzz)
- Improve build dts (#6457 by @sxzz)
- Components [cascader,cascader-panel] add namespace (#5589 by @gjfei)
- Components [breadcrumb] switch to script-setup syntax (#6073 by @buqiyuan)
- Theme-chalk message css var & add getCssVarName (#6488 by @YunYouJun)
- Utils remove isFF (#6507 by @btea)
- Theme-chalk [upload] use bem rewrite upload scss & fix bugs (#6508 by @YunYouJun)
- Perf(components): [el-table] optimize the performance of expand rows (#6480 by @msidolphin)
- Style(components): rename disabled to is-disabled (#6475 by @freedomlang)
### 2.0.5

View File

@@ -80,7 +80,7 @@ Don't do:
</template>
```
:::demo When using multiple selection, all selected tags will display by default, You can set `collapse-tags = true` to fold selected tags.
:::demo When using multiple selection, all selected tags will display by default. You can set `collapse-tags = true` to fold selected tags. You can check them when mouse hover collapse text by using `collapse-tags-tooltip` attribute.
cascader/multiple-selection
@@ -149,6 +149,7 @@ cascader/panel
| clearable | whether selected value can be cleared | boolean | — | false |
| show-all-levels | whether to display all levels of the selected value in the input | boolean | — | true |
| collapse-tags | whether to collapse tags in multiple selection mode | boolean | - | false |
| collapse-tags-tooltip | whether show all selected tags when mouse hover text of collapse-tags. To use this, `collapse-tags` must be true | boolean | - | false |
| separator | option label separator | string | — | ' / ' |
| filterable | whether the options can be searched | boolean | — | — |
| filter-method | customize search logic, the first parameter is `node`, the second is `keyword`, and need return a boolean value indicating whether it hits. | function(node, keyword) | - | - |

View File

@@ -85,7 +85,7 @@ import { ElMessage } from 'element-plus'
In this case you should call `ElMessage(options)`. We have also registered methods for different types, e.g. `ElMessage.success(options)`. You can call `ElMessage.closeAll()` to manually close all the instances.
## App context inheritance <el-tag>> 2.0.2</el-tag>
## App context inheritance <el-tag> >= 2.0.3</el-tag>
Now message accepts a `context` as second parameter of the message constructor which allows you to inject current app's context to message which allows you to inherit all the properties of the app.
@@ -106,26 +106,29 @@ const { appContext } = getCurrentInstance()!
ElMessage({}, appContext)
```
## Options
## Message API
| Attribute | Description | Type | Accepted Values | Default |
| ------------------------ | ------------------------------------------------------------------------------ | -------------------- | -------------------------- | ------------- |
| message | message text | string / VNode | — | — |
| type | message type | string | success/warning/info/error | info |
| icon | custom icon component, overrides `type` | string / Component | — | — |
| dangerouslyUseHTMLString | whether `message` is treated as HTML string | boolean | — | false |
| custom-class | custom class name for Message | string | — | — |
| duration | display duration, millisecond. If set to 0, it will not turn off automatically | number | — | 3000 |
| show-close | whether to show a close button | boolean | — | false |
| center | whether to center the text | boolean | — | false |
| on-close | callback function when closed with the message instance as the parameter | function | — | — |
| offset | set the distance to the top of viewport | number | — | 20 |
| appendTo | set the root element for the message | string / HTMLElement | - | document.body |
| grouping | merge messages with the same content, type of VNode message is not supported | boolean | — | false |
### Options
## Methods
| Attribute | Description | Type | Default |
| -------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------- | --------------- |
| `message` | message text | `string \| VNode \| (() => VNode)` | — |
| `type` | message type | `'success' \| 'warning' \| 'info' \| 'error'` | `'info'` |
| `icon` | custom icon component, overrides `type` | `string \| Component` | — |
| `dangerouslyUseHTMLString` | whether `message` is treated as HTML string | `boolean` | `false` |
| `custom-class` | custom class name for Message | `string` | — |
| `duration` | display duration, millisecond. If set to 0, it will not turn off automatically | `number` | `3000` |
| `show-close` | whether to show a close button | `boolean` | `false` |
| `center` | whether to center the text | `boolean` | `false` |
| `on-close` | callback function when closed with the message instance as the parameter | `function` | — |
| `offset` | set the distance to the top of viewport | `number` | `20` |
| `appendTo` | set the root element for the message | `string \| HTMLElement` | `document.body` |
| `grouping` | merge messages with the same content, type of VNode message is not supported | `boolean` | `false` |
### Methods
`Message` and `this.$message` returns the current Message instance. To manually close the instance, you can call `close` on it.
| Method | Description |
| ---- | ---- |
| close | close the Message |
| Method | Description |
| ------- | ----------------- |
| `close` | close the Message |

View File

@@ -39,6 +39,8 @@ select-v2/multiple
## Hide extra tags when the selected items are too many.
You can collapse tags to a text by using `collapse-tags` attribute. You can check them when mouse hover collapse text by using `collapse-tags-tooltip` attribute.
:::demo
select-v2/hide-extra-tags
@@ -135,6 +137,7 @@ select-v2/remote-search
| clearable | whether select can be cleared | boolean | — | false |
| clear-icon | custom clear icon | string / component | — | CircleClose |
| collapse-tags | whether to collapse tags to a text when multiple selecting | boolean | — | false |
| collapse-tags-tooltip | whether show all selected tags when mouse hover text of collapse-tags. To use this, `collapse-tags` must be true | boolean | true / false | false |
| multiple-limit | maximum number of options user can select when multiple is true. No limit when set to 0 | number | — | 0 |
| name | the name attribute of select input | string | — | — |
| effect | Tooltip theme, built-in theme: `dark` / `light` | string | string | light |

View File

@@ -47,7 +47,7 @@ select/clearable
Multiple select uses tags to display selected options.
:::demo Set `multiple` attribute for `el-select` to enable multiple mode. In this case, the value of `v-model` will be an array of selected options. By default the selected options will be displayed as Tags. You can collapse them to a text by using `collapse-tags` attribute.
:::demo Set `multiple` attribute for `el-select` to enable multiple mode. In this case, the value of `v-model` will be an array of selected options. By default the selected options will be displayed as Tags. You can collapse them to a text by using `collapse-tags` attribute. You can check them when mouse hover collapse text by using `collapse-tags-tooltip` attribute.
select/multiple
@@ -120,6 +120,7 @@ If the binding value of Select is an object, make sure to assign `value-key` as
| size | size of Input | string | large/default/small | default |
| 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 | whether show all selected tags when mouse hover text of collapse-tags. To use this, `collapse-tags` must be true | boolean | true / false | false |
| multiple-limit | maximum number of options user can select when `multiple` is `true`. No limit when set to 0 | number | — | 0 |
| name | the name attribute of select input | string | — | — |
| effect | Tooltip theme, built-in theme: `dark` / `light` | string | string | light |

View File

@@ -7,6 +7,16 @@
<span class="example-demonstration">Collapse tags</span>
<el-cascader :options="options" :props="props" collapse-tags clearable />
</div>
<div class="example-block">
<span class="example-demonstration">Collapse tags tooltip</span>
<el-cascader
:options="options"
:props="props"
collapse-tags
collapse-tags-tooltip
clearable
/>
</div>
</template>
<script lang="ts" setup>

View File

@@ -1,12 +1,27 @@
<template>
<el-select-v2
v-model="value"
:options="options"
placeholder="Please select"
style="width: 240px"
multiple
collapse-tags
/>
<div style="display: inline-block">
<p style="margin: 10px">use collapse-tags</p>
<el-select-v2
v-model="value"
:options="options"
placeholder="Please select"
style="width: 240px"
multiple
collapse-tags
/>
</div>
<div style="display: inline-block; margin-left: 20px">
<p style="margin: 10px">use collapse-tags-tooltip</p>
<el-select-v2
v-model="value2"
:options="options"
placeholder="Please select"
style="width: 240px"
multiple
collapse-tags
collapse-tags-tooltip
/>
</div>
</template>
<script lang="ts" setup>
@@ -14,6 +29,7 @@ import { ref } from 'vue'
const initials = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
const value = ref([])
const value2 = ref([])
const options = Array.from({ length: 1000 }).map((_, idx) => ({
value: `Option ${idx + 1}`,
label: `${initials[idx % 10]}${idx}`,

View File

@@ -1,34 +1,58 @@
<template>
<el-select
v-model="value1"
multiple
placeholder="Select"
style="width: 240px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
<div style="display: inline-block">
<p style="margin-left: 10px">default</p>
<el-select
v-model="value1"
multiple
placeholder="Select"
style="width: 240px"
>
</el-option>
</el-select>
<el-select
v-model="value2"
multiple
collapse-tags
style="margin-left: 20px; width: 240px"
placeholder="Select"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
<div style="display: inline-block; margin-left: 20px">
<p style="margin-left: 10px">use collapse-tags</p>
<el-select
v-model="value2"
multiple
collapse-tags
placeholder="Select"
style="width: 240px"
>
</el-option>
</el-select>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
<div style="display: inline-block; margin-left: 20px">
<p style="margin-left: 10px">use collapse-tags-tooltip</p>
<el-select
v-model="value3"
multiple
collapse-tags
collapse-tags-tooltip
placeholder="Select"
style="width: 240px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
</template>
<script lang="ts" setup>
@@ -36,6 +60,7 @@ import { ref } from 'vue'
const value1 = ref([])
const value2 = ref([])
const value3 = ref([])
const options = [
{
value: 'Option1',

View File

@@ -69,6 +69,7 @@
"@vueuse/core": "^7.7.1",
"async-validator": "^4.0.7",
"dayjs": "^1.10.8",
"escape-html": "^1.0.3",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lodash-unified": "^1.0.2",

View File

@@ -13,7 +13,7 @@
trigger="click"
:transition="`${ns.namespace.value}-zoom-in-top`"
persistent
@show="onSuggestionShow"
@before-show="onSuggestionShow"
>
<div
ref="listboxRef"

View File

@@ -54,6 +54,7 @@ export interface Tag {
text: string
hitState?: boolean
closable: boolean
isCollapseTag: boolean
}
export interface ElCascaderPanelContext {

View File

@@ -4,6 +4,7 @@ import { EVENT_CODE } from '@element-plus/constants'
import { triggerEvent } from '@element-plus/test-utils'
import { ArrowDown, Check, CircleClose } from '@element-plus/icons-vue'
import { POPPER_CONTAINER_SELECTOR } from '@element-plus/hooks'
import { hasClass } from '@element-plus/utils'
import Cascader from '../src/index.vue'
jest.mock('lodash-unified', () => {
@@ -30,6 +31,10 @@ const OPTIONS = [
value: 'ningbo',
label: 'Ningbo',
},
{
value: 'wenzhou',
label: 'Wenzhou',
},
],
},
]
@@ -241,13 +246,40 @@ describe('Cascader.vue', () => {
modelValue: [
['zhejiang', 'hangzhou'],
['zhejiang', 'ningbo'],
['zhejiang', 'wenzhou'],
],
},
})
await nextTick()
const [firstTag, secondTag] = wrapper.findAll(TAG)
expect(firstTag.text()).toBe('Zhejiang / Hangzhou')
expect(secondTag.text()).toBe('+ 1')
const tags = wrapper.findAll(TAG).filter((item) => {
return !hasClass(item.element, 'in-tooltip')
})
expect(tags[0].text()).toBe('Zhejiang / Hangzhou')
expect(tags.length).toBe(2)
})
test('collapse tags tooltip', async () => {
const wrapper = mount(Cascader, {
props: {
options: OPTIONS,
props: { multiple: true },
collapseTags: true,
collapseTagsTooltip: true,
modelValue: [
['zhejiang', 'hangzhou'],
['zhejiang', 'ningbo'],
['zhejiang', 'wenzhou'],
],
},
})
await nextTick()
expect(wrapper.findAll(TAG).length).toBe(5)
const tags = wrapper.findAll(TAG).filter((item) => {
return hasClass(item.element, 'in-tooltip')
})
expect(tags[0].text()).toBe('Zhejiang / Hangzhou')
expect(tags[1].text()).toBe('Zhejiang / Ningbo')
expect(tags[2].text()).toBe('Zhejiang / Wenzhou')
})
test('filterable', async () => {

View File

@@ -88,7 +88,44 @@
disable-transitions
@close="deleteTag(tag)"
>
<span>{{ tag.text }}</span>
<template v-if="tag.isCollapseTag === false">
<span>{{ tag.text }}</span>
</template>
<template v-else>
<el-tooltip
:teleported="false"
:disabled="popperVisible || !collapseTagsTooltip"
:fallback-placements="['bottom', 'top', 'right', 'left']"
placement="bottom"
effect="light"
>
<template #default>
<span>{{ tag.text }}</span>
</template>
<template #content>
<div class="el-cascader__collapse-tags">
<div
v-for="(tag2, idx) in allPresentTags"
:key="idx"
class="el-cascader__collapse-tag"
>
<el-tag
:key="tag2.key"
class="in-tooltip"
type="info"
:size="tagSize"
:hit="tag2.hitState"
:closable="tag2.closable"
disable-transitions
@close="deleteTag(tag2)"
>
<span>{{ tag2.text }}</span>
</el-tag>
</div>
</div>
</template>
</el-tooltip>
</template>
</el-tag>
<input
v-if="filterable && !isDisabled"
@@ -286,6 +323,10 @@ export default defineComponent({
default: true,
},
collapseTags: Boolean,
collapseTagsTooltip: {
type: Boolean,
default: false,
},
debounce: {
type: Number,
default: 300,
@@ -341,6 +382,7 @@ export default defineComponent({
const inputValue = ref('')
const searchInputValue = ref('')
const presentTags: Ref<Tag[]> = ref([])
const allPresentTags: Ref<Tag[]> = ref([])
const suggestions: Ref<CascaderNode[]> = ref([])
const isOnComposition = ref(false)
@@ -436,6 +478,7 @@ export default defineComponent({
text: node.calcText(showAllLevels, separator),
hitState: false,
closable: !isDisabled.value && !node.isDisabled,
isCollapseTag: false,
}
}
@@ -452,6 +495,10 @@ export default defineComponent({
const nodes = checkedNodes.value
const tags: Tag[] = []
const allTags: Tag[] = []
nodes.forEach((node) => allTags.push(genTag(node)))
allPresentTags.value = allTags
if (nodes.length) {
const [first, ...rest] = nodes
const restCount = rest.length
@@ -464,6 +511,7 @@ export default defineComponent({
key: -1,
text: `+ ${restCount}`,
closable: false,
isCollapseTag: true,
})
} else {
rest.forEach((node) => tags.push(genTag(node)))
@@ -488,6 +536,9 @@ export default defineComponent({
presentTags.value.forEach((tag) => {
tag.hitState = false
})
allPresentTags.value.forEach((tag) => {
tag.hitState = false
})
}
filtering.value = true
@@ -701,6 +752,7 @@ export default defineComponent({
inputValue,
searchInputValue,
presentTags,
allPresentTags,
suggestions,
isDisabled,
isOnComposition,

View File

@@ -209,7 +209,10 @@ export default defineComponent({
const validateIcon = computed(
() => ValidateComponentsMap[validateState.value]
)
const containerStyle = computed(() => rawAttrs.style as StyleValue)
const containerStyle = computed<StyleValue>(() => [
rawAttrs.style,
props.inputStyle,
])
const computedTextareaStyle = computed<StyleValue>(() => [
props.inputStyle,
_textareaCalcStyle.value,

View File

@@ -1,4 +1,4 @@
import { nextTick } from 'vue'
import { nextTick, h, ref } from 'vue'
import { getStyle } from '@element-plus/utils'
import { rAF } from '@element-plus/test-utils/tick'
import { ElMessage } from '..'
@@ -106,6 +106,26 @@ describe('Message on command', () => {
expect(htmlElement.querySelector(selector)).toBeFalsy()
})
test('it should render vnode function', async () => {
const i = ref(0)
Message({
message: () => h('div', i.value),
})
await rAF()
expect(document.querySelector(selector)?.textContent).toMatchInlineSnapshot(
`"0"`
)
i.value++
await rAF()
expect(document.querySelector(selector)?.textContent).toMatchInlineSnapshot(
`"1"`
)
})
describe('context inheritance', () => {
it('should globally inherit context correctly', () => {
expect(ElMessage._context).toBe(null)

View File

@@ -7,6 +7,7 @@ import {
isString,
isElement,
debugWarn,
isFunction,
} from '@element-plus/utils'
import { useZIndex } from '@element-plus/hooks'
import { messageConfig } from '@element-plus/components/config-provider/src/config-provider'
@@ -101,7 +102,11 @@ const message: MessageFn & Partial<Message> & { _context: AppContext | null } =
const vm = createVNode(
MessageConstructor,
props,
isVNode(messageContent) ? { default: () => messageContent } : null
isFunction(messageContent)
? { default: messageContent }
: isVNode(messageContent)
? { default: () => messageContent }
: null
)
vm.appContext = context || message._context

View File

@@ -33,7 +33,11 @@ export const messageProps = buildProps({
default: '',
},
message: {
type: definePropType<string | VNode>([String, Object]),
type: definePropType<string | VNode | (() => VNode)>([
String,
Object,
Function,
]),
default: '',
},
onClose: {

View File

@@ -55,7 +55,7 @@ export const rateProps = buildProps({
type: iconPropType,
default: () => Star,
},
disabledvoidIcon: {
disabledVoidIcon: {
type: iconPropType,
default: () => StarFilled,
},

View File

@@ -155,7 +155,7 @@ export default defineComponent({
getValueFromMap(props.modelValue, componentMap.value)
)
const voidComponent = computed(() =>
rateDisabled.value ? props.disabledvoidIcon : props.voidIcon
rateDisabled.value ? props.disabledVoidIcon : props.voidIcon
)
const activeComponent = computed(() =>
getValueFromMap(currentValue.value, componentMap.value)

View File

@@ -52,6 +52,8 @@ interface SelectProps {
disabled?: boolean
clearable?: boolean
multiple?: boolean
collapseTags?: boolean
collapseTagsTooltip?: boolean
filterable?: boolean
remote?: boolean
multipleLimit?: number
@@ -101,6 +103,8 @@ const createSelect = (
:disabled="disabled"
:clearable="clearable"
:multiple="multiple"
:collapseTags="collapseTags"
:collapseTagsTooltip="collapseTagsTooltip"
:filterable="filterable"
:multiple-limit="multipleLimit"
:popper-append-to-body="popperAppendToBody"
@@ -141,6 +145,8 @@ const createSelect = (
disabled: false,
clearable: false,
multiple: false,
collapseTags: false,
collapseTagsTooltip: false,
remote: false,
filterable: false,
reserveKeyword: false,
@@ -559,6 +565,62 @@ describe('Select', () => {
})
})
describe('collapseTags', () => {
it('use collapseTags', async () => {
const wrapper = createSelect({
data: () => {
return {
multiple: true,
collapseTags: true,
value: [],
}
},
})
await nextTick()
const vm = wrapper.vm as any
const options = getOptions()
options[0].click()
await nextTick()
expect(vm.value.length).toBe(1)
expect(vm.value[0]).toBe(vm.options[0].value)
options[1].click()
await nextTick()
options[2].click()
await nextTick()
expect(vm.value.length).toBe(3)
const tags = wrapper.findAll('.el-tag').filter((item) => {
return !hasClass(item.element, 'in-tooltip')
})
expect(tags.length).toBe(2)
})
it('use collapseTagsTooltip', async () => {
const wrapper = createSelect({
data: () => {
return {
multiple: true,
collapseTags: true,
collapseTagsTooltip: true,
value: [],
}
},
})
await nextTick()
const vm = wrapper.vm as any
const options = getOptions()
options[0].click()
await nextTick()
expect(vm.value.length).toBe(1)
expect(vm.value[0]).toBe(vm.options[0].value)
options[1].click()
await nextTick()
options[2].click()
await nextTick()
expect(vm.value.length).toBe(3)
expect(wrapper.findAll('.el-tag')[4].element.textContent).toBe('c2')
})
})
describe('manually set modelValue', () => {
it('set modelValue in single select', async () => {
const wrapper = createSelect({

View File

@@ -24,6 +24,10 @@ export const SelectProps = {
default: 'light',
},
collapseTags: Boolean,
collapseTagsTooltip: {
type: Boolean,
default: false,
},
defaultFirstOption: Boolean,
disabled: Boolean,
estimatedOptionHeight: {

View File

@@ -66,7 +66,53 @@
type="info"
disable-transitions
>
<el-tooltip
v-if="collapseTagsTooltip"
:disabled="dropdownMenuVisible"
:fallback-placements="['bottom', 'top', 'right', 'left']"
:effect="effect"
placement="bottom"
:teleported="false"
>
<template #default>
<span
:class="nsSelectV2.e('tags-text')"
:style="{
maxWidth: `${tagMaxWidth}px`,
}"
>+ {{ modelValue.length - 1 }}</span
>
</template>
<template #content>
<div :class="nsSelectV2.e('selection')">
<div
v-for="(selected, idx) in states.cachedOptions"
:key="idx"
:class="nsSelectV2.e('selected-item')"
>
<el-tag
:key="getValueKey(selected)"
:closable="!selectDisabled && !selected.disabled"
:size="collapseTagSize"
class="in-tooltip"
type="info"
disable-transitions
@close="deleteTag($event, selected)"
>
<span
:class="nsSelectV2.e('tags-text')"
:style="{
maxWidth: `${tagMaxWidth}px`,
}"
>{{ getLabel(selected) }}</span
>
</el-tag>
</div>
</div>
</template>
</el-tooltip>
<span
v-else
:class="nsSelectV2.e('tags-text')"
:style="{
maxWidth: `${tagMaxWidth}px`,

View File

@@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils'
import { EVENT_CODE } from '@element-plus/constants'
import { CircleClose, ArrowUp, CaretTop } from '@element-plus/icons-vue'
import { POPPER_CONTAINER_SELECTOR } from '@element-plus/hooks'
import { hasClass } from '@element-plus/utils'
import Select from '../src/select.vue'
import Group from '../src/option-group.vue'
import Option from '../src/option.vue'
@@ -920,6 +921,56 @@ describe('Select', () => {
mockInputWidth.mockRestore()
})
test('multiple select with collapseTagsTooltip', async () => {
wrapper = _mount(
`
<el-select v-model="selectedList" multiple collapseTags collapse-tags-tooltip placeholder="请选择">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
`,
() => ({
options: [
{
value: '选项1',
label: '黄金糕',
},
{
value: '选项2',
label: '双皮奶',
},
{
value: '选项3',
label: '蚵仔煎',
},
{
value: '选项4',
label: '龙须面',
},
{
value: '选项5',
label: '北京烤鸭',
},
],
selectedList: [],
})
)
await wrapper.find('.select-trigger').trigger('click')
const options = getOptions()
options[0].click()
await nextTick()
options[1].click()
await nextTick()
options[2].click()
await nextTick()
const triggerWrappers = wrapper.findAll('.el-tooltip__trigger')
expect(triggerWrappers[0]).toBeDefined()
const tags = wrapper.findAll('.el-select__tags-text')
expect(tags.length).toBe(5)
expect(tags[4].element.textContent).toBe('蚵仔煎')
})
test('multiple remove-tag', async () => {
wrapper = _mount(
`
@@ -1435,9 +1486,17 @@ describe('Select', () => {
vm.isCollapsed = true
vm.isClearable = false
await nextTick()
expect(wrapper.findAll('.el-tag').length).toBe(2)
expect(
wrapper.findAll('.el-tag').filter((item) => {
return !hasClass(item.element, 'in-tooltip')
}).length
).toBe(2)
await wrapper.find('.el-tag__close').trigger('click')
expect(wrapper.findAll('.el-tag').length).toBe(2)
expect(
wrapper.findAll('.el-tag').filter((item) => {
return !hasClass(item.element, 'in-tooltip')
}).length
).toBe(2)
expect(wrapper.findAll('.el-tag__close').length).toBe(0)
// test for collapse select if is clearable
@@ -1445,9 +1504,17 @@ describe('Select', () => {
vm.isCollapsed = true
vm.isClearable = true
await nextTick()
expect(wrapper.findAll('.el-tag__close').length).toBe(1)
expect(
wrapper.findAll('.el-tag__close').filter((item) => {
return !hasClass(item.element.parentElement, 'in-tooltip')
}).length
).toBe(1)
await wrapper.find('.el-tag__close').trigger('click')
expect(wrapper.findAll('.el-tag').length).toBe(2)
expect(
wrapper.findAll('.el-tag').filter((item) => {
return !hasClass(item.element, 'in-tooltip')
}).length
).toBe(2)
expect(wrapper.findAll('.el-tag__close').length).toBe(0)
})

View File

@@ -25,6 +25,7 @@ import {
getCurrentInstance,
onBeforeUnmount,
reactive,
nextTick,
} from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { useOption } from './useOption'
@@ -74,7 +75,9 @@ export default defineComponent({
})
// if option is not selected, remove it from cache
if (select.cachedOptions.get(key) === vm && !doesSelected) {
select.cachedOptions.delete(key)
nextTick(() => {
select.cachedOptions.delete(key)
})
}
select.onOptionDestroy(key, vm)
})

View File

@@ -57,7 +57,50 @@
:type="tagType"
disable-transitions
>
<span :class="nsSelect.e('tags-text')"
<el-tooltip
v-if="collapseTagsTooltip"
:disabled="dropMenuVisible"
:fallback-placements="['bottom', 'top', 'right', 'left']"
:effect="effect"
placement="bottom"
:teleported="false"
>
<template #default>
<span :class="nsSelect.e('tags-text')"
>+ {{ selected.length - 1 }}</span
>
</template>
<template #content>
<div :class="nsSelect.e('collapse-tags')">
<div
v-for="(item, idx) in selected"
:key="idx"
:class="nsSelect.e('collapse-tag')"
>
<el-tag
:key="getValueKey(item)"
class="in-tooltip"
:closable="!selectDisabled && !item.isDisabled"
:size="collapseTagSize"
:hit="item.hitState"
:type="tagType"
disable-transitions
:style="{ margin: '2px' }"
@close="deleteTag($event, item)"
>
<span
:class="nsSelect.e('tags-text')"
:style="{
maxWidth: inputWidth - 75 + 'px',
}"
>{{ item.currentLabel }}</span
>
</el-tag>
</div>
</div>
</template>
</el-tooltip>
<span v-else :class="nsSelect.e('tags-text')"
>+ {{ selected.length - 1 }}</span
>
</el-tag>
@@ -322,6 +365,10 @@ export default defineComponent({
default: 'value',
},
collapseTags: Boolean,
collapseTagsTooltip: {
type: Boolean,
default: false,
},
popperAppendToBody: {
type: Boolean,
default: undefined,

View File

@@ -1,5 +1,6 @@
import { createPopper } from '@popperjs/core'
import { get } from 'lodash-unified'
import escapeHtml from 'escape-html'
import { hasOwn, off, on } from '@element-plus/utils'
import { useZIndex } from '@element-plus/hooks'
import type {
@@ -326,6 +327,7 @@ export function createTablePopper(
const isLight = tooltipEffect === 'light'
const content = document.createElement('div')
content.className = `el-popper ${isLight ? 'is-light' : 'is-dark'}`
popperContent = escapeHtml(popperContent)
content.innerHTML = popperContent
content.style.zIndex = String(nextZIndex())
document.body.appendChild(content)

View File

@@ -33,7 +33,10 @@
alt=""
/>
<div
v-if="file.status === 'uploading' || listType !== 'picture-card'"
v-if="
listType !== 'picture' &&
(file.status === 'uploading' || listType !== 'picture-card')
"
:class="nsUpload.be('list', 'item-info')"
>
<a

View File

@@ -54,6 +54,7 @@
"@vueuse/core": "^7.7.1",
"async-validator": "^4.0.7",
"dayjs": "^1.10.8",
"escape-html": "^1.0.3",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lodash-unified": "^1.0.2",

View File

@@ -149,6 +149,20 @@
}
}
@include e(collapse-tags) {
white-space: normal;
z-index: var(--el-index-normal);
display: flex;
align-items: center;
flex-wrap: wrap;
}
@include e(collapse-tag) {
line-height: inherit;
height: inherit;
display: flex;
}
@include e(suggestion-panel) {
border-radius: var(--el-cascader-menu-radius);
}

View File

@@ -104,12 +104,17 @@
.#{$namespace}-list-leave-active {
transition: all 1s;
}
.#{$namespace}-list-enter-from,
.#{$namespace}-list-leave-active {
.#{$namespace}-list-leave-to {
opacity: 0;
transform: translateY(-30px);
}
.#{$namespace}-list-leave-active {
position: absolute !important;
}
.#{$namespace}-opacity-transition {
transition: opacity var(--el-transition-duration)
cubic-bezier(0.55, 0, 0.1, 1);

View File

@@ -129,6 +129,10 @@
color: var(--el-menu-text-color);
}
& .#{$namespace}-sub-menu__title {
padding-right: 40px;
}
& .#{$namespace}-menu-item.is-active,
& .#{$namespace}-sub-menu.is-active > .#{$namespace}-sub-menu__title {
color: var(--el-menu-active-color);

View File

@@ -159,6 +159,20 @@
flex-wrap: wrap;
}
@include e(collapse-tags) {
white-space: normal;
z-index: var(--el-index-normal);
display: flex;
align-items: center;
flex-wrap: wrap;
}
@include e(collapse-tag) {
line-height: inherit;
height: inherit;
display: flex;
}
.#{$namespace}-select__tags {
.#{$namespace}-tag {
box-sizing: border-box;

View File

@@ -113,24 +113,20 @@
}
@include b(upload-list) {
margin: 0;
margin: 10px 0 0;
padding: 0;
list-style: none;
position: relative;
@include e(item) {
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
font-size: 14px;
color: var(--el-text-color-regular);
margin-top: 4px;
margin-bottom: 5px;
position: relative;
box-sizing: border-box;
border-radius: 4px;
width: 100%;
display: inline-flex;
align-items: center;
padding: 1px;
.#{bem('progress')} {
position: absolute;
top: 20px;
@@ -148,10 +144,6 @@
padding-right: 0;
}
&:first-child {
margin-top: 8px;
}
& .#{bem('icon', '', 'upload-success')} {
color: var(--el-color-success);
}
@@ -160,10 +152,12 @@
display: none;
position: absolute;
right: 5px;
top: 50%;
cursor: pointer;
opacity: 0.75;
color: var(--el-text-color-regular);
transition: opacity var(--el-transition-duration);
transform: translateY(-50%);
&:hover {
opacity: 1;
@@ -179,6 +173,7 @@
cursor: pointer;
opacity: 1;
color: var(--el-color-primary);
transform: translateY(-50%);
}
&:hover {
@@ -296,6 +291,7 @@
display: inline-flex;
flex-wrap: wrap;
margin: 0;
#{getCssVarName('upload-list', 'picture-card', 'size')}: 148px;
.#{bem('upload-list', 'item')} {
@@ -426,7 +422,6 @@
box-sizing: border-box;
margin-top: 10px;
padding: 10px;
height: 92px;
.#{bem('icon', '' ,'check')},
.#{bem('icon', '' ,'circle-check')} {
@@ -450,10 +445,11 @@
display: none;
}
}
}
.#{bem('icon', '' ,'close')} {
top: 5px;
}
& .#{bem('icon', '' ,'close')} {
top: 5px;
transform: translateY(0);
}
}

View File

@@ -4,6 +4,9 @@
"allowJs": true,
"lib": ["ESNext", "DOM"],
"types": ["node", "vite/client", "../typings/components"],
"paths": {
"element-plus": ["../packages/element-plus"]
},
"allowSyntheticDefaultImports": true
},
"include": ["**/*"]

7
pnpm-lock.yaml generated
View File

@@ -54,6 +54,7 @@ importers:
cz-conventional-changelog: ^3.3.0
dayjs: ^1.10.8
esbuild: ^0.14.25
escape-html: ^1.0.3
eslint: ^8.10.0
eslint-define-config: ^1.2.5
expect-type: ^0.13.0
@@ -103,6 +104,7 @@ importers:
'@vueuse/core': 7.7.1_vue@3.2.31
async-validator: 4.0.7
dayjs: 1.10.8
escape-html: 1.0.3
lodash: 4.17.21
lodash-es: 4.17.21
lodash-unified: 1.0.2_da03a4540fbd16bbaafbb96724306afd
@@ -242,6 +244,7 @@ importers:
async-validator: ^4.0.7
csstype: ^2.6.19
dayjs: ^1.10.8
escape-html: ^1.0.3
lodash: ^4.17.21
lodash-es: ^4.17.21
lodash-unified: ^1.0.2
@@ -255,6 +258,7 @@ importers:
'@vueuse/core': 7.7.1_vue@3.2.31
async-validator: 4.0.7
dayjs: 1.10.8
escape-html: 1.0.3
lodash: 4.17.21
lodash-es: 4.17.21
lodash-unified: 1.0.2_da03a4540fbd16bbaafbb96724306afd
@@ -5986,7 +5990,6 @@ packages:
/escape-html/1.0.3:
resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=}
dev: true
/escape-string-regexp/1.0.5:
resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}
@@ -9951,7 +9954,7 @@ packages:
dev: true
/path-name/1.0.0:
resolution: {integrity: sha1-jKBjpj3nmC36lXYO2v/RAhRJTyQ=}
resolution: {integrity: sha512-/dcAb5vMXH0f51yvMuSUqFpxUcA8JelbRmE5mW/p4CUJxrNgK24IkstnV7ENtg2IDGBOu6izKTG6eilbnbNKWQ==}
dev: true
/path-parse/1.0.7: