Compare commits

..

No commits in common. "main" and "2.0.0-rc.2" have entirely different histories.

3546 changed files with 138790 additions and 575347 deletions

View File

@ -1,36 +0,0 @@
const fs = require('fs');
const path = require('path');
const restCssPath = path.join(process.cwd(), 'components', 'style', 'reset.css');
const tokenStatisticPath = path.join(process.cwd(), 'components', 'version', 'token.json');
const tokenMetaPath = path.join(process.cwd(), 'components', 'version', 'token-meta.json');
function finalizeCompile() {
if (fs.existsSync(path.join(__dirname, './es'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'es', 'style', 'reset.css'));
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'es', 'version', 'token.json'));
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'es', 'version', 'token-meta.json'));
}
if (fs.existsSync(path.join(__dirname, './lib'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'lib', 'style', 'reset.css'));
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'lib', 'version', 'token.json'));
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'lib', 'version', 'token-meta.json'));
}
}
function finalizeDist() {
if (fs.existsSync(path.join(__dirname, './dist'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'dist', 'reset.css'));
}
}
module.exports = {
compile: {
finalize: finalizeCompile,
},
dist: {
finalize: finalizeDist,
},
bail: true,
};

View File

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

75
.eslintrc Normal file
View File

@ -0,0 +1,75 @@
{
"root": true,
"env": {
"browser": true,
"node": true,
"jasmine": true,
"jest": true,
"es6": true
},
"parserOptions": {
"parser": "babel-eslint"
},
"extends": ["plugin:vue/vue3-recommended", "prettier"],
"plugins": ["markdown"],
"overrides": [
{
"files": ["**/demo/*.md"],
"processor": "markdown/markdown",
"rules": {
"no-console": "off"
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": [
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/ban-types": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-unused-vars": [
"error",
{ "vars": "all", "args": "after-used", "ignoreRestSiblings": true }
]
}
}
],
"rules": {
"comma-dangle": [2, "always-multiline"],
"no-var": "error",
"no-console": [2, { "allow": ["warn", "error"] }],
"object-shorthand": 2,
"no-unused-vars": [2, { "ignoreRestSiblings": true, "argsIgnorePattern": "^h$" }],
"no-undef": 2,
"camelcase": "off",
"no-extra-boolean-cast": "off",
"semi": ["error", "always"],
"vue/require-explicit-emits": "off",
"vue/require-prop-types": "off",
"vue/require-default-prop": "off",
"vue/no-reserved-keys": "off",
"vue/comment-directive": "off",
"vue/prop-name-casing": "off",
"vue/one-component-per-file": "off",
"vue/custom-event-name-casing": "off",
"vue/max-attributes-per-line": [
2,
{
"singleline": 20,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}
]
},
"globals": {
"h": true
}
}

View File

@ -1,112 +0,0 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
jasmine: true,
jest: true,
es6: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
parser: 'babel-eslint',
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'@vue/typescript/recommended',
'@vue/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'],
processor: 'markdown/markdown',
rules: {
'no-console': 'off',
},
},
{
files: ['*.ts', '*.tsx'],
// extends: ['@vue/typescript/recommended', '@vue/prettier'],
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/no-unused-vars': [
'error',
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true },
],
'@typescript-eslint/ban-ts-comment': 0,
},
},
{
files: ['*.vue'],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2021,
},
rules: {
'no-console': 'off',
'vue/no-reserved-component-names': 'off',
},
},
],
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',
'import/no-unresolved': [2, { ignore: ['ant-design-vue'] }],
'comma-dangle': [2, 'always-multiline'],
'no-var': 'error',
'no-console': [2, { allow: ['warn', 'error'] }],
'object-shorthand': 2,
'no-unused-vars': [2, { ignoreRestSiblings: true, argsIgnorePattern: '^_' }],
'no-undef': 2,
camelcase: 'off',
'no-extra-boolean-cast': 'off',
semi: ['error', 'always'],
'vue/no-v-html': 'off',
'vue/require-explicit-emits': 'off',
'vue/require-prop-types': 'off',
'vue/require-default-prop': 'off',
'vue/no-reserved-keys': 'off',
'vue/comment-directive': 'off',
'vue/prop-name-casing': 'off',
'vue/one-component-per-file': 'off',
'vue/custom-event-name-casing': 'off',
'vue/v-on-event-hyphenation': 'off',
'vue/max-attributes-per-line': [
2,
{
singleline: 20,
multiline: 1,
},
],
'vue/multi-word-component-names': 'off',
},
};

8
.github/FUNDING.yml vendored
View File

@ -3,3 +3,11 @@
github: # [tangjinzhou]
open_collective: ant-design-vue
patreon: tangjinzhou
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
custom:
[
"https://www.paypal.me/tangjinzhou",
"https://qn.antdv.com/alipay-and-wechat.png",
"https://www.buymeacoffee.com/antdv"
]

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 的健康持续发展需要您的支持,🙏

