mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
2
.github/renovate.json5
vendored
2
.github/renovate.json5
vendored
@@ -1,9 +1,7 @@
|
||||
{
|
||||
extends: ['config:base', 'schedule:weekly', 'group:allNonMajor'],
|
||||
labels: ['dependencies'],
|
||||
pin: false,
|
||||
rangeStrategy: 'bump',
|
||||
node: false,
|
||||
packageRules: [
|
||||
{
|
||||
depTypeList: ['peerDependencies'],
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
]"
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 | — | — |
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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" />
|
||||
```
|
||||
|
||||
@@ -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] | - |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -40,3 +40,9 @@ const onSubmit = () => {
|
||||
console.log('submit!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.demo-form-inline .el-input {
|
||||
--el-input-width: 220px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
BIN
docs/public/images/sponsors/MISBoot.png
Normal file
BIN
docs/public/images/sponsors/MISBoot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
|
||||
55
packages/hooks/__tests__/use-focus-controller.test.tsx
Normal file
55
packages/hooks/__tests__/use-focus-controller.test.tsx
Normal 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)
|
||||
})
|
||||
})
|
||||
@@ -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'
|
||||
|
||||
60
packages/hooks/use-focus-controller/index.ts
Normal file
60
packages/hooks/use-focus-controller/index.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -123,7 +123,10 @@
|
||||
|
||||
thead {
|
||||
color: getCssVar('table-header-text-color');
|
||||
font-weight: 500;
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.is-group {
|
||||
th.#{$namespace}-table__cell {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user