mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-11-29 18:48:32 +08:00
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>
This commit is contained in:
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, './es'))) {
|
||||
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'es', 'style', 'reset.css'));
|
||||
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'es', 'version', 'token.json'));
|
||||
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'es', 'version', 'token-meta.json'));
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.join(__dirname, './lib'))) {
|
||||
// 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.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'));
|
||||
}
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'lib', 'style', 'components.less'),
|
||||
componentsLessContent,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'dist', 'reset.css'));
|
||||
}
|
||||
}
|
||||
if(options.compact){
|
||||
themeVar = {
|
||||
...themeVar,
|
||||
...compactThemeSingle
|
||||
}
|
||||
}
|
||||
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,
|
||||
};
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -59,6 +59,7 @@ jspm_packages/
|
||||
dist
|
||||
lib
|
||||
es
|
||||
/locale
|
||||
_site
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
@ -78,5 +79,8 @@ report.html
|
||||
|
||||
site/src/router/demoRoutes.js
|
||||
|
||||
components/version/version.ts
|
||||
components/version/version.tsx
|
||||
components/version/token.json
|
||||
components/version/token-meta.json
|
||||
~component-api.json
|
||||
|
9
.jest.js
9
.jest.js
@ -18,6 +18,7 @@ function getTestRegex(libDir) {
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
setupFiles: ['./tests/setup.js'],
|
||||
setupFilesAfterEnv: ['./tests/setupAfterEnv.ts'],
|
||||
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'vue', 'md', 'jpg'],
|
||||
modulePathIgnorePatterns: ['/_site/'],
|
||||
testPathIgnorePatterns: testPathIgnorePatterns,
|
||||
@ -30,23 +31,19 @@ module.exports = {
|
||||
testRegex: getTestRegex(libDir),
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$/': '<rootDir>/$1',
|
||||
'ant-design-vue$/': '<rootDir>/components/index.ts',
|
||||
'ant-design-vue/es/': '<rootDir>/components',
|
||||
'^ant-design-vue$': '<rootDir>/components/index',
|
||||
'^ant-design-vue/es/(.*)$': '<rootDir>/components/$1',
|
||||
},
|
||||
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
|
||||
collectCoverage: process.env.COVERAGE === 'true',
|
||||
collectCoverageFrom: [
|
||||
'components/**/*.{js,jsx,vue}',
|
||||
'!components/*/style/index.{js,jsx}',
|
||||
'!components/style/*.{js,jsx}',
|
||||
'!components/*/locale/*.{js,jsx}',
|
||||
'!components/*/__tests__/**/type.{js,jsx}',
|
||||
'!components/vc-*/**/*',
|
||||
'!components/*/demo/**/*',
|
||||
'!components/_util/**/*',
|
||||
'!components/align/**/*',
|
||||
'!components/trigger/**/*',
|
||||
'!components/style.js',
|
||||
'!**/node_modules/**',
|
||||
],
|
||||
testEnvironment: 'jsdom',
|
||||
|
@ -18,7 +18,6 @@ yarn-error.log
|
||||
.editorconfig
|
||||
.eslintignore
|
||||
**/*.yml
|
||||
components/style/color/*.less
|
||||
**/assets
|
||||
.gitattributes
|
||||
.stylelintrc
|
||||
|
@ -4,13 +4,40 @@
|
||||
"stylelint-config-rational-order",
|
||||
"stylelint-config-prettier"
|
||||
],
|
||||
"plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"],
|
||||
"customSyntax": "postcss-less",
|
||||
"plugins": ["stylelint-declaration-block-no-ignored-properties"],
|
||||
"rules": {
|
||||
"comment-empty-line-before": null,
|
||||
"function-name-case": ["lower", { "ignoreFunctions": ["/colorPalette/"] }],
|
||||
"no-invalid-double-slash-comments": null,
|
||||
"no-descending-specificity": null,
|
||||
"declaration-empty-line-before": null
|
||||
},
|
||||
"ignoreFiles": ["components/style/color/{bezierEasing,colorPalette,tinyColor}.less"]
|
||||
"function-name-case": ["lower"],
|
||||
"function-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreFunctions": [
|
||||
"fade",
|
||||
"fadeout",
|
||||
"tint",
|
||||
"darken",
|
||||
"ceil",
|
||||
"fadein",
|
||||
"floor",
|
||||
"unit",
|
||||
"shade",
|
||||
"lighten",
|
||||
"percentage",
|
||||
"-"
|
||||
]
|
||||
}
|
||||
],
|
||||
"import-notation": null,
|
||||
"no-descending-specificity": null,
|
||||
"no-invalid-position-at-import-rule": null,
|
||||
"declaration-empty-line-before": null,
|
||||
"keyframes-name-pattern": null,
|
||||
"custom-property-pattern": null,
|
||||
"number-max-precision": 8,
|
||||
"alpha-value-notation": "number",
|
||||
"color-function-notation": "legacy",
|
||||
"selector-class-pattern": null,
|
||||
"selector-id-pattern": null,
|
||||
"selector-not-notation": null
|
||||
}
|
||||
}
|
||||
|
1357
CHANGELOG.en-US.md
1357
CHANGELOG.en-US.md
File diff suppressed because it is too large
Load Diff
1356
CHANGELOG.zh-CN.md
1356
CHANGELOG.zh-CN.md
File diff suppressed because it is too large
Load Diff
@ -66,6 +66,7 @@ $ yarn add ant-design-vue
|
||||
| [vue-cli-plugin-ant-design](https://github.com/vueComponent/vue-cli-plugin-ant-design) | 使用 vue-cli3 快速使用 ant-design-vue 组件库 |
|
||||
| [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | 在 DOM 模板中,您可以使用 ant-design-vue 组件的自定义事件(camelCase) |
|
||||
| [@formily/antdv](https://github.com/formilyjs/antdv) | 这是一个结合了 Formily 和 ant-design-vue 的组件库 |
|
||||
| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | ant-design-vue 的 nuxt 模块扩展 |
|
||||
|
||||
## 问答
|
||||
|
||||
|
@ -28,7 +28,7 @@ English | [简体中文](./README-zh_CN.md)
|
||||
|
||||
## Environment Support
|
||||
|
||||
- Modern browsers. v1.x support Internet Explorer 9+ (with [polyfills](https://www.antdv.com/docs/vue/getting-started/#Compatibility))
|
||||
- Modern browsers. v1.x support Internet Explorer 9+ (with [polyfills](https://www.antdv.com/docs/vue/getting-started/#compatibility))
|
||||
- Server-side Rendering
|
||||
- Support Vue 2 & Vue 3
|
||||
- [Electron](https://electronjs.org/)
|
||||
@ -66,6 +66,7 @@ If you are in a bad network environment,you can try other registries and tools
|
||||
| [vue-cli-plugin-ant-design](https://github.com/vueComponent/vue-cli-plugin-ant-design) | Vue-cli 3 plugin to add ant-design-vue |
|
||||
| [vue-dash-event](https://github.com/vueComponent/vue-dash-event) | The library function, implemented in the DOM template, can use the custom event of the ant-design-vue component (camelCase) |
|
||||
| [@formily/antdv](https://github.com/formilyjs/antdv) | The Library with Formily and ant-design-vue |
|
||||
| [@ant-design-vue/nuxt](https://github.com/vueComponent/ant-design-vue-nuxt) | A nuxt module for ant-design-vue |
|
||||
|
||||
## Donation
|
||||
|
||||
|
@ -91,7 +91,7 @@ export function formatter(
|
||||
!tableTitle.includes('()')
|
||||
) {
|
||||
const childTag: VueTag = {
|
||||
name: getComponentName(tableTitle.replaceAll('.', '').replaceAll('/', ''), tagPrefix),
|
||||
name: getComponentName(tableTitle.replace(/\.|\//g, ''), tagPrefix),
|
||||
slots: [],
|
||||
events: [],
|
||||
attributes: [],
|
||||
|
@ -7,6 +7,7 @@ import { outputFileSync, readFileSync } from 'fs-extra';
|
||||
import type { Options, VueTag } from './type';
|
||||
import { getComponentName, normalizePath, toKebabCase } from './utils';
|
||||
import { genVeturAttributes, genVeturTags } from './vetur';
|
||||
import { flatMap } from 'lodash';
|
||||
|
||||
async function readMarkdown(options: Options): Promise<Map<String, VueTag>> {
|
||||
const mdPaths = await glob(normalizePath(`${options.path}/**/*.md`));
|
||||
@ -22,7 +23,7 @@ async function readMarkdown(options: Options): Promise<Map<String, VueTag>> {
|
||||
})
|
||||
.filter(item => item) as VueTag[][];
|
||||
const tags: Map<String, VueTag> = new Map();
|
||||
data.flatMap(item => item).forEach(mergedTag => mergeTag(tags, mergedTag));
|
||||
flatMap(data, item => item).forEach(mergedTag => mergeTag(tags, mergedTag));
|
||||
return tags;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ function readLine(input: string) {
|
||||
function splitTableLine(line: string) {
|
||||
line = line.replace(/\\\|/g, 'JOIN');
|
||||
|
||||
const items = line.split('|').map(item => item.trim().replaceAll('JOIN', '|'));
|
||||
const items = line.split('|').map(item => item.trim().replace(/JOIN/g, '|'));
|
||||
|
||||
// remove pipe character on both sides
|
||||
items.pop();
|
||||
|
@ -20,7 +20,8 @@ module.exports = function (modules) {
|
||||
resolve('@babel/plugin-transform-runtime'),
|
||||
{
|
||||
useESModules: modules === false,
|
||||
version: '^7.10.4',
|
||||
version:
|
||||
require(`${process.cwd()}/package.json`).dependencies['@babel/runtime'] || '^7.10.4',
|
||||
},
|
||||
],
|
||||
// resolve('babel-plugin-inline-import-data-uri'),
|
||||
|
@ -150,36 +150,6 @@ function getWebpackConfig(modules) {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: ['autoprefixer'],
|
||||
},
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
lessOptions: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Images
|
||||
{
|
||||
test: svgRegex,
|
||||
@ -200,7 +170,7 @@ function getWebpackConfig(modules) {
|
||||
new webpack.BannerPlugin(`
|
||||
${pkg.name} v${pkg.version}
|
||||
|
||||
Copyright 2017-present, ant-design-vue.
|
||||
Copyright 2017-present, Ant Design Vue.
|
||||
All rights reserved.
|
||||
`),
|
||||
new WebpackBar({
|
||||
@ -215,7 +185,7 @@ All rights reserved.
|
||||
};
|
||||
|
||||
if (process.env.RUN_ENV === 'PRODUCTION') {
|
||||
const entry = ['./index'];
|
||||
let entry = ['./index'];
|
||||
config.externals = [
|
||||
{
|
||||
vue: {
|
||||
@ -223,11 +193,13 @@ All rights reserved.
|
||||
commonjs2: 'vue',
|
||||
commonjs: 'vue',
|
||||
amd: 'vue',
|
||||
module: 'vue',
|
||||
},
|
||||
},
|
||||
];
|
||||
config.output.library = distFileBaseName;
|
||||
config.output.libraryTarget = 'umd';
|
||||
config.output.globalObject = 'this';
|
||||
config.optimization = {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
@ -238,7 +210,6 @@ All rights reserved.
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
// Development
|
||||
const uncompressedConfig = merge({}, config, {
|
||||
entry: {
|
||||
|
@ -5,7 +5,6 @@ const getBabelCommonConfig = require('./getBabelCommonConfig');
|
||||
const merge2 = require('merge2');
|
||||
const { execSync } = require('child_process');
|
||||
const through2 = require('through2');
|
||||
const transformLess = require('./transformLess');
|
||||
const webpack = require('webpack');
|
||||
const babel = require('gulp-babel');
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
@ -27,15 +26,22 @@ const compareVersions = require('compare-versions');
|
||||
const getTSCommonConfig = require('./getTSCommonConfig');
|
||||
const replaceLib = require('./replaceLib');
|
||||
const sortApiTable = require('./sortApiTable');
|
||||
const { glob } = require('glob');
|
||||
|
||||
const packageJson = require(getProjectPath('package.json'));
|
||||
const tsDefaultReporter = ts.reporter.defaultReporter();
|
||||
const cwd = process.cwd();
|
||||
const libDir = getProjectPath('lib');
|
||||
const esDir = getProjectPath('es');
|
||||
const localeDir = getProjectPath('locale');
|
||||
|
||||
const tsConfig = getTSCommonConfig();
|
||||
|
||||
// FIXME: hard code, not find typescript can modify the path resolution
|
||||
const localeDts = `import type { Locale } from '../lib/locale-provider';
|
||||
declare const localeValues: Locale;
|
||||
export default localeValues;`;
|
||||
|
||||
function dist(done) {
|
||||
rimraf.sync(path.join(cwd, 'dist'));
|
||||
process.env.RUN_ENV = 'PRODUCTION';
|
||||
@ -108,6 +114,11 @@ gulp.task('tsc', () =>
|
||||
),
|
||||
);
|
||||
|
||||
gulp.task('clean', () => {
|
||||
rimraf.sync(getProjectPath('_site'));
|
||||
rimraf.sync(getProjectPath('_data'));
|
||||
});
|
||||
|
||||
function babelify(js, modules) {
|
||||
const babelConfig = getBabelCommonConfig(modules);
|
||||
babelConfig.babelrc = false;
|
||||
@ -118,17 +129,7 @@ function babelify(js, modules) {
|
||||
const stream = js.pipe(babel(babelConfig)).pipe(
|
||||
through2.obj(function z(file, encoding, next) {
|
||||
this.push(file.clone());
|
||||
if (file.path.match(/\/style\/index\.(js|jsx|ts|tsx)$/)) {
|
||||
const content = file.contents.toString(encoding);
|
||||
file.contents = Buffer.from(
|
||||
content
|
||||
.replace(/\/style\/?'/g, "/style/css'")
|
||||
.replace(/\/style\/?"/g, '/style/css"')
|
||||
.replace(/\.less/g, '.css'),
|
||||
);
|
||||
file.path = file.path.replace(/index\.(js|jsx|ts|tsx)$/, 'css.js');
|
||||
this.push(file);
|
||||
} else if (modules !== false) {
|
||||
if (modules !== false) {
|
||||
const content = file.contents.toString(encoding);
|
||||
file.contents = Buffer.from(
|
||||
content
|
||||
@ -144,47 +145,9 @@ function babelify(js, modules) {
|
||||
}
|
||||
|
||||
function compile(modules) {
|
||||
const { compile: { transformTSFile, transformFile, includeLessFile = [] } = {} } = getConfig();
|
||||
const { compile: { transformTSFile, transformFile } = {} } = getConfig();
|
||||
rimraf.sync(modules !== false ? libDir : esDir);
|
||||
|
||||
// =============================== LESS ===============================
|
||||
const less = gulp
|
||||
.src(['components/**/*.less'])
|
||||
.pipe(
|
||||
through2.obj(function (file, encoding, next) {
|
||||
// Replace content
|
||||
const cloneFile = file.clone();
|
||||
const content = file.contents.toString().replace(/^\uFEFF/, '');
|
||||
|
||||
cloneFile.contents = Buffer.from(content);
|
||||
|
||||
// Clone for css here since `this.push` will modify file.path
|
||||
const cloneCssFile = cloneFile.clone();
|
||||
|
||||
this.push(cloneFile);
|
||||
|
||||
// Transform less file
|
||||
if (
|
||||
file.path.match(/(\/|\\)style(\/|\\)index\.less$/) ||
|
||||
file.path.match(/(\/|\\)style(\/|\\)v2-compatible-reset\.less$/) ||
|
||||
includeLessFile.some(regex => file.path.match(regex))
|
||||
) {
|
||||
transformLess(cloneCssFile.contents.toString(), cloneCssFile.path)
|
||||
.then(css => {
|
||||
cloneCssFile.contents = Buffer.from(css);
|
||||
cloneCssFile.path = cloneCssFile.path.replace(/\.less$/, '.css');
|
||||
this.push(cloneCssFile);
|
||||
next();
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.pipe(gulp.dest(modules === false ? esDir : libDir));
|
||||
const assets = gulp
|
||||
.src(['components/**/*.@(png|svg)'])
|
||||
.pipe(gulp.dest(modules === false ? esDir : libDir));
|
||||
@ -259,7 +222,26 @@ function compile(modules) {
|
||||
tsResult.on('end', check);
|
||||
const tsFilesStream = babelify(tsResult.js, modules);
|
||||
const tsd = tsResult.dts.pipe(gulp.dest(modules === false ? esDir : libDir));
|
||||
return merge2([less, tsFilesStream, tsd, assets, transformFileStream].filter(s => s));
|
||||
return merge2([tsFilesStream, tsd, assets, transformFileStream].filter(s => s));
|
||||
}
|
||||
|
||||
function generateLocale() {
|
||||
if (!fs.existsSync(localeDir)) {
|
||||
fs.mkdirSync(localeDir);
|
||||
}
|
||||
|
||||
const localeFiles = glob.sync('components/locale/*.ts?(x)');
|
||||
localeFiles.forEach(item => {
|
||||
const match = item.match(/components\/locale\/(.*)\.tsx?/);
|
||||
if (match) {
|
||||
const locale = match[1];
|
||||
fs.writeFileSync(
|
||||
path.join(localeDir, `${locale}.js`),
|
||||
`module.exports = require('../lib/locale/${locale}');`,
|
||||
);
|
||||
fs.writeFileSync(path.join(localeDir, `${locale}.d.ts`), localeDts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function tag() {
|
||||
@ -395,7 +377,10 @@ gulp.task('compile-with-es', done => {
|
||||
|
||||
gulp.task('compile-with-lib', done => {
|
||||
console.log('[Parallel] Compile to js...');
|
||||
compile().on('finish', done);
|
||||
compile().on('finish', () => {
|
||||
generateLocale();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('compile-finalize', done => {
|
||||
|
@ -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,10 +1,12 @@
|
||||
import type { ExtractPropTypes, PropType } from 'vue';
|
||||
import { onMounted, ref, defineComponent, onBeforeUnmount } from 'vue';
|
||||
import { shallowRef, onMounted, defineComponent, onBeforeUnmount } from 'vue';
|
||||
import Button from '../button';
|
||||
import type { ButtonProps } from '../button';
|
||||
import type { LegacyButtonType } from '../button/buttonTypes';
|
||||
import { convertLegacyProps } from '../button/buttonTypes';
|
||||
import useDestroyed from './hooks/useDestroyed';
|
||||
import { objectType } from './type';
|
||||
import { findDOMNode } from './props-util';
|
||||
|
||||
const actionButtonProps = {
|
||||
type: {
|
||||
@ -14,15 +16,15 @@ const actionButtonProps = {
|
||||
close: Function,
|
||||
autofocus: Boolean,
|
||||
prefixCls: String,
|
||||
buttonProps: Object as PropType<ButtonProps>,
|
||||
buttonProps: objectType<ButtonProps>(),
|
||||
emitEvent: Boolean,
|
||||
quitOnNullishReturnValue: Boolean,
|
||||
};
|
||||
|
||||
export type ActionButtonProps = ExtractPropTypes<typeof actionButtonProps>;
|
||||
|
||||
function isThenable(thing?: PromiseLike<any>): boolean {
|
||||
return !!(thing && !!thing.then);
|
||||
function isThenable<T>(thing?: PromiseLike<T>): boolean {
|
||||
return !!(thing && thing.then);
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@ -30,22 +32,25 @@ export default defineComponent({
|
||||
name: 'ActionButton',
|
||||
props: actionButtonProps,
|
||||
setup(props, { slots }) {
|
||||
const clickedRef = ref<boolean>(false);
|
||||
const buttonRef = ref();
|
||||
const loading = ref(false);
|
||||
const clickedRef = shallowRef<boolean>(false);
|
||||
const buttonRef = shallowRef();
|
||||
const loading = shallowRef(false);
|
||||
let timeoutId: any;
|
||||
const isDestroyed = useDestroyed();
|
||||
onMounted(() => {
|
||||
if (props.autofocus) {
|
||||
timeoutId = setTimeout(() => buttonRef.value.$el?.focus());
|
||||
timeoutId = setTimeout(() => findDOMNode(buttonRef.value)?.focus?.());
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
const onInternalClose = (...args: any[]) => {
|
||||
props.close?.(...args);
|
||||
};
|
||||
|
||||
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
|
||||
const { close } = props;
|
||||
if (!isThenable(returnValueOfOnOk)) {
|
||||
return;
|
||||
}
|
||||
@ -55,48 +60,46 @@ export default defineComponent({
|
||||
if (!isDestroyed.value) {
|
||||
loading.value = false;
|
||||
}
|
||||
close(...args);
|
||||
onInternalClose(...args);
|
||||
clickedRef.value = false;
|
||||
},
|
||||
(e: Error) => {
|
||||
// Emit error when catch promise reject
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
// See: https://github.com/ant-design/ant-design/issues/6183
|
||||
if (!isDestroyed.value) {
|
||||
loading.value = false;
|
||||
}
|
||||
clickedRef.value = false;
|
||||
return Promise.reject(e);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const onClick = (e: MouseEvent) => {
|
||||
const { actionFn, close = () => {} } = props;
|
||||
const { actionFn } = props;
|
||||
if (clickedRef.value) {
|
||||
return;
|
||||
}
|
||||
clickedRef.value = true;
|
||||
if (!actionFn) {
|
||||
close();
|
||||
onInternalClose();
|
||||
return;
|
||||
}
|
||||
let returnValueOfOnOk;
|
||||
let returnValueOfOnOk: PromiseLike<any>;
|
||||
if (props.emitEvent) {
|
||||
returnValueOfOnOk = actionFn(e);
|
||||
if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
|
||||
clickedRef.value = false;
|
||||
close(e);
|
||||
onInternalClose(e);
|
||||
return;
|
||||
}
|
||||
} else if (actionFn.length) {
|
||||
returnValueOfOnOk = actionFn(close);
|
||||
returnValueOfOnOk = actionFn(props.close);
|
||||
// https://github.com/ant-design/ant-design/issues/23358
|
||||
clickedRef.value = false;
|
||||
} else {
|
||||
returnValueOfOnOk = actionFn();
|
||||
if (!returnValueOfOnOk) {
|
||||
close();
|
||||
onInternalClose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defineComponent, ref, withDirectives } from 'vue';
|
||||
import { defineComponent, shallowRef, withDirectives } from 'vue';
|
||||
import antInput from './antInputDirective';
|
||||
import PropTypes from './vue-types';
|
||||
const BaseInput = defineComponent({
|
||||
@ -8,7 +8,7 @@ const BaseInput = defineComponent({
|
||||
},
|
||||
emits: ['change', 'input'],
|
||||
setup(_p, { emit }) {
|
||||
const inputRef = ref(null);
|
||||
const inputRef = shallowRef(null);
|
||||
const handleChange = (e: Event) => {
|
||||
const { composing } = e.target as any;
|
||||
if ((e as any).isComposing || composing) {
|
||||
|
@ -1,20 +1,20 @@
|
||||
import PropTypes from './vue-types';
|
||||
import switchScrollingEffect from './switchScrollingEffect';
|
||||
import setStyle from './setStyle';
|
||||
import Portal from './Portal';
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
onUpdated,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
computed,
|
||||
} from 'vue';
|
||||
import canUseDom from './canUseDom';
|
||||
import ScrollLocker from '../vc-util/Dom/scrollLocker';
|
||||
import raf from './raf';
|
||||
import { booleanType } from './type';
|
||||
import useScrollLocker from './hooks/useScrollLocker';
|
||||
|
||||
let openCount = 0;
|
||||
const supportDom = canUseDom();
|
||||
@ -24,17 +24,13 @@ export function getOpenCount() {
|
||||
return process.env.NODE_ENV === 'test' ? openCount : 0;
|
||||
}
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/19340
|
||||
// https://github.com/ant-design/ant-design/issues/19332
|
||||
let cacheOverflow = {};
|
||||
|
||||
const getParent = (getContainer: GetContainer) => {
|
||||
if (!supportDom) {
|
||||
return null;
|
||||
}
|
||||
if (getContainer) {
|
||||
if (typeof getContainer === 'string') {
|
||||
return document.querySelectorAll(getContainer)[0];
|
||||
return document.querySelectorAll(getContainer)[0] as HTMLElement;
|
||||
}
|
||||
if (typeof getContainer === 'function') {
|
||||
return getContainer();
|
||||
@ -57,24 +53,25 @@ export default defineComponent({
|
||||
forceRender: { type: Boolean, default: undefined },
|
||||
getContainer: PropTypes.any,
|
||||
visible: { type: Boolean, default: undefined },
|
||||
autoLock: booleanType(),
|
||||
didUpdate: Function,
|
||||
},
|
||||
|
||||
setup(props, { slots }) {
|
||||
const container = ref<HTMLElement>();
|
||||
const componentRef = ref();
|
||||
const rafId = ref<number>();
|
||||
const scrollLocker = new ScrollLocker({
|
||||
container: getParent(props.getContainer) as HTMLElement,
|
||||
});
|
||||
const container = shallowRef<HTMLElement>();
|
||||
const componentRef = shallowRef();
|
||||
const rafId = shallowRef<number>();
|
||||
|
||||
const removeCurrentContainer = () => {
|
||||
// Portal will remove from `parentNode`.
|
||||
// Let's handle this again to avoid refactor issue.
|
||||
container.value?.parentNode?.removeChild(container.value);
|
||||
container.value = null;
|
||||
};
|
||||
let parent: HTMLElement = null;
|
||||
const attachToParent = (force = false) => {
|
||||
if (force || (container.value && !container.value.parentNode)) {
|
||||
const parent = getParent(props.getContainer);
|
||||
parent = getParent(props.getContainer);
|
||||
if (parent) {
|
||||
parent.appendChild(container.value);
|
||||
return true;
|
||||
@ -86,13 +83,13 @@ export default defineComponent({
|
||||
return true;
|
||||
};
|
||||
// attachToParent();
|
||||
|
||||
const defaultContainer = document.createElement('div');
|
||||
const getContainer = () => {
|
||||
if (!supportDom) {
|
||||
return null;
|
||||
}
|
||||
if (!container.value) {
|
||||
container.value = document.createElement('div');
|
||||
container.value = defaultContainer;
|
||||
attachToParent(true);
|
||||
}
|
||||
setWrapperClassName();
|
||||
@ -108,43 +105,35 @@ export default defineComponent({
|
||||
setWrapperClassName();
|
||||
attachToParent();
|
||||
});
|
||||
/**
|
||||
* Enhance ./switchScrollingEffect
|
||||
* 1. Simulate document body scroll bar with
|
||||
* 2. Record body has overflow style and recover when all of PortalWrapper invisible
|
||||
* 3. Disable body scroll when PortalWrapper has open
|
||||
*
|
||||
* @memberof PortalWrapper
|
||||
*/
|
||||
const switchScrolling = () => {
|
||||
if (openCount === 1 && !Object.keys(cacheOverflow).length) {
|
||||
switchScrollingEffect();
|
||||
// Must be set after switchScrollingEffect
|
||||
cacheOverflow = setStyle({
|
||||
overflow: 'hidden',
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'hidden',
|
||||
});
|
||||
} else if (!openCount) {
|
||||
setStyle(cacheOverflow);
|
||||
cacheOverflow = {};
|
||||
switchScrollingEffect(true);
|
||||
}
|
||||
};
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
useScrollLocker(
|
||||
computed(() => {
|
||||
return (
|
||||
props.autoLock &&
|
||||
props.visible &&
|
||||
canUseDom() &&
|
||||
(container.value === document.body || container.value === defaultContainer)
|
||||
);
|
||||
}),
|
||||
);
|
||||
onMounted(() => {
|
||||
let init = false;
|
||||
watch(
|
||||
[() => props.visible, () => props.getContainer],
|
||||
([visible, getContainer], [prevVisible, prevGetContainer]) => {
|
||||
// Update count
|
||||
if (supportDom && getParent(props.getContainer) === document.body) {
|
||||
if (supportDom) {
|
||||
parent = getParent(props.getContainer);
|
||||
if (parent === document.body) {
|
||||
if (visible && !prevVisible) {
|
||||
openCount += 1;
|
||||
} else if (init) {
|
||||
openCount -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (init) {
|
||||
// Clean up container if needed
|
||||
@ -157,17 +146,6 @@ export default defineComponent({
|
||||
) {
|
||||
removeCurrentContainer();
|
||||
}
|
||||
// updateScrollLocker
|
||||
if (
|
||||
visible &&
|
||||
visible !== prevVisible &&
|
||||
supportDom &&
|
||||
getParent(getContainer) !== scrollLocker.getContainer()
|
||||
) {
|
||||
scrollLocker.reLock({
|
||||
container: getParent(getContainer) as HTMLElement,
|
||||
});
|
||||
}
|
||||
}
|
||||
init = true;
|
||||
},
|
||||
@ -184,30 +162,27 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const { visible, getContainer } = props;
|
||||
if (supportDom && getParent(getContainer) === document.body) {
|
||||
const { visible } = props;
|
||||
if (supportDom && parent === document.body) {
|
||||
// 离开时不会 render, 导到离开时数值不变,改用 func 。。
|
||||
openCount = visible && openCount ? openCount - 1 : openCount;
|
||||
}
|
||||
removeCurrentContainer();
|
||||
raf.cancel(rafId.value);
|
||||
});
|
||||
|
||||
return () => {
|
||||
const { forceRender, visible } = props;
|
||||
let portal = null;
|
||||
const childProps = {
|
||||
getOpenCount: () => openCount,
|
||||
getContainer,
|
||||
switchScrollingEffect: switchScrolling,
|
||||
scrollLocker,
|
||||
};
|
||||
|
||||
if (forceRender || visible || componentRef.value) {
|
||||
portal = (
|
||||
<Portal
|
||||
getContainer={getContainer}
|
||||
ref={componentRef}
|
||||
didUpdate={props.didUpdate}
|
||||
v-slots={{ default: () => slots.default?.(childProps) }}
|
||||
></Portal>
|
||||
);
|
||||
|
@ -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);
|
||||
}
|
||||
|
22
components/_util/createContext.ts
Normal file
22
components/_util/createContext.ts
Normal file
@ -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;
|
25
components/_util/cssinjs/Cache.ts
Normal file
25
components/_util/cssinjs/Cache.ts
Normal file
@ -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;
|
19
components/_util/cssinjs/Keyframes.ts
Normal file
19
components/_util/cssinjs/Keyframes.ts
Normal file
@ -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;
|
157
components/_util/cssinjs/StyleContext.tsx
Normal file
157
components/_util/cssinjs/StyleContext.tsx
Normal file
@ -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,
|
||||
};
|
128
components/_util/cssinjs/hooks/useCacheToken.tsx
Normal file
128
components/_util/cssinjs/hooks/useCacheToken.tsx
Normal file
@ -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;
|
||||
}
|
58
components/_util/cssinjs/hooks/useGlobalCache.tsx
Normal file
58
components/_util/cssinjs/hooks/useGlobalCache.tsx
Normal file
@ -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;
|
||||
}
|
33
components/_util/cssinjs/hooks/useHMR.ts
Normal file
33
components/_util/cssinjs/hooks/useHMR.ts
Normal file
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
417
components/_util/cssinjs/hooks/useStyleRegister.tsx
Normal file
417
components/_util/cssinjs/hooks/useStyleRegister.tsx
Normal file
@ -0,0 +1,417 @@
|
||||
import hash from '@emotion/hash';
|
||||
import type * as CSS from 'csstype';
|
||||
// @ts-ignore
|
||||
import unitless from '@emotion/unitless';
|
||||
import { compile, serialize, stringify } from 'stylis';
|
||||
import type { Theme, Transformer } from '..';
|
||||
import type Cache from '../Cache';
|
||||
import type Keyframes from '../Keyframes';
|
||||
import type { Linter } from '../linters';
|
||||
import { contentQuotesLinter, hashedAnimationLinter } from '../linters';
|
||||
import type { HashPriority } from '../StyleContext';
|
||||
import {
|
||||
useStyleInject,
|
||||
ATTR_DEV_CACHE_PATH,
|
||||
ATTR_MARK,
|
||||
ATTR_TOKEN,
|
||||
CSS_IN_JS_INSTANCE,
|
||||
CSS_IN_JS_INSTANCE_ID,
|
||||
} from '../StyleContext';
|
||||
import { supportLayer } from '../util';
|
||||
import useGlobalCache from './useGlobalCache';
|
||||
import canUseDom from '../../canUseDom';
|
||||
import { removeCSS, updateCSS } from '../../../vc-util/Dom/dynamicCSS';
|
||||
import type { Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import type { VueNode } from '../../type';
|
||||
|
||||
const isClientSide = canUseDom();
|
||||
|
||||
const SKIP_CHECK = '_skip_check_';
|
||||
|
||||
export type CSSProperties = Omit<CSS.PropertiesFallback<number | string>, 'animationName'> & {
|
||||
animationName?: CSS.PropertiesFallback<number | string>['animationName'] | Keyframes;
|
||||
};
|
||||
|
||||
export type CSSPropertiesWithMultiValues = {
|
||||
[K in keyof CSSProperties]:
|
||||
| CSSProperties[K]
|
||||
| Extract<CSSProperties[K], string>[]
|
||||
| {
|
||||
[SKIP_CHECK]: boolean;
|
||||
value: CSSProperties[K] | Extract<CSSProperties[K], string>[];
|
||||
};
|
||||
};
|
||||
|
||||
export type CSSPseudos = { [K in CSS.Pseudos]?: CSSObject };
|
||||
|
||||
type ArrayCSSInterpolation = CSSInterpolation[];
|
||||
|
||||
export type InterpolationPrimitive = null | undefined | boolean | number | string | CSSObject;
|
||||
|
||||
export type CSSInterpolation = InterpolationPrimitive | ArrayCSSInterpolation | Keyframes;
|
||||
|
||||
export type CSSOthersObject = Record<string, CSSInterpolation>;
|
||||
|
||||
export interface CSSObject extends CSSPropertiesWithMultiValues, CSSPseudos, CSSOthersObject {}
|
||||
|
||||
// ============================================================================
|
||||
// == Parser ==
|
||||
// ============================================================================
|
||||
// Preprocessor style content to browser support one
|
||||
export function normalizeStyle(styleStr: string) {
|
||||
const serialized = serialize(compile(styleStr), stringify);
|
||||
return serialized.replace(/\{%%%\:[^;];}/g, ';');
|
||||
}
|
||||
|
||||
function isCompoundCSSProperty(value: CSSObject[string]) {
|
||||
return typeof value === 'object' && value && SKIP_CHECK in value;
|
||||
}
|
||||
|
||||
// 注入 hash 值
|
||||
function injectSelectorHash(key: string, hashId: string, hashPriority?: HashPriority) {
|
||||
if (!hashId) {
|
||||
return key;
|
||||
}
|
||||
|
||||
const hashClassName = `.${hashId}`;
|
||||
const hashSelector = hashPriority === 'low' ? `:where(${hashClassName})` : hashClassName;
|
||||
|
||||
// 注入 hashId
|
||||
const keys = key.split(',').map(k => {
|
||||
const fullPath = k.trim().split(/\s+/);
|
||||
|
||||
// 如果 Selector 第一个是 HTML Element,那我们就插到它的后面。反之,就插到最前面。
|
||||
let firstPath = fullPath[0] || '';
|
||||
const htmlElement = firstPath.match(/^\w+/)?.[0] || '';
|
||||
|
||||
firstPath = `${htmlElement}${hashSelector}${firstPath.slice(htmlElement.length)}`;
|
||||
|
||||
return [firstPath, ...fullPath.slice(1)].join(' ');
|
||||
});
|
||||
return keys.join(',');
|
||||
}
|
||||
|
||||
export interface ParseConfig {
|
||||
hashId?: string;
|
||||
hashPriority?: HashPriority;
|
||||
layer?: string;
|
||||
path?: string;
|
||||
transformers?: Transformer[];
|
||||
linters?: Linter[];
|
||||
}
|
||||
|
||||
export interface ParseInfo {
|
||||
root?: boolean;
|
||||
injectHash?: boolean;
|
||||
parentSelectors: string[];
|
||||
}
|
||||
|
||||
// Global effect style will mount once and not removed
|
||||
// The effect will not save in SSR cache (e.g. keyframes)
|
||||
const globalEffectStyleKeys = new Set();
|
||||
|
||||
/**
|
||||
* @private Test only. Clear the global effect style keys.
|
||||
*/
|
||||
export const _cf =
|
||||
process.env.NODE_ENV !== 'production' ? () => globalEffectStyleKeys.clear() : undefined;
|
||||
|
||||
// Parse CSSObject to style content
|
||||
export const parseStyle = (
|
||||
interpolation: CSSInterpolation,
|
||||
config: ParseConfig = {},
|
||||
{ root, injectHash, parentSelectors }: ParseInfo = {
|
||||
root: true,
|
||||
parentSelectors: [],
|
||||
},
|
||||
): [
|
||||
parsedStr: string,
|
||||
// Style content which should be unique on all of the style (e.g. Keyframes).
|
||||
// Firefox will flick with same animation name when exist multiple same keyframes.
|
||||
effectStyle: Record<string, string>,
|
||||
] => {
|
||||
const { hashId, layer, path, hashPriority, transformers = [], linters = [] } = config;
|
||||
let styleStr = '';
|
||||
let effectStyle: Record<string, string> = {};
|
||||
|
||||
function parseKeyframes(keyframes: Keyframes) {
|
||||
const animationName = keyframes.getName(hashId);
|
||||
if (!effectStyle[animationName]) {
|
||||
const [parsedStr] = parseStyle(keyframes.style, config, {
|
||||
root: false,
|
||||
parentSelectors,
|
||||
});
|
||||
|
||||
effectStyle[animationName] = `@keyframes ${keyframes.getName(hashId)}${parsedStr}`;
|
||||
}
|
||||
}
|
||||
|
||||
function flattenList(list: ArrayCSSInterpolation, fullList: CSSObject[] = []) {
|
||||
list.forEach(item => {
|
||||
if (Array.isArray(item)) {
|
||||
flattenList(item, fullList);
|
||||
} else if (item) {
|
||||
fullList.push(item as CSSObject);
|
||||
}
|
||||
});
|
||||
|
||||
return fullList;
|
||||
}
|
||||
|
||||
const flattenStyleList = flattenList(
|
||||
Array.isArray(interpolation) ? interpolation : [interpolation],
|
||||
);
|
||||
|
||||
flattenStyleList.forEach(originStyle => {
|
||||
// Only root level can use raw string
|
||||
const style: CSSObject = typeof originStyle === 'string' && !root ? {} : originStyle;
|
||||
|
||||
if (typeof style === 'string') {
|
||||
styleStr += `${style}\n`;
|
||||
} else if ((style as any)._keyframe) {
|
||||
// Keyframe
|
||||
parseKeyframes(style as unknown as Keyframes);
|
||||
} else {
|
||||
const mergedStyle = transformers.reduce((prev, trans) => trans?.visit?.(prev) || prev, style);
|
||||
|
||||
// Normal CSSObject
|
||||
Object.keys(mergedStyle).forEach(key => {
|
||||
const value = mergedStyle[key];
|
||||
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
value &&
|
||||
(key !== 'animationName' || !(value as Keyframes)._keyframe) &&
|
||||
!isCompoundCSSProperty(value)
|
||||
) {
|
||||
let subInjectHash = false;
|
||||
|
||||
// 当成嵌套对象来处理
|
||||
let mergedKey = key.trim();
|
||||
// Whether treat child as root. In most case it is false.
|
||||
let nextRoot = false;
|
||||
|
||||
// 拆分多个选择器
|
||||
if ((root || injectHash) && hashId) {
|
||||
if (mergedKey.startsWith('@')) {
|
||||
// 略过媒体查询,交给子节点继续插入 hashId
|
||||
subInjectHash = true;
|
||||
} else {
|
||||
// 注入 hashId
|
||||
mergedKey = injectSelectorHash(key, hashId, hashPriority);
|
||||
}
|
||||
} else if (root && !hashId && (mergedKey === '&' || mergedKey === '')) {
|
||||
// In case of `{ '&': { a: { color: 'red' } } }` or `{ '': { a: { color: 'red' } } }` without hashId,
|
||||
// we will get `&{a:{color:red;}}` or `{a:{color:red;}}` string for stylis to compile.
|
||||
// But it does not conform to stylis syntax,
|
||||
// and finally we will get `{color:red;}` as css, which is wrong.
|
||||
// So we need to remove key in root, and treat child `{ a: { color: 'red' } }` as root.
|
||||
mergedKey = '';
|
||||
nextRoot = true;
|
||||
}
|
||||
|
||||
const [parsedStr, childEffectStyle] = parseStyle(value as any, config, {
|
||||
root: nextRoot,
|
||||
injectHash: subInjectHash,
|
||||
parentSelectors: [...parentSelectors, mergedKey],
|
||||
});
|
||||
|
||||
effectStyle = {
|
||||
...effectStyle,
|
||||
...childEffectStyle,
|
||||
};
|
||||
|
||||
styleStr += `${mergedKey}${parsedStr}`;
|
||||
} else {
|
||||
const actualValue = (value as any)?.value ?? value;
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
(typeof value !== 'object' || !(value as any)?.[SKIP_CHECK])
|
||||
) {
|
||||
[contentQuotesLinter, hashedAnimationLinter, ...linters].forEach(linter =>
|
||||
linter(key, actualValue, { path, hashId, parentSelectors }),
|
||||
);
|
||||
}
|
||||
|
||||
// 如果是样式则直接插入
|
||||
const styleName = key.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
|
||||
|
||||
// Auto suffix with px
|
||||
let formatValue = actualValue;
|
||||
if (!unitless[key] && typeof formatValue === 'number' && formatValue !== 0) {
|
||||
formatValue = `${formatValue}px`;
|
||||
}
|
||||
|
||||
// handle animationName & Keyframe value
|
||||
if (key === 'animationName' && (value as Keyframes)?._keyframe) {
|
||||
parseKeyframes(value as Keyframes);
|
||||
formatValue = (value as Keyframes).getName(hashId);
|
||||
}
|
||||
|
||||
styleStr += `${styleName}:${formatValue};`;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!root) {
|
||||
styleStr = `{${styleStr}}`;
|
||||
} else if (layer && supportLayer()) {
|
||||
const layerCells = layer.split(',');
|
||||
const layerName = layerCells[layerCells.length - 1].trim();
|
||||
styleStr = `@layer ${layerName} {${styleStr}}`;
|
||||
|
||||
// Order of layer if needed
|
||||
if (layerCells.length > 1) {
|
||||
// zombieJ: stylis do not support layer order, so we need to handle it manually.
|
||||
styleStr = `@layer ${layer}{%%%:%}${styleStr}`;
|
||||
}
|
||||
}
|
||||
|
||||
return [styleStr, effectStyle];
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// == Register ==
|
||||
// ============================================================================
|
||||
function uniqueHash(path: (string | number)[], styleStr: string) {
|
||||
return hash(`${path.join('%')}${styleStr}`);
|
||||
}
|
||||
|
||||
// function Empty() {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Register a style to the global style sheet.
|
||||
*/
|
||||
export default function useStyleRegister(
|
||||
info: Ref<{
|
||||
theme: Theme<any, any>;
|
||||
token: any;
|
||||
path: string[];
|
||||
hashId?: string;
|
||||
layer?: string;
|
||||
}>,
|
||||
styleFn: () => CSSInterpolation,
|
||||
) {
|
||||
const styleContext = useStyleInject();
|
||||
|
||||
const tokenKey = computed(() => info.value.token._tokenKey as string);
|
||||
|
||||
const fullPath = computed(() => [tokenKey.value, ...info.value.path]);
|
||||
|
||||
// Check if need insert style
|
||||
let isMergedClientSide = isClientSide;
|
||||
if (process.env.NODE_ENV !== 'production' && styleContext.value.mock !== undefined) {
|
||||
isMergedClientSide = styleContext.value.mock === 'client';
|
||||
}
|
||||
|
||||
// const [cacheStyle[0], cacheStyle[1], cacheStyle[2]]
|
||||
useGlobalCache(
|
||||
'style',
|
||||
fullPath,
|
||||
// Create cache if needed
|
||||
() => {
|
||||
const styleObj = styleFn();
|
||||
const { hashPriority, container, transformers, linters } = styleContext.value;
|
||||
const { path, hashId, layer } = info.value;
|
||||
const [parsedStyle, effectStyle] = parseStyle(styleObj, {
|
||||
hashId,
|
||||
hashPriority,
|
||||
layer,
|
||||
path: path.join('-'),
|
||||
transformers,
|
||||
linters,
|
||||
});
|
||||
const styleStr = normalizeStyle(parsedStyle);
|
||||
const styleId = uniqueHash(fullPath.value, styleStr);
|
||||
|
||||
if (isMergedClientSide) {
|
||||
const style = updateCSS(styleStr, styleId, {
|
||||
mark: ATTR_MARK,
|
||||
prepend: 'queue',
|
||||
attachTo: container,
|
||||
});
|
||||
|
||||
(style as any)[CSS_IN_JS_INSTANCE] = CSS_IN_JS_INSTANCE_ID;
|
||||
|
||||
// Used for `useCacheToken` to remove on batch when token removed
|
||||
style.setAttribute(ATTR_TOKEN, tokenKey.value);
|
||||
|
||||
// Dev usage to find which cache path made this easily
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
style.setAttribute(ATTR_DEV_CACHE_PATH, fullPath.value.join('|'));
|
||||
}
|
||||
|
||||
// Inject client side effect style
|
||||
Object.keys(effectStyle).forEach(effectKey => {
|
||||
if (!globalEffectStyleKeys.has(effectKey)) {
|
||||
globalEffectStyleKeys.add(effectKey);
|
||||
|
||||
// Inject
|
||||
updateCSS(normalizeStyle(effectStyle[effectKey]), `_effect-${effectKey}`, {
|
||||
mark: ATTR_MARK,
|
||||
prepend: 'queue',
|
||||
attachTo: container,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return [styleStr, tokenKey.value, styleId];
|
||||
},
|
||||
// Remove cache if no need
|
||||
([, , styleId], fromHMR) => {
|
||||
if ((fromHMR || styleContext.value.autoClear) && isClientSide) {
|
||||
removeCSS(styleId, { mark: ATTR_MARK });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return (node: VueNode) => {
|
||||
return node;
|
||||
// let styleNode: VueNode;
|
||||
// if (!styleContext.ssrInline || isMergedClientSide || !styleContext.defaultCache) {
|
||||
// styleNode = <Empty />;
|
||||
// } else {
|
||||
// styleNode = (
|
||||
// <style
|
||||
// {...{
|
||||
// [ATTR_TOKEN]: cacheStyle.value[1],
|
||||
// [ATTR_MARK]: cacheStyle.value[2],
|
||||
// }}
|
||||
// innerHTML={cacheStyle.value[0]}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// {styleNode}
|
||||
// {node}
|
||||
// </>
|
||||
// );
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// == SSR ==
|
||||
// ============================================================================
|
||||
export function extractStyle(cache: Cache) {
|
||||
// prefix with `style` is used for `useStyleRegister` to cache style context
|
||||
const styleKeys = Array.from(cache.cache.keys()).filter(key => key.startsWith('style%'));
|
||||
|
||||
// const tokenStyles: Record<string, string[]> = {};
|
||||
|
||||
let styleText = '';
|
||||
|
||||
styleKeys.forEach(key => {
|
||||
const [styleStr, tokenKey, styleId]: [string, string, string] = cache.cache.get(key)![1];
|
||||
|
||||
styleText += `<style ${ATTR_TOKEN}="${tokenKey}" ${ATTR_MARK}="${styleId}">${styleStr}</style>`;
|
||||
});
|
||||
|
||||
return styleText;
|
||||
}
|
67
components/_util/cssinjs/index.ts
Normal file
67
components/_util/cssinjs/index.ts
Normal file
@ -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;
|
25
components/_util/cssinjs/linters/contentQuotesLinter.ts
Normal file
25
components/_util/cssinjs/linters/contentQuotesLinter.ts
Normal file
@ -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;
|
15
components/_util/cssinjs/linters/hashedAnimationLinter.ts
Normal file
15
components/_util/cssinjs/linters/hashedAnimationLinter.ts
Normal file
@ -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;
|
5
components/_util/cssinjs/linters/index.ts
Normal file
5
components/_util/cssinjs/linters/index.ts
Normal file
@ -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';
|
9
components/_util/cssinjs/linters/interface.ts
Normal file
9
components/_util/cssinjs/linters/interface.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface LinterInfo {
|
||||
path?: string;
|
||||
hashId?: string;
|
||||
parentSelectors: string[];
|
||||
}
|
||||
|
||||
export interface Linter {
|
||||
(key: string, value: string | number, info: LinterInfo): void;
|
||||
}
|
33
components/_util/cssinjs/linters/legacyNotSelectorLinter.ts
Normal file
33
components/_util/cssinjs/linters/legacyNotSelectorLinter.ts
Normal file
@ -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;
|
88
components/_util/cssinjs/linters/logicalPropertiesLinter.ts
Normal file
88
components/_util/cssinjs/linters/logicalPropertiesLinter.ts
Normal file
@ -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;
|
13
components/_util/cssinjs/linters/utils.ts
Normal file
13
components/_util/cssinjs/linters/utils.ts
Normal file
@ -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(' -> ')}` : ''
|
||||
}`,
|
||||
);
|
||||
}
|
38
components/_util/cssinjs/theme/Theme.ts
Normal file
38
components/_util/cssinjs/theme/Theme.ts
Normal file
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
135
components/_util/cssinjs/theme/ThemeCache.ts
Normal file
135
components/_util/cssinjs/theme/ThemeCache.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
26
components/_util/cssinjs/theme/createTheme.ts
Normal file
26
components/_util/cssinjs/theme/createTheme.ts
Normal file
@ -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)!;
|
||||
}
|
4
components/_util/cssinjs/theme/index.ts
Normal file
4
components/_util/cssinjs/theme/index.ts
Normal file
@ -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';
|
5
components/_util/cssinjs/theme/interface.ts
Normal file
5
components/_util/cssinjs/theme/interface.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type TokenType = object;
|
||||
export type DerivativeFunc<DesignToken extends TokenType, DerivativeToken extends TokenType> = (
|
||||
designToken: DesignToken,
|
||||
derivativeToken?: DerivativeToken,
|
||||
) => DerivativeToken;
|
5
components/_util/cssinjs/transformers/interface.ts
Normal file
5
components/_util/cssinjs/transformers/interface.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import type { CSSObject } from '..';
|
||||
|
||||
export interface Transformer {
|
||||
visit?: (cssObj: CSSObject) => CSSObject;
|
||||
}
|
162
components/_util/cssinjs/transformers/legacyLogicalProperties.ts
Normal file
162
components/_util/cssinjs/transformers/legacyLogicalProperties.ts
Normal file
@ -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;
|
68
components/_util/cssinjs/util.ts
Normal file
68
components/_util/cssinjs/util.ts
Normal file
@ -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!;
|
||||
}
|
21
components/_util/extendsObject.ts
Normal file
21
components/_util/extendsObject.ts
Normal file
@ -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;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export function isWindow(obj: any) {
|
||||
export function isWindow(obj: any): obj is Window {
|
||||
return obj !== null && obj !== undefined && obj === obj.window;
|
||||
}
|
||||
|
||||
@ -12,16 +12,22 @@ export default function getScroll(
|
||||
const method = top ? 'scrollTop' : 'scrollLeft';
|
||||
let result = 0;
|
||||
if (isWindow(target)) {
|
||||
result = (target as Window)[top ? 'pageYOffset' : 'pageXOffset'];
|
||||
result = target[top ? 'pageYOffset' : 'pageXOffset'];
|
||||
} else if (target instanceof Document) {
|
||||
result = target.documentElement[method];
|
||||
} else if (target instanceof HTMLElement) {
|
||||
result = target[method];
|
||||
} else if (target) {
|
||||
result = (target as HTMLElement)[method];
|
||||
// According to the type inference, the `target` is `never` type.
|
||||
// Since we configured the loose mode type checking, and supports mocking the target with such shape below::
|
||||
// `{ documentElement: { scrollLeft: 200, scrollTop: 400 } }`,
|
||||
// the program may falls into this branch.
|
||||
// Check the corresponding tests for details. Don't sure what is the real scenario this happens.
|
||||
result = target[method];
|
||||
}
|
||||
|
||||
if (target && !isWindow(target) && typeof result !== 'number') {
|
||||
result = ((target as HTMLElement).ownerDocument || (target as Document)).documentElement?.[
|
||||
method
|
||||
];
|
||||
result = ((target.ownerDocument ?? target) as any).documentElement?.[method];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ref, watch } from 'vue';
|
||||
import { shallowRef, watch } from 'vue';
|
||||
import type { MaybeComputedElementRef } from './unrefElement';
|
||||
import type { UseResizeObserverOptions } from './useResizeObserver';
|
||||
import { useResizeObserver } from './useResizeObserver';
|
||||
@ -23,8 +23,8 @@ export function useElementSize(
|
||||
options: UseResizeObserverOptions = {},
|
||||
) {
|
||||
const { box = 'content-box' } = options;
|
||||
const width = ref(initialSize.width);
|
||||
const height = ref(initialSize.height);
|
||||
const width = shallowRef(initialSize.width);
|
||||
const height = shallowRef(initialSize.height);
|
||||
|
||||
useResizeObserver(
|
||||
target,
|
||||
|
62
components/_util/hooks/_vueuse/useMutationObserver.ts
Normal file
62
components/_util/hooks/_vueuse/useMutationObserver.ts
Normal file
@ -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,9 +1,8 @@
|
||||
import { tryOnMounted } from './tryOnMounted';
|
||||
import type { Ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { shallowRef } from 'vue';
|
||||
|
||||
export function useSupported(callback: () => unknown, sync = false) {
|
||||
const isSupported = ref() as Ref<boolean>;
|
||||
const isSupported = shallowRef<boolean>();
|
||||
|
||||
const update = () => (isSupported.value = Boolean(callback()));
|
||||
|
||||
|
@ -1,19 +1,21 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { onMounted, onUnmounted, shallowRef } from 'vue';
|
||||
import type { ScreenMap } from '../../_util/responsiveObserve';
|
||||
import ResponsiveObserve from '../../_util/responsiveObserve';
|
||||
import useResponsiveObserve from '../../_util/responsiveObserve';
|
||||
|
||||
function useBreakpoint(): Ref<ScreenMap> {
|
||||
const screens = ref<ScreenMap>({});
|
||||
const screens = shallowRef<ScreenMap>({});
|
||||
let token = null;
|
||||
const responsiveObserve = useResponsiveObserve();
|
||||
|
||||
onMounted(() => {
|
||||
token = ResponsiveObserve.subscribe(supportScreens => {
|
||||
token = responsiveObserve.value.subscribe(supportScreens => {
|
||||
screens.value = supportScreens;
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
ResponsiveObserve.unsubscribe(token);
|
||||
responsiveObserve.value.unsubscribe(token);
|
||||
});
|
||||
|
||||
return screens;
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { onBeforeUnmount, shallowRef } from 'vue';
|
||||
|
||||
const useDestroyed = () => {
|
||||
const destroyed = ref(false);
|
||||
const destroyed = shallowRef(false);
|
||||
onBeforeUnmount(() => {
|
||||
destroyed.value = true;
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, shallowRef } from 'vue';
|
||||
import { detectFlexGapSupported } from '../styleChecker';
|
||||
|
||||
export default () => {
|
||||
const flexible = ref(false);
|
||||
const flexible = shallowRef(false);
|
||||
onMounted(() => {
|
||||
flexible.value = detectFlexGapSupported();
|
||||
});
|
||||
|
30
components/_util/hooks/useId.ts
Normal file
30
components/_util/hooks/useId.ts
Normal file
@ -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;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { onBeforeUnmount, shallowRef } from 'vue';
|
||||
import raf from '../raf';
|
||||
|
||||
export type Updater<State> = (prev: State) => State;
|
||||
@ -9,11 +9,11 @@ export type Updater<State> = (prev: State) => State;
|
||||
export function useLayoutState<State>(
|
||||
defaultState: State,
|
||||
): [Ref<State>, (updater: Updater<State>) => void] {
|
||||
const stateRef = ref(defaultState);
|
||||
const stateRef = shallowRef(defaultState);
|
||||
let tempState = stateRef.value;
|
||||
|
||||
let updateBatchRef = [];
|
||||
const rafRef = ref();
|
||||
const rafRef = shallowRef();
|
||||
function setFrameState(updater: Updater<State>) {
|
||||
raf.cancel(rafRef.value);
|
||||
updateBatchRef.push(updater);
|
||||
|
48
components/_util/hooks/useScrollLocker.ts
Normal file
48
components/_util/hooks/useScrollLocker.ts
Normal file
@ -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;
|
@ -71,6 +71,7 @@ const getSlots = ele => {
|
||||
return { ...slots, ...getScopedSlots(ele) };
|
||||
};
|
||||
|
||||
export const skipFlattenKey = Symbol('skipFlatten');
|
||||
const flattenChildren = (children = [], filterEmpty = true) => {
|
||||
const temp = Array.isArray(children) ? children : [children];
|
||||
const res = [];
|
||||
@ -78,7 +79,11 @@ const flattenChildren = (children = [], filterEmpty = true) => {
|
||||
if (Array.isArray(child)) {
|
||||
res.push(...flattenChildren(child, filterEmpty));
|
||||
} else if (child && child.type === Fragment) {
|
||||
if (child.key === skipFlattenKey) {
|
||||
res.push(child);
|
||||
} else {
|
||||
res.push(...flattenChildren(child.children, filterEmpty));
|
||||
}
|
||||
} else if (child && isVNode(child)) {
|
||||
if (filterEmpty && !isEmptyElement(child)) {
|
||||
res.push(child);
|
||||
|
@ -1,26 +1,35 @@
|
||||
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'];
|
||||
|
||||
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)',
|
||||
};
|
||||
|
||||
type SubscribeFunc = (screens: ScreenMap) => void;
|
||||
|
||||
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}`,
|
||||
});
|
||||
|
||||
export default function useResponsiveObserver() {
|
||||
const [, token] = useToken();
|
||||
|
||||
return computed(() => {
|
||||
const responsiveMap: BreakpointMap = getResponsiveMap(token.value);
|
||||
const subscribers = new Map<Number, SubscribeFunc>();
|
||||
let subUid = -1;
|
||||
let screens = {};
|
||||
|
||||
const responsiveObserve = {
|
||||
return {
|
||||
matchHandlers: {} as {
|
||||
[prop: string]: {
|
||||
mql: MediaQueryList;
|
||||
@ -39,8 +48,8 @@ const responsiveObserve = {
|
||||
func(screens);
|
||||
return subUid;
|
||||
},
|
||||
unsubscribe(token: number) {
|
||||
subscribers.delete(token);
|
||||
unsubscribe(paramToken: number) {
|
||||
subscribers.delete(paramToken);
|
||||
if (!subscribers.size) this.unregister();
|
||||
},
|
||||
unregister() {
|
||||
@ -70,6 +79,7 @@ const responsiveObserve = {
|
||||
listener(mql);
|
||||
});
|
||||
},
|
||||
responsiveMap,
|
||||
};
|
||||
|
||||
export default responsiveObserve;
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import raf from './raf';
|
||||
import getScroll, { isWindow } from './getScroll';
|
||||
import { easeInOutCubic } from './easings';
|
||||
import getScroll, { isWindow } from './getScroll';
|
||||
|
||||
interface ScrollToOptions {
|
||||
/** Scroll container, default as window */
|
||||
@ -23,8 +23,8 @@ export default function scrollTo(y: number, options: ScrollToOptions = {}) {
|
||||
const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration);
|
||||
if (isWindow(container)) {
|
||||
(container as Window).scrollTo(window.pageXOffset, nextScrollTop);
|
||||
} else if (container instanceof HTMLDocument || container.constructor.name === 'HTMLDocument') {
|
||||
(container as HTMLDocument).documentElement.scrollTop = nextScrollTop;
|
||||
} else if (container instanceof Document || container.constructor.name === 'HTMLDocument') {
|
||||
(container as Document).documentElement.scrollTop = nextScrollTop;
|
||||
} else {
|
||||
(container as HTMLElement).scrollTop = nextScrollTop;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
function shallowEqual(objA, objB, compare, compareContext) {
|
||||
function shallowEqual(objA: any, objB: any, compare?: any, compareContext?: any) {
|
||||
let ret = compare ? compare.call(compareContext, objA, objB) : void 0;
|
||||
|
||||
if (ret !== void 0) {
|
||||
@ -45,6 +45,6 @@ function shallowEqual(objA, objB, compare, compareContext) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export default function (value, other, customizer, thisArg) {
|
||||
return shallowEqual(toRaw(value), toRaw(other), customizer, thisArg);
|
||||
export default function (value: any, other: any) {
|
||||
return shallowEqual(toRaw(value), toRaw(other));
|
||||
}
|
23
components/_util/statusUtils.tsx
Normal file
23
components/_util/statusUtils.tsx
Normal file
@ -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;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { defineComponent, ref, onMounted } from 'vue';
|
||||
import { defineComponent, shallowRef, onMounted } from 'vue';
|
||||
/**
|
||||
* Wrap of sub component which need use as Button capacity (like Icon component).
|
||||
* This helps accessibility reader to tread as a interactive button to operation.
|
||||
@ -25,7 +25,7 @@ const TransButton = defineComponent({
|
||||
autofocus: { type: Boolean, default: undefined },
|
||||
},
|
||||
setup(props, { slots, emit, attrs, expose }) {
|
||||
const domRef = ref();
|
||||
const domRef = shallowRef();
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
const { keyCode } = event;
|
||||
if (keyCode === KeyCode.ENTER) {
|
||||
|
17
components/_util/transKeys.ts
Normal file
17
components/_util/transKeys.ts
Normal file
@ -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;
|
||||
};
|
@ -9,7 +9,7 @@ import { nextTick, Transition, TransitionGroup } from 'vue';
|
||||
import { tuple } from './type';
|
||||
|
||||
const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
|
||||
export type SelectCommonPlacement = typeof SelectPlacements[number];
|
||||
export type SelectCommonPlacement = (typeof SelectPlacements)[number];
|
||||
|
||||
const getTransitionDirection = (placement: SelectCommonPlacement | undefined) => {
|
||||
if (placement !== undefined && (placement === 'topLeft' || placement === 'topRight')) {
|
||||
@ -27,7 +27,7 @@ export const getTransitionProps = (transitionName: string, opt: TransitionProps
|
||||
// appearFromClass: `${transitionName}-appear ${transitionName}-appear-prepare`,
|
||||
// appearActiveClass: `antdv-base-transtion`,
|
||||
// appearToClass: `${transitionName}-appear ${transitionName}-appear-active`,
|
||||
enterFromClass: `${transitionName}-enter ${transitionName}-enter-prepare`,
|
||||
enterFromClass: `${transitionName}-enter ${transitionName}-enter-prepare ${transitionName}-enter-start`,
|
||||
enterActiveClass: `${transitionName}-enter ${transitionName}-enter-prepare`,
|
||||
enterToClass: `${transitionName}-enter ${transitionName}-enter-active`,
|
||||
leaveFromClass: ` ${transitionName}-leave`,
|
||||
|
@ -15,7 +15,7 @@ export type ElementOf<T> = T extends (infer E)[] ? E : T extends readonly (infer
|
||||
/**
|
||||
* https://github.com/Microsoft/TypeScript/issues/29729
|
||||
*/
|
||||
export type LiteralUnion<T extends U, U> = T | (U & {});
|
||||
export type LiteralUnion<T extends string> = T | (string & {});
|
||||
|
||||
export type Data = Record<string, unknown>;
|
||||
|
||||
@ -44,4 +44,49 @@ export const withInstall = <T>(comp: T) => {
|
||||
|
||||
export type MaybeRef<T> = T | Ref<T>;
|
||||
|
||||
export function eventType<T>() {
|
||||
return { type: [Function, Array] as PropType<T | T[]> };
|
||||
}
|
||||
|
||||
export function objectType<T = {}>(defaultVal?: T) {
|
||||
return { type: Object as PropType<T>, default: defaultVal as T };
|
||||
}
|
||||
|
||||
export function booleanType(defaultVal?: boolean) {
|
||||
return { type: Boolean, default: defaultVal as boolean };
|
||||
}
|
||||
|
||||
export function functionType<T = () => {}>(defaultVal?: T) {
|
||||
return { type: Function as PropType<T>, default: defaultVal as T };
|
||||
}
|
||||
|
||||
export function anyType<T = any>(defaultVal?: T, required?: boolean) {
|
||||
const type = { validator: () => true, default: defaultVal as T } as unknown;
|
||||
return required
|
||||
? (type as {
|
||||
type: PropType<T>;
|
||||
default: T;
|
||||
required: true;
|
||||
})
|
||||
: (type as {
|
||||
default: T;
|
||||
type: PropType<T>;
|
||||
});
|
||||
}
|
||||
export function vNodeType<T = VueNode>() {
|
||||
return { validator: () => true } as unknown as { type: PropType<T> };
|
||||
}
|
||||
|
||||
export function arrayType<T extends any[]>(defaultVal?: T) {
|
||||
return { type: Array as unknown as PropType<T>, default: defaultVal as T };
|
||||
}
|
||||
|
||||
export function stringType<T extends string = string>(defaultVal?: T) {
|
||||
return { type: String as unknown as PropType<T>, default: defaultVal as T };
|
||||
}
|
||||
|
||||
export function someType<T>(types?: any[], defaultVal?: T) {
|
||||
return types ? { type: types as PropType<T>, default: defaultVal as T } : anyType<T>(defaultVal);
|
||||
}
|
||||
|
||||
export type CustomSlotsType<T> = SlotsType<T>;
|
||||
|
@ -56,7 +56,7 @@ function resolvePropValue(options, props, key, value) {
|
||||
|
||||
export function getDataAndAriaProps(props) {
|
||||
return Object.keys(props).reduce((memo, key) => {
|
||||
if (key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-') {
|
||||
if (key.startsWith('data-') || key.startsWith('aria-')) {
|
||||
memo[key] = props[key];
|
||||
}
|
||||
return memo;
|
||||
@ -78,5 +78,24 @@ export function renderHelper<T = Record<string, any>>(
|
||||
}
|
||||
return v ?? defaultV;
|
||||
}
|
||||
export function wrapPromiseFn(openFn: (resolve: VoidFunction) => VoidFunction) {
|
||||
let closeFn: VoidFunction;
|
||||
|
||||
const closePromise = new Promise<boolean>(resolve => {
|
||||
closeFn = openFn(() => {
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
|
||||
const result: any = () => {
|
||||
closeFn?.();
|
||||
};
|
||||
|
||||
result.then = (filled: VoidFunction, rejected: VoidFunction) =>
|
||||
closePromise.then(filled, rejected);
|
||||
result.promise = closePromise;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export { isOn, cacheStringFunction, camelize, hyphenate, capitalize, resolvePropValue };
|
||||
|
@ -1,7 +0,0 @@
|
||||
import warning, { resetWarned } from '../vc-util/warning';
|
||||
|
||||
export { resetWarned };
|
||||
|
||||
export default (valid, component, message = '') => {
|
||||
warning(valid, `[antdv: ${component}] ${message}`);
|
||||
};
|
21
components/_util/warning.ts
Normal file
21
components/_util/warning.ts
Normal file
@ -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];
|
||||
};
|
||||
},
|
||||
});
|
164
components/_util/wave/WaveEffect.tsx
Normal file
164
components/_util/wave/WaveEffect.tsx
Normal file
@ -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;
|
96
components/_util/wave/index.tsx
Normal file
96
components/_util/wave/index.tsx
Normal file
@ -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;
|
||||
};
|
||||
},
|
||||
});
|
38
components/_util/wave/style.ts
Normal file
38
components/_util/wave/style.ts
Normal file
@ -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)]);
|
16
components/_util/wave/useWave.ts
Normal file
16
components/_util/wave/useWave.ts
Normal file
@ -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;
|
||||
}
|
35
components/_util/wave/util.ts
Normal file
35
components/_util/wave/util.ts
Normal file
@ -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;
|
||||
}
|
@ -2,12 +2,14 @@
|
||||
|
||||
exports[`renders ./components/affix/demo/basic.vue correctly 1`] = `
|
||||
<div>
|
||||
<!---->
|
||||
<div class=""><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>Affix top</span>
|
||||
</button></div>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<!---->
|
||||
<div class=""><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>Affix bottom</span>
|
||||
</button></div>
|
||||
@ -16,7 +18,8 @@ exports[`renders ./components/affix/demo/basic.vue correctly 1`] = `
|
||||
|
||||
exports[`renders ./components/affix/demo/on-change.vue correctly 1`] = `
|
||||
<div>
|
||||
<div class=""><button class="ant-btn" type="button">
|
||||
<!---->
|
||||
<div class=""><button class="ant-btn ant-btn-default" type="button">
|
||||
<!----><span>120px to affix top</span>
|
||||
</button></div>
|
||||
</div>
|
||||
@ -26,6 +29,7 @@ exports[`renders ./components/affix/demo/target.vue correctly 1`] = `
|
||||
<div id="components-affix-demo-target" class="scrollable-container">
|
||||
<div class="background">
|
||||
<div>
|
||||
<!---->
|
||||
<div class=""><button class="ant-btn ant-btn-primary" type="button">
|
||||
<!----><span>Fixed at the top of container</span>
|
||||
</button></div>
|
||||
|
@ -26,16 +26,8 @@ The simplest usage.
|
||||
</a-affix>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
const top = ref<number>(10);
|
||||
const bottom = ref<number>(10);
|
||||
return {
|
||||
top,
|
||||
bottom,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -21,17 +21,8 @@ Callback with affixed state.
|
||||
<a-button>120px to affix top</a-button>
|
||||
</a-affix>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
<script lang="ts" setup>
|
||||
const change = (affixed: boolean) => {
|
||||
console.log(affixed);
|
||||
};
|
||||
|
||||
return {
|
||||
change,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -25,23 +25,16 @@ Set a `target` for 'Affix', which is listen to scroll event of target element (d
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
const containerRef = ref();
|
||||
return {
|
||||
containerRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
<style scoped>
|
||||
#components-affix-demo-target.scrollable-container {
|
||||
height: 100px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#components-affix-demo-target .background {
|
||||
padding-top: 60px;
|
||||
height: 300px;
|
||||
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
category: Components
|
||||
type: Navigation
|
||||
type: Other
|
||||
title: Affix
|
||||
cover: https://gw.alipayobjects.com/zos/alicdn/tX6-md4H6/Affix.svg
|
||||
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YSm4RI3iOJ8AAAAAAAAAAAAADrJ8AQ/original
|
||||
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*03dxS64LxeQAAAAAAAAAAAAADrJ8AQ/original
|
||||
---
|
||||
|
||||
Wrap Affix around another component to make it stick the viewport.
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { ComponentPublicInstance, CSSProperties, ExtractPropTypes, PropType } from 'vue';
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
shallowRef,
|
||||
reactive,
|
||||
watch,
|
||||
onMounted,
|
||||
@ -21,9 +21,9 @@ import {
|
||||
getFixedTop,
|
||||
getFixedBottom,
|
||||
} from './utils';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import useConfigInject from '../config-provider/hooks/useConfigInject';
|
||||
import omit from '../_util/omit';
|
||||
|
||||
import useStyle from './style';
|
||||
function getDefaultTarget() {
|
||||
return typeof window !== 'undefined' ? window : null;
|
||||
}
|
||||
@ -74,10 +74,11 @@ export type AffixInstance = ComponentPublicInstance<AffixProps, AffixExpose>;
|
||||
const Affix = defineComponent({
|
||||
compatConfig: { MODE: 3 },
|
||||
name: 'AAffix',
|
||||
inheritAttrs: false,
|
||||
props: affixProps(),
|
||||
setup(props, { slots, emit, expose }) {
|
||||
const placeholderNode = ref();
|
||||
const fixedNode = ref();
|
||||
setup(props, { slots, emit, expose, attrs }) {
|
||||
const placeholderNode = shallowRef();
|
||||
const fixedNode = shallowRef();
|
||||
const state = reactive({
|
||||
affixStyle: undefined,
|
||||
placeholderStyle: undefined,
|
||||
@ -123,6 +124,14 @@ const Affix = defineComponent({
|
||||
const targetRect = getTargetRect(targetNode);
|
||||
const fixedTop = getFixedTop(placeholderRect, targetRect, offsetTop.value);
|
||||
const fixedBottom = getFixedBottom(placeholderRect, targetRect, offsetBottom.value);
|
||||
if (
|
||||
placeholderRect.top === 0 &&
|
||||
placeholderRect.left === 0 &&
|
||||
placeholderRect.width === 0 &&
|
||||
placeholderRect.height === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fixedTop !== undefined) {
|
||||
const width = `${placeholderRect.width}px`;
|
||||
@ -245,11 +254,12 @@ const Affix = defineComponent({
|
||||
});
|
||||
|
||||
const { prefixCls } = useConfigInject('affix', props);
|
||||
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
return () => {
|
||||
const { affixStyle, placeholderStyle } = state;
|
||||
const className = classNames({
|
||||
[prefixCls.value]: affixStyle,
|
||||
[hashId.value]: true,
|
||||
});
|
||||
const restProps = omit(props, [
|
||||
'prefixCls',
|
||||
@ -259,14 +269,15 @@ const Affix = defineComponent({
|
||||
'onChange',
|
||||
'onTestUpdatePosition',
|
||||
]);
|
||||
return (
|
||||
return wrapSSR(
|
||||
<ResizeObserver onResize={updatePosition}>
|
||||
<div {...restProps} style={placeholderStyle} ref={placeholderNode}>
|
||||
<div {...restProps} {...attrs} ref={placeholderNode}>
|
||||
{affixStyle && <div style={placeholderStyle} aria-hidden="true" />}
|
||||
<div class={className} ref={fixedNode} style={affixStyle}>
|
||||
{slots.default?.()}
|
||||
</div>
|
||||
</div>
|
||||
</ResizeObserver>
|
||||
</ResizeObserver>,
|
||||
);
|
||||
};
|
||||
},
|
||||
|
@ -1,9 +1,10 @@
|
||||
---
|
||||
category: Components
|
||||
subtitle: 固钉
|
||||
type: 导航
|
||||
type: 其他
|
||||
title: Affix
|
||||
cover: https://gw.alipayobjects.com/zos/alicdn/tX6-md4H6/Affix.svg
|
||||
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YSm4RI3iOJ8AAAAAAAAAAAAADrJ8AQ/original
|
||||
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*03dxS64LxeQAAAAAAAAAAAAADrJ8AQ/original
|
||||
---
|
||||
|
||||
将页面元素钉在可视范围。
|
||||
|
@ -1,6 +0,0 @@
|
||||
@import '../../style/themes/index';
|
||||
|
||||
.@{ant-prefix}-affix {
|
||||
position: fixed;
|
||||
z-index: @zindex-affix;
|
||||
}
|
27
components/affix/style/index.ts
Normal file
27
components/affix/style/index.ts
Normal file
@ -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';
|
@ -1,5 +1,4 @@
|
||||
import addEventListener from '../vc-util/Dom/addEventListener';
|
||||
import type { ComponentPublicInstance } from 'vue';
|
||||
import supportsPassive from '../_util/supportsPassive';
|
||||
|
||||
export type BindElement = HTMLElement | Window | null | undefined;
|
||||
@ -42,7 +41,7 @@ const TRIGGER_EVENTS = [
|
||||
|
||||
interface ObserverEntity {
|
||||
target: HTMLElement | Window;
|
||||
affixList: ComponentPublicInstance<any>[];
|
||||
affixList: any[];
|
||||
eventHandlers: { [eventName: string]: any };
|
||||
}
|
||||
|
||||
@ -53,13 +52,10 @@ export function getObserverEntities() {
|
||||
return observerEntities;
|
||||
}
|
||||
|
||||
export function addObserveTarget(
|
||||
target: HTMLElement | Window | null,
|
||||
affix: ComponentPublicInstance<any>,
|
||||
): void {
|
||||
export function addObserveTarget<T>(target: HTMLElement | Window | null, affix: T): void {
|
||||
if (!target) return;
|
||||
|
||||
let entity: ObserverEntity | undefined = observerEntities.find(item => item.target === target);
|
||||
let entity = observerEntities.find(item => item.target === target);
|
||||
|
||||
if (entity) {
|
||||
entity.affixList.push(affix);
|
||||
@ -88,7 +84,7 @@ export function addObserveTarget(
|
||||
}
|
||||
}
|
||||
|
||||
export function removeObserveTarget(affix: ComponentPublicInstance<any>): void {
|
||||
export function removeObserveTarget<T>(affix: T): void {
|
||||
const observerEntity = observerEntities.find(oriObserverEntity => {
|
||||
const hasAffix = oriObserverEntity.affixList.some(item => item === affix);
|
||||
if (hasAffix) {
|
||||
|
@ -1,21 +1,99 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ./components/alert/demo/action.vue correctly 1`] = `
|
||||
<div style="width: 100%;" class="ant-space ant-space-vertical">
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-success ant-alert-closable" data-show="true"><span role="img" aria-label="check-circle" class="anticon anticon-check-circle ant-alert-icon"><svg focusable="false" class="" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Success Tips</div>
|
||||
<!---->
|
||||
</div>
|
||||
<div class="ant-alert-action"><button class="ant-btn ant-btn-text ant-btn-sm" type="button">
|
||||
<!----><span>UNDO</span>
|
||||
</button></div><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-error ant-alert-with-description" data-show="true"><span role="img" aria-label="close-circle" class="anticon anticon-close-circle ant-alert-icon"><svg focusable="false" class="" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M685.4 354.8c0-4.4-3.6-8-8-8l-66 .3L512 465.6l-99.3-118.4-66.1-.3c-4.4 0-8 3.5-8 8 0 1.9.7 3.7 1.9 5.2l130.1 155L340.5 670a8.32 8.32 0 00-1.9 5.2c0 4.4 3.6 8 8 8l66.1-.3L512 564.4l99.3 118.4 66 .3c4.4 0 8-3.5 8-8 0-1.9-.7-3.7-1.9-5.2L553.5 515l130.1-155c1.2-1.4 1.8-3.3 1.8-5.2z"></path><path d="M512 65C264.6 65 64 265.6 64 513s200.6 448 448 448 448-200.6 448-448S759.4 65 512 65zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Error Text</div>
|
||||
<div class="ant-alert-description">Error Description Error Description Error Description Error Description</div>
|
||||
</div>
|
||||
<div class="ant-alert-action"><button class="ant-btn ant-btn-default ant-btn-sm ant-btn-dangerous" type="button">
|
||||
<!----><span>Detail</span>
|
||||
</button></div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-no-icon ant-alert-closable" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Warning Text</div>
|
||||
<!---->
|
||||
</div>
|
||||
<div class="ant-alert-action">
|
||||
<div class="ant-space ant-space-horizontal ant-space-align-center">
|
||||
<div class="ant-space-item"><button class="ant-btn ant-btn-ghost ant-btn-sm" type="button">
|
||||
<!----><span>Done</span>
|
||||
</button></div>
|
||||
<!---->
|
||||
</div>
|
||||
</div><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item">
|
||||
<div role="alert" class="ant-alert ant-alert-info ant-alert-with-description ant-alert-no-icon ant-alert-closable" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Info Text</div>
|
||||
<div class="ant-alert-description">Info Description Info Description Info Description Info Description</div>
|
||||
</div>
|
||||
<div class="ant-alert-action">
|
||||
<div class="ant-space ant-space-vertical">
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;"><button class="ant-btn ant-btn-primary ant-btn-sm" type="button">
|
||||
<!----><span>Accept</span>
|
||||
</button></div>
|
||||
<!---->
|
||||
<div class="ant-space-item"><button class="ant-btn ant-btn-ghost ant-btn-sm ant-btn-dangerous" type="button">
|
||||
<!----><span>Decline</span>
|
||||
</button></div>
|
||||
<!---->
|
||||
</div>
|
||||
</div><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/banner.vue correctly 1`] = `
|
||||
<div style="width: 100%;" class="ant-space ant-space-vertical">
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-banner" data-show="true"><span role="img" aria-label="exclamation-circle" class="anticon anticon-exclamation-circle ant-alert-icon"><svg focusable="false" class="" data-icon="exclamation-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Warning text</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-banner ant-alert-closable" data-show="true"><span role="img" aria-label="exclamation-circle" class="anticon anticon-exclamation-circle ant-alert-icon"><svg focusable="false" class="" data-icon="exclamation-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Very long warning text warning text text text text text text text</div>
|
||||
<!---->
|
||||
</div><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
<br>
|
||||
<!----><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-no-icon ant-alert-banner" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -23,14 +101,21 @@ exports[`renders ./components/alert/demo/banner.vue correctly 1`] = `
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item">
|
||||
<div role="alert" class="ant-alert ant-alert-error ant-alert-banner" data-show="true"><span role="img" aria-label="close-circle" class="anticon anticon-close-circle ant-alert-icon"><svg focusable="false" class="" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Error text</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -42,23 +127,34 @@ exports[`renders ./components/alert/demo/basic.vue correctly 1`] = `
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/closable.vue correctly 1`] = `
|
||||
<div style="width: 100%;" class="ant-space ant-space-vertical">
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-no-icon ant-alert-closable" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text</div>
|
||||
<!---->
|
||||
</div><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
<!----><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item">
|
||||
<div role="alert" class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon ant-alert-closable" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Error Text</div>
|
||||
<div class="ant-alert-description">Error Description Error Description Error Description Error Description Error Description Error Description</div>
|
||||
</div><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
<!----><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -68,11 +164,14 @@ exports[`renders ./components/alert/demo/close-text.vue correctly 1`] = `
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Info Text</div>
|
||||
<!---->
|
||||
</div><button type="button" class="ant-alert-close-icon" tabindex="0"><span class="ant-alert-close-text">Close Now</span></button>
|
||||
</div>
|
||||
<!----><button type="button" class="ant-alert-close-icon" tabindex="0"><span class="ant-alert-close-text">Close Now</span></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/custom-icon.vue correctly 1`] = `
|
||||
<div style="width: 100%;" class="ant-space ant-space-vertical">
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-success ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -80,66 +179,104 @@ exports[`renders ./components/alert/demo/custom-icon.vue correctly 1`] = `
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-success" data-show="true"><span role="img" aria-label="smile" class="anticon anticon-smile ant-alert-icon"><svg focusable="false" class="" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Success Tips</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-info" data-show="true"><span role="img" aria-label="smile" class="anticon anticon-smile ant-alert-icon"><svg focusable="false" class="" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Informational Notes</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning" data-show="true"><span role="img" aria-label="smile" class="anticon anticon-smile ant-alert-icon"><svg focusable="false" class="" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Warning</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-error" data-show="true"><span role="img" aria-label="smile" class="anticon anticon-smile ant-alert-icon"><svg focusable="false" class="" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Error</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-success ant-alert-with-description" data-show="true"><span role="img" aria-label="smile" class="anticon anticon-smile ant-alert-icon"><svg focusable="false" class="" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Success Tips</div>
|
||||
<div class="ant-alert-description">Detailed description and advices about successful copywriting.</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-info ant-alert-with-description" data-show="true"><span role="img" aria-label="smile" class="anticon anticon-smile ant-alert-icon"><svg focusable="false" class="" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Informational Notes</div>
|
||||
<div class="ant-alert-description">Additional description and informations about copywriting.</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-with-description" data-show="true"><span role="img" aria-label="smile" class="anticon anticon-smile ant-alert-icon"><svg focusable="false" class="" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Warning</div>
|
||||
<div class="ant-alert-description">This is a warning notice about copywriting.</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item">
|
||||
<div role="alert" class="ant-alert ant-alert-error ant-alert-with-description" data-show="true"><span role="img" aria-label="smile" class="anticon anticon-smile ant-alert-icon"><svg focusable="false" class="" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Error</div>
|
||||
<div class="ant-alert-description">This is an error message about copywriting.</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/description.vue correctly 1`] = `
|
||||
<div style="width: 100%;" class="ant-space ant-space-vertical">
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-success ant-alert-with-description ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -149,7 +286,11 @@ exports[`renders ./components/alert/demo/description.vue correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-info ant-alert-with-description ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -157,7 +298,11 @@ exports[`renders ./components/alert/demo/description.vue correctly 1`] = `
|
||||
<div class="ant-alert-description">Info Description Info Description Info Description Info Description</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-with-description ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -165,7 +310,11 @@ exports[`renders ./components/alert/demo/description.vue correctly 1`] = `
|
||||
<div class="ant-alert-description">Warning Description Warning Description Warning Description Warning Description</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item">
|
||||
<div role="alert" class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -173,65 +322,103 @@ exports[`renders ./components/alert/demo/description.vue correctly 1`] = `
|
||||
<div class="ant-alert-description">Error Description Error Description Error Description Error Description</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/icon.vue correctly 1`] = `
|
||||
<div style="width: 100%;" class="ant-space ant-space-vertical">
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-success" data-show="true"><span role="img" aria-label="check-circle" class="anticon anticon-check-circle ant-alert-icon"><svg focusable="false" class="" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Success Tips</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-info" data-show="true"><span role="img" aria-label="info-circle" class="anticon anticon-info-circle ant-alert-icon"><svg focusable="false" class="" data-icon="info-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Informational Notes</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning" data-show="true"><span role="img" aria-label="exclamation-circle" class="anticon anticon-exclamation-circle ant-alert-icon"><svg focusable="false" class="" data-icon="exclamation-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Warning</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-error" data-show="true"><span role="img" aria-label="close-circle" class="anticon anticon-close-circle ant-alert-icon"><svg focusable="false" class="" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Error</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-success ant-alert-with-description" data-show="true"><span role="img" aria-label="check-circle" class="anticon anticon-check-circle ant-alert-icon"><svg focusable="false" class="" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0051.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z"></path><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Success Tips</div>
|
||||
<div class="ant-alert-description">Detailed description and advices about successful copywriting.</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-info ant-alert-with-description" data-show="true"><span role="img" aria-label="info-circle" class="anticon anticon-info-circle ant-alert-icon"><svg focusable="false" class="" data-icon="info-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path><path d="M464 336a48 48 0 1096 0 48 48 0 10-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Informational Notes</div>
|
||||
<div class="ant-alert-description">Additional description and informations about copywriting.</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-with-description" data-show="true"><span role="img" aria-label="exclamation-circle" class="anticon anticon-exclamation-circle ant-alert-icon"><svg focusable="false" class="" data-icon="exclamation-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path><path d="M464 688a48 48 0 1096 0 48 48 0 10-96 0zm24-112h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Warning</div>
|
||||
<div class="ant-alert-description">This is a warning notice about copywriting.</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item">
|
||||
<div role="alert" class="ant-alert ant-alert-error ant-alert-with-description" data-show="true"><span role="img" aria-label="close-circle" class="anticon anticon-close-circle ant-alert-icon"><svg focusable="false" class="" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M685.4 354.8c0-4.4-3.6-8-8-8l-66 .3L512 465.6l-99.3-118.4-66.1-.3c-4.4 0-8 3.5-8 8 0 1.9.7 3.7 1.9 5.2l130.1 155L340.5 670a8.32 8.32 0 00-1.9 5.2c0 4.4 3.6 8 8 8l66.1-.3L512 564.4l99.3 118.4 66 .3c4.4 0 8-3.5 8-8 0-1.9-.7-3.7-1.9-5.2L553.5 515l130.1-155c1.2-1.4 1.8-3.3 1.8-5.2z"></path><path d="M512 65C264.6 65 64 265.6 64 513s200.6 448 448 448 448-200.6 448-448S759.4 65 512 65zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path></svg></span>
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Error</div>
|
||||
<div class="ant-alert-description">This is an error message about copywriting.</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -241,11 +428,14 @@ exports[`renders ./components/alert/demo/smooth-closed.vue correctly 1`] = `
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">Alert Message Text</div>
|
||||
<!---->
|
||||
</div><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
<!----><button type="button" class="ant-alert-close-icon" tabindex="0"><span role="img" aria-label="close" class="anticon anticon-close"><svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></span></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/style.vue correctly 1`] = `
|
||||
<div style="width: 100%;" class="ant-space ant-space-vertical">
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-success ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -253,7 +443,11 @@ exports[`renders ./components/alert/demo/style.vue correctly 1`] = `
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-info ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -261,7 +455,11 @@ exports[`renders ./components/alert/demo/style.vue correctly 1`] = `
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item" style="margin-bottom: 8px;">
|
||||
<div role="alert" class="ant-alert ant-alert-warning ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -269,7 +467,11 @@ exports[`renders ./components/alert/demo/style.vue correctly 1`] = `
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-space-item">
|
||||
<div role="alert" class="ant-alert ant-alert-error ant-alert-no-icon" data-show="true">
|
||||
<!---->
|
||||
<div class="ant-alert-content">
|
||||
@ -277,5 +479,9 @@ exports[`renders ./components/alert/demo/style.vue correctly 1`] = `
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
57
components/alert/demo/action.vue
Normal file
57
components/alert/demo/action.vue
Normal file
@ -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>
|
@ -17,15 +17,14 @@ Display Alert as a banner at top of page.
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-alert message="Warning text" banner />
|
||||
<br />
|
||||
<a-alert
|
||||
message="Very long warning text warning text text text text text text text"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
<br />
|
||||
<a-alert :show-icon="false" message="Warning text without icon" banner />
|
||||
<br />
|
||||
<a-alert type="error" message="Error text" banner />
|
||||
</a-space>
|
||||
</template>
|
||||
|
@ -17,6 +17,7 @@ To show close button.
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-alert
|
||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||
type="warning"
|
||||
@ -30,18 +31,11 @@ To show close button.
|
||||
closable
|
||||
@close="onClose"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
<script lang="ts" setup>
|
||||
const onClose = (e: MouseEvent) => {
|
||||
console.log(e, 'I was closed.');
|
||||
};
|
||||
return {
|
||||
onClose,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -17,6 +17,7 @@ Custom Icon make information more clear and more friendly.
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-alert message="showIcon = false" type="success">
|
||||
<template #icon><smile-outlined /></template>
|
||||
</a-alert>
|
||||
@ -64,14 +65,9 @@ Custom Icon make information more clear and more friendly.
|
||||
>
|
||||
<template #icon><smile-outlined /></template>
|
||||
</a-alert>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { SmileOutlined } from '@ant-design/icons-vue';
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
components: {
|
||||
SmileOutlined,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -17,6 +17,7 @@ Additional description for alert message.
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-alert message="Success Text" type="success">
|
||||
<template #description>
|
||||
<p>
|
||||
@ -41,4 +42,5 @@ Additional description for alert message.
|
||||
description="Error Description Error Description Error Description Error Description"
|
||||
type="error"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user