Feat v4 (#6329)
* refactor(icon): remove style dir (#6215) * refactor: rename locale * refactor: locale-provider * refactor: modal * refactor: menu * fix: custom class (#6217) * refactor: tooltip * refactor: grid (#6220) * refactor: grid * fix(grid): align & justify responsive * chore: update demo and snapshot * fix: row ts type not work * doc: update demo * refactor: ts * refactor: spin (#6222) * fix: typo (#6218) * fix: typo * docs<upload>: docs update * refactor: spin * refactor: spin * refactor: spin * refactor: spinnn * refactor: spin --------- Co-authored-by: lyn <76365499@qq.com> * fix: spin error #6222 * test: test case error (#6225) * fix: inject value maybe undefined * fix: tootip emit correct value * fix: rollback warning suffix avoid test break * doc(grid): remove unused type="flex" * refactor: skeleton (#6224) * refactor: skeleton * refactor: skeleton style * chore: modify skeleton demo style * fix(button): link and text should not have wave (#6226) * refactor: dropdown * refactor: popover & popconfirm * refactor(tag): less to cssinjs (#6227) * refactor(empty): less to cssinjs (#6230) * refactor(empty): less to cssinjs * chore: remove unuse code * fix: reactivity lose * fix: empty props #6230 * refactor: progress style (#6234) * refactor: progress * refactor: progress style * fix: progress attrs * refactor: progress #6234 * refactor: switch (#6236) * refactor: switch style * refactor: delete switch style * refactor:input (#6237) * refactor:input * fix inheritAttrs:false * fix attrs.class * feat: input add disabled * refactor:comment (#6238) * refactor:comment * fix inheritAttrs: false & attrs.class * refactor:pageheader (#6239) * refactor:pageheader * fix inheritAttrs: false & attrs.class * refactor:statistic (#6240) * refactor:statistic * fix inheritAttrs: false & attrs.class * refactor:list (#6241) * refactor:list * fix inheritAttrs: false & attrs.class * feat: update type * refactor(Space): less to cssinjs & add compact mode (#6229) * refactor(Space): less to cssinjs & add compact mode * chore(space): update md * chore(space): add demo * chore(space): add some demo * feat(button): add compact mode * fix: reactivity lose * docs: fix props version --------- Co-authored-by: tangjinzhou <415800467@qq.com> * perf: space compact * refactor:typography (#6244) * refactor:typography * fix return * fix import type * fix: typography #6244 * refactor:datepicker (#6245) * refactor: datepicker type * refactor: rate style (#6254) * refactor(layout): less to cssinjs (#6249) * doc: update layout cover * refactor(result): less to cssinjs (#6246) * refactor(result): less to cssinjs * fix: class name is overridden * docs: update result cover * refactor:slider (#6250) * feat: slider deprecated tooltipVisible * refactor(crad): less to cssinjs (#6258) * update * switch * Style adjustment * refactor(Card): less to cssinjs * Eliminate invalid code * optimization and adjustment css * Adjust the css * Optimize each item * adjustment css * refactor: card #6258 * refactor:carousel (#6262) * refactor:carousel * docs:update & refactor: carousel type --------- Co-authored-by: tangjinzhou <415800467@qq.com> * refactor:transfer (#6247) * refactor:transfer * merge v4 branch & fix theme interface conflict * docs:update & refactor: transfer type * perf: transfer * refactor:checkbox (#6248) * refactor:checkbox * docs:update & refactor: checkbox type * feat: checkbox add disabled context * refactor:pagination (#6251) * refactor:pagination * docs:update & refactor: pagination type * style: update pagination props type * refactor: mentions (#6255) * refactor: mentions * refactor: mentions menu provider * doc: update mentions demo * refcator:upload (#6261) * refcator:upload * docs:update & refactor: upload type * Update style.ts --------- Co-authored-by: tangjinzhou <415800467@qq.com> * perf: upload motion * refactor:timeline (#6263) * refactor:timeline * docs:update & refactor: timeline type * perf: timeline * refactor:steps (#6264) * refactor:steps * fix ...attrs * fix StepsToken error * docs:update & refactor: steps type * fix: steps icon clss error * refactor:collapse (#6266) * refactor:collapse * fix collapse props version * docs:update & refactor: collapse type & fix collapsible * feat: update collapse type * refactor:inputnumber (#6265) * refactor:inputnumber * docs:update & refactor: inputnumber type --------- Co-authored-by: tangjinzhou <415800467@qq.com> * feat: number add compactSize & disabledContext * refactor:table (#6267) * refactor:table * docs:update & refactor: table type --------- Co-authored-by: tangjinzhou <415800467@qq.com> * refactor: table * feat: table add expandColumnTitle slot * refactor:calendar (#6269) * refactor:calendar * docs:update * refactor:timepicker (#6270) * refactor:timepicker * docs:update & refactor: timepicker type * refactor:tree (#6276) * Feat v4 fix type errors (#6285) * fix compile type errors * fix menuprops type import * fix lint errors * fix lint errors * fix format error * fix node version * fix run dist error * fix run lint * fix as any * fix string type * refactor: rename locale file * feat: tree add leafIcon * [tabs] :less to cssinjs (#6288) * update * switch * Style adjustment * refactor(Card): less to cssinjs * tabs: less to cssinjs 开发ing * add function cssinjs * Eliminate irrelevant code * Eliminate irrelevant code 2 * update components * Eliminate irrelevant input code * refactor: tabs #6288 * feat: add segmented (#6286) * refactor: segmented #6286 * refactor:select (#6295) * refactor:select * update doc * delete useless * feat: select add context size * refactor: tree select (#6296) * feat: tree-select add context size * perf: table * docs: update doc toc * refactor: cascader * refactor: auto-complete * refactor: image * refactor: drawer * refactor:radio (#6299) * refactor:radio * fix attrs * feat: radio add disabled context * fix: some type & doc (#6292) * fix: typo (#6218) * fix: typo * docs<upload>: docs update * fix: type of minute in props disabledDateTime of DatePicker (#6233) * docs: typo (#6256) * feat: tooltip added overlayInnerStyle attribute * Update abstractTooltipProps.ts * Update Tooltip.tsx --------- Co-authored-by: lyn <76365499@qq.com> Co-authored-by: H1mple <35363759+baohangxing@users.noreply.github.com> Co-authored-by: tangjinzhou <415800467@qq.com> * refactor: form * fix: directive not work * fix: use open, remove visible * doc: update cover * refactor: remove not use code * chore: update build script * doc: update doc * doc: refactor doc * chore: update token error * chore: update style * refactor: rename _style to style * fix: tag warning * fix(dropdown): open invalid (#6316) * feat: add watermark (#6300) * feat: add watermark * feat: add watermark demo * feat: add mutationObserver * feat: add watermark demo * refactor: watermark type * doc: add theme-editor * fix: inject value maybe undefined && tag style invalid (#6320) * fix: inject value maybe undefined * fix(tag): style invalid * feat: add qrcode (#6315) * feat: add qrcode * fix: qrcode bug * fix: qrcode value required * refactor: props deconstruct * Feat v4 floatbutton (#6294) * feat: add float-button components * fix type & demo display * fix components entry * fix review bug * fix bug * fix .value * refactor: qrcode #6315 * refactor: float-button * fix: groupsize context error * fix: floatbutton animation not work * Feat v4 theme editor (#6348) * feat: add theme editor container * feat: add theme editor layout * add left panel * add vue-colorful & fix bug * 修复hue组件抖动问题 * fix bug && add demo * fix bug * fix demo preview * fix theme editor components demo * fix: token effect error * Feat v4 theme editor (#6349) * feat: add theme editor container * feat: add theme editor layout * add left panel * add vue-colorful & fix bug * 修复hue组件抖动问题 * fix bug && add demo * fix bug * fix demo preview * fix theme editor components demo * add theme editor token drawer * add theme editor token drawer * fix bug * open commment * fix error demo * fix theme editor bug * fix: cssinjs effect error * doc: format code * fix: tag click event not trigger * release 4.0.0-alpha.1 * fix: qrcode type * fix: remove not use file * doc: update doc site * doc: update site * doc: fix theme editor bgcolor (#6358) * fix: motion not work * release 4.0.0-alpha.2 * fix: qrcode ; error, close #6362 * fix docs dark theme & add docs coverDark (#6367) * fix docs dark theme & add docs coverDark * fix theme Editor edit * fix: dropdown divider disappear, close #6365 (#6369) * doc: update baner * fix: button wave not work * fix: ant-piker-cell-range-hover-end style error (#6373) * fix: ant-piker-cell-range-hover-end style error * feat: be consistent with antd * feat: be consistent with antd * fix: ConfigProvider error for style, close #6368 * release 4.0.0-alpha.4 * style: add dark style for `pre` and `code` (#6382) * docs: version menu (#6390) * Feat(DatePicker): increase presets prop (#6387) * feat(date-picker): add PresetDate type * feat(date-picker): add usePresets hook * feat(date-picker): add PresetPanel Component * feat(date-picker): add PresetPanel Component * feat(demo): update Preset Ranges Examples * feat(docs): add new prop presets * feat(docs): add new prop presets with english * fix(RangePicker): footer is not managed by panels * chore(Picker): prefixCls default rc-picker * chore(date-picker): update presetted-ranges demo * chore(date-picker): update rangePickerProps'presets * feat(date-picker): presets reactively processing * chore(date-picker): update type * refactor(RangePicker): deprecated ranges prop * chore(date-picker): update type * chore(PickerPanel): del notuse panelRef --------- Co-authored-by: tangjinzhou <415800467@qq.com> * fix: datepicker presets error #6387 * docs: update datepicker doc #6387 * feat(Steps): add items prop and variants (#6406) * refactor(steps): add items prop and variants * feat(steps): add Label Placement and Inline Steps demo * feat(steps): Label Placement and Inline Steps snap * test(steps): Steps demo snap * feat(Steps): update docs * fix(Step): progressDot * chore(useLegacyItems): change from warning to devWarning * refactor(Steps): Remove useLegacyItems * refactor(Steps): renderStep * test(Steps): update test snapshot * chore(Steps): filterEmpty * feat(Steps): update docs * docs: update site * refactor: steps #6406 * test: update steps * perf: shallowRef instead ref * fix(Modal): fix modal locale (#6423) * feat(StyleProvider): add StyleProvider handle cssinjs features (#6415) * feat(StyleProvider): StyleProvider * feat(StyleProvider): refactor to use context * chore(StyleProvider): update AStyleProviderProps type * chore(App): reback * chore(StyleProvider): export StyleProvider * feat(StyleProvider): update StyleProvider docs * feat(StyleProvider): update StyleProvider docs * feat(StyleProvider): add StyleProvider docs routes * chore(StyleProvider): with useStyleProvider * docs: update compatiple #6415 * feat(Progress): enhance size prop and add variants (#6409) * refactor(progress): Progress size and add variants * feat(progress): add `getsize` * refactor(progress): Progress size and add variants * chore(progress): update props type * chore(progress): update props type * feat(progress): update demo * feat(progress): update docs * test(progress): update test snap * fix(Circle): Merging classes * test(progress): update test snap * feat(progress): add size demo * test(progress): add size snapshot * chore(Progress): reback Circle svg class change * fix: progress borderRadius reactive #6409 * fix(defaultConfigProvider): add getPopupContainer (#6425), close #6419 * fix: qrcode size error, close #6418 * release 4.0.0-alpha.4 * fix: picker import error * test: add QRCode unit testing (#6441) * fix * fix compile type errors * fix menuprops type import * fix lint errors * fix lint errors * fix format error * fix node version * fix run dist error * fix run lint * fix as any * fix string type * fix steps error & fix docs version select option & fix theme editor error * fix(badge): badge props count default value error (#6433) * docs: update site responsive * fix: modal api method i18n not work, close #6438 * release 4.0.0-alpha.5 * chore(docs): update docs (#6446) * docs(space): update demo * docs(affix): update docs * fix: cssinjs compatibility (#6454) * feat: add convertLegacyToken * docs: v4 vuedocs (#6468) * fix introduce doc * fix getting-started doc * add migration-v4 doc * fix docs * Update migration-v4.zh-CN.md * Update migration-v4.zh-CN.md * Update migration-v4.en-US.md * Update migration-v4.zh-CN.md * Update getting-started.en-US.md * Update getting-started.zh-CN.md * Update introduce.en-US.md * Update introduce.zh-CN.md --------- Co-authored-by: tangjinzhou <415800467@qq.com> * feat: remove backtop * feat(anchor): add direction action (#6447) * refactor(anchor): direction show * refactor(anchor): update anchor css * feat(anchor): update demo * test(anchor): update demo test snap * feat(anchor): update docs * Update index.zh-CN.md * Update index.en-US.md --------- Co-authored-by: tangjinzhou <415800467@qq.com> * feat: anchor add customTitle slot #6447 * docs: update doc anchor * feat(menu): icon support function components with items and update demo (#6457) * fix(menu): icon do not show problem * fix(menu): icon do not show problem * feat(menu): update demo * test(menu): update demo snap * chore(Menu): update docs * test(Menu): update demo * Update MenuItem.tsx * Update SubMenu.tsx --------- Co-authored-by: tangjinzhou <415800467@qq.com> * doc: update menu icon * feat: menu items icon add arg * fix: antd.min error * release 4.0.0-alpha.6 * fix: table resizable not work && type error (#6514) * Refactor(demo): change options to composition api (#6499) * feat(demo): A-B * feat(demo): update B-checkbox * feat(demo): update CheckBox -DatePicker * feat(demo): update DatePicker - Form * feat(demo): update Form - List * feat(demo): update List-pagination * feat(demo): update List - skeleton * feat(demo): update skeleton - switch * feat(demo): update skeleton - switch * feat(demo): update switch - upload * feat(demo): update watermark * fix(demo): del hashId * fix: submenu type lose theme * fix: dropdown menu hide error * fix: dealing with switching topics modal, notification, message does not take effect close #6512 (#6518) * fix: resolve dark mode not support * fix: unified expression * feat(modal): add useModal (#6517) * feat(modal): add useModal hook * feat(modal): add HookModal demo * test(modal): update HookModal demo snap * feat(modal): update modal docs * chore(modal): update modal type * perf: useModal #6517 * release 4.0.0-beta.1 * docs: fix tab demo error * fix(config-provider): fix ConfigProvider.config is not function close #6528 (#6529) * Feat(use): add useMessage useNotification (#6527) * feat(Message): add useMessage hook * feat(Notification): add useNotification hook * feat(Message): add Hook demo * feat(Notification): add Hook demo * test(Message): update demo snap * test(Notification): update demo snap * docs(Message): update docs with FAQ * docs(Notification): update docs with FAQ * refactor: useMessage #6527 * refactor: useNotification #6527 * release 4.0.0-beta.2 * docs(button): update demo with space (#6536) * feat(button): demo space * test(button): update demo snap * chore(button): disabled demo Ghost space * test(button): update disabled demo snap * docs(introduce): update docs (#6539) * docs(introduce): update docs * docs(introduce): add Dollar * Update introduce.zh-CN.md * Update introduce.en-US.md --------- Co-authored-by: tangjinzhou <415800467@qq.com> * docs(customize-theme): update docs (#6540) * fix introduce doc * fix getting-started doc * add migration-v4 doc * fix docs * Update migration-v4.zh-CN.md * Update migration-v4.zh-CN.md * Update migration-v4.en-US.md * Update migration-v4.zh-CN.md * Update getting-started.en-US.md * Update getting-started.zh-CN.md * Update introduce.en-US.md * Update introduce.zh-CN.md * update customize-theme doc & fix migration-v4 error * update customize-theme doc * fix migration-v4 error * remove SSR & shadowDom * Update customize-theme.zh-CN.md * Update customize-theme.en-US.md --------- Co-authored-by: tangjinzhou <415800467@qq.com> * fix: getPopupContainer not work * release 4.0.0-beta.3 * release 4.0.0-beta.4 * docs: update grid docs (#6549) Co-authored-by: zhuzhengjian <zhuzhengjian@hoteamsoft.com> * test(alert): update demo with space (#6541) * docs(alert): update demo with space * docs(alert): update alert test snap --------- Co-authored-by: zhuzhengjian <zhuzhengjian@hoteamsoft.com> * fix: components bug & update docs (#6548) * fix bug * fix test case and update snapshot,fix space merge class * docs(grid): update migrate docs && delete xxxl in grid docs (#6562) * fix: segmentd disabled label is undefined (#6556) * fix: segmentd disabled label is undefined * fix: segmentd disabled label is undefined * fix: segmentd disabled label is undefined * fix(grid): remove grid xxxl attribute (#6572) * fix: remove grid xxxl attribute * docs: remove xxxl in grid docs * fix: tooltip custom color error * feat: remove Step __legacy * feat: add tour (#6332) * feat v4 add tour * fix type error * sync tour from antd5.4.6 & fix type error * fix error * refactor: tour #6332 * fix: tour center * fix: picker support v-show * test: update snap * test: update tour test * fix: tour-mask attrs pointer-events (#6577) * fix: tour animated * feat: support vue 3.3 slot type * release 4.0.0-rc.1 * release 4.0.0-rc.2, close #6588 * 4.0.0-rc.3 * chore: remove vue private api * fix: paginantion error, close #6590 * release 4.0.0-rc.4 * fix: checxbox style * fix: pagination mini size style * release 4.0.0-rc.5 * docs: update v4 tabs doc error(#6606) (#6607) * docs: add ant-design-vue nuxt module (#6620) * fix: layout-sider and menu transition style(#6637) (#6640) * docs: fixed the style error of online demo (#6630) * feat: ✨checkbox label slot support use option label (#6642) * docs: 📃change the default setting of "treeNodeFilterProp" from "value" to "label" * revert: ↩revert this config and create another pr to commit * feat: ✨checkbox label slot support use option label * test: 🧪update checkbox *.snap file --------- Co-authored-by: tangjinzhou <415800467@qq.com> * fix: add disabledContext override with form components (#6618) * fix: add disabledContext override with form components * test: update snap * fix: LabelWidth demo filename * fix: fontsize spelling mistake * fix(tour): target position (#6629) * style: format lint * docs(form): add form disabled demo (#6658) * fix: comment node error * release 4.0 * fix: portalWrapper add autoLock prop (#6687), close #6649 * fix: image animation & zindex, close #6675 * docs(QRCode): Synchronize QR code demonstration and add SVG (#6660) * fix: Synchronize QR code demonstration and add SVG * fix: responsive loss and invalid border style * docs: synchronize antd5.6.3 QRCode color in dark mode * feat: calendar select support info.source param (#6697) * docs: add ant-design-vue nuxt module * feat: calendar select support info.source param * docs: synchronous config-provider demo (#6706) * revert: #6706 * docs: export space-compact types (#6716) * release 4.0.0 --------- Co-authored-by: bqy_fe <1743369777@qq.com> Co-authored-by: zkwolf <chenhao5866@gmail.com> Co-authored-by: Zev Zhu <45655660+aibayanyu20@users.noreply.github.com> Co-authored-by: lyn <76365499@qq.com> Co-authored-by: 果冻橙 <shifeng199307@gmail.com> Co-authored-by: songsong0707 <74165917+songsong0707@users.noreply.github.com> Co-authored-by: yang <30883395+webvs2@users.noreply.github.com> Co-authored-by: selicens <1244620067@qq.com> Co-authored-by: 一堆菠萝 <53335668+JavanShen@users.noreply.github.com> Co-authored-by: H1mple <35363759+baohangxing@users.noreply.github.com> Co-authored-by: Cherry7 <79909910+CCherry07@users.noreply.github.com> Co-authored-by: Konv Suu <2583695112@qq.com> Co-authored-by: luoawai <32483950+luoawai@users.noreply.github.com> Co-authored-by: 鱼见 <657715602@qq.com> Co-authored-by: zhuzhengjian <zhuzhengjian@hoteamsoft.com> Co-authored-by: Cupid Valentine <53572196+valcosmos@users.noreply.github.com> Co-authored-by: 专业逮虾户aa <30494925+waldonUB@users.noreply.github.com> Co-authored-by: PanStar <PanStar@users.noreply.github.com>pull/6719/head 4.0.0
parent
a0e94978f5
commit
a2e50dc43e
@ -1,195 +1,36 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const defaultVars = require('./scripts/default-vars');
|
||||
const darkVars = require('./scripts/dark-vars');
|
||||
const compactVars = require('./scripts/compact-vars');
|
||||
|
||||
function generateThemeFileContent(theme) {
|
||||
return `const { ${theme}ThemeSingle } = require('./theme');\nconst defaultTheme = require('./default-theme');\n
|
||||
module.exports = {
|
||||
...defaultTheme,
|
||||
...${theme}ThemeSingle
|
||||
}`;
|
||||
}
|
||||
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');
|
||||
|
||||
// We need compile additional content for antd user
|
||||
function finalizeCompile() {
|
||||
if (fs.existsSync(path.join(__dirname, './lib'))) {
|
||||
// Build a entry less file to dist/antd.less
|
||||
const componentsPath = path.join(process.cwd(), 'components');
|
||||
let componentsLessContent = '';
|
||||
// Build components in one file: lib/style/components.less
|
||||
fs.readdir(componentsPath, (err, files) => {
|
||||
files.forEach(file => {
|
||||
if (fs.existsSync(path.join(componentsPath, file, 'style', 'index.less'))) {
|
||||
componentsLessContent += `@import "../${path.posix.join(
|
||||
file,
|
||||
'style',
|
||||
'index-pure.less',
|
||||
)}";\n`;
|
||||
}
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'lib', 'style', 'components.less'),
|
||||
componentsLessContent,
|
||||
);
|
||||
});
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
function buildThemeFile(theme, vars) {
|
||||
// Build less entry file: dist/antd.${theme}.less
|
||||
if (theme !== 'default') {
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `antd.${theme}.less`),
|
||||
`@import "../lib/style/${theme}.less";\n@import "../lib/style/components.less";`,
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Built a entry less file to dist/antd.${theme}.less`);
|
||||
} else {
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `default-theme.js`),
|
||||
`module.exports = ${JSON.stringify(vars, null, 2)};\n`,
|
||||
);
|
||||
return;
|
||||
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'));
|
||||
}
|
||||
|
||||
// Build ${theme}.js: dist/${theme}-theme.js, for less-loader
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `theme.js`),
|
||||
`const ${theme}ThemeSingle = ${JSON.stringify(vars, null, 2)};\n`,
|
||||
{
|
||||
flag: 'a',
|
||||
},
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `${theme}-theme.js`),
|
||||
generateThemeFileContent(theme),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Built a ${theme} theme js file to dist/${theme}-theme.js`);
|
||||
}
|
||||
|
||||
function finalizeDist() {
|
||||
if (fs.existsSync(path.join(__dirname, './dist'))) {
|
||||
// Build less entry file: dist/antd.less
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', 'antd.less'),
|
||||
'@import "../lib/style/default.less";\n@import "../lib/style/components.less";',
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', 'theme.js'),
|
||||
`const defaultTheme = require('./default-theme.js');\n`,
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Built a entry less file to dist/antd.less');
|
||||
buildThemeFile('default', defaultVars);
|
||||
buildThemeFile('dark', darkVars);
|
||||
buildThemeFile('compact', compactVars);
|
||||
buildThemeFile('variable', {});
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `theme.js`),
|
||||
`
|
||||
function getThemeVariables(options = {}) {
|
||||
let themeVar = {
|
||||
'hack': \`true;@import "\${require.resolve('ant-design-vue/lib/style/color/colorPalette.less')}";\`,
|
||||
...defaultTheme
|
||||
};
|
||||
if(options.dark) {
|
||||
themeVar = {
|
||||
...themeVar,
|
||||
...darkThemeSingle
|
||||
}
|
||||
}
|
||||
if(options.compact){
|
||||
themeVar = {
|
||||
...themeVar,
|
||||
...compactThemeSingle
|
||||
}
|
||||
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'dist', 'reset.css'));
|
||||
}
|
||||
return themeVar;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
darkThemeSingle,
|
||||
compactThemeSingle,
|
||||
getThemeVariables
|
||||
}`,
|
||||
{
|
||||
flag: 'a',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function isComponentStyleEntry(file) {
|
||||
return file.path.match(/style(\/|\\)index\.tsx/);
|
||||
}
|
||||
|
||||
function needTransformStyle(content) {
|
||||
return content.includes('../../style/index.less') || content.includes('./index.less');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
compile: {
|
||||
includeLessFile: [/(\/|\\)components(\/|\\)style(\/|\\)default.less$/],
|
||||
transformTSFile(file) {
|
||||
if (isComponentStyleEntry(file)) {
|
||||
let content = file.contents.toString();
|
||||
|
||||
if (needTransformStyle(content)) {
|
||||
const cloneFile = file.clone();
|
||||
|
||||
// Origin
|
||||
content = content.replace('../../style/index.less', '../../style/default.less');
|
||||
cloneFile.contents = Buffer.from(content);
|
||||
|
||||
return cloneFile;
|
||||
}
|
||||
}
|
||||
},
|
||||
transformFile(file) {
|
||||
if (isComponentStyleEntry(file)) {
|
||||
const indexLessFilePath = file.path.replace('index.tsx', 'index.less');
|
||||
|
||||
if (fs.existsSync(indexLessFilePath)) {
|
||||
// We put origin `index.less` file to `index-pure.less`
|
||||
const pureFile = file.clone();
|
||||
pureFile.contents = Buffer.from(fs.readFileSync(indexLessFilePath, 'utf8'));
|
||||
pureFile.path = pureFile.path.replace('index.tsx', 'index-pure.less');
|
||||
|
||||
// Rewrite `index.less` file with `root-entry-name`
|
||||
const indexLessFile = file.clone();
|
||||
indexLessFile.contents = Buffer.from(
|
||||
[
|
||||
// Inject variable
|
||||
'@root-entry-name: default;',
|
||||
// Point to origin file
|
||||
"@import './index-pure.less';",
|
||||
].join('\n\n'),
|
||||
);
|
||||
indexLessFile.path = indexLessFile.path.replace('index.tsx', 'index.less');
|
||||
|
||||
return [indexLessFile, pureFile];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
lessConfig: {
|
||||
modifyVars: {
|
||||
'root-entry-name': 'default',
|
||||
},
|
||||
},
|
||||
finalize: finalizeCompile,
|
||||
},
|
||||
dist: {
|
||||
finalize: finalizeDist,
|
||||
},
|
||||
generateThemeFileContent,
|
||||
bail: true,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,27 +0,0 @@
|
||||
const less = require('less');
|
||||
const path = require('path');
|
||||
const postcss = require('postcss');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const NpmImportPlugin = require('less-plugin-npm-import');
|
||||
const { getConfig } = require('./utils/projectHelper');
|
||||
|
||||
function transformLess(lessContent, lessFilePath, config = {}) {
|
||||
const { cwd = process.cwd() } = config;
|
||||
const { compile: { lessConfig } = {} } = getConfig();
|
||||
const resolvedLessFile = path.resolve(cwd, lessFilePath);
|
||||
|
||||
// Do less compile
|
||||
const lessOpts = {
|
||||
paths: [path.dirname(resolvedLessFile)],
|
||||
filename: resolvedLessFile,
|
||||
plugins: [new NpmImportPlugin({ prefix: '~' })],
|
||||
javascriptEnabled: true,
|
||||
...lessConfig,
|
||||
};
|
||||
return less
|
||||
.render(lessContent, lessOpts)
|
||||
.then(result => postcss([autoprefixer]).process(result.css, { from: undefined }))
|
||||
.then(r => r.css);
|
||||
}
|
||||
|
||||
module.exports = transformLess;
|
@ -1,11 +0,0 @@
|
||||
// We convert less import in es/lib to css file path
|
||||
function cssInjection(content) {
|
||||
return content
|
||||
.replace(/\/style\/?'/g, "/style/css'")
|
||||
.replace(/\/style\/?"/g, '/style/css"')
|
||||
.replace(/\.less/g, '.css');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cssInjection,
|
||||
};
|
@ -1,23 +1,34 @@
|
||||
import type { ElementOf } from './type';
|
||||
import { tuple } from './type';
|
||||
import type { PresetColorKey } from '../theme/interface';
|
||||
import { PresetColors } from '../theme/interface';
|
||||
|
||||
export const PresetStatusColorTypes = tuple('success', 'processing', 'error', 'default', 'warning');
|
||||
type InverseColor = `${PresetColorKey}-inverse`;
|
||||
const inverseColors = PresetColors.map<InverseColor>(color => `${color}-inverse`);
|
||||
|
||||
export const PresetColorTypes = tuple(
|
||||
'pink',
|
||||
'red',
|
||||
'yellow',
|
||||
'orange',
|
||||
'cyan',
|
||||
'green',
|
||||
'blue',
|
||||
'purple',
|
||||
'geekblue',
|
||||
'magenta',
|
||||
'volcano',
|
||||
'gold',
|
||||
'lime',
|
||||
);
|
||||
export const PresetStatusColorTypes = [
|
||||
'success',
|
||||
'processing',
|
||||
'error',
|
||||
'default',
|
||||
'warning',
|
||||
] as const;
|
||||
|
||||
export type PresetColorType = ElementOf<typeof PresetColorTypes>;
|
||||
export type PresetStatusColorType = ElementOf<typeof PresetStatusColorTypes>;
|
||||
export type PresetColorType = PresetColorKey | InverseColor;
|
||||
|
||||
export type PresetStatusColorType = (typeof PresetStatusColorTypes)[number];
|
||||
|
||||
/**
|
||||
* determine if the color keyword belongs to the `Ant Design` {@link PresetColors}.
|
||||
* @param color color to be judged
|
||||
* @param includeInverse whether to include reversed colors
|
||||
*/
|
||||
export function isPresetColor(color?: any, includeInverse = true) {
|
||||
if (includeInverse) {
|
||||
return [...inverseColors, ...PresetColors].includes(color);
|
||||
}
|
||||
|
||||
return PresetColors.includes(color);
|
||||
}
|
||||
|
||||
export function isPresetStatusColor(color?: any): color is PresetStatusColorType {
|
||||
return PresetStatusColorTypes.includes(color);
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { inject, provide, reactive, watchEffect } from 'vue';
|
||||
|
||||
function createContext<T extends Record<string, any>>(defaultValue?: T) {
|
||||
const contextKey = Symbol('contextKey');
|
||||
const useProvide = (props: T, newProps?: T) => {
|
||||
const mergedProps = reactive<T>({} as T);
|
||||
provide(contextKey, mergedProps);
|
||||
watchEffect(() => {
|
||||
Object.assign(mergedProps, props, newProps || {});
|
||||
});
|
||||
return mergedProps;
|
||||
};
|
||||
const useInject = () => {
|
||||
return inject(contextKey, defaultValue as T) || ({} as T);
|
||||
};
|
||||
return {
|
||||
useProvide,
|
||||
useInject,
|
||||
};
|
||||
}
|
||||
|
||||
export default createContext;
|
@ -0,0 +1,25 @@
|
||||
export type KeyType = string | number;
|
||||
type ValueType = [number, any]; // [times, realValue]
|
||||
|
||||
class Entity {
|
||||
/** @private Internal cache map. Do not access this directly */
|
||||
cache = new Map<string, ValueType>();
|
||||
|
||||
get(keys: KeyType[] | string): ValueType | null {
|
||||
return this.cache.get(Array.isArray(keys) ? keys.join('%') : keys) || null;
|
||||
}
|
||||
|
||||
update(keys: KeyType[] | string, valueFn: (origin: ValueType | null) => ValueType | null) {
|
||||
const path = Array.isArray(keys) ? keys.join('%') : keys;
|
||||
const prevValue = this.cache.get(path)!;
|
||||
const nextValue = valueFn(prevValue);
|
||||
|
||||
if (nextValue === null) {
|
||||
this.cache.delete(path);
|
||||
} else {
|
||||
this.cache.set(path, nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Entity;
|
@ -0,0 +1,19 @@
|
||||
import type { CSSInterpolation } from './hooks/useStyleRegister';
|
||||
|
||||
class Keyframe {
|
||||
private name: string;
|
||||
style: CSSInterpolation;
|
||||
|
||||
constructor(name: string, style: CSSInterpolation) {
|
||||
this.name = name;
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
getName(hashId = ''): string {
|
||||
return hashId ? `${hashId}-${this.name}` : this.name;
|
||||
}
|
||||
|
||||
_keyframe = true;
|
||||
}
|
||||
|
||||
export default Keyframe;
|
@ -0,0 +1,157 @@
|
||||
import type { ShallowRef, ExtractPropTypes, InjectionKey, Ref } from 'vue';
|
||||
import { provide, defineComponent, unref, inject, watch, shallowRef } from 'vue';
|
||||
import CacheEntity from './Cache';
|
||||
import type { Linter } from './linters/interface';
|
||||
import type { Transformer } from './transformers/interface';
|
||||
import { arrayType, booleanType, objectType, someType, stringType, withInstall } from '../type';
|
||||
import initDefaultProps from '../props-util/initDefaultProps';
|
||||
export const ATTR_TOKEN = 'data-token-hash';
|
||||
export const ATTR_MARK = 'data-css-hash';
|
||||
export const ATTR_DEV_CACHE_PATH = 'data-dev-cache-path';
|
||||
|
||||
// Mark css-in-js instance in style element
|
||||
export const CSS_IN_JS_INSTANCE = '__cssinjs_instance__';
|
||||
export const CSS_IN_JS_INSTANCE_ID = Math.random().toString(12).slice(2);
|
||||
|
||||
export function createCache() {
|
||||
if (typeof document !== 'undefined' && document.head && document.body) {
|
||||
const styles = document.body.querySelectorAll(`style[${ATTR_MARK}]`) || [];
|
||||
const { firstChild } = document.head;
|
||||
|
||||
Array.from(styles).forEach(style => {
|
||||
(style as any)[CSS_IN_JS_INSTANCE] =
|
||||
(style as any)[CSS_IN_JS_INSTANCE] || CSS_IN_JS_INSTANCE_ID;
|
||||
|
||||
// Not force move if no head
|
||||
document.head.insertBefore(style, firstChild);
|
||||
});
|
||||
|
||||
// Deduplicate of moved styles
|
||||
const styleHash: Record<string, boolean> = {};
|
||||
Array.from(document.querySelectorAll(`style[${ATTR_MARK}]`)).forEach(style => {
|
||||
const hash = style.getAttribute(ATTR_MARK)!;
|
||||
if (styleHash[hash]) {
|
||||
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
|
||||
style.parentNode?.removeChild(style);
|
||||
}
|
||||
} else {
|
||||
styleHash[hash] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new CacheEntity();
|
||||
}
|
||||
|
||||
export type HashPriority = 'low' | 'high';
|
||||
|
||||
export interface StyleContextProps {
|
||||
autoClear?: boolean;
|
||||
/** @private Test only. Not work in production. */
|
||||
mock?: 'server' | 'client';
|
||||
/**
|
||||
* Only set when you need ssr to extract style on you own.
|
||||
* If not provided, it will auto create <style /> on the end of Provider in server side.
|
||||
*/
|
||||
cache: CacheEntity;
|
||||
/** Tell children that this context is default generated context */
|
||||
defaultCache: boolean;
|
||||
/** Use `:where` selector to reduce hashId css selector priority */
|
||||
hashPriority?: HashPriority;
|
||||
/** Tell cssinjs where to inject style in */
|
||||
container?: Element | ShadowRoot;
|
||||
/** Component wil render inline `<style />` for fallback in SSR. Not recommend. */
|
||||
ssrInline?: boolean;
|
||||
/** Transform css before inject in document. Please note that `transformers` do not support dynamic update */
|
||||
transformers?: Transformer[];
|
||||
/**
|
||||
* Linters to lint css before inject in document.
|
||||
* Styles will be linted after transforming.
|
||||
* Please note that `linters` do not support dynamic update.
|
||||
*/
|
||||
linters?: Linter[];
|
||||
}
|
||||
|
||||
const StyleContextKey: InjectionKey<ShallowRef<Partial<StyleContextProps>>> =
|
||||
Symbol('StyleContextKey');
|
||||
|
||||
export type UseStyleProviderProps = Partial<StyleContextProps> | Ref<Partial<StyleContextProps>>;
|
||||
const defaultStyleContext: StyleContextProps = {
|
||||
cache: createCache(),
|
||||
defaultCache: true,
|
||||
hashPriority: 'low',
|
||||
};
|
||||
export const useStyleInject = () => {
|
||||
return inject(StyleContextKey, shallowRef({ ...defaultStyleContext }));
|
||||
};
|
||||
export const useStyleProvider = (props: UseStyleProviderProps) => {
|
||||
const parentContext = useStyleInject();
|
||||
const context = shallowRef<Partial<StyleContextProps>>({ ...defaultStyleContext });
|
||||
watch(
|
||||
[props, parentContext],
|
||||
() => {
|
||||
const mergedContext: Partial<StyleContextProps> = {
|
||||
...parentContext.value,
|
||||
};
|
||||
const propsValue = unref(props);
|
||||
Object.keys(propsValue).forEach(key => {
|
||||
const value = propsValue[key];
|
||||
if (propsValue[key] !== undefined) {
|
||||
mergedContext[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
const { cache } = propsValue;
|
||||
mergedContext.cache = mergedContext.cache || createCache();
|
||||
mergedContext.defaultCache = !cache && parentContext.value.defaultCache;
|
||||
context.value = mergedContext;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
provide(StyleContextKey, context);
|
||||
return context;
|
||||
};
|
||||
export const styleProviderProps = () => ({
|
||||
autoClear: booleanType(),
|
||||
/** @private Test only. Not work in production. */
|
||||
mock: stringType<'server' | 'client'>(),
|
||||
/**
|
||||
* Only set when you need ssr to extract style on you own.
|
||||
* If not provided, it will auto create <style /> on the end of Provider in server side.
|
||||
*/
|
||||
cache: objectType<CacheEntity>(),
|
||||
/** Tell children that this context is default generated context */
|
||||
defaultCache: booleanType(),
|
||||
/** Use `:where` selector to reduce hashId css selector priority */
|
||||
hashPriority: stringType<HashPriority>(),
|
||||
/** Tell cssinjs where to inject style in */
|
||||
container: someType<Element | ShadowRoot>(),
|
||||
/** Component wil render inline `<style />` for fallback in SSR. Not recommend. */
|
||||
ssrInline: booleanType(),
|
||||
/** Transform css before inject in document. Please note that `transformers` do not support dynamic update */
|
||||
transformers: arrayType<Transformer[]>(),
|
||||
/**
|
||||
* Linters to lint css before inject in document.
|
||||
* Styles will be linted after transforming.
|
||||
* Please note that `linters` do not support dynamic update.
|
||||
*/
|
||||
linters: arrayType<Linter[]>(),
|
||||
});
|
||||
export type StyleProviderProps = Partial<ExtractPropTypes<ReturnType<typeof styleProviderProps>>>;
|
||||
export const StyleProvider = withInstall(
|
||||
defineComponent({
|
||||
name: 'AStyleProvider',
|
||||
inheritAttrs: false,
|
||||
props: initDefaultProps(styleProviderProps(), defaultStyleContext),
|
||||
setup(props, { slots }) {
|
||||
useStyleProvider(props);
|
||||
return () => slots.default?.();
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export default {
|
||||
useStyleInject,
|
||||
useStyleProvider,
|
||||
StyleProvider,
|
||||
};
|
@ -0,0 +1,128 @@
|
||||
import hash from '@emotion/hash';
|
||||
import { ATTR_TOKEN, CSS_IN_JS_INSTANCE, CSS_IN_JS_INSTANCE_ID } from '../StyleContext';
|
||||
import type Theme from '../theme/Theme';
|
||||
import useGlobalCache from './useGlobalCache';
|
||||
import { flattenToken, token2key } from '../util';
|
||||
import type { Ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const EMPTY_OVERRIDE = {};
|
||||
|
||||
// Generate different prefix to make user selector break in production env.
|
||||
// This helps developer not to do style override directly on the hash id.
|
||||
const hashPrefix = process.env.NODE_ENV !== 'production' ? 'css-dev-only-do-not-override' : 'css';
|
||||
|
||||
export interface Option<DerivativeToken> {
|
||||
/**
|
||||
* Generate token with salt.
|
||||
* This is used to generate different hashId even same derivative token for different version.
|
||||
*/
|
||||
salt?: string;
|
||||
override?: object;
|
||||
/**
|
||||
* Format token as you need. Such as:
|
||||
*
|
||||
* - rename token
|
||||
* - merge token
|
||||
* - delete token
|
||||
*
|
||||
* This should always be the same since it's one time process.
|
||||
* It's ok to useMemo outside but this has better cache strategy.
|
||||
*/
|
||||
formatToken?: (mergedToken: any) => DerivativeToken;
|
||||
}
|
||||
|
||||
const tokenKeys = new Map<string, number>();
|
||||
function recordCleanToken(tokenKey: string) {
|
||||
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) + 1);
|
||||
}
|
||||
|
||||
function removeStyleTags(key: string) {
|
||||
if (typeof document !== 'undefined') {
|
||||
const styles = document.querySelectorAll(`style[${ATTR_TOKEN}="${key}"]`);
|
||||
|
||||
styles.forEach(style => {
|
||||
if ((style as any)[CSS_IN_JS_INSTANCE] === CSS_IN_JS_INSTANCE_ID) {
|
||||
style.parentNode?.removeChild(style);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove will check current keys first
|
||||
function cleanTokenStyle(tokenKey: string) {
|
||||
tokenKeys.set(tokenKey, (tokenKeys.get(tokenKey) || 0) - 1);
|
||||
|
||||
const tokenKeyList = Array.from(tokenKeys.keys());
|
||||
const cleanableKeyList = tokenKeyList.filter(key => {
|
||||
const count = tokenKeys.get(key) || 0;
|
||||
|
||||
return count <= 0;
|
||||
});
|
||||
|
||||
if (cleanableKeyList.length < tokenKeyList.length) {
|
||||
cleanableKeyList.forEach(key => {
|
||||
removeStyleTags(key);
|
||||
tokenKeys.delete(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache theme derivative token as global shared one
|
||||
* @param theme Theme entity
|
||||
* @param tokens List of tokens, used for cache. Please do not dynamic generate object directly
|
||||
* @param option Additional config
|
||||
* @returns Call Theme.getDerivativeToken(tokenObject) to get token
|
||||
*/
|
||||
export default function useCacheToken<DerivativeToken = object, DesignToken = DerivativeToken>(
|
||||
theme: Ref<Theme<any, any>>,
|
||||
tokens: Ref<Partial<DesignToken>[]>,
|
||||
option: Ref<Option<DerivativeToken>> = ref({}),
|
||||
) {
|
||||
// Basic - We do basic cache here
|
||||
const mergedToken = computed(() => Object.assign({}, ...tokens.value));
|
||||
const tokenStr = computed(() => flattenToken(mergedToken.value));
|
||||
const overrideTokenStr = computed(() => flattenToken(option.value.override || EMPTY_OVERRIDE));
|
||||
|
||||
const cachedToken = useGlobalCache<[DerivativeToken & { _tokenKey: string }, string]>(
|
||||
'token',
|
||||
computed(() => [
|
||||
option.value.salt || '',
|
||||
theme.value.id,
|
||||
tokenStr.value,
|
||||
overrideTokenStr.value,
|
||||
]),
|
||||
() => {
|
||||
const { salt = '', override = EMPTY_OVERRIDE, formatToken } = option.value;
|
||||
const derivativeToken = theme.value.getDerivativeToken(mergedToken.value);
|
||||
|
||||
// Merge with override
|
||||
let mergedDerivativeToken = {
|
||||
...derivativeToken,
|
||||
...override,
|
||||
};
|
||||
|
||||
// Format if needed
|
||||
if (formatToken) {
|
||||
mergedDerivativeToken = formatToken(mergedDerivativeToken);
|
||||
}
|
||||
|
||||
// Optimize for `useStyleRegister` performance
|
||||
const tokenKey = token2key(mergedDerivativeToken, salt);
|
||||
mergedDerivativeToken._tokenKey = tokenKey;
|
||||
recordCleanToken(tokenKey);
|
||||
|
||||
const hashId = `${hashPrefix}-${hash(tokenKey)}`;
|
||||
mergedDerivativeToken._hashId = hashId; // Not used
|
||||
|
||||
return [mergedDerivativeToken, hashId];
|
||||
},
|
||||
cache => {
|
||||
// Remove token will remove all related style
|
||||
cleanTokenStyle(cache[0]._tokenKey);
|
||||
},
|
||||
);
|
||||
|
||||
return cachedToken;
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import { useStyleInject } from '../StyleContext';
|
||||
import type { KeyType } from '../Cache';
|
||||
import useHMR from './useHMR';
|
||||
import type { ShallowRef, Ref } from 'vue';
|
||||
import { onBeforeUnmount, watch, watchEffect, shallowRef } from 'vue';
|
||||
export default function useClientCache<CacheType>(
|
||||
prefix: string,
|
||||
keyPath: Ref<KeyType[]>,
|
||||
cacheFn: () => CacheType,
|
||||
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
|
||||
): ShallowRef<CacheType> {
|
||||
const styleContext = useStyleInject();
|
||||
const fullPathStr = shallowRef('');
|
||||
const res = shallowRef<CacheType>();
|
||||
watchEffect(() => {
|
||||
fullPathStr.value = [prefix, ...keyPath.value].join('%');
|
||||
});
|
||||
const HMRUpdate = useHMR();
|
||||
const clearCache = (pathStr: string) => {
|
||||
styleContext.value.cache.update(pathStr, prevCache => {
|
||||
const [times = 0, cache] = prevCache || [];
|
||||
const nextCount = times - 1;
|
||||
if (nextCount === 0) {
|
||||
onCacheRemove?.(cache, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
return [times - 1, cache];
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
fullPathStr,
|
||||
(newStr, oldStr) => {
|
||||
if (oldStr) clearCache(oldStr);
|
||||
// Create cache
|
||||
styleContext.value.cache.update(newStr, prevCache => {
|
||||
const [times = 0, cache] = prevCache || [];
|
||||
|
||||
// HMR should always ignore cache since developer may change it
|
||||
let tmpCache = cache;
|
||||
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
|
||||
onCacheRemove?.(tmpCache, HMRUpdate);
|
||||
tmpCache = null;
|
||||
}
|
||||
const mergedCache = tmpCache || cacheFn();
|
||||
|
||||
return [times + 1, mergedCache];
|
||||
});
|
||||
res.value = styleContext.value.cache.get(fullPathStr.value)![1];
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
onBeforeUnmount(() => {
|
||||
clearCache(fullPathStr.value);
|
||||
});
|
||||
return res;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
function useProdHMR() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let webpackHMR = false;
|
||||
|
||||
function useDevHMR() {
|
||||
return webpackHMR;
|
||||
}
|
||||
|
||||
export default process.env.NODE_ENV === 'production' ? useProdHMR : useDevHMR;
|
||||
|
||||
// Webpack `module.hot.accept` do not support any deps update trigger
|
||||
// We have to hack handler to force mark as HRM
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
typeof module !== 'undefined' &&
|
||||
module &&
|
||||
(module as any).hot
|
||||
) {
|
||||
const win = window as any;
|
||||
if (typeof win.webpackHotUpdate === 'function') {
|
||||
const originWebpackHotUpdate = win.webpackHotUpdate;
|
||||
|
||||
win.webpackHotUpdate = (...args: any[]) => {
|
||||
webpackHMR = true;
|
||||
setTimeout(() => {
|
||||
webpackHMR = false;
|
||||
}, 0);
|
||||
return originWebpackHotUpdate(...args);
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
import useCacheToken from './hooks/useCacheToken';
|
||||
import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister';
|
||||
import useStyleRegister, { extractStyle } from './hooks/useStyleRegister';
|
||||
import Keyframes from './Keyframes';
|
||||
import type { Linter } from './linters';
|
||||
import { legacyNotSelectorLinter, logicalPropertiesLinter } from './linters';
|
||||
import type { StyleContextProps, StyleProviderProps } from './StyleContext';
|
||||
import { createCache, useStyleInject, useStyleProvider, StyleProvider } from './StyleContext';
|
||||
import type { DerivativeFunc, TokenType } from './theme';
|
||||
import { createTheme, Theme } from './theme';
|
||||
import type { Transformer } from './transformers/interface';
|
||||
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
|
||||
|
||||
const cssinjs = {
|
||||
Theme,
|
||||
createTheme,
|
||||
useStyleRegister,
|
||||
useCacheToken,
|
||||
createCache,
|
||||
useStyleInject,
|
||||
useStyleProvider,
|
||||
Keyframes,
|
||||
extractStyle,
|
||||
|
||||
// Transformer
|
||||
legacyLogicalPropertiesTransformer,
|
||||
|
||||
// Linters
|
||||
logicalPropertiesLinter,
|
||||
legacyNotSelectorLinter,
|
||||
|
||||
// cssinjs
|
||||
StyleProvider,
|
||||
};
|
||||
export {
|
||||
Theme,
|
||||
createTheme,
|
||||
useStyleRegister,
|
||||
useCacheToken,
|
||||
createCache,
|
||||
useStyleInject,
|
||||
useStyleProvider,
|
||||
Keyframes,
|
||||
extractStyle,
|
||||
|
||||
// Transformer
|
||||
legacyLogicalPropertiesTransformer,
|
||||
|
||||
// Linters
|
||||
logicalPropertiesLinter,
|
||||
legacyNotSelectorLinter,
|
||||
|
||||
// cssinjs
|
||||
StyleProvider,
|
||||
};
|
||||
export type {
|
||||
TokenType,
|
||||
CSSObject,
|
||||
CSSInterpolation,
|
||||
DerivativeFunc,
|
||||
Transformer,
|
||||
Linter,
|
||||
StyleContextProps,
|
||||
StyleProviderProps,
|
||||
};
|
||||
|
||||
export default cssinjs;
|
@ -0,0 +1,25 @@
|
||||
import type { Linter } from './interface';
|
||||
import { lintWarning } from './utils';
|
||||
|
||||
const linter: Linter = (key, value, info) => {
|
||||
if (key === 'content') {
|
||||
// From emotion: https://github.com/emotion-js/emotion/blob/main/packages/serialize/src/index.js#L63
|
||||
const contentValuePattern =
|
||||
/(attr|counters?|url|(((repeating-)?(linear|radial))|conic)-gradient)\(|(no-)?(open|close)-quote/;
|
||||
const contentValues = ['normal', 'none', 'initial', 'inherit', 'unset'];
|
||||
if (
|
||||
typeof value !== 'string' ||
|
||||
(contentValues.indexOf(value) === -1 &&
|
||||
!contentValuePattern.test(value) &&
|
||||
(value.charAt(0) !== value.charAt(value.length - 1) ||
|
||||
(value.charAt(0) !== '"' && value.charAt(0) !== "'")))
|
||||
) {
|
||||
lintWarning(
|
||||
`You seem to be using a value for 'content' without quotes, try replacing it with \`content: '"${value}"'\`.`,
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default linter;
|
@ -0,0 +1,15 @@
|
||||
import type { Linter } from './interface';
|
||||
import { lintWarning } from './utils';
|
||||
|
||||
const linter: Linter = (key, value, info) => {
|
||||
if (key === 'animation') {
|
||||
if (info.hashId && value !== 'none') {
|
||||
lintWarning(
|
||||
`You seem to be using hashed animation '${value}', in which case 'animationName' with Keyframe as value is recommended.`,
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default linter;
|
@ -0,0 +1,5 @@
|
||||
export { default as contentQuotesLinter } from './contentQuotesLinter';
|
||||
export { default as hashedAnimationLinter } from './hashedAnimationLinter';
|
||||
export type { Linter } from './interface';
|
||||
export { default as legacyNotSelectorLinter } from './legacyNotSelectorLinter';
|
||||
export { default as logicalPropertiesLinter } from './logicalPropertiesLinter';
|
@ -0,0 +1,9 @@
|
||||
export interface LinterInfo {
|
||||
path?: string;
|
||||
hashId?: string;
|
||||
parentSelectors: string[];
|
||||
}
|
||||
|
||||
export interface Linter {
|
||||
(key: string, value: string | number, info: LinterInfo): void;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import type { Linter, LinterInfo } from './interface';
|
||||
import { lintWarning } from './utils';
|
||||
|
||||
function isConcatSelector(selector: string) {
|
||||
const notContent = selector.match(/:not\(([^)]*)\)/)?.[1] || '';
|
||||
|
||||
// split selector. e.g.
|
||||
// `h1#a.b` => ['h1', #a', '.b']
|
||||
const splitCells = notContent.split(/(\[[^[]*])|(?=[.#])/).filter(str => str);
|
||||
|
||||
return splitCells.length > 1;
|
||||
}
|
||||
|
||||
function parsePath(info: LinterInfo) {
|
||||
return info.parentSelectors.reduce((prev, cur) => {
|
||||
if (!prev) {
|
||||
return cur;
|
||||
}
|
||||
|
||||
return cur.includes('&') ? cur.replace(/&/g, prev) : `${prev} ${cur}`;
|
||||
}, '');
|
||||
}
|
||||
|
||||
const linter: Linter = (_key, _value, info) => {
|
||||
const parentSelectorPath = parsePath(info);
|
||||
const notList = parentSelectorPath.match(/:not\([^)]*\)/g) || [];
|
||||
|
||||
if (notList.length > 0 && notList.some(isConcatSelector)) {
|
||||
lintWarning(`Concat ':not' selector not support in legacy browsers.`, info);
|
||||
}
|
||||
};
|
||||
|
||||
export default linter;
|
@ -0,0 +1,88 @@
|
||||
import type { Linter } from './interface';
|
||||
import { lintWarning } from './utils';
|
||||
|
||||
const linter: Linter = (key, value, info) => {
|
||||
switch (key) {
|
||||
case 'marginLeft':
|
||||
case 'marginRight':
|
||||
case 'paddingLeft':
|
||||
case 'paddingRight':
|
||||
case 'left':
|
||||
case 'right':
|
||||
case 'borderLeft':
|
||||
case 'borderLeftWidth':
|
||||
case 'borderLeftStyle':
|
||||
case 'borderLeftColor':
|
||||
case 'borderRight':
|
||||
case 'borderRightWidth':
|
||||
case 'borderRightStyle':
|
||||
case 'borderRightColor':
|
||||
case 'borderTopLeftRadius':
|
||||
case 'borderTopRightRadius':
|
||||
case 'borderBottomLeftRadius':
|
||||
case 'borderBottomRightRadius':
|
||||
lintWarning(
|
||||
`You seem to be using non-logical property '${key}' which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
|
||||
info,
|
||||
);
|
||||
return;
|
||||
case 'margin':
|
||||
case 'padding':
|
||||
case 'borderWidth':
|
||||
case 'borderStyle':
|
||||
// case 'borderColor':
|
||||
if (typeof value === 'string') {
|
||||
const valueArr = value.split(' ').map(item => item.trim());
|
||||
if (valueArr.length === 4 && valueArr[1] !== valueArr[3]) {
|
||||
lintWarning(
|
||||
`You seem to be using '${key}' property with different left ${key} and right ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'clear':
|
||||
case 'textAlign':
|
||||
if (value === 'left' || value === 'right') {
|
||||
lintWarning(
|
||||
`You seem to be using non-logical value '${value}' of ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
|
||||
info,
|
||||
);
|
||||
}
|
||||
return;
|
||||
case 'borderRadius':
|
||||
if (typeof value === 'string') {
|
||||
const radiusGroups = value.split('/').map(item => item.trim());
|
||||
const invalid = radiusGroups.reduce((result, group) => {
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
const radiusArr = group.split(' ').map(item => item.trim());
|
||||
// borderRadius: '2px 4px'
|
||||
if (radiusArr.length >= 2 && radiusArr[0] !== radiusArr[1]) {
|
||||
return true;
|
||||
}
|
||||
// borderRadius: '4px 4px 2px'
|
||||
if (radiusArr.length === 3 && radiusArr[1] !== radiusArr[2]) {
|
||||
return true;
|
||||
}
|
||||
// borderRadius: '4px 4px 2px 4px'
|
||||
if (radiusArr.length === 4 && radiusArr[2] !== radiusArr[3]) {
|
||||
return true;
|
||||
}
|
||||
return result;
|
||||
}, false);
|
||||
|
||||
if (invalid) {
|
||||
lintWarning(
|
||||
`You seem to be using non-logical value '${value}' of ${key}, which is not compatible with RTL mode. Please use logical properties and values instead. For more information: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties.`,
|
||||
info,
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
export default linter;
|
@ -0,0 +1,13 @@
|
||||
import devWarning from '../../../vc-util/warning';
|
||||
import type { LinterInfo } from './interface';
|
||||
|
||||
export function lintWarning(message: string, info: LinterInfo) {
|
||||
const { path, parentSelectors } = info;
|
||||
|
||||
devWarning(
|
||||
false,
|
||||
`[Ant Design Vue CSS-in-JS] ${path ? `Error in '${path}': ` : ''}${message}${
|
||||
parentSelectors.length ? ` Selector info: ${parentSelectors.join(' -> ')}` : ''
|
||||
}`,
|
||||
);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import warning from '../../warning';
|
||||
import type { DerivativeFunc, TokenType } from './interface';
|
||||
|
||||
let uuid = 0;
|
||||
|
||||
/**
|
||||
* Theme with algorithms to derive tokens from design tokens.
|
||||
* Use `createTheme` first which will help to manage the theme instance cache.
|
||||
*/
|
||||
export default class Theme<DesignToken extends TokenType, DerivativeToken extends TokenType> {
|
||||
private derivatives: DerivativeFunc<DesignToken, DerivativeToken>[];
|
||||
public readonly id: number;
|
||||
|
||||
constructor(
|
||||
derivatives:
|
||||
| DerivativeFunc<DesignToken, DerivativeToken>
|
||||
| DerivativeFunc<DesignToken, DerivativeToken>[],
|
||||
) {
|
||||
this.derivatives = Array.isArray(derivatives) ? derivatives : [derivatives];
|
||||
this.id = uuid;
|
||||
|
||||
if (derivatives.length === 0) {
|
||||
warning(
|
||||
derivatives.length > 0,
|
||||
'[Ant Design Vue CSS-in-JS] Theme should have at least one derivative function.',
|
||||
);
|
||||
}
|
||||
|
||||
uuid += 1;
|
||||
}
|
||||
|
||||
getDerivativeToken(token: DesignToken): DerivativeToken {
|
||||
return this.derivatives.reduce<DerivativeToken>(
|
||||
(result, derivative) => derivative(token, result),
|
||||
undefined as any,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
import type Theme from './Theme';
|
||||
import type { DerivativeFunc } from './interface';
|
||||
|
||||
// ================================== Cache ==================================
|
||||
type ThemeCacheMap = Map<
|
||||
DerivativeFunc<any, any>,
|
||||
{
|
||||
map?: ThemeCacheMap;
|
||||
value?: [Theme<any, any>, number];
|
||||
}
|
||||
>;
|
||||
|
||||
type DerivativeOptions = DerivativeFunc<any, any>[];
|
||||
|
||||
export function sameDerivativeOption(left: DerivativeOptions, right: DerivativeOptions) {
|
||||
if (left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
if (left[i] !== right[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default class ThemeCache {
|
||||
public static MAX_CACHE_SIZE = 20;
|
||||
public static MAX_CACHE_OFFSET = 5;
|
||||
|
||||
private readonly cache: ThemeCacheMap;
|
||||
private keys: DerivativeOptions[];
|
||||
private cacheCallTimes: number;
|
||||
|
||||
constructor() {
|
||||
this.cache = new Map();
|
||||
this.keys = [];
|
||||
this.cacheCallTimes = 0;
|
||||
}
|
||||
|
||||
public size(): number {
|
||||
return this.keys.length;
|
||||
}
|
||||
|
||||
private internalGet(
|
||||
derivativeOption: DerivativeOptions,
|
||||
updateCallTimes = false,
|
||||
): [Theme<any, any>, number] | undefined {
|
||||
let cache: ReturnType<ThemeCacheMap['get']> = { map: this.cache };
|
||||
derivativeOption.forEach(derivative => {
|
||||
if (!cache) {
|
||||
cache = undefined;
|
||||
} else {
|
||||
cache = cache?.map?.get(derivative);
|
||||
}
|
||||
});
|
||||
if (cache?.value && updateCallTimes) {
|
||||
cache.value[1] = this.cacheCallTimes++;
|
||||
}
|
||||
return cache?.value;
|
||||
}
|
||||
|
||||
public get(derivativeOption: DerivativeOptions): Theme<any, any> | undefined {
|
||||
return this.internalGet(derivativeOption, true)?.[0];
|
||||
}
|
||||
|
||||
public has(derivativeOption: DerivativeOptions): boolean {
|
||||
return !!this.internalGet(derivativeOption);
|
||||
}
|
||||
|
||||
public set(derivativeOption: DerivativeOptions, value: Theme<any, any>): void {
|
||||
// New cache
|
||||
if (!this.has(derivativeOption)) {
|
||||
if (this.size() + 1 > ThemeCache.MAX_CACHE_SIZE + ThemeCache.MAX_CACHE_OFFSET) {
|
||||
const [targetKey] = this.keys.reduce<[DerivativeOptions, number]>(
|
||||
(result, key) => {
|
||||
const [, callTimes] = result;
|
||||
if (this.internalGet(key)![1] < callTimes) {
|
||||
return [key, this.internalGet(key)![1]];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[this.keys[0], this.cacheCallTimes],
|
||||
);
|
||||
this.delete(targetKey);
|
||||
}
|
||||
|
||||
this.keys.push(derivativeOption);
|
||||
}
|
||||
|
||||
let cache = this.cache;
|
||||
derivativeOption.forEach((derivative, index) => {
|
||||
if (index === derivativeOption.length - 1) {
|
||||
cache.set(derivative, { value: [value, this.cacheCallTimes++] });
|
||||
} else {
|
||||
const cacheValue = cache.get(derivative);
|
||||
if (!cacheValue) {
|
||||
cache.set(derivative, { map: new Map() });
|
||||
} else if (!cacheValue.map) {
|
||||
cacheValue.map = new Map();
|
||||
}
|
||||
cache = cache.get(derivative)!.map!;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private deleteByPath(
|
||||
currentCache: ThemeCacheMap,
|
||||
derivatives: DerivativeFunc<any, any>[],
|
||||
): Theme<any, any> | undefined {
|
||||
const cache = currentCache.get(derivatives[0])!;
|
||||
if (derivatives.length === 1) {
|
||||
if (!cache.map) {
|
||||
currentCache.delete(derivatives[0]);
|
||||
} else {
|
||||
currentCache.set(derivatives[0], { map: cache.map });
|
||||
}
|
||||
return cache.value?.[0];
|
||||
}
|
||||
const result = this.deleteByPath(cache.map!, derivatives.slice(1));
|
||||
if ((!cache.map || cache.map.size === 0) && !cache.value) {
|
||||
currentCache.delete(derivatives[0]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public delete(derivativeOption: DerivativeOptions): Theme<any, any> | undefined {
|
||||
// If cache exists
|
||||
if (this.has(derivativeOption)) {
|
||||
this.keys = this.keys.filter(item => !sameDerivativeOption(item, derivativeOption));
|
||||
return this.deleteByPath(this.cache, derivativeOption);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import ThemeCache from './ThemeCache';
|
||||
import Theme from './Theme';
|
||||
import type { DerivativeFunc, TokenType } from './interface';
|
||||
|
||||
const cacheThemes = new ThemeCache();
|
||||
|
||||
/**
|
||||
* Same as new Theme, but will always return same one if `derivative` not changed.
|
||||
*/
|
||||
export default function createTheme<
|
||||
DesignToken extends TokenType,
|
||||
DerivativeToken extends TokenType,
|
||||
>(
|
||||
derivatives:
|
||||
| DerivativeFunc<DesignToken, DerivativeToken>[]
|
||||
| DerivativeFunc<DesignToken, DerivativeToken>,
|
||||
) {
|
||||
const derivativeArr = Array.isArray(derivatives) ? derivatives : [derivatives];
|
||||
// Create new theme if not exist
|
||||
if (!cacheThemes.has(derivativeArr)) {
|
||||
cacheThemes.set(derivativeArr, new Theme(derivativeArr));
|
||||
}
|
||||
|
||||
// Get theme from cache and return
|
||||
return cacheThemes.get(derivativeArr)!;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export { default as createTheme } from './createTheme';
|
||||
export { default as Theme } from './Theme';
|
||||
export { default as ThemeCache } from './ThemeCache';
|
||||
export type { TokenType, DerivativeFunc } from './interface';
|
@ -0,0 +1,5 @@
|
||||
export type TokenType = object;
|
||||
export type DerivativeFunc<DesignToken extends TokenType, DerivativeToken extends TokenType> = (
|
||||
designToken: DesignToken,
|
||||
derivativeToken?: DerivativeToken,
|
||||
) => DerivativeToken;
|
@ -0,0 +1,5 @@
|
||||
import type { CSSObject } from '..';
|
||||
|
||||
export interface Transformer {
|
||||
visit?: (cssObj: CSSObject) => CSSObject;
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
import type { CSSObject } from '..';
|
||||
import type { Transformer } from './interface';
|
||||
|
||||
function splitValues(value: string | number) {
|
||||
if (typeof value === 'number') {
|
||||
return [value];
|
||||
}
|
||||
|
||||
const splitStyle = String(value).split(/\s+/);
|
||||
|
||||
// Combine styles split in brackets, like `calc(1px + 2px)`
|
||||
let temp = '';
|
||||
let brackets = 0;
|
||||
return splitStyle.reduce<string[]>((list, item) => {
|
||||
if (item.includes('(')) {
|
||||
temp += item;
|
||||
brackets += item.split('(').length - 1;
|
||||
} else if (item.includes(')')) {
|
||||
temp += ` ${item}`;
|
||||
brackets -= item.split(')').length - 1;
|
||||
if (brackets === 0) {
|
||||
list.push(temp);
|
||||
temp = '';
|
||||
}
|
||||
} else if (brackets > 0) {
|
||||
temp += ` ${item}`;
|
||||
} else {
|
||||
list.push(item);
|
||||
}
|
||||
return list;
|
||||
}, []);
|
||||
}
|
||||
|
||||
type MatchValue = string[] & {
|
||||
notSplit?: boolean;
|
||||
};
|
||||
|
||||
function noSplit(list: MatchValue): MatchValue {
|
||||
list.notSplit = true;
|
||||
return list;
|
||||
}
|
||||
|
||||
const keyMap: Record<string, MatchValue> = {
|
||||
// Inset
|
||||
inset: ['top', 'right', 'bottom', 'left'],
|
||||
insetBlock: ['top', 'bottom'],
|
||||
insetBlockStart: ['top'],
|
||||
insetBlockEnd: ['bottom'],
|
||||
insetInline: ['left', 'right'],
|
||||
insetInlineStart: ['left'],
|
||||
insetInlineEnd: ['right'],
|
||||
|
||||
// Margin
|
||||
marginBlock: ['marginTop', 'marginBottom'],
|
||||
marginBlockStart: ['marginTop'],
|
||||
marginBlockEnd: ['marginBottom'],
|
||||
marginInline: ['marginLeft', 'marginRight'],
|
||||
marginInlineStart: ['marginLeft'],
|
||||
marginInlineEnd: ['marginRight'],
|
||||
|
||||
// Padding
|
||||
paddingBlock: ['paddingTop', 'paddingBottom'],
|
||||
paddingBlockStart: ['paddingTop'],
|
||||
paddingBlockEnd: ['paddingBottom'],
|
||||
paddingInline: ['paddingLeft', 'paddingRight'],
|
||||
paddingInlineStart: ['paddingLeft'],
|
||||
paddingInlineEnd: ['paddingRight'],
|
||||
|
||||
// Border
|
||||
borderBlock: noSplit(['borderTop', 'borderBottom']),
|
||||
borderBlockStart: noSplit(['borderTop']),
|
||||
borderBlockEnd: noSplit(['borderBottom']),
|
||||
borderInline: noSplit(['borderLeft', 'borderRight']),
|
||||
borderInlineStart: noSplit(['borderLeft']),
|
||||
borderInlineEnd: noSplit(['borderRight']),
|
||||
|
||||
// Border width
|
||||
borderBlockWidth: ['borderTopWidth', 'borderBottomWidth'],
|
||||
borderBlockStartWidth: ['borderTopWidth'],
|
||||
borderBlockEndWidth: ['borderBottomWidth'],
|
||||
borderInlineWidth: ['borderLeftWidth', 'borderRightWidth'],
|
||||
borderInlineStartWidth: ['borderLeftWidth'],
|
||||
borderInlineEndWidth: ['borderRightWidth'],
|
||||
|
||||
// Border style
|
||||
borderBlockStyle: ['borderTopStyle', 'borderBottomStyle'],
|
||||
borderBlockStartStyle: ['borderTopStyle'],
|
||||
borderBlockEndStyle: ['borderBottomStyle'],
|
||||
borderInlineStyle: ['borderLeftStyle', 'borderRightStyle'],
|
||||
borderInlineStartStyle: ['borderLeftStyle'],
|
||||
borderInlineEndStyle: ['borderRightStyle'],
|
||||
|
||||
// Border color
|
||||
borderBlockColor: ['borderTopColor', 'borderBottomColor'],
|
||||
borderBlockStartColor: ['borderTopColor'],
|
||||
borderBlockEndColor: ['borderBottomColor'],
|
||||
borderInlineColor: ['borderLeftColor', 'borderRightColor'],
|
||||
borderInlineStartColor: ['borderLeftColor'],
|
||||
borderInlineEndColor: ['borderRightColor'],
|
||||
|
||||
// Border radius
|
||||
borderStartStartRadius: ['borderTopLeftRadius'],
|
||||
borderStartEndRadius: ['borderTopRightRadius'],
|
||||
borderEndStartRadius: ['borderBottomLeftRadius'],
|
||||
borderEndEndRadius: ['borderBottomRightRadius'],
|
||||
};
|
||||
|
||||
function skipCheck(value: string | number) {
|
||||
return { _skip_check_: true, value };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert css logical properties to legacy properties.
|
||||
* Such as: `margin-block-start` to `margin-top`.
|
||||
* Transform list:
|
||||
* - inset
|
||||
* - margin
|
||||
* - padding
|
||||
* - border
|
||||
*/
|
||||
const transform: Transformer = {
|
||||
visit: cssObj => {
|
||||
const clone: CSSObject = {};
|
||||
|
||||
Object.keys(cssObj).forEach(key => {
|
||||
const value = cssObj[key];
|
||||
const matchValue = keyMap[key];
|
||||
|
||||
if (matchValue && (typeof value === 'number' || typeof value === 'string')) {
|
||||
const values = splitValues(value);
|
||||
|
||||
if (matchValue.length && matchValue.notSplit) {
|
||||
// not split means always give same value like border
|
||||
matchValue.forEach(matchKey => {
|
||||
clone[matchKey] = skipCheck(value);
|
||||
});
|
||||
} else if (matchValue.length === 1) {
|
||||
// Handle like `marginBlockStart` => `marginTop`
|
||||
clone[matchValue[0]] = skipCheck(value);
|
||||
} else if (matchValue.length === 2) {
|
||||
// Handle like `marginBlock` => `marginTop` & `marginBottom`
|
||||
matchValue.forEach((matchKey, index) => {
|
||||
clone[matchKey] = skipCheck(values[index] ?? values[0]);
|
||||
});
|
||||
} else if (matchValue.length === 4) {
|
||||
// Handle like `inset` => `top` & `right` & `bottom` & `left`
|
||||
matchValue.forEach((matchKey, index) => {
|
||||
clone[matchKey] = skipCheck(values[index] ?? values[index - 2] ?? values[0]);
|
||||
});
|
||||
} else {
|
||||
clone[key] = value;
|
||||
}
|
||||
} else {
|
||||
clone[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return clone;
|
||||
},
|
||||
};
|
||||
|
||||
export default transform;
|
@ -0,0 +1,68 @@
|
||||
import hash from '@emotion/hash';
|
||||
import { removeCSS, updateCSS } from '../../vc-util/Dom/dynamicCSS';
|
||||
import canUseDom from '../canUseDom';
|
||||
|
||||
export function flattenToken(token: any) {
|
||||
let str = '';
|
||||
Object.keys(token).forEach(key => {
|
||||
const value = token[key];
|
||||
str += key;
|
||||
if (value && typeof value === 'object') {
|
||||
str += flattenToken(value);
|
||||
} else {
|
||||
str += value;
|
||||
}
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert derivative token to key string
|
||||
*/
|
||||
export function token2key(token: any, salt: string): string {
|
||||
return hash(`${salt}_${flattenToken(token)}`);
|
||||
}
|
||||
|
||||
const layerKey = `layer-${Date.now()}-${Math.random()}`.replace(/\./g, '');
|
||||
const layerWidth = '903px';
|
||||
|
||||
function supportSelector(styleStr: string, handleElement?: (ele: HTMLElement) => void): boolean {
|
||||
if (canUseDom()) {
|
||||
updateCSS(styleStr, layerKey);
|
||||
|
||||
const ele = document.createElement('div');
|
||||
ele.style.position = 'fixed';
|
||||
ele.style.left = '0';
|
||||
ele.style.top = '0';
|
||||
handleElement?.(ele);
|
||||
document.body.appendChild(ele);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
ele.innerHTML = 'Test';
|
||||
ele.style.zIndex = '9999999';
|
||||
}
|
||||
|
||||
const support = getComputedStyle(ele).width === layerWidth;
|
||||
|
||||
ele.parentNode?.removeChild(ele);
|
||||
removeCSS(layerKey);
|
||||
|
||||
return support;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let canLayer: boolean | undefined = undefined;
|
||||
export function supportLayer(): boolean {
|
||||
if (canLayer === undefined) {
|
||||
canLayer = supportSelector(
|
||||
`@layer ${layerKey} { .${layerKey} { width: ${layerWidth}!important; } }`,
|
||||
ele => {
|
||||
ele.className = layerKey;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return canLayer!;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
type RecordType = Record<string, any>;
|
||||
|
||||
function extendsObject<T extends RecordType>(...list: T[]) {
|
||||
const result: RecordType = { ...list[0] };
|
||||
|
||||
for (let i = 1; i < list.length; i++) {
|
||||
const obj = list[i];
|
||||
if (obj) {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const val = obj[key];
|
||||
if (val !== undefined) {
|
||||
result[key] = val;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default extendsObject;
|
@ -1,30 +0,0 @@
|
||||
export function getComponentLocale(props, context, componentName, getDefaultLocale) {
|
||||
let locale = {};
|
||||
if (context && context.antLocale && context.antLocale[componentName]) {
|
||||
locale = context.antLocale[componentName];
|
||||
} else {
|
||||
const defaultLocale = getDefaultLocale();
|
||||
// TODO: make default lang of antd be English
|
||||
// https://github.com/ant-design/ant-design/issues/6334
|
||||
locale = defaultLocale.default || defaultLocale;
|
||||
}
|
||||
|
||||
const result = {
|
||||
...locale,
|
||||
...props.locale,
|
||||
};
|
||||
result.lang = {
|
||||
...locale.lang,
|
||||
...props.locale.lang,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getLocaleCode(context) {
|
||||
const localeCode = context.antLocale && context.antLocale.locale;
|
||||
// Had use LocaleProvide but didn't set locale
|
||||
if (context.antLocale && context.antLocale.exist && !localeCode) {
|
||||
return 'zh-cn';
|
||||
}
|
||||
return localeCode;
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import { tryOnScopeDispose } from './tryOnScopeDispose';
|
||||
import { watch } from 'vue';
|
||||
import type { MaybeElementRef } from './unrefElement';
|
||||
import { unrefElement } from './unrefElement';
|
||||
import { useSupported } from './useSupported';
|
||||
import type { ConfigurableWindow } from './_configurable';
|
||||
import { defaultWindow } from './_configurable';
|
||||
|
||||
export interface UseMutationObserverOptions extends MutationObserverInit, ConfigurableWindow {}
|
||||
|
||||
/**
|
||||
* Watch for changes being made to the DOM tree.
|
||||
*
|
||||
* @see https://vueuse.org/useMutationObserver
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver MutationObserver MDN
|
||||
* @param target
|
||||
* @param callback
|
||||
* @param options
|
||||
*/
|
||||
export function useMutationObserver(
|
||||
target: MaybeElementRef,
|
||||
callback: MutationCallback,
|
||||
options: UseMutationObserverOptions = {},
|
||||
) {
|
||||
const { window = defaultWindow, ...mutationOptions } = options;
|
||||
let observer: MutationObserver | undefined;
|
||||
const isSupported = useSupported(() => window && 'MutationObserver' in window);
|
||||
|
||||
const cleanup = () => {
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
observer = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const stopWatch = watch(
|
||||
() => unrefElement(target),
|
||||
el => {
|
||||
cleanup();
|
||||
|
||||
if (isSupported.value && window && el) {
|
||||
observer = new MutationObserver(callback);
|
||||
observer!.observe(el, mutationOptions);
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const stop = () => {
|
||||
cleanup();
|
||||
stopWatch();
|
||||
};
|
||||
|
||||
tryOnScopeDispose(stop);
|
||||
|
||||
return {
|
||||
isSupported,
|
||||
stop,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseMutationObserverReturn = ReturnType<typeof useMutationObserver>;
|
@ -1,84 +0,0 @@
|
||||
import type { RequiredMark } from '../../form/Form';
|
||||
import type { ComputedRef, UnwrapRef } from 'vue';
|
||||
import { computed, inject } from 'vue';
|
||||
import type { ConfigProviderProps, CSPConfig, Direction, SizeType } from '../../config-provider';
|
||||
import { defaultConfigProvider } from '../../config-provider';
|
||||
import type { VueNode } from '../type';
|
||||
import type { ValidateMessages } from '../../form/interface';
|
||||
|
||||
export default (
|
||||
name: string,
|
||||
props: Record<any, any>,
|
||||
): {
|
||||
configProvider: UnwrapRef<ConfigProviderProps>;
|
||||
prefixCls: ComputedRef<string>;
|
||||
rootPrefixCls: ComputedRef<string>;
|
||||
direction: ComputedRef<Direction>;
|
||||
size: ComputedRef<SizeType>;
|
||||
getTargetContainer: ComputedRef<() => HTMLElement>;
|
||||
space: ComputedRef<{ size: SizeType | number }>;
|
||||
pageHeader: ComputedRef<{ ghost: boolean }>;
|
||||
form?: ComputedRef<{
|
||||
requiredMark?: RequiredMark;
|
||||
colon?: boolean;
|
||||
validateMessages?: ValidateMessages;
|
||||
}>;
|
||||
autoInsertSpaceInButton: ComputedRef<boolean>;
|
||||
renderEmpty?: ComputedRef<(componentName?: string) => VueNode>;
|
||||
virtual: ComputedRef<boolean>;
|
||||
dropdownMatchSelectWidth: ComputedRef<boolean | number>;
|
||||
getPopupContainer: ComputedRef<ConfigProviderProps['getPopupContainer']>;
|
||||
getPrefixCls: ConfigProviderProps['getPrefixCls'];
|
||||
autocomplete: ComputedRef<string>;
|
||||
csp: ComputedRef<CSPConfig>;
|
||||
} => {
|
||||
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
|
||||
'configProvider',
|
||||
defaultConfigProvider,
|
||||
);
|
||||
const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls));
|
||||
const direction = computed(() => props.direction ?? configProvider.direction);
|
||||
const rootPrefixCls = computed(() => configProvider.getPrefixCls());
|
||||
const autoInsertSpaceInButton = computed(() => configProvider.autoInsertSpaceInButton);
|
||||
const renderEmpty = computed(() => configProvider.renderEmpty);
|
||||
const space = computed(() => configProvider.space);
|
||||
const pageHeader = computed(() => configProvider.pageHeader);
|
||||
const form = computed(() => configProvider.form);
|
||||
const getTargetContainer = computed(
|
||||
() => props.getTargetContainer || configProvider.getTargetContainer,
|
||||
);
|
||||
const getPopupContainer = computed(
|
||||
() => props.getPopupContainer || configProvider.getPopupContainer,
|
||||
);
|
||||
|
||||
const dropdownMatchSelectWidth = computed<boolean | number>(
|
||||
() => props.dropdownMatchSelectWidth ?? configProvider.dropdownMatchSelectWidth,
|
||||
);
|
||||
const virtual = computed(
|
||||
() =>
|
||||
(props.virtual === undefined ? configProvider.virtual !== false : props.virtual !== false) &&
|
||||
dropdownMatchSelectWidth.value !== false,
|
||||
);
|
||||
const size = computed(() => props.size || configProvider.componentSize);
|
||||
const autocomplete = computed(() => props.autocomplete || configProvider.input?.autocomplete);
|
||||
const csp = computed(() => configProvider.csp);
|
||||
return {
|
||||
configProvider,
|
||||
prefixCls,
|
||||
direction,
|
||||
size,
|
||||
getTargetContainer,
|
||||
getPopupContainer,
|
||||
space,
|
||||
pageHeader,
|
||||
form,
|
||||
autoInsertSpaceInButton,
|
||||
renderEmpty,
|
||||
virtual,
|
||||
dropdownMatchSelectWidth,
|
||||
rootPrefixCls,
|
||||
getPrefixCls: configProvider.getPrefixCls,
|
||||
autocomplete,
|
||||
csp,
|
||||
};
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
import { ref } from 'vue';
|
||||
import canUseDom from '../../_util/canUseDom';
|
||||
|
||||
let uuid = 0;
|
||||
|
||||
/** Is client side and not jsdom */
|
||||
export const isBrowserClient = process.env.NODE_ENV !== 'test' && canUseDom();
|
||||
|
||||
/** Get unique id for accessibility usage */
|
||||
export function getUUID(): number | string {
|
||||
let retId: string | number;
|
||||
|
||||
// Test never reach
|
||||
/* istanbul ignore if */
|
||||
if (isBrowserClient) {
|
||||
retId = uuid;
|
||||
uuid += 1;
|
||||
} else {
|
||||
retId = 'TEST_OR_SSR';
|
||||
}
|
||||
|
||||
return retId;
|
||||
}
|
||||
|
||||
export default function useId(id = ref('')) {
|
||||
// Inner id for accessibility usage. Only work in client side
|
||||
const innerId = `vc_unique_${getUUID()}`;
|
||||
|
||||
return id.value || innerId;
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, watchEffect } from 'vue';
|
||||
import { updateCSS, removeCSS } from '../../vc-util/Dom/dynamicCSS';
|
||||
import getScrollBarSize from '../../_util/getScrollBarSize';
|
||||
|
||||
const UNIQUE_ID = `vc-util-locker-${Date.now()}`;
|
||||
|
||||
let uuid = 0;
|
||||
|
||||
/**../vc-util/Dom/dynam
|
||||
* Test usage export. Do not use in your production
|
||||
*/
|
||||
export function isBodyOverflowing() {
|
||||
return (
|
||||
document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
window.innerWidth > document.body.offsetWidth
|
||||
);
|
||||
}
|
||||
|
||||
export default function useScrollLocker(lock?: Ref<boolean>) {
|
||||
const mergedLock = computed(() => !!lock && !!lock.value);
|
||||
uuid += 1;
|
||||
const id = `${UNIQUE_ID}_${uuid}`;
|
||||
|
||||
watchEffect(
|
||||
onClear => {
|
||||
if (mergedLock.value) {
|
||||
const scrollbarSize = getScrollBarSize();
|
||||
const isOverflow = isBodyOverflowing();
|
||||
|
||||
updateCSS(
|
||||
`
|
||||
html body {
|
||||
overflow-y: hidden;
|
||||
${isOverflow ? `width: calc(100% - ${scrollbarSize}px);` : ''}
|
||||
}`,
|
||||
id,
|
||||
);
|
||||
} else {
|
||||
removeCSS(id);
|
||||
}
|
||||
onClear(() => {
|
||||
removeCSS(id);
|
||||
});
|
||||
},
|
||||
{ flush: 'post' },
|
||||
);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import type { ComputedRef, UnwrapRef } from 'vue';
|
||||
import { computed, inject, provide } from 'vue';
|
||||
import type { ConfigProviderProps, SizeType } from '../../config-provider';
|
||||
import { defaultConfigProvider } from '../../config-provider';
|
||||
|
||||
const sizeProvider = Symbol('SizeProvider');
|
||||
|
||||
const useProvideSize = <T = SizeType>(props: Record<any, any>): ComputedRef<T> => {
|
||||
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
|
||||
'configProvider',
|
||||
defaultConfigProvider,
|
||||
);
|
||||
const size = computed<T>(() => props.size || configProvider.componentSize);
|
||||
provide(sizeProvider, size);
|
||||
return size;
|
||||
};
|
||||
|
||||
const useInjectSize = <T = SizeType>(props?: Record<any, any>): ComputedRef<T> => {
|
||||
const size: ComputedRef<T> = props
|
||||
? computed(() => props.size)
|
||||
: inject(
|
||||
sizeProvider,
|
||||
computed(() => 'default' as unknown as T),
|
||||
);
|
||||
return size;
|
||||
};
|
||||
|
||||
export { useInjectSize, sizeProvider, useProvideSize };
|
||||
|
||||
export default useProvideSize;
|
@ -1,110 +0,0 @@
|
||||
// MIT License from https://github.com/kaimallea/isMobile
|
||||
|
||||
const applePhone = /iPhone/i;
|
||||
const appleIpod = /iPod/i;
|
||||
const appleTablet = /iPad/i;
|
||||
const androidPhone = /\bAndroid(?:.+)Mobile\b/i; // Match 'Android' AND 'Mobile'
|
||||
const androidTablet = /Android/i;
|
||||
const amazonPhone = /\bAndroid(?:.+)SD4930UR\b/i;
|
||||
const amazonTablet = /\bAndroid(?:.+)(?:KF[A-Z]{2,4})\b/i;
|
||||
const windowsPhone = /Windows Phone/i;
|
||||
const windowsTablet = /\bWindows(?:.+)ARM\b/i; // Match 'Windows' AND 'ARM'
|
||||
const otherBlackberry = /BlackBerry/i;
|
||||
const otherBlackberry10 = /BB10/i;
|
||||
const otherOpera = /Opera Mini/i;
|
||||
const otherChrome = /\b(CriOS|Chrome)(?:.+)Mobile/i;
|
||||
const otherFirefox = /Mobile(?:.+)Firefox\b/i; // Match 'Mobile' AND 'Firefox'
|
||||
|
||||
function match(regex, userAgent) {
|
||||
return regex.test(userAgent);
|
||||
}
|
||||
|
||||
function isMobile(userAgent) {
|
||||
let ua = userAgent || (typeof navigator !== 'undefined' ? navigator.userAgent : '');
|
||||
|
||||
// Facebook mobile app's integrated browser adds a bunch of strings that
|
||||
// match everything. Strip it out if it exists.
|
||||
let tmp = ua.split('[FBAN');
|
||||
if (typeof tmp[1] !== 'undefined') {
|
||||
[ua] = tmp;
|
||||
}
|
||||
|
||||
// Twitter mobile app's integrated browser on iPad adds a "Twitter for
|
||||
// iPhone" string. Same probably happens on other tablet platforms.
|
||||
// This will confuse detection so strip it out if it exists.
|
||||
tmp = ua.split('Twitter');
|
||||
if (typeof tmp[1] !== 'undefined') {
|
||||
[ua] = tmp;
|
||||
}
|
||||
|
||||
const result = {
|
||||
apple: {
|
||||
phone: match(applePhone, ua) && !match(windowsPhone, ua),
|
||||
ipod: match(appleIpod, ua),
|
||||
tablet: !match(applePhone, ua) && match(appleTablet, ua) && !match(windowsPhone, ua),
|
||||
device:
|
||||
(match(applePhone, ua) || match(appleIpod, ua) || match(appleTablet, ua)) &&
|
||||
!match(windowsPhone, ua),
|
||||
},
|
||||
amazon: {
|
||||
phone: match(amazonPhone, ua),
|
||||
tablet: !match(amazonPhone, ua) && match(amazonTablet, ua),
|
||||
device: match(amazonPhone, ua) || match(amazonTablet, ua),
|
||||
},
|
||||
android: {
|
||||
phone:
|
||||
(!match(windowsPhone, ua) && match(amazonPhone, ua)) ||
|
||||
(!match(windowsPhone, ua) && match(androidPhone, ua)),
|
||||
tablet:
|
||||
!match(windowsPhone, ua) &&
|
||||
!match(amazonPhone, ua) &&
|
||||
!match(androidPhone, ua) &&
|
||||
(match(amazonTablet, ua) || match(androidTablet, ua)),
|
||||
device:
|
||||
(!match(windowsPhone, ua) &&
|
||||
(match(amazonPhone, ua) ||
|
||||
match(amazonTablet, ua) ||
|
||||
match(androidPhone, ua) ||
|
||||
match(androidTablet, ua))) ||
|
||||
match(/\bokhttp\b/i, ua),
|
||||
},
|
||||
windows: {
|
||||
phone: match(windowsPhone, ua),
|
||||
tablet: match(windowsTablet, ua),
|
||||
device: match(windowsPhone, ua) || match(windowsTablet, ua),
|
||||
},
|
||||
other: {
|
||||
blackberry: match(otherBlackberry, ua),
|
||||
blackberry10: match(otherBlackberry10, ua),
|
||||
opera: match(otherOpera, ua),
|
||||
firefox: match(otherFirefox, ua),
|
||||
chrome: match(otherChrome, ua),
|
||||
device:
|
||||
match(otherBlackberry, ua) ||
|
||||
match(otherBlackberry10, ua) ||
|
||||
match(otherOpera, ua) ||
|
||||
match(otherFirefox, ua) ||
|
||||
match(otherChrome, ua),
|
||||
},
|
||||
|
||||
// Additional
|
||||
any: null,
|
||||
phone: null,
|
||||
tablet: null,
|
||||
};
|
||||
result.any =
|
||||
result.apple.device || result.android.device || result.windows.device || result.other.device;
|
||||
|
||||
// excludes 'other' devices and ipods, targeting touchscreen phones
|
||||
result.phone = result.apple.phone || result.android.phone || result.windows.phone;
|
||||
result.tablet = result.apple.tablet || result.android.tablet || result.windows.tablet;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const defaultResult = {
|
||||
...isMobile(),
|
||||
isMobile,
|
||||
};
|
||||
|
||||
export default defaultResult;
|
@ -1,75 +1,85 @@
|
||||
import { computed } from 'vue';
|
||||
import type { GlobalToken } from '../theme/interface';
|
||||
import { useToken } from '../theme/internal';
|
||||
|
||||
export type Breakpoint = 'xxxl' | 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
||||
export type BreakpointMap = Record<Breakpoint, string>;
|
||||
export type ScreenMap = Partial<Record<Breakpoint, boolean>>;
|
||||
export type ScreenSizeMap = Partial<Record<Breakpoint, number>>;
|
||||
|
||||
export const responsiveArray: Breakpoint[] = ['xxxl', 'xxl', 'xl', 'lg', 'md', 'sm', 'xs'];
|
||||
type SubscribeFunc = (screens: ScreenMap) => void;
|
||||
|
||||
export const responsiveMap: BreakpointMap = {
|
||||
xs: '(max-width: 575px)',
|
||||
sm: '(min-width: 576px)',
|
||||
md: '(min-width: 768px)',
|
||||
lg: '(min-width: 992px)',
|
||||
xl: '(min-width: 1200px)',
|
||||
xxl: '(min-width: 1600px)',
|
||||
xxxl: '(min-width: 2000px)',
|
||||
};
|
||||
const getResponsiveMap = (token: GlobalToken): BreakpointMap => ({
|
||||
xs: `(max-width: ${token.screenXSMax}px)`,
|
||||
sm: `(min-width: ${token.screenSM}px)`,
|
||||
md: `(min-width: ${token.screenMD}px)`,
|
||||
lg: `(min-width: ${token.screenLG}px)`,
|
||||
xl: `(min-width: ${token.screenXL}px)`,
|
||||
xxl: `(min-width: ${token.screenXXL}px)`,
|
||||
xxxl: `{min-width: ${token.screenXXXL}px}`,
|
||||
});
|
||||
|
||||
type SubscribeFunc = (screens: ScreenMap) => void;
|
||||
const subscribers = new Map<Number, SubscribeFunc>();
|
||||
let subUid = -1;
|
||||
let screens = {};
|
||||
export default function useResponsiveObserver() {
|
||||
const [, token] = useToken();
|
||||
|
||||
const responsiveObserve = {
|
||||
matchHandlers: {} as {
|
||||
[prop: string]: {
|
||||
mql: MediaQueryList;
|
||||
listener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null;
|
||||
};
|
||||
},
|
||||
dispatch(pointMap: ScreenMap) {
|
||||
screens = pointMap;
|
||||
subscribers.forEach(func => func(screens));
|
||||
return subscribers.size >= 1;
|
||||
},
|
||||
subscribe(func: SubscribeFunc): number {
|
||||
if (!subscribers.size) this.register();
|
||||
subUid += 1;
|
||||
subscribers.set(subUid, func);
|
||||
func(screens);
|
||||
return subUid;
|
||||
},
|
||||
unsubscribe(token: number) {
|
||||
subscribers.delete(token);
|
||||
if (!subscribers.size) this.unregister();
|
||||
},
|
||||
unregister() {
|
||||
Object.keys(responsiveMap).forEach((screen: string) => {
|
||||
const matchMediaQuery = responsiveMap[screen];
|
||||
const handler = this.matchHandlers[matchMediaQuery];
|
||||
handler?.mql.removeListener(handler?.listener);
|
||||
});
|
||||
subscribers.clear();
|
||||
},
|
||||
register() {
|
||||
Object.keys(responsiveMap).forEach((screen: string) => {
|
||||
const matchMediaQuery = responsiveMap[screen];
|
||||
const listener = ({ matches }: { matches: boolean }) => {
|
||||
this.dispatch({
|
||||
...screens,
|
||||
[screen]: matches,
|
||||
});
|
||||
};
|
||||
const mql = window.matchMedia(matchMediaQuery);
|
||||
mql.addListener(listener);
|
||||
this.matchHandlers[matchMediaQuery] = {
|
||||
mql,
|
||||
listener,
|
||||
};
|
||||
return computed(() => {
|
||||
const responsiveMap: BreakpointMap = getResponsiveMap(token.value);
|
||||
const subscribers = new Map<Number, SubscribeFunc>();
|
||||
let subUid = -1;
|
||||
let screens = {};
|
||||
|
||||
listener(mql);
|
||||
});
|
||||
},
|
||||
};
|
||||
return {
|
||||
matchHandlers: {} as {
|
||||
[prop: string]: {
|
||||
mql: MediaQueryList;
|
||||
listener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null;
|
||||
};
|
||||
},
|
||||
dispatch(pointMap: ScreenMap) {
|
||||
screens = pointMap;
|
||||
subscribers.forEach(func => func(screens));
|
||||
return subscribers.size >= 1;
|
||||
},
|
||||
subscribe(func: SubscribeFunc): number {
|
||||
if (!subscribers.size) this.register();
|
||||
subUid += 1;
|
||||
subscribers.set(subUid, func);
|
||||
func(screens);
|
||||
return subUid;
|
||||
},
|
||||
unsubscribe(paramToken: number) {
|
||||
subscribers.delete(paramToken);
|
||||
if (!subscribers.size) this.unregister();
|
||||
},
|
||||
unregister() {
|
||||
Object.keys(responsiveMap).forEach((screen: string) => {
|
||||
const matchMediaQuery = responsiveMap[screen];
|
||||
const handler = this.matchHandlers[matchMediaQuery];
|
||||
handler?.mql.removeListener(handler?.listener);
|
||||
});
|
||||
subscribers.clear();
|
||||
},
|
||||
register() {
|
||||
Object.keys(responsiveMap).forEach((screen: string) => {
|
||||
const matchMediaQuery = responsiveMap[screen];
|
||||
const listener = ({ matches }: { matches: boolean }) => {
|
||||
this.dispatch({
|
||||
...screens,
|
||||
[screen]: matches,
|
||||
});
|
||||
};
|
||||
const mql = window.matchMedia(matchMediaQuery);
|
||||
mql.addListener(listener);
|
||||
this.matchHandlers[matchMediaQuery] = {
|
||||
mql,
|
||||
listener,
|
||||
};
|
||||
|
||||
export default responsiveObserve;
|
||||
listener(mql);
|
||||
});
|
||||
},
|
||||
responsiveMap,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
import type { ValidateStatus } from '../form/FormItem';
|
||||
import classNames from './classNames';
|
||||
|
||||
const InputStatuses = ['warning', 'error', ''] as const;
|
||||
|
||||
export type InputStatus = (typeof InputStatuses)[number];
|
||||
|
||||
export function getStatusClassNames(
|
||||
prefixCls: string,
|
||||
status?: ValidateStatus,
|
||||
hasFeedback?: boolean,
|
||||
) {
|
||||
return classNames({
|
||||
[`${prefixCls}-status-success`]: status === 'success',
|
||||
[`${prefixCls}-status-warning`]: status === 'warning',
|
||||
[`${prefixCls}-status-error`]: status === 'error',
|
||||
[`${prefixCls}-status-validating`]: status === 'validating',
|
||||
[`${prefixCls}-has-feedback`]: hasFeedback,
|
||||
});
|
||||
}
|
||||
|
||||
export const getMergedStatus = (contextStatus?: ValidateStatus, customStatus?: InputStatus) =>
|
||||
customStatus || contextStatus;
|
@ -1,42 +0,0 @@
|
||||
import getScrollBarSize from './getScrollBarSize';
|
||||
import setStyle from './setStyle';
|
||||
|
||||
function isBodyOverflowing() {
|
||||
return (
|
||||
document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
window.innerWidth > document.body.offsetWidth
|
||||
);
|
||||
}
|
||||
|
||||
let cacheStyle = {};
|
||||
|
||||
export default (close?: boolean) => {
|
||||
if (!isBodyOverflowing() && !close) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/19729
|
||||
const scrollingEffectClassName = 'ant-scrolling-effect';
|
||||
const scrollingEffectClassNameReg = new RegExp(`${scrollingEffectClassName}`, 'g');
|
||||
const bodyClassName = document.body.className;
|
||||
|
||||
if (close) {
|
||||
if (!scrollingEffectClassNameReg.test(bodyClassName)) return;
|
||||
setStyle(cacheStyle);
|
||||
cacheStyle = {};
|
||||
document.body.className = bodyClassName.replace(scrollingEffectClassNameReg, '').trim();
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollBarSize = getScrollBarSize();
|
||||
if (scrollBarSize) {
|
||||
cacheStyle = setStyle({
|
||||
position: 'relative',
|
||||
width: `calc(100% - ${scrollBarSize}px)`,
|
||||
});
|
||||
if (!scrollingEffectClassNameReg.test(bodyClassName)) {
|
||||
const addClassName = `${bodyClassName} ${scrollingEffectClassName}`;
|
||||
document.body.className = addClassName.trim();
|
||||
}
|
||||
}
|
||||
};
|
@ -1,47 +1,29 @@
|
||||
import raf from './raf';
|
||||
|
||||
export default function throttleByAnimationFrame(fn: (...args: any[]) => void) {
|
||||
let requestId: number;
|
||||
type throttledFn = (...args: any[]) => void;
|
||||
|
||||
const later = (args: any[]) => () => {
|
||||
type throttledCancelFn = { cancel: () => void };
|
||||
|
||||
function throttleByAnimationFrame<T extends any[]>(fn: (...args: T) => void) {
|
||||
let requestId: number | null;
|
||||
|
||||
const later = (args: T) => () => {
|
||||
requestId = null;
|
||||
fn(...args);
|
||||
};
|
||||
|
||||
const throttled = (...args: any[]) => {
|
||||
const throttled: throttledFn & throttledCancelFn = (...args: T) => {
|
||||
if (requestId == null) {
|
||||
requestId = raf(later(args));
|
||||
}
|
||||
};
|
||||
|
||||
(throttled as any).cancel = () => raf.cancel(requestId!);
|
||||
throttled.cancel = () => {
|
||||
raf.cancel(requestId!);
|
||||
requestId = null;
|
||||
};
|
||||
|
||||
return throttled;
|
||||
}
|
||||
|
||||
export function throttleByAnimationFrameDecorator() {
|
||||
// eslint-disable-next-line func-names
|
||||
return function (target: any, key: string, descriptor: any) {
|
||||
const fn = descriptor.value;
|
||||
let definingProperty = false;
|
||||
return {
|
||||
configurable: true,
|
||||
get() {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (definingProperty || this === target.prototype || this.hasOwnProperty(key)) {
|
||||
return fn;
|
||||
}
|
||||
|
||||
const boundFn = throttleByAnimationFrame(fn.bind(this));
|
||||
definingProperty = true;
|
||||
Object.defineProperty(this, key, {
|
||||
value: boundFn,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
definingProperty = false;
|
||||
return boundFn;
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
export default throttleByAnimationFrame;
|
||||
|
@ -0,0 +1,17 @@
|
||||
export const groupKeysMap = (keys: string[]) => {
|
||||
const map = new Map<string, number>();
|
||||
keys.forEach((key, index) => {
|
||||
map.set(key, index);
|
||||
});
|
||||
return map;
|
||||
};
|
||||
|
||||
export const groupDisabledKeysMap = <RecordType extends any[]>(dataSource: RecordType) => {
|
||||
const map = new Map<string, number>();
|
||||
dataSource.forEach(({ disabled, key }, index) => {
|
||||
if (disabled) {
|
||||
map.set(key, index);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
import warning, { resetWarned } from '../vc-util/warning';
|
||||
|
||||
export { resetWarned };
|
||||
|
||||
export default (valid, component, message = '') => {
|
||||
warning(valid, `[antdv: ${component}] ${message}`);
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import vcWarning, { resetWarned } from '../vc-util/warning';
|
||||
|
||||
export { resetWarned };
|
||||
export function noop() {}
|
||||
|
||||
type Warning = (valid: boolean, component: string, message?: string) => void;
|
||||
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
let warning: Warning = noop;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warning = (valid, component, message) => {
|
||||
vcWarning(valid, `[ant-design-vue: ${component}] ${message}`);
|
||||
|
||||
// StrictMode will inject console which will not throw warning in React 17.
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
resetWarned();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default warning;
|
@ -1,178 +0,0 @@
|
||||
import { nextTick, defineComponent, getCurrentInstance, onMounted, onBeforeUnmount } from 'vue';
|
||||
import TransitionEvents from './css-animation/Event';
|
||||
import raf from './raf';
|
||||
import { findDOMNode } from './props-util';
|
||||
import useConfigInject from './hooks/useConfigInject';
|
||||
let styleForPesudo: HTMLStyleElement;
|
||||
|
||||
// Where el is the DOM element you'd like to test for visibility
|
||||
function isHidden(element: HTMLElement) {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return false;
|
||||
}
|
||||
return !element || element.offsetParent === null;
|
||||
}
|
||||
function isNotGrey(color: string) {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\.\d]*)?\)/);
|
||||
if (match && match[1] && match[2] && match[3]) {
|
||||
return !(match[1] === match[2] && match[2] === match[3]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
export default defineComponent({
|
||||
compatConfig: { MODE: 3 },
|
||||
name: 'Wave',
|
||||
props: {
|
||||
insertExtraNode: Boolean,
|
||||
disabled: Boolean,
|
||||
},
|
||||
setup(props, { slots, expose }) {
|
||||
const instance = getCurrentInstance();
|
||||
const { csp, prefixCls } = useConfigInject('', props);
|
||||
expose({
|
||||
csp,
|
||||
});
|
||||
let eventIns = null;
|
||||
let clickWaveTimeoutId = null;
|
||||
let animationStartId = null;
|
||||
let animationStart = false;
|
||||
let extraNode = null;
|
||||
let isUnmounted = false;
|
||||
const onTransitionStart = e => {
|
||||
if (isUnmounted) return;
|
||||
|
||||
const node = findDOMNode(instance);
|
||||
if (!e || e.target !== node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!animationStart) {
|
||||
resetEffect(node);
|
||||
}
|
||||
};
|
||||
const onTransitionEnd = (e: any) => {
|
||||
if (!e || e.animationName !== 'fadeEffect') {
|
||||
return;
|
||||
}
|
||||
resetEffect(e.target);
|
||||
};
|
||||
const getAttributeName = () => {
|
||||
const { insertExtraNode } = props;
|
||||
return insertExtraNode
|
||||
? `${prefixCls.value}-click-animating`
|
||||
: `${prefixCls.value}-click-animating-without-extra-node`;
|
||||
};
|
||||
const onClick = (node: HTMLElement, waveColor: string) => {
|
||||
const { insertExtraNode, disabled } = props;
|
||||
if (disabled || !node || isHidden(node) || node.className.indexOf('-leave') >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
extraNode = document.createElement('div');
|
||||
extraNode.className = `${prefixCls.value}-click-animating-node`;
|
||||
const attributeName = getAttributeName();
|
||||
node.removeAttribute(attributeName);
|
||||
node.setAttribute(attributeName, 'true');
|
||||
// Not white or transparent or grey
|
||||
styleForPesudo = styleForPesudo || document.createElement('style');
|
||||
if (
|
||||
waveColor &&
|
||||
waveColor !== '#ffffff' &&
|
||||
waveColor !== 'rgb(255, 255, 255)' &&
|
||||
isNotGrey(waveColor) &&
|
||||
!/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color
|
||||
waveColor !== 'transparent'
|
||||
) {
|
||||
// Add nonce if CSP exist
|
||||
if (csp.value?.nonce) {
|
||||
styleForPesudo.nonce = csp.value.nonce;
|
||||
}
|
||||
extraNode.style.borderColor = waveColor;
|
||||
styleForPesudo.innerHTML = `
|
||||
[${prefixCls.value}-click-animating-without-extra-node='true']::after, .${prefixCls.value}-click-animating-node {
|
||||
--antd-wave-shadow-color: ${waveColor};
|
||||
}`;
|
||||
if (!document.body.contains(styleForPesudo)) {
|
||||
document.body.appendChild(styleForPesudo);
|
||||
}
|
||||
}
|
||||
if (insertExtraNode) {
|
||||
node.appendChild(extraNode);
|
||||
}
|
||||
TransitionEvents.addStartEventListener(node, onTransitionStart);
|
||||
TransitionEvents.addEndEventListener(node, onTransitionEnd);
|
||||
};
|
||||
const resetEffect = (node: HTMLElement) => {
|
||||
if (!node || node === extraNode || !(node instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
const { insertExtraNode } = props;
|
||||
const attributeName = getAttributeName();
|
||||
node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466
|
||||
if (styleForPesudo) {
|
||||
styleForPesudo.innerHTML = '';
|
||||
}
|
||||
if (insertExtraNode && extraNode && node.contains(extraNode)) {
|
||||
node.removeChild(extraNode);
|
||||
}
|
||||
TransitionEvents.removeStartEventListener(node, onTransitionStart);
|
||||
TransitionEvents.removeEndEventListener(node, onTransitionEnd);
|
||||
};
|
||||
const bindAnimationEvent = (node: HTMLElement) => {
|
||||
if (
|
||||
!node ||
|
||||
!node.getAttribute ||
|
||||
node.getAttribute('disabled') ||
|
||||
node.className.indexOf('disabled') >= 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const newClick = (e: MouseEvent) => {
|
||||
// Fix radio button click twice
|
||||
if ((e.target as any).tagName === 'INPUT' || isHidden(e.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
resetEffect(node);
|
||||
// Get wave color from target
|
||||
const waveColor =
|
||||
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
|
||||
getComputedStyle(node).getPropertyValue('border-color') ||
|
||||
getComputedStyle(node).getPropertyValue('background-color');
|
||||
clickWaveTimeoutId = setTimeout(() => onClick(node, waveColor), 0);
|
||||
raf.cancel(animationStartId);
|
||||
animationStart = true;
|
||||
|
||||
// Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this.
|
||||
animationStartId = raf(() => {
|
||||
animationStart = false;
|
||||
}, 10);
|
||||
};
|
||||
node.addEventListener('click', newClick, true);
|
||||
return {
|
||||
cancel: () => {
|
||||
node.removeEventListener('click', newClick, true);
|
||||
},
|
||||
};
|
||||
};
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const node = findDOMNode(instance);
|
||||
if (node.nodeType !== 1) {
|
||||
return;
|
||||
}
|
||||
eventIns = bindAnimationEvent(node);
|
||||
});
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (eventIns) {
|
||||
eventIns.cancel();
|
||||
}
|
||||
clearTimeout(clickWaveTimeoutId);
|
||||
isUnmounted = true;
|
||||
});
|
||||
return () => {
|
||||
return slots.default?.()[0];
|
||||
};
|
||||
},
|
||||
});
|
@ -0,0 +1,164 @@
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, Transition, render, defineComponent, shallowRef } from 'vue';
|
||||
import useState from '../hooks/useState';
|
||||
import { objectType } from '../type';
|
||||
import { getTargetWaveColor } from './util';
|
||||
import wrapperRaf from '../raf';
|
||||
function validateNum(value: number) {
|
||||
return Number.isNaN(value) ? 0 : value;
|
||||
}
|
||||
|
||||
export interface WaveEffectProps {
|
||||
className: string;
|
||||
target: HTMLElement;
|
||||
}
|
||||
|
||||
const WaveEffect = defineComponent({
|
||||
props: {
|
||||
target: objectType<HTMLElement>(),
|
||||
className: String,
|
||||
},
|
||||
setup(props) {
|
||||
const divRef = shallowRef<HTMLDivElement | null>(null);
|
||||
|
||||
const [color, setWaveColor] = useState<string | null>(null);
|
||||
const [borderRadius, setBorderRadius] = useState<number[]>([]);
|
||||
const [left, setLeft] = useState(0);
|
||||
const [top, setTop] = useState(0);
|
||||
const [width, setWidth] = useState(0);
|
||||
const [height, setHeight] = useState(0);
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
function syncPos() {
|
||||
const { target } = props;
|
||||
const nodeStyle = getComputedStyle(target);
|
||||
|
||||
// Get wave color from target
|
||||
setWaveColor(getTargetWaveColor(target));
|
||||
|
||||
const isStatic = nodeStyle.position === 'static';
|
||||
|
||||
// Rect
|
||||
const { borderLeftWidth, borderTopWidth } = nodeStyle;
|
||||
setLeft(isStatic ? target.offsetLeft : validateNum(-parseFloat(borderLeftWidth)));
|
||||
setTop(isStatic ? target.offsetTop : validateNum(-parseFloat(borderTopWidth)));
|
||||
setWidth(target.offsetWidth);
|
||||
setHeight(target.offsetHeight);
|
||||
|
||||
// Get border radius
|
||||
const {
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
} = nodeStyle;
|
||||
|
||||
setBorderRadius(
|
||||
[
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
].map(radius => validateNum(parseFloat(radius))),
|
||||
);
|
||||
}
|
||||
// Add resize observer to follow size
|
||||
let resizeObserver: ResizeObserver;
|
||||
let rafId: number;
|
||||
let timeoutId: any;
|
||||
const clear = () => {
|
||||
clearTimeout(timeoutId);
|
||||
wrapperRaf.cancel(rafId);
|
||||
resizeObserver?.disconnect();
|
||||
};
|
||||
const removeDom = () => {
|
||||
const holder = divRef.value?.parentElement;
|
||||
if (holder) {
|
||||
render(null, holder);
|
||||
if (holder.parentElement) {
|
||||
holder.parentElement.removeChild(holder);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
clear();
|
||||
timeoutId = setTimeout(() => {
|
||||
removeDom();
|
||||
}, 5000);
|
||||
const { target } = props;
|
||||
if (target) {
|
||||
// We need delay to check position here
|
||||
// since UI may change after click
|
||||
rafId = wrapperRaf(() => {
|
||||
syncPos();
|
||||
|
||||
setEnabled(true);
|
||||
});
|
||||
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
resizeObserver = new ResizeObserver(syncPos);
|
||||
|
||||
resizeObserver.observe(target);
|
||||
}
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
clear();
|
||||
});
|
||||
|
||||
const onTransitionend = (e: TransitionEvent) => {
|
||||
if (e.propertyName === 'opacity') {
|
||||
removeDom();
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
if (!enabled.value) {
|
||||
return null;
|
||||
}
|
||||
const waveStyle = {
|
||||
left: `${left.value}px`,
|
||||
top: `${top.value}px`,
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
borderRadius: borderRadius.value.map(radius => `${radius}px`).join(' '),
|
||||
} as CSSProperties & {
|
||||
[name: string]: number | string;
|
||||
};
|
||||
|
||||
if (color) {
|
||||
waveStyle['--wave-color'] = color.value as string;
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
name="wave-motion"
|
||||
appearFromClass="wave-motion-appear"
|
||||
appearActiveClass="wave-motion-appear"
|
||||
appearToClass="wave-motion-appear wave-motion-appear-active"
|
||||
>
|
||||
<div
|
||||
ref={divRef}
|
||||
class={props.className}
|
||||
style={waveStyle}
|
||||
onTransitionend={onTransitionend}
|
||||
/>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function showWaveEffect(node: HTMLElement, className: string) {
|
||||
// Create holder
|
||||
const holder = document.createElement('div');
|
||||
holder.style.position = 'absolute';
|
||||
holder.style.left = `0px`;
|
||||
holder.style.top = `0px`;
|
||||
node?.insertBefore(holder, node?.firstChild);
|
||||
|
||||
render(<WaveEffect target={node} className={className} />, holder);
|
||||
}
|
||||
|
||||
export default showWaveEffect;
|
@ -0,0 +1,96 @@
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import useConfigInject from '../../config-provider/hooks/useConfigInject';
|
||||
import isVisible from '../../vc-util/Dom/isVisible';
|
||||
import classNames from '../classNames';
|
||||
import { findDOMNode } from '../props-util';
|
||||
import useStyle from './style';
|
||||
import useWave from './useWave';
|
||||
|
||||
export interface WaveProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
compatConfig: { MODE: 3 },
|
||||
name: 'Wave',
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const instance = getCurrentInstance();
|
||||
const { prefixCls } = useConfigInject('wave', props);
|
||||
|
||||
// ============================== Style ===============================
|
||||
const [, hashId] = useStyle(prefixCls);
|
||||
|
||||
// =============================== Wave ===============================
|
||||
const showWave = useWave(
|
||||
instance,
|
||||
computed(() => classNames(prefixCls.value, hashId.value)),
|
||||
);
|
||||
let onClick: (e: MouseEvent) => void;
|
||||
const clear = () => {
|
||||
const node = findDOMNode(instance);
|
||||
node.removeEventListener('click', onClick, true);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
watch(
|
||||
() => props.disabled,
|
||||
() => {
|
||||
clear();
|
||||
nextTick(() => {
|
||||
const node = findDOMNode(instance);
|
||||
|
||||
if (!node || node.nodeType !== 1 || props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Click handler
|
||||
const onClick = (e: MouseEvent) => {
|
||||
// Fix radio button click twice
|
||||
if (
|
||||
(e.target as HTMLElement).tagName === 'INPUT' ||
|
||||
!isVisible(e.target as HTMLElement) ||
|
||||
// No need wave
|
||||
!node.getAttribute ||
|
||||
node.getAttribute('disabled') ||
|
||||
(node as HTMLInputElement).disabled ||
|
||||
node.className.includes('disabled') ||
|
||||
node.className.includes('-leave')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
showWave();
|
||||
};
|
||||
|
||||
// Bind events
|
||||
node.addEventListener('click', onClick, true);
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
flush: 'post',
|
||||
},
|
||||
);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
clear();
|
||||
});
|
||||
|
||||
return () => {
|
||||
// ============================== Render ==============================
|
||||
const children = slots.default?.()[0];
|
||||
return children;
|
||||
};
|
||||
},
|
||||
});
|
@ -0,0 +1,38 @@
|
||||
import { genComponentStyleHook } from '../../theme/internal';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ComponentToken {}
|
||||
|
||||
export type WaveToken = FullToken<'Wave'>;
|
||||
|
||||
const genWaveStyle: GenerateStyle<WaveToken> = token => {
|
||||
const { componentCls, colorPrimary } = token;
|
||||
return {
|
||||
[componentCls]: {
|
||||
position: 'absolute',
|
||||
background: 'transparent',
|
||||
pointerEvents: 'none',
|
||||
boxSizing: 'border-box',
|
||||
color: `var(--wave-color, ${colorPrimary})`,
|
||||
|
||||
boxShadow: `0 0 0 0 currentcolor`,
|
||||
opacity: 0.2,
|
||||
|
||||
// =================== Motion ===================
|
||||
'&.wave-motion-appear': {
|
||||
transition: [
|
||||
`box-shadow 0.4s ${token.motionEaseOutCirc}`,
|
||||
`opacity 2s ${token.motionEaseOutCirc}`,
|
||||
].join(','),
|
||||
|
||||
'&-active': {
|
||||
boxShadow: `0 0 0 6px currentcolor`,
|
||||
opacity: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default genComponentStyleHook('Wave', token => [genWaveStyle(token)]);
|
@ -0,0 +1,16 @@
|
||||
import type { ComponentInternalInstance, Ref } from 'vue';
|
||||
import { findDOMNode } from '../props-util';
|
||||
import showWaveEffect from './WaveEffect';
|
||||
|
||||
export default function useWave(
|
||||
instance: ComponentInternalInstance | null,
|
||||
className: Ref<string>,
|
||||
): VoidFunction {
|
||||
function showWave() {
|
||||
const node = findDOMNode(instance);
|
||||
|
||||
showWaveEffect(node, className.value);
|
||||
}
|
||||
|
||||
return showWave;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
export function isNotGrey(color: string) {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\d.]*)?\)/);
|
||||
if (match && match[1] && match[2] && match[3]) {
|
||||
return !(match[1] === match[2] && match[2] === match[3]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isValidWaveColor(color: string) {
|
||||
return (
|
||||
color &&
|
||||
color !== '#fff' &&
|
||||
color !== '#ffffff' &&
|
||||
color !== 'rgb(255, 255, 255)' &&
|
||||
color !== 'rgba(255, 255, 255, 1)' &&
|
||||
isNotGrey(color) &&
|
||||
!/rgba\((?:\d*, ){3}0\)/.test(color) && // any transparent rgba color
|
||||
color !== 'transparent'
|
||||
);
|
||||
}
|
||||
|
||||
export function getTargetWaveColor(node: HTMLElement) {
|
||||
const { borderTopColor, borderColor, backgroundColor } = getComputedStyle(node);
|
||||
if (isValidWaveColor(borderTopColor)) {
|
||||
return borderTopColor;
|
||||
}
|
||||
if (isValidWaveColor(borderColor)) {
|
||||
return borderColor;
|
||||
}
|
||||
if (isValidWaveColor(backgroundColor)) {
|
||||
return backgroundColor;
|
||||
}
|
||||
return null;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
@import '../../style/themes/index';
|
||||
|
||||
.@{ant-prefix}-affix {
|
||||
position: fixed;
|
||||
z-index: @zindex-affix;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import type { CSSObject } from '../../_util/cssinjs';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
|
||||
interface AffixToken extends FullToken<'Affix'> {
|
||||
zIndexPopup: number;
|
||||
}
|
||||
|
||||
// ============================== Shared ==============================
|
||||
const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => {
|
||||
const { componentCls } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
position: 'fixed',
|
||||
zIndex: token.zIndexPopup,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genComponentStyleHook('Affix', token => {
|
||||
const affixToken = mergeToken<AffixToken>(token, {
|
||||
zIndexPopup: token.zIndexBase + 10,
|
||||
});
|
||||
return [genSharedAffixStyle(affixToken)];
|
||||
});
|
@ -1,2 +0,0 @@
|
||||
import '../../style/index.less';
|
||||
import './index.less';
|
@ -0,0 +1,57 @@
|
||||
<docs>
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 操作
|
||||
en-US: Action
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
可以在右上角自定义操作项。
|
||||
|
||||
## en-US
|
||||
|
||||
Custom action.
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-alert message="Success Tips" type="success" show-icon closable>
|
||||
<template #action>
|
||||
<a-button size="small" type="text">UNDO</a-button>
|
||||
</template>
|
||||
</a-alert>
|
||||
<a-alert
|
||||
message="Error Text"
|
||||
show-icon
|
||||
description="Error Description Error Description Error Description Error Description"
|
||||
type="error"
|
||||
>
|
||||
<template #action>
|
||||
<a-button size="small" danger>Detail</a-button>
|
||||
</template>
|
||||
</a-alert>
|
||||
<a-alert message="Warning Text" type="warning" closable>
|
||||
<template #action>
|
||||
<a-space>
|
||||
<a-button size="small" type="ghost">Done</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-alert>
|
||||
<a-alert
|
||||
message="Info Text"
|
||||
description="Info Description Info Description Info Description Info Description"
|
||||
type="info"
|
||||
closable
|
||||
>
|
||||
<template #action>
|
||||
<a-space direction="vertical">
|
||||
<a-button size="small" type="primary">Accept</a-button>
|
||||
<a-button size="small" danger type="ghost">Decline</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-space>
|
||||
</template>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue