@vectoriox/iox-ui 5.14.0 → 5.16.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.
@@ -1,5 +1,6 @@
1
1
  import { DesignMode } from './design-token.model';
2
2
  import { SPEC_A11Y, SPEC_SCHEMES } from './flavor-spec.generated';
3
+ import { specTokenNames } from './token-names';
3
4
 
4
5
  /**
5
6
  * Design-token → CSS compilation, shared by BOTH renderers (flavors F0, design-system DS1).
@@ -14,6 +15,11 @@ import { SPEC_A11Y, SPEC_SCHEMES } from './flavor-spec.generated';
14
15
  * 4. reduced transparency `@media (prefers-reduced-transparency: reduce)`
15
16
  * 5. dark — LAST `@media (prefers-color-scheme: dark)`
16
17
  *
18
+ * MODE (design-system D6) is a compile choice, never a row change: `auto` is the visitor-following
19
+ * order above; `light` compiles the base from light values and emits no dark block; `dark` compiles
20
+ * the base from DARK values (schemes and placeholders resolved from them too) and emits no dark block —
21
+ * so a Midnight site is dark for a light-OS visitor.
22
+ *
17
23
  * ⚠️ The appended-last contract: the dark block REDEFINES custom properties, so every consumer
18
24
  * appends it AFTER all of its base rules — the engine pushes it after the page's css blocks, the
19
25
  * builder registry keeps it as its last token rule (responsive A3 lesson).
@@ -32,6 +38,16 @@ export interface DesignTokenLike {
32
38
  export interface TokenCssOptions {
33
39
  /** Selector that carries the variables instead of `:root` — a preview wrapper, a specimen. */
34
40
  scope?: string;
41
+ /** The studio's colour mode (`settings.design.mode`). Absent = `auto`. */
42
+ mode?: DesignMode;
43
+ /**
44
+ * Declare every SPEC token the rows lack as `initial` on the scope (the guaranteed-invalid value),
45
+ * so a `var()` inside the scope cannot inherit a same-named property from an ANCESTOR. The builder
46
+ * needs it: the admin's `:root` chrome tokens share names with org tokens (`accent`, `surface-2`…),
47
+ * and a missing org row would silently paint the canvas in admin chrome. With it, the canvas
48
+ * computes exactly what the live site computes for that row — unset.
49
+ */
50
+ isolate?: boolean;
35
51
  }
36
52
 
37
53
  export interface TokenCssBlocks {
@@ -114,6 +130,16 @@ const DARK: ResolvedMode = 'dark';
114
130
 
115
131
  const modeValue = (t: DesignTokenLike, mode: ResolvedMode): string => (mode === DARK ? darkOf(t) ?? t.value : t.value);
116
132
 
133
+ /** The rows as ONE mode: each row's value in that mode, no dark override left. `auto` (or none) →
134
+ * the rows unchanged. */
135
+ function rowsForMode(rows: ReadonlyArray<DesignTokenLike>, mode: DesignMode | undefined): DesignTokenLike[] {
136
+ if (mode !== LIGHT && mode !== DARK) return [...rows];
137
+ return rows.map(t => {
138
+ const { values: _dropped, ...rest } = t;
139
+ return { ...rest, value: modeValue(t, mode) };
140
+ });
141
+ }
142
+
117
143
  /** Every token's ROOT value in one mode, with `var(--iox-…)` references resolved against the same
118
144
  * set (recursively; a reference cycle or an unknown token is left as written). */
119
145
  export function resolveTokenValues(
@@ -223,10 +249,14 @@ export function compileTokenCssBlocks(
223
249
  tokens: ReadonlyArray<DesignTokenLike> | null | undefined,
224
250
  opts: TokenCssOptions = {},
225
251
  ): TokenCssBlocks {
226
- const rows = usableTokens(tokens);
252
+ const rows = rowsForMode(usableTokens(tokens), opts.mode);
227
253
  const root = opts.scope ?? ROOT;
254
+ const present = new Set(rows.map(t => t.name));
255
+ const unset: Array<[string, string]> = opts.isolate
256
+ ? [...specTokenNames()].filter(name => !present.has(name)).map((name): [string, string] => [name, 'initial'])
257
+ : [];
228
258
  const base = [
229
- rule(root, rows.map((t): [string, string] => [t.name, t.value])),
259
+ rule(root, [...unset, ...rows.map((t): [string, string] => [t.name, t.value])]),
230
260
  compileSchemeCss(rows, opts.scope),
231
261
  ...ACCESSIBILITY_MEDIA.map(({ query, overrides }) =>
232
262
  media(query, [rule(root, presentOverrides(rows, overrides, false), ' ')])),
@@ -30,3 +30,24 @@ export function unknownTokenReferences(css: string, alsoAllowed: Iterable<string
30
30
  const extra = new Set(alsoAllowed);
31
31
  return tokenReferences(css).filter(name => !spec.has(name) && !extra.has(name));
32
32
  }
33
+
34
+ const LEGACY_SCOPE = /:not\(\s*\.iox-es\s*\)/;
35
+ const RULE = /([^{}]+)\{([^{}]*)\}/g;
36
+
37
+ /**
38
+ * Tokens read by a LEGACY rule — any rule whose selector is scoped `:not(.iox-es)` (the pre-DS2 look
39
+ * of a node that wears no element style, design-system §3b-bis). Such a rule must read NO token at
40
+ * all: a token it reads repaints a pre-DS2 page the moment the org's rows are seeded or a flavor is
41
+ * applied (D5). Returns `selector → names` for each offending rule; comments are ignored.
42
+ */
43
+ export function legacyRuleTokenReferences(css: string): Array<{ selector: string; tokens: string[] }> {
44
+ const text = String(css ?? '').replace(/\/\*[\s\S]*?\*\//g, '');
45
+ const out: Array<{ selector: string; tokens: string[] }> = [];
46
+ for (const m of text.matchAll(RULE)) {
47
+ const selector = m[1].trim().replace(/\s+/g, ' ');
48
+ if (!LEGACY_SCOPE.test(selector)) continue;
49
+ const tokens = tokenReferences(m[2]);
50
+ if (tokens.length) out.push({ selector, tokens });
51
+ }
52
+ return out;
53
+ }