diff --git a/.antd-tools.config.js b/.antd-tools.config.js index 7118b5000..36b9b7598 100644 --- a/.antd-tools.config.js +++ b/.antd-tools.config.js @@ -1,195 +1,36 @@ const fs = require('fs'); const path = require('path'); -const defaultVars = require('./scripts/default-vars'); -const darkVars = require('./scripts/dark-vars'); -const compactVars = require('./scripts/compact-vars'); -function generateThemeFileContent(theme) { - return `const { ${theme}ThemeSingle } = require('./theme');\nconst defaultTheme = require('./default-theme');\n -module.exports = { - ...defaultTheme, - ...${theme}ThemeSingle -}`; -} +const restCssPath = path.join(process.cwd(), 'components', 'style', 'reset.css'); +const tokenStatisticPath = path.join(process.cwd(), 'components', 'version', 'token.json'); +const tokenMetaPath = path.join(process.cwd(), 'components', 'version', 'token-meta.json'); -// We need compile additional content for antd user function finalizeCompile() { - if (fs.existsSync(path.join(__dirname, './lib'))) { - // Build a entry less file to dist/antd.less - const componentsPath = path.join(process.cwd(), 'components'); - let componentsLessContent = ''; - // Build components in one file: lib/style/components.less - fs.readdir(componentsPath, (err, files) => { - files.forEach(file => { - if (fs.existsSync(path.join(componentsPath, file, 'style', 'index.less'))) { - componentsLessContent += `@import "../${path.posix.join( - file, - 'style', - 'index-pure.less', - )}";\n`; - } - }); - fs.writeFileSync( - path.join(process.cwd(), 'lib', 'style', 'components.less'), - componentsLessContent, - ); - }); + if (fs.existsSync(path.join(__dirname, './es'))) { + fs.copyFileSync(restCssPath, path.join(process.cwd(), 'es', 'style', 'reset.css')); + fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'es', 'version', 'token.json')); + fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'es', 'version', 'token-meta.json')); } -} -function buildThemeFile(theme, vars) { - // Build less entry file: dist/antd.${theme}.less - if (theme !== 'default') { - fs.writeFileSync( - path.join(process.cwd(), 'dist', `antd.${theme}.less`), - `@import "../lib/style/${theme}.less";\n@import "../lib/style/components.less";`, - ); - // eslint-disable-next-line no-console - console.log(`Built a entry less file to dist/antd.${theme}.less`); - } else { - fs.writeFileSync( - path.join(process.cwd(), 'dist', `default-theme.js`), - `module.exports = ${JSON.stringify(vars, null, 2)};\n`, - ); - return; + if (fs.existsSync(path.join(__dirname, './lib'))) { + fs.copyFileSync(restCssPath, path.join(process.cwd(), 'lib', 'style', 'reset.css')); + fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'lib', 'version', 'token.json')); + fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'lib', 'version', 'token-meta.json')); } - - // Build ${theme}.js: dist/${theme}-theme.js, for less-loader - - fs.writeFileSync( - path.join(process.cwd(), 'dist', `theme.js`), - `const ${theme}ThemeSingle = ${JSON.stringify(vars, null, 2)};\n`, - { - flag: 'a', - }, - ); - - fs.writeFileSync( - path.join(process.cwd(), 'dist', `${theme}-theme.js`), - generateThemeFileContent(theme), - ); - - // eslint-disable-next-line no-console - console.log(`Built a ${theme} theme js file to dist/${theme}-theme.js`); } function finalizeDist() { if (fs.existsSync(path.join(__dirname, './dist'))) { - // Build less entry file: dist/antd.less - fs.writeFileSync( - path.join(process.cwd(), 'dist', 'antd.less'), - '@import "../lib/style/default.less";\n@import "../lib/style/components.less";', - ); - // eslint-disable-next-line no-console - fs.writeFileSync( - path.join(process.cwd(), 'dist', 'theme.js'), - `const defaultTheme = require('./default-theme.js');\n`, - ); - // eslint-disable-next-line no-console - console.log('Built a entry less file to dist/antd.less'); - buildThemeFile('default', defaultVars); - buildThemeFile('dark', darkVars); - buildThemeFile('compact', compactVars); - buildThemeFile('variable', {}); - fs.writeFileSync( - path.join(process.cwd(), 'dist', `theme.js`), - ` -function getThemeVariables(options = {}) { - let themeVar = { - 'hack': \`true;@import "\${require.resolve('ant-design-vue/lib/style/color/colorPalette.less')}";\`, - ...defaultTheme - }; - if(options.dark) { - themeVar = { - ...themeVar, - ...darkThemeSingle - } - } - if(options.compact){ - themeVar = { - ...themeVar, - ...compactThemeSingle - } + fs.copyFileSync(restCssPath, path.join(process.cwd(), 'dist', 'reset.css')); } - return themeVar; -} - -module.exports = { - darkThemeSingle, - compactThemeSingle, - getThemeVariables -}`, - { - flag: 'a', - }, - ); - } -} - -function isComponentStyleEntry(file) { - return file.path.match(/style(\/|\\)index\.tsx/); -} - -function needTransformStyle(content) { - return content.includes('../../style/index.less') || content.includes('./index.less'); } module.exports = { compile: { - includeLessFile: [/(\/|\\)components(\/|\\)style(\/|\\)default.less$/], - transformTSFile(file) { - if (isComponentStyleEntry(file)) { - let content = file.contents.toString(); - - if (needTransformStyle(content)) { - const cloneFile = file.clone(); - - // Origin - content = content.replace('../../style/index.less', '../../style/default.less'); - cloneFile.contents = Buffer.from(content); - - return cloneFile; - } - } - }, - transformFile(file) { - if (isComponentStyleEntry(file)) { - const indexLessFilePath = file.path.replace('index.tsx', 'index.less'); - - if (fs.existsSync(indexLessFilePath)) { - // We put origin `index.less` file to `index-pure.less` - const pureFile = file.clone(); - pureFile.contents = Buffer.from(fs.readFileSync(indexLessFilePath, 'utf8')); - pureFile.path = pureFile.path.replace('index.tsx', 'index-pure.less'); - - // Rewrite `index.less` file with `root-entry-name` - const indexLessFile = file.clone(); - indexLessFile.contents = Buffer.from( - [ - // Inject variable - '@root-entry-name: default;', - // Point to origin file - "@import './index-pure.less';", - ].join('\n\n'), - ); - indexLessFile.path = indexLessFile.path.replace('index.tsx', 'index.less'); - - return [indexLessFile, pureFile]; - } - } - - return []; - }, - lessConfig: { - modifyVars: { - 'root-entry-name': 'default', - }, - }, finalize: finalizeCompile, }, dist: { finalize: finalizeDist, }, - generateThemeFileContent, bail: true, }; diff --git a/.gitignore b/.gitignore index e68dd6e1d..b45e673a2 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ jspm_packages/ dist lib es +/locale _site yarn.lock package-lock.json @@ -78,5 +79,8 @@ report.html site/src/router/demoRoutes.js +components/version/version.ts components/version/version.tsx +components/version/token.json +components/version/token-meta.json ~component-api.json diff --git a/.jest.js b/.jest.js index 3086ad2d2..f75b9b1a5 100644 --- a/.jest.js +++ b/.jest.js @@ -18,6 +18,7 @@ function getTestRegex(libDir) { module.exports = { verbose: true, setupFiles: ['./tests/setup.js'], + setupFilesAfterEnv: ['./tests/setupAfterEnv.ts'], moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'vue', 'md', 'jpg'], modulePathIgnorePatterns: ['/_site/'], testPathIgnorePatterns: testPathIgnorePatterns, @@ -30,23 +31,19 @@ module.exports = { testRegex: getTestRegex(libDir), moduleNameMapper: { '^@/(.*)$/': '/$1', - 'ant-design-vue$/': '/components/index.ts', - 'ant-design-vue/es/': '/components', + '^ant-design-vue$': '/components/index', + '^ant-design-vue/es/(.*)$': '/components/$1', }, snapshotSerializers: ['/node_modules/jest-serializer-vue'], collectCoverage: process.env.COVERAGE === 'true', collectCoverageFrom: [ 'components/**/*.{js,jsx,vue}', - '!components/*/style/index.{js,jsx}', - '!components/style/*.{js,jsx}', - '!components/*/locale/*.{js,jsx}', '!components/*/__tests__/**/type.{js,jsx}', '!components/vc-*/**/*', '!components/*/demo/**/*', '!components/_util/**/*', '!components/align/**/*', '!components/trigger/**/*', - '!components/style.js', '!**/node_modules/**', ], testEnvironment: 'jsdom', diff --git a/.prettierignore b/.prettierignore index fc219ac37..b3b276aff 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,7 +18,6 @@ yarn-error.log .editorconfig .eslintignore **/*.yml -components/style/color/*.less **/assets .gitattributes .stylelintrc diff --git a/.stylelintrc.json b/.stylelintrc.json index 40ce56859..4d48dbf97 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -4,13 +4,40 @@ "stylelint-config-rational-order", "stylelint-config-prettier" ], - "plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"], + "customSyntax": "postcss-less", + "plugins": ["stylelint-declaration-block-no-ignored-properties"], "rules": { - "comment-empty-line-before": null, - "function-name-case": ["lower", { "ignoreFunctions": ["/colorPalette/"] }], - "no-invalid-double-slash-comments": null, + "function-name-case": ["lower"], + "function-no-unknown": [ + true, + { + "ignoreFunctions": [ + "fade", + "fadeout", + "tint", + "darken", + "ceil", + "fadein", + "floor", + "unit", + "shade", + "lighten", + "percentage", + "-" + ] + } + ], + "import-notation": null, "no-descending-specificity": null, - "declaration-empty-line-before": null - }, - "ignoreFiles": ["components/style/color/{bezierEasing,colorPalette,tinyColor}.less"] + "no-invalid-position-at-import-rule": null, + "declaration-empty-line-before": null, + "keyframes-name-pattern": null, + "custom-property-pattern": null, + "number-max-precision": 8, + "alpha-value-notation": "number", + "color-function-notation": "legacy", + "selector-class-pattern": null, + "selector-id-pattern": null, + "selector-not-notation": null + } } diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 7574c43e6..d898593e1 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -10,1335 +10,76 @@ --- -## 3.2.20 +## 4.0 -`2023-04-27` - -- 🌟 Optimize the repeated instantiation of Space subcomponents [#6500](https://github.com/vueComponent/ant-design-vue/issues/6500) -- 🐞 Fix RangePicker does not support null value problem [#6510](https://github.com/vueComponent/ant-design-vue/issues/6510) - -## 3.2.19 - -`2023-04-23` - -- 🐞 Fix antd.min.js file error - -## 3.2.18 - -`2023-04-23` - -- 🐞 Fix the style of input addonAfter when Form disabled [#6403](https://github.com/vueComponent/ant-design-vue/issues/6403) -- 🐞 Fix Upload class name error [#6413](https://github.com/vueComponent/ant-design-vue/issues/6413) -- 🐞 Fix date component's week, quarter does not support format problem [#6385](https://github.com/vueComponent/ant-design-vue/issues/6385) -- 🐞 Fix the problem that Select scrolls under Firefox [#6470](https://github.com/vueComponent/ant-design-vue/issues/6470) -- 🌟 Button added focus and blur methods [#6483](https://github.com/vueComponent/ant-design-vue/issues/6483) -- 🐞 Fix the problem that the container height changes after Select is selected [#6467](https://github.com/vueComponent/ant-design-vue/issues/6467) -- 🐞 Fix Form name not taking effect [#6460](https://github.com/vueComponent/ant-design-vue/issues/6460) - -## 3.2.17 - -`2023-04-04` - -- 🐞 revert [#6324](https://github.com/vueComponent/ant-design-vue/issues/6324),fix table filter hidden error [#6400](https://github.com/vueComponent/ant-design-vue/issues/6400) - -## 3.2.16 - -`2023-03-23` - -- 🐞 Fix notification close event triggered multiple times [#6150](https://github.com/vueComponent/ant-design-vue/issues/6150) -- 🐞 Fix the carousel map responsive change problem [#6100](https://github.com/vueComponent/ant-design-vue/issues/6100) -- 🐞 Fix Table ceiling scroll bar style error [#6169](https://github.com/vueComponent/ant-design-vue/issues/6169) -- 🐞 Fix DatePicker disabledMinutes parameter error [#6233](https://github.com/vueComponent/ant-design-vue/issues/6233) -- 🐞 Fix the problem that the visibleChange event is not triggered when the Popup is closed [#6324](https://github.com/vueComponent/ant-design-vue/issues/6324) -- 🐞 Fix Image preview image error [#6331](https://github.com/vueComponent/ant-design-vue/issues/6331) - -## 3.2.15 - -`2022-11-10` - -- 🐞 Fix the problem of preview image error when `Image` is deleted dynamically - -## 3.2.14 - -`2022-11-07` - -- 🐞 Fix the problem of dynamic theme failure when custom `prefixCls` [#6063](https://github.com/vueComponent/ant-design-vue/issues/6063) -- 🐞 Fix `DatePicker` error when using select and other popup components as slots [#6062](https://github.com/vueComponent/ant-design-vue/issues/6062) -- 🐞 Fix `DirectoryTree` not exposing scrollTo method [#6067](https://github.com/vueComponent/ant-design-vue/issues/6067) -- 🐞 Fix `RangePicker` popup position does not change [#6073](https://github.com/vueComponent/ant-design-vue/issues/6073) - -## 3.2.13 - -`2022-10-08` - -- 🌟 Support Vue 3 upgrade tool `@vue/compat` [#5973](https://github.com/vueComponent/ant-design-vue/issues/5973) -- 🌟 Cascader add tagRender slot [#5954](https://github.com/vueComponent/ant-design-vue/issues/5954) -- 🐞 Fix image flickering issue when Image preview is closed [#5955](https://github.com/vueComponent/ant-design-vue/issues/5955) -- 🐞 Fix Tag close icon style display misplaced [#5956](https://github.com/vueComponent/ant-design-vue/issues/5956) -- 🐞 Fix Table loading property ts type error [#5964](https://github.com/vueComponent/ant-design-vue/issues/5964) -- 🐞 Fix Transfer deletion exception [#5975](https://github.com/vueComponent/ant-design-vue/issues/5975) -- 🐞 Fix the scroll shadow display issue of Table fixed column [#5996](https://github.com/vueComponent/ant-design-vue/issues/5996) -- 🐞 Fix DirectoryTree's default expansion failure issue when customizing fieldNames [#6007](https://github.com/vueComponent/ant-design-vue/issues/6007) - -## 3.2.12 - -`2022-09-02` - -- 🐞 Fix DescriptionItem labelStyle does not take effect [#5920](https://github.com/vueComponent/ant-design-vue/issues/5920) -- 🌟 Typography copy button prevents bubbling [##5746](https://github.com/vueComponent/ant-design-vue/issues/5746) -- 🐞 Fix table merged column scroll shadow occlusion issue [#5786](https://github.com/vueComponent/ant-design-vue/issues/5786) -- 🐞 Fix the inconsistency between css var and ConfigProvider variables [#5929](https://github.com/vueComponent/ant-design-vue/issues/5929) - -## 3.2.11 - -`2022-08-08` - -- 🐞 Fix dayjs error when CDN introduces component library [#5874](https://github.com/vueComponent/ant-design-vue/issues/5874) -- 🐞 Fix `Dropdown` submenu wrapping issue [#5798](https://github.com/vueComponent/ant-design-vue/issues/5798) -- 🐞 Fix the problem that the package size increases when the icon is introduced [#5822](https://github.com/vueComponent/ant-design-vue/issues/5822) -- 🐞 Fix `Select` custom field, no auto-focus selected node issue [#5843](https://github.com/vueComponent/ant-design-vue/issues/5843) -- 🐞 Fix `InputNumber` size=large, the style is not aligned [#5853](https://github.com/vueComponent/ant-design-vue/issues/5853) - -## 3.2.10 - -`2022-07-07` - -- 🐞 Fix the problem that the popup component cannot be used under `process.env.NODE_ENV = 'test'` [#4565](https://github.com/vueComponent/ant-design-vue/issues/4565) -- 🐞 Fix the problem that the popup layer is directly closed when the Menu component hovers quickly [36df58](https://github.com/vueComponent/ant-design-vue/commit/36df585acf9a7d53c8b50be2ab240f54588a3b20) -- 🐞 Fix Input autosize type error [#5766](https://github.com/vueComponent/ant-design-vue/issues/5766) -- 🐞 Fix Table ellipsis tilte not working under fixed [#5755](https://github.com/vueComponent/ant-design-vue/issues/5755) - -## 3.2.9 - -`2022-06-25` - -- 🐞 Fix the flickering problem when the Select edge position is closed [8a659d](https://github.com/vueComponent/ant-design-vue/commit/8a659da84fb8c44620fa279d9d6d73d6bd93d1f7) - -## 3.2.8 - -`2022-06-24` - -- 🌟 Image add scroll wheel to zoom in and out [#5703](https://github.com/vueComponent/ant-design-vue/issues/5703) -- 🌟 ConfigProvider.config added getPopupContainer [62dc24](https://github.com/vueComponent/ant-design-vue/commit/62dc2402f37c0ca0514f5b8fbb363247f0216bb2) -- 🐞 Upload tooltip does not show issues [#5714](https://github.com/vueComponent/ant-design-vue/issues/5714) -- 🐞 Row gutter property type error [#5725](https://github.com/vueComponent/ant-design-vue/issues/5725) -- 🐞 Whether Typography is editable or not, the state is not reset after switching [#5743](https://github.com/vueComponent/ant-design-vue/issues/5743) -- 🐞 In DirectoryTree multi-selection mode, a single node should be selected when clicking (multi-selection only selects multiple nodes when pressing ctrl and shift keys) [#5732](https://github.com/vueComponent/ant-design-vue/ issues/5732) - -## 3.2.7 - -`2022-06-13` - -- 🌟 `Checkbox` supports adding extra properties [#5678](https://github.com/vueComponent/ant-design-vue/issues/5678) -- 🌟 `RadioGroup` support global size [#5690](https://github.com/vueComponent/ant-design-vue/issues/5690) -- 🌟 `Table` expandedRowKeys support v-model [#5695](https://github.com/vueComponent/ant-design-vue/issues/5695) -- 🐞 Fix global Form message not taking effect [#5693](https://github.com/vueComponent/ant-design-vue/issues/5693) -- 🐞 Fix `Typography` Enter key triggers end event twice, end is no longer triggered when blur [#5696](https://github.com/vueComponent/ant-design-vue/issues/5696) - -## 3.2.6 - -`2022-06-07` - -- 🌟 `Cascader` custom trigger supports custom components [#5677](https://github.com/vueComponent/ant-design-vue/issues/5677) -- 🐞 Fix `DateRangePicker` `TimeRangePicker` arrow not following the movement issue [#71c619](https://github.com/vueComponent/ant-design-vue/commit/71c6195771c0b9ddffadd294ce01f7515c5adc40) -- 🐞 Fix `TimeRangePicker` minSteps, hourSteps, secondStep not taking effect [#5671](https://github.com/vueComponent/ant-design-vue/issues/5671) - -## 3.2.5 - -`2022-05-26` - -- 🌟 Image Added left and right arrow switching function [#5642](https://github.com/vueComponent/ant-design-vue/issues/5642) -- 🐞 Fix Steps progressDot slot failure [#5648](https://github.com/vueComponent/ant-design-vue/issues/5648) -- 🐞 Fix Tooltip global getPopupContainer invalid problem [#5636](https://github.com/vueComponent/ant-design-vue/issues/5636) -- 🐞 Fix useForm help style issue [#5635](https://github.com/vueComponent/ant-design-vue/issues/5635) -- 🐞 Fix Table, Tree drag and drop style conflict [#5644](https://github.com/vueComponent/ant-design-vue/issues/5644) - -## 3.2.4 - -`2022-05-23` - -- 🐞 Fix InputNumber v-model type error [#5577](https://github.com/vueComponent/ant-design-vue/issues/5677) -- 🌟 Select supports global size [#5590](https://github.com/vueComponent/ant-design-vue/issues/5590) -- 🐞 Form clearValidate resetValidate support array [#5619](https://github.com/vueComponent/ant-design-vue/issues/5619) -- 🐞 Drawer custom closeIcon does not take effect [#5616](https://github.com/vueComponent/ant-design-vue/issues/5616) -- 🌟 Fix CountDown support dayjs [#5edca6](https://github.com/vueComponent/ant-design-vue/commit/5edca6be5a4e1aee9cde46724b14900f6c86bfb2) -- 🌟 Tree support scrollTo [#5626](https://github.com/vueComponent/ant-design-vue/issues/5626) -- 🐞 Tooltip disabled class name error [#5627](https://github.com/vueComponent/ant-design-vue/issues/5627) - -## 3.2.3 - -`2022-05-05` - -- 🌟 Optimize `Tree` performance [#5551](https://github.com/vueComponent/ant-design-vue/issues/5551) -- 🐞 Fix `Progress` `type='dashboard'` invalid problem [#5549](https://github.com/vueComponent/ant-design-vue/issues/5549) -- 🐞 Fix console warning when `Table` customRender returns `Fragment` component [#5556](https://github.com/vueComponent/ant-design-vue/issues/5556) -- 🐞 Fix the issue of rendering redundant dom nodes when the `Card` slot is empty - -## 3.2.2 - -`2022-04-26` - -- 🐞 Fix `Table` ceiling infinite loop problem [#5543](https://github.com/vueComponent/ant-design-vue/issues/5543) - -## 3.2.1 - -`2022-04-25` - -- 🌟 `Image` previewMask supports `false`, `function` [#5531](https://github.com/vueComponent/ant-design-vue/issues/5531) -- 🌟 `Select` option to add title -- 🌟 `Table` optimizes the drag handle to prevent sorting, filtering, etc. from being triggered when dragging -- 🐞 Fix the issue of triggering search event after `Select` is selected [#5537](https://github.com/vueComponent/ant-design-vue/issues/5537) -- 🐞 Fix SSR memory leak issue [#5502](https://github.com/vueComponent/ant-design-vue/issues/5502) -- 🐞 Fix `Table` expandFixed ts type error [#5539](https://github.com/vueComponent/ant-design-vue/issues/5539) - -#### Documentation: - -- 🌟 Added Modal drag and drop demo [More](https://www.antdv.com/components/modal#components-modal-demo-modal-render) - -## 3.2.0 - -`2022-04-19` - -- 🌟 `InputNumber` supports lazy modifier -- 🌟 `Image` add `previewMask` property, `error` event [#5479](https://github.com/vueComponent/ant-design-vue/issues/5479) -- 🌟 `Modal` style supports string type [#5449](https://github.com/vueComponent/ant-design-vue/issues/5449) -- 🌟 `Cascader` supports `clearIcon`, `removeIcon` slots -- 🌟 Optimize `DatePicker` panel switching logic [#5488](https://github.com/vueComponent/ant-design-vue/issues/5488) -- 🐞 Fix `Cascader` not automatically correcting the popup position [#5482](https://github.com/vueComponent/ant-design-vue/issues/5482) -- 🐞 `Tabs` left, right direction disable animation [#5464](https://github.com/vueComponent/ant-design-vue/issues/5464) -- 🐞 `TimeRangePicker` value ts type supports string -- 🐞 `Tree` supports deep monitoring [#5480](https://github.com/vueComponent/ant-design-vue/issues/5480) -- 🐞 Fix `Table` not showing virtual scroll bar when keepalive active -- 🐞 Fix `Input` size warning [#5508](https://github.com/vueComponent/ant-design-vue/issues/5508) - -## 3.1.1 - -`2022-04-06` - -- 🌟 Optimize `Form` rule type hints [#5439](https://github.com/vueComponent/ant-design-vue/issues/5439) -- 🐞 Fix the problem of incorrect height calculation when virtual scroll related components dynamically update content [4a4670](https://github.com/vueComponent/ant-design-vue/commit/4a4670bdce9e1043348fd741ec7a262ba2413a3a) - -## 3.1.0 - -`2022-04-06` - -### 🔥🔥🔥 3.1.0 official version released 🔥🔥🔥 - -- 🐞 Fix `Select.Option` child element is empty, the error is reported [272430](https://github.com/vueComponent/ant-design-vue/commit/272430ba06e44e06eb07694d6aef4d474fb741cb) - -## 3.1.0-rc.6 - -`2022-04-01` - -- 🌟 Optimize `Table` performance, reduce the number of render times when hovering [900464](https://github.com/vueComponent/ant-design-vue/commit/900464473823277e4b06ace1c1ddc69ab3ef21d9) -- 🐞 Fix `Tabs` not folding when setting addIcon [669b22](https://github.com/vueComponent/ant-design-vue/commit/669b22a54b33892c193dfd36150ae1ac2fb350dd) -- 🐞 Fix `Mentions` component cannot be selected [#5432](https://github.com/vueComponent/ant-design-vue/issues/5432) -- 🐞 Fix component focus and blur events do not carry event parameters, resulting in popover error [#5434](https://github.com/vueComponent/ant-design-vue/issues/5434) -- 🐞 Fix `Select.Option`, when setting Tooltip, error is reported [#5307](https://github.com/vueComponent/ant-design-vue/issues/5307) - -## 3.1.0-rc.5 - -`2022-03-28` - -### 🔥🔥🔥 The official version is expected to be released at the end of March or early April, when 3.x will become the default version, and the documentation will also point to the 3.x documentation by default 🔥🔥🔥 - -- 🌟 Optimize component ts type hints [#5408](https://github.com/vueComponent/ant-design-vue/issues/5408) -- 🐞 Fix `Form` cannot scroll to nested fields [#5404](https://github.com/vueComponent/ant-design-vue/issues/5404) -- 🐞 Fix `Table` bottom-sucking scroll bar responsive failure [afd74c](https://github.com/vueComponent/ant-design-vue/commit/afd74c95d8ccd6ced5ce5f5c1a9abe3a398a0217) - -## 3.1.0-rc.4 - -`2022-03-25` - -### 🔥🔥🔥 The official version is expected to be released at the end of March or early April, when 3.x will become the default version, and the documentation will also point to the 3.x documentation by default 🔥🔥🔥 - -- 🐞 Fix `Select` options do not support push and other variant methods [#5398](https://github.com/vueComponent/ant-design-vue/issues/5398) -- 🐞 Fix `MenuItem` custom icon, the original icon class name is lost [#5390](https://github.com/vueComponent/ant-design-vue/issues/5390) - -## 3.1.0-rc.3 - -`2022-03-24` - -### 🔥🔥🔥 The official version is expected to be released at the end of March or early April, when 3.x will become the default version, and the documentation will also point to the 3.x documentation by default 🔥🔥🔥 - -- 🌟 Optimize the search and click performance of `Tree` `TreeSelect` [#5365](https://github.com/vueComponent/ant-design-vue/issues/5365) -- 🌟 `Menu` selectedKeys, openKeys support depth watch [7bf1e0](https://github.com/vueComponent/ant-design-vue/commit/7bf1e0dda1fe8f70f6c8b17ba90b217df2a75bd4) -- 🐞 Fix `Checkbox` `Radio` triggering two `click` events for one click [#5363](https://github.com/vueComponent/ant-design-vue/issues/5363) [#5389](https://github.com/vueComponent/ant-design-vue/issues/5389) -- 🐞 Fix `FormItem` `htmlFor` property invalid issue [#5384](https://github.com/vueComponent/ant-design-vue/issues/5384) -- 🐞 Fix `Upload` limit the number, the last upload is abort issue [#5385](https://github.com/vueComponent/ant-design-vue/issues/5385) -- 🐞 Fix `RangePicker` `showTime`, disabled does not consider time issue [#5286](https://github.com/vueComponent/ant-design-vue/issues/5286) -- 🐞 Fix `Layout.Sidebar` cannot be expanded after responsive collapse [#5373](https://github.com/vueComponent/ant-design-vue/issues/5373) -- 🐞 Fix `AutoComplete` custom children's class not mounted [414e7a](https://github.com/vueComponent/ant-design-vue/commit/414e7a1c56455017dbc3780ce7b1b4abd0f1c4d7) -- 🐞 Fix `TimeRangePicker` disabledTime not taking effect [#5387](https://github.com/vueComponent/ant-design-vue/issues/5387) -- 🐞 Fix `Dropdown` automatic correction of pop-up window position is invalid [#5391](https://github.com/vueComponent/ant-design-vue/issues/5391) - -## 3.1.0-rc.2 - -`2022-03-19` - -### 🔥🔥🔥 The official version is expected to be released at the end of March or early April, when 3.x will become the default version, and the documentation will also point to the 3.x documentation by default 🔥🔥🔥 - -### 🔥🔥🔥 Surely Vue supports css var sync 🔥🔥🔥 - -- 🌟 Optimize the underlying virtual scrolling components to scroll millions of data smoothly, involving `Select` `Tree` `TreeSelect` `AutoComplete` `Cascader` components -- 🐞 Fix the animation does not take effect when the `Button` component switches loading [#5360](https://github.com/vueComponent/ant-design-vue/issues/5360) -- 🐞 Fix the console error when `Modal` switches loading [#5361](https://github.com/vueComponent/ant-design-vue/issues/5361) - -## 3.1.0-rc.1 - -`2022-03-18` - -### 🔥🔥🔥 The official version is expected to be released at the end of March or early April, when 3.x will become the default version, and the documentation will also point to the 3.x documentation by default 🔥🔥🔥 - -- 🌟 Export more component properties to facilitate secondary development [#5340](https://github.com/vueComponent/ant-design-vue/issues/5340) [#5353](https://github.com/ vueComponent/ant-design-vue/issues/5353) -- 🌟 `Timeline` can be used for antd icon when custom color [2b81a7](https://github.com/vueComponent/ant-design-vue/commit/2b81a7213b169dc72f02c7e0f57afffd67333f0e) -- 🌟 `Radio` `Checkbox` supports number type -- 🌟 `Popover` does not display the popup when the slot is empty [71e110](https://github.com/vueComponent/ant-design-vue/commit/71e110036ea0339207c168f268907dcc0de277e8) -- 🌟 `Pagination` supports responsiveness [85197c](https://github.com/vueComponent/ant-design-vue/commit/85197c4b50a7aae95079bfaa700c8868ed36cbad) -- 🌟 Adjust the truncation logic after `Textarea` exceeds the maximum length (the text will not change after it exceeds) [d92921](https://github.com/vueComponent/ant-design-vue/commit/d929217752aac2dcfcd56852c7dbc3a834819de1) -- 🐞 Fix `Table` column drag handle style missing [#5348](https://github.com/vueComponent/ant-design-vue/issues/5348) -- 🐞 Fix `FormItem` error prompt repeated display problem [#5349](https://github.com/vueComponent/ant-design-vue/issues/5349) -- 🐞 Fix `FormItem` cannot be used alone [#5343](https://github.com/vueComponent/ant-design-vue/issues/5343) -- 🐞 Fix `Tooltip` not displaying in `Switch` in loading state [625eff](https://github.com/vueComponent/ant-design-vue/commit/625efff1fa8fb3c93a5c657538274fe76a4a4f1f) - -## 3.1.0-rc.0 - -`2022-03-15` - -### 🔥🔥🔥 The official version is expected to be released at the end of March or early April, when 3.x will become the default version, and the documentation will also point to the 3.x documentation by default 🔥🔥🔥 - -- 🔥 Support CSS variables, if you encounter style building errors after upgrading, please check [Dynamic Theme Documentation](https://ant.design/docs/react/customize-theme-variable-en) to troubleshoot the problem. [Experience](https://antdv.com/components/config-provider-cn/#components-config-provider-demo-theme) -- 🔥 Support RTL [experience](https://antdv.com/components/config-provider-cn/#components-config-provider-demo-direction) -- 🌟 `LayoutSider` `trigger` support slot [#5317](https://github.com/vueComponent/ant-design-vue/issues/5317) -- 🌟 `Select` add `filterSort` `virtual` `listHeight` configuration [#5310](https://github.com/vueComponent/ant-design-vue/issues/5310) -- 🌟 `SubMenu` added `popupOffset` [#5312](https://github.com/vueComponent/ant-design-vue/issues/5312) -- 🌟 `Affix` add customIcon slot [60e259](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/60e2597f7f80ca354acc859a832a71d1110b3f4c) -- 🌟 `Avatar` add `crossOrigin` `maxPopoverTrigger` property [5bdb45](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/5bdb45d6688700f0fcc10324c898cb114a1fa469) -- 🌟 `Button` adds global size support [16b3b5f](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/16b3b5fc366fcce155b4c37459a0b12f1031bfe6) -- 🌟 `DatePicker` added slots `prevIcon` `nextIcon` `superPrevIcon` `superNextIcon` [27e7ed](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/27e7ed68fb4331e9e9a07738c68f135820496dd9) -- 🌟 `Divider` added `orientationMargin` [c528d7](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/c528d74c11dd323403705250b918e5408bce2c3c) -- 🌟 `Dropdown` added `destroyPopupOnHide` `loading` [c4c691](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/c4c691b20777fe459a24a429b50e0fc8cdbdef85) -- 🌟 `Form` added `labelWrap` [cb95d1](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/cb95d1202adce3375f73e55598cccea619a4d861) -- 🌟 `Input` added `showCount` [85767d](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/85767de39688b5da6157df9317666adaad6e184f) -- 🌟 `InputNumber` add `prefix` slot [efea6b](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/efea6b000e581f9c71ba98f80febace4e024910c) -- 🌟 `MenuDivider` added `dashed` dotted line [32fc4f](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/32fc4fc7c4f3913dec771a6a96b097bcda754b40) -- 🌟 `Modal` method usage added `wrapClassName` [d38ecc](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/d38ecce22c63adc5e8e52657fcbbef89e048b621) -- 🌟 `Modal.confirm` adds `showCancel` and `propmise` support [a041b5](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/a041b5bacea2f94f55fee358ff39e5abd0d1b39f) -- 🌟 Added `Skeleton.Button` `Skeleton.Input` two sub-components [2bd5fc](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/2bd5fc15ffecf3cb3083accab02ceb97bd9ade38) -- 🌟 `Spin` supports `tip` slot [93a06a](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/93a06a45f58c0920e8f43c49c9859ce4ca10c94e) -- 🌟 `Table` added `filterMode` to support tree filtering [79ff7a](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/79ff7ac2dba4ab5cf01241ceef072f2c4be20e12) -- 🌟 `Typography` add `enterIcon` slot, `triggerType` property [e777bc](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/e777bc17435b2610a0c0e1c29f60b900bcaab03c) -- 🐞 Fix `DatePicker` using string mode, console output type is wrong [#5323](https://github.com/vueComponent/ant-design-vue/issues/5323) -- 🐞 Fix the problem that the parent node cannot be selected after all the child nodes of `TreeSelect` are disabled [#5316](https://github.com/vueComponent/ant-design-vue/issues/5316) -- 🐞 Fix `Row` `gutter` ts type hint error [2efe1a](https://github.com/vueComponent/ant-design-vue/commit/2efe1af6b66247b6bc89bf43bc3d2f1dc1f2a5d9) -- 🐞 Fix `Wave` not working when custom prefixCls [#5334](https://github.com/vueComponent/ant-design-vue/issues/5334) - -## 3.0.0-beta.13 - -`2022-03-04` - -- 🌟 Optimize the animation after Menu overflow to avoid flickering -- 🐞 Fix the issue of automatic parse when inputting invalid values when using dateFns [#5302](https://github.com/vueComponent/ant-design-vue/issues/5302) -- 🐞 Fix `Carousel` click error when using image [#5299](https://github.com/vueComponent/ant-design-vue/issues/5299) - -## 3.0.0-beta.12 - -`2022-03-02` - -- 🌟 Optimize `Menu` horizontal mode animation to avoid flickering -- 🐞 Fix the height issue caused by `Upload` animation [#5298](https://github.com/vueComponent/ant-design-vue/issues/5298) - -## 3.0.0-beta.11 - -`2022-02-28` - -- 🌟 Refactor `Upload`, add showDownloadIcon, directory, isImageUrl, itemRender, maxCount, openFileDialogOnClick, progress, previewIcon, removeIcon, downloadIcon, drop and other features -- 🌟 Refactor `Carousel` -- 🐞 Fix `Mentions` cannot be selected when long-pressed [#5233](https://github.com/vueComponent/ant-design-vue/issues/5233) -- 🐞 Fix the issue of rendering multiple expand icons when `Table` dynamically changes the expand icon position [#5295](https://github.com/vueComponent/ant-design-vue/issues/5295) -- 🐞 Fix `Slider` type error [#5289](https://github.com/vueComponent/ant-design-vue/issues/5289) - -## 3.0.0-beta.10 - -`2022-02-18` - -- 🐞 Fix the issue of automatic parse when inputting invalid values when the date component uses dayjs or dateFns [#5221](https://github.com/vueComponent/ant-design-vue/issues/5221) -- 🐞 Fix the issue that virtual scrolling is not turned off when dropdownMatchSelectWidth is false [#5242](https://github.com/vueComponent/ant-design-vue/issues/5242) -- 🐞 Fix descriptions console warning issue [#5250](https://github.com/vueComponent/ant-design-vue/issues/5250) -- 🐞 Fix the problem of provoking when the right-click of dropdown is expanded [#5259](https://github.com/vueComponent/ant-design-vue/issues/5259) -- 🐞 Fix TreeSelect windows touchpad expansion failure issue [#5220](https://github.com/vueComponent/ant-design-vue/issues/5220) - -## 3.0.0-beta.9 - -`2022-01-28` - -🔥🔥🔥 Happy New Year 🔥🔥🔥 - -- 🌟 `Progress` add title attribute to avoid title being overwritten by internal title [#4929](https://github.com/vueComponent/ant-design-vue/issues/4929) -- 🐞 Fix `Input` focus state, style border issue [#5188](https://github.com/vueComponent/ant-design-vue/issues/5188) -- 🌟 Optimize the scrolling effect of virtual scrolling under mobile [#5191](https://github.com/vueComponent/ant-design-vue/issues/5191) -- 🐞 Fix the style issue of `Tree` component when dragging [6d4248](https://github.com/vueComponent/ant-design-vue/commit/6d4248d046a420aa6a1ddfeb78632e4405b91e51) -- 🐞 Fix `TreeSelect` when the content is empty, the Enter button fills the empty node problem [#5217](https://github.com/vueComponent/ant-design-vue/issues/5217) -- 🐞 Fix `Button` block style invalid after setting size [#5219](https://github.com/vueComponent/ant-design-vue/issues/5219) - -## 3.0.0-beta.8 - -`2022-01-21` - -- 🔥 Refactor `Cascader`, support multiple selection, add `tagRender` `multiple` `maxTagCount` `maxTagPlaceholder` `expandIcon`, use `dropdownClassName` `dropdownStyle` `open` `placement` to replace `popupClassName` `popupStyle` respectively ` `popupVisible` `popupPlacement` property -- 🌟 Select, TreeSelect support slot maxTagPlaceholder -- 🌟 `Table.Summary.Cell` supports `style`, `class` native properties -- 🌟 Export more component types: `ConfigProviderProps` `InputProps` `TextAreaProps` `PopconfirmProps` `PopoverProps` `SliderProps` `StepProps` `StepsProps` -- 🐞 Fix Modal reporting error under vue@3.2.28 [#5190](https://github.com/vueComponent/ant-design-vue/issues/5190) -- 🐞 Fix `Modal` `getContainer` invalid problem [#5147](https://github.com/vueComponent/ant-design-vue/issues/5147) -- 🐞 Fix `Table` `responsive` invalid problem [#5172](https://github.com/vueComponent/ant-design-vue/issues/5172) -- 🐞 Fix `Tabs` activeKey controlled invalidation issue [#5180](https://github.com/vueComponent/ant-design-vue/issues/5180) - -## 3.0.0-beta.7 - -`2022-01-10` - -- 🌟 Export FormItemInstance type [23f5fb](https://github.com/vueComponent/ant-design-vue/commit/23f5fba013ae8a76fb814c218fb319488da3c70b) -- 🐞 Fix Modal not showing issue under Dropdown [#5139](https://github.com/vueComponent/ant-design-vue/issues/5139) -- 🐞 Fix Modal esc shortcut key invalid issue [3297f7](https://github.com/vueComponent/ant-design-vue/commit/3297f7aa58f6098b2b1dd147341b5c8dc5f2f5e5) - -## 3.0.0-beta.6 - -`2022-01-07` - -- Modal - - 🌟 Refactor Modal component [#5129](https://github.com/vueComponent/ant-design-vue/issues/5129) - - 🐞 Fix the problem of unable to scroll when Modal and Drawer are mixed [#5096](https://github.com/vueComponent/ant-design-vue/issues/5096) -- 🐞 Fix Menu under Dropdown, bind the click event, the attribute verification fails [#5127](https://github.com/vueComponent/ant-design-vue/issues/5127) -- 🐞 Fix Table virtual scroll bar not updating issue [#5124](https://github.com/vueComponent/ant-design-vue/issues/5124) -- 🐞 Adjust DatePicker to a single root node to support v-show [#5132](https://github.com/vueComponent/ant-design-vue/issues/5132) - -#### Documentation: - -- 🌟 Dynamically update document.title to facilitate document switching [#5121](https://github.com/vueComponent/ant-design-vue/issues/5121) -- 🐞 Fix Empty type error [#5136](https://github.com/vueComponent/ant-design-vue/issues/5136) -- 🐞 Fix RangeTime range selection example error [#5125](https://github.com/vueComponent/ant-design-vue/issues/5125) - -## 3.0.0-beta.5 - -`2022-01-04` - -- 🌟 Refactor message and notification components [#5113](https://github.com/vueComponent/ant-design-vue/issues/5113) -- 🐞 Fix TimePicker, Slider, TreeSelect type errors [#5109](https://github.com/vueComponent/ant-design-vue/issues/5109) -- 🐞 Fix the issue that it does not take effect when Space size=0 [#5101](https://github.com/vueComponent/ant-design-vue/issues/5101) - -## 3.0.0-beta.4 - -`2021-12-28` - -- 🌟 Refactor the Checkbox component for better performance -- 🌟 FormItem adds noStyle property, which makes it more convenient to organize form layout -- 🐞 Fix the problem that InputNumber cannot enter the minimum value when the precision is 0 [#5083](https://github.com/vueComponent/ant-design-vue/issues/5083) - -#### Documentation: - -- 🌟 Form adds 2 new examples: Time-related Controls, Other Form Controls - -## 3.0.0-beta.3 - -`2021-12-27` - -- 🐞 Fix `Select` virtual scroll, dynamically correct the height error problem [#5082](https://github.com/vueComponent/ant-design-vue/issues/5082) - -## 3.0.0-beta.2 - -`2021-12-27` - -- 🐞 Fix the issue of triggering inspection when FormItem does not pass the name [#5081](https://github.com/vueComponent/ant-design-vue/issues/5081) -- 🐞 Fix the width flickering problem when Table is first rendered [#5075](https://github.com/vueComponent/ant-design-vue/issues/5075) [#4993](https://github.com/vueComponent/ant-design-vue/issues/4993) - -## 3.0.0-beta.1 - -`2021-12-24` - -- 🌟 Refactor the InputNumber component, add new attributes: `bordered` `controls` `keyboard` `stringMode`, slot: `addonAfter` `addonBefore`, event: `step`, please refer to InputNumber API description for details -- 🌟 Add global.d.ts type file to facilitate volar recognition [#5067](https://github.com/vueComponent/ant-design-vue/issues/5067) -- 🐞 Fix web-type.json missing issue [#4860](https://github.com/vueComponent/ant-design-vue/issues/4860) - -- Tabs - - - 🌟 Tabs collapsed node added delete function - - 🐞 Tabs special scene not activated option issue [#5056](https://github.com/vueComponent/ant-design-vue/issues/5056) - - 🐞 Fix the problem of the default export TabPane component name error [b645f8](https://github.com/vueComponent/ant-design-vue/commit/b645f827d0e13d60bc01c740ae8cbc8f61cf2cdf) - -- Form - - - 🌟 7 new usage examples added to the document - - 🌟 New FormInstance type export - - 🌟 No need to specify the type when verifying the Number type [#5064](https://github.com/vueComponent/ant-design-vue/issues/5064) - - 🐞 Roll back the automatic verification feature when FormItem is actively assigned. This scenario should not be automatically verified [#5056](https://github.com/vueComponent/ant-design-vue/issues/5056) - - 🐞 Fix validateMessages error problem - -- 🌟 Optimize the basic components of the virtual list and improve the performance of Tree, TreeSelect, and Select [4e70c6](https://github.com/vueComponent/ant-design-vue/commit/4e70c6dd775254ae713d8633db2d0363027708e1) [#5069](https://github. com/vueComponent/ant-design-vue/issues/5069) -- 🐞 Fix the stuttering problem when Tree expands [#5069](https://github.com/vueComponent/ant-design-vue/issues/5069) -- 🐞 Fix the issue that Input is not updated when reset to undefined - -## 3.0.0-alpha.16 - -`2021-12-19` - -- 🌟 Refactored Input and added borderless configuration -- Table - - 🌟 Table customCell added column parameter [#5052](https://github.com/vueComponent/ant-design-vue/issues/5052) - - 🐞 Fix the console output error warning problem when turning Table pages [#5029](https://github.com/vueComponent/ant-design-vue/issues/5029) - - 🐞 Fix the problem that the pop-up box of the Table page turning component is hidden, and the pop-up box position is wrong [#5028](https://github.com/vueComponent/ant-design-vue/issues/5028) -- 🐞 Fix the issue that the global prefixCls of the Rate component does not take effect [#5026](https://github.com/vueComponent/ant-design-vue/issues/5026) -- 🐞 Fix Menu custom class not taking effect [#5038](https://github.com/vueComponent/ant-design-vue/issues/5038) -- 🐞 Fix the problem of printing warning when Carousel mobile device is touched [#5040](https://github.com/vueComponent/ant-design-vue/issues/5040) -- 🐞 Fix the problem that Select cannot be selected when customizing prefixCls [#5023](https://github.com/vueComponent/ant-design-vue/issues/5023) - -## 3.0.0-alpha.15 - -`2021-12-12` - -- 🌟 Optimize Layout performance -- 🌟 Menu supports lazy loading (SubMenu must fill in the key) to improve performance [#4812](https://github.com/vueComponent/ant-design-vue/issues/4812) -- 🌟 Input and Textarea support lazy command modifier [#4951](https://github.com/vueComponent/ant-design-vue/issues/4951) -- 🐞 Select placeholder supports slot [#4995](https://github.com/vueComponent/ant-design-vue/issues/4995) -- 🐞 Fix Radio cursor style [#4997](https://github.com/vueComponent/ant-design-vue/issues/4997) -- 🐞 Fix Statistic.Countdown property support slot [#4996](https://github.com/vueComponent/ant-design-vue/issues/4996) -- 🐞 Fix FormItem name attribute type error [#4998](https://github.com/vueComponent/ant-design-vue/issues/4998) -- 🐞 Fix Menu hidden animation loss problem -- 🐞 Fix FormItem explain style not responding issue [#5004](https://github.com/vueComponent/ant-design-vue/issues/5004) -- 🐞 Fix the problem that Slider tooltip does not display under special conditions -- 🐞 Fix the problem that Dropdown special conditions trigger two click events [#5002](https://github.com/vueComponent/ant-design-vue/issues/5002) -- 🐞 Fix some components reporting errors under SSR, support Nuxt -- 🐞 Fix the problem that the drop-down box component jumps at the edge [#5008](https://github.com/vueComponent/ant-design-vue/issues/5008) -- 🐞 Fix Table type error [#5009](https://github.com/vueComponent/ant-design-vue/issues/5009) - -## 3.0.0-alpha.14 - -`2021-12-05` - -- 🌟 Add xxxl grid [#4953](https://github.com/vueComponent/ant-design-vue/issues/4953) -- 🌟 Collapse activeKey supports deep monitoring [#4969](https://github.com/vueComponent/ant-design-vue/issues/4969) -- 🐞 Fix the problem that the form verification is not triggered when textarea blur [af5440](https://github.com/vueComponent/ant-design-vue/commit/af54405381d60bfadb383996a6ad64724b80f996) -- 🐞 Fix the issue of unchecked form during active assignment [#4955](https://github.com/vueComponent/ant-design-vue/issues/4955) -- 🐞 Fix the problem of unable to scroll after Select search [#4971](https://github.com/vueComponent/ant-design-vue/issues/4971) -- 🐞 Fix rangePicker, slider type issues - -## 3.0.0-alpha.13 - -`2021-11-28` - -🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 - -Publish Performant advanced table component Surely Vue - -Official website : [https://surely.cool/](https://surely.cool/) - -Github:[https://github.com/surely-vue/table] - -- 🐞 Upgrade ts, fix component type error [e28168](https://github.com/vueComponent/ant-design-vue/commit/e28168e0bed28a97ef8c7b33f80d03f6fd0b5a02)[#4908](https://github.com/vueComponent/ant-design-vue/issues/4908)[#4912](https://github.com/vueComponent/ant-design-vue/issues/4912) -- 🐞 Drawer visible is changed to optional to avoid reporting type errors in the writing of jsx v-model [#4908](https://github.com/vueComponent/ant-design-vue/issues/4908) -- 🐞 Fix the problem that the tabs moreIcon slot does not take effect [#4928](https://github.com/vueComponent/ant-design-vue/issues/4928) -- 🐞 Fix Button :disabled="false" when the style is wrong [#4930](https://github.com/vueComponent/ant-design-vue/issues/4930) -- 🐞 Fix the expansion component (Select, AutoComplete, TreeSelect), the animation direction is wrong, the expansion flashing problem [#4909](https://github.com/vueComponent/ant-design-vue/issues/4909) -- 🐞 Anchor class name fixed has no prefix, which leads to naming conflicts [#4931](https://github.com/vueComponent/ant-design-vue/issues/4931) - -## 3.0.0-alpha.12 - -`2021-11-20` - -- 🐞 Fix the problem that TimeRangePicker does not hide the panel correctly [#4902](https://github.com/vueComponent/ant-design-vue/issues/4902) -- 🐞 Fix the problem that TreeSelect is not cleared when resetting undefined [#4897](https://github.com/vueComponent/ant-design-vue/issues/4897) -- 🐞 Fix the issue that TreeSelect isLeaf does not take effect [#4883](https://github.com/vueComponent/ant-design-vue/issues/4883) -- 🐞 Fix Table rowSelection reporting loop response warning problem [#4885](https://github.com/vueComponent/ant-design-vue/issues/4885) -- 🐞 Fix the problem that Table rowSelection does not respond when dynamically updated [#4889](https://github.com/vueComponent/ant-design-vue/issues/4889) -- 🐞 Fix missing component types [#4863](https://github.com/vueComponent/ant-design-vue/issues/4863) - -## 3.0.0-alpha.11 - -`2021-11-08` - -- 🌟 Add codesanbox link to the document [#4861](https://github.com/vueComponent/ant-design-vue/issues/4861) -- 🐞 Fix Collapse animation loss problem [#4856](https://github.com/vueComponent/ant-design-vue/issues/4856) -- 🐞 Fix the warning problem when Table does not set dataIndex - -## 3.0.0-alpha.10 - -`2021-11-05` - -- 🐞 Fix the problem that Tree does not trigger loadData [#4835](https://github.com/vueComponent/ant-design-vue/issues/4835) -- 🐞 Fix Breadcrumb.Item click event not triggering issue [#4845](https://github.com/vueComponent/ant-design-vue/issues/4845) -- 🐞 Fix Checkbox sometimes not centered under Group [#4846](https://github.com/vueComponent/ant-design-vue/issues/4846) - -## 3.0.0-alpha.9 - -`2021-11-03` - -- 🐞 Fix requestAnimationFrame undefined error under ssr for some components [#4833](https://github.com/vueComponent/ant-design-vue/issues/4833) -- 🐞 Fix the problem that TreeSelect selectable and checkable cannot be closed [#4838](https://github.com/vueComponent/ant-design-vue/issues/4838) -- 🐞 Fix the problem that Tabs cannot be scrolled on the mobile terminal [#4828](https://github.com/vueComponent/ant-design-vue/issues/4828) -- 🐞 Fix InputNumber does not trigger inspection under form [#4831](https://github.com/vueComponent/ant-design-vue/issues/4831) -- 🐞 Fix that when Select uses `\` to build a node, the automatic word segmentation fails [#4844](https://github.com/vueComponent/ant-design-vue/issues/4844) - -## 3.0.0-alpha.8 - -`2021-10-30` - -- 🐞 Fix the missing component type [#4823](https://github.com/vueComponent/ant-design-vue/issues/4823) - -## 3.0.0-alpha.7 - -`2021-10-29` - -- 🌟 Form added validate event [#4817](https://github.com/vueComponent/ant-design-vue/issues/4817) -- 🌟 Tree provides ref to get internal state api [#4820](https://github.com/vueComponent/ant-design-vue/issues/4820) -- 🐞 Fix the width mutation problem when dragging Table [#4811](https://github.com/vueComponent/ant-design-vue/issues/4811) -- 🐞 Fix the problem that TreeSelect is empty and does not update when opened again [a5604b](https://github.com/vueComponent/ant-design-vue/commit/a5604bb96796b9ec0090d3ec0c6d32d13d0df740) - -## 3.0.0-alpha.6 - -`2021-10-27` - -- 🌟 Table add drag column feature - -## 3.0.0-alpha.5 - -`2021-10-26` - -- Table - - 🐞 Fix sticky time reporting error [#4804](https://github.com/vueComponent/ant-design-vue/issues/4804) [#4808](https://github.com/vueComponent/ant-design-vue/issues/4808) - - 🐞 Fix emptyText internationalization failure problem [#4805](https://github.com/vueComponent/ant-design-vue/issues/4805) - - 🌟 Optimize performance issues when size changes [#4787](https://github.com/vueComponent/ant-design-vue/issues/4787) -- 🌟 useForm supports deep responsive rule [#4799](https://github.com/vueComponent/ant-design-vue/issues/4799) -- 🌟 Dropdown type supports text type [#4802](https://github.com/vueComponent/ant-design-vue/issues/4802) -- 🐞 Fix Menu reporting error on mobile terminal [#4794](https://github.com/vueComponent/ant-design-vue/issues/4794) -- 🐞 Fix the invalidation problem when checking the Tree custom fieldNames [#4790](https://github.com/vueComponent/ant-design-vue/issues/4790) -- 🐞 Fix api component internationalization failure problem [#4780](https://github.com/vueComponent/ant-design-vue/issues/4780) - -## 3.0.0-alpha.4 - -`2021-10-20` - -- Use shallowRef to improve performance in part of the component state [3a968f](https://github.com/vueComponent/ant-design-vue/commit/3a968fdd33960a788f2037d944aca4e8ee81294f) - -## 3.0.0-alpha.3 - -`2021-10-08` - -- Table - - 🐞 Fix the problem that the sorting prompt does not display [f64d7a](https://github.com/vueComponent/ant-design-vue/commit/f64d7adb22952cfdd5bf642343335fd78460d745) - - 🐞 Fix the responsive loss of some attributes [#4756](https://github.com/vueComponent/ant-design-vue/issues/4756) -- 🐞 Fix the problem that the default automatic calibration position of `Popover` and `Popconfirm` does not take effect [98b5e5](https://github.com/vueComponent/ant-design-vue/commit/98b5e5d53fd10620eddc2c386181f868ef941397) - -## 3.0.0-alpha.2 - -`2021-10-08` - -- 🐞 Fix the issue of referencing process.nextTick [#4737](https://github.com/vueComponent/ant-design-vue/issues/4737) - -## 3.0.0-alpha.1 - -`2021-10-07` - -- 🌟 Refactor `Tabs` [#4732](https://github.com/vueComponent/ant-design-vue/issues/4732) - - Removed `prevClick`, `nextClick` events, and use `tabScroll` event instead - - Obsolete the `tabBarExtraContent` slot, replace it with the rightExtra slot, and add the `leftExtra` slot - - Added `addIcon`, `closeIcon`, `moreIcon` slots -- 🌟 Refactor `Card`, discard the tabList slots configuration, and use the customTab slot for unified configuration [#4732](https://github.com/vueComponent/ant-design-vue/issues/4732) -- 🌟 Refactor `Drawer` - - Added `autofocus` `contentWrapperStyle` `footerStyle` `headerStyle` `push` `size` `forceRender` and other attributes - - Added `closeIcon` `extra` `footer` and other slots - - Deprecated `afterVisibleChange` property, use event with the same name instead -- 🐞 Fix the problem that `Table` pagination does not respond to changes [1add0d](https://github.com/vueComponent/ant-design-vue/commit/1add0d251cd35aa2c55404f7a60f1531425490c1) -- 🐞 Fix `notification` style misalignment problem [#4703](https://github.com/vueComponent/ant-design-vue/issues/4703) -- 🐞 Fix the selection, dragging and other abnormalities caused by `Tree` fieldsName [#4726](https://github.com/vueComponent/ant-design-vue/issues/4726) - -## 3.0.0-alpha.0 - -`2021-09-24` - -🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 - -- Open source documentation. -- Removed the `lazy` attribute of Transfer, it does not have a real optimization effect. -- Removed the `combobox` mode of Select, please use `AutoComplete` instead. -- Deprecated Button.Group, please use `Space` instead. -- `Timeline.Item` new label. -- `Steps` added `responsive`, `percent`. -- `Collapse` added `ghost`, `collapsible`. -- `Popconfirm` added `cancelButton`, `okButton`, and `esc` button hiding. -- `ConfigProvider` added ConfigProvider.config to define the configuration of `Modal.xxx` `message` `notification`. -- `Tree` `TreeSelect`. - - - Added virtual scrolling, discarded using `a-tree-node` `a-tree-select-node` to build nodes, using `treeData` property instead to improve component performance. - - Deprecated `scopedSlots` `slots` custom rendering node, and replace it with `v-slot:title` to improve ease of use, avoid slot configuration expansion, and also avoid slot conflicts. - -- `Table` - - - Removed the `rowSelection.hideDefaultSelections` property of Table, please use `SELECTION_ALL` and `SELECTION_INVERT` in `rowSelection.selections` instead, [custom options](/components/table/#components-table-demo- row-selection-custom). - - Removed Column slots and replaced them with `v-slot:headerCell` `v-slot:headerCell` `v-slot:bodyCell` `v-slot:customFilterDropdown` `v-slot:customFilterIcon` to improve ease of use , To avoid slot configuration expansion, but also to avoid the problem of slot conflicts. - - Added expandFixed to control whether the expanded icon is fixed. - - Added the showSorterTooltip header whether to display the tooltip for the next sort. - - Added sticky for setting sticky head and scroll bar. - - Added rowExpandable to set whether to allow row expansion. - - New slot headerCell is used to personalize the header cell. - - Added slot bodyCell for personalized cell. - - New slot customFilterDropdown is used to customize the filter menu, which needs to be used with `column.customFilterDropdown`. - - Added slot customFilterIcon for custom filter icons. - - New slot emptyText is used to customize the display content of empty data. - - Added slot summary for the summary column. - -- `DatePicker` `TimePicker` `Calendar` - - - By default, a more lightweight dayjs is used to replace momentjs. If your project is too large and uses a lot of momentjs methods, you can refer to the document [Custom Time Library](/docs/vue/replace-date-cn), Replace with momentjs. - - UI interaction adjustment, align with antd 4.x interaction specifications. - -- `Form` The main goal of this update is to improve performance. If you don't have custom form controls, you can almost ignore this part - - - Since version 3.0, Form.Item no longer hijacks child elements, but automatically checks through provider/inject dependency injection. This method can improve component performance, and there is no limit to the number of child elements. The same is true for child elements. It can be a high-level component that is further encapsulated. - - You can reference [Customized Form Controls](#components-form-demo-customized-form-controls), but it also has some disadvantages: - - 1. If the custom component wants Form.Item to be verified and displayed, you need to inject `const {id, onFieldChange, onFieldBlur} = useInjectFormItemContext()` and call the corresponding method. - - 2. A Form.Item can only collect the data of one form item. If there are multiple form items, it will cause collection confusion. For example, - - ```html - - - - - ``` - - As above Form.Item does not know whether to collect `name="a"` or `name="b"`, you can solve this kind of problem in the following two ways: - - The first is to use multiple `a-form-item`: - - ```html - - - - - ``` - - The second way is to wrap it with a custom component and call `useInjectFormItemContext` in the custom component, It is equivalent to merging multiple form items into one. - - ```html - - ``` - - ```html - - - - - - - ``` - - Third, the component library provides an `a-form-item-rest` component, which will prevent data collection. You can put form items that do not need to be collected and verified into this component. It is the same as the first This method is very similar, but it does not generate additional dom nodes. - - ```html - - - - - ``` - -## 2.2.8 - -`2021-09-17` - -- 🌟 Upload method supports patch [#4637](https://github.com/vueComponent/ant-design-vue/issues/4637) -- 🌟 List gutter supports array [d2b721](https://github.com/vueComponent/ant-design-vue/commit/d2b72143f0e15c8716b4ea8f68b2b72eff5cf510) -- 🐞 Fix Modal type error [#4632](https://github.com/vueComponent/ant-design-vue/issues/4632) -- 🐞 Fix the problem that AutoComplete cannot reset undefined [741718](https://github.com/vueComponent/ant-design-vue/commit/741718a0f92c790266e7a07d8d129c5673344a7e) -- 🐞 Fix the missing style of Tag closed icon [#4649](https://github.com/vueComponent/ant-design-vue/issues/4649) -- 🐞 Fix the problem that the TreeSelect clear button does not display under special conditions [#4655](https://github.com/vueComponent/ant-design-vue/issues/4655) -- 🐞 Fix useForm immdiate not working issue [#4646](https://github.com/vueComponent/ant-design-vue/issues/4646) - -## 2.2.7 - -`2021-09-08` - -- 🌟 Menu supports overflowedIndicator slot [#4515](https://github.com/vueComponent/ant-design-vue/issues/4515) -- 🌟 useForm supports dynamic rule [#4498](https://github.com/vueComponent/ant-design-vue/issues/4498) -- 🌟 Select supports Number type [#4570](https://github.com/vueComponent/ant-design-vue/issues/4570) -- 🐞 Fix the warning problem caused by css zoom [#4554](https://github.com/vueComponent/ant-design-vue/issues/4554) -- 🐞 Fix Mentions input Chinese error report [#4524](https://github.com/vueComponent/ant-design-vue/issues/4524) -- 🐞 Fix the issue that AutoComplete does not support global prefixCls [#4566](https://github.com/vueComponent/ant-design-vue/issues/4566) -- 🐞 Fix Table nested table error report [#4600](https://github.com/vueComponent/ant-design-vue/issues/4600) -- 🐞 Fix MenuItem danger property under Dropdown has no style problem [#4618](https://github.com/vueComponent/ant-design-vue/issues/4618) -- 🐞 Fix Modal.xxx and other methods passing appContext invalid problem [#4627](https://github.com/vueComponent/ant-design-vue/issues/4627) -- 🐞 Fix some TS type errors - -## 2.2.6 - -`2021-08-12` - -- 🐞 Fix `Table` expanded list rendering problem [#4507](https://github.com/vueComponent/ant-design-vue/issues/4507) -- 🐞 Fix `Rate` custom `character` slot not taking effect [#4509](https://github.com/vueComponent/ant-design-vue/issues/4509) -- 🐞 Add resize-observer-polyfill to fix the problem of reporting errors in low versions of Chrome [#4508](https://github.com/vueComponent/ant-design-vue/issues/4508) - -## 2.2.5 - -`2021-08-11` - -- 🌟 `Select` supports customizing nodes through option slots [68c1f4](https://github.com/vueComponent/ant-design-vue/commit/68c1f4550108a3a6bbe4f1b2c5c168523fd6c84a) -- 🐞 Fix the problem that the pop-up window component in the development environment does not display in the lower version of chrome, and avoid the pop-up window flashing [#4409](https://github.com/vueComponent/ant-design-vue/issues/4409) -- 🐞 Fix the problem of not scrolling to the active position when `Select` is opened [ccb240](https://github.com/vueComponent/ant-design-vue/commit/ccb24016c07632f49550646c971060c402586c67) - -## 2.2.4 - -`2021-08-10` - -- 🌟 Support Vue@3.2 [#4490](https://github.com/vueComponent/ant-design-vue/issues/4490) -- 🌟 Automatically hide the horizontal scroll bar of `Table` [#4484](https://github.com/vueComponent/ant-design-vue/issues/4484) -- 🐞 Fix the issue of `Progress` trailColor not taking effect [#4483](https://github.com/vueComponent/ant-design-vue/issues/4483) - -## 2.2.3 - -`2021-08-07` - -- 🌟 Use `position: sticky` for the fixed column of `Table` to improve performance and solve the problem of misalignment in some scenes [38569c](https://github.com/vueComponent/ant-design-vue/commit/38569c28c7eb4eaa34f2cc096982daea901062d4) -- 🌟 `Collapse` supports number type key [#4405](https://github.com/vueComponent/ant-design-vue/issues/4405) -- 🌟 Optimize the flickering problem of `Tabs` when selected under windows [#4241](https://github.com/vueComponent/ant-design-vue/issues/4241) -- 🌟 `InputPassword` supports global setting prefixCls [#4430](https://github.com/vueComponent/ant-design-vue/issues/4430) -- 🐞 Fix `Select` cannot scroll issue [#4396](https://github.com/vueComponent/ant-design-vue/issues/4396) -- 🐞 Fix `Badge` error reporting under ssr [#4384](https://github.com/vueComponent/ant-design-vue/issues/4384) -- 🐞 Fix the issue of invalid data fields in `Form` [#4435](https://github.com/vueComponent/ant-design-vue/issues/4435) -- 🐞 Fix an error when the child element of `FormItem` is a native label [#4383](https://github.com/vueComponent/ant-design-vue/issues/4383) -- 🐞 Fix the error when `TreeSelect` customize title through slot [#4459](https://github.com/vueComponent/ant-design-vue/issues/4459) - -## 2.2.2 - -`2021-07-11` - -- 🌟 Switch added checkedValue and unCheckedValue attributes to customize checked binding value [#4329](https://github.com/vueComponent/ant-design-vue/issues/4329) -- 🐞 Fix the issue of missing SubMenu animation [#4325](https://github.com/vueComponent/ant-design-vue/issues/4325) -- 🐞 Fix that there is no red box problem when TimePicker validates the error under Form [#4331](https://github.com/vueComponent/ant-design-vue/issues/4331) -- 🐞 Fix UploadDragger does not support vite-plugin-components on-demand loading problem [#4334](https://github.com/vueComponent/ant-design-vue/issues/4334) -- 🐞 Fix the error when TreeSelect customize title through slot [1152e8](https://github.com/vueComponent/ant-design-vue/commit/1152e8cd71cadf9e8fb4797916adca20c0e35974) -- 🐞 Fix the dropdown submenu style loss issue [#4351](https://github.com/vueComponent/ant-design-vue/issues/4351) -- TS - - Fix the type error of Table in ts 4.3.5 version [#4296](https://github.com/vueComponent/ant-design-vue/issues/4296) - - Improve notification type [#4346](https://github.com/vueComponent/ant-design-vue/issues/4346) - -## 2.2.1 - -`2021-07-06` - -- 🐞 Fix the issue that the Space component does not take effect in browsers that do not support flex -- 🐞 Fix the issue of DatePicker triggering scrolling under safari [#4323](https://github.com/vueComponent/ant-design-vue/issues/4323) - -## 2.2.0 - -`2021-07-06` - -- 🎉 Refactor the Button component, remove type="danger", and add the `danger` attribute [#4291](https://github.com/vueComponent/ant-design-vue/issues/4291) -- 🐞 Fix Rate component not updating issue [#4294](https://github.com/vueComponent/ant-design-vue/issues/4294) -- 🐞 Fix Tree replaceFields error report [#4298](https://github.com/vueComponent/ant-design-vue/issues/4298) -- 🐞 Fix Modal missing parentContext type problem [#4305](https://github.com/vueComponent/ant-design-vue/issues/4305) - -## 2.2.0-rc.1 - -`2021-06-29` - -- 🌟 Change babel configuration, smaller build package size -- 🌟 Form provides the useForm function natively, and we will deprecate the @ant-design-vue/use library -- 🐞 Fix the issue that the Form validateFirst property does not trigger reject when there are multiple validation rules [#4273](https://github.com/vueComponent/ant-design-vue/issues/4273) -- 🐞 Fix List circular references causing errors in Vite [#4263](https://github.com/vueComponent/ant-design-vue/issues/4263) -- 🐞 Fix the missing item attribute problem in Menu event callback [#4290](https://github.com/vueComponent/ant-design-vue/issues/4290) - -## 2.2.0-beta.6 - -`2021-06-26` - -- 🌟 Menu performance optimization [e8b957](https://github.com/vueComponent/ant-design-vue/commit/e8b95784eb1ee0554b0d6b17bdc14e18775f2ae6) -- 🐞 Fix `Layout` `RangePicker` `WeekPicker` `Textarea` on-demand loading failure - -## 2.2.0-beta.5 - -`2021-06-24` - -- 🎉 Support vite-plugin-components to be loaded on demand -- 🎉 Refactor the List component -- 🌟 Select adds responsive folding option [656d14](https://github.com/vueComponent/ant-design-vue/commit/656d14fc4e4ef0f781324438f0d58cfb6816d583) -- 🐞 Fix the problem that the virtual list cannot be scrolled when the Select dynamic update option [b2aa49d](https://github.com/vueComponent/ant-design-vue/commit/b2aa49d064a83c6ce786a6bb4cd9fc5266a5964d) -- 🐞 Fix the incorrect location of Select keyboard events [604372](https://github.com/vueComponent/ant-design-vue/commit/604372ff2da521dd580ad5229f7dbd445c1c6190) -- 🐞 Fix the issue that AutoComplete does not support options slot [#4012](https://github.com/vueComponent/ant-design-vue/issues/4012) - -## 2.2.0-beta.4 - -`2021-06-21` - -- 🎉 Refactor Descriptions component [#4219](https://github.com/vueComponent/ant-design-vue/issues/4219) -- 🐞 Fix the issue that Countdown does not trigger the finish event [#4222](https://github.com/vueComponent/ant-design-vue/issues/4222) -- 🐞 Fix ConfigProvider reporting errors under vue 3.1 [#4225](https://github.com/vueComponent/ant-design-vue/issues/4225) -- 🐞 Fix the problem of using SubMenu under Dropdown to report an error [#4205](https://github.com/vueComponent/ant-design-vue/issues/4205) -- 🐞 Fix Col type error [#4226](https://github.com/vueComponent/ant-design-vue/issues/4226) -- 🐞 Fix the problem that onEnd is not triggered when Typography is out of focus [#4227](https://github.com/vueComponent/ant-design-vue/issues/4227) -- 🐞 Fix ImagePreview style loss problem [#4231](https://github.com/vueComponent/ant-design-vue/issues/4231) - -## 2.2.0-beta.3 - -`2021-06-11` - -- 🎉 Refactor Breadcrumb, Statistic, Tag components -- 🌟 Statistic supports loading attribute -- 🐞 Fix the problem of Menu rendering multiple sub-components to improve performance [6ae707](https://github.com/vueComponent/ant-design-vue/commit/6ae707edf508a9c5e8dca7dacf1410de5251bcf8) -- 🐞 Fix FormItem custom class invalidation [617e53](https://github.com/vueComponent/ant-design-vue/commit/617e534fda2ae6d468b5e9d3eb43370f8a4b0000) -- 🐞 Fix MenuDivider class error [#4195](https://github.com/vueComponent/ant-design-vue/issues/4195) -- 🐞 Fix Tag and Image type errors -- 🐞 Fix the issue of missing component animations such as Modal [#4191](https://github.com/vueComponent/ant-design-vue/issues/4191) -- 🐞 Fix the issue that Select class cannot be dynamically updated [#4194](https://github.com/vueComponent/ant-design-vue/issues/4194) -- 🐞 Fix the problem that the Dropdown mail expands and cannot be collapsed by clicking [#4198](https://github.com/vueComponent/ant-design-vue/issues/4198) -- 🐞 Fix the issue of missing some export methods of FormItem [#4183](https://github.com/vueComponent/ant-design-vue/issues/4183) - -## 2.2.0-beta.2 - -`2021-06-08` - -- 🐞 Fix PageHeader display extension problem [4de73](https://github.com/vueComponent/ant-design-vue/commit/4de7737907d485d3dd3be44b70e599cc53edb171) -- 🐞 Fix the problem that some components cannot be rendered normally under Vue3.1[#4173](https://github.com/vueComponent/ant-design-vue/issues/4173) -- 🐞 Fix Menu.Divider name error problem [6c5c84](https://github.com/vueComponent/ant-design-vue/commit/6c5c84a3fc4b8abcd7aed0922852a64e0ac293c7) - -## 2.2.0-beta.1 - -`2021-06-17` - -- 🔥🔥🔥 Virtual Table independent library released https://www.npmjs.com/package/@surely-vue/table, this component is an independent library, the document example is not yet complete, it is a completely ts-developed component , There are good type hints, there are API documents on npm, those who are in a hurry can explore and use it, here is an online experience example, https://store.antdv.com/pro/preview/list/big-table-list -- 🔥🔥🔥 Refactored a large number of components, the source code is more readable, the performance is better, and the ts type is more comprehensive -Refactored components in this version Anchor, Alert, Avatar, Badge, BackTop, Col, Form, Layout, Menu, Space, Spin, Switch, Row, Result, Rate -- 🎉 Menu - - - Better performance [#3300](https://github.com/vueComponent/ant-design-vue/issues/3300) - - Fix the problem of incorrect highlighting [#4053](https://github.com/vueComponent/ant-design-vue/issues/4053) - - Fix console invalid warning [#4169](https://github.com/vueComponent/ant-design-vue/issues/4169) - - Easier to use, simpler to use single file recursion [#4133](https://github.com/vueComponent/ant-design-vue/issues/4133) - - 💄 icon icon needs to be passed through slot - -- Skeleton - - - 🌟 Support Skeleton.Avatar placeholder component. - - 🌟 Support Skeleton.Button placeholder component. - - 🌟 Support Skeleton.Input placeholder component. - -- 💄 Destructive update - - - The `a-menu-item` and `a-sub-menu` icons need to be passed through the slot, and the icon is not automatically obtained through the sub-node - - row gutter supports row-wrap, no need to use multiple rows to divide col - - `Menu` removes `defaultOpenKeys` and `defaultSelectedKeys`; `Switch` removes `defaultChecked`; `Rate` removes `defaultValue`; Please be cautious to use the defaultXxx-named attributes of other unrefactored components, and they will be removed in future versions. - -- 🌟 Added Avatar.Group component -- 🐞 Fix AutoComplete filterOptions not taking effect [#4170](https://github.com/vueComponent/ant-design-vue/issues/4170) -- 🐞 Fix Select automatic width invalidation problem [#4118](https://github.com/vueComponent/ant-design-vue/issues/4118) -- 🐞 Fix the lack of internationalized files in dist [#3684](https://github.com/vueComponent/ant-design-vue/issues/3684) - -## 2.1.6 - -`2021-05-13` - -- 🐞 Use vue@3.0.10 to rebuild to avoid console warning [#3998](https://github.com/vueComponent/ant-design-vue/issues/3998) - -## 2.1.5 - -`2021-05-12` - -- 🐞 Fix SSR time reporting error [#3983](https://github.com/vueComponent/ant-design-vue/issues/3983) - -## 2.1.4 - -`2021-05-09` - -- 🐞 Fix `Table` scrolling misalignment issue [#4045](https://github.com/vueComponent/ant-design-vue/issues/4045) -- 🐞 Fix `Typography` editable mode triggering link jump issue [#4105](https://github.com/vueComponent/ant-design-vue/issues/4105) -- 🐞 Fix the issue that `Carousel` variableWidth does not take effect [#3977](https://github.com/vueComponent/ant-design-vue/issues/3977) -- 🐞 Fix the problem that `TreeSelect` cannot delete parent and child nodes at the same time through the keyboard [#3508](https://github.com/vueComponent/ant-design-vue/issues/3508) -- 🐞 Fix some types of errors - -## 2.1.3 - -`2021-04-25` - -- 🎉🎉🎉 remove ads during npm installation -- 🐞 `Select` - - Fix the first issue of default activation [#3842](https://github.com/vueComponent/ant-design-vue/issues/3842) - - Fix group display abnormal problem [#3841](https://github.com/vueComponent/ant-design-vue/issues/3841) - - Fix scrolling abnormal issue after dynamically updating selections [#3972](https://github.com/vueComponent/ant-design-vue/issues/3972) -- 🐞 Fix the issue that `Checkbox` triggers twice `update:checked` [#3838](https://github.com/vueComponent/ant-design-vue/issues/3838) -- 🌟 `Table` column group supports fixed [#3882](https://github.com/vueComponent/ant-design-vue/issues/3882) -- 🌟 `Table` column supports v-for [#3934](https://github.com/vueComponent/ant-design-vue/issues/3934) -- 🐞 Fix the problem that `Table` displays horizontal scroll bar on windows [6d33d6](https://github.com/vueComponent/ant-design-vue/commit/6d33d60d2bca98825f274e48bcc3badd1857f742) -- 🌟 `Form` scrollToFirstError supports option parameter passing [#3918](https://github.com/vueComponent/ant-design-vue/issues/3918) -- 🐞 Fix the issue of `Calendar` month selector displaying wrong characters [#3915](https://github.com/vueComponent/ant-design-vue/issues/3915) -- 🌟 Refactor the `Switch` component and remove the defaultChecked attribute [#3885](https://github.com/vueComponent/ant-design-vue/issues/3885) -- 🐞 Fix the process exception when using Vite [#3930](https://github.com/vueComponent/ant-design-vue/issues/3930) -- 🐞 Fix `Radio` shadow occlusion problem [#3955](https://github.com/vueComponent/ant-design-vue/issues/3955) -- 🐞 Fix the issue that span does not take effect in `Form` inline mode [#3862](https://github.com/vueComponent/ant-design-vue/issues/3862) -- 🐞 Fix the issue that `Cascader` keydown selection does not take effect [#958](https://github.com/vueComponent/ant-design-vue/issues/958) -- 🐞 Fix `Image` preview function failure problem [#3701](https://github.com/vueComponent/ant-design-vue/issues/3701) -- 🐞 Fix some TS type issues - -## 2.1.2 - -`2021-03-28` - -- 🌟 Recompile with Vue 3.0.9, compatible with 3.0.7 and below - -## 2.1.1 - -`2021-03-27` - -- 🌟 Compatible with Vue 3.0.8, note: Due to the destructive update of 3.0.8, 2.1.1 is not compatible with versions below 3.0.7 [vue#3493](https://github.com/vuejs/vue-next/issues /3493) -- 🐞 Fix Modal.confirm missing closable ts type [#3684](https://github.com/vueComponent/ant-design-vue/issues/3845) -- 🐞 Fix upload custom method not working issue [#3843](https://github.com/vueComponent/ant-design-vue/issues/3843) - -## 2.1.0 - -`2021-03-20` - -- 🎉🎉🎉 Added `Typography` component [#3807](https://github.com/vueComponent/ant-design-vue/issues/3807) -- 🌟 Modal method adds close icon customization [#3753](https://github.com/vueComponent/ant-design-vue/issues/3753) -- 🐞 Fix missing build files containing internationalization [#3684](https://github.com/vueComponent/ant-design-vue/issues/3684) -- 🐞 Fix Drawer error after destruction [#848d64](https://github.com/vueComponent/ant-design-vue/commit/848d6497e68c87566790dfa889a1913199a6699a) -- 🐞 Fix BackTop incorrect position when KeepAlive is activated [#3803](https://github.com/vueComponent/ant-design-vue/issues/3803) -- 🐞 Fix the problem that the TreeNode class does not take effect [#3822](https://github.com/vueComponent/ant-design-vue/issues/3822) -- 🐞 Fix Table tags being an array error issue [#3812](https://github.com/vueComponent/ant-design-vue/issues/3812) -- 🐞 Fix the sorting issue when Table custom filterIcon is triggered [#3819](https://github.com/vueComponent/ant-design-vue/issues/3819) -- 🐞 Fix Select style misalignment under Form [#3781](https://github.com/vueComponent/ant-design-vue/issues/3781) - -## 2.0.1 - -`2021-02-27` - -- 🌟 `Badge` adds `Ribbon` [#3681](https://github.com/vueComponent/ant-design-vue/issues/3681) -- 🌟 Adjust the trigger order of `SearchInput` search event [#3725](https://github.com/vueComponent/ant-design-vue/issues/3725) -- 🐞 Fix the stuck problem when `Table` is destroyed [#3531](https://github.com/vueComponent/ant-design-vue/issues/3531) -- 🐞 Fix the issue of less file introduced in `Menu` css [#3678](https://github.com/vueComponent/ant-design-vue/issues/3678) -- 🐞 Fix the problem of `Alert` custom icon misalignment [#3712](https://github.com/vueComponent/ant-design-vue/issues/3712) - -## 2.0.0 - -`2021-02-06` - -- 🎉🎉🎉 2.0 official version released -- 🎉🎉🎉 support dark theme [#3410](https://github.com/vueComponent/ant-design-vue/issues/3410) -- 🎉🎉🎉 The new version of the document is online, use the Composition API to completely reconstruct the document example, and provide the TS and JS dual version source code -- 🌟 Refactor the `Alert` component using Composition API [#3654](https://github.com/vueComponent/ant-design-vue/pull/3654) -- 🌟 `Tooltip` supports custom colors [#3603](https://github.com/vueComponent/ant-design-vue/issues/3603) -- 🐞 Fix the problem that `TimePicker` does not automatically scroll to the selected position [#ab7537](https://github.com/vueComponent/ant-design-vue/commit/ab75379f0c2f5e54ab7c348284a7391939ab5aaf) - -## 2.0.0-rc.9 - -`2021-01-24` - -- 🌟 `@ant-design/icons-vue` upgrade to 6.0, use es module by default -- 🌟 `Tabs` adds `centered` centered mode [#3501](https://github.com/vueComponent/ant-design-vue/issues/3501) -- 🐞 `Progress` Add opacity animation [#3505](https://github.com/vueComponent/ant-design-vue/issues/3505) -- 🐞 Fix an error when installing npm [#3515](https://github.com/vueComponent/ant-design-vue/issues/3515) -- 🐞 Fix the problem of `Breadcrumn` split line not displaying [#3522](https://github.com/vueComponent/ant-design-vue/issues/3522) -- 🐞 Fix `Radio` uncontrolled issue [#3517](https://github.com/vueComponent/ant-design-vue/issues/3517) -- 🐞 Fix `FormItem` not wrapping issue [#3538](https://github.com/vueComponent/ant-design-vue/issues/3538) -- 🐞 Fix `Carousel` `pauseOnDotsHover` not working problem [#3519](https://github.com/vueComponent/ant-design-vue/issues/3519) -- 🐞 Fix `Input.Search` `class` not working issue [#3541](https://github.com/vueComponent/ant-design-vue/issues/3541) -- 🐞 Fix the issue that `InputNumber` triggers the change event multiple times under Microsoft input method [#3550](https://github.com/vueComponent/ant-design-vue/issues/3550) -- 🐞 Fix the problem that the keyboard can still be switched in the disabled state of `Tabs` [#3575](https://github.com/vueComponent/ant-design-vue/issues/3575) -- 🐞 Fix the issue that `Switch` does not take effect in the table [#3512](https://github.com/vueComponent/ant-design-vue/issues/3512) - -## 2.0.0-rc.8 - -`2021-01-07` - -- 🌟 Support Vite 2 [#3490](https://github.com/vueComponent/ant-design-vue/issues/3490) -- 🌟 Use Composition API to refactor Affix component [#3447](https://github.com/vueComponent/ant-design-vue/issues/3447) -- 🐞 Fix Image component type definition error [#3488](https://github.com/vueComponent/ant-design-vue/issues/3488) -- 🐞 Upgrade icons-vue Fix IconFont component type error [#3474](https://github.com/vueComponent/ant-design-vue/issues/3474) -- 🐞 Fix Tooltip arrow style error in less 4 [#3477](https://github.com/vueComponent/ant-design-vue/issues/3477) -- 🐞 Fix DatePicker type definition parsing error under Vue 3.0.5 [#bf7c62](https://github.com/vueComponent/ant-design-vue/commit/bf7c62f457fc14624881f69c5baf9a62219383f7) - -## 2.0.0-rc.7 - -`2020-12-28` - -- 🐞 Fix Switch `change`、`click` not work [#3453](https://github.com/vueComponent/ant-design-vue/issues/3453) - -## 2.0.0-rc.6 - -`2020-12-27` - -- 🌟 Support Less 4 [#3449](https://github.com/vueComponent/ant-design-vue/issues/3449) -- 🌟 Added Image component [#3235](https://github.com/vueComponent/ant-design-vue/issues/3235) -- 🌟 Functional component, add displayName attribute [#3445](https://github.com/vueComponent/ant-design-vue/issues/3445) -- 🐞 Message adds custom class style function [#3443](https://github.com/vueComponent/ant-design-vue/issues/3443) -- 🐞 Fix the initial disabled state of the Tabs component does not take effect [#3366](https://github.com/vueComponent/ant-design-vue/issues/3366) -- 🐞 Fix Slider accuracy issue [#3346](https://github.com/vueComponent/ant-design-vue/issues/3346) -- 🐞 Fix the incorrect scroll height of Select [#3419](https://github.com/vueComponent/ant-design-vue/issues/3419) -- 🐞 Fix the problem that Input small is too small and the height is 2px [#3396](https://github.com/vueComponent/ant-design-vue/issues/3396) -- 🐞 Fix the problem that TreeSelect triggers two change events -- 🐞 Fix the endless loop problem of TreeSelect defining title through slot -- 🐞 Fix the problem that Drawer handle slot triggers two click events -- 🌟 Added Checkbox and Switch event declaration - -## 2.0.0-rc.5 - -`2020-12-13` - -- 🐞 Fix the undefined warning problem of this.dom output in the Drawer component console -- 🐞 Fix Menu in Vue 3.0.3 and above versions, display confusion problem [#3354](https://github.com/vueComponent/ant-design-vue/issues/3354) - -## 2.0.0-rc.4 - -`2020-12-10` - -- 🌟 Input.Password supports custom icons [#3320](https://github.com/vueComponent/ant-design-vue/issues/3320) -- 🐞 Fix the issue that the Select Option click event does not trigger [#4ea00d](https://github.com/vueComponent/ant-design-vue/commit/4ea00d3a70d0afd7bea07f814df03ab7d0b25ebd) -- 🐞 Fix the problem that the dark theme does not work after the Menu exceeds the width [#10f35a](https://github.com/vueComponent/ant-design-vue/commit/10f35a1fa510de91e9484b07fcfff253920cee29) -- 🐞 Fix Menu console vue key some waring [#520d6a](https://github.com/vueComponent/ant-design-vue/commit/520d6a5e85eb391e5294211c9d7b2ea598c59119) -- 🐞 Remove console passive prompt log [#8d1669](https://github.com/vueComponent/ant-design-vue/commit/8d1669b8896d84a67c61d3a00d0b13c42d70f30f) - -## 2.0.0-rc.3 - -`2020-12-05` - -- 🐞 Fix the problem of functional components reporting type errors in Vue 3.0.3 [#f5cf7e](https://github.com/vueComponent/ant-design-vue/commit/f5cf7e0920a51f0ac024046996c99260aa41becf) -- 🐞 Fix Menu display error after detecting width [#3262](https://github.com/vueComponent/ant-design-vue/issues/3262) -- 🐞 Fix Menu subMenuOpenDelay subMenuCloseDelay not working problem [#3291](https://github.com/vueComponent/ant-design-vue/pull/3291) -- 🐞 Fix TreeSelect stack overflow problem [#28aeea](https://github.com/vueComponent/ant-design-vue/commit/28aeea6f0b142ed68950a3738f7cf2c1581a7a5b) -- 🐞 Fix Input custom style class being overwritten [#3273](https://github.com/vueComponent/ant-design-vue/issues/3273) -- 🐞 Fix InputNumber parse error in production environment [#3249](https://github.com/vueComponent/ant-design-vue/issues/3249) - -## 2.0.0-rc.2 - -`2020-11-24` - -- 🌟 Optimize Menu performance, enable lazy loading by default [#3243](https://github.com/vueComponent/ant-design-vue/pull/3243) -- 🌟 Tag supports defining icon via slot [#3185](https://github.com/vueComponent/ant-design-vue/pull/3185) -- 🌟 Small type table changed to borderless [#3221](https://github.com/vueComponent/ant-design-vue/issues/3221) -- 🌟 @ant-design/icons-vue upgraded to 5.1.6, support SSR, support spin attribute shorthand -- 🐞 Fix the style problem of Alert's close button in Safari [#3184](https://github.com/vueComponent/ant-design-vue/issues/3184) -- 🐞 Fix the problem of Notification top attribute type error [#3187](https://github.com/vueComponent/ant-design-vue/issues/3187) -- 🐞 Fix DirectoryTree custom icon does not take effect [#3183](https://github.com/vueComponent/ant-design-vue/issues/3183) -- 🐞 Fix Button loading delay not taking effect [#3194](https://github.com/vueComponent/ant-design-vue/issues/3194) -- 💄 Select optionFilterProp no longer supports filtering by children [#3204](https://github.com/vueComponent/ant-design-vue/issues/3204) -- 🐞 Fix Select labelInValue error when reporting [#3216](https://github.com/vueComponent/ant-design-vue/issues/3216) -- 🐞 Fix ConfigProvider transformCellText missing issue [#3206](https://github.com/vueComponent/ant-design-vue/issues/3206) -- 🐞 Fix the style disorder problem when Dropdown Button is mixed together [#3244](https://github.com/vueComponent/ant-design-vue/issues/3244) -- 🐞 Fix RangePicker custom width invalidation issue [#3244](https://github.com/vueComponent/ant-design-vue/issues/3245) -- 🐞 Fix multiple errors or missing Ts types - -## 2.0.0-rc.1 - -`2020-11-14` - -- 🎉🎉🎉 -- 🌟 Menu cancel the default lazy loading, improve the first animation effect, optimize the Bezier curve function, and make it smoother [#3177](https://github.com/vueComponent/ant-design-vue/pull/3177) -- 🐞 Fix Select search function failure problem [#3144](https://github.com/vueComponent/ant-design-vue/issues/3144) -- 🐞 Fix the Drawer component does not have automatic focus, which can not be closed directly by the ESC button [#3148](https://github.com/vueComponent/ant-design-vue/issues/3148) -- 🐞 Fix the incorrect position of popover elements in Popover [#3147](https://github.com/vueComponent/ant-design-vue/issues/3147) -- 🐞 Fix CountDown not updating problem [#3170](https://github.com/vueComponent/ant-design-vue/pull/3170) -- 🐞 Fix multiple errors or missing Ts types - -## 2.0.0-beta.15 - -`2020-11-08` - -- 🌟 Optimize the Menu animation to make it smoother [#3095](https://github.com/vueComponent/ant-design-vue/issues/3095) -- 🌟 Optimize VirtualList to avoid invalid render [#2e61e9](https://github.com/vueComponent/ant-design-vue/commit/2e61e9cb502f2bb6910f59abfb483fd2517e594f) -- 🐞 Fix Menu overflowedIndicator not taking effect [#689113](https://github.com/vueComponent/ant-design-vue/commit/689113b3c9c19e929607567a4c8252c6511bff5c) -- 🐞 Select - - Fix the issue that dropdownRender does not support slot [#3098](https://github.com/vueComponent/ant-design-vue/issues/3098) - - Fix the issue of abnormal empty values ​​in tag mode [#3100](https://github.com/vueComponent/ant-design-vue/issues/3100) - - Fix the problem that the selected item is not updated in single selection mode [#3099](https://github.com/vueComponent/ant-design-vue/issues/3099) - - Fix foucs status not taking effect in special scenarios [#3099](https://github.com/vueComponent/ant-design-vue/issues/3099) -- 🐞 Fix DatePicker default formatting invalid problem [#3091](https://github.com/vueComponent/ant-design-vue/issues/3091) -- 🐞 Fix Table customRow configuration event not taking effect [#3121](https://github.com/vueComponent/ant-design-vue/issues/3121) -- 🐞 Fix the style of TreeSelect search box [ee4cd3c](https://github.com/vueComponent/ant-design-vue/commit/ ee4cd3c35a84658cbbb148ce368bc247a927d528) -- 🐞 Fix Ts type error or missing problem - -## 2.0.0-beta.13 - -`2020-11-02` - -- 🐞 Fix npm install error report [#3080](https://github.com/vueComponent/ant-design-vue/issues/3080) -- 🐞 Fix Select maxPlaceHolder display error problem [#3085](https://github.com/vueComponent/ant-design-vue/issues/3085) -- 🐞 Fix the pop-up component, the pop-up position is not updated [#3085](https://github.com/vueComponent/ant-design-vue/issues/3085) -- 🐞 Fix the warning problem when Table data is empty [#3082](https://github.com/vueComponent/ant-design-vue/issues/3082) -- 🐞 Fix Input display multiple borders in Form [#3084](https://github.com/vueComponent/ant-design-vue/issues/3084) - -## 2.0.0-beta.12 - -`2020-11-01` - -- 🐞 Fix dist/antd.css missing component style issue [#3069](https://github.com/vueComponent/ant-design-vue/issues/3069) -- 🐞 Fix Input style issue [#3074](https://github.com/vueComponent/ant-design-vue/issues/3074) -- 🐞 Fix Form layout="vertical" style issue [#3075](https://github.com/vueComponent/ant-design-vue/issues/3075) -- 🐞 Fix Select cannot open popup problem [#3070](https://github.com/vueComponent/ant-design-vue/issues/3070) - -## 2.0.0-beta.11 - -`2020-10-30` - -- 🎉🎉🎉 Refactored Select and AutoComplete components, supports virtual lists, and greatly improves performance -- 🔥🔥🔥 Use Typescript to refactor all components, type support is more friendly -- 🔥 Optimize the underlying animation components, with better performance and smoother -- 🌟 Textarea component added showCount to support word count function -- 🌟 Recursive Menu component, supports arbitrary nesting of other elements [#1452](https://github.com/vueComponent/ant-design-vue/issues/1452) -- 🇮🇪 Add Irish language internationalization support -- 🐞 Fix webpack 5 compatibility issues. -- 🐞 Fix the problem that the Upload method attribute does not take effect [#2837](https://github.com/vueComponent/ant-design-vue/issues/2837) -- 🐞 Fix Table component filter not supporting number type problem [#3052](https://github.com/vueComponent/ant-design-vue/issues/3052) -- 🐞 Fix Table fixed column ellipsis not working issue [#2916](https://github.com/vueComponent/ant-design-vue/issues/2916) -- 🐞 Fix Table custom expandIcon not taking effect [#3013](https://github.com/vueComponent/ant-design-vue/issues/3013) -- 🐞 Fix the problem that TreeSelect cannot customize slot [#2827](https://github.com/vueComponent/ant-design-vue/issues/2827) -- 🛎 Change Avatar's srcSet to srcset - -## 2.0.0-beta.10 - -`2020-09-24` - -- 🌟 Update Vue dependency to release version -- 🐞 Fix the problem that Menu does not collapse in Layout [#2819](https://github.com/vueComponent/ant-design-vue/issues/2819) -- 🐞 Fix a warning issue when switching Tabs [#2865](https://github.com/vueComponent/ant-design-vue/issues/2865) -- 🐞 Fix the problem that the input box does not trigger the change event when compositionend -- 🐞 Fix the problem that the Upload button does not disappear [#2884](https://github.com/vueComponent/ant-design-vue/issues/2884) -- 🐞 Fix upload custom method not working issue [#2837](https://github.com/vueComponent/ant-design-vue/issues/2837) -- 🐞 Fix some ts type errors - -## 2.0.0-beta.8 - -- 🐞 Fix ts types error - -## 2.0.0-beta.7 - -- 🐞 Fix the problem that Descriptions Item does not support v-for [#2793](https://github.com/vueComponent/ant-design-vue/issues/2793) -- 🐞 Fix Modal button loading effect not working problem [9257c1](https://github.com/vueComponent/ant-design-vue/commit/9257c1ea685db4339239589153aee3189d0434fe) -- 🐞 Fix the problem that the Steps component cannot be clicked when using v-model [ec7309](https://github.com/vueComponent/ant-design-vue/commit/ec73097d9b6ea8e2f2942ac28853c19191ca3298) -- 🌟 Checkbox, Radio add event declaration -- 🐞 Fix ts type error [802446](https://github.com/vueComponent/ant-design-vue/commit/8024469b8832cfc4fe85498b639bfb48820531aa) - -## 2.0.0-beta.6 - -- 🐞 Fix the problem that TreeSelectNode subcomponent TreeSelectNode is not registered - -## 2.0.0-beta.5 - -- 🔥 Support Vite. - -## 2.0.0-beta.4 - -- 🌟 Remove polyfills that are no longer used -- 🐞 Fix the problem of calling `Modal` afterClose twice -- 🐞 Supplement the declaration that ts type files lack native attributes - -## 2.0.0-beta.3 - -- 🔥 Support Typescript. -- 🔥 Added `Space` component. -- 🐞 Fix the problem that some components cannot use css scope [4bdb24](https://github.com/vueComponent/ant-design-vue/commit/4bdb241aa674b50fafa29b3b98e291643f2a06cc). -- 🐞 Fix `List.Meta` registration failure problem [03a42a](https://github.com/vueComponent/ant-design-vue/commit/03a42a5b35e7d42a39aedb1aba8346995be2c27e) -- 🐞 Fix the problem of misalignment in the fixed column of Table [#1493](https://github.com/vueComponent/ant-design-vue/issues/1493) -- 🐞 Fix the problem that the `Button` is not vertically centered [bd71e3](https://github.com/vueComponent/ant-design-vue/commit/bd71e3806b73881f9a95028982d17a10b2cd0b5c) -- 🐞 Fix `Tabs` multiple departure `change` event issue [8ed937](https://github.com/vueComponent/ant-design-vue/commit/8ed937344a57142a575e5272f50933c9c4459a43) - -## 2.0.0-beta.2 +### 🔥🔥🔥 4.0 official version released 🔥🔥🔥 ### Design specification adjustment -- Adjust the row height from `1.5`(`21px`) to `1.5715`(`22px`). -- Basic round corner adjustment, changed from `4px` to `2px`. -- The color brightness of the dividing line is reduced, from `#E8E8E8` to `#F0F0F0`. -- The default background color of Table is changed from transparent to white. - -### Compatibility adjustment +- Basic rounded corner adjustment, changed from unified `2px` to four-level rounded corners, which are `2px` `4px` `6px` `8px` respectively, which are applied to different scenarios, for example, the rounded corners of the default size Button are adjusted to `6px`. +- Main color adjustment, changed from `#1890ff` to `#1677ff`. +- Overall shadow adjustment, from the original three-level shadow adjustment to two levels, which are used for resident page components (such as Card) and interactive feedback (such as Dropdown). +- Adjust the internal spacing of some components. +- Overall de-wireframing. -- The minimum supported version of Vue is Vue 3.0. +### Add 5 new components -#### Adjusted API +- Segmented segment controller +- WaterMark watermark +- QrCode QR code +- FloatButton floating button +- Tour roaming guide -- Removed LocaleProvider, please use `ConfigProvider` instead. -- Removed the afterClose property of Tag. -- Merged FormModel and Form, see the Form refactoring part below for details. -- `tabIndex`, `maxLength`, `readOnly`, `autoComplete`, `autoFocus` are changed to all lowercase. -- In order to use the slot more friendly in template syntax, all related to xxxRender, renderXxxx are changed to single parameter, involving `itemRender`, `renderItem`, `customRender`, `dropdownRender`, `dateCellRender`, `dateFullCellRender`, `monthCellRender`, `monthFullCellRender`, `renderTabBar`. -- All the places where scopedSlots are configured are changed to slots. -- `{ on, props, attrs, ... }` configuration is flattened, such as `{ props: {type:'xxx'}, on: {click: this.handleClick}}` changed to `{ type: 'xxx', onClick: this.handleClick }`, related fields: `okButtonProps`, `cancelButtonProps`. -- Change xxx.sync to v-model:xxx -- v-model is changed to v-model:xxx, which specifically involves components: +### Technical adjustments - - The components changed from v-model to v-model:checked are: CheckableTag, Checkbox, Switch - - The components changed from v-model to v-model:value are: Radio, Mentions, CheckboxGroup, Rate, DatePicker - - The components changed from v-model to v-model:visible are: Tag, Popconfirm, Popove, Tooltip, Moda, Dropdown - - The components changed from v-model to v-model:activeKey are: Collaps, Tabs - - The components changed from v-model to v-model:current are: Steps - - The components changed from v-model to v-model:selectedKeys are: Menu +- Deprecated less and adopted CSS-in-JS to better support dynamic themes. + - All less files are removed, and less variables no longer support leaking. + - css files are no longer included in the product. Since CSS-in-JS supports importing on demand, the original `ant-design-vue/dist/antd.css` has also been removed. If you need to reset some basic styles, please import `ant-design-vue/dist/reset .css`. + - If you need to reset the style of the component and don't want to introduce `ant-design-vue/dist/reset.css` to pollute the global style, you can try to use [App component](/components/app), to solve the problem that native elements do not have ant-design-vue specification style. +- Removed css variables and dynamic theme schemes built on top of it. +- LocaleProvider has been deprecated in 3.x (use `` instead), we have completely removed the related directories `ant-design-vue/es/locale-provider`, `ant- design-vue/lib/locale-provider`. +- `babel-plugin-import` is no longer supported, CSS-in-JS itself has the ability to load on demand, no longer need plug-in support. -#### Icon Upgrade +#### Component API adjustments -In `ant-design-vue@1.2.0`, we introduced the svg icon ([Why use the svg icon?](https://github.com/ant-design/ant-design/issues/10353)). The icon API that uses string naming cannot be loaded on demand, so the svg icon file is fully introduced, which greatly increases the size of the packaged product. In 2.0, we adjusted the icon usage API to support tree shaking, reducing the default package size by approximately 150 KB (Gzipped). +- The classname API of the component popup is unified to `popupClassName`, and similar APIs such as `dropdownClassName` will be replaced. -The old way of using Icon will be obsolete: + - AutoComplete component + - Cascader component + - Select component + - TreeSelect component + - TimePicker component + - DatePicker component + - Mentions component -```html - - -``` +- The controlled visibility API of the component popup is unified as `open`, and `visible` and other similar APIs will be replaced. + - Drawer component `visible` becomes `open`. + - Modal component `visible` becomes `open`. + - Dropdown component `visible` becomes `open`. + - Tooltip component `visible` becomes `open`. + - Tag component `visible` has been removed. + - Slider component `tooltip` related API converges to `tooltip` property. + - Table component `filterDropdownVisible` changed to `filterDropdownOpen`. +- `getPopupContainer`: All `getPopupContainer` needs to ensure that the returned div is unique. +- Drawer `style` and `class` are migrated to the Drawer popup area, and the original attributes are replaced by `rootClassName` and `rootStyle`. -In 2.0, an on-demand introduction method will be adopted: +#### Component refactoring and removal -```html - - -``` +- Remove the `locale-provider` directory. `LocaleProvider` has been removed in v4, please use `ConfigProvider` instead. -#### Component refactoring +- Remove `xxxl` breakpoint attribute in grid layout. `xxxl` attribute has been removed in v4, you can use [theme customization](/docs/vue/customize-theme) to modify `screen[XS|SM|MD|LG|XL|XXL]` to modify the break Point value achieved. -In 1.x, we provide two form components, Form and FormModel. The original Form component uses v-decorator for data binding. In Vue2, we use context to force update components. However, in Vue3, due to the introduction of patchFlag, etc. Optimization method, forced refresh will destroy the performance advantage brought by patchFlag. So in version 2.0, we merged Form and FormModel, retained the use of FormModel, enriched related functions, and renamed it to Form. +- The BackTop component was deprecated in `4.0.0` and moved to the FloatButton floating button. If needed, it can be imported from FloatButton. -Involving changes: +### [Upgrade Guide](/docs/vue/migration-v4) -- Added `scrollToFirstError`, `name`, `validateTrigger` properties for Form, added `finish`, `finishFailed` events, and added `scrollToField` method. -- Form.Item adds `validateFirst`, `validateTrigger`, and discards the `prop` attribute, and replaces it with `name`. -- The nested field path uses an array. In the past version, we used. To represent the nested path (such as user.name to represent {user: {name:''} }). However, in some back-end systems, the variable name will also carry .. This causes users to need additional codes for conversion. Therefore, in the new version, nested paths are represented by arrays to avoid wrong handling behaviors (such as ['user','name']). -- validateFields no longer supports callback. validateFields will return a Promise object, so you can perform corresponding error handling through async/await or then/catch. It is no longer necessary to determine whether errors is empty: +## 3.x -```js -// v1 -// eslint-disable-next-line no-undef,no-unused-vars -validateFields((err, value) => { - if (!err) { - // Do something with value - } -}); -``` +Visit [GitHub](https://github.com/vueComponent/ant-design-vue/blob/3.x/CHANGELOG.zh-CN.md) `3.x` Change Log。 -Change to +## 2.x -```js -// v2 -// eslint-disable-next-line no-undef,no-unused-vars -validateFields().then(values ​​=> { - // Do something with value -}); -``` +Visit [GitHub](https://github.com/vueComponent/ant-design-vue/blob/2.x/CHANGELOG.zh-CN.md) `2.x` Change Log。 ## 1.x diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 02be1e765..008831f95 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -10,1334 +10,76 @@ --- -## 3.2.20 +## 4.0 -`2023-04-27` - -- 🌟 优化 Space 子组件重复实例化问题 [#6500](https://github.com/vueComponent/ant-design-vue/issues/6500) -- 🐞 修复 RangePicker 不支持空值问题 [#6510](https://github.com/vueComponent/ant-design-vue/issues/6510) - -## 3.2.19 - -`2023-04-23` - -- 🐞 修复 antd.min.js 文件错误 - -## 3.2.18 - -`2023-04-23` - -- 🐞 修复 input addonAfter 在 Form diabled 时的样式 [#6403](https://github.com/vueComponent/ant-design-vue/issues/6403) -- 🐞 修复 Upload 类名错误 [#6413](https://github.com/vueComponent/ant-design-vue/issues/6413) -- 🐞 修复日期组件的 周、季度 不支持 format 问题 [#6385](https://github.com/vueComponent/ant-design-vue/issues/6385) -- 🐞 修复 Select 在 Firefox 下滚动显示异常问题 [#6470](https://github.com/vueComponent/ant-design-vue/issues/6470) -- 🌟 Button 新增 focus、blur 方法 [#6483](https://github.com/vueComponent/ant-design-vue/issues/6483) -- 🐞 修复 Select 选中后导致容器高度变化问题 [#6467](https://github.com/vueComponent/ant-design-vue/issues/6467) -- 🐞 修复 Form name 未生效问题 [#6460](https://github.com/vueComponent/ant-design-vue/issues/6460) - -## 3.2.17 - -`2023-04-04` - -- 🐞 回滚 [#6324](https://github.com/vueComponent/ant-design-vue/issues/6324),解决引起的 Table 过滤器隐藏问题 [#6400](https://github.com/vueComponent/ant-design-vue/issues/6400) - -## 3.2.16 - -`2023-03-23` - -- 🐞 修复 notification close 事件多次触发问题 [#6150](https://github.com/vueComponent/ant-design-vue/issues/6150) -- 🐞 修复轮播图响应式变化问题 [#6100](https://github.com/vueComponent/ant-design-vue/issues/6100) -- 🐞 修复 Table 吸顶滚动条样式错误问题 [#6169](https://github.com/vueComponent/ant-design-vue/issues/6169) -- 🐞 修复 DatePicker disabledMinutes 参数错误 [#6233](https://github.com/vueComponent/ant-design-vue/issues/6233) -- 🐞 修复 Popup 关闭时没有触发 visibleChange 事件问题 [#6324](https://github.com/vueComponent/ant-design-vue/issues/6324) -- 🐞 修复 Image 预览图片错误问题 [#6331](https://github.com/vueComponent/ant-design-vue/issues/6331) - -## 3.2.15 - -`2022-11-10` - -- 🐞 修复 `Image` 动态删除时,预览图片错误问题 - -## 3.2.14 - -`2022-11-07` - -- 🐞 修复自定义 `prefixCls` 时,动态主题失效问题 [#6063](https://github.com/vueComponent/ant-design-vue/issues/6063) -- 🐞 修复 `DatePicker` 使用 select 等弹窗组件作为插槽时,报错问题 [#6062](https://github.com/vueComponent/ant-design-vue/issues/6062) -- 🐞 修复 `DirectoryTree` 未暴露 scrollTo 方法 [#6067](https://github.com/vueComponent/ant-design-vue/issues/6067) -- 🐞 修复 `RangePicker` 弹窗位置不改变问题 [#6073](https://github.com/vueComponent/ant-design-vue/issues/6073) - -## 3.2.13 - -`2022-10-08` - -- 🌟 支持 Vue 3 升级工具 `@vue/compat` [#5973](https://github.com/vueComponent/ant-design-vue/issues/5973) -- 🌟 Cascader 添加 tagRender 插槽 [#5954](https://github.com/vueComponent/ant-design-vue/issues/5954) -- 🐞 修复 Image 预览关闭时,图片闪动问题 [#5955](https://github.com/vueComponent/ant-design-vue/issues/5955) -- 🐞 修复 Tag 关闭图标样式显示错位 [#5956](https://github.com/vueComponent/ant-design-vue/issues/5956) -- 🐞 修复 Table loading 属性 ts 类型错误 [#5964](https://github.com/vueComponent/ant-design-vue/issues/5964) -- 🐞 修复 Transfer 删除异常问题 [#5975](https://github.com/vueComponent/ant-design-vue/issues/5975) -- 🐞 修复 Table 固定列的滚动阴影显示问题 [#5996](https://github.com/vueComponent/ant-design-vue/issues/5996) -- 🐞 修复 DirectoryTree 在自定义 fieldNames 时,默认展开失效问题 [#6007](https://github.com/vueComponent/ant-design-vue/issues/6007) - -## 3.2.12 - -`2022-09-02` - -- 🐞 修复 DescriptionItem labelStyle 不生效问题 [#5920](https://github.com/vueComponent/ant-design-vue/issues/5920) -- 🌟 Typography 复制按钮阻止冒泡 [##5746](https://github.com/vueComponent/ant-design-vue/issues/5746) -- 🐞 修复 table 合并列滚动阴影遮挡问题 [#5786](https://github.com/vueComponent/ant-design-vue/issues/5786) -- 🐞 修复 css var 和 ConfigProvider 变量不一致问题 [#5929](https://github.com/vueComponent/ant-design-vue/issues/5929) - -## 3.2.11 - -`2022-08-08` - -- 🐞 修复 CDN 引入组件库时,dayjs 报错问题 [#5874](https://github.com/vueComponent/ant-design-vue/issues/5874) -- 🐞 修复 `Dropdown` 子菜单换行问题 [#5798](https://github.com/vueComponent/ant-design-vue/issues/5798) -- 🐞 修复图标引入打包体积增大问题 [#5822](https://github.com/vueComponent/ant-design-vue/issues/5822) -- 🐞 修复 `Select` 自定义字段时,没有自动聚焦选中节点问题 [#5843](https://github.com/vueComponent/ant-design-vue/issues/5843) -- 🐞 修复 `InputNumber` size=large 时, 样式未对齐问题 [#5853](https://github.com/vueComponent/ant-design-vue/issues/5853) - -## 3.2.10 - -`2022-07-07` - -- 🐞 修复在 `process.env.NODE_ENV = 'test'` 下弹窗类组件无法使用问题 [#4565](https://github.com/vueComponent/ant-design-vue/issues/4565) -- 🐞 修复 Menu 组件在快速 hover 弹出层时,弹出层直接关闭问题 [36df58](https://github.com/vueComponent/ant-design-vue/commit/36df585acf9a7d53c8b50be2ab240f54588a3b20) -- 🐞 修复 Input autosize 类型错误 [#5766](https://github.com/vueComponent/ant-design-vue/issues/5766) -- 🐞 修复 Table ellipsis tilte 在 fixed 下失效问题 [#5755](https://github.com/vueComponent/ant-design-vue/issues/5755) - -## 3.2.9 - -`2022-06-25` - -- 🐞 修复 Select 边缘位置关闭时闪动问题 [8a659d](https://github.com/vueComponent/ant-design-vue/commit/8a659da84fb8c44620fa279d9d6d73d6bd93d1f7) - -## 3.2.8 - -`2022-06-24` - -- 🌟 Image 新增滚轮放大缩小 [#5703](https://github.com/vueComponent/ant-design-vue/issues/5703) -- 🌟 ConfigProvider.config 新增 getPopupContainer [62dc24](https://github.com/vueComponent/ant-design-vue/commit/62dc2402f37c0ca0514f5b8fbb363247f0216bb2) -- 🐞 Upload tooltip 不展示问题 [#5714](https://github.com/vueComponent/ant-design-vue/issues/5714) -- 🐞 Row gutter 属性类型错误 [#5725](https://github.com/vueComponent/ant-design-vue/issues/5725) -- 🐞 Typography 是否可编辑切换后,状态未重置问题 [#5743](https://github.com/vueComponent/ant-design-vue/issues/5743) -- 🐞 DirectoryTree 多选模式下,点击时应该选中单个节点(多选只有配合 ctrl、shift 按键时选中多个节点) [#5732](https://github.com/vueComponent/ant-design-vue/issues/5732) - -## 3.2.7 - -`2022-06-13` - -- 🌟 `Checkbox` 支持添加额外属性 [#5678](https://github.com/vueComponent/ant-design-vue/issues/5678) -- 🌟 `RadioGroup` 支持全局 size [#5690](https://github.com/vueComponent/ant-design-vue/issues/5690) -- 🌟 `Table` expandedRowKeys 支持 v-model [#5695](https://github.com/vueComponent/ant-design-vue/issues/5695) -- 🐞 修复全局 Form message 未生效问题 [#5693](https://github.com/vueComponent/ant-design-vue/issues/5693) -- 🐞 修复 `Typography` 回车键触发两次 end 事件问题,blur 时不再触发 end [#5696](https://github.com/vueComponent/ant-design-vue/issues/5696) - -## 3.2.6 - -`2022-06-07` - -- 🌟 `Cascader` 自定义 trigger 支持自定义组件 [#5677](https://github.com/vueComponent/ant-design-vue/issues/5677) -- 🐞 修复 `DateRangePicker` `TimeRangePicker` 箭头没有跟随移动问题 [#71c619](https://github.com/vueComponent/ant-design-vue/commit/71c6195771c0b9ddffadd294ce01f7515c5adc40) -- 🐞 修复 `TimeRangePicker` minSteps、hourSteps、secondStep 未生效问题 [#5671](https://github.com/vueComponent/ant-design-vue/issues/5671) - -## 3.2.5 - -`2022-05-26` - -- 🌟 Image 新增左右箭头切换功能 [#5642](https://github.com/vueComponent/ant-design-vue/issues/5642) -- 🐞 修复 Steps progressDot 插槽失效问题 [#5648](https://github.com/vueComponent/ant-design-vue/issues/5648) -- 🐞 修复 Tooltip 全局 getPopupContainer 失效问题 [#5636](https://github.com/vueComponent/ant-design-vue/issues/5636) -- 🐞 修复 useForm help 样式问题 [#5635](https://github.com/vueComponent/ant-design-vue/issues/5635) -- 🐞 修复 Table、Tree 拖拽样式冲突问题 [#5644](https://github.com/vueComponent/ant-design-vue/issues/5644) - -## 3.2.4 - -`2022-05-23` - -- 🐞 修复 InputNumber v-model 类型错误 [#5577](https://github.com/vueComponent/ant-design-vue/issues/5677) -- 🌟 Select 支持全局 size [#5590](https://github.com/vueComponent/ant-design-vue/issues/5590) -- 🐞 Form clearValidate resetValidate 支持数组 [#5619](https://github.com/vueComponent/ant-design-vue/issues/5619) -- 🐞 Drawer 自定义 closeIcon 不生效问题 [#5616](https://github.com/vueComponent/ant-design-vue/issues/5616) -- 🌟 修复 CountDown 支持 dayjs [#5edca6](https://github.com/vueComponent/ant-design-vue/commit/5edca6be5a4e1aee9cde46724b14900f6c86bfb2) -- 🌟 Tree 支持 scrollTo [#5626](https://github.com/vueComponent/ant-design-vue/issues/5626) -- 🐞 Tooltip disabled 类名错误问题 [#5627](https://github.com/vueComponent/ant-design-vue/issues/5627) - -## 3.2.3 - -`2022-05-05` - -- 🌟 优化 `Tree` 性能 [#5551](https://github.com/vueComponent/ant-design-vue/issues/5551) -- 🐞 修复 `Progress` `type='dashboard'` 失效问题 [#5549](https://github.com/vueComponent/ant-design-vue/issues/5549) -- 🐞 修复 `Table` customRender 返回 `Fragment` 组件时,控制台 warning 问题 [#5556](https://github.com/vueComponent/ant-design-vue/issues/5556) -- 🐞 修复 `Card` 插槽为空时,渲染多余 dom 节点问题 - -## 3.2.2 - -`2022-04-26` - -- 🐞 修复 `Table` 吸顶死循环问题 [#5543](https://github.com/vueComponent/ant-design-vue/issues/5543) - -## 3.2.1 - -`2022-04-25` - -- 🌟 `Image` previewMask 支持 `false`、`function` [#5531](https://github.com/vueComponent/ant-design-vue/issues/5531) -- 🌟 `Select` option 添加 title -- 🌟 `Table` 优化拖拽手柄,防止拖拽时触发排序、筛选等 -- 🐞 修复 `Select` 选中选项后,触发 search 事件问题 [#5537](https://github.com/vueComponent/ant-design-vue/issues/5537) -- 🐞 修复 SSR 内存泄漏问题 [#5502](https://github.com/vueComponent/ant-design-vue/issues/5502) -- 🐞 修复 `Table` expandFixed ts 类型错误 [#5539](https://github.com/vueComponent/ant-design-vue/issues/5539) - -#### 文档: - -- 🌟 新增 Modal 拖拽示例 [体验](https://www.antdv.com/components/modal#components-modal-demo-modal-render) - -## 3.2.0 - -`2022-04-19` - -- 🌟 `InputNumber` 支持 lazy 修饰符 -- 🌟 `Image` 新增 `previewMask` 属性, `error`事件 [#5479](https://github.com/vueComponent/ant-design-vue/issues/5479) -- 🌟 `Modal` style 支持字符串类型 [#5449](https://github.com/vueComponent/ant-design-vue/issues/5449) -- 🌟 `Cascader` 支持 `clearIcon`、`removeIcon` 插槽 -- 🌟 优化 `DatePicker` 面板切换逻辑 [#5488](https://github.com/vueComponent/ant-design-vue/issues/5488) -- 🐞 修复 `Cascader` 没有自动修正弹窗位置 [#5482](https://github.com/vueComponent/ant-design-vue/issues/5482) -- 🐞 `Tabs` left、right 方向禁止动画 [#5464](https://github.com/vueComponent/ant-design-vue/issues/5464) -- 🐞 `TimeRangePicker` value ts type 支持 string -- 🐞 `Tree` 支持深度监听 [#5480](https://github.com/vueComponent/ant-design-vue/issues/5480) -- 🐞 修复 `Table` 在 keepalive 激活时未显示虚拟滚动条 -- 🐞 修复 `Input` size warning [#5508](https://github.com/vueComponent/ant-design-vue/issues/5508) - -## 3.1.1 - -`2022-04-06` - -- 🌟 优化 `Form` rule 类型提示 [#5439](https://github.com/vueComponent/ant-design-vue/issues/5439) -- 🐞 修复虚拟滚动相关组件动态更新内容时,高度计算错误问题 [4a4670](https://github.com/vueComponent/ant-design-vue/commit/4a4670bdce9e1043348fd741ec7a262ba2413a3a) - -## 3.1.0 - -`2022-04-06` - -### 🔥🔥🔥 3.1.0 正式版发布 🔥🔥🔥 - -- 🐞 修复 `Select.Option` 子元素为空的时候,报错问题 [272430](https://github.com/vueComponent/ant-design-vue/commit/272430ba06e44e06eb07694d6aef4d474fb741cb) - -## 3.1.0-rc.6 - -`2022-04-01` - -- 🌟 优化 `Table` 性能,减少 hover 时 render 次数 [900464](https://github.com/vueComponent/ant-design-vue/commit/900464473823277e4b06ace1c1ddc69ab3ef21d9) -- 🐞 修复 `Tabs` 在设置 addIcon 时,未折叠问题 [669b22](https://github.com/vueComponent/ant-design-vue/commit/669b22a54b33892c193dfd36150ae1ac2fb350dd) -- 🐞 修复 `Mentions` 组件无法选中问题 [#5432](https://github.com/vueComponent/ant-design-vue/issues/5432) -- 🐞 修复组件 focus、blur 事件未携带 event 参数,导致 popover 报错 [#5434](https://github.com/vueComponent/ant-design-vue/issues/5434) -- 🐞 修复 `Select.Option`,设置 Tooltip 时,报错问题 [#5307](https://github.com/vueComponent/ant-design-vue/issues/5307) - -## 3.1.0-rc.5 - -`2022-03-28` - -### 🔥🔥🔥 预计 3 月底或 4 月初发布正式版,届时 3.x 将成为默认版本,文档也将默认指向 3.x 文档 🔥🔥🔥 - -- 🌟 优化组件 ts 类型提示 [#5408](https://github.com/vueComponent/ant-design-vue/issues/5408) -- 🐞 修复 `Form` 无法滚动到嵌套字段 [#5404](https://github.com/vueComponent/ant-design-vue/issues/5404) -- 🐞 修复 `Table` 吸底滚动条响应式失效 [afd74c](https://github.com/vueComponent/ant-design-vue/commit/afd74c95d8ccd6ced5ce5f5c1a9abe3a398a0217) - -## 3.1.0-rc.4 - -`2022-03-25` - -### 🔥🔥🔥 预计 3 月底或 4 月初发布正式版,届时 3.x 将成为默认版本,文档也将默认指向 3.x 文档 🔥🔥🔥 - -- 🐞 修复 `Select` options 不支持 push 等变种方法 [#5398](https://github.com/vueComponent/ant-design-vue/issues/5398) -- 🐞 修复 `MenuItem` 自定义 icon 时,icon 原始类名丢失 [#5390](https://github.com/vueComponent/ant-design-vue/issues/5390) - -## 3.1.0-rc.3 - -`2022-03-24` - -### 🔥🔥🔥 预计 3 月底或 4 月初发布正式版,届时 3.x 将成为默认版本,文档也将默认指向 3.x 文档 🔥🔥🔥 - -- 🌟 优化 `Tree` `TreeSelect` 的搜索、点击性能 [#5365](https://github.com/vueComponent/ant-design-vue/issues/5365) -- 🌟 `Menu` selectedKeys、openKeys 支持深度 watch [7bf1e0](https://github.com/vueComponent/ant-design-vue/commit/7bf1e0dda1fe8f70f6c8b17ba90b217df2a75bd4) -- 🐞 修复 `Checkbox` `Radio` 一次点击触发两次 `click` 事件问题 [#5363](https://github.com/vueComponent/ant-design-vue/issues/5363) [#5389](https://github.com/vueComponent/ant-design-vue/issues/5389) -- 🐞 修复 `FormItem` `htmlFor` 属性失效问题 [#5384](https://github.com/vueComponent/ant-design-vue/issues/5384) -- 🐞 修复 `Upload` 限制数量时,最后一个上传被 abort 问题 [#5385](https://github.com/vueComponent/ant-design-vue/issues/5385) -- 🐞 修复 `RangePicker` `showTime`时, disabled 未考虑 time 问题 [#5286](https://github.com/vueComponent/ant-design-vue/issues/5286) -- 🐞 修复 `Layout.Sidebar` 响应式折叠后,无法展开问题 [#5373](https://github.com/vueComponent/ant-design-vue/issues/5373) -- 🐞 修复 `AutoComplete` 自定义 children 的 class 未挂载问题 [414e7a](https://github.com/vueComponent/ant-design-vue/commit/414e7a1c56455017dbc3780ce7b1b4abd0f1c4d7) -- 🐞 修复 `TimeRangePicker` disabledTime 未生效问题 [#5387](https://github.com/vueComponent/ant-design-vue/issues/5387) -- 🐞 修复 `Dropdown` 自动修正弹窗位置失效问题 [#5391](https://github.com/vueComponent/ant-design-vue/issues/5391) - -## 3.1.0-rc.2 - -`2022-03-19` - -### 🔥🔥🔥 预计 3 月底或 4 月初发布正式版,届时 3.x 将成为默认版本,文档也将默认指向 3.x 文档 🔥🔥🔥 - -### 🔥🔥🔥 Surely Vue 同步支持 css var 🔥🔥🔥 - -- 🌟 优化底层虚拟滚动组件,流畅滚动百万数据,涉及 `Select` `Tree` `TreeSelect` `AutoComplete` `Cascader` 组件 -- 🐞 修复 `Button` 组件切换 loading 时,动画未生效 [#5360](https://github.com/vueComponent/ant-design-vue/issues/5360) -- 🐞 修复 `Modal` 切换 loading 时,控制台报错问题 [#5361](https://github.com/vueComponent/ant-design-vue/issues/5361) - -## 3.1.0-rc.1 - -`2022-03-18` - -### 🔥🔥🔥 预计 3 月底或 4 月初发布正式版,届时 3.x 将成为默认版本,文档也将默认指向 3.x 文档 🔥🔥🔥 - -- 🌟 导出更多组件属性,方便二次开发 [#5340](https://github.com/vueComponent/ant-design-vue/issues/5340) [#5353](https://github.com/vueComponent/ant-design-vue/issues/5353) -- 🌟 `Timeline` 自定义颜色时可对 antd icon 生效 [2b81a7](https://github.com/vueComponent/ant-design-vue/commit/2b81a7213b169dc72f02c7e0f57afffd67333f0e) -- 🌟 `Radio` `Checkbox` 支持 number 类型 -- 🌟 `Popover` 在 slot 为空时,不展示弹窗 [71e110](https://github.com/vueComponent/ant-design-vue/commit/71e110036ea0339207c168f268907dcc0de277e8) -- 🌟 `Pagination` 支持响应式 [85197c](https://github.com/vueComponent/ant-design-vue/commit/85197c4b50a7aae95079bfaa700c8868ed36cbad) -- 🌟 调整 `Textarea` 超出最大长度后的截断逻辑(超出后文本不再变化)[d92921](https://github.com/vueComponent/ant-design-vue/commit/d929217752aac2dcfcd56852c7dbc3a834819de1) -- 🐞 修复 `Table` column 拖动手柄样式丢失问题 [#5348](https://github.com/vueComponent/ant-design-vue/issues/5348) -- 🐞 修复 `FormItem` 错误提示重复显示问题 [#5349](https://github.com/vueComponent/ant-design-vue/issues/5349) -- 🐞 修复 `FormItem` 不可单独使用问题 [#5343](https://github.com/vueComponent/ant-design-vue/issues/5343) -- 🐞 修复 `Switch` 在 loading 状态下的 `Tooltip` 不展示问题 [625eff](https://github.com/vueComponent/ant-design-vue/commit/625efff1fa8fb3c93a5c657538274fe76a4a4f1f) - -## 3.1.0-rc.0 - -`2022-03-15` - -### 🔥🔥🔥 预计 3 月底或 4 月初发布正式版,届时 3.x 将成为默认版本,文档也将默认指向 3.x 文档 🔥🔥🔥 - -- 🔥 支持 CSS variables,升级后如遇样式构建报错,请查看[动态主题文档](https://ant.design/docs/react/customize-theme-variable-cn)排查问题。 [体验](https://antdv.com/components/config-provider-cn/#components-config-provider-demo-theme) -- 🔥 支持 RTL [体验](https://antdv.com/components/config-provider-cn/#components-config-provider-demo-direction) -- 🌟 `LayoutSider` `trigger` 支持插槽 [#5317](https://github.com/vueComponent/ant-design-vue/issues/5317) -- 🌟 `Select` 新增 `filterSort` `virtual` `listHeight` 配置 [#5310](https://github.com/vueComponent/ant-design-vue/issues/5310) -- 🌟 `SubMenu` 新增 `popupOffset` [#5312](https://github.com/vueComponent/ant-design-vue/issues/5312) -- 🌟 `Affix` 新增 customIcon 插槽 [60e259](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/60e2597f7f80ca354acc859a832a71d1110b3f4c) -- 🌟 `Avatar` 新增 `crossOrigin` `maxPopoverTrigger` 属性 [5bdb45](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/5bdb45d6688700f0fcc10324c898cb114a1fa469) -- 🌟 `Button` 新增全局 size 支持 [16b3b5f](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/16b3b5fc366fcce155b4c37459a0b12f1031bfe6) -- 🌟 `DatePicker` 新增插槽 `prevIcon` `nextIcon` `superPrevIcon` `superNextIcon` [27e7ed](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/27e7ed68fb4331e9e9a07738c68f135820496dd9) -- 🌟 `Divider` 新增 `orientationMargin` [c528d7](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/c528d74c11dd323403705250b918e5408bce2c3c) -- 🌟 `Dropdown` 新增 `destroyPopupOnHide` `loading` [c4c691](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/c4c691b20777fe459a24a429b50e0fc8cdbdef85) -- 🌟 `Form` 新增 `labelWrap` [cb95d1](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/cb95d1202adce3375f73e55598cccea619a4d861) -- 🌟 `Input` 新增 `showCount` [85767d](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/85767de39688b5da6157df9317666adaad6e184f) -- 🌟 `InputNumber` 新增 `prefix` 插槽 [efea6b](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/efea6b000e581f9c71ba98f80febace4e024910c) -- 🌟 `MenuDivider` 新增 `dashed`虚线 [32fc4f](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/32fc4fc7c4f3913dec771a6a96b097bcda754b40) -- 🌟 `Modal` 方法使用方式新增 `wrapClassName` [d38ecc](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/d38ecce22c63adc5e8e52657fcbbef89e048b621) -- 🌟 `Modal.confirm` 新增 `showCancel` 以及 `propmise` 支持 [a041b5](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/a041b5bacea2f94f55fee358ff39e5abd0d1b39f) -- 🌟 新增 `Skeleton.Button` `Skeleton.Input` 两个子组件 [2bd5fc](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/2bd5fc15ffecf3cb3083accab02ceb97bd9ade38) -- 🌟 `Spin` 支持 `tip` 插槽 [93a06a](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/93a06a45f58c0920e8f43c49c9859ce4ca10c94e) -- 🌟 `Table` 新增 `filterMode` 支持树形筛选 [79ff7a](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/79ff7ac2dba4ab5cf01241ceef072f2c4be20e12) -- 🌟 `Typography` 新增 `enterIcon` 插槽、`triggerType` 属性 [e777bc](https://github.com/vueComponent/ant-design-vue/pull/5327/commits/e777bc17435b2610a0c0e1c29f60b900bcaab03c) -- 🐞 修复 `DatePicker` 使用字符串模式时,控制台输出类型错误问题 [#5323](https://github.com/vueComponent/ant-design-vue/issues/5323) -- 🐞 修复 `TreeSelect` 子节点全部 disabled 后,父节点无法选中问题 [#5316](https://github.com/vueComponent/ant-design-vue/issues/5316) -- 🐞 修复 `Row` `gutter` ts 类型提示错误问题 [2efe1a](https://github.com/vueComponent/ant-design-vue/commit/2efe1af6b66247b6bc89bf43bc3d2f1dc1f2a5d9) -- 🐞 修复 `Wave` 在自定义 prefixCls 时失效问题 [#5334](https://github.com/vueComponent/ant-design-vue/issues/5334) - -## 3.0.0-beta.13 - -`2022-03-04` - -- 🌟 优化 Menu overflow 后动画,避免闪动 -- 🐞 修复日期组件使用 dateFns 时,输入不合法值时自动 parse 问题 [#5302](https://github.com/vueComponent/ant-design-vue/issues/5302) -- 🐞 修复 `Carousel` 使用图片时点击报错问题 [#5299](https://github.com/vueComponent/ant-design-vue/issues/5299) - -## 3.0.0-beta.12 - -`2022-03-02` - -- 🌟 优化 `Menu` horizontal 模式动画,避免闪动 -- 🐞 修复 `Upload` 动画引起的高度问题 [#5298](https://github.com/vueComponent/ant-design-vue/issues/5298) - -## 3.0.0-beta.11 - -`2022-02-28` - -- 🌟 重构 `Upload`, 新增 showDownloadIcon、directory、isImageUrl、itemRender、maxCount、openFileDialogOnClick、progress、previewIcon、removeIcon、downloadIcon、drop 等特性 -- 🌟 重构 `Carousel` -- 🐞 修复 `Mentions` 长按时无法选中问题 [#5233](https://github.com/vueComponent/ant-design-vue/issues/5233) -- 🐞 修复 `Table` 动态更改展开图标位置时,渲染多个展开图标问题 [#5295](https://github.com/vueComponent/ant-design-vue/issues/5295) -- 🐞 修复 `Slider` 类型错误问题 [#5289](https://github.com/vueComponent/ant-design-vue/issues/5289) - -## 3.0.0-beta.10 - -`2022-02-18` - -- 🐞 修复日期组件使用 dayjs 或 dateFns 时,输入不合法值时自动 parse 问题 [#5221](https://github.com/vueComponent/ant-design-vue/issues/5221) -- 🐞 修复 dropdownMatchSelectWidth 为 false 时,未关闭虚拟滚动问题 [#5242](https://github.com/vueComponent/ant-design-vue/issues/5242) -- 🐞 修复 descriptions 控制台 warning 问题 [#5250](https://github.com/vueComponent/ant-design-vue/issues/5250) -- 🐞 修复 dropdown 的右键展开时,挑动问题 [#5259](https://github.com/vueComponent/ant-design-vue/issues/5259) -- 🐞 修复 TreeSelect windows 触摸板展开失效问题 [#5220](https://github.com/vueComponent/ant-design-vue/issues/5220) - -## 3.0.0-beta.9 - -`2022-01-28` - -🔥🔥🔥 新年快乐 🔥🔥🔥 - -- 🌟 `Progress` 添加 title 属性,避免 title 被内部 title 覆盖问题 [#4929](https://github.com/vueComponent/ant-design-vue/issues/4929) -- 🐞 修复 `Input` focus 状态时,样式边框问题 [#5188](https://github.com/vueComponent/ant-design-vue/issues/5188) -- 🌟 优化虚拟滚动在 mobile 下的滚动效果 [#5191](https://github.com/vueComponent/ant-design-vue/issues/5191) -- 🐞 修复 `Tree` 组件在拖拽时的样式问题 [6d4248](https://github.com/vueComponent/ant-design-vue/commit/6d4248d046a420aa6a1ddfeb78632e4405b91e51) -- 🐞 修复 `TreeSelect` 在空内容时,回车按键填充空节点问题 [#5217](https://github.com/vueComponent/ant-design-vue/issues/5217) -- 🐞 修复 `Button` 在设置 size 后,block 样式失效问题 [#5219](https://github.com/vueComponent/ant-design-vue/issues/5219) - -## 3.0.0-beta.8 - -`2022-01-21` - -- 🔥 重构 `Cascader`, 支持多选,新增 `tagRender` `multiple` `maxTagCount` `maxTagPlaceholder` `expandIcon`, 使用 `dropdownClassName` `dropdownStyle` `open` `placement` 分别替换 `popupClassName` `popupStyle` `popupVisible` `popupPlacement` 属性 -- 🌟 Select、TreeSelect 支持插槽 maxTagPlaceholder -- 🌟 `Table.Summary.Cell` 支持 `style`、`class` 的原生属性 -- 🌟 导出更多组件类型: `ConfigProviderProps` `InputProps` `TextAreaProps` `PopconfirmProps` `PopoverProps` `SliderProps` `StepProps` `StepsProps` -- 🐞 修复 Modal 在 vue@3.2.28 下报错问题 [#5190](https://github.com/vueComponent/ant-design-vue/issues/5190) -- 🐞 修复 `Modal` `getContainer` 失效问题 [#5147](https://github.com/vueComponent/ant-design-vue/issues/5147) -- 🐞 修复 `Table` `responsive` 失效问题 [#5172](https://github.com/vueComponent/ant-design-vue/issues/5172) -- 🐞 修复 `Tabs` activeKey 受控失效问题 [#5180](https://github.com/vueComponent/ant-design-vue/issues/5180) - -## 3.0.0-beta.7 - -`2022-01-10` - -- 🌟 导出 FormItemInstance 类型 [23f5fb](https://github.com/vueComponent/ant-design-vue/commit/23f5fba013ae8a76fb814c218fb319488da3c70b) -- 🐞 修复 Modal 在 Dropdown 下不显示问题 [#5139](https://github.com/vueComponent/ant-design-vue/issues/5139) -- 🐞 修复 Modal esc 快捷键失效问题 [3297f7](https://github.com/vueComponent/ant-design-vue/commit/3297f7aa58f6098b2b1dd147341b5c8dc5f2f5e5) - -## 3.0.0-beta.6 - -`2022-01-07` - -- Modal - - 🌟 重构 Modal 组件 [#5129](https://github.com/vueComponent/ant-design-vue/issues/5129) - - 🐞 修复 Modal、Drawer 混合使用时,出现无法滚动问题 [#5096](https://github.com/vueComponent/ant-design-vue/issues/5096) -- 🐞 修复 Menu 在 Dropdown 下,绑定 click 事件,属性校验不通过问题 [#5127](https://github.com/vueComponent/ant-design-vue/issues/5127) -- 🐞 修复 Table 虚拟滚动条不更新问题 [#5124](https://github.com/vueComponent/ant-design-vue/issues/5124) -- 🐞 调整 DatePicker 为单一根节点,用于支持 v-show [#5132](https://github.com/vueComponent/ant-design-vue/issues/5132) - -#### 文档: - -- 🌟 动态更新 document.title,方便切换文档 [#5121](https://github.com/vueComponent/ant-design-vue/issues/5121) -- 🐞 修复 Empty 类型错误 [#5136](https://github.com/vueComponent/ant-design-vue/issues/5136) -- 🐞 修复 RangeTime 范围选择示例错误 [#5125](https://github.com/vueComponent/ant-design-vue/issues/5125) - -## 3.0.0-beta.5 - -`2022-01-04` - -- 🌟 重构 message、notification 组件 [#5113](https://github.com/vueComponent/ant-design-vue/issues/5113) -- 🐞 修复 TimePicker、Slider、TreeSelect 类型错误 [#5109](https://github.com/vueComponent/ant-design-vue/issues/5109) -- 🐞 修复 Space size=0 时未生效问题 [#5101](https://github.com/vueComponent/ant-design-vue/issues/5101) - -## 3.0.0-beta.4 - -`2021-12-28` - -- 🌟 重构 Checkbox 组件,性能更优 -- 🌟 FormItem 新增 noStyle 属性,更加方便组织表单布局 -- 🐞 修复 InputNumber 在 precision 为 0 时,无法输入最小值问题 [#5083](https://github.com/vueComponent/ant-design-vue/issues/5083) - -#### 文档: - -- 🌟 Form 新增 2 个示例:校验时间类组件、校验其它组件 - -## 3.0.0-beta.3 - -`2021-12-27` - -- 🐞 修复 `Select` 虚拟滚动,动态修正高度错误问题 [#5082](https://github.com/vueComponent/ant-design-vue/issues/5082) - -## 3.0.0-beta.2 - -`2021-12-27` - -- 🐞 修复 FormItem 未传递 name 时,触发检验问题 [#5081](https://github.com/vueComponent/ant-design-vue/issues/5081) -- 🐞 修复 Table 首次渲染时宽度闪动问题 [#5075](https://github.com/vueComponent/ant-design-vue/issues/5075) [#4993](https://github.com/vueComponent/ant-design-vue/issues/4993) - -## 3.0.0-beta.1 - -`2021-12-24` - -- 🌟 重构 InputNumber 组件,新增属性: `bordered` `controls` `keyboard` `stringMode`, 插槽: `addonAfter` `addonBefore`, 事件:`step`,具体请查看 InputNumber API 说明 -- 🌟 添加 global.d.ts 类型文件,方便 volar 识别 [#5067](https://github.com/vueComponent/ant-design-vue/issues/5067) -- 🐞 修复 web-type.json 丢失问题 [#4860](https://github.com/vueComponent/ant-design-vue/issues/4860) -- Tabs - - - 🌟 Tabs 折叠节点新增删除功能 - - 🐞 Tabs 特殊场景未激活选项问题 [#5056](https://github.com/vueComponent/ant-design-vue/issues/5056) - - 🐞 修复默认导出的 TabPane 组件名称错误问题 [b645f8](https://github.com/vueComponent/ant-design-vue/commit/b645f827d0e13d60bc01c740ae8cbc8f61cf2cdf) - -- Form - - - 🌟 文档新增 7 个使用示例 - - 🌟 新增 FormInstance 类型导出 - - 🌟 校验 Number 类型时无需指定类型 [#5064](https://github.com/vueComponent/ant-design-vue/issues/5064) - - 🐞 回滚 FormItem 主动赋值时自动校验特性,此场景不应该自动校验 [#5056](https://github.com/vueComponent/ant-design-vue/issues/5056) - - 🐞 修复 validateMessages 错误问题 - -- 🌟 优化虚拟列表基础组件,提升 Tree、TreeSelect、Select 性能 [4e70c6](https://github.com/vueComponent/ant-design-vue/commit/4e70c6dd775254ae713d8633db2d0363027708e1) [#5069](https://github.com/vueComponent/ant-design-vue/issues/5069) -- 🐞 修复 Tree 展开时卡顿闪动问题 [#5069](https://github.com/vueComponent/ant-design-vue/issues/5069) -- 🐞 修复 Input 重置 undefined 时,不更新问题 - -## 3.0.0-alpha.16 - -`2021-12-19` - -- 🌟 重构 Input,新增无边框配置 -- Table - - 🌟 Table customCell 新增 column 参数[#5052](https://github.com/vueComponent/ant-design-vue/issues/5052) - - 🐞 修复 Table 翻页时,控制台输出错误 warning 问题 [#5029](https://github.com/vueComponent/ant-design-vue/issues/5029) - - 🐞 修复 Table 翻页组件弹出框隐藏时,弹框位置错误问题 [#5028](https://github.com/vueComponent/ant-design-vue/issues/5028) -- 🐞 修复 Rate 组件全局 prefixCls 未生效问题 [#5026](https://github.com/vueComponent/ant-design-vue/issues/5026) -- 🐞 修复 Menu 自定义 class 未生效问题 [#5038](https://github.com/vueComponent/ant-design-vue/issues/5038) -- 🐞 修复 Carousel 移动设备触摸时,打印 warning 问题 [#5040](https://github.com/vueComponent/ant-design-vue/issues/5040) -- 🐞 修复自定义 prefixCls 时,Select 无法选中问题 [#5023](https://github.com/vueComponent/ant-design-vue/issues/5023) - -## 3.0.0-alpha.15 - -`2021-12-12` - -- 🌟 优化 Layout 性能 -- 🌟 Menu 支持懒加载(SubMenu 必须填写 key),提升性能 [#4812](https://github.com/vueComponent/ant-design-vue/issues/4812) -- 🌟 Input、Textarea 支持 lazy 指令修饰符 [#4951](https://github.com/vueComponent/ant-design-vue/issues/4951) -- 🐞 Select placeholder 支持 slot [#4995](https://github.com/vueComponent/ant-design-vue/issues/4995) -- 🐞 修复 Radio cursor 样式 [#4997](https://github.com/vueComponent/ant-design-vue/issues/4997) -- 🐞 修复 Statistic.Countdown 属性支持插槽 [#4996](https://github.com/vueComponent/ant-design-vue/issues/4996) -- 🐞 修复 FormItem name 属性类型错误 [#4998](https://github.com/vueComponent/ant-design-vue/issues/4998) -- 🐞 修复 Menu 隐藏动画丢失问题 -- 🐞 修复 FormItem explain style 未响应问题 [#5004](https://github.com/vueComponent/ant-design-vue/issues/5004) -- 🐞 修复 Slider tooltip 特殊条件不显示问题 -- 🐞 修复 Dropdown 特殊条件触发两次 click 事件问题 [#5002](https://github.com/vueComponent/ant-design-vue/issues/5002) -- 🐞 修复部分组件在 SSR 下报错问题,支持 Nuxt -- 🐞 修复下拉框组件,在边缘处位置跳动问题 [#5008](https://github.com/vueComponent/ant-design-vue/issues/5008) -- 🐞 修复 Table 类型错误 [#5009](https://github.com/vueComponent/ant-design-vue/issues/5009) - -## 3.0.0-alpha.14 - -`2021-12-05` - -- 🌟 新增 xxxl 网格 [#4953](https://github.com/vueComponent/ant-design-vue/issues/4953) -- 🌟 Collapse activeKey 支持深度监听 [#4969](https://github.com/vueComponent/ant-design-vue/issues/4969) -- 🐞 修复 textarea blur 时未触发表单校验问题 [af5440](https://github.com/vueComponent/ant-design-vue/commit/af54405381d60bfadb383996a6ad64724b80f996) -- 🐞 修复 Form 主动赋值时未校验问题 [#4955](https://github.com/vueComponent/ant-design-vue/issues/4955) -- 🐞 修复 Select 搜索后无法滚动问题 [#4971](https://github.com/vueComponent/ant-design-vue/issues/4971) -- 🐞 修复 rangePicker、slider 类型问题 - -## 3.0.0-alpha.13 - -`2021-11-28` - -🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 - -高级组件 Surely Vue 发布!!! - -官方站点 : [https://surely.cool/](https://surely.cool/) - -Github:[https://github.com/surely-vue/table] - -- 🐞 升级 ts,修复组件类型错误 [e28168](https://github.com/vueComponent/ant-design-vue/commit/e28168e0bed28a97ef8c7b33f80d03f6fd0b5a02)[#4908](https://github.com/vueComponent/ant-design-vue/issues/4908)[#4912](https://github.com/vueComponent/ant-design-vue/issues/4912) -- 🐞 Drawer visible 改为可选,避免在 jsx v-model 写法中报类型错误 [#4908](https://github.com/vueComponent/ant-design-vue/issues/4908) -- 🐞 修复 tabs moreIcon 插槽不生效问题 [#4928](https://github.com/vueComponent/ant-design-vue/issues/4928) -- 🐞 修复 Button :disabled="false" 时,样式错误问题 [#4930](https://github.com/vueComponent/ant-design-vue/issues/4930) -- 🐞 修复展开类组件(Select、AutoComplete、TreeSelect),动画方向错误、展开闪动问题 [#4909](https://github.com/vueComponent/ant-design-vue/issues/4909) -- 🐞 Anchor 类名 fixed 没有前缀,导致命名冲突问题 [#4931](https://github.com/vueComponent/ant-design-vue/issues/4931) - -## 3.0.0-alpha.12 - -`2021-11-20` - -- 🐞 修复 TimeRangePicker 没有正确隐藏 panel 问题 [#4902](https://github.com/vueComponent/ant-design-vue/issues/4902) -- 🐞 修复 TreeSelect 重置 undefined 时,没有清空问题 [#4897](https://github.com/vueComponent/ant-design-vue/issues/4897) -- 🐞 修复 TreeSelect isLeaf 不生效问题 [#4883](https://github.com/vueComponent/ant-design-vue/issues/4883) -- 🐞 修复 Table rowSelection 报循环响应 warning 问题 [#4885](https://github.com/vueComponent/ant-design-vue/issues/4885) -- 🐞 修复 Table rowSelection 动态更新时不响应问题 [#4889](https://github.com/vueComponent/ant-design-vue/issues/4889) -- 🐞 修复部分组件类型丢失问题 [#4863](https://github.com/vueComponent/ant-design-vue/issues/4863) - -## 3.0.0-alpha.11 - -`2021-11-08` - -- 🌟 文档添加 codesanbox 链接 [#4861](https://github.com/vueComponent/ant-design-vue/issues/4861) -- 🐞 修复 Collapse 动画丢失问题 [#4856](https://github.com/vueComponent/ant-design-vue/issues/4856) -- 🐞 修复 Table 未设置 dataIndex 时报 warning 问题 - -## 3.0.0-alpha.10 - -`2021-11-05` - -- 🐞 修复 Tree 不触发 loadData 问题 [#4835](https://github.com/vueComponent/ant-design-vue/issues/4835) -- 🐞 修复 Breadcrumb.Item click 事件不触发问题 [#4845](https://github.com/vueComponent/ant-design-vue/issues/4845) -- 🐞 修复 Checkbox 在 Group 下有时不居中问题 [#4846](https://github.com/vueComponent/ant-design-vue/issues/4846) - -## 3.0.0-alpha.9 - -`2021-11-03` - -- 🐞 修复部分组件在 ssr 下 requestAnimationFrame 未定义错误 [#4833](https://github.com/vueComponent/ant-design-vue/issues/4833) -- 🐞 修复 TreeSelect selectable、checkable 无法关闭问题 [#4838](https://github.com/vueComponent/ant-design-vue/issues/4838) -- 🐞 修复 Tabs 在移动端无法滚动问题 [#4828](https://github.com/vueComponent/ant-design-vue/issues/4828) -- 🐞 修复 InputNumber 在 form 下不触发检验问题 [#4831](https://github.com/vueComponent/ant-design-vue/issues/4831) -- 🐞 修复 Select 使用 `` 构建节点时,自动分词失效 [#4844](https://github.com/vueComponent/ant-design-vue/issues/4844) - -## 3.0.0-alpha.8 - -`2021-10-30` - -- 🐞 修复组件类型丢失问题 [#4823](https://github.com/vueComponent/ant-design-vue/issues/4823) - -## 3.0.0-alpha.7 - -`2021-10-29` - -- 🌟 Form 新增 validate 事件 [#4817](https://github.com/vueComponent/ant-design-vue/issues/4817) -- 🌟 Tree 提供 ref 获取内部状态 api [#4820](https://github.com/vueComponent/ant-design-vue/issues/4820) -- 🐞 修复 Table 拖动时宽度突变问题 [#4811](https://github.com/vueComponent/ant-design-vue/issues/4811) -- 🐞 修复 TreeSelect 为空后,再次打开不更新问题 [a5604b](https://github.com/vueComponent/ant-design-vue/commit/a5604bb96796b9ec0090d3ec0c6d32d13d0df740) - -## 3.0.0-alpha.6 - -`2021-10-27` - -- 🌟 Table 新增拖动列 - -## 3.0.0-alpha.5 - -`2021-10-26` - -- Table - - 🐞 修复 sticky 时报错问题 [#4804](https://github.com/vueComponent/ant-design-vue/issues/4804) [#4808](https://github.com/vueComponent/ant-design-vue/issues/4808) - - 🐞 修复 emptyText 国际化失效问题 [#4805](https://github.com/vueComponent/ant-design-vue/issues/4805) - - 🌟 优化大小改变时的性能问题 [#4787](https://github.com/vueComponent/ant-design-vue/issues/4787) -- 🌟 useForm 支持深度响应式 rule [#4799](https://github.com/vueComponent/ant-design-vue/issues/4799) -- 🌟 Dropdown type 支持 text 类型 [#4802](https://github.com/vueComponent/ant-design-vue/issues/4802) -- 🐞 修复 Menu 在移动端报错问题 [#4794](https://github.com/vueComponent/ant-design-vue/issues/4794) -- 🐞 修复 Tree 自定义 fieldNames 时,勾选失效问题 [#4790](https://github.com/vueComponent/ant-design-vue/issues/4790) -- 🐞 修复 api 组件国际化失效问题 [#4780](https://github.com/vueComponent/ant-design-vue/issues/4780) - -## 3.0.0-alpha.4 - -`2021-10-20` - -- 组件部分状态使用 shallowRef 提升性能 [3a968f](https://github.com/vueComponent/ant-design-vue/commit/3a968fdd33960a788f2037d944aca4e8ee81294f) - -## 3.0.0-alpha.3 - -`2021-10-08` - -- Table - - 🐞 修复排序提示不显示问题 [f64d7a](https://github.com/vueComponent/ant-design-vue/commit/f64d7adb22952cfdd5bf642343335fd78460d745) - - 🐞 修复部分属性响应式丢失问题 [#4756](https://github.com/vueComponent/ant-design-vue/issues/4756) -- 🐞 修复 `Popover` `Popconfirm` 默认自动校准位置不生效问题 [98b5e5](https://github.com/vueComponent/ant-design-vue/commit/98b5e5d53fd10620eddc2c386181f868ef941397) - -## 3.0.0-alpha.2 - -`2021-10-08` - -- 🐞 修复引用 process.nextTick 问题 [#4737](https://github.com/vueComponent/ant-design-vue/issues/4737) - -## 3.0.0-alpha.1 - -`2021-10-07` - -- 🌟 重构 `Tabs` [#4732](https://github.com/vueComponent/ant-design-vue/issues/4732) - - 移除 `prevClick`、`nextClick` 事件,使用 `tabScroll` 事件替代 - - 废弃 `tabBarExtraContent` 插槽,使用 rightExtra 插槽替换,同时新增 `leftExtra` 插槽 - - 新增 `addIcon`、`closeIcon`、`moreIcon` 插槽 -- 🌟 重构 `Card`,废弃 tabList slots 配置,使用 customTab 插槽统一配置 [#4732](https://github.com/vueComponent/ant-design-vue/issues/4732) -- 🌟 重构 `Drawer` - - 新增 `autofocus` `contentWrapperStyle` `footerStyle` `headerStyle` `push` `size` `forceRender` 等属性 - - 新增 `closeIcon` `extra` `footer` 等插槽 - - 废弃 `afterVisibleChange` 属性,使用同名事件替代 -- 🐞 修复 `Table` pagination 没有响应式变化问题 [1add0d](https://github.com/vueComponent/ant-design-vue/commit/1add0d251cd35aa2c55404f7a60f1531425490c1) -- 🐞 修复 `notification` 样式错位问题 [#4703](https://github.com/vueComponent/ant-design-vue/issues/4703) -- 🐞 修复 `Tree` fieldsName 导致的选中、拖拽等异常 [#4726](https://github.com/vueComponent/ant-design-vue/issues/4726) - -## 3.0.0-alpha.0 - -`2021-09-24` - -🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 - -- 文档开源,如果您的公司不能外网访问,可以私有化部署,但不可以传播,不可以商业化。 -- 移除了 Transfer 的 `lazy` 属性,它并没有起到真正的优化效果。 -- 移除了 Select 的 `combobox` 模式,请使用 `AutoComplete` 替代。 -- 废弃 Button.Group,请使用 `Space` 代替。 -- `Timeline.Item` 新增 label。 -- `Steps` 新增 `responsive`、`percent`。 -- `Collapse` 新增 `ghost`、`collapsible`。 -- `Popconfirm` 新增 `cancelButton`、`okButton`, 以及 `esc` 按键隐藏。 -- `ConfigProvider` 新增 ConfigProvider.config,定义 `Modal.xxx` `message` `notification` 的配置。 -- `Tree` `TreeSelect` - - - 新增了虚拟滚动,废弃使用 `a-tree-node` `a-tree-select-node` 构建节点,使用 `treeData` 属性替代,提升组件性能。 - - 废弃 `scopedSlots` `slots` 自定义渲染节点,使用 `v-slot:title` 替换,提升易用性,避免插槽配置膨胀,同时也避免了插槽冲突问题。 - -- `Table` - - - 移除了 Table 的 `rowSelection.hideDefaultSelections` 属性,请在 `rowSelection.selections` 中使用 `SELECTION_ALL` 和 `SELECTION_INVERT` 替代,[自定义选择项](/components/table/#components-table-demo-row-selection-custom)。 - - 移除了 Column slots,分别使用 `v-slot:headerCell` `v-slot:headerCell` `v-slot:bodyCell` `v-slot:customFilterDropdown` `v-slot:customFilterIcon` 替换,提升易用性,避免插槽配置膨胀,同时也避免了插槽冲突问题。 - - 新增 expandFixed 控制展开图标是否固定。 - - 新增 showSorterTooltip 表头是否显示下一次排序的 tooltip 提示。 - - 新增 sticky 用于设置粘性头部和滚动条。 - - 新增 rowExpandable 用于设置是否允许行展开。 - - 新增插槽 headerCell 用于个性化头部单元格。 - - 新增插槽 bodyCell 用于个性化单元格。 - - 新增插槽 customFilterDropdown 用于自定义筛选菜单,需要配合 `column.customFilterDropdown` 使用。 - - 新增插槽 customFilterIcon 用于自定义筛选图标。 - - 新增插槽 emptyText 用于自定义空数据时的显示内容。 - - 新增插槽 summary 用于总结栏。 - -- `DatePicker` `TimePicker` `Calendar` - - - 默认使用更加轻量级的 dayjs 替换 momentjs,如果你的项目过大,使用了大量的 momentjs 的方法,你可以参考文档[自定义时间库](/docs/vue/replace-date-cn),替换成 momentjs。 - - UI 交互调整,对齐 antd 4.x 交互规范。 - -- `Form` 这次更新主要目标是提升性能,如果你没有自定义表单控件,几乎可以忽略该部分 - - - 自 3.0 版本以后,Form.Item 不再劫持子元素,而是通过 provider / inject 依赖注入的方式进行自动校验,这种方式可以提高组件性能,子元素也不会限制个数,同样子元素也可以是进一步封装的高级组件。你可以参考[自定义表单控件示例](#components-form-demo-customized-form-controls),但它同样会有一些缺点: - - 1、自定义组件如果希望 Form.Item 进行校验展示,你需要 `const {id, onFieldChange, onFieldBlur} = useInjectFormItemContext()` 注入,并调用相应的方法。 - - 2、一个 Form.Item 只能收集一个表单项的数据,如果有多个表单项,会导致收集错乱。例如: - - ```html - - - - - ``` - - 如上 Form.Item 并不知道需要收集 `name="a"` 还是 `name="b"`,你可以通过如下三种方式去解决此类问题: - - 第一种,使用多个 `a-form-item`: - - ```html - - - - - ``` - - 第二种,使用自定义组件包裹,并在自定义组件中调用 `useInjectFormItemContext`,相当于把多个表单项合并成了一个。 - - ```html - - ``` - - ```html - - - - - - - ``` - - 第三种,组件库提供了一个 `a-form-item-rest` 组件,它会阻止数据的收集,你可以将不需要收集校验的表单项放到这个组件中即可,它和第一种方式很类似,但它不会产生额外的 dom 节点。 - - ```html - - - - - ``` - -## 2.2.8 - -`2021-09-17` - -- 🌟 Upload method 支持 patch [#4637](https://github.com/vueComponent/ant-design-vue/issues/4637) -- 🌟 List gutter 支持数组 [d2b721](https://github.com/vueComponent/ant-design-vue/commit/d2b72143f0e15c8716b4ea8f68b2b72eff5cf510) -- 🐞 修复 Modal 类型错误 [#4632](https://github.com/vueComponent/ant-design-vue/issues/4632) -- 🐞 修复 AutoComplete 无法重置 undefined 问题 [741718](https://github.com/vueComponent/ant-design-vue/commit/741718a0f92c790266e7a07d8d129c5673344a7e) -- 🐞 修复 Tag 关闭图标样式丢失问题 [#4649](https://github.com/vueComponent/ant-design-vue/issues/4649) -- 🐞 修复 TreeSelect 清楚按钮在特殊条件下不显示问题 [#4655](https://github.com/vueComponent/ant-design-vue/issues/4655) -- 🐞 修复 useForm immdiate 不生效问题 [#4646](https://github.com/vueComponent/ant-design-vue/issues/4646) - -## 2.2.7 - -`2021-09-08` - -- 🌟 Menu 支持 overflowedIndicator 插槽 [#4515](https://github.com/vueComponent/ant-design-vue/issues/4515) -- 🌟 useForm 支持动态 rule [#4498](https://github.com/vueComponent/ant-design-vue/issues/4498) -- 🌟 Select 支持 Number 类型 [#4570](https://github.com/vueComponent/ant-design-vue/issues/4570) -- 🐞 修复 css zoom 引起的 warning 问题 [#4554](https://github.com/vueComponent/ant-design-vue/issues/4554) -- 🐞 修复 Mentions 输入中文报错问题 [#4524](https://github.com/vueComponent/ant-design-vue/issues/4524) -- 🐞 修复 AutoComplete 不支持全局 prefixCls 问题 [#4566](https://github.com/vueComponent/ant-design-vue/issues/4566) -- 🐞 修复 Table 嵌套表格报错问题 [#4600](https://github.com/vueComponent/ant-design-vue/issues/4600) -- 🐞 修复 Dropdown 下的 MenuItem danger 属性无样式问题 [#4618](https://github.com/vueComponent/ant-design-vue/issues/4618) -- 🐞 修复 Modal.xxx 等方法传递 appContext 失效问题 [#4627](https://github.com/vueComponent/ant-design-vue/issues/4627) -- 🐞 修复一些 TS 类型错误 - -## 2.2.6 - -`2021-08-12` - -- 🐞 修复 `Table` 展开列表渲染错位问题 [#4507](https://github.com/vueComponent/ant-design-vue/issues/4507) -- 🐞 修复 `Rate` 自定义 `character` 插槽未生效问题 [#4509](https://github.com/vueComponent/ant-design-vue/issues/4509) -- 🐞 添加 resize-observer-polyfill, 修复在低版本 Chrome 下报错问题 [#4508](https://github.com/vueComponent/ant-design-vue/issues/4508) - -## 2.2.5 - -`2021-08-11` - -- 🌟 `Select` 支持通过 option 插槽定制化节点 [68c1f4](https://github.com/vueComponent/ant-design-vue/commit/68c1f4550108a3a6bbe4f1b2c5c168523fd6c84a) -- 🐞 修复开发环境下弹窗类组件在低版本 chrome 下,不显示问题,并避免弹窗闪动 [#4409](https://github.com/vueComponent/ant-design-vue/issues/4409) -- 🐞 修复 `Select` 打开时没有滚动到激活位置问题 [ccb240](https://github.com/vueComponent/ant-design-vue/commit/ccb24016c07632f49550646c971060c402586c67) - -## 2.2.4 - -`2021-08-10` - -- 🌟 支持 Vue@3.2 [#4490](https://github.com/vueComponent/ant-design-vue/issues/4490) -- 🌟 自动隐藏 `Table` 横向滚动条 [#4484](https://github.com/vueComponent/ant-design-vue/issues/4484) -- 🐞 修复 `Progress` trailColor 不生效问题 [#4483](https://github.com/vueComponent/ant-design-vue/issues/4483) - -## 2.2.3 - -`2021-08-07` - -- 🌟 `Table` 固定列使用 `position: sticky` , 提升性能,解决部分场景不对齐问题 [38569c](https://github.com/vueComponent/ant-design-vue/commit/38569c28c7eb4eaa34f2cc096982daea901062d4) -- 🌟 `Collapse` 支持 number 类型 key [#4405](https://github.com/vueComponent/ant-design-vue/issues/4405) -- 🌟 优化 `Tabs` 在 windows 下选中时闪动问题 [#4241](https://github.com/vueComponent/ant-design-vue/issues/4241) -- 🌟 `InputPassword` 支持全局设置 prefixCls [#4430](https://github.com/vueComponent/ant-design-vue/issues/4430) -- 🐞 修复 `Select` 无法滚动问题 [#4396](https://github.com/vueComponent/ant-design-vue/issues/4396) -- 🐞 修复 `Badge` 在 ssr 下报错问题 [#4384](https://github.com/vueComponent/ant-design-vue/issues/4384) -- 🐞 修复 `Form` 多出无效数据字段问题 [#4435](https://github.com/vueComponent/ant-design-vue/issues/4435) -- 🐞 修复 `FormItem` 子元素是原生标签时报错问题 [#4383](https://github.com/vueComponent/ant-design-vue/issues/4383) -- 🐞 修复 `TreeSelect` 通过 slot 自定义 title 时报错问题 [#4459](https://github.com/vueComponent/ant-design-vue/issues/4459) - -## 2.2.2 - -`2021-07-11` - -- 🌟 Switch 新增 checkedValue、unCheckedValue 属性用于自定义 checked 绑定值 [#4329](https://github.com/vueComponent/ant-design-vue/issues/4329) -- 🐞 修复 SubMenu 动画丢失的问题 [#4325](https://github.com/vueComponent/ant-design-vue/issues/4325) -- 🐞 修复 TimePicker 在 Form 下验证错误时没有红框问题 [#4331](https://github.com/vueComponent/ant-design-vue/issues/4331) -- 🐞 修复 UploadDragger 不支持 vite-plugin-components 按需加载问题 [#4334](https://github.com/vueComponent/ant-design-vue/issues/4334) -- 🐞 修复 TreeSelect 通过 slot 自定义 title 时报错问题 [1152e8](https://github.com/vueComponent/ant-design-vue/commit/1152e8cd71cadf9e8fb4797916adca20c0e35974) -- 🐞 修复 Dropdown submenu 样式丢失问题 [#4351](https://github.com/vueComponent/ant-design-vue/issues/4351) -- TS - - 修复 Table 在 ts 4.3.5 版本下类型报错问题 [#4296](https://github.com/vueComponent/ant-design-vue/issues/4296) - - 完善 notification 类型 [#4346](https://github.com/vueComponent/ant-design-vue/issues/4346) - -## 2.2.1 - -`2021-07-06` - -- 🐞 修复 Space 组件在不支持 flex 的浏览器中样式不生效问题 -- 🐞 修复 DatePicker 在 safari 下触发滚动问题 [#4323](https://github.com/vueComponent/ant-design-vue/issues/4323) - -## 2.2.0 - -`2021-07-06` - -- 🎉 重构 Button 组件,移除 type="danger",新增 `danger` 属性 [#4291](https://github.com/vueComponent/ant-design-vue/issues/4291) -- 🐞 修复 Rate 组件不更新问题 [#4294](https://github.com/vueComponent/ant-design-vue/issues/4294) -- 🐞 修复 Tree replaceFields 报错问题 [#4298](https://github.com/vueComponent/ant-design-vue/issues/4298) -- 🐞 修复 Modal 缺少 parentContext 类型问题 [#4305](https://github.com/vueComponent/ant-design-vue/issues/4305) - -## 2.2.0-rc.1 - -`2021-06-29` - -- 🌟 更改 babel 配置,较小构建包大小 -- 🌟 Form 原生提供 useForm 功能,废弃 @ant-design-vue/use 库 -- 🐞 修复 Form validateFirst 属性在多个校验规则时不触发 reject 问题 [#4273](https://github.com/vueComponent/ant-design-vue/issues/4273) -- 🐞 修复 List 循环引用导致 Vite 下报错问题 [#4263](https://github.com/vueComponent/ant-design-vue/issues/4263) -- 🐞 修复 Menu 事件回调缺少 item 属性问题 [#4290](https://github.com/vueComponent/ant-design-vue/issues/4290) - -## 2.2.0-beta.6 - -`2021-06-26` - -- 🌟 Menu 性能优化 [e8b957](https://github.com/vueComponent/ant-design-vue/commit/e8b95784eb1ee0554b0d6b17bdc14e18775f2ae6) -- 🐞 修复 Layout、RangePicker、WeekPicker、Textarea 按需加载失效 - -## 2.2.0-beta.5 - -`2021-06-24` - -- 🎉 支持 vite-plugin-components 按需加载 -- 🎉 重构 List 组件 -- 🌟 Select 新增响应式折叠选项 [656d14](https://github.com/vueComponent/ant-design-vue/commit/656d14fc4e4ef0f781324438f0d58cfb6816d583) -- 🐞 修复 Select 动态更新选项时虚拟列表无法滚动问题 [b2aa49d](https://github.com/vueComponent/ant-design-vue/commit/b2aa49d064a83c6ce786a6bb4cd9fc5266a5964d) -- 🐞 修复 Select 键盘事件位置不正确问题 [604372](https://github.com/vueComponent/ant-design-vue/commit/604372ff2da521dd580ad5229f7dbd445c1c6190) -- 🐞 修复 AutoComplete 不支持 options slot 问题 [#4012](https://github.com/vueComponent/ant-design-vue/issues/4012) - -## 2.2.0-beta.4 - -`2021-06-21` - -- 🎉 重构 Descriptions 组件 [#4219](https://github.com/vueComponent/ant-design-vue/issues/4219) -- 🐞 修复 Countdown 不触发 finish 事件问题 [#4222](https://github.com/vueComponent/ant-design-vue/issues/4222) -- 🐞 修复 ConfigProvider 在 vue 3.1 下报错问题 [#4225](https://github.com/vueComponent/ant-design-vue/issues/4225) -- 🐞 修复 Dropdown 下使用 SubMenu 报错问题 [#4205](https://github.com/vueComponent/ant-design-vue/issues/4205) -- 🐞 修复 Col 类型错误 [#4226](https://github.com/vueComponent/ant-design-vue/issues/4226) -- 🐞 修复 Typography 失焦时不触发 onEnd 问题 [#4227](https://github.com/vueComponent/ant-design-vue/issues/4227) -- 🐞 修复 ImagePreview 样式丢失问题 [#4231](https://github.com/vueComponent/ant-design-vue/issues/4231) - -## 2.2.0-beta.3 - -`2021-06-11` - -- 🎉 重构 Breadcrumb、Statistic、Tag 组件 -- 🌟 Statistic 支持 loading 属性 -- 🐞 修复 Menu 渲染多次子组件问题,提升性能 [6ae707](https://github.com/vueComponent/ant-design-vue/commit/6ae707edf508a9c5e8dca7dacf1410de5251bcf8) -- 🐞 修复 FormItem 自定义 class 失效 [617e53](https://github.com/vueComponent/ant-design-vue/commit/617e534fda2ae6d468b5e9d3eb43370f8a4b0000) -- 🐞 修复 MenuDivider class 错误问题 [#4195](https://github.com/vueComponent/ant-design-vue/issues/4195) -- 🐞 修复 Tag、Image 类型错误 -- 🐞 修复 Modal 等组件动画丢失问题 [#4191](https://github.com/vueComponent/ant-design-vue/issues/4191) -- 🐞 修复 Select class 不能动态更新问题 [#4194](https://github.com/vueComponent/ant-design-vue/issues/4194) -- 🐞 修复 Dropdown 邮件展开,不能点击收起的问题 [#4198](https://github.com/vueComponent/ant-design-vue/issues/4198) -- 🐞 修复 FormItem 缺少部分导出方法问题 [#4183](https://github.com/vueComponent/ant-design-vue/issues/4183) - -## 2.2.0-beta.2 - -`2021-06-08` - -- 🐞 修复 PageHeader 显示多余字符问题 [4de773](https://github.com/vueComponent/ant-design-vue/commit/4de7737907d485d3dd3be44b70e599cc53edb171) -- 🐞 修复部分组件不能在 Vue3.1 下不能正常渲染问题 [#4173](https://github.com/vueComponent/ant-design-vue/issues/4173) -- 🐞 修复 Menu.Divider 名称错误问题 [6c5c84](https://github.com/vueComponent/ant-design-vue/commit/6c5c84a3fc4b8abcd7aed0922852a64e0ac293c7) - -## 2.2.0-beta.1 - -`2021-06-07` - -- 🔥🔥🔥 虚拟 Table 独立库发布 https://www.npmjs.com/package/@surely-vue/table , 该组件是一个独立的库,目前文档示例尚未完善,他是一个完全 ts 开发的组件,有较好的类型提示,npm 上已有 API 文档,着急使用的的可以摸索着用起来了,这里有个在线体验示例,https://store.antdv.com/pro/preview/list/big-table-list -- 🔥🔥🔥 重构大量组件,源码更加易读,性能更优,ts 类型更加全面 - - 本版本重构组件 Anchor、Alert、Avatar、Badge、BackTop、Col、Form、Layout、Menu、Space、Spin、Switch、Row、Result、Rate -- 🎉 Menu - - - 性能更优 [#3300](https://github.com/vueComponent/ant-design-vue/issues/3300) - - 修复高亮不正确问题 [#4053](https://github.com/vueComponent/ant-design-vue/issues/4053) - - 修复控制台无效 warning [#4169](https://github.com/vueComponent/ant-design-vue/issues/4169) - - 更加易用,更加简单的使用单文件递归 [#4133](https://github.com/vueComponent/ant-design-vue/issues/4133) - - 💄 图标 icon 需要通过 slot 传递 - -- Skeleton - - - 🌟 支持 Skeleton.Avatar 占位组件。 - - 🌟 支持 Skeleton.Button 占位组件。 - - 🌟 支持 Skeleton.Input 占位组件。 - -- 💄 破坏性更新 - - - `a-menu-item`、`a-sub-menu` 图标需要通过 slot 传递,不在通过子节点自动获取图标 - - row gutter 支持 row-wrap, 无需使用多个 row 划分 col - - Menu 移除 defaultOpenKeys、defaultSelectedKeys; Switch 移除 defaultChecked; Rate 移除 defaultValue; 其它未重构组件的 defaultXxx 命名的属性请谨慎使用,在未来的版本中也会被移除。 - -- 🌟 新增 Avatar.Group 组件 -- 🐞 修复 AutoComplete filterOptions 不生效问题 [#4170](https://github.com/vueComponent/ant-design-vue/issues/4170) -- 🐞 修复 Select 自动宽度失效问题 [#4118](https://github.com/vueComponent/ant-design-vue/issues/4118) -- 🐞 修复 dist 缺少国际化文件问题 [#3684](https://github.com/vueComponent/ant-design-vue/issues/3684) - -## 2.1.6 - -`2021-05-13` - -- 🐞 使用 vue@3.0.10 重新构建,避免控制台 warning [#3998](https://github.com/vueComponent/ant-design-vue/issues/3998) - -## 2.1.5 - -`2021-05-12` - -- 🐞 修复 SSR 时报错问题 [#3983](https://github.com/vueComponent/ant-design-vue/issues/3983) - -## 2.1.4 - -`2021-05-09` - -- 🐞 修复 `Table` 滚动错位问题 [#4045](https://github.com/vueComponent/ant-design-vue/issues/4045) -- 🐞 修复 `Typography` editable 模式触发链接跳转问题 [#4105](https://github.com/vueComponent/ant-design-vue/issues/4105) -- 🐞 修复 `Carousel` variableWidth 不生效问题 [#3977](https://github.com/vueComponent/ant-design-vue/issues/3977) -- 🐞 修复 `TreeSelect` 无法通过键盘同时删除父子节点问题 [#3508](https://github.com/vueComponent/ant-design-vue/issues/3508) -- 🐞 修复若干类型错误问题 - -## 2.1.3 - -`2021-04-25` - -- 🎉🎉🎉 移除 npm 安装时的广告 -- 🐞 `Select` - - 修复默认激活第一项问题 [#3842](https://github.com/vueComponent/ant-design-vue/issues/3842) - - 修复分组显示异常问题 [#3841](https://github.com/vueComponent/ant-design-vue/issues/3841) - - 修复动态更新选择项后滚动异常问题 [#3972](https://github.com/vueComponent/ant-design-vue/issues/3972) -- 🐞 修复 `Checkbox` 触发两次 `update:checked` 问题 [#3838](https://github.com/vueComponent/ant-design-vue/issues/3838) -- 🌟 `Table` column group 支持 fixed [#3882](https://github.com/vueComponent/ant-design-vue/issues/3882) -- 🌟 `Table` column 支持 v-for [#3934](https://github.com/vueComponent/ant-design-vue/issues/3934) -- 🐞 修复 `Table` 在 windows 显示横向滚动条问题 [6d33d6](https://github.com/vueComponent/ant-design-vue/commit/6d33d60d2bca98825f274e48bcc3badd1857f742) -- 🌟 `Form` scrollToFirstError 支持选项参数传递 [#3918](https://github.com/vueComponent/ant-design-vue/issues/3918) -- 🐞 修复 `Calendar` 月份选择器显示错误字符问题 [#3915](https://github.com/vueComponent/ant-design-vue/issues/3915) -- 🌟 重构 `Switch` 组件,移除 defaultChecked 属性 [#3885](https://github.com/vueComponent/ant-design-vue/issues/3885) -- 🐞 修复使用 Vite 时,抛出 process 异常问题 [#3930](https://github.com/vueComponent/ant-design-vue/issues/3930) -- 🐞 修复 `Radio` 阴影遮挡问题 [#3955](https://github.com/vueComponent/ant-design-vue/issues/3955) -- 🐞 修复 `Form` inline 模式下, span 不生效问题 [#3862](https://github.com/vueComponent/ant-design-vue/issues/3862) -- 🐞 修复 `Cascader` keydown 选择不生效问题 [#958](https://github.com/vueComponent/ant-design-vue/issues/958) -- 🐞 修复 `Image` 预览功能失败问题 [#3701](https://github.com/vueComponent/ant-design-vue/issues/3701) -- 🐞 修复一些 TS 类型问题 - -## 2.1.2 - -`2021-03-28` - -- 🌟 使用 Vue 3.0.9 重新编译,兼容 3.0.7 及以下版本 - -## 2.1.1 - -`2021-03-27` - -- 🌟 兼容 Vue 3.0.8,注意:由于 3.0.8 的破坏性更新,2.1.1 无法兼容 3.0.7 以下版本 [vue#3493](https://github.com/vuejs/vue-next/issues/3493) -- 🐞 修复 Modal.confirm 缺失 closable ts 类型 [#3684](https://github.com/vueComponent/ant-design-vue/issues/3845) -- 🐞 修复 Upload 自定义 method 不生效问题 [#3843](https://github.com/vueComponent/ant-design-vue/issues/3843) - -## 2.1.0 - -`2021-03-20` - -- 🎉🎉🎉 新增 `Typography` 组件 [#3807](https://github.com/vueComponent/ant-design-vue/issues/3807) -- 🌟 Modal 方法新增关闭图标定制 [#3753](https://github.com/vueComponent/ant-design-vue/issues/3753) -- 🐞 修复缺失包含国际化的构建文件 [#3684](https://github.com/vueComponent/ant-design-vue/issues/3684) -- 🐞 修复 Drawer 销毁后报错问题 [#848d64](https://github.com/vueComponent/ant-design-vue/commit/848d6497e68c87566790dfa889a1913199a6699a) -- 🐞 修复 BackTop 在 KeepAlive 激活时,位置不对的问题 [#3803](https://github.com/vueComponent/ant-design-vue/issues/3803) -- 🐞 修复 TreeNode class 不生效问题 [#3822](https://github.com/vueComponent/ant-design-vue/issues/3822) -- 🐞 修复 Table tags 为数组时报错问题 [#3812](https://github.com/vueComponent/ant-design-vue/issues/3812) -- 🐞 修复 Table 自定义 filterIcon 时,触发排序问题 [#3819](https://github.com/vueComponent/ant-design-vue/issues/3819) -- 🐞 修复 Select 样式在 Form 下错位问题 [#3781](https://github.com/vueComponent/ant-design-vue/issues/3781) - -## 2.0.1 - -`2021-02-27` - -- 🌟 `Badge` 新增 `Ribbon` [#3681](https://github.com/vueComponent/ant-design-vue/issues/3681) -- 🌟 调整 `SearchInput` search 事件触发顺序 [#3725](https://github.com/vueComponent/ant-design-vue/issues/3725) -- 🐞 修复 `Table` 销毁时卡死问题 [#3531](https://github.com/vueComponent/ant-design-vue/issues/3531) -- 🐞 修复 `Menu` css 中引入了 less 文件问题 [#3678](https://github.com/vueComponent/ant-design-vue/issues/3678) -- 🐞 修复 `Alert` 自定义图标错位问题 [#3712](https://github.com/vueComponent/ant-design-vue/issues/3712) - -## 2.0.0 - -`2021-02-06` - -- 🎉🎉🎉 2.0 正式版发布 -- 🎉🎉🎉 支持暗黑主题 [#3410](https://github.com/vueComponent/ant-design-vue/issues/3410) -- 🎉🎉🎉 新版文档上线,使用 Composition API 完全重构文档示例,提供 TS、JS 双版本源码 -- 🌟 使用 Composition API 重构 `Alert` 组件 [#3654](https://github.com/vueComponent/ant-design-vue/pull/3654) -- 🌟 `Tooltip` 支持自定义颜色 [#3603](https://github.com/vueComponent/ant-design-vue/issues/3603) -- 🐞 修复 `TimePicker` 没有自动滚动到已选位置问题 [#ab7537](https://github.com/vueComponent/ant-design-vue/commit/ab75379f0c2f5e54ab7c348284a7391939ab5aaf) - -## 2.0.0-rc.9 - -`2021-01-24` - -- 🌟 `@ant-design/icons-vue` 升级至 6.0,默认使用 es module -- 🌟 `Tabs` 增加 `centered` 居中模式 [#3501](https://github.com/vueComponent/ant-design-vue/issues/3501) -- 🐞 `Progress` 添加 opacity 动画 [#3505](https://github.com/vueComponent/ant-design-vue/issues/3505) -- 🐞 修复 npm 安装时报错问题 [#3515](https://github.com/vueComponent/ant-design-vue/issues/3515) -- 🐞 修复 `Breadcrumn` 分割线不显示问题 [#3522](https://github.com/vueComponent/ant-design-vue/issues/3522) -- 🐞 修复 `Radio` 不受控问题 [#3517](https://github.com/vueComponent/ant-design-vue/issues/3517) -- 🐞 修复 `FormItem` 不换行问题 [#3538](https://github.com/vueComponent/ant-design-vue/issues/3538) -- 🐞 修复 `Carousel` `pauseOnDotsHover` 不生效问题 [#3519](https://github.com/vueComponent/ant-design-vue/issues/3519) -- 🐞 修复 `Input.Search` `class` 不生效问题 [#3541](https://github.com/vueComponent/ant-design-vue/issues/3541) -- 🐞 修复 `InputNumber` 在微软输入法下多次触发 change 事件问题 [#3550](https://github.com/vueComponent/ant-design-vue/issues/3550) -- 🐞 修复 `Tabs` disabled 状态下依然可以通过键盘切换问题 [#3575](https://github.com/vueComponent/ant-design-vue/issues/3575) -- 🐞 修复 `Switch` 在 table 中切换不生效问题 [#3512](https://github.com/vueComponent/ant-design-vue/issues/3512) - -## 2.0.0-rc.8 - -`2021-01-07` - -- 🌟 支持 Vite 2 [#3490](https://github.com/vueComponent/ant-design-vue/issues/3490) -- 🌟 使用 Composition API 重构 Affix 组件 [#3447](https://github.com/vueComponent/ant-design-vue/issues/3447) -- 🐞 修复 Image 组件类型定义错误 [#3488](https://github.com/vueComponent/ant-design-vue/issues/3488) -- 🐞 升级 icons-vue 修复 IconFont 组件类型错误 [#3474](https://github.com/vueComponent/ant-design-vue/issues/3474) -- 🐞 修复 less 4 下 Tooltip 箭头样式错误问题 [#3477](https://github.com/vueComponent/ant-design-vue/issues/3477) -- 🐞 修复 Vue 3.0.5 下 DatePicker 类型定义解析错误问题 [#bf7c62](https://github.com/vueComponent/ant-design-vue/commit/bf7c62f457fc14624881f69c5baf9a62219383f7) - -## 2.0.0-rc.7 - -`2020-12-28` - -- 🐞 修复 Switch `change`、`click` 不生效问题 [#3453](https://github.com/vueComponent/ant-design-vue/issues/3453) - -## 2.0.0-rc.6 - -`2020-12-27` - -- 🌟 支持 Less 4 [#3449](https://github.com/vueComponent/ant-design-vue/issues/3449) -- 🌟 新增 Image 组件 [#3235](https://github.com/vueComponent/ant-design-vue/issues/3235) -- 🌟 函数式组件,添加 displayName 属性 [#3445](https://github.com/vueComponent/ant-design-vue/issues/3445) -- 🐞 Message 新增自定义 class style 功能 [#3443](https://github.com/vueComponent/ant-design-vue/issues/3443) -- 🐞 修复 Tabs 组件初始 disabled 状态没生效 [#3366](https://github.com/vueComponent/ant-design-vue/issues/3366) -- 🐞 修复 Slider 精准度问题 [#3346](https://github.com/vueComponent/ant-design-vue/issues/3346) -- 🐞 修复 Select 滚动高度不正确问题 [#3419](https://github.com/vueComponent/ant-design-vue/issues/3419) -- 🐞 修复 Input small 大小时,高度偏大 2px 问题 [#3396](https://github.com/vueComponent/ant-design-vue/issues/3396) -- 🐞 修复 TreeSelect 触发两次 change 事件问题 -- 🐞 修复 TreeSelect 通过 slot 定义 title 死循环问题 -- 🐞 修复 Drawer handle slot 触发两次 click 事件问题 -- 🌟 新增 Checkbox、Switch 事件声明 - -## 2.0.0-rc.5 - -`2020-12-13` - -- 🐞 修复 Drawer 组件控制台输出 this.dom 未定义的 warning 问题 -- 🐞 修复 Menu 在 Vue 3.0.3 及以上版本,出现显示错乱问题 [#3354](https://github.com/vueComponent/ant-design-vue/issues/3354) - -## 2.0.0-rc.4 - -`2020-12-10` - -- 🌟 Input.Password 支持自定义图标 [#3320](https://github.com/vueComponent/ant-design-vue/issues/3320) -- 🐞 修复 Select Option click 事件不触发问题 [#4ea00d](https://github.com/vueComponent/ant-design-vue/commit/4ea00d3a70d0afd7bea07f814df03ab7d0b25ebd) -- 🐞 修复 Menu 超出宽度后 dark 主题不生效问题 [#10f35a](https://github.com/vueComponent/ant-design-vue/commit/10f35a1fa510de91e9484b07fcfff253920cee29) -- 🐞 修复 Menu 控制台 vue key some waring [#520d6a](https://github.com/vueComponent/ant-design-vue/commit/520d6a5e85eb391e5294211c9d7b2ea598c59119) -- 🐞 移除控制台 passive 提示日志 [#8d1669](https://github.com/vueComponent/ant-design-vue/commit/8d1669b8896d84a67c61d3a00d0b13c42d70f30f) - -## 2.0.0-rc.3 - -`2020-12-05` - -- 🐞 修复函数式组件在 Vue 3.0.3 下报类型错误问题 [#f5cf7e](https://github.com/vueComponent/ant-design-vue/commit/f5cf7e0920a51f0ac024046996c99260aa41becf) -- 🐞 修复 Menu 超出宽度后显示错误问题 [#3262](https://github.com/vueComponent/ant-design-vue/issues/3262) -- 🐞 修复 Menu subMenuOpenDelay subMenuCloseDelay 不生效问题 [#3291](https://github.com/vueComponent/ant-design-vue/pull/3291) -- 🐞 修复 TreeSelect 堆栈溢出问题 [#28aeea](https://github.com/vueComponent/ant-design-vue/commit/28aeea6f0b142ed68950a3738f7cf2c1581a7a5b) -- 🐞 修复 Input 自定义 style class 被覆盖问题 [#3273](https://github.com/vueComponent/ant-design-vue/issues/3273) -- 🐞 修复 InputNumber 在生产环境下 parse 错误 [#3249](https://github.com/vueComponent/ant-design-vue/issues/3249) - -## 2.0.0-rc.2 - -`2020-11-24` - -- 🌟 优化 Menu 性能,默认开启懒加载 [#3243](https://github.com/vueComponent/ant-design-vue/pull/3243) -- 🌟 Tag 支持通过 slot 定义 icon [#3185](https://github.com/vueComponent/ant-design-vue/pull/3185) -- 🌟 small 类型的 table 改成无边框 [#3221](https://github.com/vueComponent/ant-design-vue/issues/3221) -- 🌟 @ant-design/icons-vue 升级到 5.1.6,支持 SSR,支持 spin 属性简写 -- 🐞 修复 Alert 的关闭按钮在 Safari 下样式问题 [#3184](https://github.com/vueComponent/ant-design-vue/issues/3184) -- 🐞 修复 Notification top 属性类型错误问题 [#3187](https://github.com/vueComponent/ant-design-vue/issues/3187) -- 🐞 修复 DirectoryTree 自定义图标不生效问题 [#3183](https://github.com/vueComponent/ant-design-vue/issues/3183) -- 🐞 修复 Button loading delay 不生效问题 [#3194](https://github.com/vueComponent/ant-design-vue/issues/3194) -- 💄 Select optionFilterProp 不在支持按照 children 来过滤 [#3204](https://github.com/vueComponent/ant-design-vue/issues/3204) -- 🐞 修复 Select labelInValue 时报错问题 [#3216](https://github.com/vueComponent/ant-design-vue/issues/3216) -- 🐞 修复 ConfigProvider transformCellText 丢失问题 [#3206](https://github.com/vueComponent/ant-design-vue/issues/3206) -- 🐞 修复 Dropdown Button 混合使用时,样式错乱问题 [#3244](https://github.com/vueComponent/ant-design-vue/issues/3244) -- 🐞 修复 RangePicker 自定义宽度失效问题 [#3244](https://github.com/vueComponent/ant-design-vue/issues/3245) -- 🐞 修复多处 Ts 类型错误或缺失问题 - -## 2.0.0-rc.1 - -`2020-11-14` - -- 🎉🎉🎉 -- 🌟 Menu 取消默认懒加载,提升首次动画效果,优化贝塞尔曲线函数,更加流畅 [#3177](https://github.com/vueComponent/ant-design-vue/pull/3177) -- 🐞 修复 Select 搜索功能失效问题 [#3144](https://github.com/vueComponent/ant-design-vue/issues/3144) -- 🐞 修复 Drawer 组件没有自动 focus,导致不能直接通过 ESC 按键关闭 [#3148](https://github.com/vueComponent/ant-design-vue/issues/3148) -- 🐞 修复 Popover 弹出元素位置不正确问题 [#3147](https://github.com/vueComponent/ant-design-vue/issues/3147) -- 🐞 修复 CountDown 不更新问题 [#3170](https://github.com/vueComponent/ant-design-vue/pull/3170) -- 🐞 修复多处 Ts 类型错误或缺失问题 - -## 2.0.0-beta.15 - -`2020-11-08` - -- 🌟 优化 Menu 动画,更加流畅 [#3095](https://github.com/vueComponent/ant-design-vue/issues/3095) -- 🌟 优化 VirtualList,避免无效 render [#2e61e9](https://github.com/vueComponent/ant-design-vue/commit/2e61e9cb502f2bb6910f59abfb483fd2517e594f) -- 🐞 修复 Menu overflowedIndicator 未生效问题 [#689113](https://github.com/vueComponent/ant-design-vue/commit/689113b3c9c19e929607567a4c8252c6511bff5c) -- 🐞 Select - - 修复 dropdownRender 不支持 slot 问题 [#3098](https://github.com/vueComponent/ant-design-vue/issues/3098) - - 修复 tag 模式下,空值异常问题 [#3100](https://github.com/vueComponent/ant-design-vue/issues/3100) - - 修复单选模式下选择项不更新问题 [#3099](https://github.com/vueComponent/ant-design-vue/issues/3099) - - 修复特殊场景下 foucs 状态不生效问题 [#3099](https://github.com/vueComponent/ant-design-vue/issues/3099) -- 🐞 修复 DatePicker 默认格式化失效问题 [#3091](https://github.com/vueComponent/ant-design-vue/issues/3091) -- 🐞 修复 Table customRow 配置事件不生效问题 [#3121](https://github.com/vueComponent/ant-design-vue/issues/3121) -- 🐞 修复 TreeSelect 搜索框样式错乱问题 [ee4cd3c](https://github.com/vueComponent/ant-design-vue/commit/ ee4cd3c35a84658cbbb148ce368bc247a927d528) -- 🐞 修复 Ts 类型错误或缺失问题 - -## 2.0.0-beta.13 - -`2020-11-02` - -- 🐞 修复 npm install 报错问题 [#3080](https://github.com/vueComponent/ant-design-vue/issues/3080) -- 🐞 修复 Select maxPlaceHolder 显示错误问题 [#3085](https://github.com/vueComponent/ant-design-vue/issues/3085) -- 🐞 修复弹窗类组件,弹出位置不更新问题 [#3085](https://github.com/vueComponent/ant-design-vue/issues/3085) -- 🐞 修复 Table 数据为空时的 warning 问题 [#3082](https://github.com/vueComponent/ant-design-vue/issues/3082) -- 🐞 修复 Input 在 Form 中显示多个边框问题 [#3084](https://github.com/vueComponent/ant-design-vue/issues/3084) - -## 2.0.0-beta.12 - -`2020-11-01` - -- 🐞 修复 dist/antd.css 缺失组件样式问题 [#3069](https://github.com/vueComponent/ant-design-vue/issues/3069) -- 🐞 修复 Input 样式问题 [#3074](https://github.com/vueComponent/ant-design-vue/issues/3074) -- 🐞 修复 Form layout="vertical" 样式问题 [#3075](https://github.com/vueComponent/ant-design-vue/issues/3075) -- 🐞 修复 Select 无法打开弹窗问题 [#3070](https://github.com/vueComponent/ant-design-vue/issues/3070) - -## 2.0.0-beta.11 - -`2020-10-30` - -- 🎉🎉🎉 重构 Select、AutoComplete 组件,支持虚拟列表,性能大幅提升 -- 🔥🔥🔥 使用 Typescript 重构所有组件,类型支持更加友好 -- 🔥 优化底层动画组件,性能更好,更流畅 -- 🌟 Textarea 组件添加 showCount 支持统计字数功能 -- 🌟 递归 Menu 组件,支持任意嵌套其他元素 [#1452](https://github.com/vueComponent/ant-design-vue/issues/1452) -- 🇮🇪 添加爱尔兰语国际化支持 -- 🐞 修复 webpack 5 兼容问题。 -- 🐞 修复 Upload method 属性不生效问题 [#2837](https://github.com/vueComponent/ant-design-vue/issues/2837) -- 🐞 修复 Table 组件 filter 不支持 number 类型问题 [#3052](https://github.com/vueComponent/ant-design-vue/issues/3052) -- 🐞 修复 Table 固定列 ellipsis 不生效问题 [#2916](https://github.com/vueComponent/ant-design-vue/issues/2916) -- 🐞 修复 Table 自定义 expandIcon 不生效问题 [#3013](https://github.com/vueComponent/ant-design-vue/issues/3013) -- 🐞 修复 TreeSelect 不能自定义 slot 问题 [#2827](https://github.com/vueComponent/ant-design-vue/issues/2827) -- 🛎 更改 Avatar 的 srcSet 为 srcset - -## 2.0.0-beta.10 - -`2020-09-24` - -- 🌟 更新 Vue 依赖到 release 版本 -- 🐞 修复 Menu 在 Layout 中不折叠问题 [#2819](https://github.com/vueComponent/ant-design-vue/issues/2819) -- 🐞 修复 Tabs 切换时出现 warning 问题 [#2865](https://github.com/vueComponent/ant-design-vue/issues/2865) -- 🐞 修复输入框在 compositionend 时不触发 change 事件问题 -- 🐞 修复 Upload 上传按钮不消失问题 [#2884](https://github.com/vueComponent/ant-design-vue/issues/2884) -- 🐞 修复 Upload 自定义 method 不生效问题 [#2837](https://github.com/vueComponent/ant-design-vue/issues/2837) -- 🐞 修复若干 ts 类型错误 - -## 2.0.0-beta.8 - -- 🐞 修复 ts 类型错误 - -## 2.0.0-beta.7 - -- 🐞 修复 Descriptions Item 不支持 v-for 问题 [#2793](https://github.com/vueComponent/ant-design-vue/issues/2793) -- 🐞 修复 Modal button loading 效果不生效问题 [9257c1](https://github.com/vueComponent/ant-design-vue/commit/9257c1ea685db4339239589153aee3189d0434fe) -- 🐞 修复 Steps 组件使用 v-model 时不可点击的问题 [ec7309](https://github.com/vueComponent/ant-design-vue/commit/ec73097d9b6ea8e2f2942ac28853c19191ca3298) -- 🌟 Checkbox、Radio 添加事件声明 -- 🐞 修复 ts 类型错误 [802446](https://github.com/vueComponent/ant-design-vue/commit/8024469b8832cfc4fe85498b639bfb48820531aa) - -## 2.0.0-beta.6 - -- 🐞 修复 TreeSelectNode 子组件 TreeSelectNode 没有注册的问题 - -## 2.0.0-beta.5 - -- 🔥 支持 Vite。 - -## 2.0.0-beta.4 - -- 🌟 移除不再使用的 polyfill -- 🐞 修复 `Modal` afterClose 调用两次的问题 -- 🐞 补充 ts 类型文件缺少原生属性的声明 - -## 2.0.0-beta.3 - -- 🔥 支持 Typescript。 -- 🔥 新增 `Space` 组件。 -- 🐞 修复部分组件无法使用 css scope 问题 [4bdb24](https://github.com/vueComponent/ant-design-vue/commit/4bdb241aa674b50fafa29b3b98e291643f2a06cc)。 -- 🐞 修复 `List.Meta` 注册失败的问题 [03a42a](https://github.com/vueComponent/ant-design-vue/commit/03a42a5b35e7d42a39aedb1aba8346995be2c27e) -- 🐞 修复 `Table` 固定列情况下错位问题 [#1493](https://github.com/vueComponent/ant-design-vue/issues/1493) -- 🐞 修复 `Button` 没有垂直居中的问题 [bd71e3](https://github.com/vueComponent/ant-design-vue/commit/bd71e3806b73881f9a95028982d17a10b2cd0b5c) -- 🐞 修复 `Tabs` 多次出发 `change` 事件问题 [8ed937](https://github.com/vueComponent/ant-design-vue/commit/8ed937344a57142a575e5272f50933c9c4459a43) - -## 2.0.0-beta.2 - -`2020-08-14` +### 🔥🔥🔥 4.0 正式版发布 🔥🔥🔥 ### 设计规范调整 -- 行高从 `1.5`(`21px`) 调整为 `1.5715`(`22px`)。 -- 基础圆角调整,由`4px` 改为 `2px`。 -- 分割线颜色明度降低,由 `#E8E8E8` 改为 `#F0F0F0`。 -- Table 默认背景颜色从透明修改为白色。 - -### 兼容性调整 +- 基础圆角调整,由统一的 `2px` 改为四级圆角,分别为 `2px` `4px` `6px` `8px`,分别应用于不同场景,比如默认尺寸的 Button 的圆角调整为了 `6px`。 +- 主色调整,由 `#1890ff` 改为 `#1677ff`。 +- 整体阴影调整,由原本的三级阴影调整为两级,分别用于常驻页面的组件(如 Card)和交互反馈(如 Dropdown)。 +- 部分组件内间距调整。 +- 整体去线框化。 -- Vue 最低支持版本为 Vue 3.0。 +### 新增 5 个组件 -#### 调整的 API +- Segmented 分段控制器 +- WaterMark 水印 +- QrCode 二维码 +- FloatButton 悬浮按钮 +- Tour 漫游式引导 -- 移除了 LocaleProvider,请使用 `ConfigProvider` 替代。 -- 移除了 Tag 的 afterClose 属性。 -- 合并了 FormModel、Form,详见下方的 Form 重构部分。 -- `tabIndex`、`maxLength`、`readOnly`、`autoComplete`、`autoFocus` 更改为全小写。 -- 为了在 template 语法中更友好的使用插槽,所有涉及到 xxxRender, renderXxxx 的均改成单参数,涉及到 `itemRender`、`renderItem`、`customRender`、`dropdownRender`、`dateCellRender`、`dateFullCellRender`、`monthCellRender`、`monthFullCellRender`、`renderTabBar`。 -- 所有配置 scopedSlots 的地方统一改成 slots。 -- `{ on, props, attrs, ... }` 配置进行扁平化处理,如 `{ props: {type: 'xxx'}, on: {click: this.handleClick}}` 改成 `{ type: 'xxx', onClick: this.handleClick }`, 涉及相关字段:`okButtonProps`、`cancelButtonProps`。 -- xxx.sync 改成 v-model:xxx -- v-model 更改成 v-model:xxx,具体涉及组件: +### 技术调整 - - v-model 改成 v-model:checked 的组件有: CheckableTag、Checkbox、Switch - - v-model 改成 v-model:value 的组件有: Radio、Mentions、CheckboxGroup、Rate、DatePicker - - v-model 改成 v-model:visible 的组件有: Tag、Popconfirm、Popove、Tooltip、Moda、Dropdown - - v-model 改成 v-model:activeKey 的组件有: Collaps、Tabs - - v-model 改成 v-model:current 的组件有: Steps - - v-model 改成 v-model:selectedKeys 的组件有: Menu +- 弃用 less,采用 CSS-in-JS,更好地支持动态主题。 + - 所有 less 文件全部移除,less 变量不再支持透出。 + - 产物中不再包含 css 文件。由于 CSS-in-JS 支持按需引入,原本的 `ant-design-vue/dist/antd.css` 也已经移除,如果需要重置一些基本样式请引入 `ant-design-vue/dist/reset.css`。 + - 如果需要组件重置样式,又不想引入 `ant-design-vue/dist/reset.css` 从而导致污染全局样式的话,可以尝试在应用最外层使用[App 组件](/components/app-cn),解决原生元素没有 ant-design-vue 规范样式的问题。 +- 移除 css variables 以及在此之上构筑的动态主题方案。 +- LocaleProvider 在 3.x 中已经废弃(使用 `` 替代),我们在 4.x 里彻底移除了相关目录 `ant-design-vue/es/locale-provider`、`ant-design-vue/lib/locale-provider`。 +- 不再支持 `babel-plugin-import`,CSS-in-JS 本身具有按需加载的能力,不再需要插件支持。 -#### 图标升级 +#### 组件 API 调整 -在 `ant-design-vue@1.2.0` 中,我们引入了 svg 图标([为何使用 svg 图标?](https://github.com/ant-design/ant-design/issues/10353))。使用了字符串命名的图标 API 无法做到按需加载,因而全量引入了 svg 图标文件,这大大增加了打包产物的尺寸。在 2.0 中,我们调整了图标的使用 API 从而支持 tree shaking,减少默认包体积约 150 KB(Gzipped)。 +- 组件弹框的 classname API 统一为 `popupClassName`,`dropdownClassName` 等类似 API 都会被替换。 -旧版 Icon 使用方式将被废弃: + - AutoComplete 组件 + - Cascader 组件 + - Select 组件 + - TreeSelect 组件 + - TimePicker 组件 + - DatePicker 组件 + - Mentions 组件 -```html - - -``` +- 组件弹框的受控可见 API 统一为 `open`,`visible` 等类似 API 都会被替换。 + - Drawer 组件 `visible` 变为 `open`。 + - Modal 组件 `visible` 变为 `open`。 + - Dropdown 组件 `visible` 变为 `open`。 + - Tooltip 组件 `visible` 变为 `open`。 + - Tag 组件 `visible` 已移除。 + - Slider 组件 `tooltip` 相关 API 收敛到 `tooltip` 属性中。 + - Table 组件 `filterDropdownVisible` 变为 `filterDropdownOpen`。 +- `getPopupContainer`: 所有的 `getPopupContainer` 都需要保证返回的是唯一的 div。 +- Drawer `style` 和 `class` 迁移至 Drawer 弹层区域上,原属性替换为 `rootClassName` 和 `rootStyle`。 -2.0 中会采用按需引入的方式: +#### 组件重构与移除 -```html - - -``` +- 移除 `locale-provider` 目录。`LocaleProvider` 在 v4 中已移除,请使用 `ConfigProvider` 替代。 -#### 组件重构 +- 移除栅格布局中的`xxxl`断点属性。 `xxxl`属性已经在 v4 被移除,您可以使用 [主题定制](/docs/vue/customize-theme-cn) 修改 `screen[XS|SM|MD|LG|XL|XXL]` 来修改断点值实现。 -在 1.x 中我们提供了 Form、FormModel 两个表单组件,原有的 Form 组件使用 v-decorator 进行数据绑定,在 Vue2 中我们通过上下文进行强制更新组件,但是在 Vue3 中,由于引入 patchFlag 等优化方式,强制刷新会破坏 patchFlag 带来的性能优势。所以在 2.0 版本中我们将 Form、FormModel 进行合并,保留了 FormModel 的使用方式,丰富了相关功能,并改名成 Form。 +- BackTop 组件在 `4.0.0` 中废弃,移至 FloatButton 悬浮按钮中。如需使用,可以从 FloatButton 中引入。 -涉及改动: +### [升级指南](/docs/vue/migration-v4-cn) -- Form 新增 `scrollToFirstError`,`name`,`validateTrigger` 属性,新增 `finish`、`finishFailed` 事件,新增 `scrollToField` 方法。 -- Form.Item 新增 `validateFirst`, `validateTrigger`, 废弃 `prop` 属性,使用 `name` 替换。 -- 嵌套字段路径使用数组,过去版本我们通过 . 代表嵌套路径(诸如 user.name 来代表 { user: { name: '' } })。然而在一些后台系统中,变量名中也会带上 .。这造成用户需要额外的代码进行转化,因而新版中,嵌套路径通过数组来表示以避免错误的处理行为(如 ['user', 'name'])。 -- validateFields 不再支持 callback。validateFields 会返回 Promise 对象,因而你可以通过 async/await 或者 then/catch 来执行对应的错误处理。不再需要判断 errors 是否为空: +## 3.x -```js -// v1 -// eslint-disable-next-line no-undef,no-unused-vars -validateFields((err, value) => { - if (!err) { - // Do something with value - } -}); -``` +去 [GitHub](https://github.com/vueComponent/ant-design-vue/blob/3.x/CHANGELOG.zh-CN.md) 查看 `3.x` 的 Change Log。 -改成 +## 2.x -```js -// v2 -// eslint-disable-next-line no-undef,no-unused-vars -validateFields().then(values => { - // Do something with value -}); -``` +去 [GitHub](https://github.com/vueComponent/ant-design-vue/blob/2.x/CHANGELOG.zh-CN.md) 查看 `2.x` 的 Change Log。 ## 1.x diff --git a/README-zh_CN.md b/README-zh_CN.md index 7e209e324..b13bfeb79 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -66,6 +66,7 @@ $ yarn add ant-design-vue | [vue-cli-plugin-ant-design](https://github.com/vueComponent/vue-cli-plugin-ant-design) | 使用 vue-cli3 快速使用 ant-design-vue 组件库 | | [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | 在 DOM 模板中,您可以使用 ant-design-vue 组件的自定义事件(camelCase) | | [@formily/antdv](https://github.com/formilyjs/antdv) | 这是一个结合了 Formily 和 ant-design-vue 的组件库 | +| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | ant-design-vue 的 nuxt 模块扩展 | ## 问答 diff --git a/README.md b/README.md index 6ddc5b4f2..42c1cd876 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ English | [简体中文](./README-zh_CN.md) ## Environment Support -- Modern browsers. v1.x support Internet Explorer 9+ (with [polyfills](https://www.antdv.com/docs/vue/getting-started/#Compatibility)) +- Modern browsers. v1.x support Internet Explorer 9+ (with [polyfills](https://www.antdv.com/docs/vue/getting-started/#compatibility)) - Server-side Rendering - Support Vue 2 & Vue 3 - [Electron](https://electronjs.org/) @@ -66,6 +66,7 @@ If you are in a bad network environment,you can try other registries and tools | [vue-cli-plugin-ant-design](https://github.com/vueComponent/vue-cli-plugin-ant-design) | Vue-cli 3 plugin to add ant-design-vue | | [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | The library function, implemented in the DOM template, can use the custom event of the ant-design-vue component (camelCase) | | [@formily/antdv](https://github.com/formilyjs/antdv) | The Library with Formily and ant-design-vue | +| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | A nuxt module for ant-design-vue | ## Donation diff --git a/antd-tools/generator-types/src/formatter.ts b/antd-tools/generator-types/src/formatter.ts index 722077ecf..581ac46be 100644 --- a/antd-tools/generator-types/src/formatter.ts +++ b/antd-tools/generator-types/src/formatter.ts @@ -91,7 +91,7 @@ export function formatter( !tableTitle.includes('()') ) { const childTag: VueTag = { - name: getComponentName(tableTitle.replaceAll('.', '').replaceAll('/', ''), tagPrefix), + name: getComponentName(tableTitle.replace(/\.|\//g, ''), tagPrefix), slots: [], events: [], attributes: [], diff --git a/antd-tools/generator-types/src/index.ts b/antd-tools/generator-types/src/index.ts index c500b38e1..4b1e3d43f 100644 --- a/antd-tools/generator-types/src/index.ts +++ b/antd-tools/generator-types/src/index.ts @@ -7,6 +7,7 @@ import { outputFileSync, readFileSync } from 'fs-extra'; import type { Options, VueTag } from './type'; import { getComponentName, normalizePath, toKebabCase } from './utils'; import { genVeturAttributes, genVeturTags } from './vetur'; +import { flatMap } from 'lodash'; async function readMarkdown(options: Options): Promise> { const mdPaths = await glob(normalizePath(`${options.path}/**/*.md`)); @@ -22,7 +23,7 @@ async function readMarkdown(options: Options): Promise> { }) .filter(item => item) as VueTag[][]; const tags: Map = new Map(); - data.flatMap(item => item).forEach(mergedTag => mergeTag(tags, mergedTag)); + flatMap(data, item => item).forEach(mergedTag => mergeTag(tags, mergedTag)); return tags; } diff --git a/antd-tools/generator-types/src/parser.ts b/antd-tools/generator-types/src/parser.ts index 5a20559e5..59f7d2483 100644 --- a/antd-tools/generator-types/src/parser.ts +++ b/antd-tools/generator-types/src/parser.ts @@ -27,7 +27,7 @@ function readLine(input: string) { function splitTableLine(line: string) { line = line.replace(/\\\|/g, 'JOIN'); - const items = line.split('|').map(item => item.trim().replaceAll('JOIN', '|')); + const items = line.split('|').map(item => item.trim().replace(/JOIN/g, '|')); // remove pipe character on both sides items.pop(); diff --git a/antd-tools/getBabelCommonConfig.js b/antd-tools/getBabelCommonConfig.js index 53809d287..3232837b9 100644 --- a/antd-tools/getBabelCommonConfig.js +++ b/antd-tools/getBabelCommonConfig.js @@ -20,7 +20,8 @@ module.exports = function (modules) { resolve('@babel/plugin-transform-runtime'), { useESModules: modules === false, - version: '^7.10.4', + version: + require(`${process.cwd()}/package.json`).dependencies['@babel/runtime'] || '^7.10.4', }, ], // resolve('babel-plugin-inline-import-data-uri'), diff --git a/antd-tools/getWebpackConfig.js b/antd-tools/getWebpackConfig.js index 45ec35566..59d7bdba0 100644 --- a/antd-tools/getWebpackConfig.js +++ b/antd-tools/getWebpackConfig.js @@ -150,36 +150,6 @@ function getWebpackConfig(modules) { }, ], }, - { - test: /\.less$/, - use: [ - MiniCssExtractPlugin.loader, - { - loader: 'css-loader', - options: { - sourceMap: true, - }, - }, - { - loader: 'postcss-loader', - options: { - postcssOptions: { - plugins: ['autoprefixer'], - }, - sourceMap: true, - }, - }, - { - loader: 'less-loader', - options: { - lessOptions: { - javascriptEnabled: true, - }, - sourceMap: true, - }, - }, - ], - }, // Images { test: svgRegex, @@ -200,7 +170,7 @@ function getWebpackConfig(modules) { new webpack.BannerPlugin(` ${pkg.name} v${pkg.version} -Copyright 2017-present, ant-design-vue. +Copyright 2017-present, Ant Design Vue. All rights reserved. `), new WebpackBar({ @@ -215,7 +185,7 @@ All rights reserved. }; if (process.env.RUN_ENV === 'PRODUCTION') { - const entry = ['./index']; + let entry = ['./index']; config.externals = [ { vue: { @@ -223,11 +193,13 @@ All rights reserved. commonjs2: 'vue', commonjs: 'vue', amd: 'vue', + module: 'vue', }, }, ]; config.output.library = distFileBaseName; config.output.libraryTarget = 'umd'; + config.output.globalObject = 'this'; config.optimization = { minimizer: [ new TerserPlugin({ @@ -238,7 +210,6 @@ All rights reserved. }), ], }; - // Development const uncompressedConfig = merge({}, config, { entry: { diff --git a/antd-tools/gulpfile.js b/antd-tools/gulpfile.js index bb8c8b6dd..b7c7c20cd 100644 --- a/antd-tools/gulpfile.js +++ b/antd-tools/gulpfile.js @@ -5,7 +5,6 @@ const getBabelCommonConfig = require('./getBabelCommonConfig'); const merge2 = require('merge2'); const { execSync } = require('child_process'); const through2 = require('through2'); -const transformLess = require('./transformLess'); const webpack = require('webpack'); const babel = require('gulp-babel'); const argv = require('minimist')(process.argv.slice(2)); @@ -27,15 +26,22 @@ const compareVersions = require('compare-versions'); const getTSCommonConfig = require('./getTSCommonConfig'); const replaceLib = require('./replaceLib'); const sortApiTable = require('./sortApiTable'); +const { glob } = require('glob'); const packageJson = require(getProjectPath('package.json')); const tsDefaultReporter = ts.reporter.defaultReporter(); const cwd = process.cwd(); const libDir = getProjectPath('lib'); const esDir = getProjectPath('es'); +const localeDir = getProjectPath('locale'); const tsConfig = getTSCommonConfig(); +// FIXME: hard code, not find typescript can modify the path resolution +const localeDts = `import type { Locale } from '../lib/locale-provider'; +declare const localeValues: Locale; +export default localeValues;`; + function dist(done) { rimraf.sync(path.join(cwd, 'dist')); process.env.RUN_ENV = 'PRODUCTION'; @@ -108,6 +114,11 @@ gulp.task('tsc', () => ), ); +gulp.task('clean', () => { + rimraf.sync(getProjectPath('_site')); + rimraf.sync(getProjectPath('_data')); +}); + function babelify(js, modules) { const babelConfig = getBabelCommonConfig(modules); babelConfig.babelrc = false; @@ -118,17 +129,7 @@ function babelify(js, modules) { const stream = js.pipe(babel(babelConfig)).pipe( through2.obj(function z(file, encoding, next) { this.push(file.clone()); - if (file.path.match(/\/style\/index\.(js|jsx|ts|tsx)$/)) { - const content = file.contents.toString(encoding); - file.contents = Buffer.from( - content - .replace(/\/style\/?'/g, "/style/css'") - .replace(/\/style\/?"/g, '/style/css"') - .replace(/\.less/g, '.css'), - ); - file.path = file.path.replace(/index\.(js|jsx|ts|tsx)$/, 'css.js'); - this.push(file); - } else if (modules !== false) { + if (modules !== false) { const content = file.contents.toString(encoding); file.contents = Buffer.from( content @@ -144,47 +145,9 @@ function babelify(js, modules) { } function compile(modules) { - const { compile: { transformTSFile, transformFile, includeLessFile = [] } = {} } = getConfig(); + const { compile: { transformTSFile, transformFile } = {} } = getConfig(); rimraf.sync(modules !== false ? libDir : esDir); - // =============================== LESS =============================== - const less = gulp - .src(['components/**/*.less']) - .pipe( - through2.obj(function (file, encoding, next) { - // Replace content - const cloneFile = file.clone(); - const content = file.contents.toString().replace(/^\uFEFF/, ''); - - cloneFile.contents = Buffer.from(content); - - // Clone for css here since `this.push` will modify file.path - const cloneCssFile = cloneFile.clone(); - - this.push(cloneFile); - - // Transform less file - if ( - file.path.match(/(\/|\\)style(\/|\\)index\.less$/) || - file.path.match(/(\/|\\)style(\/|\\)v2-compatible-reset\.less$/) || - includeLessFile.some(regex => file.path.match(regex)) - ) { - transformLess(cloneCssFile.contents.toString(), cloneCssFile.path) - .then(css => { - cloneCssFile.contents = Buffer.from(css); - cloneCssFile.path = cloneCssFile.path.replace(/\.less$/, '.css'); - this.push(cloneCssFile); - next(); - }) - .catch(e => { - console.error(e); - }); - } else { - next(); - } - }), - ) - .pipe(gulp.dest(modules === false ? esDir : libDir)); const assets = gulp .src(['components/**/*.@(png|svg)']) .pipe(gulp.dest(modules === false ? esDir : libDir)); @@ -259,7 +222,26 @@ function compile(modules) { tsResult.on('end', check); const tsFilesStream = babelify(tsResult.js, modules); const tsd = tsResult.dts.pipe(gulp.dest(modules === false ? esDir : libDir)); - return merge2([less, tsFilesStream, tsd, assets, transformFileStream].filter(s => s)); + return merge2([tsFilesStream, tsd, assets, transformFileStream].filter(s => s)); +} + +function generateLocale() { + if (!fs.existsSync(localeDir)) { + fs.mkdirSync(localeDir); + } + + const localeFiles = glob.sync('components/locale/*.ts?(x)'); + localeFiles.forEach(item => { + const match = item.match(/components\/locale\/(.*)\.tsx?/); + if (match) { + const locale = match[1]; + fs.writeFileSync( + path.join(localeDir, `${locale}.js`), + `module.exports = require('../lib/locale/${locale}');`, + ); + fs.writeFileSync(path.join(localeDir, `${locale}.d.ts`), localeDts); + } + }); } function tag() { @@ -395,7 +377,10 @@ gulp.task('compile-with-es', done => { gulp.task('compile-with-lib', done => { console.log('[Parallel] Compile to js...'); - compile().on('finish', done); + compile().on('finish', () => { + generateLocale(); + done(); + }); }); gulp.task('compile-finalize', done => { diff --git a/antd-tools/transformLess.js b/antd-tools/transformLess.js deleted file mode 100644 index 0e949b243..000000000 --- a/antd-tools/transformLess.js +++ /dev/null @@ -1,27 +0,0 @@ -const less = require('less'); -const path = require('path'); -const postcss = require('postcss'); -const autoprefixer = require('autoprefixer'); -const NpmImportPlugin = require('less-plugin-npm-import'); -const { getConfig } = require('./utils/projectHelper'); - -function transformLess(lessContent, lessFilePath, config = {}) { - const { cwd = process.cwd() } = config; - const { compile: { lessConfig } = {} } = getConfig(); - const resolvedLessFile = path.resolve(cwd, lessFilePath); - - // Do less compile - const lessOpts = { - paths: [path.dirname(resolvedLessFile)], - filename: resolvedLessFile, - plugins: [new NpmImportPlugin({ prefix: '~' })], - javascriptEnabled: true, - ...lessConfig, - }; - return less - .render(lessContent, lessOpts) - .then(result => postcss([autoprefixer]).process(result.css, { from: undefined })) - .then(r => r.css); -} - -module.exports = transformLess; diff --git a/antd-tools/utils/styleUtil.js b/antd-tools/utils/styleUtil.js deleted file mode 100644 index 7b05ee315..000000000 --- a/antd-tools/utils/styleUtil.js +++ /dev/null @@ -1,11 +0,0 @@ -// We convert less import in es/lib to css file path -function cssInjection(content) { - return content - .replace(/\/style\/?'/g, "/style/css'") - .replace(/\/style\/?"/g, '/style/css"') - .replace(/\.less/g, '.css'); -} - -module.exports = { - cssInjection, -}; diff --git a/components/_util/ActionButton.tsx b/components/_util/ActionButton.tsx index 19b1365ae..0958d2404 100644 --- a/components/_util/ActionButton.tsx +++ b/components/_util/ActionButton.tsx @@ -1,10 +1,12 @@ import type { ExtractPropTypes, PropType } from 'vue'; -import { onMounted, ref, defineComponent, onBeforeUnmount } from 'vue'; +import { shallowRef, onMounted, defineComponent, onBeforeUnmount } from 'vue'; import Button from '../button'; import type { ButtonProps } from '../button'; import type { LegacyButtonType } from '../button/buttonTypes'; import { convertLegacyProps } from '../button/buttonTypes'; import useDestroyed from './hooks/useDestroyed'; +import { objectType } from './type'; +import { findDOMNode } from './props-util'; const actionButtonProps = { type: { @@ -14,15 +16,15 @@ const actionButtonProps = { close: Function, autofocus: Boolean, prefixCls: String, - buttonProps: Object as PropType, + buttonProps: objectType(), emitEvent: Boolean, quitOnNullishReturnValue: Boolean, }; export type ActionButtonProps = ExtractPropTypes; -function isThenable(thing?: PromiseLike): boolean { - return !!(thing && !!thing.then); +function isThenable(thing?: PromiseLike): boolean { + return !!(thing && thing.then); } export default defineComponent({ @@ -30,22 +32,25 @@ export default defineComponent({ name: 'ActionButton', props: actionButtonProps, setup(props, { slots }) { - const clickedRef = ref(false); - const buttonRef = ref(); - const loading = ref(false); + const clickedRef = shallowRef(false); + const buttonRef = shallowRef(); + const loading = shallowRef(false); let timeoutId: any; const isDestroyed = useDestroyed(); onMounted(() => { if (props.autofocus) { - timeoutId = setTimeout(() => buttonRef.value.$el?.focus()); + timeoutId = setTimeout(() => findDOMNode(buttonRef.value)?.focus?.()); } }); onBeforeUnmount(() => { clearTimeout(timeoutId); }); + const onInternalClose = (...args: any[]) => { + props.close?.(...args); + }; + const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike) => { - const { close } = props; if (!isThenable(returnValueOfOnOk)) { return; } @@ -55,48 +60,46 @@ export default defineComponent({ if (!isDestroyed.value) { loading.value = false; } - close(...args); + onInternalClose(...args); clickedRef.value = false; }, (e: Error) => { - // Emit error when catch promise reject - // eslint-disable-next-line no-console - console.error(e); // See: https://github.com/ant-design/ant-design/issues/6183 if (!isDestroyed.value) { loading.value = false; } clickedRef.value = false; + return Promise.reject(e); }, ); }; const onClick = (e: MouseEvent) => { - const { actionFn, close = () => {} } = props; + const { actionFn } = props; if (clickedRef.value) { return; } clickedRef.value = true; if (!actionFn) { - close(); + onInternalClose(); return; } - let returnValueOfOnOk; + let returnValueOfOnOk: PromiseLike; if (props.emitEvent) { returnValueOfOnOk = actionFn(e); if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) { clickedRef.value = false; - close(e); + onInternalClose(e); return; } } else if (actionFn.length) { - returnValueOfOnOk = actionFn(close); + returnValueOfOnOk = actionFn(props.close); // https://github.com/ant-design/ant-design/issues/23358 clickedRef.value = false; } else { returnValueOfOnOk = actionFn(); if (!returnValueOfOnOk) { - close(); + onInternalClose(); return; } } diff --git a/components/_util/BaseInput.tsx b/components/_util/BaseInput.tsx index 009613a74..db85e3a4f 100644 --- a/components/_util/BaseInput.tsx +++ b/components/_util/BaseInput.tsx @@ -1,4 +1,4 @@ -import { defineComponent, ref, withDirectives } from 'vue'; +import { defineComponent, shallowRef, withDirectives } from 'vue'; import antInput from './antInputDirective'; import PropTypes from './vue-types'; const BaseInput = defineComponent({ @@ -8,7 +8,7 @@ const BaseInput = defineComponent({ }, emits: ['change', 'input'], setup(_p, { emit }) { - const inputRef = ref(null); + const inputRef = shallowRef(null); const handleChange = (e: Event) => { const { composing } = e.target as any; if ((e as any).isComposing || composing) { diff --git a/components/_util/PortalWrapper.tsx b/components/_util/PortalWrapper.tsx index 5c02f3ba3..65d15c780 100644 --- a/components/_util/PortalWrapper.tsx +++ b/components/_util/PortalWrapper.tsx @@ -1,20 +1,20 @@ import PropTypes from './vue-types'; -import switchScrollingEffect from './switchScrollingEffect'; -import setStyle from './setStyle'; import Portal from './Portal'; import { defineComponent, - ref, + shallowRef, watch, onMounted, onBeforeUnmount, onUpdated, getCurrentInstance, nextTick, + computed, } from 'vue'; import canUseDom from './canUseDom'; -import ScrollLocker from '../vc-util/Dom/scrollLocker'; import raf from './raf'; +import { booleanType } from './type'; +import useScrollLocker from './hooks/useScrollLocker'; let openCount = 0; const supportDom = canUseDom(); @@ -24,17 +24,13 @@ export function getOpenCount() { return process.env.NODE_ENV === 'test' ? openCount : 0; } -// https://github.com/ant-design/ant-design/issues/19340 -// https://github.com/ant-design/ant-design/issues/19332 -let cacheOverflow = {}; - const getParent = (getContainer: GetContainer) => { if (!supportDom) { return null; } if (getContainer) { if (typeof getContainer === 'string') { - return document.querySelectorAll(getContainer)[0]; + return document.querySelectorAll(getContainer)[0] as HTMLElement; } if (typeof getContainer === 'function') { return getContainer(); @@ -57,24 +53,25 @@ export default defineComponent({ forceRender: { type: Boolean, default: undefined }, getContainer: PropTypes.any, visible: { type: Boolean, default: undefined }, + autoLock: booleanType(), + didUpdate: Function, }, setup(props, { slots }) { - const container = ref(); - const componentRef = ref(); - const rafId = ref(); - const scrollLocker = new ScrollLocker({ - container: getParent(props.getContainer) as HTMLElement, - }); + const container = shallowRef(); + const componentRef = shallowRef(); + const rafId = shallowRef(); const removeCurrentContainer = () => { // Portal will remove from `parentNode`. // Let's handle this again to avoid refactor issue. container.value?.parentNode?.removeChild(container.value); + container.value = null; }; + let parent: HTMLElement = null; const attachToParent = (force = false) => { if (force || (container.value && !container.value.parentNode)) { - const parent = getParent(props.getContainer); + parent = getParent(props.getContainer); if (parent) { parent.appendChild(container.value); return true; @@ -86,13 +83,13 @@ export default defineComponent({ return true; }; // attachToParent(); - + const defaultContainer = document.createElement('div'); const getContainer = () => { if (!supportDom) { return null; } if (!container.value) { - container.value = document.createElement('div'); + container.value = defaultContainer; attachToParent(true); } setWrapperClassName(); @@ -108,41 +105,33 @@ export default defineComponent({ setWrapperClassName(); attachToParent(); }); - /** - * Enhance ./switchScrollingEffect - * 1. Simulate document body scroll bar with - * 2. Record body has overflow style and recover when all of PortalWrapper invisible - * 3. Disable body scroll when PortalWrapper has open - * - * @memberof PortalWrapper - */ - const switchScrolling = () => { - if (openCount === 1 && !Object.keys(cacheOverflow).length) { - switchScrollingEffect(); - // Must be set after switchScrollingEffect - cacheOverflow = setStyle({ - overflow: 'hidden', - overflowX: 'hidden', - overflowY: 'hidden', - }); - } else if (!openCount) { - setStyle(cacheOverflow); - cacheOverflow = {}; - switchScrollingEffect(true); - } - }; + const instance = getCurrentInstance(); + + useScrollLocker( + computed(() => { + return ( + props.autoLock && + props.visible && + canUseDom() && + (container.value === document.body || container.value === defaultContainer) + ); + }), + ); onMounted(() => { let init = false; watch( [() => props.visible, () => props.getContainer], ([visible, getContainer], [prevVisible, prevGetContainer]) => { // Update count - if (supportDom && getParent(props.getContainer) === document.body) { - if (visible && !prevVisible) { - openCount += 1; - } else if (init) { - openCount -= 1; + if (supportDom) { + parent = getParent(props.getContainer); + if (parent === document.body) { + if (visible && !prevVisible) { + openCount += 1; + } else if (init) { + openCount -= 1; + } } } @@ -157,17 +146,6 @@ export default defineComponent({ ) { removeCurrentContainer(); } - // updateScrollLocker - if ( - visible && - visible !== prevVisible && - supportDom && - getParent(getContainer) !== scrollLocker.getContainer() - ) { - scrollLocker.reLock({ - container: getParent(getContainer) as HTMLElement, - }); - } } init = true; }, @@ -184,30 +162,27 @@ export default defineComponent({ }); onBeforeUnmount(() => { - const { visible, getContainer } = props; - if (supportDom && getParent(getContainer) === document.body) { + const { visible } = props; + if (supportDom && parent === document.body) { // 离开时不会 render, 导到离开时数值不变,改用 func 。。 openCount = visible && openCount ? openCount - 1 : openCount; } removeCurrentContainer(); raf.cancel(rafId.value); }); - return () => { const { forceRender, visible } = props; let portal = null; const childProps = { getOpenCount: () => openCount, getContainer, - switchScrollingEffect: switchScrolling, - scrollLocker, }; - if (forceRender || visible || componentRef.value) { portal = ( slots.default?.(childProps) }} > ); diff --git a/components/_util/colors.ts b/components/_util/colors.ts index bb3c2ca2c..8bb5a90ab 100644 --- a/components/_util/colors.ts +++ b/components/_util/colors.ts @@ -1,23 +1,34 @@ -import type { ElementOf } from './type'; -import { tuple } from './type'; +import type { PresetColorKey } from '../theme/interface'; +import { PresetColors } from '../theme/interface'; -export const PresetStatusColorTypes = tuple('success', 'processing', 'error', 'default', 'warning'); +type InverseColor = `${PresetColorKey}-inverse`; +const inverseColors = PresetColors.map(color => `${color}-inverse`); -export const PresetColorTypes = tuple( - 'pink', - 'red', - 'yellow', - 'orange', - 'cyan', - 'green', - 'blue', - 'purple', - 'geekblue', - 'magenta', - 'volcano', - 'gold', - 'lime', -); +export const PresetStatusColorTypes = [ + 'success', + 'processing', + 'error', + 'default', + 'warning', +] as const; -export type PresetColorType = ElementOf; -export type PresetStatusColorType = ElementOf; +export type PresetColorType = PresetColorKey | InverseColor; + +export type PresetStatusColorType = (typeof PresetStatusColorTypes)[number]; + +/** + * determine if the color keyword belongs to the `Ant Design` {@link PresetColors}. + * @param color color to be judged + * @param includeInverse whether to include reversed colors + */ +export function isPresetColor(color?: any, includeInverse = true) { + if (includeInverse) { + return [...inverseColors, ...PresetColors].includes(color); + } + + return PresetColors.includes(color); +} + +export function isPresetStatusColor(color?: any): color is PresetStatusColorType { + return PresetStatusColorTypes.includes(color); +} diff --git a/components/_util/createContext.ts b/components/_util/createContext.ts new file mode 100644 index 000000000..2a666d3f6 --- /dev/null +++ b/components/_util/createContext.ts @@ -0,0 +1,22 @@ +import { inject, provide, reactive, watchEffect } from 'vue'; + +function createContext>(defaultValue?: T) { + const contextKey = Symbol('contextKey'); + const useProvide = (props: T, newProps?: T) => { + const mergedProps = reactive({} as T); + provide(contextKey, mergedProps); + watchEffect(() => { + Object.assign(mergedProps, props, newProps || {}); + }); + return mergedProps; + }; + const useInject = () => { + return inject(contextKey, defaultValue as T) || ({} as T); + }; + return { + useProvide, + useInject, + }; +} + +export default createContext; diff --git a/components/_util/cssinjs/Cache.ts b/components/_util/cssinjs/Cache.ts new file mode 100644 index 000000000..007e4a867 --- /dev/null +++ b/components/_util/cssinjs/Cache.ts @@ -0,0 +1,25 @@ +export type KeyType = string | number; +type ValueType = [number, any]; // [times, realValue] + +class Entity { + /** @private Internal cache map. Do not access this directly */ + cache = new Map(); + + get(keys: KeyType[] | string): ValueType | null { + return this.cache.get(Array.isArray(keys) ? keys.join('%') : keys) || null; + } + + update(keys: KeyType[] | string, valueFn: (origin: ValueType | null) => ValueType | null) { + const path = Array.isArray(keys) ? keys.join('%') : keys; + const prevValue = this.cache.get(path)!; + const nextValue = valueFn(prevValue); + + if (nextValue === null) { + this.cache.delete(path); + } else { + this.cache.set(path, nextValue); + } + } +} + +export default Entity; diff --git a/components/_util/cssinjs/Keyframes.ts b/components/_util/cssinjs/Keyframes.ts new file mode 100644 index 000000000..64b99e27c --- /dev/null +++ b/components/_util/cssinjs/Keyframes.ts @@ -0,0 +1,19 @@ +import type { CSSInterpolation } from './hooks/useStyleRegister'; + +class Keyframe { + private name: string; + style: CSSInterpolation; + + constructor(name: string, style: CSSInterpolation) { + this.name = name; + this.style = style; + } + + getName(hashId = ''): string { + return hashId ? `${hashId}-${this.name}` : this.name; + } + + _keyframe = true; +} + +export default Keyframe; diff --git a/components/_util/cssinjs/StyleContext.tsx b/components/_util/cssinjs/StyleContext.tsx new file mode 100644 index 000000000..c1abe883e --- /dev/null +++ b/components/_util/cssinjs/StyleContext.tsx @@ -0,0 +1,157 @@ +import type { ShallowRef, ExtractPropTypes, InjectionKey, Ref } from 'vue'; +import { provide, defineComponent, unref, inject, watch, shallowRef } from 'vue'; +import CacheEntity from './Cache'; +import type { Linter } from './linters/interface'; +import type { Transformer } from './transformers/interface'; +import { arrayType, booleanType, objectType, someType, stringType, withInstall } from '../type'; +import initDefaultProps from '../props-util/initDefaultProps'; +export const ATTR_TOKEN = 'data-token-hash'; +export const ATTR_MARK = 'data-css-hash'; +export const ATTR_DEV_CACHE_PATH = 'data-dev-cache-path'; + +// Mark css-in-js instance in style element +export const CSS_IN_JS_INSTANCE = '__cssinjs_instance__'; +export const CSS_IN_JS_INSTANCE_ID = Math.random().toString(12).slice(2); + +export function createCache() { + if (typeof document !== 'undefined' && document.head && document.body) { + const styles = document.body.querySelectorAll(`style[${ATTR_MARK}]`) || []; + const { firstChild } = document.head; + + Array.from(styles).forEach(style => { + (style as any)[CSS_IN_JS_INSTANCE] = + (style as any)[CSS_IN_JS_INSTANCE] || CSS_IN_JS_INSTANCE_ID; + + // Not force move if no head + document.head.insertBefore(style, firstChild); + }); + + // Deduplicate of moved styles + const styleHash: Record = {}; + Array.from(document.querySelectorAll(`style[${ATTR_MARK}]`)).forEach(style => { + const hash = style.getAttribute(ATTR_MARK)!; + if (styleHash[hash]) { + if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) { + style.parentNode?.removeChild(style); + } + } else { + styleHash[hash] = true; + } + }); + } + + return new CacheEntity(); +} + +export type HashPriority = 'low' | 'high'; + +export interface StyleContextProps { + autoClear?: boolean; + /** @private Test only. Not work in production. */ + mock?: 'server' | 'client'; + /** + * Only set when you need ssr to extract style on you own. + * If not provided, it will auto create `; + }); + + return styleText; +} diff --git a/components/_util/cssinjs/index.ts b/components/_util/cssinjs/index.ts new file mode 100644 index 000000000..49d0443b5 --- /dev/null +++ b/components/_util/cssinjs/index.ts @@ -0,0 +1,67 @@ +import useCacheToken from './hooks/useCacheToken'; +import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister'; +import useStyleRegister, { extractStyle } from './hooks/useStyleRegister'; +import Keyframes from './Keyframes'; +import type { Linter } from './linters'; +import { legacyNotSelectorLinter, logicalPropertiesLinter } from './linters'; +import type { StyleContextProps, StyleProviderProps } from './StyleContext'; +import { createCache, useStyleInject, useStyleProvider, StyleProvider } from './StyleContext'; +import type { DerivativeFunc, TokenType } from './theme'; +import { createTheme, Theme } from './theme'; +import type { Transformer } from './transformers/interface'; +import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties'; + +const cssinjs = { + Theme, + createTheme, + useStyleRegister, + useCacheToken, + createCache, + useStyleInject, + useStyleProvider, + Keyframes, + extractStyle, + + // Transformer + legacyLogicalPropertiesTransformer, + + // Linters + logicalPropertiesLinter, + legacyNotSelectorLinter, + + // cssinjs + StyleProvider, +}; +export { + Theme, + createTheme, + useStyleRegister, + useCacheToken, + createCache, + useStyleInject, + useStyleProvider, + Keyframes, + extractStyle, + + // Transformer + legacyLogicalPropertiesTransformer, + + // Linters + logicalPropertiesLinter, + legacyNotSelectorLinter, + + // cssinjs + StyleProvider, +}; +export type { + TokenType, + CSSObject, + CSSInterpolation, + DerivativeFunc, + Transformer, + Linter, + StyleContextProps, + StyleProviderProps, +}; + +export default cssinjs; diff --git a/components/_util/cssinjs/linters/contentQuotesLinter.ts b/components/_util/cssinjs/linters/contentQuotesLinter.ts new file mode 100644 index 000000000..b1e60f08c --- /dev/null +++ b/components/_util/cssinjs/linters/contentQuotesLinter.ts @@ -0,0 +1,25 @@ +import type { Linter } from './interface'; +import { lintWarning } from './utils'; + +const linter: Linter = (key, value, info) => { + if (key === 'content') { + // From emotion: https://github.com/emotion-js/emotion/blob/main/packages/serialize/src/index.js#L63 + const contentValuePattern = + /(attr|counters?|url|(((repeating-)?(linear|radial))|conic)-gradient)\(|(no-)?(open|close)-quote/; + const contentValues = ['normal', 'none', 'initial', 'inherit', 'unset']; + if ( + typeof value !== 'string' || + (contentValues.indexOf(value) === -1 && + !contentValuePattern.test(value) && + (value.charAt(0) !== value.charAt(value.length - 1) || + (value.charAt(0) !== '"' && value.charAt(0) !== "'"))) + ) { + lintWarning( + `You seem to be using a value for 'content' without quotes, try replacing it with \`content: '"${value}"'\`.`, + info, + ); + } + } +}; + +export default linter; diff --git a/components/_util/cssinjs/linters/hashedAnimationLinter.ts b/components/_util/cssinjs/linters/hashedAnimationLinter.ts new file mode 100644 index 000000000..4c6fc948b --- /dev/null +++ b/components/_util/cssinjs/linters/hashedAnimationLinter.ts @@ -0,0 +1,15 @@ +import type { Linter } from './interface'; +import { lintWarning } from './utils'; + +const linter: Linter = (key, value, info) => { + if (key === 'animation') { + if (info.hashId && value !== 'none') { + lintWarning( + `You seem to be using hashed animation '${value}', in which case 'animationName' with Keyframe as value is recommended.`, + info, + ); + } + } +}; + +export default linter; diff --git a/components/_util/cssinjs/linters/index.ts b/components/_util/cssinjs/linters/index.ts new file mode 100644 index 000000000..a7a3ee100 --- /dev/null +++ b/components/_util/cssinjs/linters/index.ts @@ -0,0 +1,5 @@ +export { default as contentQuotesLinter } from './contentQuotesLinter'; +export { default as hashedAnimationLinter } from './hashedAnimationLinter'; +export type { Linter } from './interface'; +export { default as legacyNotSelectorLinter } from './legacyNotSelectorLinter'; +export { default as logicalPropertiesLinter } from './logicalPropertiesLinter'; diff --git a/components/_util/cssinjs/linters/interface.ts b/components/_util/cssinjs/linters/interface.ts new file mode 100644 index 000000000..2df3b6bc2 --- /dev/null +++ b/components/_util/cssinjs/linters/interface.ts @@ -0,0 +1,9 @@ +export interface LinterInfo { + path?: string; + hashId?: string; + parentSelectors: string[]; +} + +export interface Linter { + (key: string, value: string | number, info: LinterInfo): void; +} diff --git a/components/_util/cssinjs/linters/legacyNotSelectorLinter.ts b/components/_util/cssinjs/linters/legacyNotSelectorLinter.ts new file mode 100644 index 000000000..f38bf5a33 --- /dev/null +++ b/components/_util/cssinjs/linters/legacyNotSelectorLinter.ts @@ -0,0 +1,33 @@ +import type { Linter, LinterInfo } from './interface'; +import { lintWarning } from './utils'; + +function isConcatSelector(selector: string) { + const notContent = selector.match(/:not\(([^)]*)\)/)?.[1] || ''; + + // split selector. e.g. + // `h1#a.b` => ['h1', #a', '.b'] + const splitCells = notContent.split(/(\[[^[]*])|(?=[.#])/).filter(str => str); + + return splitCells.length > 1; +} + +function parsePath(info: LinterInfo) { + return info.parentSelectors.reduce((prev, cur) => { + if (!prev) { + return cur; + } + + return cur.includes('&') ? cur.replace(/&/g, prev) : `${prev} ${cur}`; + }, ''); +} + +const linter: Linter = (_key, _value, info) => { + const parentSelectorPath = parsePath(info); + const notList = parentSelectorPath.match(/:not\([^)]*\)/g) || []; + + if (notList.length > 0 && notList.some(isConcatSelector)) { + lintWarning(`Concat ':not' selector not support in legacy browsers.`, info); + } +}; + +export default linter; diff --git a/components/_util/cssinjs/linters/logicalPropertiesLinter.ts b/components/_util/cssinjs/linters/logicalPropertiesLinter.ts new file mode 100644 index 000000000..bdddcf73f --- /dev/null +++ b/components/_util/cssinjs/linters/logicalPropertiesLinter.ts @@ -0,0 +1,88 @@ +import type { Linter } from './interface'; +import { lintWarning } from './utils'; + +const linter: Linter = (key, value, info) => { + switch (key) { + case 'marginLeft': + case 'marginRight': + case 'paddingLeft': + case 'paddingRight': + case 'left': + case 'right': + case 'borderLeft': + case 'borderLeftWidth': + case 'borderLeftStyle': + case 'borderLeftColor': + case 'borderRight': + case 'borderRightWidth': + case 'borderRightStyle': + case 'borderRightColor': + case 'borderTopLeftRadius': + case 'borderTopRightRadius': + case 'borderBottomLeftRadius': + case 'borderBottomRightRadius': + lintWarning( + `You seem to be using non-logical property '${key}' which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`, + info, + ); + return; + case 'margin': + case 'padding': + case 'borderWidth': + case 'borderStyle': + // case 'borderColor': + if (typeof value === 'string') { + const valueArr = value.split(' ').map(item => item.trim()); + if (valueArr.length === 4 && valueArr[1] !== valueArr[3]) { + lintWarning( + `You seem to be using '${key}' property with different left ${key} and right ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`, + info, + ); + } + } + return; + case 'clear': + case 'textAlign': + if (value === 'left' || value === 'right') { + lintWarning( + `You seem to be using non-logical value '${value}' of ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`, + info, + ); + } + return; + case 'borderRadius': + if (typeof value === 'string') { + const radiusGroups = value.split('/').map(item => item.trim()); + const invalid = radiusGroups.reduce((result, group) => { + if (result) { + return result; + } + const radiusArr = group.split(' ').map(item => item.trim()); + // borderRadius: '2px 4px' + if (radiusArr.length >= 2 && radiusArr[0] !== radiusArr[1]) { + return true; + } + // borderRadius: '4px 4px 2px' + if (radiusArr.length === 3 && radiusArr[1] !== radiusArr[2]) { + return true; + } + // borderRadius: '4px 4px 2px 4px' + if (radiusArr.length === 4 && radiusArr[2] !== radiusArr[3]) { + return true; + } + return result; + }, false); + + if (invalid) { + lintWarning( + `You seem to be using non-logical value '${value}' of ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`, + info, + ); + } + } + return; + default: + } +}; + +export default linter; diff --git a/components/_util/cssinjs/linters/utils.ts b/components/_util/cssinjs/linters/utils.ts new file mode 100644 index 000000000..5b0853ff2 --- /dev/null +++ b/components/_util/cssinjs/linters/utils.ts @@ -0,0 +1,13 @@ +import devWarning from '../../../vc-util/warning'; +import type { LinterInfo } from './interface'; + +export function lintWarning(message: string, info: LinterInfo) { + const { path, parentSelectors } = info; + + devWarning( + false, + `[Ant Design Vue CSS-in-JS] ${path ? `Error in '${path}': ` : ''}${message}${ + parentSelectors.length ? ` Selector info: ${parentSelectors.join(' -> ')}` : '' + }`, + ); +} diff --git a/components/_util/cssinjs/theme/Theme.ts b/components/_util/cssinjs/theme/Theme.ts new file mode 100644 index 000000000..608bdde6c --- /dev/null +++ b/components/_util/cssinjs/theme/Theme.ts @@ -0,0 +1,38 @@ +import warning from '../../warning'; +import type { DerivativeFunc, TokenType } from './interface'; + +let uuid = 0; + +/** + * Theme with algorithms to derive tokens from design tokens. + * Use `createTheme` first which will help to manage the theme instance cache. + */ +export default class Theme { + private derivatives: DerivativeFunc[]; + public readonly id: number; + + constructor( + derivatives: + | DerivativeFunc + | DerivativeFunc[], + ) { + this.derivatives = Array.isArray(derivatives) ? derivatives : [derivatives]; + this.id = uuid; + + if (derivatives.length === 0) { + warning( + derivatives.length > 0, + '[Ant Design Vue CSS-in-JS] Theme should have at least one derivative function.', + ); + } + + uuid += 1; + } + + getDerivativeToken(token: DesignToken): DerivativeToken { + return this.derivatives.reduce( + (result, derivative) => derivative(token, result), + undefined as any, + ); + } +} diff --git a/components/_util/cssinjs/theme/ThemeCache.ts b/components/_util/cssinjs/theme/ThemeCache.ts new file mode 100644 index 000000000..db76ffa6a --- /dev/null +++ b/components/_util/cssinjs/theme/ThemeCache.ts @@ -0,0 +1,135 @@ +import type Theme from './Theme'; +import type { DerivativeFunc } from './interface'; + +// ================================== Cache ================================== +type ThemeCacheMap = Map< + DerivativeFunc, + { + map?: ThemeCacheMap; + value?: [Theme, number]; + } +>; + +type DerivativeOptions = DerivativeFunc[]; + +export function sameDerivativeOption(left: DerivativeOptions, right: DerivativeOptions) { + if (left.length !== right.length) { + return false; + } + for (let i = 0; i < left.length; i++) { + if (left[i] !== right[i]) { + return false; + } + } + return true; +} + +export default class ThemeCache { + public static MAX_CACHE_SIZE = 20; + public static MAX_CACHE_OFFSET = 5; + + private readonly cache: ThemeCacheMap; + private keys: DerivativeOptions[]; + private cacheCallTimes: number; + + constructor() { + this.cache = new Map(); + this.keys = []; + this.cacheCallTimes = 0; + } + + public size(): number { + return this.keys.length; + } + + private internalGet( + derivativeOption: DerivativeOptions, + updateCallTimes = false, + ): [Theme, number] | undefined { + let cache: ReturnType = { map: this.cache }; + derivativeOption.forEach(derivative => { + if (!cache) { + cache = undefined; + } else { + cache = cache?.map?.get(derivative); + } + }); + if (cache?.value && updateCallTimes) { + cache.value[1] = this.cacheCallTimes++; + } + return cache?.value; + } + + public get(derivativeOption: DerivativeOptions): Theme | undefined { + return this.internalGet(derivativeOption, true)?.[0]; + } + + public has(derivativeOption: DerivativeOptions): boolean { + return !!this.internalGet(derivativeOption); + } + + public set(derivativeOption: DerivativeOptions, value: Theme): void { + // New cache + if (!this.has(derivativeOption)) { + if (this.size() + 1 > ThemeCache.MAX_CACHE_SIZE + ThemeCache.MAX_CACHE_OFFSET) { + const [targetKey] = this.keys.reduce<[DerivativeOptions, number]>( + (result, key) => { + const [, callTimes] = result; + if (this.internalGet(key)![1] < callTimes) { + return [key, this.internalGet(key)![1]]; + } + return result; + }, + [this.keys[0], this.cacheCallTimes], + ); + this.delete(targetKey); + } + + this.keys.push(derivativeOption); + } + + let cache = this.cache; + derivativeOption.forEach((derivative, index) => { + if (index === derivativeOption.length - 1) { + cache.set(derivative, { value: [value, this.cacheCallTimes++] }); + } else { + const cacheValue = cache.get(derivative); + if (!cacheValue) { + cache.set(derivative, { map: new Map() }); + } else if (!cacheValue.map) { + cacheValue.map = new Map(); + } + cache = cache.get(derivative)!.map!; + } + }); + } + + private deleteByPath( + currentCache: ThemeCacheMap, + derivatives: DerivativeFunc[], + ): Theme | undefined { + const cache = currentCache.get(derivatives[0])!; + if (derivatives.length === 1) { + if (!cache.map) { + currentCache.delete(derivatives[0]); + } else { + currentCache.set(derivatives[0], { map: cache.map }); + } + return cache.value?.[0]; + } + const result = this.deleteByPath(cache.map!, derivatives.slice(1)); + if ((!cache.map || cache.map.size === 0) && !cache.value) { + currentCache.delete(derivatives[0]); + } + return result; + } + + public delete(derivativeOption: DerivativeOptions): Theme | undefined { + // If cache exists + if (this.has(derivativeOption)) { + this.keys = this.keys.filter(item => !sameDerivativeOption(item, derivativeOption)); + return this.deleteByPath(this.cache, derivativeOption); + } + return undefined; + } +} diff --git a/components/_util/cssinjs/theme/createTheme.ts b/components/_util/cssinjs/theme/createTheme.ts new file mode 100644 index 000000000..9f73f58a1 --- /dev/null +++ b/components/_util/cssinjs/theme/createTheme.ts @@ -0,0 +1,26 @@ +import ThemeCache from './ThemeCache'; +import Theme from './Theme'; +import type { DerivativeFunc, TokenType } from './interface'; + +const cacheThemes = new ThemeCache(); + +/** + * Same as new Theme, but will always return same one if `derivative` not changed. + */ +export default function createTheme< + DesignToken extends TokenType, + DerivativeToken extends TokenType, +>( + derivatives: + | DerivativeFunc[] + | DerivativeFunc, +) { + const derivativeArr = Array.isArray(derivatives) ? derivatives : [derivatives]; + // Create new theme if not exist + if (!cacheThemes.has(derivativeArr)) { + cacheThemes.set(derivativeArr, new Theme(derivativeArr)); + } + + // Get theme from cache and return + return cacheThemes.get(derivativeArr)!; +} diff --git a/components/_util/cssinjs/theme/index.ts b/components/_util/cssinjs/theme/index.ts new file mode 100644 index 000000000..b3c2ff4b1 --- /dev/null +++ b/components/_util/cssinjs/theme/index.ts @@ -0,0 +1,4 @@ +export { default as createTheme } from './createTheme'; +export { default as Theme } from './Theme'; +export { default as ThemeCache } from './ThemeCache'; +export type { TokenType, DerivativeFunc } from './interface'; diff --git a/components/_util/cssinjs/theme/interface.ts b/components/_util/cssinjs/theme/interface.ts new file mode 100644 index 000000000..827706ce2 --- /dev/null +++ b/components/_util/cssinjs/theme/interface.ts @@ -0,0 +1,5 @@ +export type TokenType = object; +export type DerivativeFunc = ( + designToken: DesignToken, + derivativeToken?: DerivativeToken, +) => DerivativeToken; diff --git a/components/_util/cssinjs/transformers/interface.ts b/components/_util/cssinjs/transformers/interface.ts new file mode 100644 index 000000000..a7120e814 --- /dev/null +++ b/components/_util/cssinjs/transformers/interface.ts @@ -0,0 +1,5 @@ +import type { CSSObject } from '..'; + +export interface Transformer { + visit?: (cssObj: CSSObject) => CSSObject; +} diff --git a/components/_util/cssinjs/transformers/legacyLogicalProperties.ts b/components/_util/cssinjs/transformers/legacyLogicalProperties.ts new file mode 100644 index 000000000..58e00c89f --- /dev/null +++ b/components/_util/cssinjs/transformers/legacyLogicalProperties.ts @@ -0,0 +1,162 @@ +import type { CSSObject } from '..'; +import type { Transformer } from './interface'; + +function splitValues(value: string | number) { + if (typeof value === 'number') { + return [value]; + } + + const splitStyle = String(value).split(/\s+/); + + // Combine styles split in brackets, like `calc(1px + 2px)` + let temp = ''; + let brackets = 0; + return splitStyle.reduce((list, item) => { + if (item.includes('(')) { + temp += item; + brackets += item.split('(').length - 1; + } else if (item.includes(')')) { + temp += ` ${item}`; + brackets -= item.split(')').length - 1; + if (brackets === 0) { + list.push(temp); + temp = ''; + } + } else if (brackets > 0) { + temp += ` ${item}`; + } else { + list.push(item); + } + return list; + }, []); +} + +type MatchValue = string[] & { + notSplit?: boolean; +}; + +function noSplit(list: MatchValue): MatchValue { + list.notSplit = true; + return list; +} + +const keyMap: Record = { + // Inset + inset: ['top', 'right', 'bottom', 'left'], + insetBlock: ['top', 'bottom'], + insetBlockStart: ['top'], + insetBlockEnd: ['bottom'], + insetInline: ['left', 'right'], + insetInlineStart: ['left'], + insetInlineEnd: ['right'], + + // Margin + marginBlock: ['marginTop', 'marginBottom'], + marginBlockStart: ['marginTop'], + marginBlockEnd: ['marginBottom'], + marginInline: ['marginLeft', 'marginRight'], + marginInlineStart: ['marginLeft'], + marginInlineEnd: ['marginRight'], + + // Padding + paddingBlock: ['paddingTop', 'paddingBottom'], + paddingBlockStart: ['paddingTop'], + paddingBlockEnd: ['paddingBottom'], + paddingInline: ['paddingLeft', 'paddingRight'], + paddingInlineStart: ['paddingLeft'], + paddingInlineEnd: ['paddingRight'], + + // Border + borderBlock: noSplit(['borderTop', 'borderBottom']), + borderBlockStart: noSplit(['borderTop']), + borderBlockEnd: noSplit(['borderBottom']), + borderInline: noSplit(['borderLeft', 'borderRight']), + borderInlineStart: noSplit(['borderLeft']), + borderInlineEnd: noSplit(['borderRight']), + + // Border width + borderBlockWidth: ['borderTopWidth', 'borderBottomWidth'], + borderBlockStartWidth: ['borderTopWidth'], + borderBlockEndWidth: ['borderBottomWidth'], + borderInlineWidth: ['borderLeftWidth', 'borderRightWidth'], + borderInlineStartWidth: ['borderLeftWidth'], + borderInlineEndWidth: ['borderRightWidth'], + + // Border style + borderBlockStyle: ['borderTopStyle', 'borderBottomStyle'], + borderBlockStartStyle: ['borderTopStyle'], + borderBlockEndStyle: ['borderBottomStyle'], + borderInlineStyle: ['borderLeftStyle', 'borderRightStyle'], + borderInlineStartStyle: ['borderLeftStyle'], + borderInlineEndStyle: ['borderRightStyle'], + + // Border color + borderBlockColor: ['borderTopColor', 'borderBottomColor'], + borderBlockStartColor: ['borderTopColor'], + borderBlockEndColor: ['borderBottomColor'], + borderInlineColor: ['borderLeftColor', 'borderRightColor'], + borderInlineStartColor: ['borderLeftColor'], + borderInlineEndColor: ['borderRightColor'], + + // Border radius + borderStartStartRadius: ['borderTopLeftRadius'], + borderStartEndRadius: ['borderTopRightRadius'], + borderEndStartRadius: ['borderBottomLeftRadius'], + borderEndEndRadius: ['borderBottomRightRadius'], +}; + +function skipCheck(value: string | number) { + return { _skip_check_: true, value }; +} + +/** + * Convert css logical properties to legacy properties. + * Such as: `margin-block-start` to `margin-top`. + * Transform list: + * - inset + * - margin + * - padding + * - border + */ +const transform: Transformer = { + visit: cssObj => { + const clone: CSSObject = {}; + + Object.keys(cssObj).forEach(key => { + const value = cssObj[key]; + const matchValue = keyMap[key]; + + if (matchValue && (typeof value === 'number' || typeof value === 'string')) { + const values = splitValues(value); + + if (matchValue.length && matchValue.notSplit) { + // not split means always give same value like border + matchValue.forEach(matchKey => { + clone[matchKey] = skipCheck(value); + }); + } else if (matchValue.length === 1) { + // Handle like `marginBlockStart` => `marginTop` + clone[matchValue[0]] = skipCheck(value); + } else if (matchValue.length === 2) { + // Handle like `marginBlock` => `marginTop` & `marginBottom` + matchValue.forEach((matchKey, index) => { + clone[matchKey] = skipCheck(values[index] ?? values[0]); + }); + } else if (matchValue.length === 4) { + // Handle like `inset` => `top` & `right` & `bottom` & `left` + matchValue.forEach((matchKey, index) => { + clone[matchKey] = skipCheck(values[index] ?? values[index - 2] ?? values[0]); + }); + } else { + clone[key] = value; + } + } else { + clone[key] = value; + } + }); + + return clone; + }, +}; + +export default transform; diff --git a/components/_util/cssinjs/util.ts b/components/_util/cssinjs/util.ts new file mode 100644 index 000000000..b4115a37d --- /dev/null +++ b/components/_util/cssinjs/util.ts @@ -0,0 +1,68 @@ +import hash from '@emotion/hash'; +import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS'; +import canUseDom from '../canUseDom'; + +export function flattenToken(token: any) { + let str = ''; + Object.keys(token).forEach(key => { + const value = token[key]; + str += key; + if (value && typeof value === 'object') { + str += flattenToken(value); + } else { + str += value; + } + }); + return str; +} + +/** + * Convert derivative token to key string + */ +export function token2key(token: any, salt: string): string { + return hash(`${salt}_${flattenToken(token)}`); +} + +const layerKey = `layer-${Date.now()}-${Math.random()}`.replace(/\./g, ''); +const layerWidth = '903px'; + +function supportSelector(styleStr: string, handleElement?: (ele: HTMLElement) => void): boolean { + if (canUseDom()) { + updateCSS(styleStr, layerKey); + + const ele = document.createElement('div'); + ele.style.position = 'fixed'; + ele.style.left = '0'; + ele.style.top = '0'; + handleElement?.(ele); + document.body.appendChild(ele); + + if (process.env.NODE_ENV !== 'production') { + ele.innerHTML = 'Test'; + ele.style.zIndex = '9999999'; + } + + const support = getComputedStyle(ele).width === layerWidth; + + ele.parentNode?.removeChild(ele); + removeCSS(layerKey); + + return support; + } + + return false; +} + +let canLayer: boolean | undefined = undefined; +export function supportLayer(): boolean { + if (canLayer === undefined) { + canLayer = supportSelector( + `@layer ${layerKey} { .${layerKey} { width: ${layerWidth}!important; } }`, + ele => { + ele.className = layerKey; + }, + ); + } + + return canLayer!; +} diff --git a/components/_util/extendsObject.ts b/components/_util/extendsObject.ts new file mode 100644 index 000000000..3f6959ce4 --- /dev/null +++ b/components/_util/extendsObject.ts @@ -0,0 +1,21 @@ +type RecordType = Record; + +function extendsObject(...list: T[]) { + const result: RecordType = { ...list[0] }; + + for (let i = 1; i < list.length; i++) { + const obj = list[i]; + if (obj) { + Object.keys(obj).forEach(key => { + const val = obj[key]; + if (val !== undefined) { + result[key] = val; + } + }); + } + } + + return result; +} + +export default extendsObject; diff --git a/components/_util/getLocale.js b/components/_util/getLocale.js deleted file mode 100644 index 03aeca95b..000000000 --- a/components/_util/getLocale.js +++ /dev/null @@ -1,30 +0,0 @@ -export function getComponentLocale(props, context, componentName, getDefaultLocale) { - let locale = {}; - if (context && context.antLocale && context.antLocale[componentName]) { - locale = context.antLocale[componentName]; - } else { - const defaultLocale = getDefaultLocale(); - // TODO: make default lang of antd be English - // https://github.com/ant-design/ant-design/issues/6334 - locale = defaultLocale.default || defaultLocale; - } - - const result = { - ...locale, - ...props.locale, - }; - result.lang = { - ...locale.lang, - ...props.locale.lang, - }; - return result; -} - -export function getLocaleCode(context) { - const localeCode = context.antLocale && context.antLocale.locale; - // Had use LocaleProvide but didn't set locale - if (context.antLocale && context.antLocale.exist && !localeCode) { - return 'zh-cn'; - } - return localeCode; -} diff --git a/components/_util/getRequestAnimationFrame.js b/components/_util/getRequestAnimationFrame.ts similarity index 100% rename from components/_util/getRequestAnimationFrame.js rename to components/_util/getRequestAnimationFrame.ts diff --git a/components/_util/getScroll.ts b/components/_util/getScroll.ts index 70b50141d..2889b43ed 100644 --- a/components/_util/getScroll.ts +++ b/components/_util/getScroll.ts @@ -1,4 +1,4 @@ -export function isWindow(obj: any) { +export function isWindow(obj: any): obj is Window { return obj !== null && obj !== undefined && obj === obj.window; } @@ -12,16 +12,22 @@ export default function getScroll( const method = top ? 'scrollTop' : 'scrollLeft'; let result = 0; if (isWindow(target)) { - result = (target as Window)[top ? 'pageYOffset' : 'pageXOffset']; + result = target[top ? 'pageYOffset' : 'pageXOffset']; } else if (target instanceof Document) { result = target.documentElement[method]; + } else if (target instanceof HTMLElement) { + result = target[method]; } else if (target) { - result = (target as HTMLElement)[method]; + // According to the type inference, the `target` is `never` type. + // Since we configured the loose mode type checking, and supports mocking the target with such shape below:: + // `{ documentElement: { scrollLeft: 200, scrollTop: 400 } }`, + // the program may falls into this branch. + // Check the corresponding tests for details. Don't sure what is the real scenario this happens. + result = target[method]; } + if (target && !isWindow(target) && typeof result !== 'number') { - result = ((target as HTMLElement).ownerDocument || (target as Document)).documentElement?.[ - method - ]; + result = ((target.ownerDocument ?? target) as any).documentElement?.[method]; } return result; } diff --git a/components/_util/hooks/_vueuse/useElementSize.ts b/components/_util/hooks/_vueuse/useElementSize.ts index 90beea16b..bc90e9a06 100644 --- a/components/_util/hooks/_vueuse/useElementSize.ts +++ b/components/_util/hooks/_vueuse/useElementSize.ts @@ -1,4 +1,4 @@ -import { ref, watch } from 'vue'; +import { shallowRef, watch } from 'vue'; import type { MaybeComputedElementRef } from './unrefElement'; import type { UseResizeObserverOptions } from './useResizeObserver'; import { useResizeObserver } from './useResizeObserver'; @@ -23,8 +23,8 @@ export function useElementSize( options: UseResizeObserverOptions = {}, ) { const { box = 'content-box' } = options; - const width = ref(initialSize.width); - const height = ref(initialSize.height); + const width = shallowRef(initialSize.width); + const height = shallowRef(initialSize.height); useResizeObserver( target, diff --git a/components/_util/hooks/_vueuse/useMutationObserver.ts b/components/_util/hooks/_vueuse/useMutationObserver.ts new file mode 100644 index 000000000..3a191d396 --- /dev/null +++ b/components/_util/hooks/_vueuse/useMutationObserver.ts @@ -0,0 +1,62 @@ +import { tryOnScopeDispose } from './tryOnScopeDispose'; +import { watch } from 'vue'; +import type { MaybeElementRef } from './unrefElement'; +import { unrefElement } from './unrefElement'; +import { useSupported } from './useSupported'; +import type { ConfigurableWindow } from './_configurable'; +import { defaultWindow } from './_configurable'; + +export interface UseMutationObserverOptions extends MutationObserverInit, ConfigurableWindow {} + +/** + * Watch for changes being made to the DOM tree. + * + * @see https://vueuse.org/useMutationObserver + * @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver MutationObserver MDN + * @param target + * @param callback + * @param options + */ +export function useMutationObserver( + target: MaybeElementRef, + callback: MutationCallback, + options: UseMutationObserverOptions = {}, +) { + const { window = defaultWindow, ...mutationOptions } = options; + let observer: MutationObserver | undefined; + const isSupported = useSupported(() => window && 'MutationObserver' in window); + + const cleanup = () => { + if (observer) { + observer.disconnect(); + observer = undefined; + } + }; + + const stopWatch = watch( + () => unrefElement(target), + el => { + cleanup(); + + if (isSupported.value && window && el) { + observer = new MutationObserver(callback); + observer!.observe(el, mutationOptions); + } + }, + { immediate: true }, + ); + + const stop = () => { + cleanup(); + stopWatch(); + }; + + tryOnScopeDispose(stop); + + return { + isSupported, + stop, + }; +} + +export type UseMutationObserverReturn = ReturnType; diff --git a/components/_util/hooks/_vueuse/useSupported.ts b/components/_util/hooks/_vueuse/useSupported.ts index 7705bf63d..360e8e613 100644 --- a/components/_util/hooks/_vueuse/useSupported.ts +++ b/components/_util/hooks/_vueuse/useSupported.ts @@ -1,9 +1,8 @@ import { tryOnMounted } from './tryOnMounted'; -import type { Ref } from 'vue'; -import { ref } from 'vue'; +import { shallowRef } from 'vue'; export function useSupported(callback: () => unknown, sync = false) { - const isSupported = ref() as Ref; + const isSupported = shallowRef(); const update = () => (isSupported.value = Boolean(callback())); diff --git a/components/_util/hooks/useBreakpoint.ts b/components/_util/hooks/useBreakpoint.ts index 932c22f5e..1c20a996d 100644 --- a/components/_util/hooks/useBreakpoint.ts +++ b/components/_util/hooks/useBreakpoint.ts @@ -1,19 +1,21 @@ import type { Ref } from 'vue'; -import { onMounted, onUnmounted, ref } from 'vue'; +import { onMounted, onUnmounted, shallowRef } from 'vue'; import type { ScreenMap } from '../../_util/responsiveObserve'; -import ResponsiveObserve from '../../_util/responsiveObserve'; +import useResponsiveObserve from '../../_util/responsiveObserve'; function useBreakpoint(): Ref { - const screens = ref({}); + const screens = shallowRef({}); let token = null; + const responsiveObserve = useResponsiveObserve(); + onMounted(() => { - token = ResponsiveObserve.subscribe(supportScreens => { + token = responsiveObserve.value.subscribe(supportScreens => { screens.value = supportScreens; }); }); onUnmounted(() => { - ResponsiveObserve.unsubscribe(token); + responsiveObserve.value.unsubscribe(token); }); return screens; diff --git a/components/_util/hooks/useConfigInject.ts b/components/_util/hooks/useConfigInject.ts deleted file mode 100644 index be02d9241..000000000 --- a/components/_util/hooks/useConfigInject.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { RequiredMark } from '../../form/Form'; -import type { ComputedRef, UnwrapRef } from 'vue'; -import { computed, inject } from 'vue'; -import type { ConfigProviderProps, CSPConfig, Direction, SizeType } from '../../config-provider'; -import { defaultConfigProvider } from '../../config-provider'; -import type { VueNode } from '../type'; -import type { ValidateMessages } from '../../form/interface'; - -export default ( - name: string, - props: Record, -): { - configProvider: UnwrapRef; - prefixCls: ComputedRef; - rootPrefixCls: ComputedRef; - direction: ComputedRef; - size: ComputedRef; - getTargetContainer: ComputedRef<() => HTMLElement>; - space: ComputedRef<{ size: SizeType | number }>; - pageHeader: ComputedRef<{ ghost: boolean }>; - form?: ComputedRef<{ - requiredMark?: RequiredMark; - colon?: boolean; - validateMessages?: ValidateMessages; - }>; - autoInsertSpaceInButton: ComputedRef; - renderEmpty?: ComputedRef<(componentName?: string) => VueNode>; - virtual: ComputedRef; - dropdownMatchSelectWidth: ComputedRef; - getPopupContainer: ComputedRef; - getPrefixCls: ConfigProviderProps['getPrefixCls']; - autocomplete: ComputedRef; - csp: ComputedRef; -} => { - const configProvider = inject>( - 'configProvider', - defaultConfigProvider, - ); - const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls)); - const direction = computed(() => props.direction ?? configProvider.direction); - const rootPrefixCls = computed(() => configProvider.getPrefixCls()); - const autoInsertSpaceInButton = computed(() => configProvider.autoInsertSpaceInButton); - const renderEmpty = computed(() => configProvider.renderEmpty); - const space = computed(() => configProvider.space); - const pageHeader = computed(() => configProvider.pageHeader); - const form = computed(() => configProvider.form); - const getTargetContainer = computed( - () => props.getTargetContainer || configProvider.getTargetContainer, - ); - const getPopupContainer = computed( - () => props.getPopupContainer || configProvider.getPopupContainer, - ); - - const dropdownMatchSelectWidth = computed( - () => props.dropdownMatchSelectWidth ?? configProvider.dropdownMatchSelectWidth, - ); - const virtual = computed( - () => - (props.virtual === undefined ? configProvider.virtual !== false : props.virtual !== false) && - dropdownMatchSelectWidth.value !== false, - ); - const size = computed(() => props.size || configProvider.componentSize); - const autocomplete = computed(() => props.autocomplete || configProvider.input?.autocomplete); - const csp = computed(() => configProvider.csp); - return { - configProvider, - prefixCls, - direction, - size, - getTargetContainer, - getPopupContainer, - space, - pageHeader, - form, - autoInsertSpaceInButton, - renderEmpty, - virtual, - dropdownMatchSelectWidth, - rootPrefixCls, - getPrefixCls: configProvider.getPrefixCls, - autocomplete, - csp, - }; -}; diff --git a/components/_util/hooks/useDestroyed.ts b/components/_util/hooks/useDestroyed.ts index 57cc17230..7fa1f5022 100644 --- a/components/_util/hooks/useDestroyed.ts +++ b/components/_util/hooks/useDestroyed.ts @@ -1,7 +1,7 @@ -import { onBeforeUnmount, ref } from 'vue'; +import { onBeforeUnmount, shallowRef } from 'vue'; const useDestroyed = () => { - const destroyed = ref(false); + const destroyed = shallowRef(false); onBeforeUnmount(() => { destroyed.value = true; }); diff --git a/components/_util/hooks/useFlexGapSupport.ts b/components/_util/hooks/useFlexGapSupport.ts index eb3c100ec..592cc762a 100644 --- a/components/_util/hooks/useFlexGapSupport.ts +++ b/components/_util/hooks/useFlexGapSupport.ts @@ -1,8 +1,8 @@ -import { onMounted, ref } from 'vue'; +import { onMounted, shallowRef } from 'vue'; import { detectFlexGapSupported } from '../styleChecker'; export default () => { - const flexible = ref(false); + const flexible = shallowRef(false); onMounted(() => { flexible.value = detectFlexGapSupported(); }); diff --git a/components/_util/hooks/useId.ts b/components/_util/hooks/useId.ts new file mode 100644 index 000000000..fea54f908 --- /dev/null +++ b/components/_util/hooks/useId.ts @@ -0,0 +1,30 @@ +import { ref } from 'vue'; +import canUseDom from '../../_util/canUseDom'; + +let uuid = 0; + +/** Is client side and not jsdom */ +export const isBrowserClient = process.env.NODE_ENV !== 'test' && canUseDom(); + +/** Get unique id for accessibility usage */ +export function getUUID(): number | string { + let retId: string | number; + + // Test never reach + /* istanbul ignore if */ + if (isBrowserClient) { + retId = uuid; + uuid += 1; + } else { + retId = 'TEST_OR_SSR'; + } + + return retId; +} + +export default function useId(id = ref('')) { + // Inner id for accessibility usage. Only work in client side + const innerId = `vc_unique_${getUUID()}`; + + return id.value || innerId; +} diff --git a/components/_util/hooks/useLayoutState.ts b/components/_util/hooks/useLayoutState.ts index 78630a882..95189fccd 100644 --- a/components/_util/hooks/useLayoutState.ts +++ b/components/_util/hooks/useLayoutState.ts @@ -1,5 +1,5 @@ import type { Ref } from 'vue'; -import { onBeforeUnmount, ref } from 'vue'; +import { onBeforeUnmount, shallowRef } from 'vue'; import raf from '../raf'; export type Updater = (prev: State) => State; @@ -9,11 +9,11 @@ export type Updater = (prev: State) => State; export function useLayoutState( defaultState: State, ): [Ref, (updater: Updater) => void] { - const stateRef = ref(defaultState); + const stateRef = shallowRef(defaultState); let tempState = stateRef.value; let updateBatchRef = []; - const rafRef = ref(); + const rafRef = shallowRef(); function setFrameState(updater: Updater) { raf.cancel(rafRef.value); updateBatchRef.push(updater); diff --git a/components/_util/hooks/useScrollLocker.ts b/components/_util/hooks/useScrollLocker.ts new file mode 100644 index 000000000..993ff7d1c --- /dev/null +++ b/components/_util/hooks/useScrollLocker.ts @@ -0,0 +1,48 @@ +import type { Ref } from 'vue'; +import { computed, watchEffect } from 'vue'; +import { updateCSS, removeCSS } from '../../vc-util/Dom/dynamicCSS'; +import getScrollBarSize from '../../_util/getScrollBarSize'; + +const UNIQUE_ID = `vc-util-locker-${Date.now()}`; + +let uuid = 0; + +/**../vc-util/Dom/dynam + * Test usage export. Do not use in your production + */ +export function isBodyOverflowing() { + return ( + document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) && + window.innerWidth > document.body.offsetWidth + ); +} + +export default function useScrollLocker(lock?: Ref) { + const mergedLock = computed(() => !!lock && !!lock.value); + uuid += 1; + const id = `${UNIQUE_ID}_${uuid}`; + + watchEffect( + onClear => { + if (mergedLock.value) { + const scrollbarSize = getScrollBarSize(); + const isOverflow = isBodyOverflowing(); + + updateCSS( + ` +html body { + overflow-y: hidden; + ${isOverflow ? `width: calc(100% - ${scrollbarSize}px);` : ''} +}`, + id, + ); + } else { + removeCSS(id); + } + onClear(() => { + removeCSS(id); + }); + }, + { flush: 'post' }, + ); +} diff --git a/components/_util/hooks/useSize.ts b/components/_util/hooks/useSize.ts deleted file mode 100644 index 62b392e60..000000000 --- a/components/_util/hooks/useSize.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { ComputedRef, UnwrapRef } from 'vue'; -import { computed, inject, provide } from 'vue'; -import type { ConfigProviderProps, SizeType } from '../../config-provider'; -import { defaultConfigProvider } from '../../config-provider'; - -const sizeProvider = Symbol('SizeProvider'); - -const useProvideSize = (props: Record): ComputedRef => { - const configProvider = inject>( - 'configProvider', - defaultConfigProvider, - ); - const size = computed(() => props.size || configProvider.componentSize); - provide(sizeProvider, size); - return size; -}; - -const useInjectSize = (props?: Record): ComputedRef => { - const size: ComputedRef = props - ? computed(() => props.size) - : inject( - sizeProvider, - computed(() => 'default' as unknown as T), - ); - return size; -}; - -export { useInjectSize, sizeProvider, useProvideSize }; - -export default useProvideSize; diff --git a/components/_util/isMobile.js b/components/_util/isMobile.js deleted file mode 100644 index 96927fd3b..000000000 --- a/components/_util/isMobile.js +++ /dev/null @@ -1,110 +0,0 @@ -// MIT License from https://github.com/kaimallea/isMobile - -const applePhone = /iPhone/i; -const appleIpod = /iPod/i; -const appleTablet = /iPad/i; -const androidPhone = /\bAndroid(?:.+)Mobile\b/i; // Match 'Android' AND 'Mobile' -const androidTablet = /Android/i; -const amazonPhone = /\bAndroid(?:.+)SD4930UR\b/i; -const amazonTablet = /\bAndroid(?:.+)(?:KF[A-Z]{2,4})\b/i; -const windowsPhone = /Windows Phone/i; -const windowsTablet = /\bWindows(?:.+)ARM\b/i; // Match 'Windows' AND 'ARM' -const otherBlackberry = /BlackBerry/i; -const otherBlackberry10 = /BB10/i; -const otherOpera = /Opera Mini/i; -const otherChrome = /\b(CriOS|Chrome)(?:.+)Mobile/i; -const otherFirefox = /Mobile(?:.+)Firefox\b/i; // Match 'Mobile' AND 'Firefox' - -function match(regex, userAgent) { - return regex.test(userAgent); -} - -function isMobile(userAgent) { - let ua = userAgent || (typeof navigator !== 'undefined' ? navigator.userAgent : ''); - - // Facebook mobile app's integrated browser adds a bunch of strings that - // match everything. Strip it out if it exists. - let tmp = ua.split('[FBAN'); - if (typeof tmp[1] !== 'undefined') { - [ua] = tmp; - } - - // Twitter mobile app's integrated browser on iPad adds a "Twitter for - // iPhone" string. Same probably happens on other tablet platforms. - // This will confuse detection so strip it out if it exists. - tmp = ua.split('Twitter'); - if (typeof tmp[1] !== 'undefined') { - [ua] = tmp; - } - - const result = { - apple: { - phone: match(applePhone, ua) && !match(windowsPhone, ua), - ipod: match(appleIpod, ua), - tablet: !match(applePhone, ua) && match(appleTablet, ua) && !match(windowsPhone, ua), - device: - (match(applePhone, ua) || match(appleIpod, ua) || match(appleTablet, ua)) && - !match(windowsPhone, ua), - }, - amazon: { - phone: match(amazonPhone, ua), - tablet: !match(amazonPhone, ua) && match(amazonTablet, ua), - device: match(amazonPhone, ua) || match(amazonTablet, ua), - }, - android: { - phone: - (!match(windowsPhone, ua) && match(amazonPhone, ua)) || - (!match(windowsPhone, ua) && match(androidPhone, ua)), - tablet: - !match(windowsPhone, ua) && - !match(amazonPhone, ua) && - !match(androidPhone, ua) && - (match(amazonTablet, ua) || match(androidTablet, ua)), - device: - (!match(windowsPhone, ua) && - (match(amazonPhone, ua) || - match(amazonTablet, ua) || - match(androidPhone, ua) || - match(androidTablet, ua))) || - match(/\bokhttp\b/i, ua), - }, - windows: { - phone: match(windowsPhone, ua), - tablet: match(windowsTablet, ua), - device: match(windowsPhone, ua) || match(windowsTablet, ua), - }, - other: { - blackberry: match(otherBlackberry, ua), - blackberry10: match(otherBlackberry10, ua), - opera: match(otherOpera, ua), - firefox: match(otherFirefox, ua), - chrome: match(otherChrome, ua), - device: - match(otherBlackberry, ua) || - match(otherBlackberry10, ua) || - match(otherOpera, ua) || - match(otherFirefox, ua) || - match(otherChrome, ua), - }, - - // Additional - any: null, - phone: null, - tablet: null, - }; - result.any = - result.apple.device || result.android.device || result.windows.device || result.other.device; - - // excludes 'other' devices and ipods, targeting touchscreen phones - result.phone = result.apple.phone || result.android.phone || result.windows.phone; - result.tablet = result.apple.tablet || result.android.tablet || result.windows.tablet; - - return result; -} - -const defaultResult = { - ...isMobile(), - isMobile, -}; - -export default defaultResult; diff --git a/components/tooltip/placements.ts b/components/_util/placements.ts similarity index 100% rename from components/tooltip/placements.ts rename to components/_util/placements.ts diff --git a/components/_util/props-util/index.js b/components/_util/props-util/index.js index d67820cd9..38b0c9252 100644 --- a/components/_util/props-util/index.js +++ b/components/_util/props-util/index.js @@ -71,6 +71,7 @@ const getSlots = ele => { return { ...slots, ...getScopedSlots(ele) }; }; +export const skipFlattenKey = Symbol('skipFlatten'); const flattenChildren = (children = [], filterEmpty = true) => { const temp = Array.isArray(children) ? children : [children]; const res = []; @@ -78,7 +79,11 @@ const flattenChildren = (children = [], filterEmpty = true) => { if (Array.isArray(child)) { res.push(...flattenChildren(child, filterEmpty)); } else if (child && child.type === Fragment) { - res.push(...flattenChildren(child.children, filterEmpty)); + if (child.key === skipFlattenKey) { + res.push(child); + } else { + res.push(...flattenChildren(child.children, filterEmpty)); + } } else if (child && isVNode(child)) { if (filterEmpty && !isEmptyElement(child)) { res.push(child); diff --git a/components/_util/requestAnimationTimeout.js b/components/_util/requestAnimationTimeout.ts similarity index 100% rename from components/_util/requestAnimationTimeout.js rename to components/_util/requestAnimationTimeout.ts diff --git a/components/_util/responsiveObserve.ts b/components/_util/responsiveObserve.ts index 497f01bb9..11bd47db2 100644 --- a/components/_util/responsiveObserve.ts +++ b/components/_util/responsiveObserve.ts @@ -1,75 +1,85 @@ +import { computed } from 'vue'; +import type { GlobalToken } from '../theme/interface'; +import { useToken } from '../theme/internal'; + export type Breakpoint = 'xxxl' | 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs'; export type BreakpointMap = Record; export type ScreenMap = Partial>; export type ScreenSizeMap = Partial>; export const responsiveArray: Breakpoint[] = ['xxxl', 'xxl', 'xl', 'lg', 'md', 'sm', 'xs']; +type SubscribeFunc = (screens: ScreenMap) => void; -export const responsiveMap: BreakpointMap = { - xs: '(max-width: 575px)', - sm: '(min-width: 576px)', - md: '(min-width: 768px)', - lg: '(min-width: 992px)', - xl: '(min-width: 1200px)', - xxl: '(min-width: 1600px)', - xxxl: '(min-width: 2000px)', -}; +const getResponsiveMap = (token: GlobalToken): BreakpointMap => ({ + xs: `(max-width: ${token.screenXSMax}px)`, + sm: `(min-width: ${token.screenSM}px)`, + md: `(min-width: ${token.screenMD}px)`, + lg: `(min-width: ${token.screenLG}px)`, + xl: `(min-width: ${token.screenXL}px)`, + xxl: `(min-width: ${token.screenXXL}px)`, + xxxl: `{min-width: ${token.screenXXXL}px}`, +}); -type SubscribeFunc = (screens: ScreenMap) => void; -const subscribers = new Map(); -let subUid = -1; -let screens = {}; +export default function useResponsiveObserver() { + const [, token] = useToken(); -const responsiveObserve = { - matchHandlers: {} as { - [prop: string]: { - mql: MediaQueryList; - listener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null; - }; - }, - dispatch(pointMap: ScreenMap) { - screens = pointMap; - subscribers.forEach(func => func(screens)); - return subscribers.size >= 1; - }, - subscribe(func: SubscribeFunc): number { - if (!subscribers.size) this.register(); - subUid += 1; - subscribers.set(subUid, func); - func(screens); - return subUid; - }, - unsubscribe(token: number) { - subscribers.delete(token); - if (!subscribers.size) this.unregister(); - }, - unregister() { - Object.keys(responsiveMap).forEach((screen: string) => { - const matchMediaQuery = responsiveMap[screen]; - const handler = this.matchHandlers[matchMediaQuery]; - handler?.mql.removeListener(handler?.listener); - }); - subscribers.clear(); - }, - register() { - Object.keys(responsiveMap).forEach((screen: string) => { - const matchMediaQuery = responsiveMap[screen]; - const listener = ({ matches }: { matches: boolean }) => { - this.dispatch({ - ...screens, - [screen]: matches, - }); - }; - const mql = window.matchMedia(matchMediaQuery); - mql.addListener(listener); - this.matchHandlers[matchMediaQuery] = { - mql, - listener, - }; + return computed(() => { + const responsiveMap: BreakpointMap = getResponsiveMap(token.value); + const subscribers = new Map(); + let subUid = -1; + let screens = {}; - listener(mql); - }); - }, -}; + return { + matchHandlers: {} as { + [prop: string]: { + mql: MediaQueryList; + listener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null; + }; + }, + dispatch(pointMap: ScreenMap) { + screens = pointMap; + subscribers.forEach(func => func(screens)); + return subscribers.size >= 1; + }, + subscribe(func: SubscribeFunc): number { + if (!subscribers.size) this.register(); + subUid += 1; + subscribers.set(subUid, func); + func(screens); + return subUid; + }, + unsubscribe(paramToken: number) { + subscribers.delete(paramToken); + if (!subscribers.size) this.unregister(); + }, + unregister() { + Object.keys(responsiveMap).forEach((screen: string) => { + const matchMediaQuery = responsiveMap[screen]; + const handler = this.matchHandlers[matchMediaQuery]; + handler?.mql.removeListener(handler?.listener); + }); + subscribers.clear(); + }, + register() { + Object.keys(responsiveMap).forEach((screen: string) => { + const matchMediaQuery = responsiveMap[screen]; + const listener = ({ matches }: { matches: boolean }) => { + this.dispatch({ + ...screens, + [screen]: matches, + }); + }; + const mql = window.matchMedia(matchMediaQuery); + mql.addListener(listener); + this.matchHandlers[matchMediaQuery] = { + mql, + listener, + }; -export default responsiveObserve; + listener(mql); + }); + }, + responsiveMap, + }; + }); +} diff --git a/components/_util/scrollTo.ts b/components/_util/scrollTo.ts index f41c1b649..c9dbb8910 100644 --- a/components/_util/scrollTo.ts +++ b/components/_util/scrollTo.ts @@ -1,6 +1,6 @@ import raf from './raf'; -import getScroll, { isWindow } from './getScroll'; import { easeInOutCubic } from './easings'; +import getScroll, { isWindow } from './getScroll'; interface ScrollToOptions { /** Scroll container, default as window */ @@ -23,8 +23,8 @@ export default function scrollTo(y: number, options: ScrollToOptions = {}) { const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration); if (isWindow(container)) { (container as Window).scrollTo(window.pageXOffset, nextScrollTop); - } else if (container instanceof HTMLDocument || container.constructor.name === 'HTMLDocument') { - (container as HTMLDocument).documentElement.scrollTop = nextScrollTop; + } else if (container instanceof Document || container.constructor.name === 'HTMLDocument') { + (container as Document).documentElement.scrollTop = nextScrollTop; } else { (container as HTMLElement).scrollTop = nextScrollTop; } diff --git a/components/_util/shallowequal.js b/components/_util/shallowequal.ts similarity index 82% rename from components/_util/shallowequal.js rename to components/_util/shallowequal.ts index b06a5ea60..9a09cc9b6 100644 --- a/components/_util/shallowequal.js +++ b/components/_util/shallowequal.ts @@ -1,6 +1,6 @@ import { toRaw } from 'vue'; -function shallowEqual(objA, objB, compare, compareContext) { +function shallowEqual(objA: any, objB: any, compare?: any, compareContext?: any) { let ret = compare ? compare.call(compareContext, objA, objB) : void 0; if (ret !== void 0) { @@ -45,6 +45,6 @@ function shallowEqual(objA, objB, compare, compareContext) { return true; } -export default function (value, other, customizer, thisArg) { - return shallowEqual(toRaw(value), toRaw(other), customizer, thisArg); +export default function (value: any, other: any) { + return shallowEqual(toRaw(value), toRaw(other)); } diff --git a/components/_util/statusUtils.tsx b/components/_util/statusUtils.tsx new file mode 100644 index 000000000..a9cb68aaa --- /dev/null +++ b/components/_util/statusUtils.tsx @@ -0,0 +1,23 @@ +import type { ValidateStatus } from '../form/FormItem'; +import classNames from './classNames'; + +const InputStatuses = ['warning', 'error', ''] as const; + +export type InputStatus = (typeof InputStatuses)[number]; + +export function getStatusClassNames( + prefixCls: string, + status?: ValidateStatus, + hasFeedback?: boolean, +) { + return classNames({ + [`${prefixCls}-status-success`]: status === 'success', + [`${prefixCls}-status-warning`]: status === 'warning', + [`${prefixCls}-status-error`]: status === 'error', + [`${prefixCls}-status-validating`]: status === 'validating', + [`${prefixCls}-has-feedback`]: hasFeedback, + }); +} + +export const getMergedStatus = (contextStatus?: ValidateStatus, customStatus?: InputStatus) => + customStatus || contextStatus; diff --git a/components/_util/switchScrollingEffect.ts b/components/_util/switchScrollingEffect.ts deleted file mode 100644 index e87985bdd..000000000 --- a/components/_util/switchScrollingEffect.ts +++ /dev/null @@ -1,42 +0,0 @@ -import getScrollBarSize from './getScrollBarSize'; -import setStyle from './setStyle'; - -function isBodyOverflowing() { - return ( - document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) && - window.innerWidth > document.body.offsetWidth - ); -} - -let cacheStyle = {}; - -export default (close?: boolean) => { - if (!isBodyOverflowing() && !close) { - return; - } - - // https://github.com/ant-design/ant-design/issues/19729 - const scrollingEffectClassName = 'ant-scrolling-effect'; - const scrollingEffectClassNameReg = new RegExp(`${scrollingEffectClassName}`, 'g'); - const bodyClassName = document.body.className; - - if (close) { - if (!scrollingEffectClassNameReg.test(bodyClassName)) return; - setStyle(cacheStyle); - cacheStyle = {}; - document.body.className = bodyClassName.replace(scrollingEffectClassNameReg, '').trim(); - return; - } - - const scrollBarSize = getScrollBarSize(); - if (scrollBarSize) { - cacheStyle = setStyle({ - position: 'relative', - width: `calc(100% - ${scrollBarSize}px)`, - }); - if (!scrollingEffectClassNameReg.test(bodyClassName)) { - const addClassName = `${bodyClassName} ${scrollingEffectClassName}`; - document.body.className = addClassName.trim(); - } - } -}; diff --git a/components/_util/throttleByAnimationFrame.ts b/components/_util/throttleByAnimationFrame.ts index dd84132a4..ce469a3f0 100644 --- a/components/_util/throttleByAnimationFrame.ts +++ b/components/_util/throttleByAnimationFrame.ts @@ -1,47 +1,29 @@ import raf from './raf'; -export default function throttleByAnimationFrame(fn: (...args: any[]) => void) { - let requestId: number; +type throttledFn = (...args: any[]) => void; - const later = (args: any[]) => () => { +type throttledCancelFn = { cancel: () => void }; + +function throttleByAnimationFrame(fn: (...args: T) => void) { + let requestId: number | null; + + const later = (args: T) => () => { requestId = null; fn(...args); }; - const throttled = (...args: any[]) => { + const throttled: throttledFn & throttledCancelFn = (...args: T) => { if (requestId == null) { requestId = raf(later(args)); } }; - (throttled as any).cancel = () => raf.cancel(requestId!); + throttled.cancel = () => { + raf.cancel(requestId!); + requestId = null; + }; return throttled; } -export function throttleByAnimationFrameDecorator() { - // eslint-disable-next-line func-names - return function (target: any, key: string, descriptor: any) { - const fn = descriptor.value; - let definingProperty = false; - return { - configurable: true, - get() { - // eslint-disable-next-line no-prototype-builtins - if (definingProperty || this === target.prototype || this.hasOwnProperty(key)) { - return fn; - } - - const boundFn = throttleByAnimationFrame(fn.bind(this)); - definingProperty = true; - Object.defineProperty(this, key, { - value: boundFn, - configurable: true, - writable: true, - }); - definingProperty = false; - return boundFn; - }, - }; - }; -} +export default throttleByAnimationFrame; diff --git a/components/_util/transButton.tsx b/components/_util/transButton.tsx index caced47cd..548948d55 100644 --- a/components/_util/transButton.tsx +++ b/components/_util/transButton.tsx @@ -1,5 +1,5 @@ import type { CSSProperties } from 'vue'; -import { defineComponent, ref, onMounted } from 'vue'; +import { defineComponent, shallowRef, onMounted } from 'vue'; /** * Wrap of sub component which need use as Button capacity (like Icon component). * This helps accessibility reader to tread as a interactive button to operation. @@ -25,7 +25,7 @@ const TransButton = defineComponent({ autofocus: { type: Boolean, default: undefined }, }, setup(props, { slots, emit, attrs, expose }) { - const domRef = ref(); + const domRef = shallowRef(); const onKeyDown = (event: KeyboardEvent) => { const { keyCode } = event; if (keyCode === KeyCode.ENTER) { diff --git a/components/_util/transKeys.ts b/components/_util/transKeys.ts new file mode 100644 index 000000000..d196b1137 --- /dev/null +++ b/components/_util/transKeys.ts @@ -0,0 +1,17 @@ +export const groupKeysMap = (keys: string[]) => { + const map = new Map(); + keys.forEach((key, index) => { + map.set(key, index); + }); + return map; +}; + +export const groupDisabledKeysMap = (dataSource: RecordType) => { + const map = new Map(); + dataSource.forEach(({ disabled, key }, index) => { + if (disabled) { + map.set(key, index); + } + }); + return map; +}; diff --git a/components/_util/transition.tsx b/components/_util/transition.tsx index f0fe0eb18..fd6ebc490 100644 --- a/components/_util/transition.tsx +++ b/components/_util/transition.tsx @@ -9,7 +9,7 @@ import { nextTick, Transition, TransitionGroup } from 'vue'; import { tuple } from './type'; const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight'); -export type SelectCommonPlacement = typeof SelectPlacements[number]; +export type SelectCommonPlacement = (typeof SelectPlacements)[number]; const getTransitionDirection = (placement: SelectCommonPlacement | undefined) => { if (placement !== undefined && (placement === 'topLeft' || placement === 'topRight')) { @@ -27,7 +27,7 @@ export const getTransitionProps = (transitionName: string, opt: TransitionProps // appearFromClass: `${transitionName}-appear ${transitionName}-appear-prepare`, // appearActiveClass: `antdv-base-transtion`, // appearToClass: `${transitionName}-appear ${transitionName}-appear-active`, - enterFromClass: `${transitionName}-enter ${transitionName}-enter-prepare`, + enterFromClass: `${transitionName}-enter ${transitionName}-enter-prepare ${transitionName}-enter-start`, enterActiveClass: `${transitionName}-enter ${transitionName}-enter-prepare`, enterToClass: `${transitionName}-enter ${transitionName}-enter-active`, leaveFromClass: ` ${transitionName}-leave`, diff --git a/components/_util/type.ts b/components/_util/type.ts index 20c7e30c3..bc1cb530e 100644 --- a/components/_util/type.ts +++ b/components/_util/type.ts @@ -15,7 +15,7 @@ export type ElementOf = T extends (infer E)[] ? E : T extends readonly (infer /** * https://github.com/Microsoft/TypeScript/issues/29729 */ -export type LiteralUnion = T | (U & {}); +export type LiteralUnion = T | (string & {}); export type Data = Record; @@ -44,4 +44,49 @@ export const withInstall = (comp: T) => { export type MaybeRef = T | Ref; +export function eventType() { + return { type: [Function, Array] as PropType }; +} + +export function objectType(defaultVal?: T) { + return { type: Object as PropType, default: defaultVal as T }; +} + +export function booleanType(defaultVal?: boolean) { + return { type: Boolean, default: defaultVal as boolean }; +} + +export function functionType {}>(defaultVal?: T) { + return { type: Function as PropType, default: defaultVal as T }; +} + +export function anyType(defaultVal?: T, required?: boolean) { + const type = { validator: () => true, default: defaultVal as T } as unknown; + return required + ? (type as { + type: PropType; + default: T; + required: true; + }) + : (type as { + default: T; + type: PropType; + }); +} +export function vNodeType() { + return { validator: () => true } as unknown as { type: PropType }; +} + +export function arrayType(defaultVal?: T) { + return { type: Array as unknown as PropType, default: defaultVal as T }; +} + +export function stringType(defaultVal?: T) { + return { type: String as unknown as PropType, default: defaultVal as T }; +} + +export function someType(types?: any[], defaultVal?: T) { + return types ? { type: types as PropType, default: defaultVal as T } : anyType(defaultVal); +} + export type CustomSlotsType = SlotsType; diff --git a/components/_util/util.ts b/components/_util/util.ts index 1adfa26ef..aff378832 100644 --- a/components/_util/util.ts +++ b/components/_util/util.ts @@ -56,7 +56,7 @@ function resolvePropValue(options, props, key, value) { export function getDataAndAriaProps(props) { return Object.keys(props).reduce((memo, key) => { - if (key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-') { + if (key.startsWith('data-') || key.startsWith('aria-')) { memo[key] = props[key]; } return memo; @@ -78,5 +78,24 @@ export function renderHelper>( } return v ?? defaultV; } +export function wrapPromiseFn(openFn: (resolve: VoidFunction) => VoidFunction) { + let closeFn: VoidFunction; + + const closePromise = new Promise(resolve => { + closeFn = openFn(() => { + resolve(true); + }); + }); + + const result: any = () => { + closeFn?.(); + }; + + result.then = (filled: VoidFunction, rejected: VoidFunction) => + closePromise.then(filled, rejected); + result.promise = closePromise; + + return result; +} export { isOn, cacheStringFunction, camelize, hyphenate, capitalize, resolvePropValue }; diff --git a/components/_util/warning.js b/components/_util/warning.js deleted file mode 100644 index 59681e40a..000000000 --- a/components/_util/warning.js +++ /dev/null @@ -1,7 +0,0 @@ -import warning, { resetWarned } from '../vc-util/warning'; - -export { resetWarned }; - -export default (valid, component, message = '') => { - warning(valid, `[antdv: ${component}] ${message}`); -}; diff --git a/components/_util/warning.ts b/components/_util/warning.ts new file mode 100644 index 000000000..b4819faa2 --- /dev/null +++ b/components/_util/warning.ts @@ -0,0 +1,21 @@ +import vcWarning, { resetWarned } from '../vc-util/warning'; + +export { resetWarned }; +export function noop() {} + +type Warning = (valid: boolean, component: string, message?: string) => void; + +// eslint-disable-next-line import/no-mutable-exports +let warning: Warning = noop; +if (process.env.NODE_ENV !== 'production') { + warning = (valid, component, message) => { + vcWarning(valid, `[ant-design-vue: ${component}] ${message}`); + + // StrictMode will inject console which will not throw warning in React 17. + if (process.env.NODE_ENV === 'test') { + resetWarned(); + } + }; +} + +export default warning; diff --git a/components/_util/wave.tsx b/components/_util/wave.tsx deleted file mode 100644 index ef69ef66c..000000000 --- a/components/_util/wave.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { nextTick, defineComponent, getCurrentInstance, onMounted, onBeforeUnmount } from 'vue'; -import TransitionEvents from './css-animation/Event'; -import raf from './raf'; -import { findDOMNode } from './props-util'; -import useConfigInject from './hooks/useConfigInject'; -let styleForPesudo: HTMLStyleElement; - -// Where el is the DOM element you'd like to test for visibility -function isHidden(element: HTMLElement) { - if (process.env.NODE_ENV === 'test') { - return false; - } - return !element || element.offsetParent === null; -} -function isNotGrey(color: string) { - // eslint-disable-next-line no-useless-escape - const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\.\d]*)?\)/); - if (match && match[1] && match[2] && match[3]) { - return !(match[1] === match[2] && match[2] === match[3]); - } - return true; -} -export default defineComponent({ - compatConfig: { MODE: 3 }, - name: 'Wave', - props: { - insertExtraNode: Boolean, - disabled: Boolean, - }, - setup(props, { slots, expose }) { - const instance = getCurrentInstance(); - const { csp, prefixCls } = useConfigInject('', props); - expose({ - csp, - }); - let eventIns = null; - let clickWaveTimeoutId = null; - let animationStartId = null; - let animationStart = false; - let extraNode = null; - let isUnmounted = false; - const onTransitionStart = e => { - if (isUnmounted) return; - - const node = findDOMNode(instance); - if (!e || e.target !== node) { - return; - } - - if (!animationStart) { - resetEffect(node); - } - }; - const onTransitionEnd = (e: any) => { - if (!e || e.animationName !== 'fadeEffect') { - return; - } - resetEffect(e.target); - }; - const getAttributeName = () => { - const { insertExtraNode } = props; - return insertExtraNode - ? `${prefixCls.value}-click-animating` - : `${prefixCls.value}-click-animating-without-extra-node`; - }; - const onClick = (node: HTMLElement, waveColor: string) => { - const { insertExtraNode, disabled } = props; - if (disabled || !node || isHidden(node) || node.className.indexOf('-leave') >= 0) { - return; - } - - extraNode = document.createElement('div'); - extraNode.className = `${prefixCls.value}-click-animating-node`; - const attributeName = getAttributeName(); - node.removeAttribute(attributeName); - node.setAttribute(attributeName, 'true'); - // Not white or transparent or grey - styleForPesudo = styleForPesudo || document.createElement('style'); - if ( - waveColor && - waveColor !== '#ffffff' && - waveColor !== 'rgb(255, 255, 255)' && - isNotGrey(waveColor) && - !/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color - waveColor !== 'transparent' - ) { - // Add nonce if CSP exist - if (csp.value?.nonce) { - styleForPesudo.nonce = csp.value.nonce; - } - extraNode.style.borderColor = waveColor; - styleForPesudo.innerHTML = ` - [${prefixCls.value}-click-animating-without-extra-node='true']::after, .${prefixCls.value}-click-animating-node { - --antd-wave-shadow-color: ${waveColor}; - }`; - if (!document.body.contains(styleForPesudo)) { - document.body.appendChild(styleForPesudo); - } - } - if (insertExtraNode) { - node.appendChild(extraNode); - } - TransitionEvents.addStartEventListener(node, onTransitionStart); - TransitionEvents.addEndEventListener(node, onTransitionEnd); - }; - const resetEffect = (node: HTMLElement) => { - if (!node || node === extraNode || !(node instanceof Element)) { - return; - } - const { insertExtraNode } = props; - const attributeName = getAttributeName(); - node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466 - if (styleForPesudo) { - styleForPesudo.innerHTML = ''; - } - if (insertExtraNode && extraNode && node.contains(extraNode)) { - node.removeChild(extraNode); - } - TransitionEvents.removeStartEventListener(node, onTransitionStart); - TransitionEvents.removeEndEventListener(node, onTransitionEnd); - }; - const bindAnimationEvent = (node: HTMLElement) => { - if ( - !node || - !node.getAttribute || - node.getAttribute('disabled') || - node.className.indexOf('disabled') >= 0 - ) { - return; - } - const newClick = (e: MouseEvent) => { - // Fix radio button click twice - if ((e.target as any).tagName === 'INPUT' || isHidden(e.target as HTMLElement)) { - return; - } - resetEffect(node); - // Get wave color from target - const waveColor = - getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible - getComputedStyle(node).getPropertyValue('border-color') || - getComputedStyle(node).getPropertyValue('background-color'); - clickWaveTimeoutId = setTimeout(() => onClick(node, waveColor), 0); - raf.cancel(animationStartId); - animationStart = true; - - // Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this. - animationStartId = raf(() => { - animationStart = false; - }, 10); - }; - node.addEventListener('click', newClick, true); - return { - cancel: () => { - node.removeEventListener('click', newClick, true); - }, - }; - }; - onMounted(() => { - nextTick(() => { - const node = findDOMNode(instance); - if (node.nodeType !== 1) { - return; - } - eventIns = bindAnimationEvent(node); - }); - }); - onBeforeUnmount(() => { - if (eventIns) { - eventIns.cancel(); - } - clearTimeout(clickWaveTimeoutId); - isUnmounted = true; - }); - return () => { - return slots.default?.()[0]; - }; - }, -}); diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx new file mode 100644 index 000000000..1f3ce23d1 --- /dev/null +++ b/components/_util/wave/WaveEffect.tsx @@ -0,0 +1,164 @@ +import type { CSSProperties } from 'vue'; +import { onBeforeUnmount, onMounted, Transition, render, defineComponent, shallowRef } from 'vue'; +import useState from '../hooks/useState'; +import { objectType } from '../type'; +import { getTargetWaveColor } from './util'; +import wrapperRaf from '../raf'; +function validateNum(value: number) { + return Number.isNaN(value) ? 0 : value; +} + +export interface WaveEffectProps { + className: string; + target: HTMLElement; +} + +const WaveEffect = defineComponent({ + props: { + target: objectType(), + className: String, + }, + setup(props) { + const divRef = shallowRef(null); + + const [color, setWaveColor] = useState(null); + const [borderRadius, setBorderRadius] = useState([]); + const [left, setLeft] = useState(0); + const [top, setTop] = useState(0); + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + const [enabled, setEnabled] = useState(false); + + function syncPos() { + const { target } = props; + const nodeStyle = getComputedStyle(target); + + // Get wave color from target + setWaveColor(getTargetWaveColor(target)); + + const isStatic = nodeStyle.position === 'static'; + + // Rect + const { borderLeftWidth, borderTopWidth } = nodeStyle; + setLeft(isStatic ? target.offsetLeft : validateNum(-parseFloat(borderLeftWidth))); + setTop(isStatic ? target.offsetTop : validateNum(-parseFloat(borderTopWidth))); + setWidth(target.offsetWidth); + setHeight(target.offsetHeight); + + // Get border radius + const { + borderTopLeftRadius, + borderTopRightRadius, + borderBottomLeftRadius, + borderBottomRightRadius, + } = nodeStyle; + + setBorderRadius( + [ + borderTopLeftRadius, + borderTopRightRadius, + borderBottomRightRadius, + borderBottomLeftRadius, + ].map(radius => validateNum(parseFloat(radius))), + ); + } + // Add resize observer to follow size + let resizeObserver: ResizeObserver; + let rafId: number; + let timeoutId: any; + const clear = () => { + clearTimeout(timeoutId); + wrapperRaf.cancel(rafId); + resizeObserver?.disconnect(); + }; + const removeDom = () => { + const holder = divRef.value?.parentElement; + if (holder) { + render(null, holder); + if (holder.parentElement) { + holder.parentElement.removeChild(holder); + } + } + }; + + onMounted(() => { + clear(); + timeoutId = setTimeout(() => { + removeDom(); + }, 5000); + const { target } = props; + if (target) { + // We need delay to check position here + // since UI may change after click + rafId = wrapperRaf(() => { + syncPos(); + + setEnabled(true); + }); + + if (typeof ResizeObserver !== 'undefined') { + resizeObserver = new ResizeObserver(syncPos); + + resizeObserver.observe(target); + } + } + }); + onBeforeUnmount(() => { + clear(); + }); + + const onTransitionend = (e: TransitionEvent) => { + if (e.propertyName === 'opacity') { + removeDom(); + } + }; + return () => { + if (!enabled.value) { + return null; + } + const waveStyle = { + left: `${left.value}px`, + top: `${top.value}px`, + width: `${width.value}px`, + height: `${height.value}px`, + borderRadius: borderRadius.value.map(radius => `${radius}px`).join(' '), + } as CSSProperties & { + [name: string]: number | string; + }; + + if (color) { + waveStyle['--wave-color'] = color.value as string; + } + + return ( + +
+ + ); + }; + }, +}); + +function showWaveEffect(node: HTMLElement, className: string) { + // Create holder + const holder = document.createElement('div'); + holder.style.position = 'absolute'; + holder.style.left = `0px`; + holder.style.top = `0px`; + node?.insertBefore(holder, node?.firstChild); + + render(, holder); +} + +export default showWaveEffect; diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx new file mode 100644 index 000000000..d816e426a --- /dev/null +++ b/components/_util/wave/index.tsx @@ -0,0 +1,96 @@ +import { + computed, + defineComponent, + getCurrentInstance, + nextTick, + onBeforeUnmount, + onMounted, + watch, +} from 'vue'; +import useConfigInject from '../../config-provider/hooks/useConfigInject'; +import isVisible from '../../vc-util/Dom/isVisible'; +import classNames from '../classNames'; +import { findDOMNode } from '../props-util'; +import useStyle from './style'; +import useWave from './useWave'; + +export interface WaveProps { + disabled?: boolean; +} + +export default defineComponent({ + compatConfig: { MODE: 3 }, + name: 'Wave', + props: { + disabled: Boolean, + }, + setup(props, { slots }) { + const instance = getCurrentInstance(); + const { prefixCls } = useConfigInject('wave', props); + + // ============================== Style =============================== + const [, hashId] = useStyle(prefixCls); + + // =============================== Wave =============================== + const showWave = useWave( + instance, + computed(() => classNames(prefixCls.value, hashId.value)), + ); + let onClick: (e: MouseEvent) => void; + const clear = () => { + const node = findDOMNode(instance); + node.removeEventListener('click', onClick, true); + }; + + onMounted(() => { + watch( + () => props.disabled, + () => { + clear(); + nextTick(() => { + const node = findDOMNode(instance); + + if (!node || node.nodeType !== 1 || props.disabled) { + return; + } + + // Click handler + const onClick = (e: MouseEvent) => { + // Fix radio button click twice + if ( + (e.target as HTMLElement).tagName === 'INPUT' || + !isVisible(e.target as HTMLElement) || + // No need wave + !node.getAttribute || + node.getAttribute('disabled') || + (node as HTMLInputElement).disabled || + node.className.includes('disabled') || + node.className.includes('-leave') + ) { + return; + } + + showWave(); + }; + + // Bind events + node.addEventListener('click', onClick, true); + }); + }, + { + immediate: true, + flush: 'post', + }, + ); + }); + onBeforeUnmount(() => { + clear(); + }); + + return () => { + // ============================== Render ============================== + const children = slots.default?.()[0]; + return children; + }; + }, +}); diff --git a/components/_util/wave/style.ts b/components/_util/wave/style.ts new file mode 100644 index 000000000..63f75a557 --- /dev/null +++ b/components/_util/wave/style.ts @@ -0,0 +1,38 @@ +import { genComponentStyleHook } from '../../theme/internal'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ComponentToken {} + +export type WaveToken = FullToken<'Wave'>; + +const genWaveStyle: GenerateStyle = token => { + const { componentCls, colorPrimary } = token; + return { + [componentCls]: { + position: 'absolute', + background: 'transparent', + pointerEvents: 'none', + boxSizing: 'border-box', + color: `var(--wave-color, ${colorPrimary})`, + + boxShadow: `0 0 0 0 currentcolor`, + opacity: 0.2, + + // =================== Motion =================== + '&.wave-motion-appear': { + transition: [ + `box-shadow 0.4s ${token.motionEaseOutCirc}`, + `opacity 2s ${token.motionEaseOutCirc}`, + ].join(','), + + '&-active': { + boxShadow: `0 0 0 6px currentcolor`, + opacity: 0, + }, + }, + }, + }; +}; + +export default genComponentStyleHook('Wave', token => [genWaveStyle(token)]); diff --git a/components/_util/wave/useWave.ts b/components/_util/wave/useWave.ts new file mode 100644 index 000000000..7a2c23188 --- /dev/null +++ b/components/_util/wave/useWave.ts @@ -0,0 +1,16 @@ +import type { ComponentInternalInstance, Ref } from 'vue'; +import { findDOMNode } from '../props-util'; +import showWaveEffect from './WaveEffect'; + +export default function useWave( + instance: ComponentInternalInstance | null, + className: Ref, +): VoidFunction { + function showWave() { + const node = findDOMNode(instance); + + showWaveEffect(node, className.value); + } + + return showWave; +} diff --git a/components/_util/wave/util.ts b/components/_util/wave/util.ts new file mode 100644 index 000000000..cd5bf6377 --- /dev/null +++ b/components/_util/wave/util.ts @@ -0,0 +1,35 @@ +export function isNotGrey(color: string) { + // eslint-disable-next-line no-useless-escape + const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\d.]*)?\)/); + if (match && match[1] && match[2] && match[3]) { + return !(match[1] === match[2] && match[2] === match[3]); + } + return true; +} + +export function isValidWaveColor(color: string) { + return ( + color && + color !== '#fff' && + color !== '#ffffff' && + color !== 'rgb(255, 255, 255)' && + color !== 'rgba(255, 255, 255, 1)' && + isNotGrey(color) && + !/rgba\((?:\d*, ){3}0\)/.test(color) && // any transparent rgba color + color !== 'transparent' + ); +} + +export function getTargetWaveColor(node: HTMLElement) { + const { borderTopColor, borderColor, backgroundColor } = getComputedStyle(node); + if (isValidWaveColor(borderTopColor)) { + return borderTopColor; + } + if (isValidWaveColor(borderColor)) { + return borderColor; + } + if (isValidWaveColor(backgroundColor)) { + return backgroundColor; + } + return null; +} diff --git a/components/affix/__tests__/__snapshots__/demo.test.js.snap b/components/affix/__tests__/__snapshots__/demo.test.js.snap index 65a2e4b0d..608656187 100644 --- a/components/affix/__tests__/__snapshots__/demo.test.js.snap +++ b/components/affix/__tests__/__snapshots__/demo.test.js.snap @@ -2,12 +2,14 @@ exports[`renders ./components/affix/demo/basic.vue correctly 1`] = `
+

+
@@ -16,7 +18,8 @@ exports[`renders ./components/affix/demo/basic.vue correctly 1`] = ` exports[`renders ./components/affix/demo/on-change.vue correctly 1`] = `
-
@@ -26,6 +29,7 @@ exports[`renders ./components/affix/demo/target.vue correctly 1`] = `
+
diff --git a/components/affix/demo/basic.vue b/components/affix/demo/basic.vue index a1baee459..cab12bdd3 100644 --- a/components/affix/demo/basic.vue +++ b/components/affix/demo/basic.vue @@ -26,16 +26,8 @@ The simplest usage. - diff --git a/components/affix/demo/on-change.vue b/components/affix/demo/on-change.vue index ab9df6076..2bd905c44 100644 --- a/components/affix/demo/on-change.vue +++ b/components/affix/demo/on-change.vue @@ -21,17 +21,8 @@ Callback with affixed state. 120px to affix top - diff --git a/components/affix/demo/target.vue b/components/affix/demo/target.vue index 8256c26b1..852d46f28 100644 --- a/components/affix/demo/target.vue +++ b/components/affix/demo/target.vue @@ -25,23 +25,16 @@ Set a `target` for 'Affix', which is listen to scroll event of target element (d
- - diff --git a/components/back-top/index.en-US.md b/components/back-top/index.en-US.md deleted file mode 100644 index 1fa126df2..000000000 --- a/components/back-top/index.en-US.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -category: Components -type: Other -title: BackTop -cover: https://gw.alipayobjects.com/zos/alicdn/tJZ5jbTwX/BackTop.svg ---- - -`BackTop` makes it easy to go back to the top of the page. - -## When To Use - -- When the page content is very long. -- When you need to go back to the top very frequently in order to view the contents. - -## API - -> The distance to the bottom is set to `50px` by default, which is overridable. -> -> If you decide to use custom styles, please note the size limit: no more than `40px * 40px`. - -| Property | Description | Type | Default | Version | -| --- | --- | --- | --- | --- | -| target | specifies the scrollable area dom node | () => HTMLElement | () => window | | -| visibilityHeight | the `BackTop` button will not show until the scroll height reaches this value | number | 400 | | - -### events - -| Events Name | Description | Arguments | Version | -| --- | --- | --- | --- | -| click | a callback function, which can be executed when you click the button | Function | | diff --git a/components/back-top/index.zh-CN.md b/components/back-top/index.zh-CN.md deleted file mode 100644 index 743e546fc..000000000 --- a/components/back-top/index.zh-CN.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -category: Components -type: 其他 -subtitle: 回到顶部 -title: BackTop -cover: https://gw.alipayobjects.com/zos/alicdn/tJZ5jbTwX/BackTop.svg ---- - -返回页面顶部的操作按钮。 - -## 何时使用 - -- 当页面内容区域比较长时; -- 当用户需要频繁返回顶部查看相关内容时。 - -## API - -> 有默认样式,距离底部 `50px`,可覆盖。 -> -> 自定义样式宽高不大于 40px \* 40px。 - -| 参数 | 说明 | 类型 | 默认值 | 版本 | -| --- | --- | --- | --- | --- | -| target | 设置需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 | Function | () => window | | -| visibilityHeight | 滚动高度达到此参数值才出现 `BackTop` | number | 400 | | - -### 事件 - -| 事件名称 | 说明 | 回调参数 | 版本 | -| -------- | ------------------ | -------- | ---- | -| click | 点击按钮的回调函数 | Function | | diff --git a/components/back-top/style/index.less b/components/back-top/style/index.less deleted file mode 100644 index 60a3da575..000000000 --- a/components/back-top/style/index.less +++ /dev/null @@ -1,49 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@backtop-prefix-cls: ~'@{ant-prefix}-back-top'; - -.@{backtop-prefix-cls} { - .reset-component(); - - position: fixed; - right: 100px; - bottom: 50px; - z-index: @zindex-back-top; - width: 40px; - height: 40px; - cursor: pointer; - - &:empty { - display: none; - } - - &-rtl { - right: auto; - left: 100px; - direction: rtl; - } - - &-content { - width: 40px; - height: 40px; - overflow: hidden; - color: @back-top-color; - text-align: center; - background-color: @back-top-bg; - border-radius: 20px; - transition: all 0.3s; - - &:hover { - background-color: @back-top-hover-bg; - transition: all 0.3s; - } - } - - &-icon { - font-size: 24px; - line-height: 40px; - } -} - -@import './responsive'; diff --git a/components/back-top/style/index.tsx b/components/back-top/style/index.tsx deleted file mode 100644 index 3a3ab0de5..000000000 --- a/components/back-top/style/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import '../../style/index.less'; -import './index.less'; diff --git a/components/back-top/style/responsive.less b/components/back-top/style/responsive.less deleted file mode 100644 index 7b21a8500..000000000 --- a/components/back-top/style/responsive.less +++ /dev/null @@ -1,11 +0,0 @@ -@media screen and (max-width: @screen-md) { - .@{backtop-prefix-cls} { - right: 60px; - } -} - -@media screen and (max-width: @screen-xs) { - .@{backtop-prefix-cls} { - right: 20px; - } -} diff --git a/components/badge/Badge.tsx b/components/badge/Badge.tsx index d7c0f151d..e1d8a9e6d 100644 --- a/components/badge/Badge.tsx +++ b/components/badge/Badge.tsx @@ -7,15 +7,17 @@ import { getTransitionProps, Transition } from '../_util/transition'; import type { ExtractPropTypes, CSSProperties, PropType } from 'vue'; import { defineComponent, computed, ref, watch } from 'vue'; import Ribbon from './Ribbon'; -import { isPresetColor } from './utils'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; import isNumeric from '../_util/isNumeric'; +import useStyle from './style'; +import type { PresetColorKey } from '../theme/interface'; +import type { LiteralUnion, CustomSlotsType } from '../_util/type'; import type { PresetStatusColorType } from '../_util/colors'; -import type { CustomSlotsType } from '../_util/type'; +import { isPresetColor } from '../_util/colors'; export const badgeProps = () => ({ /** Number to show in badge */ - count: PropTypes.any, + count: PropTypes.any.def(null), showZero: { type: Boolean, default: undefined }, /** Max count to show */ overflowCount: { type: Number, default: 99 }, @@ -25,7 +27,7 @@ export const badgeProps = () => ({ scrollNumberPrefixCls: String, status: { type: String as PropType }, size: { type: String as PropType<'default' | 'small'>, default: 'default' }, - color: String, + color: String as PropType>, text: PropTypes.any, offset: Array as unknown as PropType<[number | string, number | string]>, numberStyle: { type: Object as PropType, default: undefined as CSSProperties }, @@ -47,6 +49,7 @@ export default defineComponent({ }>, setup(props, { slots, attrs }) { const { prefixCls, direction } = useConfigInject('badge', props); + const [wrapSSR, hashId] = useStyle(prefixCls); // ================================ Misc ================================ const numberedDisplayCount = computed(() => { @@ -57,15 +60,16 @@ export default defineComponent({ ) as string | number | null; }); - const hasStatus = computed( - () => - (props.status !== null && props.status !== undefined) || - (props.color !== null && props.color !== undefined), - ); - const isZero = computed( () => numberedDisplayCount.value === '0' || numberedDisplayCount.value === 0, ); + const ignoreCount = computed(() => props.count === null || (isZero.value && !props.showZero)); + const hasStatus = computed( + () => + ((props.status !== null && props.status !== undefined) || + (props.color !== null && props.color !== undefined)) && + ignoreCount.value, + ); const showAsDot = computed(() => props.dot && !isZero.value); @@ -97,17 +101,18 @@ export default defineComponent({ }, { immediate: true }, ); - + // InternalColor + const isInternalColor = computed(() => isPresetColor(props.color, false)); // Shared styles const statusCls = computed(() => ({ [`${prefixCls.value}-status-dot`]: hasStatus.value, [`${prefixCls.value}-status-${props.status}`]: !!props.status, - [`${prefixCls.value}-status-${props.color}`]: isPresetColor(props.color), + [`${prefixCls.value}-status-${props.color}`]: isInternalColor.value, })); const statusStyle = computed(() => { - if (props.color && !isPresetColor(props.color)) { - return { background: props.color }; + if (props.color && !isInternalColor.value) { + return { background: props.color, color: props.color }; } else { return {}; } @@ -120,7 +125,7 @@ export default defineComponent({ [`${prefixCls.value}-multiple-words`]: !isDotRef.value && displayCount.value && displayCount.value.toString().length > 1, [`${prefixCls.value}-status-${props.status}`]: !!props.status, - [`${prefixCls.value}-status-${props.color}`]: isPresetColor(props.color), + [`${prefixCls.value}-status-${props.color}`]: isInternalColor.value, })); return () => { @@ -184,18 +189,19 @@ export default defineComponent({ [`${pre}-rtl`]: direction.value === 'rtl', }, attrs.class, + hashId.value, ); // if (!children && hasStatus.value) { const statusTextColor = mergedStyle.color; - return ( + return wrapSSR( {text} - + , ); } @@ -203,12 +209,12 @@ export default defineComponent({ appear: false, }); let scrollNumberStyle: CSSProperties = { ...mergedStyle, ...(props.numberStyle as object) }; - if (color && !isPresetColor(color)) { + if (color && !isInternalColor.value) { scrollNumberStyle = scrollNumberStyle || {}; scrollNumberStyle.background = color; } - return ( + return wrapSSR( {children} @@ -226,7 +232,7 @@ export default defineComponent({ {statusTextNode} - + , ); }; }, diff --git a/components/badge/Ribbon.tsx b/components/badge/Ribbon.tsx index b7a0aafbc..6725d833b 100644 --- a/components/badge/Ribbon.tsx +++ b/components/badge/Ribbon.tsx @@ -1,14 +1,15 @@ import type { CustomSlotsType, LiteralUnion } from '../_util/type'; import type { PresetColorType } from '../_util/colors'; -import { isPresetColor } from './utils'; +import useStyle from './style'; +import { isPresetColor } from '../_util/colors'; import type { CSSProperties, PropType, ExtractPropTypes } from 'vue'; import { defineComponent, computed } from 'vue'; import PropTypes from '../_util/vue-types'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; export const ribbonProps = () => ({ prefix: String, - color: { type: String as PropType> }, + color: { type: String as PropType> }, text: PropTypes.any, placement: { type: String as PropType<'start' | 'end'>, default: 'end' }, }); @@ -26,7 +27,8 @@ export default defineComponent({ }>, setup(props, { attrs, slots }) { const { prefixCls, direction } = useConfigInject('ribbon', props); - const colorInPreset = computed(() => isPresetColor(props.color)); + const [wrapSSR, hashId] = useStyle(prefixCls); + const colorInPreset = computed(() => isPresetColor(props.color, false)); const ribbonCls = computed(() => [ prefixCls.value, `${prefixCls.value}-placement-${props.placement}`, @@ -43,17 +45,17 @@ export default defineComponent({ colorStyle.background = props.color; cornerColorStyle.color = props.color; } - return ( -
+ return wrapSSR( +
{slots.default?.()}
{props.text || slots.text?.()}
-
+
, ); }; }, diff --git a/components/badge/ScrollNumber.tsx b/components/badge/ScrollNumber.tsx index 37c3b2c4d..cba1e29c8 100644 --- a/components/badge/ScrollNumber.tsx +++ b/components/badge/ScrollNumber.tsx @@ -3,7 +3,7 @@ import PropTypes from '../_util/vue-types'; import { cloneElement } from '../_util/vnode'; import type { ExtractPropTypes, CSSProperties, DefineComponent, HTMLAttributes } from 'vue'; import { defineComponent } from 'vue'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; import SingleNumber from './SingleNumber'; import { filterEmpty } from '../_util/props-util'; diff --git a/components/badge/__tests__/__snapshots__/demo.test.js.snap b/components/badge/__tests__/__snapshots__/demo.test.js.snap index 7739dcaa4..b08e26cb0 100644 --- a/components/badge/__tests__/__snapshots__/demo.test.js.snap +++ b/components/badge/__tests__/__snapshots__/demo.test.js.snap @@ -12,9 +12,9 @@ exports[`renders ./components/badge/demo/basic.vue correctly 1`] = ` exports[`renders ./components/badge/demo/change.vue correctly 1`] = `

5

-
@@ -26,7 +26,7 @@ exports[`renders ./components/badge/demo/change.vue correctly 1`] = `
`; @@ -48,13 +48,13 @@ exports[`renders ./components/badge/demo/colors.vue correctly 1`] = `
lime
-#f50 +#f50
-#2db7f5 +#2db7f5
-#87d068 +#87d068
-#108ee9 +#108ee9 `; exports[`renders ./components/badge/demo/dot.vue correctly 1`] = ` @@ -89,8 +89,8 @@ exports[`renders ./components/badge/demo/overflow.vue correctly 1`] = ` `; exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = ` -
-
+
+
Pushes open the window
@@ -106,8 +106,8 @@ exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = `
-
-
+
+
Pushes open the window
@@ -123,8 +123,8 @@ exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = `
-
-
+
+
Pushes open the window
@@ -140,8 +140,8 @@ exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = `
-
-
+
+
Pushes open the window
@@ -157,8 +157,8 @@ exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = `
-
-
+
+
Pushes open the window
@@ -174,8 +174,8 @@ exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = `
-
-
+
+
Pushes open the window
@@ -191,8 +191,8 @@ exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = `
-
-
+
+
Pushes open the window
@@ -208,8 +208,8 @@ exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = `
-
-
+
+
Pushes open the window
diff --git a/components/badge/demo/basic.vue b/components/badge/demo/basic.vue index 883813466..1dba25843 100644 --- a/components/badge/demo/basic.vue +++ b/components/badge/demo/basic.vue @@ -30,12 +30,6 @@ Simplest Usage. Badge will be hidden when `count` is `0`, but we can use `showZe - diff --git a/components/badge/demo/change.vue b/components/badge/demo/change.vue index 996b70d3f..55c10dae2 100644 --- a/components/badge/demo/change.vue +++ b/components/badge/demo/change.vue @@ -35,31 +35,18 @@ The count will be animated as it changes. - diff --git a/components/badge/demo/colors.vue b/components/badge/demo/colors.vue index dc6d47d71..00bd146d4 100644 --- a/components/badge/demo/colors.vue +++ b/components/badge/demo/colors.vue @@ -32,9 +32,7 @@ New feature after 3.16.0. We preset a series of colorful Badge styles for use in
- diff --git a/components/badge/demo/dot.vue b/components/badge/demo/dot.vue index 48998a752..5aeae1cd9 100644 --- a/components/badge/demo/dot.vue +++ b/components/badge/demo/dot.vue @@ -24,12 +24,6 @@ If count equals 0, it won't display the dot. Link something - diff --git a/components/badge/index.en_US.md b/components/badge/index.en_US.md index 402b5eb58..778c065ce 100644 --- a/components/badge/index.en_US.md +++ b/components/badge/index.en_US.md @@ -2,7 +2,8 @@ category: Components type: Data Display title: Badge -cover: https://gw.alipayobjects.com/zos/antfincdn/6%26GF9WHwvY/Badge.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*e0qITYqF394AAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*v8EQT7KoGbcAAAAAAAAAAAAADrJ8AQ/original --- Small numerical value or status descriptor for UI elements. diff --git a/components/badge/index.zh-CN.md b/components/badge/index.zh-CN.md index 7c4a7fd42..89df9cc5a 100644 --- a/components/badge/index.zh-CN.md +++ b/components/badge/index.zh-CN.md @@ -3,7 +3,8 @@ category: Components type: 数据展示 title: Badge subtitle: 徽标数 -cover: https://gw.alipayobjects.com/zos/antfincdn/6%26GF9WHwvY/Badge.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*e0qITYqF394AAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*v8EQT7KoGbcAAAAAAAAAAAAADrJ8AQ/original --- 图标右上角的圆形徽标数字。 diff --git a/components/badge/style/index.less b/components/badge/style/index.less deleted file mode 100644 index 8059ae581..000000000 --- a/components/badge/style/index.less +++ /dev/null @@ -1,281 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@badge-prefix-cls: ~'@{ant-prefix}-badge'; -@number-prefix-cls: ~'@{ant-prefix}-scroll-number'; - -.@{badge-prefix-cls} { - .reset-component(); - - position: relative; - display: inline-block; - line-height: 1; - - &-count { - z-index: @zindex-badge; - min-width: @badge-height; - height: @badge-height; - padding: 0 6px; - color: @badge-text-color; - font-weight: @badge-font-weight; - font-size: @badge-font-size; - line-height: @badge-height; - white-space: nowrap; - text-align: center; - background: @badge-color; - border-radius: (@badge-height / 2); - box-shadow: 0 0 0 1px @shadow-color-inverse; - - a, - a:hover { - color: @badge-text-color; - } - } - - &-count-sm { - min-width: @badge-height-sm; - height: @badge-height-sm; - padding: 0; - font-size: @badge-font-size-sm; - line-height: @badge-height-sm; - border-radius: (@badge-height-sm / 2); - } - - &-multiple-words { - padding: 0 8px; - } - - &-dot { - z-index: @zindex-badge; - width: @badge-dot-size; - min-width: @badge-dot-size; - height: @badge-dot-size; - background: @highlight-color; - border-radius: 100%; - box-shadow: 0 0 0 1px @shadow-color-inverse; - } - - // Tricky way to resolve https://github.com/ant-design/ant-design/issues/30088 - &-dot.@{number-prefix-cls} { - transition: background 1.5s; - } - - &-count, - &-dot, - .@{number-prefix-cls}-custom-component { - position: absolute; - top: 0; - right: 0; - transform: translate(50%, -50%); - transform-origin: 100% 0%; - - &.@{iconfont-css-prefix}-spin { - animation: antBadgeLoadingCircle 1s infinite linear; - } - } - - &-status { - line-height: inherit; - vertical-align: baseline; - - &-dot { - position: relative; - top: -1px; - display: inline-block; - width: @badge-status-size; - height: @badge-status-size; - vertical-align: middle; - border-radius: 50%; - } - - &-success { - background-color: @success-color; - } - - &-processing { - position: relative; - background-color: @processing-color; - - &::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid @processing-color; - border-radius: 50%; - animation: antStatusProcessing 1.2s infinite ease-in-out; - content: ''; - } - } - - &-default { - background-color: @normal-color; - } - - &-error { - background-color: @error-color; - } - - &-warning { - background-color: @warning-color; - } - - // mixin to iterate over colors and create CSS class for each one - .make-color-classes(@i: length(@preset-colors)) when (@i > 0) { - .make-color-classes(@i - 1); - @color: extract(@preset-colors, @i); - @darkColor: '@{color}-6'; - &-@{color} { - background: @@darkColor; - } - } - .make-color-classes(); - - &-text { - margin-left: 8px; - color: @text-color; - font-size: @font-size-base; - } - } - - &-zoom-appear, - &-zoom-enter { - animation: antZoomBadgeIn @animation-duration-slow @ease-out-back; - animation-fill-mode: both; - } - - &-zoom-leave { - animation: antZoomBadgeOut @animation-duration-slow @ease-in-back; - animation-fill-mode: both; - } - - &-not-a-wrapper { - .@{badge-prefix-cls}-zoom-appear, - .@{badge-prefix-cls}-zoom-enter { - animation: antNoWrapperZoomBadgeIn @animation-duration-slow @ease-out-back; - } - - .@{badge-prefix-cls}-zoom-leave { - animation: antNoWrapperZoomBadgeOut @animation-duration-slow @ease-in-back; - } - - &:not(.@{badge-prefix-cls}-status) { - vertical-align: middle; - } - - .@{number-prefix-cls}-custom-component, - .@{badge-prefix-cls}-count { - transform: none; - } - - .@{number-prefix-cls}-custom-component, - .@{number-prefix-cls} { - position: relative; - top: auto; - display: block; - transform-origin: 50% 50%; - } - } -} - -@keyframes antStatusProcessing { - 0% { - transform: scale(0.8); - opacity: 0.5; - } - - 100% { - transform: scale(2.4); - opacity: 0; - } -} - -// Safari will blink with transform when inner element has absolute style. -.safari-fix-motion() { - /* stylelint-disable property-no-vendor-prefix */ - -webkit-transform-style: preserve-3d; - -webkit-backface-visibility: hidden; - /* stylelint-enable property-no-vendor-prefix */ -} - -.@{number-prefix-cls} { - overflow: hidden; - direction: ltr; - - &-only { - position: relative; - display: inline-block; - height: @badge-height; - transition: all @animation-duration-slow @ease-in-out; - .safari-fix-motion; - - > p.@{number-prefix-cls}-only-unit { - height: @badge-height; - margin: 0; - .safari-fix-motion; - } - } - - &-symbol { - vertical-align: top; - } -} - -@keyframes antZoomBadgeIn { - 0% { - transform: scale(0) translate(50%, -50%); - opacity: 0; - } - - 100% { - transform: scale(1) translate(50%, -50%); - } -} - -@keyframes antZoomBadgeOut { - 0% { - transform: scale(1) translate(50%, -50%); - } - - 100% { - transform: scale(0) translate(50%, -50%); - opacity: 0; - } -} - -@keyframes antNoWrapperZoomBadgeIn { - 0% { - transform: scale(0); - opacity: 0; - } - - 100% { - transform: scale(1); - } -} - -@keyframes antNoWrapperZoomBadgeOut { - 0% { - transform: scale(1); - } - - 100% { - transform: scale(0); - opacity: 0; - } -} - -@keyframes antBadgeLoadingCircle { - 0% { - transform-origin: 50%; - } - - 100% { - transform: translate(50%, -50%) rotate(360deg); - transform-origin: 50%; - } -} - -@import './ribbon'; -@import './rtl'; diff --git a/components/badge/style/index.ts b/components/badge/style/index.ts new file mode 100644 index 000000000..1e46c6007 --- /dev/null +++ b/components/badge/style/index.ts @@ -0,0 +1,376 @@ +import type { CSSObject } from '../../_util/cssinjs'; +import { Keyframes } from '../../_util/cssinjs'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { genPresetColor, resetComponent } from '../../style'; + +interface BadgeToken extends FullToken<'Badge'> { + badgeFontHeight: number; + badgeZIndex: number | string; + badgeHeight: number; + badgeHeightSm: number; + badgeTextColor: string; + badgeFontWeight: string; + badgeFontSize: number; + badgeColor: string; + badgeColorHover: string; + badgeDotSize: number; + badgeFontSizeSm: number; + badgeStatusSize: number; + badgeShadowSize: number; + badgeShadowColor: string; + badgeProcessingDuration: string; + badgeRibbonOffset: number; + badgeRibbonCornerTransform: string; + badgeRibbonCornerFilter: string; +} + +const antStatusProcessing = new Keyframes('antStatusProcessing', { + '0%': { transform: 'scale(0.8)', opacity: 0.5 }, + '100%': { transform: 'scale(2.4)', opacity: 0 }, +}); + +const antZoomBadgeIn = new Keyframes('antZoomBadgeIn', { + '0%': { transform: 'scale(0) translate(50%, -50%)', opacity: 0 }, + '100%': { transform: 'scale(1) translate(50%, -50%)' }, +}); + +const antZoomBadgeOut = new Keyframes('antZoomBadgeOut', { + '0%': { transform: 'scale(1) translate(50%, -50%)' }, + '100%': { transform: 'scale(0) translate(50%, -50%)', opacity: 0 }, +}); + +const antNoWrapperZoomBadgeIn = new Keyframes('antNoWrapperZoomBadgeIn', { + '0%': { transform: 'scale(0)', opacity: 0 }, + '100%': { transform: 'scale(1)' }, +}); +const antNoWrapperZoomBadgeOut = new Keyframes('antNoWrapperZoomBadgeOut', { + '0%': { transform: 'scale(1)' }, + '100%': { transform: 'scale(0)', opacity: 0 }, +}); +const antBadgeLoadingCircle = new Keyframes('antBadgeLoadingCircle', { + '0%': { transformOrigin: '50%' }, + '100%': { + transform: 'translate(50%, -50%) rotate(360deg)', + transformOrigin: '50%', + }, +}); + +const genSharedBadgeStyle: GenerateStyle = (token: BadgeToken): CSSObject => { + const { + componentCls, + iconCls, + antCls, + badgeFontHeight, + badgeShadowSize, + badgeHeightSm, + motionDurationSlow, + badgeStatusSize, + marginXS, + badgeRibbonOffset, + } = token; + const numberPrefixCls = `${antCls}-scroll-number`; + const ribbonPrefixCls = `${antCls}-ribbon`; + const ribbonWrapperPrefixCls = `${antCls}-ribbon-wrapper`; + + const statusPreset = genPresetColor(token, (colorKey, { darkColor }) => ({ + [`${componentCls}-status-${colorKey}`]: { + background: darkColor, + }, + })); + + const statusRibbonPreset = genPresetColor(token, (colorKey, { darkColor }) => ({ + [`&${ribbonPrefixCls}-color-${colorKey}`]: { + background: darkColor, + color: darkColor, + }, + })); + + return { + [componentCls]: { + ...resetComponent(token), + position: 'relative', + display: 'inline-block', + width: 'fit-content', + lineHeight: 1, + + [`${componentCls}-count`]: { + zIndex: token.badgeZIndex, + minWidth: token.badgeHeight, + height: token.badgeHeight, + color: token.badgeTextColor, + fontWeight: token.badgeFontWeight, + fontSize: token.badgeFontSize, + lineHeight: `${token.badgeHeight}px`, + whiteSpace: 'nowrap', + textAlign: 'center', + background: token.badgeColor, + borderRadius: token.badgeHeight / 2, + boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`, + transition: `background ${token.motionDurationMid}`, + + a: { + color: token.badgeTextColor, + }, + 'a:hover': { + color: token.badgeTextColor, + }, + + 'a:hover &': { + background: token.badgeColorHover, + }, + }, + [`${componentCls}-count-sm`]: { + minWidth: badgeHeightSm, + height: badgeHeightSm, + fontSize: token.badgeFontSizeSm, + lineHeight: `${badgeHeightSm}px`, + borderRadius: badgeHeightSm / 2, + }, + + [`${componentCls}-multiple-words`]: { + padding: `0 ${token.paddingXS}px`, + }, + + [`${componentCls}-dot`]: { + zIndex: token.badgeZIndex, + width: token.badgeDotSize, + minWidth: token.badgeDotSize, + height: token.badgeDotSize, + background: token.badgeColor, + borderRadius: '100%', + boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`, + }, + [`${componentCls}-dot${numberPrefixCls}`]: { + transition: `background ${motionDurationSlow}`, + }, + [`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: { + position: 'absolute', + top: 0, + insetInlineEnd: 0, + transform: 'translate(50%, -50%)', + transformOrigin: '100% 0%', + [`${iconCls}-spin`]: { + animationName: antBadgeLoadingCircle, + animationDuration: token.motionDurationMid, + animationIterationCount: 'infinite', + animationTimingFunction: 'linear', + }, + }, + [`&${componentCls}-status`]: { + lineHeight: 'inherit', + verticalAlign: 'baseline', + + [`${componentCls}-status-dot`]: { + position: 'relative', + top: -1, // Magic number, but seems better experience + display: 'inline-block', + width: badgeStatusSize, + height: badgeStatusSize, + verticalAlign: 'middle', + borderRadius: '50%', + }, + + [`${componentCls}-status-success`]: { + backgroundColor: token.colorSuccess, + }, + [`${componentCls}-status-processing`]: { + position: 'relative', + color: token.colorPrimary, + backgroundColor: token.colorPrimary, + + '&::after': { + position: 'absolute', + top: 0, + insetInlineStart: 0, + width: '100%', + height: '100%', + borderWidth: badgeShadowSize, + borderStyle: 'solid', + borderColor: 'inherit', + borderRadius: '50%', + animationName: antStatusProcessing, + animationDuration: token.badgeProcessingDuration, + animationIterationCount: 'infinite', + animationTimingFunction: 'ease-in-out', + content: '""', + }, + }, + [`${componentCls}-status-default`]: { + backgroundColor: token.colorTextPlaceholder, + }, + + [`${componentCls}-status-error`]: { + backgroundColor: token.colorError, + }, + + [`${componentCls}-status-warning`]: { + backgroundColor: token.colorWarning, + }, + ...statusPreset, + [`${componentCls}-status-text`]: { + marginInlineStart: marginXS, + color: token.colorText, + fontSize: token.fontSize, + }, + }, + [`${componentCls}-zoom-appear, ${componentCls}-zoom-enter`]: { + animationName: antZoomBadgeIn, + animationDuration: token.motionDurationSlow, + animationTimingFunction: token.motionEaseOutBack, + animationFillMode: 'both', + }, + [`${componentCls}-zoom-leave`]: { + animationName: antZoomBadgeOut, + animationDuration: token.motionDurationSlow, + animationTimingFunction: token.motionEaseOutBack, + animationFillMode: 'both', + }, + [`&${componentCls}-not-a-wrapper`]: { + [`${componentCls}-zoom-appear, ${componentCls}-zoom-enter`]: { + animationName: antNoWrapperZoomBadgeIn, + animationDuration: token.motionDurationSlow, + animationTimingFunction: token.motionEaseOutBack, + }, + + [`${componentCls}-zoom-leave`]: { + animationName: antNoWrapperZoomBadgeOut, + animationDuration: token.motionDurationSlow, + animationTimingFunction: token.motionEaseOutBack, + }, + [`&:not(${componentCls}-status)`]: { + verticalAlign: 'middle', + }, + [`${numberPrefixCls}-custom-component, ${componentCls}-count`]: { + transform: 'none', + }, + [`${numberPrefixCls}-custom-component, ${numberPrefixCls}`]: { + position: 'relative', + top: 'auto', + display: 'block', + transformOrigin: '50% 50%', + }, + }, + [`${numberPrefixCls}`]: { + overflow: 'hidden', + [`${numberPrefixCls}-only`]: { + position: 'relative', + display: 'inline-block', + height: token.badgeHeight, + transition: `all ${token.motionDurationSlow} ${token.motionEaseOutBack}`, + WebkitTransformStyle: 'preserve-3d', + WebkitBackfaceVisibility: 'hidden', + [`> p${numberPrefixCls}-only-unit`]: { + height: token.badgeHeight, + margin: 0, + WebkitTransformStyle: 'preserve-3d', + WebkitBackfaceVisibility: 'hidden', + }, + }, + [`${numberPrefixCls}-symbol`]: { verticalAlign: 'top' }, + }, + + // ====================== RTL ======================= + '&-rtl': { + direction: 'rtl', + + [`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: { + transform: 'translate(-50%, -50%)', + }, + }, + }, + [`${ribbonWrapperPrefixCls}`]: { position: 'relative' }, + [`${ribbonPrefixCls}`]: { + ...resetComponent(token), + position: 'absolute', + top: marginXS, + height: badgeFontHeight, + padding: `0 ${token.paddingXS}px`, + color: token.colorPrimary, + lineHeight: `${badgeFontHeight}px`, + whiteSpace: 'nowrap', + backgroundColor: token.colorPrimary, + borderRadius: token.borderRadiusSM, + [`${ribbonPrefixCls}-text`]: { color: token.colorTextLightSolid }, + [`${ribbonPrefixCls}-corner`]: { + position: 'absolute', + top: '100%', + width: badgeRibbonOffset, + height: badgeRibbonOffset, + color: 'currentcolor', + border: `${badgeRibbonOffset / 2}px solid`, + transform: token.badgeRibbonCornerTransform, + transformOrigin: 'top', + filter: token.badgeRibbonCornerFilter, + }, + ...statusRibbonPreset, + [`&${ribbonPrefixCls}-placement-end`]: { + insetInlineEnd: -badgeRibbonOffset, + borderEndEndRadius: 0, + [`${ribbonPrefixCls}-corner`]: { + insetInlineEnd: 0, + borderInlineEndColor: 'transparent', + borderBlockEndColor: 'transparent', + }, + }, + [`&${ribbonPrefixCls}-placement-start`]: { + insetInlineStart: -badgeRibbonOffset, + borderEndStartRadius: 0, + [`${ribbonPrefixCls}-corner`]: { + insetInlineStart: 0, + borderBlockEndColor: 'transparent', + borderInlineStartColor: 'transparent', + }, + }, + + // ====================== RTL ======================= + '&-rtl': { + direction: 'rtl', + }, + }, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('Badge', token => { + const { fontSize, lineHeight, fontSizeSM, lineWidth, marginXS, colorBorderBg } = token; + + const badgeFontHeight = Math.round(fontSize * lineHeight); + const badgeShadowSize = lineWidth; + const badgeZIndex = 'auto'; + const badgeHeight = badgeFontHeight - 2 * badgeShadowSize; + const badgeTextColor = token.colorBgContainer; + const badgeFontWeight = 'normal'; + const badgeFontSize = fontSizeSM; + const badgeColor = token.colorError; + const badgeColorHover = token.colorErrorHover; + const badgeHeightSm = fontSize; + const badgeDotSize = fontSizeSM / 2; + const badgeFontSizeSm = fontSizeSM; + const badgeStatusSize = fontSizeSM / 2; + + const badgeToken = mergeToken(token, { + badgeFontHeight, + badgeShadowSize, + badgeZIndex, + badgeHeight, + badgeTextColor, + badgeFontWeight, + badgeFontSize, + badgeColor, + badgeColorHover, + badgeShadowColor: colorBorderBg, + badgeHeightSm, + badgeDotSize, + badgeFontSizeSm, + badgeStatusSize, + badgeProcessingDuration: '1.2s', + badgeRibbonOffset: marginXS, + + // Follow token just by Design. Not related with token + badgeRibbonCornerTransform: 'scaleY(0.75)', + badgeRibbonCornerFilter: `brightness(75%)`, + }); + + return [genSharedBadgeStyle(badgeToken)]; +}); diff --git a/components/badge/style/index.tsx b/components/badge/style/index.tsx deleted file mode 100644 index 3a3ab0de5..000000000 --- a/components/badge/style/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import '../../style/index.less'; -import './index.less'; diff --git a/components/badge/style/ribbon.less b/components/badge/style/ribbon.less deleted file mode 100644 index 6a6e366bf..000000000 --- a/components/badge/style/ribbon.less +++ /dev/null @@ -1,81 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@ribbon-prefix-cls: ~'@{ant-prefix}-ribbon'; -@ribbon-wrapper-prefix-cls: ~'@{ant-prefix}-ribbon-wrapper'; - -.@{ribbon-wrapper-prefix-cls} { - position: relative; -} - -.@{ribbon-prefix-cls} { - .reset-component(); - - position: absolute; - top: 8px; - height: 22px; - padding: 0 8px; - color: @badge-text-color; - line-height: 22px; - white-space: nowrap; - background-color: @primary-color; - border-radius: @border-radius-sm; - - &-text { - color: @white; - } - - &-corner { - position: absolute; - top: 100%; - width: 8px; - height: 8px; - color: currentcolor; - border: 4px solid; - transform: scaleY(0.75); - transform-origin: top; - // If not support IE 11, use filter: brightness(75%) instead - &::after { - position: absolute; - top: -4px; - left: -4px; - width: inherit; - height: inherit; - color: rgba(0, 0, 0, 0.25); - border: inherit; - content: ''; - } - } - - // colors - // mixin to iterate over colors and create CSS class for each one - .make-color-classes(@i: length(@preset-colors)) when (@i > 0) { - .make-color-classes(@i - 1); - @color: extract(@preset-colors, @i); - @darkColor: '@{color}-6'; - &-color-@{color} { - color: @@darkColor; - background: @@darkColor; - } - } - .make-color-classes(); - - // placement - &.@{ribbon-prefix-cls}-placement-end { - right: -8px; - border-bottom-right-radius: 0; - .@{ribbon-prefix-cls}-corner { - right: 0; - border-color: currentcolor transparent transparent currentcolor; - } - } - - &.@{ribbon-prefix-cls}-placement-start { - left: -8px; - border-bottom-left-radius: 0; - .@{ribbon-prefix-cls}-corner { - left: 0; - border-color: currentcolor currentcolor transparent transparent; - } - } -} diff --git a/components/badge/style/rtl.less b/components/badge/style/rtl.less deleted file mode 100644 index 7d7c02867..000000000 --- a/components/badge/style/rtl.less +++ /dev/null @@ -1,67 +0,0 @@ -.@{badge-prefix-cls} { - &-rtl { - direction: rtl; - } - - &:not(&-not-a-wrapper) &-count, - &:not(&-not-a-wrapper) &-dot, - &:not(&-not-a-wrapper) .@{number-prefix-cls}-custom-component { - .@{badge-prefix-cls}-rtl & { - right: auto; - left: 0; - direction: ltr; - transform: translate(-50%, -50%); - transform-origin: 0% 0%; - } - } - - &-rtl&:not(&-not-a-wrapper) .@{number-prefix-cls}-custom-component { - right: auto; - left: 0; - transform: translate(-50%, -50%); - transform-origin: 0% 0%; - } - - &-status { - &-text { - .@{badge-prefix-cls}-rtl & { - margin-right: 8px; - margin-left: 0; - } - } - } -} - -.@{ribbon-prefix-cls}-rtl { - direction: rtl; - &.@{ribbon-prefix-cls}-placement-end { - right: unset; - left: -8px; - border-bottom-right-radius: @border-radius-sm; - border-bottom-left-radius: 0; - .@{ribbon-prefix-cls}-corner { - right: unset; - left: 0; - border-color: currentcolor currentcolor transparent transparent; - - &::after { - border-color: currentcolor currentcolor transparent transparent; - } - } - } - &.@{ribbon-prefix-cls}-placement-start { - right: -8px; - left: unset; - border-bottom-right-radius: 0; - border-bottom-left-radius: @border-radius-sm; - .@{ribbon-prefix-cls}-corner { - right: 0; - left: unset; - border-color: currentcolor transparent transparent currentcolor; - - &::after { - border-color: currentcolor transparent transparent currentcolor; - } - } - } -} diff --git a/components/badge/utils.ts b/components/badge/utils.ts deleted file mode 100644 index 21bebac2e..000000000 --- a/components/badge/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PresetColorTypes } from '../_util/colors'; - -export function isPresetColor(color?: string): boolean { - return (PresetColorTypes as any[]).indexOf(color) !== -1; -} diff --git a/components/breadcrumb/Breadcrumb.tsx b/components/breadcrumb/Breadcrumb.tsx index c14eaa1d5..ad9e24b0d 100644 --- a/components/breadcrumb/Breadcrumb.tsx +++ b/components/breadcrumb/Breadcrumb.tsx @@ -3,10 +3,12 @@ import { cloneVNode, defineComponent } from 'vue'; import PropTypes from '../_util/vue-types'; import { flattenChildren, getPropsSlot } from '../_util/props-util'; import warning from '../_util/warning'; +import type { BreadcrumbItemProps } from './BreadcrumbItem'; import BreadcrumbItem from './BreadcrumbItem'; import Menu from '../menu'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; +import useStyle from './style'; import type { CustomSlotsType, VueNode } from '../_util/type'; -import useConfigInject from '../_util/hooks/useConfigInject'; export interface Route { path: string; @@ -54,15 +56,16 @@ function defaultItemRender(opt: { export default defineComponent({ compatConfig: { MODE: 3 }, name: 'ABreadcrumb', + inheritAttrs: false, props: breadcrumbProps(), slots: Object as CustomSlotsType<{ separator: any; itemRender: { route: Route; params: any; routes: Route[]; paths: string[] }; default: any; }>, - setup(props, { slots }) { + setup(props, { slots, attrs }) { const { prefixCls, direction } = useConfigInject('breadcrumb', props); - + const [wrapSSR, hashId] = useStyle(prefixCls); const getPath = (path: string, params: unknown) => { path = (path || '').replace(/^\//, ''); Object.keys(params).forEach(key => { @@ -98,27 +101,25 @@ export default defineComponent({ let overlay = null; if (route.children && route.children.length) { overlay = ( - - {route.children.map(child => ( - - {itemRender({ - route: child, - params, - routes, - paths: addChildPath(tempPaths, child.path, params), - })} - - ))} - + ({ + key: child.path || child.breadcrumbName, + label: itemRender({ + route: child, + params, + routes, + paths: addChildPath(tempPaths, child.path, params), + }), + }))} + > ); } - + const itemProps: BreadcrumbItemProps = { separator }; + if (overlay) { + itemProps.overlay = overlay; + } return ( - + {itemRender({ route, params, routes, paths: tempPaths })} ); @@ -156,8 +157,15 @@ export default defineComponent({ const breadcrumbClassName = { [prefixCls.value]: true, [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [`${attrs.class}`]: !!attrs.class, + [hashId.value]: true, }; - return
{crumbs}
; + + return wrapSSR( + , + ); }; }, }); diff --git a/components/breadcrumb/BreadcrumbItem.tsx b/components/breadcrumb/BreadcrumbItem.tsx index c7f2fa7a3..194114c98 100644 --- a/components/breadcrumb/BreadcrumbItem.tsx +++ b/components/breadcrumb/BreadcrumbItem.tsx @@ -1,19 +1,22 @@ -import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; +import type { CSSProperties, ExtractPropTypes } from 'vue'; import { defineComponent } from 'vue'; import PropTypes from '../_util/vue-types'; import { getPropsSlot } from '../_util/props-util'; -import DropDown from '../dropdown/dropdown'; +import type { DropdownProps } from '../dropdown/dropdown'; +import Dropdown from '../dropdown/dropdown'; import DownOutlined from '@ant-design/icons-vue/DownOutlined'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; import type { MouseEventHandler } from '../_util/EventInterface'; +import { eventType, objectType } from '../_util/type'; import type { CustomSlotsType } from '../_util/type'; export const breadcrumbItemProps = () => ({ prefixCls: String, href: String, separator: PropTypes.any, + dropdownProps: objectType(), overlay: PropTypes.any, - onClick: Function as PropType, + onClick: eventType(), }); export type BreadcrumbItemProps = Partial>>; @@ -29,27 +32,29 @@ export default defineComponent({ overlay: any; default: any; }>, - setup(props, { slots, attrs }) { + setup(props, { slots, attrs, emit }) { const { prefixCls } = useConfigInject('breadcrumb', props); /** * if overlay is have - * Wrap a DropDown + * Wrap a Dropdown */ const renderBreadcrumbNode = (breadcrumbItem: JSX.Element, prefixCls: string) => { const overlay = getPropsSlot(slots, props, 'overlay'); if (overlay) { return ( - + {breadcrumbItem} - + ); } return breadcrumbItem; }; - + const handleClick = (e: MouseEvent) => { + emit('click', e); + }; return () => { const separator = getPropsSlot(slots, props, 'separator') ?? '/'; const children = getPropsSlot(slots, props); @@ -57,25 +62,25 @@ export default defineComponent({ let link: JSX.Element; if (props.href !== undefined) { link = ( - + {children} ); } else { link = ( - + {children} ); } // wrap to dropDown link = renderBreadcrumbNode(link, prefixCls.value); - if (children) { + if (children !== undefined && children !== null) { return ( - +
  • {link} {separator && {separator}} - +
  • ); } return null; diff --git a/components/breadcrumb/BreadcrumbSeparator.tsx b/components/breadcrumb/BreadcrumbSeparator.tsx index 91e90732d..6f981dff4 100644 --- a/components/breadcrumb/BreadcrumbSeparator.tsx +++ b/components/breadcrumb/BreadcrumbSeparator.tsx @@ -1,7 +1,7 @@ import type { ExtractPropTypes } from 'vue'; import { defineComponent } from 'vue'; import { flattenChildren } from '../_util/props-util'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; export const breadcrumbSeparatorProps = () => ({ prefixCls: String, diff --git a/components/breadcrumb/__tests__/Breadcrumb.test.js b/components/breadcrumb/__tests__/Breadcrumb.test.js index 4375e885d..0af7f08e0 100644 --- a/components/breadcrumb/__tests__/Breadcrumb.test.js +++ b/components/breadcrumb/__tests__/Breadcrumb.test.js @@ -25,7 +25,7 @@ describe('Breadcrumb', () => { }); expect(errorSpy.mock.calls).toHaveLength(1); expect(errorSpy.mock.calls[0][0]).toMatch( - "Warning: [antdv: Breadcrumb] Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children", + "Warning: [ant-design-vue: Breadcrumb] Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children", ); }); diff --git a/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap b/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap index 819219069..2da2de9ed 100644 --- a/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap +++ b/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap @@ -1,15 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Breadcrumb should allow Breadcrumb.Item is null or undefined 1`] = `
    Home/
    `; +exports[`Breadcrumb should allow Breadcrumb.Item is null or undefined 1`] = ` + +`; exports[`Breadcrumb should not display Breadcrumb Item when its children is falsy 1`] = ` -
    - xxx/yyy/ -
    + `; -exports[`Breadcrumb should render a menu 1`] = `
    home/first/second/
    `; +exports[`Breadcrumb should render a menu 1`] = ` + +`; -exports[`Breadcrumb should support Breadcrumb.Item default separator 1`] = `
    Location/Mock Node/Application Center/
    `; +exports[`Breadcrumb should support Breadcrumb.Item default separator 1`] = ` + +`; -exports[`Breadcrumb should support custom attribute 1`] = `
    xxx/yyy/
    `; +exports[`Breadcrumb should support custom attribute 1`] = ` + +`; diff --git a/components/breadcrumb/__tests__/__snapshots__/demo.test.js.snap b/components/breadcrumb/__tests__/__snapshots__/demo.test.js.snap index eb22264cf..1a0c70eb2 100644 --- a/components/breadcrumb/__tests__/__snapshots__/demo.test.js.snap +++ b/components/breadcrumb/__tests__/__snapshots__/demo.test.js.snap @@ -1,14 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders ./components/breadcrumb/demo/basic.vue correctly 1`] = ``; +exports[`renders ./components/breadcrumb/demo/basic.vue correctly 1`] = ` + +`; -exports[`renders ./components/breadcrumb/demo/overlay.vue correctly 1`] = `
    Ant Design Vue/Component/General/Button/
    `; +exports[`renders ./components/breadcrumb/demo/overlay.vue correctly 1`] = ` + +`; exports[`renders ./components/breadcrumb/demo/separator.vue correctly 1`] = ` - - + + `; -exports[`renders ./components/breadcrumb/demo/separator-indepent.vue correctly 1`] = `
    Location:Application Center/Application List/An Application
    `; +exports[`renders ./components/breadcrumb/demo/separator-indepent.vue correctly 1`] = ` + +`; -exports[`renders ./components/breadcrumb/demo/withIcon.vue correctly 1`] = `
    /Application List/Application/
    `; +exports[`renders ./components/breadcrumb/demo/withIcon.vue correctly 1`] = ` + +`; diff --git a/components/breadcrumb/demo/router.vue b/components/breadcrumb/demo/router.vue index c5037485a..cafd12a63 100644 --- a/components/breadcrumb/demo/router.vue +++ b/components/breadcrumb/demo/router.vue @@ -32,8 +32,8 @@ Used together with `vue-router` {{ $route.path }}
    - diff --git a/components/breadcrumb/demo/withIcon.vue b/components/breadcrumb/demo/withIcon.vue index 02c28b8cd..636c7dbed 100644 --- a/components/breadcrumb/demo/withIcon.vue +++ b/components/breadcrumb/demo/withIcon.vue @@ -28,13 +28,6 @@ The icon should be placed in front of the text. Application - diff --git a/components/breadcrumb/index.en-US.md b/components/breadcrumb/index.en-US.md index 504affdfe..d1f9b219b 100644 --- a/components/breadcrumb/index.en-US.md +++ b/components/breadcrumb/index.en-US.md @@ -2,7 +2,8 @@ category: Components type: Navigation title: Breadcrumb -cover: https://gw.alipayobjects.com/zos/alicdn/9Ltop8JwH/Breadcrumb.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*I5a2Tpqs3y0AAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*Tr90QKrE_LcAAAAAAAAAAAAADrJ8AQ/original --- A breadcrumb displays the current location within a hierarchy. It allows going back to states higher up in the hierarchy. diff --git a/components/breadcrumb/index.zh-CN.md b/components/breadcrumb/index.zh-CN.md index 548c09675..5e67b0fb4 100644 --- a/components/breadcrumb/index.zh-CN.md +++ b/components/breadcrumb/index.zh-CN.md @@ -3,7 +3,8 @@ category: Components subtitle: 面包屑 type: 导航 title: Breadcrumb -cover: https://gw.alipayobjects.com/zos/alicdn/9Ltop8JwH/Breadcrumb.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*I5a2Tpqs3y0AAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*Tr90QKrE_LcAAAAAAAAAAAAADrJ8AQ/original --- 显示当前页面在系统层级结构中的位置,并能向上返回。 diff --git a/components/breadcrumb/style/index.less b/components/breadcrumb/style/index.less deleted file mode 100644 index ca29afb3a..000000000 --- a/components/breadcrumb/style/index.less +++ /dev/null @@ -1,56 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@breadcrumb-prefix-cls: ~'@{ant-prefix}-breadcrumb'; - -.@{breadcrumb-prefix-cls} { - .reset-component(); - - color: @breadcrumb-base-color; - font-size: @breadcrumb-font-size; - - .@{iconfont-css-prefix} { - font-size: @breadcrumb-icon-font-size; - } - - a { - color: @breadcrumb-link-color; - transition: color 0.3s; - - &:hover { - color: @breadcrumb-link-color-hover; - } - } - - & > span:last-child { - color: @breadcrumb-last-item-color; - - a { - color: @breadcrumb-last-item-color; - } - } - - & > span:last-child &-separator { - display: none; - } - - &-separator { - margin: @breadcrumb-separator-margin; - color: @breadcrumb-separator-color; - } - - &-link { - > .@{iconfont-css-prefix} + span, - > .@{iconfont-css-prefix} + a { - margin-left: 4px; - } - } - - &-overlay-link { - > .@{iconfont-css-prefix} { - margin-left: 4px; - } - } -} - -@import './rtl'; diff --git a/components/breadcrumb/style/index.ts b/components/breadcrumb/style/index.ts new file mode 100644 index 000000000..9d3b24d9c --- /dev/null +++ b/components/breadcrumb/style/index.ts @@ -0,0 +1,127 @@ +import type { CSSObject } from '../../_util/cssinjs'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { genFocusStyle, resetComponent } from '../../style'; + +interface BreadcrumbToken extends FullToken<'Breadcrumb'> { + breadcrumbBaseColor: string; + breadcrumbFontSize: number; + breadcrumbIconFontSize: number; + breadcrumbLinkColor: string; + breadcrumbLinkColorHover: string; + breadcrumbLastItemColor: string; + breadcrumbSeparatorMargin: number; + breadcrumbSeparatorColor: string; +} + +const genBreadcrumbStyle: GenerateStyle = token => { + const { componentCls, iconCls } = token; + + return { + [componentCls]: { + ...resetComponent(token), + color: token.breadcrumbBaseColor, + fontSize: token.breadcrumbFontSize, + + [iconCls]: { + fontSize: token.breadcrumbIconFontSize, + }, + + ol: { + display: 'flex', + flexWrap: 'wrap', + margin: 0, + padding: 0, + listStyle: 'none', + }, + + a: { + color: token.breadcrumbLinkColor, + transition: `color ${token.motionDurationMid}`, + padding: `0 ${token.paddingXXS}px`, + borderRadius: token.borderRadiusSM, + height: token.lineHeight * token.fontSize, + display: 'inline-block', + marginInline: -token.marginXXS, + + '&:hover': { + color: token.breadcrumbLinkColorHover, + backgroundColor: token.colorBgTextHover, + }, + + ...genFocusStyle(token), + }, + + [`li:last-child`]: { + color: token.breadcrumbLastItemColor, + + [`& > ${componentCls}-separator`]: { + display: 'none', + }, + }, + + [`${componentCls}-separator`]: { + marginInline: token.breadcrumbSeparatorMargin, + color: token.breadcrumbSeparatorColor, + }, + + [`${componentCls}-link`]: { + [` + > ${iconCls} + span, + > ${iconCls} + a + `]: { + marginInlineStart: token.marginXXS, + }, + }, + + [`${componentCls}-overlay-link`]: { + borderRadius: token.borderRadiusSM, + height: token.lineHeight * token.fontSize, + display: 'inline-block', + padding: `0 ${token.paddingXXS}px`, + marginInline: -token.marginXXS, + + [`> ${iconCls}`]: { + marginInlineStart: token.marginXXS, + fontSize: token.fontSizeIcon, + }, + + '&:hover': { + color: token.breadcrumbLinkColorHover, + backgroundColor: token.colorBgTextHover, + + a: { + color: token.breadcrumbLinkColorHover, + }, + }, + + a: { + '&:hover': { + backgroundColor: 'transparent', + }, + }, + }, + + // rtl style + [`&${token.componentCls}-rtl`]: { + direction: 'rtl', + }, + }, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('Breadcrumb', token => { + const BreadcrumbToken = mergeToken(token, { + breadcrumbBaseColor: token.colorTextDescription, + breadcrumbFontSize: token.fontSize, + breadcrumbIconFontSize: token.fontSize, + breadcrumbLinkColor: token.colorTextDescription, + breadcrumbLinkColorHover: token.colorText, + breadcrumbLastItemColor: token.colorText, + breadcrumbSeparatorMargin: token.marginXS, + breadcrumbSeparatorColor: token.colorTextDescription, + }); + + return [genBreadcrumbStyle(BreadcrumbToken)]; +}); diff --git a/components/breadcrumb/style/index.tsx b/components/breadcrumb/style/index.tsx deleted file mode 100644 index 3d7084daa..000000000 --- a/components/breadcrumb/style/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import '../../style/index.less'; -import './index.less'; - -// style dependencies -import '../../menu/style'; -import '../../dropdown/style'; diff --git a/components/breadcrumb/style/rtl.less b/components/breadcrumb/style/rtl.less deleted file mode 100644 index c1141c993..000000000 --- a/components/breadcrumb/style/rtl.less +++ /dev/null @@ -1,29 +0,0 @@ -.@{breadcrumb-prefix-cls} { - &-rtl { - .clearfix(); - direction: rtl; - - > span { - float: right; - } - } - - &-link { - > .@{iconfont-css-prefix} + span, - > .@{iconfont-css-prefix} + a { - .@{breadcrumb-prefix-cls}-rtl & { - margin-right: 4px; - margin-left: 0; - } - } - } - - &-overlay-link { - > .@{iconfont-css-prefix} { - .@{breadcrumb-prefix-cls}-rtl & { - margin-right: 4px; - margin-left: 0; - } - } - } -} diff --git a/components/button/__tests__/__snapshots__/demo.test.js.snap b/components/button/__tests__/__snapshots__/demo.test.js.snap index 25dbe2644..cd54d6e05 100644 --- a/components/button/__tests__/__snapshots__/demo.test.js.snap +++ b/components/button/__tests__/__snapshots__/demo.test.js.snap @@ -1,61 +1,75 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders ./components/button/demo/basic.vue correctly 1`] = ` - - - - - +
    +
    + +
    + +
    + +
    + +
    + +
    `; exports[`renders ./components/button/demo/block.vue correctly 1`] = ` - - - - - +
    +
    + +
    + +
    + +
    + +
    + +
    `; exports[`renders ./components/button/demo/button-group.vue correctly 1`] = `

    Basic

    -
    -
    +
    +
    + +
    + +
    + +
    + +
    + +
    `; exports[`renders ./components/button/demo/disabled.vue correctly 1`] = ` - - -
    - - -
    - - -
    - - -
    - - -
    - - -
    - - -
    - - -
    -
    +
    +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    `; exports[`renders ./components/button/demo/ghost.vue correctly 1`] = ` -
    +
    +
    +
    + +
    + +
    + +
    + +
    +
    `; exports[`renders ./components/button/demo/icon.vue correctly 1`] = ` - - - - - - - - - - -
    -
    - - - - - -
    - - - - - +
    +
    +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    `; exports[`renders ./components/button/demo/loading.vue correctly 1`] = ` -
    -
    +
    +
    +
    +
    + +
    + +
    +
    -
    +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    + +
    +
    -
    +`; + +exports[`renders ./components/button/demo/multiple.vue correctly 1`] = ` +
    -
    - -
    -
    -
    - -
    +
    -
    +
    + +
    `; -exports[`renders ./components/button/demo/multiple.vue correctly 1`] = ` - - - -`; - exports[`renders ./components/button/demo/size.vue correctly 1`] = ` -
    -
    -
    - - - - - -
    - - - - - -
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    `; diff --git a/components/button/__tests__/__snapshots__/index.test.js.snap b/components/button/__tests__/__snapshots__/index.test.js.snap index 00b3398d8..316ca068b 100644 --- a/components/button/__tests__/__snapshots__/index.test.js.snap +++ b/components/button/__tests__/__snapshots__/index.test.js.snap @@ -1,53 +1,53 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Button fixbug renders {0} , 0 and {false} 1`] = ` - `; exports[`Button fixbug renders {0} , 0 and {false} 2`] = ` - `; exports[`Button fixbug renders {0} , 0 and {false} 3`] = ` - `; exports[`Button renders Chinese characters correctly 1`] = ` - `; exports[`Button renders Chinese characters correctly 2`] = ` - `; exports[`Button renders Chinese characters correctly 3`] = ` - `; -exports[`Button renders Chinese characters correctly 4`] = ``; +exports[`Button renders Chinese characters correctly 4`] = ``; -exports[`Button renders Chinese characters correctly 5`] = ``; +exports[`Button renders Chinese characters correctly 5`] = ``; exports[`Button renders Chinese characters correctly 6`] = ` - `; exports[`Button renders correctly 1`] = ` - `; @@ -59,7 +59,7 @@ exports[`Button should not render as link button when href is undefined 1`] = ` `; exports[`Button should support link button 1`] = ` - + link button `; diff --git a/components/button/__tests__/wave.test.js b/components/button/__tests__/wave.test.js index 0e9300484..cfe216642 100644 --- a/components/button/__tests__/wave.test.js +++ b/components/button/__tests__/wave.test.js @@ -13,54 +13,6 @@ describe('click wave effect', () => { await sleep(20); } - it('should have click wave effect for primary button', async () => { - const wrapper = mount({ - render() { - return ; - }, - }); - await clickButton(wrapper); - expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe( - 'true', - ); - }); - - it('should have click wave effect for default button', async () => { - const wrapper = mount({ - render() { - return ; - }, - }); - await clickButton(wrapper); - expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe( - 'true', - ); - }); - - it('should not have click wave effect for link type button', async () => { - const wrapper = mount({ - render() { - return ; - }, - }); - await clickButton(wrapper); - expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe( - undefined, - ); - }); - - it('should not have click wave effect for text type button', async () => { - const wrapper = mount({ - render() { - return ; - }, - }); - await clickButton(wrapper); - expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe( - undefined, - ); - }); - it('should handle transitionstart', async () => { const wrapper = mount({ render() { @@ -70,9 +22,6 @@ describe('click wave effect', () => { await clickButton(wrapper); const buttonNode = wrapper.find('.ant-btn').element; buttonNode.dispatchEvent(new Event('transitionstart')); - expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe( - 'true', - ); wrapper.unmount(); buttonNode.dispatchEvent(new Event('transitionstart')); }); diff --git a/components/button/button-group.tsx b/components/button/button-group.tsx index f934ece19..140338265 100644 --- a/components/button/button-group.tsx +++ b/components/button/button-group.tsx @@ -1,10 +1,11 @@ -import { computed, defineComponent } from 'vue'; +import { computed, defineComponent, reactive } from 'vue'; import { flattenChildren } from '../_util/props-util'; -import useConfigInject from '../_util/hooks/useConfigInject'; - +import useConfigInject from '../config-provider/hooks/useConfigInject'; +import { useToken } from '../theme/internal'; import type { ExtractPropTypes, PropType } from 'vue'; import type { SizeType } from '../config-provider'; -import UnreachableException from '../_util/unreachableException'; +import devWarning from '../vc-util/devWarning'; +import createContext from '../_util/createContext'; export const buttonGroupProps = () => ({ prefixCls: String, @@ -14,17 +15,23 @@ export const buttonGroupProps = () => ({ }); export type ButtonGroupProps = Partial>>; - +export const GroupSizeContext = createContext<{ + size: SizeType; +}>(); export default defineComponent({ compatConfig: { MODE: 3 }, name: 'AButtonGroup', props: buttonGroupProps(), setup(props, { slots }) { const { prefixCls, direction } = useConfigInject('btn-group', props); + const [, , hashId] = useToken(); + GroupSizeContext.useProvide( + reactive({ + size: computed(() => props.size), + }), + ); const classes = computed(() => { const { size } = props; - // large => lg - // small => sm let sizeCls = ''; switch (size) { case 'large': @@ -38,12 +45,13 @@ export default defineComponent({ break; default: // eslint-disable-next-line no-console - console.warn(new UnreachableException(size).error); + devWarning(!size, 'Button.Group', 'Invalid prop `size`.'); } return { [`${prefixCls.value}`]: true, [`${prefixCls.value}-${sizeCls}`]: sizeCls, [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [hashId.value]: true, }; }); return () => { diff --git a/components/button/button.tsx b/components/button/button.tsx index 951c298d2..32fdf9e21 100644 --- a/components/button/button.tsx +++ b/components/button/button.tsx @@ -4,7 +4,7 @@ import { onBeforeUnmount, onMounted, onUpdated, - ref, + shallowRef, Text, watch, watchEffect, @@ -12,12 +12,15 @@ import { import Wave from '../_util/wave'; import buttonProps from './buttonTypes'; import { flattenChildren, initDefaultProps } from '../_util/props-util'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; +import { useInjectDisabled } from '../config-provider/DisabledContext'; import devWarning from '../vc-util/devWarning'; import LoadingIcon from './LoadingIcon'; - +import useStyle from './style'; import type { ButtonType } from './buttonTypes'; -import type { VNode, Ref } from 'vue'; +import type { VNode } from 'vue'; +import { GroupSizeContext } from './button-group'; +import { useCompactItemContext } from '../space/Compact'; import type { CustomSlotsType } from '../_util/type'; type Loading = boolean | number; @@ -25,7 +28,7 @@ type Loading = boolean | number; const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/; const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar); -function isUnborderedButtonType(type: ButtonType | undefined) { +function isUnBorderedButtonType(type: ButtonType | undefined) { return type === 'text' || type === 'link'; } export { buttonProps }; @@ -42,15 +45,19 @@ export default defineComponent({ // emits: ['click', 'mousedown'], setup(props, { slots, attrs, emit, expose }) { const { prefixCls, autoInsertSpaceInButton, direction, size } = useConfigInject('btn', props); - - const buttonNodeRef = ref(null); - const delayTimeoutRef = ref(undefined); + const [wrapSSR, hashId] = useStyle(prefixCls); + const groupSizeContext = GroupSizeContext.useInject(); + const disabledContext = useInjectDisabled(); + const mergedDisabled = computed(() => props.disabled ?? disabledContext.value); + const buttonNodeRef = shallowRef(null); + const delayTimeoutRef = shallowRef(undefined); let isNeedInserted = false; - const innerLoading: Ref = ref(false); - const hasTwoCNChar = ref(false); + const innerLoading = shallowRef(false); + const hasTwoCNChar = shallowRef(false); const autoInsertSpace = computed(() => autoInsertSpaceInButton.value !== false); + const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); // =============== Update Loading =============== const loadingOrDelay = computed(() => @@ -81,21 +88,25 @@ export default defineComponent({ const pre = prefixCls.value; const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined }; - const sizeFullname = size.value; + const sizeFullname = compactSize.value || groupSizeContext?.size || size.value; const sizeCls = sizeFullname ? sizeClassNameMap[sizeFullname] || '' : ''; - return { - [`${pre}`]: true, - [`${pre}-${type}`]: type, - [`${pre}-${shape}`]: shape !== 'default' && shape, - [`${pre}-${sizeCls}`]: sizeCls, - [`${pre}-loading`]: innerLoading.value, - [`${pre}-background-ghost`]: ghost && !isUnborderedButtonType(type), - [`${pre}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value, - [`${pre}-block`]: block, - [`${pre}-dangerous`]: !!danger, - [`${pre}-rtl`]: direction.value === 'rtl', - }; + return [ + compactItemClassnames.value, + { + [hashId.value]: true, + [`${pre}`]: true, + [`${pre}-${shape}`]: shape !== 'default' && shape, + [`${pre}-${type}`]: type, + [`${pre}-${sizeCls}`]: sizeCls, + [`${pre}-loading`]: innerLoading.value, + [`${pre}-background-ghost`]: ghost && !isUnBorderedButtonType(type), + [`${pre}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value, + [`${pre}-block`]: block, + [`${pre}-dangerous`]: !!danger, + [`${pre}-rtl`]: direction.value === 'rtl', + }, + ]; }); const fixTwoCNChar = () => { @@ -116,12 +127,15 @@ export default defineComponent({ }; const handleClick = (event: Event) => { // https://github.com/ant-design/ant-design/issues/30207 - if (innerLoading.value || props.disabled) { + if (innerLoading.value || mergedDisabled.value) { event.preventDefault(); return; } emit('click', event); }; + const handleMousedown = (event: Event) => { + emit('mousedown', event); + }; const insertSpace = (child: VNode, needInserted: boolean) => { const SPACE = needInserted ? ' ' : ''; @@ -137,7 +151,7 @@ export default defineComponent({ watchEffect(() => { devWarning( - !(props.ghost && isUnborderedButtonType(props.type)), + !(props.ghost && isUnBorderedButtonType(props.type)), 'Button', "`link` or `text` button can't be a `ghost` button.", ); @@ -165,28 +179,27 @@ export default defineComponent({ const { icon = slots.icon?.() } = props; const children = flattenChildren(slots.default?.()); - isNeedInserted = children.length === 1 && !icon && !isUnborderedButtonType(props.type); + isNeedInserted = children.length === 1 && !icon && !isUnBorderedButtonType(props.type); - const { type, htmlType, disabled, href, title, target, onMousedown } = props; + const { type, htmlType, href, title, target } = props; const iconType = innerLoading.value ? 'loading' : icon; const buttonProps = { ...attrs, title, - disabled, + disabled: mergedDisabled.value, class: [ classes.value, attrs.class, { [`${prefixCls.value}-icon-only`]: children.length === 0 && !!iconType }, ], onClick: handleClick, - onMousedown, + onMousedown: handleMousedown, }; // https://github.com/vueComponent/ant-design-vue/issues/4930 - if (!disabled) { + if (!mergedDisabled.value) { delete buttonProps.disabled; } - const iconNode = icon && !innerLoading.value ? ( icon @@ -203,30 +216,30 @@ export default defineComponent({ ); if (href !== undefined) { - return ( + return wrapSSR( {iconNode} {kids} - + , ); } - const buttonNode = ( + let buttonNode = ( ); - if (isUnborderedButtonType(type)) { - return buttonNode; + if (!isUnBorderedButtonType(type)) { + buttonNode = ( + + {buttonNode} + + ); } - return ( - - {buttonNode} - - ); + return wrapSSR(buttonNode); }; }, }); diff --git a/components/button/buttonTypes.ts b/components/button/buttonTypes.ts index d4e49551d..4ad33168a 100644 --- a/components/button/buttonTypes.ts +++ b/components/button/buttonTypes.ts @@ -2,6 +2,8 @@ import PropTypes from '../_util/vue-types'; import type { ExtractPropTypes, PropType } from 'vue'; import type { SizeType } from '../config-provider'; +import { eventType } from '../_util/type'; +import type { MouseEventHandler } from '../_util/EventInterface'; export type ButtonType = 'link' | 'default' | 'primary' | 'ghost' | 'dashed' | 'text'; export type ButtonShape = 'default' | 'circle' | 'round'; @@ -36,12 +38,8 @@ export const buttonProps = () => ({ href: String, target: String, title: String, - onClick: { - type: Function as PropType<(event: MouseEvent) => void>, - }, - onMousedown: { - type: Function as PropType<(event: MouseEvent) => void>, - }, + onClick: eventType(), + onMousedown: eventType(), }); export type ButtonProps = Partial>>; diff --git a/components/button/demo/basic.vue b/components/button/demo/basic.vue index ff7893f34..c42c6105b 100644 --- a/components/button/demo/basic.vue +++ b/components/button/demo/basic.vue @@ -17,9 +17,11 @@ There are `primary` button, `default` button, `dashed` button, `text` button and diff --git a/components/button/demo/block.vue b/components/button/demo/block.vue index 1d9a65a65..888e36d47 100644 --- a/components/button/demo/block.vue +++ b/components/button/demo/block.vue @@ -16,9 +16,11 @@ title: diff --git a/components/button/demo/button-group.vue b/components/button/demo/button-group.vue index 23a750b0d..8cfffbce1 100644 --- a/components/button/demo/button-group.vue +++ b/components/button/demo/button-group.vue @@ -34,7 +34,6 @@ Debug usage M R -

    With Icon

    @@ -56,23 +55,17 @@ Debug usage
    - - diff --git a/components/carousel/demo/basic.vue b/components/carousel/demo/basic.vue index 78dbddb04..04f86fc36 100644 --- a/components/carousel/demo/basic.vue +++ b/components/carousel/demo/basic.vue @@ -24,24 +24,16 @@ Basic usage.

    4

    - + diff --git a/components/carousel/demo/customArrows.vue b/components/carousel/demo/customArrows.vue index 7e224cbaa..2054458ca 100644 --- a/components/carousel/demo/customArrows.vue +++ b/components/carousel/demo/customArrows.vue @@ -34,19 +34,14 @@ Custom arrows display

    4

    - + diff --git a/components/carousel/demo/customPaging.vue b/components/carousel/demo/customPaging.vue index 7b9b4ebdd..f6a6f69ea 100644 --- a/components/carousel/demo/customPaging.vue +++ b/components/carousel/demo/customPaging.vue @@ -46,33 +46,33 @@ export default defineComponent({ diff --git a/components/carousel/demo/fade.vue b/components/carousel/demo/fade.vue index bd5fce111..b4d3a3f54 100644 --- a/components/carousel/demo/fade.vue +++ b/components/carousel/demo/fade.vue @@ -24,9 +24,10 @@ Slides use fade for transition.

    4

    + diff --git a/components/carousel/demo/position.vue b/components/carousel/demo/position.vue index 215565fa7..51a32b267 100644 --- a/components/carousel/demo/position.vue +++ b/components/carousel/demo/position.vue @@ -29,21 +29,16 @@ There are 4 position options available.

    4

    - + diff --git a/components/carousel/index.en-US.md b/components/carousel/index.en-US.md index b0d3c8380..80df0d676 100644 --- a/components/carousel/index.en-US.md +++ b/components/carousel/index.en-US.md @@ -2,7 +2,8 @@ category: Components type: Data Display title: Carousel -cover: https://gw.alipayobjects.com/zos/antfincdn/%24C9tmj978R/Carousel.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*bPMSSqbaTMkAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*a-58QpYnqOsAAAAAAAAAAAAADrJ8AQ/original --- A carousel component. Scales with its container. diff --git a/components/carousel/index.tsx b/components/carousel/index.tsx index 8417d5c54..6166c3c25 100644 --- a/components/carousel/index.tsx +++ b/components/carousel/index.tsx @@ -1,11 +1,14 @@ -import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; +import type { CSSProperties, ExtractPropTypes } from 'vue'; import { ref, computed, watchEffect, defineComponent } from 'vue'; import PropTypes from '../_util/vue-types'; import warning from '../_util/warning'; import classNames from '../_util/classNames'; import SlickCarousel from '../vc-slick'; -import { withInstall } from '../_util/type'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import { withInstall, booleanType, functionType, stringType } from '../_util/type'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; + +// CSSINJS +import useStyle from './style'; export type SwipeDirection = 'left' | 'down' | 'right' | 'up' | string; @@ -24,49 +27,49 @@ export interface CarouselRef { // Carousel export const carouselProps = () => ({ - effect: String as PropType, - dots: { type: Boolean, default: true }, - vertical: { type: Boolean, default: undefined }, - autoplay: { type: Boolean, default: undefined }, + effect: stringType(), + dots: booleanType(true), + vertical: booleanType(), + autoplay: booleanType(), easing: String, - beforeChange: Function as PropType<(currentSlide: number, nextSlide: number) => void>, - afterChange: Function as PropType<(currentSlide: number) => void>, + beforeChange: functionType<(currentSlide: number, nextSlide: number) => void>(), + afterChange: functionType<(currentSlide: number) => void>(), // style: PropTypes.React.CSSProperties, prefixCls: String, - accessibility: { type: Boolean, default: undefined }, + accessibility: booleanType(), nextArrow: PropTypes.any, prevArrow: PropTypes.any, - pauseOnHover: { type: Boolean, default: undefined }, + pauseOnHover: booleanType(), // className: String, - adaptiveHeight: { type: Boolean, default: undefined }, - arrows: { type: Boolean, default: false }, + adaptiveHeight: booleanType(), + arrows: booleanType(false), autoplaySpeed: Number, - centerMode: { type: Boolean, default: undefined }, + centerMode: booleanType(), centerPadding: String, cssEase: String, dotsClass: String, - draggable: { type: Boolean, default: false }, - fade: { type: Boolean, default: undefined }, - focusOnSelect: { type: Boolean, default: undefined }, - infinite: { type: Boolean, default: undefined }, + draggable: booleanType(false), + fade: booleanType(), + focusOnSelect: booleanType(), + infinite: booleanType(), initialSlide: Number, - lazyLoad: String as PropType, - rtl: { type: Boolean, default: undefined }, + lazyLoad: stringType(), + rtl: booleanType(), slide: String, slidesToShow: Number, slidesToScroll: Number, speed: Number, - swipe: { type: Boolean, default: undefined }, - swipeToSlide: { type: Boolean, default: undefined }, - swipeEvent: Function as PropType<(swipeDirection: SwipeDirection) => void>, - touchMove: { type: Boolean, default: undefined }, + swipe: booleanType(), + swipeToSlide: booleanType(), + swipeEvent: functionType<(swipeDirection: SwipeDirection) => void>(), + touchMove: booleanType(), touchThreshold: Number, - variableWidth: { type: Boolean, default: undefined }, - useCSS: { type: Boolean, default: undefined }, + variableWidth: booleanType(), + useCSS: booleanType(), slickGoTo: Number, responsive: Array, - dotPosition: { type: String as PropType, default: undefined }, - verticalSwiping: { type: Boolean, default: false }, + dotPosition: stringType(), + verticalSwiping: booleanType(false), }); export type CarouselProps = Partial>>; const Carousel = defineComponent({ @@ -104,6 +107,10 @@ const Carousel = defineComponent({ ); }); const { prefixCls, direction } = useConfigInject('carousel', props); + + // style + const [wrapSSR, hashId] = useStyle(prefixCls); + const dotPosition = computed(() => { if (props.dotPosition) return props.dotPosition; if (props.vertical !== undefined) return props.vertical ? 'right' : 'bottom'; @@ -122,12 +129,16 @@ const Carousel = defineComponent({ const { dots, arrows, draggable, effect } = props; const { class: cls, style, ...restAttrs } = attrs; const fade = effect === 'fade' ? true : props.fade; - const className = classNames(prefixCls.value, { - [`${prefixCls.value}-rtl`]: direction.value === 'rtl', - [`${prefixCls.value}-vertical`]: vertical.value, - [`${cls}`]: !!cls, - }); - return ( + const className = classNames( + prefixCls.value, + { + [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [`${prefixCls.value}-vertical`]: vertical.value, + [`${cls}`]: !!cls, + }, + hashId.value, + ); + return wrapSSR(
    -
    +
    , ); }; }, diff --git a/components/carousel/index.zh-CN.md b/components/carousel/index.zh-CN.md index bb02f2be8..d40cc435e 100644 --- a/components/carousel/index.zh-CN.md +++ b/components/carousel/index.zh-CN.md @@ -3,7 +3,8 @@ category: Components type: 数据展示 title: Carousel subtitle: 走马灯 -cover: https://gw.alipayobjects.com/zos/antfincdn/%24C9tmj978R/Carousel.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*bPMSSqbaTMkAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*a-58QpYnqOsAAAAAAAAAAAAADrJ8AQ/original --- 旋转木马,一组轮播的区域。 diff --git a/components/carousel/style/index.less b/components/carousel/style/index.less deleted file mode 100644 index 031383b2b..000000000 --- a/components/carousel/style/index.less +++ /dev/null @@ -1,294 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@carousel-prefix-cls: ~'@{ant-prefix}-carousel'; - -.@{carousel-prefix-cls} { - .reset-component(); - - .slick-slider { - position: relative; - display: block; - box-sizing: border-box; - touch-action: pan-y; - -webkit-touch-callout: none; - -webkit-tap-highlight-color: transparent; - } - - .slick-list { - position: relative; - display: block; - margin: 0; - padding: 0; - overflow: hidden; - - &:focus { - outline: none; - } - - &.dragging { - cursor: pointer; - } - - .slick-slide { - pointer-events: none; - - // https://github.com/ant-design/ant-design/issues/23294 - input.@{ant-prefix}-radio-input, - input.@{ant-prefix}-checkbox-input { - visibility: hidden; - } - - &.slick-active { - pointer-events: auto; - - input.@{ant-prefix}-radio-input, - input.@{ant-prefix}-checkbox-input { - visibility: visible; - } - } - - // fix Carousel content height not match parent node - // when children is empty node - // https://github.com/ant-design/ant-design/issues/25878 - > div > div { - vertical-align: bottom; - } - } - } - - .slick-slider .slick-track, - .slick-slider .slick-list { - transform: translate3d(0, 0, 0); - touch-action: pan-y; - } - - .slick-track { - position: relative; - top: 0; - left: 0; - display: block; - - &::before, - &::after { - display: table; - content: ''; - } - - &::after { - clear: both; - } - - .slick-loading & { - visibility: hidden; - } - } - - .slick-slide { - display: none; - float: left; - height: 100%; - min-height: 1px; - - img { - display: block; - } - - &.slick-loading img { - display: none; - } - - &.dragging img { - pointer-events: none; - } - } - - .slick-initialized .slick-slide { - display: block; - } - - .slick-loading .slick-slide { - visibility: hidden; - } - - .slick-vertical .slick-slide { - display: block; - height: auto; - } - - .slick-arrow.slick-hidden { - display: none; - } - - // Arrows - .slick-prev, - .slick-next { - position: absolute; - top: 50%; - display: block; - width: 20px; - height: 20px; - margin-top: -10px; - padding: 0; - color: transparent; - font-size: 0; - line-height: 0; - background: transparent; - border: 0; - outline: none; - cursor: pointer; - - &:hover, - &:focus { - color: transparent; - background: transparent; - outline: none; - - &::before { - opacity: 1; - } - } - - &.slick-disabled::before { - opacity: 0.25; - } - } - - .slick-prev { - left: -25px; - - &::before { - content: '←'; - } - } - - .slick-next { - right: -25px; - - &::before { - content: '→'; - } - } - - // Dots - .slick-dots { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 15; - display: flex !important; - justify-content: center; - margin-right: 15%; - margin-left: 15%; - padding-left: 0; - list-style: none; - - &-bottom { - bottom: 12px; - } - - &-top { - top: 12px; - bottom: auto; - } - - li { - position: relative; - display: inline-block; - flex: 0 1 auto; - box-sizing: content-box; - width: @carousel-dot-width; - height: @carousel-dot-height; - margin: 0 2px; - margin-right: 3px; - margin-left: 3px; - padding: 0; - text-align: center; - text-indent: -999px; - vertical-align: top; - transition: all 0.5s; - - button { - display: block; - width: 100%; - height: @carousel-dot-height; - padding: 0; - color: transparent; - font-size: 0; - background: @component-background; - border: 0; - border-radius: 1px; - outline: none; - cursor: pointer; - opacity: 0.3; - transition: all 0.5s; - - &:hover, - &:focus { - opacity: 0.75; - } - } - - &.slick-active { - width: @carousel-dot-active-width; - - & button { - background: @component-background; - opacity: 1; - } - - &:hover, - &:focus { - opacity: 1; - } - } - } - } -} - -.@{ant-prefix}-carousel-vertical { - .slick-dots { - top: 50%; - bottom: auto; - flex-direction: column; - width: @carousel-dot-height; - height: auto; - margin: 0; - transform: translateY(-50%); - - &-left { - right: auto; - left: 12px; - } - - &-right { - right: 12px; - left: auto; - } - - li { - width: @carousel-dot-height; - height: @carousel-dot-width; - margin: 4px 2px; - vertical-align: baseline; - - button { - width: @carousel-dot-height; - height: @carousel-dot-width; - } - - &.slick-active { - width: @carousel-dot-height; - height: @carousel-dot-active-width; - - button { - width: @carousel-dot-height; - height: @carousel-dot-active-width; - } - } - } - } -} - -@import './rtl'; diff --git a/components/carousel/style/index.tsx b/components/carousel/style/index.tsx index 3a3ab0de5..06e29a3e9 100644 --- a/components/carousel/style/index.tsx +++ b/components/carousel/style/index.tsx @@ -1,2 +1,350 @@ -import '../../style/index.less'; -import './index.less'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { resetComponent } from '../../style'; + +export interface ComponentToken { + dotWidth: number; + dotHeight: number; + dotWidthActive: number; +} + +interface CarouselToken extends FullToken<'Carousel'> { + carouselArrowSize: number; + carouselDotOffset: number; + carouselDotInline: number; +} + +const genCarouselStyle: GenerateStyle = token => { + const { componentCls, antCls, carouselArrowSize, carouselDotOffset, marginXXS } = token; + const arrowOffset = -carouselArrowSize * 1.25; + + const carouselDotMargin = marginXXS; + + return { + [componentCls]: { + ...resetComponent(token), + + '.slick-slider': { + position: 'relative', + display: 'block', + boxSizing: 'border-box', + touchAction: 'pan-y', + WebkitTouchCallout: 'none', + WebkitTapHighlightColor: 'transparent', + + '.slick-track, .slick-list': { + transform: 'translate3d(0, 0, 0)', + touchAction: 'pan-y', + }, + }, + + '.slick-list': { + position: 'relative', + display: 'block', + margin: 0, + padding: 0, + overflow: 'hidden', + + '&:focus': { + outline: 'none', + }, + + '&.dragging': { + cursor: 'pointer', + }, + + '.slick-slide': { + pointerEvents: 'none', + + // https://github.com/ant-design/ant-design/issues/23294 + [`input${antCls}-radio-input, input${antCls}-checkbox-input`]: { + visibility: 'hidden', + }, + + '&.slick-active': { + pointerEvents: 'auto', + + [`input${antCls}-radio-input, input${antCls}-checkbox-input`]: { + visibility: 'visible', + }, + }, + + // fix Carousel content height not match parent node + // when children is empty node + // https://github.com/ant-design/ant-design/issues/25878 + '> div > div': { + verticalAlign: 'bottom', + }, + }, + }, + + '.slick-track': { + position: 'relative', + top: 0, + insetInlineStart: 0, + display: 'block', + + '&::before, &::after': { + display: 'table', + content: '""', + }, + + '&::after': { + clear: 'both', + }, + }, + + '.slick-slide': { + display: 'none', + float: 'left', + height: '100%', + minHeight: 1, + + img: { + display: 'block', + }, + + '&.dragging img': { + pointerEvents: 'none', + }, + }, + + '.slick-initialized .slick-slide': { + display: 'block', + }, + + '.slick-vertical .slick-slide': { + display: 'block', + height: 'auto', + }, + + '.slick-arrow.slick-hidden': { + display: 'none', + }, + + // Arrows + '.slick-prev, .slick-next': { + position: 'absolute', + top: '50%', + display: 'block', + width: carouselArrowSize, + height: carouselArrowSize, + marginTop: -carouselArrowSize / 2, + padding: 0, + color: 'transparent', + fontSize: 0, + lineHeight: 0, + background: 'transparent', + border: 0, + outline: 'none', + cursor: 'pointer', + + '&:hover, &:focus': { + color: 'transparent', + background: 'transparent', + outline: 'none', + + '&::before': { + opacity: 1, + }, + }, + + '&.slick-disabled::before': { + opacity: 0.25, + }, + }, + + '.slick-prev': { + insetInlineStart: arrowOffset, + + '&::before': { + content: '"←"', + }, + }, + + '.slick-next': { + insetInlineEnd: arrowOffset, + + '&::before': { + content: '"→"', + }, + }, + + // Dots + '.slick-dots': { + position: 'absolute', + insetInlineEnd: 0, + bottom: 0, + insetInlineStart: 0, + zIndex: 15, + display: 'flex !important', + justifyContent: 'center', + paddingInlineStart: 0, + listStyle: 'none', + + '&-bottom': { + bottom: carouselDotOffset, + }, + + '&-top': { + top: carouselDotOffset, + bottom: 'auto', + }, + + li: { + position: 'relative', + display: 'inline-block', + flex: '0 1 auto', + boxSizing: 'content-box', + width: token.dotWidth, + height: token.dotHeight, + marginInline: carouselDotMargin, + padding: 0, + textAlign: 'center', + textIndent: -999, + verticalAlign: 'top', + transition: `all ${token.motionDurationSlow}`, + + button: { + position: 'relative', + display: 'block', + width: '100%', + height: token.dotHeight, + padding: 0, + color: 'transparent', + fontSize: 0, + background: token.colorBgContainer, + border: 0, + borderRadius: 1, + outline: 'none', + cursor: 'pointer', + opacity: 0.3, + transition: `all ${token.motionDurationSlow}`, + + '&: hover, &:focus': { + opacity: 0.75, + }, + + '&::after': { + position: 'absolute', + inset: -carouselDotMargin, + content: '""', + }, + }, + + '&.slick-active': { + width: token.dotWidthActive, + + '& button': { + background: token.colorBgContainer, + opacity: 1, + }, + + '&: hover, &:focus': { + opacity: 1, + }, + }, + }, + }, + }, + }; +}; + +const genCarouselVerticalStyle: GenerateStyle = token => { + const { componentCls, carouselDotOffset, marginXXS } = token; + + const reverseSizeOfDot = { + width: token.dotHeight, + height: token.dotWidth, + }; + + return { + [`${componentCls}-vertical`]: { + '.slick-dots': { + top: '50%', + bottom: 'auto', + flexDirection: 'column', + width: token.dotHeight, + height: 'auto', + margin: 0, + transform: 'translateY(-50%)', + + '&-left': { + insetInlineEnd: 'auto', + insetInlineStart: carouselDotOffset, + }, + + '&-right': { + insetInlineEnd: carouselDotOffset, + insetInlineStart: 'auto', + }, + + li: { + // reverse width and height in vertical situation + ...reverseSizeOfDot, + margin: `${marginXXS}px 0`, + verticalAlign: 'baseline', + + button: reverseSizeOfDot, + + '&.slick-active': { + ...reverseSizeOfDot, + + button: reverseSizeOfDot, + }, + }, + }, + }, + }; +}; + +const genCarouselRtlStyle: GenerateStyle = token => { + const { componentCls } = token; + + return [ + { + [`${componentCls}-rtl`]: { + direction: 'rtl', + + // Dots + '.slick-dots': { + [`${componentCls}-rtl&`]: { + flexDirection: 'row-reverse', + }, + }, + }, + }, + { + [`${componentCls}-vertical`]: { + '.slick-dots': { + [`${componentCls}-rtl&`]: { + flexDirection: 'column', + }, + }, + }, + }, + ]; +}; + +// ============================== Export ============================== +export default genComponentStyleHook( + 'Carousel', + token => { + const { controlHeightLG, controlHeightSM } = token; + const carouselToken = mergeToken(token, { + carouselArrowSize: controlHeightLG / 2, + carouselDotOffset: controlHeightSM / 2, + }); + + return [ + genCarouselStyle(carouselToken), + genCarouselVerticalStyle(carouselToken), + genCarouselRtlStyle(carouselToken), + ]; + }, + { + dotWidth: 16, + dotHeight: 3, + dotWidthActive: 24, + }, +); diff --git a/components/carousel/style/rtl.less b/components/carousel/style/rtl.less deleted file mode 100644 index c2853a2ac..000000000 --- a/components/carousel/style/rtl.less +++ /dev/null @@ -1,54 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@carousel-prefix-cls: ~'@{ant-prefix}-carousel'; - -.@{carousel-prefix-cls} { - &-rtl { - direction: rtl; - } - - .slick-track { - .@{carousel-prefix-cls}-rtl & { - right: 0; - left: auto; - } - } - - .slick-prev { - .@{carousel-prefix-cls}-rtl & { - right: -25px; - left: auto; - - &::before { - content: '→'; - } - } - } - - .slick-next { - .@{carousel-prefix-cls}-rtl & { - right: auto; - left: -25px; - - &::before { - content: '←'; - } - } - } - - // Dots - .slick-dots { - .@{carousel-prefix-cls}-rtl& { - flex-direction: row-reverse; - } - } -} - -.@{ant-prefix}-carousel-vertical { - .slick-dots { - .@{carousel-prefix-cls}-rtl& { - flex-direction: column; - } - } -} diff --git a/components/cascader/__tests__/__snapshots__/demo.test.js.snap b/components/cascader/__tests__/__snapshots__/demo.test.js.snap index 9cee6bb7b..c3718f2a7 100644 --- a/components/cascader/__tests__/__snapshots__/demo.test.js.snap +++ b/components/cascader/__tests__/__snapshots__/demo.test.js.snap @@ -6,7 +6,8 @@ exports[`renders ./components/cascader/demo/basic.vue correctly 1`] = `
    Please select -
    +
    `; @@ -17,7 +18,8 @@ exports[`renders ./components/cascader/demo/change-on-select.vue correctly 1`] =
    Please select -
    +
    `; @@ -28,7 +30,8 @@ exports[`renders ./components/cascader/demo/custom-render.vue correctly 1`] = `
    Zhejiang /Hangzhou /West Lake ( 752100 ) -
    +
    `; @@ -40,7 +43,8 @@ exports[`renders ./components/cascader/demo/disabled-option.vue correctly 1`] =
    Please select -
    +
    `; @@ -51,7 +55,8 @@ exports[`renders ./components/cascader/demo/fields-name.vue correctly 1`] = `
    Please select -
    +
    `; @@ -62,7 +67,8 @@ exports[`renders ./components/cascader/demo/hover.vue correctly 1`] = `
    Please select -
    +
    `; @@ -73,25 +79,57 @@ exports[`renders ./components/cascader/demo/lazy.vue correctly 1`] = `
    Please select -
    +
    `; exports[`renders ./components/cascader/demo/multiple.vue correctly 1`] = ` -
    - +
    +
    +

    Cascader.SHOW_PARENT

    +
    -
    -
    +
    +
    -
    - + +
    +
    + +
    + +
    + +
    Please select
    -
    Please select + +
    +
    +

    Cascader.SHOW_CHILD

    +
    + +
    +
    + + +
    +
    + +
    + +
    + +
    Please select +
    + + +
    +
    `; @@ -102,7 +140,8 @@ exports[`renders ./components/cascader/demo/search.vue correctly 1`] = `
    Please select -
    +
    `; @@ -113,7 +152,8 @@ exports[`renders ./components/cascader/demo/size.vue correctly 1`] = `
    Please select -
    +

    @@ -123,7 +163,8 @@ exports[`renders ./components/cascader/demo/size.vue correctly 1`] = `
    Please select -
    +

    @@ -133,7 +174,8 @@ exports[`renders ./components/cascader/demo/size.vue correctly 1`] = `
    Please select -
    +

    @@ -148,7 +190,8 @@ exports[`renders ./components/cascader/demo/suffix.vue correctly 1`] = `
    Please select -
    +
    @@ -159,7 +202,7 @@ exports[`renders ./components/cascader/demo/suffix.vue correctly 1`] = `
    Please select -
    +
    diff --git a/components/cascader/demo/basic.vue b/components/cascader/demo/basic.vue index 1f1f77ff5..8ff8a4589 100644 --- a/components/cascader/demo/basic.vue +++ b/components/cascader/demo/basic.vue @@ -18,8 +18,8 @@ Cascade selection box for selecting province/city/district. - diff --git a/components/cascader/demo/change-on-select.vue b/components/cascader/demo/change-on-select.vue index 2cfd5ad57..8d250951e 100644 --- a/components/cascader/demo/change-on-select.vue +++ b/components/cascader/demo/change-on-select.vue @@ -23,8 +23,9 @@ Allow only select parent options. change-on-select /> - diff --git a/components/cascader/demo/custom-render.vue b/components/cascader/demo/custom-render.vue index 8a52a0d00..b19fd7be1 100644 --- a/components/cascader/demo/custom-render.vue +++ b/components/cascader/demo/custom-render.vue @@ -36,8 +36,9 @@ For instance, add an external link after the selected value. - diff --git a/components/cascader/demo/custom-trigger.vue b/components/cascader/demo/custom-trigger.vue index 06220cc43..13aafa44e 100644 --- a/components/cascader/demo/custom-trigger.vue +++ b/components/cascader/demo/custom-trigger.vue @@ -28,8 +28,8 @@ Separate trigger button and result. - diff --git a/components/cascader/demo/disabled-option.vue b/components/cascader/demo/disabled-option.vue index cfe603238..710760001 100644 --- a/components/cascader/demo/disabled-option.vue +++ b/components/cascader/demo/disabled-option.vue @@ -18,8 +18,8 @@ Disable option by specifying the `disabled` property in `options`. - diff --git a/components/cascader/demo/fields-name.vue b/components/cascader/demo/fields-name.vue index 7b3ffaa67..63c4d1f53 100644 --- a/components/cascader/demo/fields-name.vue +++ b/components/cascader/demo/fields-name.vue @@ -23,8 +23,8 @@ Custom Field Names placeholder="Please select" /> - diff --git a/components/cascader/demo/hover.vue b/components/cascader/demo/hover.vue index 2e59ed0b6..452e312e7 100644 --- a/components/cascader/demo/hover.vue +++ b/components/cascader/demo/hover.vue @@ -23,8 +23,8 @@ Hover to expand sub menu, click to select option. placeholder="Please select" /> - diff --git a/components/cascader/demo/lazy.vue b/components/cascader/demo/lazy.vue index daa36e205..c2dd7d4a5 100644 --- a/components/cascader/demo/lazy.vue +++ b/components/cascader/demo/lazy.vue @@ -26,51 +26,43 @@ Load options lazily with `loadData`. change-on-select /> - diff --git a/components/cascader/demo/multiple.vue b/components/cascader/demo/multiple.vue index eb1f82c54..816415568 100644 --- a/components/cascader/demo/multiple.vue +++ b/components/cascader/demo/multiple.vue @@ -16,18 +16,32 @@ title: Select multiple options - diff --git a/components/cascader/demo/search.vue b/components/cascader/demo/search.vue index 778a3c426..4832ef409 100644 --- a/components/cascader/demo/search.vue +++ b/components/cascader/demo/search.vue @@ -25,8 +25,8 @@ Search and select options directly. placeholder="Please select" /> - diff --git a/components/cascader/demo/size.vue b/components/cascader/demo/size.vue index e72b69a74..ca4db878a 100644 --- a/components/cascader/demo/size.vue +++ b/components/cascader/demo/size.vue @@ -26,8 +26,8 @@ Cascade selection box of different sizes.

    - diff --git a/components/cascader/demo/suffix.vue b/components/cascader/demo/suffix.vue index e486dd870..f166ab2cd 100644 --- a/components/cascader/demo/suffix.vue +++ b/components/cascader/demo/suffix.vue @@ -34,9 +34,9 @@ Custom suffix icon /> - diff --git a/components/cascader/demo/tagRender.vue b/components/cascader/demo/tagRender.vue index 3052c3445..e2c32f876 100644 --- a/components/cascader/demo/tagRender.vue +++ b/components/cascader/demo/tagRender.vue @@ -21,8 +21,8 @@ title: - diff --git a/components/cascader/index.en-US.md b/components/cascader/index.en-US.md index 25a3415cc..9ac50cd4a 100644 --- a/components/cascader/index.en-US.md +++ b/components/cascader/index.en-US.md @@ -2,7 +2,8 @@ category: Components type: Data Entry title: Cascader -cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*tokLTp73TsQAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5-ArSLl5UBsAAAAAAAAAAAAADrJ8AQ/original --- Cascade selection box. @@ -28,7 +29,7 @@ Cascade selection box. | changeOnSelect | (Work on single select) change value on each selection if set to true, see above demo for details | boolean | false | | | disabled | whether disabled select | boolean | false | | | displayRender | render function of displaying selected options, you can use #displayRender="{labels, selectedOptions}". | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` | | -| dropdownClassName | additional className of popup overlay | string | - | 3.0 | +| popupClassName | additional className of popup overlay | string | - | 4.0 | | dropdownStyle | additional style of popup overlay | CSSProperties | {} | 3.0 | | expandIcon | Customize the current item expand icon | slot | - | 3.0 | | expandTrigger | expand current item when click or hover | `click` \| `hover` | 'click' | | @@ -47,7 +48,9 @@ Cascade selection box. | searchValue | Set search value,Need work with `showSearch` | string | - | 3.0 | | showSearch | Whether show search input in single mode. | boolean \| [object](#showsearch) | false | | | size | input size | `large` \| `default` \| `small` | `default` | | +| status | Set validation status | 'error' \| 'warning' | - | 3.3.0 | | suffixIcon | The custom suffix icon | string \| VNode \| slot | - | | +| showCheckedStrategy | The way show selected item in box. ** `SHOW_CHILD`: ** just show child treeNode. **`Cascader.SHOW_PARENT`:** just show parent treeNode (when all child treeNode under the parent treeNode are checked) | `Cascader.SHOW_PARENT` \| `Cascader.SHOW_CHILD` | `Cascader.SHOW_PARENT` | 3.3.0 | | tagRender | Customize tag render when `multiple` | slot | - | 3.0 | | value(v-model) | selected value | string\[] \| number\[] | - | | diff --git a/components/cascader/index.tsx b/components/cascader/index.tsx index 9328736c8..e79a675cf 100644 --- a/components/cascader/index.tsx +++ b/components/cascader/index.tsx @@ -1,5 +1,9 @@ import type { ShowSearchType, FieldNames, BaseOptionType, DefaultOptionType } from '../vc-cascader'; -import VcCascader, { cascaderProps as vcCascaderProps } from '../vc-cascader'; +import VcCascader, { + cascaderProps as vcCascaderProps, + SHOW_CHILD, + SHOW_PARENT, +} from '../vc-cascader'; import RightOutlined from '@ant-design/icons-vue/RightOutlined'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; import LeftOutlined from '@ant-design/icons-vue/LeftOutlined'; @@ -11,7 +15,7 @@ import { computed, defineComponent, ref, watchEffect } from 'vue'; import type { ExtractPropTypes, PropType } from 'vue'; import PropTypes from '../_util/vue-types'; import { initDefaultProps } from '../_util/props-util'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; import classNames from '../_util/classNames'; import type { SizeType } from '../config-provider'; import devWarning from '../vc-util/devWarning'; @@ -19,7 +23,14 @@ import type { SelectCommonPlacement } from '../_util/transition'; import { getTransitionDirection, getTransitionName } from '../_util/transition'; import { useInjectFormItemContext } from '../form'; import type { ValueType } from '../vc-cascader/Cascader'; +import type { InputStatus } from '../_util/statusUtils'; +import { getStatusClassNames, getMergedStatus } from '../_util/statusUtils'; +import { FormItemInputContext } from '../form/FormItemContext'; +import { useCompactItemContext } from '../space/Compact'; +import useSelectStyle from '../select/style'; +import useStyle from './style'; +import { useInjectDisabled } from '../config-provider/DisabledContext'; // Align the design since we use `rc-select` in root. This help: // - List search content will show all content // - Hover opacity style @@ -31,7 +42,7 @@ export type FieldNamesType = FieldNames; export type FilledFieldNamesType = Required; -function highlightKeyword(str: string, lowerKeyword: string, prefixCls: string | undefined) { +function highlightKeyword(str: string, lowerKeyword: string, prefixCls?: string) { const cells = str .toLowerCase() .split(lowerKeyword) @@ -99,7 +110,11 @@ export function cascaderProps }, suffixIcon: PropTypes.any, + status: String as PropType, options: Array as PropType, + popupClassName: String, + /** @deprecated Please use `popupClassName` instead */ + dropdownClassName: String, 'onUpdate:value': Function as PropType<(value: ValueType) => void>, }; } @@ -121,7 +136,17 @@ const Cascader = defineComponent({ allowClear: true, }), setup(props, { attrs, expose, slots, emit }) { + // ====================== Warning ====================== + if (process.env.NODE_ENV !== 'production') { + devWarning( + !props.dropdownClassName, + 'Cascader', + '`dropdownClassName` is deprecated. Please use `popupClassName` instead.', + ); + } const formItemContext = useInjectFormItemContext(); + const formItemInputContext = FormItemInputContext.useInject(); + const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status)); const { prefixCls: cascaderPrefixCls, rootPrefixCls, @@ -129,9 +154,18 @@ const Cascader = defineComponent({ direction, getPopupContainer, renderEmpty, - size, + size: contextSize, + disabled, } = useConfigInject('cascader', props); const prefixCls = computed(() => getPrefixCls('select', props.prefixCls)); + const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); + const mergedSize = computed(() => compactSize.value || contextSize.value); + const contextDisabled = useInjectDisabled(); + const mergedDisabled = computed(() => disabled.value ?? contextDisabled.value); + + const [wrapSelectSSR, hashId] = useSelectStyle(prefixCls); + const [wrapCascaderSSR] = useStyle(cascaderPrefixCls); + const isRtl = computed(() => direction.value === 'rtl'); // =================== Warning ===================== if (process.env.NODE_ENV !== 'production') { @@ -166,11 +200,12 @@ const Cascader = defineComponent({ // =================== Dropdown ==================== const mergedDropdownClassName = computed(() => classNames( - props.dropdownClassName || props.popupClassName, + props.popupClassName || props.dropdownClassName, `${cascaderPrefixCls.value}-dropdown`, { [`${cascaderPrefixCls.value}-dropdown-rtl`]: isRtl.value, }, + hashId.value, ), ); @@ -217,7 +252,7 @@ const Cascader = defineComponent({ ...restProps } = props; // =================== No Found ==================== - const mergedNotFoundContent = notFoundContent || renderEmpty.value('Cascader'); + const mergedNotFoundContent = notFoundContent || renderEmpty('Cascader'); // ===================== Icon ====================== let mergedExpandIcon = expandIcon; @@ -235,64 +270,86 @@ const Cascader = defineComponent({ const { suffixIcon, removeIcon, clearIcon } = getIcons( { ...props, + hasFeedback: formItemInputContext.hasFeedback, + feedbackIcon: formItemInputContext.feedbackIcon, multiple, prefixCls: prefixCls.value, showArrow: mergedShowArrow.value, }, slots, ); - return ( - , - }} - tagRender={props.tagRender || slots.tagRender} - displayRender={props.displayRender || slots.displayRender} - maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder} - showArrow={props.showArrow} - onChange={handleChange} - onBlur={handleBlur} - v-slots={slots} - ref={selectRef} - /> + return wrapCascaderSSR( + wrapSelectSSR( + , + }} + tagRender={props.tagRender || slots.tagRender} + displayRender={props.displayRender || slots.displayRender} + maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder} + showArrow={formItemInputContext.hasFeedback || props.showArrow} + onChange={handleChange} + onBlur={handleBlur} + v-slots={slots} + ref={selectRef} + />, + ), ); }; }, }); - -export default withInstall(Cascader); +export default withInstall< + typeof Cascader & { + SHOW_PARENT: typeof SHOW_PARENT; + SHOW_CHILD: typeof SHOW_CHILD; + } +>( + Object.assign(Cascader, { + SHOW_CHILD, + SHOW_PARENT, + } as any), +); diff --git a/components/cascader/index.zh-CN.md b/components/cascader/index.zh-CN.md index d8363fd23..731a51ac6 100644 --- a/components/cascader/index.zh-CN.md +++ b/components/cascader/index.zh-CN.md @@ -3,7 +3,8 @@ category: Components type: 数据录入 title: Cascader subtitle: 级联选择 -cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*tokLTp73TsQAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5-ArSLl5UBsAAAAAAAAAAAAADrJ8AQ/original --- 级联选择框。 @@ -30,7 +31,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg | defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | | | disabled | 禁用 | boolean | false | | | displayRender | 选择后展示的渲染函数,可使用 #displayRender="{labels, selectedOptions}" | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` | | -| dropdownClassName | 自定义浮层类名 | string | - | 3.0 | +| popupClassName | 自定义浮层类名 | string | - | 4.0 | | dropdownStyle | 自定义浮层样式 | CSSProperties | {} | 3.0 | | expandIcon | 自定义次级菜单展开图标 | slot | - | 3.0 | | expandTrigger | 次级菜单的展开方式 | `click` \| `hover` | 'click' | | @@ -45,9 +46,11 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg | options | 可选项数据源 | [Option](#option)\[] | - | | | placeholder | 输入框占位文本 | string | '请选择' | | | placement | 浮层预设位置 | `bottomLeft` \| `bottomRight` \| `topLeft` \| `topRight` | `bottomLeft` | 3.0 | +| showCheckedStrategy | 定义选中项回填的方式。`Cascader.SHOW_CHILD`: 只显示选中的子节点。`Cascader.SHOW_PARENT`: 只显示父节点(当父节点下所有子节点都选中时)。 | `Cascader.SHOW_PARENT` \| `Cascader.SHOW_CHILD` | `Cascader.SHOW_PARENT` | 3.3.0 | | removeIcon | 自定义的多选框清除图标 | slot | - | 3.2 | | searchValue | 设置搜索的值,需要与 `showSearch` 配合使用 | string | - | 3.0 | | showSearch | 在选择框中显示搜索框 | boolean \| [object](#showsearch) | false | | +| status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 | | size | 输入框大小 | `large` \| `default` \| `small` | `default` | | | suffixIcon | 自定义的选择框后缀图标 | string \| VNode \| slot | - | | | tagRender | 自定义 tag 内容,多选时生效 | slot | - | 3.0 | diff --git a/components/cascader/style/index.less b/components/cascader/style/index.less deleted file mode 100644 index 8df1d85fc..000000000 --- a/components/cascader/style/index.less +++ /dev/null @@ -1,104 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; -@import '../../input/style/mixin'; -@import '../../checkbox/style/mixin'; - -@cascader-prefix-cls: ~'@{ant-prefix}-cascader'; - -.antCheckboxFn(@checkbox-prefix-cls: ~'@{cascader-prefix-cls}-checkbox'); - -.@{cascader-prefix-cls} { - width: 184px; - - &-checkbox { - top: 0; - margin-right: @padding-xs; - } - - &-menus { - display: flex; - flex-wrap: nowrap; - align-items: flex-start; - - &.@{cascader-prefix-cls}-menu-empty { - .@{cascader-prefix-cls}-menu { - width: 100%; - height: auto; - } - } - } - - &-menu { - min-width: 111px; - height: 180px; - margin: 0; - margin: -@dropdown-edge-child-vertical-padding 0; - padding: @cascader-dropdown-edge-child-vertical-padding 0; - overflow: auto; - vertical-align: top; - list-style: none; - border-right: @border-width-base @border-style-base @cascader-menu-border-color-split; - -ms-overflow-style: -ms-autohiding-scrollbar; // https://github.com/ant-design/ant-design/issues/11857 - - &-item { - display: flex; - flex-wrap: nowrap; - align-items: center; - padding: @cascader-dropdown-vertical-padding @control-padding-horizontal; - overflow: hidden; - line-height: @cascader-dropdown-line-height; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - transition: all 0.3s; - - &:hover { - background: @item-hover-bg; - } - - &-disabled { - color: @disabled-color; - cursor: not-allowed; - - &:hover { - background: transparent; - } - } - - .@{cascader-prefix-cls}-menu-empty & { - color: @disabled-color; - cursor: default; - pointer-events: none; - } - - &-active:not(&-disabled) { - &, - &:hover { - font-weight: @select-item-selected-font-weight; - background-color: @cascader-item-selected-bg; - } - } - - &-content { - flex: auto; - } - - &-expand &-expand-icon, - &-loading-icon { - margin-left: @padding-xss; - color: @text-color-secondary; - font-size: 10px; - - .@{cascader-prefix-cls}-menu-item-disabled& { - color: @disabled-color; - } - } - - &-keyword { - color: @highlight-color; - } - } - } -} - -@import './rtl'; diff --git a/components/cascader/style/index.ts b/components/cascader/style/index.ts new file mode 100644 index 000000000..07e6417ae --- /dev/null +++ b/components/cascader/style/index.ts @@ -0,0 +1,165 @@ +import { getStyle as getCheckboxStyle } from '../../checkbox/style'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook } from '../../theme/internal'; +import { textEllipsis } from '../../style'; +import { genCompactItemStyle } from '../../style/compact-item'; + +export interface ComponentToken { + controlWidth: number; + controlItemWidth: number; + dropdownHeight: number; +} + +type CascaderToken = FullToken<'Cascader'>; + +// =============================== Base =============================== +const genBaseStyle: GenerateStyle = token => { + const { prefixCls, componentCls, antCls } = token; + const cascaderMenuItemCls = `${componentCls}-menu-item`; + const iconCls = ` + &${cascaderMenuItemCls}-expand ${cascaderMenuItemCls}-expand-icon, + ${cascaderMenuItemCls}-loading-icon + `; + + const itemPaddingVertical = Math.round( + (token.controlHeight - token.fontSize * token.lineHeight) / 2, + ); + + return [ + // ===================================================== + // == Control == + // ===================================================== + { + [componentCls]: { + width: token.controlWidth, + }, + }, + // ===================================================== + // == Popup == + // ===================================================== + { + [`${componentCls}-dropdown`]: [ + // ==================== Checkbox ==================== + getCheckboxStyle(`${prefixCls}-checkbox`, token), + { + [`&${antCls}-select-dropdown`]: { + padding: 0, + }, + }, + { + [componentCls]: { + // ================== Checkbox ================== + '&-checkbox': { + top: 0, + marginInlineEnd: token.paddingXS, + }, + + // ==================== Menu ==================== + // >>> Menus + '&-menus': { + display: 'flex', + flexWrap: 'nowrap', + alignItems: 'flex-start', + + [`&${componentCls}-menu-empty`]: { + [`${componentCls}-menu`]: { + width: '100%', + height: 'auto', + + [cascaderMenuItemCls]: { + color: token.colorTextDisabled, + }, + }, + }, + }, + + // >>> Menu + '&-menu': { + flexGrow: 1, + minWidth: token.controlItemWidth, + height: token.dropdownHeight, + margin: 0, + padding: token.paddingXXS, + overflow: 'auto', + verticalAlign: 'top', + listStyle: 'none', + '-ms-overflow-style': '-ms-autohiding-scrollbar', // https://github.com/ant-design/ant-design/issues/11857 + + '&:not(:last-child)': { + borderInlineEnd: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`, + }, + + '&-item': { + ...textEllipsis, + display: 'flex', + flexWrap: 'nowrap', + alignItems: 'center', + padding: `${itemPaddingVertical}px ${token.paddingSM}px`, + lineHeight: token.lineHeight, + cursor: 'pointer', + transition: `all ${token.motionDurationMid}`, + borderRadius: token.borderRadiusSM, + + '&:hover': { + background: token.controlItemBgHover, + }, + '&-disabled': { + color: token.colorTextDisabled, + cursor: 'not-allowed', + + '&:hover': { + background: 'transparent', + }, + + [iconCls]: { + color: token.colorTextDisabled, + }, + }, + + [`&-active:not(${cascaderMenuItemCls}-disabled)`]: { + [`&, &:hover`]: { + fontWeight: token.fontWeightStrong, + backgroundColor: token.controlItemBgActive, + }, + }, + + '&-content': { + flex: 'auto', + }, + + [iconCls]: { + marginInlineStart: token.paddingXXS, + color: token.colorTextDescription, + fontSize: token.fontSizeIcon, + }, + + '&-keyword': { + color: token.colorHighlight, + }, + }, + }, + }, + }, + ], + }, + // ===================================================== + // == RTL == + // ===================================================== + { + [`${componentCls}-dropdown-rtl`]: { + direction: 'rtl', + }, + }, + // ===================================================== + // == Space Compact == + // ===================================================== + genCompactItemStyle(token), + ]; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('Cascader', token => [genBaseStyle(token)], { + controlWidth: 184, + controlItemWidth: 111, + dropdownHeight: 180, +}); diff --git a/components/cascader/style/index.tsx b/components/cascader/style/index.tsx deleted file mode 100644 index e07deeea0..000000000 --- a/components/cascader/style/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import '../../style/index.less'; -import './index.less'; - -// style dependencies -import '../../empty/style'; -import '../../select/style'; diff --git a/components/cascader/style/rtl.less b/components/cascader/style/rtl.less deleted file mode 100644 index c70bf1dd3..000000000 --- a/components/cascader/style/rtl.less +++ /dev/null @@ -1,19 +0,0 @@ -// We can not import reference of `./index` directly since it will make dead loop in less -@import (reference) '../../style/themes/index'; -@cascader-prefix-cls: ~'@{ant-prefix}-cascader'; - -.@{cascader-prefix-cls}-rtl { - .@{cascader-prefix-cls}-menu-item { - &-expand-icon, - &-loading-icon { - margin-right: @padding-xss; - margin-left: 0; - } - } - - .@{cascader-prefix-cls}-checkbox { - top: 0; - margin-right: 0; - margin-left: @padding-xs; - } -} diff --git a/components/checkbox/Checkbox.tsx b/components/checkbox/Checkbox.tsx index 3c6f62f18..a52109a5c 100644 --- a/components/checkbox/Checkbox.tsx +++ b/components/checkbox/Checkbox.tsx @@ -1,16 +1,27 @@ import type { CSSProperties } from 'vue'; -import { watchEffect, onMounted, defineComponent, inject, onBeforeUnmount, ref } from 'vue'; +import { + computed, + watchEffect, + onMounted, + defineComponent, + inject, + onBeforeUnmount, + ref, +} from 'vue'; import classNames from '../_util/classNames'; import VcCheckbox from '../vc-checkbox/Checkbox'; import { flattenChildren } from '../_util/props-util'; import warning from '../_util/warning'; import type { EventHandler } from '../_util/EventInterface'; -import { useInjectFormItemContext } from '../form/FormItemContext'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItemContext'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; import type { CheckboxChangeEvent, CheckboxProps } from './interface'; import { CheckboxGroupContextKey, checkboxProps } from './interface'; +// CSSINJS +import useStyle from './style'; + export default defineComponent({ compatConfig: { MODE: 3 }, name: 'ACheckbox', @@ -20,10 +31,16 @@ export default defineComponent({ // emits: ['change', 'update:checked'], setup(props, { emit, attrs, slots, expose }) { const formItemContext = useInjectFormItemContext(); - const { prefixCls, direction } = useConfigInject('checkbox', props); + const formItemInputContext = FormItemInputContext.useInject(); + const { prefixCls, direction, disabled } = useConfigInject('checkbox', props); + // style + const [wrapSSR, hashId] = useStyle(prefixCls); + const checkboxGroup = inject(CheckboxGroupContextKey, undefined); const uniId = Symbol('checkboxUniId'); - + const mergedDisabled = computed(() => { + return checkboxGroup?.disabled.value || disabled.value; + }); watchEffect(() => { if (!props.skipGroup && checkboxGroup) { checkboxGroup.registerValue(uniId, props.value); @@ -36,7 +53,7 @@ export default defineComponent({ }); onMounted(() => { warning( - props.checked !== undefined || checkboxGroup || props.value === undefined, + !!(props.checked !== undefined || checkboxGroup || props.value === undefined), 'Checkbox', '`value` is not validate prop, do you mean `checked`?', ); @@ -67,6 +84,7 @@ export default defineComponent({ id, prefixCls: prefixCls.value, ...restAttrs, + disabled: mergedDisabled.value, }; if (checkboxGroup && !skipGroup) { checkboxProps.onChange = (...args) => { @@ -74,8 +92,8 @@ export default defineComponent({ checkboxGroup.toggleOption({ label: children, value: props.value }); }; checkboxProps.name = checkboxGroup.name.value; - checkboxProps.checked = checkboxGroup.mergedValue.value.indexOf(props.value) !== -1; - checkboxProps.disabled = props.disabled || checkboxGroup.disabled.value; + checkboxProps.checked = checkboxGroup.mergedValue.value.includes(props.value); + checkboxProps.disabled = mergedDisabled.value || checkboxGroup.disabled.value; checkboxProps.indeterminate = indeterminate; } else { checkboxProps.onChange = handleChange; @@ -86,22 +104,34 @@ export default defineComponent({ [`${prefixCls.value}-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-wrapper-checked`]: checkboxProps.checked, [`${prefixCls.value}-wrapper-disabled`]: checkboxProps.disabled, + [`${prefixCls.value}-wrapper-in-form-item`]: formItemInputContext.isFormItemInput, }, className, + hashId.value, + ); + const checkboxClass = classNames( + { + [`${prefixCls.value}-indeterminate`]: indeterminate, + }, + hashId.value, ); - const checkboxClass = classNames({ - [`${prefixCls.value}-indeterminate`]: indeterminate, - }); - return ( + const ariaChecked = indeterminate ? 'mixed' : undefined; + return wrapSSR( + , ); }; }, diff --git a/components/checkbox/Group.tsx b/components/checkbox/Group.tsx index d096fd8ce..57fb871ba 100644 --- a/components/checkbox/Group.tsx +++ b/components/checkbox/Group.tsx @@ -1,18 +1,27 @@ import { computed, ref, watch, defineComponent, provide } from 'vue'; import Checkbox from './Checkbox'; import { useInjectFormItemContext } from '../form/FormItemContext'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; import type { CheckboxOptionType } from './interface'; import { CheckboxGroupContextKey, checkboxGroupProps } from './interface'; +// CSSINJS +import useStyle from './style'; + export default defineComponent({ compatConfig: { MODE: 3 }, name: 'ACheckboxGroup', + inheritAttrs: false, props: checkboxGroupProps(), // emits: ['change', 'update:value'], - setup(props, { slots, emit, expose }) { + setup(props, { slots, attrs, emit, expose }) { const formItemContext = useInjectFormItemContext(); const { prefixCls, direction } = useConfigInject('checkbox', props); + const groupPrefixCls = computed(() => `${prefixCls.value}-group`); + + // style + const [wrapSSR, hashId] = useStyle(groupPrefixCls); + const mergedValue = ref((props.value === undefined ? props.defaultValue : props.value) || []); watch( () => props.value, @@ -87,7 +96,6 @@ export default defineComponent({ return () => { const { id = formItemContext.id.value } = props; let children = null; - const groupPrefixCls = `${prefixCls.value}-group`; if (options.value && options.value.length > 0) { children = options.value.map(option => ( - {option.label === undefined ? slots.label?.(option) : option.label} + {slots.label !== undefined ? slots.label?.(option) : option.label} )); } - return ( + return wrapSSR(
    {children || slots.default?.()} -
    +
    , ); }; }, diff --git a/components/checkbox/__tests__/__snapshots__/demo.test.js.snap b/components/checkbox/__tests__/__snapshots__/demo.test.js.snap index 01ce052f8..55fb8740d 100644 --- a/components/checkbox/__tests__/__snapshots__/demo.test.js.snap +++ b/components/checkbox/__tests__/__snapshots__/demo.test.js.snap @@ -3,7 +3,7 @@ exports[`renders ./components/checkbox/demo/basic.vue correctly 1`] = ``; exports[`renders ./components/checkbox/demo/check-all.vue correctly 1`] = ` -
    +
    @@ -39,11 +39,11 @@ exports[`renders ./components/checkbox/demo/group.vue correctly 1`] = `


    -
    +
    `; exports[`renders ./components/checkbox/demo/layout.vue correctly 1`] = ` -
    +
    diff --git a/components/checkbox/__tests__/checkbox.test.js b/components/checkbox/__tests__/checkbox.test.js index 0dc53d165..904bc0478 100644 --- a/components/checkbox/__tests__/checkbox.test.js +++ b/components/checkbox/__tests__/checkbox.test.js @@ -34,7 +34,7 @@ describe('Checkbox', () => { }, }); expect(errorSpy).toHaveBeenCalledWith( - 'Warning: [antdv: Checkbox] `value` is not validate prop, do you mean `checked`?', + 'Warning: [ant-design-vue: Checkbox] `value` is not validate prop, do you mean `checked`?', ); errorSpy.mockRestore(); }); diff --git a/components/checkbox/demo/basic.vue b/components/checkbox/demo/basic.vue index d1057f6f6..4840d5ec6 100644 --- a/components/checkbox/demo/basic.vue +++ b/components/checkbox/demo/basic.vue @@ -19,13 +19,7 @@ Basic usage of checkbox - diff --git a/components/checkbox/demo/check-all.vue b/components/checkbox/demo/check-all.vue index 93e3fe989..a0be3c8b2 100644 --- a/components/checkbox/demo/check-all.vue +++ b/components/checkbox/demo/check-all.vue @@ -19,46 +19,36 @@ The `indeterminate` property can help you to achieve a 'check all' effect. - diff --git a/components/checkbox/demo/controller.vue b/components/checkbox/demo/controller.vue index e2e6833b1..5ebf14d80 100644 --- a/components/checkbox/demo/controller.vue +++ b/components/checkbox/demo/controller.vue @@ -31,34 +31,21 @@ Communicated with other components

    - diff --git a/components/checkbox/demo/disabled.vue b/components/checkbox/demo/disabled.vue index 6586d9f36..025f33748 100644 --- a/components/checkbox/demo/disabled.vue +++ b/components/checkbox/demo/disabled.vue @@ -17,18 +17,14 @@ Disabled checkbox - diff --git a/components/checkbox/demo/group.vue b/components/checkbox/demo/group.vue index e2169312a..fe737b83b 100644 --- a/components/checkbox/demo/group.vue +++ b/components/checkbox/demo/group.vue @@ -17,23 +17,23 @@ Generate a group of checkboxes from an array - diff --git a/components/collapse/demo/ghost.vue b/components/collapse/demo/ghost.vue index 5390d237a..087813bb1 100644 --- a/components/collapse/demo/ghost.vue +++ b/components/collapse/demo/ghost.vue @@ -29,22 +29,12 @@ Making collapse's background to transparent. - diff --git a/components/collapse/demo/index.vue b/components/collapse/demo/index.vue index 6b4afef1c..bf6158e16 100644 --- a/components/collapse/demo/index.vue +++ b/components/collapse/demo/index.vue @@ -8,6 +8,7 @@ + @@ -20,6 +21,7 @@ import Mix from './mix.vue'; import Noarrow from './noarrow.vue'; import Extra from './extra.vue'; import Ghost from './ghost.vue'; +import Collapsible from './collapsible.vue'; import CN from '../index.zh-CN.md'; import US from '../index.en-US.md'; @@ -35,6 +37,7 @@ export default { Noarrow, Extra, Ghost, + Collapsible, }, }; diff --git a/components/collapse/demo/mix.vue b/components/collapse/demo/mix.vue index afba125df..5286d4ca4 100644 --- a/components/collapse/demo/mix.vue +++ b/components/collapse/demo/mix.vue @@ -33,23 +33,13 @@ title: - diff --git a/components/collapse/demo/noarrow.vue b/components/collapse/demo/noarrow.vue index 1d0ad74b4..85c287925 100644 --- a/components/collapse/demo/noarrow.vue +++ b/components/collapse/demo/noarrow.vue @@ -25,22 +25,12 @@ You can hide the arrow icon by passing `showArrow={false}` to `CollapsePanel` co - diff --git a/components/collapse/index.en-US.md b/components/collapse/index.en-US.md index 5c4c1f113..4a80753b0 100644 --- a/components/collapse/index.en-US.md +++ b/components/collapse/index.en-US.md @@ -2,7 +2,8 @@ category: Components type: Data Display title: Collapse -cover: https://gw.alipayobjects.com/zos/alicdn/IxH16B9RD/Collapse.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*B7HKR5OBe8gAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAAAAAAAAAAAADrJ8AQ/original --- A content area which can be collapsed and expanded. @@ -19,12 +20,12 @@ A content area which can be collapsed and expanded. | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | | accordion | If `true`, `Collapse` renders as `Accordion` | boolean | `false` | | -| activeKey(v-model) | Key of the active panel | string\[]\|string | No default value. In `accordion` mode, it's the key of the first panel. | | +| activeKey(v-model) | Key of the active panel | string\[] \| string
    number\[] \| number | No default value. In `accordion` mode, it's the key of the first panel. | | | bordered | Toggles rendering of the border around the collapse block | boolean | `true` | | -| collapsible | Specify whether the panels of children be collapsible or the trigger area of collapsible | `header` \| `disabled` | - | 3.0 | +| collapsible | Specify whether the panels of children be collapsible or the trigger area of collapsible | `header` \| `icon` \| `disabled` | - | 4.0 | | destroyInactivePanel | Destroy Inactive Panel | boolean | `false` | | | expandIcon | allow to customize collapse icon | Function(props):VNode \| v-slot:expandIcon="props" | | | -| expandIconPosition | Set expand icon position: `left`, `right` | `left` | - | 1.5.0 | +| expandIconPosition | Set expand icon position | `start` \| `end` | - | 4.0 | | ghost | Make the collapse borderless and its background transparent | boolean | false | 3.0 | ### events diff --git a/components/collapse/index.zh-CN.md b/components/collapse/index.zh-CN.md index 9216f0018..1c750c85f 100644 --- a/components/collapse/index.zh-CN.md +++ b/components/collapse/index.zh-CN.md @@ -3,7 +3,8 @@ category: Components type: 数据展示 title: Collapse subtitle: 折叠面板 -cover: https://gw.alipayobjects.com/zos/alicdn/IxH16B9RD/Collapse.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*B7HKR5OBe8gAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAAAAAAAAAAAADrJ8AQ/original --- 可以折叠/展开的内容区域。 @@ -20,12 +21,12 @@ cover: https://gw.alipayobjects.com/zos/alicdn/IxH16B9RD/Collapse.svg | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | | accordion | 手风琴模式 | boolean | `false` | | -| activeKey(v-model) | 当前激活 tab 面板的 key | string\[]\|string | 默认无,accordion 模式下默认第一个元素 | | +| activeKey(v-model) | 当前激活 tab 面板的 key | string\[] \| string
    number\[] \| number | 默认无,accordion 模式下默认第一个元素 | | | bordered | 带边框风格的折叠面板 | boolean | `true` | | -| collapsible | 所有子面板是否可折叠或指定可折叠触发区域 | `header` \| `disabled` | - | 3.0 | +| collapsible | 所有子面板是否可折叠或指定可折叠触发区域 | `header` \| `icon` \| `disabled` | - | 4.0 | | destroyInactivePanel | 销毁折叠隐藏的面板 | boolean | `false` | | | expandIcon | 自定义切换图标 | Function(props):VNode \| slot="expandIcon" slot-scope="props"\|#expandIcon="props" | | | -| expandIconPosition | 设置图标位置: `left`, `right` | `left` | - | 1.5.0 | +| expandIconPosition | 设置图标位置 | `start` \| `end` | - | 4.0 | | ghost | 使折叠面板透明且无边框 | boolean | false | 3.0 | ### 事件 diff --git a/components/collapse/style/index.less b/components/collapse/style/index.less deleted file mode 100644 index 6b10c990b..000000000 --- a/components/collapse/style/index.less +++ /dev/null @@ -1,157 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@collapse-prefix-cls: ~'@{ant-prefix}-collapse'; - -.@{collapse-prefix-cls} { - .reset-component(); - - background-color: @collapse-header-bg; - border: @border-width-base @border-style-base @border-color-base; - border-bottom: 0; - border-radius: @collapse-panel-border-radius; - - & > &-item { - border-bottom: @border-width-base @border-style-base @border-color-base; - - &:last-child { - &, - & > .@{collapse-prefix-cls}-header { - border-radius: 0 0 @collapse-panel-border-radius @collapse-panel-border-radius; - } - } - - > .@{collapse-prefix-cls}-header { - position: relative; // Compatible with old version of antd, should remove in next version - display: flex; - flex-wrap: nowrap; - align-items: flex-start; - padding: @collapse-header-padding; - color: @heading-color; - line-height: @line-height-base; - cursor: pointer; - transition: all 0.3s, visibility 0s; - - .@{collapse-prefix-cls}-arrow { - display: inline-block; - margin-right: @margin-sm; - font-size: @font-size-sm; - vertical-align: -1px; - - & svg { - transition: transform 0.24s; - } - } - - .@{collapse-prefix-cls}-extra { - margin-left: auto; - } - - &:focus { - outline: none; - } - } - - .@{collapse-prefix-cls}-header-collapsible-only { - cursor: default; - .@{collapse-prefix-cls}-header-text { - cursor: pointer; - } - } - - &.@{collapse-prefix-cls}-no-arrow { - > .@{collapse-prefix-cls}-header { - padding-left: @padding-sm; - } - } - } - - // Expand Icon right - &-icon-position-right { - & > .@{collapse-prefix-cls}-item { - > .@{collapse-prefix-cls}-header { - position: relative; - padding: @collapse-header-padding; - padding-right: @collapse-header-padding-extra; - - .@{collapse-prefix-cls}-arrow { - position: absolute; - top: 50%; - right: @padding-md; - left: auto; - margin: 0; - transform: translateY(-50%); - } - } - } - } - - &-content { - color: @text-color; - background-color: @collapse-content-bg; - border-top: @border-width-base @border-style-base @border-color-base; - - & > &-box { - padding: @collapse-content-padding; - } - - &-hidden { - display: none; - } - } - - &-item:last-child { - > .@{collapse-prefix-cls}-content { - border-radius: 0 0 @collapse-panel-border-radius @collapse-panel-border-radius; - } - } - - &-borderless { - background-color: @collapse-header-bg; - border: 0; - } - - &-borderless > &-item { - border-bottom: 1px solid @border-color-base; - } - - &-borderless > &-item:last-child, - &-borderless > &-item:last-child &-header { - border-radius: 0; - } - - &-borderless > &-item > &-content { - background-color: transparent; - border-top: 0; - } - - &-borderless > &-item > &-content > &-content-box { - padding-top: 4px; - } - - &-ghost { - background-color: transparent; - border: 0; - > .@{collapse-prefix-cls}-item { - border-bottom: 0; - > .@{collapse-prefix-cls}-content { - background-color: transparent; - border-top: 0; - > .@{collapse-prefix-cls}-content-box { - padding-top: 12px; - padding-bottom: 12px; - } - } - } - } - - & &-item-disabled > &-header { - &, - & > .arrow { - color: @disabled-color; - cursor: not-allowed; - } - } -} - -@import './rtl'; diff --git a/components/collapse/style/index.tsx b/components/collapse/style/index.tsx index 3a3ab0de5..3d9b43302 100644 --- a/components/collapse/style/index.tsx +++ b/components/collapse/style/index.tsx @@ -1,2 +1,271 @@ -import '../../style/index.less'; -import './index.less'; +import { genCollapseMotion } from '../../style/motion'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { resetComponent, resetIcon } from '../../style'; + +export interface ComponentToken {} + +type CollapseToken = FullToken<'Collapse'> & { + collapseContentBg: string; + collapseHeaderBg: string; + collapseHeaderPadding: string; + collapsePanelBorderRadius: number; + collapseContentPaddingHorizontal: number; +}; + +export const genBaseStyle: GenerateStyle = token => { + const { + componentCls, + collapseContentBg, + padding, + collapseContentPaddingHorizontal, + collapseHeaderBg, + collapseHeaderPadding, + collapsePanelBorderRadius, + + lineWidth, + lineType, + colorBorder, + colorText, + colorTextHeading, + colorTextDisabled, + fontSize, + lineHeight, + marginSM, + paddingSM, + motionDurationSlow, + fontSizeIcon, + } = token; + + const borderBase = `${lineWidth}px ${lineType} ${colorBorder}`; + + return { + [componentCls]: { + ...resetComponent(token), + backgroundColor: collapseHeaderBg, + border: borderBase, + borderBottom: 0, + borderRadius: `${collapsePanelBorderRadius}px`, + + [`&-rtl`]: { + direction: 'rtl', + }, + + [`& > ${componentCls}-item`]: { + borderBottom: borderBase, + [`&:last-child`]: { + [` + &, + & > ${componentCls}-header`]: { + borderRadius: `0 0 ${collapsePanelBorderRadius}px ${collapsePanelBorderRadius}px`, + }, + }, + + [`> ${componentCls}-header`]: { + position: 'relative', // Compatible with old version of antd, should remove in next version + display: 'flex', + flexWrap: 'nowrap', + alignItems: 'flex-start', + padding: collapseHeaderPadding, + color: colorTextHeading, + lineHeight, + cursor: 'pointer', + transition: `all ${motionDurationSlow}, visibility 0s`, + + [`> ${componentCls}-header-text`]: { + flex: 'auto', + }, + + '&:focus': { + outline: 'none', + }, + + // >>>>> Arrow + [`${componentCls}-expand-icon`]: { + height: fontSize * lineHeight, + display: 'flex', + alignItems: 'center', + paddingInlineEnd: marginSM, + }, + + [`${componentCls}-arrow`]: { + ...resetIcon(), + fontSize: fontSizeIcon, + + svg: { + transition: `transform ${motionDurationSlow}`, + }, + }, + + // >>>>> Text + [`${componentCls}-header-text`]: { + marginInlineEnd: 'auto', + }, + }, + + [`${componentCls}-header-collapsible-only`]: { + cursor: 'default', + + [`${componentCls}-header-text`]: { + flex: 'none', + cursor: 'pointer', + }, + [`${componentCls}-expand-icon`]: { + cursor: 'pointer', + }, + }, + + [`${componentCls}-icon-collapsible-only`]: { + cursor: 'default', + + [`${componentCls}-expand-icon`]: { + cursor: 'pointer', + }, + }, + + [`&${componentCls}-no-arrow`]: { + [`> ${componentCls}-header`]: { + paddingInlineStart: paddingSM, + }, + }, + }, + + [`${componentCls}-content`]: { + color: colorText, + backgroundColor: collapseContentBg, + borderTop: borderBase, + + [`& > ${componentCls}-content-box`]: { + padding: `${padding}px ${collapseContentPaddingHorizontal}px`, + }, + + [`&-hidden`]: { + display: 'none', + }, + }, + + [`${componentCls}-item:last-child`]: { + [`> ${componentCls}-content`]: { + borderRadius: `0 0 ${collapsePanelBorderRadius}px ${collapsePanelBorderRadius}px`, + }, + }, + + [`& ${componentCls}-item-disabled > ${componentCls}-header`]: { + [` + &, + & > .arrow + `]: { + color: colorTextDisabled, + cursor: 'not-allowed', + }, + }, + + // ========================== Icon Position ========================== + [`&${componentCls}-icon-position-end`]: { + [`& > ${componentCls}-item`]: { + [`> ${componentCls}-header`]: { + [`${componentCls}-expand-icon`]: { + order: 1, + paddingInlineEnd: 0, + paddingInlineStart: marginSM, + }, + }, + }, + }, + }, + }; +}; + +const genArrowStyle: GenerateStyle = token => { + const { componentCls } = token; + + const fixedSelector = `> ${componentCls}-item > ${componentCls}-header ${componentCls}-arrow svg`; + + return { + [`${componentCls}-rtl`]: { + [fixedSelector]: { + transform: `rotate(180deg)`, + }, + }, + }; +}; + +const genBorderlessStyle: GenerateStyle = token => { + const { + componentCls, + collapseHeaderBg, + paddingXXS, + + colorBorder, + } = token; + + return { + [`${componentCls}-borderless`]: { + backgroundColor: collapseHeaderBg, + border: 0, + + [`> ${componentCls}-item`]: { + borderBottom: `1px solid ${colorBorder}`, + }, + + [` + > ${componentCls}-item:last-child, + > ${componentCls}-item:last-child ${componentCls}-header + `]: { + borderRadius: 0, + }, + + [`> ${componentCls}-item:last-child`]: { + borderBottom: 0, + }, + + [`> ${componentCls}-item > ${componentCls}-content`]: { + backgroundColor: 'transparent', + borderTop: 0, + }, + + [`> ${componentCls}-item > ${componentCls}-content > ${componentCls}-content-box`]: { + paddingTop: paddingXXS, + }, + }, + }; +}; + +const genGhostStyle: GenerateStyle = token => { + const { componentCls, paddingSM } = token; + + return { + [`${componentCls}-ghost`]: { + backgroundColor: 'transparent', + border: 0, + [`> ${componentCls}-item`]: { + borderBottom: 0, + [`> ${componentCls}-content`]: { + backgroundColor: 'transparent', + border: 0, + [`> ${componentCls}-content-box`]: { + paddingBlock: paddingSM, + }, + }, + }, + }, + }; +}; + +export default genComponentStyleHook('Collapse', token => { + const collapseToken = mergeToken(token, { + collapseContentBg: token.colorBgContainer, + collapseHeaderBg: token.colorFillAlter, + collapseHeaderPadding: `${token.paddingSM}px ${token.padding}px`, + collapsePanelBorderRadius: token.borderRadiusLG, + collapseContentPaddingHorizontal: 16, // Fixed value + }); + + return [ + genBaseStyle(collapseToken), + genBorderlessStyle(collapseToken), + genGhostStyle(collapseToken), + genArrowStyle(collapseToken), + genCollapseMotion(collapseToken), + ]; +}); diff --git a/components/collapse/style/rtl.less b/components/collapse/style/rtl.less deleted file mode 100644 index 559a922de..000000000 --- a/components/collapse/style/rtl.less +++ /dev/null @@ -1,48 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@collapse-prefix-cls: ~'@{ant-prefix}-collapse'; - -.@{collapse-prefix-cls} { - &-rtl { - direction: rtl; - } - - & > &-item { - > .@{collapse-prefix-cls}-header { - .@{collapse-prefix-cls}-rtl & { - padding: @collapse-header-padding; - padding-right: @collapse-header-padding-extra; - } - - .@{collapse-prefix-cls}-arrow { - .@{collapse-prefix-cls}-rtl& { - margin-right: 0; - margin-left: @margin-sm; - } - - & svg { - .@{collapse-prefix-cls}-rtl& { - transform: rotate(180deg); - } - } - } - - .@{collapse-prefix-cls}-extra { - .@{collapse-prefix-cls}-rtl& { - margin-right: auto; - margin-left: 0; - } - } - } - - &.@{collapse-prefix-cls}-no-arrow { - > .@{collapse-prefix-cls}-header { - .@{collapse-prefix-cls}-rtl& { - padding-right: @padding-sm; - padding-left: 0; - } - } - } - } -} diff --git a/components/color-picker/ColorPicker.jsx b/components/color-picker/ColorPicker.jsx deleted file mode 100644 index 66b6c3673..000000000 --- a/components/color-picker/ColorPicker.jsx +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -import PropTypes from '../_util/vue-types'; -import { defaultConfigProvider } from '../config-provider'; -import BaseMixin from '../_util/BaseMixin'; -import Pickr from '@simonwep/pickr/dist/pickr.es5.min'; -import Icon from '../icon'; -import LocaleReceiver from '../locale-provider/LocaleReceiver'; -import enUS from './locale/en_US'; -import debounce from 'lodash-es/debounce'; - -import { getOptionProps, findDOMNode } from '../_util/props-util'; -let colors = '#194d33'; -export default { - name: 'AColorPicker', - mixins: [BaseMixin], - inject: { - configProvider: { default: () => defaultConfigProvider }, - }, - model: { - prop: 'value', - event: 'change.value', //为了支持v-model直接返回颜色字符串 所以用了自定义的事件,与pickr自带change事件进行区分 - }, - props: { - prefixCls: String, - defaultValue: String, //默认值 - config: PropTypes.object, //pickr配置 - value: String, //颜色值 - locale: PropTypes.object, //双语包 - colorRounded: Number, //颜色数值保留几位小数 - size: PropTypes.oneOf(['default', 'small', 'large']).def('default'), //尺寸 - getPopupContainer: Function, //指定渲染容器 - disabled: { type: Boolean, default: false }, //是否禁用 - format: String, //颜色格式设置 - alpha: { type: Boolean, default: false }, //是否开启透明通道 - hue: { type: Boolean, default: true }, //是否开启色彩预选 - }, - - data() { - return { - colors, - myOpen: false, - pickr: null, - i18n: enUS, - }; - }, - watch: { - 'configProvider.locale.ColorPicker': { - handler(val) { - if (this.locale) return; - this.i18n = val; - this.reInitialize(); - }, - }, - locale(val) { - this.i18n = val.ColorPicker || val.lang; - this.reInitialize(); - }, - value(val) { - this.setColor(val); - }, - disabled(val) { - this.pickr[val ? 'disable' : 'enable'](); - }, - config: { - handler() { - this.reInitialize(); - }, - deep: true, - }, - format(val) { - const type = val.toLocaleUpperCase(); - let res = this.pickr.setColorRepresentation(type); - if (res) { - this.pickr.applyColor(); - } else { - throw new TypeError('format was invalid'); - } - }, - }, - mounted() { - if (this.locale) { - this.i18n = this.locale.ColorPicker || this.locale.lang; - } - this.createPickr(); - this.eventsBinding(); - }, - unmounted() { - this.pickr.destroyAndRemove(); - }, - methods: { - reInitialize() { - this.pickr.destroyAndRemove(); - const dom = document.createElement('div'); - dom.id = 'color-picker' + this._uid; - const box = findDOMNode(this).querySelector('#color-picker-box' + this._uid); - box.appendChild(dom); - this.createPickr(); - this.eventsBinding(); - }, - setColor: debounce(function (val) { - this.pickr.setColor(val); - }, 1000), - eventsBinding() { - const pickrEvents = [ - 'init', - 'hide', - 'show', - 'save', - 'clear', - 'change', - 'changestop', - 'cancel', - 'swatchselect', - ]; - Object.keys(this.$listeners).forEach(event => { - pickrEvents.includes(event) && this.pickr.on(event, this.$listeners[event]); - }); - }, - createPickr() { - const { getPopupContainer } = getOptionProps(this); - const { getPopupContainer: getContextPopupContainer } = this.configProvider; - const container = getPopupContainer || getContextPopupContainer; - this.pickr = Pickr.create( - Object.assign( - { - el: '#color-picker' + this._uid, - container: (container && container(findDOMNode(this))) || document.body, - theme: 'monolith', // or 'monolith', or 'nano' - default: this.value || this.defaultValue || null, // 有默认颜色pickr才可以获取到_representation - components: { - // Main components - preview: true, - opacity: this.alpha, - hue: this.hue, - // Input / output Options - interaction: { - hex: true, - rgba: true, - input: true, - clear: true, - save: true, - }, - }, - }, - this.config, - { i18n: this.i18n }, - ), - ) - .on('save', (color, instance) => { - if (color) { - let _representation = instance._representation || 'HEXA'; - color = color['to' + _representation]().toString(this.colorRounded || 0); - } - this.$emit('change.value', color || ''); - }) - .on('hide', () => { - this.setState({ myOpen: false }); - }); - }, - handleOpenChange() { - const open = !this.myOpen; - this.setState({ myOpen: open }); - this.pickr[open ? 'show' : 'hide'](); - this.$emit('openChange', open); - }, - getDefaultLocale() { - const result = { - ...enUS, - ...this.$props.locale, - }; - result.lang = { - ...result.lang, - ...(this.$props.locale || {}).lang, - }; - return result; - }, - renderColorPicker() { - const { prefixCls: customizePrefixCls } = this.$props; - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('color-picker', customizePrefixCls); - const { disabled } = getOptionProps(this); - const classString = { - [prefixCls]: true, - [`${prefixCls}-open`]: this.myOpen, - [`${prefixCls}-lg`]: this.size === 'large', - [`${prefixCls}-sm`]: this.size === 'small', - [`${prefixCls}-disabled`]: this.disabled, - }; - return ( -
    -
    -
    -
    -
    - -
    -
    - ); - }, - }, - render() { - return ( - - ); - }, -}; diff --git a/components/color-picker/__tests__/__snapshots__/index.test.js.snap b/components/color-picker/__tests__/__snapshots__/index.test.js.snap deleted file mode 100644 index 488cb5ecf..000000000 --- a/components/color-picker/__tests__/__snapshots__/index.test.js.snap +++ /dev/null @@ -1,337 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ColorPicker prop locale should works 1`] = ` -
    -
    -
    -
    - - - - -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - -
    - -
    - - - - - - - - - - - -
    -
    -
    -`; - -exports[`ColorPicker save event should works 1`] = ` -
    -
    -
    -
    - - - - -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - -
    - -
    - - - - - - - - - - - -
    -
    -
    -`; - -exports[`ColorPicker should support default value 1`] = ` -
    -
    -
    -
    - - - - -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - -
    - -
    - - - - - - - - - - - -
    -
    -
    -`; - -exports[`ColorPicker should support disabled 1`] = ` -
    -
    -
    -
    - - - - -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - -
    - -
    - - - - - - - - - - - -
    -
    -
    -`; - -exports[`ColorPicker should support format 1`] = ` -
    -
    -
    -
    - - - - -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - -
    - -
    - - - - - - - - - - - -
    -
    -
    -`; - -exports[`ColorPicker should support v-model 1`] = ` -
    -
    -
    -
    - - - - -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - -
    - -
    - - - - - - - - - - - -
    -
    -
    -`; diff --git a/components/color-picker/__tests__/index.test.js b/components/color-picker/__tests__/index.test.js deleted file mode 100644 index 1ad6a1d03..000000000 --- a/components/color-picker/__tests__/index.test.js +++ /dev/null @@ -1,155 +0,0 @@ -import { mount } from '@vue/test-utils'; -import ColorPicker from '..'; -import { asyncExpect } from '../../../tests/utils'; -describe('ColorPicker', () => { - xit('should support default value', async () => { - const wrapper = mount( - { - render() { - return p}>; - }, - }, - { sync: false, attachTo: 'body' }, - ); - await asyncExpect(() => { - expect(wrapper.html()).toMatchSnapshot(); - wrapper.unmount(); - }, 1000); - }); - xit('should support v-model', async () => { - let color = 'rgba(10, 10, 10, 1)'; - const wrapper = mount( - { - data() { - return { - color, - }; - }, - render() { - return p}>; - }, - mounted() { - this.color = 'rgba(110, 120, 130, 1)'; - }, - }, - { sync: false, attachTo: 'body' }, - ); - - await asyncExpect(() => { - expect(wrapper.html()).toMatchSnapshot(); - wrapper.unmount(); - }, 1000); - }); - xit('should support disabled', async () => { - const wrapper = mount( - { - data() { - return { - disabled: false, - }; - }, - render() { - return p}>; - }, - mounted() { - this.disabled = true; - }, - }, - { sync: false, attachTo: 'body' }, - ); - - await asyncExpect(async () => { - expect(wrapper.html()).toMatchSnapshot(); - await asyncExpect(() => { - wrapper.unmount(); - }); - }, 1000); - }); - xit('should support format', async () => { - const wrapper = mount( - { - data() { - return { - format: 'RGBA', - }; - }, - render() { - return p}>; - }, - mounted() { - this.format = 'HEX'; - }, - }, - { sync: false, attachTo: 'body' }, - ); - - await asyncExpect(async () => { - expect(wrapper.html()).toMatchSnapshot(); - await asyncExpect(() => { - wrapper.unmount(); - }); - }, 1000); - }); - xit('prop locale should works', async () => { - const wrapper = mount( - { - data() { - return { - locale: { - lang: { - 'btn:save': 'セーブ', - 'btn:cancel': 'キャンセル', - 'btn:clear': '晴れ', - }, - }, - }; - }, - render() { - return ( - p} /> - ); - }, - mounted() { - this.locale = { - lang: { - 'btn:save': '1セーブ', - 'btn:cancel': '1キャンセル', - 'btn:clear': '1晴れ', - }, - }; - }, - }, - { sync: false, attachTo: 'body' }, - ); - await asyncExpect(async () => { - expect(wrapper.html()).toMatchSnapshot(); - await asyncExpect(() => { - wrapper.unmount(); - }); - }, 1000); - }); - xit('save event should works', async () => { - const wrapper = mount( - { - render() { - return ( - p} onSave={this.save} /> - ); - }, - methods: { - save(val) { - return val; - }, - }, - }, - { sync: false, attachTo: 'body' }, - ); - await asyncExpect(async () => { - wrapper.find('.pcr-save').trigger('click'); - expect(wrapper.html()).toMatchSnapshot(); - await asyncExpect(() => { - wrapper.unmount(); - }); - }, 1000); - }); -}); diff --git a/components/color-picker/demo/alpha.md b/components/color-picker/demo/alpha.md deleted file mode 100644 index 181412876..000000000 --- a/components/color-picker/demo/alpha.md +++ /dev/null @@ -1,24 +0,0 @@ - -#### 透明度 -开启属性 `alpha`,可以选择带 Alpha 通道的颜色。 - - - -#### Alpha -Set the property `alpha` true, to select a color with alpha channel. - - -```vue - - -``` diff --git a/components/color-picker/demo/basic.md b/components/color-picker/demo/basic.md deleted file mode 100644 index 1f25f7136..000000000 --- a/components/color-picker/demo/basic.md +++ /dev/null @@ -1,34 +0,0 @@ - -#### 基础用法 -基本用法,可以使用 v-model 实现数据的双向绑定。 - - - -#### Basic -Basic usage. You can use v-model to enable a two-way bingding on data. - - -```vue - - -``` diff --git a/components/color-picker/demo/colors.md b/components/color-picker/demo/colors.md deleted file mode 100644 index 5609088c3..000000000 --- a/components/color-picker/demo/colors.md +++ /dev/null @@ -1,44 +0,0 @@ - -#### 颜色预设 -可以通过`config.swatches`设置,来自定义预设颜色 -更多配置 - - - -#### Color Presets -Set `config.swatches ` to customize the default color presets. -More config settings - - -```vue - - -``` diff --git a/components/color-picker/demo/hue.md b/components/color-picker/demo/hue.md deleted file mode 100644 index 4d4bd2c1b..000000000 --- a/components/color-picker/demo/hue.md +++ /dev/null @@ -1,24 +0,0 @@ - -#### 色彩 -设置属性 `hue` 为 false,可以禁用色彩选项。 - - - -#### Hue -Set property `hue` to false, can hide hue slider. - - -```vue - - -``` diff --git a/components/color-picker/demo/size.md b/components/color-picker/demo/size.md deleted file mode 100644 index 469d428c0..000000000 --- a/components/color-picker/demo/size.md +++ /dev/null @@ -1,36 +0,0 @@ - -#### 尺寸 -选择器有三种尺寸:大、默认(中)、小。 - - - -#### Size -There are three size of ColorPicker: large, medium(default), small. - - -```vue - - -``` diff --git a/components/color-picker/index.en-US.md b/components/color-picker/index.en-US.md deleted file mode 100644 index 99e345b59..000000000 --- a/components/color-picker/index.en-US.md +++ /dev/null @@ -1,27 +0,0 @@ -## API - -| Property | Description | Type | Default | -| --- | --- | --- | --- | -| colorRounded | precision of color | number | 0 | -| config | pickr config | [pickr options](https://github.com/Simonwep/pickr) | - | -| defaultValue | default color | string | - | -| disabled | whether disabled picker | boolean | false | -| format | Color format | 'HEXA' \|'RGBA' \|'HSVA' \|'HSLA' \|'CMYK' | 'HEXA' | -| getPopupContainer | to set the container of the floating layer, while the default is to create a div element in body | Function(triggerNode) | () => document.body | -| locale | locale package | [default setting](https://github.com/vueComponent/ant-design-vue/blob/main/components/color-picker/locale) | - | -| size | size of pickr | 'large'\|'small'\|'default' | 'default' | -| value | color value | string | - | - -### Event - -| Event | Description | Arguments | -| --- | --- | --- | -| `cancel` | User clicked the cancel button (return to previous color). | `PickrInstance` | -| `change` | Color has changed (but not saved). Also fired on `swatchselect` | `HSVaColorObject, PickrInstance` | -| `changestop` | User stopped to change the color | `PickrInstance` | -| `clear` | User cleared the color. | `PickrInstance` | -| `hide` | Pickr got closed | `PickrInstance` | -| `init` | Initialization done - pickr can be used | `PickrInstance` | -| `save` | User clicked the save / clear button. Also fired on clear with `null` as color. | `HSVaColorObject or null, PickrInstance` | -| `show` | Pickr got opened | `PickrInstance` | -| `swatchselect` | User clicked one of the swatches | `HSVaColorObject, PickrInstance` | diff --git a/components/color-picker/index.js b/components/color-picker/index.js deleted file mode 100644 index 3bf52f607..000000000 --- a/components/color-picker/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import ColorPicker from './ColorPicker'; -/* istanbul ignore next */ -ColorPicker.install = function (app) { - app.component(ColorPicker.name, ColorPicker); - return app; -}; - -export default ColorPicker; diff --git a/components/color-picker/index.zh-CN.md b/components/color-picker/index.zh-CN.md deleted file mode 100644 index 2bee7391e..000000000 --- a/components/color-picker/index.zh-CN.md +++ /dev/null @@ -1,27 +0,0 @@ -## API - -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| colorRounded | 颜色数值精度 | number | 0 | -| config | pickr 配置项 | [pickr options](https://github.com/Simonwep/pickr) | - | -| defaultValue | 默认颜色 | string | - | -| disabled | 是否禁用 | boolean | false | -| format | 定义返回的颜色格式 | 'HEXA' \|'RGBA' \|'HSVA' \|'HSLA' \|'CMYK' | 'HEXA' | -| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | Function(triggerNode) | () => document.body | -| locale | 语言包 | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/color-picker/locale) | - | -| size | 取色器尺寸 | 'large'\|'small'\|'default' | 'default' | -| value | 颜色值 | string | - | - -### 事件 - -| 事件名称 | 说明 | 回调参数 | -| --- | --- | --- | -| `cancel` | 用户点击取消时(颜色返回至上个颜色) | `PickrInstance` | -| `change` | 颜色值发生变更时(非保存).`swatchselect`也会触发 | `HSVaColorObject, PickrInstance` | -| `changestop` | 用户不再改变颜色时 | `PickrInstance` | -| `clear` | 清空颜色时 | `PickrInstance` | -| `hide` | Pickr 关闭时 | `PickrInstance` | -| `init` | 初始化完成,可以使用 pickr | `PickrInstance` | -| `save` | 用户点击保存/清空按钮时 | `HSVaColorObject or null, PickrInstance` | -| `show` | Pickr 开启时 | `PickrInstance` | -| `swatchselect` | 用户切换了色板 | `HSVaColorObject, PickrInstance` | diff --git a/components/color-picker/locale/en_US.js b/components/color-picker/locale/en_US.js deleted file mode 100644 index d8f2854de..000000000 --- a/components/color-picker/locale/en_US.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - 'btn:save': 'Save', - 'btn:cancel': 'Cancel', - 'btn:clear': 'Clear', -}; diff --git a/components/color-picker/locale/ku_KU.js b/components/color-picker/locale/ku_KU.js deleted file mode 100644 index 6da0dccd1..000000000 --- a/components/color-picker/locale/ku_KU.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - 'btn:save': 'پاشکەوت کردن', - 'btn:cancel': 'هەڵوەشاندنەوە', - 'btn:clear': 'پاککردنەوە', -}; diff --git a/components/color-picker/locale/zh_CN.js b/components/color-picker/locale/zh_CN.js deleted file mode 100644 index 74117e2c7..000000000 --- a/components/color-picker/locale/zh_CN.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - 'btn:save': '保存', - 'btn:cancel': '取消', - 'btn:clear': '清除', -}; diff --git a/components/color-picker/locale/zh_TW.js b/components/color-picker/locale/zh_TW.js deleted file mode 100644 index 74117e2c7..000000000 --- a/components/color-picker/locale/zh_TW.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - 'btn:save': '保存', - 'btn:cancel': '取消', - 'btn:clear': '清除', -}; diff --git a/components/color-picker/style/index.tsx b/components/color-picker/style/index.tsx deleted file mode 100644 index 934b4e144..000000000 --- a/components/color-picker/style/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// TODO -import '../../style/index.less'; diff --git a/components/comment/__tests__/__snapshots__/demo.test.js.snap b/components/comment/__tests__/__snapshots__/demo.test.js.snap index 002c75586..323601690 100644 --- a/components/comment/__tests__/__snapshots__/demo.test.js.snap +++ b/components/comment/__tests__/__snapshots__/demo.test.js.snap @@ -31,29 +31,33 @@ exports[`renders ./components/comment/demo/editor.vue correctly 1`] = `
    -
    - -
    -
    -
    +
    +
    + +
    +
    +
    +
    +
    - -
    -
    -
    -
    -
    -
    +
    +
    +
    + +
    +
    +
    +
    +
    - -
    +
    diff --git a/components/comment/__tests__/__snapshots__/index.test.js.snap b/components/comment/__tests__/__snapshots__/index.test.js.snap index 99acaabac..4b5ccb05d 100644 --- a/components/comment/__tests__/__snapshots__/index.test.js.snap +++ b/components/comment/__tests__/__snapshots__/index.test.js.snap @@ -42,29 +42,33 @@ exports[`Comment Comment can be used as editor, user can customize the editor co
    -
    - -
    -
    -
    +
    +
    + +
    +
    +
    +
    +
    - -
    -
    -
    -
    -
    -
    +
    +
    +
    + +
    +
    +
    +
    +
    - -
    +
    diff --git a/components/comment/demo/basic.vue b/components/comment/demo/basic.vue index 23cff5321..540fc8eca 100644 --- a/components/comment/demo/basic.vue +++ b/components/comment/demo/basic.vue @@ -65,45 +65,26 @@ A basic comment with author, avatar, time and actions. - diff --git a/components/comment/demo/editor.vue b/components/comment/demo/editor.vue index 32f5326e6..7f9abd762 100644 --- a/components/comment/demo/editor.vue +++ b/components/comment/demo/editor.vue @@ -50,47 +50,36 @@ Comment can be used as editor, user can customize the editor component. - diff --git a/components/comment/demo/list.vue b/components/comment/demo/list.vue index 81db3af10..b9efef012 100644 --- a/components/comment/demo/list.vue +++ b/components/comment/demo/list.vue @@ -44,35 +44,27 @@ Displaying a series of comments using the `antd` List Component. - diff --git a/components/comment/index.tsx b/components/comment/index.tsx index 8502f1a56..eda4e8ac5 100644 --- a/components/comment/index.tsx +++ b/components/comment/index.tsx @@ -4,7 +4,11 @@ import PropTypes from '../_util/vue-types'; import { flattenChildren } from '../_util/props-util'; import type { CustomSlotsType, VueNode } from '../_util/type'; import { withInstall } from '../_util/type'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; + +// CSSINJS +import useStyle from './style'; + export const commentProps = () => ({ actions: Array, /** The element to display as the comment author. */ @@ -24,8 +28,8 @@ export type CommentProps = Partial, - setup(props, { slots }) { + setup(props, { slots, attrs }) { const { prefixCls, direction } = useConfigInject('comment', props); + + // style + const [wrapSSR, hashId] = useStyle(prefixCls); + const renderNested = (prefixCls: string, children: VueNode) => { return
    {children}
    ; }; @@ -87,18 +95,21 @@ const Comment = defineComponent({
    ); const children = flattenChildren(slots.default?.()); - return ( + return wrapSSR(
    {comment} {children && children.length ? renderNested(pre, children) : null} -
    +
    , ); }; }, diff --git a/components/comment/style/index.less b/components/comment/style/index.less deleted file mode 100644 index 84da3a3a2..000000000 --- a/components/comment/style/index.less +++ /dev/null @@ -1,105 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@comment-prefix-cls: ~'@{ant-prefix}-comment'; - -.@{comment-prefix-cls} { - position: relative; - background-color: @comment-bg; - - &-inner { - display: flex; - padding: @comment-padding-base; - } - - &-avatar { - position: relative; - flex-shrink: 0; - margin-right: @margin-sm; - cursor: pointer; - - img { - width: 32px; - height: 32px; - border-radius: 50%; - } - } - - &-content { - position: relative; - flex: 1 1 auto; - min-width: 1px; - font-size: @comment-font-size-base; - word-wrap: break-word; - - &-author { - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - margin-bottom: @margin-xss; - font-size: @comment-font-size-base; - - & > a, - & > span { - padding-right: @padding-xs; - font-size: @comment-font-size-sm; - line-height: 18px; - } - - &-name { - color: @comment-author-name-color; - font-size: @comment-font-size-base; - transition: color 0.3s; - - > * { - color: @comment-author-name-color; - - &:hover { - color: @comment-author-name-color; - } - } - } - - &-time { - color: @comment-author-time-color; - white-space: nowrap; - cursor: auto; - } - } - - &-detail p { - margin-bottom: @comment-content-detail-p-margin-bottom; - white-space: pre-wrap; - } - } - - &-actions { - margin-top: @comment-actions-margin-top; - margin-bottom: @comment-actions-margin-bottom; - padding-left: 0; - - > li { - display: inline-block; - color: @comment-action-color; - - > span { - margin-right: 10px; - color: @comment-action-color; - font-size: @comment-font-size-sm; - cursor: pointer; - transition: color 0.3s; - user-select: none; - - &:hover { - color: @comment-action-hover-color; - } - } - } - } - - &-nested { - margin-left: @comment-nest-indent; - } -} - -@import './rtl'; diff --git a/components/comment/style/index.ts b/components/comment/style/index.ts new file mode 100644 index 000000000..c1ac4592a --- /dev/null +++ b/components/comment/style/index.ts @@ -0,0 +1,160 @@ +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; + +export interface ComponentToken {} + +type CommentToken = FullToken<'Comment'> & { + commentBg: string; + commentPaddingBase: string; + commentNestIndent: string; + commentFontSizeBase: number; + commentFontSizeSm: number; + commentAuthorNameColor: string; + commentAuthorTimeColor: string; + commentActionColor: string; + commentActionHoverColor: string; + commentActionsMarginBottom: string; + commentActionsMarginTop: number; + commentContentDetailPMarginBottom: string; +}; + +const genBaseStyle: GenerateStyle = token => { + const { + componentCls, + commentBg, + commentPaddingBase, + commentNestIndent, + commentFontSizeBase, + commentFontSizeSm, + commentAuthorNameColor, + commentAuthorTimeColor, + commentActionColor, + commentActionHoverColor, + commentActionsMarginBottom, + commentActionsMarginTop, + commentContentDetailPMarginBottom, + } = token; + + return { + [componentCls]: { + position: 'relative', + backgroundColor: commentBg, + + [`${componentCls}-inner`]: { + display: 'flex', + padding: commentPaddingBase, + }, + + [`${componentCls}-avatar`]: { + position: 'relative', + flexShrink: 0, + marginRight: token.marginSM, + cursor: 'pointer', + + [`img`]: { + width: '32px', + height: '32px', + borderRadius: '50%', + }, + }, + + [`${componentCls}-content`]: { + position: 'relative', + flex: `1 1 auto`, + minWidth: `1px`, + fontSize: commentFontSizeBase, + wordWrap: 'break-word', + + [`&-author`]: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'flex-start', + marginBottom: token.marginXXS, + fontSize: commentFontSizeBase, + + [`& > a,& > span`]: { + paddingRight: token.paddingXS, + fontSize: commentFontSizeSm, + lineHeight: `18px`, + }, + + [`&-name`]: { + color: commentAuthorNameColor, + fontSize: commentFontSizeBase, + transition: `color ${token.motionDurationSlow}`, + + [`> *`]: { + color: commentAuthorNameColor, + + [`&:hover`]: { + color: commentAuthorNameColor, + }, + }, + }, + + [`&-time`]: { + color: commentAuthorTimeColor, + whiteSpace: 'nowrap', + cursor: 'auto', + }, + }, + + [`&-detail p`]: { + marginBottom: commentContentDetailPMarginBottom, + whiteSpace: 'pre-wrap', + }, + }, + + [`${componentCls}-actions`]: { + marginTop: commentActionsMarginTop, + marginBottom: commentActionsMarginBottom, + paddingLeft: 0, + + [`> li`]: { + display: 'inline-block', + color: commentActionColor, + + [`> span`]: { + marginRight: '10px', + color: commentActionColor, + fontSize: commentFontSizeSm, + cursor: 'pointer', + transition: `color ${token.motionDurationSlow}`, + userSelect: 'none', + + [`&:hover`]: { + color: commentActionHoverColor, + }, + }, + }, + }, + + [`${componentCls}-nested`]: { + marginLeft: commentNestIndent, + }, + + '&-rtl': { + direction: 'rtl', + }, + }, + }; +}; + +export default genComponentStyleHook('Comment', token => { + const commentToken = mergeToken(token, { + commentBg: 'inherit', + commentPaddingBase: `${token.paddingMD}px 0`, + commentNestIndent: `44px`, + commentFontSizeBase: token.fontSize, + commentFontSizeSm: token.fontSizeSM, + commentAuthorNameColor: token.colorTextTertiary, + commentAuthorTimeColor: token.colorTextPlaceholder, + commentActionColor: token.colorTextTertiary, + commentActionHoverColor: token.colorTextSecondary, + commentActionsMarginBottom: 'inherit', + commentActionsMarginTop: token.marginSM, + commentContentDetailPMarginBottom: 'inherit', + }); + + return [genBaseStyle(commentToken)]; +}); diff --git a/components/comment/style/index.tsx b/components/comment/style/index.tsx deleted file mode 100644 index 3a3ab0de5..000000000 --- a/components/comment/style/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import '../../style/index.less'; -import './index.less'; diff --git a/components/comment/style/rtl.less b/components/comment/style/rtl.less deleted file mode 100644 index a930d8384..000000000 --- a/components/comment/style/rtl.less +++ /dev/null @@ -1,51 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@comment-prefix-cls: ~'@{ant-prefix}-comment'; - -.@{comment-prefix-cls} { - &-rtl { - direction: rtl; - } - - &-avatar { - .@{comment-prefix-cls}-rtl & { - margin-right: 0; - margin-left: 12px; - } - } - - &-content { - &-author { - & > a, - & > span { - .@{comment-prefix-cls}-rtl & { - padding-right: 0; - padding-left: 8px; - } - } - } - } - - &-actions { - .@{comment-prefix-cls}-rtl & { - padding-right: 0; - } - - > li { - > span { - .@{comment-prefix-cls}-rtl & { - margin-right: 0; - margin-left: 10px; - } - } - } - } - - &-nested { - .@{comment-prefix-cls}-rtl & { - margin-right: @comment-nest-indent; - margin-left: 0; - } - } -} diff --git a/components/components.ts b/components/components.ts index 020c5add2..7b741d1c6 100644 --- a/components/components.ts +++ b/components/components.ts @@ -13,9 +13,6 @@ export { default as Alert } from './alert'; export type { AvatarProps } from './avatar'; export { default as Avatar, AvatarGroup } from './avatar'; -export type { BackTopProps } from './back-top'; -export { default as BackTop } from './back-top'; - export type { BadgeProps } from './badge'; export { default as Badge, BadgeRibbon } from './badge'; @@ -76,6 +73,13 @@ export { default as Drawer } from './drawer'; export type { EmptyProps } from './empty'; export { default as Empty } from './empty'; +export type { + FloatButtonProps, + FloatButtonGroupProps, + BackTopProps, +} from './float-button/interface'; +export { default as FloatButton, FloatButtonGroup, BackTop } from './float-button'; + export type { FormProps, FormItemProps, FormInstance, FormItemInstance } from './form'; export { default as Form, FormItem, FormItemRest } from './form'; @@ -112,6 +116,7 @@ export type { MenuItemProps, MenuMode, MenuDividerProps, + ItemType, } from './menu'; export { default as Menu, MenuDivider, MenuItem, MenuItemGroup, SubMenu } from './menu'; @@ -178,7 +183,7 @@ export type { SliderProps } from './slider'; export { default as Slider } from './slider'; export type { SpaceProps } from './space'; -export { default as Space } from './space'; +export { default as Space, Compact } from './space'; export type { SpinProps } from './spin'; export { default as Spin } from './spin'; @@ -244,3 +249,15 @@ export type { UploadProps, UploadListProps, UploadChangeParam, UploadFile } from export { default as Upload, UploadDragger } from './upload'; export { default as LocaleProvider } from './locale-provider'; + +export { default as Watermark } from './watermark'; +export type { WatermarkProps } from './watermark'; + +export type { SegmentedProps } from './segmented'; +export { default as Segmented } from './segmented'; + +export type { QRCodeProps } from './qrcode'; +export { default as QRCode } from './qrcode'; + +export type { TourProps, TourStepProps } from './tour'; +export { default as Tour } from './tour'; diff --git a/components/config-provider/DisabledContext.ts b/components/config-provider/DisabledContext.ts new file mode 100644 index 000000000..7f5388661 --- /dev/null +++ b/components/config-provider/DisabledContext.ts @@ -0,0 +1,17 @@ +import type { InjectionKey, Ref } from 'vue'; +import { computed, inject, ref, provide } from 'vue'; + +export type DisabledType = boolean | undefined; +const DisabledContextKey: InjectionKey> = Symbol('DisabledContextKey'); + +export const useInjectDisabled = () => { + return inject(DisabledContextKey, ref(undefined)); +}; +export const useProviderDisabled = (disabled: Ref) => { + const parentDisabled = useInjectDisabled(); + provide( + DisabledContextKey, + computed(() => disabled.value ?? parentDisabled.value), + ); + return disabled; +}; diff --git a/components/config-provider/SizeContext.ts b/components/config-provider/SizeContext.ts new file mode 100644 index 000000000..9fe6c9c65 --- /dev/null +++ b/components/config-provider/SizeContext.ts @@ -0,0 +1,16 @@ +import type { InjectionKey, Ref } from 'vue'; +import { computed, inject, ref, provide } from 'vue'; +export type SizeType = 'small' | 'middle' | 'large' | undefined; +const SizeContextKey: InjectionKey> = Symbol('SizeContextKey'); + +export const useInjectSize = () => { + return inject(SizeContextKey, ref(undefined as SizeType)); +}; +export const useProviderSize = (size: Ref) => { + const parentSize = useInjectSize(); + provide( + SizeContextKey, + computed(() => size.value || parentSize.value), + ); + return size; +}; diff --git a/components/config-provider/context.ts b/components/config-provider/context.ts index f1661a215..aa2626673 100644 --- a/components/config-provider/context.ts +++ b/components/config-provider/context.ts @@ -1,14 +1,24 @@ -import type { ExtractPropTypes, InjectionKey, PropType, Ref } from 'vue'; +import type { ComputedRef, ExtractPropTypes, InjectionKey, PropType, Ref } from 'vue'; import { computed, inject, provide } from 'vue'; import type { ValidateMessages } from '../form/interface'; import type { RequiredMark } from '../form/Form'; import type { RenderEmptyHandler } from './renderEmpty'; import type { TransformCellTextProps } from '../table/interface'; import type { Locale } from '../locale-provider'; +import type { DerivativeFunc } from '../_util/cssinjs'; +import type { AliasToken, SeedToken } from '../theme/internal'; +import type { MapToken, OverrideToken } from '../theme/interface'; +import type { VueNode } from '../_util/type'; +import { objectType } from '../_util/type'; + +export const defaultIconPrefixCls = 'anticon'; type GlobalFormCOntextProps = { validateMessages?: Ref; }; + +export type DirectionType = 'ltr' | 'rtl' | undefined; + export const GlobalFormContextKey: InjectionKey = Symbol('GlobalFormContextKey'); @@ -39,38 +49,20 @@ export type SizeType = 'small' | 'middle' | 'large' | undefined; export type Direction = 'ltr' | 'rtl'; -export interface ConfigConsumerProps { - getTargetContainer?: () => HTMLElement; - getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement; - rootPrefixCls?: string; - getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string; - renderEmpty: RenderEmptyHandler; - transformCellText?: (tableProps: TransformCellTextProps) => any; - csp?: CSPConfig; - autoInsertSpaceInButton?: boolean; - input?: { - autocomplete?: string; - }; - locale?: Locale; - pageHeader?: { - ghost: boolean; - }; - componentSize?: SizeType; - direction?: 'ltr' | 'rtl'; - space?: { - size?: SizeType | number; - }; - virtual?: boolean; - dropdownMatchSelectWidth?: boolean | number; - form?: { - requiredMark?: RequiredMark; - colon?: boolean; - }; +export type MappingAlgorithm = DerivativeFunc; + +export interface ThemeConfig { + token?: Partial; + components?: OverrideToken; + algorithm?: MappingAlgorithm | MappingAlgorithm[]; + hashed?: boolean; + inherit?: boolean; } export const configProviderProps = () => ({ + iconPrefixCls: String, getTargetContainer: { - type: Function as PropType<() => HTMLElement>, + type: Function as PropType<() => HTMLElement | Window>, }, getPopupContainer: { type: Function as PropType<(triggerNode?: HTMLElement) => HTMLElement>, @@ -85,46 +77,90 @@ export const configProviderProps = () => ({ transformCellText: { type: Function as PropType<(tableProps: TransformCellTextProps) => any>, }, - csp: { - type: Object as PropType, - default: undefined as CSPConfig, - }, - input: { - type: Object as PropType<{ autocomplete: string }>, - }, + csp: objectType(), + input: objectType<{ autocomplete?: string }>(), autoInsertSpaceInButton: { type: Boolean, default: undefined }, - locale: { - type: Object as PropType, - default: undefined as Locale, - }, - pageHeader: { - type: Object as PropType<{ ghost: boolean }>, - }, + locale: objectType(), + pageHeader: objectType<{ ghost?: boolean }>(), componentSize: { type: String as PropType, }, + componentDisabled: { type: Boolean, default: undefined }, direction: { type: String as PropType<'ltr' | 'rtl'>, }, - space: { - type: Object as PropType<{ size: SizeType | number }>, - }, + space: objectType<{ size?: SizeType | number }>(), virtual: { type: Boolean, default: undefined }, dropdownMatchSelectWidth: { type: [Number, Boolean], default: true }, - form: { - type: Object as PropType<{ - validateMessages?: ValidateMessages; - requiredMark?: RequiredMark; - colon?: boolean; - }>, - default: undefined as { - validateMessages?: ValidateMessages; - requiredMark?: RequiredMark; - colon?: boolean; - }, - }, - // internal use - notUpdateGlobalConfig: Boolean, + form: objectType<{ + validateMessages?: ValidateMessages; + requiredMark?: RequiredMark; + colon?: boolean; + }>(), + pagination: objectType<{ + showSizeChanger?: boolean; + }>(), + theme: objectType(), + select: objectType<{ + showSearch?: boolean; + }>(), }); export type ConfigProviderProps = Partial>>; + +export interface ConfigProviderInnerProps { + csp?: ComputedRef; + autoInsertSpaceInButton?: ComputedRef; + locale?: ComputedRef; + direction?: ComputedRef<'ltr' | 'rtl'>; + space?: ComputedRef<{ + size?: number | SizeType; + }>; + virtual?: ComputedRef; + dropdownMatchSelectWidth?: ComputedRef; + getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string; + iconPrefixCls: ComputedRef; + theme?: ComputedRef; + renderEmpty?: (name?: string) => VueNode; + getTargetContainer?: ComputedRef<() => HTMLElement | Window>; + getPopupContainer?: ComputedRef<(triggerNode?: HTMLElement) => HTMLElement>; + pageHeader?: ComputedRef<{ + ghost?: boolean; + }>; + input?: ComputedRef<{ + autocomplete?: string; + }>; + pagination?: ComputedRef<{ + showSizeChanger?: boolean; + }>; + form?: ComputedRef<{ + validateMessages?: ValidateMessages; + requiredMark?: RequiredMark; + colon?: boolean; + }>; + select?: ComputedRef<{ + showSearch?: boolean; + }>; + componentSize?: ComputedRef; + componentDisabled?: ComputedRef; + transformCellText?: ComputedRef<(tableProps: TransformCellTextProps) => any>; +} + +export const configProviderKey: InjectionKey = Symbol('configProvider'); + +export const defaultConfigProvider: ConfigProviderInnerProps = { + getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => { + if (customizePrefixCls) return customizePrefixCls; + return suffixCls ? `ant-${suffixCls}` : 'ant'; + }, + iconPrefixCls: computed(() => defaultIconPrefixCls), + getPopupContainer: computed(() => () => document.body), +}; + +export const useConfigContextInject = () => { + return inject(configProviderKey, defaultConfigProvider); +}; + +export const useConfigContextProvider = (props: ConfigProviderInnerProps) => { + return provide(configProviderKey, props); +}; diff --git a/components/config-provider/cssVariables.tsx b/components/config-provider/cssVariables.ts similarity index 86% rename from components/config-provider/cssVariables.tsx rename to components/config-provider/cssVariables.ts index 4d1140e1f..505380ae3 100644 --- a/components/config-provider/cssVariables.tsx +++ b/components/config-provider/cssVariables.ts @@ -5,17 +5,14 @@ import { generate } from '@ant-design/colors'; import type { Theme } from './context'; import { updateCSS } from '../vc-util/Dom/dynamicCSS'; import canUseDom from '../_util/canUseDom'; -import devWarning from '../vc-util/devWarning'; +import warning from '../_util/warning'; const dynamicStyleMark = `-ant-${Date.now()}-${Math.random()}`; -export function registerTheme(globalPrefixCls: string, theme: Theme) { +export function getStyle(globalPrefixCls: string, theme: Theme) { const variables: Record = {}; - const formatColor = ( - color: TinyColor, - updater?: (cloneColor: TinyColor) => TinyColor | undefined, - ) => { + const formatColor = (color: TinyColor, updater?: (cloneColor: TinyColor) => TinyColor) => { let clone = color.clone(); clone = updater?.(clone) || clone; return clone.toRgbString(); @@ -30,8 +27,8 @@ export function registerTheme(globalPrefixCls: string, theme: Theme) { variables[`${type}-color-hover`] = colorPalettes[4]; variables[`${type}-color-active`] = colorPalettes[6]; variables[`${type}-color-outline`] = baseColor.clone().setAlpha(0.2).toRgbString(); - variables[`${type}-color-deprecated-bg`] = colorPalettes[1]; - variables[`${type}-color-deprecated-border`] = colorPalettes[3]; + variables[`${type}-color-deprecated-bg`] = colorPalettes[0]; + variables[`${type}-color-deprecated-border`] = colorPalettes[2]; }; // ================ Primary Color ================ @@ -88,16 +85,19 @@ export function registerTheme(globalPrefixCls: string, theme: Theme) { key => `--${globalPrefixCls}-${key}: ${variables[key]};`, ); - if (canUseDom()) { - updateCSS( - ` + return ` :root { ${cssList.join('\n')} } - `, - `${dynamicStyleMark}-dynamic-theme`, - ); + `.trim(); +} + +export function registerTheme(globalPrefixCls: string, theme: Theme) { + const style = getStyle(globalPrefixCls, theme); + + if (canUseDom()) { + updateCSS(style, `${dynamicStyleMark}-dynamic-theme`); } else { - devWarning(false, 'ConfigProvider', 'SSR do not support dynamic theme with css variables.'); + warning(false, 'ConfigProvider', 'SSR do not support dynamic theme with css variables.'); } } diff --git a/components/config-provider/demo/direction.vue b/components/config-provider/demo/direction.vue index f8564144b..3d5280930 100644 --- a/components/config-provider/demo/direction.vue +++ b/components/config-provider/demo/direction.vue @@ -226,7 +226,7 @@ Components which support rtl direction are listed here, you can toggle the direc Modal example
    Open Modal - +

    نگاشته‌های خود را اینجا قراردهید

    نگاشته‌های خود را اینجا قراردهید

    نگاشته‌های خود را اینجا قراردهید

    @@ -239,17 +239,43 @@ Components which support rtl direction are listed here, you can toggle the direc Steps example
    - - - - - +
    - - - - - +
    @@ -332,8 +358,8 @@ Components which support rtl direction are listed here, you can toggle the direc
    - - +const fileList: UploadFile[] = [ + { + uid: '-1', + name: 'image.png', + status: 'done', + url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }, + { + uid: '-2', + percent: 50, + name: 'image.png', + status: 'uploading', + url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }, + { + uid: '-3', + name: 'image.png', + status: 'error', + }, +]; + +const ref1 = ref(null); +const ref2 = ref(null); +const ref3 = ref(null); +const current = ref(0); +const tourOpen = ref(false); +const steps: TourProps['steps'] = [ + { + title: 'Upload File', + description: 'Put your files here.', + target: () => ref1.value && ref1.value.$el, + }, + { + title: 'Save', + description: 'Save your changes.', + target: () => ref2.value && ref2.value.$el, + }, + { + title: 'Other Actions', + description: 'Click to see other actions.', + target: () => ref3.value && ref3.value.$el, + }, +]; + diff --git a/components/config-provider/demo/size.vue b/components/config-provider/demo/size.vue index 42657e6b6..eea737d23 100644 --- a/components/config-provider/demo/size.vue +++ b/components/config-provider/demo/size.vue @@ -59,40 +59,32 @@ Config component default size.
    - diff --git a/components/config-provider/hooks/useConfigInject.ts b/components/config-provider/hooks/useConfigInject.ts new file mode 100644 index 000000000..a2c3cf4d9 --- /dev/null +++ b/components/config-provider/hooks/useConfigInject.ts @@ -0,0 +1,67 @@ +import { computed, h, inject } from 'vue'; +import type { SizeType } from '../context'; +import { defaultConfigProvider, configProviderKey } from '../context'; +import { useInjectDisabled } from '../DisabledContext'; +import { DefaultRenderEmpty } from '../renderEmpty'; +import { useInjectSize } from '../SizeContext'; +export default (name: string, props: Record) => { + const sizeContext = useInjectSize(); + const disabledContext = useInjectDisabled(); + const configProvider = inject(configProviderKey, { + ...defaultConfigProvider, + renderEmpty: (name?: string) => h(DefaultRenderEmpty, { componentName: name }), + }); + const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls)); + const direction = computed(() => props.direction ?? configProvider.direction?.value); + const iconPrefixCls = computed(() => props.iconPrefixCls ?? configProvider.iconPrefixCls.value); + const rootPrefixCls = computed(() => configProvider.getPrefixCls()); + const autoInsertSpaceInButton = computed(() => configProvider.autoInsertSpaceInButton?.value); + const renderEmpty = configProvider.renderEmpty; + const space = configProvider.space; + const pageHeader = configProvider.pageHeader; + const form = configProvider.form; + const getTargetContainer = computed( + () => props.getTargetContainer ?? configProvider.getTargetContainer?.value, + ); + const getPopupContainer = computed( + () => props.getPopupContainer ?? configProvider.getPopupContainer?.value, + ); + + const dropdownMatchSelectWidth = computed( + () => props.dropdownMatchSelectWidth ?? configProvider.dropdownMatchSelectWidth?.value, + ); + const virtual = computed( + () => + (props.virtual === undefined + ? configProvider.virtual?.value !== false + : props.virtual !== false) && dropdownMatchSelectWidth.value !== false, + ); + const size = computed(() => (props.size as SizeType) || sizeContext.value); + const autocomplete = computed( + () => props.autocomplete ?? configProvider.input?.value?.autocomplete, + ); + const disabled = computed(() => props.disabled ?? disabledContext.value); + const csp = computed(() => props.csp ?? configProvider.csp); + return { + configProvider, + prefixCls, + direction, + size, + getTargetContainer, + getPopupContainer, + space, + pageHeader, + form, + autoInsertSpaceInButton, + renderEmpty, + virtual, + dropdownMatchSelectWidth, + rootPrefixCls, + getPrefixCls: configProvider.getPrefixCls, + autocomplete, + csp, + iconPrefixCls, + disabled, + select: configProvider.select, + }; +}; diff --git a/components/config-provider/hooks/useTheme.ts b/components/config-provider/hooks/useTheme.ts new file mode 100644 index 000000000..0ed451193 --- /dev/null +++ b/components/config-provider/hooks/useTheme.ts @@ -0,0 +1,43 @@ +import type { ThemeConfig } from '../context'; +import { defaultConfig } from '../../theme/internal'; +import type { Ref } from 'vue'; +import { computed } from 'vue'; + +export default function useTheme(theme?: Ref, parentTheme?: Ref) { + const themeConfig = computed(() => theme?.value || {}); + const parentThemeConfig = computed(() => + themeConfig.value.inherit === false || !parentTheme?.value ? defaultConfig : parentTheme.value, + ); + + const mergedTheme = computed(() => { + if (!theme?.value) { + return parentTheme?.value; + } + + // Override + const mergedComponents = { + ...parentThemeConfig.value.components, + }; + + Object.keys(theme.value.components || {}).forEach(componentName => { + mergedComponents[componentName] = { + ...mergedComponents[componentName], + ...theme.value.components![componentName], + } as any; + }); + + // Base token + return { + ...parentThemeConfig.value, + ...themeConfig.value, + + token: { + ...parentThemeConfig.value.token, + ...themeConfig.value.token, + }, + components: mergedComponents, + }; + }); + + return mergedTheme; +} diff --git a/components/config-provider/index.en-US.md b/components/config-provider/index.en-US.md index ddb3ea1a6..36fab4605 100644 --- a/components/config-provider/index.en-US.md +++ b/components/config-provider/index.en-US.md @@ -3,7 +3,8 @@ category: Components type: Other cols: 1 title: ConfigProvider -cover: https://gw.alipayobjects.com/zos/alicdn/kegYxl1wj/ConfigProvider.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*NVKORa7BCVwAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YC4ERpGAddoAAAAAAAAAAAAADrJ8AQ/original --- `ConfigProvider` provides a uniform configuration support for components. @@ -52,7 +53,7 @@ Some components use dynamic style to support wave effect. You can config `csp` p | csp | Set [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config | { nonce: string } | - | | | direction | Set direction of layout. See [demo](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | 3.0 | | dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | - | 3.0 | -| form | Set Form common props | { validateMessages?: [ValidateMessages](/components/form/#validateMessages), requiredMark?: boolean \| `optional` } | - | 3.0 | +| form | Set Form common props | { validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional` } | - | 3.0 | | getPopupContainer | to set the container of the popup element. The default is to create a `div` element in `body`. | Function(triggerNode, dialogContext) | `() => document.body` | | | getTargetContainer | Config Affix, Anchor scroll target container | () => HTMLElement | () => window | 3.0 | | input | Set Input common props | { autocomplete?: string } | - | 3.0 | @@ -107,3 +108,7 @@ When you config `getPopupContainer` to parentNode globally, Modal will throw err ``` + +#### Why can't ConfigProvider props (like `prefixCls` and `theme`) affect VueNode inside `message.info`, `notification.open`, `Modal.confirm`? + +antd will dynamic create Vue instance by `Vue.render` when call message methods. Whose context is different with origin code located context. diff --git a/components/config-provider/index.tsx b/components/config-provider/index.tsx index cfd67e220..506a31092 100644 --- a/components/config-provider/index.tsx +++ b/components/config-provider/index.tsx @@ -1,5 +1,5 @@ -import type { UnwrapRef, App, Plugin, WatchStopHandle } from 'vue'; -import { computed, reactive, provide, defineComponent, watch, watchEffect } from 'vue'; +import type { App, Plugin, WatchStopHandle } from 'vue'; +import { watch, computed, reactive, defineComponent, watchEffect } from 'vue'; import defaultRenderEmpty from './renderEmpty'; import type { RenderEmptyHandler } from './renderEmpty'; import type { Locale } from '../locale-provider'; @@ -11,50 +11,74 @@ import type { MaybeRef } from '../_util/type'; import message from '../message'; import notification from '../notification'; import { registerTheme } from './cssVariables'; -import defaultLocale from '../locale/default'; +import defaultLocale from '../locale/en_US'; import type { ValidateMessages } from '../form/interface'; +import useStyle from './style'; +import useTheme from './hooks/useTheme'; +import defaultSeedToken from '../theme/themes/seed'; +import type { ConfigProviderInnerProps, ConfigProviderProps, Theme } from './context'; +import { + useConfigContextProvider, + useConfigContextInject, + configProviderProps, + useProvideGlobalForm, + defaultIconPrefixCls, +} from './context'; +import { useProviderSize } from './SizeContext'; +import { useProviderDisabled } from './DisabledContext'; +import { createTheme } from '../_util/cssinjs'; +import { DesignTokenProvider } from '../theme/internal'; -import type { ConfigProviderProps, Theme } from './context'; -import { configProviderProps, useProvideGlobalForm } from './context'; - -export type { ConfigProviderProps, Theme, SizeType, Direction, CSPConfig } from './context'; +export type { + ConfigProviderProps, + Theme, + SizeType, + Direction, + CSPConfig, + DirectionType, +} from './context'; export const defaultPrefixCls = 'ant'; +export { defaultIconPrefixCls }; function getGlobalPrefixCls() { return globalConfigForApi.prefixCls || defaultPrefixCls; } -const globalConfigByCom = reactive({}); + +function getGlobalIconPrefixCls() { + return globalConfigForApi.iconPrefixCls || defaultIconPrefixCls; +} const globalConfigBySet = reactive({}); // 权重最大 -export const globalConfigForApi = reactive< - ConfigProviderProps & { - getRootPrefixCls?: (rootPrefixCls?: string, customizePrefixCls?: string) => string; - } ->({}); +export const globalConfigForApi: ConfigProviderProps & { + getRootPrefixCls?: (rootPrefixCls?: string, customizePrefixCls?: string) => string; +} = reactive({}); + +export const configConsumerProps = [ + 'getTargetContainer', + 'getPopupContainer', + 'rootPrefixCls', + 'getPrefixCls', + 'renderEmpty', + 'csp', + 'autoInsertSpaceInButton', + 'locale', + 'pageHeader', +]; watchEffect(() => { - Object.assign(globalConfigForApi, globalConfigByCom, globalConfigBySet); + Object.assign(globalConfigForApi, globalConfigBySet); globalConfigForApi.prefixCls = getGlobalPrefixCls(); + globalConfigForApi.iconPrefixCls = getGlobalIconPrefixCls(); globalConfigForApi.getPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => { if (customizePrefixCls) return customizePrefixCls; return suffixCls ? `${globalConfigForApi.prefixCls}-${suffixCls}` : globalConfigForApi.prefixCls; }; - globalConfigForApi.getRootPrefixCls = (rootPrefixCls?: string, customizePrefixCls?: string) => { - // Customize rootPrefixCls is first priority - if (rootPrefixCls) { - return rootPrefixCls; - } - + globalConfigForApi.getRootPrefixCls = () => { // If Global prefixCls provided, use this if (globalConfigForApi.prefixCls) { return globalConfigForApi.prefixCls; } - // [Legacy] If customize prefixCls provided, we cut it to get the prefixCls - if (customizePrefixCls && customizePrefixCls.includes('-')) { - return customizePrefixCls.replace(/^(.*)-[^-]*$/, '$1'); - } - // Fallback to default prefixCls return getGlobalPrefixCls(); }; @@ -62,6 +86,7 @@ watchEffect(() => { type GlobalConfigProviderProps = { prefixCls?: MaybeRef; + iconPrefixCls?: MaybeRef; getPopupContainer?: ConfigProviderProps['getPopupContainer']; }; @@ -84,22 +109,13 @@ export const globalConfig = () => ({ if (customizePrefixCls) return customizePrefixCls; return suffixCls ? `${getGlobalPrefixCls()}-${suffixCls}` : getGlobalPrefixCls(); }, - getRootPrefixCls: (rootPrefixCls?: string, customizePrefixCls?: string) => { - // Customize rootPrefixCls is first priority - if (rootPrefixCls) { - return rootPrefixCls; - } - + getIconPrefixCls: getGlobalIconPrefixCls, + getRootPrefixCls: () => { // If Global prefixCls provided, use this if (globalConfigForApi.prefixCls) { return globalConfigForApi.prefixCls; } - // [Legacy] If customize prefixCls provided, we cut it to get the prefixCls - if (customizePrefixCls && customizePrefixCls.includes('-')) { - return customizePrefixCls.replace(/^(.*)-[^-]*$/, '$1'); - } - // Fallback to default prefixCls return getGlobalPrefixCls(); }, @@ -111,55 +127,127 @@ const ConfigProvider = defineComponent({ inheritAttrs: false, props: configProviderProps(), setup(props, { slots }) { + const parentContext = useConfigContextInject(); const getPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => { const { prefixCls = 'ant' } = props; if (customizePrefixCls) return customizePrefixCls; - return suffixCls ? `${prefixCls}-${suffixCls}` : prefixCls; + const mergedPrefixCls = prefixCls || parentContext.getPrefixCls(''); + return suffixCls ? `${mergedPrefixCls}-${suffixCls}` : mergedPrefixCls; }; + const iconPrefixCls = computed( + () => props.iconPrefixCls || parentContext.iconPrefixCls.value || defaultIconPrefixCls, + ); + const shouldWrapSSR = computed(() => iconPrefixCls.value !== parentContext.iconPrefixCls.value); + const csp = computed(() => props.csp || parentContext.csp?.value); + + const wrapSSR = useStyle(iconPrefixCls); + const mergedTheme = useTheme( + computed(() => props.theme), + computed(() => parentContext.theme?.value), + ); const renderEmptyComponent = (name?: string) => { const renderEmpty = (props.renderEmpty || slots.renderEmpty || + parentContext.renderEmpty || defaultRenderEmpty) as RenderEmptyHandler; return renderEmpty(name); }; + const autoInsertSpaceInButton = computed( + () => props.autoInsertSpaceInButton ?? parentContext.autoInsertSpaceInButton?.value, + ); + const locale = computed(() => props.locale || parentContext.locale?.value); + watch( + locale, + () => { + globalConfigBySet.locale = locale.value; + }, + { immediate: true }, + ); + const direction = computed(() => props.direction || parentContext.direction?.value); + const space = computed(() => props.space ?? parentContext.space?.value); + const virtual = computed(() => props.virtual ?? parentContext.virtual?.value); + const dropdownMatchSelectWidth = computed( + () => props.dropdownMatchSelectWidth ?? parentContext.dropdownMatchSelectWidth?.value, + ); + const getTargetContainer = computed(() => + props.getTargetContainer !== undefined + ? props.getTargetContainer + : parentContext.getTargetContainer?.value, + ); + const getPopupContainer = computed(() => + props.getPopupContainer !== undefined + ? props.getPopupContainer + : parentContext.getPopupContainer?.value, + ); + const pageHeader = computed(() => + props.pageHeader !== undefined ? props.pageHeader : parentContext.pageHeader?.value, + ); + const input = computed(() => + props.input !== undefined ? props.input : parentContext.input?.value, + ); + const pagination = computed(() => + props.pagination !== undefined ? props.pagination : parentContext.pagination?.value, + ); + const form = computed(() => + props.form !== undefined ? props.form : parentContext.form?.value, + ); + const select = computed(() => + props.select !== undefined ? props.select : parentContext.select?.value, + ); + const componentSize = computed(() => props.componentSize); + const componentDisabled = computed(() => props.componentDisabled); + const configProvider: ConfigProviderInnerProps = { + csp, + autoInsertSpaceInButton, + locale, + direction, + space, + virtual, + dropdownMatchSelectWidth, + getPrefixCls, + iconPrefixCls, + theme: computed(() => { + return mergedTheme.value ?? parentContext.theme?.value; + }), + renderEmpty: renderEmptyComponent, + getTargetContainer, + getPopupContainer, + pageHeader, + input, + pagination, + form, + select, + componentSize, + componentDisabled, + transformCellText: computed(() => props.transformCellText), + }; - const getPrefixClsWrapper = (suffixCls: string, customizePrefixCls?: string) => { - const { prefixCls } = props; - - if (customizePrefixCls) return customizePrefixCls; - - const mergedPrefixCls = prefixCls || getPrefixCls(''); + // ================================ Dynamic theme ================================ + const memoTheme = computed(() => { + const { algorithm, token, ...rest } = mergedTheme.value || {}; + const themeObj = + algorithm && (!Array.isArray(algorithm) || algorithm.length > 0) + ? createTheme(algorithm) + : undefined; - return suffixCls ? `${mergedPrefixCls}-${suffixCls}` : mergedPrefixCls; - }; + return { + ...rest, + theme: themeObj, - const configProvider = reactive({ - ...props, - getPrefixCls: getPrefixClsWrapper, - renderEmpty: renderEmptyComponent, - }); - Object.keys(props).forEach(key => { - watch( - () => props[key], - () => { - configProvider[key] = props[key]; + token: { + ...defaultSeedToken, + ...token, }, - ); + }; }); - if (!props.notUpdateGlobalConfig) { - Object.assign(globalConfigByCom, configProvider); - watch(configProvider, () => { - Object.assign(globalConfigByCom, configProvider); - }); - } const validateMessagesRef = computed(() => { // Additional Form provider let validateMessages: ValidateMessages = {}; - if (props.locale) { + if (locale.value) { validateMessages = - props.locale.Form?.defaultValidateMessages || + locale.value.Form?.defaultValidateMessages || defaultLocale.Form?.defaultValidateMessages || {}; } @@ -168,24 +256,29 @@ const ConfigProvider = defineComponent({ } return validateMessages; }); + useConfigContextProvider(configProvider); useProvideGlobalForm({ validateMessages: validateMessagesRef }); - provide('configProvider', configProvider); + useProviderSize(componentSize); + useProviderDisabled(componentDisabled); const renderProvider = (legacyLocale: Locale) => { + let childNode = shouldWrapSSR.value ? wrapSSR(slots.default?.()) : slots.default?.(); + if (props.theme) + childNode = {childNode}; return ( - - {slots.default?.()} + + {childNode} ); }; watchEffect(() => { - if (props.direction) { + if (direction.value) { message.config({ - rtl: props.direction === 'rtl', + rtl: direction.value === 'rtl', }); notification.config({ - rtl: props.direction === 'rtl', + rtl: direction.value === 'rtl', }); } }); @@ -196,16 +289,8 @@ const ConfigProvider = defineComponent({ }, }); -export const defaultConfigProvider: UnwrapRef = reactive({ - getPrefixCls: (suffixCls: string, customizePrefixCls?: string) => { - if (customizePrefixCls) return customizePrefixCls; - return suffixCls ? `ant-${suffixCls}` : 'ant'; - }, - renderEmpty: defaultRenderEmpty, - direction: 'ltr', -}); - ConfigProvider.config = setGlobalConfig; + ConfigProvider.install = function (app: App) { app.component(ConfigProvider.name, ConfigProvider); }; diff --git a/components/config-provider/index.zh-CN.md b/components/config-provider/index.zh-CN.md index 5515ab00c..8c0ee791f 100644 --- a/components/config-provider/index.zh-CN.md +++ b/components/config-provider/index.zh-CN.md @@ -4,7 +4,8 @@ subtitle: 全局化配置 cols: 1 type: 其他 title: ConfigProvider -cover: https://gw.alipayobjects.com/zos/alicdn/kegYxl1wj/ConfigProvider.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*NVKORa7BCVwAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YC4ERpGAddoAAAAAAAAAAAAADrJ8AQ/original --- 为组件提供统一的全局化配置。 @@ -53,7 +54,7 @@ ConfigProvider 使用 Vue 的 [provide / inject](https://vuejs.org/v2/api/#provi | csp | 设置 [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | | | direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | 3.0 | | dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | | -| form | 设置 Form 组件的通用属性 | { validateMessages?: [ValidateMessages](/components/form/#validateMessages), requiredMark?: boolean \| `optional`, colon?: boolean} | - | 3.0 | +| form | 设置 Form 组件的通用属性 | { validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean} | - | 3.0 | | getPopupContainer | 弹出框(Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | Function(triggerNode, dialogContext) | () => document.body | | | getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 3.0 | | input | 设置 Input 组件的通用属性 | { autocomplete?: string } | - | 3.0 | @@ -108,3 +109,7 @@ ConfigProvider.config({ ``` + +#### 为什么 message.info、notification.open 或 Modal.confirm 等方法内的 VueNode 无法继承 ConfigProvider 的属性?比如 `prefixCls` 和 `theme`。 + +静态方法是使用 Vue.render 重新渲染一个 Vue 根节点上,和主应用的 Vue 节点是脱离的。 diff --git a/components/config-provider/renderEmpty.tsx b/components/config-provider/renderEmpty.tsx index d8c1a5a5b..10a28cd89 100644 --- a/components/config-provider/renderEmpty.tsx +++ b/components/config-provider/renderEmpty.tsx @@ -1,26 +1,24 @@ import Empty from '../empty'; import type { VueNode } from '../_util/type'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from './hooks/useConfigInject'; export interface RenderEmptyProps { componentName?: string; } -const RenderEmpty = (props: RenderEmptyProps) => { +export const DefaultRenderEmpty = (props: RenderEmptyProps) => { const { prefixCls } = useConfigInject('empty', props); const renderHtml = (componentName?: string) => { switch (componentName) { case 'Table': case 'List': return ; - case 'Select': case 'TreeSelect': case 'Cascader': case 'Transfer': case 'Mentions': return ; - default: return ; } @@ -29,7 +27,7 @@ const RenderEmpty = (props: RenderEmptyProps) => { }; function renderEmpty(componentName?: string): VueNode { - return ; + return ; } export type RenderEmptyHandler = typeof renderEmpty; diff --git a/components/config-provider/style/index.less b/components/config-provider/style/index.less deleted file mode 100644 index eaf3a7d1e..000000000 --- a/components/config-provider/style/index.less +++ /dev/null @@ -1,2 +0,0 @@ -// placeholder -@import '../../style/themes/index'; diff --git a/components/config-provider/style/index.ts b/components/config-provider/style/index.ts new file mode 100644 index 000000000..77ed478a0 --- /dev/null +++ b/components/config-provider/style/index.ts @@ -0,0 +1,29 @@ +import { useStyleRegister } from '../../_util/cssinjs'; +import { resetIcon } from '../../style'; +import { useToken } from '../../theme/internal'; +import { computed, Ref } from 'vue'; + +const useStyle = (iconPrefixCls: Ref) => { + const [theme, token] = useToken(); + // Generate style for icons + return useStyleRegister( + computed(() => ({ + theme: theme.value, + token: token.value, + hashId: '', + path: ['ant-design-icons', iconPrefixCls.value], + })), + () => [ + { + [`.${iconPrefixCls.value}`]: { + ...resetIcon(), + [`.${iconPrefixCls.value} .${iconPrefixCls.value}-icon`]: { + display: 'block', + }, + }, + }, + ], + ); +}; + +export default useStyle; diff --git a/components/config-provider/style/index.tsx b/components/config-provider/style/index.tsx deleted file mode 100644 index d74e52ee9..000000000 --- a/components/config-provider/style/index.tsx +++ /dev/null @@ -1 +0,0 @@ -import './index.less'; diff --git a/components/date-picker/__tests__/__snapshots__/DatePicker.test.js.snap b/components/date-picker/__tests__/__snapshots__/DatePicker.test.js.snap index 6c3b99d1f..68a99ce11 100644 --- a/components/date-picker/__tests__/__snapshots__/DatePicker.test.js.snap +++ b/components/date-picker/__tests__/__snapshots__/DatePicker.test.js.snap @@ -2,176 +2,182 @@ exports[`DatePicker prop locale should works 1`] = `
    -
    +
    + +
    +
    @@ -441,8 +572,10 @@ exports[`renders ./components/date-picker/demo/range-picker.vue correctly 1`] =
    -
    +
    + +
    @@ -452,8 +585,10 @@ exports[`renders ./components/date-picker/demo/range-picker.vue correctly 1`] =
    -
    +
    + +
    @@ -463,8 +598,10 @@ exports[`renders ./components/date-picker/demo/range-picker.vue correctly 1`] =
    -
    +
    + +
    @@ -477,8 +614,10 @@ exports[`renders ./components/date-picker/demo/select-in-range.vue correctly 1`]
    -
    +
    + +
    `; @@ -491,18 +630,22 @@ exports[`renders ./components/date-picker/demo/size.vue correctly 1`] = `
    -
    +
    +
    +
    -
    +
    +
    +
    @@ -512,17 +655,21 @@ exports[`renders ./components/date-picker/demo/size.vue correctly 1`] = `
    -
    +
    + +
    -
    +
    +
    +
    @@ -534,18 +681,75 @@ exports[`renders ./components/date-picker/demo/start-end.vue correctly 1`] = `
    -
    +
    +
    +
    -
    +
    +
    +
    + +
    +
    + +
    +`; + +exports[`renders ./components/date-picker/demo/status.vue correctly 1`] = ` +
    +
    +
    +
    + + +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    @@ -557,18 +761,22 @@ exports[`renders ./components/date-picker/demo/suffix.vue correctly 1`] = `
    -
    +
    +
    +
    -
    +
    +
    +
    @@ -578,17 +786,21 @@ exports[`renders ./components/date-picker/demo/suffix.vue correctly 1`] = `
    -
    +
    + +
    -
    +
    +
    +
    @@ -598,6 +810,7 @@ exports[`renders ./components/date-picker/demo/suffix.vue correctly 1`] = `
    ab
    +
    @@ -607,6 +820,7 @@ exports[`renders ./components/date-picker/demo/suffix.vue correctly 1`] = `
    ab
    +
    @@ -618,6 +832,7 @@ exports[`renders ./components/date-picker/demo/suffix.vue correctly 1`] = `
    ab +
    @@ -627,6 +842,7 @@ exports[`renders ./components/date-picker/demo/suffix.vue correctly 1`] = `
    ab
    +
    @@ -638,20 +854,23 @@ exports[`renders ./components/date-picker/demo/switchable.vue correctly 1`] = `
    -
    Time -
    +
    +
    -
    +
    +
    +
    @@ -663,9 +882,11 @@ exports[`renders ./components/date-picker/demo/text.vue correctly 1`] = `
    -
    +
    +
    +
    @@ -675,8 +896,10 @@ exports[`renders ./components/date-picker/demo/text.vue correctly 1`] = `
    -
    +
    + +
    @@ -688,9 +911,11 @@ exports[`renders ./components/date-picker/demo/time.vue correctly 1`] = `
    -
    +
    +
    +
    @@ -700,8 +925,10 @@ exports[`renders ./components/date-picker/demo/time.vue correctly 1`] = `
    -
    +
    + +
    diff --git a/components/date-picker/__tests__/__snapshots__/other.test.js.snap b/components/date-picker/__tests__/__snapshots__/other.test.js.snap index 2c5b62eb5..7e172d5d5 100644 --- a/components/date-picker/__tests__/__snapshots__/other.test.js.snap +++ b/components/date-picker/__tests__/__snapshots__/other.test.js.snap @@ -6,1072 +6,40 @@ exports[`MonthPicker and WeekPicker render WeekPicker 1`] = `""`; exports[`Picker format by locale date 1`] = `
    -
    -
    - - +
    +
    +
    +
    `; exports[`Picker format by locale dateTime 1`] = `
    -
    -
    - - +
    +
    +
    +
    `; exports[`Picker format by locale month 1`] = `
    -
    -
    - - +
    +
    +
    +
    `; exports[`Picker format by locale week 1`] = `
    -
    -
    - - +
    +
    +
    +
    `; diff --git a/components/date-picker/__tests__/other.test.js b/components/date-picker/__tests__/other.test.js index 4ec54f861..e88b7f5ab 100644 --- a/components/date-picker/__tests__/other.test.js +++ b/components/date-picker/__tests__/other.test.js @@ -3,7 +3,7 @@ import { asyncExpect, sleep } from '../../../tests/utils'; import dayjs from 'dayjs'; import DatePicker from '../'; import LocaleProvider from '../../locale-provider'; -import locale from '../../locale-provider/zh_CN'; +import locale from '../../locale/zh_CN'; jest.mock('../../_util/Portal'); const { MonthPicker, WeekPicker } = DatePicker; diff --git a/components/date-picker/demo/basic.vue b/components/date-picker/demo/basic.vue index f51e4b7d2..50bef7996 100644 --- a/components/date-picker/demo/basic.vue +++ b/components/date-picker/demo/basic.vue @@ -25,18 +25,12 @@ Basic use case. Users can select or input a date in panel. - diff --git a/components/date-picker/demo/bordered.vue b/components/date-picker/demo/bordered.vue index 65a6e1be7..3ea29877c 100644 --- a/components/date-picker/demo/bordered.vue +++ b/components/date-picker/demo/bordered.vue @@ -30,22 +30,16 @@ Bordered-less style component. - diff --git a/components/date-picker/demo/date-render.vue b/components/date-picker/demo/date-render.vue index 939cc1a73..9ff0dbeaf 100644 --- a/components/date-picker/demo/date-render.vue +++ b/components/date-picker/demo/date-render.vue @@ -33,28 +33,20 @@ We can customize the rendering of date cells in the calendar by providing a `dat - diff --git a/components/date-picker/demo/disabled-date.vue b/components/date-picker/demo/disabled-date.vue index 7f21b5359..18dea0e56 100644 --- a/components/date-picker/demo/disabled-date.vue +++ b/components/date-picker/demo/disabled-date.vue @@ -40,59 +40,49 @@ Disabled part of dates and time by `disabledDate` and `disabledTime` respectivel /> - diff --git a/components/date-picker/demo/disabled.vue b/components/date-picker/demo/disabled.vue index 9e14f783f..467c5cf71 100644 --- a/components/date-picker/demo/disabled.vue +++ b/components/date-picker/demo/disabled.vue @@ -24,24 +24,18 @@ A disabled state of the `DatePicker`. - diff --git a/components/date-picker/demo/format.vue b/components/date-picker/demo/format.vue index 9aef11c15..2b0e8035f 100644 --- a/components/date-picker/demo/format.vue +++ b/components/date-picker/demo/format.vue @@ -26,36 +26,26 @@ We can set the date format by `format`. - diff --git a/components/date-picker/demo/index.vue b/components/date-picker/demo/index.vue index 3bfbe8052..92808d6ba 100644 --- a/components/date-picker/demo/index.vue +++ b/components/date-picker/demo/index.vue @@ -15,6 +15,8 @@ + + diff --git a/components/date-picker/demo/placement.vue b/components/date-picker/demo/placement.vue new file mode 100644 index 000000000..aca354acb --- /dev/null +++ b/components/date-picker/demo/placement.vue @@ -0,0 +1,39 @@ + +--- +order: 28 +title: + zh-CN: 弹出位置 + en-US: Popup Placement +--- + +## zh-CN + +可以通过 `placement` 手动指定弹出的位置。 + +## en-US + +You can manually specify the position of the popup via `placement`. + + + + + diff --git a/components/date-picker/demo/presetted-ranges.vue b/components/date-picker/demo/presetted-ranges.vue index a825bebb5..1e27a39c3 100644 --- a/components/date-picker/demo/presetted-ranges.vue +++ b/components/date-picker/demo/presetted-ranges.vue @@ -18,30 +18,47 @@ We can set presetted ranges to RangePicker to improve user experience. - diff --git a/components/date-picker/demo/range-picker.vue b/components/date-picker/demo/range-picker.vue index 68f25e67d..df1e2f127 100644 --- a/components/date-picker/demo/range-picker.vue +++ b/components/date-picker/demo/range-picker.vue @@ -25,19 +25,13 @@ Set range picker type by `picker` prop. - diff --git a/components/date-picker/demo/select-in-range.vue b/components/date-picker/demo/select-in-range.vue index be5203974..e784ca47c 100644 --- a/components/date-picker/demo/select-in-range.vue +++ b/components/date-picker/demo/select-in-range.vue @@ -24,51 +24,37 @@ A example shows how to select a dynamic range by using `onCalendarChange` and `d @calendarChange="onCalendarChange" /> - diff --git a/components/date-picker/demo/size.vue b/components/date-picker/demo/size.vue index 120591f1a..0b17f6e59 100644 --- a/components/date-picker/demo/size.vue +++ b/components/date-picker/demo/size.vue @@ -29,13 +29,7 @@ The input box comes in three sizes. `default` will be used if `size` is omitted. - diff --git a/components/date-picker/demo/start-end.vue b/components/date-picker/demo/start-end.vue index f1fe901db..88296fe41 100644 --- a/components/date-picker/demo/start-end.vue +++ b/components/date-picker/demo/start-end.vue @@ -41,58 +41,44 @@ When `RangePicker` does not satisfied your requirements, try to implement simila /> - diff --git a/components/date-picker/demo/status.vue b/components/date-picker/demo/status.vue new file mode 100644 index 000000000..de11cbe42 --- /dev/null +++ b/components/date-picker/demo/status.vue @@ -0,0 +1,27 @@ + +--- +order: 19 +version: 3.3.0 +title: + zh-CN: 自定义状态 + en-US: Status +--- + +## zh-CN + +使用 `status` 为 DatePicker 添加状态,可选 `error` 或者 `warning`。 + +## en-US + +Add status to DatePicker with `status`, which could be `error` or `warning`. + + + + diff --git a/components/date-picker/demo/suffix.vue b/components/date-picker/demo/suffix.vue index 42ed119bf..f4fbfd735 100644 --- a/components/date-picker/demo/suffix.vue +++ b/components/date-picker/demo/suffix.vue @@ -44,26 +44,14 @@ Customize the suffix icon through `suffixIcon` - diff --git a/components/date-picker/demo/switchable.vue b/components/date-picker/demo/switchable.vue index b396de918..5c2f04f2a 100644 --- a/components/date-picker/demo/switchable.vue +++ b/components/date-picker/demo/switchable.vue @@ -32,13 +32,7 @@ Switch in different types of pickers by Select. - diff --git a/components/date-picker/demo/text.vue b/components/date-picker/demo/text.vue index 30baf83b7..e58db6af5 100644 --- a/components/date-picker/demo/text.vue +++ b/components/date-picker/demo/text.vue @@ -28,28 +28,13 @@ Added custom rendering function, in the default `slot`, you can set any componen - diff --git a/components/date-picker/demo/time.vue b/components/date-picker/demo/time.vue index 41f64c098..f91dc0361 100644 --- a/components/date-picker/demo/time.vue +++ b/components/date-picker/demo/time.vue @@ -28,35 +28,24 @@ This property provide an additional time selection. When `showTime` is an Object /> - diff --git a/components/date-picker/generatePicker/generateRangePicker.tsx b/components/date-picker/generatePicker/generateRangePicker.tsx index a14ec2617..a321706c3 100644 --- a/components/date-picker/generatePicker/generateRangePicker.tsx +++ b/components/date-picker/generatePicker/generateRangePicker.tsx @@ -6,18 +6,23 @@ import { RangePicker as VCRangePicker } from '../../vc-picker'; import type { GenerateConfig } from '../../vc-picker/generate/index'; import enUS from '../locale/en_US'; import { useLocaleReceiver } from '../../locale-provider/LocaleReceiver'; -import { getRangePlaceholder } from '../util'; +import { getRangePlaceholder, transPlacement2DropdownAlign } from '../util'; import { getTimeProps, Components } from '.'; import { computed, defineComponent, ref } from 'vue'; -import useConfigInject from '../../_util/hooks/useConfigInject'; +import useConfigInject from '../../config-provider/hooks/useConfigInject'; import classNames from '../../_util/classNames'; import type { CommonProps, RangePickerProps } from './props'; import { commonProps, rangePickerProps } from './props'; import type { PanelMode, RangeValue } from '../../vc-picker/interface'; import type { RangePickerSharedProps } from '../../vc-picker/RangePicker'; -import devWarning from '../../vc-util/devWarning'; -import { useInjectFormItemContext } from '../../form/FormItemContext'; +import { FormItemInputContext, useInjectFormItemContext } from '../../form/FormItemContext'; import omit from '../../_util/omit'; +import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils'; + +//CSSINJS +import useStyle from '../style'; +import { useCompactItemContext } from '../../space/Compact'; +import devWarning from '../../vc-util/devWarning'; import type { CustomSlotsType } from '../../_util/type'; export default function generateRangePicker( @@ -48,15 +53,28 @@ export default function generateRangePicker( setup(_props, { expose, slots, attrs, emit }) { const props = _props as unknown as CommonProps & RangePickerProps; const formItemContext = useInjectFormItemContext(); - devWarning( - !attrs.getCalendarContainer, - 'DatePicker', - '`getCalendarContainer` is deprecated. Please use `getPopupContainer"` instead.', - ); - const { prefixCls, direction, getPopupContainer, size, rootPrefixCls } = useConfigInject( - 'picker', - props, - ); + const formItemInputContext = FormItemInputContext.useInject(); + + // =================== Warning ===================== + if (process.env.NODE_ENV !== 'production') { + devWarning( + !props.dropdownClassName, + 'RangePicker', + '`dropdownClassName` is deprecated. Please use `popupClassName` instead.', + ); + devWarning( + !attrs.getCalendarContainer, + 'DatePicker', + '`getCalendarContainer` is deprecated. Please use `getPopupContainer"` instead.', + ); + } + + const { prefixCls, direction, getPopupContainer, size, rootPrefixCls, disabled } = + useConfigInject('picker', props); + const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); + const mergedSize = computed(() => compactSize.value || size.value); + // style + const [wrapSSR, hashId] = useStyle(prefixCls); const pickerRef = ref(); expose({ focus: () => { @@ -159,7 +177,13 @@ export default function generateRangePicker( : {}), }; const pre = prefixCls.value; - return ( + const suffixNode = ( + <> + {suffixIcon || (picker === 'time' ? : )} + {formItemInputContext.hasFeedback && formItemInputContext.feedbackIcon} + + ); + return wrapSSR( ( ) } ref={pickerRef} - placeholder={getRangePlaceholder(picker, locale, placeholder as [string, string])} - suffixIcon={ - suffixIcon || (picker === 'time' ? : ) - } + dropdownAlign={transPlacement2DropdownAlign(direction.value, props.placement)} + placeholder={getRangePlaceholder(locale, picker, placeholder as [string, string])} + suffixIcon={suffixNode} clearIcon={clearIcon || } allowClear={allowClear} transitionName={transitionName || `${rootPrefixCls.value}-slide-up`} {...restProps} {...additionalOverrideProps} + disabled={disabled.value} id={id} value={value.value} defaultValue={defaultValue.value} @@ -187,10 +211,17 @@ export default function generateRangePicker( picker={picker} class={classNames( { - [`${pre}-${size.value}`]: size.value, + [`${pre}-${mergedSize.value}`]: mergedSize.value, [`${pre}-borderless`]: !bordered, }, + getStatusClassNames( + pre, + getMergedStatus(formItemInputContext.status, props.status), + formItemInputContext.hasFeedback, + ), attrs.class, + hashId.value, + compactItemClassnames.value, )} locale={locale!.lang} prefixCls={pre} @@ -202,6 +233,11 @@ export default function generateRangePicker( superNextIcon={slots.superNextIcon?.() || } components={Components} direction={direction.value} + dropdownClassName={classNames( + hashId.value, + props.popupClassName, + props.dropdownClassName, + )} onChange={onChange} onOpenChange={onOpenChange} onFocus={onFocus} @@ -209,7 +245,7 @@ export default function generateRangePicker( onPanelChange={onPanelChange} onOk={onOk} onCalendarChange={onCalendarChange} - /> + />, ); }; }, diff --git a/components/date-picker/generatePicker/generateSinglePicker.tsx b/components/date-picker/generatePicker/generateSinglePicker.tsx index 712c925e9..f20fbb260 100644 --- a/components/date-picker/generatePicker/generateSinglePicker.tsx +++ b/components/date-picker/generatePicker/generateSinglePicker.tsx @@ -5,18 +5,22 @@ import RCPicker from '../../vc-picker'; import type { PanelMode, PickerMode } from '../../vc-picker/interface'; import type { GenerateConfig } from '../../vc-picker/generate/index'; import enUS from '../locale/en_US'; -import { getPlaceholder } from '../util'; +import { getPlaceholder, transPlacement2DropdownAlign } from '../util'; import { useLocaleReceiver } from '../../locale-provider/LocaleReceiver'; import { getTimeProps, Components } from '.'; import { computed, defineComponent, ref } from 'vue'; -import useConfigInject from '../../_util/hooks/useConfigInject'; +import useConfigInject from '../../config-provider/hooks/useConfigInject'; import classNames from '../../_util/classNames'; import type { CommonProps, DatePickerProps } from './props'; import { commonProps, datePickerProps } from './props'; import devWarning from '../../vc-util/devWarning'; -import { useInjectFormItemContext } from '../../form/FormItemContext'; +import { FormItemInputContext, useInjectFormItemContext } from '../../form/FormItemContext'; +import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils'; +import { useCompactItemContext } from '../../space/Compact'; import type { CustomSlotsType } from '../../_util/type'; +//CSSINJS +import useStyle from '../style'; export default function generateSinglePicker( generateConfig: GenerateConfig, @@ -52,21 +56,40 @@ export default function generateSinglePicker( DatePickerProps & ExtraProps; const formItemContext = useInjectFormItemContext(); - devWarning( - !(props.monthCellContentRender || slots.monthCellContentRender), - 'DatePicker', - '`monthCellContentRender` is deprecated. Please use `monthCellRender"` instead.', - ); + const formItemInputContext = FormItemInputContext.useInject(); + // =================== Warning ===================== + if (process.env.NODE_ENV !== 'production') { + devWarning( + picker !== 'quarter', + displayName || 'DatePicker', + `DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`, + ); + + devWarning( + !props.dropdownClassName, + displayName || 'DatePicker', + '`dropdownClassName` is deprecated. Please use `popupClassName` instead.', + ); + devWarning( + !(props.monthCellContentRender || slots.monthCellContentRender), + displayName || 'DatePicker', + '`monthCellContentRender` is deprecated. Please use `monthCellRender"` instead.', + ); + + devWarning( + !attrs.getCalendarContainer, + displayName || 'DatePicker', + '`getCalendarContainer` is deprecated. Please use `getPopupContainer"` instead.', + ); + } + + const { prefixCls, direction, getPopupContainer, size, rootPrefixCls, disabled } = + useConfigInject('picker', props); + const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); + const mergedSize = computed(() => compactSize.value || size.value); + // style + const [wrapSSR, hashId] = useStyle(prefixCls); - devWarning( - !attrs.getCalendarContainer, - 'DatePicker', - '`getCalendarContainer` is deprecated. Please use `getPopupContainer"` instead.', - ); - const { prefixCls, direction, getPopupContainer, size, rootPrefixCls } = useConfigInject( - 'picker', - props, - ); const pickerRef = ref(); expose({ focus: () => { @@ -179,17 +202,21 @@ export default function generateSinglePicker( : {}), }; const pre = prefixCls.value; - return ( + const suffixNode = ( + <> + {suffixIcon || (picker === 'time' ? : )} + {formItemInputContext.hasFeedback && formItemInputContext.feedbackIcon} + + ); + return wrapSSR( : ) - } + placeholder={getPlaceholder(locale, mergedPicker, placeholder)} + suffixIcon={suffixNode} + dropdownAlign={transPlacement2DropdownAlign(direction.value, props.placement)} clearIcon={clearIcon || } allowClear={allowClear} transitionName={transitionName || `${rootPrefixCls.value}-slide-up`} @@ -204,11 +231,19 @@ export default function generateSinglePicker( locale={locale!.lang} class={classNames( { - [`${pre}-${size.value}`]: size.value, + [`${pre}-${mergedSize.value}`]: mergedSize.value, [`${pre}-borderless`]: !bordered, }, + getStatusClassNames( + pre, + getMergedStatus(formItemInputContext.status, props.status), + formItemInputContext.hasFeedback, + ), attrs.class, + hashId.value, + compactItemClassnames.value, )} + disabled={disabled.value} prefixCls={pre} getPopupContainer={attrs.getCalendarContainer || getPopupContainer.value} generateConfig={generateConfig} @@ -218,13 +253,18 @@ export default function generateSinglePicker( superNextIcon={slots.superNextIcon?.() || } components={Components} direction={direction.value} + dropdownClassName={classNames( + hashId.value, + props.popupClassName, + props.dropdownClassName, + )} onChange={onChange} onOpenChange={onOpenChange} onFocus={onFocus} onBlur={onBlur} onPanelChange={onPanelChange} onOk={onOk} - /> + />, ); }; }, diff --git a/components/date-picker/generatePicker/index.tsx b/components/date-picker/generatePicker/index.tsx index 68fa3d7db..8f9fc9be6 100644 --- a/components/date-picker/generatePicker/index.tsx +++ b/components/date-picker/generatePicker/index.tsx @@ -17,13 +17,18 @@ function toArray(list: T | T[]): T[] { return Array.isArray(list) ? list : [list]; } -export function getTimeProps( - props: { format?: string; picker?: PickerMode } & SharedTimeProps, +export function getTimeProps( + props: { format?: string; picker?: PickerMode } & Omit< + SharedTimeProps, + 'disabledTime' + > & { + disabledTime?: DisabledTime; + }, ) { const { format, picker, showHour, showMinute, showSecond, use12Hours } = props; const firstFormat = toArray(format)[0]; - const showTimeObj: SharedTimeProps = { ...props }; + const showTimeObj = { ...props }; if (firstFormat && typeof firstFormat === 'string') { if (!firstFormat.includes('s') && showSecond === undefined) { diff --git a/components/date-picker/generatePicker/props.ts b/components/date-picker/generatePicker/props.ts index 27fd3caa3..1af653de5 100644 --- a/components/date-picker/generatePicker/props.ts +++ b/components/date-picker/generatePicker/props.ts @@ -1,9 +1,9 @@ import type { FocusEventHandler, MouseEventHandler } from '../../_util/EventInterface'; -import type { CSSProperties, PropType } from 'vue'; +import type { CSSProperties } from 'vue'; import type { PickerLocale } from '.'; import type { SizeType } from '../../config-provider'; -import type { AlignType } from '../../vc-align/interface'; import type { + PresetDate, CustomFormat, DisabledTime, DisabledTimes, @@ -17,28 +17,48 @@ import type { MonthCellRender } from '../../vc-picker/panels/MonthPanel/MonthBod import type { SharedTimeProps } from '../../vc-picker/panels/TimePanel'; import type { RangeDateRender, RangeInfo, RangeType } from '../../vc-picker/RangePicker'; import type { VueNode } from '../../_util/type'; +import { + stringType, + arrayType, + someType, + booleanType, + objectType, + functionType, +} from '../../_util/type'; +import type { InputStatus } from '../../_util/statusUtils'; + +const DataPickerPlacements = ['bottomLeft', 'bottomRight', 'topLeft', 'topRight'] as const; +type DataPickerPlacement = (typeof DataPickerPlacements)[number]; + +type RangeShowTimeObject = Omit, 'defaultValue'> & { + defaultValue?: DateType[]; +}; function commonProps() { return { id: String, + /** + * @deprecated `dropdownClassName` is deprecated which will be removed in next major + * version.Please use `popupClassName` instead. + */ dropdownClassName: String, - dropdownAlign: { type: Object as PropType }, - popupStyle: { type: Object as PropType }, + popupClassName: String, + popupStyle: objectType(), transitionName: String, placeholder: String, - allowClear: { type: Boolean, default: undefined }, - autofocus: { type: Boolean, default: undefined }, - disabled: { type: Boolean, default: undefined }, + allowClear: booleanType(), + autofocus: booleanType(), + disabled: booleanType(), tabindex: Number, - open: { type: Boolean, default: undefined }, - defaultOpen: { type: Boolean, default: undefined }, + open: booleanType(), + defaultOpen: booleanType(), /** Make input readOnly to avoid popup keyboard in mobile */ - inputReadOnly: { type: Boolean, default: undefined }, - format: { - type: [String, Function, Array] as PropType< - string | CustomFormat | (string | CustomFormat)[] - >, - }, + inputReadOnly: booleanType(), + format: someType | (string | CustomFormat)[]>([ + String, + Function, + Array, + ]), // Value // format: string | CustomFormat | (string | CustomFormat)[]; // Render @@ -48,60 +68,60 @@ function commonProps() { // nextIcon?: VueNode; // superPrevIcon?: VueNode; // superNextIcon?: VueNode; - getPopupContainer: { type: Function as PropType<(node: HTMLElement) => HTMLElement> }, - panelRender: { type: Function as PropType<(originPanel: VueNode) => VueNode> }, + getPopupContainer: functionType<(node: HTMLElement) => HTMLElement>(), + panelRender: functionType<(originPanel: VueNode) => VueNode>(), // // Events - onChange: { - type: Function as PropType<(value: DateType | string | null, dateString: string) => void>, - }, - 'onUpdate:value': { type: Function as PropType<(value: DateType | string | null) => void> }, - onOk: { type: Function as PropType<(value: DateType | string | null) => void> }, - onOpenChange: { type: Function as PropType<(open: boolean) => void> }, - 'onUpdate:open': { type: Function as PropType<(open: boolean) => void> }, - onFocus: { type: Function as PropType }, - onBlur: { type: Function as PropType }, - onMousedown: { type: Function as PropType }, - onMouseup: { type: Function as PropType }, - onMouseenter: { type: Function as PropType }, - onMouseleave: { type: Function as PropType }, - onClick: { type: Function as PropType }, - onContextmenu: { type: Function as PropType }, - onKeydown: { - type: Function as PropType<(event: KeyboardEvent, preventDefault: () => void) => void>, - }, + onChange: functionType<(value: DateType | string | null, dateString: string) => void>(), + 'onUpdate:value': functionType<(value: DateType | string | null) => void>(), + onOk: functionType<(value: DateType | string | null) => void>(), + onOpenChange: functionType<(open: boolean) => void>(), + 'onUpdate:open': functionType<(open: boolean) => void>(), + onFocus: functionType(), + onBlur: functionType(), + onMousedown: functionType(), + onMouseup: functionType(), + onMouseenter: functionType(), + onMouseleave: functionType(), + onClick: functionType(), + onContextmenu: functionType(), + onKeydown: functionType<(event: KeyboardEvent, preventDefault: () => void) => void>(), // WAI-ARIA role: String, name: String, autocomplete: String, - direction: { type: String as PropType<'ltr' | 'rtl'> }, - showToday: { type: Boolean, default: undefined }, - showTime: { - type: [Boolean, Object] as PropType>, - default: undefined, - }, - locale: { type: Object as PropType }, - size: { type: String as PropType }, - bordered: { type: Boolean, default: undefined }, - dateRender: { type: Function as PropType> }, - disabledDate: { type: Function as PropType<(date: DateType) => boolean> }, - mode: { type: String as PropType }, - picker: { type: String as PropType }, + direction: stringType<'ltr' | 'rtl'>(), + showToday: booleanType(), + showTime: someType>([Boolean, Object]), + locale: objectType(), + size: stringType(), + bordered: booleanType(), + dateRender: functionType>(), + disabledDate: functionType<(date: DateType) => boolean>(), + mode: stringType(), + picker: stringType(), valueFormat: String, + placement: stringType(), + status: stringType(), /** @deprecated Please use `disabledTime` instead. */ - disabledHours: Function as PropType, + disabledHours: functionType(), /** @deprecated Please use `disabledTime` instead. */ - disabledMinutes: Function as PropType, + disabledMinutes: functionType(), /** @deprecated Please use `disabledTime` instead. */ - disabledSeconds: Function as PropType, + disabledSeconds: functionType(), }; } export interface CommonProps { id?: string; prefixCls?: string; + /** + * @deprecated `dropdownClassName` is deprecated which will be removed in next major + * version.Please use `popupClassName` instead. + */ + dropdownClassName?: string; - dropdownAlign?: AlignType; + popupClassName?: string; popupStyle?: CSSProperties; transitionName?: string; placeholder?: string; @@ -149,19 +169,22 @@ export interface CommonProps { mode?: PanelMode; picker?: PickerMode; valueFormat?: string; + placement?: DataPickerPlacement; + status?: InputStatus; } function datePickerProps() { return { - defaultPickerValue: { type: [String, Object] as PropType }, - defaultValue: { type: [String, Object] as PropType }, - value: { type: [String, Object] as PropType }, - disabledTime: { type: Function as PropType> }, - renderExtraFooter: { type: Function as PropType<(mode: PanelMode) => VueNode> }, - showNow: { type: Boolean, default: undefined }, - monthCellRender: { type: Function as PropType> }, + defaultPickerValue: someType([Object, String]), + defaultValue: someType([Object, String]), + value: someType([Object, String]), + presets: arrayType[]>(), + disabledTime: functionType>(), + renderExtraFooter: functionType<(mode: PanelMode) => VueNode>(), + showNow: booleanType(), + monthCellRender: functionType>(), // deprecated Please use `monthCellRender"` instead.', - monthCellContentRender: { type: Function as PropType> }, + monthCellContentRender: functionType>(), }; } @@ -169,6 +192,7 @@ export interface DatePickerProps { defaultPickerValue?: DateType | string; defaultValue?: DateType | string; value?: DateType | string; + presets?: PresetDate[]; disabledTime?: DisabledTime; renderExtraFooter?: (mode: PanelMode) => VueNode; showNow?: boolean; @@ -179,57 +203,48 @@ export interface DatePickerProps { function rangePickerProps() { return { - allowEmpty: { type: Array as unknown as PropType<[boolean, boolean]> }, - dateRender: { type: Function as PropType> }, - defaultPickerValue: { - type: Array as unknown as PropType | RangeValue>, - }, - defaultValue: { type: Array as unknown as PropType | RangeValue> }, - value: { type: Array as unknown as PropType | RangeValue> }, - disabledTime: { - type: Function as PropType<(date: EventValue, type: RangeType) => DisabledTimes>, - }, - disabled: { type: [Boolean, Array] as unknown as PropType }, - renderExtraFooter: { type: Function as PropType<() => VueNode> }, + allowEmpty: arrayType<[boolean, boolean]>(), + dateRender: functionType>(), + defaultPickerValue: arrayType | RangeValue>(), + defaultValue: arrayType | RangeValue>(), + value: arrayType | RangeValue>(), + presets: arrayType>[]>(), + disabledTime: functionType<(date: EventValue, type: RangeType) => DisabledTimes>(), + disabled: someType([Boolean, Array]), + renderExtraFooter: functionType<() => VueNode>(), separator: { type: String }, - ranges: { - type: Object as PropType< + showTime: someType>([Boolean, Object]), + ranges: + objectType< Record< string, Exclude, null> | (() => Exclude, null>) > - >, - }, - placeholder: Array, - mode: { type: Array as unknown as PropType<[PanelMode, PanelMode]> }, - onChange: { - type: Function as PropType< + >(), + placeholder: arrayType(), + mode: arrayType<[PanelMode, PanelMode]>(), + onChange: + functionType< ( value: RangeValue | RangeValue | null, dateString: [string, string], ) => void - >, - }, - 'onUpdate:value': { - type: Function as PropType<(value: RangeValue | RangeValue | null) => void>, - }, - onCalendarChange: { - type: Function as PropType< + >(), + 'onUpdate:value': + functionType<(value: RangeValue | RangeValue | null) => void>(), + onCalendarChange: + functionType< ( values: RangeValue | RangeValue, formatString: [string, string], info: RangeInfo, ) => void - >, - }, - onPanelChange: { - type: Function as PropType< + >(), + onPanelChange: + functionType< (values: RangeValue | RangeValue, modes: [PanelMode, PanelMode]) => void - >, - }, - onOk: { - type: Function as PropType<(dates: RangeValue | RangeValue) => void>, - }, + >(), + onOk: functionType<(dates: RangeValue | RangeValue) => void>(), }; } @@ -239,10 +254,12 @@ export interface RangePickerProps { defaultPickerValue?: RangeValue | RangeValue; defaultValue?: RangeValue | RangeValue; value?: RangeValue | RangeValue; + presets?: PresetDate>[]; disabledTime?: (date: EventValue, type: RangeType) => DisabledTimes; disabled?: [boolean, boolean]; renderExtraFooter?: () => VueNode; separator?: string; + showTime?: boolean | RangeShowTimeObject; ranges?: Record< string, Exclude, null> | (() => Exclude, null>) diff --git a/components/date-picker/index.en-US.md b/components/date-picker/index.en-US.md index 376643a67..393bb9914 100644 --- a/components/date-picker/index.en-US.md +++ b/components/date-picker/index.en-US.md @@ -2,7 +2,8 @@ category: Components type: Data Entry title: DatePicker -cover: https://gw.alipayobjects.com/zos/alicdn/RT_USzA48/DatePicker.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*xXA9TJ8BTioAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3OpRQKcygo8AAAAAAAAAAAAADrJ8AQ/original --- To select or input a date. @@ -83,7 +84,7 @@ The following APIs are shared by DatePicker, RangePicker. | dateRender | Custom rendering function for date cells | v-slot:dateRender="{current, today}" | - | | | disabled | Determine whether the DatePicker is disabled | boolean | false | | | disabledDate | Specify the date that cannot be selected | (currentDate: dayjs) => boolean | - | | -| format | To set the date format, refer to [dayjs](https://day.js.org/). When an array is provided, all values are used for parsing and first value is used for formatting, support [Custom Format](#components-date-picker-demo-format) | [formatType](#formatType) | `YYYY-MM-DD` | | +| format | To set the date format, refer to [dayjs](https://day.js.org/). When an array is provided, all values are used for parsing and first value is used for formatting, support [Custom Format](#components-date-picker-demo-format) | [formatType](#formattype) | `YYYY-MM-DD` | | | dropdownClassName | To customize the className of the popup calendar | string | - | | | getPopupContainer | To set the container of the floating layer, while the default is to create a `div` element in `body` | function(trigger) | - | | | inputReadOnly | Set the `readonly` attribute of the input tag (avoids virtual keyboard on touch devices) | boolean | false | | @@ -93,9 +94,12 @@ The following APIs are shared by DatePicker, RangePicker. | open | The open state of picker | boolean | - | | | picker | Set picker type | `date` \| `week` \| `month` \| `quarter` \| `year` | `date` | `quarter` | | placeholder | The placeholder of date input | string \| \[string,string] | - | | +| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | 3.3.0 | | popupStyle | To customize the style of the popup calendar | CSSProperties | {} | | +| presets | The preset ranges for quick selection | { label: slot, value: [dayjs](https://day.js.org/) }[] | - | 4.0 | | prevIcon | The custom prev icon | slot | - | 3.0 | | size | To determine the size of the input box, the height of `large` and `small`, are 40px and 24px respectively, while default size is 32px | `large` \| `middle` \| `small` | - | | +| status | Set validation status | 'error' \| 'warning' | - | 3.3.0 | | suffixIcon | The custom suffix icon | v-slot:suffixIcon | - | | | superNextIcon | The custom super next icon | slot | - | 3.0 | | superPrevIcon | The custom super prev icon | slot | - | 3.0 | @@ -121,10 +125,10 @@ The following APIs are shared by DatePicker, RangePicker. | --- | --- | --- | --- | --- | | defaultPickerValue | To set default picker date | [dayjs](https://day.js.org/) | - | | | disabledTime | To specify the time that cannot be selected | function(date) | - | | -| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formatType) | `YYYY-MM-DD` | | +| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formattype) | `YYYY-MM-DD` | | | renderExtraFooter | Render extra footer in panel | v-slot:renderExtraFooter="mode" | - | | | showNow | Whether to show 'Now' button on panel when `showTime` is set | boolean | - | | -| showTime | To provide an additional time selection | object \| boolean | [TimePicker Options](/components/time-picker/#API) | | +| showTime | To provide an additional time selection | object \| boolean | [TimePicker Options](/components/time-picker/#api) | | | showTime.defaultValue | To set default time of selected date, [demo](#components-date-picker-demo-disabled-date) | [dayjs](https://day.js.org/) | dayjs() | | | showToday | Whether to show `Today` button | boolean | true | | | value(v-model) | To set date | [dayjs](https://day.js.org/) | - | | @@ -140,26 +144,26 @@ The following APIs are shared by DatePicker, RangePicker. | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formatType) | `YYYY` | | +| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formattype) | `YYYY` | | ### DatePicker\[picker=quarter] | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formatType) | `YYYY-\QQ` | | +| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formattype) | `YYYY-\QQ` | | ### DatePicker\[picker=month] | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formatType) | `YYYY-MM` | | +| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formattype) | `YYYY-MM` | | | monthCellRender | Custom month cell content render method | v-slot:monthCellRender="{current, locale}" | - | | ### DatePicker\[picker=week] | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formatType) | `YYYY-wo` | | +| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formattype) | `YYYY-wo` | | ### RangePicker @@ -170,11 +174,12 @@ The following APIs are shared by DatePicker, RangePicker. | defaultPickerValue | To set default picker date | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | | | disabled | If disable start or end | \[boolean, boolean] | - | | | disabledTime | To specify the time that cannot be selected | function(date: dayjs, partial: `start` \| `end`) | - | | -| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formatType) | `YYYY-MM-DD HH:mm:ss` | | +| format | To set the date format, refer to [dayjs](https://day.js.org/) | [formatType](#formattype) | `YYYY-MM-DD HH:mm:ss` | | +| presets | The preset ranges for quick selection | { label: slot, value: [dayjs](https://day.js.org/)\[] }[] | - | 4.0 | | ranges | The preseted ranges for quick selection | { \[range: string]: [dayjs](https://day.js.org/)\[] } \| { \[range: string]: () => [dayjs](https://day.js.org/)\[] } | - | | | renderExtraFooter | Render extra footer in panel | v-slot:renderExtraFooter="mode" | - | | | separator | Set separator between inputs | string \| v-slot:separator | `` | | -| showTime | To provide an additional time selection | object \| boolean | [TimePicker Options](/components/time-picker/#API) | | +| showTime | To provide an additional time selection | object \| boolean | [TimePicker Options](/components/time-picker/#api) | | | showTime.defaultValue | To set default time of selected date, [demo](#components-date-picker-demo-disabled-date) | [dayjs](https://day.js.org/)\[] | \[dayjs(), dayjs()] | | | value(v-model) | To set date | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | | diff --git a/components/date-picker/index.zh-CN.md b/components/date-picker/index.zh-CN.md index a4ddbbf9a..8aa5fdcbd 100644 --- a/components/date-picker/index.zh-CN.md +++ b/components/date-picker/index.zh-CN.md @@ -3,7 +3,8 @@ category: Components type: 数据录入 title: DatePicker subtitle: 日期选择框 -cover: https://gw.alipayobjects.com/zos/alicdn/RT_USzA48/DatePicker.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*xXA9TJ8BTioAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3OpRQKcygo8AAAAAAAAAAAAADrJ8AQ/original --- 输入或选择日期的控件。 @@ -84,7 +85,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/RT_USzA48/DatePicker.svg | dateRender | 自定义日期单元格的内容 | v-slot:dateRender="{current, today}" | - | | | disabled | 禁用 | boolean | false | | | disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | | -| format | 设置日期格式,为数组时支持多格式匹配,展示以第一个为准。配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format),支持[自定义格式](#components-date-picker-demo-format) | [formatType](#formatType) | `YYYY-MM-DD` | | +| format | 设置日期格式,为数组时支持多格式匹配,展示以第一个为准。配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format),支持[自定义格式](#components-date-picker-demo-format) | [formatType](#formattype) | `YYYY-MM-DD` | | | dropdownClassName | 额外的弹出日历 className | string | - | | | getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | - | | | inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | | @@ -94,9 +95,12 @@ cover: https://gw.alipayobjects.com/zos/alicdn/RT_USzA48/DatePicker.svg | open | 控制弹层是否展开 | boolean | - | | | picker | 设置选择器类型 | `date` \| `week` \| `month` \| `quarter` \| `year` | `date` | `quarter` | | placeholder | 输入框提示文字 | string \| \[string, string] | - | | +| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | 3.3.0 | | popupStyle | 额外的弹出日历样式 | CSSProperties | {} | | | prevIcon | 自定义上一个图标 | slot | - | 3.0 | +| presets | 预设时间范围快捷选择 | { label: slot, value: [dayjs](https://day.js.org/) }[] | - | 4.0 | | size | 输入框大小,`large` 高度为 40px,`small` 为 24px,默认是 32px | `large` \| `middle` \| `small` | - | | +| status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 | | suffixIcon | 自定义的选择框后缀图标 | v-slot:suffixIcon | - | | | superNextIcon | 自定义 `<<` 切换图标 | slot | - | 3.0 | | superPrevIcon | 自定义 `>>` 切换图标 | slot | - | 3.0 | @@ -122,10 +126,10 @@ cover: https://gw.alipayobjects.com/zos/alicdn/RT_USzA48/DatePicker.svg | --- | --- | --- | --- | --- | | defaultPickerValue | 默认面板日期 | [dayjs](https://day.js.org/) | - | | | disabledTime | 不可选择的时间 | function(date) | - | | -| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formatType) | `YYYY-MM-DD` | | +| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formattype) | `YYYY-MM-DD` | | | renderExtraFooter | 在面板中添加额外的页脚 | v-slot:renderExtraFooter="mode" | - | | | showNow | 当设定了 `showTime` 的时候,面板是否显示“此刻”按钮 | boolean | - | | -| showTime | 增加时间选择功能 | Object \| boolean | [TimePicker Options](/components/time-picker/#API) | | +| showTime | 增加时间选择功能 | Object \| boolean | [TimePicker Options](/components/time-picker/#api) | | | showTime.defaultValue | 设置用户选择日期时默认的时分秒,[例子](#components-date-picker-demo-disabled-date) | [dayjs](https://day.js.org/) | dayjs() | | | showToday | 是否展示“今天”按钮 | boolean | true | | | value(v-model) | 日期 | [dayjs](https://day.js.org/) | - | | @@ -141,26 +145,26 @@ cover: https://gw.alipayobjects.com/zos/alicdn/RT_USzA48/DatePicker.svg | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formatType) | `YYYY` | | +| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formattype) | `YYYY` | | ### DatePicker\[picker=quarter] | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formatType) | `YYYY-\QQ` | | +| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formattype) | `YYYY-\QQ` | | ### DatePicker\[picker=month] | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formatType) | `YYYY-MM` | | +| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formattype) | `YYYY-MM` | | | monthCellRender | 自定义的月份内容渲染方法 | v-slot:monthCellRender="{current, locale}" | - | | ### DatePicker\[picker=week] | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formatType) | `YYYY-wo` | | +| format | 展示的日期格式,配置参考 [dayjs](https://day.js.org/docs/zh-CN/display/format) | [formatType](#formattype) | `YYYY-wo` | | ### RangePicker @@ -171,11 +175,12 @@ cover: https://gw.alipayobjects.com/zos/alicdn/RT_USzA48/DatePicker.svg | defaultPickerValue | 默认面板日期 | [dayjs](https://day.js.org/)\[] | - | | | disabled | 禁用起始项 | \[boolean, boolean] | - | | | disabledTime | 不可选择的时间 | function(date: dayjs, partial: `start` \| `end`) | - | | -| format | 展示的日期格式 | [formatType](#formatType) | `YYYY-MM-DD HH:mm:ss` | | +| format | 展示的日期格式 | [formatType](#formattype) | `YYYY-MM-DD HH:mm:ss` | | +| presets | 预设时间范围快捷选择 | { label: slot, value: [dayjs](https://day.js.org/)\[] }[] | - | 4.0 | | ranges | 预设时间范围快捷选择 | { \[range: string]: [dayjs](https://day.js.org/)\[] } \| { \[range: string]: () => [dayjs](https://day.js.org/)\[] } | - | | | renderExtraFooter | 在面板中添加额外的页脚 | v-slot:renderExtraFooter="mode" | - | | | separator | 设置分隔符 | string \| v-slot:separator | `` | | -| showTime | 增加时间选择功能 | Object\|boolean | [TimePicker Options](/components/time-picker/#API) | | +| showTime | 增加时间选择功能 | Object\|boolean | [TimePicker Options](/components/time-picker/#api) | | | showTime.defaultValue | 设置用户选择日期时默认的时分秒,[例子](#components-date-picker-demo-disabled-date) | [dayjs](https://day.js.org/)\[] | \[dayjs(), dayjs()] | | | value(v-model) | 日期 | [dayjs](https://day.js.org/)\[] | - | | diff --git a/components/date-picker/locale/ar_EG.tsx b/components/date-picker/locale/ar_EG.ts similarity index 100% rename from components/date-picker/locale/ar_EG.tsx rename to components/date-picker/locale/ar_EG.ts diff --git a/components/date-picker/locale/az_AZ.tsx b/components/date-picker/locale/az_AZ.ts similarity index 100% rename from components/date-picker/locale/az_AZ.tsx rename to components/date-picker/locale/az_AZ.ts diff --git a/components/date-picker/locale/bg_BG.tsx b/components/date-picker/locale/bg_BG.ts similarity index 100% rename from components/date-picker/locale/bg_BG.tsx rename to components/date-picker/locale/bg_BG.ts diff --git a/components/date-picker/locale/bn_BD.tsx b/components/date-picker/locale/bn_BD.ts similarity index 100% rename from components/date-picker/locale/bn_BD.tsx rename to components/date-picker/locale/bn_BD.ts diff --git a/components/date-picker/locale/by_BY.tsx b/components/date-picker/locale/by_BY.ts similarity index 100% rename from components/date-picker/locale/by_BY.tsx rename to components/date-picker/locale/by_BY.ts diff --git a/components/date-picker/locale/ca_ES.tsx b/components/date-picker/locale/ca_ES.ts similarity index 100% rename from components/date-picker/locale/ca_ES.tsx rename to components/date-picker/locale/ca_ES.ts diff --git a/components/date-picker/locale/cs_CZ.tsx b/components/date-picker/locale/cs_CZ.ts similarity index 100% rename from components/date-picker/locale/cs_CZ.tsx rename to components/date-picker/locale/cs_CZ.ts diff --git a/components/date-picker/locale/da_DK.tsx b/components/date-picker/locale/da_DK.ts similarity index 100% rename from components/date-picker/locale/da_DK.tsx rename to components/date-picker/locale/da_DK.ts diff --git a/components/date-picker/locale/de_DE.tsx b/components/date-picker/locale/de_DE.ts similarity index 100% rename from components/date-picker/locale/de_DE.tsx rename to components/date-picker/locale/de_DE.ts diff --git a/components/date-picker/locale/el_GR.tsx b/components/date-picker/locale/el_GR.ts similarity index 100% rename from components/date-picker/locale/el_GR.tsx rename to components/date-picker/locale/el_GR.ts diff --git a/components/date-picker/locale/en_GB.tsx b/components/date-picker/locale/en_GB.ts similarity index 100% rename from components/date-picker/locale/en_GB.tsx rename to components/date-picker/locale/en_GB.ts diff --git a/components/date-picker/locale/en_US.tsx b/components/date-picker/locale/en_US.ts similarity index 100% rename from components/date-picker/locale/en_US.tsx rename to components/date-picker/locale/en_US.ts diff --git a/components/date-picker/locale/es_ES.tsx b/components/date-picker/locale/es_ES.ts similarity index 100% rename from components/date-picker/locale/es_ES.tsx rename to components/date-picker/locale/es_ES.ts diff --git a/components/date-picker/locale/et_EE.tsx b/components/date-picker/locale/et_EE.ts similarity index 100% rename from components/date-picker/locale/et_EE.tsx rename to components/date-picker/locale/et_EE.ts diff --git a/components/date-picker/locale/fa_IR.tsx b/components/date-picker/locale/fa_IR.ts similarity index 100% rename from components/date-picker/locale/fa_IR.tsx rename to components/date-picker/locale/fa_IR.ts diff --git a/components/date-picker/locale/fi_FI.tsx b/components/date-picker/locale/fi_FI.ts similarity index 100% rename from components/date-picker/locale/fi_FI.tsx rename to components/date-picker/locale/fi_FI.ts diff --git a/components/date-picker/locale/fr_BE.tsx b/components/date-picker/locale/fr_BE.ts similarity index 100% rename from components/date-picker/locale/fr_BE.tsx rename to components/date-picker/locale/fr_BE.ts diff --git a/components/date-picker/locale/fr_CA.tsx b/components/date-picker/locale/fr_CA.ts similarity index 100% rename from components/date-picker/locale/fr_CA.tsx rename to components/date-picker/locale/fr_CA.ts diff --git a/components/date-picker/locale/fr_FR.tsx b/components/date-picker/locale/fr_FR.ts similarity index 100% rename from components/date-picker/locale/fr_FR.tsx rename to components/date-picker/locale/fr_FR.ts diff --git a/components/date-picker/locale/ga_IE.tsx b/components/date-picker/locale/ga_IE.ts similarity index 100% rename from components/date-picker/locale/ga_IE.tsx rename to components/date-picker/locale/ga_IE.ts diff --git a/components/date-picker/locale/gl_ES.tsx b/components/date-picker/locale/gl_ES.ts similarity index 100% rename from components/date-picker/locale/gl_ES.tsx rename to components/date-picker/locale/gl_ES.ts diff --git a/components/date-picker/locale/he_IL.tsx b/components/date-picker/locale/he_IL.ts similarity index 100% rename from components/date-picker/locale/he_IL.tsx rename to components/date-picker/locale/he_IL.ts diff --git a/components/date-picker/locale/hi_IN.tsx b/components/date-picker/locale/hi_IN.ts similarity index 100% rename from components/date-picker/locale/hi_IN.tsx rename to components/date-picker/locale/hi_IN.ts diff --git a/components/date-picker/locale/hr_HR.tsx b/components/date-picker/locale/hr_HR.ts similarity index 100% rename from components/date-picker/locale/hr_HR.tsx rename to components/date-picker/locale/hr_HR.ts diff --git a/components/date-picker/locale/hu_HU.tsx b/components/date-picker/locale/hu_HU.ts similarity index 100% rename from components/date-picker/locale/hu_HU.tsx rename to components/date-picker/locale/hu_HU.ts diff --git a/components/date-picker/locale/id_ID.tsx b/components/date-picker/locale/id_ID.ts similarity index 100% rename from components/date-picker/locale/id_ID.tsx rename to components/date-picker/locale/id_ID.ts diff --git a/components/date-picker/locale/is_IS.tsx b/components/date-picker/locale/is_IS.ts similarity index 100% rename from components/date-picker/locale/is_IS.tsx rename to components/date-picker/locale/is_IS.ts diff --git a/components/date-picker/locale/it_IT.tsx b/components/date-picker/locale/it_IT.ts similarity index 100% rename from components/date-picker/locale/it_IT.tsx rename to components/date-picker/locale/it_IT.ts diff --git a/components/date-picker/locale/ja_JP.tsx b/components/date-picker/locale/ja_JP.ts similarity index 100% rename from components/date-picker/locale/ja_JP.tsx rename to components/date-picker/locale/ja_JP.ts diff --git a/components/date-picker/locale/ka_GE.tsx b/components/date-picker/locale/ka_GE.ts similarity index 100% rename from components/date-picker/locale/ka_GE.tsx rename to components/date-picker/locale/ka_GE.ts diff --git a/components/date-picker/locale/kk_KZ.tsx b/components/date-picker/locale/kk_KZ.ts similarity index 100% rename from components/date-picker/locale/kk_KZ.tsx rename to components/date-picker/locale/kk_KZ.ts diff --git a/components/date-picker/locale/km_KH.tsx b/components/date-picker/locale/km_KH.ts similarity index 100% rename from components/date-picker/locale/km_KH.tsx rename to components/date-picker/locale/km_KH.ts diff --git a/components/date-picker/locale/kmr_IQ.tsx b/components/date-picker/locale/kmr_IQ.ts similarity index 100% rename from components/date-picker/locale/kmr_IQ.tsx rename to components/date-picker/locale/kmr_IQ.ts diff --git a/components/date-picker/locale/kn_IN.tsx b/components/date-picker/locale/kn_IN.ts similarity index 100% rename from components/date-picker/locale/kn_IN.tsx rename to components/date-picker/locale/kn_IN.ts diff --git a/components/date-picker/locale/ko_KR.tsx b/components/date-picker/locale/ko_KR.ts similarity index 100% rename from components/date-picker/locale/ko_KR.tsx rename to components/date-picker/locale/ko_KR.ts diff --git a/components/date-picker/locale/lt_LT.tsx b/components/date-picker/locale/lt_LT.ts similarity index 100% rename from components/date-picker/locale/lt_LT.tsx rename to components/date-picker/locale/lt_LT.ts diff --git a/components/date-picker/locale/lv_LV.tsx b/components/date-picker/locale/lv_LV.ts similarity index 100% rename from components/date-picker/locale/lv_LV.tsx rename to components/date-picker/locale/lv_LV.ts diff --git a/components/date-picker/locale/mk_MK.tsx b/components/date-picker/locale/mk_MK.ts similarity index 100% rename from components/date-picker/locale/mk_MK.tsx rename to components/date-picker/locale/mk_MK.ts diff --git a/components/date-picker/locale/ml_IN.tsx b/components/date-picker/locale/ml_IN.ts similarity index 100% rename from components/date-picker/locale/ml_IN.tsx rename to components/date-picker/locale/ml_IN.ts diff --git a/components/date-picker/locale/mn_MN.tsx b/components/date-picker/locale/mn_MN.ts similarity index 100% rename from components/date-picker/locale/mn_MN.tsx rename to components/date-picker/locale/mn_MN.ts diff --git a/components/date-picker/locale/ms_MY.tsx b/components/date-picker/locale/ms_MY.ts similarity index 100% rename from components/date-picker/locale/ms_MY.tsx rename to components/date-picker/locale/ms_MY.ts diff --git a/components/date-picker/locale/nb_NO.tsx b/components/date-picker/locale/nb_NO.ts similarity index 100% rename from components/date-picker/locale/nb_NO.tsx rename to components/date-picker/locale/nb_NO.ts diff --git a/components/date-picker/locale/nl_BE.tsx b/components/date-picker/locale/nl_BE.ts similarity index 100% rename from components/date-picker/locale/nl_BE.tsx rename to components/date-picker/locale/nl_BE.ts diff --git a/components/date-picker/locale/nl_NL.tsx b/components/date-picker/locale/nl_NL.ts similarity index 100% rename from components/date-picker/locale/nl_NL.tsx rename to components/date-picker/locale/nl_NL.ts diff --git a/components/date-picker/locale/pl_PL.tsx b/components/date-picker/locale/pl_PL.ts similarity index 100% rename from components/date-picker/locale/pl_PL.tsx rename to components/date-picker/locale/pl_PL.ts diff --git a/components/date-picker/locale/pt_BR.tsx b/components/date-picker/locale/pt_BR.ts similarity index 100% rename from components/date-picker/locale/pt_BR.tsx rename to components/date-picker/locale/pt_BR.ts diff --git a/components/date-picker/locale/pt_PT.tsx b/components/date-picker/locale/pt_PT.ts similarity index 100% rename from components/date-picker/locale/pt_PT.tsx rename to components/date-picker/locale/pt_PT.ts diff --git a/components/date-picker/locale/ro_RO.tsx b/components/date-picker/locale/ro_RO.ts similarity index 100% rename from components/date-picker/locale/ro_RO.tsx rename to components/date-picker/locale/ro_RO.ts diff --git a/components/date-picker/locale/ru_RU.tsx b/components/date-picker/locale/ru_RU.ts similarity index 100% rename from components/date-picker/locale/ru_RU.tsx rename to components/date-picker/locale/ru_RU.ts diff --git a/components/date-picker/locale/sk_SK.tsx b/components/date-picker/locale/sk_SK.ts similarity index 100% rename from components/date-picker/locale/sk_SK.tsx rename to components/date-picker/locale/sk_SK.ts diff --git a/components/date-picker/locale/sl_SI.tsx b/components/date-picker/locale/sl_SI.ts similarity index 100% rename from components/date-picker/locale/sl_SI.tsx rename to components/date-picker/locale/sl_SI.ts diff --git a/components/date-picker/locale/sr_RS.tsx b/components/date-picker/locale/sr_RS.ts similarity index 100% rename from components/date-picker/locale/sr_RS.tsx rename to components/date-picker/locale/sr_RS.ts diff --git a/components/date-picker/locale/sv_SE.tsx b/components/date-picker/locale/sv_SE.ts similarity index 100% rename from components/date-picker/locale/sv_SE.tsx rename to components/date-picker/locale/sv_SE.ts diff --git a/components/date-picker/locale/ta_IN.tsx b/components/date-picker/locale/ta_IN.ts similarity index 100% rename from components/date-picker/locale/ta_IN.tsx rename to components/date-picker/locale/ta_IN.ts diff --git a/components/date-picker/locale/th_TH.tsx b/components/date-picker/locale/th_TH.ts similarity index 100% rename from components/date-picker/locale/th_TH.tsx rename to components/date-picker/locale/th_TH.ts diff --git a/components/date-picker/locale/tr_TR.tsx b/components/date-picker/locale/tr_TR.ts similarity index 100% rename from components/date-picker/locale/tr_TR.tsx rename to components/date-picker/locale/tr_TR.ts diff --git a/components/date-picker/locale/uk_UA.tsx b/components/date-picker/locale/uk_UA.ts similarity index 100% rename from components/date-picker/locale/uk_UA.tsx rename to components/date-picker/locale/uk_UA.ts diff --git a/components/date-picker/locale/ur_PK.tsx b/components/date-picker/locale/ur_PK.ts similarity index 100% rename from components/date-picker/locale/ur_PK.tsx rename to components/date-picker/locale/ur_PK.ts diff --git a/components/date-picker/locale/vi_VN.tsx b/components/date-picker/locale/vi_VN.ts similarity index 100% rename from components/date-picker/locale/vi_VN.tsx rename to components/date-picker/locale/vi_VN.ts diff --git a/components/date-picker/locale/zh_CN.tsx b/components/date-picker/locale/zh_CN.ts similarity index 100% rename from components/date-picker/locale/zh_CN.tsx rename to components/date-picker/locale/zh_CN.ts diff --git a/components/date-picker/locale/zh_TW.tsx b/components/date-picker/locale/zh_TW.ts similarity index 100% rename from components/date-picker/locale/zh_TW.tsx rename to components/date-picker/locale/zh_TW.ts diff --git a/components/date-picker/style/index.less b/components/date-picker/style/index.less deleted file mode 100644 index 4fc64c197..000000000 --- a/components/date-picker/style/index.less +++ /dev/null @@ -1,362 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; -@import '../../input/style/mixin'; - -@picker-prefix-cls: ~'@{ant-prefix}-picker'; - -.picker-padding(@input-height, @font-size, @padding-horizontal) { - // font height probably 22.0001, So use floor better - @font-height: floor(@font-size * @line-height-base) + 2; - @padding-top: max(((@input-height - @font-height) / 2), 0); - @padding-bottom: max(@input-height - @font-height - @padding-top, 0); - padding: @padding-top @padding-horizontal @padding-bottom; -} - -.@{picker-prefix-cls} { - @arrow-size: 10px; - - .reset-component(); - .picker-padding(@input-height-base, @font-size-base, @input-padding-horizontal-base); - position: relative; - display: inline-flex; - align-items: center; - background: @picker-bg; - border: @border-width-base @border-style-base @select-border-color; - border-radius: @border-radius-base; - transition: border @animation-duration-slow, box-shadow @animation-duration-slow; - - &:hover, - &-focused { - .hover(); - } - - &-focused { - .active(); - } - - &&-disabled { - background: @input-disabled-bg; - border-color: @select-border-color; - cursor: not-allowed; - } - - &&-disabled &-suffix { - color: @disabled-color; - } - - &&-borderless { - background-color: transparent !important; - border-color: transparent !important; - box-shadow: none !important; - } - - // ======================== Input ========================= - &-input { - position: relative; - display: inline-flex; - align-items: center; - width: 100%; - - > input { - .input(); - flex: auto; - - // Fix Firefox flex not correct: - // https://github.com/ant-design/ant-design/pull/20023#issuecomment-564389553 - min-width: 1px; - height: auto; - padding: 0; - background: transparent; - - border: 0; - - &:focus { - box-shadow: none; - } - - &[disabled] { - background: transparent; - } - } - - &:hover { - .@{picker-prefix-cls}-clear { - opacity: 1; - } - } - - &-placeholder { - > input { - color: @input-placeholder-color; - } - } - } - - // Size - &-large { - .picker-padding(@input-height-lg, @font-size-lg, @input-padding-horizontal-lg); - - .@{picker-prefix-cls}-input > input { - font-size: @font-size-lg; - } - } - - &-small { - .picker-padding(@input-height-sm, @font-size-base, @input-padding-horizontal-sm); - } - - &-suffix { - align-self: center; - margin-left: (@padding-xs / 2); - color: @disabled-color; - line-height: 1; - pointer-events: none; - - > * { - vertical-align: top; - } - } - - &-clear { - position: absolute; - top: 50%; - right: 0; - color: @disabled-color; - line-height: 1; - background: @component-background; - transform: translateY(-50%); - cursor: pointer; - opacity: 0; - transition: opacity @animation-duration-slow, color @animation-duration-slow; - - > * { - vertical-align: top; - } - - &:hover { - color: @text-color-secondary; - } - } - - &-separator { - position: relative; - display: inline-block; - width: 1em; - height: @font-size-lg; - color: @disabled-color; - font-size: @font-size-lg; - vertical-align: top; - cursor: default; - - .@{picker-prefix-cls}-focused & { - color: @text-color-secondary; - } - - .@{picker-prefix-cls}-range-separator & { - .@{picker-prefix-cls}-disabled & { - cursor: not-allowed; - } - } - } - - // ======================== Range ========================= - &-range { - position: relative; - display: inline-flex; - - // Clear - .@{picker-prefix-cls}-clear { - right: @input-padding-horizontal-base; - } - - &:hover { - .@{picker-prefix-cls}-clear { - opacity: 1; - } - } - - // Active bar - .@{picker-prefix-cls}-active-bar { - bottom: -@border-width-base; - height: 2px; - margin-left: @input-padding-horizontal-base; - background: @primary-color; - opacity: 0; - transition: all @animation-duration-slow ease-out; - pointer-events: none; - } - - &.@{picker-prefix-cls}-focused { - .@{picker-prefix-cls}-active-bar { - opacity: 1; - } - } - - &-separator { - align-items: center; - padding: 0 @padding-xs; - line-height: 1; - } - - &.@{picker-prefix-cls}-small { - .@{picker-prefix-cls}-clear { - right: @input-padding-horizontal-sm; - } - - .@{picker-prefix-cls}-active-bar { - margin-left: @input-padding-horizontal-sm; - } - } - } - - // ======================= Dropdown ======================= - &-dropdown { - .reset-component(); - position: absolute; - z-index: @zindex-picker; - - &-hidden { - display: none; - } - - &-placement-bottomLeft { - .@{picker-prefix-cls}-range-arrow { - top: (@arrow-size / 2) - (@arrow-size / 3); - display: block; - transform: rotate(-45deg); - } - } - - &-placement-topLeft { - .@{picker-prefix-cls}-range-arrow { - bottom: (@arrow-size / 2) - (@arrow-size / 3); - display: block; - transform: rotate(135deg); - } - } - - &.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft, - &.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topRight, - &.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft, - &.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topRight { - animation-name: antSlideDownIn; - } - - &.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft, - &.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomRight, - &.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft, - &.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomRight { - animation-name: antSlideUpIn; - } - - &.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft, - &.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topRight { - animation-name: antSlideDownOut; - } - - &.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomLeft, - &.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomRight { - animation-name: antSlideUpOut; - } - } - - &-dropdown-range { - padding: (@arrow-size * 2 / 3) 0; - - &-hidden { - display: none; - } - } - - // Time picker with additional style - &-dropdown &-panel > &-time-panel { - padding-top: (@padding-xs / 2); - } - - // ======================== Ranges ======================== - &-ranges { - margin-bottom: 0; - padding: (@padding-xs / 2) @padding-sm; - overflow: hidden; - line-height: @picker-text-height - 2 * @border-width-base - (@padding-xs / 2); - text-align: left; - list-style: none; - - > li { - display: inline-block; - } - - // https://github.com/ant-design/ant-design/issues/23687 - .@{picker-prefix-cls}-preset > .@{ant-prefix}-tag-blue { - color: @primary-color; - background: @primary-1; - border-color: @primary-3; - cursor: pointer; - } - - .@{picker-prefix-cls}-ok { - float: right; - margin-left: @padding-xs; - } - } - - &-range-wrapper { - display: flex; - } - - &-range-arrow { - position: absolute; - z-index: 1; - display: none; - width: @arrow-size; - height: @arrow-size; - margin-left: @input-padding-horizontal-base * 1.5; - box-shadow: 2px -2px 6px fade(@black, 6%); - transition: left @animation-duration-slow ease-out; - - &::after { - position: absolute; - top: @border-width-base; - right: @border-width-base; - width: @arrow-size; - height: @arrow-size; - border: (@arrow-size / 2) solid @border-color-split; - border-color: @calendar-bg @calendar-bg transparent transparent; - content: ''; - } - } - - &-panel-container { - overflow: hidden; - vertical-align: top; - background: @calendar-bg; - border-radius: @border-radius-base; - box-shadow: @box-shadow-base; - transition: margin @animation-duration-slow; - - .@{picker-prefix-cls}-panels { - display: inline-flex; - flex-wrap: nowrap; - direction: ltr; - } - - .@{picker-prefix-cls}-panel { - vertical-align: top; - background: transparent; - border-width: 0 0 @border-width-base 0; - border-radius: 0; - - .@{picker-prefix-cls}-content, - table { - text-align: center; - } - - &-focused { - border-color: @border-color-split; - } - } - } -} - -@import './panel'; -@import './rtl'; diff --git a/components/date-picker/style/index.ts b/components/date-picker/style/index.ts new file mode 100644 index 000000000..4e92852fd --- /dev/null +++ b/components/date-picker/style/index.ts @@ -0,0 +1,1450 @@ +import type { CSSObject } from '../../_util/cssinjs'; +import { TinyColor } from '@ctrl/tinycolor'; +import type { InputToken } from '../../input/style'; +import { + genActiveStyle, + genBasicInputStyle, + genHoverStyle, + initInputToken, +} from '../../input/style'; +import { + initSlideMotion, + initMoveMotion, + slideDownIn, + slideDownOut, + slideUpIn, + slideUpOut, +} from '../../style/motion'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import type { GlobalToken } from '../../theme/interface'; +import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'; +import { resetComponent, roundedArrow, textEllipsis } from '../../style'; +import { genCompactItemStyle } from '../../style/compact-item'; + +export interface ComponentToken { + presetsWidth: number; + presetsMaxWidth: number; + zIndexPopup: number; +} + +export type PickerPanelToken = { + pickerCellCls: string; + pickerCellInnerCls: string; + pickerTextHeight: number; + pickerPanelCellWidth: number; + pickerPanelCellHeight: number; + pickerDateHoverRangeBorderColor: string; + pickerBasicCellHoverWithRangeColor: string; + pickerPanelWithoutTimeCellHeight: number; + pickerYearMonthCellWidth: number; + pickerTimePanelColumnHeight: number; + pickerTimePanelColumnWidth: number; + pickerTimePanelCellHeight: number; + pickerCellPaddingVertical: number; + pickerQuarterPanelContentHeight: number; + pickerCellBorderGap: number; + pickerControlIconSize: number; + pickerControlIconBorderWidth: number; +}; + +type PickerToken = InputToken> & PickerPanelToken; + +type SharedPickerToken = Omit; + +const genPikerPadding = ( + token: PickerToken, + inputHeight: number, + fontSize: number, + paddingHorizontal: number, +): CSSObject => { + const { lineHeight } = token; + + const fontHeight = Math.floor(fontSize * lineHeight) + 2; + const paddingTop = Math.max((inputHeight - fontHeight) / 2, 0); + const paddingBottom = Math.max(inputHeight - fontHeight - paddingTop, 0); + + return { + padding: `${paddingTop}px ${paddingHorizontal}px ${paddingBottom}px`, + }; +}; + +const genPickerCellInnerStyle = (token: SharedPickerToken): CSSObject => { + const { + componentCls, + pickerCellCls, + pickerCellInnerCls, + pickerPanelCellHeight, + motionDurationSlow, + borderRadiusSM, + motionDurationMid, + controlItemBgHover, + lineWidth, + lineType, + colorPrimary, + controlItemBgActive, + colorTextLightSolid, + controlHeightSM, + pickerDateHoverRangeBorderColor, + pickerCellBorderGap, + pickerBasicCellHoverWithRangeColor, + pickerPanelCellWidth, + colorTextDisabled, + colorBgContainerDisabled, + } = token; + + return { + '&::before': { + position: 'absolute', + top: '50%', + insetInlineStart: 0, + insetInlineEnd: 0, + zIndex: 1, + height: pickerPanelCellHeight, + transform: 'translateY(-50%)', + transition: `all ${motionDurationSlow}`, + content: '""', + }, + + // >>> Default + [pickerCellInnerCls]: { + position: 'relative', + zIndex: 2, + display: 'inline-block', + minWidth: pickerPanelCellHeight, + height: pickerPanelCellHeight, + lineHeight: `${pickerPanelCellHeight}px`, + borderRadius: borderRadiusSM, + transition: `background ${motionDurationMid}, border ${motionDurationMid}`, + }, + + // >>> Hover + [`&:hover:not(${pickerCellCls}-in-view), + &:hover:not(${pickerCellCls}-selected):not(${pickerCellCls}-range-start):not(${pickerCellCls}-range-end):not(${pickerCellCls}-range-hover-start):not(${pickerCellCls}-range-hover-end)`]: + { + [pickerCellInnerCls]: { + background: controlItemBgHover, + }, + }, + + // >>> Today + [`&-in-view${pickerCellCls}-today ${pickerCellInnerCls}`]: { + '&::before': { + position: 'absolute', + top: 0, + insetInlineEnd: 0, + bottom: 0, + insetInlineStart: 0, + zIndex: 1, + border: `${lineWidth}px ${lineType} ${colorPrimary}`, + borderRadius: borderRadiusSM, + content: '""', + }, + }, + + // >>> In Range + [`&-in-view${pickerCellCls}-in-range`]: { + position: 'relative', + + '&::before': { + background: controlItemBgActive, + }, + }, + + // >>> Selected + [`&-in-view${pickerCellCls}-selected ${pickerCellInnerCls}, + &-in-view${pickerCellCls}-range-start ${pickerCellInnerCls}, + &-in-view${pickerCellCls}-range-end ${pickerCellInnerCls}`]: { + color: colorTextLightSolid, + background: colorPrimary, + }, + + [`&-in-view${pickerCellCls}-range-start:not(${pickerCellCls}-range-start-single), + &-in-view${pickerCellCls}-range-end:not(${pickerCellCls}-range-end-single)`]: { + '&::before': { + background: controlItemBgActive, + }, + }, + + [`&-in-view${pickerCellCls}-range-start::before`]: { + insetInlineStart: '50%', + }, + + [`&-in-view${pickerCellCls}-range-end::before`]: { + insetInlineEnd: '50%', + }, + + // >>> Range Hover + [`&-in-view${pickerCellCls}-range-hover-start:not(${pickerCellCls}-in-range):not(${pickerCellCls}-range-start):not(${pickerCellCls}-range-end), + &-in-view${pickerCellCls}-range-hover-end:not(${pickerCellCls}-in-range):not(${pickerCellCls}-range-start):not(${pickerCellCls}-range-end), + &-in-view${pickerCellCls}-range-hover-start${pickerCellCls}-range-start-single, + &-in-view${pickerCellCls}-range-hover-start${pickerCellCls}-range-start${pickerCellCls}-range-end${pickerCellCls}-range-end-near-hover, + &-in-view${pickerCellCls}-range-hover-end${pickerCellCls}-range-start${pickerCellCls}-range-end${pickerCellCls}-range-start-near-hover, + &-in-view${pickerCellCls}-range-hover-end${pickerCellCls}-range-end-single, + &-in-view${pickerCellCls}-range-hover:not(${pickerCellCls}-in-range)`]: { + '&::after': { + position: 'absolute', + top: '50%', + zIndex: 0, + height: controlHeightSM, + borderTop: `${lineWidth}px dashed ${pickerDateHoverRangeBorderColor}`, + borderBottom: `${lineWidth}px dashed ${pickerDateHoverRangeBorderColor}`, + transform: 'translateY(-50%)', + transition: `all ${motionDurationSlow}`, + content: '""', + }, + }, + + // Add space for stash + [`&-range-hover-start::after, + &-range-hover-end::after, + &-range-hover::after`]: { + insetInlineEnd: 0, + insetInlineStart: pickerCellBorderGap, + }, + + // Hover with in range + [`&-in-view${pickerCellCls}-in-range${pickerCellCls}-range-hover::before, + &-in-view${pickerCellCls}-range-start${pickerCellCls}-range-hover::before, + &-in-view${pickerCellCls}-range-end${pickerCellCls}-range-hover::before, + &-in-view${pickerCellCls}-range-start:not(${pickerCellCls}-range-start-single)${pickerCellCls}-range-hover-start::before, + &-in-view${pickerCellCls}-range-end:not(${pickerCellCls}-range-end-single)${pickerCellCls}-range-hover-end::before, + ${componentCls}-panel + > :not(${componentCls}-date-panel) + &-in-view${pickerCellCls}-in-range${pickerCellCls}-range-hover-start::before, + ${componentCls}-panel + > :not(${componentCls}-date-panel) + &-in-view${pickerCellCls}-in-range${pickerCellCls}-range-hover-end::before`]: { + background: pickerBasicCellHoverWithRangeColor, + }, + + // range start border-radius + [`&-in-view${pickerCellCls}-range-start:not(${pickerCellCls}-range-start-single):not(${pickerCellCls}-range-end) ${pickerCellInnerCls}`]: + { + borderStartStartRadius: borderRadiusSM, + borderEndStartRadius: borderRadiusSM, + borderStartEndRadius: 0, + borderEndEndRadius: 0, + }, + + // range end border-radius + [`&-in-view${pickerCellCls}-range-end:not(${pickerCellCls}-range-end-single):not(${pickerCellCls}-range-start) ${pickerCellInnerCls}`]: + { + borderStartStartRadius: 0, + borderEndStartRadius: 0, + borderStartEndRadius: borderRadiusSM, + borderEndEndRadius: borderRadiusSM, + }, + + [`&-range-hover${pickerCellCls}-range-end::after`]: { + insetInlineStart: '50%', + }, + + // Edge start + [`tr > &-in-view${pickerCellCls}-range-hover:first-child::after, + tr > &-in-view${pickerCellCls}-range-hover-end:first-child::after, + &-in-view${pickerCellCls}-start${pickerCellCls}-range-hover-edge-start${pickerCellCls}-range-hover-edge-start-near-range::after, + &-in-view${pickerCellCls}-range-hover-edge-start:not(${pickerCellCls}-range-hover-edge-start-near-range)::after, + &-in-view${pickerCellCls}-range-hover-start::after`]: { + insetInlineStart: (pickerPanelCellWidth - pickerPanelCellHeight) / 2, + borderInlineStart: `${lineWidth}px dashed ${pickerDateHoverRangeBorderColor}`, + borderStartStartRadius: lineWidth, + borderEndStartRadius: lineWidth, + }, + + // Edge end + [`tr > &-in-view${pickerCellCls}-range-hover:last-child::after, + tr > &-in-view${pickerCellCls}-range-hover-start:last-child::after, + &-in-view${pickerCellCls}-end${pickerCellCls}-range-hover-edge-end${pickerCellCls}-range-hover-edge-end-near-range::after, + &-in-view${pickerCellCls}-range-hover-edge-end:not(${pickerCellCls}-range-hover-edge-end-near-range)::after, + &-in-view${pickerCellCls}-range-hover-end::after`]: { + insetInlineEnd: (pickerPanelCellWidth - pickerPanelCellHeight) / 2, + borderInlineEnd: `${lineWidth}px dashed ${pickerDateHoverRangeBorderColor}`, + borderStartEndRadius: lineWidth, + borderEndEndRadius: lineWidth, + }, + + // >>> Disabled + '&-disabled': { + color: colorTextDisabled, + pointerEvents: 'none', + + [pickerCellInnerCls]: { + background: 'transparent', + }, + + '&::before': { + background: colorBgContainerDisabled, + }, + }, + [`&-disabled${pickerCellCls}-today ${pickerCellInnerCls}::before`]: { + borderColor: colorTextDisabled, + }, + }; +}; + +export const genPanelStyle = (token: SharedPickerToken): CSSObject => { + const { + componentCls, + pickerCellInnerCls, + pickerYearMonthCellWidth, + pickerControlIconSize, + pickerPanelCellWidth, + paddingSM, + paddingXS, + paddingXXS, + colorBgContainer, + lineWidth, + lineType, + borderRadiusLG, + colorPrimary, + colorTextHeading, + colorSplit, + pickerControlIconBorderWidth, + colorIcon, + pickerTextHeight, + motionDurationMid, + colorIconHover, + fontWeightStrong, + pickerPanelCellHeight, + pickerCellPaddingVertical, + colorTextDisabled, + colorText, + fontSize, + pickerBasicCellHoverWithRangeColor, + motionDurationSlow, + pickerPanelWithoutTimeCellHeight, + pickerQuarterPanelContentHeight, + colorLink, + colorLinkActive, + colorLinkHover, + pickerDateHoverRangeBorderColor, + borderRadiusSM, + colorTextLightSolid, + borderRadius, + controlItemBgHover, + pickerTimePanelColumnHeight, + pickerTimePanelColumnWidth, + pickerTimePanelCellHeight, + controlItemBgActive, + marginXXS, + } = token; + + const pickerPanelWidth = pickerPanelCellWidth * 7 + paddingSM * 2 + 4; + + const hoverCellFixedDistance = + (pickerPanelWidth - paddingXS * 2) / 3 - pickerYearMonthCellWidth - paddingSM; + + return { + [componentCls]: { + '&-panel': { + display: 'inline-flex', + flexDirection: 'column', + textAlign: 'center', + background: colorBgContainer, + border: `${lineWidth}px ${lineType} ${colorSplit}`, + borderRadius: borderRadiusLG, + outline: 'none', + + '&-focused': { + borderColor: colorPrimary, + }, + + '&-rtl': { + direction: 'rtl', + + [`${componentCls}-prev-icon, + ${componentCls}-super-prev-icon`]: { + transform: 'rotate(45deg)', + }, + + [`${componentCls}-next-icon, + ${componentCls}-super-next-icon`]: { + transform: 'rotate(-135deg)', + }, + }, + }, + + // ======================================================== + // = Shared Panel = + // ======================================================== + [`&-decade-panel, + &-year-panel, + &-quarter-panel, + &-month-panel, + &-week-panel, + &-date-panel, + &-time-panel`]: { + display: 'flex', + flexDirection: 'column', + width: pickerPanelWidth, + }, + + // ======================= Header ======================= + '&-header': { + display: 'flex', + padding: `0 ${paddingXS}px`, + color: colorTextHeading, + borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`, + + '> *': { + flex: 'none', + }, + + button: { + padding: 0, + color: colorIcon, + lineHeight: `${pickerTextHeight}px`, + background: 'transparent', + border: 0, + cursor: 'pointer', + transition: `color ${motionDurationMid}`, + }, + + '> button': { + minWidth: '1.6em', + fontSize, + + '&:hover': { + color: colorIconHover, + }, + }, + + '&-view': { + flex: 'auto', + fontWeight: fontWeightStrong, + lineHeight: `${pickerTextHeight}px`, + + button: { + color: 'inherit', + fontWeight: 'inherit', + verticalAlign: 'top', + + '&:not(:first-child)': { + marginInlineStart: paddingXS, + }, + + '&:hover': { + color: colorPrimary, + }, + }, + }, + }, + // Arrow button + [`&-prev-icon, + &-next-icon, + &-super-prev-icon, + &-super-next-icon`]: { + position: 'relative', + display: 'inline-block', + width: pickerControlIconSize, + height: pickerControlIconSize, + + '&::before': { + position: 'absolute', + top: 0, + insetInlineStart: 0, + display: 'inline-block', + width: pickerControlIconSize, + height: pickerControlIconSize, + border: `0 solid currentcolor`, + borderBlockStartWidth: pickerControlIconBorderWidth, + borderBlockEndWidth: 0, + borderInlineStartWidth: pickerControlIconBorderWidth, + borderInlineEndWidth: 0, + content: '""', + }, + }, + + [`&-super-prev-icon, + &-super-next-icon`]: { + '&::after': { + position: 'absolute', + top: Math.ceil(pickerControlIconSize / 2), + insetInlineStart: Math.ceil(pickerControlIconSize / 2), + display: 'inline-block', + width: pickerControlIconSize, + height: pickerControlIconSize, + border: '0 solid currentcolor', + borderBlockStartWidth: pickerControlIconBorderWidth, + borderBlockEndWidth: 0, + borderInlineStartWidth: pickerControlIconBorderWidth, + borderInlineEndWidth: 0, + content: '""', + }, + }, + + [`&-prev-icon, + &-super-prev-icon`]: { + transform: 'rotate(-45deg)', + }, + + [`&-next-icon, + &-super-next-icon`]: { + transform: 'rotate(135deg)', + }, + + // ======================== Body ======================== + '&-content': { + width: '100%', + tableLayout: 'fixed', + borderCollapse: 'collapse', + + 'th, td': { + position: 'relative', + minWidth: pickerPanelCellHeight, + fontWeight: 'normal', + }, + + th: { + height: pickerPanelCellHeight + pickerCellPaddingVertical * 2, + color: colorText, + verticalAlign: 'middle', + }, + }, + + '&-cell': { + padding: `${pickerCellPaddingVertical}px 0`, + color: colorTextDisabled, + cursor: 'pointer', + + // In view + '&-in-view': { + color: colorText, + }, + + ...genPickerCellInnerStyle(token), + }, + + // DatePanel only + [`&-date-panel ${componentCls}-cell-in-view${componentCls}-cell-in-range${componentCls}-cell-range-hover-start ${pickerCellInnerCls}, + &-date-panel ${componentCls}-cell-in-view${componentCls}-cell-in-range${componentCls}-cell-range-hover-end ${pickerCellInnerCls}`]: + { + '&::after': { + position: 'absolute', + top: 0, + bottom: 0, + zIndex: -1, + background: pickerBasicCellHoverWithRangeColor, + transition: `all ${motionDurationSlow}`, + content: '""', + }, + }, + + [`&-date-panel + ${componentCls}-cell-in-view${componentCls}-cell-in-range${componentCls}-cell-range-hover-start + ${pickerCellInnerCls}::after`]: { + insetInlineEnd: -(pickerPanelCellWidth - pickerPanelCellHeight) / 2, + insetInlineStart: 0, + }, + + [`&-date-panel ${componentCls}-cell-in-view${componentCls}-cell-in-range${componentCls}-cell-range-hover-end ${pickerCellInnerCls}::after`]: + { + insetInlineEnd: 0, + insetInlineStart: -(pickerPanelCellWidth - pickerPanelCellHeight) / 2, + }, + + // Hover with range start & end + [`&-range-hover${componentCls}-range-start::after`]: { + insetInlineEnd: '50%', + }, + + [`&-decade-panel, + &-year-panel, + &-quarter-panel, + &-month-panel`]: { + [`${componentCls}-content`]: { + height: pickerPanelWithoutTimeCellHeight * 4, + }, + + [pickerCellInnerCls]: { + padding: `0 ${paddingXS}px`, + }, + }, + + '&-quarter-panel': { + [`${componentCls}-content`]: { + height: pickerQuarterPanelContentHeight, + }, + }, + + // ======================== Footer ======================== + [`&-panel ${componentCls}-footer`]: { + borderTop: `${lineWidth}px ${lineType} ${colorSplit}`, + }, + + '&-footer': { + width: 'min-content', + minWidth: '100%', + lineHeight: `${pickerTextHeight - 2 * lineWidth}px`, + textAlign: 'center', + + '&-extra': { + padding: `0 ${paddingSM}`, + lineHeight: `${pickerTextHeight - 2 * lineWidth}px`, + textAlign: 'start', + + '&:not(:last-child)': { + borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`, + }, + }, + }, + + '&-now': { + textAlign: 'start', + }, + + '&-today-btn': { + color: colorLink, + + '&:hover': { + color: colorLinkHover, + }, + + '&:active': { + color: colorLinkActive, + }, + + [`&${componentCls}-today-btn-disabled`]: { + color: colorTextDisabled, + cursor: 'not-allowed', + }, + }, + + // ======================================================== + // = Special = + // ======================================================== + + // ===================== Decade Panel ===================== + '&-decade-panel': { + [pickerCellInnerCls]: { + padding: `0 ${paddingXS / 2}px`, + }, + + [`${componentCls}-cell::before`]: { + display: 'none', + }, + }, + + // ============= Year & Quarter & Month Panel ============= + [`&-year-panel, + &-quarter-panel, + &-month-panel`]: { + [`${componentCls}-body`]: { + padding: `0 ${paddingXS}px`, + }, + + [pickerCellInnerCls]: { + width: pickerYearMonthCellWidth, + }, + + [`${componentCls}-cell-range-hover-start::after`]: { + insetInlineStart: hoverCellFixedDistance, + borderInlineStart: `${lineWidth}px dashed ${pickerDateHoverRangeBorderColor}`, + borderStartStartRadius: borderRadiusSM, + borderBottomStartRadius: borderRadiusSM, + borderStartEndRadius: 0, + borderBottomEndRadius: 0, + + [`${componentCls}-panel-rtl &`]: { + insetInlineEnd: hoverCellFixedDistance, + borderInlineEnd: `${lineWidth}px dashed ${pickerDateHoverRangeBorderColor}`, + borderStartStartRadius: 0, + borderBottomStartRadius: 0, + borderStartEndRadius: borderRadiusSM, + borderBottomEndRadius: borderRadiusSM, + }, + }, + [`${componentCls}-cell-range-hover-end::after`]: { + insetInlineEnd: hoverCellFixedDistance, + borderInlineEnd: `${lineWidth}px dashed ${pickerDateHoverRangeBorderColor}`, + borderStartStartRadius: 0, + borderEndStartRadius: 0, + borderStartEndRadius: borderRadius, + borderEndEndRadius: borderRadius, + + [`${componentCls}-panel-rtl &`]: { + insetInlineStart: hoverCellFixedDistance, + borderInlineStart: `${lineWidth}px dashed ${pickerDateHoverRangeBorderColor}`, + borderStartStartRadius: borderRadius, + borderEndStartRadius: borderRadius, + borderStartEndRadius: 0, + borderEndEndRadius: 0, + }, + }, + }, + + // ====================== Week Panel ====================== + '&-week-panel': { + [`${componentCls}-body`]: { + padding: `${paddingXS}px ${paddingSM}px`, + }, + + // Clear cell style + [`${componentCls}-cell`]: { + [`&:hover ${pickerCellInnerCls}, + &-selected ${pickerCellInnerCls}, + ${pickerCellInnerCls}`]: { + background: 'transparent !important', + }, + }, + + '&-row': { + td: { + transition: `background ${motionDurationMid}`, + + '&:first-child': { + borderStartStartRadius: borderRadiusSM, + borderEndStartRadius: borderRadiusSM, + }, + + '&:last-child': { + borderStartEndRadius: borderRadiusSM, + borderEndEndRadius: borderRadiusSM, + }, + }, + + '&:hover td': { + background: controlItemBgHover, + }, + + [`&-selected td, + &-selected:hover td`]: { + background: colorPrimary, + + [`&${componentCls}-cell-week`]: { + color: new TinyColor(colorTextLightSolid).setAlpha(0.5).toHexString(), + }, + + [`&${componentCls}-cell-today ${pickerCellInnerCls}::before`]: { + borderColor: colorTextLightSolid, + }, + + [pickerCellInnerCls]: { + color: colorTextLightSolid, + }, + }, + }, + }, + + // ====================== Date Panel ====================== + '&-date-panel': { + [`${componentCls}-body`]: { + padding: `${paddingXS}px ${paddingSM}px`, + }, + + [`${componentCls}-content`]: { + width: pickerPanelCellWidth * 7, + + th: { + width: pickerPanelCellWidth, + }, + }, + }, + + // ==================== Datetime Panel ==================== + '&-datetime-panel': { + display: 'flex', + + [`${componentCls}-time-panel`]: { + borderInlineStart: `${lineWidth}px ${lineType} ${colorSplit}`, + }, + + [`${componentCls}-date-panel, + ${componentCls}-time-panel`]: { + transition: `opacity ${motionDurationSlow}`, + }, + + // Keyboard + '&-active': { + [`${componentCls}-date-panel, + ${componentCls}-time-panel`]: { + opacity: 0.3, + + '&-active': { + opacity: 1, + }, + }, + }, + }, + + // ====================== Time Panel ====================== + '&-time-panel': { + width: 'auto', + minWidth: 'auto', + direction: 'ltr', + + [`${componentCls}-content`]: { + display: 'flex', + flex: 'auto', + height: pickerTimePanelColumnHeight, + }, + + '&-column': { + flex: '1 0 auto', + width: pickerTimePanelColumnWidth, + margin: `${paddingXXS}px 0`, + padding: 0, + overflowY: 'hidden', + textAlign: 'start', + listStyle: 'none', + transition: `background ${motionDurationMid}`, + overflowX: 'hidden', + + '&::after': { + display: 'block', + height: pickerTimePanelColumnHeight - pickerTimePanelCellHeight, + content: '""', + }, + + '&:not(:first-child)': { + borderInlineStart: `${lineWidth}px ${lineType} ${colorSplit}`, + }, + + '&-active': { + background: new TinyColor(controlItemBgActive).setAlpha(0.2).toHexString(), + }, + + '&:hover': { + overflowY: 'auto', + }, + + '> li': { + margin: 0, + padding: 0, + + [`&${componentCls}-time-panel-cell`]: { + marginInline: marginXXS, + [`${componentCls}-time-panel-cell-inner`]: { + display: 'block', + width: pickerTimePanelColumnWidth - 2 * marginXXS, + height: pickerTimePanelCellHeight, + margin: 0, + paddingBlock: 0, + paddingInlineEnd: 0, + paddingInlineStart: (pickerTimePanelColumnWidth - pickerTimePanelCellHeight) / 2, + color: colorText, + lineHeight: `${pickerTimePanelCellHeight}px`, + borderRadius: borderRadiusSM, + cursor: 'pointer', + transition: `background ${motionDurationMid}`, + + '&:hover': { + background: controlItemBgHover, + }, + }, + + '&-selected': { + [`${componentCls}-time-panel-cell-inner`]: { + background: controlItemBgActive, + }, + }, + + '&-disabled': { + [`${componentCls}-time-panel-cell-inner`]: { + color: colorTextDisabled, + background: 'transparent', + cursor: 'not-allowed', + }, + }, + }, + }, + }, + }, + // https://github.com/ant-design/ant-design/issues/39227 + [`&-datetime-panel ${componentCls}-time-panel-column:after`]: { + height: pickerTimePanelColumnHeight - pickerTimePanelCellHeight + paddingXXS * 2, + }, + }, + }; +}; + +const genPickerStatusStyle: GenerateStyle = token => { + const { + componentCls, + colorBgContainer, + colorError, + colorErrorOutline, + colorWarning, + colorWarningOutline, + } = token; + + return { + [componentCls]: { + [`&-status-error${componentCls}`]: { + '&, &:not([disabled]):hover': { + backgroundColor: colorBgContainer, + borderColor: colorError, + }, + + '&-focused, &:focus': { + ...genActiveStyle( + mergeToken(token, { + inputBorderActiveColor: colorError, + inputBorderHoverColor: colorError, + controlOutline: colorErrorOutline, + }), + ), + }, + + [`${componentCls}-active-bar`]: { + background: colorError, + }, + }, + + [`&-status-warning${componentCls}`]: { + '&, &:not([disabled]):hover': { + backgroundColor: colorBgContainer, + borderColor: colorWarning, + }, + + '&-focused, &:focus': { + ...genActiveStyle( + mergeToken(token, { + inputBorderActiveColor: colorWarning, + inputBorderHoverColor: colorWarning, + controlOutline: colorWarningOutline, + }), + ), + }, + + [`${componentCls}-active-bar`]: { + background: colorWarning, + }, + }, + }, + }; +}; + +const genPickerStyle: GenerateStyle = token => { + const { + componentCls, + antCls, + boxShadowPopoverArrow, + controlHeight, + fontSize, + inputPaddingHorizontal, + colorBgContainer, + lineWidth, + lineType, + colorBorder, + borderRadius, + motionDurationMid, + colorBgContainerDisabled, + colorTextDisabled, + colorTextPlaceholder, + controlHeightLG, + fontSizeLG, + controlHeightSM, + inputPaddingHorizontalSM, + paddingXS, + marginXS, + colorTextDescription, + lineWidthBold, + lineHeight, + colorPrimary, + motionDurationSlow, + zIndexPopup, + paddingXXS, + paddingSM, + pickerTextHeight, + controlItemBgActive, + colorPrimaryBorder, + sizePopupArrow, + borderRadiusXS, + borderRadiusOuter, + colorBgElevated, + borderRadiusLG, + boxShadowSecondary, + borderRadiusSM, + colorSplit, + controlItemBgHover, + presetsWidth, + presetsMaxWidth, + } = token; + + return [ + { + [componentCls]: { + ...resetComponent(token), + ...genPikerPadding(token, controlHeight, fontSize, inputPaddingHorizontal), + position: 'relative', + display: 'inline-flex', + alignItems: 'center', + background: colorBgContainer, + lineHeight: 1, + border: `${lineWidth}px ${lineType} ${colorBorder}`, + borderRadius, + transition: `border ${motionDurationMid}, box-shadow ${motionDurationMid}`, + + '&:hover, &-focused': { + ...genHoverStyle(token), + }, + + '&-focused': { + ...genActiveStyle(token), + }, + + [`&${componentCls}-disabled`]: { + background: colorBgContainerDisabled, + borderColor: colorBorder, + cursor: 'not-allowed', + + [`${componentCls}-suffix`]: { + color: colorTextDisabled, + }, + }, + + [`&${componentCls}-borderless`]: { + backgroundColor: 'transparent !important', + borderColor: 'transparent !important', + boxShadow: 'none !important', + }, + + // ======================== Input ========================= + [`${componentCls}-input`]: { + position: 'relative', + display: 'inline-flex', + alignItems: 'center', + width: '100%', + + '> input': { + ...genBasicInputStyle(token), + flex: 'auto', + + // Fix Firefox flex not correct: + // https://github.com/ant-design/ant-design/pull/20023#issuecomment-564389553 + minWidth: 1, + height: 'auto', + padding: 0, + background: 'transparent', + border: 0, + + '&:focus': { + boxShadow: 'none', + }, + + '&[disabled]': { + background: 'transparent', + }, + }, + + '&:hover': { + [`${componentCls}-clear`]: { + opacity: 1, + }, + }, + + '&-placeholder': { + '> input': { + color: colorTextPlaceholder, + }, + }, + }, + + // Size + '&-large': { + ...genPikerPadding(token, controlHeightLG, fontSizeLG, inputPaddingHorizontal), + + [`${componentCls}-input > input`]: { + fontSize: fontSizeLG, + }, + }, + + '&-small': { + ...genPikerPadding(token, controlHeightSM, fontSize, inputPaddingHorizontalSM), + }, + + [`${componentCls}-suffix`]: { + display: 'flex', + flex: 'none', + alignSelf: 'center', + marginInlineStart: paddingXS / 2, + color: colorTextDisabled, + lineHeight: 1, + pointerEvents: 'none', + + '> *': { + verticalAlign: 'top', + + '&:not(:last-child)': { + marginInlineEnd: marginXS, + }, + }, + }, + + [`${componentCls}-clear`]: { + position: 'absolute', + top: '50%', + insetInlineEnd: 0, + color: colorTextDisabled, + lineHeight: 1, + background: colorBgContainer, + transform: 'translateY(-50%)', + cursor: 'pointer', + opacity: 0, + transition: `opacity ${motionDurationMid}, color ${motionDurationMid}`, + + '> *': { + verticalAlign: 'top', + }, + + '&:hover': { + color: colorTextDescription, + }, + }, + + [`${componentCls}-separator`]: { + position: 'relative', + display: 'inline-block', + width: '1em', + height: fontSizeLG, + color: colorTextDisabled, + fontSize: fontSizeLG, + verticalAlign: 'top', + cursor: 'default', + + [`${componentCls}-focused &`]: { + color: colorTextDescription, + }, + + [`${componentCls}-range-separator &`]: { + [`${componentCls}-disabled &`]: { + cursor: 'not-allowed', + }, + }, + }, + + // ======================== Range ========================= + '&-range': { + position: 'relative', + display: 'inline-flex', + + // Clear + [`${componentCls}-clear`]: { + insetInlineEnd: inputPaddingHorizontal, + }, + + '&:hover': { + [`${componentCls}-clear`]: { + opacity: 1, + }, + }, + + // Active bar + [`${componentCls}-active-bar`]: { + bottom: -lineWidth, + height: lineWidthBold, + marginInlineStart: inputPaddingHorizontal, + background: colorPrimary, + opacity: 0, + transition: `all ${motionDurationSlow} ease-out`, + pointerEvents: 'none', + }, + + [`&${componentCls}-focused`]: { + [`${componentCls}-active-bar`]: { + opacity: 1, + }, + }, + + [`${componentCls}-range-separator`]: { + alignItems: 'center', + padding: `0 ${paddingXS}px`, + lineHeight: 1, + }, + + [`&${componentCls}-small`]: { + [`${componentCls}-clear`]: { + insetInlineEnd: inputPaddingHorizontalSM, + }, + + [`${componentCls}-active-bar`]: { + marginInlineStart: inputPaddingHorizontalSM, + }, + }, + }, + + // ======================= Dropdown ======================= + '&-dropdown': { + ...resetComponent(token), + ...genPanelStyle(token), + position: 'absolute', + // Fix incorrect position of picker popup + // https://github.com/ant-design/ant-design/issues/35590 + top: -9999, + left: { + _skip_check_: true, + value: -9999, + }, + zIndex: zIndexPopup, + + [`&${componentCls}-dropdown-hidden`]: { + display: 'none', + }, + + [`&${componentCls}-dropdown-placement-bottomLeft`]: { + [`${componentCls}-range-arrow`]: { + top: 0, + display: 'block', + transform: 'translateY(-100%)', + }, + }, + + [`&${componentCls}-dropdown-placement-topLeft`]: { + [`${componentCls}-range-arrow`]: { + bottom: 0, + display: 'block', + transform: 'translateY(100%) rotate(180deg)', + }, + }, + + [`&${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-dropdown-placement-topLeft, + &${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-dropdown-placement-topRight, + &${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-dropdown-placement-topLeft, + &${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-dropdown-placement-topRight`]: + { + animationName: slideDownIn, + }, + + [`&${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-dropdown-placement-bottomLeft, + &${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-dropdown-placement-bottomRight, + &${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-dropdown-placement-bottomLeft, + &${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-dropdown-placement-bottomRight`]: + { + animationName: slideUpIn, + }, + + [`&${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-dropdown-placement-topLeft, + &${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-dropdown-placement-topRight`]: + { + animationName: slideDownOut, + }, + + [`&${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-dropdown-placement-bottomLeft, + &${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-dropdown-placement-bottomRight`]: + { + animationName: slideUpOut, + }, + + // Time picker with additional style + [`${componentCls}-panel > ${componentCls}-time-panel`]: { + paddingTop: paddingXXS, + }, + + // ======================== Ranges ======================== + [`${componentCls}-ranges`]: { + marginBottom: 0, + padding: `${paddingXXS}px ${paddingSM}px`, + overflow: 'hidden', + lineHeight: `${pickerTextHeight - 2 * lineWidth - paddingXS / 2}px`, + textAlign: 'start', + listStyle: 'none', + display: 'flex', + justifyContent: 'space-between', + + '> li': { + display: 'inline-block', + }, + + // https://github.com/ant-design/ant-design/issues/23687 + [`${componentCls}-preset > ${antCls}-tag-blue`]: { + color: colorPrimary, + background: controlItemBgActive, + borderColor: colorPrimaryBorder, + cursor: 'pointer', + }, + + [`${componentCls}-ok`]: { + marginInlineStart: 'auto', + }, + }, + + [`${componentCls}-range-wrapper`]: { + display: 'flex', + position: 'relative', + }, + + [`${componentCls}-range-arrow`]: { + position: 'absolute', + zIndex: 1, + display: 'none', + marginInlineStart: inputPaddingHorizontal * 1.5, + transition: `left ${motionDurationSlow} ease-out`, + ...roundedArrow( + sizePopupArrow, + borderRadiusXS, + borderRadiusOuter, + colorBgElevated, + boxShadowPopoverArrow, + ), + }, + + [`${componentCls}-panel-container`]: { + overflow: 'hidden', + verticalAlign: 'top', + background: colorBgElevated, + borderRadius: borderRadiusLG, + boxShadow: boxShadowSecondary, + transition: `margin ${motionDurationSlow}`, + + // ======================== Layout ======================== + [`${componentCls}-panel-layout`]: { + display: 'flex', + flexWrap: 'nowrap', + alignItems: 'stretch', + }, + + // ======================== Preset ======================== + [`${componentCls}-presets`]: { + display: 'flex', + flexDirection: 'column', + minWidth: presetsWidth, + maxWidth: presetsMaxWidth, + + ul: { + height: 0, + flex: 'auto', + listStyle: 'none', + overflow: 'auto', + margin: 0, + padding: paddingXS, + borderInlineEnd: `${lineWidth}px ${lineType} ${colorSplit}`, + + li: { + ...textEllipsis, + borderRadius: borderRadiusSM, + paddingInline: paddingXS, + paddingBlock: (controlHeightSM - Math.round(fontSize * lineHeight)) / 2, + cursor: 'pointer', + transition: `all ${motionDurationSlow}`, + + '+ li': { + marginTop: marginXS, + }, + + '&:hover': { + background: controlItemBgHover, + }, + }, + }, + }, + + // ======================== Panels ======================== + [`${componentCls}-panels`]: { + display: 'inline-flex', + flexWrap: 'nowrap', + direction: 'ltr', + + [`${componentCls}-panel`]: { + borderWidth: `0 0 ${lineWidth}px`, + }, + + '&:last-child': { + [`${componentCls}-panel`]: { + borderWidth: 0, + }, + }, + }, + + [`${componentCls}-panel`]: { + verticalAlign: 'top', + background: 'transparent', + borderRadius: 0, + borderWidth: 0, + + [`${componentCls}-content, + table`]: { + textAlign: 'center', + }, + + '&-focused': { + borderColor: colorBorder, + }, + }, + }, + }, + + '&-dropdown-range': { + padding: `${(sizePopupArrow * 2) / 3}px 0`, + + '&-hidden': { + display: 'none', + }, + }, + + '&-rtl': { + direction: 'rtl', + + [`${componentCls}-separator`]: { + transform: 'rotate(180deg)', + }, + + [`${componentCls}-footer`]: { + '&-extra': { + direction: 'rtl', + }, + }, + }, + }, + }, + + // Follow code may reuse in other components + initSlideMotion(token, 'slide-up'), + initSlideMotion(token, 'slide-down'), + initMoveMotion(token, 'move-up'), + initMoveMotion(token, 'move-down'), + ]; +}; + +export const initPickerPanelToken = (token: TokenWithCommonCls): PickerPanelToken => { + const pickerTimePanelCellHeight = 28; + const { componentCls, controlHeightLG, controlHeightSM, colorPrimary, paddingXXS } = token; + + return { + pickerCellCls: `${componentCls}-cell`, + pickerCellInnerCls: `${componentCls}-cell-inner`, + pickerTextHeight: controlHeightLG, + pickerPanelCellWidth: controlHeightSM * 1.5, + pickerPanelCellHeight: controlHeightSM, + pickerDateHoverRangeBorderColor: new TinyColor(colorPrimary).lighten(20).toHexString(), + pickerBasicCellHoverWithRangeColor: new TinyColor(colorPrimary).lighten(35).toHexString(), + pickerPanelWithoutTimeCellHeight: controlHeightLG * 1.65, + pickerYearMonthCellWidth: controlHeightLG * 1.5, + pickerTimePanelColumnHeight: pickerTimePanelCellHeight * 8, + pickerTimePanelColumnWidth: controlHeightLG * 1.4, + pickerTimePanelCellHeight, + pickerQuarterPanelContentHeight: controlHeightLG * 1.4, + pickerCellPaddingVertical: paddingXXS, + pickerCellBorderGap: 2, // Magic for gap between cells + pickerControlIconSize: 7, + pickerControlIconBorderWidth: 1.5, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook( + 'DatePicker', + token => { + const pickerToken = mergeToken( + initInputToken>(token), + initPickerPanelToken(token), + ); + + return [ + genPickerStyle(pickerToken), + genPickerStatusStyle(pickerToken), + // ===================================================== + // == Space Compact == + // ===================================================== + genCompactItemStyle(token, { + focusElCls: `${token.componentCls}-focused`, + }), + ]; + }, + token => ({ + presetsWidth: 120, + presetsMaxWidth: 200, + zIndexPopup: token.zIndexPopupBase + 50, + }), +); diff --git a/components/date-picker/style/index.tsx b/components/date-picker/style/index.tsx deleted file mode 100644 index cc4424295..000000000 --- a/components/date-picker/style/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import './index.less'; - -// style dependencies -import '../../tag/style'; -import '../../button/style'; diff --git a/components/date-picker/style/panel.less b/components/date-picker/style/panel.less deleted file mode 100644 index fec6899f3..000000000 --- a/components/date-picker/style/panel.less +++ /dev/null @@ -1,677 +0,0 @@ -@picker-cell-inner-cls: ~'@{picker-prefix-cls}-cell-inner'; - -.@{picker-prefix-cls} { - @picker-arrow-size: 7px; - @picker-year-month-cell-width: 60px; - @picker-panel-width: @picker-panel-cell-width * 7 + @padding-sm * 2 + 4; - - &-panel { - display: inline-flex; - flex-direction: column; - text-align: center; - background: @calendar-bg; - border: @border-width-base @border-style-base @picker-border-color; - border-radius: @border-radius-base; - outline: none; - - &-focused { - border-color: @primary-color; - } - } - - // ======================================================== - // = Shared Panel = - // ======================================================== - &-decade-panel, - &-year-panel, - &-quarter-panel, - &-month-panel, - &-week-panel, - &-date-panel, - &-time-panel { - display: flex; - flex-direction: column; - width: @picker-panel-width; - } - - // ======================= Header ======================= - &-header { - display: flex; - padding: 0 @padding-xs; - color: @heading-color; - border-bottom: @border-width-base @border-style-base @picker-border-color; - - > * { - flex: none; - } - - button { - padding: 0; - color: @disabled-color; - line-height: @picker-text-height; - background: transparent; - border: 0; - cursor: pointer; - transition: color @animation-duration-slow; - } - - > button { - min-width: 1.6em; - font-size: @font-size-base; - - &:hover { - color: @text-color; - } - } - - &-view { - flex: auto; - font-weight: 500; - line-height: @picker-text-height; - - button { - color: inherit; - font-weight: inherit; - - &:not(:first-child) { - margin-left: @padding-xs; - } - - &:hover { - color: @primary-color; - } - } - } - } - - // Arrow button - &-prev-icon, - &-next-icon, - &-super-prev-icon, - &-super-next-icon { - position: relative; - display: inline-block; - width: @picker-arrow-size; - height: @picker-arrow-size; - - &::before { - position: absolute; - top: 0; - left: 0; - display: inline-block; - width: @picker-arrow-size; - height: @picker-arrow-size; - border: 0 solid currentcolor; - border-width: 1.5px 0 0 1.5px; - content: ''; - } - } - - &-super-prev-icon, - &-super-next-icon { - &::after { - position: absolute; - top: ceil((@picker-arrow-size / 2)); - left: ceil((@picker-arrow-size / 2)); - display: inline-block; - width: @picker-arrow-size; - height: @picker-arrow-size; - border: 0 solid currentcolor; - border-width: 1.5px 0 0 1.5px; - content: ''; - } - } - - &-prev-icon, - &-super-prev-icon { - transform: rotate(-45deg); - } - - &-next-icon, - &-super-next-icon { - transform: rotate(135deg); - } - - // ======================== Body ======================== - &-content { - width: 100%; - table-layout: fixed; - border-collapse: collapse; - - th, - td { - position: relative; - min-width: 24px; - font-weight: 400; - } - - th { - height: 30px; - color: @text-color; - line-height: 30px; - } - } - - .picker-cell-inner(@cellClassName) { - &::before { - position: absolute; - top: 50%; - right: 0; - left: 0; - z-index: 1; - height: @picker-panel-cell-height; - transform: translateY(-50%); - transition: all @animation-duration-slow; - content: ''; - } - - // >>> Default - .@{cellClassName} { - position: relative; - z-index: 2; - display: inline-block; - min-width: @picker-panel-cell-height; - height: @picker-panel-cell-height; - line-height: @picker-panel-cell-height; - border-radius: @border-radius-base; - transition: background @animation-duration-slow, border @animation-duration-slow; - } - - // >>> Hover - &:hover:not(&-in-view), - &:hover:not(&-selected):not(&-range-start):not(&-range-end):not(&-range-hover-start):not(&-range-hover-end) { - .@{cellClassName} { - background: @picker-basic-cell-hover-color; - } - } - - // >>> Today - &-in-view&-today .@{cellClassName} { - &::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - border: @border-width-base @border-style-base @primary-color; - border-radius: @border-radius-base; - content: ''; - } - } - - // >>> In Range - &-in-view&-in-range { - position: relative; - - &::before { - background: @picker-basic-cell-active-with-range-color; - } - } - - // >>> Selected - &-in-view&-selected .@{cellClassName}, - &-in-view&-range-start .@{cellClassName}, - &-in-view&-range-end .@{cellClassName} { - color: @text-color-inverse; - background: @primary-color; - } - - &-in-view&-range-start:not(&-range-start-single), - &-in-view&-range-end:not(&-range-end-single) { - &::before { - background: @picker-basic-cell-active-with-range-color; - } - } - - &-in-view&-range-start::before { - left: 50%; - } - - &-in-view&-range-end::before { - right: 50%; - } - - // >>> Range Hover - &-in-view&-range-hover-start:not(&-in-range):not(&-range-start):not(&-range-end), - &-in-view&-range-hover-end:not(&-in-range):not(&-range-start):not(&-range-end), - &-in-view&-range-hover-start&-range-start-single, - &-in-view&-range-hover-start&-range-start&-range-end&-range-end-near-hover, - &-in-view&-range-hover-end&-range-start&-range-end&-range-start-near-hover, - &-in-view&-range-hover-end&-range-end-single, - &-in-view&-range-hover:not(&-in-range) { - &::after { - position: absolute; - top: 50%; - z-index: 0; - height: 24px; - border-top: @border-width-base dashed @picker-date-hover-range-border-color; - border-bottom: @border-width-base dashed @picker-date-hover-range-border-color; - transform: translateY(-50%); - transition: all @animation-duration-slow; - content: ''; - } - } - - // Add space for stash - &-range-hover-start::after, - &-range-hover-end::after, - &-range-hover::after { - right: 0; - left: 2px; - } - - // Hover with in range - &-in-view&-in-range&-range-hover::before, - &-in-view&-range-start&-range-hover::before, - &-in-view&-range-end&-range-hover::before, - &-in-view&-range-start:not(&-range-start-single)&-range-hover-start::before, - &-in-view&-range-end:not(&-range-end-single)&-range-hover-end::before, - .@{picker-prefix-cls}-panel - > :not(.@{picker-prefix-cls}-date-panel) - &-in-view&-in-range&-range-hover-start::before, - .@{picker-prefix-cls}-panel - > :not(.@{picker-prefix-cls}-date-panel) - &-in-view&-in-range&-range-hover-end::before { - background: @picker-date-hover-range-color; - } - - // range start border-radius - &-in-view&-range-start:not(&-range-start-single):not(&-range-end) .@{cellClassName} { - border-radius: @border-radius-base 0 0 @border-radius-base; - } - - // range end border-radius - &-in-view&-range-end:not(&-range-end-single):not(&-range-start) .@{cellClassName} { - border-radius: 0 @border-radius-base @border-radius-base 0; - } - - // DatePanel only - .@{picker-prefix-cls}-date-panel &-in-view&-in-range&-range-hover-start .@{cellClassName}, - .@{picker-prefix-cls}-date-panel &-in-view&-in-range&-range-hover-end .@{cellClassName} { - &::after { - position: absolute; - top: 0; - bottom: 0; - z-index: -1; - background: @picker-date-hover-range-color; - transition: all @animation-duration-slow; - content: ''; - } - } - - .@{picker-prefix-cls}-date-panel - &-in-view&-in-range&-range-hover-start - .@{cellClassName}::after { - right: -5px - @border-width-base; - left: 0; - } - - .@{picker-prefix-cls}-date-panel &-in-view&-in-range&-range-hover-end .@{cellClassName}::after { - right: 0; - left: -5px - @border-width-base; - } - - // Hover with range start & end - &-range-hover&-range-start::after { - right: 50%; - } - - &-range-hover&-range-end::after { - left: 50%; - } - - // Edge start - tr > &-in-view&-range-hover:first-child::after, - tr > &-in-view&-range-hover-end:first-child::after, - &-in-view&-start&-range-hover-edge-start&-range-hover-edge-start-near-range::after, - &-in-view&-range-hover-edge-start:not(&-range-hover-edge-start-near-range)::after, - &-in-view&-range-hover-start::after { - left: 6px; - border-left: @border-width-base dashed @picker-date-hover-range-border-color; - border-top-left-radius: @border-radius-base; - border-bottom-left-radius: @border-radius-base; - } - - // Edge end - tr > &-in-view&-range-hover:last-child::after, - tr > &-in-view&-range-hover-start:last-child::after, - &-in-view&-end&-range-hover-edge-end&-range-hover-edge-end-near-range::after, - &-in-view&-range-hover-edge-end:not(&-range-hover-edge-end-near-range)::after, - &-in-view&-range-hover-end::after { - right: 6px; - border-right: @border-width-base dashed @picker-date-hover-range-border-color; - border-top-right-radius: @border-radius-base; - border-bottom-right-radius: @border-radius-base; - } - - // >>> Disabled - &-disabled { - color: @disabled-color; - pointer-events: none; - - .@{cellClassName} { - background: transparent; - } - - &::before { - background: @picker-basic-cell-disabled-bg; - } - } - &-disabled&-today .@{cellClassName}::before { - border-color: @disabled-color; - } - } - - &-cell { - padding: 3px 0; - color: @disabled-color; - cursor: pointer; - - // In view - &-in-view { - color: @text-color; - } - - .picker-cell-inner(~'@{picker-cell-inner-cls}'); - } - - &-decade-panel, - &-year-panel, - &-quarter-panel, - &-month-panel { - .@{picker-prefix-cls}-content { - height: @picker-panel-without-time-cell-height * 4; - } - - .@{picker-cell-inner-cls} { - padding: 0 @padding-xs; - } - } - - &-quarter-panel { - .@{picker-prefix-cls}-content { - height: 56px; - } - } - - // ======================== Footer ======================== - &-footer { - width: min-content; - min-width: 100%; - line-height: @picker-text-height - 2 * @border-width-base; - text-align: center; - border-bottom: @border-width-base @border-style-base transparent; - - .@{picker-prefix-cls}-panel & { - border-top: @border-width-base @border-style-base @picker-border-color; - } - - &-extra { - padding: 0 @padding-sm; - line-height: @picker-text-height - 2 * @border-width-base; - text-align: left; - - &:not(:last-child) { - border-bottom: @border-width-base @border-style-base @picker-border-color; - } - } - } - - &-now { - text-align: left; - } - - &-today-btn { - color: @link-color; - - &:hover { - color: @link-hover-color; - } - - &:active { - color: @link-active-color; - } - - &&-disabled { - color: @disabled-color; - cursor: not-allowed; - } - } - - // ======================================================== - // = Special = - // ======================================================== - - // ===================== Decade Panel ===================== - &-decade-panel { - .@{picker-cell-inner-cls} { - padding: 0 (@padding-xs / 2); - } - - .@{picker-prefix-cls}-cell::before { - display: none; - } - } - - // ============= Year & Quarter & Month Panel ============= - &-year-panel, - &-quarter-panel, - &-month-panel { - @hover-cell-fixed-distance: ( - (((@picker-panel-width - @padding-xs * 2) / 3) - @picker-year-month-cell-width) / 2 - ); - - .@{picker-prefix-cls}-body { - padding: 0 @padding-xs; - } - - .@{picker-cell-inner-cls} { - width: @picker-year-month-cell-width; - } - - .@{picker-prefix-cls}-cell-range-hover-start::after { - left: @hover-cell-fixed-distance; - border-left: @border-width-base dashed @picker-date-hover-range-border-color; - border-radius: @border-radius-base 0 0 @border-radius-base; - - .@{picker-prefix-cls}-panel-rtl & { - right: @hover-cell-fixed-distance; - border-right: @border-width-base dashed @picker-date-hover-range-border-color; - border-radius: 0 @border-radius-base @border-radius-base 0; - } - } - .@{picker-prefix-cls}-cell-range-hover-end::after { - right: @hover-cell-fixed-distance; - border-right: @border-width-base dashed @picker-date-hover-range-border-color; - border-radius: 0 @border-radius-base @border-radius-base 0; - - .@{picker-prefix-cls}-panel-rtl & { - left: @hover-cell-fixed-distance; - border-left: @border-width-base dashed @picker-date-hover-range-border-color; - border-radius: @border-radius-base 0 0 @border-radius-base; - } - } - } - - // ====================== Week Panel ====================== - &-week-panel { - .@{picker-prefix-cls}-body { - padding: @padding-xs @padding-sm; - } - - // Clear cell style - .@{picker-prefix-cls}-cell { - &:hover .@{picker-cell-inner-cls}, - &-selected .@{picker-cell-inner-cls}, - .@{picker-cell-inner-cls} { - background: transparent !important; - } - } - - &-row { - td { - transition: background @animation-duration-slow; - } - - &:hover td { - background: @picker-basic-cell-hover-color; - } - - &-selected td, - &-selected:hover td { - background: @primary-color; - - &.@{picker-prefix-cls}-cell-week { - color: fade(@text-color-inverse, 50%); - } - - &.@{picker-prefix-cls}-cell-today .@{picker-cell-inner-cls}::before { - border-color: @text-color-inverse; - } - - .@{picker-cell-inner-cls} { - color: @text-color-inverse; - } - } - } - } - - // ====================== Date Panel ====================== - &-date-panel { - .@{picker-prefix-cls}-body { - padding: @padding-xs @padding-sm; - } - - .@{picker-prefix-cls}-content { - width: @picker-panel-cell-width * 7; - - th { - width: @picker-panel-cell-width; - } - } - } - - // ==================== Datetime Panel ==================== - &-datetime-panel { - display: flex; - - .@{picker-prefix-cls}-time-panel { - border-left: @border-width-base @border-style-base @picker-border-color; - } - - .@{picker-prefix-cls}-date-panel, - .@{picker-prefix-cls}-time-panel { - transition: opacity @animation-duration-slow; - } - - // Keyboard - &-active { - .@{picker-prefix-cls}-date-panel, - .@{picker-prefix-cls}-time-panel { - opacity: 0.3; - - &-active { - opacity: 1; - } - } - } - } - - // ====================== Time Panel ====================== - &-time-panel { - width: auto; - min-width: auto; - - .@{picker-prefix-cls}-content { - display: flex; - flex: auto; - height: @picker-time-panel-column-height; - } - - &-column { - flex: 1 0 auto; - width: @picker-time-panel-column-width; - margin: 0; - padding: 0; - overflow-y: hidden; - text-align: left; - list-style: none; - transition: background @animation-duration-slow; - - &::after { - display: block; - height: @picker-time-panel-column-height - @picker-time-panel-cell-height; - content: ''; - .@{picker-prefix-cls}-datetime-panel & { - height: @picker-time-panel-column-height - @picker-time-panel-cell-height + 2 * - @border-width-base; - } - } - - &:not(:first-child) { - border-left: @border-width-base @border-style-base @picker-border-color; - } - - &-active { - background: @calendar-column-active-bg; - } - - &:hover { - overflow-y: auto; - } - - > li { - margin: 0; - padding: 0; - - &.@{picker-prefix-cls}-time-panel-cell { - .@{picker-prefix-cls}-time-panel-cell-inner { - display: block; - width: 100%; - height: @picker-time-panel-cell-height; - margin: 0; - padding: 0 0 0 ((@picker-time-panel-column-width - 28px) / 2); - color: @text-color; - line-height: @picker-time-panel-cell-height; - border-radius: 0; - cursor: pointer; - transition: background @animation-duration-slow; - - &:hover { - background: @item-hover-bg; - } - } - - &-selected { - .@{picker-prefix-cls}-time-panel-cell-inner { - background: @calendar-item-active-bg; - } - } - - &-disabled { - .@{picker-prefix-cls}-time-panel-cell-inner { - color: @disabled-color; - background: transparent; - cursor: not-allowed; - } - } - } - } - } - } -} - -// Fix IE11 render bug by css hacks -// https://github.com/ant-design/ant-design/issues/21559 -// https://codepen.io/afc163-1472555193/pen/mdJRaNj?editors=0110 -/* stylelint-disable-next-line selector-type-no-unknown,selector-no-vendor-prefix */ -_:-ms-fullscreen, -:root { - .@{picker-prefix-cls}-range-wrapper { - .@{picker-prefix-cls}-month-panel .@{picker-prefix-cls}-cell, - .@{picker-prefix-cls}-year-panel .@{picker-prefix-cls}-cell { - padding: 21px 0; - } - } -} diff --git a/components/date-picker/style/rtl.less b/components/date-picker/style/rtl.less deleted file mode 100644 index 3a74800e9..000000000 --- a/components/date-picker/style/rtl.less +++ /dev/null @@ -1,246 +0,0 @@ -.@{picker-prefix-cls} { - &-rtl { - direction: rtl; - } - - &-suffix { - .@{picker-prefix-cls}-rtl & { - margin-right: (@padding-xs / 2); - margin-left: 0; - } - } - - &-clear { - .@{picker-prefix-cls}-rtl & { - right: auto; - left: 0; - } - } - - &-separator { - .@{picker-prefix-cls}-rtl & { - transform: rotate(180deg); - } - } - - &-header { - &-view { - button { - &:not(:first-child) { - .@{picker-prefix-cls}-panel-rtl & { - margin-right: @padding-xs; - margin-left: 0; - } - } - } - } - } - - // ======================== Range ========================= - &-range { - // Clear - .@{picker-prefix-cls}-clear { - .@{picker-prefix-cls}-rtl& { - right: auto; - left: @input-padding-horizontal-base; - } - } - - // Active bar - .@{picker-prefix-cls}-active-bar { - .@{picker-prefix-cls}-rtl& { - margin-right: @input-padding-horizontal-base; - margin-left: 0; - } - } - - &.@{picker-prefix-cls}-small { - .@{picker-prefix-cls}-active-bar { - .@{picker-prefix-cls}-rtl& { - margin-right: @input-padding-horizontal-sm; - } - } - } - } - - // ======================== Ranges ======================== - &-ranges { - .@{picker-prefix-cls}-dropdown-rtl & { - text-align: right; - } - - .@{picker-prefix-cls}-ok { - .@{picker-prefix-cls}-dropdown-rtl & { - float: left; - margin-right: @padding-xs; - margin-left: 0; - } - } - } - - // ======================== Panel ======================== - &-panel { - &-rtl { - direction: rtl; - } - } - - &-prev-icon, - &-super-prev-icon { - .@{picker-prefix-cls}-panel-rtl & { - transform: rotate(135deg); - } - } - - &-next-icon, - &-super-next-icon { - .@{picker-prefix-cls}-panel-rtl & { - transform: rotate(-45deg); - } - } - - &-cell { - .picker-cell-inner(~'@{picker-cell-inner-cls}'); - } - - // ======================== Body ========================== - .picker-cell-inner(@cellClassName) { - .@{cellClassName} { - position: relative; - z-index: 2; - display: inline-block; - min-width: @picker-panel-cell-height; - height: @picker-panel-cell-height; - line-height: @picker-panel-cell-height; - border-radius: @border-radius-base; - transition: background @animation-duration-slow, border @animation-duration-slow; - } - - &-in-view&-range-start::before { - .@{picker-prefix-cls}-panel-rtl & { - right: 50%; - left: 0; - } - } - - &-in-view&-range-end::before { - .@{picker-prefix-cls}-panel-rtl & { - right: 0; - left: 50%; - } - } - - &-in-view&-range-start&-range-end::before { - .@{picker-prefix-cls}-panel-rtl & { - right: 50%; - left: 50%; - } - } - - .@{picker-prefix-cls}-date-panel - &-in-view&-in-range&-range-hover-start - .@{cellClassName}::after { - .@{picker-prefix-cls}-panel-rtl & { - right: 0; - left: -5px - @border-width-base; - } - } - - .@{picker-prefix-cls}-date-panel &-in-view&-in-range&-range-hover-end .@{cellClassName}::after { - .@{picker-prefix-cls}-panel-rtl & { - right: -5px - @border-width-base; - left: 0; - } - } - - // Hover with range start & end - &-range-hover&-range-start::after { - .@{picker-prefix-cls}-panel-rtl & { - right: 0; - left: 50%; - } - } - - &-range-hover&-range-end::after { - .@{picker-prefix-cls}-panel-rtl & { - right: 50%; - left: 0; - } - } - - // range start border-radius - &-in-view&-range-start:not(&-range-start-single):not(&-range-end) .@{cellClassName} { - .@{picker-prefix-cls}-panel-rtl & { - border-radius: 0 @border-radius-base @border-radius-base 0; - } - } - - // range end border-radius - &-in-view&-range-end:not(&-range-end-single):not(&-range-start) .@{cellClassName} { - .@{picker-prefix-cls}-panel-rtl & { - border-radius: @border-radius-base 0 0 @border-radius-base; - } - } - - // Edge start - tr > &-in-view&-range-hover:not(&-selected):first-child::after, - &-in-view&-start&-range-hover-edge-start&-range-hover-edge-start-near-range::after, - &-in-view&-range-hover-edge-start:not(&-range-hover-edge-start-near-range)::after, - &-in-view&-range-hover-start::after { - .@{picker-prefix-cls}-panel-rtl & { - right: 6px; - left: 0; - border-right: @border-width-base dashed @picker-date-hover-range-border-color; - border-left: none; - border-radius: 0 @border-radius-base @border-radius-base 0; - } - } - - // Edge end - tr > &-in-view&-range-hover:not(&-selected):last-child::after, - &-in-view&-end&-range-hover-edge-end&-range-hover-edge-end-near-range::after, - &-in-view&-range-hover-edge-end:not(&-range-hover-edge-end-near-range)::after, - &-in-view&-range-hover-end::after { - .@{picker-prefix-cls}-panel-rtl & { - right: 0; - left: 6px; - border-right: none; - border-left: @border-width-base dashed @picker-date-hover-range-border-color; - border-radius: @border-radius-base 0 0 @border-radius-base; - } - } - - tr > &-in-view&-range-hover-start:last-child::after, - tr > &-in-view&-range-hover-end:first-child::after, - &-in-view&-start&-range-hover-edge-start:not(&-range-hover)::after, - &-in-view&-start&-range-hover-end&-range-hover-edge-start:not(&-range-hover)::after, - &-in-view&-end&-range-hover-start&-range-hover-edge-end:not(&-range-hover)::after, - tr > &-in-view&-start&-range-hover&-range-hover-edge-start:last-child::after, - tr > &-in-view&-end&-range-hover&-range-hover-edge-end:first-child::after { - .@{picker-prefix-cls}-panel-rtl & { - right: 6px; - left: 6px; - border-right: @border-width-base dashed @picker-date-hover-range-border-color; - border-left: @border-width-base dashed @picker-date-hover-range-border-color; - border-radius: @border-radius-base; - } - } - } - - // ======================== Footer ======================== - &-footer { - &-extra { - .@{picker-prefix-cls}-dropdown-rtl & { - direction: rtl; - text-align: right; - } - } - } - - // ====================== Time Panel ====================== - &-time-panel { - .@{picker-prefix-cls}-panel-rtl & { - direction: ltr; - } - } -} diff --git a/components/date-picker/util.ts b/components/date-picker/util.ts index fbd314ed9..5eea25b42 100644 --- a/components/date-picker/util.ts +++ b/components/date-picker/util.ts @@ -1,9 +1,11 @@ import type { PickerMode } from '../vc-picker/interface'; +import type { SelectCommonPlacement } from '../_util/transition'; import type { PickerLocale } from './generatePicker'; +import type { DirectionType } from '../config-provider'; export function getPlaceholder( - picker: PickerMode | undefined, locale: PickerLocale, + picker: PickerMode, customizePlaceholder?: string, ): string { if (customizePlaceholder !== undefined) { @@ -29,8 +31,8 @@ export function getPlaceholder( } export function getRangePlaceholder( - picker: PickerMode | undefined, locale: PickerLocale, + picker: PickerMode, customizePlaceholder?: [string, string], ) { if (customizePlaceholder !== undefined) { @@ -51,3 +53,50 @@ export function getRangePlaceholder( } return locale.lang.rangePlaceholder; } + +export function transPlacement2DropdownAlign( + direction: DirectionType, + placement?: SelectCommonPlacement, +) { + const overflow = { + adjustX: 1, + adjustY: 1, + }; + switch (placement) { + case 'bottomLeft': { + return { + points: ['tl', 'bl'], + offset: [0, 4], + overflow, + }; + } + case 'bottomRight': { + return { + points: ['tr', 'br'], + offset: [0, 4], + overflow, + }; + } + case 'topLeft': { + return { + points: ['bl', 'tl'], + offset: [0, -4], + overflow, + }; + } + case 'topRight': { + return { + points: ['br', 'tr'], + offset: [0, -4], + overflow, + }; + } + default: { + return { + points: direction === 'rtl' ? ['tr', 'br'] : ['tl', 'bl'], + offset: [0, 4], + overflow, + }; + } + } +} diff --git a/components/descriptions/Cell.tsx b/components/descriptions/Cell.tsx index 6acbc6bc4..e00a189b9 100644 --- a/components/descriptions/Cell.tsx +++ b/components/descriptions/Cell.tsx @@ -11,8 +11,8 @@ interface CellProps extends HTMLAttributes { labelStyle?: CSSProperties; contentStyle?: CSSProperties; bordered?: boolean; - label?: VNodeTypes; - content?: VNodeTypes; + label?: number | VNodeTypes; + content?: number | VNodeTypes; colon?: boolean; } @@ -49,7 +49,7 @@ const Cell: FunctionalComponent = props => { return (
    - {label && ( + {(label || label === 0) && ( = props => { {label} )} - {content && ( + {(content || content === 0) && ( {content} diff --git a/components/descriptions/__tests__/__snapshots__/index.test.js.snap b/components/descriptions/__tests__/__snapshots__/index.test.js.snap index 91473a64b..1e35911db 100644 --- a/components/descriptions/__tests__/__snapshots__/index.test.js.snap +++ b/components/descriptions/__tests__/__snapshots__/index.test.js.snap @@ -18,7 +18,7 @@ exports[`Descriptions Descriptions support colon 1`] = ` `; exports[`Descriptions Descriptions support style 1`] = ` -
    +
    diff --git a/components/descriptions/demo/size.vue b/components/descriptions/demo/size.vue index 7ae9e98ae..8abc364d6 100644 --- a/components/descriptions/demo/size.vue +++ b/components/descriptions/demo/size.vue @@ -65,21 +65,12 @@ Custom sizes to fit in a variety of containers. - diff --git a/components/descriptions/index.en-US.md b/components/descriptions/index.en-US.md index 4b1fcaeb6..bc3727289 100644 --- a/components/descriptions/index.en-US.md +++ b/components/descriptions/index.en-US.md @@ -2,7 +2,8 @@ category: Components type: Data Display title: Descriptions -cover: https://gw.alipayobjects.com/zos/alicdn/MjtG9_FOI/Descriptions.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*fHdlTpif6XQAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*d27AQJrowGAAAAAAAAAAAAAADrJ8AQ/original --- Display multiple read-only fields in groups. diff --git a/components/descriptions/index.tsx b/components/descriptions/index.tsx index 9f50604be..9068ec530 100644 --- a/components/descriptions/index.tsx +++ b/components/descriptions/index.tsx @@ -20,13 +20,14 @@ import { } from 'vue'; import warning from '../_util/warning'; import type { Breakpoint, ScreenMap } from '../_util/responsiveObserve'; -import ResponsiveObserve, { responsiveArray } from '../_util/responsiveObserve'; +import useResponsiveObserve, { responsiveArray } from '../_util/responsiveObserve'; import Row from './Row'; import PropTypes from '../_util/vue-types'; import { cloneElement } from '../_util/vnode'; import { flattenChildren } from '../_util/props-util'; -import useConfigInject from '../_util/hooks/useConfigInject'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; import type { CustomSlotsType } from '../_util/type'; +import useStyle from './style'; export const DescriptionsItemProps = { prefixCls: String, @@ -82,7 +83,7 @@ function getColumn(column: DescriptionsProps['column'], screens: ScreenMap): num return 3; } -function getFilledItem(node: VNode, span: number | undefined, rowRestCol: number): VNode { +function getFilledItem(node: VNode, rowRestCol: number, span?: number): VNode { let clone = node; if (span === undefined || span > rowRestCol) { @@ -106,12 +107,12 @@ function getRows(children: VNode[], column: number) { let tmpRow: VNode[] = []; let rowRestCol = column; childNodes.forEach((node, index) => { - const span: number | undefined = node.props?.span; + const span: number = node.props?.span; const mergedSpan = span || 1; // Additional handle last one if (index === childNodes.length - 1) { - tmpRow.push(getFilledItem(node, span, rowRestCol)); + tmpRow.push(getFilledItem(node, rowRestCol, span)); rows.push(tmpRow); return; } @@ -120,7 +121,7 @@ function getRows(children: VNode[], column: number) { rowRestCol -= mergedSpan; tmpRow.push(node); } else { - tmpRow.push(getFilledItem(node, mergedSpan, rowRestCol)); + tmpRow.push(getFilledItem(node, rowRestCol, mergedSpan)); rows.push(tmpRow); rowRestCol = column; tmpRow = []; @@ -160,6 +161,7 @@ export const descriptionsContext: InjectionKey = const Descriptions = defineComponent({ compatConfig: { MODE: 3 }, name: 'ADescriptions', + inheritAttrs: false, props: descriptionsProps(), slots: Object as CustomSlotsType<{ title?: any; @@ -167,12 +169,16 @@ const Descriptions = defineComponent({ default?: any; }>, Item: DescriptionsItem, - setup(props, { slots }) { + setup(props, { slots, attrs }) { const { prefixCls, direction } = useConfigInject('descriptions', props); let token: number; const screens = ref({}); + + const [wrapSSR, hashId] = useStyle(prefixCls); + const responsiveObserve = useResponsiveObserve(); + onBeforeMount(() => { - token = ResponsiveObserve.subscribe(screen => { + token = responsiveObserve.value.subscribe(screen => { if (typeof props.column !== 'object') { return; } @@ -181,7 +187,7 @@ const Descriptions = defineComponent({ }); onBeforeUnmount(() => { - ResponsiveObserve.unsubscribe(token); + responsiveObserve.value.unsubscribe(token); }); provide(descriptionsContext, { @@ -204,8 +210,9 @@ const Descriptions = defineComponent({ const children = slots.default?.(); const rows = getRows(children, mergeColumn.value); - return ( + return wrapSSR(
    {(title || extra) && ( @@ -238,7 +247,7 @@ const Descriptions = defineComponent({
    -
    +
    , ); }; }, diff --git a/components/descriptions/index.zh-CN.md b/components/descriptions/index.zh-CN.md index cdb11d721..303898671 100644 --- a/components/descriptions/index.zh-CN.md +++ b/components/descriptions/index.zh-CN.md @@ -3,7 +3,8 @@ category: Components type: 数据展示 title: Descriptions subtitle: 描述列表 -cover: https://gw.alipayobjects.com/zos/alicdn/MjtG9_FOI/Descriptions.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*fHdlTpif6XQAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*d27AQJrowGAAAAAAAAAAAAAADrJ8AQ/original --- 成组展示多个只读字段。 diff --git a/components/descriptions/style/index.less b/components/descriptions/style/index.less deleted file mode 100644 index 2a5a631c1..000000000 --- a/components/descriptions/style/index.less +++ /dev/null @@ -1,179 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@descriptions-prefix-cls: ~'@{ant-prefix}-descriptions'; - -.@{descriptions-prefix-cls} { - &-header { - display: flex; - align-items: center; - margin-bottom: @descriptions-title-margin-bottom; - } - - &-title { - flex: auto; - overflow: hidden; - color: @heading-color; - font-weight: bold; - font-size: @font-size-lg; - line-height: @line-height-base; - white-space: nowrap; - text-overflow: ellipsis; - } - - &-extra { - margin-left: auto; - color: @descriptions-extra-color; - font-size: @font-size-base; - } - - &-view { - width: 100%; - border-radius: @border-radius-base; - - table { - width: 100%; - table-layout: fixed; - } - } - - &-row { - > th, - > td { - padding-bottom: @descriptions-item-padding-bottom; - } - - &:last-child { - border-bottom: none; - } - } - - &-item-label { - color: @heading-color; - font-weight: normal; - font-size: @font-size-base; - line-height: @line-height-base; - text-align: start; - - &::after { - & when (@descriptions-item-trailing-colon=true) { - content: ':'; - } - & when not (@descriptions-item-trailing-colon=true) { - content: ' '; - } - - position: relative; - top: -0.5px; - margin: 0 @descriptions-item-label-colon-margin-right 0 - @descriptions-item-label-colon-margin-left; - } - - &.@{descriptions-prefix-cls}-item-no-colon::after { - content: ' '; - } - } - - &-item-no-label { - &::after { - margin: 0; - content: ''; - } - } - - &-item-content { - display: table-cell; - flex: 1; - color: @text-color; - font-size: @font-size-base; - line-height: @line-height-base; - word-break: break-word; - overflow-wrap: break-word; - } - - &-item { - padding-bottom: 0; - vertical-align: top; - - &-container { - display: flex; - - .@{descriptions-prefix-cls}-item-label, - .@{descriptions-prefix-cls}-item-content { - display: inline-flex; - align-items: baseline; - } - } - } - - &-middle { - .@{descriptions-prefix-cls}-row { - > th, - > td { - padding-bottom: @padding-sm; - } - } - } - - &-small { - .@{descriptions-prefix-cls}-row { - > th, - > td { - padding-bottom: @padding-xs; - } - } - } - - &-bordered { - .@{descriptions-prefix-cls}-view { - border: 1px solid @border-color-split; - - > table { - table-layout: auto; - border-collapse: collapse; - } - } - - .@{descriptions-prefix-cls}-item-label, - .@{descriptions-prefix-cls}-item-content { - padding: @descriptions-default-padding; - border-right: 1px solid @border-color-split; - - &:last-child { - border-right: none; - } - } - - .@{descriptions-prefix-cls}-item-label { - background-color: @descriptions-bg; - - &::after { - display: none; - } - } - - .@{descriptions-prefix-cls}-row { - border-bottom: 1px solid @border-color-split; - - &:last-child { - border-bottom: none; - } - } - - &.@{descriptions-prefix-cls}-middle { - .@{descriptions-prefix-cls}-item-label, - .@{descriptions-prefix-cls}-item-content { - padding: @descriptions-middle-padding; - } - } - - &.@{descriptions-prefix-cls}-small { - .@{descriptions-prefix-cls}-item-label, - .@{descriptions-prefix-cls}-item-content { - padding: @descriptions-small-padding; - } - } - } -} - -@import './rtl'; diff --git a/components/descriptions/style/index.ts b/components/descriptions/style/index.ts new file mode 100644 index 000000000..037edb550 --- /dev/null +++ b/components/descriptions/style/index.ts @@ -0,0 +1,208 @@ +import type { CSSObject } from '../../_util/cssinjs'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { resetComponent, textEllipsis } from '../../style'; + +interface DescriptionsToken extends FullToken<'Descriptions'> { + descriptionsTitleMarginBottom: number; + descriptionsExtraColor: string; + descriptionItemPaddingBottom: number; + descriptionsDefaultPadding: string; + descriptionsBg: string; + descriptionsMiddlePadding: string; + descriptionsSmallPadding: string; + descriptionsItemLabelColonMarginRight: number; + descriptionsItemLabelColonMarginLeft: number; +} + +const genBorderedStyle = (token: DescriptionsToken): CSSObject => { + const { + componentCls, + descriptionsSmallPadding, + descriptionsDefaultPadding, + descriptionsMiddlePadding, + descriptionsBg, + } = token; + return { + [`&${componentCls}-bordered`]: { + [`${componentCls}-view`]: { + border: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`, + '> table': { + tableLayout: 'auto', + borderCollapse: 'collapse', + }, + }, + [`${componentCls}-item-label, ${componentCls}-item-content`]: { + padding: descriptionsDefaultPadding, + borderInlineEnd: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`, + '&:last-child': { + borderInlineEnd: 'none', + }, + }, + [`${componentCls}-item-label`]: { + backgroundColor: descriptionsBg, + '&::after': { + display: 'none', + }, + }, + [`${componentCls}-row`]: { + borderBottom: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`, + '&:last-child': { + borderBottom: 'none', + }, + }, + [`&${componentCls}-middle`]: { + [`${componentCls}-item-label, ${componentCls}-item-content`]: { + padding: descriptionsMiddlePadding, + }, + }, + [`&${componentCls}-small`]: { + [`${componentCls}-item-label, ${componentCls}-item-content`]: { + padding: descriptionsSmallPadding, + }, + }, + }, + }; +}; + +const genDescriptionStyles: GenerateStyle = (token: DescriptionsToken) => { + const { + componentCls, + descriptionsExtraColor, + descriptionItemPaddingBottom, + descriptionsItemLabelColonMarginRight, + descriptionsItemLabelColonMarginLeft, + descriptionsTitleMarginBottom, + } = token; + return { + [componentCls]: { + ...resetComponent(token), + ...genBorderedStyle(token), + [`&-rtl`]: { + direction: 'rtl', + }, + [`${componentCls}-header`]: { + display: 'flex', + alignItems: 'center', + marginBottom: descriptionsTitleMarginBottom, + }, + [`${componentCls}-title`]: { + ...textEllipsis, + flex: 'auto', + color: token.colorText, + fontWeight: token.fontWeightStrong, + fontSize: token.fontSizeLG, + lineHeight: token.lineHeightLG, + }, + [`${componentCls}-extra`]: { + marginInlineStart: 'auto', + color: descriptionsExtraColor, + fontSize: token.fontSize, + }, + [`${componentCls}-view`]: { + width: '100%', + borderRadius: token.borderRadiusLG, + table: { + width: '100%', + tableLayout: 'fixed', + }, + }, + [`${componentCls}-row`]: { + '> th, > td': { + paddingBottom: descriptionItemPaddingBottom, + }, + '&:last-child': { + borderBottom: 'none', + }, + }, + [`${componentCls}-item-label`]: { + color: token.colorText, + fontWeight: 'normal', + fontSize: token.fontSize, + lineHeight: token.lineHeight, + textAlign: `start`, + + '&::after': { + content: '":"', + position: 'relative', + top: -0.5, // magic for position + marginInline: `${descriptionsItemLabelColonMarginLeft}px ${descriptionsItemLabelColonMarginRight}px`, + }, + + [`&${componentCls}-item-no-colon::after`]: { + content: '""', + }, + }, + [`${componentCls}-item-no-label`]: { + '&::after': { + margin: 0, + content: '""', + }, + }, + [`${componentCls}-item-content`]: { + display: 'table-cell', + flex: 1, + color: token.colorText, + fontSize: token.fontSize, + lineHeight: token.lineHeight, + wordBreak: 'break-word', + overflowWrap: 'break-word', + }, + [`${componentCls}-item`]: { + paddingBottom: 0, + verticalAlign: 'top', + '&-container': { + display: 'flex', + [`${componentCls}-item-label`]: { + display: 'inline-flex', + alignItems: 'baseline', + }, + [`${componentCls}-item-content`]: { + display: 'inline-flex', + alignItems: 'baseline', + }, + }, + }, + '&-middle': { + [`${componentCls}-row`]: { + '> th, > td': { + paddingBottom: token.paddingSM, + }, + }, + }, + '&-small': { + [`${componentCls}-row`]: { + '> th, > td': { + paddingBottom: token.paddingXS, + }, + }, + }, + }, + }; +}; +// ============================== Export ============================== +export default genComponentStyleHook('Descriptions', token => { + const descriptionsBg = token.colorFillAlter; + const descriptionsTitleMarginBottom = token.fontSizeSM * token.lineHeightSM; + const descriptionsExtraColor = token.colorText; + const descriptionsSmallPadding = `${token.paddingXS}px ${token.padding}px`; + const descriptionsDefaultPadding = `${token.padding}px ${token.paddingLG}px`; + const descriptionsMiddlePadding = `${token.paddingSM}px ${token.paddingLG}px`; + const descriptionItemPaddingBottom = token.padding; + const descriptionsItemLabelColonMarginRight = token.marginXS; + const descriptionsItemLabelColonMarginLeft = token.marginXXS / 2; + + const descriptionToken = mergeToken(token, { + descriptionsBg, + descriptionsTitleMarginBottom, + descriptionsExtraColor, + descriptionItemPaddingBottom, + descriptionsSmallPadding, + descriptionsDefaultPadding, + descriptionsMiddlePadding, + descriptionsItemLabelColonMarginRight, + descriptionsItemLabelColonMarginLeft, + }); + + return [genDescriptionStyles(descriptionToken)]; +}); diff --git a/components/descriptions/style/index.tsx b/components/descriptions/style/index.tsx deleted file mode 100644 index 3a3ab0de5..000000000 --- a/components/descriptions/style/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import '../../style/index.less'; -import './index.less'; diff --git a/components/descriptions/style/rtl.less b/components/descriptions/style/rtl.less deleted file mode 100644 index 73d22dc3b..000000000 --- a/components/descriptions/style/rtl.less +++ /dev/null @@ -1,33 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@descriptions-prefix-cls: ~'@{ant-prefix}-descriptions'; - -.@{descriptions-prefix-cls} { - &-rtl { - direction: rtl; - } - - &-item-label { - &::after { - .@{descriptions-prefix-cls}-rtl & { - margin: 0 @descriptions-item-label-colon-margin-left 0 - @descriptions-item-label-colon-margin-right; - } - } - } - - &-bordered { - .@{descriptions-prefix-cls}-item-label, - .@{descriptions-prefix-cls}-item-content { - .@{descriptions-prefix-cls}-rtl& { - border-right: none; - border-left: 1px solid @border-color-split; - - &:last-child { - border-left: none; - } - } - } - } -} diff --git a/components/divider/__tests__/__snapshots__/demo.test.js.snap b/components/divider/__tests__/__snapshots__/demo.test.js.snap index 582d2fb35..d43609949 100644 --- a/components/divider/__tests__/__snapshots__/demo.test.js.snap +++ b/components/divider/__tests__/__snapshots__/demo.test.js.snap @@ -1,16 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders ./components/divider/demo/customize-style.vue correctly 1`] = ` -