From d8e6db29913a2b8123f3c5df82e0bc23069b87c1 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 17 Apr 2025 17:55:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(style):=20=E6=B7=BB=E5=8A=A0=20layer=20?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E4=BB=A5=E9=81=BF=E5=85=8D=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 StyleContextProps 和相关函数中引入 layer 属性,支持样式分层。 --- components/_util/cssinjs/StyleContext.tsx | 4 + .../cssinjs/hooks/useStyleRegister/index.tsx | 99 ++++++++++++++----- components/config-provider/style/index.ts | 1 + .../theme/util/genComponentStyleHook.ts | 2 + 4 files changed, 79 insertions(+), 27 deletions(-) diff --git a/components/_util/cssinjs/StyleContext.tsx b/components/_util/cssinjs/StyleContext.tsx index faf49f4b5d..480dcfb5e7 100644 --- a/components/_util/cssinjs/StyleContext.tsx +++ b/components/_util/cssinjs/StyleContext.tsx @@ -82,6 +82,8 @@ export interface StyleContextProps { * Please note that `linters` do not support dynamic update. */ linters?: Linter[]; + /** Wrap css in a layer to avoid global style conflict */ + layer?: boolean; } const StyleContextKey: InjectionKey>> = @@ -174,6 +176,8 @@ export const styleProviderProps = () => ({ * Please note that `linters` do not support dynamic update. */ linters: arrayType(), + /** Wrap css in a layer to avoid global style conflict */ + layer: booleanType(), }); export type StyleProviderProps = Partial>>; export const StyleProvider = withInstall( diff --git a/components/_util/cssinjs/hooks/useStyleRegister/index.tsx b/components/_util/cssinjs/hooks/useStyleRegister/index.tsx index d264d0744e..de1e851fe8 100644 --- a/components/_util/cssinjs/hooks/useStyleRegister/index.tsx +++ b/components/_util/cssinjs/hooks/useStyleRegister/index.tsx @@ -35,6 +35,12 @@ const isClientSide = canUseDom(); const SKIP_CHECK = '_skip_check_'; const MULTI_VALUE = '_multi_value_'; + +export interface LayerConfig { + name: string; + dependencies?: string[]; +} + export type CSSProperties = Omit, 'animationName'> & { animationName?: CSS.PropertiesFallback['animationName'] | Keyframes; }; @@ -102,7 +108,7 @@ function injectSelectorHash(key: string, hashId: string, hashPriority?: HashPrio export interface ParseConfig { hashId?: string; hashPriority?: HashPriority; - layer?: string; + layer?: LayerConfig; path?: string; transformers?: Transformer[]; linters?: Linter[]; @@ -278,14 +284,14 @@ export const parseStyle = ( 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}`; + if (styleStr) { + styleStr = `@layer ${layer.name} {${styleStr}}`; + } + + if (layer.dependencies) { + effectStyle[`@layer ${layer.name}`] = layer.dependencies + .map(deps => `@layer ${deps}, ${layer.name};`) + .join('\n'); } } @@ -312,7 +318,7 @@ export default function useStyleRegister( token: any; path: string[]; hashId?: string; - layer?: string; + layer?: LayerConfig; nonce?: string | (() => string); clientOnly?: boolean; /** @@ -328,7 +334,11 @@ export default function useStyleRegister( const tokenKey = computed(() => info.value.token._tokenKey as string); - const fullPath = computed(() => [tokenKey.value, ...info.value.path]); + const fullPath = computed(() => [ + tokenKey.value, + ...(styleContext.value.layer ? ['layer'] : []), + ...info.value.path, + ]); // Check if need insert style let isMergedClientSide = isClientSide; @@ -361,12 +371,19 @@ export default function useStyleRegister( } } const styleObj = styleFn(); - const { hashPriority, container, transformers, linters, cache } = styleContext.value; + const { + hashPriority, + container, + transformers, + linters, + cache, + layer: enableLayer, + } = styleContext.value; const [parsedStyle, effectStyle] = parseStyle(styleObj, { hashId, hashPriority, - layer, + layer: enableLayer ? layer : undefined, path: path.join('-'), transformers, linters, @@ -377,7 +394,7 @@ export default function useStyleRegister( if (isMergedClientSide) { const mergedCSSConfig: Parameters[2] = { mark: ATTR_MARK, - prepend: 'queue', + prepend: enableLayer ? false : 'queue', attachTo: container, priority: order, }; @@ -388,6 +405,30 @@ export default function useStyleRegister( mergedCSSConfig.csp = { nonce: nonceStr }; } + // ================= Split Effect Style ================= + // We will split effectStyle here since @layer should be at the top level + const effectLayerKeys: string[] = []; + const effectRestKeys: string[] = []; + + Object.keys(effectStyle).forEach(key => { + if (key.startsWith('@layer')) { + effectLayerKeys.push(key); + } else { + effectRestKeys.push(key); + } + }); + + // ================= Inject Layer Style ================= + // Inject layer style + effectLayerKeys.forEach(effectKey => { + updateCSS(normalizeStyle(effectStyle[effectKey]), `_layer-${effectKey}`, { + ...mergedCSSConfig, + prepend: true, + }); + }); + + // ==================== Inject Style ==================== + // Inject style const style = updateCSS(styleStr, styleId, mergedCSSConfig); (style as any)[CSS_IN_JS_INSTANCE] = cache.instanceId; @@ -400,18 +441,14 @@ export default function useStyleRegister( style.setAttribute(ATTR_CACHE_PATH, fullPath.value.join('|')); } + // ================ Inject Effect Style ================= // 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, - }); - } + effectRestKeys.forEach(effectKey => { + updateCSS( + normalizeStyle(effectStyle[effectKey]), + `_effect-${effectKey}`, + mergedCSSConfig, + ); }); } @@ -530,12 +567,20 @@ export function extractStyle(cache: Cache, plain = false) { // Effect style can be reused if (!effectStyles[effectKey]) { effectStyles[effectKey] = true; - keyStyleText += toStyleStr( - normalizeStyle(effectStyle[effectKey]), + + const effectStyleStr = normalizeStyle(effectStyle[effectKey]); + const effectStyleHTML = toStyleStr( + effectStyleStr, tokenKey, `_effect-${effectKey}`, sharedAttrs, ); + + if (effectKey.startsWith('@layer')) { + keyStyleText = effectStyleHTML + keyStyleText; + } else { + keyStyleText += effectStyleHTML; + } } }); } diff --git a/components/config-provider/style/index.ts b/components/config-provider/style/index.ts index 77ed478a06..e04a98bc93 100644 --- a/components/config-provider/style/index.ts +++ b/components/config-provider/style/index.ts @@ -12,6 +12,7 @@ const useStyle = (iconPrefixCls: Ref) => { token: token.value, hashId: '', path: ['ant-design-icons', iconPrefixCls.value], + layer: { name: 'antd' }, })), () => [ { diff --git a/components/theme/util/genComponentStyleHook.ts b/components/theme/util/genComponentStyleHook.ts index d5879d52e9..3c1d05d2c0 100644 --- a/components/theme/util/genComponentStyleHook.ts +++ b/components/theme/util/genComponentStyleHook.ts @@ -55,6 +55,7 @@ export default function genComponentStyleHook