@gooddata/sdk-ui-theme-provider 11.58.0-alpha.5 → 11.58.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.
@@ -49,6 +49,12 @@ export interface IThemeProviderProps {
49
49
  /**
50
50
  * Should ThemeProvider remove global styles during the unmount phase?
51
51
  *
52
+ * @remarks
53
+ * Only applies when no other global ThemeProvider with a loaded theme remains mounted; otherwise
54
+ * the theme of the last registered one with a loaded theme is shown again. When a provider with
55
+ * this set unmounts while descendant ThemeProviders stay mounted, the global styles are removed
56
+ * once the last of them unmounts, whatever their own setting.
57
+ *
52
58
  * Default: true
53
59
  */
54
60
  removeGlobalStylesOnUnmout?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/ThemeProvider/ThemeProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAWlD;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAE7B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;IAEzB;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,EAC1B,QAAQ,EACR,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,cAAc,EACzB,QAAmB,EACnB,0BAAiC,EACjC,0BAAiC,GACpC,EAAE,mBAAmB,2CAwGrB"}
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/ThemeProvider/ThemeProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAkD,MAAM,OAAO,CAAC;AAEvF,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAmBlD;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAE7B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;IAEzB;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;;;;;;;;;OAUG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,EAC1B,QAAQ,EACR,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,cAAc,EACzB,QAAmB,EACnB,0BAAiC,EACjC,0BAAiC,GACpC,EAAE,mBAAmB,2CA6GrB"}
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // (C) 2020-2026 GoodData Corporation
3
- import { useEffect, useState } from "react";
3
+ import { createContext, useContext, useEffect, useState } from "react";
4
4
  import { useBackend, useWorkspace } from "@gooddata/sdk-ui";
5
- import { clearCssProperties, setCssProperties } from "../cssProperties.js";
5
+ import { createGlobalThemeOwner, registerGlobalThemeOwner, setGlobalThemeOwnerRemovesStylesWhenLast, setGlobalThemeOwnerState, unregisterGlobalThemeOwner, } from "../globalThemeOwners.js";
6
6
  import { ThemeContextProvider } from "./Context.js";
7
- import { isDarkTheme } from "./isDarkTheme.js";
8
7
  import { prepareTheme } from "./prepareTheme.js";
9
8
  const identity = (v) => v;
9
+ const ParentGlobalThemeOwnerContext = createContext(null);
10
10
  /**
11
11
  * Fetches the theme object from the backend upon mounting and passes both theme object and isThemeLoading flag
12
12
  * to the context via ThemeContextProvider.
@@ -25,10 +25,20 @@ export function ThemeProvider({ children, theme: themeParam, backend: backendPar
25
25
  const [referenceTheme, setReferenceTheme] = useState(themeParam ?? {});
26
26
  const [isLoading, setIsLoading] = useState(false);
27
27
  const [status, setStatus] = useState("pending");
28
+ const parentOwner = useContext(ParentGlobalThemeOwnerContext);
29
+ const [owner] = useState(() => createGlobalThemeOwner(removeGlobalStylesOnUnmout, parentOwner));
30
+ // Declared first: writes to an owner that is not registered are ignored.
28
31
  useEffect(() => {
29
- // Marks the in-flight getTheme() as stale once this effect is torn down - either because the
30
- // workspace/backend changed or because the component unmounted. Without it a late resolution
31
- // would re-inject the global styles that the unmount cleanup below has just removed.
32
+ registerGlobalThemeOwner(owner);
33
+ return () => {
34
+ unregisterGlobalThemeOwner(owner);
35
+ };
36
+ }, [owner]);
37
+ useEffect(() => {
38
+ setGlobalThemeOwnerRemovesStylesWhenLast(owner, removeGlobalStylesOnUnmout);
39
+ }, [owner, removeGlobalStylesOnUnmout]);
40
+ useEffect(() => {
41
+ // Keeps a slow getTheme() for a previous workspace/backend from overwriting the newer theme.
32
42
  let cancelled = false;
33
43
  // A malformed theme (e.g. an unparseable color) must never block rendering. Preparing and
34
44
  // applying the theme is wrapped so any failure falls back to the default theme and always
@@ -43,8 +53,7 @@ export function ThemeProvider({ children, theme: themeParam, backend: backendPar
43
53
  : prepareTheme(themeToApply, true);
44
54
  setTheme(preparedTheme);
45
55
  setReferenceTheme(preparedReferenceTheme);
46
- clearCssProperties();
47
- setCssProperties(preparedTheme, isDarkTheme(preparedTheme));
56
+ setGlobalThemeOwnerState(owner, { theme: preparedTheme });
48
57
  }
49
58
  catch (error) {
50
59
  console.error("Failed to apply the theme, falling back to the default theme.", error);
@@ -52,7 +61,7 @@ export function ThemeProvider({ children, theme: themeParam, backend: backendPar
52
61
  // context consumers stay consistent with the cleared CSS variables
53
62
  setTheme({});
54
63
  setReferenceTheme({});
55
- clearCssProperties();
64
+ setGlobalThemeOwnerState(owner, "cleared");
56
65
  }
57
66
  finally {
58
67
  setIsLoading(false);
@@ -66,7 +75,7 @@ export function ThemeProvider({ children, theme: themeParam, backend: backendPar
66
75
  }
67
76
  const fetchData = async () => {
68
77
  if (!backend || !workspace) {
69
- clearCssProperties();
78
+ setGlobalThemeOwnerState(owner, "cleared");
70
79
  return;
71
80
  }
72
81
  setIsLoading(true);
@@ -85,7 +94,7 @@ export function ThemeProvider({ children, theme: themeParam, backend: backendPar
85
94
  // context consumers stay consistent with the cleared CSS variables
86
95
  setTheme({});
87
96
  setReferenceTheme({});
88
- clearCssProperties();
97
+ setGlobalThemeOwnerState(owner, "cleared");
89
98
  setIsLoading(false);
90
99
  setStatus("success");
91
100
  }
@@ -95,13 +104,6 @@ export function ThemeProvider({ children, theme: themeParam, backend: backendPar
95
104
  return () => {
96
105
  cancelled = true;
97
106
  };
98
- }, [themeParam, workspace, backend, modifier, enableComplementaryPalette]);
99
- useEffect(() => {
100
- return () => {
101
- if (removeGlobalStylesOnUnmout) {
102
- clearCssProperties();
103
- }
104
- };
105
- }, [removeGlobalStylesOnUnmout]);
106
- return (_jsx(ThemeContextProvider, { theme: theme, referenceTheme: referenceTheme, themeIsLoading: isLoading, themeStatus: status, children: children }));
107
+ }, [owner, themeParam, workspace, backend, modifier, enableComplementaryPalette]);
108
+ return (_jsx(ThemeContextProvider, { theme: theme, referenceTheme: referenceTheme, themeIsLoading: isLoading, themeStatus: status, children: _jsx(ParentGlobalThemeOwnerContext.Provider, { value: owner, children: children }) }));
107
109
  }
@@ -0,0 +1,17 @@
1
+ import { type ITheme } from "@gooddata/sdk-model";
2
+ type ResolvedGlobalThemeState = "cleared" | {
3
+ theme: ITheme;
4
+ };
5
+ export interface IGlobalThemeOwner {
6
+ state: "pending" | ResolvedGlobalThemeState;
7
+ removeGlobalStylesWhenLast: boolean;
8
+ inheritsRemoveGlobalStylesWhenLast: boolean;
9
+ parent: IGlobalThemeOwner | null;
10
+ }
11
+ export declare function createGlobalThemeOwner(removeGlobalStylesWhenLast: boolean, parent: IGlobalThemeOwner | null): IGlobalThemeOwner;
12
+ export declare function registerGlobalThemeOwner(owner: IGlobalThemeOwner): void;
13
+ export declare function setGlobalThemeOwnerRemovesStylesWhenLast(owner: IGlobalThemeOwner, value: boolean): void;
14
+ export declare function setGlobalThemeOwnerState(owner: IGlobalThemeOwner, state: ResolvedGlobalThemeState): void;
15
+ export declare function unregisterGlobalThemeOwner(owner: IGlobalThemeOwner): void;
16
+ export {};
17
+ //# sourceMappingURL=globalThemeOwners.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalThemeOwners.d.ts","sourceRoot":"","sources":["../src/globalThemeOwners.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAKlD,KAAK,wBAAwB,GAAG,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,SAAS,GAAG,wBAAwB,CAAC;IAC5C,0BAA0B,EAAE,OAAO,CAAC;IACpC,kCAAkC,EAAE,OAAO,CAAC;IAC5C,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACpC;AA6BD,wBAAgB,sBAAsB,CAClC,0BAA0B,EAAE,OAAO,EACnC,MAAM,EAAE,iBAAiB,GAAG,IAAI,GACjC,iBAAiB,CAOnB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAGvE;AAED,wBAAgB,wCAAwC,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAEvG;AAGD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,wBAAwB,GAAG,IAAI,CAQxG;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAoCzE"}
@@ -0,0 +1,85 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { clearCssProperties, setCssProperties } from "./cssProperties.js";
3
+ import { isDarkTheme } from "./ThemeProvider/isDarkTheme.js";
4
+ // Registered global ThemeProviders in effect order. Effects run child-first, so a nested provider
5
+ // only outranks its parent when it mounts in a later commit (e.g. an embedded app).
6
+ const owners = [];
7
+ function isResolved(owner) {
8
+ return owner.state !== "pending";
9
+ }
10
+ function getEffectiveOwner() {
11
+ for (let index = owners.length - 1; index >= 0; index--) {
12
+ const owner = owners[index];
13
+ if (isResolved(owner)) {
14
+ return owner;
15
+ }
16
+ }
17
+ return undefined;
18
+ }
19
+ function paint(state) {
20
+ clearCssProperties();
21
+ if (state !== "cleared") {
22
+ setCssProperties(state.theme, isDarkTheme(state.theme));
23
+ }
24
+ }
25
+ export function createGlobalThemeOwner(removeGlobalStylesWhenLast, parent) {
26
+ return {
27
+ state: "pending",
28
+ removeGlobalStylesWhenLast,
29
+ inheritsRemoveGlobalStylesWhenLast: false,
30
+ parent,
31
+ };
32
+ }
33
+ export function registerGlobalThemeOwner(owner) {
34
+ owner.state = "pending";
35
+ owners.push(owner);
36
+ }
37
+ export function setGlobalThemeOwnerRemovesStylesWhenLast(owner, value) {
38
+ owner.removeGlobalStylesWhenLast = value;
39
+ }
40
+ // Throws when the owner is the one shown and its theme cannot be converted to CSS properties.
41
+ export function setGlobalThemeOwnerState(owner, state) {
42
+ if (!owners.includes(owner)) {
43
+ return;
44
+ }
45
+ owner.state = state;
46
+ if (getEffectiveOwner() === owner) {
47
+ paint(state);
48
+ }
49
+ }
50
+ export function unregisterGlobalThemeOwner(owner) {
51
+ const index = owners.indexOf(owner);
52
+ if (index === -1) {
53
+ return;
54
+ }
55
+ const wasEffective = getEffectiveOwner() === owner;
56
+ const removesGlobalStylesWhenLast = owner.removeGlobalStylesWhenLast || owner.inheritsRemoveGlobalStylesWhenLast;
57
+ owners.splice(index, 1);
58
+ // Parents unmount before their descendants, so a parent's duty to remove the styles passes to its
59
+ // descendant providers that are still mounted.
60
+ if (removesGlobalStylesWhenLast) {
61
+ owners
62
+ .filter((childOwner) => childOwner.parent === owner)
63
+ .forEach((childOwner) => {
64
+ childOwner.inheritsRemoveGlobalStylesWhenLast = true;
65
+ });
66
+ }
67
+ const nextOwner = getEffectiveOwner();
68
+ if (!nextOwner) {
69
+ if (removesGlobalStylesWhenLast) {
70
+ clearCssProperties();
71
+ }
72
+ return;
73
+ }
74
+ if (!wasEffective) {
75
+ return;
76
+ }
77
+ try {
78
+ paint(nextOwner.state);
79
+ }
80
+ catch (error) {
81
+ console.error("Failed to apply the theme, falling back to the default theme.", error);
82
+ nextOwner.state = "cleared";
83
+ clearCssProperties();
84
+ }
85
+ }
@@ -233,6 +233,12 @@ export declare interface IThemeProviderProps {
233
233
  /**
234
234
  * Should ThemeProvider remove global styles during the unmount phase?
235
235
  *
236
+ * @remarks
237
+ * Only applies when no other global ThemeProvider with a loaded theme remains mounted; otherwise
238
+ * the theme of the last registered one with a loaded theme is shown again. When a provider with
239
+ * this set unmounts while descendant ThemeProviders stay mounted, the global styles are removed
240
+ * once the last of them unmounts, whatever their own setting.
241
+ *
236
242
  * Default: true
237
243
  */
238
244
  removeGlobalStylesOnUnmout?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-theme-provider",
3
- "version": "11.58.0-alpha.5",
3
+ "version": "11.58.0",
4
4
  "description": "GoodData SDK - Theme provider",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -32,10 +32,10 @@
32
32
  "postcss-value-parser": "4.2.0",
33
33
  "ts-invariant": "0.10.3",
34
34
  "tslib": "2.8.1",
35
- "@gooddata/sdk-backend-spi": "11.58.0-alpha.5",
36
- "@gooddata/sdk-model": "11.58.0-alpha.5",
37
- "@gooddata/sdk-ui": "11.58.0-alpha.5",
38
- "@gooddata/util": "11.58.0-alpha.5"
35
+ "@gooddata/sdk-backend-spi": "11.58.0",
36
+ "@gooddata/sdk-model": "11.58.0",
37
+ "@gooddata/util": "11.58.0",
38
+ "@gooddata/sdk-ui": "11.58.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@microsoft/api-documenter": "7.30.10",
@@ -69,10 +69,10 @@
69
69
  "react-dom": "19.1.1",
70
70
  "typescript": "7.0.2",
71
71
  "vitest": "4.1.11",
72
- "@gooddata/eslint-config": "11.58.0-alpha.5",
73
- "@gooddata/oxlint-config": "11.58.0-alpha.5",
74
- "@gooddata/reference-workspace": "11.58.0-alpha.5",
75
- "@gooddata/sdk-backend-mockingbird": "11.58.0-alpha.5"
72
+ "@gooddata/eslint-config": "11.58.0",
73
+ "@gooddata/oxlint-config": "11.58.0",
74
+ "@gooddata/reference-workspace": "11.58.0",
75
+ "@gooddata/sdk-backend-mockingbird": "11.58.0"
76
76
  },
77
77
  "peerDependencies": {
78
78
  "react": "^18.0.0 || ^19.0.0",