@vectoriox/iox-ui 5.15.0 → 5.17.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.
@@ -1187,7 +1187,7 @@ declare const SPEC_ELEMENT_STYLES: readonly [{
1187
1187
  readonly fontSize: "var(--iox-size-lead)";
1188
1188
  readonly fontWeight: "var(--iox-weight-body)";
1189
1189
  readonly lineHeight: "var(--iox-leading-body)";
1190
- readonly color: "var(--iox-text)";
1190
+ readonly color: "inherit";
1191
1191
  };
1192
1192
  }, {
1193
1193
  readonly id: "body";
@@ -1199,7 +1199,7 @@ declare const SPEC_ELEMENT_STYLES: readonly [{
1199
1199
  readonly fontSize: "var(--iox-size-body)";
1200
1200
  readonly fontWeight: "var(--iox-weight-body)";
1201
1201
  readonly lineHeight: "var(--iox-leading-body)";
1202
- readonly color: "var(--iox-text)";
1202
+ readonly color: "inherit";
1203
1203
  };
1204
1204
  }, {
1205
1205
  readonly id: "small";
@@ -8496,9 +8496,17 @@ declare const EMPTY_IMAGE_SRC: string;
8496
8496
  */
8497
8497
  declare function emptyImageLabelSrc(label: string): string;
8498
8498
  /**
8499
- * The `src` a node's Image renders when its own source is empty: the canvas label (when `label` is
8500
- * given) or the live blank for a MARKED node; `null` — render exactly as before — for an unmarked
8501
- * node or one that has a source.
8499
+ * What a node's Image shows WHENEVER its source is empty: the canvas label (when `label` is given) or
8500
+ * the live blank for a MARKED node; `null` — render exactly as before — for an unmarked node. It does
8501
+ * not look at the source, so a renderer computes it once per marker/label and the component falls
8502
+ * back to it the moment its source is cleared (a binding emptied, a context slot not yet filled).
8503
+ */
8504
+ declare function emptyImagePlaceholder(node: {
8505
+ elementStyle?: string | null;
8506
+ } | null | undefined, label?: string | null): string | null;
8507
+ /**
8508
+ * The `src` a node's Image renders for a given source: its placeholder while the source is empty,
8509
+ * `null` once it has one (the source itself renders) or when the node is unmarked.
8502
8510
  */
8503
8511
  declare function emptyImageSrc(node: {
8504
8512
  elementStyle?: string | null;
@@ -8647,7 +8655,9 @@ type SeedBaseline = ReadonlyMap<string, SeedExpectation> | ((row: StoredDesignTo
8647
8655
  * write is never touched: no `flavor:` stamp, unless it is a `user` row (a font the author CHOSE in
8648
8656
  * the design) whose value is exactly what the previous design wrote — that row is the design's own
8649
8657
  * choice, so a new choice may replace it. `group`/`tier` ride every write; a desired row's own
8650
- * `source` (a chosen font's `user`) wins over the pack stamp.
8658
+ * `source` (a chosen font's `user`) wins over the pack stamp. A machine-owned row that already holds
8659
+ * the desired value but another pack's stamp is RE-STAMPED (§10 C2 provenance: the stamp names the
8660
+ * design that owns the row, so the next switch judges it against the right baseline).
8651
8661
  */
8652
8662
  declare function planFlavorSeed(desired: ReadonlyArray<FlavorTokenDef & {
8653
8663
  source?: string;
@@ -8727,7 +8737,9 @@ declare function unknownTokenReferences(css: string, alsoAllowed?: Iterable<stri
8727
8737
  * Tokens read by a LEGACY rule — any rule whose selector is scoped `:not(.iox-es)` (the pre-DS2 look
8728
8738
  * of a node that wears no element style, design-system §3b-bis). Such a rule must read NO token at
8729
8739
  * all: a token it reads repaints a pre-DS2 page the moment the org's rows are seeded or a flavor is
8730
- * applied (D5). Returns `selector → names` for each offending rule; comments are ignored.
8740
+ * applied (D5). Returns `selector → names` for each offending rule; comments are ignored. SCSS
8741
+ * nesting is resolved (DS4.1): `.x:where(:not(.iox-es)) { &:hover { … } }` is the legacy rule
8742
+ * `.x:where(:not(.iox-es)):hover`, and an at-rule (`@media`) between them does not break the chain.
8731
8743
  */
8732
8744
  declare function legacyRuleTokenReferences(css: string): Array<{
8733
8745
  selector: string;
@@ -8784,6 +8796,12 @@ declare function isRetiredFlavorId(id: string | null | undefined): boolean;
8784
8796
  declare function resolveDesignChoices(partial: Partial<DesignChoices> | null | undefined): DesignChoices;
8785
8797
  /** The pack's own defaults for `flavorId` — what picking a card means before anything is tuned. */
8786
8798
  declare function packDesignChoices(flavorId: string): DesignChoices;
8799
+ /**
8800
+ * What picking a pack card means, given the design the studio wears: the applied pack → the applied
8801
+ * choices; another pack → that pack's defaults, CARRYING the author's chosen fonts (§5b — a font the
8802
+ * author chose survives a pack switch unless the author changes it). Both picker homes call this.
8803
+ */
8804
+ declare function choicesForPack(flavorId: string, applied: DesignChoices | null | undefined): DesignChoices;
8787
8805
  /** A stored `settings.design` config → the resolved design. No row (or no config) → Crisp's defaults. */
8788
8806
  declare function readDesignConfig(config: ReadonlyArray<DesignConfigEntry> | null | undefined, hasRow?: boolean): StoredDesign;
8789
8807
  /** The colour mode a stored config compiles in — what the public page read hands the engine. */
@@ -8809,6 +8827,15 @@ declare function planDesignApply(existing: ReadonlyArray<StoredDesignTokenRow>,
8809
8827
  declare function missingDesignRows(existing: ReadonlyArray<{
8810
8828
  name: string;
8811
8829
  }>, choices: DesignChoices): DesignTokenRow[];
8830
+ /**
8831
+ * The builder-load REPAIR (DS4.1): rows the design owns and nobody touched — stamped by the current
8832
+ * pack, light value exactly the pack's — whose dark override differs from the pack's are rewritten.
8833
+ * iox-cms dropped `values.dark` on every write from F0 to DS4 (`values` was an implicit relation),
8834
+ * and a revert could leave the previous apply's dark behind; both leave a row whose light half is the
8835
+ * design's and whose dark half is not. No author edit can produce that shape — the Tokens panel edits
8836
+ * the light value only — so rewriting it heals existing orgs without the author doing anything.
8837
+ */
8838
+ declare function designTokenRepairs(existing: ReadonlyArray<StoredDesignTokenRow>, choices: DesignChoices): FlavorSeedPlan['update'];
8812
8839
  interface SwissMigration {
8813
8840
  /** The design the org now wears: Crisp, carrying the stored brand seed when it is a colour. */
8814
8841
  choices: DesignChoices;
@@ -8823,11 +8850,13 @@ interface SwissMigration {
8823
8850
  *
8824
8851
  * - The look: `planDesignApply` from Swiss to Crisp — Swiss-stamped rows untouched since Swiss seeded
8825
8852
  * them (incl. F2's `surface` / `line` / `accent`) become Crisp's; edited ones are kept.
8826
- * - The aliases: `radius-control` / `radius-surface` / `space-control` / `space-stack` are rewritten to
8827
- * their alias reference ONLY while still machine-owned and untouched, so a node saved since F1 that
8828
- * reads `var(--iox-radius-control)` keeps resolving — now to Crisp's button radius.
8853
+ * - The aliases (`SPEC_LEGACY_ALIASES`: `radius-control` / `radius-surface` / `space-stack`) are rewritten
8854
+ * to their alias reference ONLY while still machine-owned and untouched, so a node saved since F1 that
8855
+ * reads `var(--iox-radius-control)` keeps resolving — now to Crisp's button radius. `space-control` is
8856
+ * NOT an alias: it is a v2 semantic token (`button-pad` reads it), and aliasing it closed a `var()`
8857
+ * cycle; it migrates like any other Swiss row.
8829
8858
  */
8830
8859
  declare function planSwissMigration(existing: ReadonlyArray<StoredDesignTokenRow>, stored: StoredDesign): SwissMigration | null;
8831
8860
 
8832
- export { ACCENT_TOKEN_NAMES, BUTTON_VARIANT_TO_STYLE, COLOR_SCHEMES, COLOR_SCHEME_TRAIT, COLOR_SCHEME_TYPES, ColorScheme, 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, GOOGLE_FONTS_CSS_ORIGIN, GOOGLE_FONTS_FILE_ORIGIN, OwnedTraitLink, PALETTE_PRESETS, REDUCED_MOTION_OVERRIDES, REDUCED_TRANSPARENCY_OVERRIDES, REVERT_WINDOW_MS, SPEC_A11Y, SPEC_AXES, SPEC_BUTTON_VARIANT_TO_STYLE, SPEC_CONTRAST, SPEC_DEFAULT_ELEMENT_STYLE, SPEC_ELEMENT_STYLES, SPEC_FIELD_CLASS_BASELINE, SPEC_FLAVORS, SPEC_FLAVOR_PACKS, SPEC_FONTS, SPEC_FONT_ROLES, SPEC_LEGACY_ALIASES, SPEC_PALETTE_PRESETS, SPEC_SCHEMES, SPEC_SPRINGS, SPEC_TIERS, SPEC_TIER_IDS, SPEC_TOKEN_TYPES, SWISS_FLAVOR, USER_TOKEN_SOURCE, WCAG_AA_TEXT, apcaLc, applyElementStyle, applyHeadingLevelRule, buildFlavorTokens, buildFontFaceCss, catalogFontId, colorSchemeClass, colorSchemeOf, compileSchemeCss, compileTokenCss, compileTokenCssBlocks, cssString, defaultDesignChoices, defaultElementStyleFor, deriveAccentTokens, deriveBrandTokens, designBaseline, designConfigEntries, designModeOf, designTokenRows, elementStyleById, elementStyleClass, elementStylesFor, emptyImageLabelSrc, emptyImageSrc, flavorPackById, flavorSourceStamp, fontsForTokens, googleFontHref, hasElementStyleMarker, isDesignTokenGroup, isDesignTokenTier, isDesignTokenType, isRetiredFlavorId, legacyRuleTokenReferences, missingDesignRows, nodeDesignClasses, oklabToRgb, ownedTraitStatus, packDesignChoices, parseFontFamilies, parseHexColor, planDesignApply, planFlavorRevert, planFlavorSeed, planSwissMigration, readDesignConfig, relativeLuminance, resolveDesignChoices, resolveTokenReferences, resolveTokenValues, revertAvailable, rgbToOklab, sameDesignChoices, specTokenNames, springToLinear, styleKeyToKebab, switchElementStyle, syncNodeDesignClasses, toHexColor, tokenReferences, tokensToDarkVarMap, tokensToVarMap, unknownTokenReferences, userFontStack, validateFlavorPack, wcagContrastRatio };
8861
+ export { ACCENT_TOKEN_NAMES, BUTTON_VARIANT_TO_STYLE, COLOR_SCHEMES, COLOR_SCHEME_TRAIT, COLOR_SCHEME_TYPES, ColorScheme, 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, GOOGLE_FONTS_CSS_ORIGIN, GOOGLE_FONTS_FILE_ORIGIN, OwnedTraitLink, PALETTE_PRESETS, REDUCED_MOTION_OVERRIDES, REDUCED_TRANSPARENCY_OVERRIDES, REVERT_WINDOW_MS, SPEC_A11Y, SPEC_AXES, SPEC_BUTTON_VARIANT_TO_STYLE, SPEC_CONTRAST, SPEC_DEFAULT_ELEMENT_STYLE, SPEC_ELEMENT_STYLES, SPEC_FIELD_CLASS_BASELINE, SPEC_FLAVORS, SPEC_FLAVOR_PACKS, SPEC_FONTS, SPEC_FONT_ROLES, SPEC_LEGACY_ALIASES, SPEC_PALETTE_PRESETS, SPEC_SCHEMES, SPEC_SPRINGS, SPEC_TIERS, SPEC_TIER_IDS, SPEC_TOKEN_TYPES, SWISS_FLAVOR, USER_TOKEN_SOURCE, WCAG_AA_TEXT, apcaLc, applyElementStyle, applyHeadingLevelRule, buildFlavorTokens, buildFontFaceCss, catalogFontId, choicesForPack, colorSchemeClass, colorSchemeOf, compileSchemeCss, compileTokenCss, compileTokenCssBlocks, cssString, defaultDesignChoices, defaultElementStyleFor, deriveAccentTokens, deriveBrandTokens, designBaseline, designConfigEntries, designModeOf, designTokenRepairs, designTokenRows, elementStyleById, elementStyleClass, elementStylesFor, emptyImageLabelSrc, emptyImagePlaceholder, emptyImageSrc, flavorPackById, flavorSourceStamp, fontsForTokens, googleFontHref, hasElementStyleMarker, isDesignTokenGroup, isDesignTokenTier, isDesignTokenType, isRetiredFlavorId, legacyRuleTokenReferences, missingDesignRows, nodeDesignClasses, oklabToRgb, ownedTraitStatus, packDesignChoices, parseFontFamilies, parseHexColor, planDesignApply, planFlavorRevert, planFlavorSeed, planSwissMigration, readDesignConfig, relativeLuminance, resolveDesignChoices, resolveTokenReferences, resolveTokenValues, revertAvailable, rgbToOklab, sameDesignChoices, specTokenNames, springToLinear, styleKeyToKebab, switchElementStyle, syncNodeDesignClasses, toHexColor, tokenReferences, tokensToDarkVarMap, tokensToVarMap, unknownTokenReferences, userFontStack, validateFlavorPack, wcagContrastRatio };
8833
8862
  export type { AccentBackgrounds, AccentTokenName, ApplyElementStyleResult, DensityId, DesignChoices, DesignClassSource, DesignConfigEntry, DesignMode, DesignTokenGroup, DesignTokenLike, DesignTokenRow, DesignTokenTier, DesignTokenType, ElementStyle, ElementStyleTarget, ElementStyleTraitLike, FlavorPack, FlavorPackV2, FlavorRevertPlan, FlavorRevertSnapshot, FlavorSeedPlan, FlavorSpecToken, FlavorTokenDef, FontDescriptor, FontFormat, FontPair, FontRoleToken, FontStyle, FontsForTokensOptions, LinearSpring, MotionId, OkLab, OwnedTrait, OwnedTraitStatus, PalettePreset, ResolvedMode, Rgb, SchemeId, SeedBaseline, SeedExpectation, ShapeId, StoredDesign, StoredDesignTokenRow, SwissMigration, SwitchElementStyleResult, TokenCssBlocks, TokenCssOptions };
@@ -1410,7 +1410,7 @@ const SPEC_ELEMENT_STYLES = [
1410
1410
  "fontSize": "var(--iox-size-lead)",
1411
1411
  "fontWeight": "var(--iox-weight-body)",
1412
1412
  "lineHeight": "var(--iox-leading-body)",
1413
- "color": "var(--iox-text)"
1413
+ "color": "inherit"
1414
1414
  }
1415
1415
  },
1416
1416
  {
@@ -1425,7 +1425,7 @@ const SPEC_ELEMENT_STYLES = [
1425
1425
  "fontSize": "var(--iox-size-body)",
1426
1426
  "fontWeight": "var(--iox-weight-body)",
1427
1427
  "lineHeight": "var(--iox-leading-body)",
1428
- "color": "var(--iox-text)"
1428
+ "color": "inherit"
1429
1429
  }
1430
1430
  },
1431
1431
  {
@@ -9533,23 +9533,68 @@ function unknownTokenReferences(css, alsoAllowed = []) {
9533
9533
  return tokenReferences(css).filter(name => !spec.has(name) && !extra.has(name));
9534
9534
  }
9535
9535
  const LEGACY_SCOPE = /:not\(\s*\.iox-es\s*\)/;
9536
- const RULE = /([^{}]+)\{([^{}]*)\}/g;
9536
+ /** Comments out: block comments, and SCSS `//` line comments (never the `//` of a `url(https://…)`). */
9537
+ const stripComments = (css) => String(css ?? '')
9538
+ .replace(/\/\*[\s\S]*?\*\//g, '')
9539
+ .replace(/(^|[^:\\])\/\/[^\n]*/g, '$1');
9540
+ /** A nested SCSS selector against its parent's: `&` is the parent; no `&` means a descendant. */
9541
+ function resolveNested(parent, selector) {
9542
+ if (!parent)
9543
+ return selector;
9544
+ return selector.split(',').map(part => {
9545
+ const own = part.trim();
9546
+ return own.includes('&') ? own.replace(/&/g, parent) : `${parent} ${own}`;
9547
+ }).join(', ');
9548
+ }
9537
9549
  /**
9538
9550
  * Tokens read by a LEGACY rule — any rule whose selector is scoped `:not(.iox-es)` (the pre-DS2 look
9539
9551
  * of a node that wears no element style, design-system §3b-bis). Such a rule must read NO token at
9540
9552
  * all: a token it reads repaints a pre-DS2 page the moment the org's rows are seeded or a flavor is
9541
- * applied (D5). Returns `selector → names` for each offending rule; comments are ignored.
9553
+ * applied (D5). Returns `selector → names` for each offending rule; comments are ignored. SCSS
9554
+ * nesting is resolved (DS4.1): `.x:where(:not(.iox-es)) { &:hover { … } }` is the legacy rule
9555
+ * `.x:where(:not(.iox-es)):hover`, and an at-rule (`@media`) between them does not break the chain.
9542
9556
  */
9543
9557
  function legacyRuleTokenReferences(css) {
9544
- const text = String(css ?? '').replace(/\/\*[\s\S]*?\*\//g, '');
9558
+ const text = stripComments(css);
9545
9559
  const out = [];
9546
- for (const m of text.matchAll(RULE)) {
9547
- const selector = m[1].trim().replace(/\s+/g, ' ');
9548
- if (!LEGACY_SCOPE.test(selector))
9549
- continue;
9550
- const tokens = tokenReferences(m[2]);
9560
+ const stack = [];
9561
+ const report = (frame) => {
9562
+ if (!LEGACY_SCOPE.test(frame.selector))
9563
+ return;
9564
+ const tokens = tokenReferences(frame.body);
9551
9565
  if (tokens.length)
9552
- out.push({ selector, tokens });
9566
+ out.push({ selector: frame.selector, tokens });
9567
+ };
9568
+ let buffer = '';
9569
+ for (const ch of text) {
9570
+ if (ch === '{') {
9571
+ const head = buffer.trim().replace(/\s+/g, ' ');
9572
+ buffer = '';
9573
+ // Declarations before a nested block belong to the parent (`color: x; &:hover {`).
9574
+ const cut = head.lastIndexOf(';');
9575
+ const prelude = cut >= 0 ? head.slice(cut + 1).trim() : head;
9576
+ const parent = stack[stack.length - 1];
9577
+ if (parent && cut >= 0)
9578
+ parent.body += head.slice(0, cut + 1);
9579
+ const inherited = parent?.selector ?? '';
9580
+ const selector = prelude.startsWith('@') ? inherited : resolveNested(inherited, prelude);
9581
+ stack.push({ selector, body: '' });
9582
+ }
9583
+ else if (ch === '}') {
9584
+ const frame = stack.pop();
9585
+ if (frame) {
9586
+ frame.body += buffer;
9587
+ report(frame);
9588
+ }
9589
+ buffer = '';
9590
+ }
9591
+ else if (ch === ';' && stack.length) {
9592
+ stack[stack.length - 1].body += buffer + ch;
9593
+ buffer = '';
9594
+ }
9595
+ else {
9596
+ buffer += ch;
9597
+ }
9553
9598
  }
9554
9599
  return out;
9555
9600
  }
@@ -10086,17 +10131,25 @@ function emptyImageLabelSrc(label) {
10086
10131
  + `font-size="12" font-weight="500" fill="#1f1d1b">${text}</text></svg></svg>`);
10087
10132
  }
10088
10133
  /**
10089
- * The `src` a node's Image renders when its own source is empty: the canvas label (when `label` is
10090
- * given) or the live blank for a MARKED node; `null` — render exactly as before — for an unmarked
10091
- * node or one that has a source.
10134
+ * What a node's Image shows WHENEVER its source is empty: the canvas label (when `label` is given) or
10135
+ * the live blank for a MARKED node; `null` — render exactly as before — for an unmarked node. It does
10136
+ * not look at the source, so a renderer computes it once per marker/label and the component falls
10137
+ * back to it the moment its source is cleared (a binding emptied, a context slot not yet filled).
10092
10138
  */
10093
- function emptyImageSrc(node, src, label) {
10094
- if (src != null && src !== '')
10095
- return null;
10139
+ function emptyImagePlaceholder(node, label) {
10096
10140
  if (!hasElementStyleMarker(node))
10097
10141
  return null;
10098
10142
  return label ? emptyImageLabelSrc(label) : EMPTY_IMAGE_SRC;
10099
10143
  }
10144
+ /**
10145
+ * The `src` a node's Image renders for a given source: its placeholder while the source is empty,
10146
+ * `null` once it has one (the source itself renders) or when the node is unmarked.
10147
+ */
10148
+ function emptyImageSrc(node, src, label) {
10149
+ if (src != null && src !== '')
10150
+ return null;
10151
+ return emptyImagePlaceholder(node, label);
10152
+ }
10100
10153
 
10101
10154
  function parseHexColor(hex) {
10102
10155
  const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec((hex ?? '').trim());
@@ -10598,14 +10651,16 @@ function planFlavorRevert(current, snapshot) {
10598
10651
  snapNames.add(snap.name);
10599
10652
  const live = byName.get(snap.name);
10600
10653
  if (!live) {
10601
- plan.create.push({ name: snap.name, type: snap.type, value: snap.value, values: snap.values, source: snap.source, ...classification(snap) });
10654
+ plan.create.push({ name: snap.name, type: snap.type, value: snap.value, ...(snap.values?.dark ? { values: snap.values } : {}), source: snap.source, ...classification(snap) });
10602
10655
  continue;
10603
10656
  }
10604
10657
  const differs = live.value !== snap.value
10605
10658
  || (live.values?.dark ?? null) !== (snap.values?.dark ?? null)
10606
10659
  || (live.source ?? null) !== (snap.source ?? null);
10660
+ // `values` is ALWAYS sent: an absent key is dropped by JSON and a `$set` then keeps the
10661
+ // apply's dark override — a snapshot row with no dark must clear it, so it sends `{}`.
10607
10662
  if (differs && live._id) {
10608
- plan.update.push({ id: live._id, data: { value: snap.value, values: snap.values, source: snap.source } });
10663
+ plan.update.push({ id: live._id, data: { value: snap.value, values: snap.values ?? {}, source: snap.source } });
10609
10664
  }
10610
10665
  }
10611
10666
  for (const row of current ?? []) {
@@ -10624,7 +10679,9 @@ const sameValue = (row, e) => row.value === e.value && (row.values?.dark ?? '')
10624
10679
  * write is never touched: no `flavor:` stamp, unless it is a `user` row (a font the author CHOSE in
10625
10680
  * the design) whose value is exactly what the previous design wrote — that row is the design's own
10626
10681
  * choice, so a new choice may replace it. `group`/`tier` ride every write; a desired row's own
10627
- * `source` (a chosen font's `user`) wins over the pack stamp.
10682
+ * `source` (a chosen font's `user`) wins over the pack stamp. A machine-owned row that already holds
10683
+ * the desired value but another pack's stamp is RE-STAMPED (§10 C2 provenance: the stamp names the
10684
+ * design that owns the row, so the next switch judges it against the right baseline).
10628
10685
  */
10629
10686
  function planFlavorSeed(desired, existing, stamp, baseline) {
10630
10687
  const byName = new Map((existing ?? []).map(r => [r.name, r]));
@@ -10654,8 +10711,6 @@ function planFlavorSeed(desired, existing, stamp, baseline) {
10654
10711
  const sameAsDesired = row.value === def.value && (row.values?.dark ?? '') === (def.values?.dark ?? '');
10655
10712
  if (sameAsDesired && (row.source ?? '') === source)
10656
10713
  continue; // already exactly this
10657
- if (sameAsDesired && isSeeded(row) && source !== USER_TOKEN_SOURCE)
10658
- continue; // same value, another pack's stamp — nothing to write
10659
10714
  const untouched = !!old && sameValue(row, old);
10660
10715
  if ((untouched || sameAsDesired) && row._id) {
10661
10716
  plan.update.push({ id: row._id, data: { value: def.value, values: def.values ?? {}, source, ...classification(def) } });
@@ -10817,6 +10872,17 @@ function resolveDesignChoices(partial) {
10817
10872
  function packDesignChoices(flavorId) {
10818
10873
  return resolveDesignChoices({ flavorId });
10819
10874
  }
10875
+ /**
10876
+ * What picking a pack card means, given the design the studio wears: the applied pack → the applied
10877
+ * choices; another pack → that pack's defaults, CARRYING the author's chosen fonts (§5b — a font the
10878
+ * author chose survives a pack switch unless the author changes it). Both picker homes call this.
10879
+ */
10880
+ function choicesForPack(flavorId, applied) {
10881
+ const pack = flavorPackById(flavorId) ?? defaultPack();
10882
+ if (applied && resolveDesignChoices(applied).flavorId === pack.id)
10883
+ return resolveDesignChoices(applied);
10884
+ return resolveDesignChoices({ flavorId: pack.id, fonts: applied?.fonts });
10885
+ }
10820
10886
  /** A stored `settings.design` config → the resolved design. No row (or no config) → Crisp's defaults. */
10821
10887
  function readDesignConfig(config, hasRow = !!config) {
10822
10888
  const map = configMap(config);
@@ -10926,6 +10992,31 @@ function missingDesignRows(existing, choices) {
10926
10992
  const have = new Set((existing ?? []).map(r => r.name));
10927
10993
  return designTokenRows(choices).filter(r => !have.has(r.name));
10928
10994
  }
10995
+ /**
10996
+ * The builder-load REPAIR (DS4.1): rows the design owns and nobody touched — stamped by the current
10997
+ * pack, light value exactly the pack's — whose dark override differs from the pack's are rewritten.
10998
+ * iox-cms dropped `values.dark` on every write from F0 to DS4 (`values` was an implicit relation),
10999
+ * and a revert could leave the previous apply's dark behind; both leave a row whose light half is the
11000
+ * design's and whose dark half is not. No author edit can produce that shape — the Tokens panel edits
11001
+ * the light value only — so rewriting it heals existing orgs without the author doing anything.
11002
+ */
11003
+ function designTokenRepairs(existing, choices) {
11004
+ const pack = flavorPackById(choices.flavorId);
11005
+ if (!pack)
11006
+ return [];
11007
+ const stamp = flavorSourceStamp(pack.id);
11008
+ const desired = new Map(designTokenRows(choices).map(r => [r.name, r]));
11009
+ const out = [];
11010
+ for (const row of existing ?? []) {
11011
+ const want = desired.get(row.name);
11012
+ if (!want || !row._id || row.source !== stamp || want.source !== stamp || row.value !== want.value)
11013
+ continue;
11014
+ if ((row.values?.dark ?? '') === (want.values?.dark ?? ''))
11015
+ continue;
11016
+ out.push({ id: row._id, data: { value: want.value, values: want.values ?? {}, source: stamp, ...(want.group ? { group: want.group } : {}), ...(want.tier ? { tier: want.tier } : {}) } });
11017
+ }
11018
+ return out;
11019
+ }
10929
11020
  const LEGACY_ALIASES = SPEC_LEGACY_ALIASES;
10930
11021
  /**
10931
11022
  * Swiss → Crisp (spec `legacyAliases`; D1). Needed when the design row says a retired flavor, or when
@@ -10935,9 +11026,11 @@ const LEGACY_ALIASES = SPEC_LEGACY_ALIASES;
10935
11026
  *
10936
11027
  * - The look: `planDesignApply` from Swiss to Crisp — Swiss-stamped rows untouched since Swiss seeded
10937
11028
  * them (incl. F2's `surface` / `line` / `accent`) become Crisp's; edited ones are kept.
10938
- * - The aliases: `radius-control` / `radius-surface` / `space-control` / `space-stack` are rewritten to
10939
- * their alias reference ONLY while still machine-owned and untouched, so a node saved since F1 that
10940
- * reads `var(--iox-radius-control)` keeps resolving — now to Crisp's button radius.
11029
+ * - The aliases (`SPEC_LEGACY_ALIASES`: `radius-control` / `radius-surface` / `space-stack`) are rewritten
11030
+ * to their alias reference ONLY while still machine-owned and untouched, so a node saved since F1 that
11031
+ * reads `var(--iox-radius-control)` keeps resolving — now to Crisp's button radius. `space-control` is
11032
+ * NOT an alias: it is a v2 semantic token (`button-pad` reads it), and aliasing it closed a `var()`
11033
+ * cycle; it migrates like any other Swiss row.
10941
11034
  */
10942
11035
  function planSwissMigration(existing, stored) {
10943
11036
  const swissAliasRows = (existing ?? []).filter(r => r.name in LEGACY_ALIASES && SEED_STAMP.exec(r.source ?? '')?.[1] === SWISS_FLAVOR.id);
@@ -10980,5 +11073,5 @@ function planSwissMigration(existing, stored) {
10980
11073
  * Generated bundle index. Do not edit.
10981
11074
  */
10982
11075
 
10983
- export { ACCENT_TOKEN_NAMES, BUTTON_VARIANT_TO_STYLE, COLOR_SCHEMES, COLOR_SCHEME_TRAIT, COLOR_SCHEME_TYPES, ColorScheme, 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, GOOGLE_FONTS_CSS_ORIGIN, GOOGLE_FONTS_FILE_ORIGIN, OwnedTraitLink, PALETTE_PRESETS, REDUCED_MOTION_OVERRIDES, REDUCED_TRANSPARENCY_OVERRIDES, REVERT_WINDOW_MS, SPEC_A11Y, SPEC_AXES, SPEC_BUTTON_VARIANT_TO_STYLE, SPEC_CONTRAST, SPEC_DEFAULT_ELEMENT_STYLE, SPEC_ELEMENT_STYLES, SPEC_FIELD_CLASS_BASELINE, SPEC_FLAVORS, SPEC_FLAVOR_PACKS, SPEC_FONTS, SPEC_FONT_ROLES, SPEC_LEGACY_ALIASES, SPEC_PALETTE_PRESETS, SPEC_SCHEMES, SPEC_SPRINGS, SPEC_TIERS, SPEC_TIER_IDS, SPEC_TOKEN_TYPES, SWISS_FLAVOR, USER_TOKEN_SOURCE, WCAG_AA_TEXT, apcaLc, applyElementStyle, applyHeadingLevelRule, buildFlavorTokens, buildFontFaceCss, catalogFontId, colorSchemeClass, colorSchemeOf, compileSchemeCss, compileTokenCss, compileTokenCssBlocks, cssString, defaultDesignChoices, defaultElementStyleFor, deriveAccentTokens, deriveBrandTokens, designBaseline, designConfigEntries, designModeOf, designTokenRows, elementStyleById, elementStyleClass, elementStylesFor, emptyImageLabelSrc, emptyImageSrc, flavorPackById, flavorSourceStamp, fontsForTokens, googleFontHref, hasElementStyleMarker, isDesignTokenGroup, isDesignTokenTier, isDesignTokenType, isRetiredFlavorId, legacyRuleTokenReferences, missingDesignRows, nodeDesignClasses, oklabToRgb, ownedTraitStatus, packDesignChoices, parseFontFamilies, parseHexColor, planDesignApply, planFlavorRevert, planFlavorSeed, planSwissMigration, readDesignConfig, relativeLuminance, resolveDesignChoices, resolveTokenReferences, resolveTokenValues, revertAvailable, rgbToOklab, sameDesignChoices, specTokenNames, springToLinear, styleKeyToKebab, switchElementStyle, syncNodeDesignClasses, toHexColor, tokenReferences, tokensToDarkVarMap, tokensToVarMap, unknownTokenReferences, userFontStack, validateFlavorPack, wcagContrastRatio };
11076
+ export { ACCENT_TOKEN_NAMES, BUTTON_VARIANT_TO_STYLE, COLOR_SCHEMES, COLOR_SCHEME_TRAIT, COLOR_SCHEME_TYPES, ColorScheme, 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, GOOGLE_FONTS_CSS_ORIGIN, GOOGLE_FONTS_FILE_ORIGIN, OwnedTraitLink, PALETTE_PRESETS, REDUCED_MOTION_OVERRIDES, REDUCED_TRANSPARENCY_OVERRIDES, REVERT_WINDOW_MS, SPEC_A11Y, SPEC_AXES, SPEC_BUTTON_VARIANT_TO_STYLE, SPEC_CONTRAST, SPEC_DEFAULT_ELEMENT_STYLE, SPEC_ELEMENT_STYLES, SPEC_FIELD_CLASS_BASELINE, SPEC_FLAVORS, SPEC_FLAVOR_PACKS, SPEC_FONTS, SPEC_FONT_ROLES, SPEC_LEGACY_ALIASES, SPEC_PALETTE_PRESETS, SPEC_SCHEMES, SPEC_SPRINGS, SPEC_TIERS, SPEC_TIER_IDS, SPEC_TOKEN_TYPES, SWISS_FLAVOR, USER_TOKEN_SOURCE, WCAG_AA_TEXT, apcaLc, applyElementStyle, applyHeadingLevelRule, buildFlavorTokens, buildFontFaceCss, catalogFontId, choicesForPack, colorSchemeClass, colorSchemeOf, compileSchemeCss, compileTokenCss, compileTokenCssBlocks, cssString, defaultDesignChoices, defaultElementStyleFor, deriveAccentTokens, deriveBrandTokens, designBaseline, designConfigEntries, designModeOf, designTokenRepairs, designTokenRows, elementStyleById, elementStyleClass, elementStylesFor, emptyImageLabelSrc, emptyImagePlaceholder, emptyImageSrc, flavorPackById, flavorSourceStamp, fontsForTokens, googleFontHref, hasElementStyleMarker, isDesignTokenGroup, isDesignTokenTier, isDesignTokenType, isRetiredFlavorId, legacyRuleTokenReferences, missingDesignRows, nodeDesignClasses, oklabToRgb, ownedTraitStatus, packDesignChoices, parseFontFamilies, parseHexColor, planDesignApply, planFlavorRevert, planFlavorSeed, planSwissMigration, readDesignConfig, relativeLuminance, resolveDesignChoices, resolveTokenReferences, resolveTokenValues, revertAvailable, rgbToOklab, sameDesignChoices, specTokenNames, springToLinear, styleKeyToKebab, switchElementStyle, syncNodeDesignClasses, toHexColor, tokenReferences, tokensToDarkVarMap, tokensToVarMap, unknownTokenReferences, userFontStack, validateFlavorPack, wcagContrastRatio };
10984
11077
  //# sourceMappingURL=vectoriox-iox-ui-design-core.mjs.map