@lolyjs/core 0.2.0-alpha.2 → 0.2.0-alpha.21
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/LICENCE.md +9 -0
- package/README.md +1074 -761
- package/dist/{bootstrap-BiCQmSkx.d.mts → bootstrap-BfGTMUkj.d.mts} +19 -0
- package/dist/{bootstrap-BiCQmSkx.d.ts → bootstrap-BfGTMUkj.d.ts} +19 -0
- package/dist/cli.cjs +16997 -4416
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +17007 -4416
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +14731 -1652
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +295 -57
- package/dist/index.d.ts +295 -57
- package/dist/index.js +17190 -4102
- package/dist/index.js.map +1 -1
- package/dist/index.types-DMOO-uvF.d.mts +221 -0
- package/dist/index.types-DMOO-uvF.d.ts +221 -0
- package/dist/react/cache.cjs +107 -32
- package/dist/react/cache.cjs.map +1 -1
- package/dist/react/cache.d.mts +27 -21
- package/dist/react/cache.d.ts +27 -21
- package/dist/react/cache.js +107 -32
- package/dist/react/cache.js.map +1 -1
- package/dist/react/components.cjs +10 -8
- package/dist/react/components.cjs.map +1 -1
- package/dist/react/components.js +10 -8
- package/dist/react/components.js.map +1 -1
- package/dist/react/hooks.cjs +208 -26
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/hooks.d.mts +75 -15
- package/dist/react/hooks.d.ts +75 -15
- package/dist/react/hooks.js +208 -26
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/sockets.cjs +13 -6
- package/dist/react/sockets.cjs.map +1 -1
- package/dist/react/sockets.js +13 -6
- package/dist/react/sockets.js.map +1 -1
- package/dist/react/themes.cjs +61 -18
- package/dist/react/themes.cjs.map +1 -1
- package/dist/react/themes.js +63 -20
- package/dist/react/themes.js.map +1 -1
- package/dist/runtime.cjs +544 -111
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +540 -107
- package/dist/runtime.js.map +1 -1
- package/package.json +49 -4
package/dist/react/themes.cjs
CHANGED
|
@@ -32,19 +32,29 @@ var import_react2 = require("react");
|
|
|
32
32
|
var import_react = require("react");
|
|
33
33
|
var useBroadcastChannel = (channelName) => {
|
|
34
34
|
const [message, setMessage] = (0, import_react.useState)(null);
|
|
35
|
-
const
|
|
35
|
+
const channelRef = (0, import_react.useRef)(null);
|
|
36
36
|
(0, import_react.useEffect)(() => {
|
|
37
|
+
if (!channelRef.current && typeof window !== "undefined") {
|
|
38
|
+
channelRef.current = new BroadcastChannel(channelName);
|
|
39
|
+
}
|
|
40
|
+
const channel = channelRef.current;
|
|
41
|
+
if (!channel) return;
|
|
37
42
|
const handleMessage = (event) => {
|
|
38
43
|
setMessage(event.data);
|
|
39
44
|
};
|
|
40
45
|
channel.onmessage = handleMessage;
|
|
41
46
|
return () => {
|
|
42
|
-
|
|
47
|
+
if (channelRef.current) {
|
|
48
|
+
channelRef.current.close();
|
|
49
|
+
channelRef.current = null;
|
|
50
|
+
}
|
|
43
51
|
};
|
|
44
|
-
}, [
|
|
45
|
-
const sendMessage = (msg) => {
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
}, [channelName]);
|
|
53
|
+
const sendMessage = (0, import_react.useCallback)((msg) => {
|
|
54
|
+
if (channelRef.current) {
|
|
55
|
+
channelRef.current.postMessage(msg);
|
|
56
|
+
}
|
|
57
|
+
}, []);
|
|
48
58
|
return { message, sendMessage };
|
|
49
59
|
};
|
|
50
60
|
|
|
@@ -66,6 +76,7 @@ var ThemeProvider = ({
|
|
|
66
76
|
initialTheme
|
|
67
77
|
}) => {
|
|
68
78
|
const { message: themeMessage, sendMessage } = useBroadcastChannel("theme_channel");
|
|
79
|
+
const lastSentRef = (0, import_react2.useRef)(null);
|
|
69
80
|
const [theme, setTheme] = (0, import_react2.useState)(() => {
|
|
70
81
|
if (initialTheme) return initialTheme;
|
|
71
82
|
if (typeof window !== "undefined") {
|
|
@@ -80,10 +91,29 @@ var ThemeProvider = ({
|
|
|
80
91
|
});
|
|
81
92
|
(0, import_react2.useEffect)(() => {
|
|
82
93
|
if (!themeMessage) return;
|
|
83
|
-
if (themeMessage
|
|
84
|
-
|
|
94
|
+
if (themeMessage === lastSentRef.current) {
|
|
95
|
+
lastSentRef.current = null;
|
|
96
|
+
return;
|
|
85
97
|
}
|
|
86
|
-
|
|
98
|
+
setTheme((currentTheme) => {
|
|
99
|
+
if (themeMessage !== currentTheme) {
|
|
100
|
+
if (typeof document !== "undefined") {
|
|
101
|
+
document.cookie = `theme=${themeMessage}; path=/; max-age=31536000`;
|
|
102
|
+
}
|
|
103
|
+
if (typeof window !== "undefined") {
|
|
104
|
+
if (!window.__FW_DATA__) {
|
|
105
|
+
window.__FW_DATA__ = {};
|
|
106
|
+
}
|
|
107
|
+
window.__FW_DATA__ = {
|
|
108
|
+
...window.__FW_DATA__,
|
|
109
|
+
theme: themeMessage
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return themeMessage;
|
|
113
|
+
}
|
|
114
|
+
return currentTheme;
|
|
115
|
+
});
|
|
116
|
+
}, [themeMessage]);
|
|
87
117
|
(0, import_react2.useEffect)(() => {
|
|
88
118
|
const handleDataRefresh = () => {
|
|
89
119
|
if (typeof window !== "undefined") {
|
|
@@ -98,13 +128,15 @@ var ThemeProvider = ({
|
|
|
98
128
|
}
|
|
99
129
|
}
|
|
100
130
|
};
|
|
101
|
-
window
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
131
|
+
if (typeof window !== "undefined") {
|
|
132
|
+
window.addEventListener("fw-data-refresh", handleDataRefresh);
|
|
133
|
+
return () => {
|
|
134
|
+
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
105
137
|
}, []);
|
|
106
138
|
(0, import_react2.useEffect)(() => {
|
|
107
|
-
if (initialTheme) {
|
|
139
|
+
if (initialTheme && initialTheme !== theme) {
|
|
108
140
|
setTheme(initialTheme);
|
|
109
141
|
}
|
|
110
142
|
}, [initialTheme]);
|
|
@@ -120,17 +152,28 @@ var ThemeProvider = ({
|
|
|
120
152
|
if (body.className !== newClassName) {
|
|
121
153
|
body.className = newClassName;
|
|
122
154
|
}
|
|
123
|
-
|
|
124
|
-
}, [theme, sendMessage]);
|
|
155
|
+
}, [theme]);
|
|
125
156
|
const handleThemeChange = (newTheme) => {
|
|
126
157
|
setTheme(newTheme);
|
|
127
|
-
document
|
|
128
|
-
|
|
158
|
+
if (typeof document !== "undefined") {
|
|
159
|
+
document.cookie = `theme=${newTheme}; path=/; max-age=31536000`;
|
|
160
|
+
}
|
|
161
|
+
if (typeof window !== "undefined") {
|
|
162
|
+
if (!window.__FW_DATA__) {
|
|
163
|
+
window.__FW_DATA__ = {};
|
|
164
|
+
}
|
|
129
165
|
window.__FW_DATA__ = {
|
|
130
166
|
...window.__FW_DATA__,
|
|
131
167
|
theme: newTheme
|
|
132
168
|
};
|
|
133
169
|
}
|
|
170
|
+
lastSentRef.current = newTheme;
|
|
171
|
+
sendMessage(newTheme);
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
if (lastSentRef.current === newTheme) {
|
|
174
|
+
lastSentRef.current = null;
|
|
175
|
+
}
|
|
176
|
+
}, 500);
|
|
134
177
|
};
|
|
135
178
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value: { theme, handleThemeChange }, children });
|
|
136
179
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../modules/react/themes/index.ts","../../modules/react/themes/theme-provider/index.tsx","../../modules/react/hooks/useBroadcastChannel/index.tsx"],"sourcesContent":["export {\r\n ThemeProvider,\r\n useTheme\r\n} from './theme-provider';","import React, { createContext, useContext, useState, useEffect } from \"react\";\r\nimport { useBroadcastChannel } from \"../../hooks/useBroadcastChannel\";\r\n\r\nconst ThemeContext = createContext<{\r\n theme: string;\r\n handleThemeChange: (theme: string) => void;\r\n}>({ theme: \"light\", handleThemeChange: () => {} });\r\n\r\n// Helper function to get cookie value\r\nfunction getCookie(name: string): string | null {\r\n if (typeof document === \"undefined\") return null;\r\n const value = `; ${document.cookie}`;\r\n const parts = value.split(`; ${name}=`);\r\n if (parts.length === 2) {\r\n return parts.pop()?.split(\";\").shift() || null;\r\n }\r\n return null;\r\n}\r\n\r\nexport const ThemeProvider = ({ \r\n children,\r\n initialTheme \r\n}: { \r\n children: React.ReactNode;\r\n initialTheme?: string;\r\n}) => {\r\n const { message: themeMessage, sendMessage } = useBroadcastChannel('theme_channel');\r\n\r\n // Initialize theme consistently between server and client\r\n // The server renders with initialTheme, and we must use the same value on client\r\n // to avoid hydration mismatch. Priority: initialTheme prop > window.__FW_DATA__ > cookie > default\r\n const [theme, setTheme] = useState<string>(() => {\r\n // 1. Use prop if provided (this should match what server rendered)\r\n if (initialTheme) return initialTheme;\r\n \r\n // 2. On client, use window.__FW_DATA__ from SSR (this is set before hydration)\r\n // This ensures consistency between server and client\r\n if (typeof window !== \"undefined\") {\r\n const windowData = (window as any).__FW_DATA__;\r\n if (windowData?.theme) return windowData.theme;\r\n }\r\n \r\n // 3. Fallback to cookie (only if window.__FW_DATA__ not available yet)\r\n if (typeof window !== \"undefined\") {\r\n const cookieTheme = getCookie(\"theme\");\r\n if (cookieTheme) return cookieTheme;\r\n }\r\n \r\n // Default fallback\r\n return \"light\";\r\n });\r\n\r\n // Listen for theme changes from broadcast channel (other tabs/windows)\r\n useEffect(() => {\r\n if (!themeMessage) return;\r\n if (themeMessage !== theme) {\r\n setTheme(themeMessage);\r\n }\r\n }, [themeMessage, theme]);\r\n\r\n // Listen for theme changes from window.__FW_DATA__ during SPA navigation\r\n useEffect(() => {\r\n const handleDataRefresh = () => {\r\n if (typeof window !== \"undefined\") {\r\n const windowData = (window as any).__FW_DATA__;\r\n if (windowData?.theme) {\r\n // Use functional update to avoid stale closure\r\n setTheme((currentTheme) => {\r\n if (windowData.theme !== currentTheme) {\r\n return windowData.theme;\r\n }\r\n return currentTheme;\r\n });\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n\r\n return () => {\r\n window.removeEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n };\r\n }, []);\r\n\r\n // Update theme when initialTheme prop changes (e.g., during SPA navigation)\r\n // This is the primary way theme updates during SPA navigation when layout re-renders\r\n useEffect(() => {\r\n if (initialTheme) {\r\n // Always update if initialTheme is provided, even if it's the same\r\n // This ensures theme syncs correctly during SPA navigation\r\n setTheme(initialTheme);\r\n }\r\n }, [initialTheme]);\r\n\r\n // Update body class when theme changes (skip during initial hydration to avoid mismatch)\r\n useEffect(() => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const body = document.body;\r\n const currentClasses = body.className.split(\" \").filter(Boolean);\r\n \r\n // Remove old theme classes (light, dark, etc.)\r\n const themeClasses = [\"light\", \"dark\"];\r\n const filteredClasses = currentClasses.filter(\r\n (cls) => !themeClasses.includes(cls)\r\n );\r\n \r\n // Add new theme class\r\n const newClassName = [...filteredClasses, theme].filter(Boolean).join(\" \");\r\n \r\n // Only update if different to avoid unnecessary DOM updates\r\n if (body.className !== newClassName) {\r\n body.className = newClassName;\r\n }\r\n\r\n sendMessage(theme);\r\n }, [theme, sendMessage]);\r\n\r\n const handleThemeChange = (newTheme: string) => {\r\n setTheme(newTheme);\r\n\r\n // Set theme cookie\r\n document.cookie = `theme=${newTheme}; path=/; max-age=31536000`; // 1 year expiry\r\n\r\n // Update window.__FW_DATA__.theme so getCurrentTheme() returns the correct value during navigation\r\n if (typeof window !== \"undefined\" && (window as any).__FW_DATA__) {\r\n (window as any).__FW_DATA__ = {\r\n ...(window as any).__FW_DATA__,\r\n theme: newTheme,\r\n };\r\n }\r\n };\r\n\r\n return (\r\n <ThemeContext.Provider value={{ theme, handleThemeChange }}>\r\n {children}\r\n </ThemeContext.Provider>\r\n );\r\n};\r\n\r\nexport const useTheme = () => {\r\n return useContext(ThemeContext);\r\n};\r\n","import React, { useEffect, useState } from \"react\";\r\n\r\nexport const useBroadcastChannel = (channelName: string) => {\r\n const [message, setMessage] = useState(null);\r\n const channel = new BroadcastChannel(channelName);\r\n\r\n useEffect(() => {\r\n const handleMessage = (event: MessageEvent) => {\r\n setMessage(event.data);\r\n };\r\n\r\n channel.onmessage = handleMessage;\r\n\r\n // Clean up the channel when the component unmounts\r\n return () => {\r\n channel.close();\r\n };\r\n }, [channel]);\r\n\r\n const sendMessage = (msg: unknown) => {\r\n channel.postMessage(msg);\r\n };\r\n\r\n return { message, sendMessage };\r\n};\r\n"],"mappings":"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","names":["import_react"]}
|
|
1
|
+
{"version":3,"sources":["../../modules/react/themes/index.ts","../../modules/react/themes/theme-provider/index.tsx","../../modules/react/hooks/useBroadcastChannel/index.tsx"],"sourcesContent":["export {\r\n ThemeProvider,\r\n useTheme\r\n} from './theme-provider';","import React, { createContext, useContext, useState, useEffect, useRef } from \"react\";\r\nimport { useBroadcastChannel } from \"../../hooks/useBroadcastChannel\";\r\n\r\nconst ThemeContext = createContext<{\r\n theme: string;\r\n handleThemeChange: (theme: string) => void;\r\n}>({ theme: \"light\", handleThemeChange: () => {} });\r\n\r\n// Helper function to get cookie value\r\nfunction getCookie(name: string): string | null {\r\n if (typeof document === \"undefined\") return null;\r\n const value = `; ${document.cookie}`;\r\n const parts = value.split(`; ${name}=`);\r\n if (parts.length === 2) {\r\n return parts.pop()?.split(\";\").shift() || null;\r\n }\r\n return null;\r\n}\r\n\r\nexport const ThemeProvider = ({ \r\n children,\r\n initialTheme \r\n}: { \r\n children: React.ReactNode;\r\n initialTheme?: string;\r\n}) => {\r\n const { message: themeMessage, sendMessage } = useBroadcastChannel('theme_channel');\r\n \r\n // Track what we last sent to avoid loops\r\n const lastSentRef = useRef<string | null>(null);\r\n\r\n // Initialize theme consistently between server and client\r\n const [theme, setTheme] = useState<string>(() => {\r\n if (initialTheme) return initialTheme;\r\n \r\n if (typeof window !== \"undefined\") {\r\n const windowData = (window as any).__FW_DATA__;\r\n if (windowData?.theme) return windowData.theme;\r\n }\r\n \r\n if (typeof window !== \"undefined\") {\r\n const cookieTheme = getCookie(\"theme\");\r\n if (cookieTheme) return cookieTheme;\r\n }\r\n \r\n return \"light\";\r\n });\r\n\r\n // Handle messages from broadcast channel (other tabs)\r\n // This effect ONLY responds to themeMessage changes, not theme changes\r\n useEffect(() => {\r\n if (!themeMessage) return;\r\n \r\n // Ignore if this is a message we just sent\r\n if (themeMessage === lastSentRef.current) {\r\n lastSentRef.current = null;\r\n return;\r\n }\r\n \r\n // Only update if different from current theme\r\n setTheme((currentTheme) => {\r\n if (themeMessage !== currentTheme) {\r\n // Update cookie\r\n if (typeof document !== \"undefined\") {\r\n document.cookie = `theme=${themeMessage}; path=/; max-age=31536000`;\r\n }\r\n \r\n // Update window data\r\n if (typeof window !== \"undefined\") {\r\n if (!(window as any).__FW_DATA__) {\r\n (window as any).__FW_DATA__ = {};\r\n }\r\n (window as any).__FW_DATA__ = {\r\n ...(window as any).__FW_DATA__,\r\n theme: themeMessage,\r\n };\r\n }\r\n \r\n return themeMessage;\r\n }\r\n return currentTheme;\r\n });\r\n }, [themeMessage]); // Only depend on themeMessage, NOT theme!\r\n\r\n // Handle window.__FW_DATA__ changes during SPA navigation\r\n useEffect(() => {\r\n const handleDataRefresh = () => {\r\n if (typeof window !== \"undefined\") {\r\n const windowData = (window as any).__FW_DATA__;\r\n if (windowData?.theme) {\r\n setTheme((currentTheme) => {\r\n if (windowData.theme !== currentTheme) {\r\n return windowData.theme;\r\n }\r\n return currentTheme;\r\n });\r\n }\r\n }\r\n };\r\n\r\n if (typeof window !== \"undefined\") {\r\n window.addEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n return () => {\r\n window.removeEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n };\r\n }\r\n }, []); // No dependencies - event listener doesn't need theme\r\n\r\n // Handle initialTheme prop changes\r\n useEffect(() => {\r\n if (initialTheme && initialTheme !== theme) {\r\n setTheme(initialTheme);\r\n }\r\n }, [initialTheme]); // Only depend on initialTheme, not theme\r\n\r\n // Update body class when theme changes\r\n useEffect(() => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const body = document.body;\r\n const currentClasses = body.className.split(\" \").filter(Boolean);\r\n const themeClasses = [\"light\", \"dark\"];\r\n const filteredClasses = currentClasses.filter(\r\n (cls) => !themeClasses.includes(cls)\r\n );\r\n const newClassName = [...filteredClasses, theme].filter(Boolean).join(\" \");\r\n \r\n if (body.className !== newClassName) {\r\n body.className = newClassName;\r\n }\r\n }, [theme]);\r\n\r\n const handleThemeChange = (newTheme: string) => {\r\n // Update state immediately\r\n setTheme(newTheme);\r\n\r\n // Update cookie\r\n if (typeof document !== \"undefined\") {\r\n document.cookie = `theme=${newTheme}; path=/; max-age=31536000`;\r\n }\r\n\r\n // Update window data\r\n if (typeof window !== \"undefined\") {\r\n if (!(window as any).__FW_DATA__) {\r\n (window as any).__FW_DATA__ = {};\r\n }\r\n (window as any).__FW_DATA__ = {\r\n ...(window as any).__FW_DATA__,\r\n theme: newTheme,\r\n };\r\n }\r\n \r\n // Mark this as the last value we sent\r\n lastSentRef.current = newTheme;\r\n \r\n // Broadcast to other tabs\r\n sendMessage(newTheme);\r\n \r\n // Clear the ref after a delay\r\n setTimeout(() => {\r\n if (lastSentRef.current === newTheme) {\r\n lastSentRef.current = null;\r\n }\r\n }, 500);\r\n };\r\n\r\n return (\r\n <ThemeContext.Provider value={{ theme, handleThemeChange }}>\r\n {children}\r\n </ThemeContext.Provider>\r\n );\r\n};\r\n\r\nexport const useTheme = () => {\r\n return useContext(ThemeContext);\r\n};\r\n","import React, { useEffect, useState, useRef, useCallback } from \"react\";\r\n\r\nexport const useBroadcastChannel = (channelName: string) => {\r\n const [message, setMessage] = useState(null);\r\n const channelRef = useRef<BroadcastChannel | null>(null);\r\n\r\n useEffect(() => {\r\n // Create channel only once, inside useEffect\r\n if (!channelRef.current && typeof window !== \"undefined\") {\r\n channelRef.current = new BroadcastChannel(channelName);\r\n }\r\n\r\n const channel = channelRef.current;\r\n if (!channel) return;\r\n\r\n const handleMessage = (event: MessageEvent) => {\r\n setMessage(event.data);\r\n };\r\n\r\n channel.onmessage = handleMessage;\r\n\r\n // Clean up the channel when the component unmounts\r\n return () => {\r\n if (channelRef.current) {\r\n channelRef.current.close();\r\n channelRef.current = null;\r\n }\r\n };\r\n }, [channelName]);\r\n\r\n const sendMessage = useCallback((msg: unknown) => {\r\n if (channelRef.current) {\r\n channelRef.current.postMessage(msg);\r\n }\r\n }, []);\r\n\r\n return { message, sendMessage };\r\n};\r\n"],"mappings":"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","names":["import_react"]}
|
package/dist/react/themes.js
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
// modules/react/themes/theme-provider/index.tsx
|
|
2
|
-
import { createContext, useContext, useState as useState2, useEffect as useEffect2 } from "react";
|
|
2
|
+
import { createContext, useContext, useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
3
3
|
|
|
4
4
|
// modules/react/hooks/useBroadcastChannel/index.tsx
|
|
5
|
-
import { useEffect, useState } from "react";
|
|
5
|
+
import { useEffect, useState, useRef, useCallback } from "react";
|
|
6
6
|
var useBroadcastChannel = (channelName) => {
|
|
7
7
|
const [message, setMessage] = useState(null);
|
|
8
|
-
const
|
|
8
|
+
const channelRef = useRef(null);
|
|
9
9
|
useEffect(() => {
|
|
10
|
+
if (!channelRef.current && typeof window !== "undefined") {
|
|
11
|
+
channelRef.current = new BroadcastChannel(channelName);
|
|
12
|
+
}
|
|
13
|
+
const channel = channelRef.current;
|
|
14
|
+
if (!channel) return;
|
|
10
15
|
const handleMessage = (event) => {
|
|
11
16
|
setMessage(event.data);
|
|
12
17
|
};
|
|
13
18
|
channel.onmessage = handleMessage;
|
|
14
19
|
return () => {
|
|
15
|
-
|
|
20
|
+
if (channelRef.current) {
|
|
21
|
+
channelRef.current.close();
|
|
22
|
+
channelRef.current = null;
|
|
23
|
+
}
|
|
16
24
|
};
|
|
17
|
-
}, [
|
|
18
|
-
const sendMessage = (msg) => {
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
}, [channelName]);
|
|
26
|
+
const sendMessage = useCallback((msg) => {
|
|
27
|
+
if (channelRef.current) {
|
|
28
|
+
channelRef.current.postMessage(msg);
|
|
29
|
+
}
|
|
30
|
+
}, []);
|
|
21
31
|
return { message, sendMessage };
|
|
22
32
|
};
|
|
23
33
|
|
|
@@ -39,6 +49,7 @@ var ThemeProvider = ({
|
|
|
39
49
|
initialTheme
|
|
40
50
|
}) => {
|
|
41
51
|
const { message: themeMessage, sendMessage } = useBroadcastChannel("theme_channel");
|
|
52
|
+
const lastSentRef = useRef2(null);
|
|
42
53
|
const [theme, setTheme] = useState2(() => {
|
|
43
54
|
if (initialTheme) return initialTheme;
|
|
44
55
|
if (typeof window !== "undefined") {
|
|
@@ -53,10 +64,29 @@ var ThemeProvider = ({
|
|
|
53
64
|
});
|
|
54
65
|
useEffect2(() => {
|
|
55
66
|
if (!themeMessage) return;
|
|
56
|
-
if (themeMessage
|
|
57
|
-
|
|
67
|
+
if (themeMessage === lastSentRef.current) {
|
|
68
|
+
lastSentRef.current = null;
|
|
69
|
+
return;
|
|
58
70
|
}
|
|
59
|
-
|
|
71
|
+
setTheme((currentTheme) => {
|
|
72
|
+
if (themeMessage !== currentTheme) {
|
|
73
|
+
if (typeof document !== "undefined") {
|
|
74
|
+
document.cookie = `theme=${themeMessage}; path=/; max-age=31536000`;
|
|
75
|
+
}
|
|
76
|
+
if (typeof window !== "undefined") {
|
|
77
|
+
if (!window.__FW_DATA__) {
|
|
78
|
+
window.__FW_DATA__ = {};
|
|
79
|
+
}
|
|
80
|
+
window.__FW_DATA__ = {
|
|
81
|
+
...window.__FW_DATA__,
|
|
82
|
+
theme: themeMessage
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return themeMessage;
|
|
86
|
+
}
|
|
87
|
+
return currentTheme;
|
|
88
|
+
});
|
|
89
|
+
}, [themeMessage]);
|
|
60
90
|
useEffect2(() => {
|
|
61
91
|
const handleDataRefresh = () => {
|
|
62
92
|
if (typeof window !== "undefined") {
|
|
@@ -71,13 +101,15 @@ var ThemeProvider = ({
|
|
|
71
101
|
}
|
|
72
102
|
}
|
|
73
103
|
};
|
|
74
|
-
window
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
104
|
+
if (typeof window !== "undefined") {
|
|
105
|
+
window.addEventListener("fw-data-refresh", handleDataRefresh);
|
|
106
|
+
return () => {
|
|
107
|
+
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
78
110
|
}, []);
|
|
79
111
|
useEffect2(() => {
|
|
80
|
-
if (initialTheme) {
|
|
112
|
+
if (initialTheme && initialTheme !== theme) {
|
|
81
113
|
setTheme(initialTheme);
|
|
82
114
|
}
|
|
83
115
|
}, [initialTheme]);
|
|
@@ -93,17 +125,28 @@ var ThemeProvider = ({
|
|
|
93
125
|
if (body.className !== newClassName) {
|
|
94
126
|
body.className = newClassName;
|
|
95
127
|
}
|
|
96
|
-
|
|
97
|
-
}, [theme, sendMessage]);
|
|
128
|
+
}, [theme]);
|
|
98
129
|
const handleThemeChange = (newTheme) => {
|
|
99
130
|
setTheme(newTheme);
|
|
100
|
-
document
|
|
101
|
-
|
|
131
|
+
if (typeof document !== "undefined") {
|
|
132
|
+
document.cookie = `theme=${newTheme}; path=/; max-age=31536000`;
|
|
133
|
+
}
|
|
134
|
+
if (typeof window !== "undefined") {
|
|
135
|
+
if (!window.__FW_DATA__) {
|
|
136
|
+
window.__FW_DATA__ = {};
|
|
137
|
+
}
|
|
102
138
|
window.__FW_DATA__ = {
|
|
103
139
|
...window.__FW_DATA__,
|
|
104
140
|
theme: newTheme
|
|
105
141
|
};
|
|
106
142
|
}
|
|
143
|
+
lastSentRef.current = newTheme;
|
|
144
|
+
sendMessage(newTheme);
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
if (lastSentRef.current === newTheme) {
|
|
147
|
+
lastSentRef.current = null;
|
|
148
|
+
}
|
|
149
|
+
}, 500);
|
|
107
150
|
};
|
|
108
151
|
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, handleThemeChange }, children });
|
|
109
152
|
};
|
package/dist/react/themes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../modules/react/themes/theme-provider/index.tsx","../../modules/react/hooks/useBroadcastChannel/index.tsx"],"sourcesContent":["import React, { createContext, useContext, useState, useEffect } from \"react\";\r\nimport { useBroadcastChannel } from \"../../hooks/useBroadcastChannel\";\r\n\r\nconst ThemeContext = createContext<{\r\n theme: string;\r\n handleThemeChange: (theme: string) => void;\r\n}>({ theme: \"light\", handleThemeChange: () => {} });\r\n\r\n// Helper function to get cookie value\r\nfunction getCookie(name: string): string | null {\r\n if (typeof document === \"undefined\") return null;\r\n const value = `; ${document.cookie}`;\r\n const parts = value.split(`; ${name}=`);\r\n if (parts.length === 2) {\r\n return parts.pop()?.split(\";\").shift() || null;\r\n }\r\n return null;\r\n}\r\n\r\nexport const ThemeProvider = ({ \r\n children,\r\n initialTheme \r\n}: { \r\n children: React.ReactNode;\r\n initialTheme?: string;\r\n}) => {\r\n const { message: themeMessage, sendMessage } = useBroadcastChannel('theme_channel');\r\n\r\n // Initialize theme consistently between server and client\r\n // The server renders with initialTheme, and we must use the same value on client\r\n // to avoid hydration mismatch. Priority: initialTheme prop > window.__FW_DATA__ > cookie > default\r\n const [theme, setTheme] = useState<string>(() => {\r\n // 1. Use prop if provided (this should match what server rendered)\r\n if (initialTheme) return initialTheme;\r\n \r\n // 2. On client, use window.__FW_DATA__ from SSR (this is set before hydration)\r\n // This ensures consistency between server and client\r\n if (typeof window !== \"undefined\") {\r\n const windowData = (window as any).__FW_DATA__;\r\n if (windowData?.theme) return windowData.theme;\r\n }\r\n \r\n // 3. Fallback to cookie (only if window.__FW_DATA__ not available yet)\r\n if (typeof window !== \"undefined\") {\r\n const cookieTheme = getCookie(\"theme\");\r\n if (cookieTheme) return cookieTheme;\r\n }\r\n \r\n // Default fallback\r\n return \"light\";\r\n });\r\n\r\n // Listen for theme changes from broadcast channel (other tabs/windows)\r\n useEffect(() => {\r\n if (!themeMessage) return;\r\n if (themeMessage !== theme) {\r\n setTheme(themeMessage);\r\n }\r\n }, [themeMessage, theme]);\r\n\r\n // Listen for theme changes from window.__FW_DATA__ during SPA navigation\r\n useEffect(() => {\r\n const handleDataRefresh = () => {\r\n if (typeof window !== \"undefined\") {\r\n const windowData = (window as any).__FW_DATA__;\r\n if (windowData?.theme) {\r\n // Use functional update to avoid stale closure\r\n setTheme((currentTheme) => {\r\n if (windowData.theme !== currentTheme) {\r\n return windowData.theme;\r\n }\r\n return currentTheme;\r\n });\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n\r\n return () => {\r\n window.removeEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n };\r\n }, []);\r\n\r\n // Update theme when initialTheme prop changes (e.g., during SPA navigation)\r\n // This is the primary way theme updates during SPA navigation when layout re-renders\r\n useEffect(() => {\r\n if (initialTheme) {\r\n // Always update if initialTheme is provided, even if it's the same\r\n // This ensures theme syncs correctly during SPA navigation\r\n setTheme(initialTheme);\r\n }\r\n }, [initialTheme]);\r\n\r\n // Update body class when theme changes (skip during initial hydration to avoid mismatch)\r\n useEffect(() => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const body = document.body;\r\n const currentClasses = body.className.split(\" \").filter(Boolean);\r\n \r\n // Remove old theme classes (light, dark, etc.)\r\n const themeClasses = [\"light\", \"dark\"];\r\n const filteredClasses = currentClasses.filter(\r\n (cls) => !themeClasses.includes(cls)\r\n );\r\n \r\n // Add new theme class\r\n const newClassName = [...filteredClasses, theme].filter(Boolean).join(\" \");\r\n \r\n // Only update if different to avoid unnecessary DOM updates\r\n if (body.className !== newClassName) {\r\n body.className = newClassName;\r\n }\r\n\r\n sendMessage(theme);\r\n }, [theme, sendMessage]);\r\n\r\n const handleThemeChange = (newTheme: string) => {\r\n setTheme(newTheme);\r\n\r\n // Set theme cookie\r\n document.cookie = `theme=${newTheme}; path=/; max-age=31536000`; // 1 year expiry\r\n\r\n // Update window.__FW_DATA__.theme so getCurrentTheme() returns the correct value during navigation\r\n if (typeof window !== \"undefined\" && (window as any).__FW_DATA__) {\r\n (window as any).__FW_DATA__ = {\r\n ...(window as any).__FW_DATA__,\r\n theme: newTheme,\r\n };\r\n }\r\n };\r\n\r\n return (\r\n <ThemeContext.Provider value={{ theme, handleThemeChange }}>\r\n {children}\r\n </ThemeContext.Provider>\r\n );\r\n};\r\n\r\nexport const useTheme = () => {\r\n return useContext(ThemeContext);\r\n};\r\n","import React, { useEffect, useState } from \"react\";\r\n\r\nexport const useBroadcastChannel = (channelName: string) => {\r\n const [message, setMessage] = useState(null);\r\n const channel = new BroadcastChannel(channelName);\r\n\r\n useEffect(() => {\r\n const handleMessage = (event: MessageEvent) => {\r\n setMessage(event.data);\r\n };\r\n\r\n channel.onmessage = handleMessage;\r\n\r\n // Clean up the channel when the component unmounts\r\n return () => {\r\n channel.close();\r\n };\r\n }, [channel]);\r\n\r\n const sendMessage = (msg: unknown) => {\r\n channel.postMessage(msg);\r\n };\r\n\r\n return { message, sendMessage };\r\n};\r\n"],"mappings":"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","names":["useState","useEffect","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../../modules/react/themes/theme-provider/index.tsx","../../modules/react/hooks/useBroadcastChannel/index.tsx"],"sourcesContent":["import React, { createContext, useContext, useState, useEffect, useRef } from \"react\";\r\nimport { useBroadcastChannel } from \"../../hooks/useBroadcastChannel\";\r\n\r\nconst ThemeContext = createContext<{\r\n theme: string;\r\n handleThemeChange: (theme: string) => void;\r\n}>({ theme: \"light\", handleThemeChange: () => {} });\r\n\r\n// Helper function to get cookie value\r\nfunction getCookie(name: string): string | null {\r\n if (typeof document === \"undefined\") return null;\r\n const value = `; ${document.cookie}`;\r\n const parts = value.split(`; ${name}=`);\r\n if (parts.length === 2) {\r\n return parts.pop()?.split(\";\").shift() || null;\r\n }\r\n return null;\r\n}\r\n\r\nexport const ThemeProvider = ({ \r\n children,\r\n initialTheme \r\n}: { \r\n children: React.ReactNode;\r\n initialTheme?: string;\r\n}) => {\r\n const { message: themeMessage, sendMessage } = useBroadcastChannel('theme_channel');\r\n \r\n // Track what we last sent to avoid loops\r\n const lastSentRef = useRef<string | null>(null);\r\n\r\n // Initialize theme consistently between server and client\r\n const [theme, setTheme] = useState<string>(() => {\r\n if (initialTheme) return initialTheme;\r\n \r\n if (typeof window !== \"undefined\") {\r\n const windowData = (window as any).__FW_DATA__;\r\n if (windowData?.theme) return windowData.theme;\r\n }\r\n \r\n if (typeof window !== \"undefined\") {\r\n const cookieTheme = getCookie(\"theme\");\r\n if (cookieTheme) return cookieTheme;\r\n }\r\n \r\n return \"light\";\r\n });\r\n\r\n // Handle messages from broadcast channel (other tabs)\r\n // This effect ONLY responds to themeMessage changes, not theme changes\r\n useEffect(() => {\r\n if (!themeMessage) return;\r\n \r\n // Ignore if this is a message we just sent\r\n if (themeMessage === lastSentRef.current) {\r\n lastSentRef.current = null;\r\n return;\r\n }\r\n \r\n // Only update if different from current theme\r\n setTheme((currentTheme) => {\r\n if (themeMessage !== currentTheme) {\r\n // Update cookie\r\n if (typeof document !== \"undefined\") {\r\n document.cookie = `theme=${themeMessage}; path=/; max-age=31536000`;\r\n }\r\n \r\n // Update window data\r\n if (typeof window !== \"undefined\") {\r\n if (!(window as any).__FW_DATA__) {\r\n (window as any).__FW_DATA__ = {};\r\n }\r\n (window as any).__FW_DATA__ = {\r\n ...(window as any).__FW_DATA__,\r\n theme: themeMessage,\r\n };\r\n }\r\n \r\n return themeMessage;\r\n }\r\n return currentTheme;\r\n });\r\n }, [themeMessage]); // Only depend on themeMessage, NOT theme!\r\n\r\n // Handle window.__FW_DATA__ changes during SPA navigation\r\n useEffect(() => {\r\n const handleDataRefresh = () => {\r\n if (typeof window !== \"undefined\") {\r\n const windowData = (window as any).__FW_DATA__;\r\n if (windowData?.theme) {\r\n setTheme((currentTheme) => {\r\n if (windowData.theme !== currentTheme) {\r\n return windowData.theme;\r\n }\r\n return currentTheme;\r\n });\r\n }\r\n }\r\n };\r\n\r\n if (typeof window !== \"undefined\") {\r\n window.addEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n return () => {\r\n window.removeEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n };\r\n }\r\n }, []); // No dependencies - event listener doesn't need theme\r\n\r\n // Handle initialTheme prop changes\r\n useEffect(() => {\r\n if (initialTheme && initialTheme !== theme) {\r\n setTheme(initialTheme);\r\n }\r\n }, [initialTheme]); // Only depend on initialTheme, not theme\r\n\r\n // Update body class when theme changes\r\n useEffect(() => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const body = document.body;\r\n const currentClasses = body.className.split(\" \").filter(Boolean);\r\n const themeClasses = [\"light\", \"dark\"];\r\n const filteredClasses = currentClasses.filter(\r\n (cls) => !themeClasses.includes(cls)\r\n );\r\n const newClassName = [...filteredClasses, theme].filter(Boolean).join(\" \");\r\n \r\n if (body.className !== newClassName) {\r\n body.className = newClassName;\r\n }\r\n }, [theme]);\r\n\r\n const handleThemeChange = (newTheme: string) => {\r\n // Update state immediately\r\n setTheme(newTheme);\r\n\r\n // Update cookie\r\n if (typeof document !== \"undefined\") {\r\n document.cookie = `theme=${newTheme}; path=/; max-age=31536000`;\r\n }\r\n\r\n // Update window data\r\n if (typeof window !== \"undefined\") {\r\n if (!(window as any).__FW_DATA__) {\r\n (window as any).__FW_DATA__ = {};\r\n }\r\n (window as any).__FW_DATA__ = {\r\n ...(window as any).__FW_DATA__,\r\n theme: newTheme,\r\n };\r\n }\r\n \r\n // Mark this as the last value we sent\r\n lastSentRef.current = newTheme;\r\n \r\n // Broadcast to other tabs\r\n sendMessage(newTheme);\r\n \r\n // Clear the ref after a delay\r\n setTimeout(() => {\r\n if (lastSentRef.current === newTheme) {\r\n lastSentRef.current = null;\r\n }\r\n }, 500);\r\n };\r\n\r\n return (\r\n <ThemeContext.Provider value={{ theme, handleThemeChange }}>\r\n {children}\r\n </ThemeContext.Provider>\r\n );\r\n};\r\n\r\nexport const useTheme = () => {\r\n return useContext(ThemeContext);\r\n};\r\n","import React, { useEffect, useState, useRef, useCallback } from \"react\";\r\n\r\nexport const useBroadcastChannel = (channelName: string) => {\r\n const [message, setMessage] = useState(null);\r\n const channelRef = useRef<BroadcastChannel | null>(null);\r\n\r\n useEffect(() => {\r\n // Create channel only once, inside useEffect\r\n if (!channelRef.current && typeof window !== \"undefined\") {\r\n channelRef.current = new BroadcastChannel(channelName);\r\n }\r\n\r\n const channel = channelRef.current;\r\n if (!channel) return;\r\n\r\n const handleMessage = (event: MessageEvent) => {\r\n setMessage(event.data);\r\n };\r\n\r\n channel.onmessage = handleMessage;\r\n\r\n // Clean up the channel when the component unmounts\r\n return () => {\r\n if (channelRef.current) {\r\n channelRef.current.close();\r\n channelRef.current = null;\r\n }\r\n };\r\n }, [channelName]);\r\n\r\n const sendMessage = useCallback((msg: unknown) => {\r\n if (channelRef.current) {\r\n channelRef.current.postMessage(msg);\r\n }\r\n }, []);\r\n\r\n return { message, sendMessage };\r\n};\r\n"],"mappings":"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","names":["useState","useEffect","useRef","useRef","useState","useEffect"]}
|