@ttoss/fsl-theme 1.1.22 → 2.1.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 +54 -41
  2. package/dist/{Types-BiBa17RL.d.cts → Types-a_sfpbha.d.cts} +84 -24
  3. package/dist/{Types-BiBa17RL.d.mts → Types-a_sfpbha.d.mts} +84 -24
  4. package/dist/{baseBundle-DiYlHNG5.mjs → baseBundle-CXdxj_re.mjs} +3 -2
  5. package/dist/{baseBundle-BWbICg4p.cjs → baseBundle-a9LDsqYy.cjs} +3 -2
  6. package/dist/{createTheme-BHGc1IFI.cjs → createTheme-B7H0xSVp.cjs} +128 -19
  7. package/dist/{createTheme-C1r5nJco.mjs → createTheme-Bm9LVQ4w.mjs} +123 -20
  8. package/dist/css.cjs +5 -4
  9. package/dist/css.d.cts +21 -2
  10. package/dist/css.d.mts +21 -2
  11. package/dist/css.mjs +5 -4
  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-CIqeJ8yd.cjs → helpers-BU40JvFA.cjs} +27 -5
  21. package/dist/{helpers-Dz7NfNK6.mjs → helpers-BXaKe2s3.mjs} +27 -5
  22. package/dist/{index-BuszkWMy.d.mts → index-BmZQVd3G.d.cts} +9 -12
  23. package/dist/{index-Djro6G4e.d.cts → index-XZsRrMTF.d.mts} +9 -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-D2J482kx.d.mts → react-BwsjWKmo.d.mts} +59 -16
  29. package/dist/{react-BKLEzIN8.cjs → react-DQErMGJz.cjs} +107 -19
  30. package/dist/{react-BXqSYnmk.d.cts → react-Drr-QSuX.d.cts} +59 -16
  31. package/dist/react.cjs +3 -1
  32. package/dist/react.d.cts +1 -1
  33. package/dist/react.d.mts +1 -1
  34. package/dist/react.mjs +109 -19
  35. package/dist/runtime-entry.cjs +1 -1
  36. package/dist/runtime-entry.mjs +1 -1
  37. package/dist/{ssrScript-D3mYscp1.cjs → ssrScript-D73WcrCM.cjs} +52 -4
  38. package/dist/{ssrScript-DfmopH-w.mjs → ssrScript-zDK1dqXr.mjs} +52 -4
  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-CeckLjW1.mjs → toCssVars-CWTlp9St.mjs} +65 -5
  44. package/dist/{toCssVars-DDowz4wU.cjs → toCssVars-D0BR23cP.cjs} +65 -5
  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 +8 -8
  48. package/dist/vars.d.cts +6 -6
  49. package/dist/vars.d.mts +6 -6
  50. package/dist/vars.mjs +8 -8
  51. package/dist/{withDataviz-BzZwFV2e.cjs → withDataviz-DrUtw8cc.cjs} +2 -2
  52. package/dist/{withDataviz-CrgXLR5A.mjs → withDataviz-Ln3sEwb7.mjs} +2 -2
  53. package/llms.txt +11 -9
  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
- const require_helpers = require('./helpers-CIqeJ8yd.cjs');
2
+ const require_helpers = require('./helpers-BU40JvFA.cjs');
3
3
 
4
4
  //#region src/themeBootstrap.ts
5
5
  /**
@@ -18,7 +18,7 @@ var resolveTheme = cfg => {
18
18
  const mode = stored != null && stored.mode != null && cfg.validModes.indexOf(stored.mode) !== -1 ? stored.mode : cfg.defaultMode;
19
19
  return {
20
20
  mode,
21
- resolvedMode: mode === "system" ? typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : mode
21
+ resolvedMode: mode === "system" ? typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : mode
22
22
  };
23
23
  };
24
24
 
@@ -34,6 +34,20 @@ var writeStorage = (key, data) => {
34
34
  localStorage.setItem(key, JSON.stringify(data));
35
35
  } catch {}
36
36
  };
37
+ var createMediaQuery = () => {
38
+ if (typeof window.matchMedia === "function") return window.matchMedia("(prefers-color-scheme: dark)");
39
+ return {
40
+ matches: false,
41
+ addEventListener: () => {},
42
+ removeEventListener: () => {}
43
+ };
44
+ };
45
+ /**
46
+ * DEV-only registry of live runtimes per root element. Two runtimes writing
47
+ * `data-tt-*` to the same root overwrite each other (last writer wins), so
48
+ * the second attach warns. Entries are decremented on `destroy()`.
49
+ */
50
+ var activeRuntimesPerRoot = /* @__PURE__ */new WeakMap();
37
51
  /**
38
52
  * Creates a framework-agnostic runtime that manages theme switching.
39
53
  *
@@ -60,6 +74,11 @@ var createThemeRuntime = (config = {}) => {
60
74
  } = config;
61
75
  const listeners = /* @__PURE__ */new Set();
