Merge pull request #13591 from element-plus/dev

D2M
This commit is contained in:
iamkun
2023-07-14 11:18:13 +08:00
committed by GitHub
43 changed files with 469 additions and 184 deletions

View File

@@ -1,9 +1,7 @@
{
extends: ['config:base', 'schedule:weekly', 'group:allNonMajor'],
labels: ['dependencies'],
pin: false,
rangeStrategy: 'bump',
node: false,
packageRules: [
{
depTypeList: ['peerDependencies'],

View File

@@ -1,5 +1,30 @@
## Changelog
### 2.3.8
_2023-07-14_
#### Bug fixes
- Correct sass map has-key parameter (#13132 by @wonderl17)
- Components [date-picker] One input affect the other input (#12181 by @HeftyKoo)
- Components [virtual-list] Resetting the vertical scrollbar state (#13319 by @huangguangfa)
- Components [tree] fix node can drop inner (#9770 by @init-qy)
- Style [progress] linecap value of track is consistent with path (#13395 by @btea)
- Theme-chalk [form] remove default width of Input in inline form (#13375 by @tolking)
- Components [checkbox] default after border color is transparent (#13402 by @chenxch)
- Components [table] multiple line ellipsis tooltip takes effect (#12805 by @btea)
- Style [select] tooltip positioning is incorrect (#13424 by @wuzhidexiaoming)
- Tree content checkbox height larger than item height, close #13396 (#13446 by @YunYouJun)
- Types in exports (#13443 by @zhuying547)
- Add more exports types (#13498 by @sxzz)
- Components [color-picker] fix incorrect cursor position (#13516 by @kooriookami)
- Components [input] lose focus when click suffix icon (#13264 by @tolking)
- Style(theme-chalk): [table] fix th font weight (#13556 by @kaine0923)
#### Refactors
- Components [select] override the class using computed (#13302 by @wzc520pyfm)
### 2.3.7
_2023-06-21_

View File

@@ -73,6 +73,11 @@ You can also try Element Plus out with the components built-in playground.
<img width="150px" src="https://user-images.githubusercontent.com/17680888/160634485-df0d00af-8633-4ab8-9a72-aac2b65d1d36.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.misboot.com/?from=element-plus" target="_blank">
<img width="150px" src="https://github-production-user-asset-6210df.s3.amazonaws.com/82012629/250157573-b8ab8d68-ff6b-496f-beb1-9863a545d2af.png">
</a>
</td>
</tr>
</tbody>
</table>

View File

@@ -1,4 +1,13 @@
export const rightRichTextSponsors = []
export const rightRichTextSponsors = [
{
name: 'MISBoot',
img: '/images/sponsors/MISBoot.png',
url: 'https://www.misboot.com/?from=element-plus',
slogan: 'Low-code platform for rapid development',
slogan_cn: '低代码开发平台,应用可视化快速开发',
slogan_index: '低代码开发平台,应用快速开发',
},
]
export const rightLogoSmallSponsors = [
{

View File

@@ -28,7 +28,7 @@ const detail = computed(() => apiTypingLocale[lang.value].detail)
/>
<template #content>
<slot>
<div class="m-1">
<div class="m-1" style="max-width: 600px">
<code
style="
color: var(--code-tooltip-color);

View File

@@ -25,6 +25,9 @@ const getSponsorName = (sponsor) => {
}
const getSponsorSlogan = (sponsor) => {
if (lang.value === langZhCN) {
if (sponsor.slogan_index) {
return sponsor.slogan_index
}
return sponsor.slogan_cn || sponsor.slogan
}
return sponsor.slogan

View File

@@ -46,7 +46,7 @@ const onItemClick = (item: any) => {
</div>
<div
:class="[
'm-t-2px font-400 text-12px color-#909399',
'm-t-2px font-400 text-12px color-#909399 break-all',
isDark && '!color-#A3A6AD',
]"
>

View File

@@ -23,7 +23,7 @@ $breakpoints: (
) !default;
@mixin respond-to($breakpoint) {
@if #{map.has-key($breakpoints, $breakpoints)} {
@if #{map.has-key($breakpoints, $breakpoint)} {
@media screen and (min-width: #{map.get($breakpoints, $breakpoint)}) {
@content;
}

View File

@@ -121,6 +121,7 @@ When using `modal` = false, please make sure that `append-to-body` was set to **
| fullscreen | whether the Dialog takes up full screen | boolean | — | false |
| top | value for `margin-top` of Dialog CSS | string | — | 15vh |
| modal | whether a mask is displayed | boolean | — | true |
| modal-class | custom class names for mask | string | — | — |
| append-to-body | whether to append Dialog itself to body. A nested Dialog should have this attribute set to `true` | boolean | — | false |
| lock-scroll | whether scroll of body is disabled while Dialog is displayed | boolean | — | true |
| custom-class ^(deprecated) | custom class names for Dialog | string | — | — |

View File

@@ -37,7 +37,7 @@ To prevent this behavior, you can add `@submit.prevent` on `<el-form>`.
When the vertical space is limited and the form is relatively simple, you can put it in one line.
:::demo Set the `inline` attribute to `true` and the form will be inline. After ^(2.3.6), the ElInput component will get a fixed width to avoid the width change caused by the clearable icon.
:::demo Set the `inline` attribute to `true` and the form will be inline.
form/inline-form
@@ -125,25 +125,24 @@ form/accessibility
### Form Attributes
| Name | Description | Type | Default |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------- |
| model | Data of form component. | ^[object]`Record<string, any>` | — |
| rules | Validation rules of form. | ^[object]`FormRules` | — |
| inline | Whether the form is inline. | ^[boolean] | false |
| label-position | Position of label. If set to `'left'` or `'right'`, `label-width` prop is also required. | ^[enum]`'left' \| 'right' \| 'top'` | right |
| label-width | Width of label, e.g. `'50px'`. All its direct child form items will inherit this value. `auto` is supported. | ^[string] / ^[number] | '' |
| label-suffix | Suffix of the label. | ^[string] | '' |
| hide-required-asterisk | Whether to hide required fields should have a red asterisk (star) beside their labels. | ^[boolean] | false |
| require-asterisk-position | Position of asterisk. | ^[enum]`'left' \| 'right'` | left |
| show-message | Whether to show the error message. | ^[boolean] | true |
| inline-message | Whether to display the error message inline with the form item. | ^[boolean] | false |
| status-icon | Whether to display an icon indicating the validation result. | ^[boolean] | false |
| validate-on-rule-change | Whether to trigger validation when the `rules` prop is changed. | ^[boolean] | true |
| size | Control the size of components in this form. | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | — |
| disabled | Whether to disable all components in this form. If set to `true`, it will override the `disabled` prop of the inner component. | ^[boolean] | false |
| scroll-to-error | When validation fails, scroll to the first error form entry. | ^[boolean] | false |
| scroll-into-view-options | When validation fails, it scrolls to the first error item based on the scrollIntoView option. [scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView). | ^[object]`Record<string, any>` / ^[boolean] | — |
| Name | Description | Type | Default |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------- |
| model | Data of form component. | ^[object]`Record<string, any>` | — |
| rules | Validation rules of form. | ^[object]`FormRules` | — |
| inline | Whether the form is inline. | ^[boolean] | false |
| label-position | Position of label. If set to `'left'` or `'right'`, `label-width` prop is also required. | ^[enum]`'left' \| 'right' \| 'top'` | right |
| label-width | Width of label, e.g. `'50px'`. All its direct child form items will inherit this value. `auto` is supported. | ^[string] / ^[number] | '' |
| label-suffix | Suffix of the label. | ^[string] | '' |
| hide-required-asterisk | Whether to hide required fields should have a red asterisk (star) beside their labels. | ^[boolean] | false |
| require-asterisk-position | Position of asterisk. | ^[enum]`'left' \| 'right'` | left |
| show-message | Whether to show the error message. | ^[boolean] | true |
| inline-message | Whether to display the error message inline with the form item. | ^[boolean] | false |
| status-icon | Whether to display an icon indicating the validation result. | ^[boolean] | false |
| validate-on-rule-change | Whether to trigger validation when the `rules` prop is changed. | ^[boolean] | true |
| size | Control the size of components in this form. | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | — |
| disabled | Whether to disable all components in this form. If set to `true`, it will override the `disabled` prop of the inner component. | ^[boolean] | false |
| scroll-to-error | When validation fails, scroll to the first error form entry. | ^[boolean] | false |
| scroll-into-view-options | When validation fails, it scrolls to the first error item based on the scrollIntoView option. [scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView). | ^[object]`Record<string, any>` / ^[boolean] | — |
### Form Events
@@ -237,6 +236,6 @@ interface FormItemRule extends RuleItem {
trigger?: Arrayable<string>
}
type FormRules = Partial<Record<string, Arrayable<FormItemRule>>>
```
</details>

View File

@@ -93,7 +93,7 @@ image/image-preview
| ----------- | -------------------------------------------------------- |
| placeholder | custom placeholder content when image hasn't loaded yet. |
| error | custom image load failed content. |
| viewer | description of the image. |
| viewer | custom content when image preview. |
## Image Viewer API

View File

@@ -183,3 +183,15 @@ input/length-limiting
| select | select the text in input element | ^[Function]`() => void` |
| textarea | HTML textarea element | ^[object]`Ref<HTMLTextAreaElement>` |
| textareaStyle | style of textarea | ^[object]`Ref<StyleValue>` |
## FAQ
#### Why is the width of the ElInput component expanded by clearable?
Typical issue: [#7287](https://github.com/element-plus/element-plus/issues/7287)
PS: Since the ElInput component does not have a default width, when the clearable icon is displayed, the width of the component will be expanded, which can be solved by setting width.
```vue
<el-input v-model="input" clearable style="width: 200px" />
```

View File

@@ -154,41 +154,43 @@ import { ElMessageBox } from 'element-plus'
The corresponding methods are: `ElMessageBox`, `ElMessageBox.alert`, `ElMessageBox.confirm` and `ElMessageBox.prompt`. The parameters are the same as above.
## Options
## API
| Attribute | Description | Type | Accepted Values | Default |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------ |
| autofocus | auto focus when open MessageBox | boolean | — | true |
| title | title of the MessageBox | string | — | — |
| message | content of the MessageBox | `string \| VNode \| (() => VNode)` | — | — |
| dangerouslyUseHTMLString | whether `message` is treated as HTML string | boolean | | false |
| type | message type, used for icon display | string | success / info / warning / error | — |
| icon | custom icon component, overrides `type` | `string \| Component` | — | — |
| custom-class | custom class name for MessageBox | string | — | |
| custom-style | custom inline style for MessageBox | CSSProperties | — | — |
| callback | MessageBox closing callback if you don't prefer Promise | function(action, instance), where `action` can be 'confirm', 'cancel' or 'close', and `instance` is the MessageBox instance. You can access to that instance's attributes and methods | — | — |
| showClose | whether to show close icon of MessageBox | boolean | — | true |
| before-close | callback before MessageBox closes, and it will prevent MessageBox from closing | function(action, instance, done), where `action` can be 'confirm', 'cancel' or 'close'; `instance` is the MessageBox instance, and you can access to that instance's attributes and methods; `done` is for closing the instance | — | |
| distinguish-cancel-and-close | whether to distinguish canceling and closing the MessageBox | boolean | — | false |
| lock-scroll | whether to lock body scroll when MessageBox prompts | boolean | — | true |
| show-cancel-button | whether to show a cancel button | boolean | | false (true when called with confirm and prompt) |
| show-confirm-button | whether to show a confirm button | boolean | — | true |
| cancel-button-text | text content of cancel button | string | — | Cancel |
| confirm-button-text | text content of confirm button | string | — | OK |
| cancel-button-class | custom class name of cancel button | string | — | — |
| confirm-button-class | custom class name of confirm button | string | — | — |
| close-on-click-modal | whether MessageBox can be closed by clicking the mask | boolean | — | true (false when called with alert) |
| close-on-press-escape | whether MessageBox can be closed by pressing the ESC | boolean | — | true (false when called with alert) |
| close-on-hash-change | whether to close MessageBox when hash changes | boolean | — | true |
| show-input | whether to show an input | boolean | — | false (true when called with prompt) |
| input-placeholder | placeholder of input | string | — | — |
| input-type | type of input | string | — | text |
| input-value | initial value of input | string | — | — |
| input-pattern | regexp for the input | regexp | — | — |
| input-validator | validation function for the input. Should returns a boolean or string. If a string is returned, it will be assigned to inputErrorMessage | function | — | — |
| input-error-message | error message when validation fails | string | | Illegal input |
| center | whether to align the content in center | boolean | — | false |
| draggable | whether MessageBox is draggable | boolean | — | false |
| round-button | whether to use round button | boolean | — | false |
| button-size | custom size of confirm and cancel buttons | string | small / default / large | default |
| append-to | set the root element for the message box | string \| HTMLElement | | — |
### Options
| Name | Description | Type | Default |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------ |
| autofocus | auto focus when open MessageBox | ^[boolean] | true |
| title | title of the MessageBox | ^[string] | '' |
| message | content of the MessageBox | ^[string] / ^[VNode] / ^[Function]`() => VNode` | — |
| dangerouslyUseHTMLString | whether `message` is treated as HTML string | ^[boolean] | false |
| type | message type, used for icon display | ^[enum]`'success' \| 'info' \| 'warning' \| 'error'` | '' |
| icon | custom icon component, overrides `type` | ^[string] / ^[Component] | '' |
| custom-class | custom class name for MessageBox | ^[string] | '' |
| custom-style | custom inline style for MessageBox | ^[CSSProperties] | {} |
| 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 |
| before-close | callback before MessageBox closes, and it will prevent MessageBox from closing | ^[Function]`(action: Action, instance: MessageBoxState, done: () => void) => void` | null |
| distinguish-cancel-and-close | whether to distinguish canceling and closing the MessageBox | ^[boolean] | false |
| lock-scroll | whether to lock body scroll when MessageBox prompts | ^[boolean] | true |
| show-cancel-button | whether to show a cancel button | ^[boolean] | false (true when called with confirm and prompt) |
| show-confirm-button | whether to show a confirm button | ^[boolean] | true |
| cancel-button-text | text content of cancel button | ^[string] | Cancel |
| confirm-button-text | text content of confirm button | ^[string] | OK |
| cancel-button-class | custom class name of cancel button | ^[string] | '' |
| confirm-button-class | custom class name of confirm button | ^[string] | '' |
| close-on-click-modal | whether MessageBox can be closed by clicking the mask | ^[boolean] | true (false when called with alert) |
| close-on-press-escape | whether MessageBox can be closed by pressing the ESC | ^[boolean] | true (false when called with alert) |
| close-on-hash-change | whether to close MessageBox when hash changes | ^[boolean] | true |
| show-input | whether to show an input | ^[boolean] | false (true when called with prompt) |
| input-placeholder | placeholder of input | ^[string] | '' |
| input-type | type of input | ^[string] | text |
| input-value | initial value of input | ^[string] | null |
| input-pattern | regexp for the input | ^[regexp] | null | |
| input-validator | validation function for the input. Should returns a boolean or string. If a string is returned, it will be assigned to inputErrorMessage | ^[Function]`(value: string) => boolea \| string` | null |
| input-error-message | error message when validation fails | ^[string] | Illegal input |
| center | whether to align the content in center | ^[boolean] | false |
| draggable | whether MessageBox is draggable | ^[boolean] | false |
| round-button | whether to use round button | ^[boolean] | false |
| button-size | custom size of confirm and cancel buttons | ^[string]`'small' \| 'default' \| 'large'` | default |
| append-to | set the root element for the message box | ^[string] / ^[HTMLElement] | - |

View File

@@ -81,22 +81,22 @@ progress/striped-progress
### Attributes
| Name | Description | Type | Default |
| -------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------- |
| percentage | percentage, **required** | ^[number]`(0-100)` | 0 |
| type | the type of progress bar | ^[enum]`'line' \| 'circle' \| 'dashboard'` | line |
| stroke-width | the width of progress bar | ^[number] | 6 |
| text-inside | whether to place the percentage inside progress bar, only works when `type` is 'line' | ^[boolean] | false |
| status | the current status of progress bar | ^[enum]`'success' \| 'exception' \| 'warning'` | — |
| indeterminate | set indeterminate progress | ^[boolean] | false |
| duration | control the animation duration of indeterminate progress or striped flow progress | ^[number] | 3 |
| color | background color of progress bar. Overrides `status` prop | ^[string] / ^[function]`(percentage: number) => string` / ^[Array]`{ color: string; percentage: number }[]` | '' |
| width | the canvas width of circle progress bar | ^[number] | 126 |
| show-text | whether to show percentage | ^[boolean] | true |
| stroke-linecap | circle/dashboard type shape at the end path | ^[enum]`'butt' \| 'round' \| 'square'` | round |
| format | custom text format | ^[Function]`(percentage: number) => string` | — |
| striped ^(2.3.4) | stripe over the progress bar's color | ^[boolean] | false |
| striped-flow ^(2.3.4) | get the stripes to flow | ^[boolean] | false |
| Name | Description | Type | Default |
| --------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------- |
| percentage | percentage, **required** | ^[number]`(0-100)` | 0 |
| type | the type of progress bar | ^[enum]`'line' \| 'circle' \| 'dashboard'` | line |
| stroke-width | the width of progress bar | ^[number] | 6 |
| text-inside | whether to place the percentage inside progress bar, only works when `type` is 'line' | ^[boolean] | false |
| status | the current status of progress bar | ^[enum]`'success' \| 'exception' \| 'warning'` | — |
| indeterminate | set indeterminate progress | ^[boolean] | false |
| duration | control the animation duration of indeterminate progress or striped flow progress | ^[number] | 3 |
| color | background color of progress bar. Overrides `status` prop | ^[string] / ^[function]`(percentage: number) => string` / ^[Array]`{ color: string; percentage: number }[]` | '' |
| width | the canvas width of circle progress bar | ^[number] | 126 |
| show-text | whether to show percentage | ^[boolean] | true |
| stroke-linecap | circle/dashboard type shape at the end path | ^[enum]`'butt' \| 'round' \| 'square'` | round |
| format | custom text format | ^[Function]`(percentage: number) => string` | — |
| striped ^(2.3.4) | stripe over the progress bar's color | ^[boolean] | false |
| striped-flow ^(2.3.4) | get the stripes to flow | ^[boolean] | false |
### Slots

View File

@@ -40,3 +40,9 @@ const onSubmit = () => {
console.log('submit!')
}
</script>
<style>
.demo-form-inline .el-input {
--el-input-width: 220px;
}
</style>

View File

@@ -8,10 +8,6 @@
</div>
</template>
<style scoped>
.demo-progress .el-progress--line {
margin-bottom: 15px;
width: 350px;
}
.demo-progress .el-progress--circle {
margin-right: 15px;
}

View File

@@ -6,6 +6,7 @@ interface Tree {
id: string
label: string
children?: Tree[]
disabled: boolean
}
const getKey = (prefix: string, id: number) => {
@@ -32,6 +33,7 @@ const createData = (
children: childrenNumber
? createData(maxDeep, maxChildren, childrenNumber, deep + 1, nodeKey)
: undefined,
disabled: nodeKey.includes('2'),
}
})
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -260,7 +260,7 @@ describe('Color-picker', () => {
await wrapper.find('.el-color-picker__trigger').trigger('click')
const colorPickerWrapper = wrapper.findComponent(ColorPicker)
const svPanelWrapper = colorPickerWrapper.findComponent({ ref: 'svPanel' })
const svPanelWrapper = colorPickerWrapper.findComponent({ ref: 'sv' })
;(svPanelWrapper.vm as ColorPickerVM).handleDrag({
type: 'mousemove',
clientX: 0,

View File

@@ -17,7 +17,7 @@
<div v-click-outside="hide">
<div :class="ns.be('dropdown', 'main-wrapper')">
<hue-slider ref="hue" class="hue-slider" :color="color" vertical />
<sv-panel ref="svPanel" :color="color" />
<sv-panel ref="sv" :color="color" />
</div>
<alpha-slider v-if="showAlpha" ref="alpha" :color="color" />
<predefine

View File

@@ -543,7 +543,6 @@ const handleMaxTimeClose = () => {
const handleDateInput = (value: string | null, type: ChangeType) => {
dateUserInput.value[type] = value
const parsedValueD = dayjs(value, dateFormat.value).locale(lang.value)
if (parsedValueD.isValid()) {
if (disabledDate && disabledDate(parsedValueD.toDate())) {
return
@@ -554,7 +553,10 @@ const handleDateInput = (value: string | null, type: ChangeType) => {
.year(parsedValueD.year())
.month(parsedValueD.month())
.date(parsedValueD.date())
if (!props.unlinkPanels) {
if (
!props.unlinkPanels &&
(!maxDate.value || maxDate.value.isBefore(minDate.value))
) {
rightDate.value = parsedValueD.add(1, 'month')
maxDate.value = minDate.value.add(1, 'month')
}
@@ -564,7 +566,10 @@ const handleDateInput = (value: string | null, type: ChangeType) => {
.year(parsedValueD.year())
.month(parsedValueD.month())
.date(parsedValueD.date())
if (!props.unlinkPanels) {
if (
!props.unlinkPanels &&
(!minDate.value || minDate.value.isAfter(maxDate.value))
) {
leftDate.value = parsedValueD.subtract(1, 'month')
minDate.value = maxDate.value.subtract(1, 'month')
}

View File

@@ -15,10 +15,10 @@
<slot name="prepend" />
</div>
<div :class="wrapperKls">
<div ref="wrapperRef" :class="wrapperKls">
<!-- prefix slot -->
<span v-if="$slots.prefix || prefixIcon" :class="nsInput.e('prefix')">
<span :class="nsInput.e('prefix-inner')" @click="focus">
<span :class="nsInput.e('prefix-inner')">
<slot name="prefix" />
<el-icon v-if="prefixIcon" :class="nsInput.e('icon')">
<component :is="prefixIcon" />
@@ -54,7 +54,7 @@
<!-- suffix slot -->
<span v-if="suffixVisible" :class="nsInput.e('suffix')">
<span :class="nsInput.e('suffix-inner')" @click="focus">
<span :class="nsInput.e('suffix-inner')">
<template
v-if="!showClear || !showPwdVisible || !isWordLimitVisible"
>
@@ -172,7 +172,12 @@ import {
isKorean,
isObject,
} from '@element-plus/utils'
import { useAttrs, useCursor, useNamespace } from '@element-plus/hooks'
import {
useAttrs,
useCursor,
useFocusController,
useNamespace,
} from '@element-plus/hooks'
import { UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { calcTextareaHeight } from './utils'
import { inputEmits, inputProps } from './input'
@@ -220,7 +225,7 @@ const containerKls = computed(() => [
const wrapperKls = computed(() => [
nsInput.e('wrapper'),
nsInput.is('focus', focused.value),
nsInput.is('focus', isFocused.value),
])
const attrs = useAttrs({
@@ -240,7 +245,6 @@ const nsTextarea = useNamespace('textarea')
const input = shallowRef<HTMLInputElement>()
const textarea = shallowRef<HTMLTextAreaElement>()
const focused = ref(false)
const hovering = ref(false)
const isComposing = ref(false)
const passwordVisible = ref(false)
@@ -249,6 +253,17 @@ const textareaCalcStyle = shallowRef(props.inputStyle)
const _ref = computed(() => input.value || textarea.value)
const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
_ref,
{
afterBlur() {
if (props.validateEvent) {
formItem?.validate?.('blur').catch((err) => debugWarn(err))
}
},
}
)
const needStatusIcon = computed(() => form?.statusIcon ?? false)
const validateState = computed(() => formItem?.validateState || '')
const validateIcon = computed(
@@ -275,7 +290,7 @@ const showClear = computed(
!inputDisabled.value &&
!props.readonly &&
!!nativeInputValue.value &&
(focused.value || hovering.value)
(isFocused.value || hovering.value)
)
const showPwdVisible = computed(
() =>
@@ -283,7 +298,7 @@ const showPwdVisible = computed(
!inputDisabled.value &&
!props.readonly &&
!!nativeInputValue.value &&
(!!nativeInputValue.value || focused.value)
(!!nativeInputValue.value || isFocused.value)
)
const isWordLimitVisible = computed(
() =>
@@ -445,19 +460,6 @@ const focus = async () => {
const blur = () => _ref.value?.blur()
const handleFocus = (event: FocusEvent) => {
focused.value = true
emit('focus', event)
}
const handleBlur = (event: FocusEvent) => {
focused.value = false
emit('blur', event)
if (props.validateEvent) {
formItem?.validate?.('blur').catch((err) => debugWarn(err))
}
}
const handleMouseLeave = (evt: MouseEvent) => {
hovering.value = false
emit('mouseleave', evt)

View File

@@ -49,6 +49,7 @@
:class="ns.be('circle', 'track')"
:d="trackPath"
:stroke="`var(${ns.cssVarName('fill-color-light')}, #e5e9f2)`"
:stroke-linecap="strokeLinecap"
:stroke-width="relativeStrokeWidth"
fill="none"
:style="trailPathStyle"

View File

@@ -1,14 +1,7 @@
<template>
<li
v-show="visible"
:class="[
ns.be('dropdown', 'item'),
ns.is('disabled', isDisabled),
{
selected: itemSelected,
hover,
},
]"
:class="containerKls"
@mouseenter="hoverItem"
@click.stop="selectOptionClick"
>
@@ -21,12 +14,14 @@
<script lang="ts">
// @ts-nocheck
import {
computed,
defineComponent,
getCurrentInstance,
nextTick,
onBeforeUnmount,
reactive,
toRefs,
unref,
} from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { useOption } from './useOption'
@@ -51,6 +46,16 @@ export default defineComponent({
setup(props) {
const ns = useNamespace('select')
const containerKls = computed(() => [
ns.be('dropdown', 'item'),
ns.is('disabled', unref(isDisabled)),
{
selected: unref(itemSelected),
hover: unref(hover),
},
])
const states = reactive({
index: -1,
groupDisabled: false,
@@ -92,6 +97,7 @@ export default defineComponent({
return {
ns,
containerKls,
currentLabel,
itemSelected,
isDisabled,

View File

@@ -33,22 +33,14 @@
<div
v-if="multiple"
ref="tags"
:class="[
nsSelect.e('tags'),
nsSelect.is('disabled', selectDisabled),
]"
:class="tagsKls"
:style="selectTagsStyle"
>
<transition
v-if="collapseTags && selected.length"
@after-leave="resetInputHeight"
>
<span
:class="[
nsSelect.b('tags-wrapper'),
{ 'has-prefix': prefixWidth && selected.length },
]"
>
<span :class="tagWrapperKls">
<el-tag
v-for="item in showTagList"
:key="getValueKey(item)"
@@ -72,6 +64,7 @@
>
<el-tooltip
v-if="collapseTagsTooltip"
ref="tagTooltipRef"
:disabled="dropMenuVisible"
:fallback-placements="['bottom', 'top', 'right', 'left']"
:effect="effect"
@@ -98,7 +91,7 @@
:type="tagType"
disable-transitions
:style="{ margin: '2px' }"
@close="deleteTag($event, item)"
@close="handleDeleteTooltipTag($event, item)"
>
<span
:class="nsSelect.e('tags-text')"
@@ -120,10 +113,7 @@
</transition>
<transition v-if="!collapseTags" @after-leave="resetInputHeight">
<span
:class="[
nsSelect.b('tags-wrapper'),
{ 'has-prefix': prefixWidth && selected.length },
]"
:class="tagWrapperKls"
:style="
prefixWidth && selected.length
? { marginLeft: `${prefixWidth}px` }
@@ -153,19 +143,10 @@
ref="input"
v-model="query"
type="text"
:class="[
nsSelect.e('input'),
nsSelect.is(selectSize),
nsSelect.is('disabled', selectDisabled),
]"
:class="inputKls"
:disabled="selectDisabled"
:autocomplete="autocomplete"
:style="{
marginLeft: `${prefixWidth}px`,
flexGrow: 1,
width: `${inputLength / (inputWidth - 32)}%`,
maxWidth: `${inputWidth - 42}px`,
}"
:style="inputStyle"
@focus="handleFocus"
@blur="handleBlur"
@keyup="managePlaceholder"
@@ -186,11 +167,7 @@
<input
v-if="isIOS && !multiple && filterable && readonly"
ref="iOSInput"
:class="[
nsSelect.e('input'),
nsSelect.is(selectSize),
nsSelect.em('input', 'iOS'),
]"
:class="iOSInputKls"
:disabled="selectDisabled"
type="text"
/>
@@ -263,12 +240,7 @@
tag="ul"
:wrap-class="nsSelect.be('dropdown', 'wrap')"
:view-class="nsSelect.be('dropdown', 'list')"
:class="[
nsSelect.is(
'empty',
!allowCreate && Boolean(query) && filteredOptionsCount === 0
),
]"
:class="scrollbarKls"
>
<el-option v-if="showNewOption" :value="query" :created="true" />
<el-options @update-options="onOptionsRendered">
@@ -499,12 +471,14 @@ export default defineComponent({
selectOption,
getValueKey,
navigateOptions,
handleDeleteTooltipTag,
dropMenuVisible,
reference,
input,
iOSInput,
tooltipRef,
tagTooltipRef,
tags,
selectWrapper,
scrollbar,
@@ -549,6 +523,37 @@ export default defineComponent({
return classList
})
const tagsKls = computed(() => [
nsSelect.e('tags'),
nsSelect.is('disabled', unref(selectDisabled)),
])
const tagWrapperKls = computed(() => [
nsSelect.b('tags-wrapper'),
{ 'has-prefix': unref(prefixWidth) && unref(selected).length },
])
const inputKls = computed(() => [
nsSelect.e('input'),
nsSelect.is(unref(selectSize)),
nsSelect.is('disabled', unref(selectDisabled)),
])
const iOSInputKls = computed(() => [
nsSelect.e('input'),
nsSelect.is(unref(selectSize)),
nsSelect.em('input', 'iOS'),
])
const scrollbarKls = computed(() => [
nsSelect.is(
'empty',
!props.allowCreate &&
Boolean(unref(query)) &&
unref(filteredOptionsCount) === 0
),
])
const selectTagsStyle = computed(() => ({
maxWidth: `${unref(inputWidth) - 32}px`,
width: '100%',
@@ -562,6 +567,13 @@ export default defineComponent({
return { maxWidth: `${maxWidth}px` }
})
const inputStyle = computed(() => ({
marginLeft: `${unref(prefixWidth)}px`,
flexGrow: 1,
width: `${unref(inputLength) / (unref(inputWidth) - 32)}%`,
maxWidth: `${unref(inputWidth) - 42}px`,
}))
provide(
selectKey,
reactive({
@@ -640,6 +652,7 @@ export default defineComponent({
debouncedQueryChange,
deletePrevTag,
deleteTag,
handleDeleteTooltipTag,
deleteSelected,
handleOptionSelect,
scrollToOption,
@@ -691,13 +704,20 @@ export default defineComponent({
scrollbar,
wrapperKls,
tagsKls,
tagWrapperKls,
inputKls,
iOSInputKls,
scrollbarKls,
selectTagsStyle,
nsSelect,
tagTextStyle,
inputStyle,
handleMouseEnter,
handleMouseLeave,
showTagList,
collapseTagList,
tagTooltipRef,
}
},
})

View File

@@ -87,6 +87,7 @@ export const useSelect = (props, states: States, ctx) => {
const input = ref<HTMLInputElement | null>(null)
const iOSInput = ref<HTMLInputElement | null>(null)
const tooltipRef = ref<InstanceType<typeof ElTooltip> | null>(null)
const tagTooltipRef = ref<InstanceType<typeof ElTooltip> | null>(null)
const tags = ref<HTMLElement | null>(null)
const selectWrapper = ref<HTMLElement | null>(null)
const scrollbar = ref<{
@@ -916,7 +917,10 @@ export const useSelect = (props, states: States, ctx) => {
const handleMouseLeave = () => {
states.mouseEnter = false
}
const handleDeleteTooltipTag = (event, tag) => {
deleteTag(event, tag)
tagTooltipRef.value?.updatePopper?.()
}
return {
optionList,
optionsArray,
@@ -956,6 +960,7 @@ export const useSelect = (props, states: States, ctx) => {
selectOption,
getValueKey,
navigateOptions,
handleDeleteTooltipTag,
dropMenuVisible,
queryChange,
groupQueryChange,
@@ -967,6 +972,7 @@ export const useSelect = (props, states: States, ctx) => {
input,
iOSInput,
tooltipRef,
tagTooltipRef,
tags,
selectWrapper,
scrollbar,

View File

@@ -1632,4 +1632,31 @@ describe('Table.vue', () => {
// 5 rows and 2 columns should be 10
expect(findTooltipEl).toEqual(10)
})
it('add show-overflow-tooltip to table and table-column', async () => {
const wrapper = mount({
components: {
ElTable,
ElTableColumn,
},
template: `
<el-table :data="testData" show-overflow-tooltip>
<el-table-column prop="name" label="name" :show-overflow-tooltip="false" />
<el-table-column prop="release" label="release" />
</el-table>
`,
data() {
const testData = getTestData() as any
return {
testData,
}
},
})
await doubleWait()
const findTooltipEl = wrapper.findAll('.el-tooltip').length
expect(findTooltipEl).toEqual(5)
})
})

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { h, inject, ref } from 'vue'
import { debounce } from 'lodash-unified'
import { getStyle, hasClass } from '@element-plus/utils'
import { hasClass } from '@element-plus/utils'
import { useZIndex } from '@element-plus/hooks'
import { createTablePopper, getCell, getColumnByCell } from '../util'
import { TABLE_INJECTION_KEY } from '../tokens'
@@ -49,6 +49,19 @@ function useEvents<T>(props: Partial<TableBodyProps<T>>) {
const handleMouseLeave = debounce(() => {
props.store.commit('setHoverRow', null)
}, 30)
const getPadding = (el: HTMLElement) => {
const style = window.getComputedStyle(el, null)
const paddingLeft = Number.parseInt(style.paddingLeft, 10) || 0
const paddingRight = Number.parseInt(style.paddingRight, 10) || 0
const paddingTop = Number.parseInt(style.paddingTop, 10) || 0
const paddingBottom = Number.parseInt(style.paddingBottom, 10) || 0
return {
left: paddingLeft,
right: paddingRight,
top: paddingTop,
bottom: paddingBottom,
}
}
const handleCellMouseEnter = (
event: MouseEvent,
row: T,
@@ -103,11 +116,13 @@ function useEvents<T>(props: Partial<TableBodyProps<T>>) {
* - Actual: 188.00000762939453
*/
const rangeWidth = Math.round(range.getBoundingClientRect().width)
const padding =
(Number.parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
(Number.parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0)
const rangeHeight = Math.round(range.getBoundingClientRect().height)
const { top, left, right, bottom } = getPadding(cellChild)
const horizontalPadding = left + right
const verticalPadding = top + bottom
if (
rangeWidth + padding > cellChild.offsetWidth ||
rangeWidth + horizontalPadding > cellChild.offsetWidth ||
rangeHeight + verticalPadding > cellChild.offsetHeight ||
cellChild.scrollWidth > cellChild.offsetWidth
) {
createTablePopper(

View File

@@ -1,6 +1,5 @@
import '@element-plus/components/base/style/css'
import '@element-plus/theme-chalk/el-table.css'
import '@element-plus/components/checkbox/style/css'
import '@element-plus/components/tag/style/css'
import '@element-plus/components/tooltip/style/css'
import '@element-plus/components/scrollbar/style/css'

View File

@@ -1,6 +1,5 @@
import '@element-plus/components/base/style'
import '@element-plus/theme-chalk/src/table.scss'
import '@element-plus/components/checkbox/style'
import '@element-plus/components/tag/style'
import '@element-plus/components/tooltip/style'
import '@element-plus/components/scrollbar/style'

View File

@@ -110,7 +110,10 @@ export function useDragNodeHandler({ props, ctx, el$, dropIndicator$, store }) {
dropNext = false
}
const targetPosition = dropNode.$el.getBoundingClientRect()
// find target node without children, just calc content node height
const targetPosition = dropNode.$el
.querySelector(`.${ns.be('node', 'content')}`)
.getBoundingClientRect()
const treePosition = el$.value.getBoundingClientRect()
let dropType: NodeDropType

View File

@@ -339,7 +339,7 @@ const createGrid = ({
},
(x: number, y: number) => {
hScrollbar.value?.onMouseUp?.()
hScrollbar.value?.onMouseUp?.()
vScrollbar.value?.onMouseUp?.()
const width = unref(parsedWidth)
const height = unref(parsedHeight)
scrollTo({

View File

@@ -24,12 +24,36 @@
"import": "./es/index.mjs",
"require": "./lib/index.js"
},
"./es": "./es/index.mjs",
"./lib": "./lib/index.js",
"./es/*.mjs": "./es/*.mjs",
"./es/*": "./es/*.mjs",
"./lib/*.js": "./lib/*.js",
"./lib/*": "./lib/*.js",
"./es": {
"types": "./es/index.d.ts",
"import": "./es/index.mjs"
},
"./lib": {
"types": "./lib/index.d.ts",
"require": "./lib/index.js"
},
"./es/*.mjs": {
"types": "./es/*.d.ts",
"import": "./es/*.mjs"
},
"./es/*": {
"types": [
"./es/*.d.ts",
"./es/*/index.d.ts"
],
"import": "./es/*.mjs"
},
"./lib/*.js": {
"types": "./lib/*.d.ts",
"require": "./lib/*.js"
},
"./lib/*": {
"types": [
"./lib/*.d.ts",
"./lib/*/index.d.ts"
],
"require": "./lib/*.js"
},
"./*": "./*"
},
"unpkg": "dist/index.full.js",

View File

@@ -0,0 +1,55 @@
import { nextTick, ref } from 'vue'
import { mount } from '@vue/test-utils'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { useFocusController } from '../use-focus-controller'
describe('useFocusController', () => {
afterEach(() => {
vi.restoreAllMocks()
})
it('it will avoid trigger unnecessary blur event', async () => {
const focusHandler = vi.fn()
const blurHandler = vi.fn()
const wrapper = mount({
emits: ['focus', 'blur'],
setup() {
const targetRef = ref()
const { wrapperRef, isFocused, handleFocus, handleBlur } =
useFocusController(targetRef, {
afterFocus: focusHandler,
afterBlur: blurHandler,
})
return () => (
<div ref={wrapperRef}>
<input ref={targetRef} onFocus={handleFocus} onBlur={handleBlur} />
<span>{String(isFocused.value)}</span>
</div>
)
},
})
await nextTick()
expect(wrapper.find('span').text()).toBe('false')
expect(wrapper.find('div').attributes('tabindex')).toBe('-1')
expect(focusHandler).toHaveBeenCalledTimes(0)
expect(blurHandler).toHaveBeenCalledTimes(0)
await wrapper.find('input').trigger('focus')
expect(wrapper.emitted()).toHaveProperty('focus')
expect(wrapper.find('span').text()).toBe('true')
expect(focusHandler).toHaveBeenCalledTimes(1)
expect(blurHandler).toHaveBeenCalledTimes(0)
await wrapper.find('span').trigger('click')
expect(wrapper.emitted()).not.toHaveProperty('blur')
expect(focusHandler).toHaveBeenCalledTimes(1)
expect(blurHandler).toHaveBeenCalledTimes(0)
await wrapper.find('input').trigger('blur')
expect(wrapper.emitted()).toHaveProperty('blur')
expect(wrapper.find('span').text()).toBe('false')
expect(blurHandler).toHaveBeenCalledTimes(1)
})
})

View File

@@ -26,3 +26,4 @@ export * from './use-floating'
export * from './use-cursor'
export * from './use-ordered-children'
export * from './use-size'
export * from './use-focus-controller'

View File

@@ -0,0 +1,60 @@
import { getCurrentInstance, ref, shallowRef, watch } from 'vue'
import { useEventListener } from '@vueuse/core'
import type { ShallowRef } from 'vue'
interface UseFocusControllerOptions {
afterFocus?: () => void
afterBlur?: () => void
}
export function useFocusController<T extends HTMLElement>(
target: ShallowRef<T | undefined>,
{ afterFocus, afterBlur }: UseFocusControllerOptions = {}
) {
const instance = getCurrentInstance()!
const { emit } = instance
const wrapperRef = shallowRef<HTMLElement>()
const isFocused = ref(false)
const handleFocus = (event: FocusEvent) => {
if (isFocused.value) return
isFocused.value = true
emit('focus', event)
afterFocus?.()
}
const handleBlur = (event: FocusEvent) => {
if (
event.relatedTarget &&
wrapperRef.value?.contains(event.relatedTarget as Node)
)
return
isFocused.value = false
emit('blur', event)
afterBlur?.()
}
const handleClick = () => {
target.value?.focus()
}
watch(wrapperRef, (el) => {
if (el) {
el.setAttribute('role', 'button')
el.setAttribute('tabindex', '-1')
}
})
// TODO: using useEventListener will fail the test
// useEventListener(target, 'focus', handleFocus)
// useEventListener(target, 'blur', handleBlur)
useEventListener(wrapperRef, 'click', handleClick)
return {
wrapperRef,
isFocused,
handleFocus,
handleBlur,
}
}

View File

@@ -26,7 +26,7 @@
map.get($button, 'bg-color')
);
border: getCssVar('border');
border-left-color:transparent;
border-left-color: transparent;
color: var(
#{getCssVarName('button-text-color')},
map.get($button, 'text-color')

View File

@@ -49,7 +49,10 @@ $checkbox-bordered-input-width: map.merge(
white-space: nowrap;
user-select: none;
margin-right: 30px;
height: map.get($checkbox-height, 'default');
height: getCssVarWithDefault(
'checkbox-height',
map.get($checkbox-height, 'default')
);
@include when(disabled) {
cursor: not-allowed;
@@ -167,6 +170,7 @@ $checkbox-bordered-input-width: map.merge(
&::after {
transform: rotate(45deg) scaleY(1);
border-color: getCssVar('checkbox-checked-icon-color');
}
}
@@ -226,7 +230,7 @@ $checkbox-bordered-input-width: map.merge(
&::after {
box-sizing: content-box;
content: '';
border: 1px solid getCssVar('checkbox-checked-icon-color');
border: 1px solid transparent;
border-left: 0;
border-top: 0;
height: 7px;

View File

@@ -950,6 +950,7 @@ $text-font-size: map.merge(
$tree: () !default;
$tree: map.merge(
(
'node-content-height': 26px,
'node-hover-bg-color': getCssVar('fill-color', 'light'),
'text-color': getCssVar('text-color-regular'),
'expand-icon-color': getCssVar('text-color-placeholder'),

View File

@@ -91,13 +91,6 @@ $form-item-label-top-margin-bottom: map.merge(
display: block;
}
}
.#{$namespace}-input {
@include set-css-var-value(
'input-width',
getCssVar('form-inline-content-width')
);
}
}
@each $size in (large, default, small) {

View File

@@ -180,6 +180,7 @@
'input-border-radius',
map.get($input, 'border-radius')
);
cursor: text;
transition: getCssVar('transition-box-shadow');
transform: translate3d(0, 0, 0);
@include inset-input-border(

View File

@@ -123,7 +123,10 @@
thead {
color: getCssVar('table-header-text-color');
font-weight: 500;
th {
font-weight: 600;
}
&.is-group {
th.#{$namespace}-table__cell {

View File

@@ -58,9 +58,11 @@
}
@include e(content) {
@include css-var-from-global('checkbox-height', 'tree-node-content-height');
display: flex;
align-items: center;
height: 26px;
height: getCssVar('tree-node-content-height');
cursor: pointer;
& > .#{$namespace}-tree-node__expand-icon {