@thunderid/react 1.9.0 → 1.9.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.
@@ -1,6 +1,7 @@
1
1
  const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
2
2
  const require_FlowMetaContext = require('../FlowMeta/FlowMetaContext.cjs');
3
3
  const require_ThemeContext = require('./ThemeContext.cjs');
4
+ const require_ThemeRootContext = require('./ThemeRootContext.cjs');
4
5
  const require_applyThemeToDOM = require('../../utils/applyThemeToDOM.cjs');
5
6
  const require_buildThemeConfigFromFlowMeta = require('../../utils/buildThemeConfigFromFlowMeta.cjs');
6
7
  const require_normalizeThemeConfig = require('../../utils/normalizeThemeConfig.cjs');
@@ -53,6 +54,11 @@ const ThemeProvider = ({ children, designTheme = void 0, mode, theme: themeOverr
53
54
  return flowMetaTheme?.defaultColorScheme ?? "light";
54
55
  };
55
56
  const [colorScheme, setColorScheme] = (0, react.useState)(getInitialColorScheme);
57
+ const [seededMode, setSeededMode] = (0, react.useState)(mode);
58
+ if (mode !== seededMode) {
59
+ setSeededMode(mode);
60
+ if (mode !== void 0) setColorScheme(getInitialColorScheme());
61
+ }
56
62
  (0, react.useEffect)(() => {
57
63
  if (mode === void 0 && flowMetaTheme?.defaultColorScheme) setColorScheme(flowMetaTheme.defaultColorScheme);
58
64
  }, [mode, flowMetaTheme?.defaultColorScheme]);
@@ -103,12 +109,33 @@ const ThemeProvider = ({ children, designTheme = void 0, mode, theme: themeOverr
103
109
  ]);
104
110
  const theme = (0, react.useMemo)(() => (0, __thunderid_browser.createTheme)(finalThemeConfig, colorScheme === "dark"), [finalThemeConfig, colorScheme]);
105
111
  const direction = flowMetaTheme?.direction ?? "ltr";
112
+ const parentRoot = (0, react.useContext)(require_ThemeRootContext.default);
113
+ const claims = (0, react.useRef)(0);
114
+ const applied = (0, react.useRef)(null);
115
+ const applyToRoot = (0, react.useCallback)(() => {
116
+ if (!applied.current || typeof document === "undefined") return;
117
+ require_applyThemeToDOM.default(applied.current.theme);
118
+ document.documentElement.dir = applied.current.direction;
119
+ }, []);
120
+ const root = (0, react.useMemo)(() => ({ claim: () => {
121
+ claims.current += 1;
122
+ return () => {
123
+ claims.current -= 1;
124
+ if (claims.current === 0) applyToRoot();
125
+ };
126
+ } }), [applyToRoot]);
127
+ (0, react.useEffect)(() => parentRoot?.claim(), [parentRoot]);
106
128
  (0, react.useEffect)(() => {
107
- require_applyThemeToDOM.default(theme);
108
- }, [theme]);
109
- (0, react.useEffect)(() => {
110
- if (typeof document !== "undefined") document.documentElement.dir = direction;
111
- }, [direction]);
129
+ applied.current = {
130
+ direction,
131
+ theme
132
+ };
133
+ if (claims.current === 0) applyToRoot();
134
+ }, [
135
+ applyToRoot,
136
+ direction,
137
+ theme
138
+ ]);
112
139
  const value = {
113
140
  colorScheme,
114
141
  direction,
@@ -117,7 +144,10 @@ const ThemeProvider = ({ children, designTheme = void 0, mode, theme: themeOverr
117
144
  };
118
145
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ThemeContext.default.Provider, {
119
146
  value,
120
- children
147
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ThemeRootContext.default.Provider, {
148
+ value: root,
149
+ children
150
+ })
121
151
  });
122
152
  };