62
76
  let destroyed = false;
77
+ if (process.env.NODE_ENV !== "production") {
78
+ const count = (activeRuntimesPerRoot.get(root) ?? 0) + 1;
79
+ activeRuntimesPerRoot.set(root, count);
80
+ if (count > 1) console.warn("[fsl-theme] Multiple theme runtimes are attached to the same root element. They overwrite each other's data-tt-* attributes (last writer wins) and their mode states do not sync. Render a single <ThemeProvider> per root, or pass a distinct `root` to each.");
81
+ }
63
82
  const init = resolveTheme({
64
83
  storageKey,
65
84
  defaultMode,
@@ -94,7 +113,7 @@ var createThemeRuntime = (config = {}) => {
94
113
  const state = getState();
95
114
  for (const listener of listeners) listener(state);
96
115
  };
97
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
116
+ const mediaQuery = createMediaQuery();
98
117
  const onSystemChange = () => {
99
118
  if (destroyed) return;
100
119
  resolvedMode = mediaQuery.matches ? "dark" : "light";
@@ -107,6 +126,30 @@ var createThemeRuntime = (config = {}) => {
107
126
  if (active) mediaQuery.addEventListener("change", onSystemChange);
108
127
  };
109
128
  syncMediaListener(mode === "system");
129
+ /** Parse + validate a persisted `{ mode }` payload; null when unusable. */
130
+ const parseStoredMode = raw => {
131
+ let stored = null;
132
+ try {
133
+ stored = JSON.parse(raw);
134
+ } catch {
135
+ return null;
136
+ }
137
+ const nextMode = stored?.mode;
138
+ return nextMode && VALID_MODES.includes(nextMode) ? nextMode : null;
139
+ };
140
+ const onStorage = event => {
141
+ if (destroyed || event.key !== storageKey || event.newValue === null) return;
142
+ if (event.storageArea != null && event.storageArea !== localStorage) return;
143
+ const nextMode = parseStoredMode(event.newValue);
144
+ if (!nextMode) return;
145
+ mode = nextMode;
146
+ resolvedMode = mode === "system" ? mediaQuery.matches ? "dark" : "light" : mode;
147
+ syncMediaListener(mode === "system");
148
+ applyState({
149
+ persist: false
150
+ });
151
+ };
152
+ window.addEventListener("storage", onStorage);
110
153
  const setMode = newMode => {
111
154
  if (destroyed || !VALID_MODES.includes(newMode)) return;
112
155
  mode = newMode;
@@ -126,7 +169,12 @@ var createThemeRuntime = (config = {}) => {
126
169
  const destroy = () => {
127
170
  destroyed = true;
128
171
  syncMediaListener(false);
172
+ window.removeEventListener("storage", onStorage);
129
173
  listeners.clear();
174
+ if (process.env.NODE_ENV !== "production") {
175
+ const count = activeRuntimesPerRoot.get(root) ?? 0;
176
+ activeRuntimesPerRoot.set(root, Math.max(0, count - 1));
177
+ }
130
178
  };
131
179
  return {
132
180
  getState,
@@ -173,7 +221,7 @@ var getThemeScriptContent = (config = {}) => {
173
221
  defaultTheme,
174
222
  defaultMode,
175
223
  validModes: [...VALID_MODES]
176
- })};var d=document.documentElement;var s=null;try{var r=localStorage.getItem(cfg.storageKey);if(r)s=JSON.parse(r);}catch(e){}var m=(s!=null&&s.mode!=null&&cfg.validModes.indexOf(s.mode)!==-1)?s.mode:cfg.defaultMode;var rm=m==="system"?(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"):m;if(cfg.defaultTheme){d.setAttribute("data-tt-theme",cfg.defaultTheme);}else{d.removeAttribute("data-tt-theme");}d.setAttribute("data-tt-mode",rm);d.style.colorScheme=rm;})()`;
224
+ }).replace(/</g, "\\u003C")};var d=document.documentElement;var s=null;try{var r=localStorage.getItem(cfg.storageKey);if(r)s=JSON.parse(r);}catch(e){}var m=(s!=null&&s.mode!=null&&cfg.validModes.indexOf(s.mode)!==-1)?s.mode:cfg.defaultMode;var rm=m==="system"?(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"):m;if(cfg.defaultTheme){d.setAttribute("data-tt-theme",cfg.defaultTheme);}else{d.removeAttribute("data-tt-theme");}d.setAttribute("data-tt-mode",rm);d.style.colorScheme=rm;})()`;
177
225
  };
178
226
 
179
227
  //#endregion
@@ -1,5 +1,5 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- import { n as SAFE_ID_RE } from "./helpers-Dz7NfNK6.mjs";
2
+ import { n as SAFE_ID_RE } from "./helpers-BXaKe2s3.mjs";
3
3
 
4
4
  //#region src/themeBootstrap.ts
5
5
  /**
@@ -18,7 +18,7 @@ var resolveTheme = cfg => {
18
18
  const mode = stored != null && stored.mode != null && cfg.validModes.indexOf(stored.mode) !== -1 ? stored.mode : cfg.defaultMode;
19
19
  return {
20
20
  mode,
21
- resolvedMode: mode === "system" ? typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : mode
21
+ resolvedMode: mode === "system" ? typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : mode
22
22
  };
23
23
  };
24
24
 
@@ -34,6 +34,20 @@ var writeStorage = (key, data) => {
34
34
  localStorage.setItem(key, JSON.stringify(data));
35
35
  } catch {}
36
36
  };
37
+ var createMediaQuery = () => {
38
+ if (typeof window.matchMedia === "function") return window.matchMedia("(prefers-color-scheme: dark)");
39
+ return {
40
+ matches: false,
41
+ addEventListener: () => {},
42
+ removeEventListener: () => {}
43
+ };
44
+ };
45
+ /**
46
+ * DEV-only registry of live runtimes per root element. Two runtimes writing
47
+ * `data-tt-*` to the same root overwrite each other (last writer wins), so
48
+ * the second attach warns. Entries are decremented on `destroy()`.
49
+ */
50
+ var activeRuntimesPerRoot = /* @__PURE__ */new WeakMap();
37
51
  /**
38
52
  * Creates a framework-agnostic runtime that manages theme switching.
39
53
  *
@@ -60,6 +74,11 @@ var createThemeRuntime = (config = {}) => {
60
74
  } = config;
61
75
  const listeners = /* @__PURE__ */new Set();
62
76
  let destroyed = false;
77
+ if (process.env.NODE_ENV !== "production") {
78
+ const count = (activeRuntimesPerRoot.get(root) ?? 0) + 1;
79
+ activeRuntimesPerRoot.set(root, count);
80
+ if (count > 1) console.warn("[fsl-theme] Multiple theme runtimes are attached to the same root element. They overwrite each other's data-tt-* attributes (last writer wins) and their mode states do not sync. Render a single <ThemeProvider> per root, or pass a distinct `root` to each.");
81
+ }
63
82
  const init = resolveTheme({
64
83
  storageKey,
65
84
  defaultMode,
@@ -94,7 +113,7 @@ var createThemeRuntime = (config = {}) => {
94
113
  const state = getState();
95
114
  for (const listener of listeners) listener(state);
96
115
  };
97
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
116
+ const mediaQuery = createMediaQuery();
98
117
  const onSystemChange = () => {
99
118
  if (destroyed) return;
100
119
  resolvedMode = mediaQuery.matches ? "dark" : "light";
@@ -107,6 +126,30 @@ var createThemeRuntime = (config = {}) => {
107
126
  if (active) mediaQuery.addEventListener("change", onSystemChange);
108
127
  };
109
128
  syncMediaListener(mode === "system");
129
+ /** Parse + validate a persisted `{ mode }` payload; null when unusable. */
130
+ const parseStoredMode = raw => {
131
+ let stored = null;
132
+ try {
133
+ stored = JSON.parse(raw);
134
+ } catch {
135
+ return null;
136
+ }
137
+ const nextMode = stored?.mode;
138
+ return nextMode && VALID_MODES.includes(nextMode) ? nextMode : null;
139
+ };
140
+ const onStorage = event => {
141
+ if (destroyed || event.key !== storageKey || event.newValue === null) return;
142
+ if (event.storageArea != null && event.storageArea !== localStorage) return;
143
+ const nextMode = parseStoredMode(event.newValue);
144
+ if (!nextMode) return;
145
+ mode = nextMode;
146
+ resolvedMode = mode === "system" ? mediaQuery.matches ? "dark" : "light" : mode;
147
+ syncMediaListener(mode === "system");
148
+ applyState({
149
+ persist: false
150
+ });
151
+ };
152
+ window.addEventListener("storage", onStorage);
110
153
  const setMode = newMode => {
111
154
  if (destroyed || !VALID_MODES.includes(newMode)) return;
112
155
  mode = newMode;
@@ -126,7 +169,12 @@ var createThemeRuntime = (config = {}) => {
126
169
  const destroy = () => {
127
170
  destroyed = true;
128
171
  syncMediaListener(false);
172
+ window.removeEventListener("storage", onStorage);
129
173
  listeners.clear();
174
+ if (process.env.NODE_ENV !== "production") {
175
+ const count = activeRuntimesPerRoot.get(root) ?? 0;
176
+ activeRuntimesPerRoot.set(root, Math.max(0, count - 1));
177
+ }
130
178
  };
131
179
  return {
132
180
  getState,
@@ -173,7 +221,7 @@ var getThemeScriptContent = (config = {}) => {
173
221
  defaultTheme,
174
222
  defaultMode,
175
223
  validModes: [...VALID_MODES]
176
- })};var d=document.documentElement;var s=null;try{var r=localStorage.getItem(cfg.storageKey);if(r)s=JSON.parse(r);}catch(e){}var m=(s!=null&&s.mode!=null&&cfg.validModes.indexOf(s.mode)!==-1)?s.mode:cfg.defaultMode;var rm=m==="system"?(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"):m;if(cfg.defaultTheme){d.setAttribute("data-tt-theme",cfg.defaultTheme);}else{d.removeAttribute("data-tt-theme");}d.setAttribute("data-tt-mode",rm);d.style.colorScheme=rm;})()`;
224
+ }).replace(/</g, "\\u003C")};var d=document.documentElement;var s=null;try{var r=localStorage.getItem(cfg.storageKey);if(r)s=JSON.parse(r);}catch(e){}var m=(s!=null&&s.mode!=null&&cfg.validModes.indexOf(s.mode)!==-1)?s.mode:cfg.defaultMode;var rm=m==="system"?(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"):m;if(cfg.defaultTheme){d.setAttribute("data-tt-theme",cfg.defaultTheme);}else{d.removeAttribute("data-tt-theme");}d.setAttribute("data-tt-mode",rm);d.style.colorScheme=rm;})()`;
177
225
  };
178
226
 
179
227
  //#endregion
@@ -2,9 +2,9 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, {
3
3
  value: 'Module'
4
4
  });
5
- const require_createTheme = require('../createTheme-BHGc1IFI.cjs');
6
- const require_helpers = require('../helpers-CIqeJ8yd.cjs');
7
- const require_withDataviz = require('../withDataviz-BzZwFV2e.cjs');
5
+ const require_createTheme = require('../createTheme-B7H0xSVp.cjs');
6
+ const require_helpers = require('../helpers-BU40JvFA.cjs');
7
+ const require_withDataviz = require('../withDataviz-DrUtw8cc.cjs');
8
8
 
9
9
  //#region src/themes/bruttal.ts
10
10
  /**
@@ -67,7 +67,22 @@ var bundle = require_createTheme.createTheme({
67
67
  }
68
68
  }
69
69
  }
70
- })
70
+ }),
71
+ brief: {
72
+ name: "bruttal",
73
+ purpose: "warm-brown brutalist brand identity",
74
+ primaryPosture: "expressive",
75
+ secondaryPosture: "technical",
76
+ densityProfile: "balanced",
77
+ readingMode: "mixed",
78
+ pointerProfile: "hybrid",
79
+ interactionRisk: "medium",
80
+ surfaceModel: "flat",
81
+ brandEnergy: "expressive",
82
+ accessibilityTarget: "AA+",
83
+ colorModeStrategy: "dark-supported",
84
+ platformBias: "web"
85
+ }
71
86
  });
72
87
  var bruttal = require_withDataviz.withDataviz(bundle);
73
88
 
@@ -1,3 +1,3 @@
1
1
 
2
- import { i as bruttal } from "../index-Djro6G4e.cjs";
2
+ import { t as bruttal } from "../index-BmZQVd3G.cjs";
3
3
  export { bruttal };
@@ -1,3 +1,3 @@
1
1
 
2
- import { i as bruttal } from "../index-BuszkWMy.mjs";
2
+ import { t as bruttal } from "../index-XZsRrMTF.mjs";
3
3
  export { bruttal };
@@ -1,7 +1,7 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- import { i as darkAlternate, n as createTheme } from "../createTheme-C1r5nJco.mjs";
3
- import { r as deepMerge } from "../helpers-Dz7NfNK6.mjs";
4
- import { t as withDataviz } from "../withDataviz-CrgXLR5A.mjs";
2
+ import { a as darkAlternate, n as createTheme } from "../createTheme-Bm9LVQ4w.mjs";
3
+ import { r as deepMerge } from "../helpers-BXaKe2s3.mjs";
4
+ import { t as withDataviz } from "../withDataviz-Ln3sEwb7.mjs";
5
5
 
6
6
  //#region src/themes/bruttal.ts
7
7
  /**
@@ -64,7 +64,22 @@ var bundle = createTheme({
64
64
  }
65
65
  }
66
66
  }
67
- })
67
+ }),
68
+ brief: {
69
+ name: "bruttal",
70
+ purpose: "warm-brown brutalist brand identity",
71
+ primaryPosture: "expressive",
72
+ secondaryPosture: "technical",
73
+ densityProfile: "balanced",
74
+ readingMode: "mixed",
75
+ pointerProfile: "hybrid",
76
+ interactionRisk: "medium",
77
+ surfaceModel: "flat",
78
+ brandEnergy: "expressive",
79
+ accessibilityTarget: "AA+",
80
+ colorModeStrategy: "dark-supported",
81
+ platformBias: "web"
82
+ }
68
83
  });
69
84
  var bruttal = withDataviz(bundle);
70
85
 
@@ -1,6 +1,6 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- import { a as flattenTheme, i as flattenObject, n as SAFE_ID_RE, r as deepMerge, t as COMPOUND_REF_RE } from "./helpers-Dz7NfNK6.mjs";
3
- import { t as CSS_PATH_PREFIXES } from "./tokenRegistry-DolhB5Vf.mjs";
2
+ import { a as flattenTheme, i as flattenObject, n as SAFE_ID_RE, r as deepMerge, t as COMPOUND_REF_RE } from "./helpers-BXaKe2s3.mjs";
3
+ import { t as CSS_PATH_PREFIXES } from "./tokenRegistry-DviyqDeE.mjs";
4
4
 
5
5
  //#region src/roots/toCssVars.ts
6
6
  /**
@@ -11,7 +11,7 @@ import { t as CSS_PATH_PREFIXES } from "./tokenRegistry-DolhB5Vf.mjs";
11
11
  */
12
12
  var toCssVarName = tokenPath => {
13
13
  for (const [prefix, cssPrefix] of CSS_PATH_PREFIXES) if (tokenPath.startsWith(prefix)) return `${cssPrefix}${tokenPath.slice(prefix.length).replace(/\./g, "-")}`;
14
- return `--tt-${tokenPath.replace(/\./g, "-")}`;
14
+ return `--tt-${(tokenPath.startsWith("semantic.") ? tokenPath.slice(9) : tokenPath).replace(/\./g, "-")}`;
15
15
  };
16
16
  /**
17
17
  * Replace all embedded `{token.path}` refs inside a raw string with `var(--tt-...)` references.
@@ -26,6 +26,27 @@ var inlineRefsToVars = value => {
26
26
  });
27
27
  };
28
28
  /**
29
+ * Assert that no two token paths collapse to the same CSS custom property name.
30
+ *
31
+ * `toCssVarName` is first-match-wins with no duplicate detection, so two
32
+ * distinct token paths sharing a `cssPrefix` (e.g. a future
33
+ * `semantic.dataviz.color.state.*` vs `semantic.dataviz.geo.state.*`) could
34
+ * silently overwrite each other in the flat record. This guards the whole
35
+ * registry, not just dataviz.
36
+ *
37
+ * **DEV-only** — callers gate this behind `process.env.NODE_ENV !== 'production'`
38
+ * so bundlers tree-shake the entire call in production builds.
39
+ */
40
+ var assertDistinctCssVars = flat => {
41
+ const byVarName = /* @__PURE__ */new Map();
42
+ for (const path of Object.keys(flat)) {
43
+ const varName = toCssVarName(path);
44
+ const existing = byVarName.get(varName);
45
+ if (existing !== void 0 && existing !== path) throw new Error(`[fsl-theme] CSS variable name collision: '${varName}' is produced by both '${existing}' and '${path}'. Give one of them a distinct token path or CSS prefix (see roots/tokenRegistry.ts).`);
46
+ byVarName.set(varName, path);
47
+ }
48
+ };
49
+ /**
29
50
  * Build a flat CSS custom properties record from a ThemeTokens.
30
51
  *
31
52
  * Core tokens get raw values. Semantic tokens get `var()` references
@@ -42,6 +63,10 @@ var buildCssVars = theme => {
42
63
  const varName = toCssVarName(path);
43
64
  if (typeof value === "string" && value.includes("{")) vars[varName] = inlineRefsToVars(value);else vars[varName] = value;
44
65
  }
66
+ if (process.env.NODE_ENV !== "production") assertDistinctCssVars({
67
+ ...coreFlat,
68
+ ...semanticFlat
69
+ });
45
70
  return vars;
46
71
  };
47
72
  var sanitizeId = value => {
@@ -187,6 +212,34 @@ var isThemeBundle = input => {
187
212
  return "baseMode" in input && "base" in input;
188
213
  };
189
214
  /**
215
+ * Build the no-JS / pre-hydration system-mode fallback block.
216
+ *
217
+ * Canonical 1-theme model only (no `themeId`): before the runtime (or the SSR
218
+ * bootstrap script) stamps `data-tt-mode`, and for users with JavaScript
219
+ * disabled, the alternate mode would otherwise never apply. This block scopes
220
+ * the alternate diff vars to `@media (prefers-color-scheme: <mode>)` behind
221
+ * `:root:not([data-tt-mode])`, so it applies exactly while no explicit mode
222
+ * attribute exists and gets out of the way the moment the runtime writes one
223
+ * (persisted user choice always wins over the OS preference).
224
+ */
225
+ var buildSystemModeFallbackBlock = ({
226
+ mode,
227
+ vars
228
+ }) => {
229
+ const entries = Object.entries(vars);
230
+ const selector = ":root:not([data-tt-mode])";
231
+ const lines = entries.map(([name, value]) => {
232
+ return ` ${name}: ${hasCqUnits(value) ? toViewportFallback(String(value)) : value};`;
233
+ });
234
+ const cqEntries = entries.filter(([, value]) => {
235
+ return hasCqUnits(value);
236
+ });
237
+ const cqBlock = cqEntries.length > 0 ? `\n\n @supports (width: 1cqi) {\n ${selector} {\n${cqEntries.map(([name, value]) => {
238
+ return ` ${name}: ${value};`;
239
+ }).join("\n")}\n }\n }` : "";
240
+ return `@media (prefers-color-scheme: ${mode}) {\n ${selector} {\n color-scheme: ${mode};\n${lines.join("\n")}\n }${cqBlock}\n}`;
241
+ };
242
+ /**
190
243
  * Compute a diff record: only entries in `full` whose values differ from `base`.
191
244
  */
192
245
  var diffCssVars = ({
@@ -213,7 +266,8 @@ var diffCssVars = ({
213
266
  */
214
267
  var bundleToCssVars = (bundle, options) => {
215
268
  const {
216
- themeId
269
+ themeId,
270
+ systemModeFallback = true
217
271
  } = options;
218
272
  const {
219
273
  baseMode,
@@ -272,7 +326,13 @@ var bundleToCssVars = (bundle, options) => {
272
326
  alternate: alternateResult,
273
327
  toCssString: () => {
274
328
  const parts = [baseResult.toCssString()];
275
- if (Object.keys(diffVars).length > 0) parts.push(alternateResult.toCssString());
329
+ if (Object.keys(diffVars).length > 0) {
330
+ parts.push(alternateResult.toCssString());
331
+ if (!themeId && systemModeFallback) parts.push(buildSystemModeFallbackBlock({
332
+ mode: alternateMode,
333
+ vars: diffVars
334
+ }));
335
+ }
276
336
  return parts.join("\n\n");
277
337
  }
278
338
  };
@@ -1,6 +1,6 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- const require_helpers = require('./helpers-CIqeJ8yd.cjs');
3
- const require_tokenRegistry = require('./tokenRegistry-B9zMRjpL.cjs');
2
+ const require_helpers = require('./helpers-BU40JvFA.cjs');
3
+ const require_tokenRegistry = require('./tokenRegistry-f_Bx_BAN.cjs');
4
4
 
5
5
  //#region src/roots/toCssVars.ts
6
6
  /**
@@ -11,7 +11,7 @@ const require_tokenRegistry = require('./tokenRegistry-B9zMRjpL.cjs');
11
11
  */
12
12
  var toCssVarName = tokenPath => {
13
13
  for (const [prefix, cssPrefix] of require_tokenRegistry.CSS_PATH_PREFIXES) if (tokenPath.startsWith(prefix)) return `${cssPrefix}${tokenPath.slice(prefix.length).replace(/\./g, "-")}`;
14
- return `--tt-${tokenPath.replace(/\./g, "-")}`;
14
+ return `--tt-${(tokenPath.startsWith("semantic.") ? tokenPath.slice(9) : tokenPath).replace(/\./g, "-")}`;
15
15
  };
16
16
  /**
17
17
  * Replace all embedded `{token.path}` refs inside a raw string with `var(--tt-...)` references.
@@ -26,6 +26,27 @@ var inlineRefsToVars = value => {
26
26
  });
27
27
  };
28
28
  /**
29
+ * Assert that no two token paths collapse to the same CSS custom property name.
30
+ *
31
+ * `toCssVarName` is first-match-wins with no duplicate detection, so two
32
+ * distinct token paths sharing a `cssPrefix` (e.g. a future
33
+ * `semantic.dataviz.color.state.*` vs `semantic.dataviz.geo.state.*`) could
34
+ * silently overwrite each other in the flat record. This guards the whole
35
+ * registry, not just dataviz.
36
+ *
37
+ * **DEV-only** — callers gate this behind `process.env.NODE_ENV !== 'production'`
38
+ * so bundlers tree-shake the entire call in production builds.
39
+ */
40
+ var assertDistinctCssVars = flat => {
41
+ const byVarName = /* @__PURE__ */new Map();
42
+ for (const path of Object.keys(flat)) {
43
+ const varName = toCssVarName(path);
44
+ const existing = byVarName.get(varName);
45
+ if (existing !== void 0 && existing !== path) throw new Error(`[fsl-theme] CSS variable name collision: '${varName}' is produced by both '${existing}' and '${path}'. Give one of them a distinct token path or CSS prefix (see roots/tokenRegistry.ts).`);
46
+ byVarName.set(varName, path);
47
+ }
48
+ };
49
+ /**
29
50
  * Build a flat CSS custom properties record from a ThemeTokens.
30
51
  *
31
52
  * Core tokens get raw values. Semantic tokens get `var()` references
@@ -42,6 +63,10 @@ var buildCssVars = theme => {
42
63
  const varName = toCssVarName(path);
43
64
  if (typeof value === "string" && value.includes("{")) vars[varName] = inlineRefsToVars(value);else vars[varName] = value;
44
65
  }
66
+ if (process.env.NODE_ENV !== "production") assertDistinctCssVars({
67
+ ...coreFlat,
68
+ ...semanticFlat
69
+ });
45
70
  return vars;
46
71
  };
47
72
  var sanitizeId = value => {
@@ -187,6 +212,34 @@ var isThemeBundle = input => {
187
212
  return "baseMode" in input && "base" in input;
188
213
  };
189
214
  /**
215
+ * Build the no-JS / pre-hydration system-mode fallback block.
216
+ *
217
+ * Canonical 1-theme model only (no `themeId`): before the runtime (or the SSR
218
+ * bootstrap script) stamps `data-tt-mode`, and for users with JavaScript
219
+ * disabled, the alternate mode would otherwise never apply. This block scopes
220
+ * the alternate diff vars to `@media (prefers-color-scheme: <mode>)` behind
221
+ * `:root:not([data-tt-mode])`, so it applies exactly while no explicit mode
222
+ * attribute exists and gets out of the way the moment the runtime writes one
223
+ * (persisted user choice always wins over the OS preference).
224
+ */
225
+ var buildSystemModeFallbackBlock = ({
226
+ mode,
227
+ vars
228
+ }) => {
229
+ const entries = Object.entries(vars);
230
+ const selector = ":root:not([data-tt-mode])";
231
+ const lines = entries.map(([name, value]) => {
232
+ return ` ${name}: ${hasCqUnits(value) ? toViewportFallback(String(value)) : value};`;
233
+ });
234
+ const cqEntries = entries.filter(([, value]) => {
235
+ return hasCqUnits(value);
236
+ });
237
+ const cqBlock = cqEntries.length > 0 ? `\n\n @supports (width: 1cqi) {\n ${selector} {\n${cqEntries.map(([name, value]) => {
238
+ return ` ${name}: ${value};`;
239
+ }).join("\n")}\n }\n }` : "";
240
+ return `@media (prefers-color-scheme: ${mode}) {\n ${selector} {\n color-scheme: ${mode};\n${lines.join("\n")}\n }${cqBlock}\n}`;
241
+ };
242
+ /**
190
243
  * Compute a diff record: only entries in `full` whose values differ from `base`.
191
244
  */
192
245
  var diffCssVars = ({
@@ -213,7 +266,8 @@ var diffCssVars = ({
213
266
  */
214
267
  var bundleToCssVars = (bundle, options) => {
215
268
  const {
216
- themeId
269
+ themeId,
270
+ systemModeFallback = true
217
271
  } = options;
218
272
  const {
219
273
  baseMode,
@@ -272,7 +326,13 @@ var bundleToCssVars = (bundle, options) => {
272
326
  alternate: alternateResult,
273
327
  toCssString: () => {
274
328
  const parts = [baseResult.toCssString()];
275
- if (Object.keys(diffVars).length > 0) parts.push(alternateResult.toCssString());
329
+ if (Object.keys(diffVars).length > 0) {
330
+ parts.push(alternateResult.toCssString());
331
+ if (!themeId && systemModeFallback) parts.push(buildSystemModeFallbackBlock({
332
+ mode: alternateMode,
333
+ vars: diffVars
334
+ }));
335
+ }
276
336
  return parts.join("\n\n");
277
337
  }
278
338
  };
@@ -20,28 +20,25 @@ var TOKEN_PATH_REGISTRY = [{
20
20
  dtcgType: "number"
21
21
  }, {
22
22
  path: "core.dataviz.",
23
- cssPrefix: "--tt-core-dataviz-",
24
- dtcgType: "string"
23
+ cssPrefix: "--tt-core-dataviz-"
25
24
  }, {
26
25
  path: "semantic.dataviz.color.",
27
- cssPrefix: "--tt-dataviz-",
26
+ cssPrefix: "--tt-dataviz-color-",
28
27
  dtcgType: "color"
29
28
  }, {
30
29
  path: "semantic.dataviz.encoding.opacity.",
31
- cssPrefix: "--tt-dataviz-",
30
+ cssPrefix: "--tt-dataviz-encoding-opacity-",
32
31
  dtcgType: "number"
33
32
  }, {
34
33
  path: "semantic.dataviz.geo.",
35
- cssPrefix: "--tt-dataviz-",
34
+ cssPrefix: "--tt-dataviz-geo-",
36
35
  dtcgType: "color"
37
36
  }, {
38
37
  path: "semantic.dataviz.encoding.",
39
- cssPrefix: "--tt-dataviz-",
40
- dtcgType: "string"
38
+ cssPrefix: "--tt-dataviz-encoding-"
41
39
  }, {
42
40
  path: "semantic.dataviz.",
43
- cssPrefix: "--tt-dataviz-",
44
- dtcgType: "string"
41
+ cssPrefix: "--tt-dataviz-"
45
42
  }, {
46
43
  path: "core.colors.",
47
44
  cssPrefix: "--tt-core-colors-",
@@ -72,12 +69,10 @@ var TOKEN_PATH_REGISTRY = [{
72
69
  dtcgType: "dimension"
73
70
  }, {
74
71
  path: "core.font.optical.",
75
- cssPrefix: "--tt-core-font-optical-",
76
- dtcgType: "string"
72
+ cssPrefix: "--tt-core-font-optical-"
77
73
  }, {
78
74
  path: "core.font.numeric.",
79
- cssPrefix: "--tt-core-font-numeric-",
80
- dtcgType: "string"
75
+ cssPrefix: "--tt-core-font-numeric-"
81
76
  }, {
82
77
  path: "core.font.scale.",
83
78
  cssPrefix: "--tt-core-font-scale-",
@@ -88,8 +83,7 @@ var TOKEN_PATH_REGISTRY = [{
88
83
  dtcgType: "dimension"
89
84
  }, {
90
85
  path: "core.sizing.behavior.",
91
- cssPrefix: "--tt-core-sizing-behavior-",
92
- dtcgType: "string"
86
+ cssPrefix: "--tt-core-sizing-behavior-"
93
87
  }, {
94
88
  path: "core.sizing.",
95
89
  cssPrefix: "--tt-core-sizing-",
@@ -104,8 +98,7 @@ var TOKEN_PATH_REGISTRY = [{
104
98
  dtcgType: "dimension"
105
99
  }, {
106
100
  path: "core.border.style.",
107
- cssPrefix: "--tt-core-border-style-",
108
- dtcgType: "string"
101
+ cssPrefix: "--tt-core-border-style-"
109
102
  }, {
110
103
  path: "core.opacity.",
111
104
  cssPrefix: "--tt-core-opacity-",
@@ -116,8 +109,7 @@ var TOKEN_PATH_REGISTRY = [{
116
109
  dtcgType: "duration"
117
110
  }, {
118
111
  path: "core.motion.easing.",
119
- cssPrefix: "--tt-core-motion-easing-",
120
- dtcgType: "string"
112
+ cssPrefix: "--tt-core-motion-easing-"
121
113
  }, {
122
114
  path: "core.zIndex.level.",
123
115
  cssPrefix: "--tt-core-z-index-",
@@ -136,8 +128,7 @@ var TOKEN_PATH_REGISTRY = [{
136
128
  dtcgType: "shadow"
137
129
  }, {
138
130
  path: "semantic.text.",
139
- cssPrefix: "--tt-text-",
140
- dtcgType: "string"
131
+ cssPrefix: "--tt-text-"
141
132
  }, {
142
133
  path: "semantic.spacing.",
143
134
  cssPrefix: "--tt-spacing-",
@@ -152,24 +143,21 @@ var TOKEN_PATH_REGISTRY = [{
152
143
  dtcgType: "dimension"
153
144
  }, {
154
145
  path: "semantic.focus.",
155
- cssPrefix: "--tt-focus-",
156
- dtcgType: "string"
146
+ cssPrefix: "--tt-focus-"
157
147
  }, {
158
148
  path: "semantic.overlay.",
159
149
  cssPrefix: "--tt-overlay-",
160
150
  dtcgType: "color"
161
151
  }, {
162
152
  path: "semantic.border.",
163
- cssPrefix: "--tt-border-",
164
- dtcgType: "string"
153
+ cssPrefix: "--tt-border-"
165
154
  }, {
166
155
  path: "semantic.opacity.",
167
156
  cssPrefix: "--tt-opacity-",
168
157
  dtcgType: "number"
169
158
  }, {
170
159
  path: "semantic.motion.",
171
- cssPrefix: "--tt-motion-",
172
- dtcgType: "string"
160
+ cssPrefix: "--tt-motion-"
173
161
  }, {
174
162
  path: "semantic.zIndex.layer.",
175
163
  cssPrefix: "--tt-z-index-",
@@ -179,7 +167,10 @@ var TOKEN_PATH_REGISTRY = [{
179
167
  var CSS_PATH_PREFIXES = TOKEN_PATH_REGISTRY.map(e => {
180
168
  return [e.path, e.cssPrefix];
181
169
  });
182
- /** Derived lookup table: `[path, dtcgType]` pairs. */
170
+ /**
171
+ * Derived lookup table: `[path, dtcgType]` pairs. `dtcgType` is `undefined` for
172
+ * opaque tokens — `toDTCG` omits `$type` when the matched entry has none.
173
+ */
183
174
  var DTCG_TYPE_PREFIXES = TOKEN_PATH_REGISTRY.map(e => {
184
175
  return [e.path, e.dtcgType];
185
176
  });