51
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,51 @@
First of all, thank you for your contribution! 😄
New feature please send pull request to feature branch, and rest to master branch. Pull request will be merged after one of collaborators approve. Please makes sure that these form are filled before submitting your pull request, thank you!
[[中文版模板 / Chinese template](https://github.com/vueComponent/ant-design-vue/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]
### This is a ...
- [ ] New feature
- [ ] Bug fix
- [ ] Site / document update
- [ ] Component style update
- [ ] TypeScript definition update
- [ ] Refactoring
- [ ] Code style optimization
- [ ] Branch merge
- [ ] Other (about what?)
### What's the background?
> 1. Describe the source of requirement.
> 2. Resolve what problem.
> 3. Related issue link.
### API Realization (Optional if not new feature)
> 1. Basic thought of solution and other optional proposal.
> 2. List final API realization and usage sample.
> 3. GIF or snapshot should be provided if includes UI/interactive modification.
### What's the effect? (Optional if not new feature)
> 1. Does this PR affect user? Which part will be affected?
> 2. What will say in changelog?
> 3. Does this PR contains potential break change or other risk?
### Changelog description (Optional if not new feature)
> 1. English description
> 2. Chinese description (optional)
### Self Check before Merge
- [ ] Doc is updated/provided or not needed
- [ ] Demo is updated/provided or not needed
- [ ] TypeScript definition is updated/provided or not needed
- [ ] Changelog is provided or not needed
### Additional Plan? (Optional if not new feature)
> If this PR related with other PR or following info. You can type here.

View File

@ -1,8 +1,8 @@
首先,感谢你的贡献! 😄
新特性请提交至 feature 分支,其余可提交至 main 分支。在一个维护者审核通过后合并。请确保填写以下 pull request 的信息,谢谢!~
新特性请提交至 feature 分支,其余可提交至 master 分支。在一个维护者审核通过后合并。请确保填写以下 pull request 的信息,谢谢!~
[[English Template / 英文模板](./pr_en.md)]
[[English Template / 英文模板](?expand=1)]
### 这个变动的性质是

View File

@ -1,51 +0,0 @@
First of all, thank you for your contribution! 😄
New feature please send pull request to feature branch, and rest to main branch. Pull request will be merged after one of collaborators approve. Please makes sure that these form are filled before submitting your pull request, thank you!
[[中文版模板 / Chinese template](./pr_cn.md)]
### This is a ...
- [ ] New feature
- [ ] Bug fix
- [ ] Site / document update
- [ ] Component style update
- [ ] TypeScript definition update
- [ ] Refactoring
- [ ] Code style optimization
- [ ] Branch merge
- [ ] Other (about what?)
### What's the background?
> 1. Describe the source of requirement.
> 2. Resolve what problem.
> 3. Related issue link.
### API Realization (Optional if not new feature)
> 1. Basic thought of solution and other optional proposal.
> 2. List final API realization and usage sample.
> 3. GIF or snapshot should be provided if includes UI/interactive modification.
### What's the effect? (Optional if not new feature)
> 1. Does this PR affect user? Which part will be affected?
> 2. What will say in changelog?
> 3. Does this PR contains potential break change or other risk?
### Changelog description (Optional if not new feature)
> 1. English description
> 2. Chinese description (optional)
### Self Check before Merge
- [ ] Doc is updated/provided or not needed
- [ ] Demo is updated/provided or not needed
- [ ] TypeScript definition is updated/provided or not needed
- [ ] Changelog is provided or not needed
### Additional Plan? (Optional if not new feature)
> If this PR related with other PR or following info. You can type here.

View File

@ -16,7 +16,7 @@ jobs:
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts
run: npm i --package-lock-only
- name: hack for singe file
run: |

View File

@ -1,14 +0,0 @@
name: Emoji Helper
on:
release:
types: [published]
jobs:
emoji:
runs-on: ubuntu-latest
steps:
- uses: actions-cool/emoji-helper@v1.0.0
with:
type: 'release'
emoji: '+1, laugh, heart, hooray, rocket, eyes'

View File

@ -1,23 +0,0 @@
name: Issue Close Require
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
with:
actions: 'close-issues'
labels: '🤔 Need Reproduce'
inactive-day: 7
body: |
Since the issue was labeled with `Need Reproduce`, but no response in 7 days. This issue will be closed. If you have any questions, you can comment and reply.
由于该 issue 被标记为需要复现信息,却 7 天未收到回应。现关闭 issue若有任何问题可评论回复。

View File

@ -1,76 +0,0 @@
name: Issue Labeled
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
runs-on: ubuntu-latest
steps:
- name: Need Reproduce
if: github.event.label.name == '🤔 Need Reproduce'
uses: actions-cool/issues-helper@v3
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.
你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处 for vue2](https://codesandbox.io/s/2wpk21kzvr)、 [此处 for vue3](https://codesandbox.io/s/agitated-franklin-1w72v) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。请确保选择准确的版本。
- name: help wanted
if: github.event.label.name == 'help wanted'
uses: actions-cool/issues-helper@v3
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!
你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎直接在此仓库创建一个 Pull Request 来解决这个问题。请将 Pull Request 发到正确的分支,务必填写 Pull Request 内的预设模板,提供改动所需相应的 changelog、TypeScript 定义、测试用例、文档等,并确保 CI 通过,我们会尽快进行 Review提前感谢和期待您的贡献。
- name: Usage
if: github.event.label.name == 'Usage'
uses: actions-cool/issues-helper@v3
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 会被自动关闭。

View File

@ -1,34 +0,0 @@
name: Issue Open Check
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
runs-on: ubuntu-latest
steps:
- uses: actions-cool/check-user-permission@v1.0.0
id: checkUser
with:
require: 'write'
- name: check invalid
if: (contains(github.event.issue.body, 'issue-helper') == false) && (steps.checkUser.outputs.result == 'false')
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,add-labels,close-issue'
issue-number: ${{ github.event.issue.number }}
labels: 'Invalid'
body: |
Hello @${{ github.event.issue.user.login }}, your issue has been closed because it does not conform to our issue requirements. Please use the [Issue Helper](https://vuecomponent.github.io/issue-helper/) to create an issue, thank you!
你好 @${{ github.event.issue.user.login }},为了能够进行高效沟通,我们对 issue 有一定的格式要求,你的 issue 因为不符合要求而被自动关闭。你可以通过 [issue 助手](https://vuecomponent.github.io/issue-helper/) 来创建 issue 以方便我们定位错误。谢谢配合!

19
.github/workflows/need-reproduce.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Add need reproduce
on:
issues:
types: [labeled]
jobs:
add-need-reproduce:
runs-on: ubuntu-latest
if: github.event.label.name == '🤔 Need Reproduce'
steps:
- name: Create comment
uses: peter-evans/create-or-update-comment@v1
with:
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.
你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处 for vue2](https://codesandbox.io/s/2wpk21kzvr)、 [此处 for vue3](https://codesandbox.io/s/agitated-franklin-1w72v) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。请确保选择准确的版本。

View File

@ -1,19 +0,0 @@
name: "Close stale issues"
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
exempt-issue-labels: 'bug,enhancement'
days-before-stale: 60
days-before-close: 7

View File

@ -10,13 +10,13 @@ jobs:
uses: actions/checkout@v2
- name: cache package-lock.json
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts
run: npm i --package-lock-only
- name: hack for singe file
run: |
@ -27,7 +27,7 @@ jobs:
- name: cache node_modules
id: node_modules_cache_id
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
@ -43,25 +43,25 @@ jobs:
uses: actions/checkout@v2
- name: restore cache from package-lock.json
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: cache lib
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: lib
key: lib-${{ github.sha }}
- name: cache es
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: es
key: es-${{ github.sha }}
@ -77,13 +77,13 @@ jobs:
uses: actions/checkout@v2
- name: restore cache from package-lock.json
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
@ -98,14 +98,24 @@ jobs:
- name: checkout
uses: actions/checkout@v2
# with:
# token: ${{ secrets.ACCESS_TOKEN }}
# - name: Checkout submodules
# uses: actions/checkout@v2
# with:
# repository: tangjinzhou/antdv-demo
# token: ${{ secrets.ACCESS_TOKEN }}
# path: antdv-demo
# submodules: true
- name: restore cache from package-lock.json
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- name: restore cache from node_modules
uses: actions/cache@v2
uses: actions/cache@v1
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}

15
.gitignore vendored
View File

@ -59,11 +59,9 @@ jspm_packages/
dist
lib
es
/locale
_site
yarn.lock
package-lock.json
pnpm-lock.yaml
/coverage
# 备份文件
@ -71,16 +69,3 @@ pnpm-lock.yaml
list.txt
site/dev.js
# IDE 语法提示临时文件
vetur/
report.html
site/src/router/demoRoutes.js
components/version/version.ts
components/version/version.tsx
components/version/token.json
components/version/token-meta.json
~component-api.json

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "antdv-demo"]
path = antdv-demo
url = git@github.com:tangjinzhou/antdv-demo.git

1
.husky/.gitignore vendored
View File

@ -1 +0,0 @@
_

View File

@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install pretty-quick --staged

View File

@ -4,53 +4,47 @@ const transformIgnorePatterns = [
'/dist/',
// Ignore modules without es dir.
// Update: @babel/runtime should also be transformed
// 'node_modules/(?!.*(@babel|lodash-es))',
'node_modules/(?!@ant-design/icons-vue|@ant-design/icons-svg|lodash-es)/',
'node_modules/(?!.*(@babel|lodash-es))[^/]+?/(?!(es|node_modules)/)',
];
const testPathIgnorePatterns = ['/node_modules/', 'node'];
function getTestRegex(libDir) {
if (libDir === 'dist') {
return 'demo\\.test\\.js$';
}
return '.*\\.test\\.(j|t)sx?$';
if (process.env.WORKFLOW === 'true') {
testPathIgnorePatterns.push('demo\\.test*');
}
module.exports = {
verbose: true,
testURL: 'http://localhost/',
setupFiles: ['./tests/setup.js'],
setupFilesAfterEnv: ['./tests/setupAfterEnv.ts'],
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'vue', 'md', 'jpg'],
modulePathIgnorePatterns: ['/_site/'],
testPathIgnorePatterns: testPathIgnorePatterns,
transform: {
'\\.(vue|md)$': '<rootDir>/node_modules/@vue/vue3-jest',
'\\.(js|jsx)$': '<rootDir>/node_modules/babel-jest',
'\\.(ts|tsx)$': '<rootDir>/node_modules/ts-jest',
'\\.svg$': '<rootDir>/node_modules/jest-transform-stub',
'^.+\\.(vue|md)$': '<rootDir>/node_modules/vue-jest',
'^.+\\.(js|jsx)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.(ts|tsx)$': '<rootDir>/node_modules/ts-jest',
'^.+\\.svg$': '<rootDir>/node_modules/jest-transform-stub',
},
testRegex: getTestRegex(libDir),
testRegex: libDir === 'dist' ? 'demo\\.test\\.js$' : '.*\\.test\\.js$',
moduleNameMapper: {
'^@/(.*)$/': '<rootDir>/$1',
'^ant-design-vue$': '<rootDir>/components/index',
'^ant-design-vue/es/(.*)$': '<rootDir>/components/$1',
'^@/(.*)$': '<rootDir>/$1',
'ant-design-vue$': '<rootDir>/components/index.ts',
'ant-design-vue/es': '<rootDir>/components',
},
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
collectCoverage: process.env.COVERAGE === 'true',
collectCoverageFrom: [
'components/**/*.{js,jsx,vue}',
'!components/*/style/index.{js,jsx}',
'!components/style/*.{js,jsx}',
'!components/*/locale/*.{js,jsx}',
'!components/*/__tests__/**/type.{js,jsx}',
'!components/vc-*/**/*',
'!components/*/demo/**/*',
'!components/_util/**/*',
'!components/align/**/*',
'!components/trigger/**/*',
'!components/style.js',
'!**/node_modules/**',
],
testEnvironment: 'jsdom',
testEnvironmentOptions: {
url: 'http://localhost',
customExportConditions: ['node', 'node-addons'],
},
testEnvironment: 'jest-environment-jsdom-fifteen',
transformIgnorePatterns,
globals: {
'ts-jest': {

1
.npmrc
View File

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

View File

@ -18,6 +18,7 @@ yarn-error.log
.editorconfig
.eslintignore
**/*.yml
components/style/color/*.less
**/assets
.gitattributes
.stylelintrc
@ -27,5 +28,4 @@ yarn-error.log
.huskyrc
.gitmodules
*.png
v2-doc/

View File

@ -1,11 +1,8 @@
{
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"printWidth": 100,
"proseWrap": "never",
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"overrides": [
{
"files": ".prettierrc",

View File

@ -1,43 +0,0 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-rational-order",
"stylelint-config-prettier"
],
"customSyntax": "postcss-less",
"plugins": ["stylelint-declaration-block-no-ignored-properties"],
"rules": {
"function-name-case": ["lower"],
"function-no-unknown": [
true,
{
"ignoreFunctions": [
"fade",
"fadeout",
"tint",
"darken",
"ceil",
"fadein",
"floor",
"unit",
"shade",
"lighten",
"percentage",
"-"
]
}
],
"import-notation": null,
"no-descending-specificity": null,
"no-invalid-position-at-import-rule": null,
"declaration-empty-line-before": null,
"keyframes-name-pattern": null,
"custom-property-pattern": null,
"number-max-precision": 8,
"alpha-value-notation": "number",
"color-function-notation": "legacy",
"selector-class-pattern": null,
"selector-id-pattern": null,
"selector-not-notation": null
}
}

5
.vcmrc
View File

@ -9,9 +9,8 @@
"perf",
"test",
"chore",
"revert",
"ci"
"revert"
],
"warnOnFail": false,
"autoFix": false
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

22
LICENSE
View File

@ -44,25 +44,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
MIT License
Copyright (c) 2019-PRESENT Anthony Fu<https://github.com/antfu>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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,9 +10,9 @@
<div align="center">
基于 Ant Design 和 Vue 3 的企业级 UI 组件库。
An enterprise-class UI components based on Ant Design and Vue.
![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)
![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) [![Join the chat at https://gitter.im/vueComponent/ant-design-english](https://badges.gitter.im/vueComponent/ant-design-english.svg?style=flat-square)](https://gitter.im/vueComponent/ant-design-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) (English) [![Join the chat at https://gitter.im/vueComponent/ant-design-vue](https://img.shields.io/gitter/room/vueComponent/ant-design-vue.svg?style=flat-square)](https://gitter.im/vueComponent/ant-design-vue?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)(中文) [![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)
</div>
@ -20,28 +20,36 @@
[English](./README.md) | 简体中文
<p align="center">
<b>Special thanks to the generous sponsorship by:</b>
</p>
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://tipe.io/?ref=ant-design-vue" target="_blank">
<img width="120px" src="https://user-images.githubusercontent.com/1016365/34124854-48fafa06-e3e9-11e7-8c04-251055feebee.png">
</a>
</td>
</tr>
</tbody>
</table>
## 特性
- 提炼自企业级中后台产品的交互语言和视觉风格。
- 开箱即用的高质量 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/#兼容性)
- 现代浏览器和 IE9 及以上(需要 [polyfills](https://www.antdv.com/docs/vue/getting-started-cn/#兼容性))。
- 支持服务端渲染。
- [Electron](https://electronjs.org/)
- 支持 Vue 2 和 Vue 3
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Electron |
| --- | --- | --- | --- | --- | --- |
| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
| IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 安装
@ -71,9 +79,6 @@ $ yarn add ant-design-vue
| [ant-design-vue-helper](https://marketplace.visualstudio.com/items?itemName=ant-design-vue.vscode-ant-design-vue-helper) | ant-design-vue 的 vscode 扩展 |
| [vue-cli-plugin-ant-design](https://github.com/vueComponent/vue-cli-plugin-ant-design) | 使用 vue-cli3 快速使用 ant-design-vue 组件库 |
| [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | 在 DOM 模板中,您可以使用 ant-design-vue 组件的自定义事件camelCase |
| [@formily/antdv](https://github.com/formilyjs/antdv) | 这是一个结合了 Formily 和 ant-design-vue 的组件库 |
| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | ant-design-vue 的 nuxt 模块扩展 |
| [ant-design-x-vue](https://github.com/wzc520pyfm/ant-design-x-vue) | 基于 Ant Design X 设计规范的 Vue AI 界面解决方案 |
## 问答
@ -88,33 +93,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>
<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>
@ -12,7 +12,7 @@
An enterprise-class UI components based on Ant Design and Vue.
![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)
![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) [![Join the chat at https://gitter.im/vueComponent/ant-design-english](https://badges.gitter.im/vueComponent/ant-design-english.svg?style=flat-square)](https://gitter.im/vueComponent/ant-design-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) (English) [![Join the chat at https://gitter.im/vueComponent/ant-design-vue](https://img.shields.io/gitter/room/vueComponent/ant-design-vue.svg?style=flat-square)](https://gitter.im/vueComponent/ant-design-vue?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)(中文) [![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)
</div>
@ -20,32 +20,40 @@ An enterprise-class UI components based on Ant Design and Vue.
English | [简体中文](./README-zh_CN.md)
<p align="center">
<b>Special thanks to the generous sponsorship by:</b>
</p>
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://tipe.io/?ref=ant-design-vue" target="_blank">
<img width="120px" src="https://user-images.githubusercontent.com/1016365/34124854-48fafa06-e3e9-11e7-8c04-251055feebee.png">
</a>
</td>
</tr>
</tbody>
</table>
## Features
- An enterprise-class UI design system for desktop applications.
- 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))
- Modern browsers and Internet Explorer 9+ (with [polyfills](https://www.antdv.com/docs/vue/getting-started/#Compatibility))
- Server-side Rendering
- Support Vue 2 & Vue 3
- [Electron](https://electronjs.org/)
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Electron |
| --- | --- | --- | --- | --- | --- |
| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
| IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 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 +63,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
@ -71,9 +79,6 @@ If you are in a bad network environment, you can try other registries and tools
| [ant-design-vue-helper](https://marketplace.visualstudio.com/items?itemName=ant-design-vue.vscode-ant-design-vue-helper) | A vscode extension for ant-design-vue |
| [vue-cli-plugin-ant-design](https://github.com/vueComponent/vue-cli-plugin-ant-design) | Vue-cli 3 plugin to add ant-design-vue |
| [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | The library function, implemented in the DOM template, can use the custom event of the ant-design-vue component (camelCase) |
| [@formily/antdv](https://github.com/formilyjs/antdv) | The Library with Formily and ant-design-vue |
| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | A nuxt module for ant-design-vue |
| [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,25 +87,26 @@ 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
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>
<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
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><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>
## Patreon
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>
## [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

@ -1,17 +0,0 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 1.x | :white_check_mark: |
| 2.x | :x: |
| 3.x | :white_check_mark: |
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
Tell them where to go, how often they can expect to get an update on a reported vulnerability, what to expect if the vulnerability is accepted or declined, etc.

View File

@ -1,68 +0,0 @@
// Read all the api from current documents
const glob = require('glob');
const fs = require('fs');
const COMPONENT_NAME = /components\/([^/]*)/;
const PROP_NAME = /^\s*\|\s*([^\s|]*)/;
const components = {};
function mappingPropLine(component, line) {
const propMatch = line.match(PROP_NAME);
if (!propMatch) return;
const propName = propMatch[1];
if (!/^[a-z]/.test(propName)) return;
components[component] = Array.from(new Set([...(components[component] || []), propName]));
}
function apiReport(entities) {
const apis = {};
Object.keys(entities).forEach(component => {
const apiList = entities[component];
apiList.forEach(api => {
if (typeof apis[api] === 'function') {
apis[api] = [];
}
apis[api] = [...(apis[api] || []), component];
});
});
return apis;
}
function printReport(apis) {
const apiList = Object.keys(apis).map(api => ({
name: api,
componentList: apis[api],
}));
apiList.sort((a, b) => b.componentList.length - a.componentList.length);
// eslint-disable-next-line no-console
console.log('| name | components | comments |');
// eslint-disable-next-line no-console
console.log('| ---- | ---------- | -------- |');
apiList.forEach(({ name, componentList }) => {
// eslint-disable-next-line no-console
console.log('|', name, '|', componentList.join(', '), '| |');
});
}
module.exports = () => {
glob('components/*/*.md', (error, files) => {
files.forEach(filePath => {
// Read md file to parse content
const content = fs.readFileSync(filePath, 'utf8');
const component = filePath.match(COMPONENT_NAME)[1];
// Parse lines to get API
const lines = content.split(/[\r\n]+/);
lines.forEach(line => {
mappingPropLine(component, line);
});
});
printReport(apiReport(components));
});
};

View File

@ -4,11 +4,10 @@
'use strict';
require('colorful').colorful();
require('colorful').isatty = true;
const gulp = require('gulp');
const program = require('commander');
program.option('-c --npm-tag <type>', 'add --npm-tag=xxx');
program.on('--help', () => {
console.log(' Usage:'.to.bold.blue.color);
console.log();

View File

@ -1 +0,0 @@
fork github.com/youzan/vant packages/generator-types

View File

@ -1,23 +0,0 @@
const path = require('path');
const pkg = require('../../package.json');
const { parseAndWrite } = require('./lib/index.js');
const rootPath = path.resolve(__dirname, '../../');
parseAndWrite({
version: pkg.version,
name: 'ant-design-vue',
path: path.resolve(rootPath, './components'),
typingsPath: path.resolve(rootPath, './typings/global.d.ts'),
// default match lang
test: /en-US\.md/,
outputDir: path.resolve(rootPath, './vetur'),
tagPrefix: 'a-',
})
.then(result => {
// eslint-disable-next-line no-console
console.log(`generator types success: ${result} tags generated`);
})
.catch(error => {
console.error('generator types error', error);
return Promise.reject(error);
});

View File

@ -1,128 +0,0 @@
import type { Articals } from './parser';
import { formatType, removeVersion, toKebabCase } from './utils';
import type { VueTag } from './type';
function getComponentName(name: string, tagPrefix: string) {
if (name) {
return tagPrefix + toKebabCase(name.split(' ')[0]);
}
return '';
}
function parserProps(tag: VueTag, line: any) {
const [name, desc, type, defaultVal] = line;
if (
type &&
(type.includes('v-slot') ||
type.includes('slot') ||
type.includes('slots') ||
type.includes('slot-scoped'))
) {
tag.slots!.push({
name: removeVersion(name),
description: desc,
});
}
tag.attributes!.push({
name: removeVersion(name),
default: defaultVal,
description: desc,
value: {
type: formatType(type || ''),
kind: 'expression',
},
});
}
export function formatter(
articals: Articals,
componentName: string,
kebabComponentName: string,
tagPrefix = '',
) {
if (!articals.length) {
return;
}
const tags: VueTag[] = [];
const tag: VueTag = {
name: kebabComponentName,
slots: [],
events: [],
attributes: [],
};
tags.push(tag);
const tables = articals.filter(artical => artical.type === 'table');
tables.forEach(item => {
const { table } = item;
const prevIndex = articals.indexOf(item) - 1;
const prevArtical = articals[prevIndex];
if (!prevArtical || !prevArtical.content || !table || !table.body) {
return;
}
const tableTitle = prevArtical.content;
if (tableTitle.includes('API')) {
table.body.forEach(line => {
parserProps(tag, line);
});
return;
}
if (tableTitle.includes('events') && !tableTitle.includes(componentName)) {
table.body.forEach(line => {
const [name, desc] = line;
tag.events!.push({
name: removeVersion(name),
description: desc,
});
});
return;
}
// 额外的子组件
if (
tableTitle.includes(componentName) &&
!tableTitle.includes('events') &&
!tableTitle.includes('()')
) {
const childTag: VueTag = {
name: getComponentName(tableTitle.replace(/\.|\//g, ''), tagPrefix),
slots: [],
events: [],
attributes: [],
};
table.body.forEach(line => {
parserProps(childTag, line);
});
tags.push(childTag);
return;
}
// 额外的子组件事件
if (tableTitle.includes(componentName) && tableTitle.includes('events')) {
const childTagName = getComponentName(
tableTitle.replace('.', '').replace('events', ''),
tagPrefix,
);
const childTag: VueTag | undefined = tags.find(item => item.name === childTagName.trim());
if (!childTag) {
return;
}
table.body.forEach(line => {
const [name, desc] = line;
childTag.events!.push({
name: removeVersion(name),
description: desc,
});
});
return;
}
});
return tags;
}

View File

@ -1,84 +0,0 @@
import glob from 'fast-glob';
import { dirname, join } from 'path';
import { mdParser } from './parser';
import { formatter } from './formatter';
import { genWebTypes } from './web-types';
import { outputFileSync, readFileSync } from 'fs-extra';
import type { Options, VueTag } from './type';
import { getComponentName, normalizePath, toKebabCase } from './utils';
import { flatMap } from 'lodash';
async function readMarkdown(options: Options): Promise<Map<String, VueTag>> {
const mdPaths = await glob(normalizePath(`${options.path}/**/*.md`));
const data = mdPaths
.filter(md => options.test.test(md))
.map(path => {
const docPath = dirname(path);
const kebabComponentName =
options.tagPrefix + docPath.substring(docPath.lastIndexOf('/') + 1) || '';
const componentName = getComponentName(docPath.substring(docPath.lastIndexOf('/') + 1) || '');
const fileContent = readFileSync(path, 'utf-8');
return formatter(mdParser(fileContent), componentName, kebabComponentName, options.tagPrefix);
})
.filter(item => item) as VueTag[][];
const tags = new Map<String, VueTag>();
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 fileContent = readFileSync(options.typingsPath, 'utf-8');
fileContent
.split('\n')
.filter(line => line && line.includes('typeof'))
.map(line => {
const l = line.trim();
return toKebabCase(l.substring(0, l.indexOf(':')));
})
.forEach(tagName =>
tags.set(tagName, {
name: tagName,
slots: [],
events: [],
attributes: [],
}),
);
return tags;
}
function mergeTag(tags: Map<String, VueTag>, mergedTag: VueTag) {
const tagName = mergedTag.name;
const vueTag = tags.get(tagName);
if (vueTag) {
vueTag.slots = [...vueTag.slots, ...mergedTag.slots];
vueTag.events = [...vueTag.events, ...mergedTag.events];
vueTag.attributes = [...vueTag.attributes, ...mergedTag.attributes];
} else {
tags.set(tagName, mergedTag);
}
}
function mergeTags(mergedTagsArr: Map<String, VueTag>[]): VueTag[] {
if (mergedTagsArr.length === 1) return [...mergedTagsArr[0].values()];
const tags = new Map<String, VueTag>();
if (mergedTagsArr.length === 0) return [];
mergedTagsArr.forEach(mergedTags => {
mergedTags.forEach(mergedTag => mergeTag(tags, mergedTag));
});
return [...tags.values()];
}
export async function parseAndWrite(options: Options): Promise<Number> {
if (!options.outputDir) {
throw new Error('outputDir can not be empty.');
}
const tagsFromMarkdown = await readMarkdown(options);
const tagsFromTypings = await readTypings(options);
const tags = mergeTags([tagsFromMarkdown, tagsFromTypings]);
const webTypes = genWebTypes(tags, options);
outputFileSync(join(options.outputDir, 'web-types.json'), JSON.stringify(webTypes, null, 2));
return tags.length;
}
export default { parseAndWrite };

View File

@ -1,107 +0,0 @@
/* eslint-disable no-cond-assign */
const TITLE_REG = /^(#+)\s+([^\n]*)/;
const TABLE_REG = /^\|.+\r?\n\|\s*-+/;
const TD_REG = /\s*`[^`]+`\s*|([^|`]+)/g;
const TABLE_SPLIT_LINE_REG = /^\|\s*-/;
type TableContent = {
head: string[];
body: string[][];
};
export type Artical = {
type: string;
content?: string;
table?: TableContent;
level?: number;
};
export type Articals = Artical[];
function readLine(input: string) {
const end = input.indexOf('\n');
return input.substring(0, end !== -1 ? end : input.length);
}
function splitTableLine(line: string) {
line = line.replace(/\\\|/g, 'JOIN');
const items = line.split('|').map(item => item.trim().replace(/JOIN/g, '|'));
// remove pipe character on both sides
items.pop();
items.shift();
return items;
}
function tableParse(input: string) {
let start = 0;
let isHead = true;
const end = input.length;
const table: TableContent = {
head: [],
body: [],
};
while (start < end) {
const target = input.substring(start);
const line = readLine(target);
if (!/^\|/.test(target)) {
break;
}
if (TABLE_SPLIT_LINE_REG.test(target)) {
isHead = false;
} else if (!isHead && line.includes('|')) {
const matched = line.trim().match(TD_REG);
if (matched) {
table.body.push(splitTableLine(line));
}
}
start += line.length + 1;
}
return {
table,
usedLength: start,
};
}
export function mdParser(input: string): Articals {
const artical = [];
let start = 0;
const end = input.length;
while (start < end) {
const target = input.substring(start);
let match;
if ((match = TITLE_REG.exec(target))) {
artical.push({
type: 'title',
content: match[2].replace('\r', ''),
level: match[1].length,
});
start += match.index + match[0].length;
} else if ((match = TABLE_REG.exec(target))) {
const { table, usedLength } = tableParse(target.substring(match.index));
artical.push({
type: 'table',
table,
});
start += match.index + usedLength;
} else {
start += readLine(target).length + 1;
}
}
return artical;
}

View File

@ -1,45 +0,0 @@
import type { PathLike } from 'fs';
export type VueSlot = {
name: string;
description: string;
};
export type VueEventArgument = {
name: string;
type: string;
};
export type VueEvent = {
name: string;
description?: string;
arguments?: VueEventArgument[];
};
export type VueAttribute = {
name: string;
default: string;
description: string;
value: {
kind: 'expression';
type: string;
};
};
export type VueTag = {
name: string;
slots: VueSlot[];
events: VueEvent[];
attributes: VueAttribute[];
description?: string;
};
export type Options = {
name: string;
path: PathLike;
typingsPath: PathLike;
test: RegExp;
version: string;
outputDir?: string;
tagPrefix?: string;
};

View File

@ -1,30 +0,0 @@
// myName -> my-name
export function toKebabCase(camel: string): string {
return camel.replace(/((?<=[a-z\d])[A-Z]|(?<=[A-Z\d])[A-Z](?=[a-z]))/g, '-$1').toLowerCase();
}
// name `v2.0.0` -> name
export function removeVersion(str: string) {
return str.replace(/`(\w|\.)+`/g, '').trim();
}
// *boolean* -> boolean
// _boolean_ -> boolean
export function formatType(type: string) {
return type
.replace(/(^(\*|_))|((\*|_)$)/g, '')
.replace('\\', '')
.replace('\\', '');
}
export function getComponentName(name: string) {
const title = name
.split('-')
.map(it => it.substring(0, 1) + it.substring(1))
.join('');
return title.substring(0, 1).toUpperCase() + title.substring(1);
}
export function normalizePath(path: string): string {
return path.replace(/\\/g, '/');
}

View File

@ -1,18 +0,0 @@
import type { VueTag, Options } from './type';
// create web-types.json to provide autocomplete in JetBrains IDEs
export function genWebTypes(tags: VueTag[], options: Options) {
return {
$schema: 'https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json',
framework: 'vue',
name: options.name,
version: options.version,
contributions: {
html: {
tags,
attributes: [],
'types-syntax': 'typescript',
},
},
};
}

View File

@ -1,13 +0,0 @@
{
"compilerOptions": {
"target": "ES2017",
"outDir": "./lib",
"module": "commonjs",
"strict": true,
"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"lib": ["esnext"]
},
"include": ["src/**/*"]
}

View File

@ -1,6 +1,6 @@
const { resolve, isThereHaveBrowserslistConfig } = require('./utils/projectHelper');
const { resolve } = require('./utils/projectHelper');
module.exports = function (modules) {
module.exports = function(modules) {
const plugins = [
[
resolve('@babel/plugin-transform-typescript'),
@ -8,7 +8,7 @@ module.exports = function (modules) {
isTSX: true,
},
],
[resolve('@vue/babel-plugin-jsx'), { mergeProps: false, enableObjectSlots: false }],
[resolve('@vue/babel-plugin-jsx'), { mergeProps: false }],
resolve('@babel/plugin-proposal-optional-chaining'),
resolve('@babel/plugin-transform-object-assign'),
resolve('@babel/plugin-proposal-object-rest-spread'),
@ -16,14 +16,6 @@ module.exports = function (modules) {
resolve('@babel/plugin-proposal-export-namespace-from'),
resolve('@babel/plugin-proposal-class-properties'),
resolve('@babel/plugin-syntax-dynamic-import'),
[
resolve('@babel/plugin-transform-runtime'),
{
useESModules: modules === false,
version:
require(`${process.cwd()}/package.json`).dependencies['@babel/runtime'] || '^7.10.4',
},
],
// resolve('babel-plugin-inline-import-data-uri'),
// resolve('@babel/plugin-transform-member-expression-literals'),
// resolve('@babel/plugin-transform-property-literals'),
@ -33,17 +25,28 @@ module.exports = function (modules) {
// resolve('@babel/plugin-proposal-object-rest-spread'),
// resolve('@babel/plugin-proposal-class-properties'),
];
plugins.push([
resolve('@babel/plugin-transform-runtime'),
{
helpers: false,
},
]);
return {
presets: [
[
resolve('@babel/preset-env'),
{
modules,
targets: isThereHaveBrowserslistConfig()
? undefined
: {
browsers: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 11'],
},
targets: {
browsers: [
'last 2 versions',
'Firefox ESR',
'> 1%',
'ie >= 11',
'iOS >= 8',
'Android >= 4',
],
},
},
],
],

View File

@ -1,17 +0,0 @@
'use strict';
const runCmd = require('./runCmd');
module.exports = function (done) {
if (process.env.NPM_CLI) {
done(process.env.NPM_CLI);
return;
}
runCmd('which', ['tnpm'], code => {
let npm = 'npm';
if (!code) {
npm = 'tnpm';
}
done(npm);
});
};

View File

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

View File

@ -1,12 +1,14 @@
const { getProjectPath, resolve } = require('./utils/projectHelper');
const { getProjectPath, resolve, injectRequire } = require('./utils/projectHelper');
injectRequire();
const path = require('path');
const webpack = require('webpack');
const WebpackBar = require('webpackbar');
const { merge } = require('webpack-merge');
const webpackMerge = require('webpack-merge');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const postcssConfig = require('./postcssConfig');
const CleanUpStatsPlugin = require('./utils/CleanUpStatsPlugin');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
@ -22,7 +24,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);
@ -37,7 +39,6 @@ function getWebpackConfig(modules, esm = false) {
babelConfig.plugins.push(require.resolve('./replaceLib'));
}
/** @type {import('webpack').Configuration} */
const config = {
devtool: 'source-map',
@ -64,21 +65,23 @@ function getWebpackConfig(modules, esm = false) {
alias: {
'@': process.cwd(),
},
fallback: [
'child_process',
'cluster',
'dgram',
'dns',
'fs',
'module',
'net',
'readline',
'repl',
'tls',
].reduce((acc, name) => Object.assign({}, acc, { [name]: 'empty' }), {}),
},
node: [
'child_process',
'cluster',
'dgram',
'dns',
'fs',
'module',
'net',
'readline',
'repl',
'tls',
].reduce((acc, name) => Object.assign({}, acc, { [name]: 'empty' }), {}),
module: {
noParse: [/moment.js/],
rules: [
{
test: /\.vue$/,
@ -94,10 +97,7 @@ function getWebpackConfig(modules, esm = false) {
options: {
presets: [resolve('@babel/preset-env')],
plugins: [
[
resolve('@vue/babel-plugin-jsx'),
{ mergeProps: false, enableObjectSlots: false },
],
[resolve('@vue/babel-plugin-jsx'), { mergeProps: false }],
resolve('@babel/plugin-proposal-object-rest-spread'),
],
},
@ -123,9 +123,6 @@ function getWebpackConfig(modules, esm = false) {
},
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
},
@ -141,13 +138,33 @@ function getWebpackConfig(modules, esm = false) {
},
{
loader: 'postcss-loader',
options: Object.assign({}, postcssConfig, { sourceMap: true }),
},
],
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
postcssOptions: {
plugins: ['autoprefixer'],
},
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: Object.assign({}, postcssConfig, { sourceMap: true }),
},
{
loader: 'less-loader',
options: {
lessOptions: {
sourceMap: true,
javascriptEnabled: true,
},
},
},
],
},
// Images
@ -170,7 +187,7 @@ function getWebpackConfig(modules, esm = false) {
new webpack.BannerPlugin(`
${pkg.name} v${pkg.version}
Copyright 2017-present, Ant Design Vue.
Copyright 2017-present, ant-design-vue.
All rights reserved.
`),
new WebpackBar({
@ -179,57 +196,32 @@ All rights reserved.
}),
new CleanUpStatsPlugin(),
],
performance: {
hints: false,
},
};
if (process.env.RUN_ENV === 'PRODUCTION') {
let entry = ['./index'];
config.externals = [
{
vue: {
root: 'Vue',
commonjs2: 'vue',
commonjs: 'vue',
amd: 'vue',
module: 'vue',
},
const entry = ['./index'];
config.externals = {
vue: {
root: 'Vue',
commonjs2: 'vue',
commonjs: 'vue',
amd: 'vue',
},
];
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.optimization = {
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
warnings: false,
},
sourceMap: true,
}),
],
};
// Development
const uncompressedConfig = merge({}, config, {
const uncompressedConfig = webpackMerge({}, config, {
entry: {
[entryName]: entry,
[distFileBaseName]: entry,
},
mode: 'development',
plugins: [
@ -240,12 +232,13 @@ All rights reserved.
});
// Production
const prodConfig = merge({}, config, {
const prodConfig = webpackMerge({}, config, {
entry: {
[`${entryName}.min`]: entry,
[`${distFileBaseName}.min`]: entry,
},
mode: 'production',
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.LoaderOptionsPlugin({
minimize: true,
}),
@ -254,15 +247,14 @@ All rights reserved.
}),
],
optimization: {
minimize: true,
minimizer: [new CssMinimizerPlugin({})],
minimizer: [new OptimizeCSSAssetsPlugin({})],
},
});
return [prodConfig, uncompressedConfig];
}
return [config];
return config;
}
getWebpackConfig.webpack = webpack;

View File

@ -1,10 +1,15 @@
/* eslint-disable no-console */
const { getProjectPath, getConfig } = require('./utils/projectHelper');
const { getProjectPath, injectRequire } = require('./utils/projectHelper');
injectRequire();
// const install = require('./install')
const runCmd = require('./runCmd');
const getBabelCommonConfig = require('./getBabelCommonConfig');
const merge2 = require('merge2');
const { execSync } = require('child_process');
const through2 = require('through2');
const transformLess = require('./transformLess');
const webpack = require('webpack');
const babel = require('gulp-babel');
const argv = require('minimist')(process.argv.slice(2));
@ -21,26 +26,16 @@ const ts = require('gulp-typescript');
const gulp = require('gulp');
const fs = require('fs');
const rimraf = require('rimraf');
const tsConfig = require('./getTSCommonConfig')();
const replaceLib = require('./replaceLib');
const stripCode = require('gulp-strip-code');
const compareVersions = require('compare-versions');
const getTSCommonConfig = require('./getTSCommonConfig');
const replaceLib = require('./replaceLib');
const sortApiTable = require('./sortApiTable');
const { glob } = require('glob');
const packageJson = require(getProjectPath('package.json'));
const tsDefaultReporter = ts.reporter.defaultReporter();
const cwd = process.cwd();
const libDir = getProjectPath('lib');
const esDir = getProjectPath('es');
const localeDir = getProjectPath('locale');
const tsConfig = getTSCommonConfig();
// FIXME: hard code, not find typescript can modify the path resolution
const localeDts = `import type { Locale } from '../lib/locale-provider';
declare const localeValues: Locale;
export default localeValues;`;
function dist(done) {
rimraf.sync(path.join(cwd, 'dist'));
@ -56,17 +51,11 @@ function dist(done) {
}
const info = stats.toJson();
const { dist: { finalize } = {}, bail } = getConfig();
if (stats.hasErrors()) {
(info.errors || []).forEach(error => {
console.error(error);
});
// https://github.com/ant-design/ant-design/pull/31662
if (bail) {
process.exit(1);
}
console.error(info.errors);
}
if (stats.hasWarnings()) {
console.warn(info.warnings);
}
@ -81,11 +70,6 @@ function dist(done) {
version: false,
});
console.log(buildInfo);
// Additional process of dist finalize
if (finalize) {
console.log('[Dist] Finalization...');
finalize();
}
done(0);
});
}
@ -96,7 +80,7 @@ function compileTs(stream) {
return stream
.pipe(ts(tsConfig))
.js.pipe(
through2.obj(function (file, encoding, next) {
through2.obj(function(file, encoding, next) {
// console.log(file.path, file.base);
file.path = file.path.replace(/\.[jt]sx$/, '.js');
this.push(file);
@ -114,11 +98,6 @@ gulp.task('tsc', () =>
),
);
gulp.task('clean', () => {
rimraf.sync(getProjectPath('_site'));
rimraf.sync(getProjectPath('_data'));
});
function babelify(js, modules) {
const babelConfig = getBabelCommonConfig(modules);
babelConfig.babelrc = false;
@ -126,51 +105,64 @@ function babelify(js, modules) {
if (modules === false) {
babelConfig.plugins.push(replaceLib);
}
const stream = js.pipe(babel(babelConfig)).pipe(
let stream = js.pipe(babel(babelConfig)).pipe(
through2.obj(function z(file, encoding, next) {
this.push(file.clone());
if (modules !== false) {
if (file.path.match(/\/style\/index\.(js|jsx|ts|tsx)$/)) {
const content = file.contents.toString(encoding);
file.contents = Buffer.from(
content
.replace(/lodash-es/g, 'lodash')
.replace(/@ant-design\/icons-vue/g, '@ant-design/icons-vue/lib/icons'),
content.replace(/\/style\/?'/g, "/style/css'").replace(/\.less/g, '.css'),
);
file.path = file.path.replace(/index\.(js|jsx|ts|tsx)$/, 'css.js');
this.push(file);
next();
} else {
next();
}
next();
}),
);
if (modules === false) {
stream = stream.pipe(
stripCode({
start_comment: '@remove-on-es-build-begin',
end_comment: '@remove-on-es-build-end',
}),
);
}
return stream.pipe(gulp.dest(modules === false ? esDir : libDir));
}
function compile(modules) {
const { compile: { transformTSFile, transformFile } = {} } = getConfig();
rimraf.sync(modules !== false ? libDir : esDir);
const less = gulp
.src(['components/**/*.less'])
.pipe(
through2.obj(function(file, encoding, next) {
this.push(file.clone());
if (
file.path.match(/\/style\/index\.less$/) ||
file.path.match(/\/style\/v2-compatible-reset\.less$/)
) {
transformLess(file.path)
.then(css => {
file.contents = Buffer.from(css);
file.path = file.path.replace(/\.less$/, '.css');
this.push(file);
next();
})
.catch(e => {
console.error(e);
});
} else {
next();
}
}),
)
.pipe(gulp.dest(modules === false ? esDir : libDir));
const assets = gulp
.src(['components/**/*.@(png|svg)'])
.pipe(gulp.dest(modules === false ? esDir : libDir));
let error = 0;
// =============================== FILE ===============================
let transformFileStream;
if (transformFile) {
transformFileStream = gulp
.src(['components/**/*.tsx'])
.pipe(
through2.obj(function (file, encoding, next) {
let nextFile = transformFile(file) || file;
nextFile = Array.isArray(nextFile) ? nextFile : [nextFile];
nextFile.forEach(f => this.push(f));
next();
}),
)
.pipe(gulp.dest(modules === false ? esDir : libDir));
}
// ================================ TS ================================
const source = [
'components/**/*.js',
'components/**/*.jsx',
@ -180,29 +172,7 @@ function compile(modules) {
'!components/*/__tests__/*',
];
// Strip content if needed
let sourceStream = gulp.src(source);
if (modules === false) {
sourceStream = sourceStream.pipe(
stripCode({
start_comment: '@remove-on-es-build-begin',
end_comment: '@remove-on-es-build-end',
}),
);
}
if (transformTSFile) {
sourceStream = sourceStream.pipe(
through2.obj(function (file, encoding, next) {
let nextFile = transformTSFile(file) || file;
nextFile = Array.isArray(nextFile) ? nextFile : [nextFile];
nextFile.forEach(f => this.push(f));
next();
}),
);
}
const tsResult = sourceStream.pipe(
const tsResult = gulp.src(source).pipe(
ts(tsConfig, {
error(e) {
tsDefaultReporter.error(e);
@ -222,26 +192,7 @@ function compile(modules) {
tsResult.on('end', check);
const tsFilesStream = babelify(tsResult.js, modules);
const tsd = tsResult.dts.pipe(gulp.dest(modules === false ? esDir : libDir));
return merge2([tsFilesStream, tsd, assets, transformFileStream].filter(s => s));
}
function generateLocale() {
if (!fs.existsSync(localeDir)) {
fs.mkdirSync(localeDir);
}
const localeFiles = glob.sync('components/locale/*.ts?(x)');
localeFiles.forEach(item => {
const match = item.match(/components\/locale\/(.*)\.tsx?/);
if (match) {
const locale = match[1];
fs.writeFileSync(
path.join(localeDir, `${locale}.js`),
`module.exports = require('../lib/locale/${locale}');`,
);
fs.writeFileSync(path.join(localeDir, `${locale}.d.ts`), localeDts);
}
});
return merge2([less, tsFilesStream, tsd, assets]);
}
function tag() {
@ -347,71 +298,61 @@ function publish(tagString, done) {
}
function pub(done) {
const notOk = !packageJson.version.match(/^\d+\.\d+\.\d+$/);
let tagString;
if (argv['npm-tag']) {
tagString = argv['npm-tag'];
}
if (!tagString && notOk) {
tagString = 'next';
}
if (packageJson.scripts['pre-publish']) {
runCmd('npm', ['run', 'pre-publish'], code2 => {
if (code2) {
done(code2);
return;
}
dist(code => {
if (code) {
done(code);
return;
}
const notOk = !packageJson.version.match(/^\d+\.\d+\.\d+$/);
let tagString;
if (argv['npm-tag']) {
tagString = argv['npm-tag'];
}
if (!tagString && notOk) {
tagString = 'next';
}
if (packageJson.scripts['pre-publish']) {
runCmd('npm', ['run', 'pre-publish'], code2 => {
if (code2) {
done(code2);
return;
}
publish(tagString, done);
});
} else {
publish(tagString, done);
});
} else {
publish(tagString, done);
}
}
});
}
const startTime = new Date();
gulp.task('compile-with-es', done => {
console.log('start compile at ', startTime);
console.log('[Parallel] Compile to es...');
compile(false).on('finish', done);
});
gulp.task('compile-with-lib', done => {
console.log('[Parallel] Compile to js...');
compile().on('finish', () => {
generateLocale();
done();
});
});
gulp.task('compile-finalize', done => {
// Additional process of compile finalize
const { compile: { finalize } = {} } = getConfig();
if (finalize) {
console.log('[Compile] Finalization...');
finalize();
}
done();
});
gulp.task(
'compile-with-es',
gulp.series(done => {
compile(false).on('finish', function() {
done();
});
}),
);
gulp.task(
'compile',
gulp.series(gulp.parallel('compile-with-es', 'compile-with-lib'), 'compile-finalize', done => {
console.log('end compile at ', new Date());
console.log('compile time ', (new Date() - startTime) / 1000, 's');
done();
gulp.series('compile-with-es', done => {
compile().on('finish', function() {
done();
});
}),
);
gulp.task(
'dist',
gulp.series(done => {
gulp.series('compile', done => {
dist(done);
}),
);
gulp.task(
'pub',
gulp.series('check-git', 'compile', 'dist', done => {
gulp.series('check-git', 'compile', done => {
// if (!process.env.GITHUB_TOKEN) {
// console.log('no GitHub token found, skip');
// } else {
@ -452,7 +393,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 {
@ -475,11 +416,7 @@ gulp.task(
const npmArgs = getNpmArgs();
if (npmArgs) {
for (let arg = npmArgs.shift(); arg; arg = npmArgs.shift()) {
if (
/^pu(b(l(i(sh?)?)?)?)?$/.test(arg) &&
npmArgs.indexOf('--with-antd-tools') < 0 &&
!process.env.npm_config_with_antd_tools
) {
if (/^pu(b(l(i(sh?)?)?)?)?$/.test(arg) && npmArgs.indexOf('--with-antd-tools') < 0) {
reportError();
done(1);
return;
@ -489,11 +426,3 @@ gulp.task(
done();
}),
);
gulp.task(
'sort-api-table',
gulp.series(done => {
sortApiTable();
done();
}),
);

View File

@ -0,0 +1,6 @@
const rucksack = require('rucksack-css');
const autoprefixer = require('autoprefixer');
module.exports = {
plugins: [rucksack(), autoprefixer()],
};

View File

@ -12,19 +12,6 @@ function replacePath(path) {
path.node.source.value = esModule;
}
}
// @ant-design/icons-vue/xxx => @ant-design/icons-vue/es/icons/xxx
const antdIconMatcher = /@ant-design\/icons-vue\/([^/]*)$/;
if (path.node.source && antdIconMatcher.test(path.node.source.value)) {
const esModule = path.node.source.value.replace(
antdIconMatcher,
(_, iconName) => `@ant-design/icons-vue/es/icons/${iconName}`,
);
const esPath = dirname(getProjectPath('node_modules', esModule));
if (fs.existsSync(esPath)) {
path.node.source.value = esModule;
}
}
}
function replaceLib() {

View File

@ -1,17 +1,9 @@
'use strict';
const isWindows = require('is-windows');
const getRunCmdEnv = require('./utils/getRunCmdEnv');
function runCmd(cmd, _args, fn) {
const args = _args || [];
if (isWindows()) {
args.unshift(cmd);
args.unshift('/c');
cmd = process.env.ComSpec;
}
const runner = require('child_process').spawn(cmd, args, {
// keep color
stdio: 'inherit',

View File

@ -1,165 +0,0 @@
const program = require('commander');
const majo = require('majo');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const unified = require('unified');
const parse = require('remark-parse');
const stringify = require('remark-stringify');
const yamlConfig = require('remark-yaml-config');
const frontmatter = require('remark-frontmatter');
let fileAPIs = {};
const remarkWithYaml = unified()
.use(parse)
.use(stringify, {
paddedTable: false,
listItemIndent: 1,
stringLength: () => 3,
})
.use(frontmatter)
.use(yamlConfig);
const stream = majo.majo();
function getCellValue(node) {
return node.children[0].children[0].value;
}
// from small to large
const sizeBreakPoints = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
const whiteMethodList = ['afterChange', 'beforeChange'];
const groups = {
isDynamic: val => /^on[A-Z]/.test(val) || whiteMethodList.indexOf(val) > -1,
isSize: val => sizeBreakPoints.indexOf(val) > -1,
};
function asciiSort(prev, next) {
if (prev > next) {
return 1;
}
if (prev < next) {
return -1;
}
return 0;
}
// follow the alphabet order
function alphabetSort(nodes) {
// use toLowerCase to keep `case insensitive`
return nodes.sort((...comparison) =>
asciiSort(...comparison.map(val => getCellValue(val).toLowerCase())),
);
}
function sizeSort(nodes) {
return nodes.sort((...comparison) =>
asciiSort(...comparison.map(val => sizeBreakPoints.indexOf(getCellValue(val).toLowerCase()))),
);
}
function sort(ast, filename) {
const nameMatch = filename.match(/^components\/([^/]*)\//);
const componentName = nameMatch[1];
fileAPIs[componentName] = fileAPIs[componentName] || {
static: new Set(),
size: new Set(),
dynamic: new Set(),
};
ast.children.forEach(child => {
const staticProps = [];
// prefix with `on`
const dynamicProps = [];
// one of ['xs', 'sm', 'md', 'lg', 'xl']
const sizeProps = [];
// find table markdown type
if (child.type === 'table') {
// slice will create new array, so sort can affect the original array.
// slice(1) cut down the thead
child.children.slice(1).forEach(node => {
const value = getCellValue(node);
if (groups.isDynamic(value)) {
dynamicProps.push(node);
fileAPIs[componentName].dynamic.add(value);
} else if (groups.isSize(value)) {
sizeProps.push(node);
fileAPIs[componentName].size.add(value);
} else {
staticProps.push(node);
fileAPIs[componentName].static.add(value);
}
});
// eslint-disable-next-line
child.children = [
child.children[0],
...alphabetSort(staticProps),
...sizeSort(sizeProps),
...alphabetSort(dynamicProps),
];
}
});
return ast;
}
function sortAPI(md, filename) {
return remarkWithYaml.stringify(sort(remarkWithYaml.parse(md), filename));
}
function sortMiddleware(ctx) {
Object.keys(ctx.files).forEach(filename => {
const content = ctx.fileContents(filename);
ctx.writeContents(filename, sortAPI(content, filename));
});
}
module.exports = () => {
fileAPIs = {};
program
.version('0.1.0')
.option(
'-f, --file [file]',
'Specify which file to be transformed',
// default value
'components/**/index.+(zh-CN|en-US).md',
)
.option('-o, --output [output]', 'Specify component api output path', '~component-api.json')
.parse(process.argv);
// Get the markdown file all need to be transformed
/* eslint-disable no-console */
stream
.source(program.file)
.use(sortMiddleware)
.dest('.')
.then(() => {
if (program.output) {
const data = {};
Object.keys(fileAPIs).forEach(componentName => {
data[componentName] = {
static: [...fileAPIs[componentName].static],
size: [...fileAPIs[componentName].size],
dynamic: [...fileAPIs[componentName].dynamic],
};
});
const reportPath = path.resolve(program.output);
fs.writeFileSync(reportPath, JSON.stringify(data, null, 2), 'utf8');
console.log(chalk.cyan(`API list file: ${reportPath}`));
}
})
.then(() => {
console.log(chalk.green(`sort ant-design-vue api successfully!`));
});
/* eslint-enable no-console */
};

View File

@ -0,0 +1,33 @@
const less = require('less');
const { readFileSync } = require('fs');
const path = require('path');
const postcss = require('postcss');
const NpmImportPlugin = require('less-plugin-npm-import');
const postcssConfig = require('./postcssConfig');
function transformLess(lessFile, config = {}) {
const { cwd = process.cwd() } = config;
const resolvedLessFile = path.resolve(cwd, lessFile);
let data = readFileSync(resolvedLessFile, 'utf-8');
data = data.replace(/^\uFEFF/, '');
// Do less compile
const lessOpts = {
paths: [path.dirname(resolvedLessFile)],
filename: resolvedLessFile,
plugins: [new NpmImportPlugin({ prefix: '~' })],
javascriptEnabled: true,
};
return less
.render(data, lessOpts)
.then(result => {
const source = result.css;
return postcss(postcssConfig.plugins).process(source, { from: undefined });
})
.then(r => {
return r.css;
});
}
module.exports = transformLess;

View File

@ -24,13 +24,13 @@ class CleanUpStatsPlugin {
apply(compiler) {
compiler.hooks.done.tap('CleanUpStatsPlugin', stats => {
const { children, warnings } = stats.compilation;
const { children } = stats.compilation;
if (Array.isArray(children)) {
stats.compilation.children = children.filter(child => this.shouldPickStatChild(child));
}
if (Array.isArray(warnings)) {
stats.compilation.warnings = warnings.filter(message => this.shouldPickWarning(message));
}
// if (Array.isArray(warnings)) {
// stats.compilation.warnings = warnings.filter(message => this.shouldPickWarning(message));
// }
});
}
}

View File

@ -2,11 +2,6 @@
// NOTE: the following code was partially adopted from https://github.com/iarna/in-publish
module.exports = function getNpmArgs() {
// https://github.com/iarna/in-publish/pull/14
if (process.env.npm_command) {
return [process.env.npm_command];
}
let npmArgv = null;
try {

View File

@ -1,7 +1,10 @@
const fs = require('fs');
module.exports = function getChangelog(file, version) {
const lines = fs.readFileSync(file).toString().split('\n');
const lines = fs
.readFileSync(file)
.toString()
.split('\n');
const changeLog = [];
const startPattern = new RegExp(`^## ${version}`);
const stopPattern = /^## /; // 前一个版本

View File

@ -1,7 +1,6 @@
'use strict';
const path = require('path');
const isWindows = require('is-windows');
module.exports = function getRunCmdEnv() {
const env = {};
@ -12,12 +11,16 @@ module.exports = function getRunCmdEnv() {
const nodeModulesBinDir = path.join(__dirname, '../../node_modules/.bin');
Object.entries(env)
.filter(v => v.slice(0, 1).pop().toLowerCase() === 'path')
.filter(
v =>
v
.slice(0, 1)
.pop()
.toLowerCase() === 'path',
)
.forEach(v => {
const key = v.slice(0, 1).pop();
env[key] = env[key]
? `${nodeModulesBinDir}${isWindows() ? ';' : ':'}${env[key]}`
: nodeModulesBinDir;
env[key] = env[key] ? `${nodeModulesBinDir}:${env[key]}` : nodeModulesBinDir;
});
return env;
};

View File

@ -13,7 +13,6 @@ function resolve(moduleName) {
// We need hack the require to ensure use package module first
// For example, `typescript` is required by `gulp-typescript` but provided by `antd`
// we do not need for ant-design-vue
let injected = false;
function injectRequire() {
if (injected) return;
@ -21,7 +20,7 @@ function injectRequire() {
const Module = require('module');
const oriRequire = Module.prototype.require;
Module.prototype.require = function (...args) {
Module.prototype.require = function(...args) {
const moduleName = args[0];
try {
return oriRequire.apply(this, args);
@ -46,35 +45,9 @@ function getConfig() {
return {};
}
/**
* 是否存在可用的browserslist config
* https://github.com/browserslist/browserslist#queries
* @returns
*/
function isThereHaveBrowserslistConfig() {
try {
const packageJson = require(getProjectPath('package.json'));
if (packageJson.browserslist) {
return true;
}
} catch (e) {
//
}
if (fs.existsSync(getProjectPath('.browserslistrc'))) {
return true;
}
if (fs.existsSync(getProjectPath('browserslist'))) {
return true;
}
// parent项目的配置支持需要再补充
// ROWSERSLIST ROWSERSLIST_ENV 变量的形式,需要再补充。
return false;
}
module.exports = {
getProjectPath,
resolve,
injectRequire,
getConfig,
isThereHaveBrowserslistConfig,
};

1
antdv-demo Submodule

@ -0,0 +1 @@
Subproject commit dff6de34d15667d973f954b6c16a2b60214bc858

View File

@ -1,9 +1,9 @@
module.exports = {
env: {
test: {
presets: [['@babel/preset-env']],
presets: [['@babel/preset-env', { targets: { node: true } }]],
plugins: [
['@vue/babel-plugin-jsx', { mergeProps: false, enableObjectSlots: false }],
['@vue/babel-plugin-jsx', { mergeProps: false }],
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-transform-object-assign',
'@babel/plugin-proposal-object-rest-spread',
@ -12,7 +12,6 @@ module.exports = {
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-runtime',
'transform-require-context',
],
},
},

View File

@ -1,123 +0,0 @@
import type { ExtractPropTypes, PropType } from 'vue';
import { shallowRef, onMounted, defineComponent, onBeforeUnmount } from 'vue';
import Button from '../button';
import type { ButtonProps } from '../button';
import type { LegacyButtonType } from '../button/buttonTypes';
import { convertLegacyProps } from '../button/buttonTypes';
import useDestroyed from './hooks/useDestroyed';
import { objectType } from './type';
import { findDOMNode } from './props-util';
const actionButtonProps = {
type: {
type: String as PropType<LegacyButtonType>,
},
actionFn: Function as PropType<(...args: any[]) => any | PromiseLike<any>>,
close: Function,
autofocus: Boolean,
prefixCls: String,
buttonProps: objectType<ButtonProps>(),
emitEvent: Boolean,
quitOnNullishReturnValue: Boolean,
};
export type ActionButtonProps = ExtractPropTypes<typeof actionButtonProps>;
function isThenable<T>(thing?: PromiseLike<T>): boolean {
return !!(thing && thing.then);
}
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'ActionButton',
props: actionButtonProps,
setup(props, { slots }) {
const clickedRef = shallowRef<boolean>(false);
const buttonRef = shallowRef();
const loading = shallowRef(false);
let timeoutId: any;
const isDestroyed = useDestroyed();
onMounted(() => {
if (props.autofocus) {
timeoutId = setTimeout(() => findDOMNode(buttonRef.value)?.focus?.());
}
});
onBeforeUnmount(() => {
clearTimeout(timeoutId);
});
const onInternalClose = (...args: any[]) => {
props.close?.(...args);
};
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
if (!isThenable(returnValueOfOnOk)) {
return;
}
loading.value = true;
returnValueOfOnOk!.then(
(...args: any[]) => {
if (!isDestroyed.value) {
loading.value = false;
}
onInternalClose(...args);
clickedRef.value = false;
},
(e: Error) => {
// See: https://github.com/ant-design/ant-design/issues/6183
if (!isDestroyed.value) {
loading.value = false;
}
clickedRef.value = false;
return Promise.reject(e);
},
);
};
const onClick = (e: MouseEvent) => {
const { actionFn } = props;
if (clickedRef.value) {
return;
}
clickedRef.value = true;
if (!actionFn) {
onInternalClose();
return;
}
let returnValueOfOnOk: PromiseLike<any>;
if (props.emitEvent) {
returnValueOfOnOk = actionFn(e);
if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
clickedRef.value = false;
onInternalClose(e);
return;
}
} else if (actionFn.length) {
returnValueOfOnOk = actionFn(props.close);
// https://github.com/ant-design/ant-design/issues/23358
clickedRef.value = false;
} else {
returnValueOfOnOk = actionFn();
if (!returnValueOfOnOk) {
onInternalClose();
return;
}
}
handlePromiseOnOk(returnValueOfOnOk);
};
return () => {
const { type, prefixCls, buttonProps } = props;
return (
<Button
{...convertLegacyProps(type)}
onClick={onClick}
loading={loading.value}
prefixCls={prefixCls}
{...buttonProps}
ref={buttonRef}
v-slots={slots}
></Button>
);
};
},
});

View File

@ -1,167 +1,50 @@
import type { PropType } from 'vue';
import { computed, defineComponent, shallowRef, ref, watch } from 'vue';
import { defineComponent, ref, 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 = ref(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

@ -0,0 +1,44 @@
import { nextTick } from 'vue';
import { getOptionProps } from './props-util';
export default {
methods: {
setState(state = {}, callback) {
let newState = typeof state === 'function' ? state(this.$data, this.$props) : state;
if (this.getDerivedStateFromProps) {
const s = this.getDerivedStateFromProps(getOptionProps(this), {
...this.$data,
...newState,
});
if (s === null) {
return;
} else {
newState = { ...newState, ...(s || {}) };
}
}
Object.assign(this.$data, newState);
if (this._.isMounted) {
this.$forceUpdate();
}
nextTick(() => {
callback && callback();
});
},
__emit() {
// 直接调用事件底层组件不需要vueTool记录events
const args = [].slice.call(arguments, 0);
let eventName = args[0];
eventName = `on${eventName[0].toUpperCase()}${eventName.substring(1)}`;
const event = this.$props[eventName] || this.$attrs[eventName];
if (args.length && event) {
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
event[i](...args.slice(1));
}
} else {
event(...args.slice(1));
}
}
},
},
};

View File

@ -1,45 +0,0 @@
import { nextTick } from 'vue';
import { getOptionProps } from './props-util';
export default {
methods: {
setState(state = {}, callback: () => any) {
let newState = typeof state === 'function' ? state(this.$data, this.$props) : state;
if (this.getDerivedStateFromProps) {
const s = this.getDerivedStateFromProps(getOptionProps(this), {
...this.$data,
...newState,
});
if (s === null) {
return;
} else {
newState = { ...newState, ...(s || {}) };
}
}
Object.assign(this.$data, newState);
if (this._.isMounted) {
this.$forceUpdate();
}
nextTick(() => {
callback && callback();
});
},
__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)}`;
const event = this.$props[eventName] || this.$attrs[eventName];
if (args.length && event) {
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
event[i](...args.slice(1));
}
} else {
event(...args.slice(1));
}
}
},
},
};

View File

@ -0,0 +1,44 @@
import { nextTick } from 'vue';
import { getOptionProps } from './props-util';
export default {
methods: {
setState(state = {}, callback) {
let newState = typeof state === 'function' ? state(this, this.$props) : state;
if (this.getDerivedStateFromProps) {
const s = this.getDerivedStateFromProps(getOptionProps(this), {
...this,
...newState,
});
if (s === null) {
return;
} else {
newState = { ...newState, ...(s || {}) };
}
}
Object.assign(this, newState);
if (this._.isMounted) {
this.$forceUpdate();
}
nextTick(() => {
callback && callback();
});
},
__emit() {
// 直接调用事件底层组件不需要vueTool记录events
const args = [].slice.call(arguments, 0);
let eventName = args[0];
eventName = `on${eventName[0].toUpperCase()}${eventName.substring(1)}`;
const event = this.$props[eventName] || this.$attrs[eventName];
if (args.length && event) {
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
event[i](...args.slice(1));
}
} else {
event(...args.slice(1));
}
}
},
},
};

View File

@ -1,19 +0,0 @@
export type FocusEventHandler = (e: FocusEvent) => void;
export type MouseEventHandler = (e: MouseEvent) => void;
export type KeyboardEventHandler = (e: KeyboardEvent) => void;
export type CompositionEventHandler = (e: CompositionEvent) => void;
export type ClipboardEventHandler = (e: ClipboardEvent) => void;
export type ChangeEventHandler = (e: ChangeEvent) => void;
export type WheelEventHandler = (e: WheelEvent) => void;
export type ChangeEvent = Event & {
target: {
value?: string | undefined;
};
};
export type CheckboxChangeEvent = Event & {
target: {
checked?: boolean;
};
};
export type EventHandler = (...args: any[]) => void;

View File

@ -0,0 +1,48 @@
import PropTypes from './vue-types';
import { defineComponent, nextTick, Teleport } from 'vue';
export default defineComponent({
name: 'Portal',
props: {
getContainer: PropTypes.func.isRequired,
children: PropTypes.any.isRequired,
didUpdate: PropTypes.func,
},
data() {
this._container = null;
return {};
},
mounted() {
this.createContainer();
},
updated() {
const { didUpdate } = this.$props;
if (didUpdate) {
nextTick(() => {
didUpdate(this.$props);
});
}
},
beforeUnmount() {
this.removeContainer();
},
methods: {
createContainer() {
this._container = this.$props.getContainer();
this.$forceUpdate();
},
removeContainer() {
if (this._container && this._container.parentNode) {
this._container.parentNode.removeChild(this._container);
}
},
},
render() {
if (this._container) {
return <Teleport to={this._container}>{this.$props.children}</Teleport>;
}
return null;
},
});

View File

@ -1,72 +0,0 @@
import PropTypes from './vue-types';
import {
defineComponent,
nextTick,
onBeforeMount,
onMounted,
onUpdated,
Teleport,
watch,
} from 'vue';
import { useInjectPortal } from '../vc-trigger/context';
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'Portal',
inheritAttrs: false,
props: {
getContainer: PropTypes.func.isRequired,
didUpdate: Function,
},
setup(props, { slots }) {
let isSSR = true;
// getContainer
let container: HTMLElement;
const { shouldRender } = useInjectPortal();
function setContainer() {
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();
}
if (container) {
stopWatch();
}
});
onUpdated(() => {
nextTick(() => {
if (shouldRender.value) {
props.didUpdate?.(props);
}
});
});
// onBeforeUnmount(() => {
// if (container && container.parentNode) {
// container.parentNode.removeChild(container);
// }
// });
return () => {
if (!shouldRender.value) return null;
if (isSSR) {
return slots.default?.();
}
return container ? <Teleport to={container} v-slots={slots}></Teleport> : null;
};
},
});

View File

@ -0,0 +1,151 @@
import PropTypes from './vue-types';
import switchScrollingEffect from './switchScrollingEffect';
import setStyle from './setStyle';
import Portal from './Portal';
import { defineComponent } from 'vue';
let openCount = 0;
const windowIsUndefined = !(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
// https://github.com/ant-design/ant-design/issues/19340
// https://github.com/ant-design/ant-design/issues/19332
let cacheOverflow = {};
export default defineComponent({
name: 'PortalWrapper',
props: {
wrapperClassName: PropTypes.string,
forceRender: PropTypes.looseBool,
getContainer: PropTypes.any,
children: PropTypes.func,
visible: PropTypes.looseBool,
},
data() {
this._component = null;
const { visible } = this.$props;
openCount = visible ? openCount + 1 : openCount;
return {};
},
watch: {
visible(val) {
openCount = val ? openCount + 1 : openCount - 1;
},
getContainer(getContainer, prevGetContainer) {
const getContainerIsFunc =
typeof getContainer === 'function' && typeof prevGetContainer === 'function';
if (
getContainerIsFunc
? getContainer.toString() !== prevGetContainer.toString()
: getContainer !== prevGetContainer
) {
this.removeCurrentContainer(false);
}
},
},
updated() {
this.setWrapperClassName();
},
beforeUnmount() {
const { visible } = this.$props;
// 离开时不会 render 导到离开时数值不变,改用 func 。。
openCount = visible && openCount ? openCount - 1 : openCount;
this.removeCurrentContainer(visible);
},
methods: {
getParent() {
const { getContainer } = this.$props;
if (getContainer) {
if (typeof getContainer === 'string') {
return document.querySelectorAll(getContainer)[0];
}
if (typeof getContainer === 'function') {
return getContainer();
}
if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) {
return getContainer;
}
}
return document.body;
},
getDomContainer() {
if (windowIsUndefined) {
return null;
}
if (!this.container) {
this.container = document.createElement('div');
const parent = this.getParent();
if (parent) {
parent.appendChild(this.container);
}
}
this.setWrapperClassName();
return this.container;
},
setWrapperClassName() {
const { wrapperClassName } = this.$props;
if (this.container && wrapperClassName && wrapperClassName !== this.container.className) {
this.container.className = wrapperClassName;
}
},
savePortal(c) {
// Warning: don't rename _component
// https://github.com/react-component/util/pull/65#discussion_r352407916
this._component = c;
},
removeCurrentContainer() {
this.container = null;
this._component = null;
},
/**
* Enhance ./switchScrollingEffect
* 1. Simulate document body scroll bar with
* 2. Record body has overflow style and recover when all of PortalWrapper invisible
* 3. Disable body scroll when PortalWrapper has open
*
* @memberof PortalWrapper
*/
switchScrollingEffect() {
if (openCount === 1 && !Object.keys(cacheOverflow).length) {
switchScrollingEffect();
// Must be set after switchScrollingEffect
cacheOverflow = setStyle({
overflow: 'hidden',
overflowX: 'hidden',
overflowY: 'hidden',
});
} else if (!openCount) {
setStyle(cacheOverflow);
cacheOverflow = {};
switchScrollingEffect(true);
}
},
},
render() {
const { children, forceRender, visible } = this.$props;
let portal = null;
const childProps = {
getOpenCount: () => openCount,
getContainer: this.getDomContainer,
switchScrollingEffect: this.switchScrollingEffect,
};
if (forceRender || visible || this._component) {
portal = (
<Portal
getContainer={this.getDomContainer}
children={children(childProps)}
ref={this.savePortal}
></Portal>
);
}
return portal;
},
});

View File

@ -1,191 +0,0 @@
import PropTypes from './vue-types';
import Portal from './Portal';
import {
defineComponent,
shallowRef,
watch,
onMounted,
onBeforeUnmount,
onUpdated,
nextTick,
computed,
} from 'vue';
import canUseDom from './canUseDom';
import raf from './raf';
import { booleanType } from './type';
import useScrollLocker from './hooks/useScrollLocker';
let openCount = 0;
const supportDom = canUseDom();
/** @private Test usage only */
export function getOpenCount() {
return process.env.NODE_ENV === 'test' ? openCount : 0;
}
const getParent = (getContainer: GetContainer) => {
if (!supportDom) {
return null;
}
if (getContainer) {
if (typeof getContainer === 'string') {
return document.querySelectorAll(getContainer)[0] as HTMLElement;
}
if (typeof getContainer === 'function') {
return getContainer();
}
if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) {
return getContainer;
}
}
return document.body;
};
export type GetContainer = string | HTMLElement | (() => HTMLElement);
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'PortalWrapper',
inheritAttrs: false,
props: {
wrapperClassName: String,
forceRender: { type: Boolean, default: undefined },
getContainer: PropTypes.any,
visible: { type: Boolean, default: undefined },
autoLock: booleanType(),
didUpdate: Function,
},
setup(props, { slots }) {
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 = null;
};
let parent: HTMLElement = null;
const attachToParent = (force = false) => {
if (force || (container.value && !container.value.parentNode)) {
parent = getParent(props.getContainer);
if (parent) {
parent.appendChild(container.value);
return true;
}
return false;
}
return true;
};
const getContainer = () => {
if (!supportDom) {
return null;
}
if (!container.value) {
container.value = defaultContainer;
attachToParent(true);
}
setWrapperClassName();
return container.value;
};
const setWrapperClassName = () => {
const { wrapperClassName } = props;
if (container.value && wrapperClassName && wrapperClassName !== container.value.className) {
container.value.className = wrapperClassName;
}
};
onUpdated(() => {
setWrapperClassName();
attachToParent();
});
useScrollLocker(
computed(() => {
return (
props.autoLock &&
props.visible &&
canUseDom() &&
(container.value === document.body || container.value === defaultContainer)
);
}),
);
onMounted(() => {
let init = false;
watch(
[() => props.visible, () => props.getContainer],
([visible, getContainer], [prevVisible, prevGetContainer]) => {
// Update count
if (supportDom) {
parent = getParent(props.getContainer);
if (parent === document.body) {
if (visible && !prevVisible) {
openCount += 1;
} else if (init) {
openCount -= 1;
}
}
}
if (init) {
// Clean up container if needed
const getContainerIsFunc =
typeof getContainer === 'function' && typeof prevGetContainer === 'function';
if (
getContainerIsFunc
? getContainer.toString() !== prevGetContainer.toString()
: getContainer !== prevGetContainer
) {
removeCurrentContainer();
}
}
init = true;
},
{ immediate: true, flush: 'post' },
);
nextTick(() => {
if (!attachToParent()) {
rafId.value = raf(() => {
triggerUpdate.value += 1;
});
}
});
});
onBeforeUnmount(() => {
const { visible } = props;
if (supportDom && parent === document.body) {
// render func
openCount = visible && openCount ? openCount - 1 : openCount;
}
removeCurrentContainer();
raf.cancel(rafId.value);
});
return () => {
const { forceRender, visible } = props;
let portal = null;
const childProps = {
getOpenCount: () => openCount,
getContainer,
};
if (triggerUpdate.value && (forceRender || visible || componentRef.value)) {
portal = (
<Portal
getContainer={getContainer}
ref={componentRef}
didUpdate={props.didUpdate}
v-slots={{ default: () => slots.default?.(childProps) }}
></Portal>
);
}
return portal;
};
},
});

View File

@ -1,13 +0,0 @@
import { defineComponent } from 'vue';
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'Portal',
inheritAttrs: false,
props: ['getContainer'],
setup(_props, { slots }) {
return () => {
return slots.default?.();
};
},
});

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,8 +0,0 @@
import UnreachableException from '../unreachableException';
describe('UnreachableException', () => {
it('error thrown matches snapshot', () => {
const exception = new UnreachableException('some value');
expect(exception.error.message).toMatchInlineSnapshot(`"unreachable case: \\"some 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

@ -1,5 +0,0 @@
function canUseDom() {
return !!(typeof window !== 'undefined' && window.document && window.document.createElement);
}
export default canUseDom;

View File

@ -1,50 +0,0 @@
import { nextTick } from 'vue';
import { addClass, removeClass } from '../vc-util/Dom/class';
import type { CSSMotionProps } from './transition';
const collapseMotion = (name = 'ant-motion-collapse', appear = true): CSSMotionProps => {
return {
name,
appear,
css: true,
onBeforeEnter: (node: HTMLDivElement) => {
node.style.height = '0px';
node.style.opacity = '0';
addClass(node, name);
},
onEnter: (node: HTMLDivElement) => {
nextTick(() => {
node.style.height = `${node.scrollHeight}px`;
node.style.opacity = '1';
});
},
onAfterEnter: (node: HTMLDivElement) => {
if (node) {
removeClass(node, name);
node.style.height = null;
node.style.opacity = null;
}
},
onBeforeLeave: (node: HTMLDivElement) => {
addClass(node, name);
node.style.height = `${node.offsetHeight}px`;
node.style.opacity = null;
},
onLeave: (node: HTMLDivElement) => {
setTimeout(() => {
node.style.height = '0px';
node.style.opacity = '0';
});
},
onAfterLeave: (node: HTMLDivElement) => {
if (node) {
removeClass(node, name);
if (node.style) {
node.style.height = null;
node.style.opacity = null;
}
}
},
};
};
export default collapseMotion;

View File

@ -1,34 +1,22 @@
import type { PresetColorKey } from '../theme/interface';
import { PresetColors } from '../theme/interface';
import { ElementOf, tuple } from './type';
type InverseColor = `${PresetColorKey}-inverse`;
const inverseColors = PresetColors.map<InverseColor>(color => `${color}-inverse`);
export const PresetStatusColorTypes = tuple('success', 'processing', 'error', 'default', 'warning');
export const PresetStatusColorTypes = [
'success',
'processing',
'error',
'default',
'warning',
] as const;
export const PresetColorTypes = tuple(
'pink',
'red',
'yellow',
'orange',
'cyan',
'green',
'blue',
'purple',
'geekblue',
'magenta',
'volcano',
'gold',
'lime',
);
export type PresetColorType = PresetColorKey | InverseColor;
export type PresetStatusColorType = (typeof PresetStatusColorTypes)[number];
/**
* determine if the color keyword belongs to the `Ant Design` {@link PresetColors}.
* @param color color to be judged
* @param includeInverse whether to include reversed colors
*/
export function isPresetColor(color?: any, includeInverse = true) {
if (includeInverse) {
return [...inverseColors, ...PresetColors].includes(color);
}
return PresetColors.includes(color);
}
export function isPresetStatusColor(color?: any): color is PresetStatusColorType {
return PresetStatusColorTypes.includes(color);
}
export type PresetColorType = ElementOf<typeof PresetColorTypes>;
export type PresetStatusColorType = ElementOf<typeof PresetStatusColorTypes>;

View File

@ -1,168 +0,0 @@
/**
* source by `component-classes`
* https://github.com/component/classes.git
*/
import indexOf from 'lodash-es/indexOf';
/**
* Whitespace regexp.
*/
const re = /\s+/;
export class ClassList {
el: Element;
list: DOMTokenList;
constructor(el: Element) {
if (!el || !el.nodeType) {
throw new Error('A DOM element reference is required');
}
this.el = el;
this.list = el.classList;
}
array() {
const className = this.el.getAttribute('class') || '';
const str = className.replace(/^\s+|\s+$/g, '');
const arr = str.split(re);
if ('' === arr[0]) arr.shift();
return arr;
}
/**
* Add class `name` if not already present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
add(name: string): ClassList {
// classList
if (this.list) {
this.list.add(name);
return this;
}
// fallback
const arr = this.array();
const i = indexOf(arr, name);
if (!~i) arr.push(name);
this.el.className = arr.join(' ');
return this;
}
/**
* Remove class `name` when present, or
* pass a regular expression to remove
* any which match.
*
* @param {String|RegExp} name
* @return {ClassList}
* @api public
*/
remove(name: string | RegExp): ClassList {
if ('[object RegExp]' === toString.call(name)) {
return this._removeMatching(name as RegExp);
}
// classList
if (this.list) {
this.list.remove(name as string);
return this;
}
// fallback
const arr = this.array();
const i = indexOf(arr, name);
if (~i) arr.splice(i, 1);
this.el.className = arr.join(' ');
return this;
}
/**
* Remove all classes matching `re`.
*
* @param {RegExp} re
* @return {ClassList}
* @api private
*/
_removeMatching(re: RegExp): ClassList {
const arr = this.array();
for (let i = 0; i < arr.length; i++) {
if (re.test(arr[i])) {
this.remove(arr[i]);
}
}
return this;
}
/**
* Toggle class `name`, can force state via `force`.
*
* For browsers that support classList, but do not support `force` yet,
* the mistake will be detected and corrected.
*
* @param {String} name
* @param {Boolean} force
* @return {ClassList}
* @api public
*/
toggle(name: string, force: boolean): ClassList {
// classList
if (this.list) {
if ('undefined' !== typeof force) {
if (force !== this.list.toggle(name, force)) {
this.list.toggle(name); // toggle again to correct
}
} else {
this.list.toggle(name);
}
return this;
}
// fallback
if ('undefined' !== typeof force) {
if (!force) {
this.remove(name);
} else {
this.add(name);
}
} else {
if (this.has(name)) {
this.remove(name);
} else {
this.add(name);
}
}
return this;
}
/**
* Check if class `name` is present.
*
* @param {String} name
* @api public
*/
has(name: string) {
return this.list ? this.list.contains(name) : !!~indexOf(this.array(), name);
}
/**
* Check if class `name` is present.
*
* @param {String} name
* @api public
*/
contains(name: string) {
return this.has(name);
}
}
/**
* Wrap `el` in a `ClassList`.
*
* @param {Element} el
* @return {ClassList}
* @api public
*/
export default function (el: Element): ClassList {
return new ClassList(el);
}

View File

@ -1,120 +0,0 @@
import deselectCurrent from './toggle-selection';
interface Options {
debug?: boolean;
message?: string;
format?: string; // MIME type
onCopy?: (clipboardData: object) => void;
}
const clipboardToIE11Formatting = {
'text/plain': 'Text',
'text/html': 'Url',
default: 'Text',
};
const defaultMessage = 'Copy to clipboard: #{key}, Enter';
function format(message: string) {
const copyKey = (/mac os x/i.test(navigator.userAgent) ? '⌘' : 'Ctrl') + '+C';
return message.replace(/#{\s*key\s*}/g, copyKey);
}
function copy(text: string, options?: Options): boolean {
let message,
reselectPrevious,
range,
selection,
mark,
success = false;
if (!options) {
options = {};
}
const debug = options.debug || false;
try {
reselectPrevious = deselectCurrent();
range = document.createRange();
selection = document.getSelection();
mark = document.createElement('span');
mark.textContent = text;
// reset user styles for span element
mark.style.all = 'unset';
// prevents scrolling to the end of the page
mark.style.position = 'fixed';
mark.style.top = 0;
mark.style.clip = 'rect(0, 0, 0, 0)';
// used to preserve spaces and line breaks
mark.style.whiteSpace = 'pre';
// do not inherit user-select (it may be `none`)
mark.style.webkitUserSelect = 'text';
mark.style.MozUserSelect = 'text';
mark.style.msUserSelect = 'text';
mark.style.userSelect = 'text';
mark.addEventListener('copy', function (e) {
e.stopPropagation();
if (options.format) {
e.preventDefault();
if (typeof e.clipboardData === 'undefined') {
// IE 11
debug && console.warn('unable to use e.clipboardData');
debug && console.warn('trying IE specific stuff');
(window as any).clipboardData.clearData();
const format =
clipboardToIE11Formatting[options.format] || clipboardToIE11Formatting['default'];
(window as any).clipboardData.setData(format, text);
} else {
// all other browsers
e.clipboardData.clearData();
e.clipboardData.setData(options.format, text);
}
}
if (options.onCopy) {
e.preventDefault();
options.onCopy(e.clipboardData);
}
});
document.body.appendChild(mark);
range.selectNodeContents(mark);
selection.addRange(range);
const successful = document.execCommand('copy');
if (!successful) {
throw new Error('copy command was unsuccessful');
}
success = true;
} catch (err) {
debug && console.error('unable to copy using execCommand: ', err);
debug && console.warn('trying IE specific stuff');
try {
(window as any).clipboardData.setData(options.format || 'text', text);
options.onCopy && options.onCopy((window as any).clipboardData);
success = true;
} catch (err) {
debug && console.error('unable to copy using clipboardData: ', err);
debug && console.error('falling back to prompt');
message = format('message' in options ? options.message : defaultMessage);
window.prompt(message, text);
}
} finally {
if (selection) {
if (typeof selection.removeRange == 'function') {
selection.removeRange(range);
} else {
selection.removeAllRanges();
}
}
if (mark) {
document.body.removeChild(mark);
}
reselectPrevious();
}
return success;
}
export default copy;

View File

@ -1,41 +0,0 @@
// copy from https://github.com/sudodoki/toggle-selection
// refactor to esm
const deselectCurrent = (): (() => void) => {
const selection = document.getSelection();
if (!selection.rangeCount) {
return function () {};
}
let active = document.activeElement as any;
const ranges = [];
for (let i = 0; i < selection.rangeCount; i++) {
ranges.push(selection.getRangeAt(i));
}
switch (
active.tagName.toUpperCase() // .toUpperCase handles XHTML
) {
case 'INPUT':
case 'TEXTAREA':
active.blur();
break;
default:
active = null;
break;
}
selection.removeAllRanges();
return function () {
selection.type === 'Caret' && selection.removeAllRanges();
if (!selection.rangeCount) {
ranges.forEach(function (range) {
selection.addRange(range);
});
}
active && active.focus();
};
};
export default deselectCurrent;

View File

@ -0,0 +1,22 @@
/**
* Safe chained function
*
* Will only create a new function if needed,
* otherwise will pass back existing functions or null.
*
* @returns {function|null}
*/
export default function createChainedFunction() {
const args = [].slice.call(arguments, 0);
if (args.length === 1) {
return args[0];
}
return function chainedFunction() {
for (let i = 0; i < args.length; i++) {
if (args[i] && args[i].apply) {
args[i].apply(this, arguments);
}
}
};
}

View File

@ -1,22 +0,0 @@
import { inject, provide, reactive, watchEffect } from 'vue';
function createContext<T extends Record<string, any>>(defaultValue?: T) {
const contextKey = Symbol('contextKey');
const useProvide = (props: T, newProps?: T) => {
const mergedProps = reactive<T>({} as T);
provide(contextKey, mergedProps);
watchEffect(() => {
Object.assign(mergedProps, props, newProps || {});
});
return mergedProps;
};
const useInject = () => {
return inject(contextKey, defaultValue as T) || ({} as T);
};
return {
useProvide,
useInject,
};
}
export default createContext;

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) {
window.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) {
window.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,184 @@
// https://github.com/yiminghe/css-animation 1.5.0
import Event from './Event';
import classes from 'component-classes';
import { requestAnimationTimeout, cancelAnimationTimeout } from '../requestAnimationTimeout';
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) {
// 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,29 +0,0 @@
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;
}
update(keys: KeyType[] | string, valueFn: (origin: ValueType | null) => ValueType | null) {
const path = Array.isArray(keys) ? keys.join(SPLIT) : keys;
const prevValue = this.cache.get(path)!;
const nextValue = valueFn(prevValue);
if (nextValue === null) {
this.cache.delete(path);
} else {
this.cache.set(path, nextValue);
}
}
}
export default Entity;

View File

@ -1,19 +0,0 @@
import type { CSSInterpolation } from './hooks/useStyleRegister';
class Keyframe {
private name: string;
style: CSSInterpolation;
constructor(name: string, style: CSSInterpolation) {
this.name = name;
this.style = style;
}
getName(hashId = ''): string {
return hashId ? `${hashId}-${this.name}` : this.name;
}
_keyframe = true;
}
export default Keyframe;

View File

@ -1,195 +0,0 @@
import type { ShallowRef, ExtractPropTypes, InjectionKey, Ref } from 'vue';
import {
provide,
defineComponent,
unref,
inject,
watch,
shallowRef,
getCurrentInstance,
} 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';
export const ATTR_TOKEN = 'data-token-hash';
export const ATTR_MARK = 'data-css-hash';
export const ATTR_CACHE_PATH = 'data-cache-path';
// Mark css-in-js instance in style element
export const CSS_IN_JS_INSTANCE = '__cssinjs_instance__';
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;
// 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);
}
});
// Deduplicate of moved styles
const styleHash: Record<string, boolean> = {};
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) {
style.parentNode?.removeChild(style);
}
} else {
styleHash[hash] = true;
}
});
}
return new CacheEntity(cssinjsInstanceId);
}
export type HashPriority = 'low' | 'high';
export interface StyleContextProps {
autoClear?: boolean;
/** @private Test only. Not work in production. */
mock?: 'server' | 'client';
/**
* Only set when you need ssr to extract style on you own.
* If not provided, it will auto create <style /> on the end of Provider in server side.
*/
cache: CacheEntity;
/** Tell children that this context is default generated context */
defaultCache: boolean;
/** Use `:where` selector to reduce hashId css selector priority */
hashPriority?: HashPriority;
/** Tell cssinjs where to inject style in */
container?: Element | ShadowRoot;
/** Component wil render inline `<style />` for fallback in SSR. Not recommend. */
ssrInline?: boolean;
/** Transform css before inject in document. Please note that `transformers` do not support dynamic update */
transformers?: Transformer[];
/**
* Linters to lint css before inject in document.
* Styles will be linted after transforming.
* Please note that `linters` do not support dynamic update.
*/
linters?: Linter[];
}
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 }));
};
export const useStyleProvider = (props: UseStyleProviderProps) => {
const parentContext = useStyleInject();
const context = shallowRef<Partial<StyleContextProps>>({
...defaultStyleContext,
cache: createCache(),
});
watch(
[() => unref(props), parentContext],
() => {
const mergedContext: Partial<StyleContextProps> = {
...parentContext.value,
};
const propsValue = unref(props);
Object.keys(propsValue).forEach(key => {
const value = propsValue[key];
if (propsValue[key] !== undefined) {
mergedContext[key] = value;
}
});
const { cache } = propsValue;
mergedContext.cache = mergedContext.cache || createCache();
mergedContext.defaultCache = !cache && parentContext.value.defaultCache;
context.value = mergedContext;
},
{ immediate: true },
);
provide(StyleContextKey, context);
return context;
};
export const styleProviderProps = () => ({
autoClear: booleanType(),
/** @private Test only. Not work in production. */
mock: stringType<'server' | 'client'>(),
/**
* Only set when you need ssr to extract style on you own.
* If not provided, it will auto create <style /> on the end of Provider in server side.
*/
cache: objectType<CacheEntity>(),
/** Tell children that this context is default generated context */
defaultCache: booleanType(),
/** Use `:where` selector to reduce hashId css selector priority */
hashPriority: stringType<HashPriority>(),
/** Tell cssinjs where to inject style in */
container: someType<Element | ShadowRoot>(),
/** Component wil render inline `<style />` for fallback in SSR. Not recommend. */
ssrInline: booleanType(),
/** Transform css before inject in document. Please note that `transformers` do not support dynamic update */
transformers: arrayType<Transformer[]>(),
/**
* Linters to lint css before inject in document.
* Styles will be linted after transforming.
* Please note that `linters` do not support dynamic update.
*/
linters: arrayType<Linter[]>(),
});
export type StyleProviderProps = Partial<ExtractPropTypes<ReturnType<typeof styleProviderProps>>>;
export const StyleProvider = withInstall(
defineComponent({
name: 'AStyleProvider',
inheritAttrs: false,
props: styleProviderProps(),
setup(props, { slots }) {
useStyleProvider(props);
return () => slots.default?.();
},
}),
);
export default {
useStyleInject,
useStyleProvider,
StyleProvider,
};

View File

@ -1,165 +0,0 @@
import hash from '@emotion/hash';
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
import type Theme from '../theme/Theme';
import useGlobalCache from './useGlobalCache';
import { flattenToken, token2key } from '../util';
import type { Ref } from 'vue';
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';
export interface Option<DerivativeToken, DesignToken> {
/**
* Generate token with salt.
* This is used to generate different hashId even same derivative token for different version.
*/
salt?: string;
override?: object;
/**
* Format token as you need. Such as:
*
* - rename token
* - merge token
* - delete token
*
* This should always be the same since it's one time process.
* 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>();
function recordCleanToken(tokenKey: string) {
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) + 1);
}
function removeStyleTags(key: string, instanceId: 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) {
style.parentNode?.removeChild(style);
}
});
}
}
const TOKEN_THRESHOLD = 0;
// Remove will check current keys first
function cleanTokenStyle(tokenKey: string, instanceId: string) {
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) - 1);
const tokenKeyList = Array.from(tokenKeys.keys());
const cleanableKeyList = tokenKeyList.filter(key => {
const count = tokenKeys.get(key) || 0;
return count <= 0;
});
// Should keep tokens under threshold for not to insert style too often
if (tokenKeyList.length - cleanableKeyList.length > TOKEN_THRESHOLD) {
cleanableKeyList.forEach(key => {
removeStyleTags(key, instanceId);
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
* @param tokens List of tokens, used for cache. Please do not dynamic generate object directly
* @param option Additional config
* @returns Call Theme.getDerivativeToken(tokenObject) to get token
*/
export default function useCacheToken<DerivativeToken = object, DesignToken = DerivativeToken>(
theme: Ref<Theme<any, any>>,
tokens: Ref<Partial<DesignToken>[]>,
option: Ref<Option<DerivativeToken, DesignToken>> = ref({}),
) {
const style = useStyleInject();
// Basic - We do basic cache here
const mergedToken = computed(() => Object.assign({}, ...tokens.value));
const tokenStr = computed(() => flattenToken(mergedToken.value));
const overrideTokenStr = computed(() => flattenToken(option.value.override || EMPTY_OVERRIDE));
const cachedToken = useGlobalCache<[DerivativeToken & { _tokenKey: string }, string]>(
'token',
computed(() => [
option.value.salt || '',
theme.value.id,
tokenStr.value,
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);
// Optimize for `useStyleRegister` performance
const tokenKey = token2key(mergedDerivativeToken, salt);
mergedDerivativeToken._tokenKey = tokenKey;
recordCleanToken(tokenKey);
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);
},
);
return cachedToken;
}

View File

@ -1,58 +0,0 @@
import { useStyleInject } from '../StyleContext';
import type { KeyType } from '../Cache';
import useHMR from './useHMR';
import type { ShallowRef, Ref } from 'vue';
import { onBeforeUnmount, watch, watchEffect, shallowRef } from 'vue';
export default function useClientCache<CacheType>(
prefix: string,
keyPath: Ref<KeyType[]>,
cacheFn: () => CacheType,
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
): ShallowRef<CacheType> {
const styleContext = useStyleInject();
const fullPathStr = shallowRef('');
const res = shallowRef<CacheType>();
watchEffect(() => {
fullPathStr.value = [prefix, ...keyPath.value].join('%');
});
const HMRUpdate = useHMR();
const clearCache = (pathStr: string) => {
styleContext.value.cache.update(pathStr, prevCache => {
const [times = 0, cache] = prevCache || [];
const nextCount = times - 1;
if (nextCount === 0) {
onCacheRemove?.(cache, false);
return null;
}
return [times - 1, cache];
});
};
watch(
fullPathStr,
(newStr, oldStr) => {
if (oldStr) clearCache(oldStr);
// Create cache
styleContext.value.cache.update(newStr, prevCache => {
const [times = 0, cache] = prevCache || [];
// HMR should always ignore cache since developer may change it
let tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove?.(tmpCache, HMRUpdate);
tmpCache = null;
}
const mergedCache = tmpCache || cacheFn();
return [times + 1, mergedCache];
});
res.value = styleContext.value.cache.get(fullPathStr.value)![1];
},
{ immediate: true },
);
onBeforeUnmount(() => {
clearCache(fullPathStr.value);
});
return res;
}

View File

@ -1,34 +0,0 @@
function useProdHMR() {
return false;
}
let webpackHMR = false;
function useDevHMR() {
return webpackHMR;
}
export default process.env.NODE_ENV === 'production' ? useProdHMR : useDevHMR;
// Webpack `module.hot.accept` do not support any deps update trigger
// We have to hack handler to force mark as HRM
if (
process.env.NODE_ENV !== 'production' &&
typeof module !== 'undefined' &&
module &&
(module as any).hot &&
typeof window !== 'undefined'
) {
const win = window as any;
if (typeof win.webpackHotUpdate === 'function') {
const originWebpackHotUpdate = win.webpackHotUpdate;
win.webpackHotUpdate = (...args: any[]) => {
webpackHMR = true;
setTimeout(() => {
webpackHMR = false;
}, 0);
return originWebpackHotUpdate(...args);
};
}
}

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

@ -1,566 +0,0 @@
import hash from '@emotion/hash';
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 {
useStyleInject,
ATTR_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';
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';
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;
};
export type CSSPropertiesWithMultiValues = {
[K in keyof CSSProperties]:
| CSSProperties[K]
| readonly Extract<CSSProperties[K], string>[]
| {
[SKIP_CHECK]?: boolean;
[MULTI_VALUE]?: boolean;
value: CSSProperties[K] | CSSProperties[K][];
};
};
export type CSSPseudos = { [K in CSS.Pseudos]?: CSSObject };
type ArrayCSSInterpolation = readonly CSSInterpolation[];
export type InterpolationPrimitive = null | undefined | boolean | number | string | CSSObject;
export type CSSInterpolation = InterpolationPrimitive | ArrayCSSInterpolation | Keyframes;
export type CSSOthersObject = Record<string, CSSInterpolation>;
export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSSOthersObject {}
// ============================================================================
// == Parser ==
// ============================================================================
// Preprocessor style content to browser support one
export function normalizeStyle(styleStr: string): 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);
}
// hash
function injectSelectorHash(key: string, hashId: string, hashPriority?: HashPriority) {
if (!hashId) {
return key;
}
const hashClassName = `.${hashId}`;
const hashSelector = hashPriority === 'low' ? `:where(${hashClassName})` : hashClassName;
// hashId
const keys = key.split(',').map(k => {
const fullPath = k.trim().split(/\s+/);
// Selector HTML Element
let firstPath = fullPath[0] || '';
const htmlElement = firstPath.match(/^\w+/)?.[0] || '';
firstPath = `${htmlElement}${hashSelector}${firstPath.slice(htmlElement.length)}`;
return [firstPath, ...fullPath.slice(1)].join(' ');
});
return keys.join(',');
}
export interface ParseConfig {
hashId?: string;
hashPriority?: HashPriority;
layer?: string;
path?: string;
transformers?: Transformer[];
linters?: Linter[];
}
export interface ParseInfo {
root?: boolean;
injectHash?: boolean;
parentSelectors: string[];
}
// Global effect style will mount once and not removed
// The effect will not save in SSR cache (e.g. keyframes)
const globalEffectStyleKeys = new Set();
/**
* @private Test only. Clear the global effect style keys.
*/
export const _cf =
process.env.NODE_ENV !== 'production' ? () => globalEffectStyleKeys.clear() : undefined;
// Parse CSSObject to style content
export const parseStyle = (
interpolation: CSSInterpolation,
config: ParseConfig = {},
{ root, injectHash, parentSelectors }: ParseInfo = {
root: true,
parentSelectors: [],
},
): [
parsedStr: string,
// Style content which should be unique on all of the style (e.g. Keyframes).
// Firefox will flick with same animation name when exist multiple same keyframes.
effectStyle: Record<string, string>,
] => {
const { hashId, layer, path, hashPriority, transformers = [], linters = [] } = config;
let styleStr = '';
let effectStyle: Record<string, string> = {};
function parseKeyframes(keyframes: Keyframes) {
const animationName = keyframes.getName(hashId);
if (!effectStyle[animationName]) {
const [parsedStr] = parseStyle(keyframes.style, config, {
root: false,
parentSelectors,
});
effectStyle[animationName] = `@keyframes ${keyframes.getName(hashId)}${parsedStr}`;
}
}
function flattenList(list: ArrayCSSInterpolation, fullList: CSSObject[] = []) {
list.forEach(item => {
if (Array.isArray(item)) {
flattenList(item, fullList);
} else if (item) {
fullList.push(item as CSSObject);
}
});
return fullList;
}
const flattenStyleList = flattenList(
Array.isArray(interpolation) ? interpolation : [interpolation],
);
flattenStyleList.forEach(originStyle => {
// Only root level can use raw string
const style: CSSObject = typeof originStyle === 'string' && !root ? {} : originStyle;
if (typeof style === 'string') {
styleStr += `${style}\n`;
} else if ((style as any)._keyframe) {
// Keyframe
parseKeyframes(style as unknown as Keyframes);
} else {
const mergedStyle = transformers.reduce((prev, trans) => trans?.visit?.(prev) || prev, style);
// Normal CSSObject
Object.keys(mergedStyle).forEach(key => {
const value = mergedStyle[key];
if (
typeof value === 'object' &&
value &&
(key !== 'animationName' || !(value as Keyframes)._keyframe) &&
!isCompoundCSSProperty(value)
) {
let subInjectHash = false;
//
let mergedKey = key.trim();
// Whether treat child as root. In most case it is false.
let nextRoot = false;
//
if ((root || injectHash) && hashId) {
if (mergedKey.startsWith('@')) {
// hashId
subInjectHash = true;
} else {
// hashId
mergedKey = injectSelectorHash(key, hashId, hashPriority);
}
} else if (root && !hashId && (mergedKey === '&' || mergedKey === '')) {
// In case of `{ '&': { a: { color: 'red' } } }` or `{ '': { a: { color: 'red' } } }` without hashId,
// we will get `&{a:{color:red;}}` or `{a:{color:red;}}` string for stylis to compile.
// But it does not conform to stylis syntax,
// and finally we will get `{color:red;}` as css, which is wrong.
// So we need to remove key in root, and treat child `{ a: { color: 'red' } }` as root.
mergedKey = '';
nextRoot = true;
}
const [parsedStr, childEffectStyle] = parseStyle(value as any, config, {
root: nextRoot,
injectHash: subInjectHash,
parentSelectors: [...parentSelectors, mergedKey],
});
effectStyle = {
...effectStyle,
...childEffectStyle,
};
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)
) {
actualValue.forEach(item => {
appendStyle(key, item);
});
} else {
appendStyle(key, actualValue);
}
}
});
}
});
if (!root) {
styleStr = `{${styleStr}}`;
} else if (layer && supportLayer()) {
const layerCells = layer.split(',');
const layerName = layerCells[layerCells.length - 1].trim();
styleStr = `@layer ${layerName} {${styleStr}}`;
// Order of layer if needed
if (layerCells.length > 1) {
// zombieJ: stylis do not support layer order, so we need to handle it manually.
styleStr = `@layer ${layer}{%%%:%}${styleStr}`;
}
}
return [styleStr, effectStyle];
};
// ============================================================================
// == Register ==
// ============================================================================
function uniqueHash(path: (string | number)[], styleStr: string) {
return hash(`${path.join('%')}${styleStr}`);
}
// function Empty() {
// return null;
// }
/**
* Register a style to the global style sheet.
*/
export default function useStyleRegister(
info: Ref<{
theme: Theme<any, any>;
token: any;
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,
) {
const styleContext = useStyleInject();
const tokenKey = computed(() => info.value.token._tokenKey as string);
const fullPath = computed(() => [tokenKey.value, ...info.value.path]);
// Check if need insert style
let isMergedClientSide = isClientSide;
if (process.env.NODE_ENV !== 'production' && styleContext.value.mock !== undefined) {
isMergedClientSide = styleContext.value.mock === 'client';
}
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
useGlobalCache<
[
styleStr: string,
tokenKey: string,
styleId: string,
effectStyle: Record<string, string>,
clientOnly: boolean | undefined,
order: number,
]
>(
'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 [parsedStyle, effectStyle] = parseStyle(styleObj, {
hashId,
hashPriority,
layer,
path: path.join('-'),
transformers,
linters,
});
const styleStr = normalizeStyle(parsedStyle);
const styleId = uniqueHash(fullPath.value, styleStr);
if (isMergedClientSide) {
const mergedCSSConfig: Parameters<typeof updateCSS>[2] = {
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;
// 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('|'));
}
// Inject client side effect style
Object.keys(effectStyle).forEach(effectKey => {
if (!globalEffectStyleKeys.has(effectKey)) {
globalEffectStyleKeys.add(effectKey);
// Inject
updateCSS(normalizeStyle(effectStyle[effectKey]), `_effect-${effectKey}`, {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
});
}
});
}
return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order];
},
// Remove cache if no need
([, , styleId], fromHMR) => {
if ((fromHMR || styleContext.value.autoClear) && isClientSide) {
removeCSS(styleId, { mark: ATTR_MARK });
}
},
);
return (node: VueNode) => {
return node;
// let styleNode: VueNode;
// if (!styleContext.ssrInline || isMergedClientSide || !styleContext.defaultCache) {
// styleNode = <Empty />;
// } else {
// styleNode = (
// <style
// {...{
// [ATTR_TOKEN]: cacheStyle.value[1],
// [ATTR_MARK]: cacheStyle.value[2],
// }}
// innerHTML={cacheStyle.value[0]}
// />
// );
// }
// return (
// <>
// {styleNode}
// {node}
// </>
// );
};
}
// ============================================================================
// == SSR ==
// ============================================================================
export function extractStyle(cache: Cache, plain = false) {
const matchPrefix = `style%`;
// prefix with `style` is used for `useStyleRegister` to cache style context
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith(matchPrefix));
// Common effect styles like animation
const effectStyles: Record<string, boolean> = {};
// Mapping of cachePath to style hash
const cachePathMap: 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,
};
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,
},
);
return styleText;
}

View File

@ -1,77 +0,0 @@
import useCacheToken from './hooks/useCacheToken';
import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister';
import useStyleRegister, { extractStyle } from './hooks/useStyleRegister';
import Keyframes from './Keyframes';
import type { Linter } from './linters';
import { legacyNotSelectorLinter, logicalPropertiesLinter, parentSelectorLinter } 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,
createTheme,
useStyleRegister,
useCacheToken,
createCache,
useStyleInject,
useStyleProvider,
Keyframes,
extractStyle,
// Transformer
legacyLogicalPropertiesTransformer,
px2remTransformer,
// Linters
logicalPropertiesLinter,
legacyNotSelectorLinter,
parentSelectorLinter,
// cssinjs
StyleProvider,
};
export {
Theme,
createTheme,
useStyleRegister,
useCacheToken,
createCache,
useStyleInject,
useStyleProvider,
Keyframes,
extractStyle,
// Transformer
legacyLogicalPropertiesTransformer,
px2remTransformer,
// Linters
logicalPropertiesLinter,
legacyNotSelectorLinter,
parentSelectorLinter,
// cssinjs
StyleProvider,
};
export type {
TokenType,
CSSObject,
CSSInterpolation,
DerivativeFunc,
Transformer,
Linter,
StyleContextProps,
StyleProviderProps,
};
export const _experimental = {
supportModernCSS: () => supportWhere() && supportLogicProps(),
};
export default cssinjs;

View File

@ -1,25 +0,0 @@
import type { Linter } from './interface';
import { lintWarning } from './utils';
const linter: Linter = (key, value, info) => {
if (key === 'content') {
// From emotion: https://github.com/emotion-js/emotion/blob/main/packages/serialize/src/index.js#L63
const contentValuePattern =
/(attr|counters?|url|(((repeating-)?(linear|radial))|conic)-gradient)\(|(no-)?(open|close)-quote/;
const contentValues = ['normal', 'none', 'initial', 'inherit', 'unset'];
if (
typeof value !== 'string' ||
(contentValues.indexOf(value) === -1 &&
!contentValuePattern.test(value) &&
(value.charAt(0) !== value.charAt(value.length - 1) ||
(value.charAt(0) !== '"' && value.charAt(0) !== "'")))
) {
lintWarning(
`You seem to be using a value for 'content' without quotes, try replacing it with \`content: '"${value}"'\`.`,
info,
);
}
}
};
export default linter;

View File

@ -1,15 +0,0 @@
import type { Linter } from './interface';
import { lintWarning } from './utils';
const linter: Linter = (key, value, info) => {
if (key === 'animation') {
if (info.hashId && value !== 'none') {
lintWarning(
`You seem to be using hashed animation '${value}', in which case 'animationName' with Keyframe as value is recommended.`,
info,
);
}
}
};
export default linter;

View File

@ -1,6 +0,0 @@
export { default as contentQuotesLinter } from './contentQuotesLinter';
export { default as hashedAnimationLinter } from './hashedAnimationLinter';
export type { Linter } from './interface';
export { default as legacyNotSelectorLinter } from './legacyNotSelectorLinter';
export { default as logicalPropertiesLinter } from './logicalPropertiesLinter';
export { default as parentSelectorLinter } from './parentSelectorLinter';

View File

@ -1,9 +0,0 @@
export interface LinterInfo {
path?: string;
hashId?: string;
parentSelectors: string[];
}
export interface Linter {
(key: string, value: string | number, info: LinterInfo): void;
}

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