@ttoss/fsl-theme 1.1.21 → 2.0.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.
Files changed (66) hide show
  1. package/README.md +16 -14
  2. package/dist/{Types-BiBa17RL.d.cts → Types-BkaxtsMv.d.cts} +61 -13
  3. package/dist/{Types-BiBa17RL.d.mts → Types-BkaxtsMv.d.mts} +61 -13
  4. package/dist/{baseBundle-BXvPJYzj.cjs → baseBundle-DdRSa5mf.cjs} +3 -2
  5. package/dist/{baseBundle-BLvD8XVS.mjs → baseBundle-R7WyjyTw.mjs} +3 -2
  6. package/dist/{createTheme-JOu16Shb.cjs → createTheme--UFgVlEn.cjs} +144 -16
  7. package/dist/{createTheme-DzLnsknT.mjs → createTheme-B6tVIikC.mjs} +138 -16
  8. package/dist/css.cjs +2 -2
  9. package/dist/css.d.cts +1 -1
  10. package/dist/css.d.mts +1 -1
  11. package/dist/css.mjs +2 -2
  12. package/dist/dataviz/index.cjs +2 -2
  13. package/dist/dataviz/index.d.cts +1 -1
  14. package/dist/dataviz/index.d.mts +1 -1
  15. package/dist/dataviz/index.mjs +1 -1
  16. package/dist/dtcg.cjs +26 -9
  17. package/dist/dtcg.d.cts +17 -4
  18. package/dist/dtcg.d.mts +17 -4
  19. package/dist/dtcg.mjs +26 -9
  20. package/dist/{helpers-C29fy-Ad.cjs → helpers-BU40JvFA.cjs} +27 -78
  21. package/dist/{helpers-CA4Qkxct.mjs → helpers-BXaKe2s3.mjs} +28 -73
  22. package/dist/{index-CsIjfw86.d.cts → index-7YL90Xqs.d.cts} +36 -12
  23. package/dist/{index-nJrjI0BA.d.mts → index-BmCTIOzh.d.mts} +36 -12
  24. package/dist/index.cjs +2 -8
  25. package/dist/index.d.cts +4 -4
  26. package/dist/index.d.mts +4 -4
  27. package/dist/index.mjs +2 -5
  28. package/dist/{react-DnKxR2gK.d.mts → react-C8JrZf-b.d.mts} +43 -2
  29. package/dist/{react-CoWIQYx_.cjs → react-DLB8nrXQ.cjs} +32 -2
  30. package/dist/{react-CGa6FlNL.d.cts → react-Dt7vGAVG.d.cts} +43 -2
  31. package/dist/react.cjs +1 -1
  32. package/dist/react.d.cts +1 -1
  33. package/dist/react.d.mts +1 -1
  34. package/dist/react.mjs +32 -2
  35. package/dist/runtime-entry.cjs +1 -1
  36. package/dist/runtime-entry.mjs +1 -1
  37. package/dist/{ssrScript-CoRE6jjq.mjs → ssrScript-DNLlLDps.mjs} +2 -1
  38. package/dist/{ssrScript-DOpLExaG.cjs → ssrScript-MF5SIONH.cjs} +2 -1
  39. package/dist/themes/bruttal.cjs +19 -4
  40. package/dist/themes/bruttal.d.cts +1 -1
  41. package/dist/themes/bruttal.d.mts +1 -1
  42. package/dist/themes/bruttal.mjs +19 -4
  43. package/dist/{toCssVars-CUiqx2kh.cjs → toCssVars-BseqmMcT.cjs} +27 -2
  44. package/dist/{toCssVars-BHRiSykR.mjs → toCssVars-C-9yIZDR.mjs} +27 -2
  45. package/dist/{tokenRegistry-DolhB5Vf.mjs → tokenRegistry-DviyqDeE.mjs} +19 -28
  46. package/dist/{tokenRegistry-B9zMRjpL.cjs → tokenRegistry-f_Bx_BAN.cjs} +19 -28
  47. package/dist/vars.cjs +3 -3
  48. package/dist/vars.d.cts +1 -1
  49. package/dist/vars.d.mts +1 -1
  50. package/dist/vars.mjs +3 -3
  51. package/dist/{withDataviz-DYV1S_xr.mjs → withDataviz-BvpZJnFx.mjs} +2 -2
  52. package/dist/{withDataviz-BlJcYhcw.cjs → withDataviz-CYRf4exT.cjs} +2 -2
  53. package/llms.txt +6 -6
  54. package/package.json +1 -1
  55. package/dist/themes/corporate.cjs +0 -34
  56. package/dist/themes/corporate.d.cts +0 -3
  57. package/dist/themes/corporate.d.mts +0 -3
  58. package/dist/themes/corporate.mjs +0 -31
  59. package/dist/themes/oca.cjs +0 -34
  60. package/dist/themes/oca.d.cts +0 -3
  61. package/dist/themes/oca.d.mts +0 -3
  62. package/dist/themes/oca.mjs +0 -31
  63. package/dist/themes/ventures.cjs +0 -34
  64. package/dist/themes/ventures.d.cts +0 -3
  65. package/dist/themes/ventures.d.mts +0 -3
  66. package/dist/themes/ventures.mjs +0 -31
