mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-21 01:02:59 +08:00
style: use prettier (#3228)
* style: use prettier * style: just prettier format, no code changes * style: eslint fix object-shorthand, prefer-const * style: fix no-void * style: no-console
This commit is contained in:
parent
a4146608db
commit
55348b30b6
88
.eslintrc.js
88
.eslintrc.js
@ -12,72 +12,52 @@ module.exports = {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
],
|
||||
plugins: ['@typescript-eslint', 'prettier'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'prettier',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts'],
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
// style
|
||||
'block-spacing': 'error',
|
||||
'eol-last': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'comma-style': ['error', 'last'],
|
||||
'comma-dangle': ['error', 'always-multiline'],
|
||||
'no-multi-spaces': 'error',
|
||||
semi: ['error', 'never'],
|
||||
'arrow-parens':['error', 'as-needed'],
|
||||
'array-bracket-spacing': ['error', 'never'],
|
||||
|
||||
'indent': 'off',
|
||||
'@typescript-eslint/indent':['error', 2, { SwitchCase: 1 }],
|
||||
|
||||
'object-curly-spacing': 'off',
|
||||
'@typescript-eslint/object-curly-spacing': ['error', 'always'],
|
||||
|
||||
quotes: 'off',
|
||||
'@typescript-eslint/quotes': ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
|
||||
|
||||
'space-infix-ops': 'off',
|
||||
'@typescript-eslint/space-infix-ops': ['error', { 'int32Hint': false }],
|
||||
|
||||
'@typescript-eslint/type-annotation-spacing': ['error', {}],
|
||||
|
||||
// js/ts
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'no-redeclare': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'],
|
||||
camelcase: ['error', { properties: 'never' }],
|
||||
|
||||
'no-var': 'error',
|
||||
'prefer-const': [
|
||||
'warn',
|
||||
{ destructuring: 'all', ignoreReadBeforeAssign: true },
|
||||
],
|
||||
'object-shorthand': [
|
||||
'error',
|
||||
'always',
|
||||
{ ignoreConstructors: false, avoidQuotes: true },
|
||||
],
|
||||
'block-scoped-var': 'error',
|
||||
complexity: ['off', 11],
|
||||
'no-with': 'error',
|
||||
'no-void': 'error',
|
||||
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/member-delimiter-style': [
|
||||
'error',
|
||||
{
|
||||
multiline: {
|
||||
delimiter: 'none',
|
||||
requireLast: false,
|
||||
},
|
||||
singleline: {
|
||||
delimiter: 'semi',
|
||||
requireLast: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// vue
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/html-self-closing': ['error', {
|
||||
html: {
|
||||
void: 'never',
|
||||
normal: 'never',
|
||||
component: 'always',
|
||||
},
|
||||
}],
|
||||
'vue/max-attributes-per-line': ['error', {
|
||||
singleline: 3,
|
||||
multiline: 1,
|
||||
}],
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/html-closing-bracket-spacing': 'error',
|
||||
|
||||
'prettier/prettier': 'warn',
|
||||
},
|
||||
}
|
||||
|
1
.github/CONTRIBUTING.en-US.md
vendored
1
.github/CONTRIBUTING.en-US.md
vendored
@ -31,4 +31,3 @@ We are excited that you are interested in contributing to Element Plus. Before s
|
||||
- If your PR fixes a bug, please provide a description about the related bug.
|
||||
|
||||
- Merging a PR takes two maintainers: one approves the changes after reviewing, and then the other reviews and merges.
|
||||
|
||||
|
7
.github/CONTRIBUTING.es.md
vendored
7
.github/CONTRIBUTING.es.md
vendored
@ -6,16 +6,14 @@
|
||||
|
||||
Estamos orgullosos de que usted esta interesado en contribuir al proyecto `Element Plus`. Antes de someter sus contribuciones, por favor tome un momentito para leer estas simples guías para contribuidores.
|
||||
|
||||
|
||||
## Guía Para Reportar Problemas (“Issues”)
|
||||
|
||||
- Preguntas de otro tipo corren el riesgo de ser cerradas inmediatamente. Sí tiene preguntas sobre el uso de `Element`, vea [Discord](https://discord.link/ElementPlus) para más ayuda.
|
||||
|
||||
- Antes de someter un informe sobre algún problema, sírvase de revisar sí ya hubo un informe.
|
||||
- Antes de someter un informe sobre algún problema, sírvase de revisar sí ya hubo un informe.
|
||||
|
||||
- Por favor especifique que versión de `Element Plus` y `Vue` que esta utilizando, y que versión de sistema operativo y que versión de navegador web que está utilizando. [JSFiddle](https://jsfiddle.net/) esta recomendado para crear un entorno para reproducir el problema claramente.
|
||||
|
||||
|
||||
## Guías para un “Pull Request (PR)”
|
||||
|
||||
- Crea una bifurcación (“fork”) del repositorio a su propia cuenta en github.com. Por favor no crea ramas nuevas aquí.
|
||||
@ -28,9 +26,8 @@ Estamos orgullosos de que usted esta interesado en contribuir al proyecto `Eleme
|
||||
|
||||
- “Rebase” antes de crear un “pull request (PR)” para mantener la historia de “commits” limpia.
|
||||
|
||||
- Asegúrese que sus PRs se refrieran a la rama `dev` y no a la rama `master`.
|
||||
- Asegúrese que sus PRs se refrieran a la rama `dev` y no a la rama `master`.
|
||||
|
||||
- Si su PR arregla un error técnico, por favor, haga referencia al error especifico.
|
||||
|
||||
- Fusión de un PR requiere dos mantenedores: el primero aprueba los cambios después de revisar, y entonces el segundo mantenedor revisa los cambios y hace la fusión.
|
||||
|
||||
|
1
.github/CONTRIBUTING.fr-FR.md
vendored
1
.github/CONTRIBUTING.fr-FR.md
vendored
@ -31,4 +31,3 @@ Nous sommes ravis que vous souhaitiez contribuer à Element Plus. Avant de soume
|
||||
- Si votre PR corrige un bug, veuillez fournir une description du bug en question.
|
||||
|
||||
- La fusion d'un PR nécessite deux responsables: l'un approuve les modifications après révision, puis l'autre les révise et les fusionne.
|
||||
|
||||
|
2
.github/CONTRIBUTING.zh-CN.md
vendored
2
.github/CONTRIBUTING.zh-CN.md
vendored
@ -7,6 +7,7 @@ Element Plus 是一套为开发者、设计师和产品经理准备的开源组
|
||||
Element Plus 的成长离不开大家的支持,如果你愿意为 Element Plus 贡献代码或提供建议,请阅读以下内容。
|
||||
|
||||
## Issue 规范
|
||||
|
||||
- issue 仅用于提交 Bug 或 Feature 以及设计相关的内容,其它内容可能会被直接关闭。如果你在使用时产生了疑问,请到 Slack 或 [Discord](https://discord.link/ElementPlus) 里咨询。
|
||||
|
||||
- 在提交 issue 之前,请搜索相关内容是否已被提出。
|
||||
@ -14,6 +15,7 @@ Element Plus 的成长离不开大家的支持,如果你愿意为 Element Plus
|
||||
- 请说明 Element Plus 和 Vue 的版本号,并提供操作系统和浏览器信息。推荐使用 [JSFiddle](https://jsfiddle.net/) 生成在线 demo,这能够更直观地重现问题。
|
||||
|
||||
## Pull Request 规范
|
||||
|
||||
- 请先 fork 一份到自己的项目下,不要直接在仓库下建分支。
|
||||
|
||||
- commit 信息要以`[组件名]: 描述信息` 的形式填写,例如 `Button: fix xxx bug`。
|
||||
|
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@ -1,5 +1,5 @@
|
||||
Please make sure these boxes are checked before submitting your PR, thank you!
|
||||
|
||||
* [ ] Make sure you follow Element's contributing guide [English](https://github.com/element-plus/element-plus/blob/master/.github/CONTRIBUTING.en-US.md) | ([中文](https://github.com/element-plus/element-plus/blob/master/.github/CONTRIBUTING.zh-CN.md) | [Español](https://github.com/element-plus/element-plus/blob/master/.github/CONTRIBUTING.es.md) | [Français](https://github.com/element-plus/element-plus/blob/master/.github/CONTRIBUTING.fr-FR.md)).
|
||||
* [ ] Make sure you are merging your commits to `dev` branch.
|
||||
* [ ] Add some descriptions and refer to relative issues for your PR.
|
||||
- [ ] Make sure you follow Element's contributing guide [English](https://github.com/element-plus/element-plus/blob/master/.github/CONTRIBUTING.en-US.md) | ([中文](https://github.com/element-plus/element-plus/blob/master/.github/CONTRIBUTING.zh-CN.md) | [Español](https://github.com/element-plus/element-plus/blob/master/.github/CONTRIBUTING.es.md) | [Français](https://github.com/element-plus/element-plus/blob/master/.github/CONTRIBUTING.fr-FR.md)).
|
||||
- [ ] Make sure you are merging your commits to `dev` branch.
|
||||
- [ ] Add some descriptions and refer to relative issues for your PR.
|
||||
|
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@ -0,0 +1,4 @@
|
||||
dist
|
||||
node_modules
|
||||
packages/*/es
|
||||
packages/*/lib
|
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf",
|
||||
"trailingComma": "es5"
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
module.exports = {
|
||||
semi: false,
|
||||
trailingComma: 'all',
|
||||
singleQuote: true,
|
||||
printWidth: 80,
|
||||
tabWidth: 2,
|
||||
endOfLine: 'auto',
|
||||
overrides: [
|
||||
{
|
||||
files: '*.scss',
|
||||
options: {
|
||||
parser: 'scss',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
@ -2,14 +2,14 @@
|
||||
|
||||
### 1.1.0-beta.8
|
||||
|
||||
*2021-08-31*
|
||||
_2021-08-31_
|
||||
|
||||
#### Breaking changes:
|
||||
|
||||
- Please refer to: [Breaking changes made in 1.1.0-beta.1](https://github.com/element-plus/element-plus/discussions/3020)
|
||||
|
||||
#### Features
|
||||
|
||||
|
||||
- Components form add scrollToField method (#3110 by @sxzz)
|
||||
- Components select-v2 support filter-method & remote-search (#3092 by @msidolphin)
|
||||
- Components button-group add size (#3098 by @sxzz)
|
||||
@ -18,7 +18,7 @@
|
||||
- Menu add ellipsis in horizontal mode (#3083 by @kooriookami)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
||||
- Theme-chalk add the missing margin-left to transfer
|
||||
- Remove useless and duplicate declaration (#3082 by @BeADre)
|
||||
- Components image no emit switch event (close #3132) (#3134 by @imswk)
|
||||
@ -36,25 +36,25 @@
|
||||
- [image-viewer] two-finger zoom in and out (#3114 by @Alanscut)
|
||||
|
||||
#### Refactors
|
||||
|
||||
|
||||
- Enhance type definition (#3062 by @sxzz)
|
||||
|
||||
### 1.1.0-beta.7
|
||||
|
||||
*2021-08-26*
|
||||
_2021-08-26_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
||||
- Components style path error for radio button (#3079 by @JeremyWuuuuu)
|
||||
- Input autosize type problem (#3008 by @imswk) (#3012)
|
||||
- Build marking side effects for webpack importing styles
|
||||
|
||||
### 1.1.0-beta.6
|
||||
|
||||
*2021-08-26*
|
||||
_2021-08-26_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
||||
- Components [table] avoid table border style conflict (#3064 by @adaex)
|
||||
- Eslint ignore bundle file (#3061 by @sxzz)
|
||||
- Form el-from/src/token.d.ts file miss (#2979 by @imswk) (#2988)
|
||||
@ -63,14 +63,14 @@
|
||||
|
||||
### 1.1.0-beta.5
|
||||
|
||||
*2021-08-25*
|
||||
_2021-08-25_
|
||||
|
||||
#### Features
|
||||
|
||||
|
||||
- Select-v2 support allow-create feature (#3017 by @msidolphin)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
||||
- Missing folders and umd locale builds and i18n docs for umd builds (#3053 by @JeremyWuuuuu)
|
||||
- Rateuse constant (#3011 by @Notryag)
|
||||
- Stop pinning vue peer dependency version (#3051 by @sodatea)
|
||||
@ -81,47 +81,46 @@
|
||||
|
||||
### 1.1.0-beta.4
|
||||
|
||||
*2021-08-25*
|
||||
_2021-08-25_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
||||
- Message-box fix type (#3038 by @sxzz)
|
||||
- [label-wrap] Fix misspellings (#3030 by @wangbincyzj)
|
||||
- Components [descriptions] avoid conflict with el-table (#3005 by @adaex)
|
||||
- Build exposing installer function and version for umd build (#3041 by @JeremyWuuuuu)
|
||||
|
||||
#### Refactors
|
||||
|
||||
|
||||
- Rename el-submenu to el-sub-menu (#3037 by @sxzz)
|
||||
|
||||
### 1.1.0-beta.3
|
||||
|
||||
*2021-08-24*
|
||||
_2021-08-24_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
||||
- Build fix rollup full bundile issue (#3034 by @JeremyWuuuuu)
|
||||
|
||||
### 1.1.0-beta.2
|
||||
|
||||
*2021-08-24*
|
||||
_2021-08-24_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
||||
- Build upgrade vue to 3.2.x (#3031 by @JeremyWuuuuu)
|
||||
- Build: update browserslist (#3027 by @kooriookami)
|
||||
|
||||
|
||||
### 1.1.0-beta.1
|
||||
|
||||
*2021-08-24*
|
||||
_2021-08-24_
|
||||
|
||||
#### Features
|
||||
|
||||
- Date-picker custom date-editor width (#2836 by @YunYouJun)
|
||||
- Card add var background-color & prepare for dark mode (#2912 by @YunYouJun)
|
||||
- Link remove href prototype when empty (#2969 by @adaex)
|
||||
- Add ElSkeleton template scoped parameter `key`. (#2944 by @callmesoul)
|
||||
- Add ElSkeleton template scoped parameter `key`. (#2944 by @callmesoul)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -143,7 +142,7 @@
|
||||
|
||||
### 1.0.2-beta.71
|
||||
|
||||
*2021-08-18*
|
||||
_2021-08-18_
|
||||
|
||||
#### Features
|
||||
|
||||
@ -176,7 +175,7 @@
|
||||
|
||||
### 1.0.2-beta.70
|
||||
|
||||
*2021-08-05*
|
||||
_2021-08-05_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -186,7 +185,7 @@
|
||||
|
||||
### 1.0.2-beta.69
|
||||
|
||||
*2021-08-04*
|
||||
_2021-08-04_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -194,7 +193,8 @@
|
||||
|
||||
### 1.0.2-beta.68
|
||||
|
||||
*2021-08-04*
|
||||
_2021-08-04_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- Dropdown styles error (#2823 by @tolking)
|
||||
@ -203,7 +203,7 @@
|
||||
|
||||
### 1.0.2-beta.67
|
||||
|
||||
*2021-08-04*
|
||||
_2021-08-04_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -215,7 +215,7 @@
|
||||
|
||||
### 1.0.2-beta.66
|
||||
|
||||
*2021-08-03*
|
||||
_2021-08-03_
|
||||
|
||||
#### Features
|
||||
|
||||
@ -229,7 +229,7 @@
|
||||
- Var map merge default var (#2727 by @YunYouJun)
|
||||
- Scrollbar the problem of ScrollbarDirKey being deleted (#2722 by @msidolphin)
|
||||
- Input no trigger input event when clear value (#2723 by @msidolphin)
|
||||
- Message using iconClass causes "el-message__icon" to be lost (#2709 by @GaliMu)
|
||||
- Message using iconClass causes "el-message\_\_icon" to be lost (#2709 by @GaliMu)
|
||||
- Locale inject locale for modal like components (#2737 by @JeremyWuuuuu)
|
||||
- Tooltip styles error (#2763 by @tolking)
|
||||
- Input Improve inputStyle reference (#2780 by @adaex)
|
||||
@ -251,7 +251,7 @@
|
||||
|
||||
### 1.0.2-beta.65
|
||||
|
||||
*2021-07-28*
|
||||
_2021-07-28_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -268,20 +268,22 @@
|
||||
### 1.0.2-beta.64
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- fix(var): map merge default var (#2727)
|
||||
- fix(scrollbar): the problem of ScrollbarDirKey being deleted (#2722)
|
||||
- fix(input): no trigger input event when clear value (#2723) …
|
||||
- fix(message): using iconClass causes "el-message__icon" to be lost (#… …
|
||||
- fix(message): using iconClass causes "el-message\_\_icon" to be lost (#… …
|
||||
- fix(typing): fixed Nullable type for RefElement (#2730)
|
||||
- fix(locale): inject locale for modal like components (#2737)
|
||||
|
||||
#### Refactors
|
||||
|
||||
- refactor(backtop): migrate css var (#2711)
|
||||
- refactor(var): simplify checkbox var & move transition to root (#2729)
|
||||
|
||||
### 1.0.2-beta.63
|
||||
|
||||
*2021-07-27*
|
||||
_2021-07-27_
|
||||
|
||||
#### Refactor
|
||||
|
||||
@ -302,7 +304,7 @@
|
||||
|
||||
### 1.0.2-beta.62
|
||||
|
||||
*2021-07-26*
|
||||
_2021-07-26_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -310,7 +312,7 @@
|
||||
|
||||
### 1.0.2-beta.61
|
||||
|
||||
*2021-07-26*
|
||||
_2021-07-26_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -319,7 +321,7 @@
|
||||
|
||||
### 1.0.2-beta.60
|
||||
|
||||
*2021-07-26*
|
||||
_2021-07-26_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -327,7 +329,7 @@
|
||||
|
||||
### 1.0.2-beta.59
|
||||
|
||||
*2021-07-26*
|
||||
_2021-07-26_
|
||||
|
||||
#### Breaking change
|
||||
|
||||
@ -363,7 +365,7 @@
|
||||
|
||||
### 1.0.2-beta.58
|
||||
|
||||
*2021-07-24*
|
||||
_2021-07-24_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -371,7 +373,7 @@
|
||||
|
||||
### 1.0.2-beta.57
|
||||
|
||||
*2021-07-23*
|
||||
_2021-07-23_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -379,7 +381,7 @@
|
||||
|
||||
### 1.0.2-beta.56
|
||||
|
||||
*2021-07-23*
|
||||
_2021-07-23_
|
||||
|
||||
#### Features
|
||||
|
||||
@ -449,7 +451,7 @@
|
||||
|
||||
### 1.0.2-beta.55
|
||||
|
||||
*2021-07-09*
|
||||
_2021-07-09_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -475,7 +477,7 @@
|
||||
|
||||
### 1.0.2-beta.54
|
||||
|
||||
*2021-07-02*
|
||||
_2021-07-02_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -484,14 +486,14 @@
|
||||
- Fix checkbox label auto convert to bool when label is empty (#2287)
|
||||
- Fix update peerDependencies vue version (#2352)
|
||||
- Fix virtual-list template string to render function (#2388)
|
||||
- Fix code space (#2376)
|
||||
- Fix code space (#2376)
|
||||
- Fix cascader arrow position (#2356)
|
||||
- Fix carousel not working when using v-show (#2361)
|
||||
- Revert fix scrollbar update when slot changed (#2322)
|
||||
|
||||
### 1.0.2-beta.53
|
||||
|
||||
*2021-06-25*
|
||||
_2021-06-25_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -506,7 +508,7 @@
|
||||
|
||||
### 1.0.2-beta.52
|
||||
|
||||
*2021-06-24*
|
||||
_2021-06-24_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -514,7 +516,7 @@
|
||||
|
||||
### 1.0.2-beta.49
|
||||
|
||||
*2021-06-23*
|
||||
_2021-06-23_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -536,7 +538,7 @@
|
||||
|
||||
### 1.0.2-beta.47
|
||||
|
||||
*2021-06-11*
|
||||
_2021-06-11_
|
||||
|
||||
#### Features
|
||||
|
||||
@ -580,7 +582,7 @@
|
||||
|
||||
### 1.0.2-beta.46
|
||||
|
||||
*2021-06-04*
|
||||
_2021-06-04_
|
||||
|
||||
#### Features
|
||||
|
||||
@ -588,6 +590,7 @@
|
||||
- Feat Input: add input-style prop (#2117)
|
||||
|
||||
#### Bugfixes
|
||||
|
||||
- Fix Col: should hidden when span is zero (#1769)
|
||||
- Fix Table: hot reload (#2077) (#2097)
|
||||
- Fix Popper: in dialog isn't enough space to position problem (#2102)
|
||||
@ -597,17 +600,21 @@
|
||||
- Fix Cascader: size issue when filterable and multiple are enabled (#2123)
|
||||
|
||||
#### Chore
|
||||
|
||||
- Chore Website: update radio VCA doc (#2111)
|
||||
- Chore Project: esbuild for single component build (#2112)
|
||||
- Chore Website: use built-in throwError function. (#2131)
|
||||
|
||||
### 1.0.2-beta.45
|
||||
|
||||
*2021-05-28*
|
||||
_2021-05-28_
|
||||
|
||||
#### Features
|
||||
|
||||
- Feat VClickOutside: Expose mousedown and mouseup event object (#2038)
|
||||
|
||||
#### Bugfixes
|
||||
|
||||
- Fix Website: 'textContent' of undefined and text wrapping (#1973)
|
||||
- Fix DatePicker: attribute 'type' add ts declare (#1979)
|
||||
- Fix Table: fix sorting error #1919 (#1983)
|
||||
@ -622,7 +629,9 @@
|
||||
- Fix Website: algolia search wrong search parameter (#2065)
|
||||
- Fix ScrollBar: perf behavior of bar click (#2066)
|
||||
- Fix DateTimePicker: allow clearable to control both of the clear button (#2072)
|
||||
|
||||
#### Chore
|
||||
|
||||
- Chore Tag: add specific type declaration (#1939)
|
||||
- Chore Docs: drawer.md add modal-class attribute (#1974)
|
||||
- Chore Transfer: remove useless button style (#1975)
|
||||
@ -642,26 +651,28 @@
|
||||
- Chore Project: bump sass from 1.26.10 to 1.34.0 (#2060)
|
||||
- Chore Docs: value -> model-value in vue 3 (#2067)
|
||||
|
||||
|
||||
### 1.0.2-beta.44
|
||||
|
||||
*2021-05-11*
|
||||
_2021-05-11_
|
||||
|
||||
#### Bug fixes
|
||||
- Hot fix for #1980 (#1981)
|
||||
|
||||
- Hot fix for #1980 (#1981)
|
||||
|
||||
### 1.0.2-beta.43
|
||||
|
||||
*2021-05-10*
|
||||
_2021-05-10_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- Fix DateTimePicker positioning issue (#1980, #1981)
|
||||
|
||||
### 1.0.2-beta.42
|
||||
|
||||
*2021-05-09*
|
||||
_2021-05-09_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- Fix Rate: remove the confusing this (#1910)
|
||||
- Fix Drawer: add modal false div class (#1932)
|
||||
- Fix Button: fix incorrect style rule when hovering disabled plain button (#1937)
|
||||
@ -673,6 +684,7 @@
|
||||
- Fix Switch: fix switch component onMounted condition error (#1966)
|
||||
|
||||
#### Chore
|
||||
|
||||
- Chore Notification: Remove magic numbers (#1928)
|
||||
- Chore Tag: keep the same with property name (#1940)
|
||||
- Chore Pagination: update Pagination documentation with .sync modifier (#1920 #1957)
|
||||
@ -680,11 +692,9 @@
|
||||
- Chore website: update ad (#1968)
|
||||
- Chore ThemeChalk: add missing `!default` modifiers for global variables (#1969)
|
||||
|
||||
|
||||
|
||||
### 1.0.2-beta.41
|
||||
|
||||
*2021-04-26*
|
||||
_2021-04-26_
|
||||
|
||||
#### Features
|
||||
|
||||
@ -708,7 +718,7 @@
|
||||
|
||||
### 1.0.2-beta.40
|
||||
|
||||
*2021-04-14*
|
||||
_2021-04-14_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -726,6 +736,7 @@
|
||||
- Fix docs footer links error (#1827)
|
||||
- Fix table fix table column rerendered whenever table updates
|
||||
- Fix time-picker time picker change event bug (#1828)
|
||||
|
||||
#### Features
|
||||
|
||||
- Result: new componet Result (#1818)
|
||||
@ -736,25 +747,26 @@
|
||||
|
||||
### 1.0.2-beta.39
|
||||
|
||||
*2021-04-09*
|
||||
_2021-04-09_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- Fix `packages/utils` path issue (#1792)
|
||||
|
||||
### 1.0.2-beta.38
|
||||
|
||||
*2021-04-08*
|
||||
_2021-04-08_
|
||||
|
||||
- Fix `packages/utils` build issue caused `setConfig` not working (#1788)
|
||||
|
||||
### 1.0.2-beta.37
|
||||
|
||||
*2021-04-08*
|
||||
_2021-04-08_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- Fix input prefix-icon-incorrect-height (#1766)
|
||||
- Fix select fix filter method is not called when input first letter (#1711)
|
||||
- Fix select fix filter method is not called when input first letter (#1711)
|
||||
- Fix picker add focus (#1475)
|
||||
- Fix time-select input value not changed with v-model bindings (#1725)
|
||||
- Fix tooltip component tabindex prop (#1621)
|
||||
@ -762,12 +774,12 @@
|
||||
- Fix utils isIE logic (#1757)
|
||||
- Refactor card remove needless div tag (#1732)
|
||||
- Fix input add keydown event listener for textarea (#1723)
|
||||
- Fix time-picker update oldValue when visible change (#1635)
|
||||
- Fix time-picker update oldValue when visible change (#1635)
|
||||
- Fix drawer close button outline issue when focusing (#1727)
|
||||
|
||||
### 1.0.2-beta.36
|
||||
|
||||
*2021-03-28*
|
||||
_2021-03-28_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -806,16 +818,16 @@
|
||||
|
||||
### 1.0.2-beta.35
|
||||
|
||||
|
||||
*2021-03-15*
|
||||
_2021-03-15_
|
||||
|
||||
- Fix type generator error
|
||||
|
||||
### 1.0.2-beta.34
|
||||
|
||||
*2021-03-12*
|
||||
_2021-03-12_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- Fix slider warning of modelValue (#1622)
|
||||
- Fix tree auto expand parent on set current key and node (#1502)
|
||||
- Fix avatar watch invalid when src is missing from props (#1615)
|
||||
@ -839,11 +851,11 @@
|
||||
|
||||
### 1.0.2-beta.33
|
||||
|
||||
*2021-03-03*
|
||||
_2021-03-03_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- Fix cascader-panel value can be falsy value (#1533)
|
||||
- Fix cascader-panel value can be falsy value (#1533)
|
||||
- Fix scss error (#1542)
|
||||
- Fix popper error cause by vue version update (#1556)
|
||||
- Fix col will always be hidden when responsive span is zero (#1532)
|
||||
@ -851,7 +863,7 @@
|
||||
- Fix layout gutter bug (#1537)
|
||||
- Fix select options watch flush post (#1513)
|
||||
- Fix select noMatchText error show (#1523)
|
||||
- Fix avatar need reset hasLoadError to false, if src changed (#1515)
|
||||
- Fix avatar need reset hasLoadError to false, if src changed (#1515)
|
||||
- Fix input remove invalid attrs after render (#1489)
|
||||
- Fix select emit blur event (#1504)
|
||||
- Fix table bug of invalid tooltipEffect prop (#1470)
|
||||
@ -862,7 +874,7 @@
|
||||
|
||||
### 1.0.2-beta.32
|
||||
|
||||
*2021-01-31*
|
||||
_2021-01-31_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -897,7 +909,7 @@
|
||||
|
||||
### 1.0.2-beta.31
|
||||
|
||||
*2021-01-31*
|
||||
_2021-01-31_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -928,7 +940,7 @@
|
||||
|
||||
### 1.0.2-beta.30
|
||||
|
||||
*2021-01-25*
|
||||
_2021-01-25_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -938,7 +950,7 @@
|
||||
|
||||
### 1.0.2-beta.29
|
||||
|
||||
*2021-01-25*
|
||||
_2021-01-25_
|
||||
|
||||
#### New feature
|
||||
|
||||
@ -958,7 +970,7 @@
|
||||
|
||||
### 1.0.2-beta.28
|
||||
|
||||
*2021-01-20*
|
||||
_2021-01-20_
|
||||
|
||||
#### New feature
|
||||
|
||||
@ -980,7 +992,7 @@
|
||||
|
||||
### 1.0.1-beta.27
|
||||
|
||||
*2021-01-15*
|
||||
_2021-01-15_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -990,7 +1002,7 @@
|
||||
|
||||
### 1.0.1-beta.26
|
||||
|
||||
*2021-01-14*
|
||||
_2021-01-14_
|
||||
|
||||
#### New feature
|
||||
|
||||
@ -1007,7 +1019,7 @@
|
||||
|
||||
### 1.0.1-beta.24
|
||||
|
||||
*2021-01-11*
|
||||
_2021-01-11_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1020,7 +1032,7 @@
|
||||
|
||||
### 1.0.1-beta.23
|
||||
|
||||
*2021-01-07*
|
||||
_2021-01-07_
|
||||
|
||||
#### New feature
|
||||
|
||||
@ -1037,7 +1049,7 @@
|
||||
|
||||
### 1.0.1-beta.22
|
||||
|
||||
*2021-01-06*
|
||||
_2021-01-06_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1050,7 +1062,7 @@
|
||||
|
||||
### 1.0.1-beta.21
|
||||
|
||||
*2021-01-05*
|
||||
_2021-01-05_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1064,7 +1076,7 @@
|
||||
|
||||
### 1.0.1-beta.19
|
||||
|
||||
*2021-01-02*
|
||||
_2021-01-02_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1074,7 +1086,7 @@
|
||||
|
||||
### 1.0.1-beta.18
|
||||
|
||||
*2020-12-31*
|
||||
_2020-12-31_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1087,7 +1099,7 @@
|
||||
|
||||
### 1.0.1-beta.15
|
||||
|
||||
*2020-12-27*
|
||||
_2020-12-27_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1098,7 +1110,7 @@
|
||||
|
||||
### 1.0.1-beta.14
|
||||
|
||||
*2020-12-24*
|
||||
_2020-12-24_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1114,7 +1126,7 @@
|
||||
|
||||
### 1.0.1-beta.11
|
||||
|
||||
*2020-12-21*
|
||||
_2020-12-21_
|
||||
|
||||
#### New features
|
||||
|
||||
@ -1129,7 +1141,7 @@
|
||||
|
||||
### 1.0.1-beta.10
|
||||
|
||||
*2020-12-18*
|
||||
_2020-12-18_
|
||||
|
||||
#### New features
|
||||
|
||||
@ -1142,7 +1154,7 @@
|
||||
|
||||
### 1.0.1-beta.9
|
||||
|
||||
*2020-12-16*
|
||||
_2020-12-16_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1157,7 +1169,7 @@
|
||||
|
||||
### 1.0.1-beta.8
|
||||
|
||||
*2020-12-12*
|
||||
_2020-12-12_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1171,7 +1183,7 @@
|
||||
|
||||
### 1.0.1-beta.7
|
||||
|
||||
*2020-12-10*
|
||||
_2020-12-10_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1185,7 +1197,7 @@
|
||||
|
||||
### 1.0.1-beta.6
|
||||
|
||||
*2020-12-09*
|
||||
_2020-12-09_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1201,7 +1213,7 @@
|
||||
|
||||
### 1.0.1-beta.5
|
||||
|
||||
*2020-12-07*
|
||||
_2020-12-07_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1219,7 +1231,7 @@
|
||||
|
||||
### 1.0.1-beta.4
|
||||
|
||||
*2020-12-05*
|
||||
_2020-12-05_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1236,7 +1248,7 @@
|
||||
|
||||
### 1.0.1-beta.3
|
||||
|
||||
*2020-12-03*
|
||||
_2020-12-03_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1248,7 +1260,7 @@
|
||||
|
||||
### 1.0.1-beta.2
|
||||
|
||||
*2020-12-02*
|
||||
_2020-12-02_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
@ -1256,7 +1268,7 @@
|
||||
|
||||
### 1.0.1-beta.1
|
||||
|
||||
*2020-12-01*
|
||||
_2020-12-01_
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation.
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
|
23
README.md
23
README.md
@ -14,8 +14,8 @@
|
||||
|
||||
<p align="center">Element Plus - A Vue.js 3.0 UI library</p>
|
||||
|
||||
* 💪 Vue 3.0 Composition API
|
||||
* 🔥 Written in TypeScript
|
||||
- 💪 Vue 3.0 Composition API
|
||||
- 🔥 Written in TypeScript
|
||||
|
||||
## Status: Beta
|
||||
|
||||
@ -48,6 +48,7 @@ This project is still under heavy development. Feel free to join us and make you
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find for more details, API, and other docs on [https://element-plus.org](https://element-plus.org/)
|
||||
|
||||
国内[加速镜像站点](https://element-plus.gitee.io/)
|
||||
@ -59,25 +60,34 @@ Join our [Discord](https://discord.link/ElementPlus) to start communicating with
|
||||
You can find the breaking change list here: [Breaking Change List](https://github.com/element-plus/element-plus/issues/162).
|
||||
|
||||
## Bootstrap project
|
||||
|
||||
With command
|
||||
|
||||
```bash
|
||||
$ yarn bootstrap
|
||||
```
|
||||
|
||||
the project will install all dependencies and run `lerna bootstrap` to initialize the project
|
||||
|
||||
## Website preview
|
||||
|
||||
With command
|
||||
|
||||
```bash
|
||||
$ yarn website-dev
|
||||
```
|
||||
|
||||
the project will launch website for you to preview all existing component
|
||||
|
||||
You can also use this command to start a blank page to debug
|
||||
|
||||
```bash
|
||||
$ yarn website-dev:play
|
||||
//source file: ./website/play/index.vue
|
||||
```
|
||||
|
||||
## Component migration process
|
||||
|
||||
1. Convert the item in https://github.com/element-plus/element-plus/projects/1 to an issue
|
||||
2. Assign yourself to the issue
|
||||
3. Author your component by generating new component command below
|
||||
@ -85,7 +95,9 @@ $ yarn website-dev:play
|
||||
5. Open a new pull request, fill in the component issue link in 1
|
||||
|
||||
## Generate new component
|
||||
|
||||
With command
|
||||
|
||||
```bash
|
||||
$ yarn gen component-name
|
||||
```
|
||||
@ -93,12 +105,15 @@ $ yarn gen component-name
|
||||
Note the `component-name` must be in `kebab-case`, combining words by replacing each space with a dash.
|
||||
|
||||
## Commit template
|
||||
|
||||
With command
|
||||
|
||||
```bash
|
||||
yarn cz
|
||||
```
|
||||
|
||||
Example
|
||||
|
||||
```
|
||||
[TYPE](SCOPE):DESCRIPTION#[ISSUE]
|
||||
# example feat(components): add type 'button' for form usage #1234
|
||||
@ -109,7 +124,6 @@ Example
|
||||
ElementPlus is open source software licensed as
|
||||
[MIT](https://github.com/element-plus/element-plus/blob/master/LICENSE).
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
This project wouldn't exist without our amazing contributors
|
||||
@ -117,6 +131,3 @@ This project wouldn't exist without our amazing contributors
|
||||
<a href="https://github.com/element-plus/element-plus/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=element-plus/element-plus" />
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
@ -22,17 +22,12 @@ module.exports = {
|
||||
overrides: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
plugins: [
|
||||
'@babel/transform-typescript',
|
||||
],
|
||||
plugins: ['@babel/transform-typescript'],
|
||||
},
|
||||
],
|
||||
env: {
|
||||
utils: {
|
||||
ignore: [
|
||||
'**/*.test.ts',
|
||||
'**/*.spec.ts',
|
||||
],
|
||||
ignore: ['**/*.test.ts', '**/*.spec.ts'],
|
||||
presets: [
|
||||
[
|
||||
'@babel/env',
|
||||
|
@ -3,13 +3,10 @@ import helper from 'components-helper'
|
||||
import path from 'path'
|
||||
import { epRoot } from './paths'
|
||||
|
||||
const { name, version } = require(path.resolve(
|
||||
epRoot,
|
||||
'./package.json',
|
||||
))
|
||||
const { name, version } = require(path.resolve(epRoot, './package.json'))
|
||||
|
||||
import icon from '../website/icon.json'
|
||||
const icons = icon.map(item => 'el-icon-' + item).join('/')
|
||||
const icons = icon.map((item) => 'el-icon-' + item).join('/')
|
||||
const tagVer = process.env.TAG_VERSION
|
||||
const _version = tagVer
|
||||
? tagVer.startsWith('v')
|
||||
|
@ -25,13 +25,13 @@ const langs = {
|
||||
'fr-FR': 'element-fr',
|
||||
jp: 'element-jp',
|
||||
}
|
||||
;['zh-CN', 'en-US', 'es', 'fr-FR', 'jp'].forEach(lang => {
|
||||
;['zh-CN', 'en-US', 'es', 'fr-FR', 'jp'].forEach((lang) => {
|
||||
const indexName = langs[lang]
|
||||
const index = client.initIndex(indexName)
|
||||
index.clearObjects().then(() => {
|
||||
const files = fg.sync(`website/docs/${lang}/*.md`)
|
||||
let indices: Index[] = []
|
||||
files.forEach(file => {
|
||||
files.forEach((file) => {
|
||||
const regExp = new RegExp(`website\/docs\/${lang}\/(.*).md`)
|
||||
const pathContent = file.match(regExp)
|
||||
const path = pathContent[1]
|
||||
@ -43,13 +43,13 @@ const langs = {
|
||||
.replace(/:::[\s\S]*?:::/g, '')
|
||||
.replace(/```[\s\S]*?```/g, '')
|
||||
.match(/#{2,4}[^#]*/g)
|
||||
.map(match =>
|
||||
.map((match) =>
|
||||
match
|
||||
.replace(/\n+/g, '\n')
|
||||
.split('\n')
|
||||
.filter(part => !!part),
|
||||
.filter((part) => !!part)
|
||||
)
|
||||
.map(match => {
|
||||
.map((match) => {
|
||||
const length = match.length
|
||||
if (length > 2) {
|
||||
const desc = match.slice(1, length).join('')
|
||||
@ -59,7 +59,7 @@ const langs = {
|
||||
})
|
||||
let i = 0
|
||||
indices = indices.concat(
|
||||
matches.map(match => {
|
||||
matches.map((match) => {
|
||||
const title = match[0].replace(/#{2,4}/, '').trim()
|
||||
const index = { component, title } as Index
|
||||
index.anchor = slugify(title)
|
||||
@ -67,7 +67,7 @@ const langs = {
|
||||
index.path = path
|
||||
index.sort = i++
|
||||
return index
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -7,35 +7,39 @@ import { buildOutput } from './paths'
|
||||
const localePath = resolve(__dirname, '../packages/locale/lang')
|
||||
const fileList = fs.readdirSync(localePath)
|
||||
|
||||
const transform = function(filename, name, cb) {
|
||||
require('@babel/core').transformFile(resolve(localePath, filename), {
|
||||
plugins: [
|
||||
'@babel/plugin-transform-modules-umd',
|
||||
],
|
||||
moduleId: name,
|
||||
}, cb)
|
||||
const transform = function (filename, name, cb) {
|
||||
require('@babel/core').transformFile(
|
||||
resolve(localePath, filename),
|
||||
{
|
||||
plugins: ['@babel/plugin-transform-modules-umd'],
|
||||
moduleId: name,
|
||||
},
|
||||
cb
|
||||
)
|
||||
}
|
||||
|
||||
fileList
|
||||
.filter(function(file) {
|
||||
.filter(function (file) {
|
||||
return /\.ts$/.test(file)
|
||||
})
|
||||
.forEach(function(file) {
|
||||
.forEach(function (file) {
|
||||
const name = basename(file, '.ts')
|
||||
|
||||
transform(file, name, function(err, result) {
|
||||
transform(file, name, function (err, result) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
} else {
|
||||
const code = result.code
|
||||
const transformedCode = code
|
||||
.replace('define(\"', 'define(\"element/locale/')
|
||||
.replace('define("', 'define("element/locale/')
|
||||
.replace(
|
||||
/global\.(\S*) = mod.exports/,
|
||||
'global.ElementPlus.lang = global.ElementPlus.lang || {};\n global.ElementPlus.lang.$1 = mod.exports.default'
|
||||
)
|
||||
|
||||
save(resolve(buildOutput, 'element-plus/dist/locale', `${name}.js`)).write(transformedCode)
|
||||
save(
|
||||
resolve(buildOutput, 'element-plus/dist/locale', `${name}.js`)
|
||||
).write(transformedCode)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -22,8 +22,8 @@ async function getComponents() {
|
||||
// filter out package.json since under packages/components we only got this file
|
||||
//
|
||||
return raw
|
||||
.filter(f => f !== 'package.json' && f !== 'index.ts')
|
||||
.map(f => ({ path: path.resolve(compRoot, f), name: f }))
|
||||
.filter((f) => f !== 'package.json' && f !== 'index.ts')
|
||||
.map((f) => ({ path: path.resolve(compRoot, f), name: f }))
|
||||
}
|
||||
|
||||
const plugins = [
|
||||
@ -42,29 +42,33 @@ const externals = getDeps(pathToPkgJson)
|
||||
|
||||
const excludes = ['icons']
|
||||
|
||||
const pathsRewriter = id => {
|
||||
if (id.startsWith(`${EP_PREFIX}/components`)) return id.replace(`${EP_PREFIX}/components`, '..')
|
||||
if (id.startsWith(EP_PREFIX) && excludes.every(e => !id.endsWith(e))) return id.replace(EP_PREFIX, '../..')
|
||||
const pathsRewriter = (id) => {
|
||||
if (id.startsWith(`${EP_PREFIX}/components`))
|
||||
return id.replace(`${EP_PREFIX}/components`, '..')
|
||||
if (id.startsWith(EP_PREFIX) && excludes.every((e) => !id.endsWith(e)))
|
||||
return id.replace(EP_PREFIX, '../..')
|
||||
return id
|
||||
}
|
||||
|
||||
; (async () => {
|
||||
// run type diagnoses first
|
||||
yellow('Start building types for individual components')
|
||||
await genDefs(compRoot)
|
||||
green('Typing generated successfully')
|
||||
;(async () => {
|
||||
// run type diagnoses first
|
||||
yellow('Start building types for individual components')
|
||||
await genDefs(compRoot)
|
||||
green('Typing generated successfully')
|
||||
|
||||
yellow('Start building individual components')
|
||||
await buildComponents()
|
||||
green('Components built successfully')
|
||||
yellow('Start building individual components')
|
||||
await buildComponents()
|
||||
green('Components built successfully')
|
||||
|
||||
yellow('Start building entry file')
|
||||
await buildEntry()
|
||||
green('Entry built successfully')
|
||||
})().then(() => {
|
||||
yellow('Start building entry file')
|
||||
await buildEntry()
|
||||
green('Entry built successfully')
|
||||
})()
|
||||
.then(() => {
|
||||
console.log('Individual component build finished')
|
||||
process.exit(0)
|
||||
}).catch((e) => {
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e.message)
|
||||
process.exit(1)
|
||||
})
|
||||
@ -72,55 +76,53 @@ const pathsRewriter = id => {
|
||||
async function buildComponents() {
|
||||
const componentPaths = await getComponents()
|
||||
|
||||
const builds = componentPaths.map(async ({
|
||||
path: p,
|
||||
name: componentName,
|
||||
}) => {
|
||||
const entry = path.resolve(p, './index.ts')
|
||||
if (!fs.existsSync(entry)) return
|
||||
const builds = componentPaths.map(
|
||||
async ({ path: p, name: componentName }) => {
|
||||
const entry = path.resolve(p, './index.ts')
|
||||
if (!fs.existsSync(entry)) return
|
||||
|
||||
const external = (id) => {
|
||||
return id.startsWith(VUE_REGEX)
|
||||
|| id.startsWith(VUE_MONO)
|
||||
|| id.startsWith(EP_PREFIX)
|
||||
|| externals.some(i => id.startsWith(i))
|
||||
}
|
||||
const esm = {
|
||||
format: 'es',
|
||||
file: `${outputDir}/es/components/${componentName}/index.js`,
|
||||
plugins: [
|
||||
filesize({
|
||||
reporter,
|
||||
})
|
||||
],
|
||||
paths: pathsRewriter,
|
||||
}
|
||||
const external = (id) => {
|
||||
return (
|
||||
id.startsWith(VUE_REGEX) ||
|
||||
id.startsWith(VUE_MONO) ||
|
||||
id.startsWith(EP_PREFIX) ||
|
||||
externals.some((i) => id.startsWith(i))
|
||||
)
|
||||
}
|
||||
const esm = {
|
||||
format: 'es',
|
||||
file: `${outputDir}/es/components/${componentName}/index.js`,
|
||||
plugins: [
|
||||
filesize({
|
||||
reporter,
|
||||
}),
|
||||
],
|
||||
paths: pathsRewriter,
|
||||
}
|
||||
|
||||
const cjs = {
|
||||
format: 'cjs',
|
||||
file: `${outputDir}/lib/components/${componentName}/index.js`,
|
||||
exports: 'named',
|
||||
plugins: [
|
||||
filesize({
|
||||
reporter,
|
||||
})
|
||||
],
|
||||
paths: pathsRewriter,
|
||||
const cjs = {
|
||||
format: 'cjs',
|
||||
file: `${outputDir}/lib/components/${componentName}/index.js`,
|
||||
exports: 'named',
|
||||
plugins: [
|
||||
filesize({
|
||||
reporter,
|
||||
}),
|
||||
],
|
||||
paths: pathsRewriter,
|
||||
}
|
||||
const rollupConfig = {
|
||||
input: entry,
|
||||
plugins,
|
||||
external,
|
||||
}
|
||||
const bundle = await rollup.rollup(rollupConfig)
|
||||
await bundle.write(esm as any)
|
||||
await bundle.write(cjs as any)
|
||||
}
|
||||
const rollupConfig = {
|
||||
input: entry,
|
||||
plugins,
|
||||
external,
|
||||
}
|
||||
const bundle = await rollup.rollup(rollupConfig)
|
||||
await bundle.write(esm as any)
|
||||
await bundle.write(cjs as any)
|
||||
|
||||
})
|
||||
)
|
||||
try {
|
||||
await Promise.all(
|
||||
builds
|
||||
)
|
||||
await Promise.all(builds)
|
||||
} catch (e) {
|
||||
logAndShutdown(e)
|
||||
}
|
||||
@ -131,7 +133,7 @@ async function buildEntry() {
|
||||
const config = {
|
||||
input: entry,
|
||||
plugins,
|
||||
external: _ => true,
|
||||
external: (_) => true,
|
||||
}
|
||||
|
||||
try {
|
||||
@ -142,8 +144,8 @@ async function buildEntry() {
|
||||
plugins: [
|
||||
filesize({
|
||||
reporter,
|
||||
})
|
||||
]
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
await bundle.write({
|
||||
@ -152,7 +154,7 @@ async function buildEntry() {
|
||||
plugins: [
|
||||
filesize({
|
||||
reporter,
|
||||
})
|
||||
}),
|
||||
],
|
||||
})
|
||||
} catch (e) {
|
||||
|
@ -11,9 +11,7 @@ import genDts from './gen-entry-dts'
|
||||
import RollupResolveEntryPlugin from './rollup.plugin.entry'
|
||||
import { epRoot, buildOutput } from './paths'
|
||||
import { EP_PREFIX, excludes } from './constants'
|
||||
|
||||
;(async () => {
|
||||
|
||||
const config = {
|
||||
input: path.resolve(epRoot, './index.ts'),
|
||||
plugins: [
|
||||
@ -73,20 +71,25 @@ import { EP_PREFIX, excludes } from './constants'
|
||||
|
||||
const entryFiles = await fs.promises.readdir(epRoot, { withFileTypes: true })
|
||||
|
||||
const entryPoints = entryFiles.filter(f => f.isFile()).filter(f => {
|
||||
return f.name !== 'package.json' && f.name !== 'README.md'
|
||||
}).map(f => path.resolve(epRoot, f.name))
|
||||
const entryPoints = entryFiles
|
||||
.filter((f) => f.isFile())
|
||||
.filter((f) => {
|
||||
return f.name !== 'package.json' && f.name !== 'README.md'
|
||||
})
|
||||
.map((f) => path.resolve(epRoot, f.name))
|
||||
|
||||
const entryBundle = await rollup.rollup({
|
||||
...config,
|
||||
input: entryPoints,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
external: _ => true,
|
||||
external: (_) => true,
|
||||
})
|
||||
|
||||
const rewriter = id => {
|
||||
if (id.startsWith(`${EP_PREFIX}/components`)) return id.replace(`${EP_PREFIX}/components`, './components')
|
||||
if (id.startsWith(EP_PREFIX) && excludes.every(e => !id.endsWith(e))) return id.replace(EP_PREFIX, '.')
|
||||
const rewriter = (id) => {
|
||||
if (id.startsWith(`${EP_PREFIX}/components`))
|
||||
return id.replace(`${EP_PREFIX}/components`, './components')
|
||||
if (id.startsWith(EP_PREFIX) && excludes.every((e) => !id.endsWith(e)))
|
||||
return id.replace(EP_PREFIX, '.')
|
||||
}
|
||||
|
||||
console.log(chalk.yellow('Generating cjs entry'))
|
||||
@ -117,5 +120,4 @@ import { EP_PREFIX, excludes } from './constants'
|
||||
await genDts()
|
||||
|
||||
console.log(chalk.green('Entry file definitions generated'))
|
||||
|
||||
})()
|
||||
|
@ -17,12 +17,15 @@ const excludedFiles = [
|
||||
'css',
|
||||
'.DS_Store',
|
||||
]
|
||||
const exclude = (path: string) => !excludedFiles.some(f => path.includes(f))
|
||||
const exclude = (path: string) => !excludedFiles.some((f) => path.includes(f))
|
||||
|
||||
/**
|
||||
* fork = require( https://github.com/egoist/vue-dts-gen/blob/main/src/index.ts
|
||||
*/
|
||||
const genVueTypes = async (root, outDir = path.resolve(__dirname, '../dist/types')) => {
|
||||
const genVueTypes = async (
|
||||
root,
|
||||
outDir = path.resolve(__dirname, '../dist/types')
|
||||
) => {
|
||||
const project = new Project({
|
||||
compilerOptions: {
|
||||
allowJs: true,
|
||||
@ -45,13 +48,13 @@ const genVueTypes = async (root, outDir = path.resolve(__dirname, '../dist/types
|
||||
const filePaths = klawSync(root, {
|
||||
nodir: true,
|
||||
})
|
||||
.map(item => item.path)
|
||||
.filter(path => !DEMO_RE.test(path))
|
||||
.filter(path => !TEST_RE.test(path))
|
||||
.map((item) => item.path)
|
||||
.filter((path) => !DEMO_RE.test(path))
|
||||
.filter((path) => !TEST_RE.test(path))
|
||||
.filter(exclude)
|
||||
|
||||
await Promise.all(
|
||||
filePaths.map(async file => {
|
||||
filePaths.map(async (file) => {
|
||||
if (file.endsWith('.vue')) {
|
||||
const content = await fs.promises.readFile(file, 'utf-8')
|
||||
const sfc = vueCompiler.parse(content)
|
||||
@ -72,7 +75,7 @@ const genVueTypes = async (root, outDir = path.resolve(__dirname, '../dist/types
|
||||
}
|
||||
const sourceFile = project.createSourceFile(
|
||||
path.relative(process.cwd(), file) + (isTS ? '.ts' : '.js'),
|
||||
content,
|
||||
content
|
||||
)
|
||||
sourceFiles.push(sourceFile)
|
||||
}
|
||||
@ -80,7 +83,7 @@ const genVueTypes = async (root, outDir = path.resolve(__dirname, '../dist/types
|
||||
const sourceFile = project.addSourceFileAtPath(file)
|
||||
sourceFiles.push(sourceFile)
|
||||
}
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
const diagnostics = project.getPreEmitDiagnostics()
|
||||
@ -95,10 +98,8 @@ const genVueTypes = async (root, outDir = path.resolve(__dirname, '../dist/types
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Generating definition for file: ' +
|
||||
chalk.bold(
|
||||
sourceFile.getBaseName(),
|
||||
),
|
||||
),
|
||||
chalk.bold(sourceFile.getBaseName())
|
||||
)
|
||||
)
|
||||
|
||||
const emitOutput = sourceFile.getEmitOutput()
|
||||
@ -109,21 +110,27 @@ const genVueTypes = async (root, outDir = path.resolve(__dirname, '../dist/types
|
||||
recursive: true,
|
||||
})
|
||||
|
||||
await fs.promises.writeFile(filepath,
|
||||
await fs.promises.writeFile(
|
||||
filepath,
|
||||
outputFile
|
||||
.getText()
|
||||
.replace(new RegExp('@element-plus/components', 'g'), 'element-plus/es')
|
||||
.replace(new RegExp('@element-plus/theme-chalk', 'g'), 'element-plus/theme-chalk')
|
||||
.replace(
|
||||
new RegExp('@element-plus/components', 'g'),
|
||||
'element-plus/es'
|
||||
)
|
||||
.replace(
|
||||
new RegExp('@element-plus/theme-chalk', 'g'),
|
||||
'element-plus/theme-chalk'
|
||||
)
|
||||
.replace(new RegExp('@element-plus', 'g'), 'element-plus/es'),
|
||||
'utf8')
|
||||
'utf8'
|
||||
)
|
||||
console.log(
|
||||
chalk.green(
|
||||
'Definition for file: ' +
|
||||
chalk.bold(
|
||||
sourceFile.getBaseName(),
|
||||
) +
|
||||
' generated',
|
||||
),
|
||||
chalk.bold(sourceFile.getBaseName()) +
|
||||
' generated'
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -26,16 +26,15 @@ const gen = async () => {
|
||||
skipAddingFilesFromTsConfig: true,
|
||||
})
|
||||
const sourceFiles = []
|
||||
files.map(f => {
|
||||
files.map((f) => {
|
||||
const sourceFile = project.addSourceFileAtPath(f)
|
||||
sourceFiles.push(sourceFile)
|
||||
})
|
||||
|
||||
for (const sourceFile of sourceFiles) {
|
||||
|
||||
console.log(chalk.yellow(
|
||||
`Emitting file: ${chalk.bold(sourceFile.getFilePath())}`,
|
||||
))
|
||||
console.log(
|
||||
chalk.yellow(`Emitting file: ${chalk.bold(sourceFile.getFilePath())}`)
|
||||
)
|
||||
await sourceFile.emit()
|
||||
const emitOutput = sourceFile.getEmitOutput()
|
||||
for (const outputFile of emitOutput.getOutputFiles()) {
|
||||
@ -44,24 +43,21 @@ const gen = async () => {
|
||||
await fs.promises.mkdir(path.dirname(filepath), {
|
||||
recursive: true,
|
||||
})
|
||||
await fs.promises.writeFile(filepath,
|
||||
outputFile
|
||||
.getText()
|
||||
.replace(new RegExp('@element-plus', 'g'), '.'),
|
||||
await fs.promises.writeFile(
|
||||
filepath,
|
||||
outputFile.getText().replace(new RegExp('@element-plus', 'g'), '.'),
|
||||
// .replaceAll('@element-plus/theme-chalk', 'element-plus/theme-chalk'),
|
||||
'utf8')
|
||||
'utf8'
|
||||
)
|
||||
console.log(
|
||||
chalk.green(
|
||||
'Definition for file: ' +
|
||||
chalk.bold(
|
||||
sourceFile.getBaseName(),
|
||||
) +
|
||||
' generated',
|
||||
),
|
||||
chalk.bold(sourceFile.getBaseName()) +
|
||||
' generated'
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default gen
|
||||
|
@ -13,5 +13,5 @@ if (tagVer) {
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, '../packages/element-plus/version.ts'),
|
||||
`export const version = '${version}'
|
||||
`,
|
||||
`
|
||||
)
|
||||
|
@ -4,7 +4,7 @@
|
||||
* @param {PathLike} path path to dependencies
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export default path => {
|
||||
export default (path) => {
|
||||
const pkgJson = require(path)
|
||||
|
||||
const { dependencies } = pkgJson
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* eslint-disable */
|
||||
import { getPackagesSync } from '@lerna/project';
|
||||
import { getPackagesSync } from '@lerna/project'
|
||||
|
||||
export default getPackagesSync();
|
||||
export default getPackagesSync()
|
||||
|
@ -1,13 +1,11 @@
|
||||
import through2 from 'through2'
|
||||
|
||||
const rewriter = (rewriteTo = '../..') => {
|
||||
return through2.obj(function(file, _, cb) {
|
||||
return through2.obj(function (file, _, cb) {
|
||||
const compIdentifier = new RegExp('@element-plus', 'g')
|
||||
|
||||
file.contents = Buffer.from(
|
||||
file.contents
|
||||
.toString()
|
||||
.replace(compIdentifier, rewriteTo),
|
||||
file.contents.toString().replace(compIdentifier, rewriteTo)
|
||||
)
|
||||
cb(null, file)
|
||||
})
|
||||
|
@ -13,7 +13,7 @@ const tsProject = ts.createProject('tsconfig.json', {
|
||||
})
|
||||
|
||||
const rewriter = () => {
|
||||
return through2.obj(function(file, _, cb) {
|
||||
return through2.obj(function (file, _, cb) {
|
||||
const compIdentifier = new RegExp('@element-plus/components', 'g')
|
||||
const compReplacer = '../../../components'
|
||||
const themeIdentifier = new RegExp('@element-plus/theme-chalk', 'g')
|
||||
@ -22,7 +22,7 @@ const rewriter = () => {
|
||||
file.contents
|
||||
.toString()
|
||||
.replace(compIdentifier, compReplacer)
|
||||
.replace(themeIdentifier, themeReplacer),
|
||||
.replace(themeIdentifier, themeReplacer)
|
||||
)
|
||||
cb(null, file)
|
||||
})
|
||||
@ -48,7 +48,7 @@ function compileCjs() {
|
||||
target: 'ESNEXT',
|
||||
skipLibCheck: true,
|
||||
module: 'ESNEXT',
|
||||
})(),
|
||||
})()
|
||||
)
|
||||
.pipe(gulp.dest(path.resolve(output, 'es')))
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ const epRoot = path.resolve(pkgRoot, './element-plus')
|
||||
const utilRoot = path.resolve(pkgRoot, './utils')
|
||||
const buildOutput = path.resolve(projRoot, './dist')
|
||||
|
||||
|
||||
export {
|
||||
projRoot,
|
||||
pkgRoot,
|
||||
|
@ -17,34 +17,37 @@ const getOutFile = (name, dir = 'lib') => {
|
||||
|
||||
const deps = Object.keys(pkg.dependencies)
|
||||
const inputs = getPackagesSync()
|
||||
.map(pkg => pkg.name)
|
||||
.filter(name =>
|
||||
name.includes('@element-plus') &&
|
||||
!name.includes('utils'),
|
||||
)
|
||||
.map((pkg) => pkg.name)
|
||||
.filter((name) => name.includes('@element-plus') && !name.includes('utils'))
|
||||
|
||||
export default inputs.map(name => ({
|
||||
input: path.resolve(__dirname, `../packages/${name.split('@element-plus/')[1]}/index.ts`),
|
||||
output: [{
|
||||
format: 'es',
|
||||
file: getOutFile(name, 'es'),
|
||||
paths(id) {
|
||||
if (/^@element-plus/.test(id)) {
|
||||
if (noElPrefixFile.test(id)) return id.replace('@element-plus', '..')
|
||||
return id.replace('@element-plus/', '../el-')
|
||||
}
|
||||
export default inputs.map((name) => ({
|
||||
input: path.resolve(
|
||||
__dirname,
|
||||
`../packages/${name.split('@element-plus/')[1]}/index.ts`
|
||||
),
|
||||
output: [
|
||||
{
|
||||
format: 'es',
|
||||
file: getOutFile(name, 'es'),
|
||||
paths(id) {
|
||||
if (/^@element-plus/.test(id)) {
|
||||
if (noElPrefixFile.test(id)) return id.replace('@element-plus', '..')
|
||||
return id.replace('@element-plus/', '../el-')
|
||||
}
|
||||
},
|
||||
},
|
||||
}, {
|
||||
format: 'cjs',
|
||||
file: getOutFile(name, 'lib'),
|
||||
exports: 'named',
|
||||
paths(id) {
|
||||
if (/^@element-plus/.test(id)) {
|
||||
if (noElPrefixFile.test(id)) return id.replace('@element-plus', '..')
|
||||
return id.replace('@element-plus/', '../el-')
|
||||
}
|
||||
{
|
||||
format: 'cjs',
|
||||
file: getOutFile(name, 'lib'),
|
||||
exports: 'named',
|
||||
paths(id) {
|
||||
if (/^@element-plus/.test(id)) {
|
||||
if (noElPrefixFile.test(id)) return id.replace('@element-plus', '..')
|
||||
return id.replace('@element-plus/', '../el-')
|
||||
}
|
||||
},
|
||||
},
|
||||
}],
|
||||
],
|
||||
plugins: [
|
||||
css(),
|
||||
vue({
|
||||
@ -55,8 +58,10 @@ export default inputs.map(name => ({
|
||||
esbuild(),
|
||||
],
|
||||
external(id) {
|
||||
return /^vue/.test(id)
|
||||
|| /^@element-plus/.test(id)
|
||||
|| deps.some(k => new RegExp('^' + k).test(id))
|
||||
return (
|
||||
/^vue/.test(id) ||
|
||||
/^@element-plus/.test(id) ||
|
||||
deps.some((k) => new RegExp('^' + k).test(id))
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
@ -11,8 +11,8 @@ export default function entryPlugin(): Plugin {
|
||||
/@element-plus\//g,
|
||||
`${path.relative(
|
||||
path.dirname(id),
|
||||
path.resolve(__dirname, '../packages'),
|
||||
)}/`,
|
||||
path.resolve(__dirname, '../packages')
|
||||
)}/`
|
||||
),
|
||||
map: null,
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import chalk from 'chalk'
|
||||
|
||||
|
||||
export default async function reporter(opt, outputOptions, info) {
|
||||
const values = [
|
||||
// ...(outputOptions.file || outputOptions.dest
|
||||
@ -10,9 +9,7 @@ export default async function reporter(opt, outputOptions, info) {
|
||||
// )}`,
|
||||
// ]
|
||||
// :
|
||||
info.fileName
|
||||
? [`${outputOptions.file.split('packages/').pop()}`]
|
||||
: [],
|
||||
info.fileName ? [`${outputOptions.file.split('packages/').pop()}`] : [],
|
||||
// )
|
||||
// ...(info.bundleSizeBefore
|
||||
// ? [
|
||||
@ -27,18 +24,17 @@ export default async function reporter(opt, outputOptions, info) {
|
||||
[`${info.bundleSize}`],
|
||||
// ),
|
||||
...(info.minSize
|
||||
?
|
||||
// info.minSizeBefore
|
||||
// ? [
|
||||
// `${title('Minified Size: ')} ${value(info.minSize)} (was ${value(
|
||||
// info.minSizeBefore,
|
||||
// )}${info.lastVersion
|
||||
// ? ` in version ${info.lastVersion}`
|
||||
// : ' in last build'
|
||||
// })`,
|
||||
// ]
|
||||
// :
|
||||
[`${info.minSize}`]
|
||||
? // info.minSizeBefore
|
||||
// ? [
|
||||
// `${title('Minified Size: ')} ${value(info.minSize)} (was ${value(
|
||||
// info.minSizeBefore,
|
||||
// )}${info.lastVersion
|
||||
// ? ` in version ${info.lastVersion}`
|
||||
// : ' in last build'
|
||||
// })`,
|
||||
// ]
|
||||
// :
|
||||
[`${info.minSize}`]
|
||||
: []),
|
||||
// ...(info.gzipSize
|
||||
// ? info.gzipSizeBefore
|
||||
@ -66,12 +62,7 @@ export default async function reporter(opt, outputOptions, info) {
|
||||
// : []),
|
||||
]
|
||||
|
||||
return `${
|
||||
chalk.cyan(chalk.bold(values[0]))
|
||||
}: bundle size ${
|
||||
chalk.yellow(values[1])
|
||||
} -> minified ${
|
||||
chalk.green(values[2])
|
||||
}`
|
||||
return `${chalk.cyan(chalk.bold(values[0]))}: bundle size ${chalk.yellow(
|
||||
values[1]
|
||||
)} -> minified ${chalk.green(values[2])}`
|
||||
}
|
||||
|
||||
|
@ -8,21 +8,22 @@ const gitHead = process.env.GIT_HEAD
|
||||
if (!tagVersion || !gitHead) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
'No tag version or git head were found, make sure that you set the environment variable $TAG_VERSION \n',
|
||||
),
|
||||
'No tag version or git head were found, make sure that you set the environment variable $TAG_VERSION \n'
|
||||
)
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('Start updating version'))
|
||||
|
||||
console.log(chalk.cyan([
|
||||
'NOTICE:',
|
||||
`$TAG_VERSION: ${tagVersion}`,
|
||||
`$GIT_HEAD: ${gitHead}`,
|
||||
].join('\n')))
|
||||
|
||||
; (async () => {
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
['NOTICE:', `$TAG_VERSION: ${tagVersion}`, `$GIT_HEAD: ${gitHead}`].join(
|
||||
'\n'
|
||||
)
|
||||
)
|
||||
)
|
||||
;(async () => {
|
||||
console.log(chalk.yellow(`Updating package.json for element-plus`))
|
||||
|
||||
const pkgJson = path.resolve(epRoot, './package.json')
|
||||
|
@ -23,15 +23,17 @@ const plugins = [
|
||||
const entry = path.resolve(__dirname, '../packages/element-plus/index.ts')
|
||||
|
||||
if (!isFullMode) {
|
||||
externals.push({
|
||||
'@popperjs/core': '@popperjs/core',
|
||||
'async-validator': 'async-validator',
|
||||
'mitt': 'mitt',
|
||||
'normalize-wheel': 'normalize-wheel',
|
||||
'resize-observer-polyfill': 'resize-observer-polyfill',
|
||||
},
|
||||
/^dayjs.*/,
|
||||
/^lodash.*/)
|
||||
externals.push(
|
||||
{
|
||||
'@popperjs/core': '@popperjs/core',
|
||||
'async-validator': 'async-validator',
|
||||
mitt: 'mitt',
|
||||
'normalize-wheel': 'normalize-wheel',
|
||||
'resize-observer-polyfill': 'resize-observer-polyfill',
|
||||
},
|
||||
/^dayjs.*/,
|
||||
/^lodash.*/
|
||||
)
|
||||
}
|
||||
|
||||
const config = {
|
||||
@ -44,7 +46,7 @@ const config = {
|
||||
libraryTarget: 'umd',
|
||||
library: 'ElementPlus',
|
||||
umdNamedDefine: true,
|
||||
globalObject: 'typeof self !== \'undefined\' ? self : this',
|
||||
globalObject: "typeof self !== 'undefined' ? self : this",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
@ -6,10 +6,9 @@ const { Project } = require('ts-morph')
|
||||
const chalk = require('chalk')
|
||||
const vueCompiler = require('@vue/compiler-sfc')
|
||||
|
||||
|
||||
const TSCONFIG_PATH = path.resolve(process.cwd(), 'tsconfig.dts.json')
|
||||
|
||||
; (async () => {
|
||||
;(async () => {
|
||||
const { filePaths, workerId, outDir } = workerData
|
||||
let messagePort
|
||||
parentPort.once('message', async ({ port }) => {
|
||||
@ -17,7 +16,9 @@ const TSCONFIG_PATH = path.resolve(process.cwd(), 'tsconfig.dts.json')
|
||||
|
||||
messagePort.postMessage({
|
||||
type: 'log',
|
||||
message: chalk.yellow(`Worker: ${chalk.bold(workerData.workerId)} started`),
|
||||
message: chalk.yellow(
|
||||
`Worker: ${chalk.bold(workerData.workerId)} started`
|
||||
),
|
||||
})
|
||||
|
||||
const project = new Project({
|
||||
@ -30,36 +31,38 @@ const TSCONFIG_PATH = path.resolve(process.cwd(), 'tsconfig.dts.json')
|
||||
|
||||
const sourceFiles = []
|
||||
|
||||
await Promise.all(filePaths.map(async file => {
|
||||
if (file.endsWith('.vue')) {
|
||||
const content = await fs.promises.readFile(file, 'utf-8')
|
||||
const sfc = vueCompiler.parse(content)
|
||||
const { script, scriptSetup } = sfc.descriptor
|
||||
if (script || scriptSetup) {
|
||||
let content = ''
|
||||
let isTS = false
|
||||
if (script && script.content) {
|
||||
content += script.content
|
||||
if (script.lang === 'ts') isTS = true
|
||||
await Promise.all(
|
||||
filePaths.map(async (file) => {
|
||||
if (file.endsWith('.vue')) {
|
||||
const content = await fs.promises.readFile(file, 'utf-8')
|
||||
const sfc = vueCompiler.parse(content)
|
||||
const { script, scriptSetup } = sfc.descriptor
|
||||
if (script || scriptSetup) {
|
||||
let content = ''
|
||||
let isTS = false
|
||||
if (script && script.content) {
|
||||
content += script.content
|
||||
if (script.lang === 'ts') isTS = true
|
||||
}
|
||||
if (scriptSetup) {
|
||||
const compiled = vueCompiler.compileScript(sfc.descriptor, {
|
||||
id: 'xxx',
|
||||
})
|
||||
content += compiled.content
|
||||
if (scriptSetup.lang === 'ts') isTS = true
|
||||
}
|
||||
const sourceFile = project.createSourceFile(
|
||||
path.relative(process.cwd(), file) + (isTS ? '.ts' : '.js'),
|
||||
content
|
||||
)
|
||||
sourceFiles.push(sourceFile)
|
||||
}
|
||||
if (scriptSetup) {
|
||||
const compiled = vueCompiler.compileScript(sfc.descriptor, {
|
||||
id: 'xxx',
|
||||
})
|
||||
content += compiled.content
|
||||
if (scriptSetup.lang === 'ts') isTS = true
|
||||
}
|
||||
const sourceFile = project.createSourceFile(
|
||||
path.relative(process.cwd(), file) + (isTS ? '.ts' : '.js'),
|
||||
content,
|
||||
)
|
||||
} else if (file.endsWith('.ts')) {
|
||||
const sourceFile = project.addSourceFileAtPath(file)
|
||||
sourceFiles.push(sourceFile)
|
||||
}
|
||||
} else if (file.endsWith('.ts')) {
|
||||
const sourceFile = project.addSourceFileAtPath(file)
|
||||
sourceFiles.push(sourceFile)
|
||||
}
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
const diagnostics = project.getPreEmitDiagnostics()
|
||||
await project.emit({
|
||||
@ -72,28 +75,28 @@ const TSCONFIG_PATH = path.resolve(process.cwd(), 'tsconfig.dts.json')
|
||||
})
|
||||
|
||||
for (const sourceFile of sourceFiles) {
|
||||
|
||||
messagePort.postMessage({
|
||||
type: 'log',
|
||||
message: chalk.yellow(
|
||||
'Generating definition for file: ' +
|
||||
chalk.bold(
|
||||
sourceFile.getFilePath(),
|
||||
),
|
||||
chalk.bold(sourceFile.getFilePath())
|
||||
),
|
||||
})
|
||||
|
||||
// console.log(sourceFile.getStructure())
|
||||
const ElementPlusSign = '@element-plus/'
|
||||
|
||||
sourceFile.getImportDeclarations(dec => dec.getModuleSpecifierValue().startsWith(ElementPlusSign)).map(modifySpecifier)
|
||||
sourceFile
|
||||
.getImportDeclarations((dec) =>
|
||||
dec.getModuleSpecifierValue().startsWith(ElementPlusSign)
|
||||
)
|
||||
.map(modifySpecifier)
|
||||
|
||||
function modifySpecifier(d) {
|
||||
|
||||
const replaceTo = 'element-plus/es/' + d.getModuleSpecifierValue().slice(ElementPlusSign.length)
|
||||
d.setModuleSpecifier(
|
||||
replaceTo,
|
||||
)
|
||||
const replaceTo =
|
||||
'element-plus/es/' +
|
||||
d.getModuleSpecifierValue().slice(ElementPlusSign.length)
|
||||
d.setModuleSpecifier(replaceTo)
|
||||
}
|
||||
|
||||
// console.log(sourceFile.getFilePath())
|
||||
@ -114,19 +117,14 @@ const TSCONFIG_PATH = path.resolve(process.cwd(), 'tsconfig.dts.json')
|
||||
type: 'log',
|
||||
message: chalk.green(
|
||||
'Definition for file: ' +
|
||||
chalk.bold(
|
||||
sourceFile.getBaseName(),
|
||||
) +
|
||||
' generated',
|
||||
chalk.bold(sourceFile.getBaseName()) +
|
||||
' generated'
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
messagePort.postMessage({ type: 'fulfill', message: workerId })
|
||||
}
|
||||
|
||||
|
||||
|
||||
})
|
||||
// parentPort.emit
|
||||
})()
|
||||
|
@ -10,10 +10,10 @@ function getPackages(context) {
|
||||
const project = new Project(cwd)
|
||||
return project.getPackages()
|
||||
})
|
||||
.then(packages => {
|
||||
.then((packages) => {
|
||||
return packages
|
||||
.map(pkg => pkg.name)
|
||||
.map(name => (name.charAt(0) === '@' ? name.split('/')[1] : name))
|
||||
.map((pkg) => pkg.name)
|
||||
.map((name) => (name.charAt(0) === '@' ? name.split('/')[1] : name))
|
||||
})
|
||||
}
|
||||
|
||||
@ -35,8 +35,8 @@ const scopes = [
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
'scope-enum': ctx =>
|
||||
getPackages(ctx).then(packages => [
|
||||
'scope-enum': (ctx) =>
|
||||
getPackages(ctx).then((packages) => [
|
||||
2,
|
||||
'always',
|
||||
[...packages, ...scopes],
|
||||
|
@ -11,7 +11,8 @@ module.exports = {
|
||||
transform: {
|
||||
'^.+\\.vue$': 'vue-jest',
|
||||
'^.+\\.(t|j)sx?$': [
|
||||
'babel-jest', {
|
||||
'babel-jest',
|
||||
{
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
|
@ -1,7 +1,5 @@
|
||||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"packages": ["packages/*"],
|
||||
"version": "independent",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true
|
||||
|
18
package.json
18
package.json
@ -4,13 +4,13 @@
|
||||
"cz": "npx git-cz",
|
||||
"test": "jest",
|
||||
"gen": "bash ./scripts/gc.sh",
|
||||
"bootstrap": "yarn --frozen-lockfile && npx lerna bootstrap && yarn gen:version",
|
||||
"gen:version": "esno build/gen-version.ts",
|
||||
"update:version": "esno build/update-version.ts",
|
||||
"bootstrap": "yarn --frozen-lockfile && npx lerna bootstrap && yarn gen:version",
|
||||
"clean:lib": "rimraf lib && rimraf es && rimraf dist",
|
||||
"build:locale-umd": "esno ./build/build-locale.ts",
|
||||
"build:helper": "esno build/build-helper.ts",
|
||||
"build:indices": "esno build/build-indices.ts",
|
||||
"update:version": "esno build/update-version.ts",
|
||||
"build:comps": "rimraf dist/components && esno build/components.ts",
|
||||
"build:style": "gulp --cwd ./build",
|
||||
"build:prod": "sh scripts/monorepo.sh",
|
||||
@ -21,10 +21,9 @@
|
||||
"build:utils": "cd packages/utils && yarn clean && yarn build",
|
||||
"build:tokens": "cd packages/tokens && yarn clean && yarn build",
|
||||
"build:full-bundle": "esno build/full-bundle.ts",
|
||||
"format": "yarn format:scss",
|
||||
"format:scss": "prettier --write **/*.scss",
|
||||
"lint": "eslint ./packages --ext .vue,.js,.ts",
|
||||
"lint-fix": "eslint --fix ./packages --ext .vue,.js,.ts",
|
||||
"format": "prettier --write '**/*.{vue,js,ts,jsx,tsx,scss,json,yaml,md,html}'",
|
||||
"lint": "eslint ./packages --ext .vue,.js,.ts,.jsx,.tsx && prettier --check '**/*.{scss,json,yaml,md,html}'",
|
||||
"lint:fix": "eslint --fix ./packages --ext .vue,.js,.ts,.jsx,.tsx && prettier --write '**/*.{scss,json,yaml,md,html}'",
|
||||
"website-build": "rimraf website-dist && cross-env NODE_ENV=production webpack --config ./website/webpack.config.js",
|
||||
"website-dev": "webpack-dev-server --config ./website/webpack.config.js",
|
||||
"website-dev:play": "cross-env PLAY_ENV=true yarn website-dev"
|
||||
@ -68,6 +67,8 @@
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"esbuild": "^0.12.5",
|
||||
"eslint": "^7.7.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^7.0.0-beta.0",
|
||||
"esno": "^0.9.1",
|
||||
"fast-glob": "^3.2.7",
|
||||
@ -89,6 +90,7 @@
|
||||
"markdown-it-container": "^3.0.0",
|
||||
"mini-css-extract-plugin": "^0.11.2",
|
||||
"ora": "^5.4.1",
|
||||
"prettier": "^2.3.2",
|
||||
"progress-bar-webpack-plugin": "^2.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.28.2",
|
||||
@ -133,8 +135,8 @@
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,vue}": "eslint --fix",
|
||||
"*.{scss}": "prettier --write"
|
||||
"*.{vue,js,ts,jsx,tsx}": "eslint --fix",
|
||||
"*.{scss,json,yaml,md,html}": "prettier --write"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
|
@ -1,4 +1,4 @@
|
||||
module.exports = jest.fn(fn => {
|
||||
module.exports = jest.fn((fn) => {
|
||||
fn.cancel = jest.fn()
|
||||
fn.flush = jest.fn()
|
||||
return fn
|
||||
|
@ -1,4 +1,4 @@
|
||||
module.exports = jest.fn(fn => {
|
||||
module.exports = jest.fn((fn) => {
|
||||
fn.cancel = jest.fn()
|
||||
fn.flush = jest.fn()
|
||||
return fn
|
||||
|
@ -4,17 +4,26 @@ import Affix from '../src/index.vue'
|
||||
|
||||
let clientHeightRestore = null
|
||||
|
||||
const _mount = (template: string) => mount({
|
||||
components: {
|
||||
'el-affix': Affix,
|
||||
},
|
||||
template,
|
||||
}, { attachTo: document.body })
|
||||
const _mount = (template: string) =>
|
||||
mount(
|
||||
{
|
||||
components: {
|
||||
'el-affix': Affix,
|
||||
},
|
||||
template,
|
||||
},
|
||||
{ attachTo: document.body }
|
||||
)
|
||||
|
||||
const AXIOM = 'Rem is the best girl'
|
||||
|
||||
beforeAll(() => {
|
||||
clientHeightRestore = defineGetter(window.HTMLElement.prototype, 'clientHeight', 1000, 0)
|
||||
clientHeightRestore = defineGetter(
|
||||
window.HTMLElement.prototype,
|
||||
'clientHeight',
|
||||
1000,
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@ -27,18 +36,22 @@ describe('Affix.vue', () => {
|
||||
<el-affix>${AXIOM}</el-affix>
|
||||
`)
|
||||
expect(wrapper.text()).toEqual(AXIOM)
|
||||
const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: -80,
|
||||
} as DOMRect)
|
||||
const mockDocumentRect = jest.spyOn(document.documentElement, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: 0,
|
||||
bottom: 200,
|
||||
} as DOMRect)
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: -80,
|
||||
} as DOMRect)
|
||||
const mockDocumentRect = jest
|
||||
.spyOn(document.documentElement, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: 0,
|
||||
bottom: 200,
|
||||
} as DOMRect)
|
||||
expect(wrapper.find('.el-affix--fixed').exists()).toBe(false)
|
||||
await makeScroll(document.documentElement, 'scrollTop', 200)
|
||||
expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
|
||||
@ -50,21 +63,27 @@ describe('Affix.vue', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-affix :offset="30">${AXIOM}</el-affix>
|
||||
`)
|
||||
const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: -80,
|
||||
} as DOMRect)
|
||||
const mockDocumentRect = jest.spyOn(document.documentElement, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: 0,
|
||||
bottom: 200,
|
||||
} as DOMRect)
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: -80,
|
||||
} as DOMRect)
|
||||
const mockDocumentRect = jest
|
||||
.spyOn(document.documentElement, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: 0,
|
||||
bottom: 200,
|
||||
} as DOMRect)
|
||||
await makeScroll(document.documentElement, 'scrollTop', 200)
|
||||
expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain('top: 30px;')
|
||||
expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain(
|
||||
'top: 30px;'
|
||||
)
|
||||
mockAffixRect.mockRestore()
|
||||
mockDocumentRect.mockRestore()
|
||||
})
|
||||
@ -74,21 +93,27 @@ describe('Affix.vue', () => {
|
||||
<el-affix position="bottom" :offset="20">${AXIOM}</el-affix>
|
||||
`)
|
||||
|
||||
const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: 2000,
|
||||
bottom: 2040,
|
||||
} as DOMRect)
|
||||
const mockDocumentRect = jest.spyOn(document.documentElement, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: 0,
|
||||
bottom: 200,
|
||||
} as DOMRect)
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: 2000,
|
||||
bottom: 2040,
|
||||
} as DOMRect)
|
||||
const mockDocumentRect = jest
|
||||
.spyOn(document.documentElement, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: 0,
|
||||
bottom: 200,
|
||||
} as DOMRect)
|
||||
await makeScroll(document.documentElement, 'scrollTop', 0)
|
||||
expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain('bottom: 20px;')
|
||||
expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain(
|
||||
'bottom: 20px;'
|
||||
)
|
||||
mockAffixRect.mockRestore()
|
||||
mockDocumentRect.mockRestore()
|
||||
})
|
||||
@ -101,18 +126,22 @@ describe('Affix.vue', () => {
|
||||
<div style="height: 1000px"></div>
|
||||
`)
|
||||
|
||||
const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: -60,
|
||||
} as DOMRect)
|
||||
const mockTargetRect = jest.spyOn(wrapper.find('.target').element, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: 100,
|
||||
} as DOMRect)
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: -60,
|
||||
} as DOMRect)
|
||||
const mockTargetRect = jest
|
||||
.spyOn(wrapper.find('.target').element, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: 100,
|
||||
} as DOMRect)
|
||||
await makeScroll(document.documentElement, 'scrollTop', 100)
|
||||
expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
|
||||
mockAffixRect.mockReturnValue({
|
||||
@ -137,21 +166,27 @@ describe('Affix.vue', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-affix :z-index="1000">${AXIOM}</el-affix>
|
||||
`)
|
||||
const mockAffixRect = jest.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: -80,
|
||||
} as DOMRect)
|
||||
const mockDocumentRect = jest.spyOn(document.documentElement, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: 0,
|
||||
bottom: 200,
|
||||
} as DOMRect)
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 40,
|
||||
width: 1000,
|
||||
top: -100,
|
||||
bottom: -80,
|
||||
} as DOMRect)
|
||||
const mockDocumentRect = jest
|
||||
.spyOn(document.documentElement, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 200,
|
||||
width: 1000,
|
||||
top: 0,
|
||||
bottom: 200,
|
||||
} as DOMRect)
|
||||
await makeScroll(document.documentElement, 'scrollTop', 200)
|
||||
expect(wrapper.find('.el-affix--fixed').exists()).toBe(true)
|
||||
expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain('z-index: 1000;')
|
||||
expect(wrapper.find('.el-affix--fixed').attributes('style')).toContain(
|
||||
'z-index: 1000;'
|
||||
)
|
||||
mockAffixRect.mockRestore()
|
||||
mockDocumentRect.mockRestore()
|
||||
})
|
||||
|
@ -4,7 +4,6 @@ import type { App } from 'vue'
|
||||
import type { SFCWithInstall } from '@element-plus/utils/types'
|
||||
|
||||
Affix.install = (app: App): void => {
|
||||
|
||||
app.component(Affix.name, Affix)
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,25 @@
|
||||
<template>
|
||||
<div ref="root" class="el-affix" :style="rootStyle">
|
||||
<div :class="{'el-affix--fixed': state.fixed}" :style="affixStyle">
|
||||
<div :class="{ 'el-affix--fixed': state.fixed }" :style="affixStyle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { getScrollContainer, off, on } from '@element-plus/utils/dom'
|
||||
import { addResizeListener, removeResizeListener } from '@element-plus/utils/resize-event'
|
||||
import {
|
||||
addResizeListener,
|
||||
removeResizeListener,
|
||||
} from '@element-plus/utils/resize-event'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
@ -61,14 +72,16 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
const offset = props.offset ? `${props.offset}px` : 0
|
||||
const transform = state.transform ? `translateY(${state.transform}px)` : ''
|
||||
const transform = state.transform
|
||||
? `translateY(${state.transform}px)`
|
||||
: ''
|
||||
|
||||
return {
|
||||
height: `${state.height}px`,
|
||||
width: `${state.width}px`,
|
||||
top: props.position === 'top' ? offset : '',
|
||||
bottom: props.position === 'bottom' ? offset : '',
|
||||
transform: transform,
|
||||
transform,
|
||||
zIndex: props.zIndex,
|
||||
}
|
||||
})
|
||||
@ -78,7 +91,10 @@ export default defineComponent({
|
||||
const targetRect = target.value.getBoundingClientRect()
|
||||
state.height = rootRect.height
|
||||
state.width = rootRect.width
|
||||
state.scrollTop = scrollContainer.value === window ? document.documentElement.scrollTop : scrollContainer.value.scrollTop
|
||||
state.scrollTop =
|
||||
scrollContainer.value === window
|
||||
? document.documentElement.scrollTop
|
||||
: scrollContainer.value.scrollTop
|
||||
state.clientHeight = document.documentElement.clientHeight
|
||||
|
||||
if (props.position === 'top') {
|
||||
@ -91,8 +107,11 @@ export default defineComponent({
|
||||
}
|
||||
} else {
|
||||
if (props.target) {
|
||||
const difference = state.clientHeight - targetRect.top - props.offset - state.height
|
||||
state.fixed = state.clientHeight - props.offset < rootRect.bottom && state.clientHeight > targetRect.top
|
||||
const difference =
|
||||
state.clientHeight - targetRect.top - props.offset - state.height
|
||||
state.fixed =
|
||||
state.clientHeight - props.offset < rootRect.bottom &&
|
||||
state.clientHeight > targetRect.top
|
||||
state.transform = difference < 0 ? -difference : 0
|
||||
} else {
|
||||
state.fixed = state.clientHeight - props.offset < rootRect.bottom
|
||||
@ -109,9 +128,12 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => state.fixed, () => {
|
||||
emit('change', state.fixed)
|
||||
})
|
||||
watch(
|
||||
() => state.fixed,
|
||||
() => {
|
||||
emit('change', state.fixed)
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.target) {
|
||||
|
@ -24,7 +24,9 @@ describe('Alert.vue', () => {
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-alert').classes()).toContain('el-alert--success')
|
||||
expect(wrapper.find('.el-alert__icon').classes()).toContain('el-icon-success')
|
||||
expect(wrapper.find('.el-alert__icon').classes()).toContain(
|
||||
'el-icon-success'
|
||||
)
|
||||
})
|
||||
|
||||
test('description', () => {
|
||||
|
@ -6,9 +6,17 @@
|
||||
:class="[typeClass, center ? 'is-center' : '', 'is-' + effect]"
|
||||
role="alert"
|
||||
>
|
||||
<i v-if="showIcon" class="el-alert__icon" :class="[ iconClass, isBigIcon ]"></i>
|
||||
<i
|
||||
v-if="showIcon"
|
||||
class="el-alert__icon"
|
||||
:class="[iconClass, isBigIcon]"
|
||||
></i>
|
||||
<div class="el-alert__content">
|
||||
<span v-if="title || $slots.title" class="el-alert__title" :class="[ isBoldTitle ]">
|
||||
<span
|
||||
v-if="title || $slots.title"
|
||||
class="el-alert__title"
|
||||
:class="[isBoldTitle]"
|
||||
>
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</span>
|
||||
<p v-if="$slots.default || !!description" class="el-alert__description">
|
||||
@ -19,7 +27,10 @@
|
||||
<i
|
||||
v-if="closable"
|
||||
class="el-alert__closebtn"
|
||||
:class="{ 'is-customed': closeText !== '', 'el-icon-close': closeText === '' }"
|
||||
:class="{
|
||||
'is-customed': closeText !== '',
|
||||
'el-icon-close': closeText === '',
|
||||
}"
|
||||
@click="close"
|
||||
>
|
||||
{{ closeText }}
|
||||
@ -28,15 +39,15 @@
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from 'vue'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
const TYPE_CLASSES_MAP = {
|
||||
'success': 'el-icon-success',
|
||||
'warning': 'el-icon-warning',
|
||||
'error': 'el-icon-error',
|
||||
success: 'el-icon-success',
|
||||
warning: 'el-icon-warning',
|
||||
error: 'el-icon-error',
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@ -67,7 +78,8 @@ export default defineComponent({
|
||||
effect: {
|
||||
type: String,
|
||||
default: 'light',
|
||||
validator: (value: string): boolean => ['light', 'dark'].indexOf(value) > -1,
|
||||
validator: (value: string): boolean =>
|
||||
['light', 'dark'].indexOf(value) > -1,
|
||||
},
|
||||
},
|
||||
emits: ['close'],
|
||||
@ -76,13 +88,19 @@ export default defineComponent({
|
||||
const visible = ref(true)
|
||||
|
||||
// computed
|
||||
const typeClass = computed(() => `el-alert--${ props.type }`)
|
||||
const iconClass = computed(() => TYPE_CLASSES_MAP[props.type] || 'el-icon-info')
|
||||
const isBigIcon = computed(() => props.description || ctx.slots.default ? 'is-big' : '')
|
||||
const isBoldTitle = computed(() => props.description || ctx.slots.default ? 'is-bold' : '')
|
||||
const typeClass = computed(() => `el-alert--${props.type}`)
|
||||
const iconClass = computed(
|
||||
() => TYPE_CLASSES_MAP[props.type] || 'el-icon-info'
|
||||
)
|
||||
const isBigIcon = computed(() =>
|
||||
props.description || ctx.slots.default ? 'is-big' : ''
|
||||
)
|
||||
const isBoldTitle = computed(() =>
|
||||
props.description || ctx.slots.default ? 'is-bold' : ''
|
||||
)
|
||||
|
||||
// methods
|
||||
const close = evt => {
|
||||
const close = (evt) => {
|
||||
visible.value = false
|
||||
ctx.emit('close', evt)
|
||||
}
|
||||
|
@ -6,28 +6,35 @@ jest.unmock('lodash/debounce')
|
||||
|
||||
import Autocomplete from '../src/index.vue'
|
||||
|
||||
const _mount = (payload = {}) => mount({
|
||||
components: {
|
||||
'el-autocomplete': Autocomplete,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
state: '',
|
||||
list: [
|
||||
{ value: 'Java', tag: 'java' },
|
||||
{ value: 'Go', tag: 'go' },
|
||||
{ value: 'JavaScript', tag: 'javascript' },
|
||||
{ value: 'Python', tag: 'python' },
|
||||
],
|
||||
payload,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
querySearch(queryString, cb) {
|
||||
cb(queryString ? this.list.filter(i => i.value.indexOf(queryString.toLowerCase()) === 0) : this.list)
|
||||
const _mount = (payload = {}) =>
|
||||
mount({
|
||||
components: {
|
||||
'el-autocomplete': Autocomplete,
|
||||
},
|
||||
},
|
||||
template: `
|
||||
data() {
|
||||
return {
|
||||
state: '',
|
||||
list: [
|
||||
{ value: 'Java', tag: 'java' },
|
||||
{ value: 'Go', tag: 'go' },
|
||||
{ value: 'JavaScript', tag: 'javascript' },
|
||||
{ value: 'Python', tag: 'python' },
|
||||
],
|
||||
payload,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
querySearch(queryString, cb) {
|
||||
cb(
|
||||
queryString
|
||||
? this.list.filter(
|
||||
(i) => i.value.indexOf(queryString.toLowerCase()) === 0
|
||||
)
|
||||
: this.list
|
||||
)
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<el-autocomplete
|
||||
ref="autocomplete"
|
||||
v-model="state"
|
||||
@ -35,7 +42,7 @@ const _mount = (payload = {}) => mount({
|
||||
v-bind="payload"
|
||||
/>
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
describe('Autocomplete.vue', () => {
|
||||
beforeEach(() => {
|
||||
@ -76,11 +83,17 @@ describe('Autocomplete.vue', () => {
|
||||
const wrapper = _mount()
|
||||
|
||||
await wrapper.setProps({ popperClass: 'error' })
|
||||
expect(document.body.querySelector('.el-popper').classList.contains('error')).toBe(true)
|
||||
expect(
|
||||
document.body.querySelector('.el-popper').classList.contains('error')
|
||||
).toBe(true)
|
||||
|
||||
await wrapper.setProps({ popperClass: 'success' })
|
||||
expect(document.body.querySelector('.el-popper').classList.contains('error')).toBe(false)
|
||||
expect(document.body.querySelector('.el-popper').classList.contains('success')).toBe(true)
|
||||
expect(
|
||||
document.body.querySelector('.el-popper').classList.contains('error')
|
||||
).toBe(false)
|
||||
expect(
|
||||
document.body.querySelector('.el-popper').classList.contains('success')
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('popperAppendToBody', async () => {
|
||||
|
@ -54,7 +54,10 @@
|
||||
<template #default>
|
||||
<div
|
||||
ref="regionRef"
|
||||
:class="['el-autocomplete-suggestion', suggestionLoading && 'is-loading']"
|
||||
:class="[
|
||||
'el-autocomplete-suggestion',
|
||||
suggestionLoading && 'is-loading',
|
||||
]"
|
||||
:style="{ width: dropdownWidth, outline: 'none' }"
|
||||
role="region"
|
||||
>
|
||||
@ -71,7 +74,7 @@
|
||||
v-for="(item, index) in suggestions"
|
||||
:id="`${id}-item-${index}`"
|
||||
:key="index"
|
||||
:class="{'highlighted': highlightedIndex === index}"
|
||||
:class="{ highlighted: highlightedIndex === index }"
|
||||
role="option"
|
||||
:aria-selected="highlightedIndex === index"
|
||||
@click="select(item)"
|
||||
@ -87,9 +90,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent, ref, computed,
|
||||
onMounted, onUpdated,
|
||||
nextTick, watch,
|
||||
defineComponent,
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
onUpdated,
|
||||
nextTick,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { NOOP } from '@vue/shared'
|
||||
import debounce from 'lodash/debounce'
|
||||
@ -131,12 +138,21 @@ export default defineComponent({
|
||||
placement: {
|
||||
type: String as PropType<Placement>,
|
||||
validator: (val: string): boolean => {
|
||||
return ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end'].includes(val)
|
||||
return [
|
||||
'top',
|
||||
'top-start',
|
||||
'top-end',
|
||||
'bottom',
|
||||
'bottom-start',
|
||||
'bottom-end',
|
||||
].includes(val)
|
||||
},
|
||||
default: 'bottom-start',
|
||||
},
|
||||
fetchSuggestions: {
|
||||
type: Function as PropType<(queryString: string, cb: (data: any[]) => void) => void>,
|
||||
type: Function as PropType<
|
||||
(queryString: string, cb: (data: any[]) => void) => void
|
||||
>,
|
||||
default: NOOP,
|
||||
},
|
||||
popperClass: {
|
||||
@ -164,7 +180,15 @@ export default defineComponent({
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: [UPDATE_MODEL_EVENT, 'input', 'change', 'focus', 'blur', 'clear', 'select'],
|
||||
emits: [
|
||||
UPDATE_MODEL_EVENT,
|
||||
'input',
|
||||
'change',
|
||||
'focus',
|
||||
'blur',
|
||||
'clear',
|
||||
'select',
|
||||
],
|
||||
setup(props, ctx) {
|
||||
const attrs = useAttrs()
|
||||
const suggestions = ref([])
|
||||
@ -181,7 +205,8 @@ export default defineComponent({
|
||||
return `el-autocomplete-${generateId()}`
|
||||
})
|
||||
const suggestionVisible = computed(() => {
|
||||
const isValidData = isArray(suggestions.value) && suggestions.value.length > 0
|
||||
const isValidData =
|
||||
isArray(suggestions.value) && suggestions.value.length > 0
|
||||
return (isValidData || loading.value) && activated.value
|
||||
})
|
||||
const suggestionLoading = computed(() => {
|
||||
@ -200,21 +225,26 @@ export default defineComponent({
|
||||
inputRef.value.inputOrTextarea.setAttribute('role', 'textbox')
|
||||
inputRef.value.inputOrTextarea.setAttribute('aria-autocomplete', 'list')
|
||||
inputRef.value.inputOrTextarea.setAttribute('aria-controls', 'id')
|
||||
inputRef.value.inputOrTextarea.setAttribute('aria-activedescendant', `${id.value}-item-${highlightedIndex.value}`)
|
||||
const $ul = regionRef.value.querySelector('.el-autocomplete-suggestion__list')
|
||||
inputRef.value.inputOrTextarea.setAttribute(
|
||||
'aria-activedescendant',
|
||||
`${id.value}-item-${highlightedIndex.value}`
|
||||
)
|
||||
const $ul = regionRef.value.querySelector(
|
||||
'.el-autocomplete-suggestion__list'
|
||||
)
|
||||
$ul.setAttribute('role', 'listbox')
|
||||
$ul.setAttribute('id', id.value)
|
||||
})
|
||||
|
||||
onUpdated(updatePopperPosition)
|
||||
|
||||
const getData = queryString => {
|
||||
const getData = (queryString) => {
|
||||
if (suggestionDisabled.value) {
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
updatePopperPosition()
|
||||
props.fetchSuggestions(queryString, suggestionsArg => {
|
||||
props.fetchSuggestions(queryString, (suggestionsArg) => {
|
||||
loading.value = false
|
||||
if (suggestionDisabled.value) {
|
||||
return
|
||||
@ -223,12 +253,15 @@ export default defineComponent({
|
||||
suggestions.value = suggestionsArg
|
||||
highlightedIndex.value = props.highlightFirstItem ? 0 : -1
|
||||
} else {
|
||||
throwError('ElAutocomplete', 'autocomplete suggestions must be an array')
|
||||
throwError(
|
||||
'ElAutocomplete',
|
||||
'autocomplete suggestions must be an array'
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
const debouncedGetData = debounce(getData, props.debounce)
|
||||
const handleInput = value => {
|
||||
const handleInput = (value) => {
|
||||
ctx.emit('input', value)
|
||||
ctx.emit(UPDATE_MODEL_EVENT, value)
|
||||
suggestionDisabled.value = false
|
||||
@ -239,17 +272,17 @@ export default defineComponent({
|
||||
}
|
||||
debouncedGetData(value)
|
||||
}
|
||||
const handleChange = value => {
|
||||
const handleChange = (value) => {
|
||||
ctx.emit('change', value)
|
||||
}
|
||||
const handleFocus = e => {
|
||||
const handleFocus = (e) => {
|
||||
activated.value = true
|
||||
ctx.emit('focus', e)
|
||||
if (props.triggerOnFocus) {
|
||||
debouncedGetData(props.modelValue)
|
||||
}
|
||||
}
|
||||
const handleBlur = e => {
|
||||
const handleBlur = (e) => {
|
||||
ctx.emit('blur', e)
|
||||
}
|
||||
const handleClear = () => {
|
||||
@ -258,9 +291,10 @@ export default defineComponent({
|
||||
ctx.emit('clear')
|
||||
}
|
||||
const handleKeyEnter = () => {
|
||||
if (suggestionVisible.value
|
||||
&& highlightedIndex.value >= 0
|
||||
&& highlightedIndex.value < suggestions.value.length
|
||||
if (
|
||||
suggestionVisible.value &&
|
||||
highlightedIndex.value >= 0 &&
|
||||
highlightedIndex.value < suggestions.value.length
|
||||
) {
|
||||
select(suggestions.value[highlightedIndex.value])
|
||||
} else if (props.selectWhenUnmatched) {
|
||||
@ -277,7 +311,7 @@ export default defineComponent({
|
||||
const focus = () => {
|
||||
inputRef.value.focus()
|
||||
}
|
||||
const select = item => {
|
||||
const select = (item) => {
|
||||
ctx.emit('input', item[props.valueKey])
|
||||
ctx.emit(UPDATE_MODEL_EVENT, item[props.valueKey])
|
||||
ctx.emit('select', item)
|
||||
@ -286,7 +320,7 @@ export default defineComponent({
|
||||
highlightedIndex.value = -1
|
||||
})
|
||||
}
|
||||
const highlight = index => {
|
||||
const highlight = (index) => {
|
||||
if (!suggestionVisible.value || loading.value) {
|
||||
return
|
||||
}
|
||||
@ -297,24 +331,29 @@ export default defineComponent({
|
||||
if (index >= suggestions.value.length) {
|
||||
index = suggestions.value.length - 1
|
||||
}
|
||||
const suggestion = regionRef.value.querySelector('.el-autocomplete-suggestion__wrap')
|
||||
const suggestionList = suggestion.querySelectorAll('.el-autocomplete-suggestion__list li')
|
||||
const suggestion = regionRef.value.querySelector(
|
||||
'.el-autocomplete-suggestion__wrap'
|
||||
)
|
||||
const suggestionList = suggestion.querySelectorAll(
|
||||
'.el-autocomplete-suggestion__list li'
|
||||
)
|
||||
const highlightItem = suggestionList[index]
|
||||
const scrollTop = suggestion.scrollTop
|
||||
const { offsetTop, scrollHeight } = highlightItem
|
||||
|
||||
if (offsetTop + scrollHeight > (scrollTop + suggestion.clientHeight)) {
|
||||
if (offsetTop + scrollHeight > scrollTop + suggestion.clientHeight) {
|
||||
suggestion.scrollTop += scrollHeight
|
||||
}
|
||||
if (offsetTop < scrollTop) {
|
||||
suggestion.scrollTop -= scrollHeight
|
||||
}
|
||||
highlightedIndex.value = index
|
||||
inputRef.value.inputOrTextarea.setAttribute('aria-activedescendant', `${id.value}-item-${highlightedIndex.value}`)
|
||||
inputRef.value.inputOrTextarea.setAttribute(
|
||||
'aria-activedescendant',
|
||||
`${id.value}-item-${highlightedIndex.value}`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return {
|
||||
Effect,
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { nextTick } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { IMAGE_SUCCESS, IMAGE_FAIL, mockImageEvent } from '@element-plus/test-utils'
|
||||
import {
|
||||
IMAGE_SUCCESS,
|
||||
IMAGE_FAIL,
|
||||
mockImageEvent,
|
||||
} from '@element-plus/test-utils'
|
||||
|
||||
import Avatar from '../src/index.vue'
|
||||
|
||||
@ -66,7 +70,9 @@ describe('Avatar.vue', () => {
|
||||
const wrapper = mount(Avatar, {
|
||||
props: { fit, src: IMAGE_SUCCESS },
|
||||
})
|
||||
expect(wrapper.find('img').attributes('style')).toContain(`object-fit: ${fit};`)
|
||||
expect(wrapper.find('img').attributes('style')).toContain(
|
||||
`object-fit: ${fit};`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@ -84,4 +90,3 @@ describe('Avatar.vue', () => {
|
||||
expect(wrapper.find('img').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
:srcset="srcSet"
|
||||
:style="fitStyle"
|
||||
@error="handleError"
|
||||
>
|
||||
/>
|
||||
<i v-else-if="icon" :class="icon"></i>
|
||||
<slot v-else></slot>
|
||||
</span>
|
||||
@ -56,7 +56,7 @@ export default defineComponent({
|
||||
|
||||
const src = toRef(props, 'src')
|
||||
// need reset hasLoadError to false if src changed
|
||||
watch(src,()=>{
|
||||
watch(src, () => {
|
||||
hasLoadError.value = false
|
||||
})
|
||||
|
||||
@ -77,23 +77,31 @@ export default defineComponent({
|
||||
|
||||
const sizeStyle = computed(() => {
|
||||
const { size } = props
|
||||
return typeof size === 'number' ? {
|
||||
height: `${size}px`,
|
||||
width: `${size}px`,
|
||||
lineHeight: `${size}px`,
|
||||
} : {}
|
||||
return typeof size === 'number'
|
||||
? {
|
||||
height: `${size}px`,
|
||||
width: `${size}px`,
|
||||
lineHeight: `${size}px`,
|
||||
}
|
||||
: {}
|
||||
})
|
||||
|
||||
const fitStyle = computed(() => ({
|
||||
objectFit: props.fit,
|
||||
}) as CSSProperties)
|
||||
const fitStyle = computed(
|
||||
() =>
|
||||
({
|
||||
objectFit: props.fit,
|
||||
} as CSSProperties)
|
||||
)
|
||||
|
||||
function handleError(e: Event) {
|
||||
hasLoadError.value = true
|
||||
emit(ERROR_EVENT, e)
|
||||
}
|
||||
return {
|
||||
hasLoadError, avatarClass, sizeStyle, handleError,
|
||||
hasLoadError,
|
||||
avatarClass,
|
||||
sizeStyle,
|
||||
handleError,
|
||||
fitStyle,
|
||||
}
|
||||
},
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Backtop from '../src/index.vue'
|
||||
|
||||
const _mount = (template: string) => mount({
|
||||
components: {
|
||||
'el-backtop': Backtop,
|
||||
},
|
||||
template,
|
||||
}, { attachTo: document.body })
|
||||
const _mount = (template: string) =>
|
||||
mount(
|
||||
{
|
||||
components: {
|
||||
'el-backtop': Backtop,
|
||||
},
|
||||
template,
|
||||
},
|
||||
{ attachTo: document.body }
|
||||
)
|
||||
|
||||
describe('Backtop.vue', () => {
|
||||
test('render', async () => {
|
||||
@ -22,7 +26,9 @@ describe('Backtop.vue', () => {
|
||||
await wrapper.trigger('scroll')
|
||||
expect(wrapper.find('.el-backtop').exists()).toBe(true)
|
||||
|
||||
expect(wrapper.find('.el-backtop').attributes('style')).toBe('right: 100px; bottom: 200px;')
|
||||
expect(wrapper.find('.el-backtop').attributes('style')).toBe(
|
||||
'right: 100px; bottom: 200px;'
|
||||
)
|
||||
expect(wrapper.find('.el-icon-caret-top').exists()).toBe(true)
|
||||
|
||||
await wrapper.trigger('click')
|
||||
|
@ -3,8 +3,8 @@
|
||||
<div
|
||||
v-if="visible"
|
||||
:style="{
|
||||
'right': styleRight,
|
||||
'bottom': styleBottom
|
||||
right: styleRight,
|
||||
bottom: styleBottom,
|
||||
}"
|
||||
class="el-backtop"
|
||||
@click.stop="handleClick"
|
||||
@ -62,7 +62,8 @@ export default defineComponent({
|
||||
const scrollToTop = () => {
|
||||
const beginTime = Date.now()
|
||||
const beginValue = el.value.scrollTop
|
||||
const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16))
|
||||
const rAF =
|
||||
window.requestAnimationFrame || ((func) => setTimeout(func, 16))
|
||||
const frameFunc = () => {
|
||||
const progress = (Date.now() - beginTime) / 500
|
||||
if (progress < 1) {
|
||||
@ -77,7 +78,7 @@ export default defineComponent({
|
||||
const onScroll = () => {
|
||||
visible.value = el.value.scrollTop >= props.visibilityHeight
|
||||
}
|
||||
const handleClick = event => {
|
||||
const handleClick = (event) => {
|
||||
scrollToTop()
|
||||
ctx.emit('click', event)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
isDot ? 'is-dot' : 'el-badge__content--' + type,
|
||||
{
|
||||
'is-fixed': $slots.default,
|
||||
}
|
||||
},
|
||||
]"
|
||||
v-text="content"
|
||||
>
|
||||
|
@ -2,19 +2,23 @@ import { mount } from '@vue/test-utils'
|
||||
import Breadcrumb from '../src/index.vue'
|
||||
import BreadcrumbItem from '../src/item.vue'
|
||||
|
||||
const _mount = (template: string) => mount({
|
||||
components: {
|
||||
'el-breadcrumb': Breadcrumb,
|
||||
'el-breadcrumb-item': BreadcrumbItem,
|
||||
},
|
||||
template,
|
||||
}, {
|
||||
global: {
|
||||
provide: {
|
||||
breadcrumb: {},
|
||||
const _mount = (template: string) =>
|
||||
mount(
|
||||
{
|
||||
components: {
|
||||
'el-breadcrumb': Breadcrumb,
|
||||
'el-breadcrumb-item': BreadcrumbItem,
|
||||
},
|
||||
template,
|
||||
},
|
||||
},
|
||||
})
|
||||
{
|
||||
global: {
|
||||
provide: {
|
||||
breadcrumb: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
describe('Breadcrumb.vue', () => {
|
||||
test('separator', () => {
|
||||
@ -33,7 +37,9 @@ describe('Breadcrumb.vue', () => {
|
||||
</el-breadcrumb>
|
||||
`)
|
||||
expect(wrapper.find('.el-breadcrumb__separator').text()).toBe('')
|
||||
expect(wrapper.find('.el-breadcrumb__separator').classes()).toContain('test')
|
||||
expect(wrapper.find('.el-breadcrumb__separator').classes()).toContain(
|
||||
'test'
|
||||
)
|
||||
})
|
||||
|
||||
test('to', () => {
|
||||
|
@ -74,7 +74,6 @@ describe('Button.vue', () => {
|
||||
})
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.emitted()).toBeDefined()
|
||||
|
||||
})
|
||||
|
||||
test('handle click inside', async () => {
|
||||
@ -106,7 +105,6 @@ describe('Button.vue', () => {
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.emitted('click')).toBeUndefined()
|
||||
})
|
||||
|
||||
})
|
||||
describe('Button Group', () => {
|
||||
it('create', () => {
|
||||
@ -127,22 +125,31 @@ describe('Button Group', () => {
|
||||
it('button group reactive size', async () => {
|
||||
const size = ref('small')
|
||||
const wrapper = mount({
|
||||
setup(){
|
||||
return () => h(ButtonGroup, { size: size.value }, () => [
|
||||
h(Button, { type: 'primary' }, () => 'Prev'),
|
||||
h(Button, { type: 'primary' }, () => 'Next'),
|
||||
h(Button, { type: 'primary', size :'mini' }, () => 'Mini'),
|
||||
])
|
||||
setup() {
|
||||
return () =>
|
||||
h(ButtonGroup, { size: size.value }, () => [
|
||||
h(Button, { type: 'primary' }, () => 'Prev'),
|
||||
h(Button, { type: 'primary' }, () => 'Next'),
|
||||
h(Button, { type: 'primary', size: 'mini' }, () => 'Mini'),
|
||||
])
|
||||
},
|
||||
})
|
||||
expect(wrapper.classes()).toContain('el-button-group')
|
||||
expect(wrapper.findAll('.el-button-group button.el-button--small').length).toBe(2)
|
||||
expect(wrapper.findAll('.el-button-group button.el-button--mini').length).toBe(1)
|
||||
expect(
|
||||
wrapper.findAll('.el-button-group button.el-button--small').length
|
||||
).toBe(2)
|
||||
expect(
|
||||
wrapper.findAll('.el-button-group button.el-button--mini').length
|
||||
).toBe(1)
|
||||
|
||||
size.value = 'medium'
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.findAll('.el-button-group button.el-button--medium').length).toBe(2)
|
||||
expect(wrapper.findAll('.el-button-group button.el-button--mini').length).toBe(1)
|
||||
expect(
|
||||
wrapper.findAll('.el-button-group button.el-button--medium').length
|
||||
).toBe(2)
|
||||
expect(
|
||||
wrapper.findAll('.el-button-group button.el-button--mini').length
|
||||
).toBe(1)
|
||||
})
|
||||
})
|
||||
|
@ -19,10 +19,13 @@ export default defineComponent({
|
||||
validator: isValidComponentSize,
|
||||
},
|
||||
},
|
||||
setup(props){
|
||||
provide(elButtonGroupKey, reactive({
|
||||
size: toRef(props,'size'),
|
||||
}))
|
||||
setup(props) {
|
||||
provide(
|
||||
elButtonGroupKey,
|
||||
reactive({
|
||||
size: toRef(props, 'size'),
|
||||
})
|
||||
)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
@ -9,8 +9,8 @@
|
||||
'is-loading': loading,
|
||||
'is-plain': plain,
|
||||
'is-round': round,
|
||||
'is-circle': circle
|
||||
}
|
||||
'is-circle': circle,
|
||||
},
|
||||
]"
|
||||
:disabled="buttonDisabled || loading"
|
||||
:autofocus="autofocus"
|
||||
@ -23,7 +23,7 @@
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import { computed, inject, defineComponent } from 'vue'
|
||||
import { elFormKey, elFormItemKey } from '@element-plus/tokens'
|
||||
import { useGlobalConfig } from '@element-plus/utils/util'
|
||||
|
@ -1,2 +1,9 @@
|
||||
export type ButtonType = 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text' | 'default'
|
||||
export type ButtonType =
|
||||
| 'primary'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'danger'
|
||||
| 'info'
|
||||
| 'text'
|
||||
| 'default'
|
||||
export type ButtonNativeType = 'button' | 'submit' | 'reset'
|
||||
|
@ -2,34 +2,44 @@ import { mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
import Calendar from '../src/index.vue'
|
||||
|
||||
const _mount = (template: string, data?, otherObj?) => mount({
|
||||
components: {
|
||||
'el-calendar': Calendar,
|
||||
},
|
||||
template,
|
||||
data,
|
||||
...otherObj,
|
||||
})
|
||||
const _mount = (template: string, data?, otherObj?) =>
|
||||
mount({
|
||||
components: {
|
||||
'el-calendar': Calendar,
|
||||
},
|
||||
template,
|
||||
data,
|
||||
...otherObj,
|
||||
})
|
||||
|
||||
describe('Calendar.vue', () => {
|
||||
it('create', async() => {
|
||||
const wrapper = _mount(`
|
||||
it('create', async () => {
|
||||
const wrapper = _mount(
|
||||
`
|
||||
<el-calendar v-model="value"></el-calendar>
|
||||
`, () => ({ value: new Date('2019-04-01') }))
|
||||
`,
|
||||
() => ({ value: new Date('2019-04-01') })
|
||||
)
|
||||
const titleEl = wrapper.find('.el-calendar__title')
|
||||
expect(/2019.*April/.test((titleEl.element as HTMLElement).innerHTML)).toBeTruthy()
|
||||
expect(
|
||||
/2019.*April/.test((titleEl.element as HTMLElement).innerHTML)
|
||||
).toBeTruthy()
|
||||
expect(wrapper.element.querySelectorAll('thead th').length).toBe(7)
|
||||
const rows = wrapper.element.querySelectorAll('.el-calendar-table__row')
|
||||
expect(rows.length).toBe(6);
|
||||
(rows[5].firstElementChild as HTMLElement).click()
|
||||
expect(rows.length).toBe(6)
|
||||
;(rows[5].firstElementChild as HTMLElement).click()
|
||||
|
||||
await nextTick()
|
||||
expect(/2019.*May/.test((titleEl.element as HTMLElement).innerHTML)).toBeTruthy()
|
||||
expect(
|
||||
/2019.*May/.test((titleEl.element as HTMLElement).innerHTML)
|
||||
).toBeTruthy()
|
||||
const vm = wrapper.vm as any
|
||||
const date = vm.value
|
||||
expect(date.getFullYear()).toBe(2019)
|
||||
expect(date.getMonth()).toBe(4)
|
||||
expect((wrapper.find('.is-selected span').element as HTMLElement).innerHTML).toBe('5')
|
||||
expect(
|
||||
(wrapper.find('.is-selected span').element as HTMLElement).innerHTML
|
||||
).toBe('5')
|
||||
})
|
||||
|
||||
it('range', () => {
|
||||
@ -37,10 +47,14 @@ describe('Calendar.vue', () => {
|
||||
<el-calendar :range="[new Date(2019, 2, 4), new Date(2019, 2, 24)]"></el-calendar>
|
||||
`)
|
||||
const titleEl = wrapper.find('.el-calendar__title')
|
||||
expect(/2019.*March/.test((titleEl.element as HTMLElement).innerHTML)).toBeTruthy()
|
||||
expect(
|
||||
/2019.*March/.test((titleEl.element as HTMLElement).innerHTML)
|
||||
).toBeTruthy()
|
||||
const rows = wrapper.element.querySelectorAll('.el-calendar-table__row')
|
||||
expect(rows.length).toBe(4)
|
||||
expect(wrapper.element.querySelector('.el-calendar__button-group')).toBeNull()
|
||||
expect(
|
||||
wrapper.element.querySelector('.el-calendar__button-group')
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
// https://github.com/element-plus/element-plus/issues/3155
|
||||
@ -49,68 +63,92 @@ describe('Calendar.vue', () => {
|
||||
<el-calendar :range="[new Date(2021, 1, 2), new Date(2021, 1, 28)]"></el-calendar>
|
||||
`)
|
||||
const titleEl = wrapper.find('.el-calendar__title')
|
||||
expect(/2021.*January/.test((titleEl.element as HTMLElement).innerHTML)).toBeTruthy()
|
||||
expect(
|
||||
/2021.*January/.test((titleEl.element as HTMLElement).innerHTML)
|
||||
).toBeTruthy()
|
||||
const rows = wrapper.element.querySelectorAll('.el-calendar-table__row')
|
||||
expect(rows.length).toBe(5)
|
||||
expect(wrapper.element.querySelector('.el-calendar__button-group')).toBeNull()
|
||||
expect(
|
||||
wrapper.element.querySelector('.el-calendar__button-group')
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('range tow monthes', async() => {
|
||||
it('range tow monthes', async () => {
|
||||
const wrapper = _mount(`
|
||||
<el-calendar :range="[new Date(2019, 3, 14), new Date(2019, 4, 18)]"></el-calendar>
|
||||
`)
|
||||
const titleEl = wrapper.find('.el-calendar__title')
|
||||
expect(/2019.*April/.test((titleEl.element as HTMLElement).innerHTML)).toBeTruthy()
|
||||
const dateTables = wrapper.element.querySelectorAll('.el-calendar-table.is-range')
|
||||
expect(
|
||||
/2019.*April/.test((titleEl.element as HTMLElement).innerHTML)
|
||||
).toBeTruthy()
|
||||
const dateTables = wrapper.element.querySelectorAll(
|
||||
'.el-calendar-table.is-range'
|
||||
)
|
||||
expect(dateTables.length).toBe(2)
|
||||
const rows = wrapper.element.querySelectorAll('.el-calendar-table__row')
|
||||
expect(rows.length).toBe(5)
|
||||
const cell = rows[rows.length - 1].firstElementChild;
|
||||
(cell as HTMLElement).click()
|
||||
const cell = rows[rows.length - 1].firstElementChild
|
||||
;(cell as HTMLElement).click()
|
||||
|
||||
await nextTick()
|
||||
|
||||
expect(/2019.*May/.test((titleEl.element as HTMLElement).innerHTML)).toBeTruthy()
|
||||
expect(
|
||||
/2019.*May/.test((titleEl.element as HTMLElement).innerHTML)
|
||||
).toBeTruthy()
|
||||
expect(cell.classList.contains('is-selected')).toBeTruthy()
|
||||
})
|
||||
|
||||
// https://github.com/element-plus/element-plus/issues/3155
|
||||
it('range tow monthes when the start date will be calculated to last month', async() => {
|
||||
it('range tow monthes when the start date will be calculated to last month', async () => {
|
||||
const wrapper = _mount(`
|
||||
<el-calendar :range="[new Date(2021, 1, 2), new Date(2021, 2, 21)]"></el-calendar>
|
||||
`)
|
||||
const titleEl = wrapper.find('.el-calendar__title')
|
||||
expect(/2021.*January/.test((titleEl.element as HTMLElement).innerHTML)).toBeTruthy()
|
||||
const dateTables = wrapper.element.querySelectorAll('.el-calendar-table.is-range')
|
||||
expect(
|
||||
/2021.*January/.test((titleEl.element as HTMLElement).innerHTML)
|
||||
).toBeTruthy()
|
||||
const dateTables = wrapper.element.querySelectorAll(
|
||||
'.el-calendar-table.is-range'
|
||||
)
|
||||
expect(dateTables.length).toBe(3)
|
||||
const rows = wrapper.element.querySelectorAll('.el-calendar-table__row')
|
||||
expect(rows.length).toBe(8)
|
||||
const cell = rows[rows.length - 1].firstElementChild;
|
||||
(cell as HTMLElement).click()
|
||||
const cell = rows[rows.length - 1].firstElementChild
|
||||
;(cell as HTMLElement).click()
|
||||
|
||||
await nextTick()
|
||||
|
||||
expect(/2021.*March/.test((titleEl.element as HTMLElement).innerHTML)).toBeTruthy()
|
||||
expect(
|
||||
/2021.*March/.test((titleEl.element as HTMLElement).innerHTML)
|
||||
).toBeTruthy()
|
||||
expect(cell.classList.contains('is-selected')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('firstDayOfWeek', async () => {
|
||||
// default en locale, weekStart 0 Sunday
|
||||
const wrapper = _mount(`
|
||||
const wrapper = _mount(
|
||||
`
|
||||
<el-calendar v-model="value"></el-calendar>
|
||||
`, () => ({ value: new Date('2019-04-01') }))
|
||||
`,
|
||||
() => ({ value: new Date('2019-04-01') })
|
||||
)
|
||||
const head = wrapper.element.querySelector('.el-calendar-table thead')
|
||||
expect((head.firstElementChild as HTMLElement).innerHTML).toBe('Sun')
|
||||
expect((head.lastElementChild as HTMLElement).innerHTML).toBe('Sat')
|
||||
const firstRow = wrapper.element.querySelector('.el-calendar-table__row')
|
||||
expect((firstRow.firstElementChild as HTMLElement).innerHTML).toContain('31')
|
||||
expect((firstRow.firstElementChild as HTMLElement).innerHTML).toContain(
|
||||
'31'
|
||||
)
|
||||
expect((firstRow.lastElementChild as HTMLElement).innerHTML).toContain('6')
|
||||
})
|
||||
|
||||
it('firstDayOfWeek in range mode', async () => {
|
||||
const wrapper = _mount(`
|
||||
const wrapper = _mount(
|
||||
`
|
||||
<el-calendar v-model="value" :first-day-of-week="7" :range="[new Date(2019, 1, 3), new Date(2019, 2, 23)]"></el-calendar>
|
||||
`, () => ({ value: new Date('2019-03-04') }))
|
||||
`,
|
||||
() => ({ value: new Date('2019-03-04') })
|
||||
)
|
||||
const head = wrapper.element.querySelector('.el-calendar-table thead')
|
||||
expect((head.firstElementChild as HTMLElement).innerHTML).toBe('Sun')
|
||||
expect((head.lastElementChild as HTMLElement).innerHTML).toBe('Sat')
|
||||
|
@ -2,7 +2,7 @@
|
||||
<table
|
||||
:class="{
|
||||
'el-calendar-table': true,
|
||||
'is-range': isInRange
|
||||
'is-range': isInRange,
|
||||
}"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
@ -16,7 +16,7 @@
|
||||
:key="index"
|
||||
:class="{
|
||||
'el-calendar-table__row': true,
|
||||
'el-calendar-table__row--hide-border': index === 0 && hideHeader
|
||||
'el-calendar-table__row--hide-border': index === 0 && hideHeader,
|
||||
}"
|
||||
>
|
||||
<td
|
||||
@ -26,10 +26,7 @@
|
||||
@click="pickDay(cell)"
|
||||
>
|
||||
<div class="el-calendar-day">
|
||||
<slot
|
||||
name="dateCell"
|
||||
:data="getSlotData(cell)"
|
||||
>
|
||||
<slot name="dateCell" :data="getSlotData(cell)">
|
||||
<span>{{ cell.text }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
@ -40,11 +37,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
} from 'vue'
|
||||
import { computed, defineComponent, ref } from 'vue'
|
||||
import { PropType } from 'vue'
|
||||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import localeData from 'dayjs/plugin/localeData'
|
||||
@ -80,14 +73,16 @@ export default defineComponent({
|
||||
emits: ['pick'],
|
||||
setup(props, ctx) {
|
||||
const { lang } = useLocaleInject()
|
||||
const WEEK_DAYS = ref(dayjs().locale(lang.value).localeData().weekdaysShort())
|
||||
const WEEK_DAYS = ref(
|
||||
dayjs().locale(lang.value).localeData().weekdaysShort()
|
||||
)
|
||||
|
||||
const now = dayjs().locale(lang.value)
|
||||
|
||||
// todo better way to get Day.js locale object
|
||||
const firstDayOfWeek = (now as any).$locale().weekStart || 0
|
||||
|
||||
const toNestedArr = days => {
|
||||
const toNestedArr = (days) => {
|
||||
return rangeArr(days.length / 7).map((_, index) => {
|
||||
const start = index * 7
|
||||
return days.slice(start, start + 7)
|
||||
@ -143,12 +138,12 @@ export default defineComponent({
|
||||
let days = []
|
||||
if (isInRange.value) {
|
||||
const [start, end] = props.range
|
||||
const currentMonthRange = rangeArr(
|
||||
end.date() - start.date() + 1,
|
||||
).map((_, index) => ({
|
||||
text: start.date() + index,
|
||||
type: 'current',
|
||||
}))
|
||||
const currentMonthRange = rangeArr(end.date() - start.date() + 1).map(
|
||||
(_, index) => ({
|
||||
text: start.date() + index,
|
||||
type: 'current',
|
||||
})
|
||||
)
|
||||
|
||||
let remaining = currentMonthRange.length % 7
|
||||
remaining = remaining === 0 ? 0 : 7 - remaining
|
||||
@ -161,12 +156,12 @@ export default defineComponent({
|
||||
const firstDay = props.date.startOf('month').day() || 7
|
||||
const prevMonthDays = getPrevMonthLastDays(
|
||||
props.date,
|
||||
firstDay - firstDayOfWeek,
|
||||
).map(day => ({
|
||||
firstDay - firstDayOfWeek
|
||||
).map((day) => ({
|
||||
text: day,
|
||||
type: 'prev',
|
||||
}))
|
||||
const currentMonthDays = getMonthDays(props.date).map(day => ({
|
||||
const currentMonthDays = getMonthDays(props.date).map((day) => ({
|
||||
text: day,
|
||||
type: 'current',
|
||||
}))
|
||||
@ -202,5 +197,4 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
@ -4,32 +4,20 @@
|
||||
<div class="el-calendar__title">{{ i18nDate }}</div>
|
||||
<div v-if="validatedRange.length === 0" class="el-calendar__button-group">
|
||||
<el-button-group>
|
||||
<el-button
|
||||
size="mini"
|
||||
@click="selectDate('prev-month')"
|
||||
>
|
||||
<el-button size="mini" @click="selectDate('prev-month')">
|
||||
{{ t('el.datepicker.prevMonth') }}
|
||||
</el-button>
|
||||
<el-button size="mini" @click="selectDate('today')">
|
||||
{{
|
||||
t('el.datepicker.today')
|
||||
}}
|
||||
{{ t('el.datepicker.today') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
@click="selectDate('next-month')"
|
||||
>
|
||||
<el-button size="mini" @click="selectDate('next-month')">
|
||||
{{ t('el.datepicker.nextMonth') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="validatedRange.length === 0" class="el-calendar__body">
|
||||
<date-table
|
||||
:date="date"
|
||||
:selected-day="realSelectedDay"
|
||||
@pick="pickDay"
|
||||
>
|
||||
<date-table :date="date" :selected-day="realSelectedDay" @pick="pickDay">
|
||||
<template v-if="$slots.dateCell" #dateCell="data">
|
||||
<slot name="dateCell" v-bind="data"></slot>
|
||||
</template>
|
||||
@ -54,11 +42,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
} from 'vue'
|
||||
import { ref, computed, defineComponent } from 'vue'
|
||||
import type { PropType, ComputedRef } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
@ -90,10 +74,7 @@ export default defineComponent({
|
||||
validator: (range: Date): boolean => {
|
||||
if (Array.isArray(range)) {
|
||||
return (
|
||||
range.length === 2 &&
|
||||
range.every(
|
||||
item => item instanceof Date,
|
||||
)
|
||||
range.length === 2 && range.every((item) => item instanceof Date)
|
||||
)
|
||||
}
|
||||
return false
|
||||
@ -152,42 +133,58 @@ export default defineComponent({
|
||||
|
||||
// https://github.com/element-plus/element-plus/issues/3155
|
||||
// Calculate the validate date range according to the start and end dates
|
||||
const calculateValidatedDateRange = (startDayjs: dayjs.Dayjs,endDayjs: dayjs.Dayjs) => {
|
||||
const calculateValidatedDateRange = (
|
||||
startDayjs: dayjs.Dayjs,
|
||||
endDayjs: dayjs.Dayjs
|
||||
) => {
|
||||
const firstDay = startDayjs.startOf('week')
|
||||
const lastDay = endDayjs.endOf('week')
|
||||
const firstMonth = firstDay.get('month')
|
||||
const lastMonth = lastDay.get('month')
|
||||
|
||||
// Current mouth
|
||||
if(firstMonth === lastMonth){
|
||||
if (firstMonth === lastMonth) {
|
||||
return [[firstDay, lastDay]]
|
||||
}
|
||||
// Two adjacent months
|
||||
else if(firstMonth + 1 === lastMonth ){
|
||||
|
||||
else if (firstMonth + 1 === lastMonth) {
|
||||
const firstMonthLastDay = firstDay.endOf('month')
|
||||
const lastMonthFirstDay = lastDay.startOf('month')
|
||||
|
||||
// Whether the last day of the first month and the first day of the last month is in the same week
|
||||
const isSameWeek = firstMonthLastDay.isSame(lastMonthFirstDay, 'week')
|
||||
const lastMonthStartDay = isSameWeek ? lastMonthFirstDay.add(1, 'week') : lastMonthFirstDay
|
||||
const lastMonthStartDay = isSameWeek
|
||||
? lastMonthFirstDay.add(1, 'week')
|
||||
: lastMonthFirstDay
|
||||
|
||||
return [[firstDay, firstMonthLastDay], [lastMonthStartDay.startOf('week'), lastDay]]
|
||||
return [
|
||||
[firstDay, firstMonthLastDay],
|
||||
[lastMonthStartDay.startOf('week'), lastDay],
|
||||
]
|
||||
}
|
||||
// Three consecutive months (compatible: 2021-01-30 to 2021-02-28)
|
||||
else if(firstMonth + 2 === lastMonth ){
|
||||
|
||||
else if (firstMonth + 2 === lastMonth) {
|
||||
const firstMonthLastDay = firstDay.endOf('month')
|
||||
const secondMonthFisrtDay = firstDay.add(1,'month').startOf('month')
|
||||
const secondMonthFisrtDay = firstDay.add(1, 'month').startOf('month')
|
||||
|
||||
// Whether the last day of the first month and the second month is in the same week
|
||||
const secondMonthStartDay = firstMonthLastDay.isSame(secondMonthFisrtDay, 'week') ? secondMonthFisrtDay.add(1, 'week') : secondMonthFisrtDay
|
||||
const secondMonthStartDay = firstMonthLastDay.isSame(
|
||||
secondMonthFisrtDay,
|
||||
'week'
|
||||
)
|
||||
? secondMonthFisrtDay.add(1, 'week')
|
||||
: secondMonthFisrtDay
|
||||
|
||||
const secondMonthLastDay = secondMonthStartDay.endOf('month')
|
||||
const lastMonthFirstDay = lastDay.startOf('month')
|
||||
|
||||
// Whether the last day of the second month and the last day of the last month is in the same week
|
||||
const lastMonthStartDay = secondMonthLastDay.isSame(lastMonthFirstDay, 'week') ? lastMonthFirstDay.add(1, 'week') : lastMonthFirstDay
|
||||
const lastMonthStartDay = secondMonthLastDay.isSame(
|
||||
lastMonthFirstDay,
|
||||
'week'
|
||||
)
|
||||
? lastMonthFirstDay.add(1, 'week')
|
||||
: lastMonthFirstDay
|
||||
|
||||
return [
|
||||
[firstDay, firstMonthLastDay],
|
||||
@ -197,7 +194,10 @@ export default defineComponent({
|
||||
}
|
||||
// Other cases
|
||||
else {
|
||||
warn('ElCalendar', 'start time and end time interval must not exceed two months')
|
||||
warn(
|
||||
'ElCalendar',
|
||||
'start time and end time interval must not exceed two months'
|
||||
)
|
||||
return []
|
||||
}
|
||||
}
|
||||
@ -205,7 +205,7 @@ export default defineComponent({
|
||||
// if range is valid, we get a two-digit array
|
||||
const validatedRange = computed(() => {
|
||||
if (!props.range) return []
|
||||
const rangeArrDayjs = props.range.map(_ => dayjs(_).locale(lang.value))
|
||||
const rangeArrDayjs = props.range.map((_) => dayjs(_).locale(lang.value))
|
||||
const [startDayjs, endDayjs] = rangeArrDayjs
|
||||
if (startDayjs.isAfter(endDayjs)) {
|
||||
warn('ElCalendar', 'end time should be greater than start time')
|
||||
@ -217,7 +217,10 @@ export default defineComponent({
|
||||
} else {
|
||||
// two months
|
||||
if (startDayjs.add(1, 'month').month() !== endDayjs.month()) {
|
||||
warn('ElCalendar', 'start time and end time interval must not exceed two months')
|
||||
warn(
|
||||
'ElCalendar',
|
||||
'start time and end time interval must not exceed two months'
|
||||
)
|
||||
return []
|
||||
}
|
||||
return calculateValidatedDateRange(startDayjs, endDayjs)
|
||||
|
@ -65,7 +65,9 @@ describe('Card.vue', () => {
|
||||
bodyStyle: style,
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-card__body').attributes('style')).toBe('font-size: 14px;')
|
||||
expect(wrapper.find('.el-card__body').attributes('style')).toBe(
|
||||
'font-size: 14px;'
|
||||
)
|
||||
})
|
||||
|
||||
test('body style with array', () => {
|
||||
@ -78,7 +80,9 @@ describe('Card.vue', () => {
|
||||
bodyStyle: style,
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-card__body').attributes('style').replace(/[ ]/g, '')).toBe('font-size:14px;color:blue;')
|
||||
expect(
|
||||
wrapper.find('.el-card__body').attributes('style').replace(/[ ]/g, '')
|
||||
).toBe('font-size:14px;color:blue;')
|
||||
})
|
||||
|
||||
test('shadow', () => {
|
||||
|
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="el-card" :class="shadow ? 'is-' + shadow + '-shadow' : 'is-always-shadow'">
|
||||
<div
|
||||
class="el-card"
|
||||
:class="shadow ? 'is-' + shadow + '-shadow' : 'is-always-shadow'"
|
||||
>
|
||||
<div v-if="$slots.header || header" class="el-card__header">
|
||||
<slot name="header">{{ header }}</slot>
|
||||
</div>
|
||||
@ -8,7 +11,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { StyleValue } from '@element-plus/utils/types'
|
||||
|
@ -4,7 +4,7 @@ import Carousel from '../src/main.vue'
|
||||
import CarouselItem from '../src/item.vue'
|
||||
|
||||
const wait = (ms = 100) =>
|
||||
new Promise(resolve => setTimeout(() => resolve(0), ms))
|
||||
new Promise((resolve) => setTimeout(() => resolve(0), ms))
|
||||
|
||||
const _mount = (template: string, data?: () => void, methods?: any) =>
|
||||
mount({
|
||||
@ -26,14 +26,14 @@ describe('Carousel', () => {
|
||||
<el-carousel-item v-for="item in 3" :key="item"></el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
`,
|
||||
`
|
||||
)
|
||||
|
||||
expect(wrapper.vm.$refs.carousel.direction).toBe('horizontal')
|
||||
expect(wrapper.findAll('.el-carousel__item').length).toEqual(3)
|
||||
})
|
||||
|
||||
it('auto play', async done => {
|
||||
it('auto play', async (done) => {
|
||||
const wrapper = _mount(`
|
||||
<div>
|
||||
<el-carousel :interval="50">
|
||||
@ -51,7 +51,7 @@ describe('Carousel', () => {
|
||||
done()
|
||||
})
|
||||
|
||||
it('initial index', async done => {
|
||||
it('initial index', async (done) => {
|
||||
const wrapper = _mount(`
|
||||
<div>
|
||||
<el-carousel :autoplay="false" :initial-index="1">
|
||||
@ -66,12 +66,12 @@ describe('Carousel', () => {
|
||||
expect(
|
||||
wrapper.vm.$el
|
||||
.querySelectorAll('.el-carousel__item')[1]
|
||||
.classList.contains('is-active'),
|
||||
.classList.contains('is-active')
|
||||
).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
|
||||
it('reset timer', async done => {
|
||||
it('reset timer', async (done) => {
|
||||
const wrapper = _mount(`
|
||||
<div>
|
||||
<el-carousel :interval="500">
|
||||
@ -91,7 +91,7 @@ describe('Carousel', () => {
|
||||
done()
|
||||
})
|
||||
|
||||
it('change', async done => {
|
||||
it('change', async (done) => {
|
||||
const wrapper = _mount(
|
||||
`
|
||||
<div>
|
||||
@ -111,7 +111,7 @@ describe('Carousel', () => {
|
||||
this.val = val
|
||||
this.oldVal = oldVal
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
await nextTick()
|
||||
@ -121,7 +121,7 @@ describe('Carousel', () => {
|
||||
done()
|
||||
})
|
||||
|
||||
it('label', async done => {
|
||||
it('label', async (done) => {
|
||||
const wrapper = _mount(`
|
||||
<div>
|
||||
<el-carousel>
|
||||
@ -135,7 +135,7 @@ describe('Carousel', () => {
|
||||
})
|
||||
|
||||
describe('manual control', () => {
|
||||
it('hover', async done => {
|
||||
it('hover', async (done) => {
|
||||
const wrapper = _mount(`
|
||||
<div>
|
||||
<el-carousel :autoplay="false">
|
||||
@ -152,13 +152,13 @@ describe('Carousel', () => {
|
||||
expect(
|
||||
wrapper.vm.$el
|
||||
.querySelectorAll('.el-carousel__item')[1]
|
||||
.classList.contains('is-active'),
|
||||
.classList.contains('is-active')
|
||||
).toBeTruthy()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('card', async done => {
|
||||
it('card', async (done) => {
|
||||
const wrapper = _mount(`
|
||||
<div>
|
||||
<el-carousel :autoplay="false" type="card">
|
||||
@ -198,7 +198,7 @@ describe('Carousel', () => {
|
||||
expect(items[0].style.transform.indexOf('translateY') !== -1).toBeTruthy()
|
||||
})
|
||||
|
||||
it('pause auto play on hover', async done => {
|
||||
it('pause auto play on hover', async (done) => {
|
||||
const wrapper = _mount(`
|
||||
<div>
|
||||
<el-carousel :interval="50" :pause-on-hover="false">
|
||||
|
@ -31,7 +31,8 @@ export interface ICarouselProps {
|
||||
pauseOnHover: boolean
|
||||
}
|
||||
|
||||
export type UnionCarouselItemData = ICarouselItemProps & ToRefs<ICarouselItemData>
|
||||
export type UnionCarouselItemData = ICarouselItemProps &
|
||||
ToRefs<ICarouselItemData>
|
||||
|
||||
export interface CarouselItem extends UnionCarouselItemData {
|
||||
uid: number
|
||||
|
@ -31,9 +31,7 @@ import {
|
||||
getCurrentInstance,
|
||||
onUnmounted,
|
||||
} from 'vue'
|
||||
import {
|
||||
autoprefixer,
|
||||
} from '@element-plus/utils/util'
|
||||
import { autoprefixer } from '@element-plus/utils/util'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { InjectCarouselScope, ICarouselItemProps } from './carousel'
|
||||
|
||||
@ -65,7 +63,7 @@ export default defineComponent({
|
||||
|
||||
// inject
|
||||
const injectCarouselScope: InjectCarouselScope = inject(
|
||||
'injectCarouselScope',
|
||||
'injectCarouselScope'
|
||||
)
|
||||
|
||||
// computed
|
||||
@ -112,14 +110,17 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function calcTranslate(index, activeIndex, isVertical) {
|
||||
const distance = (isVertical ? injectCarouselScope.root.value?.offsetHeight : injectCarouselScope.root.value?.offsetWidth) || 0
|
||||
const distance =
|
||||
(isVertical
|
||||
? injectCarouselScope.root.value?.offsetHeight
|
||||
: injectCarouselScope.root.value?.offsetWidth) || 0
|
||||
return distance * (index - activeIndex)
|
||||
}
|
||||
|
||||
const translateItem = (
|
||||
index: number,
|
||||
activeIndex: number,
|
||||
oldIndex: number,
|
||||
oldIndex: number
|
||||
) => {
|
||||
const parentType = injectCarouselScope.type
|
||||
const length = injectCarouselScope.items.value.length
|
||||
@ -132,7 +133,7 @@ export default defineComponent({
|
||||
if (parentType === 'card') {
|
||||
if (parentDirection.value === 'vertical') {
|
||||
console.warn(
|
||||
'[Element Warn][Carousel]vertical direction is not supported in card mode',
|
||||
'[Element Warn][Carousel]vertical direction is not supported in card mode'
|
||||
)
|
||||
}
|
||||
data.inStage = Math.round(Math.abs(index - activeIndex)) <= 1
|
||||
@ -150,7 +151,7 @@ export default defineComponent({
|
||||
function handleItemClick() {
|
||||
if (injectCarouselScope && injectCarouselScope.type === 'card') {
|
||||
const index = injectCarouselScope.items.value
|
||||
.map(d => d.uid)
|
||||
.map((d) => d.uid)
|
||||
.indexOf(instance.uid)
|
||||
injectCarouselScope.setActiveItem(index)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
<button
|
||||
v-show="
|
||||
(arrow === 'always' || data.hover) &&
|
||||
(props.loop || data.activeIndex > 0)
|
||||
(props.loop || data.activeIndex > 0)
|
||||
"
|
||||
type="button"
|
||||
class="el-carousel__arrow el-carousel__arrow--left"
|
||||
@ -25,7 +25,7 @@
|
||||
<button
|
||||
v-show="
|
||||
(arrow === 'always' || data.hover) &&
|
||||
(props.loop || data.activeIndex < items.length - 1)
|
||||
(props.loop || data.activeIndex < items.length - 1)
|
||||
"
|
||||
type="button"
|
||||
class="el-carousel__arrow el-carousel__arrow--right"
|
||||
@ -76,7 +76,11 @@ import {
|
||||
removeResizeListener,
|
||||
} from '@element-plus/utils/resize-event'
|
||||
|
||||
import type { ICarouselProps, CarouselItem, InjectCarouselScope } from './carousel'
|
||||
import type {
|
||||
ICarouselProps,
|
||||
CarouselItem,
|
||||
InjectCarouselScope,
|
||||
} from './carousel'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElCarousel',
|
||||
@ -145,11 +149,11 @@ export default defineComponent({
|
||||
|
||||
// computed
|
||||
const arrowDisplay = computed(
|
||||
() => props.arrow !== 'never' && props.direction !== 'vertical',
|
||||
() => props.arrow !== 'never' && props.direction !== 'vertical'
|
||||
)
|
||||
|
||||
const hasLabel = computed(() => {
|
||||
return items.value.some(item => item.label.toString().length > 0)
|
||||
return items.value.some((item) => item.label.toString().length > 0)
|
||||
})
|
||||
|
||||
const carouselClasses = computed(() => {
|
||||
@ -176,14 +180,14 @@ export default defineComponent({
|
||||
|
||||
// methods
|
||||
const throttledArrowClick = throttle(
|
||||
index => {
|
||||
(index) => {
|
||||
setActiveItem(index)
|
||||
},
|
||||
300,
|
||||
{ trailing: true },
|
||||
{ trailing: true }
|
||||
)
|
||||
|
||||
const throttledIndicatorHover = throttle(index => {
|
||||
const throttledIndicatorHover = throttle((index) => {
|
||||
handleIndicatorHover(index)
|
||||
}, 300)
|
||||
|
||||
@ -209,7 +213,7 @@ export default defineComponent({
|
||||
|
||||
function setActiveItem(index) {
|
||||
if (typeof index === 'string') {
|
||||
const filteredItems = items.value.filter(item => item.name === index)
|
||||
const filteredItems = items.value.filter((item) => item.name === index)
|
||||
if (filteredItems.length > 0) {
|
||||
index = items.value.indexOf(filteredItems[0])
|
||||
}
|
||||
@ -219,7 +223,7 @@ export default defineComponent({
|
||||
console.warn('[Element Warn][Carousel]index must be an integer.')
|
||||
return
|
||||
}
|
||||
let length = items.value.length
|
||||
const length = items.value.length
|
||||
const oldIndex = data.activeIndex
|
||||
if (index < 0) {
|
||||
data.activeIndex = props.loop ? length - 1 : 0
|
||||
@ -244,10 +248,10 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function removeItem(uid) {
|
||||
const index = items.value.findIndex(item => item.uid === uid)
|
||||
const index = items.value.findIndex((item) => item.uid === uid)
|
||||
if (index !== -1) {
|
||||
items.value.splice(index, 1)
|
||||
if(data.activeIndex === index) next()
|
||||
if (data.activeIndex === index) next()
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,7 +298,7 @@ export default defineComponent({
|
||||
|
||||
function handleButtonLeave() {
|
||||
if (props.direction === 'vertical') return
|
||||
items.value.forEach(item => {
|
||||
items.value.forEach((item) => {
|
||||
item.hover = false
|
||||
})
|
||||
}
|
||||
@ -325,19 +329,19 @@ export default defineComponent({
|
||||
if (prev > -1) {
|
||||
emit('change', current, prev)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.autoplay,
|
||||
current => {
|
||||
(current) => {
|
||||
current ? startTimer() : pauseTimer()
|
||||
},
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.loop,
|
||||
() => {
|
||||
setActiveItem(data.activeIndex)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// lifecycle
|
||||
|
@ -97,22 +97,22 @@ const RADIO = '.el-radio__input'
|
||||
|
||||
let id = 0
|
||||
|
||||
const _mount: typeof mount = options => mount({
|
||||
components: {
|
||||
CascaderPanel,
|
||||
},
|
||||
...options,
|
||||
})
|
||||
const _mount: typeof mount = (options) =>
|
||||
mount({
|
||||
components: {
|
||||
CascaderPanel,
|
||||
},
|
||||
...options,
|
||||
})
|
||||
|
||||
const lazyLoad = (node, resolve) => {
|
||||
const { level } = node
|
||||
setTimeout(() => {
|
||||
const nodes = Array.from({ length: level + 1 })
|
||||
.map(() => ({
|
||||
value: ++id,
|
||||
label: `option${id}`,
|
||||
leaf: level >= 1,
|
||||
}))
|
||||
const nodes = Array.from({ length: level + 1 }).map(() => ({
|
||||
value: ++id,
|
||||
label: `option${id}`,
|
||||
leaf: level >= 1,
|
||||
}))
|
||||
resolve(nodes)
|
||||
}, 1000)
|
||||
}
|
||||
@ -380,7 +380,10 @@ describe('CascaderPanel.vue', () => {
|
||||
|
||||
await nbCheckbox.find('input').trigger('click')
|
||||
expect(zjCheckbox.classes('is-checked')).toBe(true)
|
||||
expect(wrapper.vm.value).toEqual([['zhejiang', 'hangzhou'], ['zhejiang', 'ningbo']])
|
||||
expect(wrapper.vm.value).toEqual([
|
||||
['zhejiang', 'hangzhou'],
|
||||
['zhejiang', 'ningbo'],
|
||||
])
|
||||
|
||||
await zjCheckbox.find('input').trigger('click')
|
||||
expect(zjCheckbox.classes('is-checked')).toBe(false)
|
||||
@ -586,12 +589,11 @@ describe('CascaderPanel.vue', () => {
|
||||
lazyLoad(node, resolve) {
|
||||
const { level } = node
|
||||
setTimeout(() => {
|
||||
const nodes = Array.from({ length: level + 1 })
|
||||
.map(() => ({
|
||||
value: { id: ++id },
|
||||
label: `option${id}`,
|
||||
leaf: level >= 1,
|
||||
}))
|
||||
const nodes = Array.from({ length: level + 1 }).map(() => ({
|
||||
value: { id: ++id },
|
||||
label: `option${id}`,
|
||||
leaf: level >= 1,
|
||||
}))
|
||||
resolve(nodes)
|
||||
}, 1000)
|
||||
},
|
||||
@ -619,18 +621,17 @@ describe('CascaderPanel.vue', () => {
|
||||
props: {
|
||||
multiple: true,
|
||||
lazy: true,
|
||||
lazyLoad (node, resolve) {
|
||||
lazyLoad(node, resolve) {
|
||||
const { level } = node
|
||||
setTimeout(() => {
|
||||
const nodes = Array.from({ length: level + 1 })
|
||||
.map(() => {
|
||||
++id
|
||||
return {
|
||||
value: id,
|
||||
label: `option${id}`,
|
||||
leaf: id === 3,
|
||||
}
|
||||
})
|
||||
const nodes = Array.from({ length: level + 1 }).map(() => {
|
||||
++id
|
||||
return {
|
||||
value: id,
|
||||
label: `option${id}`,
|
||||
leaf: id === 3,
|
||||
}
|
||||
})
|
||||
resolve(nodes)
|
||||
}, 1000)
|
||||
},
|
||||
@ -653,9 +654,13 @@ describe('CascaderPanel.vue', () => {
|
||||
expect(firstMenu.find(CHECKBOX).classes('is-checked')).toBe(false)
|
||||
expect(firstMenu.find(CHECKBOX).classes('is-indeterminate')).toBe(true)
|
||||
expect(secondMenu.findAll(CHECKBOX)[0].classes('is-checked')).toBe(false)
|
||||
expect(secondMenu.findAll(CHECKBOX)[0].classes('is-indeterminate')).toBe(false)
|
||||
expect(secondMenu.findAll(CHECKBOX)[0].classes('is-indeterminate')).toBe(
|
||||
false
|
||||
)
|
||||
expect(secondMenu.findAll(CHECKBOX)[1].classes('is-checked')).toBe(true)
|
||||
expect(secondMenu.findAll(CHECKBOX)[1].classes('is-indeterminate')).toBe(false)
|
||||
expect(secondMenu.findAll(CHECKBOX)[1].classes('is-indeterminate')).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('getCheckedNodes and clearCheckedNodes', () => {
|
||||
|
@ -2,7 +2,6 @@ import { App } from 'vue'
|
||||
import type { SFCWithInstall } from '@element-plus/utils/types'
|
||||
import CascaderPanel from './src/index.vue'
|
||||
|
||||
|
||||
CascaderPanel.install = (app: App): void => {
|
||||
app.component(CascaderPanel.name, CascaderPanel)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export const CommonProps = {
|
||||
modelValue: [Number, String, Array] as PropType<CascaderValue>,
|
||||
options: {
|
||||
type: Array as PropType<CascaderOption[]>,
|
||||
default: () => ([] as CascaderOption[]),
|
||||
default: () => [] as CascaderOption[],
|
||||
},
|
||||
props: {
|
||||
type: Object as PropType<CascaderProps>,
|
||||
@ -37,7 +37,7 @@ export const DefaultProps: CascaderConfig = {
|
||||
hoverThreshold: 500,
|
||||
}
|
||||
|
||||
export const useCascaderConfig = (props: { props: CascaderProps; }) => {
|
||||
export const useCascaderConfig = (props: { props: CascaderProps }) => {
|
||||
return computed(() => ({
|
||||
...DefaultProps,
|
||||
...props.props,
|
||||
|
@ -1,27 +1,30 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'el-cascader-panel',
|
||||
border && 'is-bordered'
|
||||
]"
|
||||
:class="['el-cascader-panel', border && 'is-bordered']"
|
||||
@keydown="handleKeyDown"
|
||||
>
|
||||
<el-cascader-menu
|
||||
v-for="(menu, index) in menus"
|
||||
:key="index"
|
||||
:ref="item => menuList[index] = item"
|
||||
:ref="(item) => (menuList[index] = item)"
|
||||
:index="index"
|
||||
:nodes="menu"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed, defineComponent, nextTick,
|
||||
onBeforeUpdate, onMounted,
|
||||
provide, reactive,
|
||||
Ref, ref, watch,
|
||||
computed,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
onBeforeUpdate,
|
||||
onMounted,
|
||||
provide,
|
||||
reactive,
|
||||
Ref,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
import { EVENT_CODE } from '@element-plus/utils/aria'
|
||||
@ -76,12 +79,7 @@ export default defineComponent({
|
||||
renderLabel: Function as PropType<RenderLabel>,
|
||||
},
|
||||
|
||||
emits: [
|
||||
UPDATE_MODEL_EVENT,
|
||||
CHANGE_EVENT,
|
||||
'close',
|
||||
'expand-change',
|
||||
],
|
||||
emits: [UPDATE_MODEL_EVENT, CHANGE_EVENT, 'close', 'expand-change'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
let initialLoaded = true
|
||||
@ -97,7 +95,9 @@ export default defineComponent({
|
||||
const expandingNode: Ref<Nullable<CascaderNode>> = ref(null)
|
||||
const checkedNodes: Ref<CascaderNode[]> = ref([])
|
||||
|
||||
const isHoverMenu = computed(() => config.value.expandTrigger === ExpandTrigger.HOVER)
|
||||
const isHoverMenu = computed(
|
||||
() => config.value.expandTrigger === ExpandTrigger.HOVER
|
||||
)
|
||||
const renderLabelFn = computed(() => props.renderLabel || slots.default)
|
||||
|
||||
const initStore = () => {
|
||||
@ -148,14 +148,18 @@ export default defineComponent({
|
||||
newMenus.push(node.children)
|
||||
}
|
||||
|
||||
if(expandingNode.value?.uid !== newExpandingNode?.uid) {
|
||||
if (expandingNode.value?.uid !== newExpandingNode?.uid) {
|
||||
expandingNode.value = node
|
||||
menus.value = newMenus
|
||||
!silent && emit('expand-change', node?.pathValues || [])
|
||||
}
|
||||
}
|
||||
|
||||
const handleCheckChange: ElCascaderPanelContext['handleCheckChange'] = (node, checked, emitClose = true) => {
|
||||
const handleCheckChange: ElCascaderPanelContext['handleCheckChange'] = (
|
||||
node,
|
||||
checked,
|
||||
emitClose = true
|
||||
) => {
|
||||
const { checkStrictly, multiple } = config.value
|
||||
const oldNode = checkedNodes.value[0]
|
||||
manualChecked = true
|
||||
@ -171,12 +175,11 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const getCheckedNodes = (leafOnly: boolean) => {
|
||||
return getFlattedNodes(leafOnly)
|
||||
.filter(node => node.checked !== false)
|
||||
return getFlattedNodes(leafOnly).filter((node) => node.checked !== false)
|
||||
}
|
||||
|
||||
const clearCheckedNodes = () => {
|
||||
checkedNodes.value.forEach(node => node.doCheck(false))
|
||||
checkedNodes.value.forEach((node) => node.doCheck(false))
|
||||
calculateCheckedValue()
|
||||
}
|
||||
|
||||
@ -186,9 +189,9 @@ export default defineComponent({
|
||||
const newNodes = getCheckedNodes(!checkStrictly)
|
||||
// ensure the original order
|
||||
const nodes = sortByOriginalOrder(oldNodes, newNodes)
|
||||
const values = nodes.map(node => node.valueByOption)
|
||||
const values = nodes.map((node) => node.valueByOption)
|
||||
checkedNodes.value = nodes
|
||||
checkedValue.value = multiple ? values : (values[0] ?? null)
|
||||
checkedValue.value = multiple ? values : values[0] ?? null
|
||||
}
|
||||
|
||||
const syncCheckedValue = (loaded = false, forced = false) => {
|
||||
@ -199,44 +202,58 @@ export default defineComponent({
|
||||
if (
|
||||
!initialLoaded ||
|
||||
manualChecked ||
|
||||
!forced && isEqual(modelValue, checkedValue.value)
|
||||
) return
|
||||
(!forced && isEqual(modelValue, checkedValue.value))
|
||||
)
|
||||
return
|
||||
|
||||
if (lazy && !loaded) {
|
||||
const values: CascaderNodeValue[] = deduplicate(arrayFlat(coerceTruthyValueToArray(modelValue)))
|
||||
const nodes = values.map(val => store.value.getNodeByValue(val))
|
||||
.filter(node => !!node && !node.loaded && !node.loading)
|
||||
const values: CascaderNodeValue[] = deduplicate(
|
||||
arrayFlat(coerceTruthyValueToArray(modelValue))
|
||||
)
|
||||
const nodes = values
|
||||
.map((val) => store.value.getNodeByValue(val))
|
||||
.filter((node) => !!node && !node.loaded && !node.loading)
|
||||
|
||||
if (nodes.length) {
|
||||
nodes.forEach(node => {
|
||||
nodes.forEach((node) => {
|
||||
lazyLoad(node, () => syncCheckedValue(false, forced))
|
||||
})
|
||||
} else {
|
||||
syncCheckedValue(true, forced)
|
||||
}
|
||||
} else {
|
||||
const values = multiple ? coerceTruthyValueToArray(modelValue) : [modelValue]
|
||||
const nodes = deduplicate(values.map(val => store.value.getNodeByValue(val, leafOnly)))
|
||||
const values = multiple
|
||||
? coerceTruthyValueToArray(modelValue)
|
||||
: [modelValue]
|
||||
const nodes = deduplicate(
|
||||
values.map((val) => store.value.getNodeByValue(val, leafOnly))
|
||||
)
|
||||
syncMenuState(nodes, false)
|
||||
checkedValue.value = modelValue
|
||||
}
|
||||
}
|
||||
|
||||
const syncMenuState = (newCheckedNodes: CascaderNode[], reserveExpandingState = true) => {
|
||||
const syncMenuState = (
|
||||
newCheckedNodes: CascaderNode[],
|
||||
reserveExpandingState = true
|
||||
) => {
|
||||
const { checkStrictly } = config.value
|
||||
const oldNodes = checkedNodes.value
|
||||
const newNodes = newCheckedNodes.filter(node => !!node && (checkStrictly || node.isLeaf))
|
||||
const newNodes = newCheckedNodes.filter(
|
||||
(node) => !!node && (checkStrictly || node.isLeaf)
|
||||
)
|
||||
const oldExpandingNode = store.value.getSameNode(expandingNode.value)
|
||||
const newExpandingNode = reserveExpandingState && oldExpandingNode || newNodes[0]
|
||||
const newExpandingNode =
|
||||
(reserveExpandingState && oldExpandingNode) || newNodes[0]
|
||||
|
||||
if (newExpandingNode) {
|
||||
newExpandingNode.pathNodes.forEach(node => expandNode(node, true))
|
||||
newExpandingNode.pathNodes.forEach((node) => expandNode(node, true))
|
||||
} else {
|
||||
expandingNode.value = null
|
||||
}
|
||||
|
||||
oldNodes.forEach(node => node.doCheck(false))
|
||||
newNodes.forEach(node => node.doCheck(true))
|
||||
oldNodes.forEach((node) => node.doCheck(false))
|
||||
newNodes.forEach((node) => node.doCheck(true))
|
||||
|
||||
checkedNodes.value = newNodes
|
||||
nextTick(scrollToExpandingNode)
|
||||
@ -245,11 +262,12 @@ export default defineComponent({
|
||||
const scrollToExpandingNode = () => {
|
||||
if (isServer) return
|
||||
|
||||
menuList.value.forEach(menu => {
|
||||
menuList.value.forEach((menu) => {
|
||||
const menuElement = menu?.$el
|
||||
if (menuElement) {
|
||||
const container = menuElement.querySelector('.el-scrollbar__wrap')
|
||||
const activeNode = menuElement.querySelector('.el-cascader-node.is-active') ||
|
||||
const activeNode =
|
||||
menuElement.querySelector('.el-cascader-node.is-active') ||
|
||||
menuElement.querySelector('.el-cascader-node.in-active-path')
|
||||
scrollIntoView(container, activeNode)
|
||||
}
|
||||
@ -268,12 +286,16 @@ export default defineComponent({
|
||||
break
|
||||
case EVENT_CODE.left:
|
||||
const preMenu = menuList.value[getMenuIndex(target) - 1]
|
||||
const expandedNode = preMenu?.$el.querySelector('.el-cascader-node[aria-expanded="true"]')
|
||||
const expandedNode = preMenu?.$el.querySelector(
|
||||
'.el-cascader-node[aria-expanded="true"]'
|
||||
)
|
||||
focusNode(expandedNode)
|
||||
break
|
||||
case EVENT_CODE.right:
|
||||
const nextMenu = menuList.value[getMenuIndex(target) + 1]
|
||||
const firstNode = nextMenu?.$el.querySelector('.el-cascader-node[tabindex="-1"]')
|
||||
const firstNode = nextMenu?.$el.querySelector(
|
||||
'.el-cascader-node[tabindex="-1"]'
|
||||
)
|
||||
focusNode(firstNode)
|
||||
break
|
||||
case EVENT_CODE.enter:
|
||||
@ -286,36 +308,41 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
provide(CASCADER_PANEL_INJECTION_KEY, reactive({
|
||||
config,
|
||||
expandingNode,
|
||||
checkedNodes,
|
||||
isHoverMenu,
|
||||
renderLabelFn,
|
||||
lazyLoad,
|
||||
expandNode,
|
||||
handleCheckChange,
|
||||
}))
|
||||
|
||||
watch(
|
||||
[config, () => props.options],
|
||||
initStore,
|
||||
{ deep: true, immediate: true },
|
||||
provide(
|
||||
CASCADER_PANEL_INJECTION_KEY,
|
||||
reactive({
|
||||
config,
|
||||
expandingNode,
|
||||
checkedNodes,
|
||||
isHoverMenu,
|
||||
renderLabelFn,
|
||||
lazyLoad,
|
||||
expandNode,
|
||||
handleCheckChange,
|
||||
})
|
||||
)
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
manualChecked = false
|
||||
syncCheckedValue()
|
||||
watch([config, () => props.options], initStore, {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
watch(checkedValue, val => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
manualChecked = false
|
||||
syncCheckedValue()
|
||||
}
|
||||
)
|
||||
|
||||
watch(checkedValue, (val) => {
|
||||
if (!isEqual(val, props.modelValue)) {
|
||||
emit(UPDATE_MODEL_EVENT, val)
|
||||
emit(CHANGE_EVENT, val)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUpdate(() => menuList.value = [])
|
||||
onBeforeUpdate(() => (menuList.value = []))
|
||||
|
||||
onMounted(() => !isEmpty(props.modelValue) && syncCheckedValue())
|
||||
|
||||
|
@ -5,10 +5,7 @@
|
||||
role="menu"
|
||||
class="el-cascader-menu"
|
||||
wrap-class="el-cascader-menu__wrap"
|
||||
:view-class="[
|
||||
'el-cascader-menu__list',
|
||||
isEmpty && 'is-empty'
|
||||
]"
|
||||
:view-class="['el-cascader-menu__list', isEmpty && 'is-empty']"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseleave="clearHoverZone"
|
||||
>
|
||||
@ -19,10 +16,7 @@
|
||||
:menu-id="menuId"
|
||||
@expand="handleExpand"
|
||||
/>
|
||||
<div
|
||||
v-if="isEmpty"
|
||||
class="el-cascader-menu__empty-text"
|
||||
>
|
||||
<div v-if="isEmpty" class="el-cascader-menu__empty-text">
|
||||
{{ t('el.cascader.noData') }}
|
||||
</div>
|
||||
<svg
|
||||
@ -34,18 +28,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed, defineComponent, getCurrentInstance,
|
||||
inject, ref,
|
||||
} from 'vue'
|
||||
import { computed, defineComponent, getCurrentInstance, inject, ref } from 'vue'
|
||||
import ElScrollbar from '@element-plus/components/scrollbar'
|
||||
import { useLocaleInject } from '@element-plus/hooks'
|
||||
import { generateId } from '@element-plus/utils/util'
|
||||
import ElCascaderNode from './node.vue'
|
||||
import { default as CascaderNode } from './node'
|
||||
import {
|
||||
CASCADER_PANEL_INJECTION_KEY,
|
||||
} from './types'
|
||||
import { CASCADER_PANEL_INJECTION_KEY } from './types'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
import type { TimeoutHandle, Nullable } from '@element-plus/utils/types'
|
||||
@ -60,7 +49,7 @@ export default defineComponent({
|
||||
|
||||
props: {
|
||||
nodes: {
|
||||
type: Array as PropType< CascaderNode[]>,
|
||||
type: Array as PropType<CascaderNode[]>,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
@ -69,7 +58,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
|
||||
setup (props) {
|
||||
setup(props) {
|
||||
const instance = getCurrentInstance()
|
||||
const { t } = useLocaleInject()
|
||||
const id = generateId()
|
||||
@ -105,7 +94,10 @@ export default defineComponent({
|
||||
<path style="pointer-events: auto;" fill="transparent" d="M${startX} ${bottom} L${offsetWidth} ${offsetHeight} V${bottom} Z" />
|
||||
`
|
||||
} else if (!hoverTimer) {
|
||||
hoverTimer = window.setTimeout(clearHoverZone, panel.config.hoverThreshold)
|
||||
hoverTimer = window.setTimeout(
|
||||
clearHoverZone,
|
||||
panel.config.hoverThreshold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,6 +125,4 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -9,7 +9,7 @@ export default defineComponent({
|
||||
return h(
|
||||
'span',
|
||||
{ class: 'el-cascader-node__label' },
|
||||
renderLabelFn ? renderLabelFn({ node, data }) : label,
|
||||
renderLabelFn ? renderLabelFn({ node, data }) : label
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -1,21 +1,26 @@
|
||||
|
||||
import { isFunction } from '@vue/shared'
|
||||
import { capitalize, isUndefined, isEmpty } from '@element-plus/utils/util'
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
export type CascaderNodeValue = string | number
|
||||
export type CascaderNodePathValue = CascaderNodeValue[]
|
||||
export type CascaderValue = CascaderNodeValue | CascaderNodePathValue | (CascaderNodeValue | CascaderNodePathValue)[]
|
||||
export type CascaderValue =
|
||||
| CascaderNodeValue
|
||||
| CascaderNodePathValue
|
||||
| (CascaderNodeValue | CascaderNodePathValue)[]
|
||||
export type CascaderConfig = Required<CascaderProps>
|
||||
export enum ExpandTrigger {
|
||||
CLICK = 'click',
|
||||
HOVER = 'hover'
|
||||
HOVER = 'hover',
|
||||
}
|
||||
export type isDisabled = (data: CascaderOption, node: Node) => boolean
|
||||
export type isLeaf = (data: CascaderOption, node: Node) => boolean
|
||||
export type Resolve = (dataList?: CascaderOption[]) => void
|
||||
export type LazyLoad = (node: Node, resolve: Resolve) => void
|
||||
export type RenderLabel = ({ node: Node, data: CascaderOption }) => VNode | VNode[]
|
||||
export type RenderLabel = ({
|
||||
node: Node,
|
||||
data: CascaderOption,
|
||||
}) => VNode | VNode[]
|
||||
export interface CascaderOption extends Record<string, unknown> {
|
||||
label?: string
|
||||
value?: CascaderNodeValue
|
||||
@ -74,11 +79,11 @@ class Node {
|
||||
indeterminate = false
|
||||
loading = false
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
readonly data: Nullable<CascaderOption>,
|
||||
readonly config: CascaderConfig,
|
||||
readonly parent?: Node,
|
||||
readonly root = false,
|
||||
readonly root = false
|
||||
) {
|
||||
const { value: valueKey, label: labelKey, children: childrenKey } = config
|
||||
|
||||
@ -89,35 +94,41 @@ class Node {
|
||||
this.value = data[valueKey] as CascaderNodeValue
|
||||
this.label = data[labelKey] as string
|
||||
this.pathNodes = pathNodes
|
||||
this.pathValues = pathNodes.map(node => node.value)
|
||||
this.pathLabels = pathNodes.map(node => node.label)
|
||||
this.pathValues = pathNodes.map((node) => node.value)
|
||||
this.pathLabels = pathNodes.map((node) => node.label)
|
||||
this.childrenData = childrenData
|
||||
this.children = (childrenData || []).map(child => new Node(child, config, this))
|
||||
this.children = (childrenData || []).map(
|
||||
(child) => new Node(child, config, this)
|
||||
)
|
||||
this.loaded = !config.lazy || this.isLeaf || !isEmpty(childrenData)
|
||||
}
|
||||
|
||||
get isDisabled (): boolean {
|
||||
get isDisabled(): boolean {
|
||||
const { data, parent, config } = this
|
||||
const { disabled, checkStrictly } = config
|
||||
const isDisabled = isFunction(disabled) ? disabled(data, this) : !!data[disabled]
|
||||
return isDisabled || !checkStrictly && parent?.isDisabled
|
||||
const isDisabled = isFunction(disabled)
|
||||
? disabled(data, this)
|
||||
: !!data[disabled]
|
||||
return isDisabled || (!checkStrictly && parent?.isDisabled)
|
||||
}
|
||||
|
||||
get isLeaf (): boolean {
|
||||
get isLeaf(): boolean {
|
||||
const { data, config, childrenData, loaded } = this
|
||||
const { lazy, leaf } = config
|
||||
const isLeaf = isFunction(leaf) ? leaf(data, this) : data[leaf]
|
||||
|
||||
return isUndefined(isLeaf)
|
||||
? lazy && !loaded ? false : !Array.isArray(childrenData)
|
||||
? lazy && !loaded
|
||||
? false
|
||||
: !Array.isArray(childrenData)
|
||||
: !!isLeaf
|
||||
}
|
||||
|
||||
get valueByOption () {
|
||||
get valueByOption() {
|
||||
return this.config.emitPath ? this.pathValues : this.value
|
||||
}
|
||||
|
||||
appendChild (childData: CascaderOption) {
|
||||
appendChild(childData: CascaderOption) {
|
||||
const { childrenData, children } = this
|
||||
const node = new Node(childData, this.config, this)
|
||||
|
||||
@ -132,15 +143,15 @@ class Node {
|
||||
return node
|
||||
}
|
||||
|
||||
calcText (allLevels: boolean, separator: string) {
|
||||
calcText(allLevels: boolean, separator: string) {
|
||||
const text = allLevels ? this.pathLabels.join(separator) : this.label
|
||||
this.text = text
|
||||
return text
|
||||
}
|
||||
|
||||
broadcast (event: string, ...args: unknown[]) {
|
||||
broadcast(event: string, ...args: unknown[]) {
|
||||
const handlerName = `onParent${capitalize(event)}`
|
||||
this.children.forEach(child => {
|
||||
this.children.forEach((child) => {
|
||||
if (child) {
|
||||
// bottom up
|
||||
child.broadcast(event, ...args)
|
||||
@ -149,7 +160,7 @@ class Node {
|
||||
})
|
||||
}
|
||||
|
||||
emit (event: string, ...args: unknown[]) {
|
||||
emit(event: string, ...args: unknown[]) {
|
||||
const { parent } = this
|
||||
const handlerName = `onChild${capitalize(event)}`
|
||||
if (parent) {
|
||||
@ -166,9 +177,9 @@ class Node {
|
||||
|
||||
onChildCheck() {
|
||||
const { children } = this
|
||||
const validChildren = children.filter(child => !child.isDisabled)
|
||||
const validChildren = children.filter((child) => !child.isDisabled)
|
||||
const checked = validChildren.length
|
||||
? validChildren.every(child => child.checked)
|
||||
? validChildren.every((child) => child.checked)
|
||||
: false
|
||||
|
||||
this.setCheckState(checked)
|
||||
@ -177,12 +188,16 @@ class Node {
|
||||
setCheckState(checked: boolean) {
|
||||
const totalNum = this.children.length
|
||||
const checkedNum = this.children.reduce((c, p) => {
|
||||
const num = p.checked ? 1 : (p.indeterminate ? 0.5 : 0)
|
||||
const num = p.checked ? 1 : p.indeterminate ? 0.5 : 0
|
||||
return c + num
|
||||
}, 0)
|
||||
|
||||
this.checked = this.loaded && this.children.every(child => child.loaded && child.checked) && checked
|
||||
this.indeterminate = this.loaded && checkedNum !== totalNum && checkedNum > 0
|
||||
this.checked =
|
||||
this.loaded &&
|
||||
this.children.every((child) => child.loaded && child.checked) &&
|
||||
checked
|
||||
this.indeterminate =
|
||||
this.loaded && checkedNum !== totalNum && checkedNum > 0
|
||||
}
|
||||
|
||||
doCheck(checked: boolean) {
|
||||
|
@ -12,7 +12,7 @@
|
||||
inExpandingPath && 'in-active-path',
|
||||
inCheckedPath && 'in-checked-path',
|
||||
node.checked && 'is-active',
|
||||
!expandable && 'is-disabled'
|
||||
!expandable && 'is-disabled',
|
||||
]"
|
||||
@mouseenter="handleHoverExpand"
|
||||
@focus="handleHoverExpand"
|
||||
@ -41,14 +41,20 @@
|
||||
-->
|
||||
<span></span>
|
||||
</el-radio>
|
||||
<i v-else-if="isLeaf && node.checked" class="el-icon-check el-cascader-node__prefix"></i>
|
||||
<i
|
||||
v-else-if="isLeaf && node.checked"
|
||||
class="el-icon-check el-cascader-node__prefix"
|
||||
></i>
|
||||
|
||||
<!-- content -->
|
||||
<node-content />
|
||||
|
||||
<!-- postfix -->
|
||||
<template v-if="!isLeaf">
|
||||
<i v-if="node.loading" class="el-icon-loading el-cascader-node__postfix"></i>
|
||||
<i
|
||||
v-if="node.loading"
|
||||
class="el-icon-loading el-cascader-node__postfix"
|
||||
></i>
|
||||
<i v-else class="el-icon-arrow-right el-cascader-node__postfix"></i>
|
||||
</template>
|
||||
</li>
|
||||
@ -60,9 +66,7 @@ import ElCheckbox from '@element-plus/components/checkbox'
|
||||
import ElRadio from '@element-plus/components/radio'
|
||||
import NodeContent from './node-content'
|
||||
import type { default as CascaderNode } from './node'
|
||||
import {
|
||||
CASCADER_PANEL_INJECTION_KEY,
|
||||
} from './types'
|
||||
import { CASCADER_PANEL_INJECTION_KEY } from './types'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
@ -85,7 +89,7 @@ export default defineComponent({
|
||||
|
||||
emits: ['expand'],
|
||||
|
||||
setup (props, { emit }) {
|
||||
setup(props, { emit }) {
|
||||
const panel = inject(CASCADER_PANEL_INJECTION_KEY)
|
||||
|
||||
const isHoverMenu = computed(() => panel.isHoverMenu)
|
||||
@ -94,10 +98,14 @@ export default defineComponent({
|
||||
const checkedNodeId = computed(() => panel.checkedNodes[0]?.uid)
|
||||
const isDisabled = computed(() => props.node.isDisabled)
|
||||
const isLeaf = computed(() => props.node.isLeaf)
|
||||
const expandable = computed(() => checkStrictly.value && !isLeaf.value || !isDisabled.value)
|
||||
const expandable = computed(
|
||||
() => (checkStrictly.value && !isLeaf.value) || !isDisabled.value
|
||||
)
|
||||
const inExpandingPath = computed(() => isInPath(panel.expandingNode))
|
||||
// only useful in check-strictly mode
|
||||
const inCheckedPath = computed(() => checkStrictly.value && panel.checkedNodes.some(isInPath))
|
||||
const inCheckedPath = computed(
|
||||
() => checkStrictly.value && panel.checkedNodes.some(isInPath)
|
||||
)
|
||||
|
||||
const isInPath = (node: CascaderNode) => {
|
||||
const { level, uid } = props.node
|
||||
@ -137,7 +145,12 @@ export default defineComponent({
|
||||
const handleClick = () => {
|
||||
if (isHoverMenu.value && !isLeaf.value) return
|
||||
|
||||
if (isLeaf.value && !isDisabled.value && !checkStrictly.value && !multiple.value) {
|
||||
if (
|
||||
isLeaf.value &&
|
||||
!isDisabled.value &&
|
||||
!checkStrictly.value &&
|
||||
!multiple.value
|
||||
) {
|
||||
handleCheck(true)
|
||||
} else {
|
||||
handleExpand()
|
||||
|
@ -9,7 +9,6 @@ import type {
|
||||
CascaderConfig,
|
||||
} from './node'
|
||||
|
||||
|
||||
const flatNodes = (nodes: Node[], leafOnly: boolean) => {
|
||||
return nodes.reduce((res, node) => {
|
||||
if (node.isLeaf) {
|
||||
@ -27,18 +26,20 @@ export default class Store {
|
||||
readonly allNodes: Node[]
|
||||
readonly leafNodes: Node[]
|
||||
|
||||
constructor (data: CascaderOption[], readonly config: CascaderConfig) {
|
||||
const nodes = (data || []).map(nodeData => new Node(nodeData, this.config))
|
||||
constructor(data: CascaderOption[], readonly config: CascaderConfig) {
|
||||
const nodes = (data || []).map(
|
||||
(nodeData) => new Node(nodeData, this.config)
|
||||
)
|
||||
this.nodes = nodes
|
||||
this.allNodes = flatNodes(nodes, false)
|
||||
this.leafNodes = flatNodes(nodes, true)
|
||||
}
|
||||
|
||||
getNodes () {
|
||||
getNodes() {
|
||||
return this.nodes
|
||||
}
|
||||
|
||||
getFlattedNodes (leafOnly: boolean) {
|
||||
getFlattedNodes(leafOnly: boolean) {
|
||||
return leafOnly ? this.leafNodes : this.allNodes
|
||||
}
|
||||
|
||||
@ -54,26 +55,30 @@ export default class Store {
|
||||
}
|
||||
|
||||
appendNodes(nodeDataList: CascaderOption[], parentNode: Node) {
|
||||
nodeDataList.forEach(nodeData => this.appendNode(nodeData, parentNode))
|
||||
nodeDataList.forEach((nodeData) => this.appendNode(nodeData, parentNode))
|
||||
}
|
||||
|
||||
// when checkStrictly, leaf node first
|
||||
getNodeByValue (value: CascaderNodeValue | CascaderNodePathValue, leafOnly = false): Nullable<Node> {
|
||||
getNodeByValue(
|
||||
value: CascaderNodeValue | CascaderNodePathValue,
|
||||
leafOnly = false
|
||||
): Nullable<Node> {
|
||||
if (!value && value !== 0) return null
|
||||
|
||||
const nodes = this.getFlattedNodes(leafOnly)
|
||||
.filter(node => isEqual(node.value, value) || isEqual(node.pathValues, value))
|
||||
const nodes = this.getFlattedNodes(leafOnly).filter(
|
||||
(node) => isEqual(node.value, value) || isEqual(node.pathValues, value)
|
||||
)
|
||||
|
||||
return nodes[0] || null
|
||||
}
|
||||
|
||||
getSameNode (node: Node): Nullable<Node> {
|
||||
getSameNode(node: Node): Nullable<Node> {
|
||||
if (!node) return null
|
||||
|
||||
const nodes = this.getFlattedNodes(false)
|
||||
.filter(({ value, level }) => isEqual(node.value, value) && node.level === level)
|
||||
const nodes = this.getFlattedNodes(false).filter(
|
||||
({ value, level }) => isEqual(node.value, value) && node.level === level
|
||||
)
|
||||
|
||||
return nodes[0] || null
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import type { VNode, InjectionKey } from 'vue'
|
||||
import type { Nullable } from '@element-plus/utils/types'
|
||||
import type { default as CascaderNode } from './node'
|
||||
@ -7,17 +6,23 @@ export type { CascaderNode }
|
||||
|
||||
export type CascaderNodeValue = string | number
|
||||
export type CascaderNodePathValue = CascaderNodeValue[]
|
||||
export type CascaderValue = CascaderNodeValue | CascaderNodePathValue | (CascaderNodeValue | CascaderNodePathValue)[]
|
||||
export type CascaderValue =
|
||||
| CascaderNodeValue
|
||||
| CascaderNodePathValue
|
||||
| (CascaderNodeValue | CascaderNodePathValue)[]
|
||||
export type CascaderConfig = Required<CascaderProps>
|
||||
export type isDisabled = (data: CascaderOption, node: CascaderNode) => boolean
|
||||
export type isLeaf = (data: CascaderOption, node: CascaderNode) => boolean
|
||||
export type Resolve = (dataList?: CascaderOption[]) => void
|
||||
export type LazyLoad = (node: CascaderNode, resolve: Resolve) => void
|
||||
export type RenderLabel = ({ node: CascaderNode, data: CascaderOption }) => VNode | VNode[]
|
||||
export type RenderLabel = ({
|
||||
node: CascaderNode,
|
||||
data: CascaderOption,
|
||||
}) => VNode | VNode[]
|
||||
|
||||
export enum ExpandTrigger {
|
||||
CLICK = 'click',
|
||||
HOVER = 'hover'
|
||||
HOVER = 'hover',
|
||||
}
|
||||
|
||||
export interface CascaderOption extends Record<string, unknown> {
|
||||
@ -57,9 +62,17 @@ export interface ElCascaderPanelContext {
|
||||
checkedNodes: CascaderNode[]
|
||||
isHoverMenu: boolean
|
||||
renderLabelFn: RenderLabel
|
||||
lazyLoad: (node?: CascaderNode, cb?: (dataList: CascaderOption[]) => void) => void
|
||||
lazyLoad: (
|
||||
node?: CascaderNode,
|
||||
cb?: (dataList: CascaderOption[]) => void
|
||||
) => void
|
||||
expandNode: (node: CascaderNode, silent?: boolean) => void
|
||||
handleCheckChange: (node: CascaderNode, checked: boolean, emitClose?: boolean) => void
|
||||
handleCheckChange: (
|
||||
node: CascaderNode,
|
||||
checked: boolean,
|
||||
emitClose?: boolean
|
||||
) => void
|
||||
}
|
||||
|
||||
export const CASCADER_PANEL_INJECTION_KEY: InjectionKey<ElCascaderPanelContext> = Symbol()
|
||||
export const CASCADER_PANEL_INJECTION_KEY: InjectionKey<ElCascaderPanelContext> =
|
||||
Symbol()
|
||||
|
@ -1,15 +1,19 @@
|
||||
|
||||
import type { Nullable } from '@element-plus/utils/types'
|
||||
import type { default as CascaderNode } from './node'
|
||||
|
||||
export const isLeaf = (el: HTMLElement) => !el.getAttribute('aria-owns')
|
||||
|
||||
export const getSibling = (el: HTMLElement, distance: number): Nullable<Element> => {
|
||||
export const getSibling = (
|
||||
el: HTMLElement,
|
||||
distance: number
|
||||
): Nullable<Element> => {
|
||||
const { parentNode } = el
|
||||
|
||||
if (!parentNode) return null
|
||||
|
||||
const siblings = parentNode.querySelectorAll('.el-cascader-node[tabindex="-1"]')
|
||||
const siblings = parentNode.querySelectorAll(
|
||||
'.el-cascader-node[tabindex="-1"]'
|
||||
)
|
||||
const index = Array.prototype.indexOf.call(siblings, el)
|
||||
return siblings[index + distance] || null
|
||||
}
|
||||
@ -20,13 +24,13 @@ export const getMenuIndex = (el: HTMLElement) => {
|
||||
return Number(pieces[pieces.length - 2])
|
||||
}
|
||||
|
||||
export const focusNode = el => {
|
||||
export const focusNode = (el) => {
|
||||
if (!el) return
|
||||
el.focus()
|
||||
!isLeaf(el) && el.click()
|
||||
}
|
||||
|
||||
export const checkNode = el => {
|
||||
export const checkNode = (el) => {
|
||||
if (!el) return
|
||||
|
||||
const input = el.querySelector('input')
|
||||
@ -37,9 +41,12 @@ export const checkNode = el => {
|
||||
}
|
||||
}
|
||||
|
||||
export const sortByOriginalOrder = (oldNodes: CascaderNode[], newNodes: CascaderNode[]) => {
|
||||
export const sortByOriginalOrder = (
|
||||
oldNodes: CascaderNode[],
|
||||
newNodes: CascaderNode[]
|
||||
) => {
|
||||
const newNodesCopy = newNodes.slice(0)
|
||||
const newIds = newNodesCopy.map(node => node.uid)
|
||||
const newIds = newNodesCopy.map((node) => node.uid)
|
||||
const res = oldNodes.reduce((acc, item) => {
|
||||
const index = newIds.indexOf(item.uid)
|
||||
if (index > -1) {
|
||||
|
@ -30,14 +30,18 @@ const TAG = '.el-tag'
|
||||
const SUGGESTION_ITEM = '.el-cascader__suggestion-item'
|
||||
const CHECK_ICON = '.el-icon-check'
|
||||
|
||||
const _mount: typeof mount = options => mount({
|
||||
components: {
|
||||
Cascader,
|
||||
},
|
||||
...options,
|
||||
}, {
|
||||
attachTo: 'body',
|
||||
})
|
||||
const _mount: typeof mount = (options) =>
|
||||
mount(
|
||||
{
|
||||
components: {
|
||||
Cascader,
|
||||
},
|
||||
...options,
|
||||
},
|
||||
{
|
||||
attachTo: 'body',
|
||||
}
|
||||
)
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
@ -79,7 +83,7 @@ describe('Cascader.vue', () => {
|
||||
@expand-change="handleExpandChange"
|
||||
/>
|
||||
`,
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
value: [],
|
||||
options: OPTIONS,
|
||||
@ -194,11 +198,14 @@ describe('Cascader.vue', () => {
|
||||
:props="props"
|
||||
/>
|
||||
`,
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
options: OPTIONS,
|
||||
props: { multiple: true },
|
||||
value: [['zhejiang', 'hangzhou'], ['zhejiang', 'ningbo']],
|
||||
value: [
|
||||
['zhejiang', 'hangzhou'],
|
||||
['zhejiang', 'ningbo'],
|
||||
],
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -219,7 +226,10 @@ describe('Cascader.vue', () => {
|
||||
options: OPTIONS,
|
||||
props: { multiple: true },
|
||||
collapseTags: true,
|
||||
modelValue: [['zhejiang', 'hangzhou'], ['zhejiang', 'ningbo']],
|
||||
modelValue: [
|
||||
['zhejiang', 'hangzhou'],
|
||||
['zhejiang', 'ningbo'],
|
||||
],
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
@ -237,7 +247,7 @@ describe('Cascader.vue', () => {
|
||||
filterable
|
||||
/>
|
||||
`,
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
options: OPTIONS,
|
||||
value: [],
|
||||
@ -249,7 +259,9 @@ describe('Cascader.vue', () => {
|
||||
const dropdown = document.querySelector(DROPDOWN)
|
||||
input.element.value = 'Ha'
|
||||
await input.trigger('input')
|
||||
const suggestions = dropdown.querySelectorAll(SUGGESTION_ITEM) as NodeListOf<HTMLElement>
|
||||
const suggestions = dropdown.querySelectorAll(
|
||||
SUGGESTION_ITEM
|
||||
) as NodeListOf<HTMLElement>
|
||||
const hzSuggestion = suggestions[0]
|
||||
expect(suggestions.length).toBe(1)
|
||||
expect(hzSuggestion.textContent).toBe('Zhejiang / Hangzhou')
|
||||
@ -272,7 +284,7 @@ describe('Cascader.vue', () => {
|
||||
filterable
|
||||
/>
|
||||
`,
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
options: OPTIONS,
|
||||
props: { multiple: true },
|
||||
|
@ -10,4 +10,3 @@ const _Cascader = Cascader as SFCWithInstall<typeof Cascader>
|
||||
|
||||
export default _Cascader
|
||||
export const ElCascader = _Cascader
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
:class="[
|
||||
'el-cascader',
|
||||
realSize && `el-cascader--${realSize}`,
|
||||
{ 'is-disabled': isDisabled }
|
||||
{ 'is-disabled': isDisabled },
|
||||
]"
|
||||
@click="() => togglePopperVisible(readonly ? undefined : true)"
|
||||
@keydown="handleKeyDown"
|
||||
@ -37,8 +37,8 @@
|
||||
:validate-event="false"
|
||||
:size="realSize"
|
||||
:class="{ 'is-focus': popperVisible }"
|
||||
@focus="e => $emit('focus', e)"
|
||||
@blur="e => $emit('blur', e)"
|
||||
@focus="(e) => $emit('focus', e)"
|
||||
@blur="(e) => $emit('blur', e)"
|
||||
@input="handleInput"
|
||||
>
|
||||
<template #suffix>
|
||||
@ -54,7 +54,7 @@
|
||||
:class="[
|
||||
'el-input__icon',
|
||||
'el-icon-arrow-down',
|
||||
popperVisible && 'is-reverse'
|
||||
popperVisible && 'is-reverse',
|
||||
]"
|
||||
@click.stop="togglePopperVisible()"
|
||||
></i>
|
||||
@ -80,10 +80,10 @@
|
||||
type="text"
|
||||
class="el-cascader__search-input"
|
||||
:placeholder="presentText ? '' : inputPlaceholder"
|
||||
@input="e => handleInput(searchInputValue, e)"
|
||||
@input="(e) => handleInput(searchInputValue, e)"
|
||||
@click.stop="togglePopperVisible(true)"
|
||||
@keydown.delete="handleDelete"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -114,7 +114,7 @@
|
||||
:key="item.uid"
|
||||
:class="[
|
||||
'el-cascader__suggestion-item',
|
||||
item.checked && 'is-checked'
|
||||
item.checked && 'is-checked',
|
||||
]"
|
||||
:tabindex="-1"
|
||||
@click="handleSuggestionClick(item)"
|
||||
@ -124,24 +124,33 @@
|
||||
</li>
|
||||
</template>
|
||||
<slot v-else name="empty">
|
||||
<li class="el-cascader__empty-text">{{ t('el.cascader.noMatch') }}</li>
|
||||
<li class="el-cascader__empty-text">
|
||||
{{ t('el.cascader.noMatch') }}
|
||||
</li>
|
||||
</slot>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
</el-popper>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed, defineComponent,
|
||||
inject, nextTick,
|
||||
onMounted, onBeforeUnmount,
|
||||
Ref, ref, watch,
|
||||
computed,
|
||||
defineComponent,
|
||||
inject,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
Ref,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { isPromise } from '@vue/shared'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
import ElCascaderPanel, { CommonProps } from '@element-plus/components/cascader-panel'
|
||||
import ElCascaderPanel, {
|
||||
CommonProps,
|
||||
} from '@element-plus/components/cascader-panel'
|
||||
import ElInput from '@element-plus/components/input'
|
||||
import ElPopper from '@element-plus/components/popper'
|
||||
import ElScrollbar from '@element-plus/components/scrollbar'
|
||||
@ -155,13 +164,20 @@ import { EVENT_CODE } from '@element-plus/utils/aria'
|
||||
import { UPDATE_MODEL_EVENT, CHANGE_EVENT } from '@element-plus/utils/constants'
|
||||
import isServer from '@element-plus/utils/isServer'
|
||||
import { useGlobalConfig } from '@element-plus/utils/util'
|
||||
import { addResizeListener, removeResizeListener } from '@element-plus/utils/resize-event'
|
||||
import {
|
||||
addResizeListener,
|
||||
removeResizeListener,
|
||||
} from '@element-plus/utils/resize-event'
|
||||
import { isValidComponentSize } from '@element-plus/utils/validators'
|
||||
import { Effect, Options } from '@element-plus/components/popper'
|
||||
|
||||
import type { ComputedRef, PropType } from 'vue'
|
||||
import type { ElFormContext, ElFormItemContext } from '@element-plus/tokens'
|
||||
import type { CascaderValue, CascaderNode, Tag } from '@element-plus/components/cascader-panel'
|
||||
import type {
|
||||
CascaderValue,
|
||||
CascaderNode,
|
||||
Tag,
|
||||
} from '@element-plus/components/cascader-panel'
|
||||
import type { ComponentSize } from '@element-plus/utils/types'
|
||||
|
||||
const DEFAULT_INPUT_HEIGHT = 40
|
||||
@ -216,8 +232,11 @@ export default defineComponent({
|
||||
clearable: Boolean,
|
||||
filterable: Boolean,
|
||||
filterMethod: {
|
||||
type: Function as PropType<(node: CascaderNode, keyword: string) => boolean>,
|
||||
default: (node: CascaderNode, keyword: string) => node.text.includes(keyword),
|
||||
type: Function as PropType<
|
||||
(node: CascaderNode, keyword: string) => boolean
|
||||
>,
|
||||
default: (node: CascaderNode, keyword: string) =>
|
||||
node.text.includes(keyword),
|
||||
},
|
||||
separator: {
|
||||
type: String,
|
||||
@ -279,20 +298,31 @@ export default defineComponent({
|
||||
const suggestions: Ref<CascaderNode[]> = ref([])
|
||||
|
||||
const isDisabled = computed(() => props.disabled || elForm.disabled)
|
||||
const inputPlaceholder = computed(() => props.placeholder || t('el.cascader.placeholder'))
|
||||
const realSize: ComputedRef<ComponentSize> = computed(() => props.size || elFormItem.size || $ELEMENT.size)
|
||||
const tagSize = computed(() => ['small', 'mini'].includes(realSize.value) ? 'mini' : 'small')
|
||||
const inputPlaceholder = computed(
|
||||
() => props.placeholder || t('el.cascader.placeholder')
|
||||
)
|
||||
const realSize: ComputedRef<ComponentSize> = computed(
|
||||
() => props.size || elFormItem.size || $ELEMENT.size
|
||||
)
|
||||
const tagSize = computed(() =>
|
||||
['small', 'mini'].includes(realSize.value) ? 'mini' : 'small'
|
||||
)
|
||||
const multiple = computed(() => !!props.props.multiple)
|
||||
const readonly = computed(() => !props.filterable || multiple.value)
|
||||
const searchKeyword = computed(() => multiple.value ? searchInputValue.value : inputValue.value)
|
||||
const checkedNodes: ComputedRef<CascaderNode[]> = computed(() => panel.value?.checkedNodes || [])
|
||||
const searchKeyword = computed(() =>
|
||||
multiple.value ? searchInputValue.value : inputValue.value
|
||||
)
|
||||
const checkedNodes: ComputedRef<CascaderNode[]> = computed(
|
||||
() => panel.value?.checkedNodes || []
|
||||
)
|
||||
const clearBtnVisible = computed(() => {
|
||||
if (
|
||||
!props.clearable ||
|
||||
isDisabled.value ||
|
||||
filtering.value ||
|
||||
!inputHover.value
|
||||
) return false
|
||||
)
|
||||
return false
|
||||
|
||||
return !!checkedNodes.value.length
|
||||
})
|
||||
@ -300,15 +330,17 @@ export default defineComponent({
|
||||
const { showAllLevels, separator } = props
|
||||
const nodes = checkedNodes.value
|
||||
return nodes.length
|
||||
? multiple.value ? ' ' : nodes[0].calcText(showAllLevels, separator)
|
||||
? multiple.value
|
||||
? ' '
|
||||
: nodes[0].calcText(showAllLevels, separator)
|
||||
: ''
|
||||
})
|
||||
|
||||
const checkedValue = computed<CascaderValue>({
|
||||
get () {
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set (val) {
|
||||
set(val) {
|
||||
emit(UPDATE_MODEL_EVENT, val)
|
||||
emit(CHANGE_EVENT, val)
|
||||
elFormItem.formItemMitt?.emit('el.form.change', [val])
|
||||
@ -387,7 +419,7 @@ export default defineComponent({
|
||||
closable: false,
|
||||
})
|
||||
} else {
|
||||
rest.forEach(node => tags.push(genTag(node)))
|
||||
rest.forEach((node) => tags.push(genTag(node)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -397,15 +429,16 @@ export default defineComponent({
|
||||
|
||||
const calculateSuggestions = () => {
|
||||
const { filterMethod, showAllLevels, separator } = props
|
||||
const res = panel.value.getFlattedNodes(!props.props.checkStrictly)
|
||||
.filter(node => {
|
||||
const res = panel.value
|
||||
.getFlattedNodes(!props.props.checkStrictly)
|
||||
.filter((node) => {
|
||||
if (node.isDisabled) return false
|
||||
node.calcText(showAllLevels, separator)
|
||||
return filterMethod(node, searchKeyword.value)
|
||||
})
|
||||
|
||||
if (multiple.value) {
|
||||
presentTags.value.forEach(tag => {
|
||||
presentTags.value.forEach((tag) => {
|
||||
tag.hitState = false
|
||||
})
|
||||
}
|
||||
@ -419,9 +452,13 @@ export default defineComponent({
|
||||
let firstNode = null
|
||||
|
||||
if (filtering.value && suggestionPanel.value) {
|
||||
firstNode = suggestionPanel.value.$el.querySelector('.el-cascader__suggestion-item')
|
||||
firstNode = suggestionPanel.value.$el.querySelector(
|
||||
'.el-cascader__suggestion-item'
|
||||
)
|
||||
} else {
|
||||
firstNode = panel.value?.$el.querySelector('.el-cascader-node[tabindex="-1"]')
|
||||
firstNode = panel.value?.$el.querySelector(
|
||||
'.el-cascader-node[tabindex="-1"]'
|
||||
)
|
||||
}
|
||||
|
||||
if (firstNode) {
|
||||
@ -438,13 +475,18 @@ export default defineComponent({
|
||||
if (isServer || !inputInner) return
|
||||
|
||||
if (suggestionPanelEl) {
|
||||
const suggestionList = suggestionPanelEl.querySelector('.el-cascader__suggestion-list')
|
||||
const suggestionList = suggestionPanelEl.querySelector(
|
||||
'.el-cascader__suggestion-list'
|
||||
)
|
||||
suggestionList.style.minWidth = inputInner.offsetWidth + 'px'
|
||||
}
|
||||
|
||||
if (tagWrapperEl) {
|
||||
const { offsetHeight } = tagWrapperEl
|
||||
const height = presentTags.value.length > 0 ? Math.max(offsetHeight + 6, inputInitialHeight) + 'px' : `${inputInitialHeight}px`
|
||||
const height =
|
||||
presentTags.value.length > 0
|
||||
? Math.max(offsetHeight + 6, inputInitialHeight) + 'px'
|
||||
: `${inputInitialHeight}px`
|
||||
inputInner.style.height = height
|
||||
updatePopperPosition()
|
||||
}
|
||||
@ -484,7 +526,7 @@ export default defineComponent({
|
||||
const handleSuggestionClick = (node: CascaderNode) => {
|
||||
const { checked } = node
|
||||
|
||||
if (multiple.value ) {
|
||||
if (multiple.value) {
|
||||
panel.value.handleCheckChange(node, !checked, false)
|
||||
} else {
|
||||
!checked && panel.value.handleCheckChange(node, true, false)
|
||||
@ -514,8 +556,9 @@ export default defineComponent({
|
||||
const passed = props.beforeFilter(value)
|
||||
|
||||
if (isPromise(passed)) {
|
||||
passed.then(calculateSuggestions)
|
||||
.catch(() => { /* prevent log error */ })
|
||||
passed.then(calculateSuggestions).catch(() => {
|
||||
/* prevent log error */
|
||||
})
|
||||
} else if (passed !== false) {
|
||||
calculateSuggestions()
|
||||
} else {
|
||||
@ -537,15 +580,14 @@ export default defineComponent({
|
||||
|
||||
watch(presentTags, () => nextTick(updateStyle))
|
||||
|
||||
watch(
|
||||
presentText,
|
||||
val => inputValue.value = val,
|
||||
{ immediate: true },
|
||||
)
|
||||
watch(presentText, (val) => (inputValue.value = val), { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
const inputEl = input.value.$el
|
||||
inputInitialHeight = inputEl?.offsetHeight || INPUT_HEIGHT_MAP[realSize.value] || DEFAULT_INPUT_HEIGHT
|
||||
inputInitialHeight =
|
||||
inputEl?.offsetHeight ||
|
||||
INPUT_HEIGHT_MAP[realSize.value] ||
|
||||
DEFAULT_INPUT_HEIGHT
|
||||
addResizeListener(inputEl, updateStyle)
|
||||
})
|
||||
|
||||
|
@ -15,25 +15,27 @@ describe('CheckTag.vue', () => {
|
||||
expect(wrapper.classes()).toContain('el-check-tag')
|
||||
})
|
||||
|
||||
|
||||
test('functionality', async () => {
|
||||
const wrapper = mount({
|
||||
template: `<el-check-tag @change="checked = !checked" :checked="checked">
|
||||
const wrapper = mount(
|
||||
{
|
||||
template: `<el-check-tag @change="checked = !checked" :checked="checked">
|
||||
${AXIOM}
|
||||
</el-check-tag>`,
|
||||
components: {
|
||||
'el-check-tag': CheckTag,
|
||||
components: {
|
||||
'el-check-tag': CheckTag,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
}
|
||||
},
|
||||
}, {
|
||||
slots: {
|
||||
default: AXIOM,
|
||||
},
|
||||
})
|
||||
{
|
||||
slots: {
|
||||
default: AXIOM,
|
||||
},
|
||||
}
|
||||
)
|
||||
expect(wrapper.text()).toEqual(AXIOM)
|
||||
|
||||
await wrapper.find('.el-check-tag').trigger('click')
|
||||
@ -43,8 +45,5 @@ describe('CheckTag.vue', () => {
|
||||
await wrapper.find('.el-check-tag').trigger('click')
|
||||
|
||||
expect(wrapper.vm.checked).toBe(false)
|
||||
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
@ -9,7 +9,7 @@
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'ElCheckTag',
|
||||
@ -18,7 +18,6 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['change'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const onChange = () => {
|
||||
emit('change', !props.checked)
|
||||
}
|
||||
|
@ -4,20 +4,28 @@ import Checkbox from '../src/checkbox.vue'
|
||||
import CheckboxButton from '../src/checkbox-button.vue'
|
||||
import CheckboxGroup from '../src/checkbox-group.vue'
|
||||
|
||||
const _mount = <D>(template: string, data: () => D, otherObj?: Record<string, unknown>) => mount<D>({
|
||||
components: {
|
||||
'el-checkbox': Checkbox,
|
||||
'el-checkbox-group': CheckboxGroup,
|
||||
'el-checkbox-button': CheckboxButton,
|
||||
},
|
||||
template,
|
||||
data,
|
||||
...otherObj,
|
||||
})
|
||||
const _mount = <D>(
|
||||
template: string,
|
||||
data: () => D,
|
||||
otherObj?: Record<string, unknown>
|
||||
) =>
|
||||
mount<D>({
|
||||
components: {
|
||||
'el-checkbox': Checkbox,
|
||||
'el-checkbox-group': CheckboxGroup,
|
||||
'el-checkbox-button': CheckboxButton,
|
||||
},
|
||||
template,
|
||||
data,
|
||||
...otherObj,
|
||||
})
|
||||
|
||||
describe('Checkbox', () => {
|
||||
test('create', async () => {
|
||||
const wrapper = _mount('<el-checkbox v-model="checkbox" label="a"/>', () => ({ checkbox: false }))
|
||||
const wrapper = _mount(
|
||||
'<el-checkbox v-model="checkbox" label="a"/>',
|
||||
() => ({ checkbox: false })
|
||||
)
|
||||
expect(wrapper.classes()).toContain('el-checkbox')
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.classes()).toContain('is-checked')
|
||||
@ -26,12 +34,17 @@ describe('Checkbox', () => {
|
||||
})
|
||||
|
||||
test('no v-model', async () => {
|
||||
const wrapper = _mount('<el-checkbox label="a"/>', () => ({ checkbox: false }))
|
||||
const wrapper = _mount('<el-checkbox label="a"/>', () => ({
|
||||
checkbox: false,
|
||||
}))
|
||||
expect(wrapper.classes('is-checked')).toBe(false)
|
||||
})
|
||||
|
||||
test('disabled', async () => {
|
||||
const wrapper = _mount('<el-checkbox v-model="checkbox" disabled label="a"/>', () => ({ checkbox: false }))
|
||||
const wrapper = _mount(
|
||||
'<el-checkbox v-model="checkbox" disabled label="a"/>',
|
||||
() => ({ checkbox: false })
|
||||
)
|
||||
expect(wrapper.classes()).toContain('is-disabled')
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.classes()).toContain('is-disabled')
|
||||
@ -50,7 +63,7 @@ describe('Checkbox', () => {
|
||||
this.data = val
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const vm = wrapper.vm
|
||||
@ -68,7 +81,7 @@ describe('Checkbox', () => {
|
||||
<el-checkbox label="d" ref="d"></el-checkbox>
|
||||
</el-checkbox-group>
|
||||
`,
|
||||
() => ({ checkList: [] }),
|
||||
() => ({ checkList: [] })
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
expect(vm.checkList.length).toBe(0)
|
||||
@ -96,7 +109,7 @@ describe('Checkbox', () => {
|
||||
this.data = val
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
await wrapper.findComponent({ ref: 'a' }).trigger('click')
|
||||
@ -117,7 +130,7 @@ describe('Checkbox', () => {
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
`,
|
||||
() => ({ checkList: [] }),
|
||||
() => ({ checkList: [] })
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
expect(vm.checkList.length).toBe(0)
|
||||
@ -130,7 +143,7 @@ describe('Checkbox', () => {
|
||||
`<el-checkbox true-label="a" :false-label="3" v-model="checked"></el-checkbox>`,
|
||||
() => ({
|
||||
checked: 'a',
|
||||
}),
|
||||
})
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
await wrapper.trigger('click')
|
||||
@ -150,13 +163,13 @@ describe('Checkbox', () => {
|
||||
() => ({
|
||||
checked: false,
|
||||
checklist: [],
|
||||
}),
|
||||
})
|
||||
) as any
|
||||
expect(wrapper.vm.checked).toBe(true)
|
||||
expect(wrapper.vm.checklist).toEqual(['a'])
|
||||
})
|
||||
|
||||
test('label', async() => {
|
||||
test('label', async () => {
|
||||
const wrapper = _mount(
|
||||
`
|
||||
<div>
|
||||
@ -170,7 +183,7 @@ describe('Checkbox', () => {
|
||||
() => ({
|
||||
checked: false,
|
||||
checklist: [],
|
||||
}),
|
||||
})
|
||||
)
|
||||
const checkbox = wrapper.find('.el-checkbox')
|
||||
await checkbox.trigger('click')
|
||||
@ -180,7 +193,10 @@ describe('Checkbox', () => {
|
||||
|
||||
describe('check-button', () => {
|
||||
test('create', async () => {
|
||||
const wrapper = _mount('<el-checkbox-button v-model="checkbox" label="a"/>', () => ({ checkbox: false }))
|
||||
const wrapper = _mount(
|
||||
'<el-checkbox-button v-model="checkbox" label="a"/>',
|
||||
() => ({ checkbox: false })
|
||||
)
|
||||
expect(wrapper.classes()).toContain('el-checkbox-button')
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.classes()).toContain('is-checked')
|
||||
@ -189,7 +205,10 @@ describe('check-button', () => {
|
||||
})
|
||||
|
||||
test('disabled', async () => {
|
||||
const wrapper = _mount('<el-checkbox-button v-model="checkbox" disabled label="a"/>', () => ({ checkbox: false }))
|
||||
const wrapper = _mount(
|
||||
'<el-checkbox-button v-model="checkbox" disabled label="a"/>',
|
||||
() => ({ checkbox: false })
|
||||
)
|
||||
expect(wrapper.classes()).toContain('is-disabled')
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.classes()).toContain('is-disabled')
|
||||
@ -210,7 +229,7 @@ describe('check-button', () => {
|
||||
this.data = val
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const vm = wrapper.vm
|
||||
@ -238,7 +257,7 @@ describe('check-button', () => {
|
||||
this.data = val
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
await wrapper.findComponent({ ref: 'a' }).trigger('click')
|
||||
@ -259,12 +278,15 @@ describe('check-button', () => {
|
||||
<el-checkbox-button label="d" ref="d"></el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
`,
|
||||
() => ({ checkList: ['a', 'b'] }),
|
||||
() => ({ checkList: ['a', 'b'] })
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
expect(vm.checkList.length).toBe(2)
|
||||
expect((vm.$refs.a as any).$el.classList.contains('is-checked')).toBe(true)
|
||||
expect((vm.$refs.a as any).$el.querySelector('.el-checkbox-button__inner').style.borderColor).toEqual('#ff0000')
|
||||
expect(
|
||||
(vm.$refs.a as any).$el.querySelector('.el-checkbox-button__inner').style
|
||||
.borderColor
|
||||
).toEqual('#ff0000')
|
||||
})
|
||||
|
||||
test('button group min and max', async () => {
|
||||
@ -284,7 +306,7 @@ describe('check-button', () => {
|
||||
() => ({
|
||||
checkList: ['a'],
|
||||
lastEvent: null,
|
||||
}),
|
||||
})
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
expect(vm.checkList.length).toBe(1)
|
||||
@ -296,8 +318,12 @@ describe('check-button', () => {
|
||||
await wrapper.findComponent({ ref: 'c' }).trigger('click')
|
||||
expect(vm.checkList.length).toBe(2)
|
||||
expect(vm.checkList).toEqual(['a', 'b'])
|
||||
expect((wrapper.findComponent({ ref: 'c' }).vm as any).isDisabled).toBe(true)
|
||||
expect((wrapper.findComponent({ ref: 'd' }).vm as any).isDisabled).toBe(true)
|
||||
expect((wrapper.findComponent({ ref: 'c' }).vm as any).isDisabled).toBe(
|
||||
true
|
||||
)
|
||||
expect((wrapper.findComponent({ ref: 'd' }).vm as any).isDisabled).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -313,7 +339,7 @@ describe('check-button', () => {
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
`,
|
||||
() => ({ checkList: [] }),
|
||||
() => ({ checkList: [] })
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
expect(vm.checkList.length).toBe(0)
|
||||
@ -326,7 +352,7 @@ describe('check-button', () => {
|
||||
`<el-checkbox-button true-label="a" :false-label="3" v-model="checked" />`,
|
||||
() => ({
|
||||
checked: 'a',
|
||||
}),
|
||||
})
|
||||
)
|
||||
const vm = wrapper.vm
|
||||
await wrapper.trigger('click')
|
||||
@ -346,17 +372,16 @@ describe('check-button', () => {
|
||||
() => ({
|
||||
checked: false,
|
||||
checklist: [],
|
||||
}),
|
||||
})
|
||||
)
|
||||
expect(wrapper.vm.checked).toBe(true)
|
||||
expect(wrapper.vm.checklist).toEqual(['a'])
|
||||
})
|
||||
|
||||
test('checked', () => {
|
||||
const wrapper = _mount(
|
||||
`<el-checkbox checked />`,
|
||||
() => ({}))
|
||||
expect(wrapper.find('.el-checkbox').classes().toString()).toMatch('is-checked')
|
||||
const wrapper = _mount(`<el-checkbox checked />`, () => ({}))
|
||||
expect(wrapper.find('.el-checkbox').classes().toString()).toMatch(
|
||||
'is-checked'
|
||||
)
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -23,7 +23,7 @@
|
||||
@change="handleChange"
|
||||
@focus="focus = true"
|
||||
@blur="focus = false"
|
||||
>
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
v-model="model"
|
||||
@ -35,7 +35,7 @@
|
||||
@change="handleChange"
|
||||
@focus="focus = true"
|
||||
@blur="focus = false"
|
||||
>
|
||||
/>
|
||||
|
||||
<span
|
||||
v-if="$slots.default || label"
|
||||
@ -44,14 +44,10 @@
|
||||
>
|
||||
<slot>{{ label }}</slot>
|
||||
</span>
|
||||
|
||||
</label>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
import {
|
||||
defineComponent,
|
||||
computed,
|
||||
} from 'vue'
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
|
||||
import { useCheckbox, useCheckboxGroup, useCheckboxProps } from './useCheckbox'
|
||||
|
||||
@ -60,7 +56,8 @@ export default defineComponent({
|
||||
props: useCheckboxProps,
|
||||
emits: [UPDATE_MODEL_EVENT, 'change'],
|
||||
setup(props) {
|
||||
const { focus, isChecked, isDisabled, size, model, handleChange } = useCheckbox(props)
|
||||
const { focus, isChecked, isDisabled, size, model, handleChange } =
|
||||
useCheckbox(props)
|
||||
const { checkboxGroup } = useCheckboxGroup()
|
||||
|
||||
const activeStyle = computed(() => {
|
||||
@ -73,7 +70,6 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
focus,
|
||||
isChecked,
|
||||
|
@ -5,7 +5,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, watch, provide, nextTick, toRefs } from 'vue'
|
||||
import {
|
||||
defineComponent,
|
||||
computed,
|
||||
watch,
|
||||
provide,
|
||||
nextTick,
|
||||
toRefs,
|
||||
} from 'vue'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
|
||||
import { isValidComponentSize } from '@element-plus/utils/validators'
|
||||
import { useCheckboxGroup } from './useCheckbox'
|
||||
@ -48,9 +55,11 @@ export default defineComponent({
|
||||
|
||||
setup(props, ctx) {
|
||||
const { elFormItem, elFormItemSize, ELEMENT } = useCheckboxGroup()
|
||||
const checkboxGroupSize = computed(() => props.size || elFormItemSize.value || ELEMENT.size)
|
||||
const checkboxGroupSize = computed(
|
||||
() => props.size || elFormItemSize.value || ELEMENT.size
|
||||
)
|
||||
|
||||
const changeEvent = value => {
|
||||
const changeEvent = (value) => {
|
||||
ctx.emit(UPDATE_MODEL_EVENT, value)
|
||||
nextTick(() => {
|
||||
ctx.emit('change', value)
|
||||
@ -74,9 +83,12 @@ export default defineComponent({
|
||||
changeEvent,
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, val => {
|
||||
elFormItem.formItemMitt?.emit('el.form.change', [val])
|
||||
})
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
elFormItem.formItemMitt?.emit('el.form.change', [val])
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
@ -6,7 +6,7 @@
|
||||
checkboxSize ? 'el-checkbox--' + checkboxSize : '',
|
||||
{ 'is-disabled': isDisabled },
|
||||
{ 'is-bordered': border },
|
||||
{ 'is-checked': isChecked }
|
||||
{ 'is-checked': isChecked },
|
||||
]"
|
||||
:aria-controls="indeterminate ? controls : null"
|
||||
>
|
||||
@ -16,7 +16,7 @@
|
||||
'is-disabled': isDisabled,
|
||||
'is-checked': isChecked,
|
||||
'is-indeterminate': indeterminate,
|
||||
'is-focus': focus
|
||||
'is-focus': focus,
|
||||
}"
|
||||
:tabindex="indeterminate ? 0 : undefined"
|
||||
:role="indeterminate ? 'checkbox' : undefined"
|
||||
@ -36,7 +36,7 @@
|
||||
@change="handleChange"
|
||||
@focus="focus = true"
|
||||
@blur="focus = false"
|
||||
>
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
v-model="model"
|
||||
@ -49,7 +49,7 @@
|
||||
@change="handleChange"
|
||||
@focus="focus = true"
|
||||
@blur="focus = false"
|
||||
>
|
||||
/>
|
||||
</span>
|
||||
<span v-if="$slots.default || label" class="el-checkbox__label">
|
||||
<slot></slot>
|
||||
@ -57,10 +57,8 @@
|
||||
</span>
|
||||
</label>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
import {
|
||||
defineComponent,
|
||||
} from 'vue'
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
|
||||
import { isValidComponentSize } from '@element-plus/utils/validators'
|
||||
import { useCheckbox } from './useCheckbox'
|
||||
|
@ -1,10 +1,4 @@
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
inject,
|
||||
getCurrentInstance,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { ref, computed, inject, getCurrentInstance, watch } from 'vue'
|
||||
import { toTypeString } from '@vue/shared'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
|
||||
import { useGlobalConfig } from '@element-plus/utils/util'
|
||||
@ -48,7 +42,9 @@ export const useCheckboxGroup = () => {
|
||||
const elForm = inject(elFormKey, {} as ElFormContext)
|
||||
const elFormItem = inject(elFormItemKey, {} as ElFormItemContext)
|
||||
const checkboxGroup = inject<ICheckboxGroupInstance>('CheckboxGroup', {})
|
||||
const isGroup = computed(() => checkboxGroup && checkboxGroup?.name === 'ElCheckboxGroup')
|
||||
const isGroup = computed(
|
||||
() => checkboxGroup && checkboxGroup?.name === 'ElCheckboxGroup'
|
||||
)
|
||||
const elFormItemSize = computed(() => {
|
||||
return elFormItem.size
|
||||
})
|
||||
@ -67,22 +63,28 @@ const useModel = (props: IUseCheckboxProps) => {
|
||||
const { emit } = getCurrentInstance()
|
||||
const { isGroup, checkboxGroup } = useCheckboxGroup()
|
||||
const isLimitExceeded = ref(false)
|
||||
const store = computed(() => checkboxGroup ? checkboxGroup.modelValue?.value : props.modelValue)
|
||||
const store = computed(() =>
|
||||
checkboxGroup ? checkboxGroup.modelValue?.value : props.modelValue
|
||||
)
|
||||
const model = computed({
|
||||
get() {
|
||||
return isGroup.value
|
||||
? store.value
|
||||
: props.modelValue ?? selfModel.value
|
||||
return isGroup.value ? store.value : props.modelValue ?? selfModel.value
|
||||
},
|
||||
|
||||
set(val: unknown) {
|
||||
if (isGroup.value && Array.isArray(val)) {
|
||||
isLimitExceeded.value = false
|
||||
|
||||
if (checkboxGroup.min !== undefined && val.length < checkboxGroup.min.value) {
|
||||
if (
|
||||
checkboxGroup.min !== undefined &&
|
||||
val.length < checkboxGroup.min.value
|
||||
) {
|
||||
isLimitExceeded.value = true
|
||||
}
|
||||
if (checkboxGroup.max !== undefined && val.length > checkboxGroup.max.value) {
|
||||
if (
|
||||
checkboxGroup.max !== undefined &&
|
||||
val.length > checkboxGroup.max.value
|
||||
) {
|
||||
isLimitExceeded.value = true
|
||||
}
|
||||
|
||||
@ -100,10 +102,18 @@ const useModel = (props: IUseCheckboxProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
const useCheckboxStatus = (props: IUseCheckboxProps, { model }: PartialReturnType<typeof useModel>) => {
|
||||
const useCheckboxStatus = (
|
||||
props: IUseCheckboxProps,
|
||||
{ model }: PartialReturnType<typeof useModel>
|
||||
) => {
|
||||
const { isGroup, checkboxGroup, elFormItemSize, ELEMENT } = useCheckboxGroup()
|
||||
const focus = ref(false)
|
||||
const size = computed<string | undefined>(() => checkboxGroup?.checkboxGroupSize?.value || elFormItemSize.value || ELEMENT.size)
|
||||
const size = computed<string | undefined>(
|
||||
() =>
|
||||
checkboxGroup?.checkboxGroupSize?.value ||
|
||||
elFormItemSize.value ||
|
||||
ELEMENT.size
|
||||
)
|
||||
const isChecked = computed<boolean>(() => {
|
||||
const value = model.value
|
||||
if (toTypeString(value) === '[object Boolean]') {
|
||||
@ -133,14 +143,20 @@ const useCheckboxStatus = (props: IUseCheckboxProps, { model }: PartialReturnTyp
|
||||
|
||||
const useDisabled = (
|
||||
props: IUseCheckboxProps,
|
||||
{ model, isChecked }: PartialReturnType<typeof useModel> & PartialReturnType<typeof useCheckboxStatus>,
|
||||
{
|
||||
model,
|
||||
isChecked,
|
||||
}: PartialReturnType<typeof useModel> &
|
||||
PartialReturnType<typeof useCheckboxStatus>
|
||||
) => {
|
||||
const { elForm, isGroup, checkboxGroup } = useCheckboxGroup()
|
||||
const isLimitDisabled = computed(() => {
|
||||
const max = checkboxGroup.max?.value
|
||||
const min = checkboxGroup.min?.value
|
||||
return !!(max || min) && (model.value.length >= max && !isChecked.value) ||
|
||||
return (
|
||||
(!!(max || min) && model.value.length >= max && !isChecked.value) ||
|
||||
(model.value.length <= min && isChecked.value)
|
||||
)
|
||||
})
|
||||
const isDisabled = computed(() => {
|
||||
const disabled = props.disabled || elForm.disabled
|
||||
@ -155,12 +171,12 @@ const useDisabled = (
|
||||
}
|
||||
}
|
||||
|
||||
const setStoreValue = (props: IUseCheckboxProps, { model }: PartialReturnType<typeof useModel>) => {
|
||||
const setStoreValue = (
|
||||
props: IUseCheckboxProps,
|
||||
{ model }: PartialReturnType<typeof useModel>
|
||||
) => {
|
||||
function addToStore() {
|
||||
if (
|
||||
Array.isArray(model.value) &&
|
||||
!model.value.includes(props.label)
|
||||
) {
|
||||
if (Array.isArray(model.value) && !model.value.includes(props.label)) {
|
||||
model.value.push(props.label)
|
||||
} else {
|
||||
model.value = props.trueLabel || true
|
||||
@ -169,7 +185,10 @@ const setStoreValue = (props: IUseCheckboxProps, { model }: PartialReturnType<ty
|
||||
props.checked && addToStore()
|
||||
}
|
||||
|
||||
const useEvent = (props: IUseCheckboxProps, { isLimitExceeded }: PartialReturnType<typeof useModel>) => {
|
||||
const useEvent = (
|
||||
props: IUseCheckboxProps,
|
||||
{ isLimitExceeded }: PartialReturnType<typeof useModel>
|
||||
) => {
|
||||
const { elFormItem } = useCheckboxGroup()
|
||||
const { emit } = getCurrentInstance()
|
||||
function handleChange(e: InputEvent) {
|
||||
@ -182,9 +201,12 @@ const useEvent = (props: IUseCheckboxProps, { isLimitExceeded }: PartialReturnTy
|
||||
emit('change', value, e)
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, val => {
|
||||
elFormItem.formItemMitt?.emit('el.form.change', [val])
|
||||
})
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
elFormItem.formItemMitt?.emit('el.form.change', [val])
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
handleChange,
|
||||
@ -193,7 +215,9 @@ const useEvent = (props: IUseCheckboxProps, { isLimitExceeded }: PartialReturnTy
|
||||
|
||||
export const useCheckbox = (props: IUseCheckboxProps) => {
|
||||
const { model, isLimitExceeded } = useModel(props)
|
||||
const { focus, size, isChecked, checkboxSize } = useCheckboxStatus(props, { model })
|
||||
const { focus, size, isChecked, checkboxSize } = useCheckboxStatus(props, {
|
||||
model,
|
||||
})
|
||||
const { isDisabled } = useDisabled(props, { model, isChecked })
|
||||
const { handleChange } = useEvent(props, { isLimitExceeded })
|
||||
|
||||
|
@ -37,7 +37,7 @@ describe('Col', () => {
|
||||
</el-row>`,
|
||||
components: {
|
||||
'el-col': Col,
|
||||
'el-row':Row,
|
||||
'el-row': Row,
|
||||
},
|
||||
}
|
||||
const wrapper = mount(TestComponent)
|
||||
@ -51,15 +51,19 @@ describe('Col', () => {
|
||||
const App = {
|
||||
setup() {
|
||||
return () => {
|
||||
return h(Row, {
|
||||
gutter: outer.value,
|
||||
ref: 'row',
|
||||
}, [
|
||||
h(Col, {
|
||||
span: 12,
|
||||
ref: 'col',
|
||||
}),
|
||||
])
|
||||
return h(
|
||||
Row,
|
||||
{
|
||||
gutter: outer.value,
|
||||
ref: 'row',
|
||||
},
|
||||
[
|
||||
h(Col, {
|
||||
span: 12,
|
||||
ref: 'col',
|
||||
}),
|
||||
]
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -88,7 +92,7 @@ describe('Col', () => {
|
||||
</el-row>`,
|
||||
components: {
|
||||
'el-col': Col,
|
||||
'el-row':Row,
|
||||
'el-row': Row,
|
||||
},
|
||||
}
|
||||
const wrapper = mount(TestComponent)
|
||||
@ -129,4 +133,3 @@ describe('Row', () => {
|
||||
expect(wrapper.classes()).toContain('is-align-bottom')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -64,22 +64,24 @@ const ElCol = defineComponent({
|
||||
const classList = computed(() => {
|
||||
const ret: string[] = []
|
||||
const pos = ['span', 'offset', 'pull', 'push'] as const
|
||||
pos.forEach(prop => {
|
||||
pos.forEach((prop) => {
|
||||
const size = props[prop]
|
||||
if (typeof size === 'number') {
|
||||
if(prop === 'span') ret.push(`el-col-${props[prop]}`)
|
||||
else if(size > 0) ret.push(`el-col-${prop}-${props[prop]}`)
|
||||
if (prop === 'span') ret.push(`el-col-${props[prop]}`)
|
||||
else if (size > 0) ret.push(`el-col-${prop}-${props[prop]}`)
|
||||
}
|
||||
})
|
||||
const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const
|
||||
sizes.forEach(size => {
|
||||
sizes.forEach((size) => {
|
||||
if (typeof props[size] === 'number') {
|
||||
ret.push(`el-col-${size}-${props[size]}`)
|
||||
} else if (typeof props[size] === 'object') {
|
||||
const sizeProps = props[size]
|
||||
Object.keys(sizeProps).forEach(prop => {
|
||||
Object.keys(sizeProps).forEach((prop) => {
|
||||
ret.push(
|
||||
prop !== 'span' ? `el-col-${size}-${prop}-${sizeProps[prop]}` : `el-col-${size}-${sizeProps[prop]}`,
|
||||
prop !== 'span'
|
||||
? `el-col-${size}-${prop}-${sizeProps[prop]}`
|
||||
: `el-col-${size}-${sizeProps[prop]}`
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -92,14 +94,15 @@ const ElCol = defineComponent({
|
||||
return ret
|
||||
})
|
||||
|
||||
return () => h(
|
||||
props.tag,
|
||||
{
|
||||
class: ['el-col', classList.value],
|
||||
style: style.value,
|
||||
},
|
||||
slots.default?.(),
|
||||
)
|
||||
return () =>
|
||||
h(
|
||||
props.tag,
|
||||
{
|
||||
class: ['el-col', classList.value],
|
||||
style: style.value,
|
||||
},
|
||||
slots.default?.()
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -6,7 +6,9 @@ CollapseTransition.install = (app: App): void => {
|
||||
app.component(CollapseTransition.name, CollapseTransition)
|
||||
}
|
||||
|
||||
const _CollapseTransition = CollapseTransition as SFCWithInstall<typeof CollapseTransition>
|
||||
const _CollapseTransition = CollapseTransition as SFCWithInstall<
|
||||
typeof CollapseTransition
|
||||
>
|
||||
|
||||
export default _CollapseTransition
|
||||
export const ElCollapseTransition = _CollapseTransition
|
||||
|
@ -36,7 +36,9 @@ describe('Collapse.vue', () => {
|
||||
const vm = wrapper.vm
|
||||
const collapseWrapper = wrapper.findComponent(Collapse)
|
||||
const collapseItemWrappers = collapseWrapper.findAllComponents(CollapseItem)
|
||||
const collapseItemHeaderEls = vm.$el.querySelectorAll('.el-collapse-item__header')
|
||||
const collapseItemHeaderEls = vm.$el.querySelectorAll(
|
||||
'.el-collapse-item__header'
|
||||
)
|
||||
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
||||
|
||||
collapseItemHeaderEls[2].click()
|
||||
@ -80,7 +82,9 @@ describe('Collapse.vue', () => {
|
||||
const vm = wrapper.vm
|
||||
const collapseWrapper = wrapper.findComponent(Collapse)
|
||||
const collapseItemWrappers = collapseWrapper.findAllComponents(CollapseItem)
|
||||
const collapseItemHeaderEls = vm.$el.querySelectorAll('.el-collapse-item__header')
|
||||
const collapseItemHeaderEls = vm.$el.querySelectorAll(
|
||||
'.el-collapse-item__header'
|
||||
)
|
||||
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
||||
|
||||
collapseItemHeaderEls[2].click()
|
||||
@ -129,7 +133,9 @@ describe('Collapse.vue', () => {
|
||||
const vm = wrapper.vm
|
||||
const collapseWrapper = wrapper.findComponent(Collapse)
|
||||
const collapseItemWrappers = collapseWrapper.findAllComponents(CollapseItem)
|
||||
const collapseItemHeaderEls = vm.$el.querySelectorAll('.el-collapse-item__header')
|
||||
const collapseItemHeaderEls = vm.$el.querySelectorAll(
|
||||
'.el-collapse-item__header'
|
||||
)
|
||||
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
||||
expect(vm.activeNames).toEqual(['1'])
|
||||
expect(onChange).not.toHaveBeenCalled()
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="el-collapse-item"
|
||||
:class="{'is-active': isActive, 'is-disabled': disabled }"
|
||||
:class="{ 'is-active': isActive, 'is-disabled': disabled }"
|
||||
>
|
||||
<div
|
||||
role="tab"
|
||||
@ -15,8 +15,8 @@
|
||||
role="button"
|
||||
:tabindex="disabled ? -1 : 0"
|
||||
:class="{
|
||||
'focusing': focusing,
|
||||
'is-active': isActive
|
||||
focusing: focusing,
|
||||
'is-active': isActive,
|
||||
}"
|
||||
@click="handleHeaderClick"
|
||||
@keyup.space.enter.stop="handleEnterClick"
|
||||
@ -26,7 +26,7 @@
|
||||
<slot name="title">{{ title }}</slot>
|
||||
<i
|
||||
class="el-collapse-item__arrow el-icon-arrow-right"
|
||||
:class="{'is-active': isActive}"
|
||||
:class="{ 'is-active': isActive }"
|
||||
>
|
||||
</i>
|
||||
</div>
|
||||
@ -47,7 +47,7 @@
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, inject, computed, ref } from 'vue'
|
||||
import { CollapseProvider } from './collapse'
|
||||
import { generateId } from '@element-plus/utils/util'
|
||||
@ -88,7 +88,7 @@ export default defineComponent({
|
||||
|
||||
const handleFocus = () => {
|
||||
setTimeout(() => {
|
||||
if(!isClick.value) {
|
||||
if (!isClick.value) {
|
||||
focusing.value = true
|
||||
} else {
|
||||
isClick.value = false
|
||||
@ -97,7 +97,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const handleHeaderClick = () => {
|
||||
if(props.disabled) return
|
||||
if (props.disabled) return
|
||||
collapseMitt?.emit('item-click', props.name)
|
||||
focusing.value = false
|
||||
isClick.value = true
|
||||
|
@ -5,13 +5,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
watch,
|
||||
provide,
|
||||
onUnmounted,
|
||||
} from 'vue'
|
||||
import { defineComponent, ref, watch, provide, onUnmounted } from 'vue'
|
||||
import type { PropType, Ref } from 'vue'
|
||||
import mitt, { Emitter } from 'mitt'
|
||||
import { UPDATE_MODEL_EVENT, CHANGE_EVENT } from '@element-plus/utils/constants'
|
||||
@ -26,7 +20,7 @@ export default defineComponent({
|
||||
accordion: Boolean,
|
||||
modelValue: {
|
||||
type: [Array, String, Number] as PropType<
|
||||
string | number | Array<string | number>
|
||||
string | number | Array<string | number>
|
||||
>,
|
||||
default: () => [],
|
||||
},
|
||||
@ -36,20 +30,20 @@ export default defineComponent({
|
||||
const activeNames = ref([].concat(props.modelValue))
|
||||
const collapseMitt: Emitter = mitt()
|
||||
|
||||
const setActiveNames = _activeNames => {
|
||||
const setActiveNames = (_activeNames) => {
|
||||
activeNames.value = [].concat(_activeNames)
|
||||
const value = props.accordion ? activeNames.value[0] : activeNames.value
|
||||
emit(UPDATE_MODEL_EVENT, value)
|
||||
emit(CHANGE_EVENT, value)
|
||||
}
|
||||
|
||||
const handleItemClick = name => {
|
||||
const handleItemClick = (name) => {
|
||||
if (props.accordion) {
|
||||
setActiveNames(
|
||||
(activeNames.value[0] || activeNames.value[0] === 0) &&
|
||||
activeNames.value[0] === name
|
||||
? ''
|
||||
: name,
|
||||
: name
|
||||
)
|
||||
} else {
|
||||
const _activeNames = activeNames.value.slice(0)
|
||||
@ -68,7 +62,7 @@ export default defineComponent({
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
activeNames.value = [].concat(props.modelValue)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
collapseMitt.on('item-click', handleItemClick)
|
||||
|
@ -4,7 +4,7 @@ import ColorPicker from '../src/index.vue'
|
||||
|
||||
import type { Nullable } from '@element-plus/utils/types'
|
||||
|
||||
const _mount = (template: string, data: () => ({ [key: string]: any; })) => {
|
||||
const _mount = (template: string, data: () => { [key: string]: any }) => {
|
||||
const Component = defineComponent({
|
||||
components: {
|
||||
ElColorPicker: ColorPicker,
|
||||
@ -24,8 +24,7 @@ type ColorPickerVM = ComponentPublicInstance<{
|
||||
clientY: number
|
||||
}) => void
|
||||
thumbTop: number
|
||||
handleDrag: (opt: { clientX: number; clientY: number; }) => void
|
||||
|
||||
handleDrag: (opt: { clientX: number; clientY: number }) => void
|
||||
}>
|
||||
|
||||
describe('Color-picker', () => {
|
||||
@ -39,7 +38,7 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color" :show-alpha="true"></el-color-picker>`,
|
||||
() => ({
|
||||
color: '#20A0FF',
|
||||
}),
|
||||
})
|
||||
)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const alphaSlider = document.querySelector('.el-color-alpha-slider')
|
||||
@ -51,11 +50,13 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color"></el-color-picker>`,
|
||||
() => ({
|
||||
color: '#20A0FF',
|
||||
}),
|
||||
})
|
||||
)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
await nextTick()
|
||||
const input = document.querySelector<HTMLInputElement>('.el-color-dropdown__value input')
|
||||
const input = document.querySelector<HTMLInputElement>(
|
||||
'.el-color-dropdown__value input'
|
||||
)
|
||||
expect(input.value.trim().toUpperCase()).toEqual('#20A0FF')
|
||||
wrapper.unmount()
|
||||
})
|
||||
@ -64,7 +65,7 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color"></el-color-picker>`,
|
||||
() => ({
|
||||
color: null,
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
@ -79,17 +80,26 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color"></el-color-picker>`,
|
||||
() => ({
|
||||
color: '#0F0',
|
||||
}),
|
||||
})
|
||||
)
|
||||
const colorPickerWrapper = wrapper.findComponent(ColorPicker)
|
||||
const hueSlideWrapper = colorPickerWrapper.findComponent({ ref: 'hue' })
|
||||
const hueSlideDom = hueSlideWrapper.element as HTMLElement
|
||||
const thumbDom = hueSlideWrapper.find<HTMLElement>('.el-color-hue-slider__thumb').element
|
||||
const mockHueSlideHeight = jest.spyOn(hueSlideDom, 'offsetHeight', 'get').mockImplementation(() => 200)
|
||||
const mockThumbDom = jest.spyOn(thumbDom, 'offsetHeight', 'get').mockImplementation(() => 4)
|
||||
const thumbDom = hueSlideWrapper.find<HTMLElement>(
|
||||
'.el-color-hue-slider__thumb'
|
||||
).element
|
||||
const mockHueSlideHeight = jest
|
||||
.spyOn(hueSlideDom, 'offsetHeight', 'get')
|
||||
.mockImplementation(() => 200)
|
||||
const mockThumbDom = jest
|
||||
.spyOn(thumbDom, 'offsetHeight', 'get')
|
||||
.mockImplementation(() => 4)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
await nextTick()
|
||||
expect((hueSlideWrapper.vm as ComponentPublicInstance<{ thumbTop: number; }>).thumbTop > 10).toBeTruthy()
|
||||
expect(
|
||||
(hueSlideWrapper.vm as ComponentPublicInstance<{ thumbTop: number }>)
|
||||
.thumbTop > 10
|
||||
).toBeTruthy()
|
||||
mockHueSlideHeight.mockRestore()
|
||||
mockThumbDom.mockRestore()
|
||||
wrapper.unmount()
|
||||
@ -99,7 +109,7 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color"></el-color-picker>`,
|
||||
() => ({
|
||||
color: '#0F0',
|
||||
}),
|
||||
})
|
||||
)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const dropdown = document.querySelector('.el-color-dropdown')
|
||||
@ -111,10 +121,12 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color"></el-color-picker>`,
|
||||
() => ({
|
||||
color: '#0F0',
|
||||
}),
|
||||
})
|
||||
)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const clearBtn = document.querySelector<HTMLElement>('.el-color-dropdown__link-btn')
|
||||
const clearBtn = document.querySelector<HTMLElement>(
|
||||
'.el-color-dropdown__link-btn'
|
||||
)
|
||||
clearBtn.click()
|
||||
expect(wrapper.vm.color).toEqual(null)
|
||||
wrapper.unmount()
|
||||
@ -124,52 +136,73 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color"></el-color-picker>`,
|
||||
() => ({
|
||||
color: '#F00',
|
||||
}),
|
||||
})
|
||||
)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const colorPickerWrapper = wrapper.findComponent(ColorPicker)
|
||||
const hueSlideWrapper = colorPickerWrapper.findComponent({ ref: 'hue' })
|
||||
const hueSlideDom = hueSlideWrapper.element
|
||||
const thumbDom = hueSlideWrapper.find<HTMLElement>('.el-color-hue-slider__thumb').element
|
||||
const mockHueBarHeight = jest.spyOn(hueSlideDom,'getBoundingClientRect').mockReturnValue({
|
||||
height: 176,
|
||||
width: 12,
|
||||
x: 0,
|
||||
y: 0,
|
||||
top: 0,
|
||||
} as DOMRect)
|
||||
const mockThumbDom = jest.spyOn(thumbDom, 'offsetHeight', 'get').mockReturnValue(4);
|
||||
(hueSlideWrapper.vm as ColorPickerVM).handleClick({ target: null, clientX: 0, clientY: 100 })
|
||||
const thumbDom = hueSlideWrapper.find<HTMLElement>(
|
||||
'.el-color-hue-slider__thumb'
|
||||
).element
|
||||
const mockHueBarHeight = jest
|
||||
.spyOn(hueSlideDom, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 176,
|
||||
width: 12,
|
||||
x: 0,
|
||||
y: 0,
|
||||
top: 0,
|
||||
} as DOMRect)
|
||||
const mockThumbDom = jest
|
||||
.spyOn(thumbDom, 'offsetHeight', 'get')
|
||||
.mockReturnValue(4)
|
||||
;(hueSlideWrapper.vm as ColorPickerVM).handleClick({
|
||||
target: null,
|
||||
clientX: 0,
|
||||
clientY: 100,
|
||||
})
|
||||
const hue = colorPickerWrapper.vm.color.get('hue')
|
||||
expect(hue > 0).toBeTruthy()
|
||||
mockHueBarHeight.mockRestore()
|
||||
mockThumbDom.mockRestore()
|
||||
wrapper.unmount()
|
||||
|
||||
})
|
||||
it('should change hue when saturation is zero', async () => {
|
||||
const wrapper = _mount(
|
||||
`<el-color-picker v-model="color"></el-color-picker>`,
|
||||
() => ({
|
||||
color: '20A0FF',
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const colorPickerWrapper = wrapper.findComponent(ColorPicker)
|
||||
const hueSlideWrapper = colorPickerWrapper.findComponent({ ref: 'hue' })
|
||||
const hueSlideDom = hueSlideWrapper.element as HTMLElement
|
||||
const thumbDom = hueSlideWrapper.find<HTMLElement>('.el-color-hue-slider__thumb').element
|
||||
const mockHueSlideRect = jest.spyOn(hueSlideDom,'getBoundingClientRect').mockReturnValue({
|
||||
height: 176,
|
||||
width: 12,
|
||||
x: 0,
|
||||
y: 0,
|
||||
top: 0,
|
||||
} as DOMRect)
|
||||
const mockHueSlideOffsetHeight = jest.spyOn(hueSlideDom, 'offsetHeight', 'get').mockReturnValue(200)
|
||||
const mockThumbDom = jest.spyOn(thumbDom, 'offsetHeight', 'get').mockReturnValue(4);
|
||||
(hueSlideWrapper.vm as ColorPickerVM).handleClick({ target: null, clientX: 0, clientY: 100 })
|
||||
const thumbDom = hueSlideWrapper.find<HTMLElement>(
|
||||
'.el-color-hue-slider__thumb'
|
||||
).element
|
||||
const mockHueSlideRect = jest
|
||||
.spyOn(hueSlideDom, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 176,
|
||||
width: 12,
|
||||
x: 0,
|
||||
y: 0,
|
||||
top: 0,
|
||||
} as DOMRect)
|
||||
const mockHueSlideOffsetHeight = jest
|
||||
.spyOn(hueSlideDom, 'offsetHeight', 'get')
|
||||
.mockReturnValue(200)
|
||||
const mockThumbDom = jest
|
||||
.spyOn(thumbDom, 'offsetHeight', 'get')
|
||||
.mockReturnValue(4)
|
||||
;(hueSlideWrapper.vm as ColorPickerVM).handleClick({
|
||||
target: null,
|
||||
clientX: 0,
|
||||
clientY: 100,
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
expect((hueSlideWrapper.vm as ColorPickerVM).thumbTop > 0).toBe(true)
|
||||
@ -183,22 +216,32 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color" show-alpha></el-color-picker>`,
|
||||
() => ({
|
||||
color: '#F00',
|
||||
}),
|
||||
})
|
||||
)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const colorPickerWrapper = wrapper.findComponent(ColorPicker)
|
||||
const alphaWrapper = colorPickerWrapper.findComponent({ ref: 'alpha' })
|
||||
const alphaDom = alphaWrapper.element as HTMLElement
|
||||
const mockAlphaDom = jest.spyOn(alphaDom, 'getBoundingClientRect').mockReturnValue({
|
||||
height: 12,
|
||||
width: 280,
|
||||
x: 0,
|
||||
y: 0,
|
||||
left: 0,
|
||||
} as DOMRect)
|
||||
const thumbDom = alphaWrapper.find<HTMLElement>('.el-color-alpha-slider__thumb').element
|
||||
const mockThumbDom = jest.spyOn(thumbDom, 'offsetWidth', 'get').mockReturnValue(4);
|
||||
(alphaWrapper.vm as ColorPickerVM).handleClick({ target: null, clientX: 50, clientY: 0 })
|
||||
const mockAlphaDom = jest
|
||||
.spyOn(alphaDom, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 12,
|
||||
width: 280,
|
||||
x: 0,
|
||||
y: 0,
|
||||
left: 0,
|
||||
} as DOMRect)
|
||||
const thumbDom = alphaWrapper.find<HTMLElement>(
|
||||
'.el-color-alpha-slider__thumb'
|
||||
).element
|
||||
const mockThumbDom = jest
|
||||
.spyOn(thumbDom, 'offsetWidth', 'get')
|
||||
.mockReturnValue(4)
|
||||
;(alphaWrapper.vm as ColorPickerVM).handleClick({
|
||||
target: null,
|
||||
clientX: 50,
|
||||
clientY: 0,
|
||||
})
|
||||
await nextTick()
|
||||
expect(colorPickerWrapper.vm.color.get('alpha') < 100).toBeTruthy()
|
||||
mockAlphaDom.mockRestore()
|
||||
@ -211,13 +254,13 @@ describe('Color-picker', () => {
|
||||
`<el-color-picker v-model="color" show-alpha color-format="hsv"></el-color-picker>`,
|
||||
() => ({
|
||||
color: 'hsv(0, 50%, 50%)',
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const colorPickerWrapper = wrapper.findComponent(ColorPicker)
|
||||
const svPanelWrapper = colorPickerWrapper.findComponent({ ref: 'svPanel' });
|
||||
(svPanelWrapper.vm as ColorPickerVM).handleDrag({ clientX: 0, clientY: 0 })
|
||||
const svPanelWrapper = colorPickerWrapper.findComponent({ ref: 'svPanel' })
|
||||
;(svPanelWrapper.vm as ColorPickerVM).handleDrag({ clientX: 0, clientY: 0 })
|
||||
wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.vm.color._saturation !== 50).toBeTruthy()
|
||||
expect(wrapper.vm.color._value !== 50).toBeTruthy()
|
||||
@ -240,21 +283,34 @@ describe('Color-picker', () => {
|
||||
'#45aa9477',
|
||||
'#892345',
|
||||
],
|
||||
}),
|
||||
})
|
||||
)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const colorPickerWrapper = wrapper.findComponent(ColorPicker)
|
||||
const predefineWrapper = colorPickerWrapper.findComponent({ ref: 'predefine' })
|
||||
const predefineWrapper = colorPickerWrapper.findComponent({
|
||||
ref: 'predefine',
|
||||
})
|
||||
const predefineDom = predefineWrapper.element as HTMLElement
|
||||
expect(predefineDom.querySelectorAll('.el-color-predefine__color-selector').length === 9).toBeTruthy()
|
||||
predefineDom.querySelector<HTMLElement>('.el-color-predefine__color-selector:nth-child(4)').click()
|
||||
expect(
|
||||
predefineDom.querySelectorAll('.el-color-predefine__color-selector')
|
||||
.length === 9
|
||||
).toBeTruthy()
|
||||
predefineDom
|
||||
.querySelector<HTMLElement>(
|
||||
'.el-color-predefine__color-selector:nth-child(4)'
|
||||
)
|
||||
.click()
|
||||
await nextTick()
|
||||
expect(colorPickerWrapper.vm.color.get('hue')).toEqual(180)
|
||||
expect(colorPickerWrapper.vm.color.get('saturation')).toEqual(65)
|
||||
expect(colorPickerWrapper.vm.color.get('value')).toEqual(20)
|
||||
expect(colorPickerWrapper.vm.color.get('alpha')).toEqual(50)
|
||||
|
||||
predefineDom.querySelector<HTMLElement>('.el-color-predefine__color-selector:nth-child(3)').click()
|
||||
predefineDom
|
||||
.querySelector<HTMLElement>(
|
||||
'.el-color-predefine__color-selector:nth-child(3)'
|
||||
)
|
||||
.click()
|
||||
await nextTick()
|
||||
expect(colorPickerWrapper.vm.color.get('hue')).toEqual(250)
|
||||
expect(colorPickerWrapper.vm.color.get('saturation')).toEqual(54)
|
||||
@ -278,30 +334,56 @@ describe('Color-picker', () => {
|
||||
'#45aa9477',
|
||||
'#892345',
|
||||
],
|
||||
}),
|
||||
})
|
||||
)
|
||||
await wrapper.find('.el-color-picker__trigger').trigger('click')
|
||||
const colorPickerWrapper = wrapper.findComponent(ColorPicker)
|
||||
const predefineWrapper = colorPickerWrapper.findComponent({ ref: 'predefine' })
|
||||
const predefineWrapper = colorPickerWrapper.findComponent({
|
||||
ref: 'predefine',
|
||||
})
|
||||
const predefineDom = predefineWrapper.element as HTMLElement
|
||||
predefineDom.querySelector<HTMLElement>('.el-color-predefine__color-selector:nth-child(4)').click()
|
||||
predefineDom
|
||||
.querySelector<HTMLElement>(
|
||||
'.el-color-predefine__color-selector:nth-child(4)'
|
||||
)
|
||||
.click()
|
||||
await nextTick()
|
||||
expect(predefineWrapper.find('.el-color-predefine__color-selector:nth-child(4)').classes()).toContain('selected')
|
||||
expect(
|
||||
predefineWrapper
|
||||
.find('.el-color-predefine__color-selector:nth-child(4)')
|
||||
.classes()
|
||||
).toContain('selected')
|
||||
const hueSlideWrapper = colorPickerWrapper.findComponent({ ref: 'hue' })
|
||||
const hueSlideDom = hueSlideWrapper.element
|
||||
const thumbDom = hueSlideWrapper.find<HTMLElement>('.el-color-hue-slider__thumb').element
|
||||
const mockHueSlideRect = jest.spyOn(hueSlideDom,'getBoundingClientRect').mockReturnValue({
|
||||
height: 176,
|
||||
width: 12,
|
||||
x: 0,
|
||||
y: 0,
|
||||
top: 0,
|
||||
} as DOMRect)
|
||||
const mockHueSlideOffsetHeight = jest.spyOn(hueSlideDom as HTMLElement, 'offsetHeight', 'get').mockReturnValue(200)
|
||||
const mockThumbDom = jest.spyOn(thumbDom, 'offsetHeight', 'get').mockReturnValue(4);
|
||||
(hueSlideWrapper.vm as ColorPickerVM).handleClick({ target: null, clientX: 0, clientY: 1000 })
|
||||
const thumbDom = hueSlideWrapper.find<HTMLElement>(
|
||||
'.el-color-hue-slider__thumb'
|
||||
).element
|
||||
const mockHueSlideRect = jest
|
||||
.spyOn(hueSlideDom, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
height: 176,
|
||||
width: 12,
|
||||
x: 0,
|
||||
y: 0,
|
||||
top: 0,
|
||||
} as DOMRect)
|
||||
const mockHueSlideOffsetHeight = jest
|
||||
.spyOn(hueSlideDom as HTMLElement, 'offsetHeight', 'get')
|
||||
.mockReturnValue(200)
|
||||
const mockThumbDom = jest
|
||||
.spyOn(thumbDom, 'offsetHeight', 'get')
|
||||
.mockReturnValue(4)
|
||||
;(hueSlideWrapper.vm as ColorPickerVM).handleClick({
|
||||
target: null,
|
||||
clientX: 0,
|
||||
clientY: 1000,
|
||||
})
|
||||
await nextTick()
|
||||
expect(predefineWrapper.find('.el-color-predefine__color-selector:nth-child(4)').classes()).not.toContain('selected')
|
||||
expect(
|
||||
predefineWrapper
|
||||
.find('.el-color-predefine__color-selector:nth-child(4)')
|
||||
.classes()
|
||||
).not.toContain('selected')
|
||||
mockHueSlideRect.mockRestore()
|
||||
mockThumbDom.mockRestore()
|
||||
mockHueSlideOffsetHeight.mockRestore()
|
||||
|
@ -1,37 +1,37 @@
|
||||
import { hasOwn } from '@vue/shared'
|
||||
|
||||
const hsv2hsl = function(hue: number, sat: number, val: number) {
|
||||
const hsv2hsl = function (hue: number, sat: number, val: number) {
|
||||
return [
|
||||
hue,
|
||||
(sat * val / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue)) || 0,
|
||||
(sat * val) / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue) || 0,
|
||||
hue / 2,
|
||||
]
|
||||
}
|
||||
|
||||
// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
||||
// <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
||||
const isOnePointZero = function(n: unknown) {
|
||||
const isOnePointZero = function (n: unknown) {
|
||||
return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1
|
||||
}
|
||||
|
||||
const isPercentage = function(n: unknown) {
|
||||
const isPercentage = function (n: unknown) {
|
||||
return typeof n === 'string' && n.indexOf('%') !== -1
|
||||
}
|
||||
|
||||
// Take input from [0, n] and return it as [0, 1]
|
||||
const bound01 = function(value: number | string, max: number | string) {
|
||||
const bound01 = function (value: number | string, max: number | string) {
|
||||
if (isOnePointZero(value)) value = '100%'
|
||||
|
||||
const processPercent = isPercentage(value)
|
||||
value = Math.min((max as number), Math.max(0, parseFloat(value + '')))
|
||||
value = Math.min(max as number, Math.max(0, parseFloat(value + '')))
|
||||
|
||||
// Automatically convert percentage into number
|
||||
if (processPercent) {
|
||||
value = parseInt((value * (max as number)) + '', 10) / 100
|
||||
value = parseInt(value * (max as number) + '', 10) / 100
|
||||
}
|
||||
|
||||
// Handle floating point rounding errors
|
||||
if ((Math.abs(value - (max as number)) < 0.000001)) {
|
||||
if (Math.abs(value - (max as number)) < 0.000001) {
|
||||
return 1
|
||||
}
|
||||
|
||||
@ -41,8 +41,8 @@ const bound01 = function(value: number | string, max: number | string) {
|
||||
|
||||
const INT_HEX_MAP = { 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F' }
|
||||
|
||||
const toHex = function({ r, g, b }) {
|
||||
const hexOne = function(value: number) {
|
||||
const toHex = function ({ r, g, b }) {
|
||||
const hexOne = function (value: number) {
|
||||
value = Math.min(Math.round(value), 255)
|
||||
const high = Math.floor(value / 16)
|
||||
const low = value % 16
|
||||
@ -56,15 +56,18 @@ const toHex = function({ r, g, b }) {
|
||||
|
||||
const HEX_INT_MAP = { A: 10, B: 11, C: 12, D: 13, E: 14, F: 15 }
|
||||
|
||||
const parseHexChannel = function(hex: string) {
|
||||
const parseHexChannel = function (hex: string) {
|
||||
if (hex.length === 2) {
|
||||
return (HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 + (HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1])
|
||||
return (
|
||||
(HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 +
|
||||
(HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1])
|
||||
)
|
||||
}
|
||||
|
||||
return HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]
|
||||
}
|
||||
|
||||
const hsl2hsv = function(hue: number, sat: number, light: number) {
|
||||
const hsl2hsv = function (hue: number, sat: number, light: number) {
|
||||
sat = sat / 100
|
||||
light = light / 100
|
||||
let smin = sat
|
||||
@ -73,10 +76,11 @@ const hsl2hsv = function(hue: number, sat: number, light: number) {
|
||||
// let v
|
||||
|
||||
light *= 2
|
||||
sat *= (light <= 1) ? light : 2 - light
|
||||
sat *= light <= 1 ? light : 2 - light
|
||||
smin *= lmin <= 1 ? lmin : 2 - lmin
|
||||
const v = (light + sat) / 2
|
||||
const sv = light === 0 ? (2 * smin) / (lmin + smin) : (2 * sat) / (light + sat)
|
||||
const sv =
|
||||
light === 0 ? (2 * smin) / (lmin + smin) : (2 * sat) / (light + sat)
|
||||
|
||||
return {
|
||||
h: hue,
|
||||
@ -89,7 +93,7 @@ const hsl2hsv = function(hue: number, sat: number, light: number) {
|
||||
// Converts an RGB color value to HSV
|
||||
// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
|
||||
// *Returns:* { h, s, v } in [0,1]
|
||||
const rgb2hsv = function(r, g, b) {
|
||||
const rgb2hsv = function (r, g, b) {
|
||||
r = bound01(r, 255)
|
||||
g = bound01(g, 255)
|
||||
b = bound01(b, 255)
|
||||
@ -129,7 +133,7 @@ const rgb2hsv = function(r, g, b) {
|
||||
// Converts an HSV color value to RGB.
|
||||
// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
|
||||
// *Returns:* { r, g, b } in the set [0, 255]
|
||||
const hsv2rgb = function(h, s, v) {
|
||||
const hsv2rgb = function (h, s, v) {
|
||||
h = bound01(h, 360) * 6
|
||||
s = bound01(s, 100)
|
||||
v = bound01(v, 100)
|
||||
@ -167,8 +171,7 @@ export default class Color {
|
||||
public value = ''
|
||||
public selected?: boolean
|
||||
constructor(options?: Options) {
|
||||
|
||||
options = options || {} as Options
|
||||
options = options || ({} as Options)
|
||||
|
||||
for (const option in options) {
|
||||
if (hasOwn(options, option)) {
|
||||
@ -179,7 +182,7 @@ export default class Color {
|
||||
this.doOnChange()
|
||||
}
|
||||
|
||||
set(prop: { [key: string]: any; } | any, value?: number) {
|
||||
set(prop: { [key: string]: any } | any, value?: number) {
|
||||
if (arguments.length === 1 && typeof prop === 'object') {
|
||||
for (const p in prop) {
|
||||
if (hasOwn(prop, p)) {
|
||||
@ -221,8 +224,11 @@ export default class Color {
|
||||
}
|
||||
|
||||
if (value.indexOf('hsl') !== -1) {
|
||||
const parts = value.replace(/hsla|hsl|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter(val => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10))
|
||||
const parts = value
|
||||
.replace(/hsla|hsl|\(|\)/gm, '')
|
||||
.split(/\s|,/g)
|
||||
.filter((val) => val !== '')
|
||||
.map((val, index) => (index > 2 ? parseFloat(val) : parseInt(val, 10)))
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100)
|
||||
@ -234,8 +240,11 @@ export default class Color {
|
||||
fromHSV(h, s, v)
|
||||
}
|
||||
} else if (value.indexOf('hsv') !== -1) {
|
||||
const parts = value.replace(/hsva|hsv|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter(val => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10))
|
||||
const parts = value
|
||||
.replace(/hsva|hsv|\(|\)/gm, '')
|
||||
.split(/\s|,/g)
|
||||
.filter((val) => val !== '')
|
||||
.map((val, index) => (index > 2 ? parseFloat(val) : parseInt(val, 10)))
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100)
|
||||
@ -246,8 +255,11 @@ export default class Color {
|
||||
fromHSV(parts[0], parts[1], parts[2])
|
||||
}
|
||||
} else if (value.indexOf('rgb') !== -1) {
|
||||
const parts = value.replace(/rgba|rgb|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter(val => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10))
|
||||
const parts = value
|
||||
.replace(/rgba|rgb|\(|\)/gm, '')
|
||||
.split(/\s|,/g)
|
||||
.filter((val) => val !== '')
|
||||
.map((val, index) => (index > 2 ? parseFloat(val) : parseInt(val, 10)))
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100)
|
||||
@ -260,7 +272,8 @@ export default class Color {
|
||||
}
|
||||
} else if (value.indexOf('#') !== -1) {
|
||||
const hex = value.replace('#', '').trim()
|
||||
if (!/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$|^[0-9a-fA-F]{8}$/.test(hex)) return
|
||||
if (!/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$|^[0-9a-fA-F]{8}$/.test(hex))
|
||||
return
|
||||
let r, g, b
|
||||
|
||||
if (hex.length === 3) {
|
||||
@ -274,7 +287,9 @@ export default class Color {
|
||||
}
|
||||
|
||||
if (hex.length === 8) {
|
||||
this._alpha = Math.floor(parseHexChannel(hex.substring(6)) / 255 * 100)
|
||||
this._alpha = Math.floor(
|
||||
(parseHexChannel(hex.substring(6)) / 255) * 100
|
||||
)
|
||||
} else if (hex.length === 3 || hex.length === 6) {
|
||||
this._alpha = 100
|
||||
}
|
||||
@ -285,10 +300,12 @@ export default class Color {
|
||||
}
|
||||
|
||||
compare(color) {
|
||||
return Math.abs(color._hue - this._hue) < 2 &&
|
||||
return (
|
||||
Math.abs(color._hue - this._hue) < 2 &&
|
||||
Math.abs(color._saturation - this._saturation) < 1 &&
|
||||
Math.abs(color._value - this._value) < 1 &&
|
||||
Math.abs(color._alpha - this._alpha) < 1
|
||||
)
|
||||
}
|
||||
|
||||
doOnChange() {
|
||||
@ -298,30 +315,38 @@ export default class Color {
|
||||
switch (format) {
|
||||
case 'hsl': {
|
||||
const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100)
|
||||
this.value = `hsla(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%, ${ _alpha / 100})`
|
||||
this.value = `hsla(${_hue}, ${Math.round(
|
||||
hsl[1] * 100
|
||||
)}%, ${Math.round(hsl[2] * 100)}%, ${_alpha / 100})`
|
||||
break
|
||||
}
|
||||
case 'hsv': {
|
||||
this.value = `hsva(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%, ${ _alpha / 100})`
|
||||
this.value = `hsva(${_hue}, ${Math.round(_saturation)}%, ${Math.round(
|
||||
_value
|
||||
)}%, ${_alpha / 100})`
|
||||
break
|
||||
}
|
||||
default: {
|
||||
const { r, g, b } = hsv2rgb(_hue, _saturation, _value)
|
||||
this.value = `rgba(${r}, ${g}, ${b}, ${ _alpha / 100 })`
|
||||
this.value = `rgba(${r}, ${g}, ${b}, ${_alpha / 100})`
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (format) {
|
||||
case 'hsl': {
|
||||
const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100)
|
||||
this.value = `hsl(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%)`
|
||||
this.value = `hsl(${_hue}, ${Math.round(hsl[1] * 100)}%, ${Math.round(
|
||||
hsl[2] * 100
|
||||
)}%)`
|
||||
break
|
||||
}
|
||||
case 'hsv': {
|
||||
this.value = `hsv(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%)`
|
||||
this.value = `hsv(${_hue}, ${Math.round(_saturation)}%, ${Math.round(
|
||||
_value
|
||||
)}%)`
|
||||
break
|
||||
}
|
||||
case 'rgb':{
|
||||
case 'rgb': {
|
||||
const { r, g, b } = hsv2rgb(_hue, _saturation, _value)
|
||||
this.value = `rgb(${r}, ${g}, ${b})`
|
||||
break
|
||||
|
@ -4,32 +4,36 @@
|
||||
ref="bar"
|
||||
class="el-color-alpha-slider__bar"
|
||||
:style="{
|
||||
background
|
||||
background,
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
</div>
|
||||
></div>
|
||||
<div
|
||||
ref="thumb"
|
||||
class="el-color-alpha-slider__thumb"
|
||||
:style="{
|
||||
left: thumbLeft + 'px',
|
||||
top: thumbTop + 'px'
|
||||
top: thumbTop + 'px',
|
||||
}"
|
||||
>
|
||||
</div>
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch, ref, onMounted, getCurrentInstance, shallowRef } from 'vue'
|
||||
import {
|
||||
defineComponent,
|
||||
watch,
|
||||
ref,
|
||||
onMounted,
|
||||
getCurrentInstance,
|
||||
shallowRef,
|
||||
} from 'vue'
|
||||
import draggable from '../draggable'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
import type { Nullable } from '@element-plus/utils/types'
|
||||
import type Color from '../color'
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElColorAlphaSlider',
|
||||
props: {
|
||||
@ -53,12 +57,18 @@ export default defineComponent({
|
||||
const thumbTop = ref(0)
|
||||
const background = ref<Nullable<string>>(null)
|
||||
|
||||
watch(() => props.color.get('alpha'), () => {
|
||||
update()
|
||||
})
|
||||
watch(() => props.color.value, () => {
|
||||
update()
|
||||
})
|
||||
watch(
|
||||
() => props.color.get('alpha'),
|
||||
() => {
|
||||
update()
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.color.value,
|
||||
() => {
|
||||
update()
|
||||
}
|
||||
)
|
||||
|
||||
//methods
|
||||
function getThumbLeft() {
|
||||
@ -67,7 +77,9 @@ export default defineComponent({
|
||||
const alpha = props.color.get('alpha')
|
||||
|
||||
if (!el) return 0
|
||||
return Math.round(alpha * (el.offsetWidth - thumb.value.offsetWidth / 2) / 100)
|
||||
return Math.round(
|
||||
(alpha * (el.offsetWidth - thumb.value.offsetWidth / 2)) / 100
|
||||
)
|
||||
}
|
||||
|
||||
function getThumbTop() {
|
||||
@ -76,7 +88,9 @@ export default defineComponent({
|
||||
const alpha = props.color.get('alpha')
|
||||
|
||||
if (!el) return 0
|
||||
return Math.round(alpha * (el.offsetHeight - thumb.value.offsetHeight / 2) / 100)
|
||||
return Math.round(
|
||||
(alpha * (el.offsetHeight - thumb.value.offsetHeight / 2)) / 100
|
||||
)
|
||||
}
|
||||
|
||||
function getBackground() {
|
||||
@ -104,13 +118,27 @@ export default defineComponent({
|
||||
left = Math.max(thumb.value.offsetWidth / 2, left)
|
||||
left = Math.min(left, rect.width - thumb.value.offsetWidth / 2)
|
||||
|
||||
props.color.set('alpha', Math.round((left - thumb.value.offsetWidth / 2) / (rect.width - thumb.value.offsetWidth) * 100))
|
||||
props.color.set(
|
||||
'alpha',
|
||||
Math.round(
|
||||
((left - thumb.value.offsetWidth / 2) /
|
||||
(rect.width - thumb.value.offsetWidth)) *
|
||||
100
|
||||
)
|
||||
)
|
||||
} else {
|
||||
let top = event.clientY - rect.top
|
||||
top = Math.max(thumb.value.offsetHeight / 2, top)
|
||||
top = Math.min(top, rect.height - thumb.value.offsetHeight / 2)
|
||||
|
||||
props.color.set('alpha', Math.round((top - thumb.value.offsetHeight / 2) / (rect.height - thumb.value.offsetHeight) * 100))
|
||||
props.color.set(
|
||||
'alpha',
|
||||
Math.round(
|
||||
((top - thumb.value.offsetHeight / 2) /
|
||||
(rect.height - thumb.value.offsetHeight)) *
|
||||
100
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,10 +151,10 @@ export default defineComponent({
|
||||
// mounded
|
||||
onMounted(() => {
|
||||
const dragConfig = {
|
||||
drag: event => {
|
||||
drag: (event) => {
|
||||
handleDrag(event)
|
||||
},
|
||||
end: event => {
|
||||
end: (event) => {
|
||||
handleDrag(event)
|
||||
},
|
||||
}
|
||||
|
@ -6,14 +6,21 @@
|
||||
class="el-color-hue-slider__thumb"
|
||||
:style="{
|
||||
left: thumbLeft + 'px',
|
||||
top: thumbTop + 'px'
|
||||
top: thumbTop + 'px',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, watch, onMounted, getCurrentInstance, defineComponent } from 'vue'
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
onMounted,
|
||||
getCurrentInstance,
|
||||
defineComponent,
|
||||
} from 'vue'
|
||||
import draggable from '../draggable'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
@ -43,9 +50,12 @@ export default defineComponent({
|
||||
return props.color.get('hue')
|
||||
})
|
||||
// watch
|
||||
watch(() => hueValue.value, () => {
|
||||
update()
|
||||
})
|
||||
watch(
|
||||
() => hueValue.value,
|
||||
() => {
|
||||
update()
|
||||
}
|
||||
)
|
||||
// methods
|
||||
function handleClick(event: Event) {
|
||||
const target = event.target
|
||||
@ -64,13 +74,21 @@ export default defineComponent({
|
||||
left = Math.min(left, rect.width - thumb.value.offsetWidth / 2)
|
||||
left = Math.max(thumb.value.offsetWidth / 2, left)
|
||||
|
||||
hue = Math.round((left - thumb.value.offsetWidth / 2) / (rect.width - thumb.value.offsetWidth) * 360)
|
||||
hue = Math.round(
|
||||
((left - thumb.value.offsetWidth / 2) /
|
||||
(rect.width - thumb.value.offsetWidth)) *
|
||||
360
|
||||
)
|
||||
} else {
|
||||
let top = event.clientY - rect.top
|
||||
|
||||
top = Math.min(top, rect.height - thumb.value.offsetHeight / 2)
|
||||
top = Math.max(thumb.value.offsetHeight / 2, top)
|
||||
hue = Math.round((top - thumb.value.offsetHeight / 2) / (rect.height - thumb.value.offsetHeight) * 360)
|
||||
hue = Math.round(
|
||||
((top - thumb.value.offsetHeight / 2) /
|
||||
(rect.height - thumb.value.offsetHeight)) *
|
||||
360
|
||||
)
|
||||
}
|
||||
props.color.set('hue', hue)
|
||||
}
|
||||
@ -81,7 +99,9 @@ export default defineComponent({
|
||||
const hue = props.color.get('hue')
|
||||
|
||||
if (!el) return 0
|
||||
return Math.round(hue * (el.offsetWidth - thumb.value.offsetWidth / 2) / 360)
|
||||
return Math.round(
|
||||
(hue * (el.offsetWidth - thumb.value.offsetWidth / 2)) / 360
|
||||
)
|
||||
}
|
||||
|
||||
function getThumbTop() {
|
||||
@ -90,7 +110,9 @@ export default defineComponent({
|
||||
const hue = props.color.get('hue')
|
||||
|
||||
if (!el) return 0
|
||||
return Math.round(hue * (el.offsetHeight - thumb.value.offsetHeight / 2) / 360)
|
||||
return Math.round(
|
||||
(hue * (el.offsetHeight - thumb.value.offsetHeight / 2)) / 360
|
||||
)
|
||||
}
|
||||
function update() {
|
||||
thumbLeft.value = getThumbLeft()
|
||||
@ -99,10 +121,10 @@ export default defineComponent({
|
||||
// mounded
|
||||
onMounted(() => {
|
||||
const dragConfig = {
|
||||
drag: event => {
|
||||
drag: (event) => {
|
||||
handleDrag(event)
|
||||
},
|
||||
end: event => {
|
||||
end: (event) => {
|
||||
handleDrag(event)
|
||||
},
|
||||
}
|
||||
|
@ -5,11 +5,10 @@
|
||||
v-for="(item, index) in rgbaColors"
|
||||
:key="colors[index]"
|
||||
class="el-color-predefine__color-selector"
|
||||
:class="{selected: item.selected, 'is-alpha': item._alpha < 100}"
|
||||
:class="{ selected: item.selected, 'is-alpha': item._alpha < 100 }"
|
||||
@click="handleSelect(index)"
|
||||
>
|
||||
<div :style="{backgroundColor: item.value}">
|
||||
</div>
|
||||
<div :style="{ backgroundColor: item.value }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -36,14 +35,17 @@ export default defineComponent({
|
||||
const rgbaColors = ref(parseColors(props.colors, props.color))
|
||||
|
||||
//watch
|
||||
watch(() => currentColor.value, val => {
|
||||
const color = new Color()
|
||||
color.fromString(val)
|
||||
watch(
|
||||
() => currentColor.value,
|
||||
(val) => {
|
||||
const color = new Color()
|
||||
color.fromString(val)
|
||||
|
||||
rgbaColors.value.forEach(item => {
|
||||
item.selected = color.compare(item)
|
||||
})
|
||||
})
|
||||
rgbaColors.value.forEach((item) => {
|
||||
item.selected = color.compare(item)
|
||||
})
|
||||
}
|
||||
)
|
||||
watchEffect(() => {
|
||||
rgbaColors.value = parseColors(props.colors, props.color)
|
||||
})
|
||||
@ -52,7 +54,7 @@ export default defineComponent({
|
||||
props.color.fromString(props.colors[index])
|
||||
}
|
||||
function parseColors(colors, color) {
|
||||
return colors.map(value => {
|
||||
return colors.map((value) => {
|
||||
const c = new Color()
|
||||
c.enableAlpha = true
|
||||
c.format = 'rgba'
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div
|
||||
class="el-color-svpanel"
|
||||
:style="{
|
||||
backgroundColor: background
|
||||
backgroundColor: background,
|
||||
}"
|
||||
>
|
||||
<div class="el-color-svpanel__white"></div>
|
||||
@ -11,7 +11,7 @@
|
||||
class="el-color-svpanel__cursor"
|
||||
:style="{
|
||||
top: cursorTop + 'px',
|
||||
left: cursorLeft + 'px'
|
||||
left: cursorLeft + 'px',
|
||||
}"
|
||||
>
|
||||
<div></div>
|
||||
@ -20,7 +20,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch, getCurrentInstance, onMounted } from 'vue'
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
getCurrentInstance,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
import draggable from '../draggable'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
@ -53,10 +60,10 @@ export default defineComponent({
|
||||
const value = props.color.get('value')
|
||||
|
||||
const el = instance.vnode.el
|
||||
let { clientWidth: width, clientHeight: height } = el
|
||||
const { clientWidth: width, clientHeight: height } = el
|
||||
|
||||
cursorLeft.value = saturation * width / 100
|
||||
cursorTop.value = (100 - value) * height / 100
|
||||
cursorLeft.value = (saturation * width) / 100
|
||||
cursorTop.value = ((100 - value) * height) / 100
|
||||
|
||||
background.value = 'hsl(' + props.color.get('hue') + ', 100%, 50%)'
|
||||
}
|
||||
@ -76,21 +83,24 @@ export default defineComponent({
|
||||
cursorLeft.value = left
|
||||
cursorTop.value = top
|
||||
props.color.set({
|
||||
saturation: left / rect.width * 100,
|
||||
value: 100 - top / rect.height * 100,
|
||||
saturation: (left / rect.width) * 100,
|
||||
value: 100 - (top / rect.height) * 100,
|
||||
})
|
||||
}
|
||||
// watch
|
||||
watch(() => colorValue.value, () => {
|
||||
update()
|
||||
})
|
||||
watch(
|
||||
() => colorValue.value,
|
||||
() => {
|
||||
update()
|
||||
}
|
||||
)
|
||||
// mounted
|
||||
onMounted(() => {
|
||||
draggable(instance.vnode.el as HTMLElement, {
|
||||
drag: event => {
|
||||
drag: (event) => {
|
||||
handleDrag(event)
|
||||
},
|
||||
end: event => {
|
||||
end: (event) => {
|
||||
handleDrag(event)
|
||||
},
|
||||
})
|
||||
|
@ -16,12 +16,7 @@
|
||||
<template #default>
|
||||
<div v-click-outside="hide">
|
||||
<div class="el-color-dropdown__main-wrapper">
|
||||
<hue-slider
|
||||
ref="hue"
|
||||
class="hue-slider"
|
||||
:color="color"
|
||||
vertical
|
||||
/>
|
||||
<hue-slider ref="hue" class="hue-slider" :color="color" vertical />
|
||||
<sv-panel ref="svPanel" :color="color" />
|
||||
</div>
|
||||
<alpha-slider v-if="showAlpha" ref="alpha" :color="color" />
|
||||
@ -65,21 +60,30 @@
|
||||
:class="[
|
||||
'el-color-picker',
|
||||
colorDisabled ? 'is-disabled' : '',
|
||||
colorSize ? `el-color-picker--${ colorSize }` : ''
|
||||
colorSize ? `el-color-picker--${colorSize}` : '',
|
||||
]"
|
||||
>
|
||||
<div v-if="colorDisabled" class="el-color-picker__mask"></div>
|
||||
<div class="el-color-picker__trigger" @click="handleTrigger">
|
||||
<span class="el-color-picker__color" :class="{ 'is-alpha': showAlpha }">
|
||||
<span
|
||||
class="el-color-picker__color"
|
||||
:class="{ 'is-alpha': showAlpha }"
|
||||
>
|
||||
<span
|
||||
class="el-color-picker__color-inner"
|
||||
:style="{
|
||||
backgroundColor: displayedColor
|
||||
backgroundColor: displayedColor,
|
||||
}"
|
||||
></span>
|
||||
<span v-if="!modelValue && !showPanelColor" class="el-color-picker__empty el-icon-close"></span>
|
||||
<span
|
||||
v-if="!modelValue && !showPanelColor"
|
||||
class="el-color-picker__empty el-icon-close"
|
||||
></span>
|
||||
</span>
|
||||
<span v-show="modelValue || showPanelColor" class="el-color-picker__icon el-icon-arrow-down"></span>
|
||||
<span
|
||||
v-show="modelValue || showPanelColor"
|
||||
class="el-color-picker__icon el-icon-arrow-down"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -87,7 +91,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, nextTick, onMounted, provide, reactive, ref, watch } from 'vue'
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
inject,
|
||||
nextTick,
|
||||
onMounted,
|
||||
provide,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import debounce from 'lodash/debounce'
|
||||
import ElButton from '@element-plus/components/button'
|
||||
import { ClickOutside } from '@element-plus/directives'
|
||||
@ -148,10 +162,12 @@ export default defineComponent({
|
||||
const alpha = ref(null)
|
||||
const popper = ref(null)
|
||||
// data
|
||||
const color = reactive(new Color({
|
||||
enableAlpha: props.showAlpha,
|
||||
format: props.colorFormat,
|
||||
}))
|
||||
const color = reactive(
|
||||
new Color({
|
||||
enableAlpha: props.showAlpha,
|
||||
format: props.colorFormat,
|
||||
})
|
||||
)
|
||||
const showPicker = ref(false)
|
||||
const showPanelColor = ref(false)
|
||||
const customInput = ref('')
|
||||
@ -173,24 +189,33 @@ export default defineComponent({
|
||||
return !props.modelValue && !showPanelColor.value ? '' : color.value
|
||||
})
|
||||
// watch
|
||||
watch(() => props.modelValue, newVal => {
|
||||
if (!newVal) {
|
||||
showPanelColor.value = false
|
||||
} else if (newVal && newVal !== color.value) {
|
||||
color.fromString(newVal)
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
showPanelColor.value = false
|
||||
} else if (newVal && newVal !== color.value) {
|
||||
color.fromString(newVal)
|
||||
}
|
||||
}
|
||||
})
|
||||
watch(() => currentColor.value, val => {
|
||||
customInput.value = val
|
||||
emit('active-change', val)
|
||||
// showPanelColor.value = true
|
||||
})
|
||||
)
|
||||
watch(
|
||||
() => currentColor.value,
|
||||
(val) => {
|
||||
customInput.value = val
|
||||
emit('active-change', val)
|
||||
// showPanelColor.value = true
|
||||
}
|
||||
)
|
||||
|
||||
watch(() => color.value, () => {
|
||||
if (!props.modelValue && !showPanelColor.value) {
|
||||
showPanelColor.value = true
|
||||
watch(
|
||||
() => color.value,
|
||||
() => {
|
||||
if (!props.modelValue && !showPanelColor.value) {
|
||||
showPanelColor.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// methods
|
||||
function displayedRgb(color, showAlpha) {
|
||||
@ -200,8 +225,8 @@ export default defineComponent({
|
||||
|
||||
const { r, g, b } = color.toRgb()
|
||||
return showAlpha
|
||||
? `rgba(${ r }, ${ g }, ${ b }, ${ color.get('alpha') / 100 })`
|
||||
: `rgb(${ r }, ${ g }, ${ b })`
|
||||
? `rgba(${r}, ${g}, ${b}, ${color.get('alpha') / 100})`
|
||||
: `rgb(${r}, ${g}, ${b})`
|
||||
}
|
||||
|
||||
function setShowPicker(value) {
|
||||
@ -269,13 +294,16 @@ export default defineComponent({
|
||||
customInput.value = currentColor.value
|
||||
}
|
||||
})
|
||||
watch(() => showPicker.value, () => {
|
||||
nextTick(() => {
|
||||
hue.value?.update()
|
||||
svPanel.value?.update()
|
||||
alpha.value?.update()
|
||||
})
|
||||
})
|
||||
watch(
|
||||
() => showPicker.value,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
hue.value?.update()
|
||||
svPanel.value?.update()
|
||||
alpha.value?.update()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
provide<IUseOptions>(OPTIONS_KEY, {
|
||||
currentColor,
|
||||
|
@ -62,28 +62,28 @@ describe('config-provider', () => {
|
||||
|
||||
it('should provide locale properly', async () => {
|
||||
expect(wrapper.find('.current-locale').text()).toBe(
|
||||
English.el.popconfirm.confirmButtonText,
|
||||
English.el.popconfirm.confirmButtonText
|
||||
)
|
||||
expect(wrapper.find('.opposite-locale').text()).toBe(
|
||||
Chinese.el.popconfirm.confirmButtonText,
|
||||
Chinese.el.popconfirm.confirmButtonText
|
||||
)
|
||||
})
|
||||
|
||||
it('should reactively update the text on page', async () => {
|
||||
expect(wrapper.find('.current-locale').text()).toBe(
|
||||
English.el.popconfirm.confirmButtonText,
|
||||
English.el.popconfirm.confirmButtonText
|
||||
)
|
||||
expect(wrapper.find('.opposite-locale').text()).toBe(
|
||||
Chinese.el.popconfirm.confirmButtonText,
|
||||
Chinese.el.popconfirm.confirmButtonText
|
||||
)
|
||||
|
||||
await wrapper.find('.to-zh').trigger('click')
|
||||
|
||||
expect(wrapper.find('.current-locale').text()).toBe(
|
||||
Chinese.el.popconfirm.confirmButtonText,
|
||||
Chinese.el.popconfirm.confirmButtonText
|
||||
)
|
||||
expect(wrapper.find('.opposite-locale').text()).toBe(
|
||||
English.el.popconfirm.confirmButtonText,
|
||||
English.el.popconfirm.confirmButtonText
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -10,10 +10,8 @@ export const ConfigProvider = defineComponent({
|
||||
},
|
||||
|
||||
setup(_, { slots }) {
|
||||
|
||||
useLocale()
|
||||
|
||||
return () => slots.default()
|
||||
},
|
||||
})
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user