Compare commits

..

17 Commits

Author SHA1 Message Date
tangjinzhou 2cda7bde09 docs: add Sponsor 2025-11-18 00:45:12 +08:00
tangjinzhou 6255f1632c docs: update readme 2025-11-18 00:29:08 +08:00
tangjinzhou ef51f7f1cc docs: update GlobalConfig type import paths across multiple components 2025-11-17 23:57:03 +08:00
tangjinzhou 252a0e2563 docs: clean up GlobalConfig type definition by moving it to a separate type file 2025-11-17 23:50:44 +08:00
tangjinzhou bbb7670df1 refactor: remove Google Ads component from top_rice.vue and update favicon.ico 2025-09-20 14:30:15 +08:00
tangjinzhou fcdda4a0be docs: update links 2025-09-19 11:28:25 +08:00
tangjinzhou 79b63b41c6 feat: add routing configuration for antdv.com in wrangler.jsonc 2025-09-19 00:16:11 +08:00
tangjinzhou 56d9b358f2 refactor: remove Surely Form links and related alert banners from Menu and Header components 2025-09-18 23:08:14 +08:00
tangjinzhou b58e73d51a refactor: update URLs from surely.cool to surelyvue.com across multiple components and documentation 2025-09-18 22:54:57 +08:00
tangjinzhou e8ec520d9a docs: update npm scripts for build process and remove cheerio dependency 2025-09-18 22:53:48 +08:00
tangjinzhou ce56f0c8f6 docs: remove aliyun 2025-09-18 22:39:56 +08:00
Sid Roberts 723bb47a42
Updated to actions/cache@v4 for GitHub workflows. (#8317)
https://github.com/actions/cache/discussions/1510
2025-08-27 15:30:19 +08:00
Junyang 57ea8a65d4
fix: fixed the Select component not taking keys according to the corresponding value when passing fieldNames. (#8247)
Co-authored-by: fanjunyang <fanjunyang@daojia-inc.com>
2025-08-27 15:27:56 +08:00
Chris Yang 7e5008080d
fix: Select tagRender click cannot open list #8132 (#8236) 2025-08-27 15:24:42 +08:00
MonsterXue 74b3018945
fix(slider): fix slider last dot can’t click (#8246) 2025-08-27 15:12:36 +08:00
Jason Ren 28b1c4f62d
docs: typo in button (#8024) 2025-08-27 15:09:00 +08:00
pengpeng 04f6ba33c7
fix(vcSelect):Maintain keyboard events when using a custom element. (#8321) 2025-08-27 15:07:36 +08:00
712 changed files with 38385 additions and 7549 deletions

36
.antd-tools.config.js Normal file
View File

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

2
.codecov.yml Normal file
View File

@ -0,0 +1,2 @@
codecov:
branch: master

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
# 🎨 editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

13
.eslintignore Normal file
View File

@ -0,0 +1,13 @@
node_modules/
**/*.spec.*
**/style/
*.html
/components/test/*
es/
lib/
_site/
dist/
site/dist/
components/version/version.ts
site/src/router/demoRoutes.js
locale/

112
.eslintrc.js Normal file
View File

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

View File

@ -12,6 +12,3 @@ contact_links:
- name: Paypal - name: Paypal
url: https://www.paypal.me/tangjinzhou url: https://www.paypal.me/tangjinzhou
about: Love Ant Design Vue? Please consider supporting us via Paypal. about: Love Ant Design Vue? Please consider supporting us via Paypal.
- name: 支付宝/微信 赞助
url: https://aliyuncdn.antdv.com/alipay-and-wechat.png
about: Ant Design Vue 的健康持续发展需要您的支持,🙏

63
.github/workflows/cloudflare.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: Build and Deploy to Cloudflare
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
name: Build Application
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm install --force
- name: Build application
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-files
path: site/dist/
retention-days: 1
deploy:
runs-on: ubuntu-latest
name: Deploy to Cloudflare
needs: build
permissions:
contents: read
deployments: write
steps:
- name: Checkout (for config files)
uses: actions/checkout@v5
with:
sparse-checkout: |
wrangler.jsonc
sparse-checkout-cone-mode: false
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-files
path: site/dist/
- name: Deploy (Workers + Static Assets)
uses: cloudflare/wrangler-action@v3.14.1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangler.jsonc
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

View File

@ -10,7 +10,7 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: cache package-lock.json - name: cache package-lock.json
uses: actions/cache@v1 uses: actions/cache@v4
with: with:
path: package-temp-dir path: package-temp-dir
key: lock-${{ github.sha }} key: lock-${{ github.sha }}
@ -27,7 +27,7 @@ jobs:
- name: cache node_modules - name: cache node_modules
id: node_modules_cache_id id: node_modules_cache_id
uses: actions/cache@v1 uses: actions/cache@v4
with: with:
path: node_modules path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
@ -52,13 +52,13 @@ jobs:
# submodules: true # submodules: true
- name: restore cache from package-lock.json - name: restore cache from package-lock.json
uses: actions/cache@v1 uses: actions/cache@v4
with: with:
path: package-temp-dir path: package-temp-dir
key: lock-${{ github.sha }} key: lock-${{ github.sha }}
- name: restore cache from node_modules - name: restore cache from node_modules
uses: actions/cache@v1 uses: actions/cache@v4
with: with:
path: node_modules path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}

View File

@ -10,7 +10,7 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: cache package-lock.json - name: cache package-lock.json
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: package-temp-dir path: package-temp-dir
key: lock-${{ github.sha }} key: lock-${{ github.sha }}
@ -27,7 +27,7 @@ jobs:
- name: cache node_modules - name: cache node_modules
id: node_modules_cache_id id: node_modules_cache_id
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: node_modules path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
@ -43,25 +43,25 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: restore cache from package-lock.json - name: restore cache from package-lock.json
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: package-temp-dir path: package-temp-dir
key: lock-${{ github.sha }} key: lock-${{ github.sha }}
- name: restore cache from node_modules - name: restore cache from node_modules
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: node_modules path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: cache lib - name: cache lib
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: lib path: lib
key: lib-${{ github.sha }} key: lib-${{ github.sha }}
- name: cache es - name: cache es
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: es path: es
key: es-${{ github.sha }} key: es-${{ github.sha }}
@ -77,13 +77,13 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: restore cache from package-lock.json - name: restore cache from package-lock.json
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: package-temp-dir path: package-temp-dir
key: lock-${{ github.sha }} key: lock-${{ github.sha }}
- name: restore cache from node_modules - name: restore cache from node_modules
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: node_modules path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
@ -99,13 +99,13 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: restore cache from package-lock.json - name: restore cache from package-lock.json
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: package-temp-dir path: package-temp-dir
key: lock-${{ github.sha }} key: lock-${{ github.sha }}
- name: restore cache from node_modules - name: restore cache from node_modules
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: node_modules path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}

44
.gitignore vendored
View File

@ -66,6 +66,9 @@ package-lock.json
pnpm-lock.yaml pnpm-lock.yaml
/coverage /coverage
# 备份文件
/components/test/*
list.txt
site/dev.js site/dev.js
@ -74,39 +77,10 @@ vetur/
report.html report.html
site/src/router/demoRoutes.js
components/version/version.ts
# Local env files components/version/version.tsx
.env components/version/token.json
.env.* components/version/token-meta.json
!.env.template ~component-api.json
# Testing
coverage
# Turbo
.turbo
# Vercel
.vercel
# Build Outputs
.next/
out/
build/
dist/
storybook-static/
# Debug
npm-debug.log*
# Misc
.DS_Store
*.pem
vite.config.*.timestamp*
*.tsbuildinfo
*.log
.npmrc
.tsup/

4
.husky/pre-commit Executable file
View File

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

7
.huskyrc Normal file
View File

@ -0,0 +1,7 @@
{
"hooks": {
"pre-commit": "pretty-quick --staged",
"pre-publish": "npm run lint",
"commit-msg": "commitlint -x @commitlint/config-conventional -e $GIT_PARAMS"
}
}

60
.jest.js Normal file
View File

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

31
.prettierignore Normal file
View File

@ -0,0 +1,31 @@
**/*.svg
lib/
es/
dist/
_site/
coverage/
CNAME
LICENSE
yarn.lock
netlify.toml
yarn-error.log
*.sh
*.snap
.gitignore
.npmignore
.prettierignore
.DS_Store
.editorconfig
.eslintignore
**/*.yml
**/assets
.gitattributes
.stylelintrc
.vcmrc
.png
.npmrc.template
.huskyrc
.gitmodules
*.png
v2-doc/

17
.prettierrc Normal file
View File

@ -0,0 +1,17 @@
{
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"printWidth": 100,
"proseWrap": "never",
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"overrides": [
{
"files": ".prettierrc",
"options": {
"parser": "json"
}
}
]
}

23
.stylelintrc Normal file
View File

@ -0,0 +1,23 @@
{
"extends": ["stylelint-config-standard", "stylelint-config-prettier"],
"rules": {
"comment-empty-line-before": null,
"declaration-empty-line-before": null,
"function-comma-newline-after": null,
"function-name-case": null,
"function-parentheses-newline-inside": null,
"function-max-empty-lines": null,
"function-whitespace-after": null,
"indentation": null,
"number-leading-zero": null,
"number-no-trailing-zeros": null,
"rule-empty-line-before": null,
"selector-combinator-space-after": null,
"selector-list-comma-newline-after": null,
"selector-pseudo-element-colon-notation": null,
"unit-no-unknown": null,
"value-list-max-empty-lines": null,
"font-family-no-missing-generic-family-keyword": null,
"no-descending-specificity": null
}
}

43
.stylelintrc.json Normal file
View File

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

17
.vcmrc Normal file
View File

@ -0,0 +1,17 @@
{
"helpMessage": "\nPlease fix your commit message (and consider using https://www.npmjs.com/package/commitizen)\n",
"types": [
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"chore",
"revert",
"ci"
],
"warnOnFail": false,
"autoFix": false
}

View File

@ -1,9 +1,3 @@
<p align="center">
<a href="https://www.antdv.com/">
<img width="200" src="https://aliyuncdn.antdv.com/logo.png">
</a>
</p>
<h1 align="center"> <h1 align="center">
<a href="https://www.antdv.com/" target="_blank">Ant Design Vue</a> <a href="https://www.antdv.com/" target="_blank">Ant Design Vue</a>
</h1> </h1>
@ -18,6 +12,14 @@
[![](https://cdn-images-1.medium.com/max/2000/1*NIlj0-TdLMbo_hzSBP8tmg.png)](https://www.antdv.com/) [![](https://cdn-images-1.medium.com/max/2000/1*NIlj0-TdLMbo_hzSBP8tmg.png)](https://www.antdv.com/)
<div align="center">
<sup><strong>赞助商</strong></sup>
<br>
<a href="https://mentorbook.ai/" target="_blank">
<img src="/site/public/mentorbook_banner_zh.svg" alt="Mentorbook.AI - 你的 AI 导师,你的学习之旅" />
</a>
</div>
[English](./README.md) | 简体中文 [English](./README.md) | 简体中文
## 特性 ## 特性
@ -88,14 +90,13 @@ ant-design-vue 是 MIT 协议的开源项目。为了项目能够更好的持续
- [Patreon](https://www.patreon.com/tangjinzhou) - [Patreon](https://www.patreon.com/tangjinzhou)
- [opencollective](https://opencollective.com/ant-design-vue) - [opencollective](https://opencollective.com/ant-design-vue)
- [paypal](https://www.paypal.me/tangjinzhou) - [paypal](https://www.paypal.me/tangjinzhou)
- [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png)
- ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2 - ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2
## 赞助商 ## 赞助商
成为赞助商,并在 Github 上的自述文件上获得您的徽标,并链接到您的网站。 [[成为赞助商](https://opencollective.com/ant-design-vue#sponsor)] 成为赞助商,并在 Github 上的自述文件上获得您的徽标,并链接到您的网站。 [[成为赞助商](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="http://www.jeecg.com/" target="_blank"><img src="https://www.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>
## 支持者 ## 支持者

View File

@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<a href="https://www.antdv.com/"> <a href="https://www.antdv.com/">
<img width="200" src="https://aliyuncdn.antdv.com/logo.png"> <img width="200" src="https://www.antdv.com/logo.png">
</a> </a>
</p> </p>
@ -18,6 +18,14 @@ An enterprise-class UI components based on Ant Design and Vue.
[![](https://cdn-images-1.medium.com/max/2000/1*NIlj0-TdLMbo_hzSBP8tmg.png)](https://www.antdv.com/) [![](https://cdn-images-1.medium.com/max/2000/1*NIlj0-TdLMbo_hzSBP8tmg.png)](https://www.antdv.com/)
<div align="center">
<sup><strong>Sponsored by</strong></sup>
<br>
<a href="https://mentorbook.ai/" target="_blank">
<img src="/site/public/mentorbook_banner_en.svg" alt="Mentorbook.AI - Your AI Mentor, Your Learning Journey" />
</a>
</div>
English | [简体中文](./README-zh_CN.md) English | [简体中文](./README-zh_CN.md)
## Features ## Features
@ -82,14 +90,13 @@ ant-design-vue is an MIT-licensed open source project. In order to achieve bette
- [Patreon](https://www.patreon.com/tangjinzhou) - [Patreon](https://www.patreon.com/tangjinzhou)
- [opencollective](https://opencollective.com/ant-design-vue) - [opencollective](https://opencollective.com/ant-design-vue)
- [paypal](https://www.paypal.me/tangjinzhou) - [paypal](https://www.paypal.me/tangjinzhou)
- [支付宝或微信](https://aliyuncdn.antdv.com/alipay-and-wechat.png)
- ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2 - ETH: 0x30cc48515d8ae9fefa20ab87226ad7e8ab9c3bc2
## Sponsors ## 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)] 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="http://www.jeecg.com/" target="_blank"><img src="https://www.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>
## [More Sponsor (From Patreon、alipay、wechat、paypal...)](https://github.com/vueComponent/ant-design-vue/blob/master/BACKERS.md) ## [More Sponsor (From Patreon、alipay、wechat、paypal...)](https://github.com/vueComponent/ant-design-vue/blob/master/BACKERS.md)

View File

@ -1,15 +0,0 @@
@import '@ant-design-vue/tailwind-config';
@source '../index.html';
@source '../src/**/*.{vue,ts}';
* {
scrollbar-width: thin;
scrollbar-color: var(--color-base-300) transparent;
}
*:focus-visible {
outline: none;
}
.shiki.github-dark,
.dark-scrollbar {
scrollbar-color: rgba(121, 121, 121, 0.4) transparent;
}

View File

@ -1,3 +0,0 @@
// @ts-check
export { default } from '@ant-design-vue/eslint-config/vue'

View File

@ -1,12 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Playground</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,45 +0,0 @@
{
"name": "playground",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite",
"lint": "eslint . --fix",
"preview": "vite preview",
"tsc": "vue-tsc --noEmit"
},
"dependencies": {
"@floating-ui/vue": "^1.1.5",
"@heroicons/vue": "^2.1.5",
"@ant-design-vue/ui": "*",
"@simonwep/pickr": "^1.9.1",
"@trpc/client": "^11.0.0",
"@trpc/server": "^11.0.0",
"@wdns/vue-code-block": "^2.3.3",
"clsx": "^2.1.1",
"cookies": "^0.9.1",
"uuid": "^10.0.0",
"vue": "^3.4.34",
"vue-router": "^4.4.0"
},
"devDependencies": {
"@ant-design-vue/eslint-config": "*",
"@ant-design-vue/prettier-config": "*",
"@ant-design-vue/typescript-config": "*",
"@ant-design-vue/vite-config": "*",
"@ant-design-vue/tailwind-config": "*",
"@tailwindcss/vite": "^4.1.3",
"@types/cookies": "^0.9.0",
"@types/node": "^20.0.0",
"@vitejs/plugin-vue": "^5.1.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^4.1.3",
"typescript": "^5.8.2",
"vite": "^5.3.5",
"vite-plugin-dts": "^3.9.1",
"vite-svg-loader": "^5.1.0",
"vue-tsc": "^3.0.3"
}
}

View File

@ -1,3 +0,0 @@
// @ts-check
export { default } from "@ant-design-vue/prettier-config/tailwind";

View File

@ -1,15 +0,0 @@
<template>
<button @click="toggleTheme" class="fixed top-2 right-2">toggle {{ appearance }}</button>
<a-theme :appearance="appearance">
<RouterView />
</a-theme>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const appearance = ref('light')
const toggleTheme = () => {
appearance.value = appearance.value === 'light' ? 'dark' : 'light'
}
</script>

View File

@ -1,29 +0,0 @@
<template>
<div class="flex h-screen" :class="pageClass">
<TheNavbar v-if="!hideNavbar" :items="navs"></TheNavbar>
<div class="flex-1 justify-center px-4 py-16" :class="contentClass">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup>
import { provideLayoutOptions } from '@/composables/layout'
import { computed, ref } from 'vue'
import TheNavbar from './TheNavbar.vue'
const props = defineProps<{
navs: { name: string; path: string }[]
hideNavbar?: boolean
hideBreadcrumbs?: boolean
}>()
const pageClass = ref<string>()
const contentClass = ref<string>()
provideLayoutOptions({
pageClass,
contentClass,
hideNavbar: computed(() => props.hideNavbar),
hideBreadcrumbs: computed(() => props.hideBreadcrumbs),
})
</script>

View File

@ -1,25 +0,0 @@
<template>
<div class="flex flex-wrap gap-8 px-8">
<RouterLink v-for="item in items" :key="item.path" :to="item.path">
<div
class="shadow-xs relative flex w-72 flex-col rounded-lg bg-primary capitalize text-primary-content transition-all hover:scale-105 hover:shadow-xl"
>
<div className="flex-col gap-2 flex flex-auto p-4 text-sm items-center text-center">
<h2 className="font-semibold flex items-center gap-2 text-xl mb-1">
{{ item.name }}
</h2>
<div className="flex flex-wrap items-start gap-2 justify-end">
<ArrowRightIcon class="size-5" />
</div>
</div>
</div>
</RouterLink>
</div>
</template>
<script lang="ts" setup>
import { ArrowRightIcon } from '@heroicons/vue/20/solid'
defineProps<{
items: { name: string; path: string }[]
}>()
</script>

View File

@ -1,29 +0,0 @@
<template>
<div class="max-w-full select-none overflow-x-auto px-4 py-2 text-sm">
<ul class="flex min-h-min items-center whitespace-nowrap capitalize">
<template v-for="(item, i) in items" :key="item.path">
<li class="flex items-center">
<template v-if="i > 0">
<span
class="ml-2 mr-3 block size-1.5 rotate-45 transform border-r-[1px] border-t-[1px] border-base-content/70 bg-transparent"
></span>
</template>
<template v-if="item.path !== route.path">
<RouterLink class="flex items-center hover:underline" :to="item.path">
{{ item.name }}
</RouterLink>
</template>
<template v-else>
<span class="text-base-content/70">{{ item.name }}</span>
</template>
</li>
</template>
</ul>
</div>
</template>
<script lang="ts" setup>
import { useRoute } from 'vue-router'
defineProps<{ items: { name: string; path: string }[] }>()
const route = useRoute()
</script>

View File

@ -1,44 +0,0 @@
<template>
<div class="bg-base-100/90 text-base-content border-base-content/10 border-r">
<div class="flex min-h-16 w-full items-center p-2">
<div class="justify-start">
<div class="group relative inline-block">
<ul
tabindex="0"
class="bg-base-100 z-[1] mt-3 flex w-52 origin-top scale-95 flex-col flex-wrap rounded-lg p-2 text-sm capitalize"
>
<li v-for="item in items" :key="item.name">
<RouterLink
:aria-disabled="item.path === route.path"
:to="item.path"
@click.stop="$event.currentTarget.blur()"
class="hover:bg-base-content/10 flex cursor-pointer flex-col rounded-lg px-3 py-2 transition duration-200"
>
{{ item.name }}
</RouterLink>
<ul v-if="item.children">
<li v-for="child in item.children" :key="child.name">
<RouterLink
:to="child.path"
@click.stop="$event.currentTarget.blur()"
class="hover:bg-base-content/10 flex cursor-pointer flex-col rounded-lg px-3 py-2 text-xs opacity-80 transition duration-200"
>
{{ child.name }}
</RouterLink>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRoute } from 'vue-router'
defineProps<{
items: { name: string; path: string; children?: { name: string; path: string }[] }[]
}>()
const route = useRoute()
</script>

View File

@ -1,22 +0,0 @@
import { inject, InjectionKey, provide, Ref } from 'vue'
export interface LayoutOptions {
pageClass: Ref<string | undefined>
contentClass: Ref<string | undefined>
hideNavbar: Ref<boolean>
hideBreadcrumbs: Ref<boolean>
}
const LayoutOptionsToken: InjectionKey<LayoutOptions> = Symbol()
export function provideLayoutOptions(options: LayoutOptions) {
provide(LayoutOptionsToken, options)
}
export function injectLayoutOptions() {
const options = inject(LayoutOptionsToken)
if (!options) {
throw new Error('"injectLayoutOptions" must be called inside pages')
}
return options
}

View File

@ -1,15 +0,0 @@
import '~/tailwind.css'
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import routes from './routes'
import antd from '@ant-design-vue/ui'
import '@ant-design-vue/ui/tailwind.css'
import '@ant-design-vue/ui/style.css'
const router = createRouter({
history: createWebHistory(),
routes,
})
createApp(App).use(router).use(antd).mount('#app')

View File

@ -1,20 +0,0 @@
<template>
<div class="flex h-[200vh] flex-col gap-2">
<a-affix :offset-top="top" @change="onChange">
<a-button type="primary" @click="top += 10">Affix top</a-button>
</a-affix>
<br />
<a-affix :offset-bottom="bottom">
<a-button type="primary" @click="bottom += 10">Affix bottom</a-button>
</a-affix>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const top = ref<number>(10)
const bottom = ref<number>(10)
const onChange = (lastAffix: boolean) => {
console.log('onChange', lastAffix)
}
</script>

View File

@ -1,56 +0,0 @@
<template>
<div class="flex flex-wrap gap-2">
<a-button variant="solid" size="lg">Solid Button</a-button>
<a-button variant="solid" size="lg" disabled>Solid Button</a-button>
<a-button variant="solid" size="lg" loading>Solid Button</a-button>
<a-button variant="solid" size="lg" danger>Danger Solid Button</a-button>
<a-button variant="solid" size="lg" danger disabled>Disabled Danger Solid Button</a-button>
<br />
<a-button variant="outlined" size="sm">Outlined Button</a-button>
<a-button variant="outlined" size="md">Outlined Button</a-button>
<a-button variant="outlined" size="lg">Outlined Button</a-button>
<a-button variant="outlined" size="lg" disabled>Disabled Outlined Button</a-button>
<a-button variant="outlined" size="lg" loading>Loading Outlined Button</a-button>
<a-button variant="outlined" size="lg" danger>Danger Outlined Button</a-button>
<a-button variant="outlined" size="lg" danger disabled>
Disabled Danger Outlined Button
</a-button>
<br />
<a-button variant="text" size="sm">Text Button</a-button>
<a-button variant="text" size="md">Text Button</a-button>
<a-button variant="text" size="lg">Text Button</a-button>
<a-button variant="text" size="lg" disabled>Disabled Text Button</a-button>
<a-button variant="text" size="lg" loading>Loading Text Button</a-button>
<a-button variant="text" size="lg" danger>Danger Text Button</a-button>
<a-button variant="text" size="lg" danger disabled>Disabled Danger Text Button</a-button>
<br />
<a-button variant="link" size="sm">Link Button</a-button>
<a-button variant="link" size="md">Link Button</a-button>
<a-button variant="link" size="lg">Link Button</a-button>
<a-button variant="link" size="lg" disabled>Disabled Link Button</a-button>
<a-button variant="link" size="lg" loading>Loading Link Button</a-button>
<a-button variant="link" size="lg" danger>Danger Link Button</a-button>
<a-button variant="link" size="lg" danger disabled>Disabled Danger Link Button</a-button>
<br />
<a-button variant="dashed" size="sm">Dashed Button</a-button>
<a-button variant="dashed" size="md">Dashed Button</a-button>
<a-button variant="dashed" size="lg">Dashed Button</a-button>
<a-button variant="dashed" size="lg" disabled>Disabled Dashed Button</a-button>
<a-button variant="dashed" size="lg" loading>Loading Dashed Button</a-button>
<a-button variant="dashed" size="lg" danger>Danger Dashed Button</a-button>
<a-button variant="dashed" size="lg" danger disabled>Disabled Danger Dashed Button</a-button>
<br />
<a-button variant="filled" size="sm">Filled Button</a-button>
<a-button variant="filled" size="md">Filled Button</a-button>
<a-button variant="filled" size="lg">Filled Button</a-button>
<a-button variant="filled" size="lg" disabled>Disabled Filled Button</a-button>
<a-button variant="filled" size="lg" loading>Loading Filled Button</a-button>
<a-button variant="filled" size="lg" danger>Danger Filled Button</a-button>
<a-button variant="filled" size="lg" danger disabled>Disabled Danger Filled Button</a-button>
<a-button color="purple">Purple Button</a-button>
<a-button color="blue">Blue Button</a-button>
<a-button color="green">Green Button</a-button>
<a-button color="red">Red Button</a-button>
</div>
</template>

View File

@ -1,12 +0,0 @@
<template>
<div class="flex flex-col gap-2">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Default</button>
<button class="btn btn-error">Danger</button>
<button class="btn btn-link">Link</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-link">Link</button>
<button class="btn btn-link">Link</button>
<button class="btn btn-link">Link</button>
</div>
</template>

View File

@ -1,93 +0,0 @@
<template>
<a-flex gap="middle" vertical>
<label>
Select axis:
<select v-model="axis">
<option v-for="item in axisOptions" :key="item">{{ item }}</option>
</select>
</label>
<a-flex :vertical="axis === 'vertical'">
<div
v-for="(item, index) in new Array(4)"
:key="item"
:style="{ ...baseStyle, background: `${index % 2 ? '#1677ff' : '#1677ffbf'}` }"
/>
</a-flex>
<hr/>
<label>
Select justify:
<select v-model="justify">
<option v-for="item in justifyOptions" :key="item">{{ item }}</option>
</select>
</label>
<label>
Select align:
<select v-model="align">
<option v-for="item in alignOptions" :key="item">{{ item }}</option>
</select>
</label>
<a-flex :style="{ ...boxStyle }" :justify="justify" :align="align">
<a-button variant="solid">Primary</a-button>
<a-button variant="solid">Primary</a-button>
<a-button variant="solid">Primary</a-button>
<a-button variant="solid">Primary</a-button>
</a-flex>
<hr/>
<a-flex gap="middle" vertical>
<label>
Select gap size:
<select v-model="gapSize">
<option v-for="item in gapSizeOptions" :key="item">{{ item }}</option>
</select>
</label>
<a-flex :gap="gapSize">
<a-button variant="solid">Primary</a-button>
<a-button>Default</a-button>
<a-button variant="dashed">Dashed</a-button>
<a-button variant="link">Link</a-button>
</a-flex>
</a-flex>
<hr/>
<label>
Auto wrap:
</label>
<a-flex wrap="wrap" gap="small">
<a-button v-for="item in new Array(24)" :key="item" variant="solid">Button</a-button>
</a-flex>
</a-flex>
</template>
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { ref, reactive } from 'vue';
const baseStyle: CSSProperties = {
width: '25%',
height: '54px',
};
const boxStyle: CSSProperties = {
width: '100%',
height: '120px',
borderRadius: '6px',
border: '1px solid #40a9ff',
};
const axisOptions = reactive(['horizontal', 'vertical']);
const axis = ref(axisOptions[0]);
const justifyOptions = reactive([
'flex-start',
'center',
'flex-end',
'space-between',
'space-around',
'space-evenly',
]);
const justify = ref(justifyOptions[0]);
const alignOptions = reactive(['flex-start', 'center', 'flex-end']);
const align = ref(alignOptions[0]);
const gapSizeOptions = reactive(['small', 'middle', 'large']);
const gapSize = ref(gapSizeOptions[0]);
</script>

View File

@ -1,65 +0,0 @@
import { RouteRecordRaw, RouterView } from 'vue-router'
import BasicLayout from './components/BasicLayout.vue'
import { Fragment, h } from 'vue'
// /pages/button/basic.vue
const items = import.meta.glob('./pages/*/*.vue', { import: 'default', eager: true })
const categoryRoutes: Record<string, RouteRecordRaw[]> = {}
Object.keys(items).forEach(path => {
const route = path.replace('./pages/', '').replace('.vue', '')
const [category, demo] = route.split('/')
if (!categoryRoutes[category]) {
categoryRoutes[category] = []
}
categoryRoutes[category].push({
path: demo,
component: items[path],
})
})
const routes: RouteRecordRaw[] = Object.entries(categoryRoutes).map(([category, children]) => {
const renderComponents = () =>
h(
'div',
children.map(child => h(child.component)),
)
renderComponents.displayName = 'renderComponents'
return {
path: `/${category}`,
component: RouterView,
children: [
...children,
{
path: ':demo*',
component: renderComponents,
},
],
}
})
const navs = Object.keys(categoryRoutes).map(category => ({
name: category,
path: `/${category}`,
children: categoryRoutes[category].map(child => ({
name: child.path,
path: `/${category}/${child.path}`,
})),
}))
routes.push({
path: '/:pathMatch(.*)*',
component: h('div', 'demo not found'),
})
export default [
{
path: '/',
component: BasicLayout,
children: routes,
props: {
navs,
},
},
]

View File

@ -1,5 +0,0 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<Record<string, never>, Record<string, never>, any>
export default component
}

View File

@ -1,8 +0,0 @@
/* eslint-disable @typescript-eslint/consistent-type-imports */
declare module 'vue' {
export interface GlobalComponents {
AButton: typeof import('@ant-design-vue/ui').Button
AAffix: typeof import('@ant-design-vue/ui').Affix
}
}
export {}

View File

@ -1,99 +0,0 @@
import { RouteRecordRaw } from 'vue-router'
export function globRoutes(
baseName: string,
globs: Record<string, () => Promise<unknown>>,
): RouteRecordRaw {
const items = Object.entries(globs).map(([path, component]) => {
const match = path.match(/^\.\/pages\/(.+)\/index\.ts$/)
if (!match) {
throw new Error('invalid glob')
}
return {
name: match[1],
component,
}
})
const home: RouteRecordRaw = {
path: '',
components: {
default: () => import('@/components/HomePage.vue'),
breadcrumbs: () => import('@/components/TheBreadcrumbs.vue'),
},
props: {
default: {
items: items.map(item => {
return {
name: item.name,
path: `/${baseName}/${item.name}`,
}
}),
},
breadcrumbs: {
items: [
{
name: 'home',
path: '/',
},
{
name: baseName,
path: `/${baseName}`,
},
],
},
},
meta: {
name: baseName,
title: baseName,
},
}
const pages: RouteRecordRaw[] = items.map(item => {
return {
path: item.name,
components: {
default: item.component,
breadcrumbs: () => import('@/components/TheBreadcrumbs.vue'),
},
props: {
breadcrumbs: {
items: [
{
name: 'home',
path: '/',
},
{
name: baseName,
path: `/${baseName}`,
},
{
name: item.name,
path: `/${baseName}/${item.name}`,
},
],
},
},
meta: {
name: item.name,
title: `${baseName} - ${item.name}`,
},
}
})
return {
path: `/${baseName}`,
component: () => import('@/components/BasicLayout.vue'),
props: {
navs: [
{
name: 'home',
path: '/',
},
],
hideNavbar: true,
hideBreadcrumbs: true,
},
children: [home, ...pages],
} as RouteRecordRaw
}

View File

@ -1,11 +0,0 @@
{
"extends": "@ant-design-vue/typescript-config/tsconfig.vue.json",
"include": ["src/**/*.ts", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }],
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"~/*": ["./assets/*"]
}
}
}

View File

@ -1,4 +0,0 @@
{
"extends": "@ant-design-vue/typescript-config/tsconfig.node.json",
"include": ["vite.config.*"]
}

View File

@ -1,20 +0,0 @@
import tailwindcss from '@tailwindcss/vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'node:path'
import { defineConfig, Plugin } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), tailwindcss()],
server: {
watch: {
ignored: ['!**/node_modules/@ant-design-vue/**'],
},
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'~': resolve(__dirname, './assets'),
},
},
})

19
babel.config.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
env: {
test: {
presets: [['@babel/preset-env']],
plugins: [
['@vue/babel-plugin-jsx', { mergeProps: false, enableObjectSlots: false }],
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-transform-object-assign',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-export-default-from',
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-runtime',
'transform-require-context',
],
},
},
};

5
build.sh Normal file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
rm -rf dist
mkdir dist
./node_modules/.bin/webpack --config webpack.site.config.js
cp dist/index.html index.html

View File

@ -1,14 +1,11 @@
export type KeyType = string | number; export type KeyType = string | number;
type ValueType = [number, any]; // [times, realValue] type ValueType = [number, any]; // [times, realValue]
const SPLIT = '%'; const SPLIT = '%';
class Entity { class Entity {
instanceId: string; instanceId: string;
constructor(instanceId: string) { constructor(instanceId: string) {
this.instanceId = instanceId; this.instanceId = instanceId;
} }
/** @private Internal cache map. Do not access this directly */ /** @private Internal cache map. Do not access this directly */
cache = new Map<string, ValueType>(); cache = new Map<string, ValueType>();

View File

@ -31,6 +31,7 @@ export function createCache() {
Array.from(styles).forEach(style => { Array.from(styles).forEach(style => {
(style as any)[CSS_IN_JS_INSTANCE] = (style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId; (style as any)[CSS_IN_JS_INSTANCE] = (style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId;
// Not force move if no head
// Not force move if no head // Not force move if no head
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) { if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
document.head.insertBefore(style, firstChild); document.head.insertBefore(style, firstChild);

View File

@ -1,82 +0,0 @@
import type Cache from './Cache';
import { extract as tokenExtractStyle, TOKEN_PREFIX } from './hooks/useCacheToken';
import { CSS_VAR_PREFIX, extract as cssVarExtractStyle } from './hooks/useCSSVarRegister';
import { extract as styleExtractStyle, STYLE_PREFIX } from './hooks/useStyleRegister';
import { toStyleStr } from './util';
import { ATTR_CACHE_MAP, serialize as serializeCacheMap } from './util/cacheMapUtil';
const ExtractStyleFns = {
[STYLE_PREFIX]: styleExtractStyle,
[TOKEN_PREFIX]: tokenExtractStyle,
[CSS_VAR_PREFIX]: cssVarExtractStyle,
};
type ExtractStyleType = keyof typeof ExtractStyleFns;
function isNotNull<T>(value: T | null): value is T {
return value !== null;
}
export default function extractStyle(
cache: Cache,
options?:
| boolean
| {
plain?: boolean;
types?: ExtractStyleType | ExtractStyleType[];
},
) {
const { plain = false, types = ['style', 'token', 'cssVar'] } =
typeof options === 'boolean' ? { plain: options } : options || {};
const matchPrefixRegexp = new RegExp(
`^(${(typeof types === 'string' ? [types] : types).join('|')})%`,
);
// prefix with `style` is used for `useStyleRegister` to cache style context
const styleKeys = Array.from(cache.cache.keys()).filter(key => matchPrefixRegexp.test(key));
// Common effect styles like animation
const effectStyles: Record<string, boolean> = {};
// Mapping of cachePath to style hash
const cachePathMap: Record<string, string> = {};
let styleText = '';
styleKeys
.map<[number, string] | null>(key => {
const cachePath = key.replace(matchPrefixRegexp, '').replace(/%/g, '|');
const [prefix] = key.split('%');
const extractFn = ExtractStyleFns[prefix as keyof typeof ExtractStyleFns];
const extractedStyle = extractFn(cache.cache.get(key)![1], effectStyles, {
plain,
});
if (!extractedStyle) {
return null;
}
const [order, styleId, styleStr] = extractedStyle;
if (key.startsWith('style')) {
cachePathMap[cachePath] = styleId;
}
return [order, styleStr];
})
.filter(isNotNull)
.sort(([o1], [o2]) => o1 - o2)
.forEach(([, style]) => {
styleText += style;
});
// ==================== Fill Cache Path ====================
styleText += toStyleStr(
`.${ATTR_CACHE_MAP}{content:"${serializeCacheMap(cachePathMap)}";}`,
undefined,
undefined,
{
[ATTR_CACHE_MAP]: ATTR_CACHE_MAP,
},
plain,
);
return styleText;
}

View File

@ -1,108 +0,0 @@
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
import { ATTR_MARK, ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
import { isClientSide, toStyleStr } from '../util';
import type { TokenWithCSSVar } from '../util/css-variables';
import { transformToken } from '../util/css-variables';
import type { ExtractStyle } from './useGlobalCache';
import useGlobalCache from './useGlobalCache';
import { uniqueHash } from './useStyleRegister';
import type { ComputedRef } from 'vue';
import { computed } from 'vue';
export const CSS_VAR_PREFIX = 'cssVar';
type CSSVarCacheValue<V, T extends Record<string, V> = Record<string, V>> = [
cssVarToken: TokenWithCSSVar<V, T>,
cssVarStr: string,
styleId: string,
cssVarKey: string,
];
const useCSSVarRegister = <V, T extends Record<string, V>>(
config: ComputedRef<{
path: string[];
key: string;
prefix?: string;
unitless?: Record<string, boolean>;
ignore?: Record<string, boolean>;
scope?: string;
token: any;
}>,
fn: () => T,
) => {
const styleContext = useStyleInject();
const stylePath = computed(() => {
return [
...config.value.path,
config.value.key,
config.value.scope || '',
config.value.token?._tokenKey,
];
});
const cache = useGlobalCache<CSSVarCacheValue<V, T>>(
CSS_VAR_PREFIX,
stylePath,
() => {
const originToken = fn();
const [mergedToken, cssVarsStr] = transformToken<V, T>(originToken, config.value.key, {
prefix: config.value.prefix,
unitless: config.value.unitless,
ignore: config.value.ignore,
scope: config.value.scope || '',
});
const styleId = uniqueHash(stylePath.value, cssVarsStr);
return [mergedToken, cssVarsStr, styleId, config.value.key];
},
([, , styleId]) => {
if (isClientSide) {
removeCSS(styleId, { mark: ATTR_MARK });
}
},
([, cssVarsStr, styleId]) => {
if (!cssVarsStr) {
return;
}
const style = updateCSS(cssVarsStr, styleId, {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: styleContext.value.container,
priority: -999,
});
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value.cache?.instanceId;
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, config.value.key);
},
);
return cache;
};
export const extract: ExtractStyle<CSSVarCacheValue<any>> = (cache, _effectStyles, options) => {
const [, styleStr, styleId, cssVarKey] = cache;
const { plain } = options || {};
if (!styleStr) {
return null;
}
const order = -999;
// ====================== Style ======================
// Used for rc-util
const sharedAttrs = {
'data-vc-order': 'prependQueue',
'data-vc-priority': `${order}`,
};
const styleText = toStyleStr(styleStr, cssVarKey, styleId, sharedAttrs, plain);
return [order, styleId, styleText];
};
export default useCSSVarRegister;

View File

@ -1,19 +1,20 @@
import hash from '@emotion/hash'; import hash from '@emotion/hash';
import { updateCSS } from '../../../vc-util/Dom/dynamicCSS'; import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
import { ATTR_MARK, ATTR_TOKEN, CSS_IN_JS_INSTANCE, useStyleInject } from '../StyleContext';
import type Theme from '../theme/Theme'; import type Theme from '../theme/Theme';
import { flattenToken, memoResult, token2key, toStyleStr } from '../util';
import { transformToken } from '../util/css-variables';
import type { ExtractStyle } from './useGlobalCache';
import useGlobalCache from './useGlobalCache'; import useGlobalCache from './useGlobalCache';
import { flattenToken, token2key } from '../util';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
const EMPTY_OVERRIDE = {}; 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. // Generate different prefix to make user selector break in production env.
// This helps developer not to do style override directly on the hash id. // This helps developer not to do style override directly on the hash id.
const hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css'; const hashPrefix = !isProduction && !isPrerender ? 'css-dev-only-do-not-override' : 'css';
export interface Option<DerivativeToken, DesignToken> { export interface Option<DerivativeToken, DesignToken> {
/** /**
@ -45,22 +46,6 @@ export interface Option<DerivativeToken, DesignToken> {
override: object, override: object,
theme: Theme<any, any>, theme: Theme<any, any>,
) => DerivativeToken; ) => DerivativeToken;
/**
* Transform token to css variables.
*/
cssVar?: {
/** Prefix for css variables */
prefix?: string;
/** Tokens that should not be appended with unit */
unitless?: Record<string, boolean>;
/** Tokens that should not be transformed to css variables */
ignore?: Record<string, boolean>;
/** Tokens that preserves origin value */
preserve?: Record<string, boolean>;
/** Key for current theme. Useful for customizing and should be unique */
key?: string;
};
} }
const tokenKeys = new Map<string, number>(); const tokenKeys = new Map<string, number>();
@ -109,7 +94,6 @@ export const getComputedToken = <DerivativeToken = object, DesignToken = Derivat
format?: (token: DesignToken) => DerivativeToken, format?: (token: DesignToken) => DerivativeToken,
) => { ) => {
const derivativeToken = theme.getDerivativeToken(originToken); const derivativeToken = theme.getDerivativeToken(originToken);
// Merge with override // Merge with override
let mergedDerivativeToken = { let mergedDerivativeToken = {
...derivativeToken, ...derivativeToken,
@ -124,16 +108,6 @@ export const getComputedToken = <DerivativeToken = object, DesignToken = Derivat
return mergedDerivativeToken; return mergedDerivativeToken;
}; };
export const TOKEN_PREFIX = 'token';
type TokenCacheValue<DerivativeToken> = [
token: DerivativeToken & { _tokenKey: string; _themeKey: string },
hashId: string,
realToken: DerivativeToken & { _tokenKey: string },
cssVarStr: string,
cssVarKey: string,
];
/** /**
* Cache theme derivative token as global shared one * Cache theme derivative token as global shared one
* @param theme Theme entity * @param theme Theme entity
@ -145,27 +119,21 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
theme: Ref<Theme<any, any>>, theme: Ref<Theme<any, any>>,
tokens: Ref<Partial<DesignToken>[]>, tokens: Ref<Partial<DesignToken>[]>,
option: Ref<Option<DerivativeToken, DesignToken>> = ref({}), option: Ref<Option<DerivativeToken, DesignToken>> = ref({}),
): Ref<TokenCacheValue<DerivativeToken>> { ) {
const styleContext = useStyleInject(); const style = useStyleInject();
// Basic - We do basic cache here // Basic - We do basic cache here
const mergedToken = computed(() => const mergedToken = computed(() => Object.assign({}, ...tokens.value));
memoResult(() => Object.assign({}, ...tokens.value), tokens.value),
);
const tokenStr = computed(() => flattenToken(mergedToken.value)); const tokenStr = computed(() => flattenToken(mergedToken.value));
const overrideTokenStr = computed(() => flattenToken(option.value.override ?? EMPTY_OVERRIDE)); const overrideTokenStr = computed(() => flattenToken(option.value.override || EMPTY_OVERRIDE));
const cssVarStr = computed(() => (option.value.cssVar ? flattenToken(option.value.cssVar) : '')); const cachedToken = useGlobalCache<[DerivativeToken & { _tokenKey: string }, string]>(
'token',
const cachedToken = useGlobalCache<TokenCacheValue<DerivativeToken>>(
TOKEN_PREFIX,
computed(() => [ computed(() => [
option.value.salt ?? '', option.value.salt || '',
theme.value?.id, theme.value.id,
tokenStr.value, tokenStr.value,
overrideTokenStr.value, overrideTokenStr.value,
cssVarStr.value,
]), ]),
() => { () => {
const { const {
@ -173,82 +141,25 @@ export default function useCacheToken<DerivativeToken = object, DesignToken = De
override = EMPTY_OVERRIDE, override = EMPTY_OVERRIDE,
formatToken, formatToken,
getComputedToken: compute, getComputedToken: compute,
cssVar,
} = option.value; } = option.value;
let mergedDerivativeToken = compute const mergedDerivativeToken = compute
? compute(mergedToken.value, override, theme.value) ? compute(mergedToken.value, override, theme.value)
: getComputedToken(mergedToken.value, override, theme.value, formatToken); : getComputedToken(mergedToken.value, override, theme.value, formatToken);
// Replace token value with css variables
const actualToken = { ...mergedDerivativeToken };
let cssVarsStr = '';
if (!!cssVar) {
[mergedDerivativeToken, cssVarsStr] = transformToken(mergedDerivativeToken, cssVar.key!, {
prefix: cssVar.prefix,
ignore: cssVar.ignore,
unitless: cssVar.unitless,
preserve: cssVar.preserve,
});
}
// Optimize for `useStyleRegister` performance // Optimize for `useStyleRegister` performance
const tokenKey = token2key(mergedDerivativeToken, salt); const tokenKey = token2key(mergedDerivativeToken, salt);
mergedDerivativeToken._tokenKey = tokenKey; mergedDerivativeToken._tokenKey = tokenKey;
actualToken._tokenKey = token2key(actualToken, salt); recordCleanToken(tokenKey);
const themeKey = cssVar?.key ?? tokenKey;
mergedDerivativeToken._themeKey = themeKey;
recordCleanToken(themeKey);
const hashId = `${hashPrefix}-${hash(tokenKey)}`; const hashId = `${hashPrefix}-${hash(tokenKey)}`;
mergedDerivativeToken._hashId = hashId; // Not used mergedDerivativeToken._hashId = hashId; // Not used
return [mergedDerivativeToken, hashId];
return [mergedDerivativeToken, hashId, actualToken, cssVarsStr, cssVar?.key || ''];
}, },
cache => { cache => {
// Remove token will remove all related style // Remove token will remove all related style
cleanTokenStyle(cache[0]._themeKey, styleContext.value?.cache?.instanceId); cleanTokenStyle(cache[0]._tokenKey, style.value?.cache.instanceId);
},
([token, , , cssVarsStr]) => {
const { cssVar } = option.value;
if (cssVar && cssVarsStr) {
const style = updateCSS(cssVarsStr, hash(`css-variables-${token._themeKey}`), {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: styleContext.value?.container,
priority: -999,
});
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value?.cache?.instanceId;
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, token._themeKey);
}
}, },
); );
return cachedToken; return cachedToken;
} }
export const extract: ExtractStyle<TokenCacheValue<any>> = (cache, _effectStyles, options) => {
const [, , realToken, styleStr, cssVarKey] = cache;
const { plain } = options || {};
if (!styleStr) {
return null;
}
const styleId = realToken._tokenKey;
const order = -999;
// ====================== Style ======================
// Used for rc-util
const sharedAttrs = {
'data-vc-order': 'prependQueue',
'data-vc-priority': `${order}`,
};
const styleText = toStyleStr(styleStr, cssVarKey, styleId, sharedAttrs, plain);
return [order, styleId, styleText];
};

View File

@ -1,30 +0,0 @@
// import canUseDom from 'rc-util/lib/Dom/canUseDom';
import useLayoutEffect from '../../../_util/hooks/useLayoutEffect';
import type { ShallowRef, WatchCallback } from 'vue';
import { watch } from 'vue';
type UseCompatibleInsertionEffect = (
renderEffect: WatchCallback,
effect: (polyfill?: boolean) => ReturnType<WatchCallback>,
deps: ShallowRef,
) => void;
/**
* Polyfill `useInsertionEffect` for React < 18
* @param renderEffect will be executed in `useMemo`, and do not have callback
* @param effect will be executed in `useLayoutEffect`
* @param deps
*/
const useInsertionEffectPolyfill: UseCompatibleInsertionEffect = (renderEffect, effect, deps) => {
watch(deps, renderEffect, { immediate: true });
useLayoutEffect(() => effect(true), deps);
};
/**
* Compatible `useInsertionEffect`
* will use `useInsertionEffect` if React version >= 18,
* otherwise use `useInsertionEffectPolyfill`.
*/
const useCompatibleInsertionEffect: UseCompatibleInsertionEffect = useInsertionEffectPolyfill;
export default useCompatibleInsertionEffect;

View File

@ -1,8 +0,0 @@
const useRun = () => {
return function (fn: () => void) {
fn();
};
};
const useEffectCleanupRegister = useRun;
export default useEffectCleanupRegister;

View File

@ -1,115 +1,58 @@
import { useStyleInject } from '../StyleContext'; import { useStyleInject } from '../StyleContext';
import type { KeyType } from '../Cache'; import type { KeyType } from '../Cache';
import useCompatibleInsertionEffect from './useCompatibleInsertionEffect';
import useHMR from './useHMR'; import useHMR from './useHMR';
import type { ShallowRef, Ref } from 'vue'; import type { ShallowRef, Ref } from 'vue';
import { onBeforeUnmount, watch, computed } from 'vue'; import { onBeforeUnmount, watch, watchEffect, shallowRef } from 'vue';
export default function useClientCache<CacheType>(
export type ExtractStyle<CacheValue> = (
cache: CacheValue,
effectStyles: Record<string, boolean>,
options?: {
plain?: boolean;
},
) => [order: number, styleId: string, style: string] | null;
export default function useGlobalCache<CacheType>(
prefix: string, prefix: string,
keyPath: Ref<KeyType[]>, keyPath: Ref<KeyType[]>,
cacheFn: () => CacheType, cacheFn: () => CacheType,
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void, onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
// Add additional effect trigger by `useInsertionEffect`
onCacheEffect?: (cachedValue: CacheType) => void,
): ShallowRef<CacheType> { ): ShallowRef<CacheType> {
const styleContext = useStyleInject(); const styleContext = useStyleInject();
const globalCache = computed(() => styleContext.value?.cache); const fullPathStr = shallowRef('');
const deps = computed(() => [prefix, ...keyPath.value].join('%')); const res = shallowRef<CacheType>();
watchEffect(() => {
fullPathStr.value = [prefix, ...keyPath.value].join('%');
});
const HMRUpdate = useHMR(); const HMRUpdate = useHMR();
const clearCache = (pathStr: string) => {
type UpdaterArgs = [times: number, cache: CacheType]; styleContext.value.cache.update(pathStr, prevCache => {
const [times = 0, cache] = prevCache || [];
const buildCache = (updater?: (data: UpdaterArgs) => UpdaterArgs) => { const nextCount = times - 1;
globalCache.value.update(deps.value, prevCache => { if (nextCount === 0) {
const [times = 0, cache] = prevCache || [undefined, undefined]; onCacheRemove?.(cache, false);
return null;
// 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, cache];
const data: UpdaterArgs = [times, mergedCache];
// Call updater if need additional logic
return updater ? updater(data) : data;
}); });
}; };
watch( watch(
deps, fullPathStr,
() => { (newStr, oldStr) => {
buildCache(); 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 }, { immediate: true },
); );
let cacheEntity = globalCache.value.get(deps.value);
// HMR clean the cache but not trigger `useMemo` again
// Let's fallback of this
// ref https://github.com/ant-design/cssinjs/issues/127
if (process.env.NODE_ENV !== 'production' && !cacheEntity) {
buildCache();
cacheEntity = globalCache.value.get(deps.value);
}
const cacheContent = computed(
() =>
(globalCache.value.get(deps.value) && globalCache.value.get(deps.value)![1]) ||
cacheEntity![1],
);
// Remove if no need anymore
useCompatibleInsertionEffect(
() => {
onCacheEffect?.(cacheContent.value);
},
polyfill => {
// It's bad to call build again in effect.
// But we have to do this since StrictMode will call effect twice
// which will clear cache on the first time.
buildCache(([times, cache]) => {
if (polyfill && times === 0) {
onCacheEffect?.(cacheContent.value);
}
return [times + 1, cache];
});
return () => {
globalCache.value.update(deps.value, prevCache => {
const [times = 0, cache] = prevCache || [];
const nextCount = times - 1;
if (nextCount <= 0) {
if (polyfill || !globalCache.value.get(deps.value)) {
onCacheRemove?.(cache, false);
}
return null;
}
return [times - 1, cache];
});
};
},
deps,
);
onBeforeUnmount(() => { onBeforeUnmount(() => {
buildCache(); clearCache(fullPathStr.value);
}); });
return res;
return cacheContent;
} }

View File

@ -1,5 +1,5 @@
import canUseDom from '../../canUseDom'; import canUseDom from '../../../../_util/canUseDom';
import { ATTR_MARK } from '../StyleContext'; import { ATTR_MARK } from '../../StyleContext';
export const ATTR_CACHE_MAP = 'data-ant-cssinjs-cache-path'; export const ATTR_CACHE_MAP = 'data-ant-cssinjs-cache-path';

View File

@ -3,30 +3,38 @@ import type * as CSS from 'csstype';
// @ts-ignore // @ts-ignore
import unitless from '@emotion/unitless'; import unitless from '@emotion/unitless';
import { compile, serialize, stringify } from 'stylis'; import { compile, serialize, stringify } from 'stylis';
import type { Theme, Transformer } from '..'; import type { Theme, Transformer } from '../..';
import type Keyframes from '../Keyframes'; import type Cache from '../../Cache';
import type { Linter } from '../linters'; import type Keyframes from '../../Keyframes';
import { contentQuotesLinter, hashedAnimationLinter } from '../linters'; import type { Linter } from '../../linters';
import type { HashPriority } from '../StyleContext'; import { contentQuotesLinter, hashedAnimationLinter } from '../../linters';
import type { HashPriority } from '../../StyleContext';
import { import {
useStyleInject, useStyleInject,
ATTR_CACHE_PATH, ATTR_CACHE_PATH,
ATTR_MARK, ATTR_MARK,
ATTR_TOKEN, ATTR_TOKEN,
CSS_IN_JS_INSTANCE, CSS_IN_JS_INSTANCE,
} from '../StyleContext'; } from '../../StyleContext';
import { isClientSide, supportLayer, toStyleStr } from '../util'; import { supportLayer } from '../../util';
import { CSS_FILE_STYLE, existPath, getStyleAndHash } from '../util/cacheMapUtil'; import useGlobalCache from '../useGlobalCache';
import type { ExtractStyle } from './useGlobalCache'; import { removeCSS, updateCSS } from '../../../../vc-util/Dom/dynamicCSS';
import useGlobalCache from './useGlobalCache';
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { computed } from 'vue'; import { computed } from 'vue';
import type { VueNode } from '../../type'; 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 SKIP_CHECK = '_skip_check_';
const MULTI_VALUE = '_multi_value_'; const MULTI_VALUE = '_multi_value_';
export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & { export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & {
animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes; animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes;
}; };
@ -52,7 +60,6 @@ export type CSSInterpolation = InterpolationPrimitive | ArrayCSSInterpolation |
export type CSSOthersObject = Record<string, CSSInterpolation>; export type CSSOthersObject = Record<string, CSSInterpolation>;
// @ts-ignore
export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSSOthersObject {} export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSSOthersObject {}
// ============================================================================ // ============================================================================
@ -107,6 +114,16 @@ export interface ParseInfo {
parentSelectors: string[]; 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 // Parse CSSObject to style content
export const parseStyle = ( export const parseStyle = (
interpolation: CSSInterpolation, interpolation: CSSInterpolation,
@ -241,7 +258,6 @@ export const parseStyle = (
styleStr += `${styleName}:${formatValue};`; styleStr += `${styleName}:${formatValue};`;
} }
const actualValue = (value as any)?.value ?? value; const actualValue = (value as any)?.value ?? value;
if ( if (
typeof value === 'object' && typeof value === 'object' &&
@ -279,7 +295,7 @@ export const parseStyle = (
// ============================================================================ // ============================================================================
// == Register == // == Register ==
// ============================================================================ // ============================================================================
export function uniqueHash(path: (string | number)[], styleStr: string) { function uniqueHash(path: (string | number)[], styleStr: string) {
return hash(`${path.join('%')}${styleStr}`); return hash(`${path.join('%')}${styleStr}`);
} }
@ -287,17 +303,6 @@ export function uniqueHash(path: (string | number)[], styleStr: string) {
// return null; // return null;
// } // }
export const STYLE_PREFIX = 'style';
type StyleCacheValue = [
styleStr: string,
tokenKey: string,
styleId: string,
effectStyle: Record<string, string>,
clientOnly: boolean | undefined,
order: number,
];
/** /**
* Register a style to the global style sheet. * Register a style to the global style sheet.
*/ */
@ -332,14 +337,22 @@ export default function useStyleRegister(
} }
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]] // const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
useGlobalCache<StyleCacheValue>( useGlobalCache<
STYLE_PREFIX, [
styleStr: string,
tokenKey: string,
styleId: string,
effectStyle: Record<string, string>,
clientOnly: boolean | undefined,
order: number,
]
>(
'style',
fullPath, fullPath,
// Create cache if needed // Create cache if needed
() => { () => {
const { path, hashId, layer, clientOnly, order = 0 } = info.value; const { path, hashId, layer, nonce, clientOnly, order = 0 } = info.value;
const cachePath = fullPath.value.join('|'); const cachePath = fullPath.value.join('|');
// Get style from SSR inline style directly // Get style from SSR inline style directly
if (existPath(cachePath)) { if (existPath(cachePath)) {
const [inlineCacheStyleStr, styleHash] = getStyleAndHash(cachePath); const [inlineCacheStyleStr, styleHash] = getStyleAndHash(cachePath);
@ -347,10 +360,8 @@ export default function useStyleRegister(
return [inlineCacheStyleStr, tokenKey.value, styleHash, {}, clientOnly, order]; return [inlineCacheStyleStr, tokenKey.value, styleHash, {}, clientOnly, order];
} }
} }
// Generate style
const styleObj = styleFn(); const styleObj = styleFn();
const { hashPriority, transformers, linters } = styleContext.value; const { hashPriority, container, transformers, linters, cache } = styleContext.value;
const [parsedStyle, effectStyle] = parseStyle(styleObj, { const [parsedStyle, effectStyle] = parseStyle(styleObj, {
hashId, hashId,
@ -360,32 +371,18 @@ export default function useStyleRegister(
transformers, transformers,
linters, linters,
}); });
const styleStr = normalizeStyle(parsedStyle); const styleStr = normalizeStyle(parsedStyle);
const styleId = uniqueHash(fullPath.value, styleStr); const styleId = uniqueHash(fullPath.value, styleStr);
return [styleStr, tokenKey.value, styleId, effectStyle, clientOnly, order]; if (isMergedClientSide) {
},
// Remove cache if no need
([, , styleId], fromHMR) => {
if ((fromHMR || styleContext.value.autoClear) && isClientSide) {
removeCSS(styleId, { mark: ATTR_MARK });
}
},
// Effect: Inject style here
([styleStr, , styleId, effectStyle]) => {
if (isMergedClientSide && styleStr !== CSS_FILE_STYLE) {
const mergedCSSConfig: Parameters<typeof updateCSS>[2] = { const mergedCSSConfig: Parameters<typeof updateCSS>[2] = {
mark: ATTR_MARK, mark: ATTR_MARK,
prepend: 'queue', prepend: 'queue',
attachTo: styleContext.value.container, attachTo: container,
priority: info.value.order, priority: order,
}; };
const nonceStr = const nonceStr = typeof nonce === 'function' ? nonce() : nonce;
typeof info.value.nonce === 'function' ? info.value.nonce() : info.value.nonce;
if (nonceStr) { if (nonceStr) {
mergedCSSConfig.csp = { nonce: nonceStr }; mergedCSSConfig.csp = { nonce: nonceStr };
@ -393,33 +390,45 @@ export default function useStyleRegister(
const style = updateCSS(styleStr, styleId, mergedCSSConfig); const style = updateCSS(styleStr, styleId, mergedCSSConfig);
(style as any)[CSS_IN_JS_INSTANCE] = styleContext.value.cache.instanceId; (style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId;
// Used for `useCacheToken` to remove on batch when token removed // Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, tokenKey.value); style.setAttribute(ATTR_TOKEN, tokenKey.value);
// Debug usage. Dev only // Dev usage to find which cache path made this easily
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|')); style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|'));
} }
// Inject client side effect style // Inject client side effect style
Object.keys(effectStyle).forEach(effectKey => { Object.keys(effectStyle).forEach(effectKey => {
updateCSS( if (!globalEffectStyleKeys.has(effectKey)) {
normalizeStyle(effectStyle[effectKey]), globalEffectStyleKeys.add(effectKey);
`_effect-${effectKey}`,
mergedCSSConfig, // 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: VueNode) => {
return node; return node;
// let styleNode: VueNode; // let styleNode: VueNode;
// if (!styleContext.ssrInline || isMergedClientSide || !styleContext.defaultCache) {
// if (!styleContext.value.ssrInline || isMergedClientSide || !styleContext.value.defaultCache) {
// styleNode = <Empty />; // styleNode = <Empty />;
// } else { // } else {
// styleNode = ( // styleNode = (
@ -442,43 +451,116 @@ export default function useStyleRegister(
}; };
} }
export const extract: ExtractStyle<StyleCacheValue> = (cache, effectStyles, options) => { // ============================================================================
const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order]: StyleCacheValue = cache; // == SSR ==
const { plain } = options || {}; // ============================================================================
export function extractStyle(cache: Cache, plain = false) {
const matchPrefix = `style%`;
// Skip client only style // prefix with `style` is used for `useStyleRegister` to cache style context
if (clientOnly) { const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith(matchPrefix));
return null;
// 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>`;
} }
let keyStyleText = styleStr; // ====================== Fill Style ======================
type OrderStyle = [order: number, style: string];
// ====================== Style ====================== const orderStyles: OrderStyle[] = styleKeys
// Used for rc-util .map(key => {
const sharedAttrs = { const cachePath = key.slice(matchPrefix.length).replace(/%/g, '|');
'data-vc-order': 'prependQueue',
'data-vc-priority': `${order}`,
};
keyStyleText = toStyleStr(styleStr, tokenKey, styleId, sharedAttrs, plain); const [styleStr, tokenKey, styleId, effectStyle, clientOnly, order]: [
string,
string,
string,
Record<string, string>,
boolean,
number,
] = cache.cache.get(key)![1];
// =============== Create effect style =============== // Skip client only style
if (effectStyle) { if (clientOnly) {
Object.keys(effectStyle).forEach(effectKey => { return null! as OrderStyle;
// Effect style can be reused
if (!effectStyles[effectKey]) {
effectStyles[effectKey] = true;
const effectStyleStr = normalizeStyle(effectStyle[effectKey]);
keyStyleText += toStyleStr(
effectStyleStr,
tokenKey,
`_effect-${effectKey}`,
sharedAttrs,
plain,
);
} }
});
}
return [order, styleId, keyStyleText]; // ====================== 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,37 +1,28 @@
import extractStyle from './extractStyle'; import useCacheToken from './hooks/useCacheToken';
import useCacheToken, { getComputedToken } from './hooks/useCacheToken';
import useCSSVarRegister from './hooks/useCSSVarRegister';
import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister'; import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister';
import useStyleRegister from './hooks/useStyleRegister'; import useStyleRegister, { extractStyle } from './hooks/useStyleRegister';
import Keyframes from './Keyframes'; import Keyframes from './Keyframes';
import type { Linter } from './linters'; import type { Linter } from './linters';
import { import { legacyNotSelectorLinter, logicalPropertiesLinter, parentSelectorLinter } from './linters';
legacyNotSelectorLinter, import type { StyleContextProps, StyleProviderProps } from './StyleContext';
logicalPropertiesLinter, import { createCache, useStyleInject, useStyleProvider, StyleProvider } from './StyleContext';
NaNLinter,
parentSelectorLinter,
} from './linters';
import type { StyleProviderProps } from './StyleContext';
import { createCache, StyleProvider } from './StyleContext';
import type { DerivativeFunc, TokenType } from './theme'; import type { DerivativeFunc, TokenType } from './theme';
import { createTheme, Theme } from './theme'; import { createTheme, Theme } from './theme';
import type { Transformer } from './transformers/interface'; import type { Transformer } from './transformers/interface';
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties'; import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
import px2remTransformer from './transformers/px2rem'; import px2remTransformer from './transformers/px2rem';
import { supportLogicProps, supportWhere, unit } from './util'; import { supportLogicProps, supportWhere } from './util';
import { token2CSSVar } from './util/css-variables';
export { const cssinjs = {
Theme, Theme,
createTheme, createTheme,
useStyleRegister, useStyleRegister,
useCSSVarRegister,
useCacheToken, useCacheToken,
createCache, createCache,
StyleProvider, useStyleInject,
useStyleProvider,
Keyframes, Keyframes,
extractStyle, extractStyle,
getComputedToken,
// Transformer // Transformer
legacyLogicalPropertiesTransformer, legacyLogicalPropertiesTransformer,
@ -41,11 +32,32 @@ export {
logicalPropertiesLinter, logicalPropertiesLinter,
legacyNotSelectorLinter, legacyNotSelectorLinter,
parentSelectorLinter, parentSelectorLinter,
NaNLinter,
// util // cssinjs
token2CSSVar, StyleProvider,
unit, };
export {
Theme,
createTheme,
useStyleRegister,
useCacheToken,
createCache,
useStyleInject,
useStyleProvider,
Keyframes,
extractStyle,
// Transformer
legacyLogicalPropertiesTransformer,
px2remTransformer,
// Linters
logicalPropertiesLinter,
legacyNotSelectorLinter,
parentSelectorLinter,
// cssinjs
StyleProvider,
}; };
export type { export type {
TokenType, TokenType,
@ -54,9 +66,12 @@ export type {
DerivativeFunc, DerivativeFunc,
Transformer, Transformer,
Linter, Linter,
StyleContextProps,
StyleProviderProps, StyleProviderProps,
}; };
export const _experimental = { export const _experimental = {
supportModernCSS: () => supportWhere() && supportLogicProps(), supportModernCSS: () => supportWhere() && supportLogicProps(),
}; };
export default cssinjs;

View File

@ -1,10 +0,0 @@
import type { Linter } from './interface';
import { lintWarning } from './utils';
const linter: Linter = (key, value, info) => {
if ((typeof value === 'string' && /NaN/g.test(value)) || Number.isNaN(value)) {
lintWarning(`Unexpected 'NaN' in property '${key}: ${value}'.`, info);
}
};
export default linter;

View File

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

View File

@ -6,8 +6,8 @@ export function lintWarning(message: string, info: LinterInfo) {
devWarning( devWarning(
false, false,
`[Ant Design Vue CSS-in-JS] ${path ? `Error in ${path}: ` : ''}${message}${ `[Ant Design Vue CSS-in-JS] ${path ? `Error in '${path}': ` : ''}${message}${
parentSelectors.length ? ` Selector: ${parentSelectors.join(' | ')}` : '' parentSelectors.length ? ` Selector info: ${parentSelectors.join(' -> ')}` : ''
}`, }`,
); );
} }

View File

@ -1,36 +1,34 @@
import type { CSSObject } from '..'; import type { CSSObject } from '..';
import type { Transformer } from './interface'; import type { Transformer } from './interface';
function splitValues(value: string | number): [values: (string | number)[], important: boolean] { function splitValues(value: string | number) {
if (typeof value === 'number') { if (typeof value === 'number') {
return [[value], false]; return [value];
} }
const rawStyle = String(value).trim(); const splitStyle = String(value).split(/\s+/);
const importantCells = rawStyle.match(/(.*)(!important)/);
const splitStyle = (importantCells ? importantCells[1] : rawStyle).trim().split(/\s+/);
// Combine styles split in brackets, like `calc(1px + 2px)` // Combine styles split in brackets, like `calc(1px + 2px)`
let temp = ''; let temp = '';
let brackets = 0; let brackets = 0;
return [ return splitStyle.reduce<string[]>((list, item) => {
splitStyle.reduce<string[]>((list, item) => { if (item.includes('(')) {
if (item.includes('(') || item.includes(')')) { temp += item;
const left = item.split('(').length - 1; brackets += item.split('(').length - 1;
const right = item.split(')').length - 1; } else if (item.includes(')')) {
brackets += left - right; temp += ` ${item}`;
} brackets -= item.split(')').length - 1;
if (brackets === 0) { if (brackets === 0) {
list.push(temp + item); list.push(temp);
temp = ''; temp = '';
} else if (brackets > 0) {
temp += item;
} }
return list; } else if (brackets > 0) {
}, []), temp += ` ${item}`;
!!importantCells, } else {
]; list.push(item);
}
return list;
}, []);
} }
type MatchValue = string[] & { type MatchValue = string[] & {
@ -107,14 +105,8 @@ const keyMap: Record<string, MatchValue> = {
borderEndEndRadius: ['borderBottomRightRadius'], borderEndEndRadius: ['borderBottomRightRadius'],
}; };
function wrapImportantAndSkipCheck(value: string | number, important: boolean) { function skipCheck(value: string | number) {
let parsedValue = value; return { _skip_check_: true, value };
if (important) {
parsedValue = `${parsedValue} !important`;
}
return { _skip_check_: true, value: parsedValue };
} }
/** /**
@ -135,28 +127,25 @@ const transform: Transformer = {
const matchValue = keyMap[key]; const matchValue = keyMap[key];
if (matchValue && (typeof value === 'number' || typeof value === 'string')) { if (matchValue && (typeof value === 'number' || typeof value === 'string')) {
const [values, important] = splitValues(value); const values = splitValues(value);
if (matchValue.length && matchValue.notSplit) { if (matchValue.length && matchValue.notSplit) {
// not split means always give same value like border // not split means always give same value like border
matchValue.forEach(matchKey => { matchValue.forEach(matchKey => {
clone[matchKey] = wrapImportantAndSkipCheck(value, important); clone[matchKey] = skipCheck(value);
}); });
} else if (matchValue.length === 1) { } else if (matchValue.length === 1) {
// Handle like `marginBlockStart` => `marginTop` // Handle like `marginBlockStart` => `marginTop`
clone[matchValue[0]] = wrapImportantAndSkipCheck(value, important); clone[matchValue[0]] = skipCheck(value);
} else if (matchValue.length === 2) { } else if (matchValue.length === 2) {
// Handle like `marginBlock` => `marginTop` & `marginBottom` // Handle like `marginBlock` => `marginTop` & `marginBottom`
matchValue.forEach((matchKey, index) => { matchValue.forEach((matchKey, index) => {
clone[matchKey] = wrapImportantAndSkipCheck(values[index] ?? values[0], important); clone[matchKey] = skipCheck(values[index] ?? values[0]);
}); });
} else if (matchValue.length === 4) { } else if (matchValue.length === 4) {
// Handle like `inset` => `top` & `right` & `bottom` & `left` // Handle like `inset` => `top` & `right` & `bottom` & `left`
matchValue.forEach((matchKey, index) => { matchValue.forEach((matchKey, index) => {
clone[matchKey] = wrapImportantAndSkipCheck( clone[matchKey] = skipCheck(values[index] ?? values[index - 2] ?? values[0]);
values[index] ?? values[index - 2] ?? values[0],
important,
);
}); });
} else { } else {
clone[key] = value; clone[key] = value;

View File

@ -1,7 +1,6 @@
/** /**
* respect https://github.com/cuth/postcss-pxtorem * respect https://github.com/cuth/postcss-pxtorem
*/ */
// @ts-ignore
import unitless from '@emotion/unitless'; import unitless from '@emotion/unitless';
import type { CSSObject } from '..'; import type { CSSObject } from '..';
import type { Transformer } from './interface'; import type { Transformer } from './interface';

View File

@ -1,37 +1,12 @@
import hash from '@emotion/hash'; import hash from '@emotion/hash';
import canUseDom from '../../canUseDom'; import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS'; import canUseDom from '../canUseDom';
import { ATTR_MARK, ATTR_TOKEN } from '../StyleContext';
import { Theme } from '../theme';
// Create a cache for memo concat import { Theme } from './theme';
type NestWeakMap<T> = WeakMap<object, NestWeakMap<T> | T>;
const resultCache: NestWeakMap<object> = new WeakMap();
const RESULT_VALUE = {};
export function memoResult<T extends object, R>(callback: () => R, deps: T[]): R {
let current: WeakMap<any, any> = resultCache;
for (let i = 0; i < deps.length; i += 1) {
const dep = deps[i];
if (!current.has(dep)) {
current.set(dep, new WeakMap());
}
current = current.get(dep)!;
}
if (!current.has(RESULT_VALUE)) {
current.set(RESULT_VALUE, callback());
}
return current.get(RESULT_VALUE);
}
// Create a cache here to avoid always loop generate // Create a cache here to avoid always loop generate
const flattenTokenCache = new WeakMap<any, string>(); const flattenTokenCache = new WeakMap<any, string>();
/**
* Flatten token to string, this will auto cache the result when token not change
*/
export function flattenToken(token: any) { export function flattenToken(token: any) {
let str = flattenTokenCache.get(token) || ''; let str = flattenTokenCache.get(token) || '';
@ -141,39 +116,3 @@ export function supportLogicProps(): boolean {
return canLogic!; return canLogic!;
} }
export const isClientSide = canUseDom();
export function unit(num: string | number) {
if (typeof num === 'number') {
return `${num}px`;
}
return num;
}
export function toStyleStr(
style: string,
tokenKey?: string,
styleId?: string,
customizeAttrs: Record<string, string> = {},
plain = false,
) {
if (plain) {
return style;
}
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 `<style ${attrStr}>${style}</style>`;
}

View File

@ -1,58 +0,0 @@
export const token2CSSVar = (token: string, prefix = '') => {
return `--${prefix ? `${prefix}-` : ''}${token}`
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, '$1-$2')
.replace(/([a-z])([A-Z0-9])/g, '$1-$2')
.toLowerCase();
};
export const serializeCSSVar = <T extends Record<string, any>>(
cssVars: T,
hashId: string,
options?: {
scope?: string;
},
) => {
if (!Object.keys(cssVars).length) {
return '';
}
return `.${hashId}${options?.scope ? `.${options.scope}` : ''}{${Object.entries(cssVars)
.map(([key, value]) => `${key}:${value};`)
.join('')}}`;
};
export type TokenWithCSSVar<V, T extends Record<string, V> = Record<string, V>> = {
[key in keyof T]?: string | V;
};
export const transformToken = <V, T extends Record<string, V> = Record<string, V>>(
token: T,
themeKey: string,
config?: {
prefix?: string;
ignore?: {
[key in keyof T]?: boolean;
};
unitless?: {
[key in keyof T]?: boolean;
};
preserve?: {
[key in keyof T]?: boolean;
};
scope?: string;
},
): [TokenWithCSSVar<V, T>, string] => {
const cssVars: Record<string, string> = {};
const result: TokenWithCSSVar<V, T> = {};
Object.entries(token).forEach(([key, value]) => {
if (config?.preserve?.[key]) {
result[key as keyof T] = value;
} else if ((typeof value === 'string' || typeof value === 'number') && !config?.ignore?.[key]) {
const cssVar = token2CSSVar(key, config?.prefix);
cssVars[cssVar] =
typeof value === 'number' && !config?.unitless?.[key] ? `${value}px` : String(value);
result[key as keyof T] = `var(${cssVar})`;
}
});
return [result, serializeCSSVar(cssVars, themeKey, { scope: config?.scope })];
};

View File

@ -2,31 +2,32 @@ export function isWindow(obj: any): obj is Window {
return obj !== null && obj !== undefined && obj === obj.window; return obj !== null && obj !== undefined && obj === obj.window;
} }
const getScroll = (target: HTMLElement | Window | Document | null): number => { export default function getScroll(
target: HTMLElement | Window | Document | null,
top: boolean,
): number {
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return 0; return 0;
} }
const method = top ? 'scrollTop' : 'scrollLeft';
let result = 0; let result = 0;
if (isWindow(target)) { if (isWindow(target)) {
result = target.pageYOffset; result = target[top ? 'scrollY' : 'scrollX'];
} else if (target instanceof Document) { } else if (target instanceof Document) {
result = target.documentElement.scrollTop; result = target.documentElement[method];
} else if (target instanceof HTMLElement) { } else if (target instanceof HTMLElement) {
result = target.scrollTop; result = target[method];
} else if (target) { } else if (target) {
// According to the type inference, the `target` is `never` type. // 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:: // Since we configured the loose mode type checking, and supports mocking the target with such shape below::
// `{ documentElement: { scrollLeft: 200, scrollTop: 400 } }`, // `{ documentElement: { scrollLeft: 200, scrollTop: 400 } }`,
// the program may falls into this branch. // the program may falls into this branch.
// Check the corresponding tests for details. Don't sure what is the real scenario this happens. // Check the corresponding tests for details. Don't sure what is the real scenario this happens.
/* biome-ignore lint/complexity/useLiteralKeys: target is a never type */ /* eslint-disable-next-line dot-notation */ result = target[method];
result = target['scrollTop'];
} }
if (target && !isWindow(target) && typeof result !== 'number') { if (target && !isWindow(target) && typeof result !== 'number') {
result = (target.ownerDocument ?? (target as Document)).documentElement?.scrollTop; result = ((target.ownerDocument ?? target) as any).documentElement?.[method];
} }
return result; return result;
}; }
export default getScroll;

View File

@ -1,48 +0,0 @@
import type { Ref, ShallowRef } from 'vue';
import { shallowRef, ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
function useLayoutEffect(
fn: (mount: boolean) => void | VoidFunction,
deps?: Ref<any> | Ref<any>[] | ShallowRef<any> | ShallowRef<any>[],
) {
const firstMount = shallowRef(true);
const cleanupFn = ref(null);
let stopWatch = null;
stopWatch = watch(
deps,
() => {
nextTick(() => {
if (cleanupFn.value) {
cleanupFn.value();
}
cleanupFn.value = fn(firstMount.value);
});
},
{ immediate: true, flush: 'post' },
);
onMounted(() => {
firstMount.value = false;
});
onUnmounted(() => {
if (cleanupFn.value) {
cleanupFn.value();
}
if (stopWatch) {
stopWatch();
}
});
}
export const useLayoutUpdateEffect = (callback, deps) => {
useLayoutEffect(firstMount => {
if (!firstMount) {
return callback();
}
}, deps);
};
export default useLayoutEffect;

View File

@ -14,7 +14,7 @@ interface ScrollToOptions {
export default function scrollTo(y: number, options: ScrollToOptions = {}) { export default function scrollTo(y: number, options: ScrollToOptions = {}) {
const { getContainer = () => window, callback, duration = 450 } = options; const { getContainer = () => window, callback, duration = 450 } = options;
const container = getContainer(); const container = getContainer();
const scrollTop = getScroll(container); const scrollTop = getScroll(container, true);
const startTime = Date.now(); const startTime = Date.now();
const frameFunc = () => { const frameFunc = () => {

View File

@ -27,11 +27,10 @@ import useStyle from './style';
function getDefaultTarget() { function getDefaultTarget() {
return typeof window !== 'undefined' ? window : null; return typeof window !== 'undefined' ? window : null;
} }
const AFFIX_STATUS_NONE = 0; enum AffixStatus {
const AFFIX_STATUS_PREPARE = 1; None,
Prepare,
type AffixStatus = typeof AFFIX_STATUS_NONE | typeof AFFIX_STATUS_PREPARE; }
export interface AffixState { export interface AffixState {
affixStyle?: CSSProperties; affixStyle?: CSSProperties;
placeholderStyle?: CSSProperties; placeholderStyle?: CSSProperties;
@ -83,7 +82,7 @@ const Affix = defineComponent({
const state = reactive({ const state = reactive({
affixStyle: undefined, affixStyle: undefined,
placeholderStyle: undefined, placeholderStyle: undefined,
status: AFFIX_STATUS_NONE, status: AffixStatus.None,
lastAffix: false, lastAffix: false,
prevTarget: null, prevTarget: null,
timeout: null, timeout: null,
@ -99,12 +98,7 @@ const Affix = defineComponent({
const measure = () => { const measure = () => {
const { status, lastAffix } = state; const { status, lastAffix } = state;
const { target } = props; const { target } = props;
if ( if (status !== AffixStatus.Prepare || !fixedNode.value || !placeholderNode.value || !target) {
status !== AFFIX_STATUS_PREPARE ||
!fixedNode.value ||
!placeholderNode.value ||
!target
) {
return; return;
} }
@ -114,7 +108,7 @@ const Affix = defineComponent({
} }
const newState = { const newState = {
status: AFFIX_STATUS_NONE, status: AffixStatus.None,
} as AffixState; } as AffixState;
const placeholderRect = getTargetRect(placeholderNode.value as HTMLElement); const placeholderRect = getTargetRect(placeholderNode.value as HTMLElement);
@ -178,7 +172,7 @@ const Affix = defineComponent({
}; };
const prepareMeasure = () => { const prepareMeasure = () => {
Object.assign(state, { Object.assign(state, {
status: AFFIX_STATUS_PREPARE, status: AffixStatus.Prepare,
affixStyle: undefined, affixStyle: undefined,
placeholderStyle: undefined, placeholderStyle: undefined,
}); });
@ -259,13 +253,12 @@ const Affix = defineComponent({
}); });
const { prefixCls } = useConfigInject('affix', props); const { prefixCls } = useConfigInject('affix', props);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);
return () => { return () => {
const { affixStyle, placeholderStyle, status } = state; const { affixStyle, placeholderStyle, status } = state;
const className = classNames({ const className = classNames({
[prefixCls.value]: affixStyle, [prefixCls.value]: affixStyle,
[hashId.value]: true, [hashId.value]: true,
[cssVarCls.value]: true,
}); });
const restProps = omit(props, [ const restProps = omit(props, [
'prefixCls', 'prefixCls',

View File

@ -1,21 +1,15 @@
import type { CSSObject } from '../../_util/cssinjs'; import type { CSSObject } from '../../_util/cssinjs';
import { FullToken, GenerateStyle, genStyleHooks, GetDefaultToken } from '../../theme/internal'; import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
export interface ComponentToken {
/**
* @desc z-index
* @descEN z-index of popup
*/
zIndexPopup: number;
}
interface AffixToken extends FullToken<'Affix'> { interface AffixToken extends FullToken<'Affix'> {
// zIndexPopup: number;
} }
// ============================== Shared ============================== // ============================== Shared ==============================
const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => { const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => {
const { componentCls } = token; const { componentCls } = token;
return { return {
[componentCls]: { [componentCls]: {
position: 'fixed', position: 'fixed',
@ -24,9 +18,10 @@ const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => {
}; };
}; };
export const prepareComponentToken: GetDefaultToken<'Affix'> = token => ({
zIndexPopup: token.zIndexBase + 10,
});
// ============================== Export ============================== // ============================== Export ==============================
export default genStyleHooks('Affix', genSharedAffixStyle, prepareComponentToken); export default genComponentStyleHook('Affix', token => {
const affixToken = mergeToken<AffixToken>(token, {
zIndexPopup: token.zIndexBase + 10,
});
return [genSharedAffixStyle(affixToken)];
});

View File

@ -9,11 +9,8 @@ export function getTargetRect(target: BindElement): DOMRect {
: ({ top: 0, bottom: window.innerHeight } as DOMRect); : ({ top: 0, bottom: window.innerHeight } as DOMRect);
} }
export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offsetTop?: number) { export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offsetTop: number) {
if ( if (offsetTop !== undefined && targetRect.top > placeholderRect.top - offsetTop) {
offsetTop !== undefined &&
Math.round(targetRect.top) > Math.round(placeholderRect.top) - offsetTop
) {
return `${offsetTop + targetRect.top}px`; return `${offsetTop + targetRect.top}px`;
} }
return undefined; return undefined;
@ -22,12 +19,9 @@ export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offse
export function getFixedBottom( export function getFixedBottom(
placeholderRect: DOMRect, placeholderRect: DOMRect,
targetRect: DOMRect, targetRect: DOMRect,
offsetBottom?: number, offsetBottom: number,
) { ) {
if ( if (offsetBottom !== undefined && targetRect.bottom < placeholderRect.bottom + offsetBottom) {
offsetBottom !== undefined &&
Math.round(targetRect.bottom) < Math.round(placeholderRect.bottom) + offsetBottom
) {
const targetBottomOffset = window.innerHeight - targetRect.bottom; const targetBottomOffset = window.innerHeight - targetRect.bottom;
return `${offsetBottom + targetBottomOffset}px`; return `${offsetBottom + targetBottomOffset}px`;
} }
@ -35,7 +29,7 @@ export function getFixedBottom(
} }
// ======================== Observer ======================== // ======================== Observer ========================
const TRIGGER_EVENTS: (keyof WindowEventMap)[] = [ const TRIGGER_EVENTS = [
'resize', 'resize',
'scroll', 'scroll',
'touchstart', 'touchstart',

View File

@ -70,7 +70,7 @@ const Alert = defineComponent({
props: alertProps(), props: alertProps(),
setup(props, { slots, emit, attrs, expose }) { setup(props, { slots, emit, attrs, expose }) {
const { prefixCls, direction } = useConfigInject('alert', props); const { prefixCls, direction } = useConfigInject('alert', props);
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);
const closing = shallowRef(false); const closing = shallowRef(false);
const closed = shallowRef(false); const closed = shallowRef(false);
const alertNode = shallowRef(); const alertNode = shallowRef();
@ -134,7 +134,6 @@ const Alert = defineComponent({
[`${prefixClsValue}-closable`]: closable, [`${prefixClsValue}-closable`]: closable,
[`${prefixClsValue}-rtl`]: direction.value === 'rtl', [`${prefixClsValue}-rtl`]: direction.value === 'rtl',
[hashId.value]: true, [hashId.value]: true,
[cssVarCls.value]: true,
}); });
const closeIcon = closable ? ( const closeIcon = closable ? (

View File

@ -1,29 +1,13 @@
import { CSSObject, unit } from '../../_util/cssinjs'; import type { CSSInterpolation, CSSObject } from '../../_util/cssinjs';
import { FullToken, GenerateStyle, genStyleHooks, GetDefaultToken } from '../../theme/internal'; import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../style'; import { resetComponent } from '../../style';
import { CSSProperties } from 'vue';
export interface ComponentToken { export interface ComponentToken {}
// Component token here
/**
* @desc
* @descEN Default padding
*/
defaultPadding: CSSProperties['padding'];
/**
* @desc
* @descEN Padding with description
*/
withDescriptionPadding: CSSProperties['padding'];
/**
* @desc
* @descEN Icon size with description
*/
withDescriptionIconSize: number;
}
type AlertToken = FullToken<'Alert'> & { type AlertToken = FullToken<'Alert'> & {
// Custom token here alertIconSizeLG: number;
alertPaddingHorizontal: number;
}; };
const genAlertTypeStyle = ( const genAlertTypeStyle = (
@ -33,8 +17,8 @@ const genAlertTypeStyle = (
token: AlertToken, token: AlertToken,
alertCls: string, alertCls: string,
): CSSObject => ({ ): CSSObject => ({
background: bgColor, backgroundColor: bgColor,
border: `${unit(token.lineWidth)} ${token.lineType} ${borderColor}`, border: `${token.lineWidth}px ${token.lineType} ${borderColor}`,
[`${alertCls}-icon`]: { [`${alertCls}-icon`]: {
color: iconColor, color: iconColor,
}, },
@ -51,11 +35,12 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
lineHeight, lineHeight,
borderRadiusLG: borderRadius, borderRadiusLG: borderRadius,
motionEaseInOutCirc, motionEaseInOutCirc,
withDescriptionIconSize, alertIconSizeLG,
colorText, colorText,
colorTextHeading, paddingContentVerticalSM,
withDescriptionPadding, alertPaddingHorizontal,
defaultPadding, paddingMD,
paddingContentHorizontalLG,
} = token; } = token;
return { return {
@ -64,7 +49,7 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
padding: defaultPadding, padding: `${paddingContentVerticalSM}px ${alertPaddingHorizontal}px`, // Fixed horizontal padding here.
wordWrap: 'break-word', wordWrap: 'break-word',
borderRadius, borderRadius,
@ -82,14 +67,14 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
lineHeight: 0, lineHeight: 0,
}, },
'&-description': { [`&-description`]: {
display: 'none', display: 'none',
fontSize, fontSize,
lineHeight, lineHeight,
}, },
'&-message': { '&-message': {
color: colorTextHeading, color: colorText,
}, },
[`&${componentCls}-motion-leave`]: { [`&${componentCls}-motion-leave`]: {
@ -111,23 +96,24 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
[`${componentCls}-with-description`]: { [`${componentCls}-with-description`]: {
alignItems: 'flex-start', alignItems: 'flex-start',
padding: withDescriptionPadding, paddingInline: paddingContentHorizontalLG,
paddingBlock: paddingMD,
[`${componentCls}-icon`]: { [`${componentCls}-icon`]: {
marginInlineEnd: marginSM, marginInlineEnd: marginSM,
fontSize: withDescriptionIconSize, fontSize: alertIconSizeLG,
lineHeight: 0, lineHeight: 0,
}, },
[`${componentCls}-message`]: { [`${componentCls}-message`]: {
display: 'block', display: 'block',
marginBottom: marginXS, marginBottom: marginXS,
color: colorTextHeading, color: colorText,
fontSize: fontSizeLG, fontSize: fontSizeLG,
}, },
[`${componentCls}-description`]: { [`${componentCls}-description`]: {
display: 'block', display: 'block',
color: colorText,
}, },
}, },
@ -201,7 +187,7 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
return { return {
[componentCls]: { [componentCls]: {
'&-action': { [`&-action`]: {
marginInlineStart: marginXS, marginInlineStart: marginXS,
}, },
@ -210,7 +196,7 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
padding: 0, padding: 0,
overflow: 'hidden', overflow: 'hidden',
fontSize: fontSizeIcon, fontSize: fontSizeIcon,
lineHeight: unit(fontSizeIcon), lineHeight: `${fontSizeIcon}px`,
backgroundColor: 'transparent', backgroundColor: 'transparent',
border: 'none', border: 'none',
outline: 'none', outline: 'none',
@ -236,17 +222,19 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
}; };
}; };
export const prepareComponentToken: GetDefaultToken<'Alert'> = token => { export const genAlertStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSInterpolation => [
const paddingHorizontal = 12; // Fixed value here. genBaseStyle(token),
return { genTypeStyle(token),
withDescriptionIconSize: token.fontSizeHeading3, genActionStyle(token),
defaultPadding: `${token.paddingContentVerticalSM}px ${paddingHorizontal}px`, ];
withDescriptionPadding: `${token.paddingMD}px ${token.paddingContentHorizontalLG}px`,
};
};
export default genStyleHooks( export default genComponentStyleHook('Alert', token => {
'Alert', const { fontSizeHeading3 } = token;
token => [genBaseStyle(token), genTypeStyle(token), genActionStyle(token)],
prepareComponentToken, const alertToken = mergeToken<AlertToken>(token, {
); alertIconSizeLG: fontSizeHeading3,
alertPaddingHorizontal: 12, // Fixed value here.
});
return [genAlertStyle(alertToken)];
});

View File

@ -23,7 +23,6 @@ import AnchorLink from './AnchorLink';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import devWarning from '../vc-util/devWarning'; import devWarning from '../vc-util/devWarning';
import { arrayType } from '../_util/type'; import { arrayType } from '../_util/type';
import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
export type AnchorDirection = 'vertical' | 'horizontal'; export type AnchorDirection = 'vertical' | 'horizontal';
@ -40,7 +39,8 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
if (rect.width || rect.height) { if (rect.width || rect.height) {
if (container === window) { if (container === window) {
return rect.top - element.ownerDocument!.documentElement!.clientTop; container = element.ownerDocument!.documentElement!;
return rect.top - container.clientTop;
} }
return rect.top - (container as HTMLElement).getBoundingClientRect().top; return rect.top - (container as HTMLElement).getBoundingClientRect().top;
} }
@ -70,7 +70,6 @@ export const anchorProps = () => ({
targetOffset: Number, targetOffset: Number,
items: arrayType<AnchorLinkItemProps[]>(), items: arrayType<AnchorLinkItemProps[]>(),
direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'), direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'),
replace: Boolean,
onChange: Function as PropType<(currentActiveLink: string) => void>, onChange: Function as PropType<(currentActiveLink: string) => void>,
onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>, onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>,
}); });
@ -92,7 +91,7 @@ export default defineComponent({
setup(props, { emit, attrs, slots, expose }) { setup(props, { emit, attrs, slots, expose }) {
const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props); const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props);
const anchorDirection = computed(() => props.direction ?? 'vertical'); const anchorDirection = computed(() => props.direction ?? 'vertical');
const rootCls = useCSSVarCls(prefixCls);
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
devWarning( devWarning(
props.items && typeof slots.default !== 'function', props.items && typeof slots.default !== 'function',
@ -134,7 +133,7 @@ export default defineComponent({
const target = document.getElementById(sharpLinkMatch[1]); const target = document.getElementById(sharpLinkMatch[1]);
if (target) { if (target) {
const top = getOffsetTop(target, container); const top = getOffsetTop(target, container);
if (top <= offsetTop + bounds) { if (top < offsetTop + bounds) {
linkSections.push({ linkSections.push({
link, link,
top, top,
@ -171,7 +170,7 @@ export default defineComponent({
} }
const container = getContainer.value(); const container = getContainer.value();
const scrollTop = getScroll(container); const scrollTop = getScroll(container, true);
const eleOffsetTop = getOffsetTop(targetElement, container); const eleOffsetTop = getOffsetTop(targetElement, container);
let y = scrollTop + eleOffsetTop; let y = scrollTop + eleOffsetTop;
y -= targetOffset !== undefined ? targetOffset : offsetTop || 0; y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
@ -278,7 +277,6 @@ export default defineComponent({
title={title} title={title}
customTitleProps={option} customTitleProps={option}
v-slots={{ customTitle: slots.customTitle }} v-slots={{ customTitle: slots.customTitle }}
replace={props.replace}
> >
{anchorDirection.value === 'vertical' ? createNestedLink(children) : null} {anchorDirection.value === 'vertical' ? createNestedLink(children) : null}
</AnchorLink> </AnchorLink>
@ -286,7 +284,7 @@ export default defineComponent({
}) })
: null; : null;
const [wrapSSR, hashId, cssVarCls] = useStyle(prefixCls, rootCls); const [wrapSSR, hashId] = useStyle(prefixCls);
return () => { return () => {
const { offsetTop, affix, showInkInFixed } = props; const { offsetTop, affix, showInkInFixed } = props;
@ -298,8 +296,6 @@ export default defineComponent({
const wrapperClass = classNames(hashId.value, props.wrapperClass, `${pre}-wrapper`, { const wrapperClass = classNames(hashId.value, props.wrapperClass, `${pre}-wrapper`, {
[`${pre}-wrapper-horizontal`]: anchorDirection.value === 'horizontal', [`${pre}-wrapper-horizontal`]: anchorDirection.value === 'horizontal',
[`${pre}-rtl`]: direction.value === 'rtl', [`${pre}-rtl`]: direction.value === 'rtl',
[rootCls.value]: true,
[cssVarCls.value]: true,
}); });
const anchorClass = classNames(pre, { const anchorClass = classNames(pre, {

View File

@ -13,7 +13,6 @@ export const anchorLinkProps = () => ({
href: String, href: String,
title: anyType<VueNode | ((item: any) => VueNode)>(), title: anyType<VueNode | ((item: any) => VueNode)>(),
target: String, target: String,
replace: Boolean,
/* private use */ /* private use */
customTitleProps: objectType<AnchorLinkItemProps>(), customTitleProps: objectType<AnchorLinkItemProps>(),
}); });
@ -54,10 +53,6 @@ export default defineComponent({
const { href } = props; const { href } = props;
contextHandleClick(e, { title: mergedTitle, href }); contextHandleClick(e, { title: mergedTitle, href });
scrollTo(href); scrollTo(href);
if (props.replace) {
e.preventDefault();
window.location.replace(href);
}
}; };
watch( watch(

View File

@ -1,55 +1,21 @@
import { unit } from '../../_util/cssinjs'; import type { CSSObject } from '../../_util/cssinjs';
import { import type { FullToken, GenerateStyle } from '../../theme/internal';
FullToken, import { genComponentStyleHook, mergeToken } from '../../theme/internal';
GenerateStyle,
genStyleHooks,
GetDefaultToken,
mergeToken,
} from '../../theme/internal';
import { resetComponent, textEllipsis } from '../../style'; import { resetComponent, textEllipsis } from '../../style';
export interface ComponentToken { export interface ComponentToken {}
/**
* @desc
* @descEN Vertical padding of link
*/
linkPaddingBlock: number;
/**
* @desc
* @descEN Horizontal padding of link
*/
linkPaddingInlineStart: number;
}
/**
* @desc Anchor Token
* @descEN Token for Anchor component
*/
interface AnchorToken extends FullToken<'Anchor'> { interface AnchorToken extends FullToken<'Anchor'> {
/**
* @desc
* @descEN Holder block offset
*/
holderOffsetBlock: number; holderOffsetBlock: number;
/** anchorPaddingBlock: number;
* @desc anchorPaddingBlockSecondary: number;
* @descEN Secondary anchor block padding anchorPaddingInline: number;
*/ anchorBallSize: number;
anchorPaddingBlockSecondary: number | string; anchorTitleBlock: number;
/**
* @desc
* @descEN Anchor ball size
*/
anchorBallSize: number | string;
/**
* @desc
* @descEN Anchor title block
*/
anchorTitleBlock: number | string;
} }
// ============================== Shared ============================== // ============================== Shared ==============================
const genSharedAnchorStyle: GenerateStyle<AnchorToken> = token => { const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
const { const {
componentCls, componentCls,
holderOffsetBlock, holderOffsetBlock,
@ -58,25 +24,26 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = token => {
colorPrimary, colorPrimary,
lineType, lineType,
colorSplit, colorSplit,
calc,
} = token; } = token;
return { return {
[`${componentCls}-wrapper`]: { [`${componentCls}-wrapper`]: {
marginBlockStart: calc(holderOffsetBlock).mul(-1).equal(), marginBlockStart: -holderOffsetBlock,
paddingBlockStart: holderOffsetBlock, paddingBlockStart: holderOffsetBlock,
// delete overflow: auto // delete overflow: auto
// overflow: 'auto', // overflow: 'auto',
backgroundColor: 'transparent',
[componentCls]: { [componentCls]: {
...resetComponent(token), ...resetComponent(token),
position: 'relative', position: 'relative',
paddingInlineStart: lineWidthBold, paddingInlineStart: lineWidthBold,
[`${componentCls}-link`]: { [`${componentCls}-link`]: {
paddingBlock: token.linkPaddingBlock, paddingBlock: token.anchorPaddingBlock,
paddingInline: `${unit(token.linkPaddingInlineStart)} 0`, paddingInline: `${token.anchorPaddingInline}px 0`,
'&-title': { '&-title': {
...textEllipsis, ...textEllipsis,
@ -106,21 +73,28 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = token => {
[componentCls]: { [componentCls]: {
'&::before': { '&::before': {
position: 'absolute', position: 'absolute',
insetInlineStart: 0, left: {
_skip_check_: true,
value: 0,
},
top: 0, top: 0,
height: '100%', height: '100%',
borderInlineStart: `${unit(lineWidthBold)} ${lineType} ${colorSplit}`, borderInlineStart: `${lineWidthBold}px ${lineType} ${colorSplit}`,
content: '" "', content: '" "',
}, },
[`${componentCls}-ink`]: { [`${componentCls}-ink`]: {
position: 'absolute', position: 'absolute',
insetInlineStart: 0, left: {
_skip_check_: true,
value: 0,
},
display: 'none', display: 'none',
transform: 'translateY(-50%)', transform: 'translateY(-50%)',
transition: `top ${motionDurationSlow} ease-in-out`, transition: `top ${motionDurationSlow} ease-in-out`,
width: lineWidthBold, width: lineWidthBold,
backgroundColor: colorPrimary, backgroundColor: colorPrimary,
[`&${componentCls}-ink-visible`]: { [`&${componentCls}-ink-visible`]: {
display: 'inline-block', display: 'inline-block',
}, },
@ -135,7 +109,7 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = token => {
}; };
}; };
const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = token => { const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
const { componentCls, motionDurationSlow, lineWidthBold, colorPrimary } = token; const { componentCls, motionDurationSlow, lineWidthBold, colorPrimary } = token;
return { return {
@ -153,7 +127,7 @@ const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = token => {
value: 0, value: 0,
}, },
bottom: 0, bottom: 0,
borderBottom: `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}`, borderBottom: `1px ${token.lineType} ${token.colorSplit}`,
content: '" "', content: '" "',
}, },
@ -183,23 +157,17 @@ const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = token => {
}; };
}; };
export const prepareComponentToken: GetDefaultToken<'Anchor'> = token => ({
linkPaddingBlock: token.paddingXXS,
linkPaddingInlineStart: token.padding,
});
// ============================== Export ============================== // ============================== Export ==============================
export default genStyleHooks( export default genComponentStyleHook('Anchor', token => {
'Anchor', const { fontSize, fontSizeLG, padding, paddingXXS } = token;
token => {
const { fontSize, fontSizeLG, paddingXXS, calc } = token; const anchorToken = mergeToken<AnchorToken>(token, {
const anchorToken = mergeToken<AnchorToken>(token, { holderOffsetBlock: paddingXXS,
holderOffsetBlock: paddingXXS, anchorPaddingBlock: paddingXXS,
anchorPaddingBlockSecondary: calc(paddingXXS).div(2).equal(), anchorPaddingBlockSecondary: paddingXXS / 2,
anchorTitleBlock: calc(fontSize).div(14).mul(3).equal(), anchorPaddingInline: padding,
anchorBallSize: calc(fontSizeLG).div(2).equal(), anchorTitleBlock: (fontSize / 14) * 3,
}); anchorBallSize: fontSizeLG / 2,
return [genSharedAnchorStyle(anchorToken), genSharedAnchorHorizontalStyle(anchorToken)]; });
}, return [genSharedAnchorStyle(anchorToken), genSharedAnchorHorizontalStyle(anchorToken)];
prepareComponentToken, });
);

View File

@ -4,8 +4,6 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal'; import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genPresetColor, resetComponent } from '../../style'; import { genPresetColor, resetComponent } from '../../style';
export interface ComponentToken {}
interface BadgeToken extends FullToken<'Badge'> { interface BadgeToken extends FullToken<'Badge'> {
badgeFontHeight: number; badgeFontHeight: number;
badgeZIndex: number | string; badgeZIndex: number | string;

View File

@ -3,8 +3,6 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal'; import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genFocusStyle, resetComponent } from '../../style'; import { genFocusStyle, resetComponent } from '../../style';
export interface ComponentToken {}
interface BreadcrumbToken extends FullToken<'Breadcrumb'> { interface BreadcrumbToken extends FullToken<'Breadcrumb'> {
breadcrumbBaseColor: string; breadcrumbBaseColor: string;
breadcrumbFontSize: number; breadcrumbFontSize: number;

View File

@ -45,11 +45,7 @@ export default defineComponent({
break; break;
default: default:
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
devWarning( devWarning(!size, 'Button.Group', 'Invalid prop `size`.');
!size || ['large', 'small', 'middle'].includes(size),
'Button.Group',
'Invalid prop `size`.',
);
} }
return { return {
[`${prefixCls.value}`]: true, [`${prefixCls.value}`]: true,

View File

@ -45,7 +45,7 @@ export default defineComponent({
// emits: ['click', 'mousedown'], // emits: ['click', 'mousedown'],
setup(props, { slots, attrs, emit, expose }) { setup(props, { slots, attrs, emit, expose }) {
const { prefixCls, autoInsertSpaceInButton, direction, size } = useConfigInject('btn', props); const { prefixCls, autoInsertSpaceInButton, direction, size } = useConfigInject('btn', props);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);
const groupSizeContext = GroupSizeContext.useInject(); const groupSizeContext = GroupSizeContext.useInject();
const disabledContext = useInjectDisabled(); const disabledContext = useInjectDisabled();
const mergedDisabled = computed(() => props.disabled ?? disabledContext.value); const mergedDisabled = computed(() => props.disabled ?? disabledContext.value);
@ -95,7 +95,6 @@ export default defineComponent({
compactItemClassnames.value, compactItemClassnames.value,
{ {
[hashId.value]: true, [hashId.value]: true,
[cssVarCls.value]: true,
[`${pre}`]: true, [`${pre}`]: true,
[`${pre}-${shape}`]: shape !== 'default' && shape, [`${pre}-${shape}`]: shape !== 'default' && shape,
[`${pre}-${type}`]: type, [`${pre}-${type}`]: type,
@ -217,7 +216,7 @@ export default defineComponent({
); );
if (href !== undefined) { if (href !== undefined) {
return wrapCSSVar( return wrapSSR(
<a {...buttonProps} href={href} target={target} ref={buttonNodeRef}> <a {...buttonProps} href={href} target={target} ref={buttonNodeRef}>
{iconNode} {iconNode}
{kids} {kids}
@ -240,7 +239,7 @@ export default defineComponent({
); );
} }
return wrapCSSVar(buttonNode); return wrapSSR(buttonNode);
}; };
}, },
}); });

View File

@ -21,7 +21,7 @@ If you want specific control over the positioning and placement of the `Icon`, t
<template> <template>
<a-space direction="vertical"> <a-space direction="vertical">
<a-space warp> <a-space wrap>
<a-tooltip title="search"> <a-tooltip title="search">
<a-button type="primary" shape="circle" :icon="h(SearchOutlined)" /> <a-button type="primary" shape="circle" :icon="h(SearchOutlined)" />
</a-tooltip> </a-tooltip>
@ -32,7 +32,7 @@ If you want specific control over the positioning and placement of the `Icon`, t
</a-tooltip> </a-tooltip>
<a-button :icon="h(SearchOutlined)">Search</a-button> <a-button :icon="h(SearchOutlined)">Search</a-button>
</a-space> </a-space>
<a-space warp> <a-space wrap>
<a-tooltip title="search"> <a-tooltip title="search">
<a-button shape="circle" :icon="h(SearchOutlined)" /> <a-button shape="circle" :icon="h(SearchOutlined)" />
</a-tooltip> </a-tooltip>

View File

@ -1,72 +0,0 @@
// Style as inline component
import type { ButtonToken } from './token';
import { prepareComponentToken, prepareToken } from './token';
import { genCompactItemStyle } from '../../style/compact-item';
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
import type { GenerateStyle } from '../../theme/internal';
import { genSubStyleComponent } from '../../theme/internal';
import type { CSSObject } from '../../_util/cssinjs';
import { unit } from '../../_util/cssinjs';
const genButtonCompactStyle: GenerateStyle<ButtonToken, CSSObject> = token => {
const { componentCls, calc } = token;
return {
[componentCls]: {
// Special styles for Primary Button
[`&-compact-item${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: calc(token.lineWidth).mul(-1).equal(),
insetInlineStart: calc(token.lineWidth).mul(-1).equal(),
display: 'inline-block',
width: token.lineWidth,
height: `calc(100% + ${unit(token.lineWidth)} * 2)`,
backgroundColor: token.colorPrimaryHover,
content: '""',
},
},
},
// Special styles for Primary Button
'&-compact-vertical-item': {
[`&${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: calc(token.lineWidth).mul(-1).equal(),
insetInlineStart: calc(token.lineWidth).mul(-1).equal(),
display: 'inline-block',
width: `calc(100% + ${unit(token.lineWidth)} * 2)`,
height: token.lineWidth,
backgroundColor: token.colorPrimaryHover,
content: '""',
},
},
},
},
},
};
};
// ============================== Export ==============================
export default genSubStyleComponent(
['Button', 'compact'],
token => {
const buttonToken = prepareToken(token);
return [
// Space Compact
genCompactItemStyle(buttonToken),
genCompactItemVerticalStyle(buttonToken),
genButtonCompactStyle(buttonToken),
] as CSSObject[];
},
prepareComponentToken,
);

View File

@ -1,5 +1,4 @@
import type { CSSObject } from '../../_util/cssinjs'; import type { ButtonToken } from '.';
import type { ButtonToken } from './token';
import type { GenerateStyle } from '../../theme/internal'; import type { GenerateStyle } from '../../theme/internal';
const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({ const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({
@ -23,8 +22,8 @@ const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({
}, },
}); });
const genGroupStyle: GenerateStyle<ButtonToken, CSSObject> = token => { const genGroupStyle: GenerateStyle<ButtonToken> = token => {
const { componentCls, fontSize, lineWidth, groupBorderColor, colorErrorHover } = token; const { componentCls, fontSize, lineWidth, colorPrimaryHover, colorErrorHover } = token;
return { return {
[`${componentCls}-group`]: [ [`${componentCls}-group`]: [
@ -42,7 +41,7 @@ const genGroupStyle: GenerateStyle<ButtonToken, CSSObject> = token => {
}, },
'&:not(:first-child)': { '&:not(:first-child)': {
marginInlineStart: token.calc(lineWidth).mul(-1).equal(), marginInlineStart: -lineWidth,
[`&, & > ${componentCls}`]: { [`&, & > ${componentCls}`]: {
borderStartStartRadius: 0, borderStartStartRadius: 0,
@ -72,7 +71,7 @@ const genGroupStyle: GenerateStyle<ButtonToken, CSSObject> = token => {
}, },
// Border Color // Border Color
genButtonBorderStyle(`${componentCls}-primary`, groupBorderColor), genButtonBorderStyle(`${componentCls}-primary`, colorPrimaryHover),
genButtonBorderStyle(`${componentCls}-danger`, colorErrorHover), genButtonBorderStyle(`${componentCls}-danger`, colorErrorHover),
], ],
}; };

View File

@ -1,59 +1,51 @@
import type { CSSObject } from '../../_util/cssinjs'; import type { CSSInterpolation, CSSObject } from '../../_util/cssinjs';
import { unit } from '../../_util/cssinjs'; import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genFocusStyle } from '../../style';
import type { GenerateStyle } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
import genGroupStyle from './group'; import genGroupStyle from './group';
import type { ButtonToken, ComponentToken } from './token'; import { genFocusStyle } from '../../style';
import { prepareComponentToken, prepareToken } from './token'; import { genCompactItemStyle } from '../../style/compact-item';
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
export type { ComponentToken }; /** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {}
export interface ButtonToken extends FullToken<'Button'> {
// FIXME: should be removed
colorOutlineDefault: string;
buttonPaddingHorizontal: number;
}
// ============================== Shared ============================== // ============================== Shared ==============================
const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => { const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => {
const { componentCls, iconCls, fontWeight } = token; const { componentCls, iconCls } = token;
return { return {
[componentCls]: { [componentCls]: {
outline: 'none', outline: 'none',
position: 'relative', position: 'relative',
display: 'inline-block', display: 'inline-block',
fontWeight, fontWeight: 400,
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textAlign: 'center', textAlign: 'center',
backgroundImage: 'none', backgroundImage: 'none',
background: 'transparent', backgroundColor: 'transparent',
border: `${unit(token.lineWidth)} ${token.lineType} transparent`, border: `${token.lineWidth}px ${token.lineType} transparent`,
cursor: 'pointer', cursor: 'pointer',
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`, transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
userSelect: 'none', userSelect: 'none',
touchAction: 'manipulation', touchAction: 'manipulation',
lineHeight: token.lineHeight,
color: token.colorText, color: token.colorText,
'&:disabled > *': {
pointerEvents: 'none',
},
'> span': { '> span': {
display: 'inline-block', display: 'inline-block',
}, },
[`${componentCls}-icon`]: {
lineHeight: 0,
},
// Leave a space between icon and text. // Leave a space between icon and text.
[`> ${iconCls} + span, > span + ${iconCls}`]: { [`> ${iconCls} + span, > span + ${iconCls}`]: {
marginInlineStart: token.marginXS, marginInlineStart: token.marginXS,
}, },
[`&:not(${componentCls}-icon-only) > ${componentCls}-icon`]: {
[`&${componentCls}-loading-icon, &:not(:last-child)`]: {
marginInlineEnd: token.marginXS,
},
},
'> a': { '> a': {
color: 'currentColor', color: 'currentColor',
}, },
@ -62,29 +54,54 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
...genFocusStyle(token), ...genFocusStyle(token),
}, },
[`&${componentCls}-two-chinese-chars::first-letter`]: {
letterSpacing: '0.34em',
},
[`&${componentCls}-two-chinese-chars > *:not(${iconCls})`]: {
marginInlineEnd: '-0.34em',
letterSpacing: '0.34em',
},
// make `btn-icon-only` not too narrow // make `btn-icon-only` not too narrow
[`&-icon-only${componentCls}-compact-item`]: { [`&-icon-only${componentCls}-compact-item`]: {
flex: 'none', flex: 'none',
}, },
// Special styles for Primary Button
[`&-compact-item${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: -token.lineWidth,
insetInlineStart: -token.lineWidth,
display: 'inline-block',
width: token.lineWidth,
height: `calc(100% + ${token.lineWidth * 2}px)`,
backgroundColor: token.colorPrimaryHover,
content: '""',
},
},
},
// Special styles for Primary Button
'&-compact-vertical-item': {
[`&${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: -token.lineWidth,
insetInlineStart: -token.lineWidth,
display: 'inline-block',
width: `calc(100% + ${token.lineWidth * 2}px)`,
height: token.lineWidth,
backgroundColor: token.colorPrimaryHover,
content: '""',
},
},
},
},
}, },
} as CSSObject; };
}; };
const genHoverActiveButtonStyle = ( const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject): CSSObject => ({
btnCls: string, '&:not(:disabled)': {
hoverStyle: CSSObject,
activeStyle: CSSObject,
): CSSObject => ({
[`&:not(:disabled):not(${btnCls}-disabled)`]: {
'&:hover': hoverStyle, '&:hover': hoverStyle,
'&:active': activeStyle, '&:active': activeStyle,
}, },
@ -100,22 +117,21 @@ const genCircleButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
const genRoundButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({ const genRoundButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
borderRadius: token.controlHeight, borderRadius: token.controlHeight,
paddingInlineStart: token.calc(token.controlHeight).div(2).equal(), paddingInlineStart: token.controlHeight / 2,
paddingInlineEnd: token.calc(token.controlHeight).div(2).equal(), paddingInlineEnd: token.controlHeight / 2,
}); });
// =============================== Type =============================== // =============================== Type ===============================
const genDisabledStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({ const genDisabledStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
cursor: 'not-allowed', cursor: 'not-allowed',
borderColor: token.borderColorDisabled, borderColor: token.colorBorder,
color: token.colorTextDisabled, color: token.colorTextDisabled,
background: token.colorBgContainerDisabled, backgroundColor: token.colorBgContainerDisabled,
boxShadow: 'none', boxShadow: 'none',
}); });
const genGhostButtonStyle = ( const genGhostButtonStyle = (
btnCls: string, btnCls: string,
background: string,
textColor: string | false, textColor: string | false,
borderColor: string | false, borderColor: string | false,
textColorDisabled: string | false, textColorDisabled: string | false,
@ -125,18 +141,17 @@ const genGhostButtonStyle = (
): CSSObject => ({ ): CSSObject => ({
[`&${btnCls}-background-ghost`]: { [`&${btnCls}-background-ghost`]: {
color: textColor || undefined, color: textColor || undefined,
background, backgroundColor: 'transparent',
borderColor: borderColor || undefined, borderColor: borderColor || undefined,
boxShadow: 'none', boxShadow: 'none',
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
btnCls,
{ {
background, backgroundColor: 'transparent',
...hoverStyle, ...hoverStyle,
}, },
{ {
background, backgroundColor: 'transparent',
...activeStyle, ...activeStyle,
}, },
), ),
@ -150,7 +165,7 @@ const genGhostButtonStyle = (
}); });
const genSolidDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({ const genSolidDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
[`&:disabled, &${token.componentCls}-disabled`]: { '&:disabled': {
...genDisabledStyle(token), ...genDisabledStyle(token),
}, },
}); });
@ -160,7 +175,7 @@ const genSolidButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
}); });
const genPureDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({ const genPureDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
[`&:disabled, &${token.componentCls}-disabled`]: { '&:disabled': {
cursor: 'not-allowed', cursor: 'not-allowed',
color: token.colorTextDisabled, color: token.colorTextDisabled,
}, },
@ -170,14 +185,12 @@ const genPureDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token
const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genSolidButtonStyle(token), ...genSolidButtonStyle(token),
background: token.defaultBg, backgroundColor: token.colorBgContainer,
borderColor: token.defaultBorderColor, borderColor: token.colorBorder,
color: token.defaultColor,
boxShadow: token.defaultShadow, boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
token.componentCls,
{ {
color: token.colorPrimaryHover, color: token.colorPrimaryHover,
borderColor: token.colorPrimaryHover, borderColor: token.colorPrimaryHover,
@ -190,9 +203,8 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genGhostButtonStyle( ...genGhostButtonStyle(
token.componentCls, token.componentCls,
token.ghostBg, token.colorBgContainer,
token.defaultGhostColor, token.colorBgContainer,
token.defaultGhostBorderColor,
token.colorTextDisabled, token.colorTextDisabled,
token.colorBorder, token.colorBorder,
), ),
@ -202,7 +214,6 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
borderColor: token.colorError, borderColor: token.colorError,
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
token.componentCls,
{ {
color: token.colorErrorHover, color: token.colorErrorHover,
borderColor: token.colorErrorBorderHover, borderColor: token.colorErrorBorderHover,
@ -215,7 +226,6 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genGhostButtonStyle( ...genGhostButtonStyle(
token.componentCls, token.componentCls,
token.ghostBg,
token.colorError, token.colorError,
token.colorError, token.colorError,
token.colorTextDisabled, token.colorTextDisabled,
@ -229,26 +239,24 @@ const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({ const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genSolidButtonStyle(token), ...genSolidButtonStyle(token),
color: token.primaryColor, color: token.colorTextLightSolid,
background: token.colorPrimary, backgroundColor: token.colorPrimary,
boxShadow: token.primaryShadow, boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
token.componentCls,
{ {
color: token.colorTextLightSolid, color: token.colorTextLightSolid,
background: token.colorPrimaryHover, backgroundColor: token.colorPrimaryHover,
}, },
{ {
color: token.colorTextLightSolid, color: token.colorTextLightSolid,
background: token.colorPrimaryActive, backgroundColor: token.colorPrimaryActive,
}, },
), ),
...genGhostButtonStyle( ...genGhostButtonStyle(
token.componentCls, token.componentCls,
token.ghostBg,
token.colorPrimary, token.colorPrimary,
token.colorPrimary, token.colorPrimary,
token.colorTextDisabled, token.colorTextDisabled,
@ -264,23 +272,20 @@ const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
), ),
[`&${token.componentCls}-dangerous`]: { [`&${token.componentCls}-dangerous`]: {
background: token.colorError, backgroundColor: token.colorError,
boxShadow: token.dangerShadow, boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
color: token.dangerColor,
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
token.componentCls,
{ {
background: token.colorErrorHover, backgroundColor: token.colorErrorHover,
}, },
{ {
background: token.colorErrorActive, backgroundColor: token.colorErrorActive,
}, },
), ),
...genGhostButtonStyle( ...genGhostButtonStyle(
token.componentCls, token.componentCls,
token.ghostBg,
token.colorError, token.colorError,
token.colorError, token.colorError,
token.colorTextDisabled, token.colorTextDisabled,
@ -309,10 +314,8 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
color: token.colorLink, color: token.colorLink,
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
token.componentCls,
{ {
color: token.colorLinkHover, color: token.colorLinkHover,
background: token.linkHoverBg,
}, },
{ {
color: token.colorLinkActive, color: token.colorLinkActive,
@ -325,7 +328,6 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
color: token.colorError, color: token.colorError,
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
token.componentCls,
{ {
color: token.colorErrorHover, color: token.colorErrorHover,
}, },
@ -341,14 +343,13 @@ const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
// Type: Text // Type: Text
const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({ const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
token.componentCls,
{ {
color: token.colorText, color: token.colorText,
background: token.textHoverBg, backgroundColor: token.colorBgTextHover,
}, },
{ {
color: token.colorText, color: token.colorText,
background: token.colorBgTextActive, backgroundColor: token.colorBgTextActive,
}, },
), ),
@ -359,19 +360,26 @@ const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genPureDisabledButtonStyle(token), ...genPureDisabledButtonStyle(token),
...genHoverActiveButtonStyle( ...genHoverActiveButtonStyle(
token.componentCls,
{ {
color: token.colorErrorHover, color: token.colorErrorHover,
background: token.colorErrorBg, backgroundColor: token.colorErrorBg,
}, },
{ {
color: token.colorErrorHover, color: token.colorErrorHover,
background: token.colorErrorBg, backgroundColor: token.colorErrorBg,
}, },
), ),
}, },
}); });
// Href and Disabled
const genDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = token => ({
...genDisabledStyle(token),
[`&${token.componentCls}:hover`]: {
...genDisabledStyle(token),
},
});
const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => { const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => {
const { componentCls } = token; const { componentCls } = token;
@ -381,30 +389,26 @@ const genTypeButtonStyle: GenerateStyle<ButtonToken> = token => {
[`${componentCls}-dashed`]: genDashedButtonStyle(token), [`${componentCls}-dashed`]: genDashedButtonStyle(token),
[`${componentCls}-link`]: genLinkButtonStyle(token), [`${componentCls}-link`]: genLinkButtonStyle(token),
[`${componentCls}-text`]: genTextButtonStyle(token), [`${componentCls}-text`]: genTextButtonStyle(token),
[`${componentCls}-ghost`]: genGhostButtonStyle( [`${componentCls}-disabled`]: genDisabledButtonStyle(token),
token.componentCls,
token.ghostBg,
token.colorBgContainer,
token.colorBgContainer,
token.colorTextDisabled,
token.colorBorder,
),
}; };
}; };
// =============================== Size =============================== // =============================== Size ===============================
const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = '') => { const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSSInterpolation => {
const { const {
componentCls, componentCls,
iconCls,
controlHeight, controlHeight,
fontSize, fontSize,
lineHeight, lineHeight,
lineWidth,
borderRadius, borderRadius,
buttonPaddingHorizontal, buttonPaddingHorizontal,
iconCls,
buttonPaddingVertical,
} = token; } = token;
const paddingVertical = Math.max(0, (controlHeight - fontSize * lineHeight) / 2 - lineWidth);
const paddingHorizontal = buttonPaddingHorizontal - lineWidth;
const iconOnlyCls = `${componentCls}-icon-only`; const iconOnlyCls = `${componentCls}-icon-only`;
return [ return [
@ -412,9 +416,8 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = '') => {
{ {
[`${componentCls}${sizePrefixCls}`]: { [`${componentCls}${sizePrefixCls}`]: {
fontSize, fontSize,
lineHeight,
height: controlHeight, height: controlHeight,
padding: `${unit(buttonPaddingVertical!)} ${unit(buttonPaddingHorizontal!)}`, padding: `${paddingVertical}px ${paddingHorizontal}px`,
borderRadius, borderRadius,
[`&${iconOnlyCls}`]: { [`&${iconOnlyCls}`]: {
@ -424,8 +427,8 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = '') => {
[`&${componentCls}-round`]: { [`&${componentCls}-round`]: {
width: 'auto', width: 'auto',
}, },
[iconCls]: { '> span': {
fontSize: token.buttonIconOnlyFontSize, transform: 'scale(1.143)', // 14px -> 16px
}, },
}, },
@ -438,6 +441,10 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = '') => {
[`${componentCls}-loading-icon`]: { [`${componentCls}-loading-icon`]: {
transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`, transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`,
}, },
[`&:not(${iconOnlyCls}) ${componentCls}-loading-icon > ${iconCls}`]: {
marginInlineEnd: token.marginXS,
},
}, },
}, },
@ -451,24 +458,14 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = '') => {
]; ];
}; };
const genSizeBaseButtonStyle: GenerateStyle<ButtonToken> = token => const genSizeBaseButtonStyle: GenerateStyle<ButtonToken> = token => genSizeButtonStyle(token);
genSizeButtonStyle(
mergeToken<ButtonToken>(token, {
fontSize: token.contentFontSize,
lineHeight: token.contentLineHeight,
}),
);
const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = token => { const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = token => {
const smallToken = mergeToken<ButtonToken>(token, { const smallToken = mergeToken<ButtonToken>(token, {
controlHeight: token.controlHeightSM, controlHeight: token.controlHeightSM,
fontSize: token.contentFontSizeSM,
lineHeight: token.contentLineHeightSM,
padding: token.paddingXS, padding: token.paddingXS,
buttonPaddingHorizontal: token.paddingInlineSM, buttonPaddingHorizontal: 8, // Fixed padding
buttonPaddingVertical: token.paddingBlockSM,
borderRadius: token.borderRadiusSM, borderRadius: token.borderRadiusSM,
buttonIconOnlyFontSize: token.onlyIconSizeSM,
}); });
return genSizeButtonStyle(smallToken, `${token.componentCls}-sm`); return genSizeButtonStyle(smallToken, `${token.componentCls}-sm`);
@ -477,12 +474,8 @@ const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = token => {
const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = token => { const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = token => {
const largeToken = mergeToken<ButtonToken>(token, { const largeToken = mergeToken<ButtonToken>(token, {
controlHeight: token.controlHeightLG, controlHeight: token.controlHeightLG,
fontSize: token.contentFontSizeLG, fontSize: token.fontSizeLG,
lineHeight: token.contentLineHeightLG,
buttonPaddingHorizontal: token.paddingInlineLG,
buttonPaddingVertical: token.paddingBlockLG,
borderRadius: token.borderRadiusLG, borderRadius: token.borderRadiusLG,
buttonIconOnlyFontSize: token.onlyIconSizeLG,
}); });
return genSizeButtonStyle(largeToken, `${token.componentCls}-lg`); return genSizeButtonStyle(largeToken, `${token.componentCls}-lg`);
@ -500,37 +493,33 @@ const genBlockButtonStyle: GenerateStyle<ButtonToken> = token => {
}; };
// ============================== Export ============================== // ============================== Export ==============================
export default genStyleHooks( export default genComponentStyleHook('Button', token => {
'Button', const { controlTmpOutline, paddingContentHorizontal } = token;
token => { const buttonToken = mergeToken<ButtonToken>(token, {
const buttonToken = prepareToken(token); colorOutlineDefault: controlTmpOutline,
buttonPaddingHorizontal: paddingContentHorizontal,
});
return [ return [
// Shared // Shared
genSharedButtonStyle(buttonToken), genSharedButtonStyle(buttonToken),
// Size // Size
genSizeSmallButtonStyle(buttonToken), genSizeSmallButtonStyle(buttonToken),
genSizeBaseButtonStyle(buttonToken), genSizeBaseButtonStyle(buttonToken),
genSizeLargeButtonStyle(buttonToken), genSizeLargeButtonStyle(buttonToken),
// Block // Block
genBlockButtonStyle(buttonToken), genBlockButtonStyle(buttonToken),
// Group (type, ghost, danger, loading) // Group (type, ghost, danger, disabled, loading)
genTypeButtonStyle(buttonToken), genTypeButtonStyle(buttonToken),
// Button Group // Button Group
genGroupStyle(buttonToken), genGroupStyle(buttonToken),
];
}, // Space Compact
prepareComponentToken, genCompactItemStyle(token, { focus: false }),
{ genCompactItemVerticalStyle(token),
unitless: { ];
fontWeight: true, });
contentLineHeight: true,
contentLineHeightSM: true,
contentLineHeightLG: true,
},
},
);

View File

@ -1,234 +0,0 @@
import type { CSSProperties } from 'vue';
import type { FullToken, GetDefaultToken } from '../../theme/internal';
import { getLineHeight, mergeToken } from '../../theme/internal';
import type { GenStyleFn } from '../../theme/util/genComponentStyleHook';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
/**
* @desc
* @descEN Font weight of text
*/
fontWeight: CSSProperties['fontWeight'];
/**
* @desc
* @descEN Shadow of default button
*/
defaultShadow: string;
/**
* @desc
* @descEN Shadow of primary button
*/
primaryShadow: string;
/**
* @desc
* @descEN Shadow of danger button
*/
dangerShadow: string;
/**
* @desc
* @descEN Text color of primary button
*/
primaryColor: string;
/**
* @desc
* @descEN Text color of default button
*/
defaultColor: string;
/**
* @desc
* @descEN Background color of default button
*/
defaultBg: string;
/**
* @desc
* @descEN Border color of default button
*/
defaultBorderColor: string;
/**
* @desc
* @descEN Text color of danger button
*/
dangerColor: string;
/**
* @desc
* @descEN Border color of disabled button
*/
borderColorDisabled: string;
/**
* @desc
* @descEN Text color of default ghost button
*/
defaultGhostColor: string;
/**
* @desc
* @descEN Background color of ghost button
*/
ghostBg: string;
/**
* @desc
* @descEN Border color of default ghost button
*/
defaultGhostBorderColor: string;
/**
* @desc
* @descEN Horizontal padding of button
*/
paddingInline: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of large button
*/
paddingInlineLG: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of small button
*/
paddingInlineSM: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of button
*/
paddingBlock: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of large button
*/
paddingBlockLG: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Horizontal padding of small button
*/
paddingBlockSM: CSSProperties['paddingInline'];
/**
* @desc
* @descEN Icon size of button which only contains icon
*/
onlyIconSize: number;
/**
* @desc
* @descEN Icon size of large button which only contains icon
*/
onlyIconSizeLG: number;
/**
* @desc
* @descEN Icon size of small button which only contains icon
*/
onlyIconSizeSM: number;
/**
* @desc
* @descEN Border color of button group
*/
groupBorderColor: string;
/**
* @desc
* @descEN Background color of link button when hover
*/
linkHoverBg: string;
/**
* @desc
* @descEN Background color of text button when hover
*/
textHoverBg: string;
/**
* @desc
* @descEN Font size of button content
*/
contentFontSize: number;
/**
* @desc
* @descEN Font size of large button content
*/
contentFontSizeLG: number;
/**
* @desc
* @descEN Font size of small button content
*/
contentFontSizeSM: number;
/**
* @desc
* @descEN Line height of button content
*/
contentLineHeight: number;
/**
* @desc
* @descEN Line height of large button content
*/
contentLineHeightLG: number;
/**
* @desc
* @descEN Line height of small button content
*/
contentLineHeightSM: number;
}
export interface ButtonToken extends FullToken<'Button'> {
buttonPaddingHorizontal: CSSProperties['paddingInline'];
buttonPaddingVertical: CSSProperties['paddingBlock'];
buttonIconOnlyFontSize: number;
}
export const prepareToken: (token: Parameters<GenStyleFn<'Button'>>[0]) => ButtonToken = token => {
const { paddingInline, onlyIconSize, paddingBlock } = token;
const buttonToken = mergeToken<ButtonToken>(token, {
buttonPaddingHorizontal: paddingInline,
buttonPaddingVertical: paddingBlock,
buttonIconOnlyFontSize: onlyIconSize,
});
return buttonToken;
};
export const prepareComponentToken: GetDefaultToken<'Button'> = token => {
const contentFontSize = token.contentFontSize ?? token.fontSize;
const contentFontSizeSM = token.contentFontSizeSM ?? token.fontSize;
const contentFontSizeLG = token.contentFontSizeLG ?? token.fontSizeLG;
const contentLineHeight = token.contentLineHeight ?? getLineHeight(contentFontSize);
const contentLineHeightSM = token.contentLineHeightSM ?? getLineHeight(contentFontSizeSM);
const contentLineHeightLG = token.contentLineHeightLG ?? getLineHeight(contentFontSizeLG);
return {
fontWeight: 400,
defaultShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
primaryShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
dangerShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
primaryColor: token.colorTextLightSolid,
dangerColor: token.colorTextLightSolid,
borderColorDisabled: token.colorBorder,
defaultGhostColor: token.colorBgContainer,
ghostBg: 'transparent',
defaultGhostBorderColor: token.colorBgContainer,
paddingInline: token.paddingContentHorizontal - token.lineWidth,
paddingInlineLG: token.paddingContentHorizontal - token.lineWidth,
paddingInlineSM: 8 - token.lineWidth,
onlyIconSize: token.fontSizeLG,
onlyIconSizeSM: token.fontSizeLG - 2,
onlyIconSizeLG: token.fontSizeLG + 2,
groupBorderColor: token.colorPrimaryHover,
linkHoverBg: 'transparent',
textHoverBg: token.colorBgTextHover,
defaultColor: token.colorText,
defaultBg: token.colorBgContainer,
defaultBorderColor: token.colorBorder,
defaultBorderColorDisabled: token.colorBorder,
contentFontSize,
contentFontSizeSM,
contentFontSizeLG,
contentLineHeight,
contentLineHeightSM,
contentLineHeightLG,
paddingBlock: Math.max(
(token.controlHeight - contentFontSize * contentLineHeight) / 2 - token.lineWidth,
0,
),
paddingBlockSM: Math.max(
(token.controlHeightSM - contentFontSizeSM * contentLineHeightSM) / 2 - token.lineWidth,
0,
),
paddingBlockLG: Math.max(
(token.controlHeightLG - contentFontSizeLG * contentLineHeightLG) / 2 - token.lineWidth,
0,
),
};
};

View File

@ -57,18 +57,6 @@ export interface ThemeConfig {
algorithm?: MappingAlgorithm | MappingAlgorithm[]; algorithm?: MappingAlgorithm | MappingAlgorithm[];
hashed?: boolean; hashed?: boolean;
inherit?: boolean; inherit?: boolean;
cssVar?:
| {
/**
* Prefix for css variable, default to `antd`.
*/
prefix?: string;
/**
* Unique key for theme, should be set manually < react@18.
*/
key?: string;
}
| boolean;
} }
export const configProviderProps = () => ({ export const configProviderProps = () => ({

View File

@ -1,16 +0,0 @@
import { useToken } from '../../theme/internal';
import type { Ref } from 'vue';
import { computed } from 'vue';
/**
* This hook is only for cssVar to add root className for components.
* If root ClassName is needed, this hook could be refactored with `-root`
* @param prefixCls
*/
const useCSSVarCls = (prefixCls: Ref<string>) => {
const [, , , , cssVar] = useToken();
return computed(() => (cssVar.value ? `${prefixCls.value}-css-var` : ''));
};
export default useCSSVarCls;

View File

@ -1,32 +0,0 @@
import type { SizeType } from '../SizeContext';
import { useInjectSize } from '../SizeContext';
import type { Ref } from 'vue';
import { computed, shallowRef, watch } from 'vue';
const useSize = <T>(customSize?: T | ((ctxSize: SizeType) => T)): Ref<T> => {
const size = useInjectSize();
const mergedSize = shallowRef(null);
watch(
computed(() => {
return [customSize, size.value];
}),
() => {
if (!customSize) {
mergedSize.value = size.value as T;
}
if (typeof customSize === 'string') {
mergedSize.value = customSize ?? (size.value as T);
}
if (customSize instanceof Function) {
mergedSize.value = customSize(size.value) as T;
}
},
{ immediate: true },
);
return mergedSize;
};
export default useSize;

View File

@ -2,26 +2,13 @@ import type { ThemeConfig } from '../context';
import { defaultConfig } from '../../theme/internal'; import { defaultConfig } from '../../theme/internal';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { computed } from 'vue'; import { computed } from 'vue';
import devWarning from '../../vc-util/warning';
const themeKey = 'antdvtheme';
export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<ThemeConfig>) { export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<ThemeConfig>) {
const themeConfig = computed(() => theme?.value || {}); const themeConfig = computed(() => theme?.value || {});
const parentThemeConfig = computed<ThemeConfig>(() => const parentThemeConfig = computed<ThemeConfig>(() =>
themeConfig.value.inherit === false || !parentTheme?.value ? defaultConfig : parentTheme.value, themeConfig.value.inherit === false || !parentTheme?.value ? defaultConfig : parentTheme.value,
); );
if (process.env.NODE_ENV !== 'production') {
const cssVarEnabled = themeConfig.value.cssVar || parentThemeConfig.value.cssVar;
const validKey = !!(
(typeof themeConfig.value.cssVar === 'object' && themeConfig.value.cssVar?.key) ||
themeKey
);
devWarning(
!cssVarEnabled || validKey,
'[Ant Design Vue ConfigProvider] Missing key in `cssVar` config. Please set `cssVar.key` manually in each ConfigProvider inside `cssVar` enabled ConfigProvider.',
);
}
const mergedTheme = computed(() => { const mergedTheme = computed(() => {
if (!theme?.value) { if (!theme?.value) {
return parentTheme?.value; return parentTheme?.value;
@ -39,17 +26,6 @@ export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<The
} as any; } as any;
}); });
const cssVarKey = `css-var-${themeKey.replace(/:/g, '')}`;
const mergedCssVar = (themeConfig.value.cssVar ?? parentThemeConfig.value.cssVar) && {
prefix: 'ant', // Default to ant
...(typeof parentThemeConfig.value.cssVar === 'object' ? parentThemeConfig.value.cssVar : {}),
...(typeof themeConfig.value.cssVar === 'object' ? themeConfig.value.cssVar : {}),
key:
(typeof themeConfig.value.cssVar === 'object' && themeConfig.value.cssVar?.key) ||
cssVarKey,
};
// Base token // Base token
return { return {
...parentThemeConfig.value, ...parentThemeConfig.value,
@ -60,7 +36,6 @@ export default function useTheme(theme?: Ref<ThemeConfig>, parentTheme?: Ref<The
...themeConfig.value.token, ...themeConfig.value.token,
}, },
components: mergedComponents, components: mergedComponents,
cssVar: mergedCssVar,
}; };
}); });

View File

@ -1,6 +0,0 @@
let uid = 0;
const useThemeKey = () => {
return 'themekey' + uid++;
};
export default useThemeKey;

View File

@ -15,7 +15,7 @@ import type { ValidateMessages } from '../form/interface';
import useStyle from './style'; import useStyle from './style';
import useTheme from './hooks/useTheme'; import useTheme from './hooks/useTheme';
import defaultSeedToken from '../theme/themes/seed'; import defaultSeedToken from '../theme/themes/seed';
import type { ConfigProviderInnerProps, ConfigProviderProps, Theme, ThemeConfig } from './context'; import type { ConfigProviderInnerProps, ConfigProviderProps, Theme } from './context';
import { import {
useConfigContextProvider, useConfigContextProvider,
useConfigContextInject, useConfigContextInject,
@ -26,7 +26,7 @@ import {
import { useProviderSize } from './SizeContext'; import { useProviderSize } from './SizeContext';
import { useProviderDisabled } from './DisabledContext'; import { useProviderDisabled } from './DisabledContext';
import { createTheme } from '../_util/cssinjs'; import { createTheme } from '../_util/cssinjs';
import { defaultTheme, DesignTokenProvider } from '../theme/context'; import { DesignTokenProvider } from '../theme/internal';
export type { export type {
ConfigProviderProps, ConfigProviderProps,
@ -226,47 +226,19 @@ const ConfigProvider = defineComponent({
// ================================ Dynamic theme ================================ // ================================ Dynamic theme ================================
const memoTheme = computed(() => { const memoTheme = computed(() => {
const { algorithm, token, components, cssVar, ...rest } = mergedTheme.value || {}; const { algorithm, token, ...rest } = mergedTheme.value || {};
const themeObj = const themeObj =
algorithm && (!Array.isArray(algorithm) || algorithm.length > 0) algorithm && (!Array.isArray(algorithm) || algorithm.length > 0)
? createTheme(algorithm) ? createTheme(algorithm)
: defaultTheme; : undefined;
const parsedComponents: any = {};
Object.entries(components || {}).forEach(([componentName, componentToken]) => {
const parsedToken: typeof componentToken & { theme?: typeof defaultTheme } = {
...componentToken,
};
if ('algorithm' in parsedToken) {
if (parsedToken.algorithm === true) {
parsedToken.theme = themeObj;
} else if (
Array.isArray(parsedToken.algorithm) ||
typeof parsedToken.algorithm === 'function'
) {
parsedToken.theme = createTheme(parsedToken.algorithm as any);
}
delete parsedToken.algorithm;
}
parsedComponents[componentName] = parsedToken;
});
const mergedToken = {
...defaultSeedToken,
...token,
};
return { return {
...rest, ...rest,
theme: themeObj, theme: themeObj,
token: mergedToken, token: {
components: parsedComponents, ...defaultSeedToken,
override: { ...token,
override: mergedToken,
...parsedComponents,
}, },
cssVar: cssVar as Exclude<ThemeConfig['cssVar'], boolean>,
}; };
}); });
const validateMessagesRef = computed(() => { const validateMessagesRef = computed(() => {

View File

@ -1,4 +1,3 @@
import type { CSSObject } from '../../_util/cssinjs';
import { useStyleRegister } from '../../_util/cssinjs'; import { useStyleRegister } from '../../_util/cssinjs';
import { resetIcon } from '../../style'; import { resetIcon } from '../../style';
import { useToken } from '../../theme/internal'; import { useToken } from '../../theme/internal';
@ -14,17 +13,16 @@ const useStyle = (iconPrefixCls: Ref<string>) => {
hashId: '', hashId: '',
path: ['ant-design-icons', iconPrefixCls.value], path: ['ant-design-icons', iconPrefixCls.value],
})), })),
() => () => [
[ {
{ [`.${iconPrefixCls.value}`]: {
[`.${iconPrefixCls.value}`]: { ...resetIcon(),
...resetIcon(), [`.${iconPrefixCls.value} .${iconPrefixCls.value}-icon`]: {
[`.${iconPrefixCls.value} .${iconPrefixCls.value}-icon`]: { display: 'block',
display: 'block',
},
}, },
}, },
] as CSSObject[], },
],
); );
}; };

View File

@ -22,8 +22,6 @@ import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'
import { resetComponent, roundedArrow, textEllipsis } from '../../style'; import { resetComponent, roundedArrow, textEllipsis } from '../../style';
import { genCompactItemStyle } from '../../style/compact-item'; import { genCompactItemStyle } from '../../style/compact-item';
export interface ComponentToken {}
export interface ComponentToken { export interface ComponentToken {
presetsWidth: number; presetsWidth: number;
presetsMaxWidth: number; presetsMaxWidth: number;

View File

@ -3,8 +3,6 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal'; import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent, textEllipsis } from '../../style'; import { resetComponent, textEllipsis } from '../../style';
export interface ComponentToken {}
interface DescriptionsToken extends FullToken<'Descriptions'> { interface DescriptionsToken extends FullToken<'Descriptions'> {
descriptionsTitleMarginBottom: number; descriptionsTitleMarginBottom: number;
descriptionsExtraColor: string; descriptionsExtraColor: string;

View File

@ -60,7 +60,7 @@ const BackTop = defineComponent({
const handleScroll = throttleByAnimationFrame((e: Event | { target: any }) => { const handleScroll = throttleByAnimationFrame((e: Event | { target: any }) => {
const { visibilityHeight } = props; const { visibilityHeight } = props;
const scrollTop = getScroll(e.target); const scrollTop = getScroll(e.target, true);
state.visible = scrollTop >= visibilityHeight; state.visible = scrollTop >= visibilityHeight;
}); });

View File

@ -5,8 +5,6 @@ import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../style'; import { resetComponent } from '../../style';
import genFormValidateMotionStyle from './explain'; import genFormValidateMotionStyle from './explain';
export interface ComponentToken {}
export interface FormToken extends FullToken<'Form'> { export interface FormToken extends FullToken<'Form'> {
formItemCls: string; formItemCls: string;
rootPrefixCls: string; rootPrefixCls: string;

View File

@ -2,8 +2,6 @@ import type { CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal'; import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal'; import { genComponentStyleHook, mergeToken } from '../../theme/internal';
export interface ComponentToken {}
interface GridRowToken extends FullToken<'Grid'> {} interface GridRowToken extends FullToken<'Grid'> {}
interface GridColToken extends FullToken<'Grid'> { interface GridColToken extends FullToken<'Grid'> {

View File

@ -58,13 +58,13 @@ exports[`renders ./components/image/demo/placeholder.vue correctly 1`] = `
`; `;
exports[`renders ./components/image/demo/preview-group.vue correctly 1`] = ` exports[`renders ./components/image/demo/preview-group.vue correctly 1`] = `
<div class="ant-image" style="width: 200px;"><img width="200" class="ant-image-img" src="https://aliyuncdn.antdv.com/vue.png"> <div class="ant-image" style="width: 200px;"><img width="200" class="ant-image-img" src="https://www.antdv.com/vue.png">
<!----> <!---->
<div class="ant-image-mask"> <div class="ant-image-mask">
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div> <div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
</div> </div>
</div> </div>
<div class="ant-image" style="width: 200px;"><img width="200" class="ant-image-img" src="https://aliyuncdn.antdv.com/logo.png"> <div class="ant-image" style="width: 200px;"><img width="200" class="ant-image-img" src="https://www.antdv.com/logo.png">
<!----> <!---->
<div class="ant-image-mask"> <div class="ant-image-mask">
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div> <div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
@ -106,7 +106,7 @@ exports[`renders ./components/image/demo/preview-group-visible.vue correctly 1`]
`; `;
exports[`renders ./components/image/demo/preview-src.vue correctly 1`] = ` exports[`renders ./components/image/demo/preview-src.vue correctly 1`] = `
<div class="ant-image" style="width: 200px;"><img width="200" class="ant-image-img" src="https://aliyuncdn.antdv.com/logo.png"> <div class="ant-image" style="width: 200px;"><img width="200" class="ant-image-img" src="https://www.antdv.com/logo.png">
<!----> <!---->
<div class="ant-image-mask"> <div class="ant-image-mask">
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div> <div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>

View File

@ -18,7 +18,7 @@ Click the left and right switch buttons to preview multiple images.
<template> <template>
<a-image-preview-group> <a-image-preview-group>
<a-image :width="200" src="https://aliyuncdn.antdv.com/vue.png" /> <a-image :width="200" src="https://www.antdv.com/vue.png" />
<a-image :width="200" src="https://aliyuncdn.antdv.com/logo.png" /> <a-image :width="200" src="https://www.antdv.com/logo.png" />
</a-image-preview-group> </a-image-preview-group>
</template> </template>

View File

@ -19,7 +19,7 @@ You can set different preview image.
<template> <template>
<a-image <a-image
:width="200" :width="200"
src="https://aliyuncdn.antdv.com/logo.png" src="https://www.antdv.com/logo.png"
:preview="{ :preview="{
src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}" }"

View File

@ -2,7 +2,7 @@ import type { App } from 'vue';
import * as components from './components'; import * as components from './components';
import { default as version } from './version'; import { default as version } from './version';
import * as cssinjs from './_util/cssinjs'; import cssinjs from './_util/cssinjs';
export * from './components'; export * from './components';
export * from './_util/cssinjs'; export * from './_util/cssinjs';

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