@vectoriox/iox-ui 5.32.1 → 5.34.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.
@@ -1341,6 +1341,7 @@ const SPEC_SCHEMES = {
1341
1341
  "set": {
1342
1342
  "section-bg": "{{accent}}",
1343
1343
  "bg": "{{accent}}",
1344
+ "nav-bg": "{{accent}}",
1344
1345
  "surface": "{{accent-hover}}",
1345
1346
  "surface-2": "{{accent-hover}}",
1346
1347
  "surface-glass": "{{accent-hover}}",
@@ -1363,6 +1364,7 @@ const SPEC_SCHEMES = {
1363
1364
  }
1364
1365
  }
1365
1366
  };
1367
+ const SPEC_SCHEME_INK = "var(--iox-text)";
1366
1368
  const SPEC_A11Y = {
1367
1369
  "reducedMotion": {
1368
1370
  "dur-fast": "0ms",
@@ -2003,6 +2005,23 @@ const SPEC_ELEMENT_STYLES = [
2003
2005
  "fontWeight": "var(--iox-weight-label)"
2004
2006
  }
2005
2007
  },
2008
+ {
2009
+ "id": "badge-label",
2010
+ "label": "Badge label",
2011
+ "group": "Content blocks",
2012
+ "appliesTo": [
2013
+ "Text"
2014
+ ],
2015
+ "base": {
2016
+ "fontFamily": "var(--iox-font-label)",
2017
+ "fontSize": "var(--iox-badge-size)",
2018
+ "fontWeight": "var(--iox-weight-label)",
2019
+ "lineHeight": "inherit",
2020
+ "letterSpacing": "inherit",
2021
+ "textTransform": "inherit",
2022
+ "color": "inherit"
2023
+ }
2024
+ },
2006
2025
  {
2007
2026
  "id": "media-scrim",
2008
2027
  "label": "Text over image",
@@ -12226,570 +12245,754 @@ function styleKeyToKebab(key) {
12226
12245
  }
12227
12246
 
12228
12247
  /**
12229
- * Which `--iox-<name>` a piece of COMPONENT CSS may reference (design-system §3b-bis: "component CSS
12230
- * may reference ONLY tokens defined in this spec"). DS2 shipped seven names nobody defines
12231
- * (`card-media-gap`, `gallery-overlay`, `dialog-close-bg`…): a token the contract does not define can
12232
- * never be set by a flavor, so it is a literal wearing a costume. One guard, run over BOTH
12233
- * renderers' component CSS (iox-ui's spec; the engine's `verify:token-names`).
12248
+ * Element styles — the author's "classes" (design-system plan §3b). A named look is an EXACT trait
12249
+ * map written into a node; every value is a `var(--iox-…)` reference or a flavor-independent
12250
+ * keyword, so a re-theme rewrites token rows and every node wearing the style follows. No renderer
12251
+ * reads `elementStyle`: it records which look a node wears, for the panel row, Reset, adopt and AI.
12252
+ *
12253
+ * Works on two node shapes through one minimal interface, so the builder (a ComponentNode whose
12254
+ * values live in `styleTraits[].traits[].default`) and the server-side composer (a plain serialized
12255
+ * layout node with `styleProps`) share every function here.
12234
12256
  */
12235
- /** The 161 names of the contract's two tiers, plus its legacy aliases. */
12236
- function specTokenNames() {
12237
- return new Set([
12238
- ...SPEC_TIERS.semantic.map(t => t.name),
12239
- ...SPEC_TIERS.component.map(t => t.name),
12240
- ...Object.keys(SPEC_LEGACY_ALIASES),
12241
- ]);
12257
+ var ElementStyleState;
12258
+ (function (ElementStyleState) {
12259
+ ElementStyleState["Hover"] = "hover";
12260
+ ElementStyleState["Active"] = "active";
12261
+ ElementStyleState["Focus"] = "focus";
12262
+ /** The page you're on (CC1 N2): compiles to `[aria-current="page"]`, never a pseudo-class. */
12263
+ ElementStyleState["Current"] = "current";
12264
+ })(ElementStyleState || (ElementStyleState = {}));
12265
+ const ELEMENT_STYLE_STATES = [ElementStyleState.Hover, ElementStyleState.Active, ElementStyleState.Focus, ElementStyleState.Current];
12266
+ const ELEMENT_STYLES = SPEC_ELEMENT_STYLES;
12267
+ /** Legacy `Button.variant` → the element style it maps to (adopt, DS7). */
12268
+ const BUTTON_VARIANT_TO_STYLE = SPEC_BUTTON_VARIANT_TO_STYLE;
12269
+ /** Palette entries that are a Container wearing an element style (Card · Panel · Navigation bar). */
12270
+ const PALETTE_PRESETS = SPEC_PALETTE_PRESETS;
12271
+ const BY_ID = new Map(ELEMENT_STYLES.map(s => [s.id, s]));
12272
+ function elementStyleById(id) {
12273
+ return id ? BY_ID.get(id) : undefined;
12242
12274
  }
12243
- const REFERENCE$1 = /var\(\s*--iox-([a-z0-9-]+)/gi;
12244
- /** Every distinct token a stylesheet READS (`var(--iox-x…)`), in first-seen order. */
12245
- function tokenReferences(css) {
12246
- return [...new Set(Array.from(css.matchAll(REFERENCE$1), m => m[1]))];
12275
+ /** The styles a node of `type` may wear — the Style panel's select. */
12276
+ function elementStylesFor(type) {
12277
+ return ELEMENT_STYLES.filter(s => s.appliesTo.includes(type));
12247
12278
  }
12248
- /** The referenced names that are neither spec tokens nor in `alsoAllowed`. */
12249
- function unknownTokenReferences(css, alsoAllowed = []) {
12250
- const spec = specTokenNames();
12251
- const extra = new Set(alsoAllowed);
12252
- return tokenReferences(css).filter(name => !spec.has(name) && !extra.has(name));
12279
+ var OwnedTraitLink;
12280
+ (function (OwnedTraitLink) {
12281
+ /** The node still holds the style's value. */
12282
+ OwnedTraitLink["Linked"] = "linked";
12283
+ /** The author replaced it on this node. */
12284
+ OwnedTraitLink["Changed"] = "changed";
12285
+ })(OwnedTraitLink || (OwnedTraitLink = {}));
12286
+ const usesTraits = (t) => Array.isArray(t.styleTraits);
12287
+ function findTrait(target, name) {
12288
+ for (const group of target.styleTraits ?? []) {
12289
+ const hit = group.traits.find(tr => tr.name === name);
12290
+ if (hit)
12291
+ return hit;
12292
+ }
12293
+ return undefined;
12253
12294
  }
12254
- const LEGACY_SCOPE = /:not\(\s*\.iox-es\s*\)/;
12255
- /** Comments out: block comments, and SCSS `//` line comments (never the `//` of a `url(https://…)`). */
12256
- const stripComments = (css) => String(css ?? '')
12257
- .replace(/\/\*[\s\S]*?\*\//g, '')
12258
- .replace(/(^|[^:\\])\/\/[^\n]*/g, '$1');
12259
- /** A nested SCSS selector against its parent's: `&` is the parent; no `&` means a descendant. */
12260
- function resolveNested(parent, selector) {
12261
- if (!parent)
12262
- return selector;
12263
- return selector.split(',').map(part => {
12264
- const own = part.trim();
12265
- return own.includes('&') ? own.replace(/&/g, parent) : `${parent} ${own}`;
12266
- }).join(', ');
12295
+ function readBase(target, name) {
12296
+ return usesTraits(target) ? findTrait(target, name)?.default : target.styleProps?.[name];
12267
12297
  }
12268
- /**
12269
- * Tokens read by a LEGACY rule — any rule whose selector is scoped `:not(.iox-es)` (the pre-DS2 look
12270
- * of a node that wears no element style, design-system §3b-bis). Such a rule must read NO token at
12271
- * all: a token it reads repaints a pre-DS2 page the moment the org's rows are seeded or a flavor is
12272
- * applied (D5). Returns `selector → names` for each offending rule; comments are ignored. SCSS
12273
- * nesting is resolved (DS4.1): `.x:where(:not(.iox-es)) { &:hover { … } }` is the legacy rule
12274
- * `.x:where(:not(.iox-es)):hover`, and an at-rule (`@media`) between them does not break the chain.
12275
- */
12276
- function legacyRuleTokenReferences(css) {
12277
- const text = stripComments(css);
12298
+ /** False when a builder node has no such trait — the value would be dropped. */
12299
+ function writeBase(target, name, value) {
12300
+ if (usesTraits(target)) {
12301
+ const trait = findTrait(target, name);
12302
+ if (!trait)
12303
+ return false;
12304
+ trait.default = value;
12305
+ return true;
12306
+ }
12307
+ target.styleProps = target.styleProps ?? {};
12308
+ if (value === undefined)
12309
+ delete target.styleProps[name];
12310
+ else
12311
+ target.styleProps[name] = value;
12312
+ return true;
12313
+ }
12314
+ function readState(target, state, name) {
12315
+ return target.stateStyles?.[state]?.[name];
12316
+ }
12317
+ function writeState(target, state, name, value) {
12318
+ target.stateStyles = target.stateStyles ?? {};
12319
+ target.stateStyles[state] = { ...(target.stateStyles[state] ?? {}), [name]: value };
12320
+ }
12321
+ function deleteState(target, state, name) {
12322
+ const map = target.stateStyles?.[state];
12323
+ if (!map)
12324
+ return;
12325
+ const { [name]: _drop, ...rest } = map;
12326
+ if (Object.keys(rest).length)
12327
+ target.stateStyles[state] = rest;
12328
+ else
12329
+ delete target.stateStyles[state];
12330
+ }
12331
+ /** Every (state, trait, value) a style owns, base first. */
12332
+ function ownedEntries(style) {
12278
12333
  const out = [];
12279
- const stack = [];
12280
- const report = (frame) => {
12281
- if (!LEGACY_SCOPE.test(frame.selector))
12282
- return;
12283
- const tokens = tokenReferences(frame.body);
12284
- if (tokens.length)
12285
- out.push({ selector: frame.selector, tokens });
12286
- };
12287
- let buffer = '';
12288
- for (const ch of text) {
12289
- if (ch === '{') {
12290
- const head = buffer.trim().replace(/\s+/g, ' ');
12291
- buffer = '';
12292
- // Declarations before a nested block belong to the parent (`color: x; &:hover {`).
12293
- const cut = head.lastIndexOf(';');
12294
- const prelude = cut >= 0 ? head.slice(cut + 1).trim() : head;
12295
- const parent = stack[stack.length - 1];
12296
- if (parent && cut >= 0)
12297
- parent.body += head.slice(0, cut + 1);
12298
- const inherited = parent?.selector ?? '';
12299
- const selector = prelude.startsWith('@') ? inherited : resolveNested(inherited, prelude);
12300
- stack.push({ selector, body: '' });
12301
- }
12302
- else if (ch === '}') {
12303
- const frame = stack.pop();
12304
- if (frame) {
12305
- frame.body += buffer;
12306
- report(frame);
12307
- }
12308
- buffer = '';
12309
- }
12310
- else if (ch === ';' && stack.length) {
12311
- stack[stack.length - 1].body += buffer + ch;
12312
- buffer = '';
12313
- }
12314
- else {
12315
- buffer += ch;
12316
- }
12334
+ for (const [trait, value] of Object.entries(style.base))
12335
+ out.push({ trait, value });
12336
+ for (const state of ELEMENT_STYLE_STATES) {
12337
+ for (const [trait, value] of Object.entries(style[state] ?? {}))
12338
+ out.push({ trait, state, value });
12317
12339
  }
12318
12340
  return out;
12319
12341
  }
12320
-
12321
- /** What `prefers-reduced-motion: reduce` collapses — the contract's complete list (`a11y.reducedMotion`). */
12322
- const REDUCED_MOTION_OVERRIDES = SPEC_A11Y.reducedMotion;
12323
- /** What `prefers-reduced-transparency: reduce` makes solid — the contract's `a11y.reducedTransparency`. */
12324
- const REDUCED_TRANSPARENCY_OVERRIDES = SPEC_A11Y.reducedTransparency;
12325
- const ROOT = ':root';
12326
- const DEFAULT_SCHEME$1 = 'default';
12327
- const REFERENCE = 'var(';
12328
- const MEDIA_DARK = '(prefers-color-scheme: dark)';
12329
- const ACCESSIBILITY_MEDIA = [
12330
- { query: '(prefers-reduced-motion: reduce)', overrides: REDUCED_MOTION_OVERRIDES },
12331
- { query: '(prefers-reduced-transparency: reduce)', overrides: REDUCED_TRANSPARENCY_OVERRIDES },
12332
- ];
12333
- const usable = (t) => !!t && typeof t.name === 'string' && t.name !== '' &&
12334
- typeof t.value === 'string' && t.value !== '';
12335
- const darkOf = (t) => {
12336
- const dark = t.values?.dark;
12337
- return typeof dark === 'string' && dark !== '' ? dark : null;
12338
- };
12339
- const usableTokens = (tokens) => (tokens ?? []).filter(usable);
12340
- const declarations = (entries, indent) => entries.map(([name, value]) => `${indent}--iox-${name}: ${value};`).join('\n');
12341
- const rule = (selector, entries, indent = '') => entries.length ? `${indent}${selector} {\n${declarations(entries, indent + ' ')}\n${indent}}` : '';
12342
- const media = (query, rules) => {
12343
- const body = rules.filter(Boolean);
12344
- return body.length ? `@media ${query} {\n${body.join('\n')}\n}` : '';
12345
- };
12346
- const schemeSelector = (id, scope) => scope ? `${scope} .iox-scheme-${id}` : `.iox-scheme-${id}`;
12347
- const nonDefaultSchemes = () => Object.entries(SPEC_SCHEMES)
12348
- .filter(([id]) => id !== DEFAULT_SCHEME$1)
12349
- .map(([id, scheme]) => [id, scheme.set]);
12350
- /** `{ 'iox-brand': '#2f6f62', … }` — the builder registry's input shape for base values. */
12351
- function tokensToVarMap(tokens) {
12352
- const map = {};
12353
- for (const t of usableTokens(tokens))
12354
- map[`iox-${t.name}`] = t.value;
12355
- return map;
12342
+ /** Every trait the style owns with the value it writes — base first, then hover/active/focus/current. */
12343
+ function ownedTraitsOf(style) {
12344
+ return ownedEntries(style);
12356
12345
  }
12357
- /** Only the tokens that carry a dark override, as `{ 'iox-name': darkValue }`. */
12358
- function tokensToDarkVarMap(tokens) {
12359
- const map = {};
12360
- for (const t of usableTokens(tokens)) {
12361
- const dark = darkOf(t);
12362
- if (dark !== null)
12363
- map[`iox-${t.name}`] = dark;
12364
- }
12365
- return map;
12346
+ /** The node's value of one owned trait (a base trait, or a trait inside a pseudo-state map). */
12347
+ function readOwnedTrait(target, owned) {
12348
+ return owned.state ? readState(target, owned.state, owned.trait) : readBase(target, owned.trait);
12366
12349
  }
12367
- const TOKEN_REF$1 = /var\(\s*--iox-([\w-]+)\s*(?:,([^()]*))?\)/g;
12368
- const PLACEHOLDER = /\{\{\s*(~?)([\w-]+)\s*\}\}/g;
12369
- const LIGHT = 'light';
12370
- const DARK = 'dark';
12371
- const modeValue = (t, mode) => (mode === DARK ? darkOf(t) ?? t.value : t.value);
12372
- /** The rows as ONE mode: each row's value in that mode, no dark override left. `auto` (or none) →
12373
- * the rows unchanged. */
12374
- function rowsForMode(rows, mode) {
12375
- if (mode !== LIGHT && mode !== DARK)
12376
- return [...rows];
12377
- return rows.map(t => {
12378
- const { values: _dropped, ...rest } = t;
12379
- return { ...rest, value: modeValue(t, mode) };
12380
- });
12350
+ /** Write one owned trait. False when a builder node has no slot for a base trait (dropped). */
12351
+ function writeOwnedTrait(target, owned, value) {
12352
+ if (!owned.state)
12353
+ return writeBase(target, owned.trait, value);
12354
+ writeState(target, owned.state, owned.trait, value);
12355
+ return true;
12381
12356
  }
12382
- /** Every token's ROOT value in one mode, with `var(--iox-…)` references resolved against the same
12383
- * set (recursively; a reference cycle or an unknown token is left as written). */
12384
- function resolveTokenValues(tokens, mode = LIGHT) {
12385
- const raw = new Map(usableTokens(tokens).map(t => [t.name, modeValue(t, mode)]));
12386
- const out = {};
12387
- const resolve = (name, trail) => {
12388
- if (name in out)
12389
- return out[name];
12390
- const value = raw.get(name);
12391
- if (value === undefined || trail.has(name))
12392
- return null;
12393
- const next = new Set(trail).add(name);
12394
- return value.replace(TOKEN_REF$1, (ref, inner, fallback) => resolve(inner, next) ?? (fallback !== undefined ? fallback.trim() : ref));
12395
- };
12396
- for (const name of raw.keys()) {
12397
- const value = resolve(name, new Set());
12398
- if (value !== null)
12399
- out[name] = value;
12357
+ const sameOwned = (a, b) => a.trait === b.trait && a.state === b.state;
12358
+ function writeEntries(target, style, skip = []) {
12359
+ const unknown = [];
12360
+ for (const entry of ownedEntries(style)) {
12361
+ if (skip.some(s => sameOwned(s, entry)))
12362
+ continue;
12363
+ if (entry.state)
12364
+ writeState(target, entry.state, entry.trait, entry.value);
12365
+ else if (!writeBase(target, entry.trait, entry.value))
12366
+ unknown.push(entry.trait);
12400
12367
  }
12401
- return out;
12368
+ target.elementStyle = style.id;
12369
+ return unknown;
12402
12370
  }
12403
- /**
12404
- * Every token's value INSIDE a section wearing `schemeId`, in one mode — what CSS computes there: the
12405
- * scheme's overrides (`{{token}}` / `{{~token}}` placeholders substituted from the root), every other
12406
- * row as declared, and `var()` references resolved within the section (the §3c re-declaration rule
12407
- * makes that the section's own resolution). `default` (or an unknown id) is the root.
12408
- */
12409
- function resolveSchemeTokenValues(tokens, mode, schemeId) {
12410
- const rows = usableTokens(tokens);
12411
- const set = schemeId === DEFAULT_SCHEME$1 ? undefined : SPEC_SCHEMES[schemeId]?.set;
12412
- if (!set)
12413
- return resolveTokenValues(rows, mode);
12414
- const root = resolveTokenValues(rows, mode);
12415
- const other = resolveTokenValues(rows, mode === DARK ? LIGHT : DARK);
12416
- const overrides = new Map(schemeEntries(set, root, other));
12417
- return resolveTokenValues(rows.map(t => ({ name: t.name, value: overrides.get(t.name) ?? modeValue(t, mode) })), LIGHT);
12371
+ /** Write the style's exact base map and its hover/active/focus/current maps into the target. */
12372
+ function applyElementStyle(target, id) {
12373
+ const style = elementStyleById(id);
12374
+ if (!style)
12375
+ return { applied: false, unknownTraits: [] };
12376
+ return { applied: true, unknownTraits: writeEntries(target, style) };
12418
12377
  }
12378
+ const RULE_KEY = /^(\w+)(?:\[(\w+)=([^\]]+)\])?$/;
12379
+ const RANGE = /^([a-z]*)(\d+)\.\.\1(\d+)$/;
12380
+ function expandValues(raw) {
12381
+ const range = RANGE.exec(raw);
12382
+ if (!range)
12383
+ return [raw];
12384
+ const [, prefix, from, to] = range;
12385
+ const out = [];
12386
+ for (let n = Number(from); n <= Number(to); n++)
12387
+ out.push(`${prefix}${n}`);
12388
+ return out;
12389
+ }
12390
+ const DEFAULT_RULES = Object.entries(SPEC_DEFAULT_ELEMENT_STYLE).map(([key, style]) => {
12391
+ const m = RULE_KEY.exec(key);
12392
+ if (!m)
12393
+ throw new Error(`defaultElementStyle: unreadable key "${key}"`);
12394
+ const [, type, prop, raw] = m;
12395
+ return prop ? { type, prop, values: expandValues(raw), style } : { type, style };
12396
+ });
12419
12397
  /**
12420
- * `value` with every `var(--iox-…)` replaced by that token's resolved root value in `mode` — what
12421
- * the Style panel's Detach writes, so the node keeps exactly what it showed as a literal. An unknown
12422
- * token falls back to its declared fallback, else stays as written.
12398
+ * The element style a NEW node of `type` is born wearing (the spec's `defaultElementStyle`), or null
12399
+ * when the type has none. `traits` is the node's own trait values (props) — a Heading's default
12400
+ * follows its `level`; a Heading with no level matches no rule.
12423
12401
  */
12424
- function resolveTokenReferences(value, tokens, mode = LIGHT) {
12425
- const resolved = resolveTokenValues(tokens, mode);
12426
- return value.replace(TOKEN_REF$1, (ref, name, fallback) => resolved[name] ?? (fallback !== undefined ? fallback.trim() : ref));
12402
+ function defaultElementStyleFor(type, traits = {}) {
12403
+ for (const rule of DEFAULT_RULES) {
12404
+ if (rule.type !== type)
12405
+ continue;
12406
+ if (!rule.prop)
12407
+ return rule.style;
12408
+ if (rule.values.includes(String(traits[rule.prop])))
12409
+ return rule.style;
12410
+ }
12411
+ return null;
12427
12412
  }
12428
- /** A scheme value with its placeholders replaced — `{{token}}` by the root's resolved value in this
12429
- * mode, `{{~token}}` by its value in the OTHER mode (an inverse section is the other mode's ground,
12430
- * DS9) — or null when a placeholder names a token the set does not have (the declaration is then
12431
- * omitted). */
12432
- function substitute(template, resolved, opposite) {
12433
- let missing = false;
12434
- const value = template.replace(PLACEHOLDER, (_, tilde, name) => {
12435
- const from = tilde ? opposite : resolved;
12436
- if (!(name in from))
12437
- missing = true;
12438
- return from[name] ?? '';
12413
+ /** Per owned trait: does the node still hold the style's value (linked) or the author's (changed)? */
12414
+ function ownedTraitStatus(target, id) {
12415
+ const style = elementStyleById(id);
12416
+ if (!style)
12417
+ return [];
12418
+ return ownedEntries(style).map(({ trait, state, value }) => {
12419
+ const actual = state ? readState(target, state, trait) : readBase(target, trait);
12420
+ return {
12421
+ trait, ...(state ? { state } : {}), expected: value, actual,
12422
+ status: actual === value ? OwnedTraitLink.Linked : OwnedTraitLink.Changed,
12423
+ };
12439
12424
  });
12440
- return missing ? null : value;
12441
12425
  }
12442
- const schemeEntries = (set, resolved, opposite) => Object.entries(set)
12443
- .map(([name, template]) => [name, substitute(template, resolved, opposite)])
12444
- .filter((e) => e[1] !== null);
12445
- const hasPlaceholder = (value) => value.includes('{{');
12446
12426
  /**
12447
- * Section colour schemes (design-system plan §3c). Two rules, both about WHERE a custom property
12448
- * resolves:
12449
- *
12450
- * - ⚠️ Custom properties resolve where they are DECLARED: `--iox-card-bg: var(--iox-surface)`
12451
- * declared on `:root` keeps `:root`'s surface inside `.iox-scheme-inverse`. So each scheme block
12452
- * carries the spec's overrides PLUS every other token whose value references a token,
12453
- * re-declared verbatim — re-resolving it in the section.
12454
- * - ⚠️ A scheme that SWAPS two tokens (Brand: accent ⇄ accent-ink) cannot write them as `var()`:
12455
- * on one element `accent: var(--iox-accent-ink)` + `accent-ink: var(--iox-accent)` is a cycle,
12456
- * and CSS makes both invalid. The contract writes `{{accent}}` instead — the token's ROOT value,
12457
- * resolved here at compile time (and again from dark values for the dark twin).
12427
+ * Move a node from one style to another, rewriting ONLY what the old style owned:
12428
+ * - an owned trait still holding the old style's value is released — a base trait goes back to
12429
+ * `baseline[trait]` (the type's unstyled value; deleted when the baseline has none), a state trait
12430
+ * is removed from its state map;
12431
+ * - an owned trait the author changed is KEPT, and the new style does not overwrite it.
12432
+ * Then the new style is written. Traits neither style owns are never touched.
12458
12433
  */
12459
- function compileSchemeCss(tokens, scope, opposite) {
12460
- const rows = usableTokens(tokens);
12461
- if (!rows.length)
12462
- return '';
12463
- const resolved = resolveTokenValues(rows, LIGHT);
12464
- const other = opposite ?? resolveTokenValues(rows, DARK);
12465
- return nonDefaultSchemes().map(([id, set]) => rule(schemeSelector(id, scope), [
12466
- ...schemeEntries(set, resolved, other),
12467
- ...rows.filter(t => !(t.name in set) && t.value.includes(REFERENCE))
12468
- .map((t) => [t.name, t.value]),
12469
- ])).filter(Boolean).join('\n');
12470
- }
12471
- /** The dark twin of both scheme rules: a re-declared token with a dark value would otherwise pin
12472
- * the LIGHT value inside the section, and a `{{token}}` swap was resolved from LIGHT values. */
12473
- function darkSchemeRules(rows, scope) {
12474
- const light = resolveTokenValues(rows, LIGHT);
12475
- const dark = resolveTokenValues(rows, DARK);
12476
- const redeclared = rows.filter(t => {
12477
- const d = darkOf(t);
12478
- return d !== null && (t.value.includes(REFERENCE) || d.includes(REFERENCE));
12479
- });
12480
- return nonDefaultSchemes().map(([id, set]) => {
12481
- const swapped = Object.fromEntries(Object.entries(set).filter(([, v]) => hasPlaceholder(v)));
12482
- const lightSwap = new Map(schemeEntries(swapped, light, dark));
12483
- return rule(schemeSelector(id, scope), [
12484
- ...schemeEntries(swapped, dark, light).filter(([name, value]) => lightSwap.get(name) !== value),
12485
- ...redeclared.filter(t => !(t.name in set)).map((t) => [t.name, darkOf(t)]),
12486
- ], ' ');
12487
- });
12488
- }
12489
- function presentOverrides(rows, overrides, onlyWithDark) {
12490
- return Object.entries(overrides).filter(([name]) => rows.some(t => t.name === name && (!onlyWithDark || darkOf(t) !== null)));
12491
- }
12492
- /** The token CSS as the two halves a renderer places around its node rules. */
12493
- function compileTokenCssBlocks(tokens, opts = {}) {
12494
- const all = usableTokens(tokens);
12495
- const rows = rowsForMode(all, opts.mode);
12496
- const root = opts.scope ?? ROOT;
12497
- // A forced mode flattens the rows to ONE mode; a `{{~token}}` still reads the other mode's value.
12498
- const opposite = resolveTokenValues(all, opts.mode === DARK ? LIGHT : DARK);
12499
- const present = new Set(rows.map(t => t.name));
12500
- const unset = opts.isolate
12501
- ? [...specTokenNames()].filter(name => !present.has(name)).map((name) => [name, 'initial'])
12502
- : [];
12503
- const base = [
12504
- rule(root, [...unset, ...rows.map((t) => [t.name, t.value])]),
12505
- compileSchemeCss(rows, opts.scope, opposite),
12506
- ...ACCESSIBILITY_MEDIA.map(({ query, overrides }) => media(query, [rule(root, presentOverrides(rows, overrides, false), ' ')])),
12507
- ].filter(Boolean).join('\n');
12508
- const darkRows = rows.map((t) => [t.name, darkOf(t)])
12509
- .filter((e) => e[1] !== null);
12510
- const dark = darkRows.length ? [
12511
- media(MEDIA_DARK, [rule(root, darkRows, ' '), ...darkSchemeRules(rows, opts.scope)]),
12512
- // Glass carries dark glass values; without this the dark block (LAST) would out-rank the
12513
- // reduced-transparency swap and keep dark surfaces translucent.
12514
- ...ACCESSIBILITY_MEDIA.map(({ query, overrides }) => media(`${MEDIA_DARK} and ${query}`, [rule(root, presentOverrides(rows, overrides, true), ' ')])),
12515
- ].filter(Boolean).join('\n') : '';
12516
- return { base, dark };
12517
- }
12518
- /** One stylesheet string: base → schemes → reduced motion → reduced transparency → dark LAST. */
12519
- function compileTokenCss(tokens, opts = {}) {
12520
- const { base, dark } = compileTokenCssBlocks(tokens, opts);
12521
- return [base, dark].filter(Boolean).join('\n');
12522
- }
12523
-
12524
- /** A pack's own sanity: every token row names itself and carries a value. Returns errors. */
12525
- function validateFlavorPack(pack) {
12526
- const errors = [];
12527
- for (const t of pack.tokens ?? []) {
12528
- if (!t.name)
12529
- errors.push(`${pack.id}: token with empty name`);
12530
- if (!t.value)
12531
- errors.push(`${pack.id}: token "${t.name}" has no value`);
12434
+ function switchElementStyle(target, fromId, toId, baseline = {}) {
12435
+ const to = elementStyleById(toId);
12436
+ if (!to)
12437
+ return { applied: false, kept: [] };
12438
+ const kept = [];
12439
+ const from = elementStyleById(fromId);
12440
+ if (from) {
12441
+ for (const status of ownedTraitStatus(target, from.id)) {
12442
+ const ref = status.state ? { trait: status.trait, state: status.state } : { trait: status.trait };
12443
+ if (status.status === OwnedTraitLink.Changed) {
12444
+ if (status.actual !== undefined)
12445
+ kept.push(ref);
12446
+ continue;
12447
+ }
12448
+ if (ref.state)
12449
+ deleteState(target, ref.state, ref.trait);
12450
+ else
12451
+ writeBase(target, ref.trait, baseline[ref.trait]);
12452
+ }
12532
12453
  }
12533
- return errors;
12454
+ writeEntries(target, to, kept);
12455
+ return { applied: true, kept };
12534
12456
  }
12535
12457
  /**
12536
- * Swiss — the retired F1 pack: four tokens (control/surface radius, control/stack spacing) and the
12537
- * hue opinion F2's brand step read. Three of its rows become the spec's `legacyAliases` on migration;
12538
- * `space-control` is a v2 token in every pack and takes Crisp's value.
12458
+ * The element-style MARKER (design-system §3b-bis, spec `elementStyleMarker`). A node that wears an
12459
+ * element style renders `iox-es iox-es-<id>` in BOTH renderers, through the one node-class helper
12460
+ * `nodeDesignClasses` (node-classes.ts); every piece of component CSS that carries the system look is
12461
+ * scoped to it, so a node saved before DS2 (no `elementStyle`) keeps its exact pre-DS2 component CSS
12462
+ * no matter which tokens the org holds.
12539
12463
  */
12540
- const SWISS_FLAVOR = {
12541
- id: 'swiss',
12542
- version: 1,
12543
- label: 'Swiss',
12544
- blurb: 'Grid discipline, hairline rules, near-zero motion.',
12545
- tokens: [
12546
- { name: 'radius-control', value: '4px', type: 'size', group: 'shape' },
12547
- { name: 'radius-surface', value: '4px', type: 'size', group: 'shape' },
12548
- { name: 'space-control', value: '10px 18px', type: 'spacing', group: 'space' },
12549
- { name: 'space-stack', value: '12px', type: 'spacing', group: 'space' },
12550
- ],
12551
- // F2: Swiss turns the seed toward a cool counterpoint.
12552
- accentHueDeg: 150,
12553
- };
12464
+ const ELEMENT_STYLE_MARKER_CLASS = 'iox-es';
12465
+ const MARKER_PREFIX$1 = `${ELEMENT_STYLE_MARKER_CLASS}-`;
12466
+ /** `iox-es-<id>` for a known style, else null — an unknown id never reaches a class attribute. */
12467
+ function elementStyleClass(id) {
12468
+ return elementStyleById(id) ? `${MARKER_PREFIX$1}${id}` : null;
12469
+ }
12470
+ /**
12471
+ * The spec's `headingLevelRule`: changing a Heading's level moves it to the new level's DEFAULT
12472
+ * style only when it still wears its old level's default (author changes kept, via
12473
+ * `switchElementStyle`); a Heading wearing any other style — or none — is left alone. Returns null
12474
+ * when nothing switched.
12475
+ */
12476
+ function applyHeadingLevelRule(target, oldLevel, newLevel, baseline = {}) {
12477
+ if (target.type !== 'Heading' || oldLevel === newLevel)
12478
+ return null;
12479
+ const from = defaultElementStyleFor('Heading', { level: oldLevel });
12480
+ const to = defaultElementStyleFor('Heading', { level: newLevel });
12481
+ if (!from || !to || target.elementStyle !== from || from === to)
12482
+ return null;
12483
+ return switchElementStyle(target, from, to, baseline);
12484
+ }
12554
12485
 
12555
12486
  /**
12556
- * Element styles — the author's "classes" (design-system plan §3b). A named look is an EXACT trait
12557
- * map written into a node; every value is a `var(--iox-…)` reference or a flavor-independent
12558
- * keyword, so a re-theme rewrites token rows and every node wearing the style follows. No renderer
12559
- * reads `elementStyle`: it records which look a node wears, for the panel row, Reset, adopt and AI.
12487
+ * The design-system classes a node renders, in BOTH renderers (builder canvas, engine component
12488
+ * path, engine string path) — ONE helper, so the three can never disagree about which class a node
12489
+ * carries:
12560
12490
  *
12561
- * Works on two node shapes through one minimal interface, so the builder (a ComponentNode whose
12562
- * values live in `styleTraits[].traits[].default`) and the server-side composer (a plain serialized
12563
- * layout node with `styleProps`) share every function here.
12491
+ * - the element-style MARKER `iox-es iox-es-<id>` (design-system §3b-bis) — component CSS carrying
12492
+ * the system look is scoped to it, so a node saved before DS2 keeps its pre-DS2 look;
12493
+ * - the section colour scheme `iox-scheme-<id>` (§3c) for a non-default `colorScheme` — the class
12494
+ * `compileSchemeCss` re-declares tokens on, so everything inside re-resolves;
12495
+ * - the composite ROLE `iox-role-<role>` (`card.media` → `iox-role-card-media`; composite-components
12496
+ * plan §3, Card v2) — what the shared CSS keys a part's structural rules on (the badge's corner, the
12497
+ * card's stretched link), so a role is never a renderer's private knowledge.
12498
+ *
12499
+ * Renderers read `elementStyle`, `colorScheme` and `role` for exactly this and nothing else.
12564
12500
  */
12565
- var ElementStyleState;
12566
- (function (ElementStyleState) {
12567
- ElementStyleState["Hover"] = "hover";
12568
- ElementStyleState["Active"] = "active";
12569
- ElementStyleState["Focus"] = "focus";
12570
- /** The page you're on (CC1 N2): compiles to `[aria-current="page"]`, never a pseudo-class. */
12571
- ElementStyleState["Current"] = "current";
12572
- })(ElementStyleState || (ElementStyleState = {}));
12573
- const ELEMENT_STYLE_STATES = [ElementStyleState.Hover, ElementStyleState.Active, ElementStyleState.Focus, ElementStyleState.Current];
12574
- const ELEMENT_STYLES = SPEC_ELEMENT_STYLES;
12575
- /** Legacy `Button.variant` → the element style it maps to (adopt, DS7). */
12576
- const BUTTON_VARIANT_TO_STYLE = SPEC_BUTTON_VARIANT_TO_STYLE;
12577
- /** Palette entries that are a Container wearing an element style (Card · Panel · Navigation bar). */
12578
- const PALETTE_PRESETS = SPEC_PALETTE_PRESETS;
12579
- const BY_ID = new Map(ELEMENT_STYLES.map(s => [s.id, s]));
12580
- function elementStyleById(id) {
12581
- return id ? BY_ID.get(id) : undefined;
12501
+ /** The section colour schemes — the contract's `schemes` keys. */
12502
+ var ColorScheme;
12503
+ (function (ColorScheme) {
12504
+ ColorScheme["Default"] = "default";
12505
+ ColorScheme["Subtle"] = "subtle";
12506
+ ColorScheme["Inverse"] = "inverse";
12507
+ ColorScheme["Accent"] = "accent";
12508
+ })(ColorScheme || (ColorScheme = {}));
12509
+ /** The content trait (stored in `props`, not a style prop) that picks a node's scheme. */
12510
+ const COLOR_SCHEME_TRAIT = 'colorScheme';
12511
+ /** The node types that offer a scheme (spec §3c: Section/Container; LinkedContainer is a Container that links). */
12512
+ const COLOR_SCHEME_TYPES = ['Section', 'Container', 'LinkedContainer'];
12513
+ /** Every scheme in contract order, with the contract's label (Default · Subtle · Inverse · Brand). */
12514
+ const COLOR_SCHEMES = Object.keys(SPEC_SCHEMES).map(id => ({ id, label: SPEC_SCHEMES[id].label }));
12515
+ const SCHEME_IDS$1 = new Set(Object.keys(SPEC_SCHEMES));
12516
+ const SCHEME_PREFIX = 'iox-scheme-';
12517
+ const ROLE_PREFIX = 'iox-role-';
12518
+ /** A role is dot-separated lowercase words (`card`, `card.title`) — anything else never reaches a class. */
12519
+ const ROLE_SHAPE = /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/;
12520
+ const MARKER_PREFIX = `${ELEMENT_STYLE_MARKER_CLASS}-`;
12521
+ /** `iox-scheme-<id>` for a known NON-default scheme, else null — an unknown id never reaches a class attribute. */
12522
+ function colorSchemeClass(id) {
12523
+ return typeof id === 'string' && id !== ColorScheme.Default && SCHEME_IDS$1.has(id) ? `${SCHEME_PREFIX}${id}` : null;
12582
12524
  }
12583
- /** The styles a node of `type` may wear — the Style panel's select. */
12584
- function elementStylesFor(type) {
12585
- return ELEMENT_STYLES.filter(s => s.appliesTo.includes(type));
12525
+ /** `iox-role-<role>` for a well-formed composite role, else null. */
12526
+ function roleClass(role) {
12527
+ return typeof role === 'string' && ROLE_SHAPE.test(role) ? `${ROLE_PREFIX}${role.replace(/\./g, '-')}` : null;
12586
12528
  }
12587
- var OwnedTraitLink;
12588
- (function (OwnedTraitLink) {
12589
- /** The node still holds the style's value. */
12590
- OwnedTraitLink["Linked"] = "linked";
12591
- /** The author replaced it on this node. */
12592
- OwnedTraitLink["Changed"] = "changed";
12593
- })(OwnedTraitLink || (OwnedTraitLink = {}));
12594
- const usesTraits = (t) => Array.isArray(t.styleTraits);
12595
- function findTrait(target, name) {
12596
- for (const group of target.styleTraits ?? []) {
12597
- const hit = group.traits.find(tr => tr.name === name);
12529
+ /** The element style whose wearer is the phone's bottom navigation bar (CC1, R1). */
12530
+ const BOTTOM_BAR_STYLE = 'bottom-bar';
12531
+ /** The page reserves the bar's height wherever this class is on the page… */
12532
+ const BAR_RESERVE_CLASS = 'iox-bar-reserve';
12533
+ /** …the responsive Breakpoint ids that can hold an override, widest first (a spec pins it to BREAKPOINTS). */
12534
+ const BAR_RESERVE_BREAKPOINTS = ['laptop', 'tablet', 'mobile', 'small'];
12535
+ function baseDisplayOf(node) {
12536
+ if (node.styleProps && 'display' in node.styleProps)
12537
+ return node.styleProps['display'];
12538
+ for (const group of node.styleTraits ?? []) {
12539
+ const hit = group.traits?.find(t => t?.name === 'display');
12598
12540
  if (hit)
12599
- return hit;
12541
+ return hit.default;
12600
12542
  }
12601
12543
  return undefined;
12602
12544
  }
12603
- function readBase(target, name) {
12604
- return usesTraits(target) ? findTrait(target, name)?.default : target.styleProps?.[name];
12545
+ /**
12546
+ * The class that makes the page reserve a bottom bar's height (iox-design.css: `padding-bottom` and
12547
+ * `scroll-padding-bottom`, WCAG 2.4.11), only at the widths where the bar SHOWS: `iox-bar-reserve` when
12548
+ * it shows at every width, `iox-bar-reserve-<bp>` when it is hidden at base and shown from that
12549
+ * breakpoint down (the phone shape's own rule), null when it never shows or is not a bar.
12550
+ */
12551
+ function barReserveClass(node) {
12552
+ if (!node || node.elementStyle !== BOTTOM_BAR_STYLE)
12553
+ return null;
12554
+ if (baseDisplayOf(node) !== 'none')
12555
+ return BAR_RESERVE_CLASS;
12556
+ for (const bp of BAR_RESERVE_BREAKPOINTS) {
12557
+ const display = node.responsive?.[bp]?.styleProps?.['display'];
12558
+ if (display !== undefined && display !== 'none')
12559
+ return `${BAR_RESERVE_CLASS}-${bp}`;
12560
+ }
12561
+ return null;
12605
12562
  }
12606
- /** False when a builder node has no such trait — the value would be dropped. */
12607
- function writeBase(target, name, value) {
12608
- if (usesTraits(target)) {
12609
- const trait = findTrait(target, name);
12563
+ /** A node's `colorScheme`, from `props` (serialized) or its trait (builder). */
12564
+ function colorSchemeOf(node) {
12565
+ if (!node)
12566
+ return null;
12567
+ const fromProps = node.props?.[COLOR_SCHEME_TRAIT];
12568
+ if (fromProps !== undefined)
12569
+ return typeof fromProps === 'string' ? fromProps : null;
12570
+ const trait = node.traits?.find(t => t?.name === COLOR_SCHEME_TRAIT)?.default;
12571
+ return typeof trait === 'string' ? trait : null;
12572
+ }
12573
+ /**
12574
+ * The ink a section colour scheme gives everything inside it (vectoriox/iox-ui#15). A scheme re-declares
12575
+ * tokens only, so without this a Container wearing one kept the colour it INHERITED from the section around
12576
+ * it: a Featured (Brand) card's body text stayed the page's dark ink on the Brand fill, 2.54:1. Two halves,
12577
+ * one rule: `compileSchemeCss` gives every `iox-scheme-*` element `color: var(--iox-text)` (the node rule,
12578
+ * emitted after it at the same specificity, still wins), and {@link withSchemeInk} reads a scheme'd node's
12579
+ * own `inherit` — what the card / box / panel element styles write for "no colour of my own" — as this ink.
12580
+ * The value is the contract's `schemeInk`.
12581
+ */
12582
+ const SCHEME_INK = SPEC_SCHEME_INK;
12583
+ /** The values that mean "no colour of my own" — `color` is inherited, so these equal no declaration. */
12584
+ const INHERITED_INK = new Set(['inherit', 'unset']);
12585
+ /**
12586
+ * A node's style map as its renderer compiles it under a section colour scheme: a NON-default scheme turns
12587
+ * an inherited `color` into {@link SCHEME_INK}; anything else — no scheme, an author's own colour, a token —
12588
+ * is returned untouched (the same object). Every render path calls it on every rule it compiles for the
12589
+ * node (base, states, breakpoint layers): the builder's StyleRegistryService and both engine paths.
12590
+ */
12591
+ function withSchemeInk(styles, scheme) {
12592
+ if (!styles || !colorSchemeClass(scheme))
12593
+ return styles;
12594
+ const color = styles['color'];
12595
+ if (typeof color !== 'string' || !INHERITED_INK.has(color.trim().toLowerCase()))
12596
+ return styles;
12597
+ return { ...styles, color: SCHEME_INK };
12598
+ }
12599
+ /**
12600
+ * Write a node's section colour scheme — THE one writer, so every control that offers Section colours (the
12601
+ * Style panel's Element style block, Card ▸ Card) edits the same setting: the `colorScheme` content trait on
12602
+ * a builder node, `props.colorScheme` on a saved one. False when nothing changed: an unknown id, the value it
12603
+ * already holds, or a builder node with no such trait (a type that offers no scheme).
12604
+ */
12605
+ function setColorScheme(node, id) {
12606
+ if (!node || !SCHEME_IDS$1.has(id) || (colorSchemeOf(node) ?? ColorScheme.Default) === id)
12607
+ return false;
12608
+ if (Array.isArray(node.traits)) {
12609
+ const trait = node.traits.find(t => t?.name === COLOR_SCHEME_TRAIT);
12610
12610
  if (!trait)
12611
12611
  return false;
12612
- trait.default = value;
12612
+ trait.default = id;
12613
12613
  return true;
12614
12614
  }
12615
- target.styleProps = target.styleProps ?? {};
12616
- if (value === undefined)
12617
- delete target.styleProps[name];
12618
- else
12619
- target.styleProps[name] = value;
12615
+ node.props = { ...(node.props ?? {}), [COLOR_SCHEME_TRAIT]: id };
12620
12616
  return true;
12621
12617
  }
12622
- function readState(target, state, name) {
12623
- return target.stateStyles?.[state]?.[name];
12618
+ /** True when the node wears a known element style — the marker is on it. */
12619
+ function hasElementStyleMarker(node) {
12620
+ return elementStyleClass(node?.elementStyle) !== null;
12624
12621
  }
12625
- function writeState(target, state, name, value) {
12626
- target.stateStyles = target.stateStyles ?? {};
12627
- target.stateStyles[state] = { ...(target.stateStyles[state] ?? {}), [name]: value };
12622
+ /** The classes a node renders: `'iox-es iox-es-card iox-scheme-inverse'` (+ a bottom bar's reserve class), or `''` for none. */
12623
+ function nodeDesignClasses(node) {
12624
+ const marker = elementStyleClass(node?.elementStyle);
12625
+ return [marker ? `${ELEMENT_STYLE_MARKER_CLASS} ${marker}` : '', colorSchemeClass(colorSchemeOf(node)) ?? '', roleClass(node?.role) ?? '', barReserveClass(node) ?? '']
12626
+ .filter(Boolean).join(' ');
12628
12627
  }
12629
- function deleteState(target, state, name) {
12630
- const map = target.stateStyles?.[state];
12631
- if (!map)
12628
+ const isDesignClass = (c) => c === ELEMENT_STYLE_MARKER_CLASS || c.startsWith(MARKER_PREFIX) || c.startsWith(SCHEME_PREFIX) || c.startsWith(ROLE_PREFIX) || c.startsWith(BAR_RESERVE_CLASS);
12629
+ /** Make an element's design classes match the node — the live switch on the canvas and the
12630
+ * component path's one-time stamp. Removes stale ones; touches no other class. */
12631
+ function syncNodeDesignClasses(el, node) {
12632
+ if (!el?.classList)
12632
12633
  return;
12633
- const { [name]: _drop, ...rest } = map;
12634
- if (Object.keys(rest).length)
12635
- target.stateStyles[state] = rest;
12636
- else
12637
- delete target.stateStyles[state];
12634
+ for (const c of Array.from(el.classList).filter(isDesignClass))
12635
+ el.classList.remove(c);
12636
+ const classes = nodeDesignClasses(node);
12637
+ if (classes)
12638
+ el.classList.add(...classes.split(' '));
12639
+ }
12640
+
12641
+ /**
12642
+ * Which `--iox-<name>` a piece of COMPONENT CSS may reference (design-system §3b-bis: "component CSS
12643
+ * may reference ONLY tokens defined in this spec"). DS2 shipped seven names nobody defines
12644
+ * (`card-media-gap`, `gallery-overlay`, `dialog-close-bg`…): a token the contract does not define can
12645
+ * never be set by a flavor, so it is a literal wearing a costume. One guard, run over BOTH
12646
+ * renderers' component CSS (iox-ui's spec; the engine's `verify:token-names`).
12647
+ */
12648
+ /** The 161 names of the contract's two tiers, plus its legacy aliases. */
12649
+ function specTokenNames() {
12650
+ return new Set([
12651
+ ...SPEC_TIERS.semantic.map(t => t.name),
12652
+ ...SPEC_TIERS.component.map(t => t.name),
12653
+ ...Object.keys(SPEC_LEGACY_ALIASES),
12654
+ ]);
12655
+ }
12656
+ const REFERENCE$1 = /var\(\s*--iox-([a-z0-9-]+)/gi;
12657
+ /** Every distinct token a stylesheet READS (`var(--iox-x…)`), in first-seen order. */
12658
+ function tokenReferences(css) {
12659
+ return [...new Set(Array.from(css.matchAll(REFERENCE$1), m => m[1]))];
12660
+ }
12661
+ /** The referenced names that are neither spec tokens nor in `alsoAllowed`. */
12662
+ function unknownTokenReferences(css, alsoAllowed = []) {
12663
+ const spec = specTokenNames();
12664
+ const extra = new Set(alsoAllowed);
12665
+ return tokenReferences(css).filter(name => !spec.has(name) && !extra.has(name));
12666
+ }
12667
+ const LEGACY_SCOPE = /:not\(\s*\.iox-es\s*\)/;
12668
+ /** Comments out: block comments, and SCSS `//` line comments (never the `//` of a `url(https://…)`). */
12669
+ const stripComments = (css) => String(css ?? '')
12670
+ .replace(/\/\*[\s\S]*?\*\//g, '')
12671
+ .replace(/(^|[^:\\])\/\/[^\n]*/g, '$1');
12672
+ /** A nested SCSS selector against its parent's: `&` is the parent; no `&` means a descendant. */
12673
+ function resolveNested(parent, selector) {
12674
+ if (!parent)
12675
+ return selector;
12676
+ return selector.split(',').map(part => {
12677
+ const own = part.trim();
12678
+ return own.includes('&') ? own.replace(/&/g, parent) : `${parent} ${own}`;
12679
+ }).join(', ');
12638
12680
  }
12639
- /** Every (state, trait, value) a style owns, base first. */
12640
- function ownedEntries(style) {
12681
+ /**
12682
+ * Tokens read by a LEGACY rule — any rule whose selector is scoped `:not(.iox-es)` (the pre-DS2 look
12683
+ * of a node that wears no element style, design-system §3b-bis). Such a rule must read NO token at
12684
+ * all: a token it reads repaints a pre-DS2 page the moment the org's rows are seeded or a flavor is
12685
+ * applied (D5). Returns `selector → names` for each offending rule; comments are ignored. SCSS
12686
+ * nesting is resolved (DS4.1): `.x:where(:not(.iox-es)) { &:hover { … } }` is the legacy rule
12687
+ * `.x:where(:not(.iox-es)):hover`, and an at-rule (`@media`) between them does not break the chain.
12688
+ */
12689
+ function legacyRuleTokenReferences(css) {
12690
+ const text = stripComments(css);
12641
12691
  const out = [];
12642
- for (const [trait, value] of Object.entries(style.base))
12643
- out.push({ trait, value });
12644
- for (const state of ELEMENT_STYLE_STATES) {
12645
- for (const [trait, value] of Object.entries(style[state] ?? {}))
12646
- out.push({ trait, state, value });
12692
+ const stack = [];
12693
+ const report = (frame) => {
12694
+ if (!LEGACY_SCOPE.test(frame.selector))
12695
+ return;
12696
+ const tokens = tokenReferences(frame.body);
12697
+ if (tokens.length)
12698
+ out.push({ selector: frame.selector, tokens });
12699
+ };
12700
+ let buffer = '';
12701
+ for (const ch of text) {
12702
+ if (ch === '{') {
12703
+ const head = buffer.trim().replace(/\s+/g, ' ');
12704
+ buffer = '';
12705
+ // Declarations before a nested block belong to the parent (`color: x; &:hover {`).
12706
+ const cut = head.lastIndexOf(';');
12707
+ const prelude = cut >= 0 ? head.slice(cut + 1).trim() : head;
12708
+ const parent = stack[stack.length - 1];
12709
+ if (parent && cut >= 0)
12710
+ parent.body += head.slice(0, cut + 1);
12711
+ const inherited = parent?.selector ?? '';
12712
+ const selector = prelude.startsWith('@') ? inherited : resolveNested(inherited, prelude);
12713
+ stack.push({ selector, body: '' });
12714
+ }
12715
+ else if (ch === '}') {
12716
+ const frame = stack.pop();
12717
+ if (frame) {
12718
+ frame.body += buffer;
12719
+ report(frame);
12720
+ }
12721
+ buffer = '';
12722
+ }
12723
+ else if (ch === ';' && stack.length) {
12724
+ stack[stack.length - 1].body += buffer + ch;
12725
+ buffer = '';
12726
+ }
12727
+ else {
12728
+ buffer += ch;
12729
+ }
12647
12730
  }
12648
12731
  return out;
12649
12732
  }
12650
- /** Every trait the style owns with the value it writes — base first, then hover/active/focus/current. */
12651
- function ownedTraitsOf(style) {
12652
- return ownedEntries(style);
12653
- }
12654
- /** The node's value of one owned trait (a base trait, or a trait inside a pseudo-state map). */
12655
- function readOwnedTrait(target, owned) {
12656
- return owned.state ? readState(target, owned.state, owned.trait) : readBase(target, owned.trait);
12657
- }
12658
- /** Write one owned trait. False when a builder node has no slot for a base trait (dropped). */
12659
- function writeOwnedTrait(target, owned, value) {
12660
- if (!owned.state)
12661
- return writeBase(target, owned.trait, value);
12662
- writeState(target, owned.state, owned.trait, value);
12663
- return true;
12733
+
12734
+ /** What `prefers-reduced-motion: reduce` collapses — the contract's complete list (`a11y.reducedMotion`). */
12735
+ const REDUCED_MOTION_OVERRIDES = SPEC_A11Y.reducedMotion;
12736
+ /** What `prefers-reduced-transparency: reduce` makes solid — the contract's `a11y.reducedTransparency`. */
12737
+ const REDUCED_TRANSPARENCY_OVERRIDES = SPEC_A11Y.reducedTransparency;
12738
+ const ROOT = ':root';
12739
+ const DEFAULT_SCHEME$1 = 'default';
12740
+ const REFERENCE = 'var(';
12741
+ const MEDIA_DARK = '(prefers-color-scheme: dark)';
12742
+ const ACCESSIBILITY_MEDIA = [
12743
+ { query: '(prefers-reduced-motion: reduce)', overrides: REDUCED_MOTION_OVERRIDES },
12744
+ { query: '(prefers-reduced-transparency: reduce)', overrides: REDUCED_TRANSPARENCY_OVERRIDES },
12745
+ ];
12746
+ const usable = (t) => !!t && typeof t.name === 'string' && t.name !== '' &&
12747
+ typeof t.value === 'string' && t.value !== '';
12748
+ const darkOf = (t) => {
12749
+ const dark = t.values?.dark;
12750
+ return typeof dark === 'string' && dark !== '' ? dark : null;
12751
+ };
12752
+ const usableTokens = (tokens) => (tokens ?? []).filter(usable);
12753
+ const declarations = (entries, indent) => entries.map(([name, value]) => `${indent}--iox-${name}: ${value};`).join('\n');
12754
+ const rule = (selector, entries, indent = '') => entries.length ? `${indent}${selector} {\n${declarations(entries, indent + ' ')}\n${indent}}` : '';
12755
+ const media = (query, rules) => {
12756
+ const body = rules.filter(Boolean);
12757
+ return body.length ? `@media ${query} {\n${body.join('\n')}\n}` : '';
12758
+ };
12759
+ const schemeSelector = (id, scope) => scope ? `${scope} .iox-scheme-${id}` : `.iox-scheme-${id}`;
12760
+ const nonDefaultSchemes = () => Object.entries(SPEC_SCHEMES)
12761
+ .filter(([id]) => id !== DEFAULT_SCHEME$1)
12762
+ .map(([id, scheme]) => [id, scheme.set]);
12763
+ /** `{ 'iox-brand': '#2f6f62', … }` — the builder registry's input shape for base values. */
12764
+ function tokensToVarMap(tokens) {
12765
+ const map = {};
12766
+ for (const t of usableTokens(tokens))
12767
+ map[`iox-${t.name}`] = t.value;
12768
+ return map;
12664
12769
  }
12665
- const sameOwned = (a, b) => a.trait === b.trait && a.state === b.state;
12666
- function writeEntries(target, style, skip = []) {
12667
- const unknown = [];
12668
- for (const entry of ownedEntries(style)) {
12669
- if (skip.some(s => sameOwned(s, entry)))
12670
- continue;
12671
- if (entry.state)
12672
- writeState(target, entry.state, entry.trait, entry.value);
12673
- else if (!writeBase(target, entry.trait, entry.value))
12674
- unknown.push(entry.trait);
12770
+ /** Only the tokens that carry a dark override, as `{ 'iox-name': darkValue }`. */
12771
+ function tokensToDarkVarMap(tokens) {
12772
+ const map = {};
12773
+ for (const t of usableTokens(tokens)) {
12774
+ const dark = darkOf(t);
12775
+ if (dark !== null)
12776
+ map[`iox-${t.name}`] = dark;
12675
12777
  }
12676
- target.elementStyle = style.id;
12677
- return unknown;
12778
+ return map;
12678
12779
  }
12679
- /** Write the style's exact base map and its hover/active/focus/current maps into the target. */
12680
- function applyElementStyle(target, id) {
12681
- const style = elementStyleById(id);
12682
- if (!style)
12683
- return { applied: false, unknownTraits: [] };
12684
- return { applied: true, unknownTraits: writeEntries(target, style) };
12780
+ const TOKEN_REF$1 = /var\(\s*--iox-([\w-]+)\s*(?:,([^()]*))?\)/g;
12781
+ const PLACEHOLDER = /\{\{\s*(~?)([\w-]+)\s*\}\}/g;
12782
+ const LIGHT = 'light';
12783
+ const DARK = 'dark';
12784
+ const modeValue = (t, mode) => (mode === DARK ? darkOf(t) ?? t.value : t.value);
12785
+ /** The rows as ONE mode: each row's value in that mode, no dark override left. `auto` (or none) →
12786
+ * the rows unchanged. */
12787
+ function rowsForMode(rows, mode) {
12788
+ if (mode !== LIGHT && mode !== DARK)
12789
+ return [...rows];
12790
+ return rows.map(t => {
12791
+ const { values: _dropped, ...rest } = t;
12792
+ return { ...rest, value: modeValue(t, mode) };
12793
+ });
12685
12794
  }
12686
- const RULE_KEY = /^(\w+)(?:\[(\w+)=([^\]]+)\])?$/;
12687
- const RANGE = /^([a-z]*)(\d+)\.\.\1(\d+)$/;
12688
- function expandValues(raw) {
12689
- const range = RANGE.exec(raw);
12690
- if (!range)
12691
- return [raw];
12692
- const [, prefix, from, to] = range;
12693
- const out = [];
12694
- for (let n = Number(from); n <= Number(to); n++)
12695
- out.push(`${prefix}${n}`);
12795
+ /** Every token's ROOT value in one mode, with `var(--iox-…)` references resolved against the same
12796
+ * set (recursively; a reference cycle or an unknown token is left as written). */
12797
+ function resolveTokenValues(tokens, mode = LIGHT) {
12798
+ const raw = new Map(usableTokens(tokens).map(t => [t.name, modeValue(t, mode)]));
12799
+ const out = {};
12800
+ const resolve = (name, trail) => {
12801
+ if (name in out)
12802
+ return out[name];
12803
+ const value = raw.get(name);
12804
+ if (value === undefined || trail.has(name))
12805
+ return null;
12806
+ const next = new Set(trail).add(name);
12807
+ return value.replace(TOKEN_REF$1, (ref, inner, fallback) => resolve(inner, next) ?? (fallback !== undefined ? fallback.trim() : ref));
12808
+ };
12809
+ for (const name of raw.keys()) {
12810
+ const value = resolve(name, new Set());
12811
+ if (value !== null)
12812
+ out[name] = value;
12813
+ }
12696
12814
  return out;
12697
12815
  }
12698
- const DEFAULT_RULES = Object.entries(SPEC_DEFAULT_ELEMENT_STYLE).map(([key, style]) => {
12699
- const m = RULE_KEY.exec(key);
12700
- if (!m)
12701
- throw new Error(`defaultElementStyle: unreadable key "${key}"`);
12702
- const [, type, prop, raw] = m;
12703
- return prop ? { type, prop, values: expandValues(raw), style } : { type, style };
12704
- });
12705
12816
  /**
12706
- * The element style a NEW node of `type` is born wearing (the spec's `defaultElementStyle`), or null
12707
- * when the type has none. `traits` is the node's own trait values (props) — a Heading's default
12708
- * follows its `level`; a Heading with no level matches no rule.
12817
+ * Every token's value INSIDE a section wearing `schemeId`, in one mode — what CSS computes there: the
12818
+ * scheme's overrides (`{{token}}` / `{{~token}}` placeholders substituted from the root), every other
12819
+ * row as declared, and `var()` references resolved within the section (the §3c re-declaration rule
12820
+ * makes that the section's own resolution). `default` (or an unknown id) is the root. A CHAIN
12821
+ * (`['inverse', 'accent']`, outermost first) is an element wearing the last scheme inside elements wearing
12822
+ * the ones before it: a token the inner scheme does not set is the outer one's (custom properties inherit).
12709
12823
  */
12710
- function defaultElementStyleFor(type, traits = {}) {
12711
- for (const rule of DEFAULT_RULES) {
12712
- if (rule.type !== type)
12824
+ function resolveSchemeTokenValues(tokens, mode, schemeId) {
12825
+ const rows = usableTokens(tokens);
12826
+ const sets = (typeof schemeId === 'string' ? [schemeId] : schemeId)
12827
+ .map(id => (id === DEFAULT_SCHEME$1 ? undefined : SPEC_SCHEMES[id]?.set));
12828
+ if (!sets.some(Boolean))
12829
+ return resolveTokenValues(rows, mode);
12830
+ const root = resolveTokenValues(rows, mode);
12831
+ const other = resolveTokenValues(rows, mode === DARK ? LIGHT : DARK);
12832
+ // Nested (`[outer, inner]` — a Featured card in an Inverse section): each scheme block re-declares the
12833
+ // rows that reference a token, so at every level those are restored before the block's own set.
12834
+ const overrides = new Map();
12835
+ for (const set of sets) {
12836
+ if (!set)
12713
12837
  continue;
12714
- if (!rule.prop)
12715
- return rule.style;
12716
- if (rule.values.includes(String(traits[rule.prop])))
12717
- return rule.style;
12838
+ for (const t of rows)
12839
+ if (modeValue(t, mode).includes(REFERENCE))
12840
+ overrides.delete(t.name);
12841
+ for (const [name, value] of schemeEntries(set, root, other))
12842
+ overrides.set(name, value);
12718
12843
  }
12719
- return null;
12720
- }
12721
- /** Per owned trait: does the node still hold the style's value (linked) or the author's (changed)? */
12722
- function ownedTraitStatus(target, id) {
12723
- const style = elementStyleById(id);
12724
- if (!style)
12725
- return [];
12726
- return ownedEntries(style).map(({ trait, state, value }) => {
12727
- const actual = state ? readState(target, state, trait) : readBase(target, trait);
12728
- return {
12729
- trait, ...(state ? { state } : {}), expected: value, actual,
12730
- status: actual === value ? OwnedTraitLink.Linked : OwnedTraitLink.Changed,
12731
- };
12732
- });
12844
+ return resolveTokenValues(rows.map(t => ({ name: t.name, value: overrides.get(t.name) ?? modeValue(t, mode) })), LIGHT);
12733
12845
  }
12734
12846
  /**
12735
- * Move a node from one style to another, rewriting ONLY what the old style owned:
12736
- * - an owned trait still holding the old style's value is released — a base trait goes back to
12737
- * `baseline[trait]` (the type's unstyled value; deleted when the baseline has none), a state trait
12738
- * is removed from its state map;
12739
- * - an owned trait the author changed is KEPT, and the new style does not overwrite it.
12740
- * Then the new style is written. Traits neither style owns are never touched.
12847
+ * `value` with every `var(--iox-…)` replaced by that token's resolved root value in `mode` — what
12848
+ * the Style panel's Detach writes, so the node keeps exactly what it showed as a literal. An unknown
12849
+ * token falls back to its declared fallback, else stays as written.
12741
12850
  */
12742
- function switchElementStyle(target, fromId, toId, baseline = {}) {
12743
- const to = elementStyleById(toId);
12744
- if (!to)
12745
- return { applied: false, kept: [] };
12746
- const kept = [];
12747
- const from = elementStyleById(fromId);
12748
- if (from) {
12749
- for (const status of ownedTraitStatus(target, from.id)) {
12750
- const ref = status.state ? { trait: status.trait, state: status.state } : { trait: status.trait };
12751
- if (status.status === OwnedTraitLink.Changed) {
12752
- if (status.actual !== undefined)
12753
- kept.push(ref);
12754
- continue;
12755
- }
12756
- if (ref.state)
12757
- deleteState(target, ref.state, ref.trait);
12758
- else
12759
- writeBase(target, ref.trait, baseline[ref.trait]);
12760
- }
12761
- }
12762
- writeEntries(target, to, kept);
12763
- return { applied: true, kept };
12851
+ function resolveTokenReferences(value, tokens, mode = LIGHT) {
12852
+ const resolved = resolveTokenValues(tokens, mode);
12853
+ return value.replace(TOKEN_REF$1, (ref, name, fallback) => resolved[name] ?? (fallback !== undefined ? fallback.trim() : ref));
12854
+ }
12855
+ /** A scheme value with its placeholders replaced — `{{token}}` by the root's resolved value in this
12856
+ * mode, `{{~token}}` by its value in the OTHER mode (an inverse section is the other mode's ground,
12857
+ * DS9) — or null when a placeholder names a token the set does not have (the declaration is then
12858
+ * omitted). */
12859
+ function substitute(template, resolved, opposite) {
12860
+ let missing = false;
12861
+ const value = template.replace(PLACEHOLDER, (_, tilde, name) => {
12862
+ const from = tilde ? opposite : resolved;
12863
+ if (!(name in from))
12864
+ missing = true;
12865
+ return from[name] ?? '';
12866
+ });
12867
+ return missing ? null : value;
12764
12868
  }
12869
+ const schemeEntries = (set, resolved, opposite) => Object.entries(set)
12870
+ .map(([name, template]) => [name, substitute(template, resolved, opposite)])
12871
+ .filter((e) => e[1] !== null);
12872
+ const hasPlaceholder = (value) => value.includes('{{');
12765
12873
  /**
12766
- * The element-style MARKER (design-system §3b-bis, spec `elementStyleMarker`). A node that wears an
12767
- * element style renders `iox-es iox-es-<id>` in BOTH renderers, through the one node-class helper
12768
- * `nodeDesignClasses` (node-classes.ts); every piece of component CSS that carries the system look is
12769
- * scoped to it, so a node saved before DS2 (no `elementStyle`) keeps its exact pre-DS2 component CSS
12770
- * no matter which tokens the org holds.
12874
+ * Section colour schemes (design-system plan §3c). Two rules, both about WHERE a custom property
12875
+ * resolves:
12876
+ *
12877
+ * - ⚠️ Custom properties resolve where they are DECLARED: `--iox-card-bg: var(--iox-surface)`
12878
+ * declared on `:root` keeps `:root`'s surface inside `.iox-scheme-inverse`. So each scheme block
12879
+ * carries the spec's overrides PLUS every other token whose value references a token,
12880
+ * re-declared verbatim — re-resolving it in the section.
12881
+ * - ⚠️ A scheme that SWAPS two tokens (Brand: accent ⇄ accent-ink) cannot write them as `var()`:
12882
+ * on one element `accent: var(--iox-accent-ink)` + `accent-ink: var(--iox-accent)` is a cycle,
12883
+ * and CSS makes both invalid. The contract writes `{{accent}}` instead — the token's ROOT value,
12884
+ * resolved here at compile time (and again from dark values for the dark twin).
12771
12885
  */
12772
- const ELEMENT_STYLE_MARKER_CLASS = 'iox-es';
12773
- const MARKER_PREFIX$1 = `${ELEMENT_STYLE_MARKER_CLASS}-`;
12774
- /** `iox-es-<id>` for a known style, else null — an unknown id never reaches a class attribute. */
12775
- function elementStyleClass(id) {
12776
- return elementStyleById(id) ? `${MARKER_PREFIX$1}${id}` : null;
12886
+ function compileSchemeCss(tokens, scope, opposite) {
12887
+ const rows = usableTokens(tokens);
12888
+ if (!rows.length)
12889
+ return '';
12890
+ const resolved = resolveTokenValues(rows, LIGHT);
12891
+ const other = opposite ?? resolveTokenValues(rows, DARK);
12892
+ return [
12893
+ ...nonDefaultSchemes().map(([id, set]) => rule(schemeSelector(id, scope), [
12894
+ ...schemeEntries(set, resolved, other),
12895
+ ...rows.filter(t => !(t.name in set) && t.value.includes(REFERENCE))
12896
+ .map((t) => [t.name, t.value]),
12897
+ ])),
12898
+ schemeInkRule(scope),
12899
+ ].filter(Boolean).join('\n');
12777
12900
  }
12778
12901
  /**
12779
- * The spec's `headingLevelRule`: changing a Heading's level moves it to the new level's DEFAULT
12780
- * style only when it still wears its old level's default (author changes kept, via
12781
- * `switchElementStyle`); a Heading wearing any other style — or none — is left alone. Returns null
12782
- * when nothing switched.
12902
+ * Every element wearing a scheme takes the scheme's ink (vectoriox/iox-ui#15): the tokens re-resolve inside
12903
+ * it, but `color` is inherited as a COMPUTED value, so without this a scheme'd Container's running text kept
12904
+ * the outer section's ink. One rule, specificity (0,1,0) in every renderer — a builder scope is wrapped in
12905
+ * `:where()` — emitted with the token CSS, BEFORE every node rule: a node's own colour (same specificity,
12906
+ * later) still wins. A node's `color: inherit` is read as this ink by `withSchemeInk`.
12783
12907
  */
12784
- function applyHeadingLevelRule(target, oldLevel, newLevel, baseline = {}) {
12785
- if (target.type !== 'Heading' || oldLevel === newLevel)
12786
- return null;
12787
- const from = defaultElementStyleFor('Heading', { level: oldLevel });
12788
- const to = defaultElementStyleFor('Heading', { level: newLevel });
12789
- if (!from || !to || target.elementStyle !== from || from === to)
12790
- return null;
12791
- return switchElementStyle(target, from, to, baseline);
12908
+ function schemeInkRule(scope) {
12909
+ const where = scope ? `:where(${scope}) ` : '';
12910
+ const selectors = nonDefaultSchemes().map(([id]) => `${where}.iox-scheme-${id}`);
12911
+ return selectors.length ? `${selectors.join(', ')} {\n color: ${SCHEME_INK};\n}` : '';
12912
+ }
12913
+ /** The dark twin of both scheme rules: a re-declared token with a dark value would otherwise pin
12914
+ * the LIGHT value inside the section, and a `{{token}}` swap was resolved from LIGHT values. */
12915
+ function darkSchemeRules(rows, scope) {
12916
+ const light = resolveTokenValues(rows, LIGHT);
12917
+ const dark = resolveTokenValues(rows, DARK);
12918
+ const redeclared = rows.filter(t => {
12919
+ const d = darkOf(t);
12920
+ return d !== null && (t.value.includes(REFERENCE) || d.includes(REFERENCE));
12921
+ });
12922
+ return nonDefaultSchemes().map(([id, set]) => {
12923
+ const swapped = Object.fromEntries(Object.entries(set).filter(([, v]) => hasPlaceholder(v)));
12924
+ const lightSwap = new Map(schemeEntries(swapped, light, dark));
12925
+ return rule(schemeSelector(id, scope), [
12926
+ ...schemeEntries(swapped, dark, light).filter(([name, value]) => lightSwap.get(name) !== value),
12927
+ ...redeclared.filter(t => !(t.name in set)).map((t) => [t.name, darkOf(t)]),
12928
+ ], ' ');
12929
+ });
12930
+ }
12931
+ function presentOverrides(rows, overrides, onlyWithDark) {
12932
+ return Object.entries(overrides).filter(([name]) => rows.some(t => t.name === name && (!onlyWithDark || darkOf(t) !== null)));
12933
+ }
12934
+ /** The token CSS as the two halves a renderer places around its node rules. */
12935
+ function compileTokenCssBlocks(tokens, opts = {}) {
12936
+ const all = usableTokens(tokens);
12937
+ const rows = rowsForMode(all, opts.mode);
12938
+ const root = opts.scope ?? ROOT;
12939
+ // A forced mode flattens the rows to ONE mode; a `{{~token}}` still reads the other mode's value.
12940
+ const opposite = resolveTokenValues(all, opts.mode === DARK ? LIGHT : DARK);
12941
+ const present = new Set(rows.map(t => t.name));
12942
+ const unset = opts.isolate
12943
+ ? [...specTokenNames()].filter(name => !present.has(name)).map((name) => [name, 'initial'])
12944
+ : [];
12945
+ const base = [
12946
+ rule(root, [...unset, ...rows.map((t) => [t.name, t.value])]),
12947
+ compileSchemeCss(rows, opts.scope, opposite),
12948
+ ...ACCESSIBILITY_MEDIA.map(({ query, overrides }) => media(query, [rule(root, presentOverrides(rows, overrides, false), ' ')])),
12949
+ ].filter(Boolean).join('\n');
12950
+ const darkRows = rows.map((t) => [t.name, darkOf(t)])
12951
+ .filter((e) => e[1] !== null);
12952
+ const dark = darkRows.length ? [
12953
+ media(MEDIA_DARK, [rule(root, darkRows, ' '), ...darkSchemeRules(rows, opts.scope)]),
12954
+ // Glass carries dark glass values; without this the dark block (LAST) would out-rank the
12955
+ // reduced-transparency swap and keep dark surfaces translucent.
12956
+ ...ACCESSIBILITY_MEDIA.map(({ query, overrides }) => media(`${MEDIA_DARK} and ${query}`, [rule(root, presentOverrides(rows, overrides, true), ' ')])),
12957
+ ].filter(Boolean).join('\n') : '';
12958
+ return { base, dark };
12959
+ }
12960
+ /** One stylesheet string: base → schemes → reduced motion → reduced transparency → dark LAST. */
12961
+ function compileTokenCss(tokens, opts = {}) {
12962
+ const { base, dark } = compileTokenCssBlocks(tokens, opts);
12963
+ return [base, dark].filter(Boolean).join('\n');
12964
+ }
12965
+
12966
+ /** A pack's own sanity: every token row names itself and carries a value. Returns errors. */
12967
+ function validateFlavorPack(pack) {
12968
+ const errors = [];
12969
+ for (const t of pack.tokens ?? []) {
12970
+ if (!t.name)
12971
+ errors.push(`${pack.id}: token with empty name`);
12972
+ if (!t.value)
12973
+ errors.push(`${pack.id}: token "${t.name}" has no value`);
12974
+ }
12975
+ return errors;
12792
12976
  }
12977
+ /**
12978
+ * Swiss — the retired F1 pack: four tokens (control/surface radius, control/stack spacing) and the
12979
+ * hue opinion F2's brand step read. Three of its rows become the spec's `legacyAliases` on migration;
12980
+ * `space-control` is a v2 token in every pack and takes Crisp's value.
12981
+ */
12982
+ const SWISS_FLAVOR = {
12983
+ id: 'swiss',
12984
+ version: 1,
12985
+ label: 'Swiss',
12986
+ blurb: 'Grid discipline, hairline rules, near-zero motion.',
12987
+ tokens: [
12988
+ { name: 'radius-control', value: '4px', type: 'size', group: 'shape' },
12989
+ { name: 'radius-surface', value: '4px', type: 'size', group: 'shape' },
12990
+ { name: 'space-control', value: '10px 18px', type: 'spacing', group: 'space' },
12991
+ { name: 'space-stack', value: '12px', type: 'spacing', group: 'space' },
12992
+ ],
12993
+ // F2: Swiss turns the seed toward a cool counterpoint.
12994
+ accentHueDeg: 150,
12995
+ };
12793
12996
 
12794
12997
  /**
12795
12998
  * ADOPT (design-system DS7, decision D5): link the nodes of a page saved before element styles to
@@ -12987,116 +13190,6 @@ function previewElementStyleAdoption(tree, ctx) {
12987
13190
  return adoptElementStyles(JSON.parse(JSON.stringify(tree ?? [])), ctx);
12988
13191
  }
12989
13192
 
12990
- /**
12991
- * The design-system classes a node renders, in BOTH renderers (builder canvas, engine component
12992
- * path, engine string path) — ONE helper, so the three can never disagree about which class a node
12993
- * carries:
12994
- *
12995
- * - the element-style MARKER `iox-es iox-es-<id>` (design-system §3b-bis) — component CSS carrying
12996
- * the system look is scoped to it, so a node saved before DS2 keeps its pre-DS2 look;
12997
- * - the section colour scheme `iox-scheme-<id>` (§3c) for a non-default `colorScheme` — the class
12998
- * `compileSchemeCss` re-declares tokens on, so everything inside re-resolves;
12999
- * - the composite ROLE `iox-role-<role>` (`card.media` → `iox-role-card-media`; composite-components
13000
- * plan §3, Card v2) — what the shared CSS keys a part's structural rules on (the badge's corner, the
13001
- * card's stretched link), so a role is never a renderer's private knowledge.
13002
- *
13003
- * Renderers read `elementStyle`, `colorScheme` and `role` for exactly this and nothing else.
13004
- */
13005
- /** The section colour schemes — the contract's `schemes` keys. */
13006
- var ColorScheme;
13007
- (function (ColorScheme) {
13008
- ColorScheme["Default"] = "default";
13009
- ColorScheme["Subtle"] = "subtle";
13010
- ColorScheme["Inverse"] = "inverse";
13011
- ColorScheme["Accent"] = "accent";
13012
- })(ColorScheme || (ColorScheme = {}));
13013
- /** The content trait (stored in `props`, not a style prop) that picks a node's scheme. */
13014
- const COLOR_SCHEME_TRAIT = 'colorScheme';
13015
- /** The node types that offer a scheme (spec §3c: Section/Container; LinkedContainer is a Container that links). */
13016
- const COLOR_SCHEME_TYPES = ['Section', 'Container', 'LinkedContainer'];
13017
- /** Every scheme in contract order, with the contract's label (Default · Subtle · Inverse · Brand). */
13018
- const COLOR_SCHEMES = Object.keys(SPEC_SCHEMES).map(id => ({ id, label: SPEC_SCHEMES[id].label }));
13019
- const SCHEME_IDS$1 = new Set(Object.keys(SPEC_SCHEMES));
13020
- const SCHEME_PREFIX = 'iox-scheme-';
13021
- const ROLE_PREFIX = 'iox-role-';
13022
- /** A role is dot-separated lowercase words (`card`, `card.title`) — anything else never reaches a class. */
13023
- const ROLE_SHAPE = /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/;
13024
- const MARKER_PREFIX = `${ELEMENT_STYLE_MARKER_CLASS}-`;
13025
- /** `iox-scheme-<id>` for a known NON-default scheme, else null — an unknown id never reaches a class attribute. */
13026
- function colorSchemeClass(id) {
13027
- return typeof id === 'string' && id !== ColorScheme.Default && SCHEME_IDS$1.has(id) ? `${SCHEME_PREFIX}${id}` : null;
13028
- }
13029
- /** `iox-role-<role>` for a well-formed composite role, else null. */
13030
- function roleClass(role) {
13031
- return typeof role === 'string' && ROLE_SHAPE.test(role) ? `${ROLE_PREFIX}${role.replace(/\./g, '-')}` : null;
13032
- }
13033
- /** The element style whose wearer is the phone's bottom navigation bar (CC1, R1). */
13034
- const BOTTOM_BAR_STYLE = 'bottom-bar';
13035
- /** The page reserves the bar's height wherever this class is on the page… */
13036
- const BAR_RESERVE_CLASS = 'iox-bar-reserve';
13037
- /** …the responsive Breakpoint ids that can hold an override, widest first (a spec pins it to BREAKPOINTS). */
13038
- const BAR_RESERVE_BREAKPOINTS = ['laptop', 'tablet', 'mobile', 'small'];
13039
- function baseDisplayOf(node) {
13040
- if (node.styleProps && 'display' in node.styleProps)
13041
- return node.styleProps['display'];
13042
- for (const group of node.styleTraits ?? []) {
13043
- const hit = group.traits?.find(t => t?.name === 'display');
13044
- if (hit)
13045
- return hit.default;
13046
- }
13047
- return undefined;
13048
- }
13049
- /**
13050
- * The class that makes the page reserve a bottom bar's height (iox-design.css: `padding-bottom` and
13051
- * `scroll-padding-bottom`, WCAG 2.4.11), only at the widths where the bar SHOWS: `iox-bar-reserve` when
13052
- * it shows at every width, `iox-bar-reserve-<bp>` when it is hidden at base and shown from that
13053
- * breakpoint down (the phone shape's own rule), null when it never shows or is not a bar.
13054
- */
13055
- function barReserveClass(node) {
13056
- if (!node || node.elementStyle !== BOTTOM_BAR_STYLE)
13057
- return null;
13058
- if (baseDisplayOf(node) !== 'none')
13059
- return BAR_RESERVE_CLASS;
13060
- for (const bp of BAR_RESERVE_BREAKPOINTS) {
13061
- const display = node.responsive?.[bp]?.styleProps?.['display'];
13062
- if (display !== undefined && display !== 'none')
13063
- return `${BAR_RESERVE_CLASS}-${bp}`;
13064
- }
13065
- return null;
13066
- }
13067
- /** A node's `colorScheme`, from `props` (serialized) or its trait (builder). */
13068
- function colorSchemeOf(node) {
13069
- if (!node)
13070
- return null;
13071
- const fromProps = node.props?.[COLOR_SCHEME_TRAIT];
13072
- if (fromProps !== undefined)
13073
- return typeof fromProps === 'string' ? fromProps : null;
13074
- const trait = node.traits?.find(t => t?.name === COLOR_SCHEME_TRAIT)?.default;
13075
- return typeof trait === 'string' ? trait : null;
13076
- }
13077
- /** True when the node wears a known element style — the marker is on it. */
13078
- function hasElementStyleMarker(node) {
13079
- return elementStyleClass(node?.elementStyle) !== null;
13080
- }
13081
- /** The classes a node renders: `'iox-es iox-es-card iox-scheme-inverse'` (+ a bottom bar's reserve class), or `''` for none. */
13082
- function nodeDesignClasses(node) {
13083
- const marker = elementStyleClass(node?.elementStyle);
13084
- return [marker ? `${ELEMENT_STYLE_MARKER_CLASS} ${marker}` : '', colorSchemeClass(colorSchemeOf(node)) ?? '', roleClass(node?.role) ?? '', barReserveClass(node) ?? '']
13085
- .filter(Boolean).join(' ');
13086
- }
13087
- const isDesignClass = (c) => c === ELEMENT_STYLE_MARKER_CLASS || c.startsWith(MARKER_PREFIX) || c.startsWith(SCHEME_PREFIX) || c.startsWith(ROLE_PREFIX) || c.startsWith(BAR_RESERVE_CLASS);
13088
- /** Make an element's design classes match the node — the live switch on the canvas and the
13089
- * component path's one-time stamp. Removes stale ones; touches no other class. */
13090
- function syncNodeDesignClasses(el, node) {
13091
- if (!el?.classList)
13092
- return;
13093
- for (const c of Array.from(el.classList).filter(isDesignClass))
13094
- el.classList.remove(c);
13095
- const classes = nodeDesignClasses(node);
13096
- if (classes)
13097
- el.classList.add(...classes.split(' '));
13098
- }
13099
-
13100
13193
  /**
13101
13194
  * Empty images (design-system DS3). A MARKED Image (`.iox-es-media`) with no source renders a box in
13102
13195
  * the `surface-2` token with its aspect ratio kept — on the canvas with an "Add image" label, on the
@@ -13836,6 +13929,8 @@ const GLYPH_GROUP = 'Media';
13836
13929
  const FORMS_GROUP = 'Forms';
13837
13930
  const DEFAULT_SCHEME = 'default';
13838
13931
  const SECTION = 'section';
13932
+ /** The element a scheme'd container's running text is reported as (iox-ui#15). */
13933
+ const INHERITED_TEXT = 'inherited text';
13839
13934
  const FIELD = 'field';
13840
13935
  const WHITE = { r: 1, g: 1, b: 1, a: 1 };
13841
13936
  const SINGLE_REF = /^var\(--iox-([\w-]+)\)$/;
@@ -13948,10 +14043,51 @@ function auditDesignContrast(rows, mode) {
13948
14043
  }
13949
14044
  }
13950
14045
  }
14046
+ findings.push(...schemedContainerFindings(rows, mode, section, containers.filter((c) => !!c)));
13951
14047
  for (const scrim of SCRIMS)
13952
14048
  findings.push(...scrimFindings(rows, mode, scrim, byId.get(scrim.style)));
13953
14049
  return findings;
13954
14050
  }
14051
+ /** What a section in `values` paints under its content: its own fill over the page, or the page. */
14052
+ function sectionGround(values) {
14053
+ const pageInk = parseCssColour(values['text']);
14054
+ const page = paintColours(values['page-bg'], pageInk);
14055
+ const sectionPaint = paintColours(values['section-bg'], pageInk);
14056
+ return opaque(sectionPaint) ? sectionPaint.map(c => (c.a < 1 ? compositeOver(c, page[0] ?? WHITE) : c)) : page;
14057
+ }
14058
+ /**
14059
+ * A container that wears a NON-default scheme, inside a section of every scheme (vectoriox/iox-ui#15 — the
14060
+ * Featured card is a Card in Brand on a Default page). Its tokens are its own scheme's laid over the outer
14061
+ * section's (what it does not set, it inherits); a translucent fill composites over the OUTER section; the running text it holds (`body`, `lead`, a label: `color: inherit`)
14062
+ * reads the scheme's ink, which is what the scheme-ink rule and `withSchemeInk` make CSS compute.
14063
+ */
14064
+ function schemedContainerFindings(rows, mode, section, containers) {
14065
+ const out = [];
14066
+ for (const outer of SCHEME_IDS) {
14067
+ const ground = sectionGround(resolveSchemeTokenValues(rows, mode, outer));
14068
+ for (const inner of SCHEME_IDS.filter(id => id !== DEFAULT_SCHEME)) {
14069
+ const values = resolveSchemeTokenValues(rows, mode, [outer, inner]);
14070
+ const schemeInk = parseCssColour(resolveIn(values, SCHEME_INK), parseCssColour(resolveIn(values, section['color'])));
14071
+ for (const box of containers) {
14072
+ const style = ownStyle(box);
14073
+ const bgs = overAll(paintColours(resolveIn(values, style['backgroundColor']), schemeInk), ground);
14074
+ const ink = style['color'] && style['color'] !== 'inherit' ? parseCssColour(resolveIn(values, style['color']), schemeInk) : schemeInk;
14075
+ if (!ink)
14076
+ continue;
14077
+ for (const g of bgs) {
14078
+ const bg = quantizeRgb(g);
14079
+ const f = quantizeRgb(ink.a < 1 ? compositeOver(ink, bg) : ink);
14080
+ const ratio = wcagContrastRatio(f, bg);
14081
+ out.push({
14082
+ scheme: inner, container: `${box.id} in a ${outer} section`, element: INHERITED_TEXT, part: 'text',
14083
+ fg: toHexColor(f), bg: toHexColor(bg), ratio: Math.round(ratio * 100) / 100, min: WCAG_AA_TEXT, pass: ratio >= WCAG_AA_TEXT - 1e-9,
14084
+ });
14085
+ }
14086
+ }
14087
+ }
14088
+ }
14089
+ return out;
14090
+ }
13955
14091
  /** Text over an image: the block's ink — and every token its ink scope pins to it — over the scrim
13956
14092
  * composited on the WORST-CASE image, in the scheme the block wears. Never over the section: the image
13957
14093
  * covers it. */
@@ -15245,5 +15381,5 @@ function planFontFamilyRemoval(existing, previous, family) {
15245
15381
  * Generated bundle index. Do not edit.
15246
15382
  */
15247
15383
 
15248
- export { ACCENT_TOKEN_NAMES, AdoptOutcome, AdoptSkipReason, BAR_RESERVE_BREAKPOINTS, BAR_RESERVE_CLASS, BOTTOM_BAR_STYLE, BUTTON_VARIANT_TO_STYLE, CANVAS_LINK_ATTRIBUTE, CARD_LINK_CLASS, COLOR_SCHEMES, COLOR_SCHEME_TRAIT, COLOR_SCHEME_TYPES, ColorScheme, ColourCheckId, DEFAULT_FLAVOR_ID, DESIGN_TOKEN_GROUPS, DESIGN_TOKEN_TIERS, DESIGN_TOKEN_TYPES, DesignConfigKey, ELEMENT_STYLES, ELEMENT_STYLE_MARKER_CLASS, ELEMENT_STYLE_STATES, EMPTY_IMAGE_ATTRIBUTE, EMPTY_IMAGE_SRC, ElementStyleState, FLAVOR_SPEC_VERSION, FONT_CATALOG, FONT_FAMILY_TOKENS, FONT_FILE_ACCEPT, FONT_FILE_EXTENSIONS, FONT_FILE_FORMATS, FONT_STYLES, FONT_WEIGHTS, FontUploadError, GOOGLE_FONTS_CSS_ORIGIN, GOOGLE_FONTS_FILE_ORIGIN, HELD_CONTRAST, LinkType, MAX_FONT_FILE_BYTES, NEW_NODE_STYLE_DEFAULTS, OwnedTraitLink, PALETTE_PRESETS, PRE_DS2_FLAVOR_DEFAULTS, PRE_TINT_DEFAULT, REDUCED_MOTION_OVERRIDES, REDUCED_TRANSPARENCY_OVERRIDES, REVERT_WINDOW_MS, SPEC_A11Y, SPEC_AXES, SPEC_BUTTON_VARIANT_TO_STYLE, SPEC_CONTRAST, SPEC_CONTRAST_AUDIT, SPEC_DEFAULT_ELEMENT_STYLE, SPEC_ELEMENT_STYLES, SPEC_FIELD_CLASS_BASELINE, SPEC_FLAVORS, SPEC_FLAVOR_PACKS, SPEC_FONTS, SPEC_FONT_ROLES, SPEC_INK_SCOPES, SPEC_LEGACY_ALIASES, SPEC_PALETTE_PRESETS, SPEC_SCHEMES, SPEC_SPRINGS, SPEC_TIERS, SPEC_TIER_IDS, SPEC_TOKEN_TYPES, SUPERSEDED_STYLE_VALUES, SWISS_FLAVOR, TINT_FULL_CHROMA, TINT_IDS, TINT_LEVELS, TINT_OWN_HUE_CHROMA, TINT_TURN_CHROMA, USER_TOKEN_SOURCE, WCAG_AA_TEXT, WCAG_AA_UI, adoptElementStyles, adoptionStyleFor, apcaLc, applyElementStyle, applyHeadingLevelRule, atLightness, atLightnessMaxChroma, auditDesignContrast, barReserveClass, buildFlavorTokens, buildFontFaceCss, buttonBlockHref, catalogFontId, checkFontWeight, choicesForPack, colorSchemeClass, colorSchemeOf, colourTabChecks, compareFontWeights, compileSchemeCss, compileTokenCss, compileTokenCssBlocks, compositeOver, cssString, defaultDesignChoices, defaultElementStyleFor, deriveAccentTokens, deriveBrandTokens, designBaseline, designConfigEntries, designModeOf, designTokenRepairs, designTokenRows, elementStyleById, elementStyleClass, elementStylesFor, emptyImageLabelSrc, emptyImagePlaceholder, emptyImageSrc, ensureAlpha, ensureContrast, flavorColours, flavorPackById, flavorSeedWrites, flavorSourceStamp, fontFamilyKey, fontFileName, fontFileStem, fontFormatOf, fontPreloads, fontRoleReferences, fontStackWithout, fontWeightCovers, fontWeightRange, fontWeightValue, fontsForTokens, formatFontFamilies, formatFontFamily, formatFontWeight, googleFontHref, groupFontFamilies, hasElementStyleMarker, headingHref, inSrgbGamut, isDesignTokenGroup, isDesignTokenTier, isDesignTokenType, isFontWeightRange, isRetiredFlavorId, legacyDesignTokenRows, legacyFlavorTokens, legacyRuleTokenReferences, matchFontWeight, missingDesignRows, missingFontRoles, nodeDesignClasses, normalizeFontStyle, normalizeFontWeight, normalizeFontWeightValue, oklabToRgb, ownedTraitStatus, ownedTraitsOf, packDesignChoices, paintColours, parseCssColour, parseFontFamilies, parseFontWeight, parseHexColor, planDesignApply, planDesignRevert, planFlavorRevert, planFlavorSeed, planFontFamilyRemoval, planSwissMigration, preTintAccentGrounds, previewElementStyleAdoption, previewFontWeight, quantizeRgb, readDesignConfig, readOwnedTrait, relativeLuminance, resolveDesignChoices, resolveSchemeTokenValues, resolveTokenReferences, resolveTokenValues, revertAvailable, rgbToOklab, ringColour, roleClass, sameDesignChoices, specTokenNames, splitTopLevel, springToLinear, stackNamesFamily, styleKeyToKebab, switchElementStyle, syncNodeDesignClasses, toHexColor, tokenReferences, tokensToDarkVarMap, tokensToVarMap, unknownTokenReferences, untouchedSeedRow, userFontStack, validateFlavorPack, wcagContrastRatio, writeOwnedTrait };
15384
+ export { ACCENT_TOKEN_NAMES, AdoptOutcome, AdoptSkipReason, BAR_RESERVE_BREAKPOINTS, BAR_RESERVE_CLASS, BOTTOM_BAR_STYLE, BUTTON_VARIANT_TO_STYLE, CANVAS_LINK_ATTRIBUTE, CARD_LINK_CLASS, COLOR_SCHEMES, COLOR_SCHEME_TRAIT, COLOR_SCHEME_TYPES, ColorScheme, ColourCheckId, DEFAULT_FLAVOR_ID, DESIGN_TOKEN_GROUPS, DESIGN_TOKEN_TIERS, DESIGN_TOKEN_TYPES, DesignConfigKey, ELEMENT_STYLES, ELEMENT_STYLE_MARKER_CLASS, ELEMENT_STYLE_STATES, EMPTY_IMAGE_ATTRIBUTE, EMPTY_IMAGE_SRC, ElementStyleState, FLAVOR_SPEC_VERSION, FONT_CATALOG, FONT_FAMILY_TOKENS, FONT_FILE_ACCEPT, FONT_FILE_EXTENSIONS, FONT_FILE_FORMATS, FONT_STYLES, FONT_WEIGHTS, FontUploadError, GOOGLE_FONTS_CSS_ORIGIN, GOOGLE_FONTS_FILE_ORIGIN, HELD_CONTRAST, INHERITED_TEXT, LinkType, MAX_FONT_FILE_BYTES, NEW_NODE_STYLE_DEFAULTS, OwnedTraitLink, PALETTE_PRESETS, PRE_DS2_FLAVOR_DEFAULTS, PRE_TINT_DEFAULT, REDUCED_MOTION_OVERRIDES, REDUCED_TRANSPARENCY_OVERRIDES, REVERT_WINDOW_MS, SCHEME_INK, SPEC_A11Y, SPEC_AXES, SPEC_BUTTON_VARIANT_TO_STYLE, SPEC_CONTRAST, SPEC_CONTRAST_AUDIT, SPEC_DEFAULT_ELEMENT_STYLE, SPEC_ELEMENT_STYLES, SPEC_FIELD_CLASS_BASELINE, SPEC_FLAVORS, SPEC_FLAVOR_PACKS, SPEC_FONTS, SPEC_FONT_ROLES, SPEC_INK_SCOPES, SPEC_LEGACY_ALIASES, SPEC_PALETTE_PRESETS, SPEC_SCHEMES, SPEC_SCHEME_INK, SPEC_SPRINGS, SPEC_TIERS, SPEC_TIER_IDS, SPEC_TOKEN_TYPES, SUPERSEDED_STYLE_VALUES, SWISS_FLAVOR, TINT_FULL_CHROMA, TINT_IDS, TINT_LEVELS, TINT_OWN_HUE_CHROMA, TINT_TURN_CHROMA, USER_TOKEN_SOURCE, WCAG_AA_TEXT, WCAG_AA_UI, adoptElementStyles, adoptionStyleFor, apcaLc, applyElementStyle, applyHeadingLevelRule, atLightness, atLightnessMaxChroma, auditDesignContrast, barReserveClass, buildFlavorTokens, buildFontFaceCss, buttonBlockHref, catalogFontId, checkFontWeight, choicesForPack, colorSchemeClass, colorSchemeOf, colourTabChecks, compareFontWeights, compileSchemeCss, compileTokenCss, compileTokenCssBlocks, compositeOver, cssString, defaultDesignChoices, defaultElementStyleFor, deriveAccentTokens, deriveBrandTokens, designBaseline, designConfigEntries, designModeOf, designTokenRepairs, designTokenRows, elementStyleById, elementStyleClass, elementStylesFor, emptyImageLabelSrc, emptyImagePlaceholder, emptyImageSrc, ensureAlpha, ensureContrast, flavorColours, flavorPackById, flavorSeedWrites, flavorSourceStamp, fontFamilyKey, fontFileName, fontFileStem, fontFormatOf, fontPreloads, fontRoleReferences, fontStackWithout, fontWeightCovers, fontWeightRange, fontWeightValue, fontsForTokens, formatFontFamilies, formatFontFamily, formatFontWeight, googleFontHref, groupFontFamilies, hasElementStyleMarker, headingHref, inSrgbGamut, isDesignTokenGroup, isDesignTokenTier, isDesignTokenType, isFontWeightRange, isRetiredFlavorId, legacyDesignTokenRows, legacyFlavorTokens, legacyRuleTokenReferences, matchFontWeight, missingDesignRows, missingFontRoles, nodeDesignClasses, normalizeFontStyle, normalizeFontWeight, normalizeFontWeightValue, oklabToRgb, ownedTraitStatus, ownedTraitsOf, packDesignChoices, paintColours, parseCssColour, parseFontFamilies, parseFontWeight, parseHexColor, planDesignApply, planDesignRevert, planFlavorRevert, planFlavorSeed, planFontFamilyRemoval, planSwissMigration, preTintAccentGrounds, previewElementStyleAdoption, previewFontWeight, quantizeRgb, readDesignConfig, readOwnedTrait, relativeLuminance, resolveDesignChoices, resolveSchemeTokenValues, resolveTokenReferences, resolveTokenValues, revertAvailable, rgbToOklab, ringColour, roleClass, sameDesignChoices, setColorScheme, specTokenNames, splitTopLevel, springToLinear, stackNamesFamily, styleKeyToKebab, switchElementStyle, syncNodeDesignClasses, toHexColor, tokenReferences, tokensToDarkVarMap, tokensToVarMap, unknownTokenReferences, untouchedSeedRow, userFontStack, validateFlavorPack, wcagContrastRatio, withSchemeInk, writeOwnedTrait };
15249
15385
  //# sourceMappingURL=vectoriox-iox-ui-design-core.mjs.map