@@ -1,5 +1,5 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- import { c as validateRefs, r as deepMerge } from "./helpers-CA4Qkxct.mjs";
2
+ import { a as flattenTheme, r as deepMerge, t as COMPOUND_REF_RE } from "./helpers-BXaKe2s3.mjs";
3
3
 
4
4
  //#region src/baseTheme.ts
5
5
  /**
@@ -7,6 +7,33 @@ import { c as validateRefs, r as deepMerge } from "./helpers-CA4Qkxct.mjs";
7
7
  *
8
8
  * System fonts, gray palette, and balanced proportions. Serves as the
9
9
  * canonical base that all other themes extend via `createTheme`.
10
+ *
11
+ * ## Theme brief
12
+ *
13
+ * Every value in this file is chosen so that the resolved bundle satisfies
14
+ * this brief. Changes that contradict the brief belong in a different theme,
15
+ * not in `baseTheme`.
16
+ *
17
+ * ```yaml
18
+ * name: base
19
+ * purpose: default built-in foundation for modern product UI
20
+ * primaryPosture: productive
21
+ * secondaryPosture: calm
22
+ * densityProfile: balanced
23
+ * readingMode: mixed
24
+ * pointerProfile: hybrid
25
+ * interactionRisk: medium
26
+ * surfaceModel: lightly-layered
27
+ * brandEnergy: quiet
28
+ * accessibilityTarget: AA+
29
+ * colorModeStrategy: dark-supported
30
+ * platformBias: web
31
+ * ```
32
+ *
33
+ * Intended feel: practical, calm, modern, trustworthy, easy to extend.
34
+ * Avoided feel: flashy, ornamental, cramped, fragile, overly branded.
35
+ *
36
+ * @see /docs/website/docs/design/design-system/design-tokens/theme-authoring.md#recommended-base-theme-brief
10
37
  */
