@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.
- package/esm/ThemeProvider/ThemeProvider.d.ts +6 -0
- package/esm/ThemeProvider/ThemeProvider.d.ts.map +1 -1
- package/esm/ThemeProvider/ThemeProvider.js +22 -20
- package/esm/globalThemeOwners.d.ts +17 -0
- package/esm/globalThemeOwners.d.ts.map +1 -0
- package/esm/globalThemeOwners.js +85 -0
- package/esm/sdk-ui-theme-provider.d.ts +6 -0
- package/package.json +9 -9
|
@@ -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,
|
|
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 {
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
36
|
-
"@gooddata/sdk-model": "11.58.0
|
|
37
|
-
"@gooddata/
|
|
38
|
-
"@gooddata/
|
|
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
|
|
73
|
-
"@gooddata/oxlint-config": "11.58.0
|
|
74
|
-
"@gooddata/reference-workspace": "11.58.0
|
|
75
|
-
"@gooddata/sdk-backend-mockingbird": "11.58.0
|
|
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",
|