Compare commits

..

No commits in common. "main" and "4.0.0" have entirely different histories.
main ... 4.0.0

463 changed files with 4300 additions and 209824 deletions

View File

@ -7,7 +7,4 @@ es/
lib/
_site/
dist/
site/dist/
components/version/version.ts
site/src/router/demoRoutes.js
locale/
components/version/version.tsx

View File

@ -15,21 +15,9 @@ module.exports = {
'plugin:vue/vue3-recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'@vue/typescript/recommended',
'@vue/prettier',
// 'prettier',
'prettier',
],
// extends: [
// 'eslint:recommended',
// 'plugin:vue/vue3-recommended',
// '@vue/typescript/recommended',
// '@vue/prettier',
// ],
plugins: ['markdown', 'jest', '@typescript-eslint', 'import'],
globals: {
h: true,
defineProps: 'readonly',
},
overrides: [
{
files: ['*.md'],
@ -40,11 +28,12 @@ module.exports = {
},
{
files: ['*.ts', '*.tsx'],
// extends: ['@vue/typescript/recommended', '@vue/prettier'],
extends: ['@vue/typescript/recommended', '@vue/prettier', '@vue/prettier/@typescript-eslint'],
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/explicit-module-boundary-types': 0,
@ -62,21 +51,17 @@ module.exports = {
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2021,
},
rules: {
'no-console': 'off',
'vue/no-reserved-component-names': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true },
],
},
},
],
rules: {
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/no-unused-vars': [
'error',
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true, argsIgnorePattern: '^_' },
],
'import/no-named-as-default': 'off',
'import/namespace': [2, { allowComputed: true }],
'import/no-named-as-default-member': 'off',
@ -109,4 +94,7 @@ module.exports = {
],
'vue/multi-word-component-names': 'off',
},
globals: {
h: true,
},
};

View File

@ -1,4 +1,4 @@
blank_issues_enabled: true
blank_issues_enabled: false
contact_links:
- name: Create new issue
url: https://vuecomponent.github.io/issue-helper/
@ -13,5 +13,5 @@ contact_links:
url: https://www.paypal.me/tangjinzhou
about: Love Ant Design Vue? Please consider supporting us via Paypal.
- name: 支付宝/微信 赞助
url: https://aliyuncdn.antdv.com/alipay-and-wechat.png
url: https://qn.antdv.com/alipay-and-wechat.png
about: Ant Design Vue 的健康持续发展需要您的支持,🙏

View File

@ -4,15 +4,12 @@ on:
schedule:
- cron: "0 0 * * *"
permissions:
contents: read
jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- name: need reproduce
uses: actions-cool/issues-helper@v3
uses: actions-cool/issues-helper@v1.7
with:
actions: 'close-issues'
labels: '🤔 Need Reproduce'

View File

@ -4,16 +4,8 @@ on:
issues:
types: [opened]
permissions:
contents: read
jobs:
issue-open-check:
permissions:
contents: read # for visiky/dingtalk-release-notify to get latest release
issues: write # for actions-cool/issues-helper to update issues
pull-requests: write # for actions-cool/issues-helper to update PRs
check-issue:
runs-on: ubuntu-latest
steps:
- uses: actions-cool/check-user-permission@v1.0.0
@ -23,7 +15,7 @@ jobs:
- name: check invalid
if: (contains(github.event.issue.body, 'issue-helper') == false) && (steps.checkUser.outputs.result == 'false')
uses: actions-cool/issues-helper@v3
uses: actions-cool/issues-helper@v1.2
with:
actions: 'create-comment,add-labels,close-issue'
issue-number: ${{ github.event.issue.number }}

View File