11
38
  var baseTheme = {
12
39
  core: {
@@ -1077,23 +1104,23 @@ var baseTheme = {
1077
1104
  spacing: {
1078
1105
  inset: {
1079
1106
  control: {
1080
- sm: "{core.spacing.2}",
1081
- md: "{core.spacing.3}",
1082
- lg: "{core.spacing.4}"
1083
- },
1084
- surface: {
1085
1107
  sm: "{core.spacing.3}",
1086
1108
  md: "{core.spacing.4}",
1087
1109
  lg: "{core.spacing.6}"
1110
+ },
1111
+ surface: {
1112
+ sm: "{core.spacing.4}",
1113
+ md: "{core.spacing.6}",
1114
+ lg: "{core.spacing.8}"
1088
1115
  }
1089
1116
  },
1090
1117
  gap: {
1091
1118
  stack: {
1092
1119
  xs: "{core.spacing.2}",
1093
- sm: "{core.spacing.3}",
1094
- md: "{core.spacing.4}",
1095
- lg: "{core.spacing.6}",
1096
- xl: "{core.spacing.8}"
1120
+ sm: "{core.spacing.4}",
1121
+ md: "{core.spacing.6}",
1122
+ lg: "{core.spacing.8}",
1123
+ xl: "{core.spacing.12}"
1097
1124
  },
1098
1125
  inline: {
1099
1126
  xs: "{core.spacing.1}",
@@ -1104,12 +1131,12 @@ var baseTheme = {
1104
1131
  }
1105
1132
  },
1106
1133
  gutter: {
1107
- page: "clamp({core.spacing.4}, {core.spacing.6}, {core.spacing.12})",
1108
- section: "clamp({core.spacing.3}, {core.spacing.4}, {core.spacing.12})"
1134
+ page: "clamp({core.spacing.6}, {core.spacing.12}, {core.spacing.16})",
1135
+ section: "clamp({core.spacing.4}, {core.spacing.8}, {core.spacing.16})"
1109
1136
  },
1110
1137
  separation: {
1111
1138
  interactive: {
1112
- min: "clamp(8px, {core.spacing.2}, 12px)"
1139
+ min: "clamp(8px, {core.spacing.3}, 16px)"
1113
1140
  }
1114
1141
  }
1115
1142
  },
@@ -1714,6 +1741,96 @@ var darkAlternate = {
1714
1741
  }
1715
1742
  }
1716
1743
  };
1744
+ /**
1745
+ * Design brief for the base theme — the recommended default from
1746
+ * theme-authoring.md §"Recommended base theme brief". Attached to the base
1747
+ * bundle via `createTheme({ brief })`; carries the `FSL-DESIGN-001..003` gate.
1748
+ */
1749
+ var baseBrief = {
1750
+ name: "base",
1751
+ purpose: "default built-in foundation for modern product UI",
1752
+ primaryPosture: "productive",
1753
+ secondaryPosture: "calm",
1754
+ densityProfile: "balanced",
1755
+ readingMode: "mixed",
1756
+ pointerProfile: "hybrid",
1757
+ interactionRisk: "medium",
1758
+ surfaceModel: "lightly-layered",
1759
+ brandEnergy: "quiet",
1760
+ accessibilityTarget: "AA+",
1761
+ colorModeStrategy: "dark-supported",
1762
+ platformBias: "web"
1763
+ };
1764
+
1765
+ //#endregion
1766
+ //#region src/roots/validateRefs.ts
1767
+ /**
1768
+ * Compute the Levenshtein edit distance between two strings.
1769
+ * Used exclusively by `validateRefs` to power "did you mean?" suggestions.
1770
+ */
1771
+ var levenshtein = (a, b) => {
1772
+ const m = a.length;
1773
+ const n = b.length;
1774
+ const dp = Array.from({
1775
+ length: m + 1
1776
+ }, () => {
1777
+ return Array.from({
1778
+ length: n + 1
1779
+ }, () => {
1780
+ return 0;
1781
+ });
1782
+ });
1783
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
1784
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
1785
+ for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
1786
+ return dp[m][n];
1787
+ };
1788
+ /**
1789
+ * Validate that every `{ref}` in the merged theme points to an existing path.
1790
+ * Emits `console.warn` for each broken reference with a "did you mean?" suggestion.
1791
+ *
1792
+ * **DEV-only** — callers gate this behind `process.env.NODE_ENV !== 'production'`
1793
+ * so bundlers tree-shake the entire call in production builds.
1794
+ */
1795
+ var validateRefs = theme => {
1796
+ const {
1797
+ core,
1798
+ semantic
1799
+ } = flattenTheme(theme);
1800
+ const all = {
1801
+ ...core,
1802
+ ...semantic
1803
+ };
1804
+ const allKeys = Object.keys(all);
1805
+ const findSuggestion = (refPath, candidates) => {
1806
+ if (candidates.length === 0) return "";
1807
+ let bestDist = Infinity;
1808
+ let bestKey = "";
1809
+ for (const candidate of candidates) {
1810
+ const dist = levenshtein(refPath, candidate);
1811
+ if (dist < bestDist) {
1812
+ bestDist = dist;
1813
+ bestKey = candidate;
1814
+ }
1815
+ }
1816
+ if (bestDist <= Math.ceil(refPath.length * .4)) return `\n Did you mean '{${bestKey}}'?`;
1817
+ return "";
1818
+ };
1819
+ for (const [ownerKey, value] of Object.entries(all)) {
1820
+ if (typeof value !== "string" || !value.includes("{")) continue;
1821
+ let match;
1822
+ const re = new RegExp(COMPOUND_REF_RE.source, COMPOUND_REF_RE.flags);
1823
+ while ((match = re.exec(value)) !== null) {
1824
+ const refPath = match[1];
1825
+ if (all[refPath] !== void 0) continue;
1826
+ const prefix = refPath.split(".")[0];
1827
+ const suggestion = findSuggestion(refPath, allKeys.filter(k => {
1828
+ return k.startsWith(prefix + ".");
1829
+ }));
1830
+ console.warn(`[fsl-theme] Invalid token reference '{${refPath}}' at path '${ownerKey}'.${suggestion}`);
1831
+ }
1832
+ }
1833
+ };
1717
1834
 
1718
1835
  //#endregion
1719
1836
  //#region src/createTheme.ts
@@ -1783,20 +1900,25 @@ var createTheme = ({
1783
1900
  baseMode,
1784
1901
  base,
1785
1902
  overrides,
1786
- alternate
1903
+ alternate,
1904
+ brief
1787
1905
  } = {}) => {
1788
1906
  const resolvedBase = base ?? parentBundle?.base;
1789
1907
  const resolvedBaseMode = baseMode ?? parentBundle?.baseMode ?? "light";
1790
1908
  const resolvedAlternate = resolveAlternate(alternate, parentBundle);
1909
+ const resolvedMeta = brief ?? parentBundle?.meta;
1791
1910
  return {
1792
1911
  baseMode: resolvedBaseMode,
1793
1912
  base: buildTheme({
1794
1913
  base: resolvedBase,
1795
1914
  overrides
1796
1915
  }),
1797
- alternate: resolvedAlternate
1916
+ alternate: resolvedAlternate,
1917
+ ...(resolvedMeta ? {
1918
+ meta: resolvedMeta
1919
+ } : {})
1798
1920
  };
1799
1921
  };
1800
1922
 
1801
1923
  //#endregion
1802
- export { darkAlternate as i, createTheme as n, baseTheme as r, buildTheme as t };
1924
+ export { darkAlternate as a, baseTheme as i, createTheme as n, baseBrief as r, buildTheme as t };
package/dist/css.cjs CHANGED
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, {
3
3
  value: 'Module'
4
4
  });
5
- const require_helpers = require('./helpers-C29fy-Ad.cjs');
6
- const require_toCssVars = require('./toCssVars-CUiqx2kh.cjs');
5
+ const require_helpers = require('./helpers-BU40JvFA.cjs');
6
+ const require_toCssVars = require('./toCssVars-BseqmMcT.cjs');
7
7
 
8
8
  //#region src/css.ts
9
9
  /**
package/dist/css.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { i as ThemeTokens, r as ThemeBundle } from "./Types-BiBa17RL.cjs";
2
+ import { a as ThemeTokens, i as ThemeBundle } from "./Types-BkaxtsMv.cjs";
3
3
 
4
4
  //#region src/roots/helpers.d.ts
5
5
  /**
package/dist/css.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { i as ThemeTokens, r as ThemeBundle } from "./Types-BiBa17RL.mjs";
2
+ import { a as ThemeTokens, i as ThemeBundle } from "./Types-BkaxtsMv.mjs";
3
3
 
4
4
  //#region src/roots/helpers.d.ts
5
5
  /**
package/dist/css.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- import { s as toFlatTokens } from "./helpers-CA4Qkxct.mjs";
3
- import { n as toCssVars, t as toCssVarName } from "./toCssVars-BHRiSykR.mjs";
2
+ import { s as toFlatTokens } from "./helpers-BXaKe2s3.mjs";
3
+ import { n as toCssVars, t as toCssVarName } from "./toCssVars-C-9yIZDR.mjs";
4
4
 
5
5
  //#region src/css.ts
6
6
  /**
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, {
3
3
  value: 'Module'
4
4
  });
5
- const require_react = require('../react-CoWIQYx_.cjs');
6
- const require_withDataviz = require('../withDataviz-BlJcYhcw.cjs');
5
+ const require_react = require('../react-DLB8nrXQ.cjs');
6
+ const require_withDataviz = require('../withDataviz-CYRf4exT.cjs');
7
7
 
8
8
  //#region src/dataviz/useDatavizTokens.ts
9
9
  /**
@@ -1,5 +1,5 @@
1
1
 
2
- import { o as CoreDataviz, r as ThemeBundle, s as SemanticDataviz } from "../Types-BiBa17RL.cjs";
2
+ import { c as SemanticDataviz, i as ThemeBundle, s as CoreDataviz } from "../Types-BkaxtsMv.cjs";
3
3
 
4
4
  //#region src/dataviz/baseTheme.d.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
 
2
- import { o as CoreDataviz, r as ThemeBundle, s as SemanticDataviz } from "../Types-BiBa17RL.mjs";
2
+ import { c as SemanticDataviz, i as ThemeBundle, s as CoreDataviz } from "../Types-BkaxtsMv.mjs";
3
3
 
4
4
  //#region src/dataviz/baseTheme.d.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- import { n as coreDataviz, r as semanticDataviz, t as withDataviz } from "../withDataviz-DYV1S_xr.mjs";
2
+ import { n as coreDataviz, r as semanticDataviz, t as withDataviz } from "../withDataviz-BvpZJnFx.mjs";
3
3
  import { useTokens } from "../react.mjs";
4
4
 
5
5
  //#region src/dataviz/useDatavizTokens.ts
package/dist/dtcg.cjs CHANGED
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, {
3
3
  value: 'Module'
4
4
  });
5
- const require_helpers = require('./helpers-C29fy-Ad.cjs');
6
- const require_tokenRegistry = require('./tokenRegistry-B9zMRjpL.cjs');
5
+ const require_helpers = require('./helpers-BU40JvFA.cjs');
6
+ const require_tokenRegistry = require('./tokenRegistry-f_Bx_BAN.cjs');
7
7
 
8
8
  //#region src/roots/toDTCG.ts
9
9
  /**
@@ -26,12 +26,20 @@ const require_tokenRegistry = require('./tokenRegistry-B9zMRjpL.cjs');
26
26
  *
27
27
  * Motion leaves (SemanticMotionEntry) overridden separately:
28
28
  * - `.duration` → 'duration'
29
+ *
30
+ * Border / focus line widths resolve to a `dimension` (`1px`, `2px`) but sit
31
+ * under the untyped `semantic.border.` / `semantic.focus.` prefixes:
32
+ * - `.width` → 'dimension'
33
+ */
34
+ var SUFFIX_TYPE_OVERRIDES = [[".duration", "duration"], [".fontFamily", "fontFamily"], [".fontSize", "dimension"], [".fontWeight", "fontWeight"], [".lineHeight", "number"], [".letterSpacing", "dimension"], [".width", "dimension"], [".ring.color", "color"]];
35
+ /**
36
+ * Infer the DTCG `$type` for a token path, or `undefined` when the token is
37
+ * opaque (keyword/curve/dash-string) and has no valid DTCG scalar type — in
38
+ * which case `toDTCG` omits `$type` rather than emitting an invalid one.
29
39
  */
30
- var SUFFIX_TYPE_OVERRIDES = [[".duration", "duration"], [".fontFamily", "fontFamily"], [".fontSize", "dimension"], [".fontWeight", "fontWeight"], [".lineHeight", "number"], [".letterSpacing", "dimension"], [".ring.color", "color"]];
31
- var inferType = path => {
40
+ var inferDtcgType = path => {
32
41
  for (const [suffix, type] of SUFFIX_TYPE_OVERRIDES) if (path.endsWith(suffix)) return type;
33
42
  for (const [prefix, type] of require_tokenRegistry.DTCG_TYPE_PREFIXES) if (path.startsWith(prefix)) return type;
34
- return "string";
35
43
  };
36
44
  /**
37
45
  * Set a value at a dot-path in a nested object, creating intermediate
@@ -71,8 +79,16 @@ var buildHitExtension = (step, theme) => {
71
79
  * Root 3 — W3C Design Tokens (DTCG JSON).
72
80
  *
73
81
  * Convert a `ThemeTokens` into a structured token tree following the
74
- * W3C Design Tokens Community Group format. Every leaf node has `$value`
75
- * (fully resolved) and `$type` (inferred from the token path).
82
+ * W3C Design Tokens Community Group format (2025.10). Every leaf has a
83
+ * `$value`; `$type` is inferred from the token path and **omitted** for opaque
84
+ * tokens with no valid DTCG scalar type (`$type` is optional per spec).
85
+ *
86
+ * **Profile:** this emits **fully-resolved scalar tokens** — `$value`s are
87
+ * concrete (no `{alias}` refs) and composite shapes (typography, shadow,
88
+ * border, transition) are emitted as their individual scalar leaves, not as
89
+ * DTCG composite objects. This is a conformant DTCG profile; richer
90
+ * alias-preserving / composite output is a deferred enhancement (see
91
+ * CONTRIBUTING.md ADR-013).
76
92
  *
77
93
  * Semantic hit tokens (`semantic.sizing.hit.*`) include a `$extensions`
78
94
  * field declaring their coarse-pointer override value, so non-CSS consumers
@@ -98,10 +114,11 @@ var toDTCG = theme => {
98
114
  const flat = require_helpers.toFlatTokens(theme);
99
115
  const tree = {};
100
116
  for (const [path, value] of Object.entries(flat)) {
117
+ const dtcgType = inferDtcgType(path);
101
118
  const token = {
102
- $value: value,
103
- $type: inferType(path)
119
+ $value: value
104
120
  };
121
+ if (dtcgType !== void 0) token.$type = dtcgType;
105
122
  if (path.startsWith(SEMANTIC_HIT_PREFIX)) {
106
123
  const ext = buildHitExtension(path.slice(20), theme);
107
124
  if (ext) token.$extensions = ext;
package/dist/dtcg.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { i as ThemeTokens } from "./Types-BiBa17RL.cjs";
2
+ import { a as ThemeTokens } from "./Types-BkaxtsMv.cjs";
3
3
 
4
4
  //#region src/roots/toDTCG.d.ts
5
5
  /**
@@ -10,7 +10,12 @@ import { i as ThemeTokens } from "./Types-BiBa17RL.cjs";
10
10
  */
11
11
  interface DTCGToken {
12
12
  $value: string | number;
13
- $type: string;
13
+ /**
14
+ * W3C DTCG type. **Optional** — omitted for opaque/keyword tokens that have
15
+ * no valid DTCG scalar type (`$type` is optional in the DTCG spec). There is
16
+ * no `'string'` DTCG type; emitting one would break conformant importers.
17
+ */
18
+ $type?: string;
14
19
  $extensions?: Record<string, unknown>;
15
20
  }
16
21
  /**
@@ -23,8 +28,16 @@ type DTCGTokenTree = DTCGToken | {
23
28
  * Root 3 — W3C Design Tokens (DTCG JSON).
24
29
  *
25
30
  * Convert a `ThemeTokens` into a structured token tree following the
26
- * W3C Design Tokens Community Group format. Every leaf node has `$value`
27
- * (fully resolved) and `$type` (inferred from the token path).
31
+ * W3C Design Tokens Community Group format (2025.10). Every leaf has a
32
+ * `$value`; `$type` is inferred from the token path and **omitted** for opaque
33
+ * tokens with no valid DTCG scalar type (`$type` is optional per spec).
34
+ *
35
+ * **Profile:** this emits **fully-resolved scalar tokens** — `$value`s are
36
+ * concrete (no `{alias}` refs) and composite shapes (typography, shadow,
37
+ * border, transition) are emitted as their individual scalar leaves, not as
38
+ * DTCG composite objects. This is a conformant DTCG profile; richer
39
+ * alias-preserving / composite output is a deferred enhancement (see
40
+ * CONTRIBUTING.md ADR-013).
28
41
  *
29
42
  * Semantic hit tokens (`semantic.sizing.hit.*`) include a `$extensions`
30
43
  * field declaring their coarse-pointer override value, so non-CSS consumers
package/dist/dtcg.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { i as ThemeTokens } from "./Types-BiBa17RL.mjs";
2
+ import { a as ThemeTokens } from "./Types-BkaxtsMv.mjs";
3
3
 
4
4
  //#region src/roots/toDTCG.d.ts
5
5
  /**
@@ -10,7 +10,12 @@ import { i as ThemeTokens } from "./Types-BiBa17RL.mjs";
10
10
  */
11
11
  interface DTCGToken {
12
12
  $value: string | number;
13
- $type: string;
13
+ /**
14
+ * W3C DTCG type. **Optional** — omitted for opaque/keyword tokens that have
15
+ * no valid DTCG scalar type (`$type` is optional in the DTCG spec). There is
16
+ * no `'string'` DTCG type; emitting one would break conformant importers.
17
+ */
18
+ $type?: string;
14
19
  $extensions?: Record<string, unknown>;
15
20
  }
16
21
  /**
@@ -23,8 +28,16 @@ type DTCGTokenTree = DTCGToken | {
23
28
  * Root 3 — W3C Design Tokens (DTCG JSON).
24
29
  *
25
30
  * Convert a `ThemeTokens` into a structured token tree following the
26
- * W3C Design Tokens Community Group format. Every leaf node has `$value`
27
- * (fully resolved) and `$type` (inferred from the token path).
31
+ * W3C Design Tokens Community Group format (2025.10). Every leaf has a
32
+ * `$value`; `$type` is inferred from the token path and **omitted** for opaque
33
+ * tokens with no valid DTCG scalar type (`$type` is optional per spec).
34
+ *
35
+ * **Profile:** this emits **fully-resolved scalar tokens** — `$value`s are
36
+ * concrete (no `{alias}` refs) and composite shapes (typography, shadow,
37
+ * border, transition) are emitted as their individual scalar leaves, not as
38
+ * DTCG composite objects. This is a conformant DTCG profile; richer
39
+ * alias-preserving / composite output is a deferred enhancement (see
40
+ * CONTRIBUTING.md ADR-013).
28
41
  *
29
42
  * Semantic hit tokens (`semantic.sizing.hit.*`) include a `$extensions`
30
43
  * field declaring their coarse-pointer override value, so non-CSS consumers
package/dist/dtcg.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- import { s as toFlatTokens } from "./helpers-CA4Qkxct.mjs";
3
- import { n as DTCG_TYPE_PREFIXES } from "./tokenRegistry-DolhB5Vf.mjs";
2
+ import { s as toFlatTokens } from "./helpers-BXaKe2s3.mjs";
3
+ import { n as DTCG_TYPE_PREFIXES } from "./tokenRegistry-DviyqDeE.mjs";
4
4
 
5
5
  //#region src/roots/toDTCG.ts
6
6
  /**
@@ -23,12 +23,20 @@ import { n as DTCG_TYPE_PREFIXES } from "./tokenRegistry-DolhB5Vf.mjs";
23
23
  *
24
24
  * Motion leaves (SemanticMotionEntry) overridden separately:
25
25
  * - `.duration` → 'duration'
26
+ *
27
+ * Border / focus line widths resolve to a `dimension` (`1px`, `2px`) but sit
28
+ * under the untyped `semantic.border.` / `semantic.focus.` prefixes:
29
+ * - `.width` → 'dimension'
30
+ */
31
+ var SUFFIX_TYPE_OVERRIDES = [[".duration", "duration"], [".fontFamily", "fontFamily"], [".fontSize", "dimension"], [".fontWeight", "fontWeight"], [".lineHeight", "number"], [".letterSpacing", "dimension"], [".width", "dimension"], [".ring.color", "color"]];
32
+ /**
33
+ * Infer the DTCG `$type` for a token path, or `undefined` when the token is
34
+ * opaque (keyword/curve/dash-string) and has no valid DTCG scalar type — in
35
+ * which case `toDTCG` omits `$type` rather than emitting an invalid one.
26
36
  */
27
- var SUFFIX_TYPE_OVERRIDES = [[".duration", "duration"], [".fontFamily", "fontFamily"], [".fontSize", "dimension"], [".fontWeight", "fontWeight"], [".lineHeight", "number"], [".letterSpacing", "dimension"], [".ring.color", "color"]];
28
- var inferType = path => {
37
+ var inferDtcgType = path => {
29
38
  for (const [suffix, type] of SUFFIX_TYPE_OVERRIDES) if (path.endsWith(suffix)) return type;
30
39
  for (const [prefix, type] of DTCG_TYPE_PREFIXES) if (path.startsWith(prefix)) return type;
31
- return "string";
32
40
  };
33
41
  /**
34
42
  * Set a value at a dot-path in a nested object, creating intermediate
@@ -68,8 +76,16 @@ var buildHitExtension = (step, theme) => {
68
76
  * Root 3 — W3C Design Tokens (DTCG JSON).
69
77
  *
70
78
  * Convert a `ThemeTokens` into a structured token tree following the
71
- * W3C Design Tokens Community Group format. Every leaf node has `$value`
72
- * (fully resolved) and `$type` (inferred from the token path).
79
+ * W3C Design Tokens Community Group format (2025.10). Every leaf has a
80
+ * `$value`; `$type` is inferred from the token path and **omitted** for opaque
81
+ * tokens with no valid DTCG scalar type (`$type` is optional per spec).
82
+ *
83
+ * **Profile:** this emits **fully-resolved scalar tokens** — `$value`s are
84
+ * concrete (no `{alias}` refs) and composite shapes (typography, shadow,
85
+ * border, transition) are emitted as their individual scalar leaves, not as
86
+ * DTCG composite objects. This is a conformant DTCG profile; richer
87
+ * alias-preserving / composite output is a deferred enhancement (see
88
+ * CONTRIBUTING.md ADR-013).
73
89
  *
74
90
  * Semantic hit tokens (`semantic.sizing.hit.*`) include a `$extensions`
75
91
  * field declaring their coarse-pointer override value, so non-CSS consumers
@@ -95,10 +111,11 @@ var toDTCG = theme => {
95
111
  const flat = toFlatTokens(theme);
96
112
  const tree = {};
97
113
  for (const [path, value] of Object.entries(flat)) {
114
+ const dtcgType = inferDtcgType(path);
98
115
  const token = {
99
- $value: value,
100
- $type: inferType(path)
116
+ $value: value
101
117
  };
118
+ if (dtcgType !== void 0) token.$type = dtcgType;
102
119
  if (path.startsWith(SEMANTIC_HIT_PREFIX)) {
103
120
  const ext = buildHitExtension(path.slice(20), theme);
104
121
  if (ext) token.$extensions = ext;
@@ -1,8 +1,19 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
2
  //#region src/roots/helpers.ts
3
- /** Check if a value is a token reference like `{core.colors.brand.500}` */
3
+ /**
4
+ * Check if a value is a **pure** token reference like `{core.colors.brand.500}`.
5
+ *
6
+ * A pure ref is a single `{path}` with no interior braces. Compound expressions
7
+ * (`clamp({a}, {b})`) and multi-ref strings (`{a} {b}`) are NOT pure refs — they
8
+ * start `{`/end `}` but carry interior braces, and must go through the compound
9
+ * resolution path instead. Without the interior-brace check `{a} {b}` would be
10
+ * mis-parsed as the single path `a} {b`.
11
+ */
12
+ var hasInteriorBraces = value => {
13
+ return value.indexOf("{", 1) !== -1 || value.indexOf("}") !== value.length - 1;
14
+ };
4
15
  var isTokenRef = value => {
5
- return typeof value === "string" && value.length > 2 && value.startsWith("{") && value.endsWith("}");
16
+ return typeof value === "string" && value.length > 2 && value.startsWith("{") && value.endsWith("}") && !hasInteriorBraces(value);
6
17
  };
7
18
  /** Extract the inner path from a token reference: `{core.colors.brand.500}` → `core.colors.brand.500` */
8
19
  var extractRefPath = ref => {
@@ -24,6 +35,14 @@ var isPlainObject = value => {
24
35
  return typeof value === "object" && value !== null && !Array.isArray(value);
25
36
  };
26
37
  /**
38
+ * Prototype-chain keys that must never be written from override data —
39
+ * `result['__proto__']` reaches the prototype setter, not an own property.
40
+ * Guards `createTheme`'s public `overrides` against prototype pollution.
41
+ */
42
+ var isUnsafeMergeKey = key => {
43
+ return key === "__proto__" || key === "constructor" || key === "prototype";
44
+ };
45
+ /**
27
46
  * Recursively merges `overrides` into `base`.
28
47
  * - Plain objects are merged recursively.
29
48
  * - All other values (primitives, arrays) are replaced.
@@ -34,6 +53,7 @@ var deepMerge = (base, overrides) => {
34
53
  ...base
35
54
  };
36
55
  for (const key of Object.keys(overrides)) {
56
+ if (isUnsafeMergeKey(key)) continue;
37
57
  const baseVal = result[key];
38
58
  const overVal = overrides[key];
39
59
  if (overVal === void 0) continue;
@@ -69,73 +89,6 @@ var flattenTheme = theme => {
69
89
  };
70
90
  };
71
91
  /**
72
- * Compute the Levenshtein edit distance between two strings.
73
- * Used exclusively by `validateRefs` to power "did you mean?" suggestions.
74
- */
75
- var levenshtein = (a, b) => {
76
- const m = a.length;
77
- const n = b.length;
78
- const dp = Array.from({
79
- length: m + 1
80
- }, () => {
81
- return Array.from({
82
- length: n + 1
83
- }, () => {
84
- return 0;
85
- });
86
- });
87
- for (let i = 0; i <= m; i++) dp[i][0] = i;
88
- for (let j = 0; j <= n; j++) dp[0][j] = j;
89
- for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
90
- return dp[m][n];
91
- };
92
- /**
93
- * Validate that every `{ref}` in the merged theme points to an existing path.
94
- * Emits `console.warn` for each broken reference with a "did you mean?" suggestion.
95
- *
96
- * **DEV-only** — callers gate this behind `process.env.NODE_ENV !== 'production'`
97
- * so bundlers tree-shake the entire call in production builds.
98
- */
99
- var validateRefs = theme => {
100
- const {
101
- core,
102
- semantic
103
- } = flattenTheme(theme);
104
- const all = {
105
- ...core,
106
- ...semantic
107
- };
108
- const allKeys = Object.keys(all);
109
- const findSuggestion = (refPath, candidates) => {
110
- if (candidates.length === 0) return "";
111
- let bestDist = Infinity;
112
- let bestKey = "";
113
- for (const candidate of candidates) {
114
- const dist = levenshtein(refPath, candidate);
115
- if (dist < bestDist) {
116
- bestDist = dist;
117
- bestKey = candidate;
118
- }
119
- }
120
- if (bestDist <= Math.ceil(refPath.length * .4)) return `\n Did you mean '{${bestKey}}'?`;
121
- return "";
122
- };
123
- for (const [ownerKey, value] of Object.entries(all)) {
124
- if (typeof value !== "string" || !value.includes("{")) continue;
125
- let match;
126
- const re = new RegExp(COMPOUND_REF_RE.source, COMPOUND_REF_RE.flags);
127
- while ((match = re.exec(value)) !== null) {
128
- const refPath = match[1];
129
- if (all[refPath] !== void 0) continue;
130
- const prefix = refPath.split(".")[0];
131
- const suggestion = findSuggestion(refPath, allKeys.filter(k => {
132
- return k.startsWith(prefix + ".");
133
- }));
134
- console.warn(`[fsl-theme] Invalid token reference '{${refPath}}' at path '${ownerKey}'.${suggestion}`);
135
- }
136
- }
137
- };
138
- /**
139
92
  * Flatten a `ThemeTokens` into a `Record<string, string | number>` with
140
93
  * every `{ref}` recursively resolved to its final raw value where possible.
141
94
  *
@@ -181,8 +134,8 @@ var toFlatTokens = (theme, options = {}) => {
181
134
  /**
182
135
  * Resolve all embedded `{path}` refs in a raw string expression.
183
136
  *
184
- * Handles both pure refs (`{core.space.4}`) and compound expressions
185
- * (`clamp({core.space.4}, {core.space.6}, {core.space.12})`).
137
+ * Handles both pure refs (`{core.spacing.4}`) and compound expressions
138
+ * (`clamp({core.spacing.4}, {core.spacing.6}, {core.spacing.12})`).
186
139
  */
187
140
  const resolveInline = (value, seen, ownerKey) => {
188
141
  return value.replace(COMPOUND_REF_RE, (_match, path) => {
@@ -195,7 +148,9 @@ var toFlatTokens = (theme, options = {}) => {
195
148
  reportUnresolved(ownerKey, path, "circular reference");
196
149
  return `{${path}}`;
197
150
  }
198
- const resolved = resolveRef(target, new Set(seen).add(path), ownerKey);
151
+ const childSeen = new Set(seen).add(path);
152
+ const resolved = resolveRef(target, childSeen, ownerKey);
153
+ if (typeof resolved === "string" && resolved.includes("{")) return resolveInline(resolved, childSeen, ownerKey);
199
154
  return String(resolved);
200
155
  });
201
156
  };
@@ -249,10 +204,4 @@ Object.defineProperty(exports, 'toFlatTokens', {
249
204
  get: function () {
250
205
  return toFlatTokens;
251
206
  }
252
- });
253
- Object.defineProperty(exports, 'validateRefs', {
254
- enumerable: true,
255
- get: function () {
256
- return validateRefs;
257
- }
258
207
  });