@vectoriox/iox-ui 5.14.0 → 5.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/design-core/index.d.ts +177 -20
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +377 -54
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/package.json +1 -1
- package/src/design-core/design-apply.ts +116 -0
- package/src/design-core/design-settings.ts +194 -0
- package/src/design-core/flavor-seed.ts +39 -13
- package/src/design-core/flavor-spec.generated.ts +12 -4
- package/src/design-core/flavor.ts +6 -5
- package/src/design-core/fonts.ts +45 -8
- package/src/design-core/public-api.ts +2 -0
- package/src/design-core/token-css.ts +32 -2
- package/src/design-core/token-names.ts +21 -0
|
@@ -1071,7 +1071,7 @@ const SPEC_SPRINGS = {
|
|
|
1071
1071
|
}
|
|
1072
1072
|
};
|
|
1073
1073
|
const SPEC_FONTS = {
|
|
1074
|
-
"Inter": "Inter:wght@400;500;600;700",
|
|
1074
|
+
"Inter": "Inter:wght@300;400;500;600;700",
|
|
1075
1075
|
"Inter Tight": "Inter+Tight:wght@500;600;700",
|
|
1076
1076
|
"Manrope": "Manrope:wght@400;500;600;700",
|
|
1077
1077
|
"IBM Plex Sans": "IBM+Plex+Sans:wght@400;500;600;700",
|
|
@@ -1086,12 +1086,21 @@ const SPEC_FONTS = {
|
|
|
1086
1086
|
"Figtree": "Figtree:wght@400;500;600;700;800",
|
|
1087
1087
|
"Google Sans Flex": "Google+Sans+Flex:wght@400;500;600;700",
|
|
1088
1088
|
"Bricolage Grotesque": "Bricolage+Grotesque:opsz,wght@12..96,500;12..96,700",
|
|
1089
|
-
"Nunito": "Nunito:wght@400;600;700;800",
|
|
1089
|
+
"Nunito": "Nunito:wght@300;400;600;700;800",
|
|
1090
1090
|
"DM Sans": "DM+Sans:wght@400;500;700",
|
|
1091
1091
|
"Archivo": "Archivo:wght@400;500;700",
|
|
1092
1092
|
"Archivo Black": "Archivo+Black",
|
|
1093
1093
|
"Syne": "Syne:wght@600;700;800",
|
|
1094
|
-
"Space Mono": "Space+Mono:wght@400;700"
|
|
1094
|
+
"Space Mono": "Space+Mono:wght@400;700",
|
|
1095
|
+
"Poppins": "Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400",
|
|
1096
|
+
"Roboto": "Roboto:ital,wght@0,300;0,400;0,500;0,700;1,400",
|
|
1097
|
+
"Lato": "Lato:ital,wght@0,300;0,400;0,700;1,400",
|
|
1098
|
+
"Open Sans": "Open+Sans:ital,wght@0,300;0,400;0,600;0,700;1,400",
|
|
1099
|
+
"Montserrat": "Montserrat:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400",
|
|
1100
|
+
"Raleway": "Raleway:wght@300;400;500;600;700",
|
|
1101
|
+
"Playfair Display": "Playfair+Display:ital,wght@0,400;0,500;0,700;1,400",
|
|
1102
|
+
"Oswald": "Oswald:wght@300;400;500;600;700",
|
|
1103
|
+
"Ubuntu": "Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,400"
|
|
1095
1104
|
};
|
|
1096
1105
|
const SPEC_SCHEMES = {
|
|
1097
1106
|
"default": {
|
|
@@ -1748,7 +1757,6 @@ const SPEC_FIELD_CLASS_BASELINE = {
|
|
|
1748
1757
|
const SPEC_LEGACY_ALIASES = {
|
|
1749
1758
|
"radius-control": "var(--iox-button-radius)",
|
|
1750
1759
|
"radius-surface": "var(--iox-card-radius)",
|
|
1751
|
-
"space-control": "var(--iox-button-pad)",
|
|
1752
1760
|
"space-stack": "var(--iox-space-gap)"
|
|
1753
1761
|
};
|
|
1754
1762
|
const SPEC_FLAVOR_PACKS = [
|
|
@@ -9498,13 +9506,61 @@ function styleKeyToKebab(key) {
|
|
|
9498
9506
|
return key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
|
|
9499
9507
|
}
|
|
9500
9508
|
|
|
9509
|
+
/**
|
|
9510
|
+
* Which `--iox-<name>` a piece of COMPONENT CSS may reference (design-system §3b-bis: "component CSS
|
|
9511
|
+
* may reference ONLY tokens defined in this spec"). DS2 shipped seven names nobody defines
|
|
9512
|
+
* (`card-media-gap`, `gallery-overlay`, `dialog-close-bg`…): a token the contract does not define can
|
|
9513
|
+
* never be set by a flavor, so it is a literal wearing a costume. One guard, run over BOTH
|
|
9514
|
+
* renderers' component CSS (iox-ui's spec; the engine's `verify:token-names`).
|
|
9515
|
+
*/
|
|
9516
|
+
/** The 161 names of the contract's two tiers, plus its legacy aliases. */
|
|
9517
|
+
function specTokenNames() {
|
|
9518
|
+
return new Set([
|
|
9519
|
+
...SPEC_TIERS.semantic.map(t => t.name),
|
|
9520
|
+
...SPEC_TIERS.component.map(t => t.name),
|
|
9521
|
+
...Object.keys(SPEC_LEGACY_ALIASES),
|
|
9522
|
+
]);
|
|
9523
|
+
}
|
|
9524
|
+
const REFERENCE$1 = /var\(\s*--iox-([a-z0-9-]+)/gi;
|
|
9525
|
+
/** Every distinct token a stylesheet READS (`var(--iox-x…)`), in first-seen order. */
|
|
9526
|
+
function tokenReferences(css) {
|
|
9527
|
+
return [...new Set(Array.from(css.matchAll(REFERENCE$1), m => m[1]))];
|
|
9528
|
+
}
|
|
9529
|
+
/** The referenced names that are neither spec tokens nor in `alsoAllowed`. */
|
|
9530
|
+
function unknownTokenReferences(css, alsoAllowed = []) {
|
|
9531
|
+
const spec = specTokenNames();
|
|
9532
|
+
const extra = new Set(alsoAllowed);
|
|
9533
|
+
return tokenReferences(css).filter(name => !spec.has(name) && !extra.has(name));
|
|
9534
|
+
}
|
|
9535
|
+
const LEGACY_SCOPE = /:not\(\s*\.iox-es\s*\)/;
|
|
9536
|
+
const RULE = /([^{}]+)\{([^{}]*)\}/g;
|
|
9537
|
+
/**
|
|
9538
|
+
* Tokens read by a LEGACY rule — any rule whose selector is scoped `:not(.iox-es)` (the pre-DS2 look
|
|
9539
|
+
* of a node that wears no element style, design-system §3b-bis). Such a rule must read NO token at
|
|
9540
|
+
* 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.
|
|
9542
|
+
*/
|
|
9543
|
+
function legacyRuleTokenReferences(css) {
|
|
9544
|
+
const text = String(css ?? '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
9545
|
+
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]);
|
|
9551
|
+
if (tokens.length)
|
|
9552
|
+
out.push({ selector, tokens });
|
|
9553
|
+
}
|
|
9554
|
+
return out;
|
|
9555
|
+
}
|
|
9556
|
+
|
|
9501
9557
|
/** What `prefers-reduced-motion: reduce` collapses — the contract's complete list (`a11y.reducedMotion`). */
|
|
9502
9558
|
const REDUCED_MOTION_OVERRIDES = SPEC_A11Y.reducedMotion;
|
|
9503
9559
|
/** What `prefers-reduced-transparency: reduce` makes solid — the contract's `a11y.reducedTransparency`. */
|
|
9504
9560
|
const REDUCED_TRANSPARENCY_OVERRIDES = SPEC_A11Y.reducedTransparency;
|
|
9505
9561
|
const ROOT = ':root';
|
|
9506
9562
|
const DEFAULT_SCHEME = 'default';
|
|
9507
|
-
const REFERENCE
|
|
9563
|
+
const REFERENCE = 'var(';
|
|
9508
9564
|
const MEDIA_DARK = '(prefers-color-scheme: dark)';
|
|
9509
9565
|
const ACCESSIBILITY_MEDIA = [
|
|
9510
9566
|
{ query: '(prefers-reduced-motion: reduce)', overrides: REDUCED_MOTION_OVERRIDES },
|
|
@@ -9549,6 +9605,16 @@ const PLACEHOLDER = /\{\{\s*([\w-]+)\s*\}\}/g;
|
|
|
9549
9605
|
const LIGHT = 'light';
|
|
9550
9606
|
const DARK = 'dark';
|
|
9551
9607
|
const modeValue = (t, mode) => (mode === DARK ? darkOf(t) ?? t.value : t.value);
|
|
9608
|
+
/** The rows as ONE mode: each row's value in that mode, no dark override left. `auto` (or none) →
|
|
9609
|
+
* the rows unchanged. */
|
|
9610
|
+
function rowsForMode(rows, mode) {
|
|
9611
|
+
if (mode !== LIGHT && mode !== DARK)
|
|
9612
|
+
return [...rows];
|
|
9613
|
+
return rows.map(t => {
|
|
9614
|
+
const { values: _dropped, ...rest } = t;
|
|
9615
|
+
return { ...rest, value: modeValue(t, mode) };
|
|
9616
|
+
});
|
|
9617
|
+
}
|
|
9552
9618
|
/** Every token's ROOT value in one mode, with `var(--iox-…)` references resolved against the same
|
|
9553
9619
|
* set (recursively; a reference cycle or an unknown token is left as written). */
|
|
9554
9620
|
function resolveTokenValues(tokens, mode = LIGHT) {
|
|
@@ -9614,7 +9680,7 @@ function compileSchemeCss(tokens, scope) {
|
|
|
9614
9680
|
const resolved = resolveTokenValues(rows, LIGHT);
|
|
9615
9681
|
return nonDefaultSchemes().map(([id, set]) => rule(schemeSelector(id, scope), [
|
|
9616
9682
|
...schemeEntries(set, resolved),
|
|
9617
|
-
...rows.filter(t => !(t.name in set) && t.value.includes(REFERENCE
|
|
9683
|
+
...rows.filter(t => !(t.name in set) && t.value.includes(REFERENCE))
|
|
9618
9684
|
.map((t) => [t.name, t.value]),
|
|
9619
9685
|
])).filter(Boolean).join('\n');
|
|
9620
9686
|
}
|
|
@@ -9625,7 +9691,7 @@ function darkSchemeRules(rows, scope) {
|
|
|
9625
9691
|
const dark = resolveTokenValues(rows, DARK);
|
|
9626
9692
|
const redeclared = rows.filter(t => {
|
|
9627
9693
|
const d = darkOf(t);
|
|
9628
|
-
return d !== null && (t.value.includes(REFERENCE
|
|
9694
|
+
return d !== null && (t.value.includes(REFERENCE) || d.includes(REFERENCE));
|
|
9629
9695
|
});
|
|
9630
9696
|
return nonDefaultSchemes().map(([id, set]) => {
|
|
9631
9697
|
const swapped = Object.fromEntries(Object.entries(set).filter(([, v]) => hasPlaceholder(v)));
|
|
@@ -9641,10 +9707,14 @@ function presentOverrides(rows, overrides, onlyWithDark) {
|
|
|
9641
9707
|
}
|
|
9642
9708
|
/** The token CSS as the two halves a renderer places around its node rules. */
|
|
9643
9709
|
function compileTokenCssBlocks(tokens, opts = {}) {
|
|
9644
|
-
const rows = usableTokens(tokens);
|
|
9710
|
+
const rows = rowsForMode(usableTokens(tokens), opts.mode);
|
|
9645
9711
|
const root = opts.scope ?? ROOT;
|
|
9712
|
+
const present = new Set(rows.map(t => t.name));
|
|
9713
|
+
const unset = opts.isolate
|
|
9714
|
+
? [...specTokenNames()].filter(name => !present.has(name)).map((name) => [name, 'initial'])
|
|
9715
|
+
: [];
|
|
9646
9716
|
const base = [
|
|
9647
|
-
rule(root, rows.map((t) => [t.name, t.value])),
|
|
9717
|
+
rule(root, [...unset, ...rows.map((t) => [t.name, t.value])]),
|
|
9648
9718
|
compileSchemeCss(rows, opts.scope),
|
|
9649
9719
|
...ACCESSIBILITY_MEDIA.map(({ query, overrides }) => media(query, [rule(root, presentOverrides(rows, overrides, false), ' ')])),
|
|
9650
9720
|
].filter(Boolean).join('\n');
|
|
@@ -9676,9 +9746,9 @@ function validateFlavorPack(pack) {
|
|
|
9676
9746
|
return errors;
|
|
9677
9747
|
}
|
|
9678
9748
|
/**
|
|
9679
|
-
* Swiss — the F1 pack: four tokens (control/surface radius, control/stack spacing) and the
|
|
9680
|
-
* opinion F2's brand step
|
|
9681
|
-
*
|
|
9749
|
+
* Swiss — the retired F1 pack: four tokens (control/surface radius, control/stack spacing) and the
|
|
9750
|
+
* hue opinion F2's brand step read. Three of its rows become the spec's `legacyAliases` on migration;
|
|
9751
|
+
* `space-control` is a v2 token in every pack and takes Crisp's value.
|
|
9682
9752
|
*/
|
|
9683
9753
|
const SWISS_FLAVOR = {
|
|
9684
9754
|
id: 'swiss',
|
|
@@ -10367,19 +10437,39 @@ function parseFontFamilies(stack) {
|
|
|
10367
10437
|
.map(f => f.trim().replace(/^(['"])(.*)\1$/, '$2').trim())
|
|
10368
10438
|
.filter(Boolean);
|
|
10369
10439
|
}
|
|
10440
|
+
/** Where Google serves the stylesheets, and the origin the font FILES come from (preconnect). */
|
|
10441
|
+
const GOOGLE_FONTS_CSS_ORIGIN = 'https://fonts.googleapis.com';
|
|
10442
|
+
const GOOGLE_FONTS_FILE_ORIGIN = 'https://fonts.gstatic.com';
|
|
10443
|
+
/** The ONE stylesheet URL for a catalog id — both renderers link exactly this (one per family, T7). */
|
|
10444
|
+
function googleFontHref(id) {
|
|
10445
|
+
return `${GOOGLE_FONTS_CSS_ORIGIN}/css2?family=${id}&display=swap`;
|
|
10446
|
+
}
|
|
10447
|
+
const CATALOG_BY_LOWER = new Map(Object.entries(FONT_CATALOG).map(([family, id]) => [family.toLowerCase(), id]));
|
|
10448
|
+
/** The catalog id of a family name (case-insensitive), or null — a system or uploaded face has none. */
|
|
10449
|
+
function catalogFontId(family) {
|
|
10450
|
+
return CATALOG_BY_LOWER.get(String(family ?? '').trim().toLowerCase()) ?? null;
|
|
10451
|
+
}
|
|
10452
|
+
const MONO_ROLE = 'font-mono';
|
|
10453
|
+
const ALWAYS_LOADED_ROLES = FONT_FAMILY_TOKENS.filter(role => role !== MONO_ROLE);
|
|
10454
|
+
const MONO_REFERENCE = /var\(\s*--iox-font-mono\b/;
|
|
10370
10455
|
/**
|
|
10371
|
-
* The Google css2 ids to load for a token set: every catalog family named by
|
|
10372
|
-
*
|
|
10456
|
+
* The Google css2 ids to load for a token set: every catalog family named by the display / body /
|
|
10457
|
+
* label roles, plus mono when referenced (above). One id per family (T7 — one `<link>` per family, so
|
|
10458
|
+
* one bad id cannot sink the others), in role order.
|
|
10373
10459
|
*/
|
|
10374
|
-
function fontsForTokens(tokens) {
|
|
10460
|
+
function fontsForTokens(tokens, opts = {}) {
|
|
10461
|
+
const rows = (tokens ?? []).filter(t => !!t && typeof t.name === 'string');
|
|
10462
|
+
const monoRead = MONO_REFERENCE.test(opts.usedCss ?? '')
|
|
10463
|
+
|| rows.some(t => t.name !== MONO_ROLE && MONO_REFERENCE.test(`${t.value ?? ''} ${t.values?.dark ?? ''}`));
|
|
10464
|
+
const roles = monoRead ? FONT_FAMILY_TOKENS : ALWAYS_LOADED_ROLES;
|
|
10375
10465
|
const seen = new Set();
|
|
10376
10466
|
const ids = [];
|
|
10377
|
-
for (const role of
|
|
10378
|
-
const row =
|
|
10467
|
+
for (const role of roles) {
|
|
10468
|
+
const row = rows.find(t => t.name === role);
|
|
10379
10469
|
for (const family of parseFontFamilies(row?.value ?? '')) {
|
|
10380
|
-
const id =
|
|
10381
|
-
if (id && !seen.has(
|
|
10382
|
-
seen.add(
|
|
10470
|
+
const id = catalogFontId(family);
|
|
10471
|
+
if (id && !seen.has(id)) {
|
|
10472
|
+
seen.add(id);
|
|
10383
10473
|
ids.push(id);
|
|
10384
10474
|
}
|
|
10385
10475
|
}
|
|
@@ -10524,42 +10614,51 @@ function planFlavorRevert(current, snapshot) {
|
|
|
10524
10614
|
}
|
|
10525
10615
|
return plan;
|
|
10526
10616
|
}
|
|
10617
|
+
const expectationFor = (baseline, row) => !baseline ? undefined : typeof baseline === 'function' ? baseline(row) : baseline.get(row.name);
|
|
10618
|
+
const sameValue = (row, e) => row.value === e.value && (row.values?.dark ?? '') === (e.dark ?? '');
|
|
10527
10619
|
/**
|
|
10528
10620
|
* The seeding MERGE: desired tokens vs existing rows, deciding create / update / keep. The §10 C2
|
|
10529
10621
|
* rule — "an upgrade only rewrites untouched rows" — is a comparison against what the PREVIOUS
|
|
10530
|
-
* seed wrote (`
|
|
10531
|
-
* rewritten; any drift means the author took it over, and their hand wins. A row
|
|
10532
|
-
* `flavor:` stamp
|
|
10622
|
+
* seed wrote (`baseline`): a seeded row whose value still equals it is the machine's and may be
|
|
10623
|
+
* rewritten; any drift means the author took it over, and their hand wins. A row the design did not
|
|
10624
|
+
* write is never touched: no `flavor:` stamp, unless it is a `user` row (a font the author CHOSE in
|
|
10625
|
+
* the design) whose value is exactly what the previous design wrote — that row is the design's own
|
|
10626
|
+
* 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.
|
|
10533
10628
|
*/
|
|
10534
|
-
function planFlavorSeed(desired, existing, stamp,
|
|
10629
|
+
function planFlavorSeed(desired, existing, stamp, baseline) {
|
|
10535
10630
|
const byName = new Map((existing ?? []).map(r => [r.name, r]));
|
|
10536
10631
|
const plan = { create: [], update: [], kept: [] };
|
|
10537
10632
|
for (const def of desired ?? []) {
|
|
10538
10633
|
if (!def?.name)
|
|
10539
10634
|
continue;
|
|
10540
10635
|
const row = byName.get(def.name);
|
|
10636
|
+
const source = def.source ?? stamp;
|
|
10541
10637
|
if (!row) {
|
|
10542
10638
|
plan.create.push({
|
|
10543
10639
|
name: def.name,
|
|
10544
10640
|
type: def.type ?? DEFAULT_TOKEN_TYPE,
|
|
10545
10641
|
value: def.value,
|
|
10546
10642
|
...(def.values ? { values: def.values } : {}),
|
|
10547
|
-
source
|
|
10643
|
+
source,
|
|
10548
10644
|
...classification(def),
|
|
10549
10645
|
});
|
|
10550
10646
|
continue;
|
|
10551
10647
|
}
|
|
10552
|
-
|
|
10648
|
+
const old = expectationFor(baseline, row);
|
|
10649
|
+
const designOwned = isSeeded(row) || (row.source === USER_TOKEN_SOURCE && !!old && sameValue(row, old));
|
|
10650
|
+
if (!designOwned) {
|
|
10553
10651
|
plan.kept.push(def.name);
|
|
10554
10652
|
continue;
|
|
10555
10653
|
}
|
|
10556
10654
|
const sameAsDesired = row.value === def.value && (row.values?.dark ?? '') === (def.values?.dark ?? '');
|
|
10557
|
-
if (sameAsDesired)
|
|
10558
|
-
continue; // already exactly this
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10655
|
+
if (sameAsDesired && (row.source ?? '') === source)
|
|
10656
|
+
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
|
+
const untouched = !!old && sameValue(row, old);
|
|
10660
|
+
if ((untouched || sameAsDesired) && row._id) {
|
|
10661
|
+
plan.update.push({ id: row._id, data: { value: def.value, values: def.values ?? {}, source, ...classification(def) } });
|
|
10563
10662
|
}
|
|
10564
10663
|
else {
|
|
10565
10664
|
plan.kept.push(def.name); // edited since seeding (or unverifiable) — the author's hand wins
|
|
@@ -10616,30 +10715,254 @@ function springToLinear(stiffness, damping) {
|
|
|
10616
10715
|
}
|
|
10617
10716
|
|
|
10618
10717
|
/**
|
|
10619
|
-
*
|
|
10620
|
-
*
|
|
10621
|
-
*
|
|
10622
|
-
*
|
|
10623
|
-
*
|
|
10718
|
+
* The `settings.design` row (design-system plan §3f), read and written by ONE pair of functions so
|
|
10719
|
+
* the builder, the server's public page read (the engine's mode) and the site build agree on what a
|
|
10720
|
+
* stored row MEANS. The row is the settings collection's generic `config: [{ key, value }]`; values
|
|
10721
|
+
* are strings there (Boolean "true"/"false", Object as JSON), but a reader also accepts the typed
|
|
10722
|
+
* value a client control may have sent.
|
|
10624
10723
|
*/
|
|
10625
|
-
/** The
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
]
|
|
10724
|
+
/** The look a studio wears until it picks one (DS2's interim seed, and DS6's first-run selection). */
|
|
10725
|
+
const DEFAULT_FLAVOR_ID = 'crisp';
|
|
10726
|
+
var DesignConfigKey;
|
|
10727
|
+
(function (DesignConfigKey) {
|
|
10728
|
+
DesignConfigKey["FlavorId"] = "flavorId";
|
|
10729
|
+
DesignConfigKey["FlavorVersion"] = "flavorVersion";
|
|
10730
|
+
DesignConfigKey["BrandSeed"] = "brandSeed";
|
|
10731
|
+
DesignConfigKey["PairId"] = "pairId";
|
|
10732
|
+
DesignConfigKey["Shape"] = "shape";
|
|
10733
|
+
DesignConfigKey["Density"] = "density";
|
|
10734
|
+
DesignConfigKey["Motion"] = "motion";
|
|
10735
|
+
DesignConfigKey["PillButtons"] = "pillButtons";
|
|
10736
|
+
DesignConfigKey["Mode"] = "mode";
|
|
10737
|
+
DesignConfigKey["Fonts"] = "fonts";
|
|
10738
|
+
DesignConfigKey["Revert"] = "revert";
|
|
10739
|
+
})(DesignConfigKey || (DesignConfigKey = {}));
|
|
10740
|
+
const MODES = SPEC_AXES.modes;
|
|
10741
|
+
const FONT_ROLE_TOKENS = SPEC_FONT_ROLES.map(r => r.token);
|
|
10742
|
+
/** The pack for an id; a retired id (Swiss) resolves to the pack that `replaces` it. */
|
|
10743
|
+
function flavorPackById(id) {
|
|
10744
|
+
if (!id)
|
|
10745
|
+
return undefined;
|
|
10746
|
+
return SPEC_FLAVORS.find(f => f.id === id) ?? SPEC_FLAVORS.find(f => f.replaces === id);
|
|
10632
10747
|
}
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
return [...new Set(Array.from(css.matchAll(REFERENCE), m => m[1]))];
|
|
10748
|
+
/** True when `id` names a retired flavor that a v2 pack replaces (Swiss → Crisp). */
|
|
10749
|
+
function isRetiredFlavorId(id) {
|
|
10750
|
+
return !!id && !SPEC_FLAVORS.some(f => f.id === id) && SPEC_FLAVORS.some(f => f.replaces === id);
|
|
10637
10751
|
}
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10752
|
+
const defaultPack = () => flavorPackById(DEFAULT_FLAVOR_ID);
|
|
10753
|
+
const asString = (v) => (typeof v === 'string' && v.trim() !== '' ? v.trim() : null);
|
|
10754
|
+
function asBoolean(v) {
|
|
10755
|
+
if (v === true || v === 'true')
|
|
10756
|
+
return true;
|
|
10757
|
+
if (v === false || v === 'false')
|
|
10758
|
+
return false;
|
|
10759
|
+
return null;
|
|
10760
|
+
}
|
|
10761
|
+
function asObject(v) {
|
|
10762
|
+
if (v && typeof v === 'object')
|
|
10763
|
+
return v;
|
|
10764
|
+
if (typeof v !== 'string' || v.trim() === '')
|
|
10765
|
+
return null;
|
|
10766
|
+
try {
|
|
10767
|
+
const parsed = JSON.parse(v);
|
|
10768
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
10769
|
+
}
|
|
10770
|
+
catch {
|
|
10771
|
+
return null;
|
|
10772
|
+
}
|
|
10773
|
+
}
|
|
10774
|
+
function asFonts(v) {
|
|
10775
|
+
const raw = asObject(v);
|
|
10776
|
+
if (!raw)
|
|
10777
|
+
return undefined;
|
|
10778
|
+
const fonts = {};
|
|
10779
|
+
for (const role of FONT_ROLE_TOKENS) {
|
|
10780
|
+
const family = raw[role];
|
|
10781
|
+
if (typeof family === 'string' && family.trim() !== '')
|
|
10782
|
+
fonts[role] = family.trim();
|
|
10783
|
+
}
|
|
10784
|
+
return Object.keys(fonts).length ? fonts : undefined;
|
|
10785
|
+
}
|
|
10786
|
+
const keyOf = (table, v) => v !== null && Object.prototype.hasOwnProperty.call(table, v) ? v : null;
|
|
10787
|
+
/** A config array → the key/value map (last entry wins, as the settings form writes it). */
|
|
10788
|
+
function configMap(config) {
|
|
10789
|
+
const map = new Map();
|
|
10790
|
+
for (const kv of config ?? [])
|
|
10791
|
+
if (kv && typeof kv.key === 'string')
|
|
10792
|
+
map.set(kv.key, kv.value);
|
|
10793
|
+
return map;
|
|
10794
|
+
}
|
|
10795
|
+
/**
|
|
10796
|
+
* A partial set of choices completed against its pack: every missing or invalid value falls back to
|
|
10797
|
+
* the pack's own default (an unknown pair → the pack's first, a bad seed → the pack's seed).
|
|
10798
|
+
*/
|
|
10799
|
+
function resolveDesignChoices(partial) {
|
|
10800
|
+
const pack = flavorPackById(partial?.flavorId) ?? defaultPack();
|
|
10801
|
+
const seed = asString(partial?.seed);
|
|
10802
|
+
const pairId = asString(partial?.pairId);
|
|
10803
|
+
const fonts = asFonts(partial?.fonts);
|
|
10804
|
+
return {
|
|
10805
|
+
flavorId: pack.id,
|
|
10806
|
+
seed: seed && parseHexColor(seed) ? seed : pack.seed,
|
|
10807
|
+
pairId: pairId && pack.pairs.some(p => p.id === pairId) ? pairId : pack.pairs[0]?.id ?? '',
|
|
10808
|
+
shape: keyOf(SPEC_AXES.shapes, asString(partial?.shape)) ?? pack.defaults.shape,
|
|
10809
|
+
density: keyOf(SPEC_AXES.densities, asString(partial?.density)) ?? pack.defaults.density,
|
|
10810
|
+
motion: keyOf(SPEC_AXES.motions, asString(partial?.motion)) ?? pack.defaults.motion,
|
|
10811
|
+
pillButtons: asBoolean(partial?.pillButtons) ?? pack.defaults.pillButtons,
|
|
10812
|
+
mode: (MODES.includes(asString(partial?.mode) ?? '') ? asString(partial?.mode) : pack.defaults.mode),
|
|
10813
|
+
...(fonts ? { fonts } : {}),
|
|
10814
|
+
};
|
|
10815
|
+
}
|
|
10816
|
+
/** The pack's own defaults for `flavorId` — what picking a card means before anything is tuned. */
|
|
10817
|
+
function packDesignChoices(flavorId) {
|
|
10818
|
+
return resolveDesignChoices({ flavorId });
|
|
10819
|
+
}
|
|
10820
|
+
/** A stored `settings.design` config → the resolved design. No row (or no config) → Crisp's defaults. */
|
|
10821
|
+
function readDesignConfig(config, hasRow = !!config) {
|
|
10822
|
+
const map = configMap(config);
|
|
10823
|
+
const storedFlavorId = asString(map.get(DesignConfigKey.FlavorId));
|
|
10824
|
+
const storedSeed = asString(map.get(DesignConfigKey.BrandSeed));
|
|
10825
|
+
const choices = resolveDesignChoices({
|
|
10826
|
+
flavorId: storedFlavorId ?? undefined,
|
|
10827
|
+
seed: storedSeed ?? undefined,
|
|
10828
|
+
pairId: asString(map.get(DesignConfigKey.PairId)) ?? undefined,
|
|
10829
|
+
shape: map.get(DesignConfigKey.Shape),
|
|
10830
|
+
density: map.get(DesignConfigKey.Density),
|
|
10831
|
+
motion: map.get(DesignConfigKey.Motion),
|
|
10832
|
+
pillButtons: asBoolean(map.get(DesignConfigKey.PillButtons)) ?? undefined,
|
|
10833
|
+
mode: map.get(DesignConfigKey.Mode),
|
|
10834
|
+
fonts: map.get(DesignConfigKey.Fonts),
|
|
10835
|
+
});
|
|
10836
|
+
return {
|
|
10837
|
+
choices,
|
|
10838
|
+
storedFlavorId,
|
|
10839
|
+
storedSeed,
|
|
10840
|
+
flavorVersion: asString(map.get(DesignConfigKey.FlavorVersion)),
|
|
10841
|
+
revert: asObject(map.get(DesignConfigKey.Revert)),
|
|
10842
|
+
hasRow,
|
|
10843
|
+
};
|
|
10844
|
+
}
|
|
10845
|
+
/** The colour mode a stored config compiles in — what the public page read hands the engine. */
|
|
10846
|
+
function designModeOf(config) {
|
|
10847
|
+
return readDesignConfig(config).choices.mode;
|
|
10848
|
+
}
|
|
10849
|
+
/** Resolved choices (+ the revert snapshot) → the config array a settings row stores. */
|
|
10850
|
+
function designConfigEntries(choices, flavorVersion, revert) {
|
|
10851
|
+
const c = resolveDesignChoices(choices);
|
|
10852
|
+
return [
|
|
10853
|
+
{ key: DesignConfigKey.FlavorId, value: c.flavorId },
|
|
10854
|
+
{ key: DesignConfigKey.FlavorVersion, value: String(flavorVersion) },
|
|
10855
|
+
{ key: DesignConfigKey.BrandSeed, value: c.seed },
|
|
10856
|
+
{ key: DesignConfigKey.PairId, value: c.pairId },
|
|
10857
|
+
{ key: DesignConfigKey.Shape, value: c.shape },
|
|
10858
|
+
{ key: DesignConfigKey.Density, value: c.density },
|
|
10859
|
+
{ key: DesignConfigKey.Motion, value: c.motion },
|
|
10860
|
+
{ key: DesignConfigKey.PillButtons, value: String(c.pillButtons) },
|
|
10861
|
+
{ key: DesignConfigKey.Mode, value: c.mode },
|
|
10862
|
+
...(c.fonts ? [{ key: DesignConfigKey.Fonts, value: JSON.stringify(c.fonts) }] : []),
|
|
10863
|
+
...(revert ? [{ key: DesignConfigKey.Revert, value: JSON.stringify(revert) }] : []),
|
|
10864
|
+
];
|
|
10865
|
+
}
|
|
10866
|
+
/** Two choice sets describe the same look (fonts compared by role). */
|
|
10867
|
+
function sameDesignChoices(a, b) {
|
|
10868
|
+
const x = resolveDesignChoices(a);
|
|
10869
|
+
const y = resolveDesignChoices(b);
|
|
10870
|
+
return x.flavorId === y.flavorId && x.seed.toLowerCase() === y.seed.toLowerCase() && x.pairId === y.pairId
|
|
10871
|
+
&& x.shape === y.shape && x.density === y.density && x.motion === y.motion
|
|
10872
|
+
&& x.pillButtons === y.pillButtons && x.mode === y.mode
|
|
10873
|
+
&& JSON.stringify(x.fonts ?? {}) === JSON.stringify(y.fonts ?? {});
|
|
10874
|
+
}
|
|
10875
|
+
|
|
10876
|
+
/**
|
|
10877
|
+
* Applying a look (design-system DS4): the COMPLETE `buildFlavorTokens` set for the chosen
|
|
10878
|
+
* `DesignChoices`, merged into the org's rows by the §10 C2 rule — rewrite only what the previous
|
|
10879
|
+
* seed wrote and nobody touched since, keep the author's rows, report what was kept. And the one
|
|
10880
|
+
* migration the contract names: an org still on Swiss becomes Crisp.
|
|
10881
|
+
* Pure plans; each caller (builder, the server-side site build) applies them with its own transport.
|
|
10882
|
+
*/
|
|
10883
|
+
const SEED_STAMP = /^flavor:([^@]+)@/;
|
|
10884
|
+
const expectations = (rows) => new Map(rows.map(r => [r.name, { value: r.value, ...(r.values?.dark ? { dark: r.values.dark } : {}) }]));
|
|
10885
|
+
/** Every row a design writes — `buildFlavorTokens` for its pack. Empty for an unknown pack. */
|
|
10886
|
+
function designTokenRows(choices) {
|
|
10887
|
+
const pack = flavorPackById(choices.flavorId);
|
|
10888
|
+
return pack ? buildFlavorTokens(pack, { ...choices, flavorId: pack.id }) : [];
|
|
10889
|
+
}
|
|
10890
|
+
/** What the F1/F2 Swiss seeding wrote: the four Swiss rows + the brand set derived from its seed. */
|
|
10891
|
+
function swissExpectations(seed) {
|
|
10892
|
+
const rows = SWISS_FLAVOR.tokens.map(t => ({ name: t.name, value: t.value, values: t.values }));
|
|
10893
|
+
if (seed) {
|
|
10894
|
+
try {
|
|
10895
|
+
rows.push(...deriveBrandTokens(seed, SWISS_FLAVOR).map(t => ({ name: t.name, value: t.value, values: t.values })));
|
|
10896
|
+
}
|
|
10897
|
+
catch { /* an unparseable stored seed proves nothing about the rows */ }
|
|
10898
|
+
}
|
|
10899
|
+
return expectations(rows);
|
|
10900
|
+
}
|
|
10901
|
+
/**
|
|
10902
|
+
* What the PREVIOUS design wrote, as a baseline keyed by the row's own stamp: a row Swiss stamped is
|
|
10903
|
+
* judged against what Swiss wrote, every other row against `buildFlavorTokens` of the stored choices
|
|
10904
|
+
* (for an org with no design row, Crisp's defaults — exactly what DS2's interim seed wrote). In a
|
|
10905
|
+
* Swiss org the only v2 rows are that interim seed's, so they are judged against Crisp's DEFAULTS,
|
|
10906
|
+
* never against the brand-seeded Crisp the migration is about to write.
|
|
10907
|
+
*/
|
|
10908
|
+
function designBaseline(previous) {
|
|
10909
|
+
const retired = isRetiredFlavorId(previous.storedFlavorId);
|
|
10910
|
+
const current = expectations(designTokenRows(retired ? packDesignChoices(previous.choices.flavorId) : previous.choices));
|
|
10911
|
+
const swiss = swissExpectations(retired ? previous.storedSeed : null);
|
|
10912
|
+
return (row) => {
|
|
10913
|
+
const stampedBy = SEED_STAMP.exec(row.source ?? '')?.[1];
|
|
10914
|
+
return stampedBy === SWISS_FLAVOR.id ? swiss.get(row.name) : current.get(row.name);
|
|
10915
|
+
};
|
|
10916
|
+
}
|
|
10917
|
+
/** Apply `next` over the org's rows, judged against `previous` — the plan FlavorApplyService runs. */
|
|
10918
|
+
function planDesignApply(existing, previous, next) {
|
|
10919
|
+
const pack = flavorPackById(next.flavorId);
|
|
10920
|
+
if (!pack)
|
|
10921
|
+
return { create: [], update: [], kept: [] };
|
|
10922
|
+
return planFlavorSeed(designTokenRows(next), existing, flavorSourceStamp(pack.id), designBaseline(previous));
|
|
10923
|
+
}
|
|
10924
|
+
/** The rows a design is missing by name — the builder-load top-up (never touches an existing row). */
|
|
10925
|
+
function missingDesignRows(existing, choices) {
|
|
10926
|
+
const have = new Set((existing ?? []).map(r => r.name));
|
|
10927
|
+
return designTokenRows(choices).filter(r => !have.has(r.name));
|
|
10928
|
+
}
|
|
10929
|
+
const LEGACY_ALIASES = SPEC_LEGACY_ALIASES;
|
|
10930
|
+
/**
|
|
10931
|
+
* Swiss → Crisp (spec `legacyAliases`; D1). Needed when the design row says a retired flavor, or when
|
|
10932
|
+
* there is no design row but the org holds Swiss-stamped alias rows (F1 seeded those before the row
|
|
10933
|
+
* existed). Idempotent: once the row says Crisp nothing is needed, and re-running the plan writes
|
|
10934
|
+
* nothing.
|
|
10935
|
+
*
|
|
10936
|
+
* - The look: `planDesignApply` from Swiss to Crisp — Swiss-stamped rows untouched since Swiss seeded
|
|
10937
|
+
* 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.
|
|
10941
|
+
*/
|
|
10942
|
+
function planSwissMigration(existing, stored) {
|
|
10943
|
+
const swissAliasRows = (existing ?? []).filter(r => r.name in LEGACY_ALIASES && SEED_STAMP.exec(r.source ?? '')?.[1] === SWISS_FLAVOR.id);
|
|
10944
|
+
const needed = isRetiredFlavorId(stored.storedFlavorId) || (!stored.hasRow && swissAliasRows.length > 0);
|
|
10945
|
+
if (!needed)
|
|
10946
|
+
return null;
|
|
10947
|
+
const choices = stored.choices;
|
|
10948
|
+
const previous = isRetiredFlavorId(stored.storedFlavorId) ? stored : { ...stored, storedFlavorId: SWISS_FLAVOR.id };
|
|
10949
|
+
const plan = planDesignApply(existing, previous, choices);
|
|
10950
|
+
const stamp = flavorSourceStamp(choices.flavorId);
|
|
10951
|
+
const swiss = swissExpectations(null);
|
|
10952
|
+
for (const row of (existing ?? []).filter(r => r.name in LEGACY_ALIASES)) {
|
|
10953
|
+
const alias = LEGACY_ALIASES[row.name];
|
|
10954
|
+
if (row.value === alias)
|
|
10955
|
+
continue;
|
|
10956
|
+
const was = swiss.get(row.name);
|
|
10957
|
+
const untouched = SEED_STAMP.test(row.source ?? '') && !!was && row.value === was.value && !row.values?.dark;
|
|
10958
|
+
if (untouched && row._id) {
|
|
10959
|
+
plan.update.push({ id: row._id, data: { value: alias, values: {}, source: stamp } });
|
|
10960
|
+
}
|
|
10961
|
+
else {
|
|
10962
|
+
plan.kept.push(row.name);
|
|
10963
|
+
}
|
|
10964
|
+
}
|
|
10965
|
+
return { choices, plan };
|
|
10643
10966
|
}
|
|
10644
10967
|
|
|
10645
10968
|
/**
|
|
@@ -10657,5 +10980,5 @@ function unknownTokenReferences(css, alsoAllowed = []) {
|
|
|
10657
10980
|
* Generated bundle index. Do not edit.
|
|
10658
10981
|
*/
|
|
10659
10982
|
|
|
10660
|
-
export { ACCENT_TOKEN_NAMES, BUTTON_VARIANT_TO_STYLE, COLOR_SCHEMES, COLOR_SCHEME_TRAIT, COLOR_SCHEME_TYPES, ColorScheme, DESIGN_TOKEN_GROUPS, DESIGN_TOKEN_TIERS, DESIGN_TOKEN_TYPES, ELEMENT_STYLES, ELEMENT_STYLE_MARKER_CLASS, ELEMENT_STYLE_STATES, EMPTY_IMAGE_ATTRIBUTE, EMPTY_IMAGE_SRC, ElementStyleState, FLAVOR_SPEC_VERSION, FONT_CATALOG, FONT_FAMILY_TOKENS, 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, colorSchemeClass, colorSchemeOf, compileSchemeCss, compileTokenCss, compileTokenCssBlocks, cssString, defaultDesignChoices, defaultElementStyleFor, deriveAccentTokens, deriveBrandTokens, elementStyleById, elementStyleClass, elementStylesFor, emptyImageLabelSrc, emptyImageSrc, flavorSourceStamp, fontsForTokens, hasElementStyleMarker, isDesignTokenGroup, isDesignTokenTier, isDesignTokenType, nodeDesignClasses, oklabToRgb, ownedTraitStatus, parseFontFamilies, parseHexColor, planFlavorRevert, planFlavorSeed, relativeLuminance, resolveTokenReferences, resolveTokenValues, revertAvailable, rgbToOklab, specTokenNames, springToLinear, styleKeyToKebab, switchElementStyle, syncNodeDesignClasses, toHexColor, tokenReferences, tokensToDarkVarMap, tokensToVarMap, unknownTokenReferences, userFontStack, validateFlavorPack, wcagContrastRatio };
|
|
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 };
|
|
10661
10984
|
//# sourceMappingURL=vectoriox-iox-ui-design-core.mjs.map
|