theme-ops-sdk 0.1.0 → 1.0.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/dist/index.cjs +89 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +89 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -5,7 +5,7 @@ var react = require('react');
|
|
|
5
5
|
// src/useThemeOps.ts
|
|
6
6
|
|
|
7
7
|
// src/buildCdnUrls.ts
|
|
8
|
-
var CDN_BASE_URL = "https://
|
|
8
|
+
var CDN_BASE_URL = "https://theme-ops.s3.sa-east-1.amazonaws.com";
|
|
9
9
|
function buildBasePath(organizationId, appId) {
|
|
10
10
|
return appId ? `${organizationId}/${appId}` : organizationId;
|
|
11
11
|
}
|
|
@@ -21,10 +21,34 @@ function buildCdnUrls({
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// src/fetchReleaseFromApi.ts
|
|
25
|
+
function buildReleaseUrl(apiUrl, releaseId) {
|
|
26
|
+
const base = apiUrl.replace(/\/+$/, "");
|
|
27
|
+
return `${base}/sdk/releases/${releaseId}`;
|
|
28
|
+
}
|
|
29
|
+
async function fetchReleaseFromApi(apiUrl, releaseId) {
|
|
30
|
+
const url = buildReleaseUrl(apiUrl, releaseId);
|
|
31
|
+
const response = await fetch(url);
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`Failed to fetch release: ${response.status}`);
|
|
34
|
+
}
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
return {
|
|
37
|
+
themeLight: data.themeLight,
|
|
38
|
+
themeDark: data.themeDark,
|
|
39
|
+
cssUrl: null,
|
|
40
|
+
cssContent: data.css || null
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
// src/fetchThemeAssets.ts
|
|
45
|
+
function appendCacheBuster(url) {
|
|
46
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
47
|
+
return `${url}${separator}_t=${Date.now()}`;
|
|
48
|
+
}
|
|
25
49
|
async function fetchJson(url) {
|
|
26
50
|
try {
|
|
27
|
-
const response = await fetch(url);
|
|
51
|
+
const response = await fetch(appendCacheBuster(url));
|
|
28
52
|
if (!response.ok) return null;
|
|
29
53
|
return await response.json();
|
|
30
54
|
} catch {
|
|
@@ -42,41 +66,68 @@ function isThemeJsonValid(json) {
|
|
|
42
66
|
}
|
|
43
67
|
async function fetchThemeAssets(urls) {
|
|
44
68
|
const activeJson = await fetchJson(urls.theme);
|
|
69
|
+
const cacheBustedCssUrl = appendCacheBuster(urls.css);
|
|
45
70
|
if (isThemeJsonValid(activeJson)) {
|
|
46
|
-
const { themeLight: themeLight2, themeDark } = extractThemesFromJson(activeJson);
|
|
47
|
-
return { themeLight: themeLight2, themeDark, cssUrl:
|
|
71
|
+
const { themeLight: themeLight2, themeDark: themeDark2 } = extractThemesFromJson(activeJson);
|
|
72
|
+
return { themeLight: themeLight2, themeDark: themeDark2, cssUrl: cacheBustedCssUrl, usingFallback: false };
|
|
48
73
|
}
|
|
49
74
|
const fallbackJson = await fetchJson(urls.themeFallback);
|
|
50
|
-
const { themeLight } = extractThemesFromJson(fallbackJson);
|
|
75
|
+
const { themeLight, themeDark } = extractThemesFromJson(fallbackJson);
|
|
51
76
|
return {
|
|
52
77
|
themeLight,
|
|
53
|
-
themeDark
|
|
54
|
-
cssUrl:
|
|
78
|
+
themeDark,
|
|
79
|
+
cssUrl: cacheBustedCssUrl,
|
|
55
80
|
usingFallback: true
|
|
56
81
|
};
|
|
57
82
|
}
|
|
58
83
|
|
|
59
84
|
// src/insertStylesIntoDOM.ts
|
|
60
|
-
var
|
|
61
|
-
|
|
85
|
+
var LINK_ELEMENT_ID = "theme-ops-css";
|
|
86
|
+
var STYLE_ELEMENT_ID = "theme-ops-css-inline";
|
|
87
|
+
function appendCacheBuster2(url) {
|
|
88
|
+
const baseUrl = url.replace(/[?&]_t=\d+/, "");
|
|
89
|
+
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
90
|
+
return `${baseUrl}${separator}_t=${Date.now()}`;
|
|
91
|
+
}
|
|
92
|
+
function findExistingLinkElement() {
|
|
93
|
+
return document.getElementById(LINK_ELEMENT_ID);
|
|
94
|
+
}
|
|
95
|
+
function findExistingInlineStyle() {
|
|
62
96
|
return document.getElementById(STYLE_ELEMENT_ID);
|
|
63
97
|
}
|
|
64
|
-
function
|
|
98
|
+
function createLinkElement(href) {
|
|
65
99
|
const link = document.createElement("link");
|
|
66
|
-
link.id =
|
|
100
|
+
link.id = LINK_ELEMENT_ID;
|
|
67
101
|
link.rel = "stylesheet";
|
|
68
102
|
link.href = href;
|
|
69
103
|
return link;
|
|
70
104
|
}
|
|
105
|
+
function removeElement(id) {
|
|
106
|
+
document.getElementById(id)?.remove();
|
|
107
|
+
}
|
|
71
108
|
function insertStylesIntoDOM(cssUrl) {
|
|
72
|
-
|
|
109
|
+
removeElement(STYLE_ELEMENT_ID);
|
|
110
|
+
const freshUrl = appendCacheBuster2(cssUrl);
|
|
111
|
+
const existing = findExistingLinkElement();
|
|
73
112
|
if (existing) {
|
|
74
|
-
existing.href =
|
|
113
|
+
existing.href = freshUrl;
|
|
75
114
|
return;
|
|
76
115
|
}
|
|
77
|
-
const link =
|
|
116
|
+
const link = createLinkElement(freshUrl);
|
|
78
117
|
document.head.appendChild(link);
|
|
79
118
|
}
|
|
119
|
+
function insertInlineStylesIntoDOM(cssContent) {
|
|
120
|
+
removeElement(LINK_ELEMENT_ID);
|
|
121
|
+
const existing = findExistingInlineStyle();
|
|
122
|
+
if (existing) {
|
|
123
|
+
existing.textContent = cssContent;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const style = document.createElement("style");
|
|
127
|
+
style.id = STYLE_ELEMENT_ID;
|
|
128
|
+
style.textContent = cssContent;
|
|
129
|
+
document.head.appendChild(style);
|
|
130
|
+
}
|
|
80
131
|
|
|
81
132
|
// src/useThemeOps.ts
|
|
82
133
|
function hookReducer(_state, action) {
|
|
@@ -90,48 +141,54 @@ function hookReducer(_state, action) {
|
|
|
90
141
|
var EMPTY_THEME_STATE = {
|
|
91
142
|
themeLight: null,
|
|
92
143
|
themeDark: null,
|
|
93
|
-
cssUrl: null
|
|
144
|
+
cssUrl: null,
|
|
145
|
+
cssContent: null
|
|
94
146
|
};
|
|
95
147
|
function resolveThemeTokens(mode, themeState) {
|
|
96
148
|
const tokens = mode === "dark" ? themeState.themeDark : themeState.themeLight;
|
|
97
149
|
if (!tokens) return {};
|
|
98
|
-
return
|
|
150
|
+
return tokens;
|
|
99
151
|
}
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
152
|
+
function isReleasePreview(options) {
|
|
153
|
+
return Boolean(options.releaseId && options.apiUrl);
|
|
154
|
+
}
|
|
155
|
+
function useThemeOps(options) {
|
|
156
|
+
const { organizationId, appId, releaseId, apiUrl } = options;
|
|
104
157
|
const [state, dispatch] = react.useReducer(hookReducer, { status: "loading" });
|
|
105
158
|
const isMountedRef = react.useRef(true);
|
|
106
159
|
react.useEffect(() => {
|
|
107
160
|
isMountedRef.current = true;
|
|
108
161
|
dispatch({ type: "FETCH_START" });
|
|
109
|
-
const
|
|
110
|
-
|
|
162
|
+
const fetchPromise = isReleasePreview(options) ? fetchReleaseFromApi(options.apiUrl, options.releaseId) : fetchThemeAssets(buildCdnUrls({ organizationId, appId })).then(
|
|
163
|
+
(assets) => ({
|
|
164
|
+
themeLight: assets.themeLight,
|
|
165
|
+
themeDark: assets.themeDark,
|
|
166
|
+
cssUrl: assets.cssUrl,
|
|
167
|
+
cssContent: null
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
fetchPromise.then((payload) => {
|
|
111
171
|
if (!isMountedRef.current) return;
|
|
112
|
-
dispatch({
|
|
113
|
-
type: "FETCH_SUCCESS",
|
|
114
|
-
payload: {
|
|
115
|
-
themeLight: assets.themeLight,
|
|
116
|
-
themeDark: assets.themeDark,
|
|
117
|
-
cssUrl: assets.cssUrl
|
|
118
|
-
}
|
|
119
|
-
});
|
|
172
|
+
dispatch({ type: "FETCH_SUCCESS", payload });
|
|
120
173
|
});
|
|
121
174
|
return () => {
|
|
122
175
|
isMountedRef.current = false;
|
|
123
176
|
};
|
|
124
|
-
}, [organizationId, appId]);
|
|
177
|
+
}, [organizationId, appId, releaseId, apiUrl]);
|
|
125
178
|
const resolvedThemeState = state.status === "ready" ? state.themeState : EMPTY_THEME_STATE;
|
|
126
179
|
const theme = react.useCallback(
|
|
127
180
|
(mode) => resolveThemeTokens(mode, resolvedThemeState),
|
|
128
181
|
[resolvedThemeState]
|
|
129
182
|
);
|
|
130
183
|
const insertStyles = react.useCallback(() => {
|
|
184
|
+
if (resolvedThemeState.cssContent) {
|
|
185
|
+
insertInlineStylesIntoDOM(resolvedThemeState.cssContent);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
131
188
|
if (resolvedThemeState.cssUrl) {
|
|
132
189
|
insertStylesIntoDOM(resolvedThemeState.cssUrl);
|
|
133
190
|
}
|
|
134
|
-
}, [resolvedThemeState.cssUrl]);
|
|
191
|
+
}, [resolvedThemeState.cssUrl, resolvedThemeState.cssContent]);
|
|
135
192
|
return { isLoading: state.status === "loading", theme, insertStyles };
|
|
136
193
|
}
|
|
137
194
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/buildCdnUrls.ts","../src/fetchThemeAssets.ts","../src/insertStylesIntoDOM.ts","../src/useThemeOps.ts"],"names":["themeLight","useReducer","useRef","useEffect","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { BuildCdnUrlsOptions, CdnUrls } from \"./types\";\n\nconst CDN_BASE_URL = \"https://cdn.themeops.xyz\";\n\nfunction buildBasePath(organizationId: string, appId?: string): string {\n return appId ? `${organizationId}/${appId}` : organizationId;\n}\n\nexport function buildCdnUrls({\n organizationId,\n appId,\n}: BuildCdnUrlsOptions): CdnUrls {\n const assetPath = buildBasePath(organizationId, appId);\n\n return {\n theme: `${CDN_BASE_URL}/${assetPath}/theme.json`,\n themeFallback: `${CDN_BASE_URL}/${assetPath}/theme-fallback.json`,\n css: `${CDN_BASE_URL}/${assetPath}/css.css`,\n };\n}\n","import type { CdnUrls, ThemeAssets, ThemeJson, ThemeTokens } from \"./types\";\n\nasync function fetchJson<T>(url: string): Promise<T | null> {\n try {\n const response = await fetch(url);\n if (!response.ok) return null;\n\n return (await response.json()) as T;\n } catch {\n return null;\n }\n}\n\nfunction extractThemesFromJson(json: ThemeJson | null): {\n themeLight: ThemeTokens | null;\n themeDark: ThemeTokens | null;\n} {\n return {\n themeLight: json?.light ?? null,\n themeDark: json?.dark ?? null,\n };\n}\n\nfunction isThemeJsonValid(json: ThemeJson | null): boolean {\n return json !== null && (json.light !== undefined || json.dark !== undefined);\n}\n\nexport async function fetchThemeAssets(urls: CdnUrls): Promise<ThemeAssets> {\n const activeJson = await fetchJson<ThemeJson>(urls.theme);\n\n if (isThemeJsonValid(activeJson)) {\n const { themeLight, themeDark } = extractThemesFromJson(activeJson);\n return { themeLight, themeDark, cssUrl: urls.css, usingFallback: false };\n }\n\n const fallbackJson = await fetchJson<ThemeJson>(urls.themeFallback);\n const { themeLight } = extractThemesFromJson(fallbackJson);\n\n return {\n themeLight,\n themeDark: null,\n cssUrl: urls.css,\n usingFallback: true,\n };\n}\n","const STYLE_ELEMENT_ID = \"theme-ops-css\";\n\nfunction findExistingStyleElement(): HTMLLinkElement | null {\n return document.getElementById(STYLE_ELEMENT_ID) as HTMLLinkElement | null;\n}\n\nfunction createStyleElement(href: string): HTMLLinkElement {\n const link = document.createElement(\"link\");\n link.id = STYLE_ELEMENT_ID;\n link.rel = \"stylesheet\";\n link.href = href;\n return link;\n}\n\nexport function insertStylesIntoDOM(cssUrl: string): void {\n const existing = findExistingStyleElement();\n\n if (existing) {\n existing.href = cssUrl;\n return;\n }\n\n const link = createStyleElement(cssUrl);\n document.head.appendChild(link);\n}\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\n\nimport { buildCdnUrls } from \"./buildCdnUrls\";\nimport { fetchThemeAssets } from \"./fetchThemeAssets\";\nimport { insertStylesIntoDOM } from \"./insertStylesIntoDOM\";\nimport type {\n ThemeConfig,\n ThemeState,\n UseThemeOpsOptions,\n UseThemeOpsResult,\n} from \"./types\";\n\ntype HookState =\n | { status: \"loading\" }\n | { status: \"ready\"; themeState: ThemeState };\n\ntype HookAction =\n | { type: \"FETCH_START\" }\n | { type: \"FETCH_SUCCESS\"; payload: ThemeState };\n\nfunction hookReducer(_state: HookState, action: HookAction): HookState {\n switch (action.type) {\n case \"FETCH_START\":\n return { status: \"loading\" };\n case \"FETCH_SUCCESS\":\n return { status: \"ready\", themeState: action.payload };\n }\n}\n\nconst EMPTY_THEME_STATE: ThemeState = {\n themeLight: null,\n themeDark: null,\n cssUrl: null,\n};\n\nfunction resolveThemeTokens(\n mode: \"light\" | \"dark\",\n themeState: ThemeState,\n): ThemeConfig {\n const tokens = mode === \"dark\" ? themeState.themeDark : themeState.themeLight;\n if (!tokens) return {};\n\n return { token: tokens };\n}\n\nexport function useThemeOps({\n organizationId,\n appId,\n}: UseThemeOpsOptions): UseThemeOpsResult {\n const [state, dispatch] = useReducer(hookReducer, { status: \"loading\" });\n\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n\n dispatch({ type: \"FETCH_START\" });\n\n const urls = buildCdnUrls({ organizationId, appId });\n\n fetchThemeAssets(urls).then((assets) => {\n if (!isMountedRef.current) return;\n\n dispatch({\n type: \"FETCH_SUCCESS\",\n payload: {\n themeLight: assets.themeLight,\n themeDark: assets.themeDark,\n cssUrl: assets.cssUrl,\n },\n });\n });\n\n return () => {\n isMountedRef.current = false;\n };\n }, [organizationId, appId]);\n\n const resolvedThemeState =\n state.status === \"ready\" ? state.themeState : EMPTY_THEME_STATE;\n\n const theme = useCallback(\n (mode: \"light\" | \"dark\"): ThemeConfig =>\n resolveThemeTokens(mode, resolvedThemeState),\n [resolvedThemeState],\n );\n\n const insertStyles = useCallback(() => {\n if (resolvedThemeState.cssUrl) {\n insertStylesIntoDOM(resolvedThemeState.cssUrl);\n }\n }, [resolvedThemeState.cssUrl]);\n\n return { isLoading: state.status === \"loading\", theme, insertStyles };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/buildCdnUrls.ts","../src/fetchReleaseFromApi.ts","../src/fetchThemeAssets.ts","../src/insertStylesIntoDOM.ts","../src/useThemeOps.ts"],"names":["themeLight","themeDark","appendCacheBuster","useReducer","useRef","useEffect","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { BuildCdnUrlsOptions, CdnUrls } from \"./types\";\n\nconst CDN_BASE_URL = \"https://theme-ops.s3.sa-east-1.amazonaws.com\";\n\nfunction buildBasePath(organizationId: string, appId?: string): string {\n return appId ? `${organizationId}/${appId}` : organizationId;\n}\n\nexport function buildCdnUrls({\n organizationId,\n appId,\n}: BuildCdnUrlsOptions): CdnUrls {\n const assetPath = buildBasePath(organizationId, appId);\n\n return {\n theme: `${CDN_BASE_URL}/${assetPath}/theme.json`,\n themeFallback: `${CDN_BASE_URL}/${assetPath}/theme-fallback.json`,\n css: `${CDN_BASE_URL}/${assetPath}/css.css`,\n };\n}\n","import type { ApiReleaseResponse, ThemeState } from \"./types\";\n\nfunction buildReleaseUrl(apiUrl: string, releaseId: string): string {\n const base = apiUrl.replace(/\\/+$/, \"\");\n return `${base}/sdk/releases/${releaseId}`;\n}\n\nexport async function fetchReleaseFromApi(\n apiUrl: string,\n releaseId: string,\n): Promise<ThemeState> {\n const url = buildReleaseUrl(apiUrl, releaseId);\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch release: ${response.status}`);\n }\n\n const data = (await response.json()) as ApiReleaseResponse;\n\n return {\n themeLight: data.themeLight,\n themeDark: data.themeDark,\n cssUrl: null,\n cssContent: data.css || null,\n };\n}\n","import type { CdnUrls, ThemeAssets, ThemeJson, ThemeTokens } from \"./types\";\n\nfunction appendCacheBuster(url: string): string {\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}_t=${Date.now()}`;\n}\n\nasync function fetchJson<T>(url: string): Promise<T | null> {\n try {\n const response = await fetch(appendCacheBuster(url));\n if (!response.ok) return null;\n\n return (await response.json()) as T;\n } catch {\n return null;\n }\n}\n\nfunction extractThemesFromJson(json: ThemeJson | null): {\n themeLight: ThemeTokens | null;\n themeDark: ThemeTokens | null;\n} {\n return {\n themeLight: json?.light ?? null,\n themeDark: json?.dark ?? null,\n };\n}\n\nfunction isThemeJsonValid(json: ThemeJson | null): boolean {\n return json !== null && (json.light !== undefined || json.dark !== undefined);\n}\n\nexport async function fetchThemeAssets(urls: CdnUrls): Promise<ThemeAssets> {\n const activeJson = await fetchJson<ThemeJson>(urls.theme);\n const cacheBustedCssUrl = appendCacheBuster(urls.css);\n\n if (isThemeJsonValid(activeJson)) {\n const { themeLight, themeDark } = extractThemesFromJson(activeJson);\n return { themeLight, themeDark, cssUrl: cacheBustedCssUrl, usingFallback: false };\n }\n\n const fallbackJson = await fetchJson<ThemeJson>(urls.themeFallback);\n const { themeLight, themeDark } = extractThemesFromJson(fallbackJson);\n\n return {\n themeLight,\n themeDark,\n cssUrl: cacheBustedCssUrl,\n usingFallback: true,\n };\n}\n","const LINK_ELEMENT_ID = \"theme-ops-css\";\nconst STYLE_ELEMENT_ID = \"theme-ops-css-inline\";\n\nfunction appendCacheBuster(url: string): string {\n const baseUrl = url.replace(/[?&]_t=\\d+/, \"\");\n const separator = baseUrl.includes(\"?\") ? \"&\" : \"?\";\n return `${baseUrl}${separator}_t=${Date.now()}`;\n}\n\nfunction findExistingLinkElement(): HTMLLinkElement | null {\n return document.getElementById(LINK_ELEMENT_ID) as HTMLLinkElement | null;\n}\n\nfunction findExistingInlineStyle(): HTMLStyleElement | null {\n return document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n}\n\nfunction createLinkElement(href: string): HTMLLinkElement {\n const link = document.createElement(\"link\");\n link.id = LINK_ELEMENT_ID;\n link.rel = \"stylesheet\";\n link.href = href;\n return link;\n}\n\nfunction removeElement(id: string): void {\n document.getElementById(id)?.remove();\n}\n\nexport function insertStylesIntoDOM(cssUrl: string): void {\n removeElement(STYLE_ELEMENT_ID);\n\n const freshUrl = appendCacheBuster(cssUrl);\n const existing = findExistingLinkElement();\n\n if (existing) {\n existing.href = freshUrl;\n return;\n }\n\n const link = createLinkElement(freshUrl);\n document.head.appendChild(link);\n}\n\nexport function insertInlineStylesIntoDOM(cssContent: string): void {\n removeElement(LINK_ELEMENT_ID);\n\n const existing = findExistingInlineStyle();\n\n if (existing) {\n existing.textContent = cssContent;\n return;\n }\n\n const style = document.createElement(\"style\");\n style.id = STYLE_ELEMENT_ID;\n style.textContent = cssContent;\n document.head.appendChild(style);\n}\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\n\nimport { buildCdnUrls } from \"./buildCdnUrls\";\nimport { fetchReleaseFromApi } from \"./fetchReleaseFromApi\";\nimport { fetchThemeAssets } from \"./fetchThemeAssets\";\nimport {\n insertInlineStylesIntoDOM,\n insertStylesIntoDOM,\n} from \"./insertStylesIntoDOM\";\nimport type {\n ThemeConfig,\n ThemeState,\n UseThemeOpsOptions,\n UseThemeOpsResult,\n} from \"./types\";\n\ntype HookState =\n | { status: \"loading\" }\n | { status: \"ready\"; themeState: ThemeState };\n\ntype HookAction =\n | { type: \"FETCH_START\" }\n | { type: \"FETCH_SUCCESS\"; payload: ThemeState };\n\nfunction hookReducer(_state: HookState, action: HookAction): HookState {\n switch (action.type) {\n case \"FETCH_START\":\n return { status: \"loading\" };\n case \"FETCH_SUCCESS\":\n return { status: \"ready\", themeState: action.payload };\n }\n}\n\nconst EMPTY_THEME_STATE: ThemeState = {\n themeLight: null,\n themeDark: null,\n cssUrl: null,\n cssContent: null,\n};\n\nfunction resolveThemeTokens(\n mode: \"light\" | \"dark\",\n themeState: ThemeState,\n): ThemeConfig {\n const tokens = mode === \"dark\" ? themeState.themeDark : themeState.themeLight;\n if (!tokens) return {};\n\n return tokens as ThemeConfig;\n}\n\nfunction isReleasePreview(\n options: UseThemeOpsOptions,\n): options is UseThemeOpsOptions & { releaseId: string; apiUrl: string } {\n return Boolean(options.releaseId && options.apiUrl);\n}\n\nexport function useThemeOps(options: UseThemeOpsOptions): UseThemeOpsResult {\n const { organizationId, appId, releaseId, apiUrl } = options;\n const [state, dispatch] = useReducer(hookReducer, { status: \"loading\" });\n\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n\n dispatch({ type: \"FETCH_START\" });\n\n const fetchPromise = isReleasePreview(options)\n ? fetchReleaseFromApi(options.apiUrl, options.releaseId)\n : fetchThemeAssets(buildCdnUrls({ organizationId, appId })).then(\n (assets) => ({\n themeLight: assets.themeLight,\n themeDark: assets.themeDark,\n cssUrl: assets.cssUrl,\n cssContent: null,\n }),\n );\n\n fetchPromise.then((payload) => {\n if (!isMountedRef.current) return;\n\n dispatch({ type: \"FETCH_SUCCESS\", payload });\n });\n\n return () => {\n isMountedRef.current = false;\n };\n }, [organizationId, appId, releaseId, apiUrl]);\n\n const resolvedThemeState =\n state.status === \"ready\" ? state.themeState : EMPTY_THEME_STATE;\n\n const theme = useCallback(\n (mode: \"light\" | \"dark\"): ThemeConfig =>\n resolveThemeTokens(mode, resolvedThemeState),\n [resolvedThemeState],\n );\n\n const insertStyles = useCallback(() => {\n if (resolvedThemeState.cssContent) {\n insertInlineStylesIntoDOM(resolvedThemeState.cssContent);\n return;\n }\n\n if (resolvedThemeState.cssUrl) {\n insertStylesIntoDOM(resolvedThemeState.cssUrl);\n }\n }, [resolvedThemeState.cssUrl, resolvedThemeState.cssContent]);\n\n return { isLoading: state.status === \"loading\", theme, insertStyles };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -5,6 +5,8 @@ interface ThemeConfig {
|
|
|
5
5
|
interface UseThemeOpsOptions {
|
|
6
6
|
organizationId: string;
|
|
7
7
|
appId?: string;
|
|
8
|
+
releaseId?: string;
|
|
9
|
+
apiUrl?: string;
|
|
8
10
|
}
|
|
9
11
|
interface UseThemeOpsResult {
|
|
10
12
|
isLoading: boolean;
|
|
@@ -12,6 +14,6 @@ interface UseThemeOpsResult {
|
|
|
12
14
|
insertStyles: () => void;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
declare function useThemeOps(
|
|
17
|
+
declare function useThemeOps(options: UseThemeOpsOptions): UseThemeOpsResult;
|
|
16
18
|
|
|
17
19
|
export { type ThemeConfig, type ThemeTokens, type UseThemeOpsOptions, type UseThemeOpsResult, useThemeOps };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ interface ThemeConfig {
|
|
|
5
5
|
interface UseThemeOpsOptions {
|
|
6
6
|
organizationId: string;
|
|
7
7
|
appId?: string;
|
|
8
|
+
releaseId?: string;
|
|
9
|
+
apiUrl?: string;
|
|
8
10
|
}
|
|
9
11
|
interface UseThemeOpsResult {
|
|
10
12
|
isLoading: boolean;
|
|
@@ -12,6 +14,6 @@ interface UseThemeOpsResult {
|
|
|
12
14
|
insertStyles: () => void;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
declare function useThemeOps(
|
|
17
|
+
declare function useThemeOps(options: UseThemeOpsOptions): UseThemeOpsResult;
|
|
16
18
|
|
|
17
19
|
export { type ThemeConfig, type ThemeTokens, type UseThemeOpsOptions, type UseThemeOpsResult, useThemeOps };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useReducer, useRef, useEffect, useCallback } from 'react';
|
|
|
3
3
|
// src/useThemeOps.ts
|
|
4
4
|
|
|
5
5
|
// src/buildCdnUrls.ts
|
|
6
|
-
var CDN_BASE_URL = "https://
|
|
6
|
+
var CDN_BASE_URL = "https://theme-ops.s3.sa-east-1.amazonaws.com";
|
|
7
7
|
function buildBasePath(organizationId, appId) {
|
|
8
8
|
return appId ? `${organizationId}/${appId}` : organizationId;
|
|
9
9
|
}
|
|
@@ -19,10 +19,34 @@ function buildCdnUrls({
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// src/fetchReleaseFromApi.ts
|
|
23
|
+
function buildReleaseUrl(apiUrl, releaseId) {
|
|
24
|
+
const base = apiUrl.replace(/\/+$/, "");
|
|
25
|
+
return `${base}/sdk/releases/${releaseId}`;
|
|
26
|
+
}
|
|
27
|
+
async function fetchReleaseFromApi(apiUrl, releaseId) {
|
|
28
|
+
const url = buildReleaseUrl(apiUrl, releaseId);
|
|
29
|
+
const response = await fetch(url);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Failed to fetch release: ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
const data = await response.json();
|
|
34
|
+
return {
|
|
35
|
+
themeLight: data.themeLight,
|
|
36
|
+
themeDark: data.themeDark,
|
|
37
|
+
cssUrl: null,
|
|
38
|
+
cssContent: data.css || null
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
// src/fetchThemeAssets.ts
|
|
43
|
+
function appendCacheBuster(url) {
|
|
44
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
45
|
+
return `${url}${separator}_t=${Date.now()}`;
|
|
46
|
+
}
|
|
23
47
|
async function fetchJson(url) {
|
|
24
48
|
try {
|
|
25
|
-
const response = await fetch(url);
|
|
49
|
+
const response = await fetch(appendCacheBuster(url));
|
|
26
50
|
if (!response.ok) return null;
|
|
27
51
|
return await response.json();
|
|
28
52
|
} catch {
|
|
@@ -40,41 +64,68 @@ function isThemeJsonValid(json) {
|
|
|
40
64
|
}
|
|
41
65
|
async function fetchThemeAssets(urls) {
|
|
42
66
|
const activeJson = await fetchJson(urls.theme);
|
|
67
|
+
const cacheBustedCssUrl = appendCacheBuster(urls.css);
|
|
43
68
|
if (isThemeJsonValid(activeJson)) {
|
|
44
|
-
const { themeLight: themeLight2, themeDark } = extractThemesFromJson(activeJson);
|
|
45
|
-
return { themeLight: themeLight2, themeDark, cssUrl:
|
|
69
|
+
const { themeLight: themeLight2, themeDark: themeDark2 } = extractThemesFromJson(activeJson);
|
|
70
|
+
return { themeLight: themeLight2, themeDark: themeDark2, cssUrl: cacheBustedCssUrl, usingFallback: false };
|
|
46
71
|
}
|
|
47
72
|
const fallbackJson = await fetchJson(urls.themeFallback);
|
|
48
|
-
const { themeLight } = extractThemesFromJson(fallbackJson);
|
|
73
|
+
const { themeLight, themeDark } = extractThemesFromJson(fallbackJson);
|
|
49
74
|
return {
|
|
50
75
|
themeLight,
|
|
51
|
-
themeDark
|
|
52
|
-
cssUrl:
|
|
76
|
+
themeDark,
|
|
77
|
+
cssUrl: cacheBustedCssUrl,
|
|
53
78
|
usingFallback: true
|
|
54
79
|
};
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
// src/insertStylesIntoDOM.ts
|
|
58
|
-
var
|
|
59
|
-
|
|
83
|
+
var LINK_ELEMENT_ID = "theme-ops-css";
|
|
84
|
+
var STYLE_ELEMENT_ID = "theme-ops-css-inline";
|
|
85
|
+
function appendCacheBuster2(url) {
|
|
86
|
+
const baseUrl = url.replace(/[?&]_t=\d+/, "");
|
|
87
|
+
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
88
|
+
return `${baseUrl}${separator}_t=${Date.now()}`;
|
|
89
|
+
}
|
|
90
|
+
function findExistingLinkElement() {
|
|
91
|
+
return document.getElementById(LINK_ELEMENT_ID);
|
|
92
|
+
}
|
|
93
|
+
function findExistingInlineStyle() {
|
|
60
94
|
return document.getElementById(STYLE_ELEMENT_ID);
|
|
61
95
|
}
|
|
62
|
-
function
|
|
96
|
+
function createLinkElement(href) {
|
|
63
97
|
const link = document.createElement("link");
|
|
64
|
-
link.id =
|
|
98
|
+
link.id = LINK_ELEMENT_ID;
|
|
65
99
|
link.rel = "stylesheet";
|
|
66
100
|
link.href = href;
|
|
67
101
|
return link;
|
|
68
102
|
}
|
|
103
|
+
function removeElement(id) {
|
|
104
|
+
document.getElementById(id)?.remove();
|
|
105
|
+
}
|
|
69
106
|
function insertStylesIntoDOM(cssUrl) {
|
|
70
|
-
|
|
107
|
+
removeElement(STYLE_ELEMENT_ID);
|
|
108
|
+
const freshUrl = appendCacheBuster2(cssUrl);
|
|
109
|
+
const existing = findExistingLinkElement();
|
|
71
110
|
if (existing) {
|
|
72
|
-
existing.href =
|
|
111
|
+
existing.href = freshUrl;
|
|
73
112
|
return;
|
|
74
113
|
}
|
|
75
|
-
const link =
|
|
114
|
+
const link = createLinkElement(freshUrl);
|
|
76
115
|
document.head.appendChild(link);
|
|
77
116
|
}
|
|
117
|
+
function insertInlineStylesIntoDOM(cssContent) {
|
|
118
|
+
removeElement(LINK_ELEMENT_ID);
|
|
119
|
+
const existing = findExistingInlineStyle();
|
|
120
|
+
if (existing) {
|
|
121
|
+
existing.textContent = cssContent;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const style = document.createElement("style");
|
|
125
|
+
style.id = STYLE_ELEMENT_ID;
|
|
126
|
+
style.textContent = cssContent;
|
|
127
|
+
document.head.appendChild(style);
|
|
128
|
+
}
|
|
78
129
|
|
|
79
130
|
// src/useThemeOps.ts
|
|
80
131
|
function hookReducer(_state, action) {
|
|
@@ -88,48 +139,54 @@ function hookReducer(_state, action) {
|
|
|
88
139
|
var EMPTY_THEME_STATE = {
|
|
89
140
|
themeLight: null,
|
|
90
141
|
themeDark: null,
|
|
91
|
-
cssUrl: null
|
|
142
|
+
cssUrl: null,
|
|
143
|
+
cssContent: null
|
|
92
144
|
};
|
|
93
145
|
function resolveThemeTokens(mode, themeState) {
|
|
94
146
|
const tokens = mode === "dark" ? themeState.themeDark : themeState.themeLight;
|
|
95
147
|
if (!tokens) return {};
|
|
96
|
-
return
|
|
148
|
+
return tokens;
|
|
97
149
|
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
150
|
+
function isReleasePreview(options) {
|
|
151
|
+
return Boolean(options.releaseId && options.apiUrl);
|
|
152
|
+
}
|
|
153
|
+
function useThemeOps(options) {
|
|
154
|
+
const { organizationId, appId, releaseId, apiUrl } = options;
|
|
102
155
|
const [state, dispatch] = useReducer(hookReducer, { status: "loading" });
|
|
103
156
|
const isMountedRef = useRef(true);
|
|
104
157
|
useEffect(() => {
|
|
105
158
|
isMountedRef.current = true;
|
|
106
159
|
dispatch({ type: "FETCH_START" });
|
|
107
|
-
const
|
|
108
|
-
|
|
160
|
+
const fetchPromise = isReleasePreview(options) ? fetchReleaseFromApi(options.apiUrl, options.releaseId) : fetchThemeAssets(buildCdnUrls({ organizationId, appId })).then(
|
|
161
|
+
(assets) => ({
|
|
162
|
+
themeLight: assets.themeLight,
|
|
163
|
+
themeDark: assets.themeDark,
|
|
164
|
+
cssUrl: assets.cssUrl,
|
|
165
|
+
cssContent: null
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
fetchPromise.then((payload) => {
|
|
109
169
|
if (!isMountedRef.current) return;
|
|
110
|
-
dispatch({
|
|
111
|
-
type: "FETCH_SUCCESS",
|
|
112
|
-
payload: {
|
|
113
|
-
themeLight: assets.themeLight,
|
|
114
|
-
themeDark: assets.themeDark,
|
|
115
|
-
cssUrl: assets.cssUrl
|
|
116
|
-
}
|
|
117
|
-
});
|
|
170
|
+
dispatch({ type: "FETCH_SUCCESS", payload });
|
|
118
171
|
});
|
|
119
172
|
return () => {
|
|
120
173
|
isMountedRef.current = false;
|
|
121
174
|
};
|
|
122
|
-
}, [organizationId, appId]);
|
|
175
|
+
}, [organizationId, appId, releaseId, apiUrl]);
|
|
123
176
|
const resolvedThemeState = state.status === "ready" ? state.themeState : EMPTY_THEME_STATE;
|
|
124
177
|
const theme = useCallback(
|
|
125
178
|
(mode) => resolveThemeTokens(mode, resolvedThemeState),
|
|
126
179
|
[resolvedThemeState]
|
|
127
180
|
);
|
|
128
181
|
const insertStyles = useCallback(() => {
|
|
182
|
+
if (resolvedThemeState.cssContent) {
|
|
183
|
+
insertInlineStylesIntoDOM(resolvedThemeState.cssContent);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
129
186
|
if (resolvedThemeState.cssUrl) {
|
|
130
187
|
insertStylesIntoDOM(resolvedThemeState.cssUrl);
|
|
131
188
|
}
|
|
132
|
-
}, [resolvedThemeState.cssUrl]);
|
|
189
|
+
}, [resolvedThemeState.cssUrl, resolvedThemeState.cssContent]);
|
|
133
190
|
return { isLoading: state.status === "loading", theme, insertStyles };
|
|
134
191
|
}
|
|
135
192
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/buildCdnUrls.ts","../src/fetchThemeAssets.ts","../src/insertStylesIntoDOM.ts","../src/useThemeOps.ts"],"names":["themeLight"],"mappings":"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","file":"index.js","sourcesContent":["import type { BuildCdnUrlsOptions, CdnUrls } from \"./types\";\n\nconst CDN_BASE_URL = \"https://cdn.themeops.xyz\";\n\nfunction buildBasePath(organizationId: string, appId?: string): string {\n return appId ? `${organizationId}/${appId}` : organizationId;\n}\n\nexport function buildCdnUrls({\n organizationId,\n appId,\n}: BuildCdnUrlsOptions): CdnUrls {\n const assetPath = buildBasePath(organizationId, appId);\n\n return {\n theme: `${CDN_BASE_URL}/${assetPath}/theme.json`,\n themeFallback: `${CDN_BASE_URL}/${assetPath}/theme-fallback.json`,\n css: `${CDN_BASE_URL}/${assetPath}/css.css`,\n };\n}\n","import type { CdnUrls, ThemeAssets, ThemeJson, ThemeTokens } from \"./types\";\n\nasync function fetchJson<T>(url: string): Promise<T | null> {\n try {\n const response = await fetch(url);\n if (!response.ok) return null;\n\n return (await response.json()) as T;\n } catch {\n return null;\n }\n}\n\nfunction extractThemesFromJson(json: ThemeJson | null): {\n themeLight: ThemeTokens | null;\n themeDark: ThemeTokens | null;\n} {\n return {\n themeLight: json?.light ?? null,\n themeDark: json?.dark ?? null,\n };\n}\n\nfunction isThemeJsonValid(json: ThemeJson | null): boolean {\n return json !== null && (json.light !== undefined || json.dark !== undefined);\n}\n\nexport async function fetchThemeAssets(urls: CdnUrls): Promise<ThemeAssets> {\n const activeJson = await fetchJson<ThemeJson>(urls.theme);\n\n if (isThemeJsonValid(activeJson)) {\n const { themeLight, themeDark } = extractThemesFromJson(activeJson);\n return { themeLight, themeDark, cssUrl: urls.css, usingFallback: false };\n }\n\n const fallbackJson = await fetchJson<ThemeJson>(urls.themeFallback);\n const { themeLight } = extractThemesFromJson(fallbackJson);\n\n return {\n themeLight,\n themeDark: null,\n cssUrl: urls.css,\n usingFallback: true,\n };\n}\n","const STYLE_ELEMENT_ID = \"theme-ops-css\";\n\nfunction findExistingStyleElement(): HTMLLinkElement | null {\n return document.getElementById(STYLE_ELEMENT_ID) as HTMLLinkElement | null;\n}\n\nfunction createStyleElement(href: string): HTMLLinkElement {\n const link = document.createElement(\"link\");\n link.id = STYLE_ELEMENT_ID;\n link.rel = \"stylesheet\";\n link.href = href;\n return link;\n}\n\nexport function insertStylesIntoDOM(cssUrl: string): void {\n const existing = findExistingStyleElement();\n\n if (existing) {\n existing.href = cssUrl;\n return;\n }\n\n const link = createStyleElement(cssUrl);\n document.head.appendChild(link);\n}\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\n\nimport { buildCdnUrls } from \"./buildCdnUrls\";\nimport { fetchThemeAssets } from \"./fetchThemeAssets\";\nimport { insertStylesIntoDOM } from \"./insertStylesIntoDOM\";\nimport type {\n ThemeConfig,\n ThemeState,\n UseThemeOpsOptions,\n UseThemeOpsResult,\n} from \"./types\";\n\ntype HookState =\n | { status: \"loading\" }\n | { status: \"ready\"; themeState: ThemeState };\n\ntype HookAction =\n | { type: \"FETCH_START\" }\n | { type: \"FETCH_SUCCESS\"; payload: ThemeState };\n\nfunction hookReducer(_state: HookState, action: HookAction): HookState {\n switch (action.type) {\n case \"FETCH_START\":\n return { status: \"loading\" };\n case \"FETCH_SUCCESS\":\n return { status: \"ready\", themeState: action.payload };\n }\n}\n\nconst EMPTY_THEME_STATE: ThemeState = {\n themeLight: null,\n themeDark: null,\n cssUrl: null,\n};\n\nfunction resolveThemeTokens(\n mode: \"light\" | \"dark\",\n themeState: ThemeState,\n): ThemeConfig {\n const tokens = mode === \"dark\" ? themeState.themeDark : themeState.themeLight;\n if (!tokens) return {};\n\n return { token: tokens };\n}\n\nexport function useThemeOps({\n organizationId,\n appId,\n}: UseThemeOpsOptions): UseThemeOpsResult {\n const [state, dispatch] = useReducer(hookReducer, { status: \"loading\" });\n\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n\n dispatch({ type: \"FETCH_START\" });\n\n const urls = buildCdnUrls({ organizationId, appId });\n\n fetchThemeAssets(urls).then((assets) => {\n if (!isMountedRef.current) return;\n\n dispatch({\n type: \"FETCH_SUCCESS\",\n payload: {\n themeLight: assets.themeLight,\n themeDark: assets.themeDark,\n cssUrl: assets.cssUrl,\n },\n });\n });\n\n return () => {\n isMountedRef.current = false;\n };\n }, [organizationId, appId]);\n\n const resolvedThemeState =\n state.status === \"ready\" ? state.themeState : EMPTY_THEME_STATE;\n\n const theme = useCallback(\n (mode: \"light\" | \"dark\"): ThemeConfig =>\n resolveThemeTokens(mode, resolvedThemeState),\n [resolvedThemeState],\n );\n\n const insertStyles = useCallback(() => {\n if (resolvedThemeState.cssUrl) {\n insertStylesIntoDOM(resolvedThemeState.cssUrl);\n }\n }, [resolvedThemeState.cssUrl]);\n\n return { isLoading: state.status === \"loading\", theme, insertStyles };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/buildCdnUrls.ts","../src/fetchReleaseFromApi.ts","../src/fetchThemeAssets.ts","../src/insertStylesIntoDOM.ts","../src/useThemeOps.ts"],"names":["themeLight","themeDark","appendCacheBuster"],"mappings":"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","file":"index.js","sourcesContent":["import type { BuildCdnUrlsOptions, CdnUrls } from \"./types\";\n\nconst CDN_BASE_URL = \"https://theme-ops.s3.sa-east-1.amazonaws.com\";\n\nfunction buildBasePath(organizationId: string, appId?: string): string {\n return appId ? `${organizationId}/${appId}` : organizationId;\n}\n\nexport function buildCdnUrls({\n organizationId,\n appId,\n}: BuildCdnUrlsOptions): CdnUrls {\n const assetPath = buildBasePath(organizationId, appId);\n\n return {\n theme: `${CDN_BASE_URL}/${assetPath}/theme.json`,\n themeFallback: `${CDN_BASE_URL}/${assetPath}/theme-fallback.json`,\n css: `${CDN_BASE_URL}/${assetPath}/css.css`,\n };\n}\n","import type { ApiReleaseResponse, ThemeState } from \"./types\";\n\nfunction buildReleaseUrl(apiUrl: string, releaseId: string): string {\n const base = apiUrl.replace(/\\/+$/, \"\");\n return `${base}/sdk/releases/${releaseId}`;\n}\n\nexport async function fetchReleaseFromApi(\n apiUrl: string,\n releaseId: string,\n): Promise<ThemeState> {\n const url = buildReleaseUrl(apiUrl, releaseId);\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch release: ${response.status}`);\n }\n\n const data = (await response.json()) as ApiReleaseResponse;\n\n return {\n themeLight: data.themeLight,\n themeDark: data.themeDark,\n cssUrl: null,\n cssContent: data.css || null,\n };\n}\n","import type { CdnUrls, ThemeAssets, ThemeJson, ThemeTokens } from \"./types\";\n\nfunction appendCacheBuster(url: string): string {\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}_t=${Date.now()}`;\n}\n\nasync function fetchJson<T>(url: string): Promise<T | null> {\n try {\n const response = await fetch(appendCacheBuster(url));\n if (!response.ok) return null;\n\n return (await response.json()) as T;\n } catch {\n return null;\n }\n}\n\nfunction extractThemesFromJson(json: ThemeJson | null): {\n themeLight: ThemeTokens | null;\n themeDark: ThemeTokens | null;\n} {\n return {\n themeLight: json?.light ?? null,\n themeDark: json?.dark ?? null,\n };\n}\n\nfunction isThemeJsonValid(json: ThemeJson | null): boolean {\n return json !== null && (json.light !== undefined || json.dark !== undefined);\n}\n\nexport async function fetchThemeAssets(urls: CdnUrls): Promise<ThemeAssets> {\n const activeJson = await fetchJson<ThemeJson>(urls.theme);\n const cacheBustedCssUrl = appendCacheBuster(urls.css);\n\n if (isThemeJsonValid(activeJson)) {\n const { themeLight, themeDark } = extractThemesFromJson(activeJson);\n return { themeLight, themeDark, cssUrl: cacheBustedCssUrl, usingFallback: false };\n }\n\n const fallbackJson = await fetchJson<ThemeJson>(urls.themeFallback);\n const { themeLight, themeDark } = extractThemesFromJson(fallbackJson);\n\n return {\n themeLight,\n themeDark,\n cssUrl: cacheBustedCssUrl,\n usingFallback: true,\n };\n}\n","const LINK_ELEMENT_ID = \"theme-ops-css\";\nconst STYLE_ELEMENT_ID = \"theme-ops-css-inline\";\n\nfunction appendCacheBuster(url: string): string {\n const baseUrl = url.replace(/[?&]_t=\\d+/, \"\");\n const separator = baseUrl.includes(\"?\") ? \"&\" : \"?\";\n return `${baseUrl}${separator}_t=${Date.now()}`;\n}\n\nfunction findExistingLinkElement(): HTMLLinkElement | null {\n return document.getElementById(LINK_ELEMENT_ID) as HTMLLinkElement | null;\n}\n\nfunction findExistingInlineStyle(): HTMLStyleElement | null {\n return document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n}\n\nfunction createLinkElement(href: string): HTMLLinkElement {\n const link = document.createElement(\"link\");\n link.id = LINK_ELEMENT_ID;\n link.rel = \"stylesheet\";\n link.href = href;\n return link;\n}\n\nfunction removeElement(id: string): void {\n document.getElementById(id)?.remove();\n}\n\nexport function insertStylesIntoDOM(cssUrl: string): void {\n removeElement(STYLE_ELEMENT_ID);\n\n const freshUrl = appendCacheBuster(cssUrl);\n const existing = findExistingLinkElement();\n\n if (existing) {\n existing.href = freshUrl;\n return;\n }\n\n const link = createLinkElement(freshUrl);\n document.head.appendChild(link);\n}\n\nexport function insertInlineStylesIntoDOM(cssContent: string): void {\n removeElement(LINK_ELEMENT_ID);\n\n const existing = findExistingInlineStyle();\n\n if (existing) {\n existing.textContent = cssContent;\n return;\n }\n\n const style = document.createElement(\"style\");\n style.id = STYLE_ELEMENT_ID;\n style.textContent = cssContent;\n document.head.appendChild(style);\n}\n","import { useCallback, useEffect, useReducer, useRef } from \"react\";\n\nimport { buildCdnUrls } from \"./buildCdnUrls\";\nimport { fetchReleaseFromApi } from \"./fetchReleaseFromApi\";\nimport { fetchThemeAssets } from \"./fetchThemeAssets\";\nimport {\n insertInlineStylesIntoDOM,\n insertStylesIntoDOM,\n} from \"./insertStylesIntoDOM\";\nimport type {\n ThemeConfig,\n ThemeState,\n UseThemeOpsOptions,\n UseThemeOpsResult,\n} from \"./types\";\n\ntype HookState =\n | { status: \"loading\" }\n | { status: \"ready\"; themeState: ThemeState };\n\ntype HookAction =\n | { type: \"FETCH_START\" }\n | { type: \"FETCH_SUCCESS\"; payload: ThemeState };\n\nfunction hookReducer(_state: HookState, action: HookAction): HookState {\n switch (action.type) {\n case \"FETCH_START\":\n return { status: \"loading\" };\n case \"FETCH_SUCCESS\":\n return { status: \"ready\", themeState: action.payload };\n }\n}\n\nconst EMPTY_THEME_STATE: ThemeState = {\n themeLight: null,\n themeDark: null,\n cssUrl: null,\n cssContent: null,\n};\n\nfunction resolveThemeTokens(\n mode: \"light\" | \"dark\",\n themeState: ThemeState,\n): ThemeConfig {\n const tokens = mode === \"dark\" ? themeState.themeDark : themeState.themeLight;\n if (!tokens) return {};\n\n return tokens as ThemeConfig;\n}\n\nfunction isReleasePreview(\n options: UseThemeOpsOptions,\n): options is UseThemeOpsOptions & { releaseId: string; apiUrl: string } {\n return Boolean(options.releaseId && options.apiUrl);\n}\n\nexport function useThemeOps(options: UseThemeOpsOptions): UseThemeOpsResult {\n const { organizationId, appId, releaseId, apiUrl } = options;\n const [state, dispatch] = useReducer(hookReducer, { status: \"loading\" });\n\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n\n dispatch({ type: \"FETCH_START\" });\n\n const fetchPromise = isReleasePreview(options)\n ? fetchReleaseFromApi(options.apiUrl, options.releaseId)\n : fetchThemeAssets(buildCdnUrls({ organizationId, appId })).then(\n (assets) => ({\n themeLight: assets.themeLight,\n themeDark: assets.themeDark,\n cssUrl: assets.cssUrl,\n cssContent: null,\n }),\n );\n\n fetchPromise.then((payload) => {\n if (!isMountedRef.current) return;\n\n dispatch({ type: \"FETCH_SUCCESS\", payload });\n });\n\n return () => {\n isMountedRef.current = false;\n };\n }, [organizationId, appId, releaseId, apiUrl]);\n\n const resolvedThemeState =\n state.status === \"ready\" ? state.themeState : EMPTY_THEME_STATE;\n\n const theme = useCallback(\n (mode: \"light\" | \"dark\"): ThemeConfig =>\n resolveThemeTokens(mode, resolvedThemeState),\n [resolvedThemeState],\n );\n\n const insertStyles = useCallback(() => {\n if (resolvedThemeState.cssContent) {\n insertInlineStylesIntoDOM(resolvedThemeState.cssContent);\n return;\n }\n\n if (resolvedThemeState.cssUrl) {\n insertStylesIntoDOM(resolvedThemeState.cssUrl);\n }\n }, [resolvedThemeState.cssUrl, resolvedThemeState.cssContent]);\n\n return { isLoading: state.status === \"loading\", theme, insertStyles };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theme-ops-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "React SDK for Theme Ops — fetch and apply themes from CDN",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -56,4 +56,4 @@
|
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|