@yahoo/uds 3.156.1 → 3.157.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/automated-config/dist/generated/autoVariants.cjs +9 -4
- package/dist/automated-config/dist/generated/autoVariants.d.cts +2 -1
- package/dist/automated-config/dist/generated/autoVariants.d.ts +2 -1
- package/dist/automated-config/dist/generated/autoVariants.js +9 -4
- package/dist/automated-config/dist/generated/generatedConfigs.cjs +3011 -3038
- package/dist/automated-config/dist/generated/generatedConfigs.d.cts +143 -140
- package/dist/automated-config/dist/generated/generatedConfigs.d.ts +143 -140
- package/dist/automated-config/dist/generated/generatedConfigs.js +3011 -3038
- package/dist/automated-config/dist/generated/universalTokensConfigAuto.cjs +1227 -501
- package/dist/automated-config/dist/generated/universalTokensConfigAuto.js +1227 -501
- package/dist/automated-config/dist/properties.cjs +1 -1
- package/dist/automated-config/dist/properties.d.cts +15 -0
- package/dist/automated-config/dist/properties.d.ts +15 -0
- package/dist/automated-config/dist/properties.js +1 -1
- package/dist/automated-config/dist/types/ComponentConfig.d.cts +77 -4
- package/dist/automated-config/dist/types/ComponentConfig.d.ts +77 -4
- package/dist/automated-config/dist/types/ConfigSchema.d.cts +14 -2
- package/dist/automated-config/dist/types/ConfigSchema.d.ts +14 -2
- package/dist/automated-config/dist/types/StateAxis.cjs +90 -0
- package/dist/automated-config/dist/types/StateAxis.d.cts +70 -0
- package/dist/automated-config/dist/types/StateAxis.d.ts +70 -0
- package/dist/automated-config/dist/types/StateAxis.js +84 -0
- package/dist/automated-config/dist/utils/buildConfigSchema.cjs +98 -82
- package/dist/automated-config/dist/utils/buildConfigSchema.d.cts +32 -10
- package/dist/automated-config/dist/utils/buildConfigSchema.d.ts +32 -10
- package/dist/automated-config/dist/utils/buildConfigSchema.js +99 -83
- package/dist/automated-config/dist/utils/canonicalizeStateKey.cjs +32 -0
- package/dist/automated-config/dist/utils/canonicalizeStateKey.d.cts +48 -0
- package/dist/automated-config/dist/utils/canonicalizeStateKey.d.ts +48 -0
- package/dist/automated-config/dist/utils/canonicalizeStateKey.js +31 -0
- package/dist/automated-config/dist/utils/getConfigComponentVariant.d.cts +8 -0
- package/dist/automated-config/dist/utils/getConfigComponentVariant.d.ts +8 -0
- package/dist/automated-config/dist/utils/getConfigVariantProperties.d.cts +3 -3
- package/dist/automated-config/dist/utils/getConfigVariantProperties.d.ts +3 -3
- package/dist/automated-config/dist/utils/getConfigVariantPseudoStates.cjs +12 -5
- package/dist/automated-config/dist/utils/getConfigVariantPseudoStates.d.cts +8 -1
- package/dist/automated-config/dist/utils/getConfigVariantPseudoStates.d.ts +8 -1
- package/dist/automated-config/dist/utils/getConfigVariantPseudoStates.js +12 -5
- package/dist/automated-config/dist/utils/getPaginationControlWidthPx.cjs +1 -1
- package/dist/automated-config/dist/utils/getPaginationControlWidthPx.js +1 -1
- package/dist/automated-config/dist/utils/index.cjs +407 -97
- package/dist/automated-config/dist/utils/index.d.cts +66 -16
- package/dist/automated-config/dist/utils/index.d.ts +66 -16
- package/dist/automated-config/dist/utils/index.js +408 -99
- package/dist/automated-config/dist/utils/pseudoStateSelectors.cjs +122 -0
- package/dist/automated-config/dist/utils/pseudoStateSelectors.d.cts +80 -0
- package/dist/automated-config/dist/utils/pseudoStateSelectors.d.ts +80 -0
- package/dist/automated-config/dist/utils/pseudoStateSelectors.js +120 -0
- package/dist/automated-config/dist/utils/resolvePropertyStates.cjs +131 -0
- package/dist/automated-config/dist/utils/resolvePropertyStates.d.cts +49 -0
- package/dist/automated-config/dist/utils/resolvePropertyStates.d.ts +49 -0
- package/dist/automated-config/dist/utils/resolvePropertyStates.js +130 -0
- package/dist/automated-config/dist/utils/resolveSlotByCascade.cjs +118 -0
- package/dist/automated-config/dist/utils/resolveSlotByCascade.d.cts +68 -0
- package/dist/automated-config/dist/utils/resolveSlotByCascade.d.ts +68 -0
- package/dist/automated-config/dist/utils/resolveSlotByCascade.js +117 -0
- package/dist/automated-config/dist/utils/variantConfigGuards.d.cts +13 -0
- package/dist/automated-config/dist/utils/variantConfigGuards.d.ts +13 -0
- package/dist/components/client/Input/Input.cjs +42 -6
- package/dist/components/client/Input/Input.d.cts +13 -0
- package/dist/components/client/Input/Input.d.ts +13 -0
- package/dist/components/client/Input/Input.js +42 -6
- package/dist/config/dist/index.cjs +221 -550
- package/dist/config/dist/index.js +221 -550
- package/dist/css/dist/commands/css.cjs +1 -0
- package/dist/css/dist/commands/css.helpers.cjs +6 -0
- package/dist/css/dist/commands/css.helpers.js +6 -0
- package/dist/css/dist/commands/css.js +1 -0
- package/dist/css/dist/css/generate.cjs +4 -2
- package/dist/css/dist/css/generate.d.cts +28 -0
- package/dist/css/dist/css/generate.d.ts +28 -0
- package/dist/css/dist/css/generate.helpers.cjs +5 -1
- package/dist/css/dist/css/generate.helpers.js +6 -2
- package/dist/css/dist/css/generate.js +4 -2
- package/dist/css/dist/css/postcss.cjs +81 -0
- package/dist/css/dist/css/postcss.helpers.cjs +60 -0
- package/dist/css/dist/css/postcss.helpers.js +59 -1
- package/dist/css/dist/css/postcss.js +82 -2
- package/dist/css/dist/css/runner.cjs +12 -2
- package/dist/css/dist/css/runner.js +12 -2
- package/dist/css/dist/css/theme.d.cts +6 -0
- package/dist/css/dist/css/theme.d.ts +6 -0
- package/dist/css/dist/packages/automated-config/dist/properties.cjs +1 -1
- package/dist/css/dist/packages/automated-config/dist/properties.js +1 -1
- package/dist/css/dist/packages/automated-config/dist/utils/index.d.cts +6 -0
- package/dist/css/dist/packages/automated-config/dist/utils/index.d.ts +6 -0
- package/dist/css/dist/packages/config/dist/index.cjs +221 -550
- package/dist/css/dist/packages/config/dist/index.js +221 -550
- package/dist/css/dist/utils/optimizeCSS.cjs +59 -0
- package/dist/css/dist/utils/optimizeCSS.js +59 -0
- package/dist/index.cjs +25 -0
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +9 -2
- package/dist/styles/styler.d.cts +14 -13
- package/dist/styles/styler.d.ts +14 -13
- package/dist/styles/variants.d.cts +9 -4
- package/dist/styles/variants.d.ts +9 -4
- package/dist/tailwind-internal/dist/packages/automated-config/dist/generated/generatedConfigs.cjs +3011 -3038
- package/dist/tailwind-internal/dist/packages/automated-config/dist/generated/generatedConfigs.js +3011 -3038
- package/dist/tailwind-internal/dist/packages/automated-config/dist/properties.cjs +1 -1
- package/dist/tailwind-internal/dist/packages/automated-config/dist/properties.js +1 -1
- package/dist/tailwind-internal/dist/packages/automated-config/dist/types/StateAxis.cjs +81 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/types/StateAxis.js +76 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/canonicalizeStateKey.cjs +33 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/canonicalizeStateKey.js +32 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/componentStatePseudoStates.cjs +0 -7
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/componentStatePseudoStates.js +1 -7
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/index.cjs +354 -97
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/index.d.cts +6 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/index.d.ts +6 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/index.js +355 -98
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/pseudoStateSelectors.cjs +122 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/pseudoStateSelectors.js +121 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/resolvePropertyStates.cjs +132 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/resolvePropertyStates.js +131 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/resolveSlotByCascade.cjs +95 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/resolveSlotByCascade.js +95 -0
- package/dist/tailwind-internal/dist/packages/config/dist/index.cjs +221 -550
- package/dist/tailwind-internal/dist/packages/config/dist/index.js +221 -550
- package/dist/tailwind-internal/dist/plugins/components.cjs +28 -24
- package/dist/tailwind-internal/dist/plugins/components.js +28 -24
- package/dist/tailwind-internal/dist/utils/composeTailwindPlugins.d.cts +3 -0
- package/dist/tailwind-internal/dist/utils/composeTailwindPlugins.d.ts +3 -0
- package/dist/tokens/automation/index.cjs +25 -0
- package/dist/tokens/automation/index.d.cts +9 -2
- package/dist/tokens/automation/index.d.ts +9 -2
- package/dist/tokens/automation/index.js +9 -2
- package/dist/tokens/index.cjs +25 -0
- package/dist/tokens/index.d.cts +10 -3
- package/dist/tokens/index.d.ts +10 -3
- package/dist/tokens/index.js +9 -2
- package/dist/tokens/types.d.cts +1 -1
- package/dist/tokens/types.d.ts +1 -1
- package/dist/uds/generated/componentData.cjs +2010 -2008
- package/dist/uds/generated/componentData.js +2010 -2008
- package/dist/uds/generated/migrationSchemaVersion.cjs +1 -1
- package/dist/uds/generated/migrationSchemaVersion.js +1 -1
- package/dist/uds/generated/tailwindPurge.cjs +79 -78
- package/dist/uds/generated/tailwindPurge.js +79 -78
- package/generated/componentData.json +2553 -2551
- package/generated/migrationSchemaVersion.ts +1 -1
- package/generated/tailwindPurge.ts +2 -2
- package/package.json +1 -1
|
@@ -26,6 +26,7 @@ const showHelp = () => {
|
|
|
26
26
|
require_print.print(` ${require_colors.cyan("--silent, -s")} Suppress output logging`);
|
|
27
27
|
require_print.print(` ${require_colors.cyan("--verbose, -v")} List every scanned file used for purging`);
|
|
28
28
|
require_print.print(` ${require_colors.cyan("--workers <n>")} Number of parallel workers (default: auto = CPU count - 1)`);
|
|
29
|
+
require_print.print(` ${require_colors.cyan("--emit <mode>")} Emission mode: exhaustive (default) | selective`);
|
|
29
30
|
require_print.print(` ${require_colors.cyan("--force")} Overwrite existing uds.theme.ts (for init)`);
|
|
30
31
|
require_print.print(` ${require_colors.cyan("--help, -h")} Show this help message`);
|
|
31
32
|
require_print.print("");
|
|
@@ -21,6 +21,11 @@ const getCssInitOptions = (options) => ({
|
|
|
21
21
|
entry: entryOption(options.entry),
|
|
22
22
|
outputPath: stringOption(options.outFile)
|
|
23
23
|
});
|
|
24
|
+
const parseEmitOption = (value) => {
|
|
25
|
+
if (value === void 0 || value === null) return;
|
|
26
|
+
if (value === "exhaustive" || value === "selective") return value;
|
|
27
|
+
throw new Error(`Invalid --emit "${String(value)}". Expected 'exhaustive' or 'selective'.`);
|
|
28
|
+
};
|
|
24
29
|
const getCssRunOptions = (workspaceDir, options) => {
|
|
25
30
|
return {
|
|
26
31
|
workspaceDir,
|
|
@@ -29,6 +34,7 @@ const getCssRunOptions = (workspaceDir, options) => {
|
|
|
29
34
|
scope: stringOption(options.scope),
|
|
30
35
|
entryOption: entryOption(options.entry),
|
|
31
36
|
configOption: stringOption(options.config),
|
|
37
|
+
emit: parseEmitOption(options.emit),
|
|
32
38
|
watch: isTruthyFlag(options.watch) || isTruthyFlag(options.w),
|
|
33
39
|
silent: isTruthyFlag(options.silent) || isTruthyFlag(options.s),
|
|
34
40
|
verbose: isTruthyFlag(options.verbose) || isTruthyFlag(options.v),
|
|
@@ -21,6 +21,11 @@ const getCssInitOptions = (options) => ({
|
|
|
21
21
|
entry: entryOption(options.entry),
|
|
22
22
|
outputPath: stringOption(options.outFile)
|
|
23
23
|
});
|
|
24
|
+
const parseEmitOption = (value) => {
|
|
25
|
+
if (value === void 0 || value === null) return;
|
|
26
|
+
if (value === "exhaustive" || value === "selective") return value;
|
|
27
|
+
throw new Error(`Invalid --emit "${String(value)}". Expected 'exhaustive' or 'selective'.`);
|
|
28
|
+
};
|
|
24
29
|
const getCssRunOptions = (workspaceDir, options) => {
|
|
25
30
|
return {
|
|
26
31
|
workspaceDir,
|
|
@@ -29,6 +34,7 @@ const getCssRunOptions = (workspaceDir, options) => {
|
|
|
29
34
|
scope: stringOption(options.scope),
|
|
30
35
|
entryOption: entryOption(options.entry),
|
|
31
36
|
configOption: stringOption(options.config),
|
|
37
|
+
emit: parseEmitOption(options.emit),
|
|
32
38
|
watch: isTruthyFlag(options.watch) || isTruthyFlag(options.w),
|
|
33
39
|
silent: isTruthyFlag(options.silent) || isTruthyFlag(options.s),
|
|
34
40
|
verbose: isTruthyFlag(options.verbose) || isTruthyFlag(options.v),
|
|
@@ -26,6 +26,7 @@ const showHelp = () => {
|
|
|
26
26
|
print(` ${cyan("--silent, -s")} Suppress output logging`);
|
|
27
27
|
print(` ${cyan("--verbose, -v")} List every scanned file used for purging`);
|
|
28
28
|
print(` ${cyan("--workers <n>")} Number of parallel workers (default: auto = CPU count - 1)`);
|
|
29
|
+
print(` ${cyan("--emit <mode>")} Emission mode: exhaustive (default) | selective`);
|
|
29
30
|
print(` ${cyan("--force")} Overwrite existing uds.theme.ts (for init)`);
|
|
30
31
|
print(` ${cyan("--help, -h")} Show this help message`);
|
|
31
32
|
print("");
|
|
@@ -47,11 +47,13 @@ const generateCSS = async (safelist, config, options) => {
|
|
|
47
47
|
safelist,
|
|
48
48
|
config,
|
|
49
49
|
enablePreflight: cssFlags.enablePreflight,
|
|
50
|
-
enableFontFaceDeclarations: cssFlags.enableFontFaceDeclarations
|
|
50
|
+
enableFontFaceDeclarations: cssFlags.enableFontFaceDeclarations,
|
|
51
|
+
emit: options?.cssOptions?.emit
|
|
51
52
|
}),
|
|
52
53
|
shouldPruneVars: cssFlags.shouldPruneVars,
|
|
53
54
|
safeVarPrefixes: options?.safeVarPrefixes ?? [],
|
|
54
|
-
scopeClass
|
|
55
|
+
scopeClass,
|
|
56
|
+
otherBoundaryScopes: options?.otherBoundaryScopes ?? []
|
|
55
57
|
});
|
|
56
58
|
const result = await require_perf.measureAsync("gen:postcss", () => (0, postcss.default)(plugins).process(sourceCSS, { from: void 0 }));
|
|
57
59
|
let css = await require_perf.measureAsync("gen:scoped-color-fix", () => require_generate_helpers.applyScopedColorModeFix(result.css, scopeClass));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
|
|
2
|
+
import { EmitMode } from "../packages/automated-config/dist/utils/index.cjs";
|
|
2
3
|
import { TailwindSafelistConfig } from "./generate.helpers.cjs";
|
|
3
4
|
import { UDSCSSOptimizationOptions } from "./theme.cjs";
|
|
4
5
|
|
|
@@ -19,6 +20,33 @@ interface UDSCSSOptions {
|
|
|
19
20
|
fontFaceDeclarations?: boolean;
|
|
20
21
|
/** Debounce delay (ms) for watch mode regenerations */
|
|
21
22
|
watchDebounce?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Emission mode. `exhaustive` (default) emits a rule for every state — safe to
|
|
25
|
+
* coexist with other themes with no extra config. `selective` emits only
|
|
26
|
+
* rest + customized states (smaller) and relies on declared boundaries
|
|
27
|
+
* (`scoped` / `excludeBoundaries`) + the isolation fence for coexistence safety.
|
|
28
|
+
*/
|
|
29
|
+
emit?: EmitMode;
|
|
30
|
+
/**
|
|
31
|
+
* Additional selectors whose subtrees this theme's bundles must not match.
|
|
32
|
+
* Every emitted bundle (main and each `scoped:` entry) gets a
|
|
33
|
+
* `:not(:where(:is(<boundaries>) *))` suffix on each theme-driven
|
|
34
|
+
* (token-referencing) rule that targets a NON-REST state (`:hover`,
|
|
35
|
+
* `:focus-within`, `:has(…)`, etc.), so its themed state styles never cross
|
|
36
|
+
* into one of these subtrees. Rest-state token rules resolve their tokens to
|
|
37
|
+
* the nested scope's own values (value-safe), so they're left untouched — as
|
|
38
|
+
* are Tailwind utilities, resets, and other non-token rules. Entries listed
|
|
39
|
+
* here are exclusion-only — they do not produce their own CSS file.
|
|
40
|
+
*
|
|
41
|
+
* Scope classes from `scoped:` entries are already added automatically;
|
|
42
|
+
* this field is the place to declare *non-bundled* boundaries (preview
|
|
43
|
+
* surfaces, opt-out subtrees, etc.) on top of those.
|
|
44
|
+
*
|
|
45
|
+
* Accepts any complete selector (`.class`, `#id`, `[attr]`, `:pseudo`).
|
|
46
|
+
* Bare identifiers are normalized to classes for backward compat with
|
|
47
|
+
* the automatic `scoped:`-derived boundary classes.
|
|
48
|
+
*/
|
|
49
|
+
excludeBoundaries?: string[];
|
|
22
50
|
}
|
|
23
51
|
/**
|
|
24
52
|
* Options for generateCSS function
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
|
|
2
|
+
import { EmitMode } from "../packages/automated-config/dist/utils/index.js";
|
|
2
3
|
import { TailwindSafelistConfig } from "./generate.helpers.js";
|
|
3
4
|
import { UDSCSSOptimizationOptions } from "./theme.js";
|
|
4
5
|
|
|
@@ -19,6 +20,33 @@ interface UDSCSSOptions {
|
|
|
19
20
|
fontFaceDeclarations?: boolean;
|
|
20
21
|
/** Debounce delay (ms) for watch mode regenerations */
|
|
21
22
|
watchDebounce?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Emission mode. `exhaustive` (default) emits a rule for every state — safe to
|
|
25
|
+
* coexist with other themes with no extra config. `selective` emits only
|
|
26
|
+
* rest + customized states (smaller) and relies on declared boundaries
|
|
27
|
+
* (`scoped` / `excludeBoundaries`) + the isolation fence for coexistence safety.
|
|
28
|
+
*/
|
|
29
|
+
emit?: EmitMode;
|
|
30
|
+
/**
|
|
31
|
+
* Additional selectors whose subtrees this theme's bundles must not match.
|
|
32
|
+
* Every emitted bundle (main and each `scoped:` entry) gets a
|
|
33
|
+
* `:not(:where(:is(<boundaries>) *))` suffix on each theme-driven
|
|
34
|
+
* (token-referencing) rule that targets a NON-REST state (`:hover`,
|
|
35
|
+
* `:focus-within`, `:has(…)`, etc.), so its themed state styles never cross
|
|
36
|
+
* into one of these subtrees. Rest-state token rules resolve their tokens to
|
|
37
|
+
* the nested scope's own values (value-safe), so they're left untouched — as
|
|
38
|
+
* are Tailwind utilities, resets, and other non-token rules. Entries listed
|
|
39
|
+
* here are exclusion-only — they do not produce their own CSS file.
|
|
40
|
+
*
|
|
41
|
+
* Scope classes from `scoped:` entries are already added automatically;
|
|
42
|
+
* this field is the place to declare *non-bundled* boundaries (preview
|
|
43
|
+
* surfaces, opt-out subtrees, etc.) on top of those.
|
|
44
|
+
*
|
|
45
|
+
* Accepts any complete selector (`.class`, `#id`, `[attr]`, `:pseudo`).
|
|
46
|
+
* Bare identifiers are normalized to classes for backward compat with
|
|
47
|
+
* the automatic `scoped:`-derived boundary classes.
|
|
48
|
+
*/
|
|
49
|
+
excludeBoundaries?: string[];
|
|
22
50
|
}
|
|
23
51
|
/**
|
|
24
52
|
* Options for generateCSS function
|
|
@@ -81,7 +81,8 @@ const createTailwindPlugin = async (options) => {
|
|
|
81
81
|
plugins: [require_tailwindPlugin.tailwindPlugin({
|
|
82
82
|
config: options.config,
|
|
83
83
|
disableFontFaceDeclarations: !options.enableFontFaceDeclarations,
|
|
84
|
-
ignorePluginSafelists: true
|
|
84
|
+
ignorePluginSafelists: true,
|
|
85
|
+
emit: options.emit
|
|
85
86
|
})]
|
|
86
87
|
});
|
|
87
88
|
};
|
|
@@ -97,6 +98,8 @@ const buildPostcssPlugins = (options) => {
|
|
|
97
98
|
const plugins = [options.tailwindPlugin, (0, autoprefixer.default)()];
|
|
98
99
|
if (options.shouldPruneVars) plugins.push(...getPruneVarPlugins(options.safeVarPrefixes));
|
|
99
100
|
if (options.scopeClass) plugins.push((0, postcss_scope.default)(options.scopeClass));
|
|
101
|
+
const otherBoundaryScopes = options.otherBoundaryScopes ?? [];
|
|
102
|
+
if (otherBoundaryScopes.length > 0) plugins.push(require_postcss.appendIsolationExclusionPlugin(otherBoundaryScopes));
|
|
100
103
|
return plugins;
|
|
101
104
|
};
|
|
102
105
|
const applyScopedColorModeFix = async (css, scopeClass) => {
|
|
@@ -109,6 +112,7 @@ const optimizeGeneratedCss = async (options) => {
|
|
|
109
112
|
removeUnusedFonts: options.optimizationConfig?.removeUnusedFonts,
|
|
110
113
|
removeEmptyRules: options.optimizationConfig?.removeEmptyRules,
|
|
111
114
|
aggregateDuplicateSelectors: options.optimizationConfig?.aggregateDuplicateSelectors,
|
|
115
|
+
mergeIdenticalDeclarations: options.optimizationConfig?.mergeIdenticalDeclarations,
|
|
112
116
|
referenceCss: options.referenceCss,
|
|
113
117
|
scopeClass: options.scopeClass
|
|
114
118
|
};
|
|
@@ -8,7 +8,7 @@ import { formatCssDuration } from "./runner.helpers.js";
|
|
|
8
8
|
import { formatBytes } from "./utils.js";
|
|
9
9
|
import { iconPruneVarsSafelist } from "../packages/icons/src/safelist.js";
|
|
10
10
|
import { optimizeCSS } from "../utils/optimizeCSS.js";
|
|
11
|
-
import { fixScopedColorModeSelectorsPlugin, fixScopedSelfOrParentSelectorsPlugin } from "./postcss.js";
|
|
11
|
+
import { appendIsolationExclusionPlugin, fixScopedColorModeSelectorsPlugin, fixScopedSelfOrParentSelectorsPlugin } from "./postcss.js";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import fs from "node:fs";
|
|
@@ -74,7 +74,8 @@ const createTailwindPlugin = async (options) => {
|
|
|
74
74
|
plugins: [tailwindPlugin({
|
|
75
75
|
config: options.config,
|
|
76
76
|
disableFontFaceDeclarations: !options.enableFontFaceDeclarations,
|
|
77
|
-
ignorePluginSafelists: true
|
|
77
|
+
ignorePluginSafelists: true,
|
|
78
|
+
emit: options.emit
|
|
78
79
|
})]
|
|
79
80
|
});
|
|
80
81
|
};
|
|
@@ -90,6 +91,8 @@ const buildPostcssPlugins = (options) => {
|
|
|
90
91
|
const plugins = [options.tailwindPlugin, autoprefixer()];
|
|
91
92
|
if (options.shouldPruneVars) plugins.push(...getPruneVarPlugins(options.safeVarPrefixes));
|
|
92
93
|
if (options.scopeClass) plugins.push(postcssScope(options.scopeClass));
|
|
94
|
+
const otherBoundaryScopes = options.otherBoundaryScopes ?? [];
|
|
95
|
+
if (otherBoundaryScopes.length > 0) plugins.push(appendIsolationExclusionPlugin(otherBoundaryScopes));
|
|
93
96
|
return plugins;
|
|
94
97
|
};
|
|
95
98
|
const applyScopedColorModeFix = async (css, scopeClass) => {
|
|
@@ -102,6 +105,7 @@ const optimizeGeneratedCss = async (options) => {
|
|
|
102
105
|
removeUnusedFonts: options.optimizationConfig?.removeUnusedFonts,
|
|
103
106
|
removeEmptyRules: options.optimizationConfig?.removeEmptyRules,
|
|
104
107
|
aggregateDuplicateSelectors: options.optimizationConfig?.aggregateDuplicateSelectors,
|
|
108
|
+
mergeIdenticalDeclarations: options.optimizationConfig?.mergeIdenticalDeclarations,
|
|
105
109
|
referenceCss: options.referenceCss,
|
|
106
110
|
scopeClass: options.scopeClass
|
|
107
111
|
};
|
|
@@ -43,11 +43,13 @@ const generateCSS = async (safelist, config, options) => {
|
|
|
43
43
|
safelist,
|
|
44
44
|
config,
|
|
45
45
|
enablePreflight: cssFlags.enablePreflight,
|
|
46
|
-
enableFontFaceDeclarations: cssFlags.enableFontFaceDeclarations
|
|
46
|
+
enableFontFaceDeclarations: cssFlags.enableFontFaceDeclarations,
|
|
47
|
+
emit: options?.cssOptions?.emit
|
|
47
48
|
}),
|
|
48
49
|
shouldPruneVars: cssFlags.shouldPruneVars,
|
|
49
50
|
safeVarPrefixes: options?.safeVarPrefixes ?? [],
|
|
50
|
-
scopeClass
|
|
51
|
+
scopeClass,
|
|
52
|
+
otherBoundaryScopes: options?.otherBoundaryScopes ?? []
|
|
51
53
|
});
|
|
52
54
|
const result = await measureAsync("gen:postcss", () => postcss(plugins).process(sourceCSS, { from: void 0 }));
|
|
53
55
|
let css = await measureAsync("gen:scoped-color-fix", () => applyScopedColorModeFix(result.css, scopeClass));
|
|
@@ -49,6 +49,87 @@ const fixScopedColorModeSelectorsPlugin = (scopeClass) => {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* True when any declaration in the rule references a UDS token variable
|
|
54
|
+
* (`var(--uds-…)`) — i.e. the rule is theme-driven. See
|
|
55
|
+
* {@link appendIsolationExclusionPlugin} for why only those rules are isolated.
|
|
56
|
+
*/
|
|
57
|
+
const ruleReferencesUdsToken = (rule) => {
|
|
58
|
+
let found = false;
|
|
59
|
+
rule.walkDecls((decl) => {
|
|
60
|
+
if (decl.value.includes("var(--uds-")) {
|
|
61
|
+
found = true;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return found;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* PostCSS plugin to enforce per-bundle style isolation by appending an
|
|
69
|
+
* exclusion suffix to *theme-driven* rules' selectors.
|
|
70
|
+
*
|
|
71
|
+
* Each qualifying rule's deepest simple selector gets augmented with
|
|
72
|
+
* `:not(:where(:is(.A, .B, …) *))`
|
|
73
|
+
*
|
|
74
|
+
* where `.A`, `.B`, … are the scope classes of OTHER scoped bundles loaded
|
|
75
|
+
* alongside this one (i.e. every `scoped:` entry in `uds.theme.ts` except
|
|
76
|
+
* this bundle's own scope) plus any non-bundled selectors declared via
|
|
77
|
+
* `css.excludeBoundaries`. This prevents this bundle's rules from matching
|
|
78
|
+
* elements that live inside another scoped library's subtree, so each
|
|
79
|
+
* scoped package gets a clean cascade inside its own scope-class wrapper
|
|
80
|
+
* without bleed-through from neighboring bundles.
|
|
81
|
+
*
|
|
82
|
+
* **Why "theme-driven" only:** rules that reference a UDS token variable
|
|
83
|
+
* (`var(--uds-…)` in any declaration value) carry committed-theme values
|
|
84
|
+
* that would wrongly leak into a preview cell when the draft theme leaves
|
|
85
|
+
* a property undefined (cascade order can't undo a value that isn't being
|
|
86
|
+
* overridden). Rules that do NOT reference a token — Tailwind utilities,
|
|
87
|
+
* structural resets, `display: flex` on a layout class, padding helpers,
|
|
88
|
+
* etc. — are theme-independent: blocking them inside a preview boundary
|
|
89
|
+
* is collateral damage that strips needed structural/utility behavior from
|
|
90
|
+
* the rendered content. The plugin therefore leaves non-token-referencing
|
|
91
|
+
* rules untouched (see {@link ruleReferencesUdsToken}).
|
|
92
|
+
*
|
|
93
|
+
* **Why non-rest only:** among token-referencing rules, only non-rest
|
|
94
|
+
* STATE rules (`:hover`, `:focus-within`, `:has(…)`, etc.) need the fence.
|
|
95
|
+
* A rest theme rule that leaks into a nested foreign scope resolves its
|
|
96
|
+
* `var(--uds-*)` to that scope's own tokens (postcss-scope rewrites
|
|
97
|
+
* `:root`→scope), so it is value-safe and fencing it would only add bytes
|
|
98
|
+
* to the wire. The harmful leak is a non-rest state rule whose higher
|
|
99
|
+
* specificity wins over the nested scope's own state rule, overriding a
|
|
100
|
+
* value the nested scope explicitly set. Only those selectors are augmented
|
|
101
|
+
* (see {@link isNonRestStateSelector}).
|
|
102
|
+
*
|
|
103
|
+
* Bundles whose neighbor list is empty (no other scoped libraries and no
|
|
104
|
+
* `excludeBoundaries` entries are configured) get the selector unchanged —
|
|
105
|
+
* the plugin is a no-op there and the bundle is byte-identical to its
|
|
106
|
+
* pre-isolation output.
|
|
107
|
+
*
|
|
108
|
+
* `:where()` contributes (0, 0, 0) specificity, so the augmented selector
|
|
109
|
+
* preserves the original's specificity exactly.
|
|
110
|
+
*
|
|
111
|
+
* NOTE: A single CSS `@scope (.<scope>) to (.A, .B, …)` declaration at the
|
|
112
|
+
* top of the bundle would be the cleaner expression of this isolation — one
|
|
113
|
+
* declaration instead of N selector tweaks — but `@scope` lacked sufficient
|
|
114
|
+
* browser support when this was implemented (May 2026 — requires Chrome
|
|
115
|
+
* 118+, Safari 17.4+, Firefox 128+). When the support floor moves up, this
|
|
116
|
+
* plugin can be replaced with an `@scope` wrapper around the bundle,
|
|
117
|
+
* dropping the per-rule suffix.
|
|
118
|
+
*/
|
|
119
|
+
const appendIsolationExclusionPlugin = (otherBoundaryScopes) => {
|
|
120
|
+
return {
|
|
121
|
+
postcssPlugin: "append-isolation-exclusion",
|
|
122
|
+
Once(root) {
|
|
123
|
+
if (otherBoundaryScopes.length === 0) return;
|
|
124
|
+
root.walkRules((rule) => {
|
|
125
|
+
if (!ruleReferencesUdsToken(rule)) return;
|
|
126
|
+
const nextSelectors = rule.selectors.map((selector) => require_postcss_helpers.isNonRestStateSelector(selector) ? require_postcss_helpers.injectIsolationExclusion(selector, otherBoundaryScopes) : selector);
|
|
127
|
+
if (nextSelectors.some((selector, idx) => selector !== rule.selectors[idx])) rule.selectors = nextSelectors;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
};
|
|
52
132
|
//#endregion
|
|
133
|
+
exports.appendIsolationExclusionPlugin = appendIsolationExclusionPlugin;
|
|
53
134
|
exports.fixScopedColorModeSelectorsPlugin = fixScopedColorModeSelectorsPlugin;
|
|
54
135
|
exports.fixScopedSelfOrParentSelectorsPlugin = fixScopedSelfOrParentSelectorsPlugin;
|
|
@@ -33,6 +33,66 @@ const getScopedSelfOrParentAlternativeSelector = (selector, scopeClass) => {
|
|
|
33
33
|
if (/^\.uds-color-mode-(?:dark|light)(?:$|[.#[:])/.test(scopedTarget)) return null;
|
|
34
34
|
return `${scopeClass}${scopedTarget}${targetMatch[2]}`;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Insert the isolation-exclusion suffix
|
|
38
|
+
* `:not(:where(:is(.A, .B, …) *))` into a single selector. The list is the
|
|
39
|
+
* set of scope classes belonging to OTHER scoped bundles loaded on the page;
|
|
40
|
+
* the suffix causes this bundle's rule to skip elements whose ancestor chain
|
|
41
|
+
* contains any of those scope classes, preventing rule bleed-through into
|
|
42
|
+
* another scoped library's subtree.
|
|
43
|
+
*
|
|
44
|
+
* For selectors ending in a pseudo-element (`.x::placeholder`, `.x::before`,
|
|
45
|
+
* etc.), the suffix is inserted *before* the pseudo-element marker, since
|
|
46
|
+
* pseudo-elements must remain at the very end of a selector and the
|
|
47
|
+
* exclusion semantically applies to the host element.
|
|
48
|
+
*
|
|
49
|
+
* PRECONDITION: a pseudo-element, when present, is the trailing token of the
|
|
50
|
+
* selector — true for every UDS/Tailwind-generated rule this plugin runs on.
|
|
51
|
+
* Detection is a simple `lastIndexOf('::')`, so a pseudo-element in a non-final
|
|
52
|
+
* compound (`.x::before .y`) or a literal `::` inside an attribute value would
|
|
53
|
+
* be mis-handled; neither shape occurs in the generated CSS this processes.
|
|
54
|
+
*
|
|
55
|
+
* Returns the selector unchanged when `otherBoundaryScopes` is empty —
|
|
56
|
+
* there's nothing to exclude.
|
|
57
|
+
*/
|
|
58
|
+
const injectIsolationExclusion = (selector, otherBoundaryScopes) => {
|
|
59
|
+
if (otherBoundaryScopes.length === 0) return selector;
|
|
60
|
+
const suffix = `:not(:where(:is(${otherBoundaryScopes.map((scope) => {
|
|
61
|
+
const trimmed = scope.trim();
|
|
62
|
+
return /^[.#[:]/.test(trimmed) ? trimmed : `.${trimmed}`;
|
|
63
|
+
}).join(", ")}) *))`;
|
|
64
|
+
const pseudoElementIdx = selector.lastIndexOf("::");
|
|
65
|
+
if (pseudoElementIdx === -1) return `${selector}${suffix}`;
|
|
66
|
+
return `${selector.slice(0, pseudoElementIdx)}${suffix}${selector.slice(pseudoElementIdx)}`;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* State pseudo-classes UDS emits for non-rest states: interactive
|
|
70
|
+
* (`:hover`, `:active`, `:focus`, `:focus-within`, `:focus-visible`,
|
|
71
|
+
* `:visited`, legacy `:disabled`) and `:has(…)` — which UDS uses only for the
|
|
72
|
+
* `invalid`/`readonly`/`placeholder-shown`/`autofill`/`disabled` atoms. The
|
|
73
|
+
* rest baseline carries none of these. Pseudo-ELEMENTS (`::placeholder`,
|
|
74
|
+
* `::before`) use `::` so the single-colon matcher skips them; the structural
|
|
75
|
+
* `:where()`/`:is()`/`:not()` (hover blockers and the isolation suffix itself)
|
|
76
|
+
* are not states.
|
|
77
|
+
*
|
|
78
|
+
* MAINTENANCE: `readonly`/`placeholder-shown`/`autofill` (and `invalid`/
|
|
79
|
+
* `disabled`) are intentionally absent as direct terms — UDS emits them wrapped
|
|
80
|
+
* in `:has(…)`, so the `:has\(` branch already catches them. If a component ever
|
|
81
|
+
* emits one of those atoms as a DIRECT pseudo-class (e.g. `:read-only`), add it
|
|
82
|
+
* here: a missed state selector is a false negative (state bleed — the leak this
|
|
83
|
+
* fence prevents), whereas an over-match would only fence a value-safe rest rule.
|
|
84
|
+
*/
|
|
85
|
+
const STATE_PSEUDO_RE = /:(?:hover|active|visited|disabled|focus(?:-within|-visible)?)\b|:has\(/;
|
|
86
|
+
/**
|
|
87
|
+
* True when a selector targets a non-rest (state) rule. Only non-rest theme
|
|
88
|
+
* rules need the isolation fence: a rest theme rule leaking into a nested
|
|
89
|
+
* foreign scope resolves its `var(--uds-*)` to that scope's own tokens
|
|
90
|
+
* (postcss-scope rewrites `:root`→scope), so it is value-safe and fencing it
|
|
91
|
+
* would only add bytes to the wire.
|
|
92
|
+
*/
|
|
93
|
+
const isNonRestStateSelector = (selector) => STATE_PSEUDO_RE.test(selector);
|
|
36
94
|
//#endregion
|
|
37
95
|
exports.buildScopedColorModeSelectorList = buildScopedColorModeSelectorList;
|
|
38
96
|
exports.getScopedSelfOrParentAlternativeSelector = getScopedSelfOrParentAlternativeSelector;
|
|
97
|
+
exports.injectIsolationExclusion = injectIsolationExclusion;
|
|
98
|
+
exports.isNonRestStateSelector = isNonRestStateSelector;
|
|
@@ -33,5 +33,63 @@ const getScopedSelfOrParentAlternativeSelector = (selector, scopeClass) => {
|
|
|
33
33
|
if (/^\.uds-color-mode-(?:dark|light)(?:$|[.#[:])/.test(scopedTarget)) return null;
|
|
34
34
|
return `${scopeClass}${scopedTarget}${targetMatch[2]}`;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Insert the isolation-exclusion suffix
|
|
38
|
+
* `:not(:where(:is(.A, .B, …) *))` into a single selector. The list is the
|
|
39
|
+
* set of scope classes belonging to OTHER scoped bundles loaded on the page;
|
|
40
|
+
* the suffix causes this bundle's rule to skip elements whose ancestor chain
|
|
41
|
+
* contains any of those scope classes, preventing rule bleed-through into
|
|
42
|
+
* another scoped library's subtree.
|
|
43
|
+
*
|
|
44
|
+
* For selectors ending in a pseudo-element (`.x::placeholder`, `.x::before`,
|
|
45
|
+
* etc.), the suffix is inserted *before* the pseudo-element marker, since
|
|
46
|
+
* pseudo-elements must remain at the very end of a selector and the
|
|
47
|
+
* exclusion semantically applies to the host element.
|
|
48
|
+
*
|
|
49
|
+
* PRECONDITION: a pseudo-element, when present, is the trailing token of the
|
|
50
|
+
* selector — true for every UDS/Tailwind-generated rule this plugin runs on.
|
|
51
|
+
* Detection is a simple `lastIndexOf('::')`, so a pseudo-element in a non-final
|
|
52
|
+
* compound (`.x::before .y`) or a literal `::` inside an attribute value would
|
|
53
|
+
* be mis-handled; neither shape occurs in the generated CSS this processes.
|
|
54
|
+
*
|
|
55
|
+
* Returns the selector unchanged when `otherBoundaryScopes` is empty —
|
|
56
|
+
* there's nothing to exclude.
|
|
57
|
+
*/
|
|
58
|
+
const injectIsolationExclusion = (selector, otherBoundaryScopes) => {
|
|
59
|
+
if (otherBoundaryScopes.length === 0) return selector;
|
|
60
|
+
const suffix = `:not(:where(:is(${otherBoundaryScopes.map((scope) => {
|
|
61
|
+
const trimmed = scope.trim();
|
|
62
|
+
return /^[.#[:]/.test(trimmed) ? trimmed : `.${trimmed}`;
|
|
63
|
+
}).join(", ")}) *))`;
|
|
64
|
+
const pseudoElementIdx = selector.lastIndexOf("::");
|
|
65
|
+
if (pseudoElementIdx === -1) return `${selector}${suffix}`;
|
|
66
|
+
return `${selector.slice(0, pseudoElementIdx)}${suffix}${selector.slice(pseudoElementIdx)}`;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* State pseudo-classes UDS emits for non-rest states: interactive
|
|
70
|
+
* (`:hover`, `:active`, `:focus`, `:focus-within`, `:focus-visible`,
|
|
71
|
+
* `:visited`, legacy `:disabled`) and `:has(…)` — which UDS uses only for the
|
|
72
|
+
* `invalid`/`readonly`/`placeholder-shown`/`autofill`/`disabled` atoms. The
|
|
73
|
+
* rest baseline carries none of these. Pseudo-ELEMENTS (`::placeholder`,
|
|
74
|
+
* `::before`) use `::` so the single-colon matcher skips them; the structural
|
|
75
|
+
* `:where()`/`:is()`/`:not()` (hover blockers and the isolation suffix itself)
|
|
76
|
+
* are not states.
|
|
77
|
+
*
|
|
78
|
+
* MAINTENANCE: `readonly`/`placeholder-shown`/`autofill` (and `invalid`/
|
|
79
|
+
* `disabled`) are intentionally absent as direct terms — UDS emits them wrapped
|
|
80
|
+
* in `:has(…)`, so the `:has\(` branch already catches them. If a component ever
|
|
81
|
+
* emits one of those atoms as a DIRECT pseudo-class (e.g. `:read-only`), add it
|
|
82
|
+
* here: a missed state selector is a false negative (state bleed — the leak this
|
|
83
|
+
* fence prevents), whereas an over-match would only fence a value-safe rest rule.
|
|
84
|
+
*/
|
|
85
|
+
const STATE_PSEUDO_RE = /:(?:hover|active|visited|disabled|focus(?:-within|-visible)?)\b|:has\(/;
|
|
86
|
+
/**
|
|
87
|
+
* True when a selector targets a non-rest (state) rule. Only non-rest theme
|
|
88
|
+
* rules need the isolation fence: a rest theme rule leaking into a nested
|
|
89
|
+
* foreign scope resolves its `var(--uds-*)` to that scope's own tokens
|
|
90
|
+
* (postcss-scope rewrites `:root`→scope), so it is value-safe and fencing it
|
|
91
|
+
* would only add bytes to the wire.
|
|
92
|
+
*/
|
|
93
|
+
const isNonRestStateSelector = (selector) => STATE_PSEUDO_RE.test(selector);
|
|
36
94
|
//#endregion
|
|
37
|
-
export { buildScopedColorModeSelectorList, getScopedSelfOrParentAlternativeSelector };
|
|
95
|
+
export { buildScopedColorModeSelectorList, getScopedSelfOrParentAlternativeSelector, injectIsolationExclusion, isNonRestStateSelector };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
-
import { buildScopedColorModeSelectorList, getScopedSelfOrParentAlternativeSelector } from "./postcss.helpers.js";
|
|
2
|
+
import { buildScopedColorModeSelectorList, getScopedSelfOrParentAlternativeSelector, injectIsolationExclusion, isNonRestStateSelector } from "./postcss.helpers.js";
|
|
3
3
|
//#region ../css/dist/css/postcss.mjs
|
|
4
4
|
/*! © 2026 Yahoo, Inc. UDS CSS v0.0.0-development */
|
|
5
5
|
const fixScopedSelfOrParentSelectorsPlugin = (scopeClass) => {
|
|
@@ -49,5 +49,85 @@ const fixScopedColorModeSelectorsPlugin = (scopeClass) => {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* True when any declaration in the rule references a UDS token variable
|
|
54
|
+
* (`var(--uds-…)`) — i.e. the rule is theme-driven. See
|
|
55
|
+
* {@link appendIsolationExclusionPlugin} for why only those rules are isolated.
|
|
56
|
+
*/
|
|
57
|
+
const ruleReferencesUdsToken = (rule) => {
|
|
58
|
+
let found = false;
|
|
59
|
+
rule.walkDecls((decl) => {
|
|
60
|
+
if (decl.value.includes("var(--uds-")) {
|
|
61
|
+
found = true;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return found;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* PostCSS plugin to enforce per-bundle style isolation by appending an
|
|
69
|
+
* exclusion suffix to *theme-driven* rules' selectors.
|
|
70
|
+
*
|
|
71
|
+
* Each qualifying rule's deepest simple selector gets augmented with
|
|
72
|
+
* `:not(:where(:is(.A, .B, …) *))`
|
|
73
|
+
*
|
|
74
|
+
* where `.A`, `.B`, … are the scope classes of OTHER scoped bundles loaded
|
|
75
|
+
* alongside this one (i.e. every `scoped:` entry in `uds.theme.ts` except
|
|
76
|
+
* this bundle's own scope) plus any non-bundled selectors declared via
|
|
77
|
+
* `css.excludeBoundaries`. This prevents this bundle's rules from matching
|
|
78
|
+
* elements that live inside another scoped library's subtree, so each
|
|
79
|
+
* scoped package gets a clean cascade inside its own scope-class wrapper
|
|
80
|
+
* without bleed-through from neighboring bundles.
|
|
81
|
+
*
|
|
82
|
+
* **Why "theme-driven" only:** rules that reference a UDS token variable
|
|
83
|
+
* (`var(--uds-…)` in any declaration value) carry committed-theme values
|
|
84
|
+
* that would wrongly leak into a preview cell when the draft theme leaves
|
|
85
|
+
* a property undefined (cascade order can't undo a value that isn't being
|
|
86
|
+
* overridden). Rules that do NOT reference a token — Tailwind utilities,
|
|
87
|
+
* structural resets, `display: flex` on a layout class, padding helpers,
|
|
88
|
+
* etc. — are theme-independent: blocking them inside a preview boundary
|
|
89
|
+
* is collateral damage that strips needed structural/utility behavior from
|
|
90
|
+
* the rendered content. The plugin therefore leaves non-token-referencing
|
|
91
|
+
* rules untouched (see {@link ruleReferencesUdsToken}).
|
|
92
|
+
*
|
|
93
|
+
* **Why non-rest only:** among token-referencing rules, only non-rest
|
|
94
|
+
* STATE rules (`:hover`, `:focus-within`, `:has(…)`, etc.) need the fence.
|
|
95
|
+
* A rest theme rule that leaks into a nested foreign scope resolves its
|
|
96
|
+
* `var(--uds-*)` to that scope's own tokens (postcss-scope rewrites
|
|
97
|
+
* `:root`→scope), so it is value-safe and fencing it would only add bytes
|
|
98
|
+
* to the wire. The harmful leak is a non-rest state rule whose higher
|
|
99
|
+
* specificity wins over the nested scope's own state rule, overriding a
|
|
100
|
+
* value the nested scope explicitly set. Only those selectors are augmented
|
|
101
|
+
* (see {@link isNonRestStateSelector}).
|
|
102
|
+
*
|
|
103
|
+
* Bundles whose neighbor list is empty (no other scoped libraries and no
|
|
104
|
+
* `excludeBoundaries` entries are configured) get the selector unchanged —
|
|
105
|
+
* the plugin is a no-op there and the bundle is byte-identical to its
|
|
106
|
+
* pre-isolation output.
|
|
107
|
+
*
|
|
108
|
+
* `:where()` contributes (0, 0, 0) specificity, so the augmented selector
|
|
109
|
+
* preserves the original's specificity exactly.
|
|
110
|
+
*
|
|
111
|
+
* NOTE: A single CSS `@scope (.<scope>) to (.A, .B, …)` declaration at the
|
|
112
|
+
* top of the bundle would be the cleaner expression of this isolation — one
|
|
113
|
+
* declaration instead of N selector tweaks — but `@scope` lacked sufficient
|
|
114
|
+
* browser support when this was implemented (May 2026 — requires Chrome
|
|
115
|
+
* 118+, Safari 17.4+, Firefox 128+). When the support floor moves up, this
|
|
116
|
+
* plugin can be replaced with an `@scope` wrapper around the bundle,
|
|
117
|
+
* dropping the per-rule suffix.
|
|
118
|
+
*/
|
|
119
|
+
const appendIsolationExclusionPlugin = (otherBoundaryScopes) => {
|
|
120
|
+
return {
|
|
121
|
+
postcssPlugin: "append-isolation-exclusion",
|
|
122
|
+
Once(root) {
|
|
123
|
+
if (otherBoundaryScopes.length === 0) return;
|
|
124
|
+
root.walkRules((rule) => {
|
|
125
|
+
if (!ruleReferencesUdsToken(rule)) return;
|
|
126
|
+
const nextSelectors = rule.selectors.map((selector) => isNonRestStateSelector(selector) ? injectIsolationExclusion(selector, otherBoundaryScopes) : selector);
|
|
127
|
+
if (nextSelectors.some((selector, idx) => selector !== rule.selectors[idx])) rule.selectors = nextSelectors;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
};
|
|
52
132
|
//#endregion
|
|
53
|
-
export { fixScopedColorModeSelectorsPlugin, fixScopedSelfOrParentSelectorsPlugin };
|
|
133
|
+
export { appendIsolationExclusionPlugin, fixScopedColorModeSelectorsPlugin, fixScopedSelfOrParentSelectorsPlugin };
|
|
@@ -340,11 +340,17 @@ const runThemeMode = async (options, context) => {
|
|
|
340
340
|
...require_safelist.getInternalSafelistClasses()
|
|
341
341
|
]);
|
|
342
342
|
const allMotionComponents = [...inheritedComponents];
|
|
343
|
+
const emit = options.emit ?? themeConfig.css?.emit ?? "exhaustive";
|
|
344
|
+
const allBoundarySelectors = [...emit === "selective" ? scopedPackageTargets.map((target) => target.scopeClass) : [], ...themeConfig.css?.excludeBoundaries ?? []];
|
|
343
345
|
require_perf.captureMemory("before-gen");
|
|
344
346
|
const mainCssResult = await require_perf.measureAsync("gen", () => require_generate.generateCSS([...themeConfig.css?.safelist ?? [], ...mainSafelist], appConfig, {
|
|
345
347
|
scope: options.scope,
|
|
346
348
|
contentDir: entryDirs,
|
|
347
|
-
cssOptions:
|
|
349
|
+
cssOptions: {
|
|
350
|
+
...themeConfig.css,
|
|
351
|
+
emit
|
|
352
|
+
},
|
|
353
|
+
otherBoundaryScopes: allBoundarySelectors,
|
|
348
354
|
safeVarPrefixes: [
|
|
349
355
|
...require_utils.getMotionVarPrefixes(context.componentData, allMotionComponents),
|
|
350
356
|
...require_utils.getConfigurableCssVariables(),
|
|
@@ -375,7 +381,11 @@ const runThemeMode = async (options, context) => {
|
|
|
375
381
|
const scopedCssResult = await require_generate.generateCSS([...themeConfig.css?.safelist ?? [], ...packageSafelist], packageConfig, {
|
|
376
382
|
scope: scopedPackageTarget.scopeClass,
|
|
377
383
|
contentDir: scopedPackageTarget.entryDirs,
|
|
378
|
-
cssOptions:
|
|
384
|
+
cssOptions: {
|
|
385
|
+
...themeConfig.css,
|
|
386
|
+
emit
|
|
387
|
+
},
|
|
388
|
+
otherBoundaryScopes: allBoundarySelectors.filter((selector) => selector !== scopedPackageTarget.scopeClass),
|
|
379
389
|
referenceCss: themeConfig.css?.optimization?.deduplicateScopedCss === false ? void 0 : mainCssResult.css,
|
|
380
390
|
safeVarPrefixes: [
|
|
381
391
|
...require_utils.getMotionVarPrefixes(context.componentData, [...packageScanResult.components]),
|
|
@@ -338,11 +338,17 @@ const runThemeMode = async (options, context) => {
|
|
|
338
338
|
...getInternalSafelistClasses()
|
|
339
339
|
]);
|
|
340
340
|
const allMotionComponents = [...inheritedComponents];
|
|
341
|
+
const emit = options.emit ?? themeConfig.css?.emit ?? "exhaustive";
|
|
342
|
+
const allBoundarySelectors = [...emit === "selective" ? scopedPackageTargets.map((target) => target.scopeClass) : [], ...themeConfig.css?.excludeBoundaries ?? []];
|
|
341
343
|
captureMemory("before-gen");
|
|
342
344
|
const mainCssResult = await measureAsync("gen", () => generateCSS([...themeConfig.css?.safelist ?? [], ...mainSafelist], appConfig, {
|
|
343
345
|
scope: options.scope,
|
|
344
346
|
contentDir: entryDirs,
|
|
345
|
-
cssOptions:
|
|
347
|
+
cssOptions: {
|
|
348
|
+
...themeConfig.css,
|
|
349
|
+
emit
|
|
350
|
+
},
|
|
351
|
+
otherBoundaryScopes: allBoundarySelectors,
|
|
346
352
|
safeVarPrefixes: [
|
|
347
353
|
...getMotionVarPrefixes(context.componentData, allMotionComponents),
|
|
348
354
|
...getConfigurableCssVariables(),
|
|
@@ -373,7 +379,11 @@ const runThemeMode = async (options, context) => {
|
|
|
373
379
|
const scopedCssResult = await generateCSS([...themeConfig.css?.safelist ?? [], ...packageSafelist], packageConfig, {
|
|
374
380
|
scope: scopedPackageTarget.scopeClass,
|
|
375
381
|
contentDir: scopedPackageTarget.entryDirs,
|
|
376
|
-
cssOptions:
|
|
382
|
+
cssOptions: {
|
|
383
|
+
...themeConfig.css,
|
|
384
|
+
emit
|
|
385
|
+
},
|
|
386
|
+
otherBoundaryScopes: allBoundarySelectors.filter((selector) => selector !== scopedPackageTarget.scopeClass),
|
|
377
387
|
referenceCss: themeConfig.css?.optimization?.deduplicateScopedCss === false ? void 0 : mainCssResult.css,
|
|
378
388
|
safeVarPrefixes: [
|
|
379
389
|
...getMotionVarPrefixes(context.componentData, [...packageScanResult.components]),
|
|
@@ -29,6 +29,12 @@ interface UDSCSSOptimizationOptions {
|
|
|
29
29
|
removeEmptyRules?: boolean;
|
|
30
30
|
/** Aggregate duplicate selectors (default: true) */
|
|
31
31
|
aggregateDuplicateSelectors?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Merge adjacent rules sharing an identical declaration block into a single
|
|
34
|
+
* comma-separated selector group (default: true). Cascade-safe: only
|
|
35
|
+
* consecutive sibling rules are merged.
|
|
36
|
+
*/
|
|
37
|
+
mergeIdenticalDeclarations?: boolean;
|
|
32
38
|
/**
|
|
33
39
|
* Remove duplicate content from scoped CSS that already exists in main CSS (default: true)
|
|
34
40
|
* Currently deduplicates @font-face declarations. Reduces bundle size when scoped packages
|
|
@@ -29,6 +29,12 @@ interface UDSCSSOptimizationOptions {
|
|
|
29
29
|
removeEmptyRules?: boolean;
|
|
30
30
|
/** Aggregate duplicate selectors (default: true) */
|
|
31
31
|
aggregateDuplicateSelectors?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Merge adjacent rules sharing an identical declaration block into a single
|
|
34
|
+
* comma-separated selector group (default: true). Cascade-safe: only
|
|
35
|
+
* consecutive sibling rules are merged.
|
|
36
|
+
*/
|
|
37
|
+
mergeIdenticalDeclarations?: boolean;
|
|
32
38
|
/**
|
|
33
39
|
* Remove duplicate content from scoped CSS that already exists in main CSS (default: true)
|
|
34
40
|
* Currently deduplicates @font-face declarations. Reduces bundle size when scoped packages
|
|
@@ -8,7 +8,7 @@ const require_index$1 = require("../../motion-tokens/dist/index.cjs");
|
|
|
8
8
|
/*! © 2026 Yahoo, Inc. UDS CSS v0.0.0-development */
|
|
9
9
|
/*! © 2026 Yahoo, Inc. UDS Default Config v0.0.0-development */
|
|
10
10
|
const isInputWrapperCtx = (context) => {
|
|
11
|
-
return context.componentName === "input" && context.layer === "inputWrapper";
|
|
11
|
+
return context.componentName === "input" && (context.layer === "inputWrapper" || context.layer === "inputWrapperDynamic");
|
|
12
12
|
};
|
|
13
13
|
const hexToRgb = (hex) => {
|
|
14
14
|
hex = hex.replace("#", "");
|