diff --git a/.eslintrc.js b/.eslintrc.js index face650ee7..d6b5da49da 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,6 +27,7 @@ module.exports = { // ], plugins: ['markdown', 'jest', '@typescript-eslint', 'import'], globals: { + h: true, defineProps: 'readonly', }, overrides: [ @@ -108,7 +109,4 @@ module.exports = { ], 'vue/multi-word-component-names': 'off', }, - globals: { - h: true, - }, }; diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 32718a94d1..00c687e3b4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - name: Create new issue url: https://vuecomponent.github.io/issue-helper/ diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index b5199b61a0..ad5aac3950 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write # for actions-cool/issues-helper to update PRs runs-on: ubuntu-latest steps: - - name: Need Reproduce + - name: Need Reproduce if: github.event.label.name == '🤔 Need Reproduce' uses: actions-cool/issues-helper@v3 with: diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 386a3e228c..84d4ffa90c 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -10,6 +10,72 @@ --- +## 4.2.6 + +- 🐞 Fix Modal component aria-hidden error problem under chrome [#7823](https://github.com/vueComponent/ant-design-vue/issues/7823) +- 🐞 Fix the problem that the built-in input method of Safari automatically fills in the decimal point when inputting Chinese [#7918](https://github.com/vueComponent/ant-design-vue/issues/7918) +- 🐞 Fix InputNumber component disabled style problem [#7776](https://github.com/vueComponent/ant-design-vue/issues/7776) +- 🐞 Fix Select cannot lose focus problem [#7819](https://github.com/vueComponent/ant-design-vue/issues/7819) + +## 4.2.5 + +- 🐞 Fix Empty component memory leak problem +- 🐞 Fix Image width & height property not working problem + +## 4.2.4 + +- 🐞 Fix Wave memory leak problem + +## 4.2.3 + +- 🌟 TourStep custom Button, support function children [#7628](https://github.com/vueComponent/ant-design-vue/pull/7628) +- 🐞 Fix the problem that the input value is hidden in Select and Cascader search multi-select mode [#7640](https://github.com/vueComponent/ant-design-vue/issues/7640) + +## 4.2.2 + +- 🐞 Fix TreeSelect placeholder slot invalid [#7545](https://github.com/vueComponent/ant-design-vue/issues/7545) +- 🐞 Fix Tree slot responsive invalid issue [40ad45](https://github.com/vueComponent/ant-design-vue/commit/40ad45bc05b2bf9d0a2445d9f6ff365468ba90b7) +- 🐞 Fix FloatButton target type error issue [#7576](https://github.com/vueComponent/ant-design-vue/issues/7576) +- 🐞 Fix FormItem className error issue [#7582](https://github.com/vueComponent/ant-design-vue/issues/7582) +- 🐞 Fix Input Cannot input problem under lazy [#7543](https://github.com/vueComponent/ant-design-vue/issues/7543) +- 🐞 Fix the problem that placeholder is not hidden when inputting Chinese in Select [#7611](https://github.com/vueComponent/ant-design-vue/issues/7611) +- 🐞 Fix the problem that the pop-up window flashes when clicking the preset option in DatePicker [#7550](https://github.com/vueComponent/ant-design-vue/issues/7550) + +## 4.2.1 + +- 🐞 fix Input clear action error [#7523](https://github.com/vueComponent/ant-design-vue/issues/7523) + +## 4.2.0 + +- 🌟 Optimize the textColor change when the layout component switches to dark mode [#7498](https://github.com/vueComponent/ant-design-vue/issues/7498) +- 🌟 Tooltip added arrow hidden configuration [#7459](https://github.com/vueComponent/ant-design-vue/issues/7459) +- 🌟 Optimize Table hover performance [#7451](https://github.com/vueComponent/ant-design-vue/issues/7451) +- 🐞 Fixed the problem of changing the model during useForm verification, resulting in verification errors [#ffd4d8](https://github.com/vueComponent/ant-design-vue/commit/ffd4d8fe927f9ea40cbb6358ad997c447bd9a74e) +- 🐞 Fix Tabs folding calculation error issue [#7491](https://github.com/vueComponent/ant-design-vue/issues/7491) +- 🐞 Fix Qrcode missing type hint issue [#7502](https://github.com/vueComponent/ant-design-vue/issues/7502) +- 🐞 Fix Menu rendering error under SSR [#7349](https://github.com/vueComponent/ant-design-vue/issues/7349) +- 🐞 Fix Select and Cascader rendering errors under SSR [#7377](https://github.com/vueComponent/ant-design-vue/issues/7377) +- 🐞 Fix AutoComplete missing option slot declaration issue [#7396](https://github.com/vueComponent/ant-design-vue/issues/7396) +- 🐞 Fix Textarea autoSize not taking effect [#7478](https://github.com/vueComponent/ant-design-vue/issues/7478) +- 🐞 Fix Pagination’s Enter key triggering two page turns [#7368](https://github.com/vueComponent/ant-design-vue/issues/7368) +- 🐞 Fix the problem of Chinese input in the input box [#7391](https://github.com/vueComponent/ant-design-vue/issues/7391)[#7516](https://github.com/vueComponent/ant- design-vue/issues/7516) +- 🐞 Fix Carousel beforeChange current parameter error issue [#7419](https://github.com/vueComponent/ant-design-vue/issues/7419) + +## 4.1.2 + +- 🐞 Fix table resize error reporting under vue 3.4 [#7291](https://github.com/vueComponent/ant-design-vue/issues/7291) +- 🐞 Fix the problem that the Segmented title attribute is not displayed [#7302](https://github.com/vueComponent/ant-design-vue/issues/7302) + +## 4.1.1 + +- 🌟 QRcode adds scanned status [#7242](https://github.com/vueComponent/ant-design-vue/issues/7242) +- 🐞 Fix css prefix issue in nuxt [#7256](https://github.com/vueComponent/ant-design-vue/issues/7256) +- 🐞 Fix dropdown closing issue [#7246](https://github.com/vueComponent/ant-design-vue/issues/7246) +- 🐞 Fix divider vertical dashed not display issue [#7218](https://github.com/vueComponent/ant-design-vue/issues/7218) +- 🐞 Fix hook mode message console warning issue [#7281](https://github.com/vueComponent/ant-design-vue/issues/7281) +- 🐞 Fix table expansion error reporting under vue 3.4 [#7265](https://github.com/vueComponent/ant-design-vue/issues/7265) +- 🐞 Fix table group filter status error issue [#7233](https://github.com/vueComponent/ant-design-vue/issues/7233) + ## 4.1.0 - 🐞 support vue 3.4 [#7239](https://github.com/vueComponent/ant-design-vue/issues/7239) diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 721265308b..c9b50d9631 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -10,6 +10,72 @@ --- +## 4.2.6 + +- 🐞 修复 Modal 组件在 chrome 下,aria-hidden 报错问题 [#7823](https://github.com/vueComponent/ant-design-vue/issues/7823) +- 🐞 修复 Safari 下自带输入法 input 组件输入中文时,自动填写小数点问题 [#7918](https://github.com/vueComponent/ant-design-vue/issues/7918) +- 🐞 修复 InputNumber 组件 disabled 样式问题 [#7776](https://github.com/vueComponent/ant-design-vue/issues/7776) +- 🐞 修复 Select 无法失焦问题 [#7819](https://github.com/vueComponent/ant-design-vue/issues/7819) + +## 4.2.5 + +- 🐞 修复 Empty 组件内存泄漏问题 +- 🐞 修复 Image width & height 属性不生效问题 + +## 4.2.4 + +- 🐞 修复 Wave 内存泄漏问题 + +## 4.2.3 + +- 🌟 TourStep 自定义 Button,支持函数 children [#7628](https://github.com/vueComponent/ant-design-vue/pull/7628) +- 🐞 修复 Select 和 Cascader 搜索多选模式下,输入值被隐藏问题 [#7640](https://github.com/vueComponent/ant-design-vue/issues/7640) + +## 4.2.2 + +- 🐞 修复 TreeSelect placeholder 插槽无效 [#7545](https://github.com/vueComponent/ant-design-vue/issues/7545) +- 🐞 修复 Tree 插槽响应式无效问题 [40ad45](https://github.com/vueComponent/ant-design-vue/commit/40ad45bc05b2bf9d0a2445d9f6ff365468ba90b7) +- 🐞 修复 FloatButton target 类型错误问题 [#7576](https://github.com/vueComponent/ant-design-vue/issues/7576) +- 🐞 修复 FormItem className 错误问题 [#7582](https://github.com/vueComponent/ant-design-vue/issues/7582) +- 🐞 修复 Input lazy 下无法输入问题 [#7543](https://github.com/vueComponent/ant-design-vue/issues/7543) +- 🐞 修复 Select 输入中文时,placeholder 未隐藏问题 [#7611](https://github.com/vueComponent/ant-design-vue/issues/7611) +- 🐞 修复 DatePicker 点击预设选项时,弹窗闪动问题 [#7550](https://github.com/vueComponent/ant-design-vue/issues/7550) + +## 4.2.1 + +- 🐞 修复 Input 清空操作才报错问题 [#7523](https://github.com/vueComponent/ant-design-vue/issues/7523) + +## 4.2.0 + +- 🌟 优化 layout 组件切换 dark 模式时 textColor 变化 [#7498](https://github.com/vueComponent/ant-design-vue/issues/7498) +- 🌟 Tooltip 新增 arrow 隐藏配置 [#7459](https://github.com/vueComponent/ant-design-vue/issues/7459) +- 🌟 优化 Table hover 性能 [#7451](https://github.com/vueComponent/ant-design-vue/issues/7451) +- 🐞 修复 useForm 校验时更改 model,导致校验错误问题 [#ffd4d8](https://github.com/vueComponent/ant-design-vue/commit/ffd4d8fe927f9ea40cbb6358ad997c447bd9a74e) +- 🐞 修复 Tabs 折叠计算错误问题 [#7491](https://github.com/vueComponent/ant-design-vue/issues/7491) +- 🐞 修复 Qrcode 缺少类型提示问题 [#7502](https://github.com/vueComponent/ant-design-vue/issues/7502) +- 🐞 修复 Menu 在 SSR 下渲染错误问题 [#7349](https://github.com/vueComponent/ant-design-vue/issues/7349) +- 🐞 修复 Select、Cascader 在 SSR 下渲染错误问题 [#7377](https://github.com/vueComponent/ant-design-vue/issues/7377) +- 🐞 修复 AutoComplete 缺少 option slot 声明问题 [#7396](https://github.com/vueComponent/ant-design-vue/issues/7396) +- 🐞 修复 Textarea autoSize 不生效问题 [#7478](https://github.com/vueComponent/ant-design-vue/issues/7478) +- 🐞 修复 Pagination 回车键触发两次翻页问题 [#7368](https://github.com/vueComponent/ant-design-vue/issues/7368) +- 🐞 修复输入框输入中文问题 [#7391](https://github.com/vueComponent/ant-design-vue/issues/7391)[#7516](https://github.com/vueComponent/ant-design-vue/issues/7516) +- 🐞 修复 Carousel beforeChange current 参数错误问题 [#7419](https://github.com/vueComponent/ant-design-vue/issues/7419) + +## 4.1.2 + +- 🐞 修复 table resize 在 vue 3.4 下报错问题 [#7291](https://github.com/vueComponent/ant-design-vue/issues/7291) +- 🐞 修复 Segmented title 属性不显示问题 [#7302](https://github.com/vueComponent/ant-design-vue/issues/7302) + +## 4.1.1 + +- 🌟 QRcode 新增 scanned 状态 [#7242](https://github.com/vueComponent/ant-design-vue/issues/7242) +- 🐞 修复 css prefix 在 nuxt 问题 [#7256](https://github.com/vueComponent/ant-design-vue/issues/7256) +- 🐞 修复 dropdown 关闭问题 [#7246](https://github.com/vueComponent/ant-design-vue/issues/7246) +- 🐞 修复 divider vertical dashed 不显示问题 [#7218](https://github.com/vueComponent/ant-design-vue/issues/7218) +- 🐞 修复 hook 模式 message 控制台 warning 问题 [#7281](https://github.com/vueComponent/ant-design-vue/issues/7281) +- 🐞 修复 table 展开在 vue 3.4 下报错问题 [#7265](https://github.com/vueComponent/ant-design-vue/issues/7265) +- 🐞 修复 table group 过滤状态错误问题 [#7233](https://github.com/vueComponent/ant-design-vue/issues/7233) + ## 4.1.0 - 🐞 适配 vue 3.4 [#7239](https://github.com/vueComponent/ant-design-vue/issues/7239) diff --git a/README-zh_CN.md b/README-zh_CN.md index d894b6576d..05a8f70302 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -10,7 +10,7 @@
-An enterprise-class UI components based on Ant Design and Vue 3. +基于 Ant Design 和 Vue 3 的企业级 UI 组件库。 ![test](https://github.com/vueComponent/ant-design-vue/workflows/test/badge.svg) [![codecov](https://img.shields.io/codecov/c/github/vueComponent/ant-design-vue/master.svg?style=flat-square)](https://codecov.io/gh/vueComponent/ant-design-vue) [![npm package](https://img.shields.io/npm/v/ant-design-vue.svg?style=flat-square)](https://www.npmjs.org/package/ant-design-vue) [![NPM downloads](http://img.shields.io/npm/dm/ant-design-vue.svg?style=flat-square)](http://www.npmtrends.com/ant-design-vue) [![backers](https://opencollective.com/ant-design-vue/backers/badge.svg)](#backers) [![sponsors](https://opencollective.com/ant-design-vue/sponsors/badge.svg)](#sponsors) [![extension-for-VSCode](https://img.shields.io/badge/extension%20for-VSCode-blue.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=ant-design-vue.vscode-ant-design-vue-helper) [![issues-helper](https://img.shields.io/badge/Issues%20Manage%20By-issues--helper-orange?style=flat-square)](https://github.com/actions-cool/issues-helper) @@ -73,6 +73,7 @@ $ yarn add 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 模块扩展 | +| [ant-design-x-vue](https://github.com/wzc520pyfm/ant-design-x-vue) | 基于 Ant Design X 设计规范的 Vue AI 界面解决方案 | ## 问答 @@ -88,22 +89,23 @@ ant-design-vue 是 MIT 协议的开源项目。为了项目能够更好的持续 - [opencollective](https://opencollective.com/ant-design-vue) - [paypal](https://www.paypal.me/tangjinzhou) - [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png) +- ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2 -## Sponsors +## 赞助商 -Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/ant-design-vue#sponsor)] +成为赞助商,并在 Github 上的自述文件上获得您的徽标,并链接到您的网站。 [[成为赞助商](https://opencollective.com/ant-design-vue#sponsor)] -## Backers +## 支持者 -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/ant-design-vue#backer)] +每月捐款支持我们,帮助我们继续我们的活动。 [[成为支持者](https://opencollective.com/ant-design-vue#backer)] ## Patreon -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://www.patreon.com/tangjinzhou)] +每月捐款支持我们,帮助我们继续我们的活动。 [[成为支持者](https://www.patreon.com/tangjinzhou)] diff --git a/README.md b/README.md index 03eff07fcc..3bc3fb0fc3 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ If you are in a bad network environment, you can try other registries and tools | [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 | +| [ant-design-x-vue](https://github.com/wzc520pyfm/ant-design-x-vue) | A Vue AI interface solutions base on the Ant Design X design specification | ## Donation @@ -82,6 +83,7 @@ ant-design-vue is an MIT-licensed open source project. In order to achieve bette - [opencollective](https://opencollective.com/ant-design-vue) - [paypal](https://www.paypal.me/tangjinzhou) - [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png) +- ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2 ## Sponsors diff --git a/antd-tools/generator-types/src/index.ts b/antd-tools/generator-types/src/index.ts index 4b1e3d43ff..aad960d39a 100644 --- a/antd-tools/generator-types/src/index.ts +++ b/antd-tools/generator-types/src/index.ts @@ -6,7 +6,6 @@ import { genWebTypes } from './web-types'; 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> { @@ -22,13 +21,13 @@ async function readMarkdown(options: Options): Promise> { return formatter(mdParser(fileContent), componentName, kebabComponentName, options.tagPrefix); }) .filter(item => item) as VueTag[][]; - const tags: Map = new Map(); + const tags = new Map(); flatMap(data, item => item).forEach(mergedTag => mergeTag(tags, mergedTag)); return tags; } function readTypings(options: Options): Map { - const tags: Map = new Map(); + const tags = new Map(); const fileContent = readFileSync(options.typingsPath, 'utf-8'); fileContent .split('\n') @@ -62,7 +61,7 @@ function mergeTag(tags: Map, mergedTag: VueTag) { function mergeTags(mergedTagsArr: Map[]): VueTag[] { if (mergedTagsArr.length === 1) return [...mergedTagsArr[0].values()]; - const tags: Map = new Map(); + const tags = new Map(); if (mergedTagsArr.length === 0) return []; mergedTagsArr.forEach(mergedTags => { mergedTags.forEach(mergedTag => mergeTag(tags, mergedTag)); @@ -78,13 +77,6 @@ export async function parseAndWrite(options: Options): Promise { const tagsFromTypings = await readTypings(options); const tags = mergeTags([tagsFromMarkdown, tagsFromTypings]); const webTypes = genWebTypes(tags, options); - const veturTags = genVeturTags(tags); - const veturAttributes = genVeturAttributes(tags); - outputFileSync(join(options.outputDir, 'tags.json'), JSON.stringify(veturTags, null, 2)); - outputFileSync( - join(options.outputDir, 'attributes.json'), - JSON.stringify(veturAttributes, null, 2), - ); outputFileSync(join(options.outputDir, 'web-types.json'), JSON.stringify(webTypes, null, 2)); return tags.length; } diff --git a/antd-tools/generator-types/src/parser.ts b/antd-tools/generator-types/src/parser.ts index 59f7d2483f..bdcafb3d50 100644 --- a/antd-tools/generator-types/src/parser.ts +++ b/antd-tools/generator-types/src/parser.ts @@ -21,7 +21,7 @@ export type Articals = Artical[]; function readLine(input: string) { const end = input.indexOf('\n'); - return input.substr(0, end !== -1 ? end : input.length); + return input.substring(0, end !== -1 ? end : input.length); } function splitTableLine(line: string) { @@ -47,7 +47,7 @@ function tableParse(input: string) { }; while (start < end) { - const target = input.substr(start); + const target = input.substring(start); const line = readLine(target); if (!/^\|/.test(target)) { @@ -79,7 +79,7 @@ export function mdParser(input: string): Articals { const end = input.length; while (start < end) { - const target = input.substr(start); + const target = input.substring(start); let match; if ((match = TITLE_REG.exec(target))) { @@ -91,7 +91,7 @@ export function mdParser(input: string): Articals { start += match.index + match[0].length; } else if ((match = TABLE_REG.exec(target))) { - const { table, usedLength } = tableParse(target.substr(match.index)); + const { table, usedLength } = tableParse(target.substring(match.index)); artical.push({ type: 'table', table, diff --git a/antd-tools/generator-types/src/type.ts b/antd-tools/generator-types/src/type.ts index faf2ed939a..6be1c0ba5e 100644 --- a/antd-tools/generator-types/src/type.ts +++ b/antd-tools/generator-types/src/type.ts @@ -34,25 +34,6 @@ export type VueTag = { description?: string; }; -export type VeturTag = { - description?: string; - attributes: string[]; -}; - -export type VeturTags = Record; - -export type VeturAttribute = { - type: string; - description: string; -}; - -export type VeturAttributes = Record; - -export type VeturResult = { - tags: VeturTags; - attributes: VeturAttributes; -}; - export type Options = { name: string; path: PathLike; diff --git a/antd-tools/generator-types/src/vetur.ts b/antd-tools/generator-types/src/vetur.ts deleted file mode 100644 index da34458230..0000000000 --- a/antd-tools/generator-types/src/vetur.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { VueTag, VeturTags, VeturAttributes } from './type'; - -export function genVeturTags(tags: VueTag[]) { - const veturTags: VeturTags = {}; - - tags.forEach(tag => { - veturTags[tag.name] = { - attributes: tag.attributes ? tag.attributes.map(item => item.name) : [], - }; - }); - - return veturTags; -} - -export function genVeturAttributes(tags: VueTag[]) { - const veturAttributes: VeturAttributes = {}; - - tags.forEach(tag => { - if (tag.attributes) { - tag.attributes.forEach(attr => { - veturAttributes[`${tag.name}/${attr.name}`] = { - type: attr.value.type, - description: `${attr.description}, Default: ${attr.default}`, - }; - }); - } - }); - - return veturAttributes; -} diff --git a/antd-tools/getTSCommonConfig.js b/antd-tools/getTSCommonConfig.js index d935888c4d..fd2a33ff61 100644 --- a/antd-tools/getTSCommonConfig.js +++ b/antd-tools/getTSCommonConfig.js @@ -1,7 +1,6 @@ 'use strict'; const fs = require('fs'); -const assign = require('object-assign'); const { getProjectPath } = require('./utils/projectHelper'); module.exports = function () { @@ -9,7 +8,7 @@ module.exports = function () { if (fs.existsSync(getProjectPath('tsconfig.json'))) { my = require(getProjectPath('tsconfig.json')); } - return assign( + return Object.assign( { noUnusedParameters: true, noUnusedLocals: true, diff --git a/components/_util/BaseInput.tsx b/components/_util/BaseInput.tsx index db85e3a4f2..e6bc9d7085 100644 --- a/components/_util/BaseInput.tsx +++ b/components/_util/BaseInput.tsx @@ -1,51 +1,167 @@ -import { defineComponent, shallowRef, withDirectives } from 'vue'; -import antInput from './antInputDirective'; +import type { PropType } from 'vue'; +import { computed, defineComponent, shallowRef, ref, watch } from 'vue'; import PropTypes from './vue-types'; +import type { BaseInputInnerExpose } from './BaseInputInner'; +import BaseInputInner from './BaseInputInner'; +import { styleObjectToString } from '../vc-util/Dom/css'; + +export interface BaseInputExpose { + focus: () => void; + blur: () => void; + input: HTMLInputElement | HTMLTextAreaElement | null; + setSelectionRange: ( + start: number, + end: number, + direction?: 'forward' | 'backward' | 'none', + ) => void; + select: () => void; + getSelectionStart: () => number | null; + getSelectionEnd: () => number | null; + getScrollTop: () => number | null; + setScrollTop: (scrollTop: number) => void; +} const BaseInput = defineComponent({ compatConfig: { MODE: 3 }, + inheritAttrs: false, props: { - value: PropTypes.string.def(''), + disabled: PropTypes.looseBool, + type: PropTypes.string, + value: PropTypes.any, + lazy: PropTypes.bool.def(true), + tag: { + type: String as PropType<'input' | 'textarea'>, + default: 'input', + }, + size: PropTypes.string, + style: PropTypes.oneOfType([String, Object]), + class: PropTypes.string, }, - emits: ['change', 'input'], - setup(_p, { emit }) { - const inputRef = shallowRef(null); + emits: [ + 'change', + 'input', + 'blur', + 'keydown', + 'focus', + 'compositionstart', + 'compositionend', + 'keyup', + 'paste', + 'mousedown', + ], + setup(props, { emit, attrs, expose }) { + const inputRef = shallowRef(null); + const renderValue = ref(); + const isComposing = ref(false); + watch( + [() => props.value, isComposing], + () => { + if (isComposing.value) return; + renderValue.value = props.value; + }, + { immediate: true }, + ); const handleChange = (e: Event) => { - const { composing } = e.target as any; - if ((e as any).isComposing || composing) { - emit('input', e); - } else { - emit('input', e); - emit('change', e); + emit('change', e); + }; + const onCompositionstart = (e: CompositionEvent) => { + isComposing.value = true; + (e.target as any).composing = true; + emit('compositionstart', e); + }; + const onCompositionend = (e: CompositionEvent) => { + isComposing.value = false; + (e.target as any).composing = false; + emit('compositionend', e); + const event = document.createEvent('HTMLEvents'); + event.initEvent('input', true, true); + e.target.dispatchEvent(event); + handleChange(e); + }; + const handleInput = (e: Event) => { + if (isComposing.value && props.lazy) { + renderValue.value = (e.target as HTMLInputElement).value; + return; } + emit('input', e); }; - return { - inputRef, - focus: () => { - if (inputRef.value) { - inputRef.value.focus(); - } - }, - blur: () => { - if (inputRef.value) { - inputRef.value.blur(); - } - }, - handleChange, + + const handleBlur = (e: Event) => { + emit('blur', e); }; - }, - render() { - return withDirectives( - ( - { + emit('focus', e); + }; + + const focus = () => { + if (inputRef.value) { + inputRef.value.focus(); + } + }; + const blur = () => { + if (inputRef.value) { + inputRef.value.blur(); + } + }; + const handleKeyDown = (e: KeyboardEvent) => { + emit('keydown', e); + }; + const handleKeyUp = (e: KeyboardEvent) => { + emit('keyup', e); + }; + const setSelectionRange = ( + start: number, + end: number, + direction?: 'forward' | 'backward' | 'none', + ) => { + inputRef.value?.setSelectionRange(start, end, direction); + }; + + const select = () => { + inputRef.value?.select(); + }; + expose({ + focus, + blur, + input: computed(() => inputRef.value?.input), + setSelectionRange, + select, + getSelectionStart: () => inputRef.value?.getSelectionStart(), + getSelectionEnd: () => inputRef.value?.getSelectionEnd(), + getScrollTop: () => inputRef.value?.getScrollTop(), + }); + const handleMousedown = (e: MouseEvent) => { + emit('mousedown', e); + }; + const handlePaste = (e: ClipboardEvent) => { + emit('paste', e); + }; + const styleString = computed(() => { + return props.style && typeof props.style !== 'string' + ? styleObjectToString(props.style) + : props.style; + }); + return () => { + const { style, lazy, ...restProps } = props; + return ( + - ) as any, - [[antInput]], - ); + ); + }; }, }); diff --git a/components/_util/BaseInputInner.tsx b/components/_util/BaseInputInner.tsx new file mode 100644 index 0000000000..10423d7a45 --- /dev/null +++ b/components/_util/BaseInputInner.tsx @@ -0,0 +1,96 @@ +import type { PropType } from 'vue'; +import { defineComponent, shallowRef } from 'vue'; +import PropTypes from './vue-types'; + +export interface BaseInputInnerExpose { + focus: () => void; + blur: () => void; + input: HTMLInputElement | HTMLTextAreaElement | null; + setSelectionRange: ( + start: number, + end: number, + direction?: 'forward' | 'backward' | 'none', + ) => void; + select: () => void; + getSelectionStart: () => number | null; + getSelectionEnd: () => number | null; + getScrollTop: () => number | null; + setScrollTop: (scrollTop: number) => void; +} +const BaseInputInner = defineComponent({ + compatConfig: { MODE: 3 }, + // inheritAttrs: false, + props: { + disabled: PropTypes.looseBool, + type: PropTypes.string, + value: PropTypes.any, + tag: { + type: String as PropType<'input' | 'textarea'>, + default: 'input', + }, + size: PropTypes.string, + onChange: Function as PropType<(e: Event) => void>, + onInput: Function as PropType<(e: Event) => void>, + onBlur: Function as PropType<(e: Event) => void>, + onFocus: Function as PropType<(e: Event) => void>, + onKeydown: Function as PropType<(e: Event) => void>, + onCompositionstart: Function as PropType<(e: Event) => void>, + onCompositionend: Function as PropType<(e: Event) => void>, + onKeyup: Function as PropType<(e: Event) => void>, + onPaste: Function as PropType<(e: Event) => void>, + onMousedown: Function as PropType<(e: Event) => void>, + }, + emits: [ + 'change', + 'input', + 'blur', + 'keydown', + 'focus', + 'compositionstart', + 'compositionend', + 'keyup', + 'paste', + 'mousedown', + ], + setup(props, { expose }) { + const inputRef = shallowRef(null); + + const focus = () => { + if (inputRef.value) { + inputRef.value.focus(); + } + }; + const blur = () => { + if (inputRef.value) { + inputRef.value.blur(); + } + }; + const setSelectionRange = ( + start: number, + end: number, + direction?: 'forward' | 'backward' | 'none', + ) => { + inputRef.value?.setSelectionRange(start, end, direction); + }; + + const select = () => { + inputRef.value?.select(); + }; + expose({ + focus, + blur, + input: inputRef, + setSelectionRange, + select, + getSelectionStart: () => inputRef.value?.selectionStart, + getSelectionEnd: () => inputRef.value?.selectionEnd, + getScrollTop: () => inputRef.value?.scrollTop, + }); + return () => { + const { tag: Tag, value, ...restProps } = props; + return ; + }; + }, +}); + +export default BaseInputInner; diff --git a/components/_util/__mocks__/RenderSlot.tsx b/components/_util/__mocks__/RenderSlot.tsx new file mode 100644 index 0000000000..42e40ee0a9 --- /dev/null +++ b/components/_util/__mocks__/RenderSlot.tsx @@ -0,0 +1,11 @@ +import { defineComponent } from 'vue'; +import { customRenderSlot } from '../vnode'; + +export default defineComponent({ + name: 'RenderSlot', + setup(_props, { slots }) { + return () => { + return customRenderSlot(slots, 'default', {}, () => ['default value']); + }; + }, +}); diff --git a/components/_util/__tests__/vNode.test.js b/components/_util/__tests__/vNode.test.js new file mode 100644 index 0000000000..4bfc7e8fa9 --- /dev/null +++ b/components/_util/__tests__/vNode.test.js @@ -0,0 +1,26 @@ +import RenderSlot from '../__mocks__/RenderSlot'; +import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; + +describe('render slot content', () => { + it('renders slot content', () => { + const wrapper = mount(RenderSlot, { + slots: { + default: () => 'This is slot content', + }, + }); + + expect(wrapper.html()).toContain('This is slot content'); + }); + + it('render default value when slot is fragment', async () => { + const wrapper = mount(RenderSlot, { + slots: { + default: () => <>, + }, + }); + + await nextTick(); + expect(wrapper.html()).toContain('default value'); + }); +}); diff --git a/components/_util/antInputDirective.ts b/components/_util/antInputDirective.ts deleted file mode 100644 index 204390f4c0..0000000000 --- a/components/_util/antInputDirective.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Directive } from 'vue'; - -function onCompositionStart(e: any) { - e.target.composing = true; -} - -function onCompositionEnd(e: any) { - // prevent triggering an input event for no reason - if (!e.target.composing) return; - e.target.composing = false; - trigger(e.target, 'input'); -} - -function trigger(el, type) { - const e = document.createEvent('HTMLEvents'); - e.initEvent(type, true, true); - el.dispatchEvent(e); -} - -export function addEventListener( - el: HTMLElement, - event: string, - handler: EventListenerOrEventListenerObject, - options?: boolean | AddEventListenerOptions, -) { - el.addEventListener(event, handler, options); -} -const antInput: Directive = { - created(el, binding) { - if (!binding.modifiers || !binding.modifiers.lazy) { - addEventListener(el, 'compositionstart', onCompositionStart); - addEventListener(el, 'compositionend', onCompositionEnd); - // Safari < 10.2 & UIWebView doesn't fire compositionend when - // switching focus before confirming composition choice - // this also fixes the issue where some browsers e.g. iOS Chrome - // fires "change" instead of "input" on autocomplete. - addEventListener(el, 'change', onCompositionEnd); - } - }, -}; - -export default antInput; diff --git a/components/_util/css-animation/Event.js b/components/_util/css-animation/Event.js deleted file mode 100644 index cd5e871552..0000000000 --- a/components/_util/css-animation/Event.js +++ /dev/null @@ -1,130 +0,0 @@ -const START_EVENT_NAME_MAP = { - transitionstart: { - transition: 'transitionstart', - WebkitTransition: 'webkitTransitionStart', - MozTransition: 'mozTransitionStart', - OTransition: 'oTransitionStart', - msTransition: 'MSTransitionStart', - }, - - animationstart: { - animation: 'animationstart', - WebkitAnimation: 'webkitAnimationStart', - MozAnimation: 'mozAnimationStart', - OAnimation: 'oAnimationStart', - msAnimation: 'MSAnimationStart', - }, -}; - -const END_EVENT_NAME_MAP = { - transitionend: { - transition: 'transitionend', - WebkitTransition: 'webkitTransitionEnd', - MozTransition: 'mozTransitionEnd', - OTransition: 'oTransitionEnd', - msTransition: 'MSTransitionEnd', - }, - - animationend: { - animation: 'animationend', - WebkitAnimation: 'webkitAnimationEnd', - MozAnimation: 'mozAnimationEnd', - OAnimation: 'oAnimationEnd', - msAnimation: 'MSAnimationEnd', - }, -}; - -const startEvents = []; -const endEvents = []; - -function detectEvents() { - const testEl = document.createElement('div'); - const style = testEl.style; - - if (!('AnimationEvent' in window)) { - delete START_EVENT_NAME_MAP.animationstart.animation; - delete END_EVENT_NAME_MAP.animationend.animation; - } - - if (!('TransitionEvent' in window)) { - delete START_EVENT_NAME_MAP.transitionstart.transition; - delete END_EVENT_NAME_MAP.transitionend.transition; - } - - function process(EVENT_NAME_MAP, events) { - for (const baseEventName in EVENT_NAME_MAP) { - if (EVENT_NAME_MAP.hasOwnProperty(baseEventName)) { - const baseEvents = EVENT_NAME_MAP[baseEventName]; - for (const styleName in baseEvents) { - if (styleName in style) { - events.push(baseEvents[styleName]); - break; - } - } - } - } - } - - process(START_EVENT_NAME_MAP, startEvents); - process(END_EVENT_NAME_MAP, endEvents); -} - -if (typeof window !== 'undefined' && typeof document !== 'undefined') { - detectEvents(); -} - -function addEventListener(node, eventName, eventListener) { - node.addEventListener(eventName, eventListener, false); -} - -function removeEventListener(node, eventName, eventListener) { - node.removeEventListener(eventName, eventListener, false); -} - -const TransitionEvents = { - // Start events - startEvents, - - addStartEventListener(node, eventListener) { - if (startEvents.length === 0) { - setTimeout(eventListener, 0); - return; - } - startEvents.forEach(startEvent => { - addEventListener(node, startEvent, eventListener); - }); - }, - - removeStartEventListener(node, eventListener) { - if (startEvents.length === 0) { - return; - } - startEvents.forEach(startEvent => { - removeEventListener(node, startEvent, eventListener); - }); - }, - - // End events - endEvents, - - addEndEventListener(node, eventListener) { - if (endEvents.length === 0) { - setTimeout(eventListener, 0); - return; - } - endEvents.forEach(endEvent => { - addEventListener(node, endEvent, eventListener); - }); - }, - - removeEndEventListener(node, eventListener) { - if (endEvents.length === 0) { - return; - } - endEvents.forEach(endEvent => { - removeEventListener(node, endEvent, eventListener); - }); - }, -}; - -export default TransitionEvents; diff --git a/components/_util/css-animation/index.js b/components/_util/css-animation/index.js deleted file mode 100644 index 86e399dec9..0000000000 --- a/components/_util/css-animation/index.js +++ /dev/null @@ -1,186 +0,0 @@ -// https://github.com/yiminghe/css-animation 1.5.0 - -import Event from './Event'; -import classes from '../component-classes'; -import { requestAnimationTimeout, cancelAnimationTimeout } from '../requestAnimationTimeout'; -import { inBrowser } from '../env'; - -const isCssAnimationSupported = Event.endEvents.length !== 0; -const capitalPrefixes = [ - 'Webkit', - 'Moz', - 'O', - // ms is special .... ! - 'ms', -]; -const prefixes = ['-webkit-', '-moz-', '-o-', 'ms-', '']; - -function getStyleProperty(node, name) { - if (inBrowser) return ''; - // old ff need null, https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle - const style = window.getComputedStyle(node, null); - let ret = ''; - for (let i = 0; i < prefixes.length; i++) { - ret = style.getPropertyValue(prefixes[i] + name); - if (ret) { - break; - } - } - return ret; -} - -function fixBrowserByTimeout(node) { - if (isCssAnimationSupported) { - const transitionDelay = parseFloat(getStyleProperty(node, 'transition-delay')) || 0; - const transitionDuration = parseFloat(getStyleProperty(node, 'transition-duration')) || 0; - const animationDelay = parseFloat(getStyleProperty(node, 'animation-delay')) || 0; - const animationDuration = parseFloat(getStyleProperty(node, 'animation-duration')) || 0; - const time = Math.max(transitionDuration + transitionDelay, animationDuration + animationDelay); - // sometimes, browser bug - node.rcEndAnimTimeout = setTimeout(() => { - node.rcEndAnimTimeout = null; - if (node.rcEndListener) { - node.rcEndListener(); - } - }, time * 1000 + 200); - } -} - -function clearBrowserBugTimeout(node) { - if (node.rcEndAnimTimeout) { - clearTimeout(node.rcEndAnimTimeout); - node.rcEndAnimTimeout = null; - } -} - -const cssAnimation = (node, transitionName, endCallback) => { - const nameIsObj = typeof transitionName === 'object'; - const className = nameIsObj ? transitionName.name : transitionName; - const activeClassName = nameIsObj ? transitionName.active : `${transitionName}-active`; - let end = endCallback; - let start; - let active; - const nodeClasses = classes(node); - - if (endCallback && Object.prototype.toString.call(endCallback) === '[object Object]') { - end = endCallback.end; - start = endCallback.start; - active = endCallback.active; - } - - if (node.rcEndListener) { - node.rcEndListener(); - } - - node.rcEndListener = e => { - if (e && e.target !== node) { - return; - } - - if (node.rcAnimTimeout) { - cancelAnimationTimeout(node.rcAnimTimeout); - node.rcAnimTimeout = null; - } - - clearBrowserBugTimeout(node); - - nodeClasses.remove(className); - nodeClasses.remove(activeClassName); - - Event.removeEndEventListener(node, node.rcEndListener); - node.rcEndListener = null; - - // Usually this optional end is used for informing an owner of - // a leave animation and telling it to remove the child. - if (end) { - end(); - } - }; - - Event.addEndEventListener(node, node.rcEndListener); - - if (start) { - start(); - } - nodeClasses.add(className); - - node.rcAnimTimeout = requestAnimationTimeout(() => { - node.rcAnimTimeout = null; - - nodeClasses.add(className); - nodeClasses.add(activeClassName); - - if (active) { - requestAnimationTimeout(active, 0); - } - fixBrowserByTimeout(node); - // 30ms for firefox - }, 30); - - return { - stop() { - if (node.rcEndListener) { - node.rcEndListener(); - } - }, - }; -}; - -cssAnimation.style = (node, style, callback) => { - if (node.rcEndListener) { - node.rcEndListener(); - } - - node.rcEndListener = e => { - if (e && e.target !== node) { - return; - } - - if (node.rcAnimTimeout) { - cancelAnimationTimeout(node.rcAnimTimeout); - node.rcAnimTimeout = null; - } - - clearBrowserBugTimeout(node); - - Event.removeEndEventListener(node, node.rcEndListener); - node.rcEndListener = null; - - // Usually this optional callback is used for informing an owner of - // a leave animation and telling it to remove the child. - if (callback) { - callback(); - } - }; - - Event.addEndEventListener(node, node.rcEndListener); - - node.rcAnimTimeout = requestAnimationTimeout(() => { - for (const s in style) { - if (style.hasOwnProperty(s)) { - node.style[s] = style[s]; - } - } - node.rcAnimTimeout = null; - fixBrowserByTimeout(node); - }, 0); -}; - -cssAnimation.setTransition = (node, p, value) => { - let property = p; - let v = value; - if (value === undefined) { - v = property; - property = ''; - } - property = property || ''; - capitalPrefixes.forEach(prefix => { - node.style[`${prefix}Transition${property}`] = v; - }); -}; - -cssAnimation.isCssAnimationSupported = isCssAnimationSupported; - -export { isCssAnimationSupported }; - -export default cssAnimation; diff --git a/components/_util/cssinjs/hooks/useCacheToken.tsx b/components/_util/cssinjs/hooks/useCacheToken.tsx index 9a11fb225f..f97d146b55 100644 --- a/components/_util/cssinjs/hooks/useCacheToken.tsx +++ b/components/_util/cssinjs/hooks/useCacheToken.tsx @@ -8,9 +8,13 @@ import { ref, computed } from 'vue'; const EMPTY_OVERRIDE = {}; +const isProduction = process.env.NODE_ENV === 'production'; +// nuxt generate when NODE_ENV is prerender +const isPrerender = process.env.NODE_ENV === 'prerender'; + // Generate different prefix to make user selector break in production env. // This helps developer not to do style override directly on the hash id. -const hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css'; +const hashPrefix = !isProduction && !isPrerender ? 'css-dev-only-do-not-override' : 'css'; export interface Option { /** diff --git a/components/_util/getScroll.ts b/components/_util/getScroll.ts index 2889b43ed7..ca0b10005d 100644 --- a/components/_util/getScroll.ts +++ b/components/_util/getScroll.ts @@ -12,7 +12,7 @@ export default function getScroll( const method = top ? 'scrollTop' : 'scrollLeft'; let result = 0; if (isWindow(target)) { - result = target[top ? 'pageYOffset' : 'pageXOffset']; + result = target[top ? 'scrollY' : 'scrollX']; } else if (target instanceof Document) { result = target.documentElement[method]; } else if (target instanceof HTMLElement) { diff --git a/components/_util/hooks/_vueuse/_configurable.ts b/components/_util/hooks/_vueuse/_configurable.ts index 858fccc7d0..a32d339399 100644 --- a/components/_util/hooks/_vueuse/_configurable.ts +++ b/components/_util/hooks/_vueuse/_configurable.ts @@ -28,7 +28,7 @@ export interface ConfigurableLocation { location?: Location; } -export const defaultWindow = isClient ? window : undefined; -export const defaultDocument = isClient ? window.document : undefined; -export const defaultNavigator = isClient ? window.navigator : undefined; -export const defaultLocation = isClient ? window.location : undefined; +export const defaultWindow = isClient ? window : undefined; +export const defaultDocument = isClient ? window.document : undefined; +export const defaultNavigator = isClient ? window.navigator : undefined; +export const defaultLocation = isClient ? window.location : undefined; diff --git a/components/_util/hooks/_vueuse/is.ts b/components/_util/hooks/_vueuse/is.ts index a17b114285..1acf122e8f 100644 --- a/components/_util/hooks/_vueuse/is.ts +++ b/components/_util/hooks/_vueuse/is.ts @@ -21,8 +21,6 @@ export const rand = (min: number, max: number) => { return Math.floor(Math.random() * (max - min + 1)) + min; }; export const isIOS = - isClient && - window?.navigator?.userAgent && - /iP(ad|hone|od)/.test(window.navigator.userAgent); + isClient && window?.navigator?.userAgent && /iP(ad|hone|od)/.test(window.navigator.userAgent); export const hasOwn = (val: T, key: K): key is K => Object.prototype.hasOwnProperty.call(val, key); diff --git a/components/_util/isCssAnimationSupported.js b/components/_util/isCssAnimationSupported.js deleted file mode 100644 index 45d51bb356..0000000000 --- a/components/_util/isCssAnimationSupported.js +++ /dev/null @@ -1,24 +0,0 @@ -let animation; - -function isCssAnimationSupported() { - if (animation !== undefined) { - return animation; - } - const domPrefixes = 'Webkit Moz O ms Khtml'.split(' '); - const elm = document.createElement('div'); - if (elm.style.animationName !== undefined) { - animation = true; - } - if (animation !== undefined) { - for (let i = 0; i < domPrefixes.length; i++) { - if (elm.style[`${domPrefixes[i]}AnimationName`] !== undefined) { - animation = true; - break; - } - } - } - animation = animation || false; - return animation; -} - -export default isCssAnimationSupported; diff --git a/components/_util/scrollTo.ts b/components/_util/scrollTo.ts index c9dbb89104..992d6a930e 100644 --- a/components/_util/scrollTo.ts +++ b/components/_util/scrollTo.ts @@ -22,8 +22,8 @@ export default function scrollTo(y: number, options: ScrollToOptions = {}) { const time = timestamp - startTime; const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration); if (isWindow(container)) { - (container as Window).scrollTo(window.pageXOffset, nextScrollTop); - } else if (container instanceof Document || container.constructor.name === 'HTMLDocument') { + (container as Window).scrollTo(window.scrollX, nextScrollTop); + } else if (container instanceof Document) { (container as Document).documentElement.scrollTop = nextScrollTop; } else { (container as HTMLElement).scrollTop = nextScrollTop; diff --git a/components/_util/transition.tsx b/components/_util/transition.tsx index fd6ebc4904..f4b69f31ed 100644 --- a/components/_util/transition.tsx +++ b/components/_util/transition.tsx @@ -5,7 +5,7 @@ import type { TransitionGroupProps, TransitionProps, } from 'vue'; -import { nextTick, Transition, TransitionGroup } from 'vue'; +import { nextTick } from 'vue'; import { tuple } from './type'; const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight'); @@ -126,6 +126,4 @@ const getTransitionName = (rootPrefixCls: string, motion: string, transitionName return `${rootPrefixCls}-${motion}`; }; -export { Transition, TransitionGroup, collapseMotion, getTransitionName, getTransitionDirection }; - -export default Transition; +export { collapseMotion, getTransitionName, getTransitionDirection }; diff --git a/components/_util/triggerEvent.ts b/components/_util/triggerEvent.ts deleted file mode 100644 index 3c8cfc5414..0000000000 --- a/components/_util/triggerEvent.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default function triggerEvent(el: Element, type: string) { - if ('createEvent' in document) { - // modern browsers, IE9+ - const e = document.createEvent('HTMLEvents'); - e.initEvent(type, false, true); - el.dispatchEvent(e); - } -} diff --git a/components/_util/vnode.ts b/components/_util/vnode.ts index e5ab010a42..8565cbe5ba 100644 --- a/components/_util/vnode.ts +++ b/components/_util/vnode.ts @@ -1,6 +1,6 @@ import { filterEmpty } from './props-util'; -import type { VNode, VNodeProps } from 'vue'; -import { cloneVNode, isVNode, render as VueRender } from 'vue'; +import type { Slots, VNode, VNodeArrayChildren, VNodeProps } from 'vue'; +import { cloneVNode, isVNode, Comment, Fragment, render as VueRender } from 'vue'; import warning from './warning'; import type { RefObject } from './createRef'; type NodeProps = Record & @@ -55,3 +55,28 @@ export function deepCloneElement( export function triggerVNodeUpdate(vm: VNode, attrs: Record, dom: any) { VueRender(cloneVNode(vm, { ...attrs }), dom); } + +const ensureValidVNode = (slot: VNodeArrayChildren | null) => { + return (slot || []).some(child => { + if (!isVNode(child)) return true; + if (child.type === Comment) return false; + if (child.type === Fragment && !ensureValidVNode(child.children as VNodeArrayChildren)) + return false; + return true; + }) + ? slot + : null; +}; + +export function customRenderSlot( + slots: Slots, + name: string, + props: Record, + fallback?: () => VNodeArrayChildren, +) { + const slot = slots[name]?.(props); + if (ensureValidVNode(slot)) { + return slot; + } + return fallback?.(); +} diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx index 1f3ce23d17..b59c8ff66a 100644 --- a/components/_util/wave/WaveEffect.tsx +++ b/components/_util/wave/WaveEffect.tsx @@ -159,6 +159,12 @@ function showWaveEffect(node: HTMLElement, className: string) { node?.insertBefore(holder, node?.firstChild); render(, holder); + return () => { + render(null, holder); + if (holder.parentElement) { + holder.parentElement.removeChild(holder); + } + }; } export default showWaveEffect; diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 26ac1aff5e..26dab40f9c 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -33,13 +33,12 @@ export default defineComponent({ // =============================== Wave =============================== const showWave = useWave( - instance, computed(() => classNames(prefixCls.value, hashId.value)), wave, ); let onClick: (e: MouseEvent) => void; const clear = () => { - const node = findDOMNode(instance); + const node = findDOMNode(instance) as HTMLElement; node.removeEventListener('click', onClick, true); }; onMounted(() => { diff --git a/components/_util/wave/useWave.ts b/components/_util/wave/useWave.ts index 84e2a6effb..f88c361688 100644 --- a/components/_util/wave/useWave.ts +++ b/components/_util/wave/useWave.ts @@ -1,21 +1,25 @@ -import type { ComponentInternalInstance, ComputedRef, Ref } from 'vue'; +import type { ComputedRef, Ref } from 'vue'; +import { onBeforeUnmount, getCurrentInstance } from 'vue'; import { findDOMNode } from '../props-util'; import showWaveEffect from './WaveEffect'; export default function useWave( - instance: ComponentInternalInstance | null, className: Ref, wave?: ComputedRef<{ disabled?: boolean }>, ): VoidFunction { + const instance = getCurrentInstance(); + let stopWave: () => void; function showWave() { const node = findDOMNode(instance); - + stopWave?.(); if (wave?.value?.disabled || !node) { return; } - - showWaveEffect(node, className.value); + stopWave = showWaveEffect(node, className.value); } + onBeforeUnmount(() => { + stopWave?.(); + }); return showWave; } diff --git a/components/alert/index.tsx b/components/alert/index.tsx index 63a61aa6e0..f3dead2ba5 100644 --- a/components/alert/index.tsx +++ b/components/alert/index.tsx @@ -1,5 +1,5 @@ import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; -import { computed, defineComponent, shallowRef } from 'vue'; +import { computed, defineComponent, shallowRef, Transition } from 'vue'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined'; import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined'; @@ -11,7 +11,7 @@ import InfoCircleFilled from '@ant-design/icons-vue/InfoCircleFilled'; import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; import classNames from '../_util/classNames'; import PropTypes from '../_util/vue-types'; -import { getTransitionProps, Transition } from '../_util/transition'; +import { getTransitionProps } from '../_util/transition'; import { isValidElement } from '../_util/props-util'; import { tuple, withInstall } from '../_util/type'; import { cloneElement } from '../_util/vnode'; diff --git a/components/app/index.en-US.md b/components/app/index.en-US.md index bcc2c46361..7d2dd7025f 100644 --- a/components/app/index.en-US.md +++ b/components/app/index.en-US.md @@ -97,7 +97,7 @@ import type { MessageInstance } from 'ant-design-vue/es/message/interface'; import type { ModalStaticFunctions } from 'ant-design-vue/es/modal/confirm'; import type { NotificationInstance } from 'ant-design-vue/es/notification/interface'; -export const useGloablStore = defineStore('global', () => { +export const useGlobalStore = defineStore('global', () => { const message: MessageInstance = ref(); const notification: NotificationInstance = ref(); const modal: Omit = ref(); diff --git a/components/app/index.zh-CN.md b/components/app/index.zh-CN.md index e70d205a18..741f04261b 100644 --- a/components/app/index.zh-CN.md +++ b/components/app/index.zh-CN.md @@ -98,7 +98,7 @@ import type { MessageInstance } from 'ant-design-vue/es/message/interface'; import type { ModalStaticFunctions } from 'ant-design-vue/es/modal/confirm'; import type { NotificationInstance } from 'ant-design-vue/es/notification/interface'; -export const useGloablStore = defineStore('global', () => { +export const useGlobalStore = defineStore('global', () => { const message: MessageInstance = ref(); const notification: NotificationInstance = ref(); const modal: Omit = ref(); diff --git a/components/auto-complete/index.tsx b/components/auto-complete/index.tsx index 3e2b7a63f7..e95ceba2c5 100644 --- a/components/auto-complete/index.tsx +++ b/components/auto-complete/index.tsx @@ -50,6 +50,8 @@ const AutoComplete = defineComponent({ props: autoCompleteProps(), // emits: ['change', 'select', 'focus', 'blur'], slots: Object as CustomSlotsType<{ + option: any; + // deprecated, should use props `options` instead, not slot options: any; default: any; notFoundContent: any; diff --git a/components/badge/Badge.tsx b/components/badge/Badge.tsx index 30195d049b..a0363732f4 100644 --- a/components/badge/Badge.tsx +++ b/components/badge/Badge.tsx @@ -3,9 +3,9 @@ import ScrollNumber from './ScrollNumber'; import classNames from '../_util/classNames'; import { getPropsSlot, flattenChildren } from '../_util/props-util'; import { cloneElement } from '../_util/vnode'; -import { getTransitionProps, Transition } from '../_util/transition'; +import { getTransitionProps } from '../_util/transition'; import type { ExtractPropTypes, CSSProperties, PropType } from 'vue'; -import { defineComponent, computed, ref, watch } from 'vue'; +import { defineComponent, computed, ref, watch, Transition } from 'vue'; import Ribbon from './Ribbon'; import useConfigInject from '../config-provider/hooks/useConfigInject'; import isNumeric from '../_util/isNumeric'; diff --git a/components/button/LoadingIcon.tsx b/components/button/LoadingIcon.tsx index 391527fd45..f865e09078 100644 --- a/components/button/LoadingIcon.tsx +++ b/components/button/LoadingIcon.tsx @@ -1,6 +1,5 @@ -import { defineComponent, nextTick } from 'vue'; +import { defineComponent, nextTick, Transition } from 'vue'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; -import Transition from '../_util/transition'; const getCollapsedWidth = (node: HTMLSpanElement) => { if (node) { node.style.width = '0px'; diff --git a/components/calendar/index.zh-CN.md b/components/calendar/index.zh-CN.md index 3d0f68d1fd..725f2d5731 100644 --- a/components/calendar/index.zh-CN.md +++ b/components/calendar/index.zh-CN.md @@ -28,16 +28,16 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| dateCellRender | 作用域插槽,用来自定义渲染日期单元格,返回内容会被追加到单元格, | v-slot:dateCellRender="{current: dayjs}" | 无 | | -| dateFullCellRender | 作用域插槽,自定义渲染日期单元格,返回内容覆盖单元格 | v-slot:dateFullCellRender="{current: dayjs}" | 无 | | -| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | 无 | | +| dateCellRender | 作用域插槽,用来自定义渲染日期单元格,返回内容会被追加到单元格, | v-slot:dateCellRender="{current: dayjs}" | - | | +| dateFullCellRender | 作用域插槽,自定义渲染日期单元格,返回内容覆盖单元格 | v-slot:dateFullCellRender="{current: dayjs}" | - | | +| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | | | fullscreen | 是否全屏显示 | boolean | true | | | headerRender | 自定义头部内容 | v-slot:headerRender="{value: dayjs, type: string, onChange: f(), onTypeChange: f()}" | - | | | locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | | | mode | 初始模式,`month/year` | string | month | | -| monthCellRender | 作用域插槽,自定义渲染月单元格,返回内容会被追加到单元格 | v-slot:monthCellRender="{current: dayjs}" | 无 | | -| monthFullCellRender | 作用域插槽,自定义渲染月单元格,返回内容覆盖单元格 | v-slot:monthFullCellRender="{current: dayjs}" | 无 | | -| validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | 无 | | +| monthCellRender | 作用域插槽,自定义渲染月单元格,返回内容会被追加到单元格 | v-slot:monthCellRender="{current: dayjs}" | - | | +| monthFullCellRender | 作用域插槽,自定义渲染月单元格,返回内容覆盖单元格 | v-slot:monthFullCellRender="{current: dayjs}" | - | | +| validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | | | value(v-model) | 展示日期 | [dayjs](https://day.js.org/) | 当前日期 | | | valueFormat | 可选,绑定值的格式,对 value、defaultValue 起作用。不指定则绑定值为 dayjs 对象 | string,[具体格式](https://day.js.org/docs/zh-CN/display/format) | - | | @@ -45,8 +45,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA | 事件名称 | 说明 | 回调参数 | | | --- | --- | --- | --- | --- | -| change | 日期变化时的回调, 面板变化有可能导致日期变化 | function(date: dayjs \| string) | 无 | -| panelChange | 日期面板变化回调 | function(date: dayjs \| string, mode: string) | 无 | +| change | 日期变化时的回调, 面板变化有可能导致日期变化 | function(date: dayjs \| string) | - | +| panelChange | 日期面板变化回调 | function(date: dayjs \| string, mode: string) | - | | select | 选择日期回调,包含来源信息 | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | | ### 如何仅获取来自面板点击的日期? diff --git a/components/card/Card.tsx b/components/card/Card.tsx index 18d330cf0f..a5f83c8496 100644 --- a/components/card/Card.tsx +++ b/components/card/Card.tsx @@ -1,5 +1,5 @@ import type { VNodeTypes, PropType, VNode, ExtractPropTypes, CSSProperties } from 'vue'; -import { isVNode, defineComponent, renderSlot } from 'vue'; +import { isVNode, defineComponent } from 'vue'; import Tabs from '../tabs'; import PropTypes from '../_util/vue-types'; import { flattenChildren, isEmptyElement, filterEmptyWithUndefined } from '../_util/props-util'; @@ -10,6 +10,8 @@ import devWarning from '../vc-util/devWarning'; import useStyle from './style'; import Skeleton from '../skeleton'; import type { CustomSlotsType } from '../_util/type'; +import { customRenderSlot } from '../_util/vnode'; + export interface CardTabListType { key: string; tab: any; @@ -152,7 +154,7 @@ const Card = defineComponent({ `tabList slots is deprecated, Please use \`customTab\` instead.`, ); let tab = temp !== undefined ? temp : slots[name] ? slots[name](item) : null; - tab = renderSlot(slots, 'customTab', item as any, () => [tab]); + tab = customRenderSlot(slots, 'customTab', item as any, () => [tab]); return ; })} diff --git a/components/checkbox/Group.tsx b/components/checkbox/Group.tsx index 57fb871ba2..f62380f73b 100644 --- a/components/checkbox/Group.tsx +++ b/components/checkbox/Group.tsx @@ -41,7 +41,7 @@ export default defineComponent({ }); }); const triggerUpdate = ref(Symbol()); - const registeredValuesMap = ref>(new Map()); + const registeredValuesMap = ref(new Map()); const cancelValue = (id: Symbol) => { registeredValuesMap.value.delete(id); triggerUpdate.value = Symbol(); diff --git a/components/collapse/CollapsePanel.tsx b/components/collapse/CollapsePanel.tsx index 4ff984dd74..95af0ec645 100644 --- a/components/collapse/CollapsePanel.tsx +++ b/components/collapse/CollapsePanel.tsx @@ -2,8 +2,7 @@ import PanelContent from './PanelContent'; import { initDefaultProps } from '../_util/props-util'; import { collapsePanelProps } from './commonProps'; import type { ExtractPropTypes } from 'vue'; -import { defineComponent } from 'vue'; -import Transition from '../_util/transition'; +import { defineComponent, Transition } from 'vue'; import classNames from '../_util/classNames'; import devWarning from '../vc-util/devWarning'; import useConfigInject from '../config-provider/hooks/useConfigInject'; diff --git a/components/collapse/index.en-US.md b/components/collapse/index.en-US.md index 4a80753b00..429a95e436 100644 --- a/components/collapse/index.en-US.md +++ b/components/collapse/index.en-US.md @@ -20,7 +20,7 @@ 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
number\[] \| number | 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](#components-collapse-demo-accordion), 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` \| `icon` \| `disabled` | - | 4.0 | | destroyInactivePanel | Destroy Inactive Panel | boolean | `false` | | diff --git a/components/collapse/index.zh-CN.md b/components/collapse/index.zh-CN.md index 1c750c85f5..ddae33295a 100644 --- a/components/collapse/index.zh-CN.md +++ b/components/collapse/index.zh-CN.md @@ -20,8 +20,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAA | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| accordion | 手风琴模式 | boolean | `false` | | -| activeKey(v-model) | 当前激活 tab 面板的 key | string\[] \| string
number\[] \| number | 默认无,accordion 模式下默认第一个元素 | | +| accordion | 手风琴模式,始终只有一个面板处在激活状态 | boolean | `false` | | +| activeKey(v-model) | 当前激活 tab 面板的 key | string\[] \| string
number\[] \| number | 默认无,[手风琴模式](#components-collapse-demo-accordion)下默认第一个元素 | | | bordered | 带边框风格的折叠面板 | boolean | `true` | | | collapsible | 所有子面板是否可折叠或指定可折叠触发区域 | `header` \| `icon` \| `disabled` | - | 4.0 | | destroyInactivePanel | 销毁折叠隐藏的面板 | boolean | `false` | | @@ -42,6 +42,6 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAA | collapsible | 是否可折叠或指定可折叠触发区域 | `header` \| `disabled` | - | 3.0 | | extra | 自定义渲染每个面板右上角的内容 | VNode \| slot | - | 1.5.0 | | forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false | | -| header | 面板头内容 | string\|slot | 无 | | -| key | 对应 activeKey | string \| number | 无 | | +| header | 面板头内容 | string\|slot | - | | +| key | 对应 activeKey | string \| number | - | | | showArrow | 是否展示当前面板上的箭头 | boolean | `true` | | diff --git a/components/config-provider/index.tsx b/components/config-provider/index.tsx index 036238ccb3..5b09ec16b8 100644 --- a/components/config-provider/index.tsx +++ b/components/config-provider/index.tsx @@ -1,4 +1,4 @@ -import type { App, Plugin, WatchStopHandle } from 'vue'; +import type { App, MaybeRef, Plugin, WatchStopHandle } from 'vue'; import { watch, computed, reactive, defineComponent, watchEffect } from 'vue'; import defaultRenderEmpty from './renderEmpty'; import type { RenderEmptyHandler } from './renderEmpty'; @@ -7,7 +7,6 @@ import LocaleProvider, { ANT_MARK } from '../locale-provider'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; -import type { MaybeRef } from '../_util/type'; import message from '../message'; import notification from '../notification'; import { registerTheme } from './cssVariables'; diff --git a/components/date-picker/index.en-US.md b/components/date-picker/index.en-US.md index dd071b05a9..2b84c13cdb 100644 --- a/components/date-picker/index.en-US.md +++ b/components/date-picker/index.en-US.md @@ -85,7 +85,7 @@ The following APIs are shared by DatePicker, RangePicker. | 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` | | -| dropdownClassName | To customize the className of the popup calendar | string | - | | +| popupClassName | 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 | | | locale | Localization configuration | object | [default](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | | diff --git a/components/date-picker/index.zh-CN.md b/components/date-picker/index.zh-CN.md index 8aa5fdcbd4..f878261bb5 100644 --- a/components/date-picker/index.zh-CN.md +++ b/components/date-picker/index.zh-CN.md @@ -86,7 +86,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3OpRQKcygo8AAA | 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` | | -| dropdownClassName | 额外的弹出日历 className | string | - | | +| popupClassName | 额外的弹出日历 className | string | - | | | getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | - | | | inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | | | locale | 国际化配置 | object | [默认配置](https://github.com/vueComponent/ant-design-vue/blob/main/components/date-picker/locale/example.json) | - | diff --git a/components/date-picker/locale/ja_JP.ts b/components/date-picker/locale/ja_JP.ts index 58ea44ef92..77814e94e3 100644 --- a/components/date-picker/locale/ja_JP.ts +++ b/components/date-picker/locale/ja_JP.ts @@ -6,7 +6,15 @@ import type { PickerLocale } from '../generatePicker'; const locale: PickerLocale = { lang: { placeholder: '日付を選択', + yearPlaceholder: '年を選択', + quarterPlaceholder: '四半期を選択', + monthPlaceholder: '月を選択', + weekPlaceholder: '週を選択', rangePlaceholder: ['開始日付', '終了日付'], + rangeYearPlaceholder: ['開始年', '終了年'], + rangeMonthPlaceholder: ['開始月', '終了月'], + rangeQuarterPlaceholder: ['開始四半期', '終了四半期'], + rangeWeekPlaceholder: ['開始週', '終了週'], ...CalendarLocale, }, timePickerLocale: { diff --git a/components/divider/style/index.ts b/components/divider/style/index.ts index 42694100d5..6dc979b047 100644 --- a/components/divider/style/index.ts +++ b/components/divider/style/index.ts @@ -105,7 +105,7 @@ const genSharedDividerStyle: GenerateStyle = (token): CSSObject => }, [`&-vertical${componentCls}-dashed`]: { - borderInlineStart: lineWidth, + borderInlineStartWidth: lineWidth, borderInlineEnd: 0, borderBlockStart: 0, borderBlockEnd: 0, diff --git a/components/dropdown/index.zh-CN.md b/components/dropdown/index.zh-CN.md index b66ae40489..1149da0a23 100644 --- a/components/dropdown/index.zh-CN.md +++ b/components/dropdown/index.zh-CN.md @@ -22,7 +22,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5qm4S4Zgh2QAAA | 参数 | 说明 | 类型 | 默认值 | | | --- | --- | --- | --- | --- | -| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | 无 | | +| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | - | | | arrow | 下拉框箭头是否显示 | boolean \| { pointAtCenter: boolean } | false | 3.3.0 | | destroyPopupOnHide | 关闭后是否销毁 Dropdown | boolean | false | 3.0 | | disabled | 菜单是否禁用 | boolean | - | | diff --git a/components/empty/index.tsx b/components/empty/index.tsx index 5d73982bce..ab778c9e65 100644 --- a/components/empty/index.tsx +++ b/components/empty/index.tsx @@ -1,4 +1,4 @@ -import { defineComponent } from 'vue'; +import { defineComponent, h } from 'vue'; import type { CSSProperties, ExtractPropTypes } from 'vue'; import classNames from '../_util/classNames'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; @@ -11,9 +11,6 @@ import useConfigInject from '../config-provider/hooks/useConfigInject'; import useStyle from './style'; -const defaultEmptyImg = ; -const simpleEmptyImg = ; - interface Locale { description?: string; } @@ -40,13 +37,16 @@ const Empty = defineComponent({ return () => { const prefixCls = prefixClsRef.value; const { - image = slots.image?.() || defaultEmptyImg, + image: mergedImage = slots.image?.() || h(DefaultEmptyImg), description = slots.description?.() || undefined, imageStyle, class: className = '', ...restProps } = { ...props, ...attrs }; - + const image = + typeof mergedImage === 'function' ? (mergedImage as () => VueNode)() : mergedImage; + const isNormal = + typeof image === 'object' && 'type' in image && (image.type as any).PRESENTED_IMAGE_SIMPLE; return wrapSSR( h(DefaultEmptyImg); +Empty.PRESENTED_IMAGE_SIMPLE = () => h(SimpleEmptyImg); export default withInstall(Empty); diff --git a/components/float-button/BackTop.tsx b/components/float-button/BackTop.tsx index 6baa6daad4..7229059752 100644 --- a/components/float-button/BackTop.tsx +++ b/components/float-button/BackTop.tsx @@ -1,5 +1,5 @@ import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined'; -import { getTransitionProps, Transition } from '../_util/transition'; +import { getTransitionProps } from '../_util/transition'; import { defineComponent, nextTick, @@ -10,6 +10,7 @@ import { ref, watch, onDeactivated, + Transition, } from 'vue'; import FloatButton, { floatButtonPrefixCls } from './FloatButton'; import useConfigInject from '../config-provider/hooks/useConfigInject'; diff --git a/components/float-button/FloatButtonGroup.tsx b/components/float-button/FloatButtonGroup.tsx index 743d60e5ad..611646299d 100644 --- a/components/float-button/FloatButtonGroup.tsx +++ b/components/float-button/FloatButtonGroup.tsx @@ -1,8 +1,8 @@ -import { defineComponent, ref, computed, watch, onBeforeUnmount } from 'vue'; +import { defineComponent, ref, computed, watch, onBeforeUnmount, Transition } from 'vue'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined'; import classNames from '../_util/classNames'; -import { getTransitionProps, Transition } from '../_util/transition'; +import { getTransitionProps } from '../_util/transition'; import FloatButton, { floatButtonPrefixCls } from './FloatButton'; import useConfigInject from '../config-provider/hooks/useConfigInject'; import { useProvideFloatButtonGroupContext } from './context'; diff --git a/components/float-button/index.zh-CN.md b/components/float-button/index.zh-CN.md index 589c658653..284cd6842b 100644 --- a/components/float-button/index.zh-CN.md +++ b/components/float-button/index.zh-CN.md @@ -33,11 +33,11 @@ tag: New | target | 相当于 a 标签的 target 属性,href 存在时生效 | string | - | | | badge | 带徽标数字的悬浮按钮(不支持 status 以及相关属性) | [BadgeProps](/components/badge-cn#api) | - | | -### common events +### 共同的事件 -| 事件名称 | 说明 | 回调参数 | 版本 | -| -------- | --------------------------------------- | ----------------- | ---- | -| click | Set the handler to handle `click` event | `(event) => void` | - | +| 事件名称 | 说明 | 回调参数 | 版本 | +| -------- | ----------------------------- | ----------------- | ---- | +| click | 设置处理 `click` 事件的处理器 | `(event) => void` | - | ### FloatButton.Group @@ -47,7 +47,7 @@ tag: New | trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | | | open(v-model) | 受控展开 | boolean | - | | -### FloatButton.Group Events +### FloatButton.Group 事件 | 事件名称 | 说明 | 回调参数 | 版本 | | ---------- | ---------------- | ----------------------- | ---- | diff --git a/components/float-button/interface.ts b/components/float-button/interface.ts index 6561910b88..9db3fba65c 100644 --- a/components/float-button/interface.ts +++ b/components/float-button/interface.ts @@ -20,7 +20,7 @@ export const floatButtonProps = () => { shape: stringType('circle'), tooltip: PropTypes.any, href: String, - target: functionType<() => Window | HTMLElement | null>(), + target: String, badge: objectType(), onClick: functionType(), }; diff --git a/components/form/FormItem.tsx b/components/form/FormItem.tsx index df70f8e6f4..6a0608de44 100644 --- a/components/form/FormItem.tsx +++ b/components/form/FormItem.tsx @@ -494,7 +494,7 @@ export default defineComponent({ > ( diff --git a/components/form/useForm.ts b/components/form/useForm.ts index 43a72f288d..cc0899dcee 100644 --- a/components/form/useForm.ts +++ b/components/form/useForm.ts @@ -214,11 +214,13 @@ function useForm( const errorList = results.filter( (result: { errors: string | any[] }) => result && result.errors.length, ); - return Promise.reject({ - values, - errorFields: errorList, - outOfDate: lastValidatePromise !== summaryPromise, - }); + return errorList.length + ? Promise.reject({ + values, + errorFields: errorList, + outOfDate: lastValidatePromise !== summaryPromise, + }) + : Promise.resolve(values); }); // Do not throw in console diff --git a/components/grid/style/index.ts b/components/grid/style/index.ts index 137b50251f..923e01837f 100644 --- a/components/grid/style/index.ts +++ b/components/grid/style/index.ts @@ -50,6 +50,10 @@ const genGridRowStyle: GenerateStyle = (token): CSSObject => { justifyContent: 'space-around', }, + '&-space-evenly ': { + justifyContent: 'space-evenly', + }, + // Align at the top '&-top': { alignItems: 'flex-start', diff --git a/components/icon/index.en-US.md b/components/icon/index.en-US.md index 758950f77b..7d753e2c33 100644 --- a/components/icon/index.en-US.md +++ b/components/icon/index.en-US.md @@ -122,6 +122,8 @@ See [iconfont.cn documents](http://iconfont.cn/help/detail?spm=a313x.7781069.199 ### Custom SVG Icon +#### vue cli 3 + You can import SVG icon as an vue component by using `vue cli 3` and [`vue-svg-loader`](https://www.npmjs.com/package/vue-svg-loader). `vue-svg-loader`'s `options` [reference](https://github.com/visualfanatic/vue-svg-loader). ```js @@ -149,6 +151,84 @@ export default defineComponent({ }); ``` +#### Rsbuild + +Rsbuild is a new generation of build tool, official website https://rsbuild.dev/ +Create your own `vue-svg-loader.js` file, which allows you to customize and beautify SVG, and then configure it in `rsbuild.config.ts` + +```js +// vue-svg-loader.js +/* eslint-disable */ +const { optimize } = require('svgo'); +const { version } = require('vue'); +const semverMajor = require('semver/functions/major'); + +module.exports = async function (svg) { + const callback = this.async(); + + try { + ({ data: svg } = await optimize(svg, { + path: this.resourcePath, + js2svg: { + indent: 2, + pretty: true, + }, + plugins: [ + 'convertStyleToAttrs', + 'removeDoctype', + 'removeXMLProcInst', + 'removeComments', + 'removeMetadata', + 'removeTitle', + 'removeDesc', + 'removeStyleElement', + 'removeXMLNS', + 'removeXMLProcInst', + ], + })); + } catch (error) { + callback(error); + return; + } + + if (semverMajor(version) === 2) { + svg = svg.replace('${svg}`); +}; +``` + +```js +// rsbuild.config.ts +/* eslint-disable */ +import { defineConfig } from '@rsbuild/core'; +import { pluginVue } from '@rsbuild/plugin-vue'; + +export default defineConfig({ + tools: { + bundlerChain(chain, { CHAIN_ID }) { + chain.module.rule(CHAIN_ID.RULE.SVG).exclude.add(/\.svg$/); + }, + rspack: { + module: { + rules: [ + { + test: /\.svg$/, + use: ['vue-loader', 'vue-svg-loader'], + }, + ], + }, + resolveLoader: { + alias: { + 'vue-svg-loader': require('path').join(__dirname, './vue-svg-loader.js'), + }, + }, + }, + }, +}); +``` + The following properties are available for the component: | Property | Description | Type | Default | diff --git a/components/icon/index.zh-CN.md b/components/icon/index.zh-CN.md index c8a02d68e5..66995e2792 100644 --- a/components/icon/index.zh-CN.md +++ b/components/icon/index.zh-CN.md @@ -119,7 +119,9 @@ export default defineComponent({ ### 自定义 SVG 图标 -如果使用 `vue cli 3`,可以通过配置 [vue-svg-loader](https://www.npmjs.com/package/vue-svg-loader) 来将 `svg` 图标作为 `Vue` 组件导入。更多`vue-svg-loader` 的使用方式请参阅 [文档](https://github.com/visualfanatic/vue-svg-loader)。 +#### vue cli 3 + +可以通过配置 [vue-svg-loader](https://www.npmjs.com/package/vue-svg-loader) 来将 `svg` 图标作为 `Vue` 组件导入。更多`vue-svg-loader` 的使用方式请参阅 [文档](https://github.com/visualfanatic/vue-svg-loader)。 ```js // vue.config.js @@ -146,6 +148,88 @@ export default defineComponent({ }); ``` +#### Rsbuild + +Rsbuild 是新一代构建工具,官网 https://rsbuild.dev/ + +自己实现一个 `vue-svg-loader.js` 文件,好处是可以自定义美化 svg,然后在 `rsbuild.config.ts` 中配置: + +```js +// vue-svg-loader.js +/* eslint-disable */ +const { optimize } = require('svgo'); +const { version } = require('vue'); +const semverMajor = require('semver/functions/major'); + +module.exports = async function (svg) { + const callback = this.async(); + + try { + ({ data: svg } = await optimize(svg, { + path: this.resourcePath, + js2svg: { + indent: 2, + pretty: true, + }, + plugins: [ + 'convertStyleToAttrs', + 'removeDoctype', + 'removeXMLProcInst', + 'removeComments', + 'removeMetadata', + 'removeTitle', + 'removeDesc', + 'removeStyleElement', + 'removeXMLNS', + 'removeXMLProcInst', + ], + })); + } catch (error) { + callback(error); + return; + } + + if (semverMajor(version) === 2) { + svg = svg.replace('${svg}`); +}; +``` + +```js +// rsbuild.config.ts +/* eslint-disable */ +import { defineConfig } from '@rsbuild/core'; +import { pluginVue } from '@rsbuild/plugin-vue'; + +export default defineConfig({ + tools: { + bundlerChain(chain, { CHAIN_ID }) { + chain.module + // 先给svg排除默认的规则,方便下面自定义loader + .rule(CHAIN_ID.RULE.SVG) + .exclude.add(/\.svg$/); + }, + rspack: { + module: { + rules: [ + { + test: /\.svg$/, + use: ['vue-loader', 'vue-svg-loader'], + }, + ], + }, + resolveLoader: { + alias: { + 'vue-svg-loader': require('path').join(__dirname, './vue-svg-loader.js'), + }, + }, + }, + }, +}); +``` + `Icon` 中的 `component` 组件的接受的属性如下: | 字段 | 说明 | 类型 | 只读值 | diff --git a/components/image/index.tsx b/components/image/index.tsx index 0e752b486b..7275f2bee6 100644 --- a/components/image/index.tsx +++ b/components/image/index.tsx @@ -13,10 +13,10 @@ export type ImageProps = Partial< ExtractPropTypes> & Omit >; -const Image = defineComponent({ +const Image = defineComponent({ name: 'AImage', inheritAttrs: false, - props: imageProps() as any, + props: imageProps(), setup(props, { slots, attrs }) { const { prefixCls, rootPrefixCls, configProvider } = useConfigInject('image', props); // Style diff --git a/components/input-number/index.tsx b/components/input-number/index.tsx index 7b1b4418a3..a2c7ad99bb 100644 --- a/components/input-number/index.tsx +++ b/components/input-number/index.tsx @@ -72,7 +72,7 @@ const InputNumber = defineComponent({ const mergedSize = computed(() => compactSize.value || size.value); - const mergedValue = shallowRef(props.value === undefined ? props.defaultValue : props.value); + const mergedValue = shallowRef(props.value ?? props.defaultValue); const focused = shallowRef(false); watch( () => props.value, diff --git a/components/input-number/index.zh-CN.md b/components/input-number/index.zh-CN.md index a603d4a705..3ce0b30946 100644 --- a/components/input-number/index.zh-CN.md +++ b/components/input-number/index.zh-CN.md @@ -34,7 +34,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*1uH-R5kLAMIAAA | parser | 指定从 formatter 里转换回数字的方式,和 formatter 搭配使用 | function( string): number | - | | | precision | 数值精度 | number | - | | | prefix | 带有前缀图标的 input | slot | - | 3.0 | -| size | 输入框大小 | string | 无 | | +| size | 输入框大小 | string | - | | | status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 | | step | 每次改变步数,可以为小数 | number\|string | 1 | | | stringMode | 字符值模式,开启后支持高精度小数。同时 `change` 事件将返回 string 类型 | boolean | false | 3.0 | diff --git a/components/input-number/src/InputNumber.tsx b/components/input-number/src/InputNumber.tsx index 811b8eeef5..9fa9a7d46e 100644 --- a/components/input-number/src/InputNumber.tsx +++ b/components/input-number/src/InputNumber.tsx @@ -395,6 +395,11 @@ export default defineComponent({ } }; + // Solve the issue of the event triggering sequence when entering numbers in chinese input (Safari) + const onBeforeInput = () => { + userTypingRef.value = true; + }; + const onKeyDown: KeyboardEventHandler = event => { const { which } = event; userTypingRef.value = true; @@ -577,6 +582,7 @@ export default defineComponent({ onBlur={onBlur} onCompositionstart={onCompositionStart} onCompositionend={onCompositionEnd} + onBeforeinput={onBeforeInput} />
diff --git a/components/input-number/style/index.tsx b/components/input-number/style/index.tsx index d119e7d663..e363f0772c 100644 --- a/components/input-number/style/index.tsx +++ b/components/input-number/style/index.tsx @@ -263,6 +263,10 @@ const genInputNumberStyles: GenerateStyle = (token: InputNumbe [`${componentCls}-handler-wrap`]: { display: 'none', }, + + [`${componentCls}-input`]: { + color: 'inherit', + }, }, [` diff --git a/components/input/Password.tsx b/components/input/Password.tsx index 0d65270b42..a516c0a84d 100644 --- a/components/input/Password.tsx +++ b/components/input/Password.tsx @@ -59,7 +59,7 @@ export default defineComponent({ }); const getIcon = (prefixCls: string) => { const { action, iconRender = slots.iconRender || defaultIconRender } = props; - const iconTrigger = ActionMap[action!] || ''; + const iconTrigger = ActionMap[action] || ''; const icon = iconRender(visible.value); const iconProps = { [iconTrigger]: onVisibleChange, diff --git a/components/input/ResizableTextArea.tsx b/components/input/ResizableTextArea.tsx index 862a7cb8a4..9ae5945173 100644 --- a/components/input/ResizableTextArea.tsx +++ b/components/input/ResizableTextArea.tsx @@ -1,4 +1,4 @@ -import type { VNode, CSSProperties } from 'vue'; +import type { CSSProperties } from 'vue'; import { computed, watchEffect, @@ -7,16 +7,16 @@ import { onBeforeUnmount, ref, defineComponent, - withDirectives, } from 'vue'; import ResizeObserver from '../vc-resize-observer'; import classNames from '../_util/classNames'; import raf from '../_util/raf'; import warning from '../_util/warning'; -import antInput from '../_util/antInputDirective'; import omit from '../_util/omit'; import { textAreaProps } from './inputProps'; import calculateAutoSizeStyle from './calculateNodeHeight'; +import type { BaseInputExpose } from '../_util/BaseInput'; +import BaseInput from '../_util/BaseInput'; const RESIZE_START = 0; const RESIZE_MEASURING = 1; @@ -30,7 +30,7 @@ const ResizableTextArea = defineComponent({ setup(props, { attrs, emit, expose }) { let nextFrameActionId: any; let resizeFrameId: any; - const textAreaRef = ref(); + const textAreaRef = ref(); const textareaStyles = ref({}); const resizeStatus = ref(RESIZE_STABLE); onBeforeUnmount(() => { @@ -41,12 +41,12 @@ const ResizableTextArea = defineComponent({ // https://github.com/ant-design/ant-design/issues/21870 const fixFirefoxAutoScroll = () => { try { - if (document.activeElement === textAreaRef.value) { - const currentStart = textAreaRef.value.selectionStart; - const currentEnd = textAreaRef.value.selectionEnd; - const scrollTop = textAreaRef.value.scrollTop; + if (textAreaRef.value && document.activeElement === textAreaRef.value.input) { + const currentStart = textAreaRef.value.getSelectionStart(); + const currentEnd = textAreaRef.value.getSelectionEnd(); + const scrollTop = textAreaRef.value.getScrollTop(); textAreaRef.value.setSelectionRange(currentStart, currentEnd); - textAreaRef.value.scrollTop = scrollTop; + textAreaRef.value.setScrollTop(scrollTop); } } catch (e) { // Fix error in Chrome: @@ -77,7 +77,7 @@ const ResizableTextArea = defineComponent({ startResize(); } }, - { immediate: true, flush: 'post' }, + { immediate: true }, ); const autoSizeStyle = ref(); watch( @@ -88,7 +88,7 @@ const ResizableTextArea = defineComponent({ resizeStatus.value = RESIZE_MEASURING; } else if (resizeStatus.value === RESIZE_MEASURING) { const textareaStyles = calculateAutoSizeStyle( - textAreaRef.value, + textAreaRef.value.input as HTMLTextAreaElement, false, minRows.value, maxRows.value, @@ -127,7 +127,7 @@ const ResizableTextArea = defineComponent({ expose({ resizeTextarea, - textArea: textAreaRef, + textArea: computed(() => textAreaRef.value?.input), instance, }); warning( @@ -146,7 +146,6 @@ const ResizableTextArea = defineComponent({ 'defaultValue', 'allowClear', 'type', - 'lazy', 'maxlength', 'valueModifiers', ]); @@ -175,9 +174,7 @@ const ResizableTextArea = defineComponent({ } return ( - {withDirectives((