123
153
  var ThemeProvider_default = ThemeProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.cjs","names":["ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>>","themeOverride: RecursivePartial<ThemeConfig> | undefined","normalizeThemeConfig","flowMetaContext: FlowMetaContextValue | null","FlowMetaContext","flowMetaTheme: FlowMetaTheme | null","toggleTheme: () => void","finalThemeConfig: RecursivePartial<ThemeConfig> | undefined","metaConfig: RecursivePartial<ThemeConfig>","buildThemeConfigFromFlowMeta","theme: Theme","direction: 'ltr' | 'rtl'","value: any","ThemeContext"],"sources":["../../../../src/contexts/Theme/ThemeProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createClassObserver,\n createMediaQueryListener,\n createTheme,\n detectThemeMode,\n FlowMetaTheme,\n RecursivePartial,\n Theme,\n ThemeConfig,\n ThemeMode,\n} from '@thunderid/browser';\nimport {FC, PropsWithChildren, ReactElement, useCallback, useContext, useEffect, useMemo, useState} from 'react';\nimport ThemeContext from './ThemeContext';\nimport applyThemeToDOM from '../../utils/applyThemeToDOM';\nimport buildThemeConfigFromFlowMeta from '../../utils/buildThemeConfigFromFlowMeta';\nimport normalizeThemeConfig from '../../utils/normalizeThemeConfig';\nimport FlowMetaContext, {FlowMetaContextValue} from '../FlowMeta/FlowMetaContext';\n\nexport interface ThemeProviderProps {\n /**\n * A Design theme to use instead of the one the server returns in flow meta, for example the theme an\n * app configuration names. `theme` overrides are still applied on top.\n */\n designTheme?: FlowMetaTheme | null;\n /**\n * Initial color scheme override. Overrides the server default when provided.\n */\n mode?: ThemeMode;\n\n /**\n * Optional theme overrides merged on top of the server-side flow meta theme.\n * User-supplied values take highest precedence.\n */\n theme?: RecursivePartial<ThemeConfig>;\n}\n\n/**\n * ThemeProvider is the v2 drop-in replacement for `ThemeProvider`.\n *\n * It reads the design theme from the nearest `FlowMetaContext` (provided by\n * `FlowMetaProvider`) and publishes a resolved `Theme` object through the\n * **same** `ThemeContext` that `useTheme` consumes. This means all existing\n * components that call `useTheme` continue to work without any changes.\n *\n * The `defaultColorScheme` field returned by the server is used to seed the\n * active color scheme; the user can still toggle it locally via the\n * `toggleTheme` value exposed in the context.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider config={{ baseUrl, type: FlowMetaType.App, id: appId }}>\n * <ThemeProvider>\n * <App /> {/* useTheme() works here as usual *\\/}\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n *\n * @example\n * With user theme overrides (user values win over server values):\n * ```tsx\n * <ThemeProvider theme={{ colors: { primary: { main: '#ff0000' } } }}>\n * <App />\n * </ThemeProvider>\n * ```\n */\nconst ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({\n children,\n designTheme = undefined,\n mode,\n theme: themeOverrideProp,\n}: PropsWithChildren<ThemeProviderProps>): ReactElement => {\n const themeOverride: RecursivePartial<ThemeConfig> | undefined = normalizeThemeConfig(themeOverrideProp);\n const flowMetaContext: FlowMetaContextValue | null = useContext(FlowMetaContext);\n\n const flowMetaTheme: FlowMetaTheme | null = designTheme ?? flowMetaContext?.meta?.design?.theme ?? null;\n const isLoading: boolean = flowMetaContext?.isLoading ?? false;\n const error: Error | null = flowMetaContext?.error ?? null;\n\n // Seed the color scheme: a fixed mode wins outright, 'system'/'class' detect it from the\n // environment via the same shared utilities Vue's ThemeProvider uses, and no mode falls back\n // to the server's default (unchanged pre-`mode` behavior).\n const getInitialColorScheme = (): 'light' | 'dark' => {\n if (mode === 'light' || mode === 'dark') return mode;\n if (mode === 'system' || mode === 'class') return detectThemeMode(mode);\n return flowMetaTheme?.defaultColorScheme ?? 'light';\n };\n\n const [colorScheme, setColorScheme] = useState<'light' | 'dark'>(getInitialColorScheme);\n\n // When mode is unset, sync the color scheme with the server default once flow meta loads.\n useEffect(() => {\n if (mode === undefined && flowMetaTheme?.defaultColorScheme) {\n setColorScheme(flowMetaTheme.defaultColorScheme);\n }\n }, [mode, flowMetaTheme?.defaultColorScheme]);\n\n // Set up automatic detection listeners for 'system'/'class' modes.\n useEffect(() => {\n if (mode !== 'system' && mode !== 'class') return undefined;\n\n const handleThemeChange = (isDark: boolean): void => {\n setColorScheme(isDark ? 'dark' : 'light');\n };\n\n if (mode === 'class') {\n if (typeof document === 'undefined') return undefined;\n const observer = createClassObserver(document.documentElement, handleThemeChange);\n return () => {\n observer.disconnect();\n };\n }\n\n const mediaQuery = createMediaQueryListener(handleThemeChange);\n return () => {\n mediaQuery?.disconnect();\n };\n }, [mode]);\n\n const toggleTheme: () => void = useCallback(() => {\n setColorScheme((prev: 'light' | 'dark') => (prev === 'light' ? 'dark' : 'light'));\n }, []);\n\n // Build the resolved ThemeConfig: flow meta base → user overrides on top.\n const finalThemeConfig: RecursivePartial<ThemeConfig> | undefined = useMemo(() => {\n if (!flowMetaTheme) {\n return themeOverride;\n }\n\n const metaConfig: RecursivePartial<ThemeConfig> = buildThemeConfigFromFlowMeta(flowMetaTheme, colorScheme);\n\n if (!themeOverride) {\n return metaConfig;\n }\n\n return {\n ...metaConfig,\n ...themeOverride,\n borderRadius: {\n ...metaConfig.borderRadius,\n ...themeOverride.borderRadius,\n },\n colors: {\n ...metaConfig.colors,\n ...themeOverride.colors,\n },\n ...(metaConfig.typography || themeOverride.typography\n ? {\n typography: {\n ...(metaConfig as any).typography,\n ...themeOverride.typography,\n },\n }\n : {}),\n };\n }, [flowMetaTheme, colorScheme, themeOverride]);\n\n const theme: Theme = useMemo(\n () => createTheme(finalThemeConfig, colorScheme === 'dark'),\n [finalThemeConfig, colorScheme],\n );\n\n const direction: 'ltr' | 'rtl' = flowMetaTheme?.direction ?? 'ltr';\n\n // Apply CSS variables to the document root.\n useEffect(() => {\n applyThemeToDOM(theme);\n }, [theme]);\n\n // Apply text direction to the document root.\n useEffect(() => {\n if (typeof document !== 'undefined') {\n document.documentElement.dir = direction;\n }\n }, [direction]);\n\n const value: any = {\n colorScheme,\n direction,\n theme,\n toggleTheme,\n };\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n};\n\nexport default ThemeProvider;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,MAAMA,iBAA4D,EAChE,UACA,cAAc,QACd,MACA,OAAO,wBACkD;CACzD,MAAMC,gBAA2DC,qCAAqB,kBAAkB;CACxG,MAAMC,wCAA0DC,gCAAgB;CAEhF,MAAMC,gBAAsC,eAAe,iBAAiB,MAAM,QAAQ,SAAS;AACxE,kBAAiB;AAChB,kBAAiB;CAK7C,MAAM,8BAAgD;AACpD,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAChD,MAAI,SAAS,YAAY,SAAS,QAAS,iDAAuB,KAAK;AACvE,SAAO,eAAe,sBAAsB;;CAG9C,MAAM,CAAC,aAAa,sCAA6C,sBAAsB;AAGvF,4BAAgB;AACd,MAAI,SAAS,UAAa,eAAe,mBACvC,gBAAe,cAAc,mBAAmB;IAEjD,CAAC,MAAM,eAAe,mBAAmB,CAAC;AAG7C,4BAAgB;AACd,MAAI,SAAS,YAAY,SAAS,QAAS,QAAO;EAElD,MAAM,qBAAqB,WAA0B;AACnD,kBAAe,SAAS,SAAS,QAAQ;;AAG3C,MAAI,SAAS,SAAS;AACpB,OAAI,OAAO,aAAa,YAAa,QAAO;GAC5C,MAAM,wDAA+B,SAAS,iBAAiB,kBAAkB;AACjF,gBAAa;AACX,aAAS,YAAY;;;EAIzB,MAAM,+DAAsC,kBAAkB;AAC9D,eAAa;AACX,eAAY,YAAY;;IAEzB,CAAC,KAAK,CAAC;CAEV,MAAMC,2CAA4C;AAChD,kBAAgB,SAA4B,SAAS,UAAU,SAAS,QAAS;IAChF,EAAE,CAAC;CAGN,MAAMC,4CAA4E;AAChF,MAAI,CAAC,cACH,QAAO;EAGT,MAAMC,aAA4CC,6CAA6B,eAAe,YAAY;AAE1G,MAAI,CAAC,cACH,QAAO;AAGT,SAAO;GACL,GAAG;GACH,GAAG;GACH,cAAc;IACZ,GAAG,WAAW;IACd,GAAG,cAAc;IAClB;GACD,QAAQ;IACN,GAAG,WAAW;IACd,GAAG,cAAc;IAClB;GACD,GAAI,WAAW,cAAc,cAAc,aACvC,EACE,YAAY;IACV,GAAI,WAAmB;IACvB,GAAG,cAAc;IAClB,EACF,GACD,EAAE;GACP;IACA;EAAC;EAAe;EAAa;EAAc,CAAC;CAE/C,MAAMC,sEACc,kBAAkB,gBAAgB,OAAO,EAC3D,CAAC,kBAAkB,YAAY,CAChC;CAED,MAAMC,YAA2B,eAAe,aAAa;AAG7D,4BAAgB;AACd,kCAAgB,MAAM;IACrB,CAAC,MAAM,CAAC;AAGX,4BAAgB;AACd,MAAI,OAAO,aAAa,YACtB,UAAS,gBAAgB,MAAM;IAEhC,CAAC,UAAU,CAAC;CAEf,MAAMC,QAAa;EACjB;EACA;EACA;EACA;EACD;AAED,QAAO,2CAACC,6BAAa;EAAgB;EAAQ;GAAiC;;AAGhF,4BAAe"}
1
+ {"version":3,"file":"ThemeProvider.cjs","names":["ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>>","themeOverride: RecursivePartial<ThemeConfig> | undefined","normalizeThemeConfig","flowMetaContext: FlowMetaContextValue | null","FlowMetaContext","flowMetaTheme: FlowMetaTheme | null","toggleTheme: () => void","finalThemeConfig: RecursivePartial<ThemeConfig> | undefined","metaConfig: RecursivePartial<ThemeConfig>","buildThemeConfigFromFlowMeta","theme: Theme","direction: 'ltr' | 'rtl'","parentRoot: ThemeRootContextValue | null","ThemeRootContext","claims: MutableRefObject<number>","applied: MutableRefObject<{direction: 'ltr' | 'rtl'; theme: Theme} | null>","applyToRoot: () => void","root: ThemeRootContextValue","value: any","ThemeContext"],"sources":["../../../../src/contexts/Theme/ThemeProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createClassObserver,\n createMediaQueryListener,\n createTheme,\n detectThemeMode,\n FlowMetaTheme,\n RecursivePartial,\n Theme,\n ThemeConfig,\n ThemeMode,\n} from '@thunderid/browser';\nimport {\n FC,\n MutableRefObject,\n PropsWithChildren,\n ReactElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport ThemeContext from './ThemeContext';\nimport ThemeRootContext, {ThemeRootContextValue} from './ThemeRootContext';\nimport applyThemeToDOM from '../../utils/applyThemeToDOM';\nimport buildThemeConfigFromFlowMeta from '../../utils/buildThemeConfigFromFlowMeta';\nimport normalizeThemeConfig from '../../utils/normalizeThemeConfig';\nimport FlowMetaContext, {FlowMetaContextValue} from '../FlowMeta/FlowMetaContext';\n\nexport interface ThemeProviderProps {\n /**\n * A Design theme to use instead of the one the server returns in flow meta, for example the theme an\n * app configuration names. `theme` overrides are still applied on top.\n */\n designTheme?: FlowMetaTheme | null;\n /**\n * Color scheme override. Overrides the server default when provided, and the color scheme follows it\n * when it changes.\n */\n mode?: ThemeMode;\n\n /**\n * Optional theme overrides merged on top of the server-side flow meta theme.\n * User-supplied values take highest precedence.\n */\n theme?: RecursivePartial<ThemeConfig>;\n}\n\n/**\n * ThemeProvider is the v2 drop-in replacement for `ThemeProvider`.\n *\n * It reads the design theme from the nearest `FlowMetaContext` (provided by\n * `FlowMetaProvider`) and publishes a resolved `Theme` object through the\n * **same** `ThemeContext` that `useTheme` consumes. This means all existing\n * components that call `useTheme` continue to work without any changes.\n *\n * The `defaultColorScheme` field returned by the server is used to seed the\n * active color scheme; the user can still toggle it locally via the\n * `toggleTheme` value exposed in the context.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider config={{ baseUrl, type: FlowMetaType.App, id: appId }}>\n * <ThemeProvider>\n * <App /> {/* useTheme() works here as usual *\\/}\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n *\n * @example\n * With user theme overrides (user values win over server values):\n * ```tsx\n * <ThemeProvider theme={{ colors: { primary: { main: '#ff0000' } } }}>\n * <App />\n * </ThemeProvider>\n * ```\n */\nconst ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({\n children,\n designTheme = undefined,\n mode,\n theme: themeOverrideProp,\n}: PropsWithChildren<ThemeProviderProps>): ReactElement => {\n const themeOverride: RecursivePartial<ThemeConfig> | undefined = normalizeThemeConfig(themeOverrideProp);\n const flowMetaContext: FlowMetaContextValue | null = useContext(FlowMetaContext);\n\n const flowMetaTheme: FlowMetaTheme | null = designTheme ?? flowMetaContext?.meta?.design?.theme ?? null;\n const isLoading: boolean = flowMetaContext?.isLoading ?? false;\n const error: Error | null = flowMetaContext?.error ?? null;\n\n // Seed the color scheme: a fixed mode wins outright, 'system'/'class' detect it from the\n // environment via the same shared utilities Vue's ThemeProvider uses, and no mode falls back\n // to the server's default (unchanged pre-`mode` behavior).\n const getInitialColorScheme = (): 'light' | 'dark' => {\n if (mode === 'light' || mode === 'dark') return mode;\n if (mode === 'system' || mode === 'class') return detectThemeMode(mode);\n return flowMetaTheme?.defaultColorScheme ?? 'light';\n };\n\n const [colorScheme, setColorScheme] = useState<'light' | 'dark'>(getInitialColorScheme);\n\n // Follow a change of mode after mount, such as a host app's own color scheme toggle.\n const [seededMode, setSeededMode] = useState<ThemeMode | undefined>(mode);\n\n if (mode !== seededMode) {\n setSeededMode(mode);\n if (mode !== undefined) {\n setColorScheme(getInitialColorScheme());\n }\n }\n\n // When mode is unset, sync the color scheme with the server default once flow meta loads.\n useEffect(() => {\n if (mode === undefined && flowMetaTheme?.defaultColorScheme) {\n setColorScheme(flowMetaTheme.defaultColorScheme);\n }\n }, [mode, flowMetaTheme?.defaultColorScheme]);\n\n // Set up automatic detection listeners for 'system'/'class' modes.\n useEffect(() => {\n if (mode !== 'system' && mode !== 'class') return undefined;\n\n const handleThemeChange = (isDark: boolean): void => {\n setColorScheme(isDark ? 'dark' : 'light');\n };\n\n if (mode === 'class') {\n if (typeof document === 'undefined') return undefined;\n const observer = createClassObserver(document.documentElement, handleThemeChange);\n return () => {\n observer.disconnect();\n };\n }\n\n const mediaQuery = createMediaQueryListener(handleThemeChange);\n return () => {\n mediaQuery?.disconnect();\n };\n }, [mode]);\n\n const toggleTheme: () => void = useCallback(() => {\n setColorScheme((prev: 'light' | 'dark') => (prev === 'light' ? 'dark' : 'light'));\n }, []);\n\n // Build the resolved ThemeConfig: flow meta base → user overrides on top.\n const finalThemeConfig: RecursivePartial<ThemeConfig> | undefined = useMemo(() => {\n if (!flowMetaTheme) {\n return themeOverride;\n }\n\n const metaConfig: RecursivePartial<ThemeConfig> = buildThemeConfigFromFlowMeta(flowMetaTheme, colorScheme);\n\n if (!themeOverride) {\n return metaConfig;\n }\n\n return {\n ...metaConfig,\n ...themeOverride,\n borderRadius: {\n ...metaConfig.borderRadius,\n ...themeOverride.borderRadius,\n },\n colors: {\n ...metaConfig.colors,\n ...themeOverride.colors,\n },\n ...(metaConfig.typography || themeOverride.typography\n ? {\n typography: {\n ...(metaConfig as any).typography,\n ...themeOverride.typography,\n },\n }\n : {}),\n };\n }, [flowMetaTheme, colorScheme, themeOverride]);\n\n const theme: Theme = useMemo(\n () => createTheme(finalThemeConfig, colorScheme === 'dark'),\n [finalThemeConfig, colorScheme],\n );\n\n const direction: 'ltr' | 'rtl' = flowMetaTheme?.direction ?? 'ltr';\n\n // The innermost ThemeProvider owns the document root: an outer one stops writing its CSS variables\n // and text direction while a nested one is mounted, and writes them again once it unmounts.\n const parentRoot: ThemeRootContextValue | null = useContext(ThemeRootContext);\n const claims: MutableRefObject<number> = useRef<number>(0);\n const applied: MutableRefObject<{direction: 'ltr' | 'rtl'; theme: Theme} | null> = useRef(null);\n\n const applyToRoot: () => void = useCallback(() => {\n if (!applied.current || typeof document === 'undefined') return;\n applyThemeToDOM(applied.current.theme);\n document.documentElement.dir = applied.current.direction;\n }, []);\n\n const root: ThemeRootContextValue = useMemo(\n () => ({\n claim: (): (() => void) => {\n claims.current += 1;\n return (): void => {\n claims.current -= 1;\n if (claims.current === 0) applyToRoot();\n };\n },\n }),\n [applyToRoot],\n );\n\n useEffect(() => parentRoot?.claim(), [parentRoot]);\n\n useEffect(() => {\n applied.current = {direction, theme};\n if (claims.current === 0) applyToRoot();\n }, [applyToRoot, direction, theme]);\n\n const value: any = {\n colorScheme,\n direction,\n theme,\n toggleTheme,\n };\n\n return (\n <ThemeContext.Provider value={value}>\n <ThemeRootContext.Provider value={root}>{children}</ThemeRootContext.Provider>\n </ThemeContext.Provider>\n );\n};\n\nexport default ThemeProvider;\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
2
+ let react = require("react");
3
+ react = require_rolldown_runtime.__toESM(react);
4
+
5
+ //#region src/contexts/Theme/ThemeRootContext.ts
6
+ const ThemeRootContext = (0, react.createContext)(null);
7
+ ThemeRootContext.displayName = "ThemeRootContext";
8
+ var ThemeRootContext_default = ThemeRootContext;
9
+
10
+ //#endregion
11
+ exports.default = ThemeRootContext_default;
12
+ //# sourceMappingURL=ThemeRootContext.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeRootContext.cjs","names":["ThemeRootContext: Context<ThemeRootContextValue | null>"],"sources":["../../../../src/contexts/Theme/ThemeRootContext.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {Context, createContext} from 'react';\n\nexport interface ThemeRootContextValue {\n /**\n * Hands the document root to a nested `ThemeProvider` until the returned release is called, so the\n * innermost provider's CSS variables and text direction are the ones on the page.\n */\n claim: () => () => void;\n}\n\nconst ThemeRootContext: Context<ThemeRootContextValue | null> = createContext<ThemeRootContextValue | null>(null);\n\nThemeRootContext.displayName = 'ThemeRootContext';\n\nexport default ThemeRootContext;\n"],"mappings":";;;;;AAaA,MAAMA,4CAAsG,KAAK;AAEjH,iBAAiB,cAAc;AAE/B,+BAAe"}
@@ -7,7 +7,8 @@ export interface ThemeProviderProps {
7
7
  */
8
8
  designTheme?: FlowMetaTheme | null;
9
9
  /**
10
- * Initial color scheme override. Overrides the server default when provided.
10
+ * Color scheme override. Overrides the server default when provided, and the color scheme follows it
11
+ * when it changes.
11
12
  */
12
13
  mode?: ThemeMode;
13
14
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../../src/contexts/Theme/ThemeProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAKL,aAAa,EACb,gBAAgB,EAEhB,WAAW,EACX,SAAS,EACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,EAAE,EAAE,iBAAiB,EAAsE,MAAM,OAAO,CAAC;AAOjH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IAEjB;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,aAAa,EAAE,EAAE,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAsH5D,CAAC;AAEF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../../src/contexts/Theme/ThemeProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAKL,aAAa,EACb,gBAAgB,EAEhB,WAAW,EACX,SAAS,EACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,EAAE,EAEF,iBAAiB,EAQlB,MAAM,OAAO,CAAC;AAQf,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IAEjB;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,aAAa,EAAE,EAAE,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAwJ5D,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -1,10 +1,11 @@
1
1
  import FlowMetaContext_default from "../FlowMeta/FlowMetaContext.js";
2
2
  import ThemeContext_default from "./ThemeContext.js";
3
+ import ThemeRootContext_default from "./ThemeRootContext.js";
3
4
  import applyThemeToDOM_default from "../../utils/applyThemeToDOM.js";
4
5
  import buildThemeConfigFromFlowMeta_default from "../../utils/buildThemeConfigFromFlowMeta.js";
5
6
  import normalizeThemeConfig_default from "../../utils/normalizeThemeConfig.js";
6
7
  import { createClassObserver, createMediaQueryListener, createTheme, detectThemeMode } from "@thunderid/browser";
7
- import { useCallback, useContext, useEffect, useMemo, useState } from "react";
8
+ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
8
9
  import { jsx } from "react/jsx-runtime";
9
10
 
10
11
  //#region src/contexts/Theme/ThemeProvider.tsx
@@ -49,6 +50,11 @@ const ThemeProvider = ({ children, designTheme = void 0, mode, theme: themeOverr
49
50
  return flowMetaTheme?.defaultColorScheme ?? "light";
50
51
  };
51
52
  const [colorScheme, setColorScheme] = useState(getInitialColorScheme);
53
+ const [seededMode, setSeededMode] = useState(mode);
54
+ if (mode !== seededMode) {
55
+ setSeededMode(mode);
56
+ if (mode !== void 0) setColorScheme(getInitialColorScheme());
57
+ }
52
58
  useEffect(() => {
53
59
  if (mode === void 0 && flowMetaTheme?.defaultColorScheme) setColorScheme(flowMetaTheme.defaultColorScheme);
54
60
  }, [mode, flowMetaTheme?.defaultColorScheme]);
@@ -99,12 +105,33 @@ const ThemeProvider = ({ children, designTheme = void 0, mode, theme: themeOverr
99
105
  ]);
100
106
  const theme = useMemo(() => createTheme(finalThemeConfig, colorScheme === "dark"), [finalThemeConfig, colorScheme]);
101
107
  const direction = flowMetaTheme?.direction ?? "ltr";
108
+ const parentRoot = useContext(ThemeRootContext_default);
109
+ const claims = useRef(0);
110
+ const applied = useRef(null);
111
+ const applyToRoot = useCallback(() => {
112
+ if (!applied.current || typeof document === "undefined") return;
113
+ applyThemeToDOM_default(applied.current.theme);
114
+ document.documentElement.dir = applied.current.direction;
115
+ }, []);
116
+ const root = useMemo(() => ({ claim: () => {
117
+ claims.current += 1;
118
+ return () => {
119
+ claims.current -= 1;
120
+ if (claims.current === 0) applyToRoot();
121
+ };
122
+ } }), [applyToRoot]);
123
+ useEffect(() => parentRoot?.claim(), [parentRoot]);
102
124
  useEffect(() => {
103
- applyThemeToDOM_default(theme);
104
- }, [theme]);
105
- useEffect(() => {
106
- if (typeof document !== "undefined") document.documentElement.dir = direction;
107
- }, [direction]);
125
+ applied.current = {
126
+ direction,
127
+ theme
128
+ };
129
+ if (claims.current === 0) applyToRoot();
130
+ }, [
131
+ applyToRoot,
132
+ direction,
133
+ theme
134
+ ]);
108
135
  const value = {
109
136
  colorScheme,
110
137
  direction,
@@ -113,7 +140,10 @@ const ThemeProvider = ({ children, designTheme = void 0, mode, theme: themeOverr
113
140
  };
114
141
  return /* @__PURE__ */ jsx(ThemeContext_default.Provider, {
115
142
  value,
116
- children
143
+ children: /* @__PURE__ */ jsx(ThemeRootContext_default.Provider, {
144
+ value: root,
145
+ children
146
+ })
117
147
  });
118
148
  };
119
149
  var ThemeProvider_default = ThemeProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.js","names":["ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>>","themeOverride: RecursivePartial<ThemeConfig> | undefined","normalizeThemeConfig","flowMetaContext: FlowMetaContextValue | null","FlowMetaContext","flowMetaTheme: FlowMetaTheme | null","toggleTheme: () => void","finalThemeConfig: RecursivePartial<ThemeConfig> | undefined","metaConfig: RecursivePartial<ThemeConfig>","buildThemeConfigFromFlowMeta","theme: Theme","direction: 'ltr' | 'rtl'","value: any","ThemeContext"],"sources":["../../../src/contexts/Theme/ThemeProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createClassObserver,\n createMediaQueryListener,\n createTheme,\n detectThemeMode,\n FlowMetaTheme,\n RecursivePartial,\n Theme,\n ThemeConfig,\n ThemeMode,\n} from '@thunderid/browser';\nimport {FC, PropsWithChildren, ReactElement, useCallback, useContext, useEffect, useMemo, useState} from 'react';\nimport ThemeContext from './ThemeContext';\nimport applyThemeToDOM from '../../utils/applyThemeToDOM';\nimport buildThemeConfigFromFlowMeta from '../../utils/buildThemeConfigFromFlowMeta';\nimport normalizeThemeConfig from '../../utils/normalizeThemeConfig';\nimport FlowMetaContext, {FlowMetaContextValue} from '../FlowMeta/FlowMetaContext';\n\nexport interface ThemeProviderProps {\n /**\n * A Design theme to use instead of the one the server returns in flow meta, for example the theme an\n * app configuration names. `theme` overrides are still applied on top.\n */\n designTheme?: FlowMetaTheme | null;\n /**\n * Initial color scheme override. Overrides the server default when provided.\n */\n mode?: ThemeMode;\n\n /**\n * Optional theme overrides merged on top of the server-side flow meta theme.\n * User-supplied values take highest precedence.\n */\n theme?: RecursivePartial<ThemeConfig>;\n}\n\n/**\n * ThemeProvider is the v2 drop-in replacement for `ThemeProvider`.\n *\n * It reads the design theme from the nearest `FlowMetaContext` (provided by\n * `FlowMetaProvider`) and publishes a resolved `Theme` object through the\n * **same** `ThemeContext` that `useTheme` consumes. This means all existing\n * components that call `useTheme` continue to work without any changes.\n *\n * The `defaultColorScheme` field returned by the server is used to seed the\n * active color scheme; the user can still toggle it locally via the\n * `toggleTheme` value exposed in the context.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider config={{ baseUrl, type: FlowMetaType.App, id: appId }}>\n * <ThemeProvider>\n * <App /> {/* useTheme() works here as usual *\\/}\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n *\n * @example\n * With user theme overrides (user values win over server values):\n * ```tsx\n * <ThemeProvider theme={{ colors: { primary: { main: '#ff0000' } } }}>\n * <App />\n * </ThemeProvider>\n * ```\n */\nconst ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({\n children,\n designTheme = undefined,\n mode,\n theme: themeOverrideProp,\n}: PropsWithChildren<ThemeProviderProps>): ReactElement => {\n const themeOverride: RecursivePartial<ThemeConfig> | undefined = normalizeThemeConfig(themeOverrideProp);\n const flowMetaContext: FlowMetaContextValue | null = useContext(FlowMetaContext);\n\n const flowMetaTheme: FlowMetaTheme | null = designTheme ?? flowMetaContext?.meta?.design?.theme ?? null;\n const isLoading: boolean = flowMetaContext?.isLoading ?? false;\n const error: Error | null = flowMetaContext?.error ?? null;\n\n // Seed the color scheme: a fixed mode wins outright, 'system'/'class' detect it from the\n // environment via the same shared utilities Vue's ThemeProvider uses, and no mode falls back\n // to the server's default (unchanged pre-`mode` behavior).\n const getInitialColorScheme = (): 'light' | 'dark' => {\n if (mode === 'light' || mode === 'dark') return mode;\n if (mode === 'system' || mode === 'class') return detectThemeMode(mode);\n return flowMetaTheme?.defaultColorScheme ?? 'light';\n };\n\n const [colorScheme, setColorScheme] = useState<'light' | 'dark'>(getInitialColorScheme);\n\n // When mode is unset, sync the color scheme with the server default once flow meta loads.\n useEffect(() => {\n if (mode === undefined && flowMetaTheme?.defaultColorScheme) {\n setColorScheme(flowMetaTheme.defaultColorScheme);\n }\n }, [mode, flowMetaTheme?.defaultColorScheme]);\n\n // Set up automatic detection listeners for 'system'/'class' modes.\n useEffect(() => {\n if (mode !== 'system' && mode !== 'class') return undefined;\n\n const handleThemeChange = (isDark: boolean): void => {\n setColorScheme(isDark ? 'dark' : 'light');\n };\n\n if (mode === 'class') {\n if (typeof document === 'undefined') return undefined;\n const observer = createClassObserver(document.documentElement, handleThemeChange);\n return () => {\n observer.disconnect();\n };\n }\n\n const mediaQuery = createMediaQueryListener(handleThemeChange);\n return () => {\n mediaQuery?.disconnect();\n };\n }, [mode]);\n\n const toggleTheme: () => void = useCallback(() => {\n setColorScheme((prev: 'light' | 'dark') => (prev === 'light' ? 'dark' : 'light'));\n }, []);\n\n // Build the resolved ThemeConfig: flow meta base → user overrides on top.\n const finalThemeConfig: RecursivePartial<ThemeConfig> | undefined = useMemo(() => {\n if (!flowMetaTheme) {\n return themeOverride;\n }\n\n const metaConfig: RecursivePartial<ThemeConfig> = buildThemeConfigFromFlowMeta(flowMetaTheme, colorScheme);\n\n if (!themeOverride) {\n return metaConfig;\n }\n\n return {\n ...metaConfig,\n ...themeOverride,\n borderRadius: {\n ...metaConfig.borderRadius,\n ...themeOverride.borderRadius,\n },\n colors: {\n ...metaConfig.colors,\n ...themeOverride.colors,\n },\n ...(metaConfig.typography || themeOverride.typography\n ? {\n typography: {\n ...(metaConfig as any).typography,\n ...themeOverride.typography,\n },\n }\n : {}),\n };\n }, [flowMetaTheme, colorScheme, themeOverride]);\n\n const theme: Theme = useMemo(\n () => createTheme(finalThemeConfig, colorScheme === 'dark'),\n [finalThemeConfig, colorScheme],\n );\n\n const direction: 'ltr' | 'rtl' = flowMetaTheme?.direction ?? 'ltr';\n\n // Apply CSS variables to the document root.\n useEffect(() => {\n applyThemeToDOM(theme);\n }, [theme]);\n\n // Apply text direction to the document root.\n useEffect(() => {\n if (typeof document !== 'undefined') {\n document.documentElement.dir = direction;\n }\n }, [direction]);\n\n const value: any = {\n colorScheme,\n direction,\n theme,\n toggleTheme,\n };\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n};\n\nexport default ThemeProvider;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,MAAMA,iBAA4D,EAChE,UACA,cAAc,QACd,MACA,OAAO,wBACkD;CACzD,MAAMC,gBAA2DC,6BAAqB,kBAAkB;CACxG,MAAMC,kBAA+C,WAAWC,wBAAgB;CAEhF,MAAMC,gBAAsC,eAAe,iBAAiB,MAAM,QAAQ,SAAS;AACxE,kBAAiB;AAChB,kBAAiB;CAK7C,MAAM,8BAAgD;AACpD,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAChD,MAAI,SAAS,YAAY,SAAS,QAAS,QAAO,gBAAgB,KAAK;AACvE,SAAO,eAAe,sBAAsB;;CAG9C,MAAM,CAAC,aAAa,kBAAkB,SAA2B,sBAAsB;AAGvF,iBAAgB;AACd,MAAI,SAAS,UAAa,eAAe,mBACvC,gBAAe,cAAc,mBAAmB;IAEjD,CAAC,MAAM,eAAe,mBAAmB,CAAC;AAG7C,iBAAgB;AACd,MAAI,SAAS,YAAY,SAAS,QAAS,QAAO;EAElD,MAAM,qBAAqB,WAA0B;AACnD,kBAAe,SAAS,SAAS,QAAQ;;AAG3C,MAAI,SAAS,SAAS;AACpB,OAAI,OAAO,aAAa,YAAa,QAAO;GAC5C,MAAM,WAAW,oBAAoB,SAAS,iBAAiB,kBAAkB;AACjF,gBAAa;AACX,aAAS,YAAY;;;EAIzB,MAAM,aAAa,yBAAyB,kBAAkB;AAC9D,eAAa;AACX,eAAY,YAAY;;IAEzB,CAAC,KAAK,CAAC;CAEV,MAAMC,cAA0B,kBAAkB;AAChD,kBAAgB,SAA4B,SAAS,UAAU,SAAS,QAAS;IAChF,EAAE,CAAC;CAGN,MAAMC,mBAA8D,cAAc;AAChF,MAAI,CAAC,cACH,QAAO;EAGT,MAAMC,aAA4CC,qCAA6B,eAAe,YAAY;AAE1G,MAAI,CAAC,cACH,QAAO;AAGT,SAAO;GACL,GAAG;GACH,GAAG;GACH,cAAc;IACZ,GAAG,WAAW;IACd,GAAG,cAAc;IAClB;GACD,QAAQ;IACN,GAAG,WAAW;IACd,GAAG,cAAc;IAClB;GACD,GAAI,WAAW,cAAc,cAAc,aACvC,EACE,YAAY;IACV,GAAI,WAAmB;IACvB,GAAG,cAAc;IAClB,EACF,GACD,EAAE;GACP;IACA;EAAC;EAAe;EAAa;EAAc,CAAC;CAE/C,MAAMC,QAAe,cACb,YAAY,kBAAkB,gBAAgB,OAAO,EAC3D,CAAC,kBAAkB,YAAY,CAChC;CAED,MAAMC,YAA2B,eAAe,aAAa;AAG7D,iBAAgB;AACd,0BAAgB,MAAM;IACrB,CAAC,MAAM,CAAC;AAGX,iBAAgB;AACd,MAAI,OAAO,aAAa,YACtB,UAAS,gBAAgB,MAAM;IAEhC,CAAC,UAAU,CAAC;CAEf,MAAMC,QAAa;EACjB;EACA;EACA;EACA;EACD;AAED,QAAO,oBAACC,qBAAa;EAAgB;EAAQ;GAAiC;;AAGhF,4BAAe"}
1
+ {"version":3,"file":"ThemeProvider.js","names":["ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>>","themeOverride: RecursivePartial<ThemeConfig> | undefined","normalizeThemeConfig","flowMetaContext: FlowMetaContextValue | null","FlowMetaContext","flowMetaTheme: FlowMetaTheme | null","toggleTheme: () => void","finalThemeConfig: RecursivePartial<ThemeConfig> | undefined","metaConfig: RecursivePartial<ThemeConfig>","buildThemeConfigFromFlowMeta","theme: Theme","direction: 'ltr' | 'rtl'","parentRoot: ThemeRootContextValue | null","ThemeRootContext","claims: MutableRefObject<number>","applied: MutableRefObject<{direction: 'ltr' | 'rtl'; theme: Theme} | null>","applyToRoot: () => void","root: ThemeRootContextValue","value: any","ThemeContext"],"sources":["../../../src/contexts/Theme/ThemeProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createClassObserver,\n createMediaQueryListener,\n createTheme,\n detectThemeMode,\n FlowMetaTheme,\n RecursivePartial,\n Theme,\n ThemeConfig,\n ThemeMode,\n} from '@thunderid/browser';\nimport {\n FC,\n MutableRefObject,\n PropsWithChildren,\n ReactElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport ThemeContext from './ThemeContext';\nimport ThemeRootContext, {ThemeRootContextValue} from './ThemeRootContext';\nimport applyThemeToDOM from '../../utils/applyThemeToDOM';\nimport buildThemeConfigFromFlowMeta from '../../utils/buildThemeConfigFromFlowMeta';\nimport normalizeThemeConfig from '../../utils/normalizeThemeConfig';\nimport FlowMetaContext, {FlowMetaContextValue} from '../FlowMeta/FlowMetaContext';\n\nexport interface ThemeProviderProps {\n /**\n * A Design theme to use instead of the one the server returns in flow meta, for example the theme an\n * app configuration names. `theme` overrides are still applied on top.\n */\n designTheme?: FlowMetaTheme | null;\n /**\n * Color scheme override. Overrides the server default when provided, and the color scheme follows it\n * when it changes.\n */\n mode?: ThemeMode;\n\n /**\n * Optional theme overrides merged on top of the server-side flow meta theme.\n * User-supplied values take highest precedence.\n */\n theme?: RecursivePartial<ThemeConfig>;\n}\n\n/**\n * ThemeProvider is the v2 drop-in replacement for `ThemeProvider`.\n *\n * It reads the design theme from the nearest `FlowMetaContext` (provided by\n * `FlowMetaProvider`) and publishes a resolved `Theme` object through the\n * **same** `ThemeContext` that `useTheme` consumes. This means all existing\n * components that call `useTheme` continue to work without any changes.\n *\n * The `defaultColorScheme` field returned by the server is used to seed the\n * active color scheme; the user can still toggle it locally via the\n * `toggleTheme` value exposed in the context.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider config={{ baseUrl, type: FlowMetaType.App, id: appId }}>\n * <ThemeProvider>\n * <App /> {/* useTheme() works here as usual *\\/}\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n *\n * @example\n * With user theme overrides (user values win over server values):\n * ```tsx\n * <ThemeProvider theme={{ colors: { primary: { main: '#ff0000' } } }}>\n * <App />\n * </ThemeProvider>\n * ```\n */\nconst ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({\n children,\n designTheme = undefined,\n mode,\n theme: themeOverrideProp,\n}: PropsWithChildren<ThemeProviderProps>): ReactElement => {\n const themeOverride: RecursivePartial<ThemeConfig> | undefined = normalizeThemeConfig(themeOverrideProp);\n const flowMetaContext: FlowMetaContextValue | null = useContext(FlowMetaContext);\n\n const flowMetaTheme: FlowMetaTheme | null = designTheme ?? flowMetaContext?.meta?.design?.theme ?? null;\n const isLoading: boolean = flowMetaContext?.isLoading ?? false;\n const error: Error | null = flowMetaContext?.error ?? null;\n\n // Seed the color scheme: a fixed mode wins outright, 'system'/'class' detect it from the\n // environment via the same shared utilities Vue's ThemeProvider uses, and no mode falls back\n // to the server's default (unchanged pre-`mode` behavior).\n const getInitialColorScheme = (): 'light' | 'dark' => {\n if (mode === 'light' || mode === 'dark') return mode;\n if (mode === 'system' || mode === 'class') return detectThemeMode(mode);\n return flowMetaTheme?.defaultColorScheme ?? 'light';\n };\n\n const [colorScheme, setColorScheme] = useState<'light' | 'dark'>(getInitialColorScheme);\n\n // Follow a change of mode after mount, such as a host app's own color scheme toggle.\n const [seededMode, setSeededMode] = useState<ThemeMode | undefined>(mode);\n\n if (mode !== seededMode) {\n setSeededMode(mode);\n if (mode !== undefined) {\n setColorScheme(getInitialColorScheme());\n }\n }\n\n // When mode is unset, sync the color scheme with the server default once flow meta loads.\n useEffect(() => {\n if (mode === undefined && flowMetaTheme?.defaultColorScheme) {\n setColorScheme(flowMetaTheme.defaultColorScheme);\n }\n }, [mode, flowMetaTheme?.defaultColorScheme]);\n\n // Set up automatic detection listeners for 'system'/'class' modes.\n useEffect(() => {\n if (mode !== 'system' && mode !== 'class') return undefined;\n\n const handleThemeChange = (isDark: boolean): void => {\n setColorScheme(isDark ? 'dark' : 'light');\n };\n\n if (mode === 'class') {\n if (typeof document === 'undefined') return undefined;\n const observer = createClassObserver(document.documentElement, handleThemeChange);\n return () => {\n observer.disconnect();\n };\n }\n\n const mediaQuery = createMediaQueryListener(handleThemeChange);\n return () => {\n mediaQuery?.disconnect();\n };\n }, [mode]);\n\n const toggleTheme: () => void = useCallback(() => {\n setColorScheme((prev: 'light' | 'dark') => (prev === 'light' ? 'dark' : 'light'));\n }, []);\n\n // Build the resolved ThemeConfig: flow meta base → user overrides on top.\n const finalThemeConfig: RecursivePartial<ThemeConfig> | undefined = useMemo(() => {\n if (!flowMetaTheme) {\n return themeOverride;\n }\n\n const metaConfig: RecursivePartial<ThemeConfig> = buildThemeConfigFromFlowMeta(flowMetaTheme, colorScheme);\n\n if (!themeOverride) {\n return metaConfig;\n }\n\n return {\n ...metaConfig,\n ...themeOverride,\n borderRadius: {\n ...metaConfig.borderRadius,\n ...themeOverride.borderRadius,\n },\n colors: {\n ...metaConfig.colors,\n ...themeOverride.colors,\n },\n ...(metaConfig.typography || themeOverride.typography\n ? {\n typography: {\n ...(metaConfig as any).typography,\n ...themeOverride.typography,\n },\n }\n : {}),\n };\n }, [flowMetaTheme, colorScheme, themeOverride]);\n\n const theme: Theme = useMemo(\n () => createTheme(finalThemeConfig, colorScheme === 'dark'),\n [finalThemeConfig, colorScheme],\n );\n\n const direction: 'ltr' | 'rtl' = flowMetaTheme?.direction ?? 'ltr';\n\n // The innermost ThemeProvider owns the document root: an outer one stops writing its CSS variables\n // and text direction while a nested one is mounted, and writes them again once it unmounts.\n const parentRoot: ThemeRootContextValue | null = useContext(ThemeRootContext);\n const claims: MutableRefObject<number> = useRef<number>(0);\n const applied: MutableRefObject<{direction: 'ltr' | 'rtl'; theme: Theme} | null> = useRef(null);\n\n const applyToRoot: () => void = useCallback(() => {\n if (!applied.current || typeof document === 'undefined') return;\n applyThemeToDOM(applied.current.theme);\n document.documentElement.dir = applied.current.direction;\n }, []);\n\n const root: ThemeRootContextValue = useMemo(\n () => ({\n claim: (): (() => void) => {\n claims.current += 1;\n return (): void => {\n claims.current -= 1;\n if (claims.current === 0) applyToRoot();\n };\n },\n }),\n [applyToRoot],\n );\n\n useEffect(() => parentRoot?.claim(), [parentRoot]);\n\n useEffect(() => {\n applied.current = {direction, theme};\n if (claims.current === 0) applyToRoot();\n }, [applyToRoot, direction, theme]);\n\n const value: any = {\n colorScheme,\n direction,\n theme,\n toggleTheme,\n };\n\n return (\n <ThemeContext.Provider value={value}>\n <ThemeRootContext.Provider value={root}>{children}</ThemeRootContext.Provider>\n </ThemeContext.Provider>\n );\n};\n\nexport default ThemeProvider;\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Context } from 'react';
2
+ export interface ThemeRootContextValue {
3
+ /**
4
+ * Hands the document root to a nested `ThemeProvider` until the returned release is called, so the
5
+ * innermost provider's CSS variables and text direction are the ones on the page.
6
+ */
7
+ claim: () => () => void;
8
+ }
9
+ declare const ThemeRootContext: Context<ThemeRootContextValue | null>;
10
+ export default ThemeRootContext;
11
+ //# sourceMappingURL=ThemeRootContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeRootContext.d.ts","sourceRoot":"","sources":["../../../src/contexts/Theme/ThemeRootContext.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,OAAO,EAAgB,MAAM,OAAO,CAAC;AAE7C,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,KAAK,EAAE,MAAM,MAAM,IAAI,CAAC;CACzB;AAED,QAAA,MAAM,gBAAgB,EAAE,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAqD,CAAC;AAIlH,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { createContext } from "react";
2
+
3
+ //#region src/contexts/Theme/ThemeRootContext.ts
4
+ const ThemeRootContext = createContext(null);
5
+ ThemeRootContext.displayName = "ThemeRootContext";
6
+ var ThemeRootContext_default = ThemeRootContext;
7
+
8
+ //#endregion
9
+ export { ThemeRootContext_default as default };
10
+ //# sourceMappingURL=ThemeRootContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeRootContext.js","names":["ThemeRootContext: Context<ThemeRootContextValue | null>"],"sources":["../../../src/contexts/Theme/ThemeRootContext.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {Context, createContext} from 'react';\n\nexport interface ThemeRootContextValue {\n /**\n * Hands the document root to a nested `ThemeProvider` until the returned release is called, so the\n * innermost provider's CSS variables and text direction are the ones on the page.\n */\n claim: () => () => void;\n}\n\nconst ThemeRootContext: Context<ThemeRootContextValue | null> = createContext<ThemeRootContextValue | null>(null);\n\nThemeRootContext.displayName = 'ThemeRootContext';\n\nexport default ThemeRootContext;\n"],"mappings":";;;AAaA,MAAMA,mBAA0D,cAA4C,KAAK;AAEjH,iBAAiB,cAAc;AAE/B,+BAAe"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thunderid/react",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "React SDK for ThunderID",
5
5
  "keywords": [
6
6
  "thunderid",