mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-11-29 17:58:08 +08:00
Merge branch 'dev' into renovate/node-22.x
This commit is contained in:
commit
5b4b6d60e2
2
.github/workflows/issue-remove-inactive.yml
vendored
2
.github/workflows/issue-remove-inactive.yml
vendored
@ -16,4 +16,4 @@ jobs:
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
labels: 'inactive'
|
||||
labels: 'inactive, needs more info'
|
||||
|
@ -7,6 +7,7 @@ import VPApp, { NotFound, globals } from '../vitepress'
|
||||
import { define } from '../utils/types'
|
||||
import 'uno.css'
|
||||
import './style.css'
|
||||
import 'vitepress/dist/client/theme-default/styles/components/vp-code-group.css'
|
||||
import type { Theme } from 'vitepress'
|
||||
|
||||
export default define<Theme>({
|
||||
|
@ -13,6 +13,7 @@ import SponsorRightLogoSmallList from '../sponsors/right-logo-small-list.vue'
|
||||
const headers = useToc()
|
||||
const lang = useLang()
|
||||
const sponsor = computed(() => sponsorLocale[lang.value])
|
||||
const removeTag = (str: string) => str.replace(/<span.*<\/span>/g, '')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -26,7 +27,7 @@ const sponsor = computed(() => sponsorLocale[lang.value])
|
||||
:href="link"
|
||||
:title="text"
|
||||
>
|
||||
<div v-html="text" />
|
||||
<div :title="removeTag(text)" v-html="text" />
|
||||
<template v-if="children" #sub-link>
|
||||
<el-anchor-link
|
||||
v-for="{ link: childLink, text: childText } in children"
|
||||
@ -34,7 +35,7 @@ const sponsor = computed(() => sponsorLocale[lang.value])
|
||||
:href="childLink"
|
||||
:title="text"
|
||||
>
|
||||
<div v-html="childText" />
|
||||
<div :title="removeTag(childText)" v-html="childText" />
|
||||
</el-anchor-link>
|
||||
</template>
|
||||
</el-anchor-link>
|
||||
@ -59,4 +60,11 @@ const sponsor = computed(() => sponsorLocale[lang.value])
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.el-anchor__item {
|
||||
.el-anchor__link > div {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -4,6 +4,7 @@ import { onMounted } from 'vue'
|
||||
import nprogress from 'nprogress'
|
||||
// import dayjs from 'dayjs'
|
||||
import { isClient, useEventListener, useToggle } from '@vueuse/core'
|
||||
import { EVENT_CODE } from 'element-plus'
|
||||
import { useSidebar } from '../composables/sidebar'
|
||||
import { useToggleWidgets } from '../composables/toggle-widgets'
|
||||
// import { useLang } from '../composables/lang'
|
||||
@ -36,7 +37,7 @@ useToggleWidgets(isSidebarOpen, () => {
|
||||
|
||||
useEventListener('keydown', (e) => {
|
||||
if (!isClient) return
|
||||
if (e.key === 'Escape' && isSidebarOpen.value) {
|
||||
if (e.code === EVENT_CODE.esc && isSidebarOpen.value) {
|
||||
toggleSidebar(false)
|
||||
document.querySelector<HTMLButtonElement>('.sidebar-button')?.focus()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, getCurrentInstance, ref, toRef } from 'vue'
|
||||
import { isClient, useClipboard, useToggle } from '@vueuse/core'
|
||||
import { EVENT_CODE } from 'element-plus'
|
||||
import { CaretTop } from '@element-plus/icons-vue'
|
||||
import { useLang } from '../composables/lang'
|
||||
import { useSourceCode } from '../composables/source-code'
|
||||
@ -38,7 +39,11 @@ const onPlaygroundClick = () => {
|
||||
}
|
||||
|
||||
const onSourceVisibleKeydown = (e: KeyboardEvent) => {
|
||||
if (['Enter', 'Space'].includes(e.code)) {
|
||||
if (
|
||||
[EVENT_CODE.enter, EVENT_CODE.numpadEnter, EVENT_CODE.space].includes(
|
||||
e.code
|
||||
)
|
||||
) {
|
||||
e.preventDefault()
|
||||
toggleSourceVisible(false)
|
||||
sourceCodeRef.value?.focus()
|
||||
|
@ -51,14 +51,15 @@ descriptions/customized-style
|
||||
|
||||
### Descriptions Attributes
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
| --------- | ------------------------------------------ | ---------------------------------------------- | ---------- |
|
||||
| border | with or without border | ^[boolean] | false |
|
||||
| column | numbers of `Descriptions Item` in one line | ^[number] | 3 |
|
||||
| direction | direction of list | ^[enum]`'vertical' \| 'horizontal'` | horizontal |
|
||||
| size | size of list | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | — |
|
||||
| title | title text, display on the top left | ^[string] | '' |
|
||||
| extra | extra text, display on the top right | ^[string] | '' |
|
||||
| Name | Description | Type | Default |
|
||||
| -------------------- | ------------------------------------------ | ---------------------------------------------- | ---------- |
|
||||
| border | with or without border | ^[boolean] | false |
|
||||
| column | numbers of `Descriptions Item` in one line | ^[number] | 3 |
|
||||
| direction | direction of list | ^[enum]`'vertical' \| 'horizontal'` | horizontal |
|
||||
| size | size of list | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | — |
|
||||
| title | title text, display on the top left | ^[string] | '' |
|
||||
| extra | extra text, display on the top right | ^[string] | '' |
|
||||
| label-width ^(2.8.8) | label width of every column | ^[string] / ^[number] | '' |
|
||||
|
||||
### Descriptions Slots
|
||||
|
||||
@ -72,17 +73,18 @@ descriptions/customized-style
|
||||
|
||||
### DescriptionsItem Attributes
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------- |
|
||||
| label | label text | ^[string] | '' |
|
||||
| span | colspan of column | ^[number] | 1 |
|
||||
| rowspan ^(2.8.1) | the number of rows a cell should span | ^[number] | 1 |
|
||||
| width | column width, the width of the same column in different rows is set by the max value (If no `border`, width contains label and content) | ^[string] / ^[number] | '' |
|
||||
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion (If no`border`, width contains label and content) | ^[string] / ^[number] | '' |
|
||||
| align | column content alignment (If no `border`, effective for both label and content) | ^[enum]`'left' \| 'center' \| 'right'` | left |
|
||||
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied (If no `border`, please use `align` attribute) | ^[enum]`'left' \| 'center' \| 'right'` | '' |
|
||||
| class-name | column content custom class name | ^[string] | '' |
|
||||
| label-class-name | column label custom class name | ^[string] | '' |
|
||||
| Name | Description | Type | Default |
|
||||
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------- |
|
||||
| label | label text | ^[string] | '' |
|
||||
| span | colspan of column | ^[number] | 1 |
|
||||
| rowspan ^(2.8.1) | the number of rows a cell should span | ^[number] | 1 |
|
||||
| width | column width, the width of the same column in different rows is set by the max value (If no `border`, width contains label and content) | ^[string] / ^[number] | '' |
|
||||
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion (If no`border`, width contains label and content) | ^[string] / ^[number] | '' |
|
||||
| label-width ^(2.8.8) | column label width, if not set, it will be the same as the width of the column. Higher priority than the `label-width` of `Descriptions` | ^[string] / ^[number] | '' |
|
||||
| align | column content alignment (If no `border`, effective for both label and content) | ^[enum]`'left' \| 'center' \| 'right'` | left |
|
||||
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied (If no `border`, please use `align` attribute) | ^[enum]`'left' \| 'center' \| 'right'` | '' |
|
||||
| class-name | column content custom class name | ^[string] | '' |
|
||||
| label-class-name | column label custom class name | ^[string] | '' |
|
||||
|
||||
### DescriptionsItem Slots
|
||||
|
||||
|
@ -17,16 +17,21 @@ Element Plus provides a set of common icons.
|
||||
|
||||
### Using packaging manager
|
||||
|
||||
```shell
|
||||
# Choose a package manager you like.
|
||||
Choose a package manager you like.
|
||||
|
||||
# NPM
|
||||
::: code-group
|
||||
```shell [npm]
|
||||
$ npm install @element-plus/icons-vue
|
||||
# Yarn
|
||||
```
|
||||
|
||||
```shell [yarn]
|
||||
$ yarn add @element-plus/icons-vue
|
||||
# pnpm
|
||||
```
|
||||
|
||||
```shell [pnpm]
|
||||
$ pnpm install @element-plus/icons-vue
|
||||
```
|
||||
:::
|
||||
|
||||
### Register All Icons
|
||||
|
||||
|
@ -39,7 +39,7 @@ page-header/custom-icon
|
||||
## No icon
|
||||
|
||||
Sometimes the page is just full of elements, and you might not want the icon to show up on the page,
|
||||
you can set the `icon` attribute to `null` to get rid of it.
|
||||
you can set the `icon` attribute to `""` to get rid of it.
|
||||
|
||||
:::demo
|
||||
|
||||
|
@ -254,6 +254,8 @@ select-v2/custom-label
|
||||
| remote | whether search data from server | ^[boolean] | false |
|
||||
| remote-method | function that gets called when the input value changes. Its parameter is the current input value. To use this, `filterable` must be true | ^[Function]`(keyword: string) => void` | — |
|
||||
| validate-event | whether to trigger form validation | ^[boolean] | true |
|
||||
| offset ^(2.8.8) | offset of the dropdown | ^[number] | 12 |
|
||||
| show-arrow ^(2.8.8) | whether the dropdown has an arrow | ^[boolean] | true |
|
||||
| placement | position of dropdown | ^[enum]`'top' \| 'top-start' \| 'top-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'left' \| 'left-start' \| 'left-end' \| 'right' \| 'right-start' \| 'right-end'` | bottom-start |
|
||||
| fallback-placements ^(2.5.6) | list of possible positions for dropdown [popper.js](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) | ^[array]`Placement[]` | ['bottom-start', 'top-start', 'right', 'left'] |
|
||||
| collapse-tags-tooltip ^(2.3.0) | whether show all selected tags when mouse hover text of collapse-tags. To use this, `collapse-tags` must be true | ^[boolean] | false |
|
||||
|
@ -223,6 +223,8 @@ select/custom-label
|
||||
| tag-type | tag type | ^[enum]`'' \| 'success' \| 'info' \| 'warning' \| 'danger'` | info |
|
||||
| tag-effect ^(2.7.7) | tag effect | ^[enum]`'' \| 'light' \| 'dark' \| 'plain'` | light |
|
||||
| validate-event | whether to trigger form validation | ^[boolean] | true |
|
||||
| offset ^(2.8.8) | offset of the dropdown | ^[number] | 12 |
|
||||
| show-arrow ^(2.8.8) | whether the dropdown has an arrow | ^[boolean] | true |
|
||||
| placement ^(2.2.17) | position of dropdown | ^[enum]`'top' \| 'top-start' \| 'top-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'left' \| 'left-start' \| 'left-end' \| 'right' \| 'right-start' \| 'right-end'` | bottom-start |
|
||||
| fallback-placements ^(2.5.6) | list of possible positions for dropdown [popper.js](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements) | ^[array]`Placement[]` | ['bottom-start', 'top-start', 'right', 'left'] |
|
||||
| max-collapse-tags ^(2.3.0) | the max tags number to be shown. To use this, `collapse-tags` must be true | ^[number] | 1 |
|
||||
|
@ -44,19 +44,24 @@ In addition, every commit and PR on the dev branch will be published to [pkg.pr.
|
||||
so that you can utilize bundlers like [Vite](https://vitejs.dev) and
|
||||
[webpack](https://webpack.js.org/).
|
||||
|
||||
```shell
|
||||
# Choose a package manager you like.
|
||||
|
||||
# NPM
|
||||
Choose a package manager you like.
|
||||
|
||||
::: code-group
|
||||
|
||||
```shell [npm]
|
||||
$ npm install element-plus --save
|
||||
|
||||
# Yarn
|
||||
```
|
||||
```shell [yarn]
|
||||
$ yarn add element-plus
|
||||
```
|
||||
|
||||
# pnpm
|
||||
```shell [pnpm]
|
||||
$ pnpm install element-plus
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
If your network environment is not good, it is recommended to use a mirror registry [cnpm](https://github.com/cnpm/cnpm) or [npmmirror](https://npmmirror.com/).
|
||||
|
||||
```shell
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-page-header :icon="null">
|
||||
<el-page-header icon="">
|
||||
<template #content>
|
||||
<div class="flex items-center">
|
||||
<el-avatar
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-page-header :icon="null">
|
||||
<el-page-header icon="">
|
||||
<template #content>
|
||||
<span class="text-large font-600 mr-3"> Title </span>
|
||||
</template>
|
||||
|
@ -324,6 +324,7 @@ export default defineComponent({
|
||||
break
|
||||
}
|
||||
case EVENT_CODE.enter:
|
||||
case EVENT_CODE.numpadEnter:
|
||||
checkNode(target)
|
||||
break
|
||||
}
|
||||
|
@ -547,6 +547,7 @@ const handleKeyDown = (e: KeyboardEvent) => {
|
||||
|
||||
switch (e.code) {
|
||||
case EVENT_CODE.enter:
|
||||
case EVENT_CODE.numpadEnter:
|
||||
togglePopperVisible()
|
||||
break
|
||||
case EVENT_CODE.down:
|
||||
@ -611,6 +612,7 @@ const handleSuggestionKeyDown = (e: KeyboardEvent) => {
|
||||
break
|
||||
}
|
||||
case EVENT_CODE.enter:
|
||||
case EVENT_CODE.numpadEnter:
|
||||
target.click()
|
||||
break
|
||||
}
|
||||
|
@ -325,6 +325,7 @@ function handleEsc(event: KeyboardEvent) {
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
switch (event.code) {
|
||||
case EVENT_CODE.enter:
|
||||
case EVENT_CODE.numpadEnter:
|
||||
case EVENT_CODE.space:
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
@ -204,4 +204,50 @@ describe('Descriptions.vue', () => {
|
||||
await nextTick()
|
||||
expect(wrapper.findComponent(ElTag).text()).toBe(CHANGE_VALUE)
|
||||
})
|
||||
|
||||
test('should render labelWidth prop of DescriptionsItem', () => {
|
||||
const wrapper = mount(() => (
|
||||
<ElDescriptions border>
|
||||
{Array.from({ length: 3 }).map(() => (
|
||||
<ElDescriptionsItem label="测试标签" labelWidth="150px" />
|
||||
))}
|
||||
</ElDescriptions>
|
||||
))
|
||||
|
||||
expect(
|
||||
wrapper.find('.el-descriptions__label').attributes('style')
|
||||
).toContain('width: 150px')
|
||||
})
|
||||
|
||||
test('should render labelWidth prop of Descriptions', () => {
|
||||
const wrapper = mount(() => (
|
||||
<ElDescriptions label-width="150px" border>
|
||||
{Array.from({ length: 3 }).map(() => (
|
||||
<ElDescriptionsItem label="测试标签" />
|
||||
))}
|
||||
</ElDescriptions>
|
||||
))
|
||||
|
||||
expect(
|
||||
wrapper.find('.el-descriptions__label').attributes('style')
|
||||
).toContain('width: 150px')
|
||||
})
|
||||
|
||||
test('should render labelWidth prop of Descriptions and DescriptionsItem with higher priority', () => {
|
||||
const wrapper = mount(() => (
|
||||
<ElDescriptions label-width="100px" border>
|
||||
<ElDescriptionsItem label="测试标签" />
|
||||
{Array.from({ length: 2 }).map(() => (
|
||||
<ElDescriptionsItem label="测试标签" label-width="150px" />
|
||||
))}
|
||||
</ElDescriptions>
|
||||
))
|
||||
|
||||
expect(
|
||||
wrapper.findAll('.el-descriptions__label')[0].attributes('style')
|
||||
).toContain('width: 100px')
|
||||
expect(
|
||||
wrapper.findAll('.el-descriptions__label')[1].attributes('style')
|
||||
).toContain('width: 150px')
|
||||
})
|
||||
})
|
||||
|
@ -39,6 +39,13 @@ export const descriptionItemProps = buildProps({
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description column label width, if not set, it will be the same as the width of the column. Higher priority than the `label-width` of `Descriptions`
|
||||
*/
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description column content alignment (If no `border`, effective for both label and content)
|
||||
*/
|
||||
|
@ -42,6 +42,13 @@ export const descriptionProps = buildProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description width of every label column
|
||||
*/
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
} as const)
|
||||
|
||||
export type DescriptionProps = ExtractPropTypes<typeof descriptionProps>
|
||||
|
@ -52,8 +52,13 @@ export default defineComponent({
|
||||
const labelAlign = item.labelAlign ? `is-${item.labelAlign}` : align
|
||||
const className = item.className
|
||||
const labelClassName = item.labelClassName
|
||||
const width =
|
||||
this.type === 'label'
|
||||
? item.labelWidth || this.descriptions.labelWidth || item.width
|
||||
: item.width
|
||||
|
||||
const style = {
|
||||
width: addUnit(item.width),
|
||||
width: addUnit(width),
|
||||
minWidth: addUnit(item.minWidth),
|
||||
}
|
||||
const ns = useNamespace('descriptions')
|
||||
|
@ -7,6 +7,7 @@ export interface IDescriptionsInject {
|
||||
size: ComponentSize
|
||||
title: string
|
||||
extra: string
|
||||
labelWidth: string | number
|
||||
}
|
||||
|
||||
export interface IDescriptionsItemInject {
|
||||
@ -15,6 +16,7 @@ export interface IDescriptionsItemInject {
|
||||
rowspan: number
|
||||
width: string | number
|
||||
minWidth: string | number
|
||||
labelWidth: string | number
|
||||
align: string
|
||||
labelAlign: string
|
||||
className: string
|
||||
|
@ -89,8 +89,11 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const handleKeydown = composeEventHandlers((e: KeyboardEvent) => {
|
||||
const { code } = e
|
||||
if (code === EVENT_CODE.enter || code === EVENT_CODE.space) {
|
||||
if (
|
||||
[EVENT_CODE.enter, EVENT_CODE.numpadEnter, EVENT_CODE.space].includes(
|
||||
e.code
|
||||
)
|
||||
) {
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
emit('clickimpl', e)
|
||||
|
@ -148,7 +148,12 @@ export default defineComponent({
|
||||
const scrollbar = ref(null)
|
||||
const currentTabId = ref<string | null>(null)
|
||||
const isUsingKeyboard = ref(false)
|
||||
const triggerKeys = [EVENT_CODE.enter, EVENT_CODE.space, EVENT_CODE.down]
|
||||
const triggerKeys = [
|
||||
EVENT_CODE.enter,
|
||||
EVENT_CODE.numpadEnter,
|
||||
EVENT_CODE.space,
|
||||
EVENT_CODE.down,
|
||||
]
|
||||
|
||||
const wrapStyle = computed<CSSProperties>(() => ({
|
||||
maxHeight: addUnit(props.maxHeight),
|
||||
|
@ -46,7 +46,7 @@ export const initDropdownDomEvent = (
|
||||
menuItems.value[0].focus()
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
} else if (code === EVENT_CODE.enter) {
|
||||
} else if ([EVENT_CODE.enter, EVENT_CODE.numpadEnter].includes(code)) {
|
||||
_instance.handleClick()
|
||||
} else if ([EVENT_CODE.tab, EVENT_CODE.esc].includes(code)) {
|
||||
_instance.hide()
|
||||
|
@ -141,7 +141,7 @@ describe('<ElFocusTrap', () => {
|
||||
expect(wrapper.emitted('release-requested')).toBeFalsy()
|
||||
|
||||
focusContainer?.trigger('keydown', {
|
||||
key: EVENT_CODE.esc,
|
||||
code: EVENT_CODE.esc,
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
@ -155,7 +155,7 @@ describe('<ElFocusTrap', () => {
|
||||
await nextTick()
|
||||
|
||||
focusContainer?.trigger('keydown', {
|
||||
key: EVENT_CODE.esc,
|
||||
code: EVENT_CODE.esc,
|
||||
})
|
||||
|
||||
// Expect no emit if esc while layer paused
|
||||
@ -173,13 +173,13 @@ describe('<ElFocusTrap', () => {
|
||||
|
||||
expect(wrapper.emitted('focusout-prevented')).toBeFalsy()
|
||||
await childComponent.trigger('keydown.shift', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).toBe(items.at(0)?.element)
|
||||
expect(wrapper.emitted('focusout-prevented')?.length).toBe(2)
|
||||
;(items.at(2)?.element as HTMLElement).focus()
|
||||
await childComponent.trigger('keydown', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(wrapper.emitted('focusout-prevented')?.length).toBe(4)
|
||||
})
|
||||
@ -203,14 +203,14 @@ describe('<ElFocusTrap', () => {
|
||||
*/
|
||||
// when loop is off
|
||||
await childComponent.trigger('keydown.shift', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).toBe(items.at(0)?.element)
|
||||
;(items.at(2)?.element as HTMLElement).focus()
|
||||
expect(document.activeElement).toBe(items.at(2)?.element)
|
||||
|
||||
await childComponent.trigger('keydown', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).toBe(items.at(2)?.element)
|
||||
|
||||
@ -220,12 +220,12 @@ describe('<ElFocusTrap', () => {
|
||||
})
|
||||
|
||||
await childComponent.trigger('keydown', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).toBe(items.at(0)?.element)
|
||||
|
||||
await childComponent.trigger('keydown.shift', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).toBe(items.at(2)?.element)
|
||||
})
|
||||
@ -239,7 +239,7 @@ describe('<ElFocusTrap', () => {
|
||||
expect(document.activeElement).toBe(focusComponent.element)
|
||||
|
||||
await focusComponent.trigger('keydown', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).toBe(focusComponent.element)
|
||||
})
|
||||
@ -268,7 +268,7 @@ describe('<ElFocusTrap', () => {
|
||||
expect(document.activeElement).toBe(beforeTrap.element)
|
||||
|
||||
await focusContainer.trigger('keydown', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
preventDefault,
|
||||
})
|
||||
if (!isDefaultPrevented) {
|
||||
@ -278,7 +278,7 @@ describe('<ElFocusTrap', () => {
|
||||
expect(document.activeElement).toBe(items.at(0)?.element)
|
||||
|
||||
await focusContainer.trigger('keydown', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
preventDefault,
|
||||
})
|
||||
if (!isDefaultPrevented) {
|
||||
@ -303,7 +303,7 @@ describe('<ElFocusTrap', () => {
|
||||
expect(document.activeElement).toBe(items.at(0)?.element)
|
||||
|
||||
await focusComponent.trigger('keydown.shift', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).toBe(items.at(2)?.element)
|
||||
|
||||
@ -313,7 +313,7 @@ describe('<ElFocusTrap', () => {
|
||||
expect(document.activeElement).toBe(newFocusTrap.find('.item').element)
|
||||
|
||||
await focusComponent.trigger('keydown', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).not.toBe(items.at(0)?.element)
|
||||
newFocusTrap.unmount()
|
||||
@ -322,7 +322,7 @@ describe('<ElFocusTrap', () => {
|
||||
expect(document.activeElement).toBe(items.at(2)?.element)
|
||||
|
||||
await focusComponent.trigger('keydown', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(document.activeElement).toBe(items.at(0)?.element)
|
||||
})
|
||||
|
@ -85,10 +85,10 @@ export default defineComponent({
|
||||
if (!props.loop && !props.trapped) return
|
||||
if (focusLayer.paused) return
|
||||
|
||||
const { key, altKey, ctrlKey, metaKey, currentTarget, shiftKey } = e
|
||||
const { code, altKey, ctrlKey, metaKey, currentTarget, shiftKey } = e
|
||||
const { loop } = props
|
||||
const isTabbing =
|
||||
key === EVENT_CODE.tab && !altKey && !ctrlKey && !metaKey
|
||||
code === EVENT_CODE.tab && !altKey && !ctrlKey && !metaKey
|
||||
|
||||
const currentFocusingEl = document.activeElement
|
||||
if (isTabbing && currentFocusingEl) {
|
||||
|
@ -18,7 +18,7 @@
|
||||
v-for="(item, index) in options"
|
||||
:id="`${contentId}-${index}`"
|
||||
ref="optionRefs"
|
||||
:key="item.value"
|
||||
:key="index"
|
||||
:class="optionkls(item, index)"
|
||||
role="option"
|
||||
:aria-disabled="item.disabled || disabled || undefined"
|
||||
|
@ -61,7 +61,7 @@ import { pick } from 'lodash-unified'
|
||||
import { useFocusController, useId, useNamespace } from '@element-plus/hooks'
|
||||
import ElInput, { inputProps } from '@element-plus/components/input'
|
||||
import ElTooltip from '@element-plus/components/tooltip'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||
import { EVENT_CODE, UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||
import { useFormDisabled } from '@element-plus/components/form'
|
||||
import { isFunction } from '@element-plus/utils'
|
||||
import { mentionEmits, mentionProps } from './mention'
|
||||
@ -125,54 +125,63 @@ const handleInputChange = (value: string) => {
|
||||
syncAfterCursorMove()
|
||||
}
|
||||
|
||||
const handleInputKeyDown = (e: KeyboardEvent | Event) => {
|
||||
if (!('key' in e)) return
|
||||
if (elInputRef.value?.isComposing) return
|
||||
if (['ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
||||
syncAfterCursorMove()
|
||||
} else if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
|
||||
if (!visible.value) return
|
||||
e.preventDefault()
|
||||
const direction = e.key === 'ArrowUp' ? 'prev' : 'next'
|
||||
dropdownRef.value?.navigateOptions(direction)
|
||||
} else if (['Enter'].includes(e.key)) {
|
||||
if (!visible.value) return
|
||||
e.preventDefault()
|
||||
if (dropdownRef.value?.hoverOption) {
|
||||
dropdownRef.value?.selectHoverOption()
|
||||
} else {
|
||||
visible.value = false
|
||||
}
|
||||
} else if (['Escape'].includes(e.key)) {
|
||||
if (!visible.value) return
|
||||
e.preventDefault()
|
||||
visible.value = false
|
||||
} else if (['Backspace'].includes(e.key)) {
|
||||
if (props.whole && mentionCtx.value) {
|
||||
const { splitIndex, selectionEnd, pattern, prefixIndex, prefix } =
|
||||
mentionCtx.value
|
||||
const inputEl = getInputEl()
|
||||
if (!inputEl) return
|
||||
const inputValue = inputEl.value
|
||||
const matchOption = props.options.find((item) => item.value === pattern)
|
||||
const isWhole = isFunction(props.checkIsWhole)
|
||||
? props.checkIsWhole(pattern, prefix)
|
||||
: matchOption
|
||||
if (isWhole && splitIndex !== -1 && splitIndex + 1 === selectionEnd) {
|
||||
e.preventDefault()
|
||||
const newValue =
|
||||
inputValue.slice(0, prefixIndex) + inputValue.slice(splitIndex + 1)
|
||||
emit(UPDATE_MODEL_EVENT, newValue)
|
||||
const handleInputKeyDown = (event: KeyboardEvent | Event) => {
|
||||
if (!('code' in event) || elInputRef.value?.isComposing) return
|
||||
|
||||
const newSelectionEnd = prefixIndex
|
||||
nextTick(() => {
|
||||
// input value is updated
|
||||
inputEl.selectionStart = newSelectionEnd
|
||||
inputEl.selectionEnd = newSelectionEnd
|
||||
syncDropdownVisible()
|
||||
})
|
||||
switch (event.code) {
|
||||
case EVENT_CODE.left:
|
||||
case EVENT_CODE.right:
|
||||
syncAfterCursorMove()
|
||||
break
|
||||
case EVENT_CODE.up:
|
||||
case EVENT_CODE.down:
|
||||
if (!visible.value) return
|
||||
event.preventDefault()
|
||||
dropdownRef.value?.navigateOptions(
|
||||
event.code === EVENT_CODE.up ? 'prev' : 'next'
|
||||
)
|
||||
break
|
||||
case EVENT_CODE.enter:
|
||||
case EVENT_CODE.numpadEnter:
|
||||
if (!visible.value) return
|
||||
event.preventDefault()
|
||||
if (dropdownRef.value?.hoverOption) {
|
||||
dropdownRef.value?.selectHoverOption()
|
||||
} else {
|
||||
visible.value = false
|
||||
}
|
||||
break
|
||||
case EVENT_CODE.esc:
|
||||
if (!visible.value) return
|
||||
event.preventDefault()
|
||||
visible.value = false
|
||||
break
|
||||
case EVENT_CODE.backspace:
|
||||
if (props.whole && mentionCtx.value) {
|
||||
const { splitIndex, selectionEnd, pattern, prefixIndex, prefix } =
|
||||
mentionCtx.value
|
||||
const inputEl = getInputEl()
|
||||
if (!inputEl) return
|
||||
const inputValue = inputEl.value
|
||||
const matchOption = props.options.find((item) => item.value === pattern)
|
||||
const isWhole = isFunction(props.checkIsWhole)
|
||||
? props.checkIsWhole(pattern, prefix)
|
||||
: matchOption
|
||||
if (isWhole && splitIndex !== -1 && splitIndex + 1 === selectionEnd) {
|
||||
event.preventDefault()
|
||||
const newValue =
|
||||
inputValue.slice(0, prefixIndex) + inputValue.slice(splitIndex + 1)
|
||||
emit(UPDATE_MODEL_EVENT, newValue)
|
||||
|
||||
const newSelectionEnd = prefixIndex
|
||||
nextTick(() => {
|
||||
// input value is updated
|
||||
inputEl.selectionStart = newSelectionEnd
|
||||
inputEl.selectionEnd = newSelectionEnd
|
||||
syncDropdownVisible()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ class MenuItem {
|
||||
break
|
||||
}
|
||||
case EVENT_CODE.enter:
|
||||
case EVENT_CODE.numpadEnter:
|
||||
case EVENT_CODE.space: {
|
||||
prevDef = true
|
||||
;(event.currentTarget as HTMLElement).click()
|
||||
|
@ -47,6 +47,7 @@ class SubMenu {
|
||||
break
|
||||
}
|
||||
case EVENT_CODE.enter:
|
||||
case EVENT_CODE.numpadEnter:
|
||||
case EVENT_CODE.space: {
|
||||
prevDef = true
|
||||
;(event.currentTarget as HTMLElement).click()
|
||||
|
@ -1,3 +1,4 @@
|
||||
import '@element-plus/components/base/style/css'
|
||||
import '@element-plus/theme-chalk/el-pagination.css'
|
||||
import '@element-plus/components/select/style/css'
|
||||
import '@element-plus/components/input/style/css'
|
||||
|
@ -1,3 +1,4 @@
|
||||
import '@element-plus/components/base/style'
|
||||
import '@element-plus/theme-chalk/src/pagination.scss'
|
||||
import '@element-plus/components/select/style'
|
||||
import '@element-plus/components/input/style'
|
||||
|
@ -149,34 +149,34 @@ describe('<ElRovingFocusItem />', () => {
|
||||
const firstDOMItem = DOMItems.at(0)
|
||||
expect(onItemShiftTab).not.toHaveBeenCalled()
|
||||
await firstDOMItem.trigger('keydown.shift', {
|
||||
key: EVENT_CODE.tab,
|
||||
code: EVENT_CODE.tab,
|
||||
})
|
||||
expect(items.at(0).emitted()).toHaveProperty('keydown')
|
||||
expect(onItemShiftTab).toHaveBeenCalled()
|
||||
// navigating clockwise
|
||||
expect(document.activeElement).toBe(document.body)
|
||||
await DOMItems.at(1).trigger('keydown', {
|
||||
key: EVENT_CODE.down,
|
||||
code: EVENT_CODE.down,
|
||||
})
|
||||
await nextTick()
|
||||
expect(document.activeElement).toStrictEqual(DOMItems.at(2).element)
|
||||
|
||||
// navigate anticlockwise
|
||||
await DOMItems.at(1).trigger('keydown', {
|
||||
key: EVENT_CODE.up,
|
||||
code: EVENT_CODE.up,
|
||||
})
|
||||
await nextTick()
|
||||
expect(document.activeElement).toStrictEqual(DOMItems.at(0).element)
|
||||
|
||||
// should be able to focus on the last element when press End
|
||||
await DOMItems.at(0).trigger('keydown', {
|
||||
key: EVENT_CODE.end,
|
||||
code: EVENT_CODE.end,
|
||||
})
|
||||
await nextTick()
|
||||
expect(document.activeElement).toStrictEqual(DOMItems.at(2).element)
|
||||
|
||||
await DOMItems.at(0).trigger('keydown', {
|
||||
key: EVENT_CODE.home,
|
||||
code: EVENT_CODE.home,
|
||||
})
|
||||
await nextTick()
|
||||
expect(document.activeElement).toStrictEqual(DOMItems.at(0).element)
|
||||
|
@ -7,7 +7,7 @@ describe('util', () => {
|
||||
expect(
|
||||
Util.getFocusIntent(
|
||||
new KeyboardEvent('mousedown', {
|
||||
key: EVENT_CODE.enter,
|
||||
code: EVENT_CODE.enter,
|
||||
})
|
||||
)
|
||||
).toBe(undefined)
|
||||
@ -15,7 +15,7 @@ describe('util', () => {
|
||||
expect(
|
||||
Util.getFocusIntent(
|
||||
new KeyboardEvent('mousedown', {
|
||||
key: EVENT_CODE.left,
|
||||
code: EVENT_CODE.left,
|
||||
})
|
||||
)
|
||||
).toBe('prev')
|
||||
@ -23,7 +23,7 @@ describe('util', () => {
|
||||
expect(
|
||||
Util.getFocusIntent(
|
||||
new KeyboardEvent('mousedown', {
|
||||
key: EVENT_CODE.left,
|
||||
code: EVENT_CODE.left,
|
||||
}),
|
||||
'vertical'
|
||||
)
|
||||
@ -31,7 +31,7 @@ describe('util', () => {
|
||||
expect(
|
||||
Util.getFocusIntent(
|
||||
new KeyboardEvent('mousedown', {
|
||||
key: EVENT_CODE.up,
|
||||
code: EVENT_CODE.up,
|
||||
}),
|
||||
'horizontal'
|
||||
)
|
||||
@ -40,7 +40,7 @@ describe('util', () => {
|
||||
expect(
|
||||
Util.getFocusIntent(
|
||||
new KeyboardEvent('mousedown', {
|
||||
key: EVENT_CODE.left,
|
||||
code: EVENT_CODE.left,
|
||||
}),
|
||||
'horizontal',
|
||||
'rtl'
|
||||
@ -50,7 +50,7 @@ describe('util', () => {
|
||||
expect(
|
||||
Util.getFocusIntent(
|
||||
new KeyboardEvent('mousedown', {
|
||||
key: EVENT_CODE.right,
|
||||
code: EVENT_CODE.right,
|
||||
}),
|
||||
'horizontal',
|
||||
'rtl'
|
||||
@ -60,7 +60,7 @@ describe('util', () => {
|
||||
expect(
|
||||
Util.getFocusIntent(
|
||||
new KeyboardEvent('mousedown', {
|
||||
key: EVENT_CODE.up,
|
||||
code: EVENT_CODE.up,
|
||||
}),
|
||||
'vertical',
|
||||
'rtl'
|
||||
|
@ -87,8 +87,8 @@ export default defineComponent({
|
||||
emit('keydown', e)
|
||||
},
|
||||
(e) => {
|
||||
const { key, shiftKey, target, currentTarget } = e as KeyboardEvent
|
||||
if (key === EVENT_CODE.tab && shiftKey) {
|
||||
const { code, shiftKey, target, currentTarget } = e as KeyboardEvent
|
||||
if (code === EVENT_CODE.tab && shiftKey) {
|
||||
onItemShiftTab()
|
||||
return
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export const getFocusIntent = (
|
||||
orientation?: Orientation,
|
||||
dir?: Direction
|
||||
) => {
|
||||
const key = getDirectionAwareKey(event.key, dir)
|
||||
const key = getDirectionAwareKey(event.code, dir)
|
||||
if (
|
||||
orientation === 'vertical' &&
|
||||
[EVENT_CODE.left, EVENT_CODE.right].includes(key)
|
||||
|
@ -237,6 +237,20 @@ export const SelectProps = buildProps({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* @description offset of the dropdown
|
||||
*/
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 12,
|
||||
},
|
||||
/**
|
||||
* @description Determines whether the arrow is displayed
|
||||
*/
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* @description position of dropdown
|
||||
*/
|
||||
|
@ -226,7 +226,7 @@ export default defineComponent({
|
||||
|
||||
const onKeydown = (e: KeyboardEvent) => {
|
||||
const { code } = e
|
||||
const { tab, esc, down, up, enter } = EVENT_CODE
|
||||
const { tab, esc, down, up, enter, numpadEnter } = EVENT_CODE
|
||||
if (code !== tab) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@ -234,22 +234,19 @@ export default defineComponent({
|
||||
|
||||
switch (code) {
|
||||
case tab:
|
||||
case esc: {
|
||||
case esc:
|
||||
onEscOrTab()
|
||||
break
|
||||
}
|
||||
case down: {
|
||||
case down:
|
||||
onForward()
|
||||
break
|
||||
}
|
||||
case up: {
|
||||
case up:
|
||||
onBackward()
|
||||
break
|
||||
}
|
||||
case enter: {
|
||||
case enter:
|
||||
case numpadEnter:
|
||||
onKeyboardSelect()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,8 @@
|
||||
trigger="click"
|
||||
:persistent="persistent"
|
||||
:append-to="appendTo"
|
||||
:show-arrow="showArrow"
|
||||
:offset="offset"
|
||||
@before-show="handleMenuEnter"
|
||||
@hide="states.isBeforeHide = false"
|
||||
>
|
||||
@ -227,7 +229,7 @@
|
||||
<component :is="clearIcon" />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
v-if="validateState && validateIcon"
|
||||
v-if="validateState && validateIcon && needStatusIcon"
|
||||
:class="[nsInput.e('icon'), nsInput.e('validateIcon')]"
|
||||
>
|
||||
<component :is="validateIcon" />
|
||||
|
@ -135,6 +135,8 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
|
||||
|
||||
const selectDisabled = computed(() => props.disabled || elForm?.disabled)
|
||||
|
||||
const needStatusIcon = computed(() => elForm?.statusIcon ?? false)
|
||||
|
||||
const popupHeight = computed(() => {
|
||||
const totalHeight = filteredOptions.value.length * props.itemHeight
|
||||
return totalHeight > props.height ? props.height : totalHeight
|
||||
@ -812,9 +814,11 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val, oldVal) => {
|
||||
const isValEmpty = !val || (isArray(val) && val.length === 0)
|
||||
|
||||
if (
|
||||
!val ||
|
||||
(props.multiple && val.toString() !== states.previousValue) ||
|
||||
isValEmpty ||
|
||||
(props.multiple && !isEqual(val.toString(), states.previousValue)) ||
|
||||
(!props.multiple &&
|
||||
getValueKey(val) !== getValueKey(states.previousValue))
|
||||
) {
|
||||
@ -913,6 +917,7 @@ const useSelect = (props: ISelectV2Props, emit: SelectEmitFn) => {
|
||||
shouldShowPlaceholder,
|
||||
selectDisabled,
|
||||
selectSize,
|
||||
needStatusIcon,
|
||||
showClearBtn,
|
||||
states,
|
||||
isFocused,
|
||||
|
@ -209,6 +209,20 @@ export const SelectProps = buildProps({
|
||||
* @description in remote search method show suffix icon
|
||||
*/
|
||||
remoteShowSuffix: Boolean,
|
||||
/**
|
||||
* @description determines whether the arrow is displayed
|
||||
*/
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* @description offset of the dropdown
|
||||
*/
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 12,
|
||||
},
|
||||
/**
|
||||
* @description position of dropdown
|
||||
*/
|
||||
|
@ -22,6 +22,8 @@
|
||||
:gpu-acceleration="false"
|
||||
:persistent="persistent"
|
||||
:append-to="appendTo"
|
||||
:show-arrow="showArrow"
|
||||
:offset="offset"
|
||||
@before-show="handleMenuEnter"
|
||||
@hide="states.isBeforeHide = false"
|
||||
>
|
||||
@ -227,7 +229,7 @@
|
||||
<component :is="clearIcon" />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
v-if="validateState && validateIcon"
|
||||
v-if="validateState && validateIcon && needStatusIcon"
|
||||
:class="[nsInput.e('icon'), nsInput.e('validateIcon')]"
|
||||
>
|
||||
<component :is="validateIcon" />
|
||||
|
@ -25,9 +25,9 @@ import {
|
||||
isIOS,
|
||||
isNumber,
|
||||
isObject,
|
||||
isPlainObject,
|
||||
isUndefined,
|
||||
scrollIntoView,
|
||||
toRawType,
|
||||
} from '@element-plus/utils'
|
||||
import {
|
||||
CHANGE_EVENT,
|
||||
@ -141,6 +141,8 @@ export const useSelect = (props: ISelectProps, emit) => {
|
||||
: !isEmptyValue(props.modelValue)
|
||||
})
|
||||
|
||||
const needStatusIcon = computed(() => form?.statusIcon ?? false)
|
||||
|
||||
const showClose = computed(() => {
|
||||
return (
|
||||
props.clearable &&
|
||||
@ -424,9 +426,7 @@ export const useSelect = (props: ISelectProps, emit) => {
|
||||
|
||||
const getOption = (value) => {
|
||||
let option
|
||||
const isObjectValue = toRawType(value).toLowerCase() === 'object'
|
||||
const isNull = toRawType(value).toLowerCase() === 'null'
|
||||
const isUndefined = toRawType(value).toLowerCase() === 'undefined'
|
||||
const isObjectValue = isPlainObject(value)
|
||||
|
||||
for (let i = states.cachedOptions.size - 1; i >= 0; i--) {
|
||||
const cachedOption = cachedOptionsArray.value[i]
|
||||
@ -445,11 +445,7 @@ export const useSelect = (props: ISelectProps, emit) => {
|
||||
}
|
||||
}
|
||||
if (option) return option
|
||||
const label = isObjectValue
|
||||
? value.label
|
||||
: !isNull && !isUndefined
|
||||
? value
|
||||
: ''
|
||||
const label = isObjectValue ? value.label : value ?? ''
|
||||
const newOption = {
|
||||
value,
|
||||
currentLabel: label,
|
||||
@ -595,7 +591,8 @@ export const useSelect = (props: ISelectProps, emit) => {
|
||||
}
|
||||
|
||||
const getValueIndex = (arr: any[] = [], option) => {
|
||||
if (!isObject(option?.value)) return arr.indexOf(option.value)
|
||||
if (isUndefined(option)) return -1
|
||||
if (!isObject(option.value)) return arr.indexOf(option.value)
|
||||
|
||||
return arr.findIndex((item) => {
|
||||
return isEqual(get(item, props.valueKey), getValueKey(option))
|
||||
@ -831,6 +828,7 @@ export const useSelect = (props: ISelectProps, emit) => {
|
||||
shouldShowPlaceholder,
|
||||
currentPlaceholder,
|
||||
mouseEnterEventName,
|
||||
needStatusIcon,
|
||||
showClose,
|
||||
iconComponent,
|
||||
iconReverse,
|
||||
|
@ -197,13 +197,13 @@ describe('Slider', () => {
|
||||
const slider = wrapper.findComponent({ name: 'ElSliderButton' })
|
||||
|
||||
slider.vm.onKeyDown(
|
||||
new KeyboardEvent('keydown', { key: EVENT_CODE.right })
|
||||
new KeyboardEvent('keydown', { code: EVENT_CODE.right })
|
||||
)
|
||||
await nextTick()
|
||||
expect(value.value).toBe(1)
|
||||
|
||||
slider.vm.onKeyDown(
|
||||
new KeyboardEvent('keydown', { key: EVENT_CODE.left })
|
||||
new KeyboardEvent('keydown', { code: EVENT_CODE.left })
|
||||
)
|
||||
await nextTick()
|
||||
expect(value.value).toBe(0)
|
||||
@ -215,12 +215,12 @@ describe('Slider', () => {
|
||||
|
||||
const slider = wrapper.findComponent({ name: 'ElSliderButton' })
|
||||
|
||||
slider.vm.onKeyDown(new KeyboardEvent('keydown', { key: EVENT_CODE.up }))
|
||||
slider.vm.onKeyDown(new KeyboardEvent('keydown', { code: EVENT_CODE.up }))
|
||||
await nextTick()
|
||||
expect(value.value).toBe(1)
|
||||
|
||||
slider.vm.onKeyDown(
|
||||
new KeyboardEvent('keydown', { key: EVENT_CODE.down })
|
||||
new KeyboardEvent('keydown', { code: EVENT_CODE.down })
|
||||
)
|
||||
await nextTick()
|
||||
expect(value.value).toBe(0)
|
||||
@ -234,13 +234,13 @@ describe('Slider', () => {
|
||||
|
||||
const slider = wrapper.findComponent({ name: 'ElSliderButton' })
|
||||
slider.vm.onKeyDown(
|
||||
new KeyboardEvent('keydown', { key: EVENT_CODE.pageUp })
|
||||
new KeyboardEvent('keydown', { code: EVENT_CODE.pageUp })
|
||||
)
|
||||
await nextTick()
|
||||
expect(value.value).toBe(3)
|
||||
|
||||
slider.vm.onKeyDown(
|
||||
new KeyboardEvent('keydown', { key: EVENT_CODE.pageDown })
|
||||
new KeyboardEvent('keydown', { code: EVENT_CODE.pageDown })
|
||||
)
|
||||
await nextTick()
|
||||
expect(value.value).toBe(-1)
|
||||
@ -254,12 +254,14 @@ describe('Slider', () => {
|
||||
|
||||
const slider = wrapper.findComponent({ name: 'ElSliderButton' })
|
||||
slider.vm.onKeyDown(
|
||||
new KeyboardEvent('keydown', { key: EVENT_CODE.home })
|
||||
new KeyboardEvent('keydown', { code: EVENT_CODE.home })
|
||||
)
|
||||
await nextTick()
|
||||
expect(value.value).toBe(-5)
|
||||
|
||||
slider.vm.onKeyDown(new KeyboardEvent('keydown', { key: EVENT_CODE.end }))
|
||||
slider.vm.onKeyDown(
|
||||
new KeyboardEvent('keydown', { code: EVENT_CODE.end })
|
||||
)
|
||||
await nextTick()
|
||||
expect(value.value).toBe(10)
|
||||
})
|
||||
|
@ -13,8 +13,6 @@ import type {
|
||||
} from '../button'
|
||||
import type { TooltipInstance } from '@element-plus/components/tooltip'
|
||||
|
||||
const { left, down, right, up, home, end, pageUp, pageDown } = EVENT_CODE
|
||||
|
||||
const useTooltip = (
|
||||
props: SliderButtonProps,
|
||||
formatTooltip: Ref<SliderProps['formatTooltip']>,
|
||||
@ -151,21 +149,33 @@ export const useSliderButton = (
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
let isPreventDefault = true
|
||||
if ([left, down].includes(event.key)) {
|
||||
onLeftKeyDown()
|
||||
} else if ([right, up].includes(event.key)) {
|
||||
onRightKeyDown()
|
||||
} else if (event.key === home) {
|
||||
onHomeKeyDown()
|
||||
} else if (event.key === end) {
|
||||
onEndKeyDown()
|
||||
} else if (event.key === pageDown) {
|
||||
onPageDownKeyDown()
|
||||
} else if (event.key === pageUp) {
|
||||
onPageUpKeyDown()
|
||||
} else {
|
||||
isPreventDefault = false
|
||||
|
||||
switch (event.code) {
|
||||
case EVENT_CODE.left:
|
||||
case EVENT_CODE.down:
|
||||
onLeftKeyDown()
|
||||
break
|
||||
case EVENT_CODE.right:
|
||||
case EVENT_CODE.up:
|
||||
onRightKeyDown()
|
||||
break
|
||||
case EVENT_CODE.home:
|
||||
onHomeKeyDown()
|
||||
break
|
||||
case EVENT_CODE.end:
|
||||
onEndKeyDown()
|
||||
break
|
||||
case EVENT_CODE.pageDown:
|
||||
onPageDownKeyDown()
|
||||
break
|
||||
case EVENT_CODE.pageUp:
|
||||
onPageUpKeyDown()
|
||||
break
|
||||
default:
|
||||
isPreventDefault = false
|
||||
break
|
||||
}
|
||||
|
||||
isPreventDefault && event.preventDefault()
|
||||
}
|
||||
|
||||
|
@ -77,9 +77,21 @@ function useWatcher<T>() {
|
||||
const sortOrder = ref(null)
|
||||
const hoverRow = ref(null)
|
||||
|
||||
watch(data, () => instance.state && scheduleLayout(false), {
|
||||
deep: true,
|
||||
})
|
||||
watch(
|
||||
data,
|
||||
() => {
|
||||
if (instance.state) {
|
||||
scheduleLayout(false)
|
||||
const needUpdateFixed = instance.props.tableLayout === 'auto'
|
||||
if (needUpdateFixed) {
|
||||
instance.refs.tableHeaderRef?.updateFixedColumnStyle()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
)
|
||||
|
||||
// 检查 rowKey 是否存在
|
||||
const assertRowKey = () => {
|
||||
|
@ -67,13 +67,38 @@ export default defineComponent({
|
||||
const ns = useNamespace('table')
|
||||
const filterPanels = ref({})
|
||||
const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent!)
|
||||
|
||||
const isTableLayoutAuto = parent?.props.tableLayout === 'auto'
|
||||
const saveIndexSelection = new Map()
|
||||
const theadRef = ref()
|
||||
|
||||
const updateFixedColumnStyle = () => {
|
||||
setTimeout(() => {
|
||||
if (saveIndexSelection.size > 0) {
|
||||
saveIndexSelection.forEach((column, key) => {
|
||||
const el = theadRef.value.querySelector(
|
||||
`.${key.replace(/\s/g, '.')}`
|
||||
)
|
||||
if (el) {
|
||||
const width = el.getBoundingClientRect().width
|
||||
column.width = width
|
||||
}
|
||||
})
|
||||
saveIndexSelection.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// Need double await, because updateColumns is executed after nextTick for now
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
const { prop, order } = props.defaultSort
|
||||
parent?.store.commit('sort', { prop, order, init: true })
|
||||
|
||||
updateFixedColumnStyle()
|
||||
})
|
||||
|
||||
const {
|
||||
handleHeaderClick,
|
||||
handleHeaderContextMenu,
|
||||
@ -118,6 +143,10 @@ export default defineComponent({
|
||||
handleFilterClick,
|
||||
isGroup,
|
||||
toggleAllSelection,
|
||||
saveIndexSelection,
|
||||
isTableLayoutAuto,
|
||||
theadRef,
|
||||
updateFixedColumnStyle,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
@ -137,11 +166,14 @@ export default defineComponent({
|
||||
handleMouseOut,
|
||||
store,
|
||||
$parent,
|
||||
saveIndexSelection,
|
||||
isTableLayoutAuto,
|
||||
} = this
|
||||
let rowSpan = 1
|
||||
return h(
|
||||
'thead',
|
||||
{
|
||||
ref: 'theadRef',
|
||||
class: { [ns.is('group')]: isGroup },
|
||||
},
|
||||
columnRows.map((subColumns, rowIndex) =>
|
||||
@ -156,15 +188,19 @@ export default defineComponent({
|
||||
if (column.rowSpan > rowSpan) {
|
||||
rowSpan = column.rowSpan
|
||||
}
|
||||
const _class = getHeaderCellClass(
|
||||
rowIndex,
|
||||
cellIndex,
|
||||
subColumns,
|
||||
column
|
||||
)
|
||||
if (isTableLayoutAuto && column.fixed) {
|
||||
saveIndexSelection.set(_class, column)
|
||||
}
|
||||
return h(
|
||||
'th',
|
||||
{
|
||||
class: getHeaderCellClass(
|
||||
rowIndex,
|
||||
cellIndex,
|
||||
subColumns,
|
||||
column
|
||||
),
|
||||
class: _class,
|
||||
colspan: column.colSpan,
|
||||
key: `${column.id}-thead`,
|
||||
rowspan: column.rowSpan,
|
||||
|
@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { createVNode, render } from 'vue'
|
||||
import { flatMap, get } from 'lodash-unified'
|
||||
import { flatMap, get, merge } from 'lodash-unified'
|
||||
import {
|
||||
hasOwn,
|
||||
isArray,
|
||||
@ -16,6 +16,7 @@ import ElTooltip, {
|
||||
} from '@element-plus/components/tooltip'
|
||||
import type { Table, TreeProps } from './table/defaults'
|
||||
import type { TableColumnCtx } from './table-column/defaults'
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
export type TableOverflowTooltipOptions = Partial<
|
||||
Pick<
|
||||
@ -36,6 +37,7 @@ export type TableOverflowTooltipOptions = Partial<
|
||||
|
||||
type RemovePopperFn = (() => void) & {
|
||||
trigger?: HTMLElement
|
||||
vm?: VNode
|
||||
}
|
||||
|
||||
export const getCell = function (event: Event) {
|
||||
@ -363,6 +365,20 @@ export function walkTreeNode(
|
||||
})
|
||||
}
|
||||
|
||||
const getTableOverflowTooltipProps = (
|
||||
props: TableOverflowTooltipOptions,
|
||||
content: string
|
||||
) => {
|
||||
return {
|
||||
content,
|
||||
...props,
|
||||
popperOptions: {
|
||||
strategy: 'fixed',
|
||||
...props.popperOptions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export let removePopper: RemovePopperFn | null = null
|
||||
|
||||
export function createTablePopper(
|
||||
@ -372,17 +388,16 @@ export function createTablePopper(
|
||||
table: Table<[]>
|
||||
) {
|
||||
if (removePopper?.trigger === trigger) {
|
||||
merge(
|
||||
removePopper!.vm.component.props,
|
||||
getTableOverflowTooltipProps(props, popperContent)
|
||||
)
|
||||
return
|
||||
}
|
||||
removePopper?.()
|
||||
const parentNode = table?.refs.tableWrapper
|
||||
const ns = parentNode?.dataset.prefix
|
||||
const popperOptions = {
|
||||
strategy: 'fixed',
|
||||
...props.popperOptions,
|
||||
}
|
||||
const vm = createVNode(ElTooltip, {
|
||||
content: popperContent,
|
||||
virtualTriggering: true,
|
||||
virtualRef: trigger,
|
||||
appendTo: parentNode,
|
||||
@ -390,11 +405,7 @@ export function createTablePopper(
|
||||
transition: 'none', // Default does not require transition
|
||||
offset: 0,
|
||||
hideAfter: 0,
|
||||
...props,
|
||||
popperOptions,
|
||||
onHide: () => {
|
||||
removePopper?.()
|
||||
},
|
||||
...getTableOverflowTooltipProps(props, popperContent),
|
||||
})
|
||||
vm.appContext = { ...table.appContext, ...table }
|
||||
const container = document.createElement('div')
|
||||
@ -407,6 +418,7 @@ export function createTablePopper(
|
||||
removePopper = null
|
||||
}
|
||||
removePopper.trigger = trigger
|
||||
removePopper.vm = vm
|
||||
scrollContainer?.addEventListener('scroll', removePopper)
|
||||
}
|
||||
|
||||
|
@ -202,38 +202,36 @@ const TabNav = defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const changeTab = (e: KeyboardEvent) => {
|
||||
const code = e.code
|
||||
const changeTab = (event: KeyboardEvent) => {
|
||||
let step = 0
|
||||
|
||||
const { up, down, left, right } = EVENT_CODE
|
||||
if (![up, down, left, right].includes(code)) return
|
||||
|
||||
// 左右上下键更换tab
|
||||
const tabList = Array.from(
|
||||
(e.currentTarget as HTMLDivElement).querySelectorAll<HTMLDivElement>(
|
||||
'[role=tab]:not(.is-disabled)'
|
||||
)
|
||||
)
|
||||
const currentIndex = tabList.indexOf(e.target as HTMLDivElement)
|
||||
|
||||
let nextIndex: number
|
||||
if (code === left || code === up) {
|
||||
// left
|
||||
if (currentIndex === 0) {
|
||||
// first
|
||||
nextIndex = tabList.length - 1
|
||||
} else {
|
||||
nextIndex = currentIndex - 1
|
||||
}
|
||||
} else {
|
||||
// right
|
||||
if (currentIndex < tabList.length - 1) {
|
||||
// not last
|
||||
nextIndex = currentIndex + 1
|
||||
} else {
|
||||
nextIndex = 0
|
||||
}
|
||||
switch (event.code) {
|
||||
case EVENT_CODE.left:
|
||||
case EVENT_CODE.up:
|
||||
step = -1
|
||||
break
|
||||
case EVENT_CODE.right:
|
||||
case EVENT_CODE.down:
|
||||
step = 1
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
const tabList = Array.from(
|
||||
(
|
||||
event.currentTarget as HTMLDivElement
|
||||
).querySelectorAll<HTMLDivElement>('[role=tab]:not(.is-disabled)')
|
||||
)
|
||||
const currentIndex = tabList.indexOf(event.target as HTMLDivElement)
|
||||
let nextIndex = currentIndex + step
|
||||
|
||||
if (nextIndex < 0) {
|
||||
nextIndex = tabList.length - 1
|
||||
} else if (nextIndex >= tabList.length) {
|
||||
nextIndex = 0
|
||||
}
|
||||
|
||||
tabList[nextIndex].focus({ preventScroll: true }) // 改变焦点元素
|
||||
tabList[nextIndex].click() // 选中下一个tab
|
||||
setFocus()
|
||||
|
@ -199,7 +199,8 @@ const Tabs = defineComponent({
|
||||
tabindex="0"
|
||||
onClick={handleTabAdd}
|
||||
onKeydown={(ev: KeyboardEvent) => {
|
||||
if (ev.code === EVENT_CODE.enter) handleTabAdd()
|
||||
if ([EVENT_CODE.enter, EVENT_CODE.numpadEnter].includes(ev.code))
|
||||
handleTabAdd()
|
||||
}}
|
||||
>
|
||||
{addSlot ? (
|
||||
|
12
packages/components/time-picker/src/time-picker-com/basic-time-spinner.vue
Normal file → Executable file
12
packages/components/time-picker/src/time-picker-com/basic-time-spinner.vue
Normal file → Executable file
@ -78,7 +78,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, ref, unref, watch } from 'vue'
|
||||
import { computed, inject, nextTick, onMounted, ref, unref, watch } from 'vue'
|
||||
import { debounce } from 'lodash-unified'
|
||||
import { vRepeatClick } from '@element-plus/directives'
|
||||
import ElScrollbar from '@element-plus/components/scrollbar'
|
||||
@ -97,6 +97,8 @@ import type { TimeUnit } from '../constants'
|
||||
import type { TimeList } from '../utils'
|
||||
|
||||
const props = defineProps(basicTimeSpinnerProps)
|
||||
const pickerBase = inject('EP_PICKER_BASE') as any
|
||||
const { isRange } = pickerBase.props
|
||||
const emit = defineEmits(['change', 'select-range', 'set-option'])
|
||||
|
||||
const ns = useNamespace('time')
|
||||
@ -135,10 +137,12 @@ const timePartials = computed<Record<TimeUnit, number>>(() => {
|
||||
|
||||
const timeList = computed(() => {
|
||||
const { hours, minutes } = unref(timePartials)
|
||||
const { role, spinnerDate } = props
|
||||
const compare = !isRange ? spinnerDate : undefined
|
||||
return {
|
||||
hours: getHoursList(props.role),
|
||||
minutes: getMinutesList(hours, props.role),
|
||||
seconds: getSecondsList(hours, minutes, props.role),
|
||||
hours: getHoursList(role, compare),
|
||||
minutes: getMinutesList(hours, role, compare),
|
||||
seconds: getSecondsList(hours, minutes, role, compare),
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -24,7 +24,7 @@ export const useTooltipTriggerProps = buildProps({
|
||||
*/
|
||||
triggerKeys: {
|
||||
type: definePropType<string[]>(Array),
|
||||
default: () => [EVENT_CODE.enter, EVENT_CODE.space],
|
||||
default: () => [EVENT_CODE.enter, EVENT_CODE.numpadEnter, EVENT_CODE.space],
|
||||
},
|
||||
} as const)
|
||||
|
||||
|
@ -99,7 +99,12 @@ export function useKeydown({ el$ }: UseKeydownOption, store: Ref<TreeStore>) {
|
||||
const hasInput = currentItem.querySelector(
|
||||
'[type="checkbox"]'
|
||||
) as Nullable<HTMLInputElement>
|
||||
if ([EVENT_CODE.enter, EVENT_CODE.space].includes(code) && hasInput) {
|
||||
if (
|
||||
[EVENT_CODE.enter, EVENT_CODE.numpadEnter, EVENT_CODE.space].includes(
|
||||
code
|
||||
) &&
|
||||
hasInput
|
||||
) {
|
||||
ev.preventDefault()
|
||||
hasInput.click()
|
||||
}
|
||||
|
@ -4,9 +4,8 @@ import { EVENT_CODE } from '@element-plus/constants'
|
||||
|
||||
let registeredEscapeHandlers: ((e: KeyboardEvent) => void)[] = []
|
||||
|
||||
const cachedHandler = (e: Event) => {
|
||||
const event = e as KeyboardEvent
|
||||
if (event.key === EVENT_CODE.esc) {
|
||||
const cachedHandler = (event: KeyboardEvent) => {
|
||||
if (event.code === EVENT_CODE.esc) {
|
||||
registeredEscapeHandlers.forEach((registeredHandler) =>
|
||||
registeredHandler(event)
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user