@ -1,25 +1,18 @@
name: Issue Labeled
name: Issue Reply
on:
issues:
types: [labeled]
permissions:
contents: read
jobs:
issue-labeled:
permissions:
issues: write # for actions-cool/issues-helper to update issues
pull-requests: write # for actions-cool/issues-helper to update PRs
issue-reply:
runs-on: ubuntu-latest
steps:
- name: Need Reproduce
if: github.event.label.name == '🤔 Need Reproduce'
uses: actions-cool/issues-helper@v3
uses: actions-cool/issues-helper@v1.2
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by forking this [link for vue2](https://codesandbox.io/s/2wpk21kzvr)、 [link for vue3](https://codesandbox.io/s/agitated-franklin-1w72v) or a minimal GitHub repository. Make sure to choose the correct version.
@ -28,10 +21,9 @@ jobs:
- name: help wanted
if: github.event.label.name == 'help wanted'
uses: actions-cool/issues-helper@v3
uses: actions-cool/issues-helper@v1.2
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome to send us a Pull Request for it. Please send your Pull Request to proper branch, fill the Pull Request Template here, provide changelog/TypeScript/documentation/test cases if needed and make sure CI passed, we will review it soon. We appreciate your effort in advance and looking forward to your contribution!
@ -40,37 +32,12 @@ jobs:
- name: Usage
if: github.event.label.name == 'Usage'
uses: actions-cool/issues-helper@v3
uses: actions-cool/issues-helper@v1.2
with:
actions: 'create-comment, close-issue'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, we use GitHub issues to trace bugs or discuss plans of Ant Design Vue. So, please don't ask usage questions here. You can try to open a new discussion in [antdv discussions](https://github.com/vueComponent/ant-design-vue/discussions), select `Q&A` to ask questions, also can ask questions on [Stack Overflow](http://stackoverflow.com/questions/) or [Segment Fault](https://segmentfault.com).
你好 @${{ github.event.issue.user.login }}Ant Design Vue Issue 板块是用于 bug 反馈与需求讨论的地方。请勿询问如何使用的问题,你可以试着在 [antdv discussions](https://github.com/vueComponent/ant-design-vue/discussions) 新开一个 discussion选择 `Q&A` 类别进行提问,也可以在 [Stack Overflow](http://stackoverflow.com/questions/) 或者 [Segment Fault](https://segmentfault.com/) 中提问。
- name: 1.x
if: github.event.label.name == '1.x'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hi @${{ github.event.issue.user.login }}. Current version (1.x) is off the maintenance period. We may not accept pull request or fix bug with it anymore. This topic will be auto closed.
你好 @${{ github.event.issue.user.login }}当前版本1.x已经过了维护期。我们不会再接受对其的相关 PR 与 issue。当前 topic 会被自动关闭。
- name: 2.x
if: github.event.label.name == '2.x'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hi @${{ github.event.issue.user.login }}. Current version (2.x) is off the maintenance period. We may not accept pull request or fix bug with it anymore. This topic will be auto closed.
你好 @${{ github.event.issue.user.login }}当前版本2.x已经过了维护期。我们不会再接受对其的相关 PR 与 issue。当前 topic 会被自动关闭。

20
.github/workflows/pr-labeled.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: PR Labeled
on:
pull_request_target:
types: [labeled]
jobs:
reply:
runs-on: ubuntu-latest
steps:
- name: Usage
if: github.event.label.name == 'Usage'
uses: actions-cool/issues-helper@v1.2
with:
actions: 'create-comment, close-issue'
issue-number: ${{ github.event.pull_request.number }}
body: |
Hello @${{ github.event.pull_request.user.login }}, we use GitHub PR to build and perfect of Ant Design Vue. So, please don't ask usage questions here. You can try to open a new discussion in [antdv discussions](https://github.com/vueComponent/ant-design-vue/discussions), select `Q&A` to ask questions, also can ask questions on [Stack Overflow](http://stackoverflow.com/questions/) or [Segment Fault](https://segmentfault.com).
你好 @${{ github.event.pull_request.user.login }}Ant Design Vue PR 是用于建设、完善项目的地方。请勿询问如何使用的问题,你可以试着在 [antdv discussions](https://github.com/vueComponent/ant-design-vue/discussions) 新开一个 discussion选择 `Q&A` 类别进行提问,也可以在 [Stack Overflow](http://stackoverflow.com/questions/) 或者 [Segment Fault](https://segmentfault.com/) 中提问。

1
.npmrc
View File

@ -1 +0,0 @@
enable-pre-post-scripts=true

View File

@ -10,159 +10,6 @@
---
## 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 Paginations 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)
## 4.0.8
- 🐞 Fix theme responsiveness failure issue under Nuxt [#7180](https://github.com/vueComponent/ant-design-vue/issues/7180)
- 🐞 Fix error reporting caused by Wave [#7108](https://github.com/vueComponent/ant-design-vue/issues/7108)
- 🐞 Fix Upload disabled inheritance issue [#7110](https://github.com/vueComponent/ant-design-vue/issues/7110)
- 🐞 Fix Tooltip popupAlign not taking effect [#7112](https://github.com/vueComponent/ant-design-vue/issues/7112)
- 🐞 Fix Typography flashing problem [#7146](https://github.com/vueComponent/ant-design-vue/issues/7146)
- 🐞 Fix the issue that RangePicker prevIcon nextIcon does not take effect [#7127](https://github.com/vueComponent/ant-design-vue/issues/7127)
- 🐞 Fixed the issue of watermark not monitoring child element changes [#7149](https://github.com/vueComponent/ant-design-vue/issues/7149)
- 🐞 Fix Menu animation missing issue [#7130](https://github.com/vueComponent/ant-design-vue/issues/7130)
- 🐞 Fix the cursor change issue when TextArea autosize [#7121](https://github.com/vueComponent/ant-design-vue/issues/7121)
## 4.0.7
- 🌟 Added Flex component [#7052](https://github.com/vueComponent/ant-design-vue/issues/7052)
- 🌟 ConfigProvider adds wave configuration [#7036](https://github.com/vueComponent/ant-design-vue/issues/7036)
- 🌟 Watermark supports dark mode [#7067](https://github.com/vueComponent/ant-design-vue/issues/7067)
- 🐞 Fix Space duplicate Key problem [#7048](https://github.com/vueComponent/ant-design-vue/issues/7048)
- 🐞 Fix Upload disabled priority error issue [#7047](https://github.com/vueComponent/ant-design-vue/issues/7047)
- 🐞 Fix Carousel rendering error in jsx [#7077](https://github.com/vueComponent/ant-design-vue/issues/7077)
- 🐞 Fix Message offset position problem [#7093](https://github.com/vueComponent/ant-design-vue/issues/7093)
- 🐞 Fix the problem of animation failure when using Collapse custom prefix [#7074](https://github.com/vueComponent/ant-design-vue/issues/7074)
## 4.0.6
- 🐞 Fix the Dropdown onVisibleChange failure issue introduced in 4.0.4 [#7031](https://github.com/vueComponent/ant-design-vue/issues/7031)
## 4.0.5
- 🐞 Fix cssinjs performance issue [#7023](https://github.com/vueComponent/ant-design-vue/issues/7023)
## 4.0.4
- 🌟 Added esm target file
- 🌟 Added tooltip attribute to FormItem [#7014](https://github.com/vueComponent/ant-design-vue/issues/7014)
- 🐞 Fix useMessage getContainer not taking effect [#6942](https://github.com/vueComponent/ant-design-vue/issues/6942)
- 🐞 Fix the problem of Image triggering onPreviewVisibleChange event multiple times [#6945](https://github.com/vueComponent/ant-design-vue/issues/6945)
- 🐞 Fix the problem that Checkbox global disabled does not take effect [#6970](https://github.com/vueComponent/ant-design-vue/issues/6970)
- 🐞 Fix Drawer contentWrapperStyle not taking effect [#6983](https://github.com/vueComponent/ant-design-vue/issues/6983)
- 🐞 Optimize Select Dropdown and other drop-down list scroll bar display hidden logic [#6987](https://github.com/vueComponent/ant-design-vue/issues/6987)
- 🐞 Fix the problem of hiding when there are components such as input in the drop-down list such as Select Dropdown [#7020](https://github.com/vueComponent/ant-design-vue/issues/7020)
## 4.0.3
- 🐞 Fix the problem of style loss under shadow Dom [#6912](https://github.com/vueComponent/ant-design-vue/issues/6912)
- 🐞 Upgrade Icon dependency and fix icon css missing problem under shadow Dom [#6914](https://github.com/vueComponent/ant-design-vue/issues/6914)
## 4.0.2
- 🐞 Fix useMessage causing body to be removed [#6880](https://github.com/vueComponent/ant-design-vue/issues/6880)
- 🐞 Fix the problem that the water ripple effect does not disappear after Button loading is switched [#6895](https://github.com/vueComponent/ant-design-vue/issues/6895)
- 🐞 Fixed the problem that flip does not reset after Image is closed [#6913](https://github.com/vueComponent/ant-design-vue/issues/6913)
- 🐞 Fix ImageGroup animation effect loss problem [#6898](https://github.com/vueComponent/ant-design-vue/issues/6898)
- 🐞 Fix Modal missing onUpdate:open attribute declaration [#6876](https://github.com/vueComponent/ant-design-vue/issues/6876)
- 🐞 Fixed the issue of multiple clicks being triggered at the edge of Transfer's Checkbox [#6902](https://github.com/vueComponent/ant-design-vue/issues/6902)
## 4.0.1
- 🌟 FloatButton add Badge support [#6738](https://github.com/vueComponent/ant-design-vue/issues/6738)
- 🌟 Image preview zoom in and out sensitivity adjustment [#6784](https://github.com/vueComponent/ant-design-vue/issues/6784)
- 🌟 Add flip feature to Image [#6785](https://github.com/vueComponent/ant-design-vue/issues/6785)
- 🌟 Add App component to provide context [#6735](https://github.com/vueComponent/ant-design-vue/issues/6735)
- 🌟 Style extraction feature for SSR [#6757](https://github.com/vueComponent/ant-design-vue/issues/6757)
- 🌟 Support px2rem [#6817](https://github.com/vueComponent/ant-design-vue/issues/6817)
- 🌟 Tag supports borderless mode [#6819](https://github.com/vueComponent/ant-design-vue/issues/6819)
- 🌟 Avatar group mode supports shape [#6822](https://github.com/vueComponent/ant-design-vue/issues/6822)
- 🌟 AutoComplete supports borderless and custom clearIcon [#6829](https://github.com/vueComponent/ant-design-vue/issues/6829)
- 🌟 InputPassword supports controlled visible [#6863](https://github.com/vueComponent/ant-design-vue/issues/6863)
- 🐞 Fix the style misalignment problem when InputGroup is large [#6866](https://github.com/vueComponent/ant-design-vue/issues/6866)
- 🐞 Fix the problem that Checkable Tag cannot customize class [#6854](https://github.com/vueComponent/ant-design-vue/issues/6854)
- 🐞 Fix the rendering problem in Tabs animation mode [#6855](https://github.com/vueComponent/ant-design-vue/issues/6855)
- 🐞 Fix the problem that the Image height attribute does not take effect [#6840](https://github.com/vueComponent/ant-design-vue/issues/6840)
- 🐞 Fix InputNumber trigger mouseup event [#6772](https://github.com/vueComponent/ant-design-vue/issues/6772)
- 🐞 Fix the Dropdown style problem when Tabs are collapsed [#6757](https://github.com/vueComponent/ant-design-vue/issues/6757)
- 🐞 Fix Table expandedRowRender property does not take effect [#6783](https://github.com/vueComponent/ant-design-vue/issues/6783)
- 🐞 Fix dayjs not packaged into dist [#6767](https://github.com/vueComponent/ant-design-vue/issues/6767)
- 🐞 Fix clipPath browser compatibility issue [#6770](https://github.com/vueComponent/ant-design-vue/issues/6770)
- 🐞 Fix Carousel autoplay responsive problem [#6768](https://github.com/vueComponent/ant-design-vue/issues/6768)
- 🐞 Fix PageHeader ghost style problem [#6761](https://github.com/vueComponent/ant-design-vue/issues/6761)
- 🐞 Fix Checkbox not triggering Form validation [#6741](https://github.com/vueComponent/ant-design-vue/issues/6741)
- 🐞 Fix the problem that the Input prefix attribute does not take effect [#6810](https://github.com/vueComponent/ant-design-vue/issues/6810)
- 🐞 Fix Badge style problem in Avatar [#6874](https://github.com/vueComponent/ant-design-vue/issues/6874)
## 4.0
### 🔥🔥🔥 4.0 official version released 🔥🔥🔥

View File

@ -10,159 +10,6 @@
---
## 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)
## 4.0.8
- 🐞 修复在 Nuxt 下 theme 响应式失效问题 [#7180](https://github.com/vueComponent/ant-design-vue/issues/7180)
- 🐞 修复 Wave 引起的报错问题 [#7108](https://github.com/vueComponent/ant-design-vue/issues/7108)
- 🐞 修复 Upload disabled 继承问题 [#7110](https://github.com/vueComponent/ant-design-vue/issues/7110)
- 🐞 修复 Tooltip popupAlign 未生效问题 [#7112](https://github.com/vueComponent/ant-design-vue/issues/7112)
- 🐞 修复 Typography 闪动问题 [#7146](https://github.com/vueComponent/ant-design-vue/issues/7146)
- 🐞 修复 RangePicker prevIcon nextIcon 未生效问题 [#7127](https://github.com/vueComponent/ant-design-vue/issues/7127)
- 🐞 修复 watermark 未监听子元素变动问题 [#7149](https://github.com/vueComponent/ant-design-vue/issues/7149)
- 🐞 修复 Menu 动画丢失问题 [#7130](https://github.com/vueComponent/ant-design-vue/issues/7130)
- 🐞 修复 TextArea autosize 时光标变化问题 [#7121](https://github.com/vueComponent/ant-design-vue/issues/7121)
## 4.0.7
- 🌟 新增 Flex 组件 [#7052](https://github.com/vueComponent/ant-design-vue/issues/7052)
- 🌟 ConfigProvider 新增 wave 配置 [#7036](https://github.com/vueComponent/ant-design-vue/issues/7036)
- 🌟 Watermark 支持暗黑模式 [#7067](https://github.com/vueComponent/ant-design-vue/issues/7067)
- 🐞 修复 Space 重复 Key 问题 [#7048](https://github.com/vueComponent/ant-design-vue/issues/7048)
- 🐞 修复 Upload disabled 优先级错误问题 [#7047](https://github.com/vueComponent/ant-design-vue/issues/7047)
- 🐞 修复 Carousel 在 jsx 中渲染错误问题 [#7077](https://github.com/vueComponent/ant-design-vue/issues/7077)
- 🐞 修复 Message 偏移位置问题 [#7093](https://github.com/vueComponent/ant-design-vue/issues/7093)
- 🐞 修复 Collapse 自定义 prefix 时动画失效问题 [#7074](https://github.com/vueComponent/ant-design-vue/issues/7074)
## 4.0.6
- 🐞 修复 4.0.4 引入的 Dropdown onVisibleChange 失效问题 [#7031](https://github.com/vueComponent/ant-design-vue/issues/7031)
## 4.0.5
- 🐞 修复 cssinjs 性能问题 [#7023](https://github.com/vueComponent/ant-design-vue/issues/7023)
## 4.0.4
- 🌟 新增 esm 目标文件
- 🌟 FormItem 新增 tooltip 属性 [#7014](https://github.com/vueComponent/ant-design-vue/issues/7014)
- 🐞 修复 useMessage getContainer 不生效问题 [#6942](https://github.com/vueComponent/ant-design-vue/issues/6942)
- 🐞 修复 Image 多次触发 onPreviewVisibleChange 事件问题 [#6945](https://github.com/vueComponent/ant-design-vue/issues/6945)
- 🐞 修复 Checkbox 全局 disabled 不生效问题 [#6970](https://github.com/vueComponent/ant-design-vue/issues/6970)
- 🐞 修复 Drawer contentWrapperStyle 不生效问题 [#6983](https://github.com/vueComponent/ant-design-vue/issues/6983)
- 🐞 优化 Select Dropdown 等下拉列表滚动条显示隐藏逻辑 [#6987](https://github.com/vueComponent/ant-design-vue/issues/6987)
- 🐞 修复 Select Dropdown 等下拉列表中有 input 等组件时,隐藏问题 [#7020](https://github.com/vueComponent/ant-design-vue/issues/7020)
## 4.0.3
- 🐞 修复 shadow Dom 下样式丢失问题 [#6912](https://github.com/vueComponent/ant-design-vue/issues/6912)
- 🐞 升级 Icon 依赖,修复 shadow Dom 下 icon css 丢失问题 [#6914](https://github.com/vueComponent/ant-design-vue/issues/6914)
## 4.0.2
- 🐞 修复 useMessage 导致 body 被移除问题 [#6880](https://github.com/vueComponent/ant-design-vue/issues/6880)
- 🐞 修复 Button loading 切换后,水波纹效果不消失问题 [#6895](https://github.com/vueComponent/ant-design-vue/issues/6895)
- 🐞 修复 Image 关闭后 flip 没有重置问题 [#6913](https://github.com/vueComponent/ant-design-vue/issues/6913)
- 🐞 修复 ImageGroup 动画效果丢失问题 [#6898](https://github.com/vueComponent/ant-design-vue/issues/6898)
- 🐞 修复 Modal 缺少 onUpdate:open 属性声明 [#6876](https://github.com/vueComponent/ant-design-vue/issues/6876)
- 🐞 修复 Transfer 的 Checkbox 边缘处会触发多次 click 问题 [#6902](https://github.com/vueComponent/ant-design-vue/issues/6902)
## 4.0.1
- 🌟 FloatButton 添加 Badge 支持 [#6738](https://github.com/vueComponent/ant-design-vue/issues/6738)
- 🌟 Image 预览放大缩小灵敏度调整 [#6784](https://github.com/vueComponent/ant-design-vue/issues/6784)
- 🌟 Image 新增翻转特性 [#6785](https://github.com/vueComponent/ant-design-vue/issues/6785)
- 🌟 新增 App 组件,用于提供上下文 [#6735](https://github.com/vueComponent/ant-design-vue/issues/6735)
- 🌟 样式抽离特性用于 SSR [#6757](https://github.com/vueComponent/ant-design-vue/issues/6757)
- 🌟 支持 px2rem [#6817](https://github.com/vueComponent/ant-design-vue/issues/6817)
- 🌟 Tag 支持无边框模式 [#6819](https://github.com/vueComponent/ant-design-vue/issues/6819)
- 🌟 Avatar group 模式支持 shape [#6822](https://github.com/vueComponent/ant-design-vue/issues/6822)
- 🌟 AutoComplete 支持无边框和自定义 clearIcon [#6829](https://github.com/vueComponent/ant-design-vue/issues/6829)
- 🌟 InputPassword 支持受控 visible [#6863](https://github.com/vueComponent/ant-design-vue/issues/6863)
- 🐞 修复 InputGroup 在 large 时样式错位问题 [#6866](https://github.com/vueComponent/ant-design-vue/issues/6866)
- 🐞 修复 Checkable Tag 无法自定义 class 问题 [#6854](https://github.com/vueComponent/ant-design-vue/issues/6854)
- 🐞 修复 Tabs 动画模式下渲染问题 [#6855](https://github.com/vueComponent/ant-design-vue/issues/6855)
- 🐞 修复 Image height 属性不生效问题 [#6840](https://github.com/vueComponent/ant-design-vue/issues/6840)
- 🐞 修复 InputNumber 触发 mouseup 事件问题 [#6772](https://github.com/vueComponent/ant-design-vue/issues/6772)
- 🐞 修复 Tabs 折叠时 Dropdown 样式问题 [#6757](https://github.com/vueComponent/ant-design-vue/issues/6757)
- 🐞 修复 Table expandedRowRender 属性不生效 [#6783](https://github.com/vueComponent/ant-design-vue/issues/6783)
- 🐞 修复 dayjs 未打包进 dist 问题 [#6767](https://github.com/vueComponent/ant-design-vue/issues/6767)
- 🐞 解决 clipPath 浏览器兼容问题 [#6770](https://github.com/vueComponent/ant-design-vue/issues/6770)
- 🐞 修复 Carousel autoplay 响应式问题 [#6768](https://github.com/vueComponent/ant-design-vue/issues/6768)
- 🐞 修复 PageHeader ghost 样式问题 [#6761](https://github.com/vueComponent/ant-design-vue/issues/6761)
- 🐞 修复 Checkbox 没有触发 Form 校验问题 [#6741](https://github.com/vueComponent/ant-design-vue/issues/6741)
- 🐞 修复 Input prefix 属性未生效问题 [#6810](https://github.com/vueComponent/ant-design-vue/issues/6810)
- 🐞 修复 Badge 在 Avatar 中样式问题 [#6874](https://github.com/vueComponent/ant-design-vue/issues/6874)
## 4.0
### 🔥🔥🔥 4.0 正式版发布 🔥🔥🔥

View File

@ -1,6 +1,6 @@
<p align="center">
<a href="https://www.antdv.com/">
<img width="200" src="https://aliyuncdn.antdv.com/logo.png">
<img width="200" src="https://qn.antdv.com/logo.png">
</a>
</p>
@ -10,7 +10,7 @@
<div align="center">
基于 Ant Design 和 Vue 3 的企业级 UI 组件库。
An enterprise-class UI components based on Ant Design and Vue 3.
![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)
@ -26,12 +26,6 @@
- 开箱即用的高质量 Vue 组件。
- 共享 [Ant Design of React](http://ant-design.gitee.io/docs/spec/introduce-cn) 设计工具体系。
## 关注我们
收藏加关注,第一时间获取更新动态!
![star us](https://user-images.githubusercontent.com/6937879/261937060-e0501ab3-9388-4712-a25d-3f2ba2271865.gif)
## 支持环境
- 现代浏览器。1.x 版本支持 IE 9+(需要 [polyfills](https://www.antdv.com/docs/vue/getting-started-cn/#兼容性)
@ -73,7 +67,6 @@ $ 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,33 +81,24 @@ ant-design-vue 是 MIT 协议的开源项目。为了项目能够更好的持续
- [Patreon](https://www.patreon.com/tangjinzhou)
- [opencollective](https://opencollective.com/ant-design-vue)
- [paypal](https://www.paypal.me/tangjinzhou)
- [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png)
- ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2
- [支付宝或微信](https://qn.antdv.com/alipay-and-wechat.png)
## 赞助商
## Sponsors
成为赞助商,并在 Github 上的自述文件上获得您的徽标,并链接到您的网站。 [[成为赞助商](https://opencollective.com/ant-design-vue#sponsor)]
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)]
<a href="http://www.jeecg.com/" target="_blank"><img src="https://aliyuncdn.antdv.com/jeecg-logo.png" height="64"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/0/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/1/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/2/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/3/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/4/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/5/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/6/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/7/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/8/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/9/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/sponsor/10/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/sponsor/10/avatar.svg"></a>
## 支持者
## Backers
每月捐款支持我们,帮助我们继续我们的活动。 [[成为支持者](https://opencollective.com/ant-design-vue#backer)]
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/ant-design-vue#backer)]
<a href="https://github.com/chuzhixin/vue-admin-beautiful" target="_blank"><img width="64" style="border-radius: 50%;" src="https://gitee.com/chu1204505056/image/raw/master/vue-admin-beautiful.png" title="vue-admin-beautiful"></a> <a href="https://opencollective.com/ant-design-vue/backer/0/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/0/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/1/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/1/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/2/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/2/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/3/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/3/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/4/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/4/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/5/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/5/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/6/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/6/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/7/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/7/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/8/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/8/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/9/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/9/avatar.svg"></a> <a href="https://opencollective.com/ant-design-vue/backer/10/website" target="_blank"><img src="https://opencollective.com/ant-design-vue/backer/10/avatar.svg"></a>
## Patreon
每月捐款支持我们,帮助我们继续我们的活动。 [[成为支持者](https://www.patreon.com/tangjinzhou)]
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://www.patreon.com/tangjinzhou)]
<a href="https://www.mokeyjay.com" target="_blank"><img width="64" style="border-radius: 50%;" src="https://www.mokeyjay.com/headimg.png" title="donation by Patreon"></a>
## [更多赞助者 (通过 Patreon、支付宝、微信、paypal 等等)](https://github.com/vueComponent/ant-design-vue/blob/master/BACKERS.md)
## 贡献者
感谢所有为 ant-design-vue 做出贡献的人!
<a href="https://github.com/vueComponent/ant-design-vue/graphs/contributors">
<img src="https://contrib.rocks/image?repo=vueComponent/ant-design-vue&max=100&columns=15" />
</a>

View File

@ -1,6 +1,6 @@
<p align="center">
<a href="https://www.antdv.com/">
<img width="200" src="https://aliyuncdn.antdv.com/logo.png">
<img width="200" src="https://qn.antdv.com/logo.png">
</a>
</p>
@ -26,12 +26,6 @@ English | [简体中文](./README-zh_CN.md)
- A set of high-quality Vue components out of the box.
- Shared [Ant Design of React](https://ant.design/docs/spec/introduce) design resources.
## Getting started & staying tuned with us.
Star us, and you will receive all releases notifications from GitHub without any delay!
![star us](https://user-images.githubusercontent.com/6937879/261937060-e0501ab3-9388-4712-a25d-3f2ba2271865.gif)
## Environment Support
- Modern browsers. v1.x support Internet Explorer 9+ (with [polyfills](https://www.antdv.com/docs/vue/getting-started/#compatibility))
@ -45,7 +39,7 @@ Star us, and you will receive all releases notifications from GitHub without any
## Using npm or yarn
**We recommend using npm or yarn to install**, it not only makes development easier, but also allow you to take advantage of the rich ecosystem of Javascript packages and tooling.
**We recommend using npm or yarn to install**it not only makes development easierbut also allow you to take advantage of the rich ecosystem of Javascript packages and tooling.
```bash
$ npm install ant-design-vue --save
@ -55,7 +49,7 @@ $ npm install ant-design-vue --save
$ yarn add ant-design-vue
```
If you are in a bad network environment, you can try other registries and tools like [cnpm](https://github.com/cnpm/cnpm).
If you are in a bad network environmentyou can try other registries and tools like [cnpm](https://github.com/cnpm/cnpm).
## Links
@ -73,7 +67,6 @@ 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,8 +75,7 @@ ant-design-vue is an MIT-licensed open source project. In order to achieve bette
- [Patreon](https://www.patreon.com/tangjinzhou)
- [opencollective](https://opencollective.com/ant-design-vue)
- [paypal](https://www.paypal.me/tangjinzhou)
- [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png)
- ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2
- [支付宝或微信](https://qn.antdv.com/alipay-and-wechat.png)
## Sponsors
@ -93,14 +85,6 @@ Become a sponsor and get your logo on our README on Github with a link to your s
## [More Sponsor (From Patreon、alipay、wechat、paypal...)](https://github.com/vueComponent/ant-design-vue/blob/master/BACKERS.md)
## Contributors
Thank you to all the people who already contributed to ant-design-vue!
<a href="https://github.com/vueComponent/ant-design-vue/graphs/contributors">
<img src="https://contrib.rocks/image?repo=vueComponent/ant-design-vue&max=100&columns=15" />
</a>
[![Let's fund issues in this repository](https://issuehunt.io/static/embed/issuehunt-button-v1.svg)](https://issuehunt.io/repos/104172832)
This project is tested with BrowserStack.

View File

@ -6,6 +6,7 @@ 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<Map<String, VueTag>> {
@ -21,13 +22,13 @@ async function readMarkdown(options: Options): Promise<Map<String, VueTag>> {
return formatter(mdParser(fileContent), componentName, kebabComponentName, options.tagPrefix);
})
.filter(item => item) as VueTag[][];
const tags = new Map<String, VueTag>();
const tags: Map<String, VueTag> = new Map();
flatMap(data, item => item).forEach(mergedTag => mergeTag(tags, mergedTag));
return tags;
}
function readTypings(options: Options): Map<String, VueTag> {
const tags = new Map<String, VueTag>();
const tags: Map<String, VueTag> = new Map();
const fileContent = readFileSync(options.typingsPath, 'utf-8');
fileContent
.split('\n')
@ -61,7 +62,7 @@ function mergeTag(tags: Map<String, VueTag>, mergedTag: VueTag) {
function mergeTags(mergedTagsArr: Map<String, VueTag>[]): VueTag[] {
if (mergedTagsArr.length === 1) return [...mergedTagsArr[0].values()];
const tags = new Map<String, VueTag>();
const tags: Map<String, VueTag> = new Map();
if (mergedTagsArr.length === 0) return [];
mergedTagsArr.forEach(mergedTags => {
mergedTags.forEach(mergedTag => mergeTag(tags, mergedTag));
@ -77,6 +78,13 @@ export async function parseAndWrite(options: Options): Promise<Number> {
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;
}

View File

@ -21,7 +21,7 @@ export type Articals = Artical[];
function readLine(input: string) {
const end = input.indexOf('\n');
return input.substring(0, end !== -1 ? end : input.length);
return input.substr(0, end !== -1 ? end : input.length);
}
function splitTableLine(line: string) {
@ -47,7 +47,7 @@ function tableParse(input: string) {
};
while (start < end) {
const target = input.substring(start);
const target = input.substr(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.substring(start);
const target = input.substr(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.substring(match.index));
const { table, usedLength } = tableParse(target.substr(match.index));
artical.push({
type: 'table',
table,

View File

@ -34,6 +34,25 @@ export type VueTag = {
description?: string;
};
export type VeturTag = {
description?: string;
attributes: string[];
};
export type VeturTags = Record<string, VeturTag>;
export type VeturAttribute = {
type: string;
description: string;
};
export type VeturAttributes = Record<string, VeturAttribute>;
export type VeturResult = {
tags: VeturTags;
attributes: VeturAttributes;
};
export type Options = {
name: string;
path: PathLike;

View File

@ -0,0 +1,30 @@
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;
}

View File

@ -1,6 +1,7 @@
'use strict';
const fs = require('fs');
const assign = require('object-assign');
const { getProjectPath } = require('./utils/projectHelper');
module.exports = function () {
@ -8,7 +9,7 @@ module.exports = function () {
if (fs.existsSync(getProjectPath('tsconfig.json'))) {
my = require(getProjectPath('tsconfig.json'));
}
return Object.assign(
return assign(
{
noUnusedParameters: true,
noUnusedLocals: true,

View File

@ -22,7 +22,7 @@ const imageOptions = {
limit: 10000,
};
function getWebpackConfig(modules, esm = false) {
function getWebpackConfig(modules) {
const pkg = require(getProjectPath('package.json'));
const babelConfig = require('./getBabelCommonConfig')(modules || false);
@ -197,25 +197,9 @@ All rights reserved.
},
},
];
if (esm) {
entry = ['./index.esm'];
config.experiments = {
...config.experiments,
outputModule: true,
};
config.output.chunkFormat = 'module';
config.output.library = {
type: 'module',
};
config.target = 'es2019';
} else {
config.output.libraryTarget = 'umd';
config.output.library = distFileBaseName;
config.output.globalObject = 'this';
}
const entryName = esm ? `${distFileBaseName}.esm` : distFileBaseName;
config.output.library = distFileBaseName;
config.output.libraryTarget = 'umd';
config.output.globalObject = 'this';
config.optimization = {
minimizer: [
new TerserPlugin({
@ -229,7 +213,7 @@ All rights reserved.
// Development
const uncompressedConfig = merge({}, config, {
entry: {
[entryName]: entry,
[distFileBaseName]: entry,
},
mode: 'development',
plugins: [
@ -242,7 +226,7 @@ All rights reserved.
// Production
const prodConfig = merge({}, config, {
entry: {
[`${entryName}.min`]: entry,
[`${distFileBaseName}.min`]: entry,
},
mode: 'production',
plugins: [

View File

@ -368,7 +368,7 @@ function pub(done) {
}
}
const startTime = new Date();
let startTime = new Date();
gulp.task('compile-with-es', done => {
console.log('start compile at ', startTime);
console.log('[Parallel] Compile to es...');
@ -452,7 +452,7 @@ gulp.task(
newVersion.trim() === version
) {
// eslint-disable-next-line no-unused-vars
runCmd('npm', ['run', 'pub'], _code => {
runCmd('npm', ['run', 'pub'], code => {
done();
});
} else {

View File

@ -1,167 +1,51 @@
import type { PropType } from 'vue';
import { computed, defineComponent, shallowRef, ref, watch } from 'vue';
import { defineComponent, shallowRef, withDirectives } from 'vue';
import antInput from './antInputDirective';
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: {
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,
value: PropTypes.string.def(''),
},
emits: [
'change',
'input',
'blur',
'keydown',
'focus',
'compositionstart',
'compositionend',
'keyup',
'paste',
'mousedown',
],
setup(props, { emit, attrs, expose }) {
const inputRef = shallowRef<BaseInputInnerExpose>(null);
const renderValue = ref();
const isComposing = ref(false);
watch(
[() => props.value, isComposing],
() => {
if (isComposing.value) return;
renderValue.value = props.value;
},
{ immediate: true },
);
emits: ['change', 'input'],
setup(_p, { emit }) {
const inputRef = shallowRef(null);
const handleChange = (e: Event) => {
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);
};
const handleBlur = (e: Event) => {
emit('blur', e);
};
const handleFocus = (e: Event) => {
emit('focus', e);
};
const focus = () => {
if (inputRef.value) {
inputRef.value.focus();
const { composing } = e.target as any;
if ((e as any).isComposing || composing) {
emit('input', e);
} else {
emit('input', e);
emit('change', e);
}
};
const blur = () => {
if (inputRef.value) {
inputRef.value.blur();
}
return {
inputRef,
focus: () => {
if (inputRef.value) {
inputRef.value.focus();
}
},
blur: () => {
if (inputRef.value) {
inputRef.value.blur();
}
},
handleChange,
};
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 (
<BaseInputInner
{...restProps}
{...attrs}
style={styleString.value}
onInput={handleInput}
onChange={handleChange}
onBlur={handleBlur}
onFocus={handleFocus}
ref={inputRef}
value={renderValue.value}
onCompositionstart={onCompositionstart}
onCompositionend={onCompositionend}
onKeyup={handleKeyUp}
onKeydown={handleKeyDown}
onPaste={handlePaste}
onMousedown={handleMousedown}
},
render() {
return withDirectives(
(
<input
{...this.$props}
{...this.$attrs}
onInput={this.handleChange}
onChange={this.handleChange}
ref="inputRef"
/>
);
};
) as any,
[[antInput]],
);
},
});

View File

@ -1,96 +0,0 @@
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 <Tag {...restProps} ref={inputRef} value={value} />;
};
},
});
export default BaseInputInner;

View File

@ -3,7 +3,7 @@ import { getOptionProps } from './props-util';
export default {
methods: {
setState(state = {}, callback: () => any) {
setState(state = {}, callback) {
let newState = typeof state === 'function' ? state(this.$data, this.$props) : state;
if (this.getDerivedStateFromProps) {
const s = this.getDerivedStateFromProps(getOptionProps(this), {
@ -26,7 +26,6 @@ export default {
},
__emit() {
// 直接调用事件底层组件不需要vueTool记录events
// eslint-disable-next-line prefer-rest-params
const args = [].slice.call(arguments, 0);
let eventName = args[0];
eventName = `on${eventName[0].toUpperCase()}${eventName.substring(1)}`;

View File

@ -3,7 +3,7 @@ import {
defineComponent,
nextTick,
onBeforeMount,
onMounted,
onBeforeUnmount,
onUpdated,
Teleport,
watch,
@ -23,24 +23,12 @@ export default defineComponent({
// getContainer
let container: HTMLElement;
const { shouldRender } = useInjectPortal();
function setContainer() {
onBeforeMount(() => {
isSSR = false;
if (shouldRender.value) {
container = props.getContainer();
}
}
onBeforeMount(() => {
isSSR = false;
// drawer
setContainer();
});
onMounted(() => {
if (container) return;
// https://github.com/vueComponent/ant-design-vue/issues/6937
setContainer();
});
const stopWatch = watch(shouldRender, () => {
if (shouldRender.value && !container) {
container = props.getContainer();
@ -56,11 +44,11 @@ export default defineComponent({
}
});
});
// onBeforeUnmount(() => {
// if (container && container.parentNode) {
// container.parentNode.removeChild(container);
// }
// });
onBeforeUnmount(() => {
if (container && container.parentNode) {
container.parentNode.removeChild(container);
}
});
return () => {
if (!shouldRender.value) return null;
if (isSSR) {

View File

@ -7,6 +7,7 @@ import {
onMounted,
onBeforeUnmount,
onUpdated,
getCurrentInstance,
nextTick,
computed,
} from 'vue';
@ -60,14 +61,11 @@ export default defineComponent({
const container = shallowRef<HTMLElement>();
const componentRef = shallowRef();
const rafId = shallowRef<number>();
const triggerUpdate = shallowRef(1);
const defaultContainer = canUseDom() && document.createElement('div');
const removeCurrentContainer = () => {
// Portal will remove from `parentNode`.
// Let's handle this again to avoid refactor issue.
if (container.value === defaultContainer) {
container.value?.parentNode?.removeChild(container.value);
}
container.value?.parentNode?.removeChild(container.value);
container.value = null;
};
let parent: HTMLElement = null;
@ -84,6 +82,8 @@ export default defineComponent({
return true;
};
// attachToParent();
const defaultContainer = document.createElement('div');
const getContainer = () => {
if (!supportDom) {
return null;
@ -106,6 +106,8 @@ export default defineComponent({
attachToParent();
});
const instance = getCurrentInstance();
useScrollLocker(
computed(() => {
return (
@ -153,7 +155,7 @@ export default defineComponent({
nextTick(() => {
if (!attachToParent()) {
rafId.value = raf(() => {
triggerUpdate.value += 1;
instance.update();
});
}
});
@ -175,7 +177,7 @@ export default defineComponent({
getOpenCount: () => openCount,
getContainer,
};
if (triggerUpdate.value && (forceRender || visible || componentRef.value)) {
if (forceRender || visible || componentRef.value) {
portal = (
<Portal
getContainer={getContainer}

View File

@ -1,11 +0,0 @@
import { defineComponent } from 'vue';
import { customRenderSlot } from '../vnode';
export default defineComponent({
name: 'RenderSlot',
setup(_props, { slots }) {
return () => {
return customRenderSlot(slots, 'default', {}, () => ['default value']);
};
},
});

View File

@ -1,26 +0,0 @@
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');
});
});

View File

@ -0,0 +1,35 @@
function onCompositionStart(e) {
e.target.composing = true;
}
function onCompositionEnd(e) {
// 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, event, handler, options) {
el.addEventListener(event, handler, options);
}
const antInput = {
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;

View File

@ -0,0 +1,130 @@
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;

View File

@ -0,0 +1,186 @@
// 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;

View File

@ -1,20 +1,16 @@
export type KeyType = string | number;
type ValueType = [number, any]; // [times, realValue]
const SPLIT = '%';
class Entity {
instanceId: string;
constructor(instanceId: string) {
this.instanceId = instanceId;
}
/** @private Internal cache map. Do not access this directly */
cache = new Map<string, ValueType>();
get(keys: KeyType[] | string): ValueType | null {
return this.cache.get(Array.isArray(keys) ? keys.join(SPLIT) : keys) || 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(SPLIT) : keys;
const path = Array.isArray(keys) ? keys.join('%') : keys;
const prevValue = this.cache.get(path)!;
const nextValue = valueFn(prevValue);

View File

@ -1,41 +1,29 @@
import type { ShallowRef, ExtractPropTypes, InjectionKey, Ref } from 'vue';
import {
provide,
defineComponent,
unref,
inject,
watch,
shallowRef,
getCurrentInstance,
} 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_CACHE_PATH = 'data-cache-path';
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() {
const cssinjsInstanceId = Math.random().toString(12).slice(2);
// Tricky SSR: Move all inline style to the head.
// PS: We do not recommend tricky mode.
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] || cssinjsInstanceId;
(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
// Not force move if no head
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
document.head.insertBefore(style, firstChild);
}
document.head.insertBefore(style, firstChild);
});
// Deduplicate of moved styles
@ -43,7 +31,7 @@ export function createCache() {
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] === cssinjsInstanceId) {
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
style.parentNode?.removeChild(style);
}
} else {
@ -52,7 +40,7 @@ export function createCache() {
});
}
return new CacheEntity(cssinjsInstanceId);
return new CacheEntity();
}
export type HashPriority = 'low' | 'high';
@ -88,45 +76,19 @@ const StyleContextKey: InjectionKey<ShallowRef<Partial<StyleContextProps>>> =
Symbol('StyleContextKey');
export type UseStyleProviderProps = Partial<StyleContextProps> | Ref<Partial<StyleContextProps>>;
// fix: https://github.com/vueComponent/ant-design-vue/issues/7023
const getCache = () => {
const instance = getCurrentInstance();
let cache: CacheEntity;
if (instance && instance.appContext) {
const globalCache = instance.appContext?.config?.globalProperties?.__ANTDV_CSSINJS_CACHE__;
if (globalCache) {
cache = globalCache;
} else {
cache = createCache();
if (instance.appContext.config.globalProperties) {
instance.appContext.config.globalProperties.__ANTDV_CSSINJS_CACHE__ = cache;
}
}
} else {
cache = createCache();
}
return cache;
};
const defaultStyleContext: StyleContextProps = {
cache: createCache(),
defaultCache: true,
hashPriority: 'low',
};
// fix: https://github.com/vueComponent/ant-design-vue/issues/6912
export const useStyleInject = () => {
const cache = getCache();
return inject(StyleContextKey, shallowRef({ ...defaultStyleContext, cache }));
return inject(StyleContextKey, shallowRef({ ...defaultStyleContext }));
};
export const useStyleProvider = (props: UseStyleProviderProps) => {
const parentContext = useStyleInject();
const context = shallowRef<Partial<StyleContextProps>>({
...defaultStyleContext,
cache: createCache(),
});
const context = shallowRef<Partial<StyleContextProps>>({ ...defaultStyleContext });
watch(
[() => unref(props), parentContext],
[props, parentContext],
() => {
const mergedContext: Partial<StyleContextProps> = {
...parentContext.value,
@ -180,7 +142,7 @@ export const StyleProvider = withInstall(
defineComponent({
name: 'AStyleProvider',
inheritAttrs: false,
props: styleProviderProps(),
props: initDefaultProps(styleProviderProps(), defaultStyleContext),
setup(props, { slots }) {
useStyleProvider(props);
return () => slots.default?.();

View File

@ -1,5 +1,5 @@
import hash from '@emotion/hash';
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, CSS_IN_JS_INSTANCE_ID } from '../StyleContext';
import type Theme from '../theme/Theme';
import useGlobalCache from './useGlobalCache';
import { flattenToken, token2key } from '../util';
@ -8,15 +8,11 @@ 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 = !isProduction && !isPrerender ? 'css-dev-only-do-not-override' : 'css';
const hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css';
export interface Option<DerivativeToken, DesignToken> {
export interface Option<DerivativeToken> {
/**
* Generate token with salt.
* This is used to generate different hashId even same derivative token for different version.
@ -34,18 +30,6 @@ export interface Option<DerivativeToken, DesignToken> {
* It's ok to useMemo outside but this has better cache strategy.
*/
formatToken?: (mergedToken: any) => DerivativeToken;
/**
* Get final token with origin token, override token and theme.
* The parameters do not contain formatToken since it's passed by user.
* @param origin The original token.
* @param override Extra tokens to override.
* @param theme Theme instance. Could get derivative token by `theme.getDerivativeToken`
*/
getComputedToken?: (
origin: DesignToken,
override: object,
theme: Theme<any, any>,
) => DerivativeToken;
}
const tokenKeys = new Map<string, number>();
@ -53,22 +37,20 @@ function recordCleanToken(tokenKey: string) {
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) + 1);
}
function removeStyleTags(key: string, instanceId: string) {
function removeStyleTags(key: string) {
if (typeof document !== 'undefined') {
const styles = document.querySelectorAll(`style[${ATTR_TOKEN}="${key}"]`);
styles.forEach(style => {
if ((style as any)[CSS_IN_JS_INSTANCE] === instanceId) {
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
style.parentNode?.removeChild(style);
}
});
}
}
const TOKEN_THRESHOLD = 0;
// Remove will check current keys first
function cleanTokenStyle(tokenKey: string, instanceId: string) {
function cleanTokenStyle(tokenKey: string) {
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) - 1);
const tokenKeyList = Array.from(tokenKeys.keys());
@ -78,36 +60,14 @@ function cleanTokenStyle(tokenKey: string, instanceId: string) {
return count <= 0;
});
// Should keep tokens under threshold for not to insert style too often
if (tokenKeyList.length - cleanableKeyList.length > TOKEN_THRESHOLD) {
if (cleanableKeyList.length < tokenKeyList.length) {
cleanableKeyList.forEach(key => {
removeStyleTags(key, instanceId);
removeStyleTags(key);
tokenKeys.delete(key);
});
}
}
export const getComputedToken = <DerivativeToken = object, DesignToken = DerivativeToken>(
originToken: DesignToken,
overrideToken: object,
theme: Theme<any, any>,
format?: (token: DesignToken) => DerivativeToken,
) => {
const derivativeToken = theme.getDerivativeToken(originToken);
// Merge with override
let mergedDerivativeToken = {
...derivativeToken,
...overrideToken,
};
// Format if needed
if (format) {
mergedDerivativeToken = format(mergedDerivativeToken);
}
return mergedDerivativeToken;
};
/**
* Cache theme derivative token as global shared one
* @param theme Theme entity
@ -118,10 +78,8 @@ export const getComputedToken = <DerivativeToken = object, DesignToken = Derivat
export default function useCacheToken<DerivativeToken = object, DesignToken = DerivativeToken>(
theme: Ref<Theme<any, any>>,
tokens: Ref<Partial<DesignToken>[]>,
option: Ref<Option<DerivativeToken, DesignToken>> = ref({}),
option: Ref<Option<DerivativeToken>> = ref({}),
) {
const style = useStyleInject();
// Basic - We do basic cache here
const mergedToken = computed(() => Object.assign({}, ...tokens.value));
const tokenStr = computed(() => flattenToken(mergedToken.value));
@ -136,15 +94,19 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
overrideTokenStr.value,
]),
() => {
const {
salt = '',
override = EMPTY_OVERRIDE,
formatToken,
getComputedToken: compute,
} = option.value;
const mergedDerivativeToken = compute
? compute(mergedToken.value, override, theme.value)
: getComputedToken(mergedToken.value, override, theme.value, formatToken);
const { salt = '', override = EMPTY_OVERRIDE, formatToken } = option.value;
const derivativeToken = theme.value.getDerivativeToken(mergedToken.value);
// Merge with override
let mergedDerivativeToken = {
...derivativeToken,
...override,
};
// Format if needed
if (formatToken) {
mergedDerivativeToken = formatToken(mergedDerivativeToken);
}
// Optimize for `useStyleRegister` performance
const tokenKey = token2key(mergedDerivativeToken, salt);
@ -153,11 +115,12 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
const hashId = `${hashPrefix}-${hash(tokenKey)}`;
mergedDerivativeToken._hashId = hashId; // Not used
return [mergedDerivativeToken, hashId];
},
cache => {
// Remove token will remove all related style
cleanTokenStyle(cache[0]._tokenKey, style.value?.cache.instanceId);
cleanTokenStyle(cache[0]._tokenKey);
},
);

View File

@ -16,8 +16,7 @@ if (
process.env.NODE_ENV !== 'production' &&
typeof module !== 'undefined' &&
module &&
(module as any).hot &&
typeof window !== 'undefined'
(module as any).hot
) {
const win = window as any;
if (typeof win.webpackHotUpdate === 'function') {

View File

@ -3,38 +3,32 @@ import type * as CSS from 'csstype';
// @ts-ignore
import unitless from '@emotion/unitless';
import { compile, serialize, stringify } from 'stylis';
import type { Theme, Transformer } from '../..';
import type Cache from '../../Cache';
import type Keyframes from '../../Keyframes';
import type { Linter } from '../../linters';
import { contentQuotesLinter, hashedAnimationLinter } from '../../linters';
import type { HashPriority } from '../../StyleContext';
import type { Theme, Transformer } from '..';
import type Cache from '../Cache';
import type Keyframes from '../Keyframes';
import type { Linter } from '../linters';
import { contentQuotesLinter, hashedAnimationLinter } from '../linters';
import type { HashPriority } from '../StyleContext';
import {
useStyleInject,
ATTR_CACHE_PATH,
ATTR_DEV_CACHE_PATH,
ATTR_MARK,
ATTR_TOKEN,
CSS_IN_JS_INSTANCE,
} from '../../StyleContext';
import { supportLayer } from '../../util';
import useGlobalCache from '../useGlobalCache';
import { removeCSS, updateCSS } from '../../../../vc-util/Dom/dynamicCSS';
CSS_IN_JS_INSTANCE_ID,
} from '../StyleContext';
import { supportLayer } from '../util';
import useGlobalCache from './useGlobalCache';
import canUseDom from '../../canUseDom';
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
import type { Ref } from 'vue';
import { computed } from 'vue';
import type { VueNode } from '../../../type';
import canUseDom from '../../../../_util/canUseDom';
import {
ATTR_CACHE_MAP,
existPath,
getStyleAndHash,
serialize as serializeCacheMap,
} from './cacheMapUtil';
import type { VueNode } from '../../type';
const isClientSide = canUseDom();
const SKIP_CHECK = '_skip_check_';
const MULTI_VALUE = '_multi_value_';
export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & {
animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes;
};
@ -42,17 +36,16 @@ export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'anima
export type CSSPropertiesWithMultiValues = {
[K in keyof CSSProperties]:
| CSSProperties[K]
| readonly Extract<CSSProperties[K], string>[]
| Extract<CSSProperties[K], string>[]
| {
[SKIP_CHECK]?: boolean;
[MULTI_VALUE]?: boolean;
value: CSSProperties[K] | CSSProperties[K][];
[SKIP_CHECK]: boolean;
value: CSSProperties[K] | Extract<CSSProperties[K], string>[];
};
};
export type CSSPseudos = { [K in CSS.Pseudos]?: CSSObject };
type ArrayCSSInterpolation = readonly CSSInterpolation[];
type ArrayCSSInterpolation = CSSInterpolation[];
export type InterpolationPrimitive = null | undefined | boolean | number | string | CSSObject;
@ -66,13 +59,13 @@ export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSS
// == Parser ==
// ============================================================================
// Preprocessor style content to browser support one
export function normalizeStyle(styleStr: string): string {
export function normalizeStyle(styleStr: string) {
const serialized = serialize(compile(styleStr), stringify);
return serialized.replace(/\{%%%\:[^;];}/g, ';');
}
function isCompoundCSSProperty(value: CSSObject[string]) {
return typeof value === 'object' && value && (SKIP_CHECK in value || MULTI_VALUE in value);
return typeof value === 'object' && value && SKIP_CHECK in value;
}
// hash
@ -231,45 +224,32 @@ export const parseStyle = (
styleStr += `${mergedKey}${parsedStr}`;
} else {
function appendStyle(cssKey: string, cssValue: any) {
if (
process.env.NODE_ENV !== 'production' &&
(typeof value !== 'object' || !(value as any)?.[SKIP_CHECK])
) {
[contentQuotesLinter, hashedAnimationLinter, ...linters].forEach(linter =>
linter(cssKey, cssValue, { path, hashId, parentSelectors }),
);
}
//
const styleName = cssKey.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
// Auto suffix with px
let formatValue = cssValue;
if (!unitless[cssKey] && typeof formatValue === 'number' && formatValue !== 0) {
formatValue = `${formatValue}px`;
}
// handle animationName & Keyframe value
if (cssKey === 'animationName' && (cssValue as Keyframes)?._keyframe) {
parseKeyframes(cssValue as Keyframes);
formatValue = (cssValue as Keyframes).getName(hashId);
}
styleStr += `${styleName}:${formatValue};`;
}
const actualValue = (value as any)?.value ?? value;
if (
typeof value === 'object' &&
(value as any)?.[MULTI_VALUE] &&
Array.isArray(actualValue)
process.env.NODE_ENV !== 'production' &&
(typeof value !== 'object' || !(value as any)?.[SKIP_CHECK])
) {
actualValue.forEach(item => {
appendStyle(key, item);
});
} else {
appendStyle(key, actualValue);
[contentQuotesLinter, hashedAnimationLinter, ...linters].forEach(linter =>
linter(key, actualValue, { path, hashId, parentSelectors }),
);
}
//
const styleName = key.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
// Auto suffix with px
let formatValue = actualValue;
if (!unitless[key] && typeof formatValue === 'number' && formatValue !== 0) {
formatValue = `${formatValue}px`;
}
// handle animationName & Keyframe value
if (key === 'animationName' && (value as Keyframes)?._keyframe) {
parseKeyframes(value as Keyframes);
formatValue = (value as Keyframes).getName(hashId);
}
styleStr += `${styleName}:${formatValue};`;
}
});
}
@ -313,14 +293,6 @@ export default function useStyleRegister(
path: string[];
hashId?: string;
layer?: string;
nonce?: string | (() => string);
clientOnly?: boolean;
/**
* Tell cssinjs the insert order of style.
* It's useful when you need to insert style
* before other style to overwrite for the same selector priority.
*/
order?: number;
}>,
styleFn: () => CSSInterpolation,
) {
@ -337,32 +309,14 @@ export default function useStyleRegister(
}
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
useGlobalCache<
[
styleStr: string,
tokenKey: string,
styleId: string,
effectStyle: Record<string, string>,
clientOnly: boolean | undefined,
order: number,
]
>(
useGlobalCache(
'style',
fullPath,
// Create cache if needed
() => {
const { path, hashId, layer, nonce, clientOnly, order = 0 } = info.value;
const cachePath = fullPath.value.join('|');
// Get style from SSR inline style directly
if (existPath(cachePath)) {
const [inlineCacheStyleStr, styleHash] = getStyleAndHash(cachePath);
if (inlineCacheStyleStr) {
return [inlineCacheStyleStr, tokenKey.value, styleHash, {}, clientOnly, order];
}
}
const styleObj = styleFn();
const { hashPriority, container, transformers, linters, cache } = styleContext.value;
const { hashPriority, container, transformers, linters } = styleContext.value;
const { path, hashId, layer } = info.value;
const [parsedStyle, effectStyle] = parseStyle(styleObj, {
hashId,
hashPriority,
@ -375,29 +329,20 @@ export default function useStyleRegister(
const styleId = uniqueHash(fullPath.value, styleStr);
if (isMergedClientSide) {
const mergedCSSConfig: Parameters<typeof updateCSS>[2] = {
const style = updateCSS(styleStr, styleId, {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
priority: order,
};
});
const nonceStr = typeof nonce === 'function' ? nonce() : nonce;
if (nonceStr) {
mergedCSSConfig.csp = { nonce: nonceStr };
}
const style = updateCSS(styleStr, styleId, mergedCSSConfig);
(style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId;
(style as any)[CSS_IN_JS_INSTANCE] = CSS_IN_JS_INSTANCE_ID;
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, tokenKey.value);
// Dev usage to find which cache path made this easily
if (process.env.NODE_ENV !== 'production') {
style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|'));
style.setAttribute(ATTR_DEV_CACHE_PATH, fullPath.value.join('|'));
}
// Inject client side effect style
@ -415,7 +360,7 @@ export default function useStyleRegister(
});
}
return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order];
return [styleStr, tokenKey.value, styleId];
},
// Remove cache if no need
([, , styleId], fromHMR) => {
@ -454,113 +399,19 @@ export default function useStyleRegister(
// ============================================================================
// == SSR ==
// ============================================================================
export function extractStyle(cache: Cache, plain = false) {
const matchPrefix = `style%`;
export function extractStyle(cache: Cache) {
// prefix with `style` is used for `useStyleRegister` to cache style context
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith(matchPrefix));
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith('style%'));
// Common effect styles like animation
const effectStyles: Record<string, boolean> = {};
// Mapping of cachePath to style hash
const cachePathMap: Record<string, string> = {};
// const tokenStyles: Record<string, string[]> = {};
let styleText = '';
function toStyleStr(
style: string,
tokenKey?: string,
styleId?: string,
customizeAttrs: Record<string, string> = {},
) {
const attrs: Record<string, string | undefined> = {
...customizeAttrs,
[ATTR_TOKEN]: tokenKey,
[ATTR_MARK]: styleId,
};
styleKeys.forEach(key => {
const [styleStr, tokenKey, styleId]: [string, string, string] = cache.cache.get(key)![1];
const attrStr = Object.keys(attrs)
.map(attr => {
const val = attrs[attr];
return val ? `${attr}="${val}"` : null;
})
.filter(v => v)
.join(' ');
return plain ? style : `<style ${attrStr}>${style}</style>`;
}
// ====================== Fill Style ======================
type OrderStyle = [order: number, style: string];
const orderStyles: OrderStyle[] = styleKeys
.map(key => {
const cachePath = key.slice(matchPrefix.length).replace(/%/g, '|');
const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order]: [
string,
string,
string,
Record<string, string>,
boolean,
number,
] = cache.cache.get(key)![1];
// Skip client only style
if (clientOnly) {
return null! as OrderStyle;
}
// ====================== Style ======================
// Used for vc-util
const sharedAttrs = {
'data-vc-order': 'prependQueue',
'data-vc-priority': `${order}`,
};
let keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs);
// Save cache path with hash mapping
cachePathMap[cachePath] = styleId;
// =============== Create effect style ===============
if (effectStyle) {
Object.keys(effectStyle).forEach(effectKey => {
// Effect style can be reused
if (!effectStyles[effectKey]) {
effectStyles[effectKey] = true;
keyStyleText += toStyleStr(
normalizeStyle(effectStyle[effectKey]),
tokenKey,
`_effect-${effectKey}`,
sharedAttrs,
);
}
});
}
const ret: OrderStyle = [order, keyStyleText];
return ret;
})
.filter(o => o);
orderStyles
.sort((o1, o2) => o1[0] - o2[0])
.forEach(([, style]) => {
styleText += style;
});
// ==================== Fill Cache Path ====================
styleText += toStyleStr(
`.${ATTR_CACHE_MAP}{content:"${serializeCacheMap(cachePathMap)}";}`,
undefined,
undefined,
{
[ATTR_CACHE_MAP]: ATTR_CACHE_MAP,
},
);
styleText += `<style ${ATTR_TOKEN}="${tokenKey}" ${ATTR_MARK}="${styleId}">${styleStr}</style>`;
});
return styleText;
}

View File

@ -1,91 +0,0 @@
import canUseDom from '../../../../_util/canUseDom';
import { ATTR_MARK } from '../../StyleContext';
export const ATTR_CACHE_MAP = 'data-ant-cssinjs-cache-path';
/**
* This marks style from the css file.
* Which means not exist in `<style />` tag.
*/
export const CSS_FILE_STYLE = '_FILE_STYLE__';
export function serialize(cachePathMap: Record<string, string>) {
return Object.keys(cachePathMap)
.map(path => {
const hash = cachePathMap[path];
return `${path}:${hash}`;
})
.join(';');
}
let cachePathMap: Record<string, string>;
let fromCSSFile = true;
/**
* @private Test usage only. Can save remove if no need.
*/
export function reset(mockCache?: Record<string, string>, fromFile = true) {
cachePathMap = mockCache!;
fromCSSFile = fromFile;
}
export function prepare() {
if (!cachePathMap) {
cachePathMap = {};
if (canUseDom()) {
const div = document.createElement('div');
div.className = ATTR_CACHE_MAP;
div.style.position = 'fixed';
div.style.visibility = 'hidden';
div.style.top = '-9999px';
document.body.appendChild(div);
let content = getComputedStyle(div).content || '';
content = content.replace(/^"/, '').replace(/"$/, '');
// Fill data
content.split(';').forEach(item => {
const [path, hash] = item.split(':');
cachePathMap[path] = hash;
});
// Remove inline record style
const inlineMapStyle = document.querySelector(`style[${ATTR_CACHE_MAP}]`);
if (inlineMapStyle) {
fromCSSFile = false;
inlineMapStyle.parentNode?.removeChild(inlineMapStyle);
}
document.body.removeChild(div);
}
}
}
export function existPath(path: string) {
prepare();
return !!cachePathMap[path];
}
export function getStyleAndHash(path: string): [style: string | null, hash: string] {
const hash = cachePathMap[path];
let styleStr: string | null = null;
if (hash && canUseDom()) {
if (fromCSSFile) {
styleStr = CSS_FILE_STYLE;
} else {
const style = document.querySelector(`style[${ATTR_MARK}="${cachePathMap[path]}"]`);
if (style) {
styleStr = style.innerHTML;
} else {
// Clean up since not exist anymore
delete cachePathMap[path];
}
}
}
return [styleStr, hash];
}

View File

@ -3,15 +3,13 @@ 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, parentSelectorLinter } 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';
import px2remTransformer from './transformers/px2rem';
import { supportLogicProps, supportWhere } from './util';
const cssinjs = {
Theme,
@ -26,12 +24,10 @@ const cssinjs = {
// Transformer
legacyLogicalPropertiesTransformer,
px2remTransformer,
// Linters
logicalPropertiesLinter,
legacyNotSelectorLinter,
parentSelectorLinter,
// cssinjs
StyleProvider,
@ -49,12 +45,10 @@ export {
// Transformer
legacyLogicalPropertiesTransformer,
px2remTransformer,
// Linters
logicalPropertiesLinter,
legacyNotSelectorLinter,
parentSelectorLinter,
// cssinjs
StyleProvider,
@ -70,8 +64,4 @@ export type {
StyleProviderProps,
};
export const _experimental = {
supportModernCSS: () => supportWhere() && supportLogicProps(),
};
export default cssinjs;

View File

@ -3,4 +3,3 @@ export { default as hashedAnimationLinter } from './hashedAnimationLinter';
export type { Linter } from './interface';
export { default as legacyNotSelectorLinter } from './legacyNotSelectorLinter';
export { default as logicalPropertiesLinter } from './logicalPropertiesLinter';
export { default as parentSelectorLinter } from './parentSelectorLinter';

View File

@ -1,15 +0,0 @@
import type { Linter } from '..';
import { lintWarning } from './utils';
const linter: Linter = (_key, _value, info) => {
if (
info.parentSelectors.some(selector => {
const selectors = selector.split(',');
return selectors.some(item => item.split('&').length > 2);
})
) {
lintWarning('Should not use more than one `&` in a selector.', info);
}
};
export default linter;

View File

@ -1,76 +0,0 @@
/**
* respect https://github.com/cuth/postcss-pxtorem
*/
import unitless from '@emotion/unitless';
import type { CSSObject } from '..';
import type { Transformer } from './interface';
export interface Options {
/**
* The root font size.
* @default 16
*/
rootValue?: number;
/**
* The decimal numbers to allow the REM units to grow to.
* @default 5
*/
precision?: number;
/**
* Whether to allow px to be converted in media queries.
* @default false
*/
mediaQuery?: boolean;
}
const pxRegex = /url\([^)]+\)|var\([^)]+\)|(\d*\.?\d+)px/g;
function toFixed(number: number, precision: number) {
const multiplier = Math.pow(10, precision + 1),
wholeNumber = Math.floor(number * multiplier);
return (Math.round(wholeNumber / 10) * 10) / multiplier;
}
const transform = (options: Options = {}): Transformer => {
const { rootValue = 16, precision = 5, mediaQuery = false } = options;
const pxReplace = (m: string, $1: any) => {
if (!$1) return m;
const pixels = parseFloat($1);
// covenant: pixels <= 1, not transform to rem @zombieJ
if (pixels <= 1) return m;
const fixedVal = toFixed(pixels / rootValue, precision);
return `${fixedVal}rem`;
};
const visit = (cssObj: CSSObject): CSSObject => {
const clone: CSSObject = { ...cssObj };
Object.entries(cssObj).forEach(([key, value]) => {
if (typeof value === 'string' && value.includes('px')) {
const newValue = value.replace(pxRegex, pxReplace);
clone[key] = newValue;
}
// no unit
if (!unitless[key] && typeof value === 'number' && value !== 0) {
clone[key] = `${value}px`.replace(pxRegex, pxReplace);
}
// Media queries
const mergedKey = key.trim();
if (mergedKey.startsWith('@') && mergedKey.includes('px') && mediaQuery) {
const newKey = key.replace(pxRegex, pxReplace);
clone[newKey] = clone[key];
delete clone[key];
}
});
return clone;
};
return { visit };
};
export default transform;

View File

@ -2,30 +2,17 @@ import hash from '@emotion/hash';
import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
import canUseDom from '../canUseDom';
import { Theme } from './theme';
// Create a cache here to avoid always loop generate
const flattenTokenCache = new WeakMap<any, string>();
export function flattenToken(token: any) {
let str = flattenTokenCache.get(token) || '';
if (!str) {
Object.keys(token).forEach(key => {
const value = token[key];
str += key;
if (value instanceof Theme) {
str += value.id;
} else if (value && typeof value === 'object') {
str += flattenToken(value);
} else {
str += value;
}
});
// Put in cache
flattenTokenCache.set(token, str);
}
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;
}
@ -36,18 +23,12 @@ export function token2key(token: any, salt: string): string {
return hash(`${salt}_${flattenToken(token)}`);
}
const randomSelectorKey = `random-${Date.now()}-${Math.random()}`.replace(/\./g, '');
const layerKey = `layer-${Date.now()}-${Math.random()}`.replace(/\./g, '');
const layerWidth = '903px';
// Magic `content` for detect selector support
const checkContent = '_bAmBoO_';
function supportSelector(
styleStr: string,
handleElement: (ele: HTMLElement) => void,
supportCheck?: (ele: HTMLElement) => boolean,
): boolean {
function supportSelector(styleStr: string, handleElement?: (ele: HTMLElement) => void): boolean {
if (canUseDom()) {
updateCSS(styleStr, randomSelectorKey);
updateCSS(styleStr, layerKey);
const ele = document.createElement('div');
ele.style.position = 'fixed';
@ -61,12 +42,10 @@ function supportSelector(
ele.style.zIndex = '9999999';
}
const support = supportCheck
? supportCheck(ele)
: getComputedStyle(ele).content?.includes(checkContent);
const support = getComputedStyle(ele).width === layerWidth;
ele.parentNode?.removeChild(ele);
removeCSS(randomSelectorKey);
removeCSS(layerKey);
return support;
}
@ -78,41 +57,12 @@ let canLayer: boolean | undefined = undefined;
export function supportLayer(): boolean {
if (canLayer === undefined) {
canLayer = supportSelector(
`@layer ${randomSelectorKey} { .${randomSelectorKey} { content: "${checkContent}"!important; } }`,
`@layer ${layerKey} { .${layerKey} { width: ${layerWidth}!important; } }`,
ele => {
ele.className = randomSelectorKey;
ele.className = layerKey;
},
);
}
return canLayer!;
}
let canWhere: boolean | undefined = undefined;
export function supportWhere(): boolean {
if (canWhere === undefined) {
canWhere = supportSelector(
`:where(.${randomSelectorKey}) { content: "${checkContent}"!important; }`,
ele => {
ele.className = randomSelectorKey;
},
);
}
return canWhere!;
}
let canLogic: boolean | undefined = undefined;
export function supportLogicProps(): boolean {
if (canLogic === undefined) {
canLogic = supportSelector(
`.${randomSelectorKey} { inset-block: 93px !important; }`,
ele => {
ele.className = randomSelectorKey;
},
ele => getComputedStyle(ele).bottom === '93px',
);
}
return canLogic!;
}

View File

@ -1,13 +0,0 @@
import type { SizeType } from '../config-provider/SizeContext';
export function isPresetSize(size?: SizeType | string | number): size is SizeType {
return ['small', 'middle', 'large'].includes(size as string);
}
export function isValidGapNumber(size?: SizeType | string | number): size is number {
if (!size) {
// The case of size = 0 is deliberately excluded here, because the default value of the gap attribute in CSS is 0, so if the user passes 0 in, we can directly ignore it.
return false;
}
return typeof size === 'number' && !Number.isNaN(size);
}

View File

@ -12,7 +12,7 @@ export default function getScroll(
const method = top ? 'scrollTop' : 'scrollLeft';
let result = 0;
if (isWindow(target)) {
result = target[top ? 'scrollY' : 'scrollX'];
result = target[top ? 'pageYOffset' : 'pageXOffset'];
} else if (target instanceof Document) {
result = target.documentElement[method];
} else if (target instanceof HTMLElement) {

View File

@ -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 = /* #__PURE__ */ isClient ? window : undefined;
export const defaultDocument = /* #__PURE__ */ isClient ? window.document : undefined;
export const defaultNavigator = /* #__PURE__ */ isClient ? window.navigator : undefined;
export const defaultLocation = /* #__PURE__ */ isClient ? window.location : undefined;

View File

@ -21,6 +21,8 @@ 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);
/* #__PURE__ */ isClient &&
window?.navigator?.userAgent &&
/iP(ad|hone|od)/.test(window.navigator.userAgent);
export const hasOwn = <T extends object, K extends keyof T>(val: T, key: K): key is K =>
Object.prototype.hasOwnProperty.call(val, key);

View File

@ -2,7 +2,6 @@ import type { Ref } from 'vue';
import { computed, watchEffect } from 'vue';
import { updateCSS, removeCSS } from '../../vc-util/Dom/dynamicCSS';
import getScrollBarSize from '../../_util/getScrollBarSize';
import canUseDom from '../../_util/canUseDom';
const UNIQUE_ID = `vc-util-locker-${Date.now()}`;
@ -25,9 +24,6 @@ export default function useScrollLocker(lock?: Ref<boolean>) {
watchEffect(
onClear => {
if (!canUseDom()) {
return;
}
if (mergedLock.value) {
const scrollbarSize = getScrollBarSize();
const isOverflow = isBodyOverflowing();

View File

@ -0,0 +1,24 @@
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;

View File

@ -3,7 +3,7 @@
* https://github.com/akiran/json2mq.git
*/
const camel2hyphen = function (str: string) {
const camel2hyphen = function (str) {
return str
.replace(/[A-Z]/g, function (match) {
return '-' + match.toLowerCase();
@ -11,12 +11,12 @@ const camel2hyphen = function (str: string) {
.toLowerCase();
};
const isDimension = function (feature: string) {
const isDimension = function (feature) {
const re = /[height|width]$/;
return re.test(feature);
};
const obj2mq = function (obj: { [x: string]: any }) {
const obj2mq = function (obj) {
let mq = '';
const features = Object.keys(obj);
features.forEach(function (feature, index) {
@ -40,7 +40,7 @@ const obj2mq = function (obj: { [x: string]: any }) {
return mq;
};
export default function (query: any[]) {
export default function (query) {
let mq = '';
if (typeof query === 'string') {
return query;

View File

@ -1,19 +1,19 @@
import isPlainObject from 'lodash-es/isPlainObject';
import classNames from '../classNames';
import { isVNode, Fragment, Comment, Text } from 'vue';
import { isVNode, Fragment, Comment, Text, h } from 'vue';
import { camelize, hyphenate, isOn, resolvePropValue } from '../util';
import isValid from '../isValid';
import initDefaultProps from './initDefaultProps';
import type { VueInstance } from '../hooks/_vueuse/unrefElement';
// function getType(fn) {
// const match = fn && fn.toString().match(/^\s*function (\w+)/);
// return match ? match[1] : '';
// }
const splitAttrs = (attrs: any) => {
const splitAttrs = attrs => {
const allAttrs = Object.keys(attrs);
const eventAttrs: Record<string, any> = {};
const onEvents: Record<string, any> = {};
const extraAttrs: Record<string, any> = {};
const eventAttrs = {};
const onEvents = {};
const extraAttrs = {};
for (let i = 0, l = allAttrs.length; i < l; i++) {
const key = allAttrs[i];
if (isOn(key)) {
@ -25,7 +25,7 @@ const splitAttrs = (attrs: any) => {
}
return { onEvents, events: eventAttrs, extraAttrs };
};
const parseStyleText = (cssText = '', camel = false) => {
const parseStyleText = (cssText = '', camel) => {
const res = {};
const listDelimiter = /;(?![^(]*\))/g;
const propertyDelimiter = /:(.+)/;
@ -42,9 +42,34 @@ const parseStyleText = (cssText = '', camel = false) => {
return res;
};
const hasProp = (instance: any, prop: string) => {
const hasProp = (instance, prop) => {
return instance[prop] !== undefined;
};
// 重构后直接使用 hasProp 替换
const slotHasProp = (slot, prop) => {
return hasProp(slot, prop);
};
const getScopedSlots = ele => {
return (ele.data && ele.data.scopedSlots) || {};
};
const getSlots = ele => {
let componentOptions = ele.componentOptions || {};
if (ele.$vnode) {
componentOptions = ele.$vnode.componentOptions || {};
}
const children = ele.children || componentOptions.children || [];
const slots = {};
children.forEach(child => {
if (!isEmptyElement(child)) {
const name = (child.data && child.data.slot) || 'default';
slots[name] = slots[name] || [];
slots[name].push(child);
}
});
return { ...slots, ...getScopedSlots(ele) };
};
export const skipFlattenKey = Symbol('skipFlatten');
const flattenChildren = (children = [], filterEmpty = true) => {
@ -72,29 +97,39 @@ const flattenChildren = (children = [], filterEmpty = true) => {
return res;
};
const getSlot = (self: any, name = 'default', options = {}) => {
const getSlot = (self, name = 'default', options = {}) => {
if (isVNode(self)) {
if (self.type === Fragment) {
return name === 'default' ? flattenChildren(self.children as any[]) : [];
return name === 'default' ? flattenChildren(self.children) : [];
} else if (self.children && self.children[name]) {
return flattenChildren(self.children[name](options));
} else {
return [];
}
} else {
const res = self.$slots[name] && self.$slots[name](options);
let res = self.$slots[name] && self.$slots[name](options);
return flattenChildren(res);
}
};
const findDOMNode = (instance: any) => {
const getAllChildren = ele => {
let componentOptions = ele.componentOptions || {};
if (ele.$vnode) {
componentOptions = ele.$vnode.componentOptions || {};
}
return ele.children || componentOptions.children || [];
};
const getSlotOptions = () => {
throw Error('使用 .type 直接取值');
};
const findDOMNode = instance => {
let node = instance?.vnode?.el || (instance && (instance.$el || instance));
while (node && !node.tagName) {
node = node.nextSibling;
}
return node;
};
const getOptionProps = (instance: VueInstance) => {
const getOptionProps = instance => {
const res = {};
if (instance.$ && instance.$.vnode) {
const props = instance.$.vnode.props || {};
@ -111,7 +146,7 @@ const getOptionProps = (instance: VueInstance) => {
Object.keys(originProps).forEach(key => {
props[camelize(key)] = originProps[key];
});
const options = (instance.type as any).props || {};
const options = instance.type.props || {};
Object.keys(options).forEach(k => {
const v = resolvePropValue(options, props, k, props[k]);
if (v !== undefined || k in props) {
@ -121,7 +156,7 @@ const getOptionProps = (instance: VueInstance) => {
}
return res;
};
const getComponent = (instance: any, prop = 'default', options = instance, execute = true) => {
const getComponent = (instance, prop = 'default', options = instance, execute = true) => {
let com = undefined;
if (instance.$) {
const temp = instance[prop];
@ -149,13 +184,94 @@ const getComponent = (instance: any, prop = 'default', options = instance, execu
}
return com;
};
const getComponentFromProp = (instance, prop, options = instance, execute = true) => {
if (instance.$createElement) {
// const h = instance.$createElement;
const temp = instance[prop];
if (temp !== undefined) {
return typeof temp === 'function' && execute ? temp(h, options) : temp;
}
return (
(instance.$scopedSlots[prop] && execute && instance.$scopedSlots[prop](options)) ||
instance.$scopedSlots[prop] ||
instance.$slots[prop] ||
undefined
);
} else {
// const h = instance.context.$createElement;
const temp = getPropsData(instance)[prop];
if (temp !== undefined) {
return typeof temp === 'function' && execute ? temp(h, options) : temp;
}
const slotScope = getScopedSlots(instance)[prop];
if (slotScope !== undefined) {
return typeof slotScope === 'function' && execute ? slotScope(h, options) : slotScope;
}
const slotsProp = [];
const componentOptions = instance.componentOptions || {};
(componentOptions.children || []).forEach(child => {
if (child.data && child.data.slot === prop) {
if (child.data.attrs) {
delete child.data.attrs.slot;
}
if (child.tag === 'template') {
slotsProp.push(child.children);
} else {
slotsProp.push(child);
}
}
});
return slotsProp.length ? slotsProp : undefined;
}
};
const getKey = (ele: any) => {
const key = ele.key;
const getAllProps = ele => {
let props = getOptionProps(ele);
if (ele.$) {
props = { ...props, ...this.$attrs };
} else {
props = { ...ele.props, ...props };
}
return props;
};
const getPropsData = ins => {
const vnode = ins.$ ? ins.$ : ins;
const res = {};
const originProps = vnode.props || {};
const props = {};
Object.keys(originProps).forEach(key => {
props[camelize(key)] = originProps[key];
});
const options = isPlainObject(vnode.type) ? vnode.type.props : {};
options &&
Object.keys(options).forEach(k => {
const v = resolvePropValue(options, props, k, props[k]);
if (k in props) {
// 仅包含 props不包含默认值
res[k] = v;
}
});
return { ...props, ...res }; // 合并事件、未声明属性等
};
const getValueByProp = (ele, prop) => {
return getPropsData(ele)[prop];
};
const getAttrs = ele => {
let data = ele.data;
if (ele.$vnode) {
data = ele.$vnode.data;
}
return data ? data.attrs || {} : {};
};
const getKey = ele => {
let key = ele.key;
return key;
};
export function getEvents(ele: any = {}, on = true) {
export function getEvents(ele = {}, on = true) {
let props = {};
if (ele.$) {
props = { ...props, ...ele.$attrs };
@ -165,9 +281,27 @@ export function getEvents(ele: any = {}, on = true) {
return splitAttrs(props)[on ? 'onEvents' : 'events'];
}
export function getClass(ele: any) {
export function getEvent(child, event) {
return child.props && child.props[event];
}
// 获取 xxx.native 或者 原生标签 事件
export function getDataEvents(child) {
let events = {};
if (child.data && child.data.on) {
events = child.data.on;
}
return { ...events };
}
// use getListeners instead this.$listeners
// https://github.com/vueComponent/ant-design-vue/issues/1705
export function getListeners(context) {
return (context.$vnode ? context.$vnode.componentOptions.listeners : context.$listeners) || {};
}
export function getClass(ele) {
const props = (isVNode(ele) ? ele.props : ele.$attrs) || {};
const tempCls = props.class || {};
let tempCls = props.class || {};
let cls = {};
if (typeof tempCls === 'string') {
tempCls.split(' ').forEach(c => {
@ -184,7 +318,7 @@ export function getClass(ele: any) {
}
return cls;
}
export function getStyle(ele: any, camel?: boolean) {
export function getStyle(ele, camel) {
const props = (isVNode(ele) ? ele.props : ele.$attrs) || {};
let style = props.style || {};
if (typeof style === 'string') {
@ -198,19 +332,19 @@ export function getStyle(ele: any, camel?: boolean) {
return style;
}
export function getComponentName(opts: any) {
export function getComponentName(opts) {
return opts && (opts.Ctor.options.name || opts.tag);
}
export function isFragment(c: any) {
export function isFragment(c) {
return c.length === 1 && c[0].type === Fragment;
}
export function isEmptyContent(c: any) {
export function isEmptyContent(c) {
return c === undefined || c === null || c === '' || (Array.isArray(c) && c.length === 0);
}
export function isEmptyElement(c: any) {
export function isEmptyElement(c) {
return (
c &&
(c.type === Comment ||
@ -219,11 +353,11 @@ export function isEmptyElement(c: any) {
);
}
export function isEmptySlot(c: any) {
export function isEmptySlot(c) {
return !c || c().every(isEmptyElement);
}
export function isStringElement(c: any) {
export function isStringElement(c) {
return c && c.type === Text;
}
@ -241,7 +375,7 @@ export function filterEmpty(children = []) {
return res.filter(c => !isEmptyElement(c));
}
export function filterEmptyWithUndefined(children: any[]) {
export function filterEmptyWithUndefined(children) {
if (children) {
const coms = filterEmpty(children);
return coms.length ? coms : undefined;
@ -250,18 +384,34 @@ export function filterEmptyWithUndefined(children: any[]) {
}
}
function isValidElement(element: any) {
export function mergeProps() {
const args = [].slice.call(arguments, 0);
const props = {};
args.forEach((p = {}) => {
for (const [k, v] of Object.entries(p)) {
props[k] = props[k] || {};
if (isPlainObject(v)) {
Object.assign(props[k], v);
} else {
props[k] = v;
}
}
});
return props;
}
function isValidElement(element) {
if (Array.isArray(element) && element.length === 1) {
element = element[0];
}
return element && element.__v_isVNode && typeof element.type !== 'symbol'; // remove text node
}
function getPropsSlot(slots: any, props: any, prop = 'default') {
function getPropsSlot(slots, props, prop = 'default') {
return props[prop] ?? slots[prop]?.();
}
export const getTextFromElement = (ele: any) => {
export const getTextFromElement = ele => {
if (isValidElement(ele) && isStringElement(ele[0])) {
return ele[0].children;
}
@ -272,12 +422,21 @@ export {
hasProp,
getOptionProps,
getComponent,
getComponentFromProp,
getSlotOptions,
slotHasProp,
getPropsData,
getKey,
getAttrs,
getValueByProp,
parseStyleText,
initDefaultProps,
isValidElement,
camelize,
getSlots,
getSlot,
getAllProps,
getAllChildren,
findDOMNode,
flattenChildren,
getPropsSlot,

View File

@ -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.scrollX, nextScrollTop);
} else if (container instanceof Document) {
(container as Window).scrollTo(window.pageXOffset, nextScrollTop);
} else if (container instanceof Document || container.constructor.name === 'HTMLDocument') {
(container as Document).documentElement.scrollTop = nextScrollTop;
} else {
(container as HTMLElement).scrollTop = nextScrollTop;

File diff suppressed because one or more lines are too long

View File

@ -1,82 +0,0 @@
// import { StyleProvider } from '../../cssinjs';
import { extractStyle } from '../index';
import { ConfigProvider } from '../../../components';
import { theme } from '../../../index';
const testGreenColor = '#008000';
describe('Static-Style-Extract', () => {
it('should extract static styles', () => {
const cssText = extractStyle();
expect(cssText).not.toContain(testGreenColor);
expect(cssText).toMatchSnapshot();
});
it('should extract static styles with customTheme', () => {
const cssText = extractStyle(node => {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: testGreenColor,
},
}}
>
{node}
</ConfigProvider>
);
});
expect(cssText).toContain(testGreenColor);
expect(cssText).toMatchSnapshot();
});
it('should extract static styles with customTheme and customStyle', () => {
const cssText = extractStyle(node => {
return (
<ConfigProvider
theme={{
algorithm: theme.darkAlgorithm,
token: {
colorPrimary: testGreenColor,
},
}}
>
{node}
</ConfigProvider>
);
});
expect(cssText).toContain('#037003');
expect(cssText).toMatchSnapshot();
});
// it('with custom hashPriority', () => {
// const cssText = extractStyle(
// (node) => (
// <StyleProvider hashPriority='high'>
// <ConfigProvider
// theme={{
// token: {
// colorPrimary: testGreenColor,
// },
// }}
// >
// {node}
// </ConfigProvider>
// </StyleProvider>
// ),
// );
// expect(cssText).toContain(testGreenColor);
// expect(cssText).not.toContain(':where');
// expect(cssText).toMatchSnapshot();
//
// const cssText2 = extractStyle((node) => (
// <ConfigProvider
// theme={{
// token: {
// colorPrimary: testGreenColor,
// },
// }}
// >
// {node}
// </ConfigProvider>
// ));
// expect(cssText2).toContain(':where');
// });
});

View File

@ -1,90 +0,0 @@
import { createCache, extractStyle as extStyle, StyleProvider } from '../cssinjs';
import * as antd from '../../components';
import { renderToString } from 'vue/server-renderer';
import type { CustomRender } from './interface';
const blackList: string[] = [
'ConfigProvider',
'Grid',
'Tour',
'SelectOptGroup',
'SelectOption',
'MentionsOption',
'TreeNode',
'TreeSelectNode',
'LocaleProvider',
];
const pickerMap = {
MonthPicker: 'month',
WeekPicker: 'week',
QuarterPicker: 'quarter',
};
const compChildNameMap = {
MenuDivider: 'Menu',
MenuItem: 'Menu',
MenuItemGroup: 'Menu',
SubMenu: 'Menu',
TableColumn: 'Table',
TableColumnGroup: 'Table',
TableSummary: 'Table',
TableSummaryRow: 'Table',
TableSummaryCell: 'Table',
TabPane: 'Tabs',
TimelineItem: 'Timeline',
};
const defaultNode = () => (
<>
{Object.keys(antd)
.filter(name => !blackList.includes(name) && name[0] === name[0].toUpperCase())
.map(compName => {
const Comp = antd[compName];
if (compName === 'Dropdown') {
return (
<Comp key={compName} menu={{ items: [] }}>
<div />
</Comp>
);
}
if (compName === 'Anchor') {
return <Comp key={compName} items={[]} />;
}
if (compName in pickerMap) {
const Comp = antd['DatePicker'];
const type = pickerMap[compName];
return <Comp key={compName} picker={type} />;
}
if (compName in compChildNameMap) {
const ParentComp = antd[compChildNameMap[compName]];
return (
<ParentComp>
<Comp />
</ParentComp>
);
}
if (compName === 'QRCode' || compName === 'Segmented') {
return (
<Comp key={compName} value={''}>
<div />
</Comp>
);
}
return <Comp key={compName} />;
})}
</>
);
export function extractStyle(customTheme?: CustomRender): string {
const cache = createCache();
renderToString(
<StyleProvider cache={cache}>
{customTheme ? customTheme(defaultNode()) : defaultNode()}
</StyleProvider>,
);
// Grab style from cache
const styleText = extStyle(cache, true);
return styleText;
}

View File

@ -1,3 +0,0 @@
import type { VueNode } from '../type';
export type CustomRender = (node: VueNode) => VueNode;

View File

@ -1,7 +1,7 @@
// Test via a getter in the options object to see if the passive property is accessed
let supportsPassive = false;
try {
const opts = Object.defineProperty({}, 'passive', {
let opts = Object.defineProperty({}, 'passive', {
get() {
supportsPassive = true;
},

View File

@ -5,7 +5,7 @@ import type {
TransitionGroupProps,
TransitionProps,
} from 'vue';
import { nextTick } from 'vue';
import { nextTick, Transition, TransitionGroup } from 'vue';
import { tuple } from './type';
const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
@ -126,4 +126,6 @@ const getTransitionName = (rootPrefixCls: string, motion: string, transitionName
return `${rootPrefixCls}-${motion}`;
};
export { collapseMotion, getTransitionName, getTransitionDirection };
export { Transition, TransitionGroup, collapseMotion, getTransitionName, getTransitionDirection };
export default Transition;

View File

@ -0,0 +1,8 @@
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);
}
}

View File

@ -31,9 +31,7 @@ export interface PropOptions<T = any, D = T> {
}
declare type VNodeChildAtom = VNode | string | number | boolean | null | undefined | void;
// eslint-disable-next-line no-undef
export type VueNode = VNodeChildAtom | VNodeChildAtom[] | VNode;
export type VueNode = VNodeChildAtom | VNodeChildAtom[] | JSX.Element;
export const withInstall = <T>(comp: T) => {
const c = comp as any;
@ -92,5 +90,3 @@ export function someType<T>(types?: any[], defaultVal?: T) {
}
export type CustomSlotsType<T> = SlotsType<T>;
export type AnyObject = Record<PropertyKey, any>;

View File

@ -1,6 +1,6 @@
import { filterEmpty } from './props-util';
import type { Slots, VNode, VNodeArrayChildren, VNodeProps } from 'vue';
import { cloneVNode, isVNode, Comment, Fragment, render as VueRender } from 'vue';
import type { VNode, VNodeProps } from 'vue';
import { cloneVNode } from 'vue';
import warning from './warning';
import type { RefObject } from './createRef';
type NodeProps = Record<string, any> &
@ -40,10 +40,6 @@ export function deepCloneElement<T, U>(
if (Array.isArray(vnode)) {
return vnode.map(item => deepCloneElement(item, nodeProps, override, mergeRef));
} else {
// 需要判断是否为vnode方可进行clone操作
if (!isVNode(vnode)) {
return vnode;
}
const cloned = cloneElement(vnode, nodeProps, override, mergeRef);
if (Array.isArray(cloned.children)) {
cloned.children = deepCloneElement(cloned.children as VNode<T, U>[]);
@ -51,32 +47,3 @@ export function deepCloneElement<T, U>(
return cloned;
}
}
export function triggerVNodeUpdate(vm: VNode, attrs: Record<string, any>, 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<string, unknown>,
fallback?: () => VNodeArrayChildren,
) {
const slot = slots[name]?.(props);
if (ensureValidVNode(slot)) {
return slot;
}
return fallback?.();
}

View File

@ -159,12 +159,6 @@ function showWaveEffect(node: HTMLElement, className: string) {
node?.insertBefore(holder, node?.firstChild);
render(<WaveEffect target={node} className={className} />, holder);
return () => {
render(null, holder);
if (holder.parentElement) {
holder.parentElement.removeChild(holder);
}
};
}
export default showWaveEffect;

View File

@ -26,35 +26,36 @@ export default defineComponent({
},
setup(props, { slots }) {
const instance = getCurrentInstance();
const { prefixCls, wave } = useConfigInject('wave', props);
const { prefixCls } = useConfigInject('wave', props);
// ============================== Style ===============================
const [, hashId] = useStyle(prefixCls);
// =============================== Wave ===============================
const showWave = useWave(
instance,
computed(() => classNames(prefixCls.value, hashId.value)),
wave,
);
let onClick: (e: MouseEvent) => void;
const clear = () => {
const node = findDOMNode(instance) as HTMLElement;
const node = findDOMNode(instance);
node.removeEventListener('click', onClick, true);
};
onMounted(() => {
watch(
() => props.disabled,
() => {
clear();
nextTick(() => {
const node: HTMLElement = findDOMNode(instance);
node?.removeEventListener('click', onClick, true);
const node = findDOMNode(instance);
if (!node || node.nodeType !== 1 || props.disabled) {
return;
}
// Click handler
onClick = (e: MouseEvent) => {
const onClick = (e: MouseEvent) => {
// Fix radio button click twice
if (
(e.target as HTMLElement).tagName === 'INPUT' ||

View File

@ -1,25 +1,16 @@
import type { ComputedRef, Ref } from 'vue';
import { onBeforeUnmount, getCurrentInstance } from 'vue';
import type { ComponentInternalInstance, Ref } from 'vue';
import { findDOMNode } from '../props-util';
import showWaveEffect from './WaveEffect';
export default function useWave(
instance: ComponentInternalInstance | null,
className: Ref<string>,
wave?: ComputedRef<{ disabled?: boolean }>,
): VoidFunction {
const instance = getCurrentInstance();
let stopWave: () => void;
function showWave() {
const node = findDOMNode(instance);
stopWave?.();
if (wave?.value?.disabled || !node) {
return;
}
stopWave = showWaveEffect(node, className.value);
showWaveEffect(node, className.value);
}
onBeforeUnmount(() => {
stopWave?.();
});
return showWave;
}

View File

@ -176,6 +176,7 @@ const Affix = defineComponent({
affixStyle: undefined,
placeholderStyle: undefined,
});
currentInstance.update();
// Test if `updatePosition` called
if (process.env.NODE_ENV === 'test') {
emit('testUpdatePosition');
@ -255,7 +256,7 @@ const Affix = defineComponent({
const { prefixCls } = useConfigInject('affix', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
return () => {
const { affixStyle, placeholderStyle, status } = state;
const { affixStyle, placeholderStyle } = state;
const className = classNames({
[prefixCls.value]: affixStyle,
[hashId.value]: true,
@ -270,7 +271,7 @@ const Affix = defineComponent({
]);
return wrapSSR(
<ResizeObserver onResize={updatePosition}>
<div {...restProps} {...attrs} ref={placeholderNode} data-measure-status={status}>
<div {...restProps} {...attrs} ref={placeholderNode}>
{affixStyle && <div style={placeholderStyle} aria-hidden="true" />}
<div class={className} ref={fixedNode} style={affixStyle}>
{slots.default?.()}

View File

@ -1,5 +1,5 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import { computed, defineComponent, shallowRef, Transition } from 'vue';
import { computed, defineComponent, shallowRef } 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 } from '../_util/transition';
import { getTransitionProps, Transition } from '../_util/transition';
import { isValidElement } from '../_util/props-util';
import { tuple, withInstall } from '../_util/type';
import { cloneElement } from '../_util/vnode';

View File

@ -1,41 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/app/demo/basic.vue correctly 1`] = `
<div class="ant-app">
<!--teleport start-->
<!--teleport end-->
<!--teleport start-->
<!--teleport end-->
<div class="ant-space ant-space-horizontal ant-space-align-center">
<div class="ant-space-item" style="margin-right: 8px;"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open message</span>
</button></div>
<!---->
<div class="ant-space-item" style="margin-right: 8px;"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open modal</span>
</button></div>
<!---->
<div class="ant-space-item"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open notification</span>
</button></div>
<!---->
</div>
</div>
`;
exports[`renders ./components/app/demo/myPage.vue correctly 1`] = `
<div class="ant-space ant-space-horizontal ant-space-align-center">
<div class="ant-space-item" style="margin-right: 8px;"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open message</span>
</button></div>
<!---->
<div class="ant-space-item" style="margin-right: 8px;"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open modal</span>
</button></div>
<!---->
<div class="ant-space-item"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open notification</span>
</button></div>
<!---->
</div>
`;

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('app');

View File

@ -1,44 +0,0 @@
import { provide, inject, reactive } from 'vue';
import type { InjectionKey } from 'vue';
import type { MessageInstance, ConfigOptions as MessageConfig } from '../message/interface';
import type { NotificationInstance, NotificationConfig } from '../notification/interface';
import type { ModalStaticFunctions } from '../modal/confirm';
export type AppConfig = {
message?: MessageConfig;
notification?: NotificationConfig;
};
export const AppConfigContextKey: InjectionKey<AppConfig> = Symbol('appConfigContext');
export const useProvideAppConfigContext = (appConfigContext: AppConfig) => {
return provide(AppConfigContextKey, appConfigContext);
};
export const useInjectAppConfigContext = () => {
return inject(AppConfigContextKey, {});
};
type ModalType = Omit<ModalStaticFunctions, 'warn'>;
export interface useAppProps {
message: MessageInstance;
notification: NotificationInstance;
modal: ModalType;
}
export const AppContextKey: InjectionKey<useAppProps> = Symbol('appContext');
export const useProvideAppContext = (appContext: useAppProps) => {
return provide(AppContextKey, appContext);
};
const defaultAppContext: useAppProps = reactive({
message: {},
notification: {},
modal: {},
} as useAppProps);
export const useInjectAppContext = () => {
return inject(AppContextKey, defaultAppContext);
};

View File

@ -1,26 +0,0 @@
<docs>
---
order: 0
title:
zh-CN: 基本使用
en-US: Basic Usage
---
## zh-CN
获取 `message``notification``modal` 静态方法
## en-US
Static method for `message`, `notification`, `modal`.
</docs>
<template>
<a-app>
<my-page />
</a-app>
</template>
<script lang="ts" setup>
import myPage from './myPage.vue';
</script>

View File

@ -1,19 +0,0 @@
<template>
<demo-sort :cols="1">
<basic />
</demo-sort>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Basic from './basic.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
export default defineComponent({
CN,
US,
components: {
Basic,
},
});
</script>

View File

@ -1,32 +0,0 @@
<template>
<a-space>
<a-button type="primary" @click="showMessage">Open message</a-button>
<a-button type="primary" @click="showModal">Open modal</a-button>
<a-button type="primary" @click="showNotification">Open notification</a-button>
</a-space>
</template>
<script setup lang="ts">
import { App } from 'ant-design-vue';
const { message, modal, notification } = App.useApp();
const showMessage = () => {
message.success('Success!');
};
const showModal = () => {
modal.warning({
title: 'This is a warning message',
content: 'some messages...some messages...',
});
};
const showNotification = () => {
notification.info({
message: `Notification topLeft`,
description: 'Hello, Ant Design Vue!!',
placement: 'topLeft',
});
};
</script>

View File

@ -1,130 +0,0 @@
---
category: Components
cols: 1
type: Other
title: App
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*JGb3RIzyOCkAAAAAAAAAAAAADrJ8AQ/original
tag: New
---
Application wrapper for some global usages.
## When To Use
- Provide reset styles based on `.ant-app` element.
- You could use static methods of `message/notification/Modal` form `useApp` without writing `contextHolder` manually.
## API
### App
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| message | Global config for Message | [MessageConfig](/components/message/#messageconfig) | - | 4.x |
| notification | Global config for Notification | [NotificationConfig](/components/notification/#notificationconfig) | - | 4.x |
## How to use
### Basic usage
App provides upstream and downstream method calls through `provide/inject`, because useApp needs to be used as a subcomponent, we recommend encapsulating App at the top level in the application.
```html
/*myPage.vue*/
<template>
<a-space>
<a-button type="primary" @click="showMessage">Open message</a-button>
<a-button type="primary" @click="showModal">Open modal</a-button>
<a-button type="primary" @click="showNotification">Open notification</a-button>
</a-space>
</template>
<script setup lang="ts">
import { App } from 'ant-design-vue';
const { message, modal, notification } = App.useApp();
const showMessage = () => {
message.success('Success!');
};
const showModal = () => {
modal.warning({
title: 'This is a warning message',
content: 'some messages...some messages...',
});
};
const showNotification = () => {
notification.info({
message: `Notification topLeft`,
description: 'Hello, Ant Design Vue!!',
placement: 'topLeft',
});
};
</script>
```
Note: App.useApp must be available under App.
#### Embedded usage scenarios (if not necessary, try not to do nesting)
```html
<a-app>
<a-space>
...
<a-app>...</a-app>
</a-space>
</a-app>
```
#### Sequence with ConfigProvider
The App component can only use the token in the `ConfigProvider`, if you need to use the Token, the ConfigProvider and the App component must appear in pairs.
```html
<a-config-provider theme="{{ ... }}">
<a-app>...</a-app>
</a-config-provider>
```
#### Global scene (pinia scene)
```ts
import { App } from 'ant-design-vue';
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 useGlobalStore = defineStore('global', () => {
const message: MessageInstance = ref();
const notification: NotificationInstance = ref();
const modal: Omit<ModalStaticFunctions, 'warn'> = ref();
(() => {
const staticFunction = App.useApp();
message.value = staticFunction.message;
modal.value = staticFunction.modal;
notification.value = staticFunction.notification;
})();
return { message, notification, modal };
});
```
```html
// sub page
<template>
<a-space>
<a-button type="primary" @click="showMessage">Open message</a-button>
</a-space>
</template>
<script setup>
import { useGlobalStore } from '@/stores/global';
const global = useGlobalStore();
const showMessage = () => {
global.message.success('Success!');
};
</script>
```

View File

@ -1,83 +0,0 @@
import { defineComponent, computed } from 'vue';
import type { App as TypeApp, Plugin } from 'vue';
import { initDefaultProps } from '../_util/props-util';
import classNames from '../_util/classNames';
import { objectType } from '../_util/type';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import useMessage from '../message/useMessage';
import useModal from '../modal/useModal';
import useNotification from '../notification/useNotification';
import type { AppConfig } from './context';
import {
useProvideAppConfigContext,
useInjectAppConfigContext,
useProvideAppContext,
useInjectAppContext,
} from './context';
import useStyle from './style';
export const AppProps = () => {
return {
rootClassName: String,
message: objectType<AppConfig['message']>(),
notification: objectType<AppConfig['notification']>(),
};
};
const useApp = () => {
return useInjectAppContext();
};
const App = defineComponent({
name: 'AApp',
props: initDefaultProps(AppProps(), {}),
setup(props, { slots }) {
const { prefixCls } = useConfigInject('app', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const customClassName = computed(() => {
return classNames(hashId.value, prefixCls.value, props.rootClassName);
});
const appConfig = useInjectAppConfigContext();
const mergedAppConfig = computed(() => ({
message: { ...appConfig.message, ...props.message },
notification: { ...appConfig.notification, ...props.notification },
}));
useProvideAppConfigContext(mergedAppConfig.value);
const [messageApi, messageContextHolder] = useMessage(mergedAppConfig.value.message);
const [notificationApi, notificationContextHolder] = useNotification(
mergedAppConfig.value.notification,
);
const [ModalApi, ModalContextHolder] = useModal();
const memoizedContextValue = computed(() => ({
message: messageApi,
notification: notificationApi,
modal: ModalApi,
}));
useProvideAppContext(memoizedContextValue.value);
return () => {
return wrapSSR(
<div class={customClassName.value}>
{ModalContextHolder()}
{messageContextHolder()}
{notificationContextHolder()}
{slots.default?.()}
</div>,
);
};
},
});
App.useApp = useApp;
App.install = function (app: TypeApp) {
app.component(App.name, App);
};
export default App as typeof App &
Plugin & {
readonly useApp: typeof useApp;
};

View File

@ -1,131 +0,0 @@
---
category: Components
subtitle: 包裹组件
cols: 1
type: 其它
title: App
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*JGb3RIzyOCkAAAAAAAAAAAAADrJ8AQ/original
tag: New
---
新的包裹组件,提供重置样式和提供消费上下文的默认环境。
## 何时使用
- 提供可消费 provide/inject 的 `message.xxx`、`Modal.xxx`、`notification.xxx` 的静态方法,可以简化 useMessage 等方法需要手动植入 `contextHolder` 的问题。
- 提供基于 `.ant-app` 的默认重置样式,解决原生元素没有 antd 规范样式的问题。
## API
### App
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| message | App 内 Message 的全局配置 | [MessageConfig](/components/message-cn/#messageconfig) | - | 4.x |
| notification | App 内 Notification 的全局配置 | [NotificationConfig](/components/notification-cn/#notificationconfig) | - | 4.x |
## 如何使用
### 基础用法
App 组件通过 `provide/inject` 提供上下文方法调用,因而 useApp 需要作为子组件才能使用,我们推荐在应用中顶层包裹 App。
```html
/*myPage.vue*/
<template>
<a-space>
<a-button type="primary" @click="showMessage">Open message</a-button>
<a-button type="primary" @click="showModal">Open modal</a-button>
<a-button type="primary" @click="showNotification">Open notification</a-button>
</a-space>
</template>
<script setup lang="ts">
import { App } from 'ant-design-vue';
const { message, modal, notification } = App.useApp();
const showMessage = () => {
message.success('Success!');
};
const showModal = () => {
modal.warning({
title: 'This is a warning message',
content: 'some messages...some messages...',
});
};
const showNotification = () => {
notification.info({
message: `Notification topLeft`,
description: 'Hello, Ant Design Vue!!',
placement: 'topLeft',
});
};
</script>
```
注意App.useApp 必须在 App 之下方可使用。
#### 内嵌使用场景(如无必要,尽量不做嵌套)
```html
<a-app>
<a-space>
...
<a-app>...</a-app>
</a-space>
</a-app>
```
#### 与 ConfigProvider 先后顺序
App 组件只能在 `ConfigProvider` 之下才能使用 Design Token 如果需要使用其样式重置能力,则 ConfigProvider 与 App 组件必须成对出现。
```html
<a-config-provider theme="{{ ... }}">
<a-app>...</a-app>
</a-config-provider>
```
#### 全局场景 (pinia 场景)
```ts
import { App } from 'ant-design-vue';
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 useGlobalStore = defineStore('global', () => {
const message: MessageInstance = ref();
const notification: NotificationInstance = ref();
const modal: Omit<ModalStaticFunctions, 'warn'> = ref();
(() => {
const staticFunction = App.useApp();
message.value = staticFunction.message;
modal.value = staticFunction.modal;
notification.value = staticFunction.notification;
})();
return { message, notification, modal };
});
```
```html
// sub page
<template>
<a-space>
<a-button type="primary" @click="showMessage">Open message</a-button>
</a-space>
</template>
<script setup>
import { useGlobalStore } from '@/stores/global';
const global = useGlobalStore();
const showMessage = () => {
global.message.success('Success!');
};
</script>
```

View File

@ -1,22 +0,0 @@
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook } from '../../theme/internal';
export type ComponentToken = {};
interface AppToken extends FullToken<'App'> {}
// =============================== Base ===============================
const genBaseStyle: GenerateStyle<AppToken> = token => {
const { componentCls, colorText, fontSize, lineHeight, fontFamily } = token;
return {
[componentCls]: {
color: colorText,
fontSize,
lineHeight,
fontFamily,
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook('App', token => [genBaseStyle(token)]);

View File

@ -1,28 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/auto-complete/demo/allow-clear.vue correctly 1`] = `
<div style="width: 200px;" class="ant-select ant-select-show-search ant-select-auto-complete ant-select-single ant-select-allow-clear ant-select-show-search">
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><input type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" class="ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<!----><span class="ant-select-selection-placeholder">Clearable</span>
</div>
<!---->
<!---->
<!---->
</div>
<br>
<br>
<div style="width: 200px;" class="ant-select ant-select-show-search ant-select-auto-complete ant-select-single ant-select-allow-clear ant-select-show-search">
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><input type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" class="ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<!----><span class="ant-select-selection-placeholder">Customized clear icon</span>
</div>
<!---->
<!---->
<!---->
</div>
`;
exports[`renders ./components/auto-complete/demo/basic.vue correctly 1`] = `
<div style="width: 200px;" class="ant-select ant-select-show-search ant-select-auto-complete ant-select-single ant-select-show-search">
<!---->
@ -35,18 +12,6 @@ exports[`renders ./components/auto-complete/demo/basic.vue correctly 1`] = `
</div>
`;
exports[`renders ./components/auto-complete/demo/border-less.vue correctly 1`] = `
<div style="width: 200px;" class="ant-select ant-select-borderless ant-select-show-search ant-select-auto-complete ant-select-single ant-select-show-search">
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><input type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" class="ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<!----><span class="ant-select-selection-placeholder">border less</span>
</div>
<!---->
<!---->
<!---->
</div>
`;
exports[`renders ./components/auto-complete/demo/certain-category.vue correctly 1`] = `
<div class="certain-category-search-wrapper" style="width: 250px;">
<div popupclassname="certain-category-search-dropdown" class="ant-select certain-category-search ant-select-show-search ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search" style="width: 250px;">

View File

@ -1,69 +0,0 @@
<docs>
---
order: 8
title:
zh-CN: 自定义清除按钮
en-US: Customize clear button
---
## zh-CN
自定义清除按钮
## en-US
Customize clear button.
</docs>
<template>
<a-auto-complete
v-model:value="value"
:options="options"
style="width: 200px"
placeholder="Clearable"
:allow-clear="true"
@select="onSelect"
@search="onSearch"
/>
<br />
<br />
<a-auto-complete
v-model:value="value"
:options="options"
style="width: 200px"
placeholder="Customized clear icon"
:allow-clear="true"
@select="onSelect"
@search="onSearch"
>
<template #clearIcon>
<close-outlined />
</template>
</a-auto-complete>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { CloseOutlined } from '@ant-design/icons-vue';
interface MockVal {
value: string;
}
const mockVal = (str: string, repeat = 1): MockVal => {
return {
value: str.repeat(repeat),
};
};
const value = ref('');
const options = ref<MockVal[]>([]);
const onSearch = (searchText: string) => {
console.log('searchText');
options.value = !searchText
? []
: [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)];
};
const onSelect = (value: string) => {
console.log('onSelect', value);
};
</script>

View File

@ -1,53 +0,0 @@
<docs>
---
order: 7
title:
zh-CN: 无边框
en-US: Border less
---
## zh-CN
没有边框
## en-US
border less.
</docs>
<template>
<a-auto-complete
v-model:value="value"
:options="options"
style="width: 200px"
placeholder="border less"
:bordered="false"
@select="onSelect"
@search="onSearch"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
interface MockVal {
value: string;
}
const mockVal = (str: string, repeat = 1): MockVal => {
return {
value: str.repeat(repeat),
};
};
const value = ref('');
const options = ref<MockVal[]>([]);
const onSearch = (searchText: string) => {
console.log('searchText');
options.value = !searchText
? []
: [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)];
};
const onSelect = (value: string) => {
console.log('onSelect', value);
};
</script>

View File

@ -7,8 +7,6 @@
<certain-category />
<uncertain-category />
<statusVue />
<border-less />
<allow-clear />
</demo-sort>
</template>
@ -20,8 +18,6 @@ import NonCaseSensitive from './non-case-sensitive.vue';
import CertainCategory from './certain-category.vue';
import UncertainCategory from './uncertain-category.vue';
import statusVue from './status.vue';
import BorderLess from './border-less.vue';
import AllowClear from './allow-clear.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
@ -38,8 +34,6 @@ export default defineComponent({
NonCaseSensitive,
CertainCategory,
UncertainCategory,
BorderLess,
AllowClear,
},
setup() {
return {};

View File

@ -30,8 +30,6 @@ The differences with Select are:
| allowClear | Show clear button, effective in multiple mode only. | boolean | false | |
| autofocus | get focus when component mounted | boolean | false | |
| backfill | backfill selected item the input when using keyboard | boolean | false | |
| bordered | Whether has border style | boolean | true | 4.0 |
| clearIcon | Use slot custom clear icon | slot | `<CloseCircleFilled />` | 4.0 |
| default (for customize input element) | customize input element | slot | `<Input />` | |
| defaultActiveFirstOption | Whether active first option by default | boolean | true | |
| defaultOpen | Initial open state of dropdown | boolean | - | |

View File

@ -50,13 +50,10 @@ 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;
dataSource: any;
clearIcon: any;
}>,
setup(props, { slots, attrs, expose }) {
warning(

View File

@ -31,8 +31,6 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*WERTQ6qvgEYAAA
| allowClear | 支持清除, 单选模式有效 | boolean | false | |
| autofocus | 自动获取焦点 | boolean | false | |
| backfill | 使用键盘选择选项的时候把选中项回填到输入框中 | boolean | false | |
| bordered | 是否有边框 | boolean | true | 4.0 |
| clearIcon | 使用插槽自定义清除按钮 | slot | `<CloseCircleFilled />` | 4.0 |
| default (自定义输入框) | 自定义输入框 | slot | `<Input />` | |
| defaultActiveFirstOption | 是否默认高亮第一个选项。 | boolean | true | |
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |

View File

@ -11,7 +11,7 @@ import useConfigInject from '../config-provider/hooks/useConfigInject';
import ResizeObserver from '../vc-resize-observer';
import eagerComputed from '../_util/eagerComputed';
import useStyle from './style';
import { useAvatarInjectContext } from './AvatarContext';
import { useInjectSize } from './SizeContext';
export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap;
@ -56,9 +56,9 @@ const Avatar = defineComponent({
const { prefixCls } = useConfigInject('avatar', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const avatarCtx = useAvatarInjectContext();
const groupSize = useInjectSize();
const size = computed(() => {
return props.size === 'default' ? avatarCtx.size : props.size;
return props.size === 'default' ? groupSize.value : props.size;
});
const screens = useBreakpoint();
const responsiveSize = eagerComputed(() => {
@ -135,7 +135,6 @@ const Avatar = defineComponent({
return () => {
const { shape, src, alt, srcset, draggable, crossOrigin } = props;
const mergeShape = avatarCtx.shape ?? shape;
const icon = getPropsSlot(slots, props, 'icon');
const pre = prefixCls.value;
const classString = {
@ -143,7 +142,7 @@ const Avatar = defineComponent({
[pre]: true,
[`${pre}-lg`]: size.value === 'large',
[`${pre}-sm`]: size.value === 'small',
[`${pre}-${mergeShape}`]: true,
[`${pre}-${shape}`]: shape,
[`${pre}-image`]: src && isImgExist.value,
[`${pre}-icon`]: icon,
[hashId.value]: true,

View File

@ -1,16 +0,0 @@
import type { InjectionKey } from 'vue';
import { inject, provide } from 'vue';
import type { ScreenSizeMap } from '../_util/responsiveObserve';
export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap;
export interface AvatarContextType {
size?: AvatarSize;
shape?: 'circle' | 'square';
}
const AvatarContextKey: InjectionKey<AvatarContextType> = Symbol('AvatarContextKey');
export const useAvatarInjectContext = () => {
return inject(AvatarContextKey, {});
};
export const useAvatarProviderContext = (context: AvatarContextType) => {
return provide(AvatarContextKey, context);
};

View File

@ -3,11 +3,11 @@ import type { AvatarSize } from './Avatar';
import Avatar from './Avatar';
import Popover from '../popover';
import type { PropType, ExtractPropTypes, CSSProperties } from 'vue';
import { computed, defineComponent, watchEffect } from 'vue';
import { computed, defineComponent } from 'vue';
import { flattenChildren, getPropsSlot } from '../_util/props-util';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import useStyle from './style';
import { useAvatarProviderContext } from './AvatarContext';
import { useProviderSize } from './SizeContext';
export const groupProps = () => ({
prefixCls: String,
@ -23,7 +23,6 @@ export const groupProps = () => ({
type: [Number, String, Object] as PropType<AvatarSize>,
default: 'default' as AvatarSize,
},
shape: { type: String as PropType<'circle' | 'square'>, default: 'circle' },
});
export type AvatarGroupProps = Partial<ExtractPropTypes<ReturnType<typeof groupProps>>>;
@ -37,17 +36,13 @@ const Group = defineComponent({
const { prefixCls, direction } = useConfigInject('avatar', props);
const groupPrefixCls = computed(() => `${prefixCls.value}-group`);
const [wrapSSR, hashId] = useStyle(prefixCls);
watchEffect(() => {
const context = { size: props.size, shape: props.shape };
useAvatarProviderContext(context);
});
useProviderSize(computed(() => props.size));
return () => {
const {
maxPopoverPlacement = 'top',
maxCount,
maxStyle,
maxPopoverTrigger = 'hover',
shape,
} = props;
const cls = {
@ -77,7 +72,7 @@ const Group = defineComponent({
placement={maxPopoverPlacement}
overlayClassName={`${groupPrefixCls.value}-popover`}
>
<Avatar style={maxStyle} shape={shape}>{`+${numOfChildren - maxCount}`}</Avatar>
<Avatar style={maxStyle}>{`+${numOfChildren - maxCount}`}</Avatar>
</Popover>,
);
return wrapSSR(

View File

@ -0,0 +1,17 @@
import type { InjectionKey, Ref } from 'vue';
import { computed, inject, ref, provide } from 'vue';
import type { ScreenSizeMap } from '../_util/responsiveObserve';
export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap;
const SizeContextKey: InjectionKey<Ref<AvatarSize>> = Symbol('SizeContextKey');
export const useInjectSize = () => {
return inject(SizeContextKey, ref('default' as AvatarSize));
};
export const useProviderSize = (size: Ref<AvatarSize>) => {
const parentSize = useInjectSize();
provide(
SizeContextKey,
computed(() => size.value || parentSize.value),
);
return size;
};

View File

@ -16,20 +16,29 @@ Usually used for reminders and notifications.
</docs>
<template>
<a-space :size="24">
<span style="margin-right: 24px">
<a-badge :count="1">
<a-avatar shape="square">
<template #icon><UserOutlined /></template>
</a-avatar>
</a-badge>
</span>
<span>
<a-badge dot>
<a-avatar shape="square">
<template #icon><UserOutlined /></template>
</a-avatar>
</a-badge>
</a-space>
</span>
</template>
<script lang="ts" setup>
import { UserOutlined } from '@ant-design/icons-vue';
</script>
<style scoped>
#components-avatar-demo-badge .ant-avatar {
margin-top: 0;
margin-right: 0;
}
</style>

View File

@ -16,36 +16,31 @@ Three sizes and two shapes are available.
</docs>
<template>
<a-space direction="vertical" :size="32">
<a-space wrap :size="16">
<a-avatar :size="64">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar size="large">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar>
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar size="small">
<template #icon><UserOutlined /></template>
</a-avatar>
</a-space>
<a-space wrap :size="16">
<a-avatar shape="square" :size="64">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square" size="large">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square" size="small">
<template #icon><UserOutlined /></template>
</a-avatar>
</a-space>
</a-space>
<a-avatar :size="64">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar size="large">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar>
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar size="small">
<template #icon><UserOutlined /></template>
</a-avatar>
<br />
<a-avatar shape="square" :size="64">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square" size="large">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square" size="small">
<template #icon><UserOutlined /></template>
</a-avatar>
</template>
<script lang="ts" setup>

View File

@ -8,22 +8,27 @@ title:
## zh-CN
对于字符型的头像当字符串较长时字体大小可以根据头像宽度自动调整也可使用 `gap`` 来设置字符距离左右两侧边界单位像素。
对于字符型的头像当字符串较长时字体大小可以根据头像宽度自动调整
## en-US
For letter type Avatar, when the letters are too long to display, the font size can be automatically adjusted according to the width of the Avatar. You can also use `gap` to set the unit distance between left and right sides.
For letter type Avatar, when the letters are too long to display, the font size can be automatically adjusted according to the width of the Avatar.
</docs>
<template>
<a-avatar size="large" :style="{ backgroundColor: color, verticalAlign: 'middle' }" :gap="gap">
<a-avatar
shape="square"
size="large"
:style="{ backgroundColor: color, verticalAlign: 'middle' }"
>
{{ avatarValue }}
</a-avatar>
<a-button size="small" :style="{ margin: '0 16px', verticalAlign: 'middle' }" @click="changeUser">
ChangeUser
</a-button>
<a-button size="small" :style="{ verticalAlign: 'middle' }" @click="changeGap">
ChangeGap
<a-button
size="small"
:style="{ marginLeft: '16px', verticalAlign: 'middle' }"
@click="changeValue"
>
改变
</a-button>
</template>
@ -34,16 +39,9 @@ const UserList = ['U', 'Lucy', 'Tom', 'Edward'];
const colorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];
const avatarValue = ref(UserList[0]);
const color = ref(colorList[0]);
const changeUser = () => {
const changeValue = () => {
const index = UserList.indexOf(avatarValue.value);
avatarValue.value = index < UserList.length - 1 ? UserList[index + 1] : UserList[0];
color.value = index < colorList.length - 1 ? colorList[index + 1] : colorList[0];
};
const GapList = [4, 3, 2, 1];
const gap = ref(GapList[0]);
const changeGap = () => {
const index = GapList.indexOf(gap.value);
gap.value = index < GapList.length - 1 ? GapList[index + 1] : GapList[0];
};
</script>

View File

@ -17,22 +17,20 @@ Avatar group display.
<template>
<a-avatar-group>
<a-avatar src="https://xsgames.co/randomusers/avatar.php?g=pixel&key=1" />
<a href="https://www.antdv.com">
<a-avatar style="background-color: #f56a00">K</a-avatar>
</a>
<a-avatar src="https://joeschmoe.io/api/v1/random" />
<a-avatar style="background-color: #f56a00">K</a-avatar>
<a-tooltip title="Ant User" placement="top">
<a-avatar style="background-color: #87d068">
<template #icon><UserOutlined /></template>
</a-avatar>
</a-tooltip>
<a-avatar style="background-color: #1890ff">
<template #icon><AntDesignOutlined /></template>
<template #icon><UserOutlined /></template>
</a-avatar>
</a-avatar-group>
<a-divider />
<a-avatar-group :max-count="2" :max-style="{ color: '#f56a00', backgroundColor: '#fde3cf' }">
<a-avatar src="https://xsgames.co/randomusers/avatar.php?g=pixel&key=2" />
<a-avatar src="https://joeschmoe.io/api/v1/random" />
<a-avatar style="background-color: #1890ff">K</a-avatar>
<a-tooltip title="Ant User" placement="top">
<a-avatar style="background-color: #87d068">
@ -40,7 +38,7 @@ Avatar group display.
</a-avatar>
</a-tooltip>
<a-avatar style="background-color: #1890ff">
<template #icon><AntDesignOutlined /></template>
<template #icon><UserOutlined /></template>
</a-avatar>
</a-avatar-group>
<a-divider />
@ -52,7 +50,7 @@ Avatar group display.
backgroundColor: '#fde3cf',
}"
>
<a-avatar src="https://xsgames.co/randomusers/avatar.php?g=pixel&key=3" />
<a-avatar src="https://joeschmoe.io/api/v1/random" />
<a-avatar style="background-color: #1890ff">K</a-avatar>
<a-tooltip title="Ant User" placement="top">
<a-avatar style="background-color: #87d068">
@ -60,42 +58,11 @@ Avatar group display.
</a-avatar>
</a-tooltip>
<a-avatar style="background-color: #1890ff">
<template #icon><AntDesignOutlined /></template>
</a-avatar>
</a-avatar-group>
<a-divider />
<a-avatar-group
:max-count="2"
max-popover-trigger="click"
size="large"
:max-style="{ color: '#f56a00', backgroundColor: '#fde3cf', cursor: 'pointer' }"
>
<a-avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
<a-avatar style="background-color: #f56a00">K</a-avatar>
<a-tooltip title="Ant User" placement="top">
<a-avatar style="background-color: #87d068">
<template #icon><UserOutlined /></template>
</a-avatar>
</a-tooltip>
<a-avatar style="background-color: #1890ff">
<template #icon><AntDesignOutlined /></template>
</a-avatar>
</a-avatar-group>
<a-divider />
<a-avatar-group shape="square">
<a-avatar style="background-color: #fde3cf">A</a-avatar>
<a-avatar style="background-color: #f56a00">K</a-avatar>
<a-tooltip title="Ant User" placement="top">
<a-avatar style="background-color: #87d068">
<template #icon><UserOutlined /></template>
</a-avatar>
</a-tooltip>
<a-avatar style="background-color: #1890ff">
<template #icon><AntDesignOutlined /></template>
<template #icon><UserOutlined /></template>
</a-avatar>
</a-avatar-group>
</template>
<script lang="ts" setup>
import { UserOutlined, AntDesignOutlined } from '@ant-design/icons-vue';
import { UserOutlined } from '@ant-design/icons-vue';
</script>

View File

@ -36,3 +36,10 @@ export default defineComponent({
},
});
</script>
<style>
[id^='components-avatar-demo-'] .ant-avatar {
margin-top: 16px;
margin-right: 16px;
}
</style>

View File

@ -16,22 +16,20 @@ Image, Icon and letter are supported, and the latter two kinds avatar can have c
</docs>
<template>
<a-space :size="16" wrap>
<a-avatar>
<template #icon>
<UserOutlined />
</template>
</a-avatar>
<a-avatar>U</a-avatar>
<a-avatar :size="40">USER</a-avatar>
<a-avatar src="https://www.antdv.com/assets/logo.1ef800a8.svg" />
<a-avatar style="color: #f56a00; background-color: #fde3cf">U</a-avatar>
<a-avatar style="background-color: #87d068">
<template #icon>
<UserOutlined />
</template>
</a-avatar>
</a-space>
<a-avatar>
<template #icon>
<UserOutlined />
</template>
</a-avatar>
<a-avatar>U</a-avatar>
<a-avatar :size="40">USER</a-avatar>
<a-avatar src="https://joeschmoe.io/api/v1/random" />
<a-avatar style="color: #f56a00; background-color: #fde3cf">U</a-avatar>
<a-avatar style="background-color: #87d068">
<template #icon>
<UserOutlined />
</template>
</a-avatar>
</template>
<script lang="ts" setup>

View File

@ -34,4 +34,3 @@ Avatars can be used to represent people or objects. It supports images, `Icon`s,
| maxPopoverTrigger | Set the trigger of excess avatar Popover | `hover` \| `focus` \| `click` | `hover` | 3.0 |
| maxStyle | The style of excess avatar style | CSSProperties | - | |
| size | The size of the avatar | number \| `large` \| `small` \| `default` \| { xs: number, sm: number, ...} | `default` | |
| shape | The shape of the avatar | `circle` \| `square` | `circle` | 4.0 |

View File

@ -39,4 +39,3 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YbgyQaRGz-UAAA
| maxPopoverTrigger | 设置多余头像 Popover 的触发方式 | `hover` \| `focus` \| `click` | `hover` | 3.0 |
| maxStyle | 多余头像样式 | CSSProperties | - | |
| size | 设置头像的大小 | number \| `large` \| `small` \| `default` \| { xs: number, sm: number, ...} | `default` | |
| shape | 设置头像的形状 | `circle` \| `square` | `circle` | 4.0 |

View File

@ -3,57 +3,20 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../style';
export interface ComponentToken {
/**
* @desc
* @descEN Background color of Avatar
*/
containerSize: number;
/**
* @desc
* @descEN Size of large Avatar
*/
containerSizeLG: number;
/**
* @desc
* @descEN Size of small Avatar
*/
containerSizeSM: number;
/**
* @desc
* @descEN Font size of Avatar
*/
textFontSize: number;
/**
* @desc
* @descEN Font size of large Avatar
*/
textFontSizeLG: number;
/**
* @desc
* @descEN Font size of small Avatar
*/
textFontSizeSM: number;
/**
* @desc
* @descEN Spacing between avatars in a group
*/
groupSpace: number;
/**
* @desc
* @descEN Overlapping of avatars in a group
*/
groupOverlapping: number;
/**
* @desc
* @descEN Border color of avatars in a group
*/
groupBorderColor: string;
}
export interface ComponentToken {}
type AvatarToken = FullToken<'Avatar'> & {
avatarBg: string;
avatarColor: string;
avatarSizeBase: number;
avatarSizeLG: number;
avatarSizeSM: number;
avatarFontSizeBase: number;
avatarFontSizeLG: number;
avatarFontSizeSM: number;
avatarGroupOverlapping: number;
avatarGroupSpace: number;
avatarGroupBorderColor: string;
avatarBgColor: string;
};
@ -64,12 +27,12 @@ const genBaseStyle: GenerateStyle<AvatarToken> = token => {
iconCls,
avatarBg,
avatarColor,
containerSize,
containerSizeLG,
containerSizeSM,
textFontSize,
textFontSizeLG,
textFontSizeSM,
avatarSizeBase,
avatarSizeLG,
avatarSizeSM,
avatarFontSizeBase,
avatarFontSizeLG,
avatarFontSizeSM,
borderRadius,
borderRadiusLG,
borderRadiusSM,
@ -126,14 +89,14 @@ const genBaseStyle: GenerateStyle<AvatarToken> = token => {
display: 'block',
},
...avatarSizeStyle(containerSize, textFontSize, borderRadius),
...avatarSizeStyle(avatarSizeBase, avatarFontSizeBase, borderRadius),
[`&-lg`]: {
...avatarSizeStyle(containerSizeLG, textFontSizeLG, borderRadiusLG),
...avatarSizeStyle(avatarSizeLG, avatarFontSizeLG, borderRadiusLG),
},
[`&-sm`]: {
...avatarSizeStyle(containerSizeSM, textFontSizeSM, borderRadiusSM),
...avatarSizeStyle(avatarSizeSM, avatarFontSizeSM, borderRadiusSM),
},
'> img': {
@ -147,65 +110,55 @@ const genBaseStyle: GenerateStyle<AvatarToken> = token => {
};
const genGroupStyle: GenerateStyle<AvatarToken> = token => {
const { componentCls, groupBorderColor, groupOverlapping, groupSpace } = token;
const { componentCls, avatarGroupBorderColor, avatarGroupSpace } = token;
return {
[`${componentCls}-group`]: {
display: 'inline-flex',
[`${componentCls}`]: {
borderColor: groupBorderColor,
borderColor: avatarGroupBorderColor,
},
[`> *:not(:first-child)`]: {
marginInlineStart: groupOverlapping,
},
},
[`${componentCls}-group-popover`]: {
[`${componentCls} + ${componentCls}`]: {
marginInlineStart: groupSpace,
marginInlineStart: avatarGroupSpace,
},
},
};
};
export default genComponentStyleHook(
'Avatar',
token => {
const { colorTextLightSolid, colorTextPlaceholder } = token;
const avatarToken = mergeToken<AvatarToken>(token, {
avatarBg: colorTextPlaceholder,
avatarColor: colorTextLightSolid,
});
return [genBaseStyle(avatarToken), genGroupStyle(avatarToken)];
},
token => {
const {
controlHeight,
controlHeightLG,
controlHeightSM,
export default genComponentStyleHook('Avatar', token => {
const {
colorTextLightSolid,
fontSize,
fontSizeLG,
fontSizeXL,
fontSizeHeading3,
controlHeight,
controlHeightLG,
controlHeightSM,
marginXS,
marginXXS,
colorBorderBg,
} = token;
return {
containerSize: controlHeight,
containerSizeLG: controlHeightLG,
containerSizeSM: controlHeightSM,
fontSize,
fontSizeLG,
fontSizeXL,
fontSizeHeading3,
textFontSize: Math.round((fontSizeLG + fontSizeXL) / 2),
textFontSizeLG: fontSizeHeading3,
textFontSizeSM: fontSize,
marginXS,
colorBorderBg,
colorTextPlaceholder,
} = token;
groupSpace: marginXXS,
groupOverlapping: -marginXS,
groupBorderColor: colorBorderBg,
};
},
);
const avatarToken = mergeToken<AvatarToken>(token, {
avatarBg: colorTextPlaceholder,
avatarColor: colorTextLightSolid,
avatarSizeBase: controlHeight,
avatarSizeLG: controlHeightLG,
avatarSizeSM: controlHeightSM,
avatarFontSizeBase: Math.round((fontSizeLG + fontSizeXL) / 2),
avatarFontSizeLG: fontSizeHeading3,
avatarFontSizeSM: fontSize,
avatarGroupSpace: -marginXS,
avatarGroupBorderColor: colorBorderBg,
});
return [genBaseStyle(avatarToken), genGroupStyle(avatarToken)];
});

View File

@ -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 } from '../_util/transition';
import { getTransitionProps, Transition } from '../_util/transition';
import type { ExtractPropTypes, CSSProperties, PropType } from 'vue';
import { defineComponent, computed, ref, watch, Transition } from 'vue';
import { defineComponent, computed, ref, watch } from 'vue';
import Ribbon from './Ribbon';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import isNumeric from '../_util/isNumeric';
@ -107,7 +107,7 @@ export default defineComponent({
const statusCls = computed(() => ({
[`${prefixCls.value}-status-dot`]: hasStatus.value,
[`${prefixCls.value}-status-${props.status}`]: !!props.status,
[`${prefixCls.value}-color-${props.color}`]: isInternalColor.value,
[`${prefixCls.value}-status-${props.color}`]: isInternalColor.value,
}));
const statusStyle = computed(() => {
@ -125,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}-color-${props.color}`]: isInternalColor.value,
[`${prefixCls.value}-status-${props.color}`]: isInternalColor.value,
}));
return () => {

View File

@ -33,19 +33,19 @@ exports[`renders ./components/badge/demo/change.vue correctly 1`] = `
exports[`renders ./components/badge/demo/colors.vue correctly 1`] = `
<h4 style="margin-bottom: 16px;">Presets:</h4>
<div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-pink"></span><span class="ant-badge-status-text">pink</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-red"></span><span class="ant-badge-status-text">red</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-yellow"></span><span class="ant-badge-status-text">yellow</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-orange"></span><span class="ant-badge-status-text">orange</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-cyan"></span><span class="ant-badge-status-text">cyan</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-green"></span><span class="ant-badge-status-text">green</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-blue"></span><span class="ant-badge-status-text">blue</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-purple"></span><span class="ant-badge-status-text">purple</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-geekblue"></span><span class="ant-badge-status-text">geekblue</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-magenta"></span><span class="ant-badge-status-text">magenta</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-volcano"></span><span class="ant-badge-status-text">volcano</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-gold"></span><span class="ant-badge-status-text">gold</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-color-lime"></span><span class="ant-badge-status-text">lime</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-pink"></span><span class="ant-badge-status-text">pink</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-red"></span><span class="ant-badge-status-text">red</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-yellow"></span><span class="ant-badge-status-text">yellow</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-orange"></span><span class="ant-badge-status-text">orange</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-cyan"></span><span class="ant-badge-status-text">cyan</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-green"></span><span class="ant-badge-status-text">green</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-blue"></span><span class="ant-badge-status-text">blue</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-purple"></span><span class="ant-badge-status-text">purple</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-geekblue"></span><span class="ant-badge-status-text">geekblue</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-magenta"></span><span class="ant-badge-status-text">magenta</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-volcano"></span><span class="ant-badge-status-text">volcano</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-gold"></span><span class="ant-badge-status-text">gold</span></span></div>
<div><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-lime"></span><span class="ant-badge-status-text">lime</span></span></div>
</div>
<div class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left" role="separator"><span class="ant-divider-inner-text">Custom</span></div>
<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot" style="background: rgb(255, 85, 0); color: rgb(255, 85, 0);"></span><span class="ant-badge-status-text">#f50</span></span>
@ -89,167 +89,141 @@ exports[`renders ./components/badge/demo/overflow.vue correctly 1`] = `
`;
exports[`renders ./components/badge/demo/ribbon.vue correctly 1`] = `
<div style="width: 100%;" class="ant-space ant-space-vertical">
<div class="ant-space-item" style="margin-bottom: 8px;">
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<!---->
</div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;">
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<div class="ant-ribbon ant-ribbon-placement-end"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
</div>
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-pink"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<!---->
</div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;">
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-pink"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
</div>
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-red"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<!---->
</div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;">
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-red"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
</div>
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-cyan"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<!---->
</div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;">
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-cyan"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
</div>
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-green"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<!---->
</div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;">
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-green"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
</div>
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-purple"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<!---->
</div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;">
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-purple"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
</div>
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-volcano"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<!---->
</div>
<!---->
<div class="ant-space-item">
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-volcano"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
</div>
<div class="ant-ribbon-wrapper ">
<div class="ant-card ant-card-bordered ant-card-small">
<div class="ant-card-head">
<div class="ant-card-head-wrapper">
<div class="ant-card-head-title">Pushes open the window</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-magenta"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
<!---->
<div class="ant-card-body">and raises the spyglass.</div>
<!---->
</div>
<div class="ant-ribbon ant-ribbon-placement-end ant-ribbon-color-magenta"><span class="ant-ribbon-text">Hippies</span>
<div class="ant-ribbon-corner"></div>
</div>
<!---->
</div>
`;
@ -260,18 +234,15 @@ exports[`renders ./components/badge/demo/status.vue correctly 1`] = `
<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-processing"></span><span class="ant-badge-status-text"><!----></span></span>
<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-warning"></span><span class="ant-badge-status-text"><!----></span></span>
<br>
<div class="ant-space ant-space-vertical">
<div class="ant-space-item" style="margin-bottom: 8px;"><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Success</span></span></div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;"><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-error"></span><span class="ant-badge-status-text">Error</span></span></div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;"><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-default"></span><span class="ant-badge-status-text">Default</span></span></div>
<!---->
<div class="ant-space-item" style="margin-bottom: 8px;"><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-processing"></span><span class="ant-badge-status-text">Processing</span></span></div>
<!---->
<div class="ant-space-item"><span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-warning"></span><span class="ant-badge-status-text">warning</span></span></div>
<!---->
</div>
<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-success"></span><span class="ant-badge-status-text">Success</span></span>
<br>
<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-error"></span><span class="ant-badge-status-text">Error</span></span>
<br>
<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-default"></span><span class="ant-badge-status-text">Default</span></span>
<br>
<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-processing"></span><span class="ant-badge-status-text">Processing</span></span>
<br>
<span class="ant-badge ant-badge-status ant-badge-not-a-wrapper"><span class="ant-badge-status-dot ant-badge-status-warning"></span><span class="ant-badge-status-text">warning</span></span>
`;
exports[`renders ./components/badge/demo/title.vue correctly 1`] = `

View File

@ -16,30 +16,28 @@ Use ribbon badge.
</docs>
<template>
<a-space direction="vertical" style="width: 100%">
<a-badge-ribbon text="Hippies">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="pink">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="red">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="cyan">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="green">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="purple">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="volcano">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="magenta">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
</a-space>
<a-badge-ribbon text="Hippies">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="pink">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="red">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="cyan">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="green">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="purple">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="volcano">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
<a-badge-ribbon text="Hippies" color="magenta">
<a-card title="Pushes open the window" size="small">and raises the spyglass.</a-card>
</a-badge-ribbon>
</template>

View File

@ -1,7 +1,7 @@
<docs>
---
order: 4
title:
title:
zh-CN: 状态点
en-US: Status
---
@ -22,12 +22,13 @@ Standalone badge with status.
<a-badge status="processing" />
<a-badge status="warning" />
<br />
<a-space direction="vertical">
<a-badge status="success" text="Success" />
<a-badge status="error" text="Error" />
<a-badge status="default" text="Default" />
<a-badge status="processing" text="Processing" />
<a-badge status="warning" text="warning" />
</a-space>
<a-badge status="success" text="Success" />
<br />
<a-badge status="error" text="Error" />
<br />
<a-badge status="default" text="Default" />
<br />
<a-badge status="processing" text="Processing" />
<br />
<a-badge status="warning" text="warning" />
</template>

View File

@ -73,12 +73,9 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
const ribbonPrefixCls = `${antCls}-ribbon`;
const ribbonWrapperPrefixCls = `${antCls}-ribbon-wrapper`;
const colorPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`&${componentCls} ${componentCls}-color-${colorKey}`]: {
const statusPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`${componentCls}-status-${colorKey}`]: {
background: darkColor,
[`&:not(${componentCls}-count)`]: {
color: darkColor,
},
},
}));
@ -153,9 +150,9 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
insetInlineEnd: 0,
transform: 'translate(50%, -50%)',
transformOrigin: '100% 0%',
[`&${iconCls}-spin`]: {
[`${iconCls}-spin`]: {
animationName: antBadgeLoadingCircle,
animationDuration: '1s',
animationDuration: token.motionDurationMid,
animationIterationCount: 'infinite',
animationTimingFunction: 'linear',
},
@ -178,7 +175,7 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
backgroundColor: token.colorSuccess,
},
[`${componentCls}-status-processing`]: {
overflow: 'visible',
position: 'relative',
color: token.colorPrimary,
backgroundColor: token.colorPrimary,
@ -210,13 +207,13 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
[`${componentCls}-status-warning`]: {
backgroundColor: token.colorWarning,
},
...statusPreset,
[`${componentCls}-status-text`]: {
marginInlineStart: marginXS,
color: token.colorText,
fontSize: token.fontSize,
},
},
...colorPreset,
[`${componentCls}-zoom-appear, ${componentCls}-zoom-enter`]: {
animationName: antZoomBadgeIn,
animationDuration: token.motionDurationSlow,
@ -287,6 +284,7 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
...resetComponent(token),
position: 'absolute',
top: marginXS,
height: badgeFontHeight,
padding: `0 ${token.paddingXS}px`,
color: token.colorPrimary,
lineHeight: `${badgeFontHeight}px`,

View File

@ -8,7 +8,7 @@ import DownOutlined from '@ant-design/icons-vue/DownOutlined';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import type { MouseEventHandler } from '../_util/EventInterface';
import { eventType, objectType } from '../_util/type';
import type { CustomSlotsType, VueNode } from '../_util/type';
import type { CustomSlotsType } from '../_util/type';
export const breadcrumbItemProps = () => ({
prefixCls: String,
@ -38,7 +38,7 @@ export default defineComponent({
* if overlay is have
* Wrap a Dropdown
*/
const renderBreadcrumbNode = (breadcrumbItem: VueNode, prefixCls: string) => {
const renderBreadcrumbNode = (breadcrumbItem: JSX.Element, prefixCls: string) => {
const overlay = getPropsSlot(slots, props, 'overlay');
if (overlay) {
return (
@ -59,7 +59,7 @@ export default defineComponent({
const separator = getPropsSlot(slots, props, 'separator') ?? '/';
const children = getPropsSlot(slots, props);
const { class: cls, style, ...restAttrs } = attrs;
let link: VueNode;
let link: JSX.Element;
if (props.href !== undefined) {
link = (
<a class={`${prefixCls.value}-link`} onClick={handleClick} {...restAttrs}>

View File

@ -1,5 +1,6 @@
import { defineComponent, nextTick, Transition } from 'vue';
import { defineComponent, nextTick } 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';

Some files were not shown because too many files have changed in this diff Show More