Merge pull request #19490 from element-plus/dev

D2M
This commit is contained in:
iamkun 2025-01-03 21:18:09 +08:00 committed by GitHub
commit 46abc4f6d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 944 additions and 222 deletions

View File

@ -1,11 +1,11 @@
name: Publish PR Commit Pkg
on:
push:
branches:
- dev
tags:
- '!**'
# push:
# branches:
# - dev
# tags:
# - '!**'
pull_request:
branches:
- dev
@ -13,6 +13,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
if: ${{ github.repository == 'element-plus/element-plus' }}
steps:
- name: Checkout code
uses: actions/checkout@v4

View File

@ -1,5 +1,34 @@
## Changelog
### 2.9.2
_2025-01-03_
#### Features
- Components [select-v2] add `fit-input-width` prop (#18834 by @YiMo1)
- Components [select, select-v2] add loading class to validateIcon (#19379 by @LoTwT)
- Components [table] add allow-drag-last-column prop (#19374 by @btea)
#### Bug fixes
- Components [select] resolve 'false' display issue when dropdown hides on search clear (#19244 by @DDDDD12138)
- Components [date-picker] model-value unexpected changes when type is week (#16795 by @FrontEndDog)
- Components [menu] fix `sliceIndex` calculation error (#19164 by @wen-lun)
- Components [anchor] scroll whether link is selected at the top (#18047 by @k983551019)
- Components [focus-trap] tryFocus is invalid for document.body (#19272 by @tolking)
- Components [inputnumber, input] resolve styling issues caused by using `prefix` and `suffix` (#19042 by @DDDDD12138)
- Components [color-picker] optimize the flickering issue (#18872 by @momei-LJM)
- Theme-chalk [input-tag] correct input-tag placeholder color (#19386 by @DDDDD12138)
- Components prevent blur event when is disabled (#19320 by @DDDDD12138)
- Types packing unexpected types (#19419 by @btea)
- Components [table] the overflowTooltip cannot be refreshed (#19440 by @xingyixiang)
- Components [upload] unable to delete files in removeFile (#19437 by @ly-yewu)
- Components [page-header] use `$slots` instead of `useSlots` (#19455 by @Dsaquel)
- Components [select] input width fills the remaining width (#19292 by @tolking)
- Style(components): [notification] word wrap (#17052 by @Liao-js)
- Perf(components): [table] prioritize use rowKey to determine whether it is selected (#19451 by @tolking)
### 2.9.1

View File

@ -1,3 +1,4 @@
import { isExternal } from 'vitepress/dist/client/shared'
import { ensureLang } from '../utils/lang'
import navLocale from '../i18n/pages/sidebar.json'
@ -12,7 +13,7 @@ function getNav() {
activeMatch?: string
}[] = Object.values(locales).map((item) => ({
...item,
link: `${ensureLang(lang)}${item.link}`,
link: `${isExternal(item.link) ? '' : ensureLang(lang)}${item.link}`,
}))
return [lang, item]

View File

@ -13,5 +13,9 @@
"text": "Resource",
"link": "/resource/index",
"activeMatch": "/resource/"
},
{
"text": "Playground",
"link": "https://element-plus.run"
}
]

View File

@ -10,7 +10,7 @@ const sponsor = computed(() => sponsorLocale[lang.value])
</script>
<template>
<div class="page-sidebar-table">
<div class="page-sidebar-panel">
<p class="title">{{ sponsor.sponsoredBy }}</p>
<VPSponsorLarge />
<VPSponsorSmall />
@ -18,7 +18,7 @@ const sponsor = computed(() => sponsorLocale[lang.value])
</template>
<style lang="scss" scoped>
.page-sidebar-table {
.page-sidebar-panel {
padding-bottom: 10px;
padding-top: 0;
.title {

View File

@ -77,15 +77,16 @@ anchor/affix
### Anchor Attributes
| Property | Description | Type | Default |
| --------- | ---------------------------------------------------------- | -------------------------------------- | ---------- |
| container | scroll container. | `string` \| `HTMLElement` \| `Window ` | — |
| offset | set the offset of the anchor scroll. | `number` | 0 |
| bound | the offset of the element starting to trigger the anchor. | `number` | 15 |
| duration | set the scroll duration of the container, in milliseconds. | `number` | 300 |
| marker | whether to show the marker. | ^[boolean] | true |
| type | set Anchor type. | ^[enum]`'default' \| 'underline'` | `default` |
| direction | Set Anchor direction. | ^[enum]`'vertical' \| 'horizontal'` | `vertical` |
| Property | Description | Type | Default |
| -------------------------- | ---------------------------------------------------------- | -------------------------------------- | ---------- |
| container | scroll container. | `string` \| `HTMLElement` \| `Window ` | — |
| offset | set the offset of the anchor scroll. | `number` | 0 |
| bound | the offset of the element starting to trigger the anchor. | `number` | 15 |
| duration | set the scroll duration of the container, in milliseconds. | `number` | 300 |
| marker | whether to show the marker. | ^[boolean] | true |
| type | set Anchor type. | ^[enum]`'default' \| 'underline'` | `default` |
| direction | Set Anchor direction. | ^[enum]`'vertical' \| 'horizontal'` | `vertical` |
| select-scroll-top ^(2.9.2) | scroll whether link is selected at the top | ^[boolean] | false |
### Anchor Events

View File

@ -105,6 +105,42 @@ When using `modal` = false, please make sure that `append-to-body` was set to **
:::
## Fullscreen
Set the `fullscreen` attribute to open fullscreen dialog.
:::demo
dialog/fullscreen
:::
:::tip
If `fullscreen` is true, `width` `top` `draggable` attributes don't work.
:::
## Modal
Setting `modal` to `false` will hide modal (overlay) of dialog.
:::demo
dialog/modal
:::
## Events
Open developer console (ctrl + shift + J), to see order of events.
:::demo
dialog/events
:::
## API
### Attributes
@ -147,7 +183,7 @@ When using `modal` = false, please make sure that `append-to-body` was set to **
| Name | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------- |
| — | content of Dialog |
| default | default content of Dialog |
| header | content of the Dialog header; Replacing this removes the title, but does not remove the close button. |
| footer | content of the Dialog footer |
| title ^(deprecated) | works the same as the header slot. Use that instead. |

View File

@ -168,6 +168,7 @@ The corresponding methods are: `ElMessageBox`, `ElMessageBox.alert`, `ElMessageB
| icon | custom icon component, overrides `type` | ^[string] / ^[Component] | '' |
| customClass | custom class name for MessageBox | ^[string] | '' |
| customStyle | custom inline style for MessageBox | ^[CSSProperties] | {} |
| modalClass | custom class names for mask | string | — |
| callback | MessageBox closing callback if you don't prefer Promise | ^[Function]`(value: string, action: Action) => any \| (action: Action) => any` | null |
| showClose | whether to show close icon of MessageBox | ^[boolean] | true |
| beforeClose | callback before MessageBox closes, and it will prevent MessageBox from closing | ^[Function]`(action: Action, instance: MessageBoxState, done: () => void) => void` | null |

View File

@ -213,6 +213,16 @@ select-v2/custom-label
:::
## Custom Width ^(2.9.2)
The width of dropdown box is calculated by default based on the value of `label`. If you customize the dropdown box options through the `default slot`, it is likely that the text displayed in the options is not equal to the value of `label`, resulting in calculation errors. In this case, you can set the `fit-input-width` attribute to a number to fix its width.
:::demo
select-v2/custom-width
:::
## API
### Attributes
@ -248,6 +258,7 @@ select-v2/custom-label
| persistent | when select dropdown is inactive and `persistent` is `false`, select dropdown will be destroyed | ^[boolean] | true |
| popper-options | [popper.js](https://popper.js.org/docs/v2/) parameters | ^[object]refer to [popper.js](https://popper.js.org/docs/v2/) doc | {} |
| automatic-dropdown | for non-filterable Select, this prop decides if the option menu pops up when the input is focused | ^[boolean] | false |
| fit-input-width ^(2.9.2) | whether the width of the dropdown is the same as the input, if the value is `number`, then the width is fixed | ^[boolean] / ^[number] | true |
| height | The height of the dropdown panel, 34px for each item | ^[number] | 274 |
| item-height | The height of the dropdown item | ^[number] | 34 |
| scrollbar-always-on | Controls whether the scrollbar is always displayed | ^[boolean] | false |

View File

@ -289,6 +289,7 @@ table/table-layout
| show-overflow-tooltip | whether to hide extra content and show them in a tooltip when hovering on the cell.It will affect all the table columns, refer to table [tooltip-options](#table-attributes) | ^[boolean] / [`object`](#table-attributes) ^(2.3.7) | — |
| flexible ^(2.2.1) | ensure main axis minimum-size doesn't follow the content | ^[boolean] | false |
| scrollbar-tabindex ^(2.8.3) | body scrollbar's wrap container tabindex | ^[string] / ^[number] | — |
| allow-drag-last-column ^(2.9.2) | whether to allow drag the last column | ^[boolean] | true |
### Table Events

View File

@ -1,5 +1,8 @@
<template>
<el-config-provider :value-on-clear="null" :empty-values="[undefined, null]">
<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"

View File

@ -0,0 +1,37 @@
<template>
<el-button plain @click="dialogVisible = true">
Open the event Dialog
</el-button>
<el-dialog
v-model="dialogVisible"
modal-class="overide-animation"
:before-close="
(doneFn) => {
console.log('before-close'), doneFn()
}
"
@open="console.log('open')"
@open-auto-focus="console.log('open-auto-focus')"
@opened="console.log('opened')"
@close="console.log('close')"
@close-auto-focus="console.log('close-auto-focus')"
@closed="console.log('closed')"
>
<span>It's a event Dialog</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const dialogVisible = ref(false)
</script>

View File

@ -0,0 +1,29 @@
<template>
<el-button plain @click="dialogVisible = true">
Open the fullscreen Dialog
</el-button>
<el-dialog
v-model="dialogVisible"
fullscreen
top="40vh"
width="70%"
draggable
>
<span>It's a fullscreen Dialog</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const dialogVisible = ref(false)
</script>

View File

@ -0,0 +1,23 @@
<template>
<el-button plain @click="dialogVisible = true">
Open the modal Dialog
</el-button>
<el-dialog v-model="dialogVisible" :modal="false">
<span>It's a modal Dialog</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const dialogVisible = ref(false)
</script>

View File

@ -1,39 +1,73 @@
<template>
<el-row>
<el-col :span="24"><div class="grid-content ep-bg-purple-dark" /></el-col>
<el-col :span="24">
<div class="grid-content ep-bg-purple-dark" />
</el-col>
</el-row>
<el-row>
<el-col :span="12"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="12"><div class="grid-content ep-bg-purple-light" /></el-col>
<el-col :span="12">
<div class="grid-content ep-bg-purple" />
</el-col>
<el-col :span="12">
<div class="grid-content ep-bg-purple-light" />
</el-col>
</el-row>
<el-row>
<el-col :span="8"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="8"><div class="grid-content ep-bg-purple-light" /></el-col>
<el-col :span="8"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="8">
<div class="grid-content ep-bg-purple" />
</el-col>
<el-col :span="8">
<div class="grid-content ep-bg-purple-light" />
</el-col>
<el-col :span="8">
<div class="grid-content ep-bg-purple" />
</el-col>
</el-row>
<el-row>
<el-col :span="6"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content ep-bg-purple-light" /></el-col>
<el-col :span="6"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content ep-bg-purple-light" /></el-col>
<el-col :span="6">
<div class="grid-content ep-bg-purple" />
</el-col>
<el-col :span="6">
<div class="grid-content ep-bg-purple-light" />
</el-col>
<el-col :span="6">
<div class="grid-content ep-bg-purple" />
</el-col>
<el-col :span="6">
<div class="grid-content ep-bg-purple-light" />
</el-col>
</el-row>
<el-row>
<el-col :span="4"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content ep-bg-purple-light" /></el-col>
<el-col :span="4"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content ep-bg-purple-light" /></el-col>
<el-col :span="4"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content ep-bg-purple-light" /></el-col>
<el-col :span="4">
<div class="grid-content ep-bg-purple" />
</el-col>
<el-col :span="4">
<div class="grid-content ep-bg-purple-light" />
</el-col>
<el-col :span="4">
<div class="grid-content ep-bg-purple" />
</el-col>
<el-col :span="4">
<div class="grid-content ep-bg-purple-light" />
</el-col>
<el-col :span="4">
<div class="grid-content ep-bg-purple" />
</el-col>
<el-col :span="4">
<div class="grid-content ep-bg-purple-light" />
</el-col>
</el-row>
</template>
<style lang="scss">
<style>
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}

View File

@ -1,12 +1,12 @@
<template>
<div>
<el-segmented v-model="value" :options="options">
<template #default="{ item }">
<template #default="scope">
<div class="flex flex-col items-center gap-2 p-2">
<el-icon size="20">
<component :is="item.icon" />
<component :is="scope.item.icon" />
</el-icon>
<div>{{ item.label }}</div>
<div>{{ scope.item.label }}</div>
</div>
</template>
</el-segmented>

View File

@ -18,7 +18,7 @@
:direction="direction"
:size="size"
>
<template #default="{ item }">
<template #default="scope">
<div
:class="[
'flex',
@ -29,9 +29,9 @@
]"
>
<el-icon size="20">
<component :is="item.icon" />
<component :is="scope.item.icon" />
</el-icon>
<div>{{ item.label }}</div>
<div>{{ scope.item.label }}</div>
</div>
</template>
</el-segmented>

View File

@ -68,7 +68,7 @@ const clear = () => {
}
</script>
<style lang="scss" scoped>
<style>
.select-footer {
display: flex;
flex-direction: column;

View File

@ -60,7 +60,7 @@ const handleCheckAll = (val: CheckboxValueType) => {
}
</script>
<style lang="scss">
<style>
.custom-header {
.el-checkbox {
display: flex;

View File

@ -0,0 +1,43 @@
<template>
<div class="flex flex-wrap gap-4 items-center">
<el-select-v2
v-model="value"
:options="options"
placeholder="Please select"
style="width: 240px"
:fit-input-width="false"
/>
<el-select-v2
v-model="value"
:options="options"
placeholder="Please select"
style="width: 240px"
fit-input-width
/>
<el-select-v2
v-model="value"
:options="options"
placeholder="Please select"
style="width: 240px"
:fit-input-width="440"
>
<template #default="{ item }">
<span>{{ item.value + item.label }}</span>
</template>
</el-select-v2>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const initials = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
const value = ref()
const options = Array.from({ length: 1000 }).map((_, idx) => ({
value: `Option ${idx + 1}`,
label: `${initials[idx % 10]}${idx}${'-'.repeat(Math.ceil(idx / 25))}`,
}))
</script>

View File

@ -81,7 +81,7 @@ const clear = () => {
}
</script>
<style lang="scss" scoped>
<style>
.option-input {
width: 100%;
margin-bottom: 8px;

View File

@ -84,7 +84,7 @@ const handleCheckAll = (val: CheckboxValueType) => {
}
</script>
<style lang="scss">
<style>
.custom-header {
.el-checkbox {
display: flex;

View File

@ -2,6 +2,7 @@
<el-table
ref="multipleTableRef"
:data="tableData"
row-key="id"
style="width: 100%"
@selection-change="handleSelectionChange"
>

View File

@ -20,7 +20,7 @@
"nprogress": "^0.2.0"
},
"devDependencies": {
"@crowdin/cli": "^3.7.10",
"@crowdin/cli": "^4.5.0",
"@docsearch/react": "^3.1.0",
"@element-plus/build": "workspace:*",
"@element-plus/build-constants": "workspace:*",

View File

@ -60,6 +60,13 @@ export const anchorProps = buildProps({
type: definePropType<'vertical' | 'horizontal'>(String),
default: 'vertical',
},
/**
* @description Scroll whether link is selected at the top
*/
selectScrollTop: {
type: Boolean,
default: false,
},
})
export type AnchorProps = ExtractPropTypes<typeof anchorProps>

View File

@ -134,13 +134,12 @@ const getCurrentHref = () => {
})
}
anchorTopList.sort((prev, next) => prev.top - next.top)
for (let i = 0; i < anchorTopList.length; i++) {
const item = anchorTopList[i]
const next = anchorTopList[i + 1]
if (i === 0 && scrollTop === 0) {
return ''
return props.selectScrollTop ? item.href : ''
}
if (item.top <= scrollTop && (!next || next.top > scrollTop)) {
return item.href

View File

@ -129,7 +129,7 @@ import { useFormDisabled } from '@element-plus/components/form'
import { autocompleteEmits, autocompleteProps } from './autocomplete'
import type { AutocompleteData } from './autocomplete'
import type { StyleValue } from 'vue'
import type { Ref, StyleValue } from 'vue'
import type { TooltipInstance } from '@element-plus/components/tooltip'
import type { InputInstance } from '@element-plus/components/input'
@ -377,7 +377,21 @@ onMounted(() => {
readonly = (inputRef.value as any).ref!.hasAttribute('readonly')
})
defineExpose({
defineExpose<{
highlightedIndex: Ref<number>
activated: Ref<boolean>
loading: Ref<boolean>
inputRef: Ref<InputInstance | undefined>
popperRef: Ref<TooltipInstance | undefined>
suggestions: Ref<AutocompleteData>
handleSelect: (item: any) => void
handleKeyEnter: () => void
focus: () => void
blur: () => void
close: () => void
highlight: (index: number) => void
getData: (queryString: string) => void
}>({
/** @description the index of the currently highlighted item */
highlightedIndex,
/** @description autocomplete whether activated */

View File

@ -16,7 +16,10 @@
@hide="setShowPicker(false)"
>
<template #content>
<div v-click-outside="handleClickOutside" @keydown.esc="handleEsc">
<div
v-click-outside:[triggerRef]="handleClickOutside"
@keydown.esc="handleEsc"
>
<div :class="ns.be('dropdown', 'main-wrapper')">
<hue-slider ref="hue" class="hue-slider" :color="color" vertical />
<sv-panel ref="sv" :color="color" />
@ -245,7 +248,6 @@ function setShowPicker(value: boolean) {
}
const debounceSetShowPicker = debounce(setShowPicker, 100, { leading: true })
function show() {
if (colorDisabled.value) return
setShowPicker(true)
@ -271,6 +273,9 @@ function resetColor() {
function handleTrigger() {
if (colorDisabled.value) return
if (showPicker.value) {
resetColor()
}
debounceSetShowPicker(!showPicker.value)
}

View File

@ -659,12 +659,17 @@ const getDefaultValue = () => {
return parseDate
}
const handleFocusPicker = async () => {
const handleFocusPicker = () => {
if (['week', 'month', 'year', 'date'].includes(selectionMode.value)) {
currentViewRef.value?.focus()
if (selectionMode.value === 'week') {
handleKeyControl(EVENT_CODE.down)
}
}
}
const _handleFocusPicker = () => {
handleFocusPicker()
// TODO: After focus the date input, the first time you use the ArrowDown keys, you cannot focus on the date cell
if (selectionMode.value === 'week') {
handleKeyControl(EVENT_CODE.down)
}
}
@ -827,5 +832,5 @@ watch(
contextEmit('set-picker-option', ['isValidValue', isValidValue])
contextEmit('set-picker-option', ['formatToString', formatToString])
contextEmit('set-picker-option', ['parseUserInput', parseUserInput])
contextEmit('set-picker-option', ['handleFocusPicker', handleFocusPicker])
contextEmit('set-picker-option', ['handleFocusPicker', _handleFocusPicker])
</script>

View File

@ -3,6 +3,7 @@ import {
focusFirstDescendant,
getEdges,
obtainAllFocusableElements,
tryFocus,
} from '../src/utils'
describe('focus-trap utils', () => {
@ -53,4 +54,38 @@ describe('focus-trap utils', () => {
focusFirstDescendant(focusable)
expect(document.activeElement).toBe(focusable[0])
})
describe('tryFocus', () => {
it('should be focus the input element', () => {
const input = document.querySelector('.focusable-input') as HTMLElement
tryFocus(input)
expect(document.activeElement).toBe(input)
})
it('should be focus the span element', () => {
const span = document.querySelector('.focusable-span') as HTMLElement
tryFocus(span)
expect(document.activeElement).toBe(span)
})
it('should be focus the disabled input element', () => {
const input = document.querySelector('[disabled]') as HTMLElement
tryFocus(input)
expect(document.activeElement).toBe(input)
})
it('should be focus the document body', () => {
const input = document.querySelector('.focusable-input') as HTMLElement
tryFocus(input)
expect(document.activeElement).not.toBe(document.body)
tryFocus(document.body)
expect(document.activeElement).toBe(document.body)
})
it('should be focus the null element', () => {
const activeElement = document.activeElement
tryFocus(null)
expect(document.activeElement).toBe(activeElement)
})
})
})

View File

@ -1,4 +1,5 @@
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { isElement, isFocusable } from '@element-plus/utils'
import { FOCUSOUT_PREVENTED, FOCUSOUT_PREVENTED_OPTS } from './tokens'
const focusReason = ref<'pointer' | 'keyboard'>()
@ -81,8 +82,20 @@ export const tryFocus = (
) => {
if (element && element.focus) {
const prevFocusedElement = document.activeElement
let cleanup: boolean = false
if (
isElement(element) &&
!isFocusable(element) &&
!element.getAttribute('tabindex')
) {
element.setAttribute('tabindex', '-1')
cleanup = true
}
element.focus({ preventScroll: true })
lastAutomatedFocusTimestamp.value = window.performance.now()
if (
element !== prevFocusedElement &&
isSelectable(element) &&
@ -90,6 +103,9 @@ export const tryFocus = (
) {
element.select()
}
if (isElement(element) && cleanup) {
element.removeAttribute('tabindex')
}
}
}

View File

@ -1,5 +1,5 @@
<template>
<div ref="wrapperRef" :class="ns.b()">
<div ref="wrapperRef" :class="[ns.b(), ns.is('disabled', disabled)]">
<el-input
v-bind="mergeProps(passInputProps, $attrs)"
ref="elInputRef"
@ -69,7 +69,7 @@ import { getCursorPosition, getMentionCtx } from './helper'
import ElMentionDropdown from './mention-dropdown.vue'
import type { Placement } from '@popperjs/core'
import type { CSSProperties } from 'vue'
import type { CSSProperties, ComputedRef, Ref } from 'vue'
import type { InputInstance } from '@element-plus/components/input'
import type { TooltipInstance } from '@element-plus/components/tooltip'
import type { MentionCtx, MentionOption } from './types'
@ -280,7 +280,11 @@ const syncDropdownVisible = () => {
visible.value = false
}
defineExpose({
defineExpose<{
input: Ref<InputInstance | undefined>
tooltip: Ref<TooltipInstance | undefined>
dropdownVisible: ComputedRef<boolean>
}>({
input: elInputRef,
tooltip: tooltipRef,
dropdownVisible,

View File

@ -326,10 +326,7 @@ export default defineComponent({
const calcSliceIndex = () => {
if (!menu.value) return -1
const items = Array.from(menu.value?.childNodes ?? []).filter(
(item) =>
// remove comment type node #12634
item.nodeName !== '#comment' &&
(item.nodeName !== '#text' || item.nodeValue)
(item) => item.nodeName !== '#text' || item.nodeValue
) as HTMLElement[]
const moreItemWidth = 64
const computedMenuStyle = getComputedStyle(menu.value!)
@ -339,6 +336,7 @@ export default defineComponent({
let calcWidth = 0
let sliceIndex = 0
items.forEach((item, index) => {
if (item.nodeName === '#comment') return
calcWidth += calcMenuItemWidth(item)
if (calcWidth <= menuWidth - moreItemWidth) {
sliceIndex = index + 1

View File

@ -94,6 +94,44 @@ describe('PageHeader.vue', () => {
expect(wrapper.find('.el-page-header__title').text()).toEqual(AXIOM)
})
test('conditional slots rendering', async () => {
const wrapper = mount(
(props: {
showDefault: boolean
showBreadcrumb: boolean
showExtra: boolean
}) => (
<PageHeader
v-slots={{
default: props.showDefault ? () => AXIOM : undefined,
breadcrumb: props.showBreadcrumb ? () => AXIOM : undefined,
extra: props.showExtra ? () => AXIOM : undefined,
}}
/>
),
{
props: {
showDefault: false,
showBreadcrumb: false,
showExtra: false,
},
}
)
expect(wrapper.classes()).not.toContain('is-contentful')
expect(wrapper.classes()).not.toContain('el-page-header--has-breadcrumb')
expect(wrapper.classes()).not.toContain('el-page-header--has-extra')
await wrapper.setProps({
showDefault: true,
showBreadcrumb: true,
showExtra: true,
})
expect(wrapper.classes()).toContain('is-contentful')
expect(wrapper.classes()).toContain('el-page-header--has-breadcrumb')
expect(wrapper.classes()).toContain('el-page-header--has-extra')
})
test('event back', async () => {
const wrapper = mount(() => <PageHeader content={AXIOM} />)
const pageHeader = wrapper.findComponent(PageHeader)

View File

@ -1,5 +1,14 @@
<template>
<div :class="kls">
<div
:class="[
ns.b(),
{
[ns.m('has-breadcrumb')]: !!$slots.breadcrumb,
[ns.m('has-extra')]: !!$slots.extra,
[ns.is('contentful')]: !!$slots.default,
},
]"
>
<div v-if="$slots.breadcrumb" :class="ns.e('breadcrumb')">
<slot name="breadcrumb" />
</div>
@ -43,7 +52,6 @@
</div>
</template>
<script lang="ts" setup>
import { computed, useSlots } from 'vue'
import { ElIcon } from '@element-plus/components/icon'
import { ElDivider } from '@element-plus/components/divider'
@ -56,20 +64,9 @@ defineOptions({
defineProps(pageHeaderProps)
const emit = defineEmits(pageHeaderEmits)
const slots = useSlots()
const { t } = useLocale()
const ns = useNamespace('page-header')
const kls = computed(() => {
return [
ns.b(),
{
[ns.m('has-breadcrumb')]: !!slots.breadcrumb,
[ns.m('has-extra')]: !!slots.extra,
[ns.is('contentful')]: !!slots.default,
},
]
})
function handleClick() {
emit('back')

View File

@ -1,6 +1,6 @@
// @ts-nocheck
import { nextTick, ref } from 'vue'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
import { NOOP, hasClass } from '@element-plus/utils'
import { EVENT_CODE } from '@element-plus/constants'
import { makeMountFunc } from '@element-plus/test-utils/make-mount'
@ -195,6 +195,10 @@ const PLACEHOLDER_CLASS_NAME = 'el-select__placeholder'
const DEFAULT_PLACEHOLDER = 'Select'
describe('Select', () => {
beforeAll(() => {
HTMLCanvasElement.prototype.getContext = vi.fn(() => null)
})
afterEach(() => {
document.body.innerHTML = ''
})

View File

@ -8,6 +8,7 @@ import {
buildProps,
definePropType,
iconPropType,
isBoolean,
isNumber,
} from '@element-plus/utils'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
@ -180,7 +181,7 @@ export const SelectProps = buildProps({
type: String,
},
/**
* @description whether select dropdown is teleported to the body
* @description whether select dropdown is teleported, if `true` it will be teleported to where `append-to` sets
*/
teleported: useTooltipContentProps.teleported,
/**
@ -285,6 +286,18 @@ export const SelectProps = buildProps({
* @description which element the select dropdown appends to
*/
appendTo: String,
/**
* @description if it is `true`, the width of the dropdown panel is the same as the input box.
* if it is `false`, the width is automatically calculated based on the value of `label`,
* or it can be set to a number to make it a fixed width
*/
fitInputWidth: {
type: [Boolean, Number],
default: true,
validator(val) {
return isBoolean(val) || isNumber(val)
},
},
...useEmptyValuesProps,
...useAriaProps(['ariaLabel']),
} as const)

View File

@ -20,7 +20,6 @@ export type SelectStates = {
hoveringIndex: number
inputHovering: boolean
selectionWidth: number
calculatorWidth: number
collapseItemWidth: number
previousQuery: string | null
previousValue: unknown

View File

@ -230,7 +230,11 @@
</el-icon>
<el-icon
v-if="validateState && validateIcon && needStatusIcon"
:class="[nsInput.e('icon'), nsInput.e('validateIcon')]"
:class="[
nsInput.e('icon'),
nsInput.e('validateIcon'),
nsInput.is('loading', validateState === 'validating'),
]"
>
<component :is="validateIcon" />
</el-icon>
@ -283,6 +287,7 @@ import { ClickOutside } from '@element-plus/directives'
import ElTooltip from '@element-plus/components/tooltip'
import ElTag from '@element-plus/components/tag'
import ElIcon from '@element-plus/components/icon'
import { useCalcInputWidth } from '@element-plus/hooks'
import ElSelectMenu from './select-dropdown'
import useSelect from './useSelect'
import { SelectProps, selectEmits } from './defaults'
@ -318,6 +323,8 @@ export default defineComponent({
}),
emit
)
const { calculatorRef, inputStyle } = useCalcInputWidth()
provide(selectV2InjectionKey, {
props: reactive({
...toRefs(props),
@ -343,6 +350,8 @@ export default defineComponent({
...API,
modelValue,
selectedLabel,
calculatorRef,
inputStyle,
}
},
})

View File

@ -20,6 +20,7 @@ import {
escapeStringRegexp,
isArray,
isFunction,
isNumber,
isObject,
} from '@element-plus/utils'
import {
@ -49,10 +50,90 @@ import type { ISelectV2Props } from './token'
import type { SelectEmitFn } from './defaults'
import type { TooltipInstance } from '@element-plus/components/tooltip'
import type { SelectDropdownInstance } from './select-dropdown'
import type { Component, ComputedRef, Ref, WritableComputedRef } from 'vue'
const MINIMUM_INPUT_WIDTH = 11
type useSelectReturnType = (
props: ISelectV2Props,
emit: SelectEmitFn
) => {
inputId: Ref<string | undefined>
collapseTagSize: ComputedRef<'default' | 'small'>
currentPlaceholder: ComputedRef<string>
expanded: Ref<boolean>
emptyText: ComputedRef<string | false | null>
popupHeight: ComputedRef<number>
debounce: ComputedRef<0 | 300>
allOptions: Ref<OptionType[]>
filteredOptions: Ref<OptionType[]>
iconComponent: ComputedRef<any>
iconReverse: ComputedRef<any>
tagStyle: ComputedRef<{ maxWidth: string }>
collapseTagStyle: ComputedRef<{ maxWidth: string }>
popperSize: Ref<number>
dropdownMenuVisible: WritableComputedRef<boolean>
hasModelValue: ComputedRef<boolean>
shouldShowPlaceholder: ComputedRef<boolean>
selectDisabled: ComputedRef<boolean | undefined>
selectSize: ComputedRef<string>
needStatusIcon: ComputedRef<boolean>
showClearBtn: ComputedRef<boolean>
states: SelectStates
isFocused: Ref<boolean>
nsSelect: ReturnType<typeof useNamespace>
nsInput: ReturnType<typeof useNamespace>
inputRef: Ref<HTMLElement | undefined>
menuRef: Ref<SelectDropdownInstance | undefined>
tagMenuRef: Ref<HTMLElement | undefined>
tooltipRef: Ref<TooltipInstance | undefined>
tagTooltipRef: Ref<TooltipInstance | undefined>
selectRef: Ref<HTMLElement | undefined>
wrapperRef: Ref<HTMLElement | undefined>
selectionRef: Ref<HTMLElement | undefined>
prefixRef: Ref<HTMLElement | undefined>
suffixRef: Ref<HTMLElement | undefined>
collapseItemRef: Ref<HTMLElement | undefined>
popperRef: ComputedRef<HTMLElement | undefined>
validateState: ComputedRef<string>
validateIcon: ComputedRef<Component | undefined>
showTagList: ComputedRef<Option[]>
collapseTagList: ComputedRef<Option[]>
debouncedOnInputChange: () => void
deleteTag: (event: MouseEvent, option: Option) => void
getLabel: (option: Option) => string
getValue: (option: Option) => unknown
getDisabled: (option: Option) => boolean
getValueKey: (item: unknown) => any
handleClear: () => void
handleClickOutside: (event: Event) => void
handleDel: (e: KeyboardEvent) => void
handleEsc: () => void
focus: () => void
blur: () => void
handleMenuEnter: () => void
handleResize: () => void
resetSelectionWidth: () => void
updateTooltip: () => void
updateTagTooltip: () => void
updateOptions: () => void
toggleMenu: () => void
scrollTo: (index: number) => void
onInput: (event: Event) => void
onKeyboardNavigate: (
direction: 'forward' | 'backward',
hoveringIndex?: number
) => void
onKeyboardSelect: () => void
onSelect: (option: Option) => void
onHover: (idx?: number) => void
handleCompositionStart: (event: CompositionEvent) => void
handleCompositionEnd: (event: CompositionEvent) => void
handleCompositionUpdate: (event: CompositionEvent) => void
}
const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
const useSelect: useSelectReturnType = (
props: ISelectV2Props,
emit: SelectEmitFn
) => {
// inject
const { t } = useLocale()
const nsSelect = useNamespace('select')
@ -72,7 +153,6 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
hoveringIndex: -1,
inputHovering: false,
selectionWidth: 0,
calculatorWidth: 0,
collapseItemWidth: 0,
previousQuery: null,
previousValue: undefined,
@ -90,7 +170,6 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
const tooltipRef = ref<TooltipInstance>()
const tagTooltipRef = ref<TooltipInstance>()
const inputRef = ref<HTMLElement>()
const calculatorRef = ref<HTMLElement>()
const prefixRef = ref<HTMLElement>()
const suffixRef = ref<HTMLElement>()
const menuRef = ref<SelectDropdownInstance>()
@ -268,7 +347,40 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
)
const calculatePopperSize = () => {
popperSize.value = selectRef.value?.offsetWidth || 200
if (isNumber(props.fitInputWidth)) {
popperSize.value = props.fitInputWidth
return
}
const width = selectRef.value?.offsetWidth || 200
if (!props.fitInputWidth && allOptions.value.length > 0) {
nextTick(() => {
popperSize.value = Math.max(width, calculateLabelMaxWidth())
})
} else {
popperSize.value = width
}
}
// TODO Caching implementation
// 1. There is no need to calculate options that have already been calculated
// 2. Repeatedly expand and close when persistent is set to false, no need for repeated calculations
const calculateLabelMaxWidth = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const selector = nsSelect.be('dropdown', 'item')
const dom = menuRef.value?.listRef?.innerRef || document
const dropdownItemEl = dom.querySelector(`.${selector}`)
if (dropdownItemEl === null || ctx === null) return 0
const style = getComputedStyle(dropdownItemEl)
const padding =
Number.parseFloat(style.paddingLeft) +
Number.parseFloat(style.paddingRight)
ctx.font = style.font
const maxWidth = filteredOptions.value.reduce((max, option) => {
const metrics = ctx.measureText(getLabel(option))
return Math.max(metrics.width, max)
}, 0)
return maxWidth + padding
}
const getGapWidth = () => {
@ -291,10 +403,6 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
return { maxWidth: `${states.selectionWidth}px` }
})
const inputStyle = computed(() => ({
width: `${Math.max(states.calculatorWidth, MINIMUM_INPUT_WIDTH)}px`,
}))
const shouldShowPlaceholder = computed(() => {
if (isArray(props.modelValue)) {
return props.modelValue.length === 0 && !states.inputValue
@ -484,10 +592,6 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
states.selectionWidth = selectionRef.value!.getBoundingClientRect().width
}
const resetCalculatorWidth = () => {
states.calculatorWidth = calculatorRef.value!.getBoundingClientRect().width
}
const resetCollapseItemWidth = () => {
states.collapseItemWidth =
collapseItemRef.value!.getBoundingClientRect().width
@ -795,12 +899,22 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
calculatePopperSize()
}
watch(
() => props.fitInputWidth,
() => {
calculatePopperSize()
}
)
// in order to track these individually, we need to turn them into refs instead of watching the entire
// reactive object which could cause perf penalty when unnecessary field gets changed the watch method will
// be invoked.
watch(expanded, (val) => {
if (val) {
if (!props.persistent) {
calculatePopperSize()
}
handleQueryChange('')
} else {
states.inputValue = ''
@ -852,6 +966,7 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
watch(
() => filteredOptions.value,
() => {
calculatePopperSize()
return menuRef.value && nextTick(menuRef.value.resetScrollTop)
}
)
@ -889,7 +1004,6 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
})
useResizeObserver(selectRef, handleResize)
useResizeObserver(selectionRef, resetSelectionWidth)
useResizeObserver(calculatorRef, resetCalculatorWidth)
useResizeObserver(menuRef, updateTooltip)
useResizeObserver(wrapperRef, updateTooltip)
useResizeObserver(tagMenuRef, updateTagTooltip)
@ -910,7 +1024,6 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
iconReverse,
tagStyle,
collapseTagStyle,
inputStyle,
popperSize,
dropdownMenuVisible,
hasModelValue,
@ -925,7 +1038,6 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
nsInput,
// refs items exports
calculatorRef,
inputRef,
menuRef,
tagMenuRef,
@ -961,7 +1073,6 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
handleMenuEnter,
handleResize,
resetSelectionWidth,
resetCalculatorWidth,
updateTooltip,
updateTagTooltip,
updateOptions,

View File

@ -161,7 +161,7 @@ export const SelectProps = buildProps({
default: 1,
},
/**
* @description whether select dropdown is teleported to the body
* @description whether select dropdown is teleported, if `true` it will be teleported to where `append-to` sets
*/
teleported: useTooltipContentProps.teleported,
/**

View File

@ -230,7 +230,11 @@
</el-icon>
<el-icon
v-if="validateState && validateIcon && needStatusIcon"
:class="[nsInput.e('icon'), nsInput.e('validateIcon')]"
:class="[
nsInput.e('icon'),
nsInput.e('validateIcon'),
nsInput.is('loading', validateState === 'validating'),
]"
>
<component :is="validateIcon" />
</el-icon>
@ -303,6 +307,7 @@ import ElTag from '@element-plus/components/tag'
import ElIcon from '@element-plus/components/icon'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { isArray } from '@element-plus/utils'
import { useCalcInputWidth } from '@element-plus/hooks'
import ElOption from './option.vue'
import ElSelectMenu from './select-dropdown.vue'
import { useSelect } from './useSelect'
@ -356,6 +361,7 @@ export default defineComponent({
})
const API = useSelect(_props, emit)
const { calculatorRef, inputStyle } = useCalcInputWidth()
provide(
selectKey,
@ -375,13 +381,15 @@ export default defineComponent({
if (!props.multiple) {
return API.states.selectedLabel
}
return API.states.selected.map((i) => i.currentLabel as string)
return API.states.selected.map((i: any) => i.currentLabel as string)
})
return {
...API,
modelValue,
selectedLabel,
calculatorRef,
inputStyle,
}
},
})

View File

@ -51,9 +51,88 @@ import {
import type ElTooltip from '@element-plus/components/tooltip'
import type { ISelectProps, SelectOptionProxy } from './token'
const MINIMUM_INPUT_WIDTH = 11
type useSelectType = (
props: ISelectProps,
emit: any
) => {
inputId: Ref<string | undefined>
contentId: Ref<string | undefined>
nsSelect: Ref<string | undefined>
nsInput: Ref<string | undefined>
states: Reactive<Record<string, any>>
isFocused: Ref<boolean>
expanded: Ref<boolean>
optionsArray: ComputedRef<any[]>
hoverOption: Ref<unknown>
selectSize: ComputedRef<'' | 'default' | 'small' | 'large'>
filteredOptionsCount: ComputedRef<number>
resetCalculatorWidth: () => void
updateTooltip: () => void
updateTagTooltip: () => void
debouncedOnInputChange: DebouncedFunc<() => void>
onInput: (event: Event) => void
deletePrevTag: (event: Event) => void
deleteTag: (event: Event, tag: any) => void
deleteSelected: (event: Event) => void
handleOptionSelect: (option: any) => void
scrollToOption: (option: any) => void
hasModelValue: ComputedRef<boolean>
shouldShowPlaceholder: ComputedRef<boolean>
currentPlaceholder: ComputedRef<string>
mouseEnterEventName: Ref<string | null>
needStatusIcon: ComputedRef<boolean>
showClose: ComputedRef<boolean>
iconComponent: ComputedRef<string>
iconReverse: ComputedRef<boolean>
validateState: ComputedRef<string>
export const useSelect = (props: ISelectProps, emit) => {
validateIcon: ComputedRef<unknown>
showNewOption: ComputedRef<boolean>
updateOptions: () => void
collapseTagSize: ComputedRef<'default' | 'small'>
setSelected: () => void
selectDisabled: ComputedRef<boolean>
emptyText: ComputedRef<string | null>
handleCompositionStart: (e: Event) => void
handleCompositionUpdate: (e: Event) => void
handleCompositionEnd: (e: Event) => void
onOptionCreate: (vm: SelectOptionProxy) => void
onOptionDestroy: (key: any, vm: SelectOptionProxy) => void
handleMenuEnter: () => void
focus: () => void
blur: () => void
handleClearClick: (event: Event) => void
handleClickOutside: (event: Event) => void
handleEsc: () => void
toggleMenu: () => void
selectOption: () => void
getValueKey: (item: any) => any
navigateOptions: (direction: string) => void
dropdownMenuVisible: WritableComputedRef<boolean>
showTagList: ComputedRef<unknown[]>
collapseTagList: ComputedRef<unknown[]>
tagStyle: ComputedRef<unknown>
collapseTagStyle: ComputedRef<unknown>
inputStyle: ComputedRef<unknown>
popperRef: ComputedRef<unknown>
inputRef: Ref<HTMLInputElement | null>
tooltipRef: Ref<InstanceType<typeof ElTooltip> | null>
tagTooltipRef: Ref<InstanceType<typeof ElTooltip> | null>
calculatorRef: Ref<HTMLElement>
prefixRef: Ref<HTMLElement>
suffixRef: Ref<HTMLElement>
selectRef: Ref<HTMLElement>
wrapperRef: Ref<HTMLElement>
selectionRef: Ref<HTMLElement>
scrollbarRef: Ref<{
handleScroll: () => void
} | null>
menuRef: Ref<HTMLElement>
tagMenuRef: Ref<HTMLElement>
collapseItemRef: Ref<HTMLElement>
}
export const useSelect: useSelectType = (props: ISelectProps, emit) => {
const { t } = useLocale()
const contentId = useId()
const nsSelect = useNamespace('select')
@ -66,7 +145,6 @@ export const useSelect = (props: ISelectProps, emit) => {
optionValues: [] as any[], // sorted value of options
selected: [] as any[],
selectionWidth: 0,
calculatorWidth: 0,
collapseItemWidth: 0,
selectedLabel: '',
hoveringIndex: -1,
@ -82,7 +160,6 @@ export const useSelect = (props: ISelectProps, emit) => {
const tooltipRef = ref<InstanceType<typeof ElTooltip> | null>(null)
const tagTooltipRef = ref<InstanceType<typeof ElTooltip> | null>(null)
const inputRef = ref<HTMLInputElement | null>(null)
const calculatorRef = ref<HTMLElement>(null)
const prefixRef = ref<HTMLElement>(null)
const suffixRef = ref<HTMLElement>(null)
const menuRef = ref<HTMLElement>(null)
@ -167,12 +244,14 @@ export const useSelect = (props: ISelectProps, emit) => {
const debounce = computed(() => (props.remote ? 300 : 0))
const isRemoteSearchEmpty = computed(
() => props.remote && !states.inputValue && states.options.size === 0
)
const emptyText = computed(() => {
if (props.loading) {
return props.loadingText || t('el.select.loading')
} else {
if (props.remote && !states.inputValue && states.options.size === 0)
return false
if (
props.filterable &&
states.inputValue &&
@ -241,7 +320,7 @@ export const useSelect = (props: ISelectProps, emit) => {
const dropdownMenuVisible = computed({
get() {
return expanded.value && emptyText.value !== false
return expanded.value && !isRemoteSearchEmpty.value
},
set(val: boolean) {
expanded.value = val
@ -457,10 +536,6 @@ export const useSelect = (props: ISelectProps, emit) => {
states.selectionWidth = selectionRef.value.getBoundingClientRect().width
}
const resetCalculatorWidth = () => {
states.calculatorWidth = calculatorRef.value.getBoundingClientRect().width
}
const resetCollapseItemWidth = () => {
states.collapseItemWidth =
collapseItemRef.value.getBoundingClientRect().width
@ -775,12 +850,7 @@ export const useSelect = (props: ISelectProps, emit) => {
return { maxWidth: `${states.selectionWidth}px` }
})
const inputStyle = computed(() => ({
width: `${Math.max(states.calculatorWidth, MINIMUM_INPUT_WIDTH)}px`,
}))
useResizeObserver(selectionRef, resetSelectionWidth)
useResizeObserver(calculatorRef, resetCalculatorWidth)
useResizeObserver(menuRef, updateTooltip)
useResizeObserver(wrapperRef, updateTooltip)
useResizeObserver(tagMenuRef, updateTagTooltip)
@ -802,7 +872,6 @@ export const useSelect = (props: ISelectProps, emit) => {
hoverOption,
selectSize,
filteredOptionsCount,
resetCalculatorWidth,
updateTooltip,
updateTagTooltip,
debouncedOnInputChange,
@ -850,14 +919,12 @@ export const useSelect = (props: ISelectProps, emit) => {
// computed style
tagStyle,
collapseTagStyle,
inputStyle,
// DOM ref
popperRef,
inputRef,
tooltipRef,
tagTooltipRef,
calculatorRef,
prefixRef,
suffixRef,
selectRef,

View File

@ -50,7 +50,27 @@ const useTooltip = (
}
}
export const useSliderButton = (
type HTMLType = HTMLDivElement | undefined
type useSliderButtonType = (
props: SliderButtonProps,
initData: SliderButtonInitData,
emit: SetupContext<SliderButtonEmits>['emit']
) => {
disabled: Ref<boolean>
button: Ref<HTMLType>
tooltip: Ref<TooltipInstance | undefined>
tooltipVisible: Ref<boolean>
showTooltip: Ref<SliderProps['showTooltip']>
wrapperStyle: ComputedRef<CSSProperties>
formatValue: ComputedRef<number | string>
handleMouseEnter: () => void
handleMouseLeave: () => void
onButtonDown: (event: MouseEvent | TouchEvent) => void
onKeyDown: (event: KeyboardEvent) => void
setPosition: (newPosition: number) => Promise<void>
}
export const useSliderButton: useSliderButtonType = (
props: SliderButtonProps,
initData: SliderButtonInitData,
emit: SetupContext<SliderButtonEmits>['emit']

View File

@ -1822,7 +1822,7 @@ describe('Table.vue', () => {
ElTableColumn,
},
template: `
<el-table :data="testData" :tree-props="treeProps" @selection-change="change">
<el-table :data="testData" :tree-props="treeProps" row-key="id" @selection-change="change">
<el-table-column type="selection" />
<el-table-column prop="name" label="name" />
<el-table-column prop="release" label="release" />

View File

@ -50,8 +50,7 @@
:class="[
ns.e('list-item'),
{
[ns.is('active')]:
filterValue === undefined || filterValue === null,
[ns.is('active')]: isPropAbsent(filterValue),
},
]"
@click="handleSelect(null)"
@ -99,8 +98,9 @@ import { ClickOutside } from '@element-plus/directives'
import { useLocale, useNamespace } from '@element-plus/hooks'
import ElTooltip from '@element-plus/components/tooltip'
import ElScrollbar from '@element-plus/components/scrollbar'
import type { Placement } from '@element-plus/components/popper'
import { isPropAbsent } from '@element-plus/utils'
import type { Placement } from '@element-plus/components/popper'
import type { PropType, WritableComputedRef } from 'vue'
import type { TableColumnCtx } from './table-column/defaults'
import type { TableHeader } from './table-header'
@ -161,7 +161,7 @@ export default defineComponent({
get: () => (props.column?.filteredValue || [])[0],
set: (value: string) => {
if (filteredValue.value) {
if (typeof value !== 'undefined' && value !== null) {
if (!isPropAbsent(value)) {
filteredValue.value.splice(0, 1, value)
} else {
filteredValue.value.splice(0, 1)
@ -212,7 +212,7 @@ export default defineComponent({
}
const handleSelect = (_filterValue?: string) => {
filterValue.value = _filterValue
if (typeof _filterValue !== 'undefined' && _filterValue !== null) {
if (!isPropAbsent(_filterValue)) {
confirmFilter(filteredValue.value)
} else {
confirmFilter([])
@ -253,6 +253,7 @@ export default defineComponent({
handleConfirm,
handleReset,
handleSelect,
isPropAbsent,
isActive,
t,
ns,

View File

@ -1,10 +1,12 @@
// @ts-nocheck
import { h } from 'vue'
import { isUndefined } from '@element-plus/utils'
export function hColgroup(props) {
const isAuto = props.tableLayout === 'auto'
let columns = props.columns || []
if (isAuto) {
if (columns.every((column) => column.width === undefined)) {
if (columns.every(({ width }) => isUndefined(width))) {
columns = []
}
}

View File

@ -1,5 +1,6 @@
// @ts-nocheck
import { getCurrentInstance, ref, unref } from 'vue'
import { isNull } from 'lodash-unified'
import { getRowIdentity } from '../util'
import type { Ref } from 'vue'
@ -59,7 +60,7 @@ function useCurrent<T>(watcherData: WatcherPropsData<T>) {
} else {
currentRow.value = null
}
if (currentRow.value === null) {
if (isNull(currentRow.value)) {
instance.emit('current-change', null, oldCurrentRow)
}
} else if (_currentRowKey.value) {

View File

@ -1,6 +1,7 @@
// @ts-nocheck
import { watch } from 'vue'
import { debounce } from 'lodash-unified'
import { isObject } from '@element-plus/utils'
import useStore from '.'
import type { Store } from '.'
@ -57,7 +58,7 @@ function proxyTableProps<T>(store: Store<T>, props: TableProps<T>) {
function handleValue<T>(value, propsKey: string, store: Store<T>) {
let newVal = value
let storeKey = InitialStateMap[propsKey]
if (typeof InitialStateMap[propsKey] === 'object') {
if (isObject(InitialStateMap[propsKey])) {
storeKey = storeKey.key
newVal = newVal || InitialStateMap[propsKey].default
}

View File

@ -1,5 +1,6 @@
// @ts-nocheck
import { getCurrentInstance, nextTick, unref } from 'vue'
import { isNull } from 'lodash-unified'
import { useNamespace } from '@element-plus/hooks'
import useWatcher from './watcher'
@ -169,7 +170,7 @@ function useStore<T>() {
const columnValue = unref(sortingColumn),
propValue = unref(sortProp),
orderValue = unref(sortOrder)
if (orderValue === null) {
if (isNull(orderValue)) {
states.sortingColumn.value = null
states.sortProp.value = null
}

View File

@ -1,6 +1,5 @@
// @ts-nocheck
import { getCurrentInstance, ref, toRefs, unref, watch } from 'vue'
import { isEqual } from 'lodash-unified'
import { computed, getCurrentInstance, ref, toRefs, unref, watch } from 'vue'
import { hasOwn, isArray, isString, isUndefined } from '@element-plus/utils'
import {
getColumnById,
@ -77,6 +76,10 @@ function useWatcher<T>() {
const sortOrder = ref(null)
const hoverRow = ref(null)
const selectedMap = computed(() => {
return rowKey.value ? getKeysMap(selection.value, rowKey.value) : undefined
})
watch(
data,
() => {
@ -184,8 +187,12 @@ function useWatcher<T>() {
}
// 选择
const isSelected = (row) => {
return selection.value.some((item) => isEqual(item, row))
const isSelected = (row: DefaultRow) => {
if (selectedMap.value) {
return !!selectedMap.value[getRowIdentity(row, rowKey.value)]
} else {
return selection.value.includes(row)
}
}
const clearSelection = () => {
@ -201,11 +208,10 @@ function useWatcher<T>() {
let deleted
if (rowKey.value) {
deleted = []
const selectedMap = getKeysMap(selection.value, rowKey.value)
const dataMap = getKeysMap(data.value, rowKey.value)
for (const key in selectedMap) {
if (hasOwn(selectedMap, key) && !dataMap[key]) {
deleted.push(selectedMap[key].row)
for (const key in selectedMap.value) {
if (hasOwn(selectedMap.value, key) && !dataMap[key]) {
deleted.push(selectedMap.value[key].row)
}
}
} else {
@ -295,10 +301,9 @@ function useWatcher<T>() {
}
const updateSelectionByRowKey = () => {
const selectedMap = getKeysMap(selection.value, rowKey.value)
data.value.forEach((row) => {
const rowId = getRowIdentity(row, rowKey.value)
const rowInfo = selectedMap[rowId]
const rowInfo = selectedMap.value![rowId]
if (rowInfo) {
selection.value[rowInfo.index] = row
}
@ -313,20 +318,9 @@ function useWatcher<T>() {
}
const { childrenColumnName } = instance.store.states
const selectedMap = rowKey.value
? getKeysMap(selection.value, rowKey.value)
: undefined
let rowIndex = 0
let selectedCount = 0
const isSelected = (row: DefaultRow) => {
if (selectedMap) {
return !!selectedMap[getRowIdentity(row, rowKey.value)]
} else {
return selection.value.includes(row)
}
}
const checkSelectedStatus = (data: DefaultRow[]) => {
for (const row of data) {
const isRowSelectable =

View File

@ -2,7 +2,12 @@
import { h, inject, ref } from 'vue'
import { debounce } from 'lodash-unified'
import { addClass, hasClass, removeClass } from '@element-plus/utils'
import { createTablePopper, getCell, getColumnByCell } from '../util'
import {
createTablePopper,
getCell,
getColumnByCell,
removePopper,
} from '../util'
import { TABLE_INJECTION_KEY } from '../tokens'
import type { TableColumnCtx } from '../table-column/defaults'
import type { TableBodyProps } from './defaults'
@ -156,6 +161,8 @@ function useEvents<T>(props: Partial<TableBodyProps<T>>) {
cell,
table
)
} else if (removePopper?.trigger === cell) {
removePopper?.()
}
}
const handleCellMouseLeave = (event) => {

View File

@ -2,7 +2,7 @@
import { computed, h, inject } from 'vue'
import { merge } from 'lodash-unified'
import { useNamespace } from '@element-plus/hooks'
import { isBoolean } from '@element-plus/utils'
import { isBoolean, isPropAbsent } from '@element-plus/utils'
import { getRowIdentity } from '../util'
import { TABLE_INJECTION_KEY } from '../tokens'
import useEvents from './events-helper'
@ -227,7 +227,7 @@ function useRender<T>(props: Partial<TableBodyProps<T>>) {
loading: false,
}
const childKey = getRowIdentity(node, rowKey.value)
if (childKey === undefined || childKey === null) {
if (isPropAbsent(childKey)) {
throw new Error('For nested data item, row-key is required.')
}
cur = { ...treeData.value[childKey] }

View File

@ -1,7 +1,7 @@
// @ts-nocheck
import { inject } from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { isArray, isFunction, isString } from '@element-plus/utils'
import { isArray, isFunction, isObject, isString } from '@element-plus/utils'
import {
ensurePosition,
getFixedColumnOffset,
@ -129,7 +129,7 @@ function useStyles<T>(props: Partial<TableBodyProps<T>>) {
if (isArray(result)) {
rowspan = result[0]
colspan = result[1]
} else if (typeof result === 'object') {
} else if (isObject(result)) {
rowspan = result.rowspan
colspan = result.colspan
}

View File

@ -9,7 +9,7 @@ import {
unref,
watchEffect,
} from 'vue'
import { debugWarn, isArray } from '@element-plus/utils'
import { debugWarn, isArray, isUndefined } from '@element-plus/utils'
import { useNamespace } from '@element-plus/hooks'
import {
cellForced,
@ -73,7 +73,7 @@ function useRender<T>(
column.minWidth = 80
}
column.realWidth = Number(
column.width === undefined ? column.minWidth : column.width
isUndefined(column.width) ? column.minWidth : column.width
)
return column
}
@ -83,7 +83,7 @@ function useRender<T>(
const source = cellForced[type] || {}
Object.keys(source).forEach((prop) => {
const value = source[prop]
if (prop !== 'className' && value !== undefined) {
if (prop !== 'className' && !isUndefined(value)) {
column[prop] = value
}
})

View File

@ -1,5 +1,6 @@
// @ts-nocheck
import { getCurrentInstance, inject, ref } from 'vue'
import { isNull } from 'lodash-unified'
import {
addClass,
hasClass,
@ -128,7 +129,8 @@ function useEvent<T>(props: TableHeaderProps<T>, emit) {
const bodyStyle = document.body.style
const isLastTh = target.parentNode?.lastElementChild === target
if (rect.width > 12 && rect.right - event.pageX < 8 && !isLastTh) {
const allowDarg = props.allowDragLastColumn || !isLastTh
if (rect.width > 12 && rect.right - event.pageX < 8 && allowDarg) {
bodyStyle.cursor = 'col-resize'
if (hasClass(target, 'is-sortable')) {
target.style.cursor = 'col-resize'
@ -189,7 +191,7 @@ function useEvent<T>(props: TableHeaderProps<T>, emit) {
if (
sortingColumn !== column ||
(sortingColumn === column && sortingColumn.order === null)
(sortingColumn === column && isNull(sortingColumn.order))
) {
if (sortingColumn) {
sortingColumn.order = null

View File

@ -33,6 +33,7 @@ export interface TableHeaderProps<T> {
store: Store<T>
border: boolean
defaultSort: Sort
allowDragLastColumn: boolean
}
export default defineComponent({
@ -62,6 +63,9 @@ export default defineComponent({
appendFilterPanelTo: {
type: String,
},
allowDragLastColumn: {
type: Boolean,
},
},
setup(props, { emit }) {
const instance = getCurrentInstance() as TableHeader

View File

@ -1,9 +1,10 @@
// @ts-nocheck
import { isRef, nextTick, ref } from 'vue'
import { isNull } from 'lodash-unified'
import { hasOwn, isClient, isNumber, isString } from '@element-plus/utils'
import { parseHeight } from './util'
import type { Ref } from 'vue'
import type { Ref } from 'vue'
import type { TableColumnCtx } from './table-column/defaults'
import type { TableHeader } from './table-header'
import type { Table } from './table/defaults'
@ -64,7 +65,7 @@ class TableLayout<T> {
* When the height is not initialized, it is null.
* After the table is initialized, when the height is not configured, the height is 0.
*/
if (height === null) return false
if (isNull(height)) return false
const scrollBarRef = this.table.refs.scrollBarRef
if (this.table.vnode.el && scrollBarRef?.wrapRef) {
let scrollY = true

View File

@ -54,6 +54,7 @@
:default-sort="defaultSort"
:store="store"
:append-filter-panel-to="appendFilterPanelTo"
:allow-drag-last-column="allowDragLastColumn"
@set-drag-visible="setDragVisible"
/>
</table>
@ -401,6 +402,10 @@ export default defineComponent({
* @description set vertical scroll position
*/
setScrollTop,
/**
* @description whether to allow drag the last column
*/
allowDragLastColumn: props.allowDragLastColumn,
}
},
})

View File

@ -395,6 +395,13 @@ export default {
type: [Number, String],
default: undefined,
},
/**
* @description whether to allow drag the last column
*/
allowDragLastColumn: {
type: Boolean,
default: true,
},
}
export type {
SummaryMethod,

View File

@ -1,6 +1,6 @@
// @ts-nocheck
import { createVNode, render } from 'vue'
import { flatMap, get, merge } from 'lodash-unified'
import { flatMap, get, isNull, merge } from 'lodash-unified'
import {
hasOwn,
isArray,
@ -9,6 +9,7 @@ import {
isNumber,
isObject,
isString,
isUndefined,
throwError,
} from '@element-plus/utils'
import ElTooltip, {
@ -206,7 +207,7 @@ export function mergeOptions<T, K>(defaults: T, config: K): T & K {
for (key in config) {
if (hasOwn(config as unknown as Record<string, any>, key)) {
const value = config[key]
if (typeof value !== 'undefined') {
if (!isUndefined(value)) {
options[key] = value
}
}
@ -216,7 +217,7 @@ export function mergeOptions<T, K>(defaults: T, config: K): T & K {
export function parseWidth(width: number | string): number | string {
if (width === '') return width
if (width !== undefined) {
if (!isUndefined(width)) {
width = Number.parseInt(width as string, 10)
if (Number.isNaN(width)) {
width = ''
@ -227,7 +228,7 @@ export function parseWidth(width: number | string): number | string {
export function parseMinWidth(minWidth: number | string): number | string {
if (minWidth === '') return minWidth
if (minWidth !== undefined) {
if (!isUndefined(minWidth)) {
minWidth = parseWidth(minWidth)
if (Number.isNaN(minWidth)) {
minWidth = 80
@ -525,7 +526,7 @@ export const getFixedColumnsClass = <T>(
function getOffset<T>(offset: number, column: TableColumnCtx<T>) {
return (
offset +
(column.realWidth === null || Number.isNaN(column.realWidth)
(isNull(column.realWidth) || Number.isNaN(column.realWidth)
? Number(column.width)
: column.realWidth)
)

View File

@ -49,6 +49,7 @@ import { useNamespace, usePopperContainerId } from '@element-plus/hooks'
import { composeEventHandlers } from '@element-plus/utils'
import { ElPopperContent } from '@element-plus/components/popper'
import ElTeleport from '@element-plus/components/teleport'
import { tryFocus } from '@element-plus/components/focus-trap'
import { TOOLTIP_INJECTION_KEY } from './constants'
import { useTooltipContentProps } from './content'
import type { PopperContentInstance } from '@element-plus/components/popper'
@ -111,6 +112,7 @@ const ariaHidden = ref(true)
const onTransitionLeave = () => {
onHide()
isFocusInsideContent() && tryFocus(document.body)
ariaHidden.value = true
}
@ -161,6 +163,14 @@ const onBlur = () => {
}
}
const isFocusInsideContent = (event?: FocusEvent) => {
const popperContent: HTMLElement | undefined =
contentRef.value?.popperContentRef
const activeElement = (event?.relatedTarget as Node) || document.activeElement
return popperContent?.contains(activeElement)
}
watch(
() => unref(open),
(val) => {
@ -187,5 +197,9 @@ defineExpose({
* @description el-popper-content component instance
*/
contentRef,
/**
* @description validate current focus event is trigger inside el-popper-content
*/
isFocusInsideContent,
})
</script>

View File

@ -72,6 +72,7 @@ import ElTooltipTrigger from './trigger.vue'
import ElTooltipContent from './content.vue'
import type { TooltipContentInstance } from './content'
import type { PopperInstance } from '@element-plus/components/popper'
import type { Ref } from 'vue'
defineOptions({
name: 'ElTooltip',
@ -155,16 +156,20 @@ watch(
)
const isFocusInsideContent = (event?: FocusEvent) => {
const popperContent: HTMLElement | undefined =
contentRef.value?.contentRef?.popperContentRef
const activeElement = (event?.relatedTarget as Node) || document.activeElement
return popperContent && popperContent.contains(activeElement)
return contentRef.value?.isFocusInsideContent(event)
}
onDeactivated(() => open.value && hide())
defineExpose({
defineExpose<{
popperRef: Ref<PopperInstance | undefined>
contentRef: Ref<TooltipContentInstance | undefined>
isFocusInsideContent: (event?: FocusEvent) => boolean | undefined
updatePopper: () => void
onOpen: (event?: Event) => void
onClose: (event?: Event) => void
hide: () => void
}>({
/**
* @description el-popper component instance
*/

View File

@ -82,6 +82,7 @@ import type {
TransferDirection,
} from './transfer'
import type { TransferPanelInstance } from './transfer-panel'
import type { Ref } from 'vue'
defineOptions({
name: 'ElTransfer',
@ -164,7 +165,11 @@ const optionRender = computed(() => (option: TransferDataItem) => {
)
})
defineExpose({
defineExpose<{
clearQuery: (which: TransferDirection) => void
leftPanel: Ref<TransferPanelInstance | undefined>
rightPanel: Ref<TransferPanelInstance | undefined>
}>({
/** @description clear the filter keyword of a certain panel */
clearQuery,
/** @description left panel ref */

View File

@ -53,7 +53,7 @@ export const useHandlers = (
function removeFile(file: UploadFile) {
uploadFiles.value = uploadFiles.value.filter(
(uploadFile) => uploadFile !== file
(uploadFile) => uploadFile.uid !== file.uid
)
}

View File

@ -271,6 +271,7 @@ $color-picker-size: map.merge($common-component-size, $color-picker-size);
}
@include when(disabled) {
pointer-events: none;
.#{$namespace}-color-picker__trigger {
cursor: not-allowed;
}

View File

@ -924,6 +924,7 @@ $input-tag: () !default;
$input-tag: map.merge(
(
'border-color-hover': getCssVar('border-color-hover'),
'placeholder-color': getCssVar('text-color-placeholder'),
'disabled-color': getCssVar('disabled-text-color'),
'disabled-border': getCssVar('disabled-border-color'),
'font-size': getCssVar('font-size-base'),

View File

@ -189,6 +189,7 @@
border-color: map.get($input-disabled, 'border');
color: map.get($input-disabled, 'text-color');
cursor: not-allowed;
pointer-events: none;
&:hover,
&:focus {

View File

@ -103,6 +103,14 @@
font-size: map.get($input-font-size, $size);
}
@include when(controls-right) {
.#{$namespace}-input--#{$size} {
.#{$namespace}-input__wrapper {
padding-right: #{map.get($input-height, $size) + 7};
}
}
}
.#{$namespace}-input--#{$size} {
.#{$namespace}-input__wrapper {
padding-left: #{map.get($input-height, $size) + 7};

View File

@ -58,6 +58,7 @@
@include when(disabled) {
cursor: not-allowed;
pointer-events: none;
background-color: getCssVar('fill-color', 'light');
@include mixed-input-border(#{getCssVar('input-tag-disabled-border')});
@ -169,6 +170,10 @@
appearance: none;
width: 100%;
background-color: transparent;
&::placeholder {
color: getCssVar('input-tag-placeholder-color');
}
}
@include e(input-calculator) {

View File

@ -199,7 +199,7 @@
}
}
@include e(inner) {
& {
// use map.get as default value for date picker range
@include set-css-var-value(
'input-inner-height',
@ -210,7 +210,9 @@
) - $border-width * 2
)
);
}
@include e(inner) {
width: 100%;
flex-grow: 1;
-webkit-appearance: none;
@ -255,6 +257,7 @@
flex-shrink: 0;
flex-wrap: nowrap;
height: 100%;
line-height: getCssVar('input-inner-height');
text-align: center;
color: var(
#{getCssVarName('input-icon-color')},
@ -320,6 +323,7 @@
.#{$namespace}-input__wrapper {
background-color: map.get($input-disabled, 'fill');
cursor: not-allowed;
pointer-events: none;
@include mixed-input-border(map.get($input-disabled, 'border'));
}
@ -359,8 +363,7 @@
@include e(wrapper) {
padding: $border-width map.get($input-padding-horizontal, $size)-$border-width;
}
@include e(inner) {
& {
@include set-css-var-value(
'input-inner-height',
calc(

View File

@ -15,6 +15,10 @@
map.get($mention, 'shadow')
);
}
@include when(disabled) {
pointer-events: none;
}
}
@include b(mention-dropdown) {

View File

@ -34,6 +34,8 @@
}
@include e(group) {
flex: 1;
min-width: 0;
margin-left: getCssVar('notification-group-margin-left');
margin-right: getCssVar('notification-group-margin-right');
}
@ -58,6 +60,7 @@
}
& .#{$namespace}-notification__icon {
flex-shrink: 0;
height: getCssVar('notification-icon-size');
width: getCssVar('notification-icon-size');
font-size: getCssVar('notification-icon-size');

View File

@ -56,6 +56,7 @@
@include when(disabled) {
cursor: not-allowed;
pointer-events: none;
background-color: getCssVar('fill-color', 'light');
color: getCssVar('text-color', 'placeholder');
@ -158,6 +159,7 @@
@include e(placeholder) {
position: absolute;
z-index: -1;
display: block;
top: 50%;
transform: translateY(-50%);
@ -183,7 +185,7 @@
}
@include e(input-wrapper) {
max-width: 100%;
flex: 1;
@include when(hidden) {
// Out of the document flow
@ -201,7 +203,7 @@
font-family: inherit;
appearance: none;
height: map.get($select-item-height, 'default');
max-width: 100%;
width: 100%;
background-color: transparent;
@include when(disabled) {

109
pnpm-lock.yaml generated
View File

@ -256,8 +256,8 @@ importers:
version: 0.2.0
devDependencies:
'@crowdin/cli':
specifier: ^3.7.10
version: 3.14.0
specifier: ^4.5.0
version: 4.5.0
'@docsearch/react':
specifier: ^3.1.0
version: 3.1.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -1183,8 +1183,8 @@ packages:
resolution: {integrity: sha512-gwRLBLra/Dozj2OywopeuHj2ac26gjGkz2cZ+86cTJOdtWfiRRr4+e77ZDAGc6MDWxaWheI+mAV5TLWWRwqrFg==}
engines: {node: '>=v18'}
'@crowdin/cli@3.14.0':
resolution: {integrity: sha512-8MnJvGPkrLprrpLT+jPgBn7aKdv/8eyxLXMN929qYadDy7ZjZiB2CzlTvdGh4DUBoMOr/anoYWDhn9kxP0fn/Q==}
'@crowdin/cli@4.5.0':
resolution: {integrity: sha512-faj84fCXT8GcM0CwZ6KuXQ6ckwRHj2OKFFib048RobykRoMFJxXJnu6P/OBjWpcqFFbxWJB0QL0VkS42hqdMng==}
hasBin: true
'@ctrl/tinycolor@3.4.1':
@ -3635,6 +3635,10 @@ packages:
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
ci-info@3.3.2:
resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==}
@ -4792,8 +4796,9 @@ packages:
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
engines: {node: '>=14.14'}
fs-minipass@1.2.7:
resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==}
fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
fs-mkdirp-stream@1.0.0:
resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==}
@ -5972,8 +5977,13 @@ packages:
minimist@1.2.6:
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
minipass@2.9.0:
resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==}
minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
@ -5982,8 +5992,9 @@ packages:
minisearch@6.3.0:
resolution: {integrity: sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==}
minizlib@1.3.3:
resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==}
minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
@ -5995,8 +6006,9 @@ packages:
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
mkdist@1.5.3:
@ -6076,6 +6088,15 @@ packages:
encoding:
optional: true
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
@ -7530,9 +7551,9 @@ packages:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tar@4.4.19:
resolution: {integrity: sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==}
engines: {node: '>=4.5'}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
terser@5.36.0:
resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==}
@ -8389,6 +8410,10 @@ packages:
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
yauzl@3.2.0:
resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==}
engines: {node: '>=12'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@ -9190,13 +9215,13 @@ snapshots:
dependencies:
chalk: 4.1.2
'@crowdin/cli@3.14.0':
'@crowdin/cli@4.5.0':
dependencies:
command-exists-promise: 2.0.2
node-fetch: 2.6.7
node-fetch: 2.7.0
shelljs: 0.8.5
tar: 4.4.19
yauzl: 2.10.0
tar: 6.2.1
yauzl: 3.2.0
transitivePeerDependencies:
- encoding
@ -12141,6 +12166,8 @@ snapshots:
chownr@1.1.4: {}
chownr@2.0.0: {}
ci-info@3.3.2: {}
citty@0.1.6:
@ -13484,9 +13511,9 @@ snapshots:
jsonfile: 6.1.0
universalify: 2.0.1
fs-minipass@1.2.7:
fs-minipass@2.1.0:
dependencies:
minipass: 2.9.0
minipass: 3.3.6
fs-mkdirp-stream@1.0.0:
dependencies:
@ -14714,18 +14741,20 @@ snapshots:
minimist@1.2.6: {}
minipass@2.9.0:
minipass@3.3.6:
dependencies:
safe-buffer: 5.2.1
yallist: 3.1.1
yallist: 4.0.0
minipass@5.0.0: {}
minipass@7.1.2: {}
minisearch@6.3.0: {}
minizlib@1.3.3:
minizlib@2.1.2:
dependencies:
minipass: 2.9.0
minipass: 3.3.6
yallist: 4.0.0
mitt@3.0.1: {}
@ -14736,9 +14765,7 @@ snapshots:
mkdirp-classic@0.5.3: {}
mkdirp@0.5.6:
dependencies:
minimist: 1.2.6
mkdirp@1.0.4: {}
mkdist@1.5.3(sass@1.79.3)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4)):
dependencies:
@ -14822,6 +14849,10 @@ snapshots:
dependencies:
whatwg-url: 5.0.0
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
node-releases@2.0.14: {}
node-releases@2.0.18: {}
@ -16299,15 +16330,14 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.0
tar@4.4.19:
tar@6.2.1:
dependencies:
chownr: 1.1.4
fs-minipass: 1.2.7
minipass: 2.9.0
minizlib: 1.3.3
mkdirp: 0.5.6
safe-buffer: 5.2.1
yallist: 3.1.1
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 5.0.0
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
terser@5.36.0:
dependencies:
@ -17368,4 +17398,9 @@ snapshots:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
yauzl@3.2.0:
dependencies:
buffer-crc32: 0.2.13
pend: 1.2.0
yocto-queue@0.1.0: {}

View File

@ -14,11 +14,15 @@ const demoRoot = path.resolve(testRoot, 'cases')
describe('Cypress Button', () => {
let browser: Browser
beforeAll(async () => {
browser = await puppeteer.launch()
browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
})
})
afterAll(() => {
browser.close()
if (browser) {
browser.close()
}
})
describe('when initialized', () => {