Compare commits

..

No commits in common. "main" and "3.0.0-beta.4" have entirely different histories.

2849 changed files with 77717 additions and 437413 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/

View File

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

View File

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

View File

@ -1,8 +1,8 @@
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!
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](./pr_cn.md)]
[[中文版模板 / Chinese template](https://github.com/vueComponent/ant-design-vue/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]
### This is a ...

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

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

View File

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

View File

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

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

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

View File

@ -98,6 +98,16 @@ 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
with:

23
.github/workflows/translate.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Translation Helper
on:
pull_request_target:
types: [opened]
issues:
types: [opened]
jobs:
translate:
runs-on: ubuntu-latest
steps:
- name: issue
if: github.event_name == 'issues'
uses: actions-cool/translation-helper@v1.1.1
with:
translate-body: false
- name: pr
if: github.event_name == 'pull_request_target'
uses: actions-cool/translation-helper@v1.1.1
with:
translate-body: false

8
.gitignore vendored
View File

@ -59,11 +59,9 @@ jspm_packages/
dist
lib
es
/locale
_site
yarn.lock
package-lock.json
pnpm-lock.yaml
/coverage
# 备份文件
@ -78,9 +76,3 @@ 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

View File

@ -9,48 +9,41 @@ const transformIgnorePatterns = [
];
const testPathIgnorePatterns = ['/node_modules/', 'node'];
function getTestRegex(libDir) {
if (libDir === 'dist') {
return 'demo\\.test\\.js$';
}
return '.*\\.test\\.(j|t)sx?$';
}
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

View File

@ -4,40 +4,13 @@
"stylelint-config-rational-order",
"stylelint-config-prettier"
],
"customSyntax": "postcss-less",
"plugins": ["stylelint-declaration-block-no-ignored-properties"],
"plugins": ["stylelint-order", "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,
"comment-empty-line-before": null,
"function-name-case": ["lower", { "ignoreFunctions": ["/colorPalette/"] }],
"no-invalid-double-slash-comments": 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
}
"declaration-empty-line-before": null
},
"ignoreFiles": ["components/style/color/{bezierEasing,colorPalette,tinyColor}.less"]
}

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,7 +10,7 @@
<div align="center">
基于 Ant Design 和 Vue 3 的企业级 UI 组件库。
An enterprise-class UI components based on Ant Design and Vue 3.
![test](https://github.com/vueComponent/ant-design-vue/workflows/test/badge.svg) [![codecov](https://img.shields.io/codecov/c/github/vueComponent/ant-design-vue/master.svg?style=flat-square)](https://codecov.io/gh/vueComponent/ant-design-vue) [![npm package](https://img.shields.io/npm/v/ant-design-vue.svg?style=flat-square)](https://www.npmjs.org/package/ant-design-vue) [![NPM downloads](http://img.shields.io/npm/dm/ant-design-vue.svg?style=flat-square)](http://www.npmtrends.com/ant-design-vue) [![backers](https://opencollective.com/ant-design-vue/backers/badge.svg)](#backers) [![sponsors](https://opencollective.com/ant-design-vue/sponsors/badge.svg)](#sponsors) [![extension-for-VSCode](https://img.shields.io/badge/extension%20for-VSCode-blue.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=ant-design-vue.vscode-ant-design-vue-helper) [![issues-helper](https://img.shields.io/badge/Issues%20Manage%20By-issues--helper-orange?style=flat-square)](https://github.com/actions-cool/issues-helper)
@ -26,12 +26,6 @@
- 开箱即用的高质量 Vue 组件。
- 共享 [Ant Design of React](http://ant-design.gitee.io/docs/spec/introduce-cn) 设计工具体系。
## 关注我们
收藏加关注,第一时间获取更新动态!
![star us](https://user-images.githubusercontent.com/6937879/261937060-e0501ab3-9388-4712-a25d-3f2ba2271865.gif)
## 支持环境
- 现代浏览器。1.x 版本支持 IE 9+(需要 [polyfills](https://www.antdv.com/docs/vue/getting-started-cn/#兼容性)
@ -72,8 +66,6 @@ $ yarn add ant-design-vue
| [vue-cli-plugin-ant-design](https://github.com/vueComponent/vue-cli-plugin-ant-design) | 使用 vue-cli3 快速使用 ant-design-vue 组件库 |
| [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | 在 DOM 模板中,您可以使用 ant-design-vue 组件的自定义事件camelCase |
| [@formily/antdv](https://github.com/formilyjs/antdv) | 这是一个结合了 Formily 和 ant-design-vue 的组件库 |
| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | ant-design-vue 的 nuxt 模块扩展 |
| [ant-design-x-vue](https://github.com/wzc520pyfm/ant-design-x-vue) | 基于 Ant Design X 设计规范的 Vue AI 界面解决方案 |
## 问答
@ -88,33 +80,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>
@ -26,15 +26,9 @@ English | [简体中文](./README-zh_CN.md)
- A set of high-quality Vue components out of the box.
- Shared [Ant Design of React](https://ant.design/docs/spec/introduce) design resources.
## Getting started & staying tuned with us.
Star us, and you will receive all releases notifications from GitHub without any delay!
![star us](https://user-images.githubusercontent.com/6937879/261937060-e0501ab3-9388-4712-a25d-3f2ba2271865.gif)
## Environment Support
- Modern browsers. v1.x support Internet Explorer 9+ (with [polyfills](https://www.antdv.com/docs/vue/getting-started/#compatibility))
- Modern browsers. v1.x support Internet Explorer 9+ (with [polyfills](https://www.antdv.com/docs/vue/getting-started/#Compatibility))
- Server-side Rendering
- Support Vue 2 & Vue 3
- [Electron](https://electronjs.org/)
@ -45,7 +39,7 @@ Star us, and you will receive all releases notifications from GitHub without any
## Using npm or yarn
**We recommend using npm or yarn to install**, it not only makes development easier, but also allow you to take advantage of the rich ecosystem of Javascript packages and tooling.
**We recommend using npm or yarn to install**it not only makes development easierbut also allow you to take advantage of the rich ecosystem of Javascript packages and tooling.
```bash
$ npm install ant-design-vue --save
@ -55,7 +49,7 @@ $ npm install ant-design-vue --save
$ yarn add ant-design-vue
```
If you are in a bad network environment, you can try other registries and tools like [cnpm](https://github.com/cnpm/cnpm).
If you are in a bad network environmentyou can try other registries and tools like [cnpm](https://github.com/cnpm/cnpm).
## Links
@ -72,8 +66,6 @@ If you are in a bad network environment, you can try other registries and tools
| [vue-cli-plugin-ant-design](https://github.com/vueComponent/vue-cli-plugin-ant-design) | Vue-cli 3 plugin to add ant-design-vue |
| [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | The library function, implemented in the DOM template, can use the custom event of the ant-design-vue component (camelCase) |
| [@formily/antdv](https://github.com/formilyjs/antdv) | The Library with Formily and ant-design-vue |
| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | A nuxt module for ant-design-vue |
| [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 +74,14 @@ 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>
## [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,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,6 +4,7 @@
'use strict';
require('colorful').colorful();
require('colorful').isatty = true;
const gulp = require('gulp');
const program = require('commander');

View File

@ -3,21 +3,18 @@ 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);
try {
parseAndWrite({
version: pkg.version,
name: 'ant-design-vue',
path: path.resolve(rootPath, './components'),
// default match lang
test: /en-US\.md/,
outputDir: path.resolve(rootPath, './vetur'),
tagPrefix: 'a-',
});
// eslint-disable-next-line no-console
console.log('generator types success');
} catch (e) {
console.error('generator types error', e);
}

View File

@ -34,19 +34,14 @@ function parserProps(tag: VueTag, line: any) {
});
}
export function formatter(
articals: Articals,
componentName: string,
kebabComponentName: string,
tagPrefix = '',
) {
export function formatter(articals: Articals, componentName: string, tagPrefix = '') {
if (!articals.length) {
return;
}
const tags: VueTag[] = [];
const tag: VueTag = {
name: kebabComponentName,
name: getComponentName(componentName, tagPrefix),
slots: [],
events: [],
attributes: [],
@ -85,13 +80,9 @@ export function formatter(
}
// 额外的子组件
if (
tableTitle.includes(componentName) &&
!tableTitle.includes('events') &&
!tableTitle.includes('()')
) {
if (tableTitle.includes(componentName) && !tableTitle.includes('events')) {
const childTag: VueTag = {
name: getComponentName(tableTitle.replace(/\.|\//g, ''), tagPrefix),
name: getComponentName(tableTitle.replace('.', ''), tagPrefix),
slots: [],
events: [],
attributes: [],
@ -102,7 +93,6 @@ export function formatter(
tags.push(childTag);
return;
}
// 额外的子组件事件
if (tableTitle.includes(componentName) && tableTitle.includes('events')) {
const childTagName = getComponentName(

View File

@ -1,84 +1,52 @@
import glob from 'fast-glob';
import { dirname, join } from 'path';
import { join, dirname } from 'path';
import { mdParser } from './parser';
import { formatter } from './formatter';
import { genWebTypes } from './web-types';
import { outputFileSync, readFileSync } from 'fs-extra';
import { readFileSync, outputFileSync } from 'fs-extra';
import type { Options, VueTag } from './type';
import { getComponentName, normalizePath, toKebabCase } from './utils';
import { flatMap } from 'lodash';
import { normalizePath, getComponentName } from './utils';
import { genVeturTags, genVeturAttributes } from './vetur';
async function readMarkdown(options: Options): Promise<Map<String, VueTag>> {
const mdPaths = await glob(normalizePath(`${options.path}/**/*.md`));
const data = mdPaths
async function readMarkdown(options: Options) {
// const mds = await glob(normalizePath(`${options.path}/**/*.md`))
const mds = await glob(normalizePath(`${options.path}/**/*.md`));
return mds
.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;
const componentName = docPath.substring(docPath.lastIndexOf('/') + 1);
return {
componentName: getComponentName(componentName || ''),
md: readFileSync(path, 'utf-8'),
};
});
}
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> {
export async function parseAndWrite(options: Options) {
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 docs = await readMarkdown(options);
const datas = docs
.map(doc => formatter(mdParser(doc.md), doc.componentName, options.tagPrefix))
.filter(item => item) as VueTag[][];
const tags: VueTag[] = [];
datas.forEach(arr => {
tags.push(...arr);
});
const webTypes = genWebTypes(tags, options);
const veturTags = genVeturTags(tags);
const veturAttributes = genVeturAttributes(tags);
outputFileSync(join(options.outputDir, 'tags.json'), JSON.stringify(veturTags, null, 2));
outputFileSync(
join(options.outputDir, 'attributes.json'),
JSON.stringify(veturAttributes, null, 2),
);
outputFileSync(join(options.outputDir, 'web-types.json'), JSON.stringify(webTypes, null, 2));
return tags.length;
}
export default { parseAndWrite };

View File

@ -21,13 +21,13 @@ export type Articals = Artical[];
function readLine(input: string) {
const end = input.indexOf('\n');
return input.substring(0, end !== -1 ? end : input.length);
return input.substr(0, end !== -1 ? end : input.length);
}
function splitTableLine(line: string) {
line = line.replace(/\\\|/g, 'JOIN');
line = line.replace('\\|', 'JOIN');
const items = line.split('|').map(item => item.trim().replace(/JOIN/g, '|'));
const items = line.split('|').map(item => item.trim().replace('JOIN', '|'));
// remove pipe character on both sides
items.pop();
@ -47,7 +47,7 @@ function tableParse(input: string) {
};
while (start < end) {
const target = input.substring(start);
const target = input.substr(start);
const line = readLine(target);
if (!/^\|/.test(target)) {
@ -77,9 +77,14 @@ export function mdParser(input: string): Articals {
const artical = [];
let start = 0;
const end = input.length;
// artical.push({
// type: 'title',
// content: title,
// level: 0,
// });
while (start < end) {
const target = input.substring(start);
const target = input.substr(start);
let match;
if ((match = TITLE_REG.exec(target))) {
@ -91,7 +96,7 @@ export function mdParser(input: string): Articals {
start += match.index + match[0].length;
} else if ((match = TABLE_REG.exec(target))) {
const { table, usedLength } = tableParse(target.substring(match.index));
const { table, usedLength } = tableParse(target.substr(match.index));
artical.push({
type: 'table',
table,
@ -103,5 +108,6 @@ export function mdParser(input: string): Articals {
}
}
// artical[0].content = title
return artical;
}

View File

@ -28,16 +28,34 @@ export type VueAttribute = {
export type VueTag = {
name: string;
slots: VueSlot[];
events: VueEvent[];
attributes: VueAttribute[];
slots?: VueSlot[];
events?: VueEvent[];
attributes?: VueAttribute[];
description?: string;
};
export type VeturTag = {
description?: string;
attributes: string[];
};
export type VeturTags = Record<string, VeturTag>;
export type VeturAttribute = {
type: string;
description: string;
};
export type VeturAttributes = Record<string, VeturAttribute>;
export type VeturResult = {
tags: VeturTags;
attributes: VeturAttributes;
};
export type Options = {
name: string;
path: PathLike;
typingsPath: PathLike;
test: RegExp;
version: string;
outputDir?: string;

View File

@ -1,6 +1,6 @@
// 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();
export function toKebabCase(input: string): string {
return input.replace(/[A-Z]/g, (val, index) => (index === 0 ? '' : '-') + val.toLowerCase());
}
// name `v2.0.0` -> name

View File

@ -0,0 +1,30 @@
import type { VueTag, VeturTags, VeturAttributes } from './type';
export function genVeturTags(tags: VueTag[]) {
const veturTags: VeturTags = {};
tags.forEach(tag => {
veturTags[tag.name] = {
attributes: tag.attributes ? tag.attributes.map(item => item.name) : [],
};
});
return veturTags;
}
export function genVeturAttributes(tags: VueTag[]) {
const veturAttributes: VeturAttributes = {};
tags.forEach(tag => {
if (tag.attributes) {
tag.attributes.forEach(attr => {
veturAttributes[`${tag.name}/${attr.name}`] = {
type: attr.value.type,
description: `${attr.description}, Default: ${attr.default}`,
};
});
}
});
return veturAttributes;
}

View File

@ -1,4 +1,4 @@
const { resolve, isThereHaveBrowserslistConfig } = require('./utils/projectHelper');
const { resolve } = require('./utils/projectHelper');
module.exports = function (modules) {
const plugins = [
@ -20,8 +20,7 @@ module.exports = function (modules) {
resolve('@babel/plugin-transform-runtime'),
{
useESModules: modules === false,
version:
require(`${process.cwd()}/package.json`).dependencies['@babel/runtime'] || '^7.10.4',
version: '^7.10.4',
},
],
// resolve('babel-plugin-inline-import-data-uri'),
@ -39,11 +38,9 @@ module.exports = function (modules) {
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%', 'not ie 11'],
},
},
],
],

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

View File

@ -22,7 +22,7 @@ const imageOptions = {
limit: 10000,
};
function getWebpackConfig(modules, esm = false) {
function getWebpackConfig(modules) {
const pkg = require(getProjectPath('package.json'));
const babelConfig = require('./getBabelCommonConfig')(modules || false);
@ -75,7 +75,7 @@ function getWebpackConfig(modules, esm = false) {
'readline',
'repl',
'tls',
].reduce((acc, name) => Object.assign({}, acc, { [name]: 'empty' }), {}),
].reduce((acc, name) => Object.assign({}, acc, { [name]: false }), {}),
},
module: {
@ -150,6 +150,36 @@ function getWebpackConfig(modules, esm = false) {
},
],
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['autoprefixer'],
},
sourceMap: true,
},
},
{
loader: 'less-loader',
options: {
lessOptions: {
sourceMap: true,
javascriptEnabled: true,
},
},
},
],
},
// Images
{
test: svgRegex,
@ -170,7 +200,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({
@ -185,37 +215,17 @@ All rights reserved.
};
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({
@ -226,10 +236,11 @@ All rights reserved.
}),
],
};
// Development
const uncompressedConfig = merge({}, config, {
entry: {
[entryName]: entry,
[distFileBaseName]: entry,
},
mode: 'development',
plugins: [
@ -242,10 +253,11 @@ All rights reserved.
// Production
const prodConfig = merge({}, config, {
entry: {
[`${entryName}.min`]: entry,
[`${distFileBaseName}.min`]: entry,
},
mode: 'production',
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.LoaderOptionsPlugin({
minimize: true,
}),
@ -262,7 +274,7 @@ All rights reserved.
return [prodConfig, uncompressedConfig];
}
return [config];
return config;
}
getWebpackConfig.webpack = webpack;

View File

@ -1,10 +1,11 @@
/* eslint-disable no-console */
const { getProjectPath, getConfig } = require('./utils/projectHelper');
const { getProjectPath } = require('./utils/projectHelper');
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));
@ -25,23 +26,15 @@ 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'));
process.env.RUN_ENV = 'PRODUCTION';
@ -56,17 +49,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 +68,6 @@ function dist(done) {
version: false,
});
console.log(buildInfo);
// Additional process of dist finalize
if (finalize) {
console.log('[Dist] Finalization...');
finalize();
}
done(0);
});
}
@ -114,11 +96,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,10 +103,20 @@ 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(/\/style\/?'/g, "/style/css'")
.replace(/\/style\/?"/g, '/style/css"')
.replace(/\.less/g, '.css'),
);
file.path = file.path.replace(/index\.(js|jsx|ts|tsx)$/, 'css.js');
this.push(file);
} else if (modules !== false) {
const content = file.contents.toString(encoding);
file.contents = Buffer.from(
content
@ -141,36 +128,48 @@ function babelify(js, modules) {
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 +179,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 +199,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() {
@ -368,7 +326,7 @@ function pub(done) {
}
}
const startTime = new Date();
let startTime = new Date();
gulp.task('compile-with-es', done => {
console.log('start compile at ', startTime);
console.log('[Parallel] Compile to es...');
@ -377,25 +335,12 @@ gulp.task('compile-with-es', 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();
compile().on('finish', done);
});
gulp.task(
'compile',
gulp.series(gulp.parallel('compile-with-es', 'compile-with-lib'), 'compile-finalize', done => {
gulp.series(gulp.parallel('compile-with-es', 'compile-with-lib'), done => {
console.log('end compile at ', new Date());
console.log('compile time ', (new Date() - startTime) / 1000, 's');
done();
@ -452,7 +397,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 +420,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 +430,3 @@ gulp.task(
done();
}),
);
gulp.task(
'sort-api-table',
gulp.series(done => {
sortApiTable();
done();
}),
);

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,30 @@
const less = require('less');
const { readFileSync } = require('fs');
const path = require('path');
const postcss = require('postcss');
const NpmImportPlugin = require('less-plugin-npm-import');
const autoprefixer = require('autoprefixer');
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 => postcss([autoprefixer]).process(result.css, { 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,6 @@
'use strict';
const path = require('path');
const isWindows = require('is-windows');
module.exports = function getRunCmdEnv() {
const env = {};
@ -15,9 +14,7 @@ module.exports = function getRunCmdEnv() {
.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;
@ -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,
};

View File

@ -1,7 +1,7 @@
module.exports = {
env: {
test: {
presets: [['@babel/preset-env']],
presets: [['@babel/preset-env', { targets: { node: true } }]],
plugins: [
['@vue/babel-plugin-jsx', { mergeProps: false, enableObjectSlots: false }],
'@babel/plugin-proposal-optional-chaining',
@ -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

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

View File

@ -1,19 +1,10 @@
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

@ -3,66 +3,37 @@ import {
defineComponent,
nextTick,
onBeforeMount,
onMounted,
onBeforeUnmount,
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,
didUpdate: PropTypes.func,
},
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();
}
container = props.getContainer();
});
onUpdated(() => {
nextTick(() => {
if (shouldRender.value) {
props.didUpdate?.(props);
}
props.didUpdate?.(props);
});
});
// onBeforeUnmount(() => {
// if (container && container.parentNode) {
// container.parentNode.removeChild(container);
// }
// });
onBeforeUnmount(() => {
if (container && container.parentNode) {
container.parentNode.removeChild(container);
}
});
return () => {
if (!shouldRender.value) return null;
if (isSSR) {
return slots.default?.();
}

View File

@ -1,19 +1,20 @@
import PropTypes from './vue-types';
import switchScrollingEffect from './switchScrollingEffect';
import setStyle from './setStyle';
import Portal from './Portal';
import {
defineComponent,
shallowRef,
ref,
watch,
onMounted,
onBeforeUnmount,
onUpdated,
getCurrentInstance,
nextTick,
computed,
} from 'vue';
import canUseDom from './canUseDom';
import ScrollLocker from '../vc-util/Dom/scrollLocker';
import raf from './raf';
import { booleanType } from './type';
import useScrollLocker from './hooks/useScrollLocker';
let openCount = 0;
const supportDom = canUseDom();
@ -23,13 +24,17 @@ export function getOpenCount() {
return process.env.NODE_ENV === 'test' ? openCount : 0;
}
// https://github.com/ant-design/ant-design/issues/19340
// https://github.com/ant-design/ant-design/issues/19332
let cacheOverflow = {};
const getParent = (getContainer: GetContainer) => {
if (!supportDom) {
return null;
}
if (getContainer) {
if (typeof getContainer === 'string') {
return document.querySelectorAll(getContainer)[0] as HTMLElement;
return document.querySelectorAll(getContainer)[0];
}
if (typeof getContainer === 'function') {
return getContainer();
@ -44,36 +49,31 @@ const getParent = (getContainer: GetContainer) => {
export type GetContainer = string | HTMLElement | (() => HTMLElement);
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'PortalWrapper',
inheritAttrs: false,
props: {
wrapperClassName: String,
forceRender: { type: Boolean, default: undefined },
wrapperClassName: PropTypes.string,
forceRender: PropTypes.looseBool,
getContainer: PropTypes.any,
visible: { type: Boolean, default: undefined },
autoLock: booleanType(),
didUpdate: Function,
visible: PropTypes.looseBool,
},
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 container = ref<HTMLElement>();
const componentRef = ref();
const rafId = ref<number>();
const scrollLocker = new ScrollLocker({
container: getParent(props.getContainer) as HTMLElement,
});
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;
container.value?.parentNode?.removeChild(container.value);
};
let parent: HTMLElement = null;
const attachToParent = (force = false) => {
if (force || (container.value && !container.value.parentNode)) {
parent = getParent(props.getContainer);
const parent = getParent(props.getContainer);
if (parent) {
parent.appendChild(container.value);
return true;
@ -84,12 +84,14 @@ export default defineComponent({
return true;
};
// attachToParent();
const getContainer = () => {
if (!supportDom) {
return null;
}
if (!container.value) {
container.value = defaultContainer;
container.value = document.createElement('div');
attachToParent(true);
}
setWrapperClassName();
@ -105,31 +107,41 @@ export default defineComponent({
setWrapperClassName();
attachToParent();
});
useScrollLocker(
computed(() => {
return (
props.autoLock &&
props.visible &&
canUseDom() &&
(container.value === document.body || container.value === defaultContainer)
);
}),
);
/**
* Enhance ./switchScrollingEffect
* 1. Simulate document body scroll bar with
* 2. Record body has overflow style and recover when all of PortalWrapper invisible
* 3. Disable body scroll when PortalWrapper has open
*
* @memberof PortalWrapper
*/
const switchScrolling = () => {
if (openCount === 1 && !Object.keys(cacheOverflow).length) {
switchScrollingEffect();
// Must be set after switchScrollingEffect
cacheOverflow = setStyle({
overflow: 'hidden',
overflowX: 'hidden',
overflowY: 'hidden',
});
} else if (!openCount) {
setStyle(cacheOverflow);
cacheOverflow = {};
switchScrollingEffect(true);
}
};
const instance = getCurrentInstance();
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 (supportDom && getParent(props.getContainer) === document.body) {
if (visible && !prevVisible) {
openCount += 1;
} else if (init) {
openCount -= 1;
}
}
@ -144,6 +156,17 @@ export default defineComponent({
) {
removeCurrentContainer();
}
// updateScrollLocker
if (
visible &&
visible !== prevVisible &&
supportDom &&
getParent(getContainer) !== scrollLocker.getContainer()
) {
scrollLocker.reLock({
container: getParent(getContainer) as HTMLElement,
});
}
}
init = true;
},
@ -153,34 +176,37 @@ export default defineComponent({
nextTick(() => {
if (!attachToParent()) {
rafId.value = raf(() => {
triggerUpdate.value += 1;
instance.update();
});
}
});
});
onBeforeUnmount(() => {
const { visible } = props;
if (supportDom && parent === document.body) {
const { visible, getContainer } = props;
if (supportDom && getParent(getContainer) === document.body) {
// render func
openCount = visible && openCount ? openCount - 1 : openCount;
}
removeCurrentContainer();
raf.cancel(rafId.value);
});
return () => {
const { forceRender, visible } = props;
let portal = null;
const childProps = {
getOpenCount: () => openCount,
getContainer,
switchScrollingEffect: switchScrolling,
scrollLocker,
};
if (triggerUpdate.value && (forceRender || visible || componentRef.value)) {
if (forceRender || visible || componentRef.value) {
portal = (
<Portal
getContainer={getContainer}
ref={componentRef}
didUpdate={props.didUpdate}
v-slots={{ default: () => slots.default?.(childProps) }}
></Portal>
);

View File

@ -1,7 +1,6 @@
import { defineComponent } from 'vue';
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'Portal',
inheritAttrs: false,
props: ['getContainer'],

View File

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

View File

@ -1,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,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,23 @@
import type { PresetColorKey } from '../theme/interface';
import { PresetColors } from '../theme/interface';
import type { ElementOf } from './type';
import { 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

@ -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) {
setTimeout(eventListener, 0);
return;
}
startEvents.forEach(startEvent => {
addEventListener(node, startEvent, eventListener);
});
},
removeStartEventListener(node, eventListener) {
if (startEvents.length === 0) {
return;
}
startEvents.forEach(startEvent => {
removeEventListener(node, startEvent, eventListener);
});
},
// End events
endEvents,
addEndEventListener(node, eventListener) {
if (endEvents.length === 0) {
setTimeout(eventListener, 0);
return;
}
endEvents.forEach(endEvent => {
addEventListener(node, endEvent, eventListener);
});
},
removeEndEventListener(node, eventListener) {
if (endEvents.length === 0) {
return;
}
endEvents.forEach(endEvent => {
removeEventListener(node, endEvent, eventListener);
});
},
};
export default TransitionEvents;

View File

@ -0,0 +1,186 @@
// https://github.com/yiminghe/css-animation 1.5.0
import Event from './Event';
import classes from '../component-classes';
import { requestAnimationTimeout, cancelAnimationTimeout } from '../requestAnimationTimeout';
import { inBrowser } from '../env';
const isCssAnimationSupported = Event.endEvents.length !== 0;
const capitalPrefixes = [
'Webkit',
'Moz',
'O',
// ms is special .... !
'ms',
];
const prefixes = ['-webkit-', '-moz-', '-o-', 'ms-', ''];
function getStyleProperty(node, name) {
if (inBrowser) return '';
// old ff need null, https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
const style = window.getComputedStyle(node, null);
let ret = '';
for (let i = 0; i < prefixes.length; i++) {
ret = style.getPropertyValue(prefixes[i] + name);
if (ret) {
break;
}
}
return ret;
}
function fixBrowserByTimeout(node) {
if (isCssAnimationSupported) {
const transitionDelay = parseFloat(getStyleProperty(node, 'transition-delay')) || 0;
const transitionDuration = parseFloat(getStyleProperty(node, 'transition-duration')) || 0;
const animationDelay = parseFloat(getStyleProperty(node, 'animation-delay')) || 0;
const animationDuration = parseFloat(getStyleProperty(node, 'animation-duration')) || 0;
const time = Math.max(transitionDuration + transitionDelay, animationDuration + animationDelay);
// sometimes, browser bug
node.rcEndAnimTimeout = setTimeout(() => {
node.rcEndAnimTimeout = null;
if (node.rcEndListener) {
node.rcEndListener();
}
}, time * 1000 + 200);
}
}
function clearBrowserBugTimeout(node) {
if (node.rcEndAnimTimeout) {
clearTimeout(node.rcEndAnimTimeout);
node.rcEndAnimTimeout = null;
}
}
const cssAnimation = (node, transitionName, endCallback) => {
const nameIsObj = typeof transitionName === 'object';
const className = nameIsObj ? transitionName.name : transitionName;
const activeClassName = nameIsObj ? transitionName.active : `${transitionName}-active`;
let end = endCallback;
let start;
let active;
const nodeClasses = classes(node);
if (endCallback && Object.prototype.toString.call(endCallback) === '[object Object]') {
end = endCallback.end;
start = endCallback.start;
active = endCallback.active;
}
if (node.rcEndListener) {
node.rcEndListener();
}
node.rcEndListener = e => {
if (e && e.target !== node) {
return;
}
if (node.rcAnimTimeout) {
cancelAnimationTimeout(node.rcAnimTimeout);
node.rcAnimTimeout = null;
}
clearBrowserBugTimeout(node);
nodeClasses.remove(className);
nodeClasses.remove(activeClassName);
Event.removeEndEventListener(node, node.rcEndListener);
node.rcEndListener = null;
// Usually this optional end is used for informing an owner of
// a leave animation and telling it to remove the child.
if (end) {
end();
}
};
Event.addEndEventListener(node, node.rcEndListener);
if (start) {
start();
}
nodeClasses.add(className);
node.rcAnimTimeout = requestAnimationTimeout(() => {
node.rcAnimTimeout = null;
nodeClasses.add(className);
nodeClasses.add(activeClassName);
if (active) {
requestAnimationTimeout(active, 0);
}
fixBrowserByTimeout(node);
// 30ms for firefox
}, 30);
return {
stop() {
if (node.rcEndListener) {
node.rcEndListener();
}
},
};
};
cssAnimation.style = (node, style, callback) => {
if (node.rcEndListener) {
node.rcEndListener();
}
node.rcEndListener = e => {
if (e && e.target !== node) {
return;
}
if (node.rcAnimTimeout) {
cancelAnimationTimeout(node.rcAnimTimeout);
node.rcAnimTimeout = null;
}
clearBrowserBugTimeout(node);
Event.removeEndEventListener(node, node.rcEndListener);
node.rcEndListener = null;
// Usually this optional callback is used for informing an owner of
// a leave animation and telling it to remove the child.
if (callback) {
callback();
}
};
Event.addEndEventListener(node, node.rcEndListener);
node.rcAnimTimeout = requestAnimationTimeout(() => {
for (const s in style) {
if (style.hasOwnProperty(s)) {
node.style[s] = style[s];
}
}
node.rcAnimTimeout = null;
fixBrowserByTimeout(node);
}, 0);
};
cssAnimation.setTransition = (node, p, value) => {
let property = p;
let v = value;
if (value === undefined) {
v = property;
property = '';
}
property = property || '';
capitalPrefixes.forEach(prefix => {
node.style[`${prefix}Transition${property}`] = v;
});
};
cssAnimation.isCssAnimationSupported = isCssAnimationSupported;
export { isCssAnimationSupported };
export default cssAnimation;

View File

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

View File

@ -1,33 +0,0 @@
import type { Linter, LinterInfo } from './interface';
import { lintWarning } from './utils';
function isConcatSelector(selector: string) {
const notContent = selector.match(/:not\(([^)]*)\)/)?.[1] || '';
// split selector. e.g.
// `h1#a.b` => ['h1', #a', '.b']
const splitCells = notContent.split(/(\[[^[]*])|(?=[.#])/).filter(str => str);
return splitCells.length > 1;
}
function parsePath(info: LinterInfo) {
return info.parentSelectors.reduce((prev, cur) => {
if (!prev) {
return cur;
}
return cur.includes('&') ? cur.replace(/&/g, prev) : `${prev} ${cur}`;
}, '');
}
const linter: Linter = (_key, _value, info) => {
const parentSelectorPath = parsePath(info);
const notList = parentSelectorPath.match(/:not\([^)]*\)/g) || [];
if (notList.length > 0 && notList.some(isConcatSelector)) {
lintWarning(`Concat ':not' selector not support in legacy browsers.`, info);
}
};
export default linter;

View File

@ -1,88 +0,0 @@
import type { Linter } from './interface';
import { lintWarning } from './utils';
const linter: Linter = (key, value, info) => {
switch (key) {
case 'marginLeft':
case 'marginRight':
case 'paddingLeft':
case 'paddingRight':
case 'left':
case 'right':
case 'borderLeft':
case 'borderLeftWidth':
case 'borderLeftStyle':
case 'borderLeftColor':
case 'borderRight':
case 'borderRightWidth':
case 'borderRightStyle':
case 'borderRightColor':
case 'borderTopLeftRadius':
case 'borderTopRightRadius':
case 'borderBottomLeftRadius':
case 'borderBottomRightRadius':
lintWarning(
`You seem to be using non-logical property '${key}' which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
info,
);
return;
case 'margin':
case 'padding':
case 'borderWidth':
case 'borderStyle':
// case 'borderColor':
if (typeof value === 'string') {
const valueArr = value.split(' ').map(item => item.trim());
if (valueArr.length === 4 && valueArr[1] !== valueArr[3]) {
lintWarning(
`You seem to be using '${key}' property with different left ${key} and right ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
info,
);
}
}
return;
case 'clear':
case 'textAlign':
if (value === 'left' || value === 'right') {
lintWarning(
`You seem to be using non-logical value '${value}' of ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
info,
);
}
return;
case 'borderRadius':
if (typeof value === 'string') {
const radiusGroups = value.split('/').map(item => item.trim());
const invalid = radiusGroups.reduce((result, group) => {
if (result) {
return result;
}
const radiusArr = group.split(' ').map(item => item.trim());
// borderRadius: '2px 4px'
if (radiusArr.length >= 2 && radiusArr[0] !== radiusArr[1]) {
return true;
}
// borderRadius: '4px 4px 2px'
if (radiusArr.length === 3 && radiusArr[1] !== radiusArr[2]) {
return true;
}
// borderRadius: '4px 4px 2px 4px'
if (radiusArr.length === 4 && radiusArr[2] !== radiusArr[3]) {
return true;
}
return result;
}, false);
if (invalid) {
lintWarning(
`You seem to be using non-logical value '${value}' of ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
info,
);
}
}
return;
default:
}
};
export default linter;

View File

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

View File

@ -1,13 +0,0 @@
import devWarning from '../../../vc-util/warning';
import type { LinterInfo } from './interface';
export function lintWarning(message: string, info: LinterInfo) {
const { path, parentSelectors } = info;
devWarning(
false,
`[Ant Design Vue CSS-in-JS] ${path ? `Error in '${path}': ` : ''}${message}${
parentSelectors.length ? ` Selector info: ${parentSelectors.join(' -> ')}` : ''
}`,
);
}

View File

@ -1,38 +0,0 @@
import warning from '../../warning';
import type { DerivativeFunc, TokenType } from './interface';
let uuid = 0;
/**
* Theme with algorithms to derive tokens from design tokens.
* Use `createTheme` first which will help to manage the theme instance cache.
*/
export default class Theme<DesignToken extends TokenType, DerivativeToken extends TokenType> {
private derivatives: DerivativeFunc<DesignToken, DerivativeToken>[];
public readonly id: number;
constructor(
derivatives:
| DerivativeFunc<DesignToken, DerivativeToken>
| DerivativeFunc<DesignToken, DerivativeToken>[],
) {
this.derivatives = Array.isArray(derivatives) ? derivatives : [derivatives];
this.id = uuid;
if (derivatives.length === 0) {
warning(
derivatives.length > 0,
'[Ant Design Vue CSS-in-JS] Theme should have at least one derivative function.',
);
}
uuid += 1;
}
getDerivativeToken(token: DesignToken): DerivativeToken {
return this.derivatives.reduce<DerivativeToken>(
(result, derivative) => derivative(token, result),
undefined as any,
);
}
}

View File

@ -1,135 +0,0 @@
import type Theme from './Theme';
import type { DerivativeFunc } from './interface';
// ================================== Cache ==================================
type ThemeCacheMap = Map<
DerivativeFunc<any, any>,
{
map?: ThemeCacheMap;
value?: [Theme<any, any>, number];
}
>;
type DerivativeOptions = DerivativeFunc<any, any>[];
export function sameDerivativeOption(left: DerivativeOptions, right: DerivativeOptions) {
if (left.length !== right.length) {
return false;
}
for (let i = 0; i < left.length; i++) {
if (left[i] !== right[i]) {
return false;
}
}
return true;
}
export default class ThemeCache {
public static MAX_CACHE_SIZE = 20;
public static MAX_CACHE_OFFSET = 5;
private readonly cache: ThemeCacheMap;
private keys: DerivativeOptions[];
private cacheCallTimes: number;
constructor() {
this.cache = new Map();
this.keys = [];
this.cacheCallTimes = 0;
}
public size(): number {
return this.keys.length;
}
private internalGet(
derivativeOption: DerivativeOptions,
updateCallTimes = false,
): [Theme<any, any>, number] | undefined {
let cache: ReturnType<ThemeCacheMap['get']> = { map: this.cache };
derivativeOption.forEach(derivative => {
if (!cache) {
cache = undefined;
} else {
cache = cache?.map?.get(derivative);
}
});
if (cache?.value && updateCallTimes) {
cache.value[1] = this.cacheCallTimes++;
}
return cache?.value;
}
public get(derivativeOption: DerivativeOptions): Theme<any, any> | undefined {
return this.internalGet(derivativeOption, true)?.[0];
}
public has(derivativeOption: DerivativeOptions): boolean {
return !!this.internalGet(derivativeOption);
}
public set(derivativeOption: DerivativeOptions, value: Theme<any, any>): void {
// New cache
if (!this.has(derivativeOption)) {
if (this.size() + 1 > ThemeCache.MAX_CACHE_SIZE + ThemeCache.MAX_CACHE_OFFSET) {
const [targetKey] = this.keys.reduce<[DerivativeOptions, number]>(
(result, key) => {
const [, callTimes] = result;
if (this.internalGet(key)![1] < callTimes) {
return [key, this.internalGet(key)![1]];
}
return result;
},
[this.keys[0], this.cacheCallTimes],
);
this.delete(targetKey);
}
this.keys.push(derivativeOption);
}
let cache = this.cache;
derivativeOption.forEach((derivative, index) => {
if (index === derivativeOption.length - 1) {
cache.set(derivative, { value: [value, this.cacheCallTimes++] });
} else {
const cacheValue = cache.get(derivative);
if (!cacheValue) {
cache.set(derivative, { map: new Map() });
} else if (!cacheValue.map) {
cacheValue.map = new Map();
}
cache = cache.get(derivative)!.map!;
}
});
}
private deleteByPath(
currentCache: ThemeCacheMap,
derivatives: DerivativeFunc<any, any>[],
): Theme<any, any> | undefined {
const cache = currentCache.get(derivatives[0])!;
if (derivatives.length === 1) {
if (!cache.map) {
currentCache.delete(derivatives[0]);
} else {
currentCache.set(derivatives[0], { map: cache.map });
}
return cache.value?.[0];
}
const result = this.deleteByPath(cache.map!, derivatives.slice(1));
if ((!cache.map || cache.map.size === 0) && !cache.value) {
currentCache.delete(derivatives[0]);
}
return result;
}
public delete(derivativeOption: DerivativeOptions): Theme<any, any> | undefined {
// If cache exists
if (this.has(derivativeOption)) {
this.keys = this.keys.filter(item => !sameDerivativeOption(item, derivativeOption));
return this.deleteByPath(this.cache, derivativeOption);
}
return undefined;
}
}

View File

@ -1,26 +0,0 @@
import ThemeCache from './ThemeCache';
import Theme from './Theme';
import type { DerivativeFunc, TokenType } from './interface';
const cacheThemes = new ThemeCache();
/**
* Same as new Theme, but will always return same one if `derivative` not changed.
*/
export default function createTheme<
DesignToken extends TokenType,
DerivativeToken extends TokenType,
>(
derivatives:
| DerivativeFunc<DesignToken, DerivativeToken>[]
| DerivativeFunc<DesignToken, DerivativeToken>,
) {
const derivativeArr = Array.isArray(derivatives) ? derivatives : [derivatives];
// Create new theme if not exist
if (!cacheThemes.has(derivativeArr)) {
cacheThemes.set(derivativeArr, new Theme(derivativeArr));
}
// Get theme from cache and return
return cacheThemes.get(derivativeArr)!;
}

View File

@ -1,4 +0,0 @@
export { default as createTheme } from './createTheme';
export { default as Theme } from './Theme';
export { default as ThemeCache } from './ThemeCache';
export type { TokenType, DerivativeFunc } from './interface';

View File

@ -1,5 +0,0 @@
export type TokenType = object;
export type DerivativeFunc<DesignToken extends TokenType, DerivativeToken extends TokenType> = (
designToken: DesignToken,
derivativeToken?: DerivativeToken,
) => DerivativeToken;

View File

@ -1,5 +0,0 @@
import type { CSSObject } from '..';
export interface Transformer {
visit?: (cssObj: CSSObject) => CSSObject;
}

View File

@ -1,162 +0,0 @@
import type { CSSObject } from '..';
import type { Transformer } from './interface';
function splitValues(value: string | number) {
if (typeof value === 'number') {
return [value];
}
const splitStyle = String(value).split(/\s+/);
// Combine styles split in brackets, like `calc(1px + 2px)`
let temp = '';
let brackets = 0;
return splitStyle.reduce<string[]>((list, item) => {
if (item.includes('(')) {
temp += item;
brackets += item.split('(').length - 1;
} else if (item.includes(')')) {
temp += ` ${item}`;
brackets -= item.split(')').length - 1;
if (brackets === 0) {
list.push(temp);
temp = '';
}
} else if (brackets > 0) {
temp += ` ${item}`;
} else {
list.push(item);
}
return list;
}, []);
}
type MatchValue = string[] & {
notSplit?: boolean;
};
function noSplit(list: MatchValue): MatchValue {
list.notSplit = true;
return list;
}
const keyMap: Record<string, MatchValue> = {
// Inset
inset: ['top', 'right', 'bottom', 'left'],
insetBlock: ['top', 'bottom'],
insetBlockStart: ['top'],
insetBlockEnd: ['bottom'],
insetInline: ['left', 'right'],
insetInlineStart: ['left'],
insetInlineEnd: ['right'],
// Margin
marginBlock: ['marginTop', 'marginBottom'],
marginBlockStart: ['marginTop'],
marginBlockEnd: ['marginBottom'],
marginInline: ['marginLeft', 'marginRight'],
marginInlineStart: ['marginLeft'],
marginInlineEnd: ['marginRight'],
// Padding
paddingBlock: ['paddingTop', 'paddingBottom'],
paddingBlockStart: ['paddingTop'],
paddingBlockEnd: ['paddingBottom'],
paddingInline: ['paddingLeft', 'paddingRight'],
paddingInlineStart: ['paddingLeft'],
paddingInlineEnd: ['paddingRight'],
// Border
borderBlock: noSplit(['borderTop', 'borderBottom']),
borderBlockStart: noSplit(['borderTop']),
borderBlockEnd: noSplit(['borderBottom']),
borderInline: noSplit(['borderLeft', 'borderRight']),
borderInlineStart: noSplit(['borderLeft']),
borderInlineEnd: noSplit(['borderRight']),
// Border width
borderBlockWidth: ['borderTopWidth', 'borderBottomWidth'],
borderBlockStartWidth: ['borderTopWidth'],
borderBlockEndWidth: ['borderBottomWidth'],
borderInlineWidth: ['borderLeftWidth', 'borderRightWidth'],
borderInlineStartWidth: ['borderLeftWidth'],
borderInlineEndWidth: ['borderRightWidth'],
// Border style
borderBlockStyle: ['borderTopStyle', 'borderBottomStyle'],
borderBlockStartStyle: ['borderTopStyle'],
borderBlockEndStyle: ['borderBottomStyle'],
borderInlineStyle: ['borderLeftStyle', 'borderRightStyle'],
borderInlineStartStyle: ['borderLeftStyle'],
borderInlineEndStyle: ['borderRightStyle'],
// Border color
borderBlockColor: ['borderTopColor', 'borderBottomColor'],
borderBlockStartColor: ['borderTopColor'],
borderBlockEndColor: ['borderBottomColor'],
borderInlineColor: ['borderLeftColor', 'borderRightColor'],
borderInlineStartColor: ['borderLeftColor'],
borderInlineEndColor: ['borderRightColor'],
// Border radius
borderStartStartRadius: ['borderTopLeftRadius'],
borderStartEndRadius: ['borderTopRightRadius'],
borderEndStartRadius: ['borderBottomLeftRadius'],
borderEndEndRadius: ['borderBottomRightRadius'],
};
function skipCheck(value: string | number) {
return { _skip_check_: true, value };
}
/**
* Convert css logical properties to legacy properties.
* Such as: `margin-block-start` to `margin-top`.
* Transform list:
* - inset
* - margin
* - padding
* - border
*/
const transform: Transformer = {
visit: cssObj => {
const clone: CSSObject = {};
Object.keys(cssObj).forEach(key => {
const value = cssObj[key];
const matchValue = keyMap[key];
if (matchValue && (typeof value === 'number' || typeof value === 'string')) {
const values = splitValues(value);
if (matchValue.length && matchValue.notSplit) {
// not split means always give same value like border
matchValue.forEach(matchKey => {
clone[matchKey] = skipCheck(value);
});
} else if (matchValue.length === 1) {
// Handle like `marginBlockStart` => `marginTop`
clone[matchValue[0]] = skipCheck(value);
} else if (matchValue.length === 2) {
// Handle like `marginBlock` => `marginTop` & `marginBottom`
matchValue.forEach((matchKey, index) => {
clone[matchKey] = skipCheck(values[index] ?? values[0]);
});
} else if (matchValue.length === 4) {
// Handle like `inset` => `top` & `right` & `bottom` & `left`
matchValue.forEach((matchKey, index) => {
clone[matchKey] = skipCheck(values[index] ?? values[index - 2] ?? values[0]);
});
} else {
clone[key] = value;
}
} else {
clone[key] = value;
}
});
return clone;
},
};
export default transform;

View File

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

View File

@ -1,118 +0,0 @@
import hash from '@emotion/hash';
import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
import canUseDom from '../canUseDom';
import { Theme } from './theme';
// Create a cache here to avoid always loop generate
const flattenTokenCache = new WeakMap<any, string>();
export function flattenToken(token: any) {
let str = flattenTokenCache.get(token) || '';
if (!str) {
Object.keys(token).forEach(key => {
const value = token[key];
str += key;
if (value instanceof Theme) {
str += value.id;
} else if (value && typeof value === 'object') {
str += flattenToken(value);
} else {
str += value;
}
});
// Put in cache
flattenTokenCache.set(token, str);
}
return str;
}
/**
* Convert derivative token to key string
*/
export function token2key(token: any, salt: string): string {
return hash(`${salt}_${flattenToken(token)}`);
}
const randomSelectorKey = `random-${Date.now()}-${Math.random()}`.replace(/\./g, '');
// Magic `content` for detect selector support
const checkContent = '_bAmBoO_';
function supportSelector(
styleStr: string,
handleElement: (ele: HTMLElement) => void,
supportCheck?: (ele: HTMLElement) => boolean,
): boolean {
if (canUseDom()) {
updateCSS(styleStr, randomSelectorKey);
const ele = document.createElement('div');
ele.style.position = 'fixed';
ele.style.left = '0';
ele.style.top = '0';
handleElement?.(ele);
document.body.appendChild(ele);
if (process.env.NODE_ENV !== 'production') {
ele.innerHTML = 'Test';
ele.style.zIndex = '9999999';
}
const support = supportCheck
? supportCheck(ele)
: getComputedStyle(ele).content?.includes(checkContent);
ele.parentNode?.removeChild(ele);
removeCSS(randomSelectorKey);
return support;
}
return false;
}
let canLayer: boolean | undefined = undefined;
export function supportLayer(): boolean {
if (canLayer === undefined) {
canLayer = supportSelector(
`@layer ${randomSelectorKey} { .${randomSelectorKey} { content: "${checkContent}"!important; } }`,
ele => {
ele.className = randomSelectorKey;
},
);
}
return canLayer!;
}
let canWhere: boolean | undefined = undefined;
export function supportWhere(): boolean {
if (canWhere === undefined) {
canWhere = supportSelector(
`:where(.${randomSelectorKey}) { content: "${checkContent}"!important; }`,
ele => {
ele.className = randomSelectorKey;
},
);
}
return canWhere!;
}
let canLogic: boolean | undefined = undefined;
export function supportLogicProps(): boolean {
if (canLogic === undefined) {
canLogic = supportSelector(
`.${randomSelectorKey} { inset-block: 93px !important; }`,
ele => {
ele.className = randomSelectorKey;
},
ele => getComputedStyle(ele).bottom === '93px',
);
}
return canLogic!;
}

View File

@ -1,16 +0,0 @@
import { watchEffect, shallowRef } from 'vue';
import type { ComputedRef } from 'vue';
export declare type ComputedGetter<T> = (...args: any[]) => T;
export default function eagerComputed<T>(fn: ComputedGetter<T>) {
const result = shallowRef<T>();
watchEffect(
() => {
result.value = fn();
},
{
flush: 'sync', // needed so updates are immediate.
},
);
return result as any as ComputedRef<T>;
}

View File

@ -1,21 +0,0 @@
type RecordType = Record<string, any>;
function extendsObject<T extends RecordType>(...list: T[]) {
const result: RecordType = { ...list[0] };
for (let i = 1; i < list.length; i++) {
const obj = list[i];
if (obj) {
Object.keys(obj).forEach(key => {
const val = obj[key];
if (val !== undefined) {
result[key] = val;
}
});
}
}
return result;
}
export default extendsObject;

View File

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

View File

@ -0,0 +1,30 @@
export function getComponentLocale(props, context, componentName, getDefaultLocale) {
let locale = {};
if (context && context.antLocale && context.antLocale[componentName]) {
locale = context.antLocale[componentName];
} else {
const defaultLocale = getDefaultLocale();
// TODO: make default lang of antd be English
// https://github.com/ant-design/ant-design/issues/6334
locale = defaultLocale.default || defaultLocale;
}
const result = {
...locale,
...props.locale,
};
result.lang = {
...locale.lang,
...props.locale.lang,
};
return result;
}
export function getLocaleCode(context) {
const localeCode = context.antLocale && context.antLocale.locale;
// Had use LocaleProvide but didn't set locale
if (context.antLocale && context.antLocale.exist && !localeCode) {
return 'zh-cn';
}
return localeCode;
}

View File

@ -1,4 +1,4 @@
export function isWindow(obj: any): obj is Window {
export function isWindow(obj: any) {
return obj !== null && obj !== undefined && obj === obj.window;
}
@ -12,22 +12,16 @@ export default function getScroll(
const method = top ? 'scrollTop' : 'scrollLeft';
let result = 0;
if (isWindow(target)) {
result = target[top ? 'scrollY' : 'scrollX'];
result = (target as Window)[top ? 'pageYOffset' : 'pageXOffset'];
} else if (target instanceof Document) {
result = target.documentElement[method];
} else if (target instanceof HTMLElement) {
result = target[method];
} else if (target) {
// According to the type inference, the `target` is `never` type.
// Since we configured the loose mode type checking, and supports mocking the target with such shape below::
// `{ documentElement: { scrollLeft: 200, scrollTop: 400 } }`,
// the program may falls into this branch.
// Check the corresponding tests for details. Don't sure what is the real scenario this happens.
result = target[method];
result = (target as HTMLElement)[method];
}
if (target && !isWindow(target) && typeof result !== 'number') {
result = ((target.ownerDocument ?? target) as any).documentElement?.[method];
result = ((target as HTMLElement).ownerDocument || (target as Document)).documentElement?.[
method
];
}
return result;
}

View File

@ -1,34 +0,0 @@
import { isClient } from './is';
export interface ConfigurableWindow {
/*
* Specify a custom `window` instance, e.g. working with iframes or in testing environments.
*/
window?: Window;
}
export interface ConfigurableDocument {
/*
* Specify a custom `document` instance, e.g. working with iframes or in testing environments.
*/
document?: Document;
}
export interface ConfigurableNavigator {
/*
* Specify a custom `navigator` instance, e.g. working with iframes or in testing environments.
*/
navigator?: Navigator;
}
export interface ConfigurableLocation {
/*
* Specify a custom `location` instance, e.g. working with iframes or in testing environments.
*/
location?: Location;
}
export const defaultWindow = isClient ? window : undefined;
export const defaultDocument = isClient ? window.document : undefined;
export const defaultNavigator = isClient ? window.navigator : undefined;
export const defaultLocation = isClient ? window.location : undefined;

View File

@ -1,26 +0,0 @@
export const isClient = typeof window !== 'undefined';
export const isDef = <T = any>(val?: T): val is T => typeof val !== 'undefined';
export const assert = (condition: boolean, ...infos: any[]) => {
if (!condition) console.warn(...infos);
};
const toString = Object.prototype.toString;
export const isBoolean = (val: any): val is boolean => typeof val === 'boolean';
export const isFunction = <T extends Function>(val: any): val is T => typeof val === 'function';
export const isNumber = (val: any): val is number => typeof val === 'number';
export const isString = (val: unknown): val is string => typeof val === 'string';
export const isObject = (val: any): val is object => toString.call(val) === '[object Object]';
export const isWindow = (val: any): val is Window =>
typeof window !== 'undefined' && toString.call(val) === '[object Window]';
export const now = () => Date.now();
export const timestamp = () => +Date.now();
export const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));
export const noop = () => {};
export const rand = (min: number, max: number) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
};
export const isIOS =
isClient && window?.navigator?.userAgent && /iP(ad|hone|od)/.test(window.navigator.userAgent);
export const hasOwn = <T extends object, K extends keyof T>(val: T, key: K): key is K =>
Object.prototype.hasOwnProperty.call(val, key);

View File

@ -1,9 +0,0 @@
import { unref } from 'vue';
import type { MaybeComputedRef } from './types';
/**
* Get the value of value/ref/getter.
*/
export function resolveUnref<T>(r: MaybeComputedRef<T>): T {
return typeof r === 'function' ? (r as any)() : unref(r);
}

View File

@ -1,15 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import { getCurrentInstance, nextTick, onMounted } from 'vue';
import type { Fn } from './types';
/**
* Call onMounted() if it's inside a component lifecycle, if not, just call the function
*
* @param fn
* @param sync if set to false, it will run in the nextTick() of Vue
*/
export function tryOnMounted(fn: Fn, sync = true) {
if (getCurrentInstance()) onMounted(fn);
else if (sync) fn();
else nextTick(fn);
}

View File

@ -1,15 +0,0 @@
import { getCurrentScope, onScopeDispose } from 'vue';
import type { Fn } from './types';
/**
* Call onScopeDispose() if it's inside a effect scope lifecycle, if not, do nothing
*
* @param fn
*/
export function tryOnScopeDispose(fn: Fn) {
if (getCurrentScope()) {
onScopeDispose(fn);
return true;
}
return false;
}

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