@ttoss/fsl-theme 2.0.0 → 2.1.1
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/README.md +41 -30
- package/dist/{Types-BkaxtsMv.d.cts → Types-a_sfpbha.d.cts} +23 -11
- package/dist/{Types-BkaxtsMv.d.mts → Types-a_sfpbha.d.mts} +23 -11
- package/dist/{baseBundle-R7WyjyTw.mjs → baseBundle-CXdxj_re.mjs} +1 -1
- package/dist/{baseBundle-DdRSa5mf.cjs → baseBundle-a9LDsqYy.cjs} +1 -1
- package/dist/{createTheme--UFgVlEn.cjs → createTheme-B7H0xSVp.cjs} +95 -17
- package/dist/{createTheme-B6tVIikC.mjs → createTheme-Bm9LVQ4w.mjs} +96 -18
- package/dist/css.cjs +4 -3
- package/dist/css.d.cts +21 -2
- package/dist/css.d.mts +21 -2
- package/dist/css.mjs +4 -3
- package/dist/dataviz/index.cjs +2 -2
- package/dist/dataviz/index.d.cts +1 -1
- package/dist/dataviz/index.d.mts +1 -1
- package/dist/dataviz/index.mjs +1 -1
- package/dist/dtcg.d.cts +1 -1
- package/dist/dtcg.d.mts +1 -1
- package/dist/{index-7YL90Xqs.d.cts → index-BmZQVd3G.d.cts} +1 -1
- package/dist/{index-BmCTIOzh.d.mts → index-XZsRrMTF.d.mts} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +1 -1
- package/dist/{react-Dt7vGAVG.d.cts → react-2D_CNVF2.d.cts} +50 -19
- package/dist/{react-C8JrZf-b.d.mts → react-BwnHHvJj.d.mts} +50 -19
- package/dist/{react-DLB8nrXQ.cjs → react-DQErMGJz.cjs} +90 -18
- package/dist/react.cjs +3 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.mjs +92 -18
- package/dist/runtime-entry.cjs +1 -1
- package/dist/runtime-entry.mjs +1 -1
- package/dist/{ssrScript-MF5SIONH.cjs → ssrScript-D73WcrCM.cjs} +51 -3
- package/dist/{ssrScript-DNLlLDps.mjs → ssrScript-zDK1dqXr.mjs} +51 -3
- package/dist/themes/bruttal.cjs +2 -2
- package/dist/themes/bruttal.d.cts +1 -1
- package/dist/themes/bruttal.d.mts +1 -1
- package/dist/themes/bruttal.mjs +2 -2
- package/dist/{toCssVars-C-9yIZDR.mjs → toCssVars-CWTlp9St.mjs} +38 -3
- package/dist/{toCssVars-BseqmMcT.cjs → toCssVars-D0BR23cP.cjs} +38 -3
- package/dist/vars.cjs +7 -7
- package/dist/vars.d.cts +6 -6
- package/dist/vars.d.mts +6 -6
- package/dist/vars.mjs +7 -7
- package/dist/{withDataviz-CYRf4exT.cjs → withDataviz-DrUtw8cc.cjs} +1 -1
- package/dist/{withDataviz-BvpZJnFx.mjs → withDataviz-Ln3sEwb7.mjs} +1 -1
- package/llms.txt +6 -4
- package/package.json +8 -4
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
import { i as ThemeBundle, n as SemanticTokens } from "./Types-
|
|
2
|
+
import { i as ThemeBundle, n as SemanticTokens } from "./Types-a_sfpbha.mjs";
|
|
3
3
|
import { a as ResolvedMode, o as ThemeMode, t as ThemeScriptConfig } from "./ssrScript-BVysxDws.mjs";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
6
|
//#region src/react.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Accepted shapes for the `root` prop: a concrete element, or a ref object.
|
|
9
|
+
* Prefer the ref form — refs are populated before layout effects run, so the
|
|
10
|
+
* runtime attaches directly to the target element with no transient attach
|
|
11
|
+
* to `<html>` on the first render (the element form is `null` during the
|
|
12
|
+
* first render when read from `ref.current`).
|
|
13
|
+
*/
|
|
14
|
+
type ThemeRootInput = HTMLElement | React.RefObject<HTMLElement | null>;
|
|
7
15
|
/**
|
|
8
16
|
* Props for the `ThemeProvider` component.
|
|
9
17
|
*/
|
|
@@ -83,25 +91,32 @@ interface ThemeProviderProps {
|
|
|
83
91
|
onModeChange?: (mode: ThemeMode, resolvedMode: ResolvedMode) => void;
|
|
84
92
|
/**
|
|
85
93
|
* Root element to anchor `data-tt-theme` / `data-tt-mode` attributes.
|
|
86
|
-
* Defaults to `document.documentElement`. Pass a container
|
|
87
|
-
*
|
|
94
|
+
* Defaults to `document.documentElement`. Pass a container for Storybook
|
|
95
|
+
* isolation or micro-frontend use cases.
|
|
88
96
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
97
|
+
* **Must be paired with `themeId`.** Without a `themeId`, the generated CSS
|
|
98
|
+
* targets `:root` / `:root[data-tt-mode="dark"]` (the `<html>` element),
|
|
99
|
+
* while the attributes are written to this element — the alternate mode CSS
|
|
100
|
+
* would never match. A dev-mode warning fires on this combination.
|
|
101
|
+
*
|
|
102
|
+
* **Prefer passing the ref object itself** (`root={rootRef}`): refs are
|
|
103
|
+
* populated before layout effects, so the runtime attaches directly to the
|
|
104
|
+
* element. Passing `rootRef.current ?? undefined` also works, but the value
|
|
105
|
+
* is `null` on the first render, causing one transient attach to `<html>`
|
|
106
|
+
* before the runtime is recreated on the element.
|
|
92
107
|
*
|
|
93
108
|
* @example
|
|
94
109
|
* ```tsx
|
|
95
110
|
* // Storybook decorator — isolates each story from <html>
|
|
96
111
|
* const rootRef = React.useRef<HTMLDivElement>(null);
|
|
97
112
|
* <div ref={rootRef}>
|
|
98
|
-
* <ThemeProvider theme={myTheme} root={rootRef
|
|
113
|
+
* <ThemeProvider theme={myTheme} themeId="story" root={rootRef}>
|
|
99
114
|
* <Story />
|
|
100
115
|
* </ThemeProvider>
|
|
101
116
|
* </div>
|
|
102
117
|
* ```
|
|
103
118
|
*/
|
|
104
|
-
root?:
|
|
119
|
+
root?: ThemeRootInput;
|
|
105
120
|
children: React.ReactNode;
|
|
106
121
|
}
|
|
107
122
|
/**
|
|
@@ -134,7 +149,7 @@ declare const ThemeProvider: ({
|
|
|
134
149
|
onModeChange,
|
|
135
150
|
root,
|
|
136
151
|
children
|
|
137
|
-
}: ThemeProviderProps) =>
|
|
152
|
+
}: ThemeProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
138
153
|
/**
|
|
139
154
|
* Access mode state and the mode setter — without subscribing to theme changes.
|
|
140
155
|
*
|
|
@@ -178,11 +193,11 @@ declare const useColorMode: () => UseColorModeResult;
|
|
|
178
193
|
* inline styles produces silently broken rendering:
|
|
179
194
|
*
|
|
180
195
|
* ```tsx
|
|
181
|
-
* // ✗ WRONG —
|
|
182
|
-
* <div style={{ color: tokens.colors.
|
|
196
|
+
* // ✗ WRONG — the leaf is '{core.colors.neutral.1000}' (a ref string), not a CSS color
|
|
197
|
+
* <div style={{ color: tokens.colors.action.primary.text.default }} />
|
|
183
198
|
*
|
|
184
199
|
* // ✓ CSS consumers — use vars:
|
|
185
|
-
* <div style={{ color: 'var(--tt-
|
|
200
|
+
* <div style={{ color: 'var(--tt-colors-action-primary-text-default)' }} />
|
|
186
201
|
*
|
|
187
202
|
* // ✓ Non-CSS consumers (React Native, canvas) — use useResolvedTokens():
|
|
188
203
|
* const resolved = useResolvedTokens();
|
|
@@ -197,8 +212,8 @@ declare const useColorMode: () => UseColorModeResult;
|
|
|
197
212
|
*
|
|
198
213
|
* const Button = () => {
|
|
199
214
|
* const tokens = useTokens(); // introspection only
|
|
200
|
-
* // tokens.colors.action.primary.background.default → '{core.colors.
|
|
201
|
-
* return <button style={{ background: 'var(--tt-action-primary-background-default)' }} />;
|
|
215
|
+
* // tokens.colors.action.primary.background.default → '{core.colors.neutral.1000}'
|
|
216
|
+
* return <button style={{ background: 'var(--tt-colors-action-primary-background-default)' }} />;
|
|
202
217
|
* };
|
|
203
218
|
* ```
|
|
204
219
|
*/
|
|
@@ -218,13 +233,21 @@ declare const useTokens: () => SemanticTokens;
|
|
|
218
233
|
*
|
|
219
234
|
* ```tsx
|
|
220
235
|
* // ✓ CSS (browser)
|
|
221
|
-
* <div style={{ color: 'var(--tt-
|
|
236
|
+
* <div style={{ color: 'var(--tt-colors-informational-primary-text-default)' }} />
|
|
222
237
|
*
|
|
223
238
|
* // ✓ Non-CSS (React Native, canvas)
|
|
224
239
|
* const resolved = useResolvedTokens();
|
|
225
240
|
* <View style={{ backgroundColor: resolved['semantic.colors.action.primary.background.default'] }} />
|
|
226
241
|
* ```
|
|
227
242
|
*
|
|
243
|
+
* ### ⚠ CSS-coupled tokens stay unresolved
|
|
244
|
+
* A registered set of dimensional tokens (model.md §8 — spacing steps, fluid
|
|
245
|
+
* `text.*.fontSize`, `sizing.hit.*`, `sizing.viewport.*`, `sizing.measure.reading`,
|
|
246
|
+
* `spacing.gutter.*`) carry CSS-only constructs (`var()`, `calc()`, `clamp()`,
|
|
247
|
+
* `cqi`, `dvh`, `ch`). This hook returns those **as-is** — they are not usable
|
|
248
|
+
* outside a CSS engine. Colors, opacity, z-index, font weights/leading and
|
|
249
|
+
* other scalar tokens resolve to plain raw values and are safe everywhere.
|
|
250
|
+
*
|
|
228
251
|
* Requires `<ThemeProvider theme={...}>`.
|
|
229
252
|
*
|
|
230
253
|
* @example
|
|
@@ -276,7 +299,7 @@ declare const ThemeScript: ({
|
|
|
276
299
|
defaultMode,
|
|
277
300
|
storageKey,
|
|
278
301
|
nonce
|
|
279
|
-
}?: ThemeScriptProps) =>
|
|
302
|
+
}?: ThemeScriptProps) => import("react/jsx-runtime").JSX.Element;
|
|
280
303
|
/**
|
|
281
304
|
* Props for the `ThemeStyles` component.
|
|
282
305
|
*/
|
|
@@ -296,6 +319,13 @@ interface ThemeStylesProps {
|
|
|
296
319
|
themeId?: string;
|
|
297
320
|
/** CSP nonce for the inline style tag. */
|
|
298
321
|
nonce?: string;
|
|
322
|
+
/**
|
|
323
|
+
* Emit the `@media (prefers-color-scheme)` fallback block (themeId-less
|
|
324
|
+
* bundles only). Default `true`. Set `false` when the app's `defaultMode`
|
|
325
|
+
* is a fixed `'light'`/`'dark'` rather than `'system'` — `<ThemeHead>`
|
|
326
|
+
* derives this automatically from its `defaultMode`.
|
|
327
|
+
*/
|
|
328
|
+
systemModeFallback?: boolean;
|
|
299
329
|
}
|
|
300
330
|
/**
|
|
301
331
|
* Renders an inline `<style>` tag with all CSS Custom Properties for a theme
|
|
@@ -346,8 +376,9 @@ interface ThemeStylesProps {
|
|
|
346
376
|
declare const ThemeStyles: ({
|
|
347
377
|
theme,
|
|
348
378
|
themeId,
|
|
349
|
-
nonce
|
|
350
|
-
|
|
379
|
+
nonce,
|
|
380
|
+
systemModeFallback
|
|
381
|
+
}: ThemeStylesProps) => import("react/jsx-runtime").JSX.Element;
|
|
351
382
|
/**
|
|
352
383
|
* Props for the `ThemeHead` component.
|
|
353
384
|
*/
|
|
@@ -406,6 +437,6 @@ declare const ThemeHead: ({
|
|
|
406
437
|
nonce,
|
|
407
438
|
defaultMode,
|
|
408
439
|
storageKey
|
|
409
|
-
}: ThemeHeadProps) =>
|
|
440
|
+
}: ThemeHeadProps) => import("react/jsx-runtime").JSX.Element;
|
|
410
441
|
//#endregion
|
|
411
442
|
export { ThemeScript as a, ThemeStylesProps as c, useResolvedTokens as d, useTokens as f, ThemeProviderProps as i, UseColorModeResult as l, ThemeHeadProps as n, ThemeScriptProps as o, ThemeProvider as r, ThemeStyles as s, ThemeHead as t, useColorMode as u };
|
|
@@ -28,7 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
//#endregion
|
|
29
29
|
const require_helpers = require('./helpers-BU40JvFA.cjs');
|
|
30
30
|
const require_css = require('./css.cjs');
|
|
31
|
-
const require_ssrScript = require('./ssrScript-
|
|
31
|
+
const require_ssrScript = require('./ssrScript-D73WcrCM.cjs');
|
|
32
32
|
let react = require("react");
|
|
33
33
|
react = __toESM(react);
|
|
34
34
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -45,6 +45,57 @@ let react_jsx_runtime = require("react/jsx-runtime");
|
|
|
45
45
|
var themeStyleHref = themeId => {
|
|
46
46
|
return `tt-theme-${themeId ?? "root"}`;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* DEV-only registry of injected theme CSS per hoisted-style `href`.
|
|
50
|
+
* React dedups hoisted `<style>` tags by `href`, so two providers with
|
|
51
|
+
* *different* themes but the same `href` silently drop the second theme's
|
|
52
|
+
* CSS. This registry detects that mismatch and warns.
|
|
53
|
+
*/
|
|
54
|
+
var injectedThemeCss = /* @__PURE__ */new Map();
|
|
55
|
+
/**
|
|
56
|
+
* Runs on the client before paint; falls back to `useEffect` on the server
|
|
57
|
+
* so SSR renders never warn. Used for the runtime-creation effect so
|
|
58
|
+
* `data-tt-*` attributes land before first paint in CSR apps (less mode flash).
|
|
59
|
+
*/
|
|
60
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
61
|
+
var resolveRootElement = root => {
|
|
62
|
+
if (!root) return void 0;
|
|
63
|
+
if (root instanceof HTMLElement) return root;
|
|
64
|
+
return root.current ?? void 0;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The OS-preference CSS fallback only makes sense when the app follows the
|
|
68
|
+
* OS. A fixed `'light'`/`'dark'` default must not let `prefers-color-scheme`
|
|
69
|
+
* override it on first paint.
|
|
70
|
+
*/
|
|
71
|
+
var shouldEmitSystemFallback = defaultMode => {
|
|
72
|
+
return (defaultMode ?? "system") === "system";
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* DEV-only: warn when two providers with different themes share the same
|
|
76
|
+
* hoisted-style `href` — React dedups by href, so the second theme's CSS is
|
|
77
|
+
* silently dropped. Distinct `themeId`s give distinct hrefs and coexist.
|
|
78
|
+
*/
|
|
79
|
+
var useDedupMismatchWarning = (cssContent, themeId) => {
|
|
80
|
+
react.useEffect(() => {
|
|
81
|
+
if (process.env.NODE_ENV === "production" || !cssContent) return;
|
|
82
|
+
const href = themeStyleHref(themeId);
|
|
83
|
+
const entry = injectedThemeCss.get(href);
|
|
84
|
+
if (entry && entry.css !== cssContent) console.warn(`[fsl-theme] Two <ThemeProvider>s with different themes share the same style key "${href}". React dedups hoisted <style> tags by href, so only the first theme's CSS is injected. Give each theme a distinct themeId: <ThemeProvider theme={...} themeId="my-theme">.`);
|
|
85
|
+
const next = entry ?? {
|
|
86
|
+
css: cssContent,
|
|
87
|
+
count: 0
|
|
88
|
+
};
|
|
89
|
+
next.count += 1;
|
|
90
|
+
injectedThemeCss.set(href, next);
|
|
91
|
+
return () => {
|
|
92
|
+
const current = injectedThemeCss.get(href);
|
|
93
|
+
if (!current) return;
|
|
94
|
+
current.count -= 1;
|
|
95
|
+
if (current.count <= 0) injectedThemeCss.delete(href);
|
|
96
|
+
};
|
|
97
|
+
}, [cssContent, themeId]);
|
|
98
|
+
};
|
|
48
99
|
var COARSE_QUERY = "(any-pointer: coarse)";
|
|
49
100
|
/**
|
|
50
101
|
* Subscribe to coarse-pointer media query changes.
|
|
@@ -127,20 +178,26 @@ var ThemeProvider = ({
|
|
|
127
178
|
children
|
|
128
179
|
}) => {
|
|
129
180
|
const runtimeRef = react.useRef(null);
|
|
130
|
-
const
|
|
131
|
-
const
|
|
181
|
+
const [initialDefaultMode] = react.useState(defaultMode);
|
|
182
|
+
const [initialStorageKey] = react.useState(storageKey);
|
|
132
183
|
const [state, setState] = react.useState(() => {
|
|
133
184
|
return {
|
|
134
185
|
mode: defaultMode ?? "system",
|
|
135
186
|
resolvedMode: (defaultMode ?? "system") === "system" ? "light" : defaultMode
|
|
136
187
|
};
|
|
137
188
|
});
|
|
138
|
-
react.
|
|
189
|
+
const [rootRetry, setRootRetry] = react.useState(0);
|
|
190
|
+
useIsomorphicLayoutEffect(() => {
|
|
191
|
+
if (root !== void 0 && !(root instanceof HTMLElement) && root.current === null && rootRetry === 0) {
|
|
192
|
+
setRootRetry(1);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (process.env.NODE_ENV !== "production" && root && !themeId) console.warn("[fsl-theme] `root` was passed without `themeId`. Without a themeId, the generated CSS targets `:root`/`:root[data-tt-mode=\"dark\"]` on <html>, but mode attributes are written to the `root` element — the alternate mode CSS will never match. Pass a `themeId` so CSS is scoped to `[data-tt-theme=\"<id>\"]` selectors that match the `root` element.");
|
|
139
196
|
const runtime = require_ssrScript.createThemeRuntime({
|
|
140
197
|
defaultTheme: themeId,
|
|
141
|
-
defaultMode:
|
|
142
|
-
storageKey:
|
|
143
|
-
root
|
|
198
|
+
defaultMode: initialDefaultMode,
|
|
199
|
+
storageKey: initialStorageKey,
|
|
200
|
+
root: resolveRootElement(root)
|
|
144
201
|
});
|
|
145
202
|
runtimeRef.current = runtime;
|
|
146
203
|
setState(runtime.getState());
|
|
@@ -150,7 +207,7 @@ var ThemeProvider = ({
|
|
|
150
207
|
runtime.destroy();
|
|
151
208
|
runtimeRef.current = null;
|
|
152
209
|
};
|
|
153
|
-
}, [root, themeId]);
|
|
210
|
+
}, [root, themeId, rootRetry]);
|
|
154
211
|
const setMode = react.useCallback(mode => {
|
|
155
212
|
runtimeRef.current?.setMode(mode);
|
|
156
213
|
}, []);
|
|
@@ -169,8 +226,11 @@ var ThemeProvider = ({
|
|
|
169
226
|
return applyCoarseHitOverrides(result, theme.base, isCoarse);
|
|
170
227
|
}, [theme, semanticTokens, isCoarse]);
|
|
171
228
|
const cssContent = react.useMemo(() => {
|
|
172
|
-
return theme ? require_css.getThemeStylesContent(theme, themeId
|
|
229
|
+
return theme ? require_css.getThemeStylesContent(theme, themeId, {
|
|
230
|
+
systemModeFallback: shouldEmitSystemFallback(initialDefaultMode)
|
|
231
|
+
}) : null;
|
|
173
232
|
}, [theme, themeId]);
|
|
233
|
+
useDedupMismatchWarning(cssContent, themeId);
|
|
174
234
|
const onModeChangeRef = react.useRef(onModeChange);
|
|
175
235
|
react.useEffect(() => {
|
|
176
236
|
onModeChangeRef.current = onModeChange;
|
|
@@ -231,11 +291,11 @@ var useColorMode = () => {
|
|
|
231
291
|
* inline styles produces silently broken rendering:
|
|
232
292
|
*
|
|
233
293
|
* ```tsx
|
|
234
|
-
* // ✗ WRONG —
|
|
235
|
-
* <div style={{ color: tokens.colors.
|
|
294
|
+
* // ✗ WRONG — the leaf is '{core.colors.neutral.1000}' (a ref string), not a CSS color
|
|
295
|
+
* <div style={{ color: tokens.colors.action.primary.text.default }} />
|
|
236
296
|
*
|
|
237
297
|
* // ✓ CSS consumers — use vars:
|
|
238
|
-
* <div style={{ color: 'var(--tt-
|
|
298
|
+
* <div style={{ color: 'var(--tt-colors-action-primary-text-default)' }} />
|
|
239
299
|
*
|
|
240
300
|
* // ✓ Non-CSS consumers (React Native, canvas) — use useResolvedTokens():
|
|
241
301
|
* const resolved = useResolvedTokens();
|
|
@@ -250,8 +310,8 @@ var useColorMode = () => {
|
|
|
250
310
|
*
|
|
251
311
|
* const Button = () => {
|
|
252
312
|
* const tokens = useTokens(); // introspection only
|
|
253
|
-
* // tokens.colors.action.primary.background.default → '{core.colors.
|
|
254
|
-
* return <button style={{ background: 'var(--tt-action-primary-background-default)' }} />;
|
|
313
|
+
* // tokens.colors.action.primary.background.default → '{core.colors.neutral.1000}'
|
|
314
|
+
* return <button style={{ background: 'var(--tt-colors-action-primary-background-default)' }} />;
|
|
255
315
|
* };
|
|
256
316
|
* ```
|
|
257
317
|
*/
|
|
@@ -276,13 +336,21 @@ var useTokens = () => {
|
|
|
276
336
|
*
|
|
277
337
|
* ```tsx
|
|
278
338
|
* // ✓ CSS (browser)
|
|
279
|
-
* <div style={{ color: 'var(--tt-
|
|
339
|
+
* <div style={{ color: 'var(--tt-colors-informational-primary-text-default)' }} />
|
|
280
340
|
*
|
|
281
341
|
* // ✓ Non-CSS (React Native, canvas)
|
|
282
342
|
* const resolved = useResolvedTokens();
|
|
283
343
|
* <View style={{ backgroundColor: resolved['semantic.colors.action.primary.background.default'] }} />
|
|
284
344
|
* ```
|
|
285
345
|
*
|
|
346
|
+
* ### ⚠ CSS-coupled tokens stay unresolved
|
|
347
|
+
* A registered set of dimensional tokens (model.md §8 — spacing steps, fluid
|
|
348
|
+
* `text.*.fontSize`, `sizing.hit.*`, `sizing.viewport.*`, `sizing.measure.reading`,
|
|
349
|
+
* `spacing.gutter.*`) carry CSS-only constructs (`var()`, `calc()`, `clamp()`,
|
|
350
|
+
* `cqi`, `dvh`, `ch`). This hook returns those **as-is** — they are not usable
|
|
351
|
+
* outside a CSS engine. Colors, opacity, z-index, font weights/leading and
|
|
352
|
+
* other scalar tokens resolve to plain raw values and are safe everywhere.
|
|
353
|
+
*
|
|
286
354
|
* Requires `<ThemeProvider theme={...}>`.
|
|
287
355
|
*
|
|
288
356
|
* @example
|
|
@@ -393,12 +461,15 @@ var ThemeScript = ({
|
|
|
393
461
|
var ThemeStyles = ({
|
|
394
462
|
theme,
|
|
395
463
|
themeId,
|
|
396
|
-
nonce
|
|
464
|
+
nonce,
|
|
465
|
+
systemModeFallback
|
|
397
466
|
}) => {
|
|
398
467
|
return /* @__PURE__ */(0, react_jsx_runtime.jsx)("style", {
|
|
399
468
|
nonce,
|
|
400
469
|
dangerouslySetInnerHTML: {
|
|
401
|
-
__html: require_css.getThemeStylesContent(theme, themeId
|
|
470
|
+
__html: require_css.getThemeStylesContent(theme, themeId, {
|
|
471
|
+
systemModeFallback
|
|
472
|
+
})
|
|
402
473
|
}
|
|
403
474
|
});
|
|
404
475
|
};
|
|
@@ -461,7 +532,8 @@ var ThemeHead = ({
|
|
|
461
532
|
}), /* @__PURE__ */(0, react_jsx_runtime.jsx)(ThemeStyles, {
|
|
462
533
|
theme,
|
|
463
534
|
themeId,
|
|
464
|
-
nonce
|
|
535
|
+
nonce,
|
|
536
|
+
systemModeFallback: shouldEmitSystemFallback(defaultMode)
|
|
465
537
|
})]
|
|
466
538
|
});
|
|
467
539
|
};
|
package/dist/react.cjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
2
4
|
Object.defineProperty(exports, Symbol.toStringTag, {
|
|
3
5
|
value: 'Module'
|
|
4
6
|
});
|
|
5
|
-
const require_react = require('./react-
|
|
7
|
+
const require_react = require('./react-DQErMGJz.cjs');
|
|
6
8
|
exports.ThemeHead = require_react.ThemeHead;
|
|
7
9
|
exports.ThemeProvider = require_react.ThemeProvider;
|
|
8
10
|
exports.ThemeScript = require_react.ThemeScript;
|
package/dist/react.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
2
|
import { a as ResolvedMode, o as ThemeMode } from "./ssrScript-BVysxDws.cjs";
|
|
3
|
-
import { a as ThemeScript, c as ThemeStylesProps, d as useResolvedTokens, f as useTokens, i as ThemeProviderProps, l as UseColorModeResult, n as ThemeHeadProps, o as ThemeScriptProps, r as ThemeProvider, s as ThemeStyles, t as ThemeHead, u as useColorMode } from "./react-
|
|
3
|
+
import { a as ThemeScript, c as ThemeStylesProps, d as useResolvedTokens, f as useTokens, i as ThemeProviderProps, l as UseColorModeResult, n as ThemeHeadProps, o as ThemeScriptProps, r as ThemeProvider, s as ThemeStyles, t as ThemeHead, u as useColorMode } from "./react-2D_CNVF2.cjs";
|
|
4
4
|
export { type ResolvedMode, ThemeHead, ThemeHeadProps, type ThemeMode, ThemeProvider, ThemeProviderProps, ThemeScript, ThemeScriptProps, ThemeStyles, ThemeStylesProps, UseColorModeResult, useColorMode, useResolvedTokens, useTokens };
|
package/dist/react.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
2
|
import { a as ResolvedMode, o as ThemeMode } from "./ssrScript-BVysxDws.mjs";
|
|
3
|
-
import { a as ThemeScript, c as ThemeStylesProps, d as useResolvedTokens, f as useTokens, i as ThemeProviderProps, l as UseColorModeResult, n as ThemeHeadProps, o as ThemeScriptProps, r as ThemeProvider, s as ThemeStyles, t as ThemeHead, u as useColorMode } from "./react-
|
|
3
|
+
import { a as ThemeScript, c as ThemeStylesProps, d as useResolvedTokens, f as useTokens, i as ThemeProviderProps, l as UseColorModeResult, n as ThemeHeadProps, o as ThemeScriptProps, r as ThemeProvider, s as ThemeStyles, t as ThemeHead, u as useColorMode } from "./react-BwnHHvJj.mjs";
|
|
4
4
|
export { type ResolvedMode, ThemeHead, ThemeHeadProps, type ThemeMode, ThemeProvider, ThemeProviderProps, ThemeScript, ThemeScriptProps, ThemeStyles, ThemeStylesProps, UseColorModeResult, useColorMode, useResolvedTokens, useTokens };
|
package/dist/react.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
2
4
|
import { r as deepMerge, s as toFlatTokens } from "./helpers-BXaKe2s3.mjs";
|
|
3
5
|
import { getThemeStylesContent } from "./css.mjs";
|
|
4
|
-
import { i as createThemeRuntime, t as getThemeScriptContent } from "./ssrScript-
|
|
6
|
+
import { i as createThemeRuntime, t as getThemeScriptContent } from "./ssrScript-zDK1dqXr.mjs";
|
|
5
7
|
import * as React from "react";
|
|
6
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
9
|
|
|
@@ -17,6 +19,57 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
17
19
|
var themeStyleHref = themeId => {
|
|
18
20
|
return `tt-theme-${themeId ?? "root"}`;
|
|
19
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* DEV-only registry of injected theme CSS per hoisted-style `href`.
|
|
24
|
+
* React dedups hoisted `<style>` tags by `href`, so two providers with
|
|
25
|
+
* *different* themes but the same `href` silently drop the second theme's
|
|
26
|
+
* CSS. This registry detects that mismatch and warns.
|
|
27
|
+
*/
|
|
28
|
+
var injectedThemeCss = /* @__PURE__ */new Map();
|
|
29
|
+
/**
|
|
30
|
+
* Runs on the client before paint; falls back to `useEffect` on the server
|
|
31
|
+
* so SSR renders never warn. Used for the runtime-creation effect so
|
|
32
|
+
* `data-tt-*` attributes land before first paint in CSR apps (less mode flash).
|
|
33
|
+
*/
|
|
34
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
35
|
+
var resolveRootElement = root => {
|
|
36
|
+
if (!root) return void 0;
|
|
37
|
+
if (root instanceof HTMLElement) return root;
|
|
38
|
+
return root.current ?? void 0;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The OS-preference CSS fallback only makes sense when the app follows the
|
|
42
|
+
* OS. A fixed `'light'`/`'dark'` default must not let `prefers-color-scheme`
|
|
43
|
+
* override it on first paint.
|
|
44
|
+
*/
|
|
45
|
+
var shouldEmitSystemFallback = defaultMode => {
|
|
46
|
+
return (defaultMode ?? "system") === "system";
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* DEV-only: warn when two providers with different themes share the same
|
|
50
|
+
* hoisted-style `href` — React dedups by href, so the second theme's CSS is
|
|
51
|
+
* silently dropped. Distinct `themeId`s give distinct hrefs and coexist.
|
|
52
|
+
*/
|
|
53
|
+
var useDedupMismatchWarning = (cssContent, themeId) => {
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
if (process.env.NODE_ENV === "production" || !cssContent) return;
|
|
56
|
+
const href = themeStyleHref(themeId);
|
|
57
|
+
const entry = injectedThemeCss.get(href);
|
|
58
|
+
if (entry && entry.css !== cssContent) console.warn(`[fsl-theme] Two <ThemeProvider>s with different themes share the same style key "${href}". React dedups hoisted <style> tags by href, so only the first theme's CSS is injected. Give each theme a distinct themeId: <ThemeProvider theme={...} themeId="my-theme">.`);
|
|
59
|
+
const next = entry ?? {
|
|
60
|
+
css: cssContent,
|
|
61
|
+
count: 0
|
|
62
|
+
};
|
|
63
|
+
next.count += 1;
|
|
64
|
+
injectedThemeCss.set(href, next);
|
|
65
|
+
return () => {
|
|
66
|
+
const current = injectedThemeCss.get(href);
|
|
67
|
+
if (!current) return;
|
|
68
|
+
current.count -= 1;
|
|
69
|
+
if (current.count <= 0) injectedThemeCss.delete(href);
|
|
70
|
+
};
|
|
71
|
+
}, [cssContent, themeId]);
|
|
72
|
+
};
|
|
20
73
|
var COARSE_QUERY = "(any-pointer: coarse)";
|
|
21
74
|
/**
|
|
22
75
|
* Subscribe to coarse-pointer media query changes.
|
|
@@ -99,20 +152,26 @@ var ThemeProvider = ({
|
|
|
99
152
|
children
|
|
100
153
|
}) => {
|
|
101
154
|
const runtimeRef = React.useRef(null);
|
|
102
|
-
const
|
|
103
|
-
const
|
|
155
|
+
const [initialDefaultMode] = React.useState(defaultMode);
|
|
156
|
+
const [initialStorageKey] = React.useState(storageKey);
|
|
104
157
|
const [state, setState] = React.useState(() => {
|
|
105
158
|
return {
|
|
106
159
|
mode: defaultMode ?? "system",
|
|
107
160
|
resolvedMode: (defaultMode ?? "system") === "system" ? "light" : defaultMode
|
|
108
161
|
};
|
|
109
162
|
});
|
|
110
|
-
React.
|
|
163
|
+
const [rootRetry, setRootRetry] = React.useState(0);
|
|
164
|
+
useIsomorphicLayoutEffect(() => {
|
|
165
|
+
if (root !== void 0 && !(root instanceof HTMLElement) && root.current === null && rootRetry === 0) {
|
|
166
|
+
setRootRetry(1);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (process.env.NODE_ENV !== "production" && root && !themeId) console.warn("[fsl-theme] `root` was passed without `themeId`. Without a themeId, the generated CSS targets `:root`/`:root[data-tt-mode=\"dark\"]` on <html>, but mode attributes are written to the `root` element — the alternate mode CSS will never match. Pass a `themeId` so CSS is scoped to `[data-tt-theme=\"<id>\"]` selectors that match the `root` element.");
|
|
111
170
|
const runtime = createThemeRuntime({
|
|
112
171
|
defaultTheme: themeId,
|
|
113
|
-
defaultMode:
|
|
114
|
-
storageKey:
|
|
115
|
-
root
|
|
172
|
+
defaultMode: initialDefaultMode,
|
|
173
|
+
storageKey: initialStorageKey,
|
|
174
|
+
root: resolveRootElement(root)
|
|
116
175
|
});
|
|
117
176
|
runtimeRef.current = runtime;
|
|
118
177
|
setState(runtime.getState());
|
|
@@ -122,7 +181,7 @@ var ThemeProvider = ({
|
|
|
122
181
|
runtime.destroy();
|
|
123
182
|
runtimeRef.current = null;
|
|
124
183
|
};
|
|
125
|
-
}, [root, themeId]);
|
|
184
|
+
}, [root, themeId, rootRetry]);
|
|
126
185
|
const setMode = React.useCallback(mode => {
|
|
127
186
|
runtimeRef.current?.setMode(mode);
|
|
128
187
|
}, []);
|
|
@@ -141,8 +200,11 @@ var ThemeProvider = ({
|
|
|
141
200
|
return applyCoarseHitOverrides(result, theme.base, isCoarse);
|
|
142
201
|
}, [theme, semanticTokens, isCoarse]);
|
|
143
202
|
const cssContent = React.useMemo(() => {
|
|
144
|
-
return theme ? getThemeStylesContent(theme, themeId
|
|
203
|
+
return theme ? getThemeStylesContent(theme, themeId, {
|
|
204
|
+
systemModeFallback: shouldEmitSystemFallback(initialDefaultMode)
|
|
205
|
+
}) : null;
|
|
145
206
|
}, [theme, themeId]);
|
|
207
|
+
useDedupMismatchWarning(cssContent, themeId);
|
|
146
208
|
const onModeChangeRef = React.useRef(onModeChange);
|
|
147
209
|
React.useEffect(() => {
|
|
148
210
|
onModeChangeRef.current = onModeChange;
|
|
@@ -203,11 +265,11 @@ var useColorMode = () => {
|
|
|
203
265
|
* inline styles produces silently broken rendering:
|
|
204
266
|
*
|
|
205
267
|
* ```tsx
|
|
206
|
-
* // ✗ WRONG —
|
|
207
|
-
* <div style={{ color: tokens.colors.
|
|
268
|
+
* // ✗ WRONG — the leaf is '{core.colors.neutral.1000}' (a ref string), not a CSS color
|
|
269
|
+
* <div style={{ color: tokens.colors.action.primary.text.default }} />
|
|
208
270
|
*
|
|
209
271
|
* // ✓ CSS consumers — use vars:
|
|
210
|
-
* <div style={{ color: 'var(--tt-
|
|
272
|
+
* <div style={{ color: 'var(--tt-colors-action-primary-text-default)' }} />
|
|
211
273
|
*
|
|
212
274
|
* // ✓ Non-CSS consumers (React Native, canvas) — use useResolvedTokens():
|
|
213
275
|
* const resolved = useResolvedTokens();
|
|
@@ -222,8 +284,8 @@ var useColorMode = () => {
|
|
|
222
284
|
*
|
|
223
285
|
* const Button = () => {
|
|
224
286
|
* const tokens = useTokens(); // introspection only
|
|
225
|
-
* // tokens.colors.action.primary.background.default → '{core.colors.
|
|
226
|
-
* return <button style={{ background: 'var(--tt-action-primary-background-default)' }} />;
|
|
287
|
+
* // tokens.colors.action.primary.background.default → '{core.colors.neutral.1000}'
|
|
288
|
+
* return <button style={{ background: 'var(--tt-colors-action-primary-background-default)' }} />;
|
|
227
289
|
* };
|
|
228
290
|
* ```
|
|
229
291
|
*/
|
|
@@ -248,13 +310,21 @@ var useTokens = () => {
|
|
|
248
310
|
*
|
|
249
311
|
* ```tsx
|
|
250
312
|
* // ✓ CSS (browser)
|
|
251
|
-
* <div style={{ color: 'var(--tt-
|
|
313
|
+
* <div style={{ color: 'var(--tt-colors-informational-primary-text-default)' }} />
|
|
252
314
|
*
|
|
253
315
|
* // ✓ Non-CSS (React Native, canvas)
|
|
254
316
|
* const resolved = useResolvedTokens();
|
|
255
317
|
* <View style={{ backgroundColor: resolved['semantic.colors.action.primary.background.default'] }} />
|
|
256
318
|
* ```
|
|
257
319
|
*
|
|
320
|
+
* ### ⚠ CSS-coupled tokens stay unresolved
|
|
321
|
+
* A registered set of dimensional tokens (model.md §8 — spacing steps, fluid
|
|
322
|
+
* `text.*.fontSize`, `sizing.hit.*`, `sizing.viewport.*`, `sizing.measure.reading`,
|
|
323
|
+
* `spacing.gutter.*`) carry CSS-only constructs (`var()`, `calc()`, `clamp()`,
|
|
324
|
+
* `cqi`, `dvh`, `ch`). This hook returns those **as-is** — they are not usable
|
|
325
|
+
* outside a CSS engine. Colors, opacity, z-index, font weights/leading and
|
|
326
|
+
* other scalar tokens resolve to plain raw values and are safe everywhere.
|
|
327
|
+
*
|
|
258
328
|
* Requires `<ThemeProvider theme={...}>`.
|
|
259
329
|
*
|
|
260
330
|
* @example
|
|
@@ -365,12 +435,15 @@ var ThemeScript = ({
|
|
|
365
435
|
var ThemeStyles = ({
|
|
366
436
|
theme,
|
|
367
437
|
themeId,
|
|
368
|
-
nonce
|
|
438
|
+
nonce,
|
|
439
|
+
systemModeFallback
|
|
369
440
|
}) => {
|
|
370
441
|
return /* @__PURE__ */jsx("style", {
|
|
371
442
|
nonce,
|
|
372
443
|
dangerouslySetInnerHTML: {
|
|
373
|
-
__html: getThemeStylesContent(theme, themeId
|
|
444
|
+
__html: getThemeStylesContent(theme, themeId, {
|
|
445
|
+
systemModeFallback
|
|
446
|
+
})
|
|
374
447
|
}
|
|
375
448
|
});
|
|
376
449
|
};
|
|
@@ -433,7 +506,8 @@ var ThemeHead = ({
|
|
|
433
506
|
}), /* @__PURE__ */jsx(ThemeStyles, {
|
|
434
507
|
theme,
|
|
435
508
|
themeId,
|
|
436
|
-
nonce
|
|
509
|
+
nonce,
|
|
510
|
+
systemModeFallback: shouldEmitSystemFallback(defaultMode)
|
|
437
511
|
})]
|
|
438
512
|
});
|
|
439
513
|
};
|
package/dist/runtime-entry.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, {
|
|
3
3
|
value: 'Module'
|
|
4
4
|
});
|
|
5
|
-
const require_ssrScript = require('./ssrScript-
|
|
5
|
+
const require_ssrScript = require('./ssrScript-D73WcrCM.cjs');
|
|
6
6
|
exports.DATA_MODE_ATTR = require_ssrScript.DATA_MODE_ATTR;
|
|
7
7
|
exports.DATA_THEME_ATTR = require_ssrScript.DATA_THEME_ATTR;
|
|
8
8
|
exports.createThemeRuntime = require_ssrScript.createThemeRuntime;
|
package/dist/runtime-entry.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
-
import { i as createThemeRuntime, n as DATA_MODE_ATTR, r as DATA_THEME_ATTR, t as getThemeScriptContent } from "./ssrScript-
|
|
2
|
+
import { i as createThemeRuntime, n as DATA_MODE_ATTR, r as DATA_THEME_ATTR, t as getThemeScriptContent } from "./ssrScript-zDK1dqXr.mjs";
|
|
3
3
|
export { DATA_MODE_ATTR, DATA_THEME_ATTR, createThemeRuntime, getThemeScriptContent };
|