@lolyjs/core 0.2.0-alpha.3 → 0.2.0-alpha.4
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/{bootstrap-BiCQmSkx.d.mts → bootstrap-DgvWWDim.d.mts} +7 -0
- package/dist/{bootstrap-BiCQmSkx.d.ts → bootstrap-DgvWWDim.d.ts} +7 -0
- package/dist/cli.cjs +29 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +29 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +86 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +86 -1
- package/dist/index.js.map +1 -1
- package/dist/react/hooks.cjs +98 -22
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/hooks.d.mts +5 -0
- package/dist/react/hooks.d.ts +5 -0
- package/dist/react/hooks.js +99 -23
- package/dist/react/hooks.js.map +1 -1
- package/dist/runtime.cjs +57 -0
- 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 +57 -0
- package/dist/runtime.js.map +1 -1
- package/package.json +2 -2
package/dist/react/hooks.cjs
CHANGED
|
@@ -89,6 +89,8 @@ var RouterContext = (0, import_react3.createContext)(null);
|
|
|
89
89
|
|
|
90
90
|
// modules/runtime/client/constants.ts
|
|
91
91
|
var WINDOW_DATA_KEY = "__FW_DATA__";
|
|
92
|
+
var ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
|
|
93
|
+
var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
|
|
92
94
|
|
|
93
95
|
// modules/runtime/client/window-data.ts
|
|
94
96
|
function getWindowData() {
|
|
@@ -97,77 +99,133 @@ function getWindowData() {
|
|
|
97
99
|
}
|
|
98
100
|
return window[WINDOW_DATA_KEY] ?? null;
|
|
99
101
|
}
|
|
102
|
+
function getRouterData() {
|
|
103
|
+
if (typeof window === "undefined") {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return window[ROUTER_DATA_KEY] ?? null;
|
|
107
|
+
}
|
|
100
108
|
|
|
101
109
|
// modules/react/hooks/useRouter/index.ts
|
|
102
110
|
function useRouter() {
|
|
103
111
|
const context = (0, import_react4.useContext)(RouterContext);
|
|
104
112
|
const navigate = context?.navigate;
|
|
113
|
+
const navigateRef = (0, import_react4.useRef)(navigate);
|
|
114
|
+
(0, import_react4.useEffect)(() => {
|
|
115
|
+
navigateRef.current = navigate;
|
|
116
|
+
}, [navigate]);
|
|
105
117
|
const [routeData, setRouteData] = (0, import_react4.useState)(() => {
|
|
106
118
|
if (typeof window === "undefined") {
|
|
107
119
|
return {
|
|
108
120
|
pathname: "",
|
|
109
121
|
query: {},
|
|
122
|
+
searchParams: {},
|
|
110
123
|
params: {}
|
|
111
124
|
};
|
|
112
125
|
}
|
|
113
126
|
const data = getWindowData();
|
|
127
|
+
const routerData = getRouterData();
|
|
128
|
+
const searchParams = routerData?.searchParams || parseQueryString(window.location.search);
|
|
114
129
|
return {
|
|
115
|
-
pathname: data?.pathname || window.location.pathname,
|
|
116
|
-
query:
|
|
117
|
-
|
|
130
|
+
pathname: routerData?.pathname || data?.pathname || window.location.pathname,
|
|
131
|
+
query: searchParams,
|
|
132
|
+
// For backward compatibility
|
|
133
|
+
searchParams,
|
|
134
|
+
params: routerData?.params || data?.params || {}
|
|
118
135
|
};
|
|
119
136
|
});
|
|
120
137
|
(0, import_react4.useEffect)(() => {
|
|
121
138
|
if (typeof window === "undefined") return;
|
|
122
139
|
const handleDataRefresh = () => {
|
|
123
140
|
const data = getWindowData();
|
|
141
|
+
const routerData = getRouterData();
|
|
124
142
|
const currentPathname = window.location.pathname;
|
|
125
143
|
const currentSearch = window.location.search;
|
|
144
|
+
const searchParams = routerData?.searchParams || parseQueryString(currentSearch);
|
|
126
145
|
setRouteData({
|
|
127
|
-
pathname: data?.pathname || currentPathname,
|
|
128
|
-
query:
|
|
129
|
-
|
|
146
|
+
pathname: routerData?.pathname || data?.pathname || currentPathname,
|
|
147
|
+
query: searchParams,
|
|
148
|
+
// For backward compatibility
|
|
149
|
+
searchParams,
|
|
150
|
+
params: routerData?.params || data?.params || {}
|
|
130
151
|
});
|
|
131
152
|
};
|
|
132
153
|
window.addEventListener("fw-data-refresh", handleDataRefresh);
|
|
154
|
+
window.addEventListener("fw-router-data-refresh", handleDataRefresh);
|
|
133
155
|
const handlePopState = () => {
|
|
134
156
|
handleDataRefresh();
|
|
135
157
|
};
|
|
136
158
|
window.addEventListener("popstate", handlePopState);
|
|
137
159
|
return () => {
|
|
138
160
|
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
161
|
+
window.removeEventListener("fw-router-data-refresh", handleDataRefresh);
|
|
139
162
|
window.removeEventListener("popstate", handlePopState);
|
|
140
163
|
};
|
|
141
164
|
}, []);
|
|
142
165
|
const push = (0, import_react4.useCallback)(
|
|
143
166
|
async (url, options) => {
|
|
144
167
|
const fullUrl = url.startsWith("/") ? url : `/${url}`;
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
168
|
+
const getCurrentNavigate = () => {
|
|
169
|
+
if (navigateRef.current) return navigateRef.current;
|
|
170
|
+
if (navigate) return navigate;
|
|
171
|
+
if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
|
|
172
|
+
return window[ROUTER_NAVIGATE_KEY];
|
|
148
173
|
}
|
|
174
|
+
return null;
|
|
175
|
+
};
|
|
176
|
+
let currentNavigate = getCurrentNavigate();
|
|
177
|
+
if (typeof window === "undefined") {
|
|
149
178
|
return;
|
|
150
179
|
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
180
|
+
if (!currentNavigate) {
|
|
181
|
+
for (let i = 0; i < 10; i++) {
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
183
|
+
currentNavigate = getCurrentNavigate();
|
|
184
|
+
if (currentNavigate) break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (!currentNavigate) {
|
|
188
|
+
window.location.href = fullUrl;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
192
|
+
if (fullUrl === currentUrl) {
|
|
193
|
+
return;
|
|
153
194
|
}
|
|
154
|
-
|
|
195
|
+
window.history.pushState({}, "", fullUrl);
|
|
196
|
+
await currentNavigate(fullUrl, options);
|
|
155
197
|
},
|
|
156
198
|
[navigate]
|
|
199
|
+
// Include navigate in dependencies so it updates when context becomes available
|
|
157
200
|
);
|
|
158
201
|
const replace = (0, import_react4.useCallback)(
|
|
159
202
|
async (url, options) => {
|
|
160
203
|
const fullUrl = url.startsWith("/") ? url : `/${url}`;
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
204
|
+
const getCurrentNavigate = () => {
|
|
205
|
+
if (navigateRef.current) return navigateRef.current;
|
|
206
|
+
if (navigate) return navigate;
|
|
207
|
+
if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
|
|
208
|
+
return window[ROUTER_NAVIGATE_KEY];
|
|
164
209
|
}
|
|
210
|
+
return null;
|
|
211
|
+
};
|
|
212
|
+
let currentNavigate = getCurrentNavigate();
|
|
213
|
+
if (typeof window === "undefined") {
|
|
165
214
|
return;
|
|
166
215
|
}
|
|
167
|
-
if (
|
|
168
|
-
|
|
216
|
+
if (!currentNavigate) {
|
|
217
|
+
for (let i = 0; i < 10; i++) {
|
|
218
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
219
|
+
currentNavigate = getCurrentNavigate();
|
|
220
|
+
if (currentNavigate) break;
|
|
221
|
+
}
|
|
169
222
|
}
|
|
170
|
-
|
|
223
|
+
if (!currentNavigate) {
|
|
224
|
+
window.location.replace(fullUrl);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
window.history.replaceState({}, "", fullUrl);
|
|
228
|
+
await currentNavigate(fullUrl, options);
|
|
171
229
|
},
|
|
172
230
|
[navigate]
|
|
173
231
|
);
|
|
@@ -178,13 +236,30 @@ function useRouter() {
|
|
|
178
236
|
}, []);
|
|
179
237
|
const refresh = (0, import_react4.useCallback)(async () => {
|
|
180
238
|
const currentUrl = typeof window !== "undefined" ? window.location.pathname + window.location.search : routeData.pathname;
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
|
|
239
|
+
const getCurrentNavigate = () => {
|
|
240
|
+
if (navigateRef.current) return navigateRef.current;
|
|
241
|
+
if (navigate) return navigate;
|
|
242
|
+
if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
|
|
243
|
+
return window[ROUTER_NAVIGATE_KEY];
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
};
|
|
247
|
+
let currentNavigate = getCurrentNavigate();
|
|
248
|
+
if (typeof window === "undefined") {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!currentNavigate) {
|
|
252
|
+
for (let i = 0; i < 10; i++) {
|
|
253
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
254
|
+
currentNavigate = getCurrentNavigate();
|
|
255
|
+
if (currentNavigate) break;
|
|
184
256
|
}
|
|
257
|
+
}
|
|
258
|
+
if (!currentNavigate) {
|
|
259
|
+
window.location.reload();
|
|
185
260
|
return;
|
|
186
261
|
}
|
|
187
|
-
await
|
|
262
|
+
await currentNavigate(currentUrl, { revalidate: true });
|
|
188
263
|
}, [navigate, routeData.pathname]);
|
|
189
264
|
return {
|
|
190
265
|
push,
|
|
@@ -193,6 +268,7 @@ function useRouter() {
|
|
|
193
268
|
refresh,
|
|
194
269
|
pathname: routeData.pathname,
|
|
195
270
|
query: routeData.query,
|
|
271
|
+
searchParams: routeData.searchParams,
|
|
196
272
|
params: routeData.params
|
|
197
273
|
};
|
|
198
274
|
}
|
package/dist/react/hooks.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../modules/react/hooks/index.ts","../../modules/react/hooks/useBroadcastChannel/index.tsx","../../modules/react/hooks/usePageProps/index.ts","../../modules/react/hooks/useRouter/index.ts","../../modules/runtime/client/RouterContext.tsx","../../modules/runtime/client/constants.ts","../../modules/runtime/client/window-data.ts"],"sourcesContent":["export { useBroadcastChannel } from \"./useBroadcastChannel\";\r\nexport { usePageProps } from \"./usePageProps\";\r\nexport { useRouter } from \"./useRouter\";\r\nexport type { Router } from \"./useRouter\";\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","import React, { useEffect, useState } from \"react\";\r\n\r\n/**\r\n * Hook to access page props and route parameters.\r\n *\r\n * Reads initial data from window.__FW_DATA__ set during SSR.\r\n * Automatically updates when `revalidate()` is called.\r\n *\r\n * @template P - Type for page props (default: any)\r\n * @template T - Type for route params (default: any)\r\n * @returns Object containing params and props\r\n *\r\n * @example\r\n * // With props type only\r\n * const { props } = usePageProps<{ title: string }>();\r\n *\r\n * @example\r\n * // With both props and params types\r\n * const { props, params } = usePageProps<{ title: string }, { id: string }>();\r\n */\r\nexport function usePageProps<P = any, T = any>(): { params: T, props: P } {\r\n const [state, setState] = useState<{ params: T, props: P }>(() => {\r\n // Initialize with current data if available\r\n if (typeof window !== \"undefined\" && (window as any)?.__FW_DATA__) {\r\n const data = (window as any).__FW_DATA__;\r\n return {\r\n params: data.params || {} as T,\r\n props: data.props || {} as P,\r\n };\r\n }\r\n return {\r\n params: {} as T,\r\n props: {} as P,\r\n };\r\n });\r\n\r\n useEffect(() => {\r\n // Listen for data refresh events (from revalidate() or navigation)\r\n const handleDataRefresh = () => {\r\n if ((window as any)?.__FW_DATA__) {\r\n const data = (window as any).__FW_DATA__;\r\n setState({\r\n params: data.params || {} as T,\r\n props: data.props || {} as P,\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 return { params: state.params as T, props: state.props as P };\r\n};\r\n","import { useState, useEffect, useCallback, useContext } from \"react\";\r\nimport { RouterContext } from \"../../../runtime/client/RouterContext\";\r\nimport { getWindowData } from \"../../../runtime/client/window-data\";\r\n\r\nexport interface Router {\r\n /**\r\n * Navigate to a new route.\r\n * @param url - The URL to navigate to (e.g., \"/about\" or \"/blog/[slug]\" with params)\r\n * @param options - Navigation options\r\n */\r\n push: (url: string, options?: { revalidate?: boolean }) => Promise<void>;\r\n \r\n /**\r\n * Replace the current route without adding to history.\r\n * @param url - The URL to navigate to\r\n * @param options - Navigation options\r\n */\r\n replace: (url: string, options?: { revalidate?: boolean }) => Promise<void>;\r\n \r\n /**\r\n * Go back in the browser history.\r\n */\r\n back: () => void;\r\n \r\n /**\r\n * Refresh the current route data by revalidating.\r\n */\r\n refresh: () => Promise<void>;\r\n \r\n /**\r\n * Current pathname (e.g., \"/blog/my-post\")\r\n */\r\n pathname: string;\r\n \r\n /**\r\n * Query parameters from the URL (e.g., ?id=123&name=test)\r\n */\r\n query: Record<string, string>;\r\n \r\n /**\r\n * Dynamic route parameters (e.g., { slug: \"my-post\" } for /blog/[slug])\r\n */\r\n params: Record<string, string>;\r\n}\r\n\r\n/**\r\n * Hook to access router functionality and current route information.\r\n * \r\n * Provides methods to navigate programmatically and access current route data.\r\n * \r\n * @returns Router object with navigation methods and route information\r\n * \r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const router = useRouter();\r\n * \r\n * const handleClick = () => {\r\n * router.push(\"/about\");\r\n * };\r\n * \r\n * return (\r\n * <div>\r\n * <p>Current path: {router.pathname}</p>\r\n * <p>Params: {JSON.stringify(router.params)}</p>\r\n * <button onClick={handleClick}>Go to About</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n * \r\n * @example\r\n * ```tsx\r\n * // Navigate with dynamic params\r\n * router.push(\"/blog/my-post\");\r\n * \r\n * // Replace current route\r\n * router.replace(\"/login\");\r\n * \r\n * // Refresh current route data\r\n * await router.refresh();\r\n * ```\r\n */\r\nexport function useRouter(): Router {\r\n // Try to get context, but don't throw if it's not available (SSR)\r\n const context = useContext(RouterContext);\r\n const navigate = context?.navigate;\r\n \r\n const [routeData, setRouteData] = useState(() => {\r\n // During SSR, return empty/default values\r\n if (typeof window === \"undefined\") {\r\n return {\r\n pathname: \"\",\r\n query: {},\r\n params: {},\r\n };\r\n }\r\n \r\n // On client, get data from window\r\n const data = getWindowData();\r\n return {\r\n pathname: data?.pathname || window.location.pathname,\r\n query: parseQueryString(window.location.search),\r\n params: data?.params || {},\r\n };\r\n });\r\n\r\n // Listen for route changes (only on client)\r\n useEffect(() => {\r\n if (typeof window === \"undefined\") return;\r\n \r\n const handleDataRefresh = () => {\r\n const data = getWindowData();\r\n const currentPathname = window.location.pathname;\r\n const currentSearch = window.location.search;\r\n \r\n setRouteData({\r\n pathname: data?.pathname || currentPathname,\r\n query: parseQueryString(currentSearch),\r\n params: data?.params || {},\r\n });\r\n };\r\n\r\n // Listen for navigation events\r\n window.addEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n \r\n // Also listen for popstate (browser back/forward)\r\n const handlePopState = () => {\r\n handleDataRefresh();\r\n };\r\n window.addEventListener(\"popstate\", handlePopState);\r\n\r\n return () => {\r\n window.removeEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n window.removeEventListener(\"popstate\", handlePopState);\r\n };\r\n }, []);\r\n\r\n const push = useCallback(\r\n async (url: string, options?: { revalidate?: boolean }) => {\r\n const fullUrl = url.startsWith(\"/\") ? url : `/${url}`;\r\n \r\n // During SSR or if context is not available, use window.location\r\n if (!navigate) {\r\n if (typeof window !== \"undefined\") {\r\n window.location.href = fullUrl;\r\n }\r\n return;\r\n }\r\n \r\n if (typeof window !== \"undefined\") {\r\n window.history.pushState({}, \"\", fullUrl);\r\n }\r\n await navigate(fullUrl, options);\r\n },\r\n [navigate]\r\n );\r\n\r\n const replace = useCallback(\r\n async (url: string, options?: { revalidate?: boolean }) => {\r\n const fullUrl = url.startsWith(\"/\") ? url : `/${url}`;\r\n \r\n // During SSR or if context is not available, use window.location\r\n if (!navigate) {\r\n if (typeof window !== \"undefined\") {\r\n window.location.replace(fullUrl);\r\n }\r\n return;\r\n }\r\n \r\n if (typeof window !== \"undefined\") {\r\n window.history.replaceState({}, \"\", fullUrl);\r\n }\r\n await navigate(fullUrl, options);\r\n },\r\n [navigate]\r\n );\r\n\r\n const back = useCallback(() => {\r\n if (typeof window !== \"undefined\") {\r\n window.history.back();\r\n }\r\n }, []);\r\n\r\n const refresh = useCallback(async () => {\r\n const currentUrl = typeof window !== \"undefined\" \r\n ? window.location.pathname + window.location.search \r\n : routeData.pathname;\r\n \r\n // During SSR or if context is not available, reload the page\r\n if (!navigate) {\r\n if (typeof window !== \"undefined\") {\r\n window.location.reload();\r\n }\r\n return;\r\n }\r\n \r\n await navigate(currentUrl, { revalidate: true });\r\n }, [navigate, routeData.pathname]);\r\n\r\n return {\r\n push,\r\n replace,\r\n back,\r\n refresh,\r\n pathname: routeData.pathname,\r\n query: routeData.query,\r\n params: routeData.params,\r\n };\r\n}\r\n\r\n/**\r\n * Parse query string into an object.\r\n * @param search - Query string (e.g., \"?id=123&name=test\")\r\n * @returns Object with query parameters\r\n */\r\nfunction parseQueryString(search: string): Record<string, string> {\r\n const params: Record<string, string> = {};\r\n if (!search || search.length === 0) return params;\r\n \r\n const queryString = search.startsWith(\"?\") ? search.slice(1) : search;\r\n const pairs = queryString.split(\"&\");\r\n \r\n for (const pair of pairs) {\r\n const [key, value] = pair.split(\"=\");\r\n if (key) {\r\n params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : \"\";\r\n }\r\n }\r\n \r\n return params;\r\n}\r\n","import { createContext, useContext } from \"react\";\r\n\r\nexport type NavigateFunction = (\r\n url: string,\r\n options?: { revalidate?: boolean; replace?: boolean }\r\n) => Promise<void>;\r\n\r\nexport interface RouterContextValue {\r\n navigate: NavigateFunction;\r\n}\r\n\r\nexport const RouterContext = createContext<RouterContextValue | null>(null);\r\n\r\nexport function useRouterContext(): RouterContextValue {\r\n const context = useContext(RouterContext);\r\n if (!context) {\r\n throw new Error(\r\n \"useRouter must be used within a RouterProvider. Make sure you're using it inside a Loly app.\"\r\n );\r\n }\r\n return context;\r\n}\r\n","// Client-side constants (hardcoded to avoid alias resolution issues in Rspack)\r\nexport const WINDOW_DATA_KEY = \"__FW_DATA__\";\r\nexport const APP_CONTAINER_ID = \"__app\";\r\n\r\n","import { WINDOW_DATA_KEY } from \"./constants\";\r\nimport type { InitialData } from \"./types\";\r\n\r\nexport function getWindowData(): InitialData | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return ((window as any)[WINDOW_DATA_KEY] as InitialData | undefined) ?? null;\r\n}\r\n\r\nexport function setWindowData(data: InitialData): void {\r\n (window as any)[WINDOW_DATA_KEY] = data;\r\n \r\n // Dispatch event for components to listen to (e.g., usePageProps, ThemeProvider)\r\n // This ensures components update when navigating in SPA mode\r\n if (typeof window !== \"undefined\") {\r\n window.dispatchEvent(\r\n new CustomEvent(\"fw-data-refresh\", {\r\n detail: { data },\r\n })\r\n );\r\n }\r\n}\r\n\r\nexport function getCurrentTheme(): string | null {\r\n return getWindowData()?.theme ?? null;\r\n}\r\n\r\n"],"mappings":"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","names":["import_react","import_react","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../../modules/react/hooks/index.ts","../../modules/react/hooks/useBroadcastChannel/index.tsx","../../modules/react/hooks/usePageProps/index.ts","../../modules/react/hooks/useRouter/index.ts","../../modules/runtime/client/RouterContext.tsx","../../modules/runtime/client/constants.ts","../../modules/runtime/client/window-data.ts"],"sourcesContent":["export { useBroadcastChannel } from \"./useBroadcastChannel\";\r\nexport { usePageProps } from \"./usePageProps\";\r\nexport { useRouter } from \"./useRouter\";\r\nexport type { Router } from \"./useRouter\";\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","import React, { useEffect, useState } from \"react\";\r\n\r\n/**\r\n * Hook to access page props and route parameters.\r\n *\r\n * Reads initial data from window.__FW_DATA__ set during SSR.\r\n * Automatically updates when `revalidate()` is called.\r\n *\r\n * @template P - Type for page props (default: any)\r\n * @template T - Type for route params (default: any)\r\n * @returns Object containing params and props\r\n *\r\n * @example\r\n * // With props type only\r\n * const { props } = usePageProps<{ title: string }>();\r\n *\r\n * @example\r\n * // With both props and params types\r\n * const { props, params } = usePageProps<{ title: string }, { id: string }>();\r\n */\r\nexport function usePageProps<P = any, T = any>(): { params: T, props: P } {\r\n const [state, setState] = useState<{ params: T, props: P }>(() => {\r\n // Initialize with current data if available\r\n if (typeof window !== \"undefined\" && (window as any)?.__FW_DATA__) {\r\n const data = (window as any).__FW_DATA__;\r\n return {\r\n params: data.params || {} as T,\r\n props: data.props || {} as P,\r\n };\r\n }\r\n return {\r\n params: {} as T,\r\n props: {} as P,\r\n };\r\n });\r\n\r\n useEffect(() => {\r\n // Listen for data refresh events (from revalidate() or navigation)\r\n const handleDataRefresh = () => {\r\n if ((window as any)?.__FW_DATA__) {\r\n const data = (window as any).__FW_DATA__;\r\n setState({\r\n params: data.params || {} as T,\r\n props: data.props || {} as P,\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 return { params: state.params as T, props: state.props as P };\r\n};\r\n","import { useState, useEffect, useCallback, useContext, useRef } from \"react\";\r\nimport { RouterContext } from \"../../../runtime/client/RouterContext\";\r\nimport { getWindowData, getRouterData } from \"../../../runtime/client/window-data\";\r\nimport { ROUTER_NAVIGATE_KEY } from \"../../../runtime/client/constants\";\r\n\r\nexport interface Router {\r\n /**\r\n * Navigate to a new route.\r\n * @param url - The URL to navigate to (e.g., \"/about\" or \"/blog/[slug]\" with params)\r\n * @param options - Navigation options\r\n */\r\n push: (url: string, options?: { revalidate?: boolean }) => Promise<void>;\r\n \r\n /**\r\n * Replace the current route without adding to history.\r\n * @param url - The URL to navigate to\r\n * @param options - Navigation options\r\n */\r\n replace: (url: string, options?: { revalidate?: boolean }) => Promise<void>;\r\n \r\n /**\r\n * Go back in the browser history.\r\n */\r\n back: () => void;\r\n \r\n /**\r\n * Refresh the current route data by revalidating.\r\n */\r\n refresh: () => Promise<void>;\r\n \r\n /**\r\n * Current pathname (e.g., \"/blog/my-post\")\r\n */\r\n pathname: string;\r\n \r\n /**\r\n * Query parameters from the URL (e.g., ?id=123&name=test)\r\n * Alias for searchParams for backward compatibility\r\n */\r\n query: Record<string, string>;\r\n \r\n /**\r\n * Search parameters from the URL (e.g., ?id=123&name=test)\r\n */\r\n searchParams: Record<string, unknown>;\r\n \r\n /**\r\n * Dynamic route parameters (e.g., { slug: \"my-post\" } for /blog/[slug])\r\n */\r\n params: Record<string, string>;\r\n}\r\n\r\n/**\r\n * Hook to access router functionality and current route information.\r\n * \r\n * Provides methods to navigate programmatically and access current route data.\r\n * \r\n * @returns Router object with navigation methods and route information\r\n * \r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const router = useRouter();\r\n * \r\n * const handleClick = () => {\r\n * router.push(\"/about\");\r\n * };\r\n * \r\n * return (\r\n * <div>\r\n * <p>Current path: {router.pathname}</p>\r\n * <p>Params: {JSON.stringify(router.params)}</p>\r\n * <button onClick={handleClick}>Go to About</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n * \r\n * @example\r\n * ```tsx\r\n * // Navigate with dynamic params\r\n * router.push(\"/blog/my-post\");\r\n * \r\n * // Replace current route\r\n * router.replace(\"/login\");\r\n * \r\n * // Refresh current route data\r\n * await router.refresh();\r\n * ```\r\n */\r\nexport function useRouter(): Router {\r\n // Try to get context, but don't throw if it's not available (SSR)\r\n const context = useContext(RouterContext);\r\n const navigate = context?.navigate;\r\n \r\n // Use a ref to store navigate so we can access it in callbacks even if context updates\r\n // Initialize with current navigate value\r\n const navigateRef = useRef(navigate);\r\n \r\n // Update ref when navigate changes (this ensures we always have the latest value)\r\n useEffect(() => {\r\n navigateRef.current = navigate;\r\n }, [navigate]);\r\n \r\n const [routeData, setRouteData] = useState(() => {\r\n // During SSR, return empty/default values\r\n if (typeof window === \"undefined\") {\r\n return {\r\n pathname: \"\",\r\n query: {},\r\n searchParams: {},\r\n params: {},\r\n };\r\n }\r\n \r\n // On client, get data from window\r\n const data = getWindowData();\r\n const routerData = getRouterData();\r\n \r\n // Parse search params from URL if routerData is not available\r\n const searchParams = routerData?.searchParams || parseQueryString(window.location.search);\r\n \r\n return {\r\n pathname: routerData?.pathname || data?.pathname || window.location.pathname,\r\n query: searchParams as Record<string, string>, // For backward compatibility\r\n searchParams: searchParams,\r\n params: routerData?.params || data?.params || {},\r\n };\r\n });\r\n\r\n // Listen for route changes (only on client)\r\n useEffect(() => {\r\n if (typeof window === \"undefined\") return;\r\n \r\n const handleDataRefresh = () => {\r\n const data = getWindowData();\r\n const routerData = getRouterData();\r\n const currentPathname = window.location.pathname;\r\n const currentSearch = window.location.search;\r\n \r\n const searchParams = routerData?.searchParams || parseQueryString(currentSearch);\r\n \r\n setRouteData({\r\n pathname: routerData?.pathname || data?.pathname || currentPathname,\r\n query: searchParams as Record<string, string>, // For backward compatibility\r\n searchParams: searchParams,\r\n params: routerData?.params || data?.params || {},\r\n });\r\n };\r\n\r\n // Listen for navigation events\r\n window.addEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n window.addEventListener(\"fw-router-data-refresh\", handleDataRefresh);\r\n \r\n // Also listen for popstate (browser back/forward)\r\n const handlePopState = () => {\r\n handleDataRefresh();\r\n };\r\n window.addEventListener(\"popstate\", handlePopState);\r\n\r\n return () => {\r\n window.removeEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n window.removeEventListener(\"fw-router-data-refresh\", handleDataRefresh);\r\n window.removeEventListener(\"popstate\", handlePopState);\r\n };\r\n }, []);\r\n\r\n const push = useCallback(\r\n async (url: string, options?: { revalidate?: boolean }) => {\r\n const fullUrl = url.startsWith(\"/\") ? url : `/${url}`;\r\n \r\n /**\r\n * SOLUTION: Multi-source navigate function resolution\r\n * \r\n * During React hydration, RouterContext may not be available immediately.\r\n * We try three sources in order:\r\n * 1. navigateRef.current - Most up-to-date, updated via useEffect\r\n * 2. navigate from context - Direct context access\r\n * 3. window.__LOLY_ROUTER_NAVIGATE__ - Global fallback exposed by AppShell\r\n * \r\n * This ensures SPA navigation works even during hydration timing issues.\r\n */\r\n const getCurrentNavigate = () => {\r\n if (navigateRef.current) return navigateRef.current;\r\n if (navigate) return navigate;\r\n if (typeof window !== \"undefined\" && (window as any)[ROUTER_NAVIGATE_KEY]) {\r\n return (window as any)[ROUTER_NAVIGATE_KEY];\r\n }\r\n return null;\r\n };\r\n \r\n let currentNavigate = getCurrentNavigate();\r\n \r\n if (typeof window === \"undefined\") {\r\n return; // SSR\r\n }\r\n \r\n // Wait for context during hydration (up to 100ms)\r\n if (!currentNavigate) {\r\n for (let i = 0; i < 10; i++) {\r\n await new Promise(resolve => setTimeout(resolve, 10));\r\n currentNavigate = getCurrentNavigate();\r\n if (currentNavigate) break;\r\n }\r\n }\r\n \r\n // Final fallback: full page reload if navigate is still unavailable\r\n if (!currentNavigate) {\r\n window.location.href = fullUrl;\r\n return;\r\n }\r\n \r\n // Check if we're already on this URL (same as link handler)\r\n const currentUrl = window.location.pathname + window.location.search;\r\n if (fullUrl === currentUrl) {\r\n return; // Already on this route, no need to navigate\r\n }\r\n \r\n // Update URL in browser history (same as link handler does)\r\n // This is done BEFORE navigation to match link behavior\r\n window.history.pushState({}, \"\", fullUrl);\r\n \r\n // Navigate using SPA navigation (same as link handler)\r\n // If navigation fails, navigate() will handle the reload internally\r\n await currentNavigate(fullUrl, options);\r\n },\r\n [navigate] // Include navigate in dependencies so it updates when context becomes available\r\n );\r\n\r\n const replace = useCallback(\r\n async (url: string, options?: { revalidate?: boolean }) => {\r\n const fullUrl = url.startsWith(\"/\") ? url : `/${url}`;\r\n \r\n const getCurrentNavigate = () => {\r\n if (navigateRef.current) return navigateRef.current;\r\n if (navigate) return navigate;\r\n if (typeof window !== \"undefined\" && (window as any)[ROUTER_NAVIGATE_KEY]) {\r\n return (window as any)[ROUTER_NAVIGATE_KEY];\r\n }\r\n return null;\r\n };\r\n \r\n let currentNavigate = getCurrentNavigate();\r\n \r\n if (typeof window === \"undefined\") {\r\n return;\r\n }\r\n \r\n if (!currentNavigate) {\r\n for (let i = 0; i < 10; i++) {\r\n await new Promise(resolve => setTimeout(resolve, 10));\r\n currentNavigate = getCurrentNavigate();\r\n if (currentNavigate) break;\r\n }\r\n }\r\n \r\n if (!currentNavigate) {\r\n window.location.replace(fullUrl);\r\n return;\r\n }\r\n \r\n // Update URL in browser history using replace (doesn't add to history)\r\n window.history.replaceState({}, \"\", fullUrl);\r\n \r\n // Navigate using SPA navigation\r\n await currentNavigate(fullUrl, options);\r\n },\r\n [navigate]\r\n );\r\n\r\n const back = useCallback(() => {\r\n if (typeof window !== \"undefined\") {\r\n window.history.back();\r\n }\r\n }, []);\r\n\r\n const refresh = useCallback(async () => {\r\n const currentUrl = typeof window !== \"undefined\" \r\n ? window.location.pathname + window.location.search \r\n : routeData.pathname;\r\n \r\n const getCurrentNavigate = () => {\r\n if (navigateRef.current) return navigateRef.current;\r\n if (navigate) return navigate;\r\n if (typeof window !== \"undefined\" && (window as any)[ROUTER_NAVIGATE_KEY]) {\r\n return (window as any)[ROUTER_NAVIGATE_KEY];\r\n }\r\n return null;\r\n };\r\n \r\n let currentNavigate = getCurrentNavigate();\r\n \r\n if (typeof window === \"undefined\") {\r\n return;\r\n }\r\n \r\n if (!currentNavigate) {\r\n for (let i = 0; i < 10; i++) {\r\n await new Promise(resolve => setTimeout(resolve, 10));\r\n currentNavigate = getCurrentNavigate();\r\n if (currentNavigate) break;\r\n }\r\n }\r\n \r\n if (!currentNavigate) {\r\n window.location.reload();\r\n return;\r\n }\r\n \r\n await currentNavigate(currentUrl, { revalidate: true });\r\n }, [navigate, routeData.pathname]);\r\n\r\n return {\r\n push,\r\n replace,\r\n back,\r\n refresh,\r\n pathname: routeData.pathname,\r\n query: routeData.query,\r\n searchParams: routeData.searchParams,\r\n params: routeData.params,\r\n };\r\n}\r\n\r\n/**\r\n * Parse query string into an object.\r\n * @param search - Query string (e.g., \"?id=123&name=test\")\r\n * @returns Object with query parameters\r\n */\r\nfunction parseQueryString(search: string): Record<string, string> {\r\n const params: Record<string, string> = {};\r\n if (!search || search.length === 0) return params;\r\n \r\n const queryString = search.startsWith(\"?\") ? search.slice(1) : search;\r\n const pairs = queryString.split(\"&\");\r\n \r\n for (const pair of pairs) {\r\n const [key, value] = pair.split(\"=\");\r\n if (key) {\r\n params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : \"\";\r\n }\r\n }\r\n \r\n return params;\r\n}\r\n","import { createContext, useContext } from \"react\";\r\n\r\nexport type NavigateFunction = (\r\n url: string,\r\n options?: { revalidate?: boolean; replace?: boolean }\r\n) => Promise<void>;\r\n\r\nexport interface RouterContextValue {\r\n navigate: NavigateFunction;\r\n}\r\n\r\nexport const RouterContext = createContext<RouterContextValue | null>(null);\r\n\r\nexport function useRouterContext(): RouterContextValue {\r\n const context = useContext(RouterContext);\r\n if (!context) {\r\n throw new Error(\r\n \"useRouter must be used within a RouterProvider. Make sure you're using it inside a Loly app.\"\r\n );\r\n }\r\n return context;\r\n}\r\n","// Client-side constants (hardcoded to avoid alias resolution issues in Rspack)\r\nexport const WINDOW_DATA_KEY = \"__FW_DATA__\";\r\nexport const ROUTER_DATA_KEY = \"__LOLY_ROUTER_DATA__\";\r\nexport const APP_CONTAINER_ID = \"__app\";\r\n// Global key for navigate function fallback (exposed by AppShell for hydration timing issues)\r\nexport const ROUTER_NAVIGATE_KEY = \"__LOLY_ROUTER_NAVIGATE__\";\r\n\r\n","import { WINDOW_DATA_KEY, ROUTER_DATA_KEY } from \"./constants\";\r\nimport type { InitialData, RouterData } from \"./types\";\r\n\r\nexport function getWindowData(): InitialData | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return ((window as any)[WINDOW_DATA_KEY] as InitialData | undefined) ?? null;\r\n}\r\n\r\nexport function getRouterData(): RouterData | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return ((window as any)[ROUTER_DATA_KEY] as RouterData | undefined) ?? null;\r\n}\r\n\r\nexport function setWindowData(data: InitialData): void {\r\n (window as any)[WINDOW_DATA_KEY] = data;\r\n \r\n // Dispatch event for components to listen to (e.g., usePageProps, ThemeProvider)\r\n // This ensures components update when navigating in SPA mode\r\n if (typeof window !== \"undefined\") {\r\n window.dispatchEvent(\r\n new CustomEvent(\"fw-data-refresh\", {\r\n detail: { data },\r\n })\r\n );\r\n }\r\n}\r\n\r\nexport function setRouterData(data: RouterData): void {\r\n (window as any)[ROUTER_DATA_KEY] = data;\r\n \r\n // Dispatch event for router data updates\r\n if (typeof window !== \"undefined\") {\r\n window.dispatchEvent(\r\n new CustomEvent(\"fw-router-data-refresh\", {\r\n detail: { data },\r\n })\r\n );\r\n }\r\n}\r\n\r\nexport function getCurrentTheme(): string | null {\r\n return getWindowData()?.theme ?? null;\r\n}\r\n\r\n"],"mappings":"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","names":["import_react","import_react","import_react"]}
|
package/dist/react/hooks.d.mts
CHANGED
|
@@ -57,8 +57,13 @@ interface Router {
|
|
|
57
57
|
pathname: string;
|
|
58
58
|
/**
|
|
59
59
|
* Query parameters from the URL (e.g., ?id=123&name=test)
|
|
60
|
+
* Alias for searchParams for backward compatibility
|
|
60
61
|
*/
|
|
61
62
|
query: Record<string, string>;
|
|
63
|
+
/**
|
|
64
|
+
* Search parameters from the URL (e.g., ?id=123&name=test)
|
|
65
|
+
*/
|
|
66
|
+
searchParams: Record<string, unknown>;
|
|
62
67
|
/**
|
|
63
68
|
* Dynamic route parameters (e.g., { slug: "my-post" } for /blog/[slug])
|
|
64
69
|
*/
|
package/dist/react/hooks.d.ts
CHANGED
|
@@ -57,8 +57,13 @@ interface Router {
|
|
|
57
57
|
pathname: string;
|
|
58
58
|
/**
|
|
59
59
|
* Query parameters from the URL (e.g., ?id=123&name=test)
|
|
60
|
+
* Alias for searchParams for backward compatibility
|
|
60
61
|
*/
|
|
61
62
|
query: Record<string, string>;
|
|
63
|
+
/**
|
|
64
|
+
* Search parameters from the URL (e.g., ?id=123&name=test)
|
|
65
|
+
*/
|
|
66
|
+
searchParams: Record<string, unknown>;
|
|
62
67
|
/**
|
|
63
68
|
* Dynamic route parameters (e.g., { slug: "my-post" } for /blog/[slug])
|
|
64
69
|
*/
|
package/dist/react/hooks.js
CHANGED
|
@@ -53,7 +53,7 @@ function usePageProps() {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// modules/react/hooks/useRouter/index.ts
|
|
56
|
-
import { useState as useState3, useEffect as useEffect3, useCallback, useContext as useContext2 } from "react";
|
|
56
|
+
import { useState as useState3, useEffect as useEffect3, useCallback, useContext as useContext2, useRef } from "react";
|
|
57
57
|
|
|
58
58
|
// modules/runtime/client/RouterContext.tsx
|
|
59
59
|
import { createContext, useContext } from "react";
|
|
@@ -61,6 +61,8 @@ var RouterContext = createContext(null);
|
|
|
61
61
|
|
|
62
62
|
// modules/runtime/client/constants.ts
|
|
63
63
|
var WINDOW_DATA_KEY = "__FW_DATA__";
|
|
64
|
+
var ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
|
|
65
|
+
var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
|
|
64
66
|
|
|
65
67
|
// modules/runtime/client/window-data.ts
|
|
66
68
|
function getWindowData() {
|
|
@@ -69,77 +71,133 @@ function getWindowData() {
|
|
|
69
71
|
}
|
|
70
72
|
return window[WINDOW_DATA_KEY] ?? null;
|
|
71
73
|
}
|
|
74
|
+
function getRouterData() {
|
|
75
|
+
if (typeof window === "undefined") {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return window[ROUTER_DATA_KEY] ?? null;
|
|
79
|
+
}
|
|
72
80
|
|
|
73
81
|
// modules/react/hooks/useRouter/index.ts
|
|
74
82
|
function useRouter() {
|
|
75
83
|
const context = useContext2(RouterContext);
|
|
76
84
|
const navigate = context?.navigate;
|
|
85
|
+
const navigateRef = useRef(navigate);
|
|
86
|
+
useEffect3(() => {
|
|
87
|
+
navigateRef.current = navigate;
|
|
88
|
+
}, [navigate]);
|
|
77
89
|
const [routeData, setRouteData] = useState3(() => {
|
|
78
90
|
if (typeof window === "undefined") {
|
|
79
91
|
return {
|
|
80
92
|
pathname: "",
|
|
81
93
|
query: {},
|
|
94
|
+
searchParams: {},
|
|
82
95
|
params: {}
|
|
83
96
|
};
|
|
84
97
|
}
|
|
85
98
|
const data = getWindowData();
|
|
99
|
+
const routerData = getRouterData();
|
|
100
|
+
const searchParams = routerData?.searchParams || parseQueryString(window.location.search);
|
|
86
101
|
return {
|
|
87
|
-
pathname: data?.pathname || window.location.pathname,
|
|
88
|
-
query:
|
|
89
|
-
|
|
102
|
+
pathname: routerData?.pathname || data?.pathname || window.location.pathname,
|
|
103
|
+
query: searchParams,
|
|
104
|
+
// For backward compatibility
|
|
105
|
+
searchParams,
|
|
106
|
+
params: routerData?.params || data?.params || {}
|
|
90
107
|
};
|
|
91
108
|
});
|
|
92
109
|
useEffect3(() => {
|
|
93
110
|
if (typeof window === "undefined") return;
|
|
94
111
|
const handleDataRefresh = () => {
|
|
95
112
|
const data = getWindowData();
|
|
113
|
+
const routerData = getRouterData();
|
|
96
114
|
const currentPathname = window.location.pathname;
|
|
97
115
|
const currentSearch = window.location.search;
|
|
116
|
+
const searchParams = routerData?.searchParams || parseQueryString(currentSearch);
|
|
98
117
|
setRouteData({
|
|
99
|
-
pathname: data?.pathname || currentPathname,
|
|
100
|
-
query:
|
|
101
|
-
|
|
118
|
+
pathname: routerData?.pathname || data?.pathname || currentPathname,
|
|
119
|
+
query: searchParams,
|
|
120
|
+
// For backward compatibility
|
|
121
|
+
searchParams,
|
|
122
|
+
params: routerData?.params || data?.params || {}
|
|
102
123
|
});
|
|
103
124
|
};
|
|
104
125
|
window.addEventListener("fw-data-refresh", handleDataRefresh);
|
|
126
|
+
window.addEventListener("fw-router-data-refresh", handleDataRefresh);
|
|
105
127
|
const handlePopState = () => {
|
|
106
128
|
handleDataRefresh();
|
|
107
129
|
};
|
|
108
130
|
window.addEventListener("popstate", handlePopState);
|
|
109
131
|
return () => {
|
|
110
132
|
window.removeEventListener("fw-data-refresh", handleDataRefresh);
|
|
133
|
+
window.removeEventListener("fw-router-data-refresh", handleDataRefresh);
|
|
111
134
|
window.removeEventListener("popstate", handlePopState);
|
|
112
135
|
};
|
|
113
136
|
}, []);
|
|
114
137
|
const push = useCallback(
|
|
115
138
|
async (url, options) => {
|
|
116
139
|
const fullUrl = url.startsWith("/") ? url : `/${url}`;
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
140
|
+
const getCurrentNavigate = () => {
|
|
141
|
+
if (navigateRef.current) return navigateRef.current;
|
|
142
|
+
if (navigate) return navigate;
|
|
143
|
+
if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
|
|
144
|
+
return window[ROUTER_NAVIGATE_KEY];
|
|
120
145
|
}
|
|
146
|
+
return null;
|
|
147
|
+
};
|
|
148
|
+
let currentNavigate = getCurrentNavigate();
|
|
149
|
+
if (typeof window === "undefined") {
|
|
121
150
|
return;
|
|
122
151
|
}
|
|
123
|
-
if (
|
|
124
|
-
|
|
152
|
+
if (!currentNavigate) {
|
|
153
|
+
for (let i = 0; i < 10; i++) {
|
|
154
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
155
|
+
currentNavigate = getCurrentNavigate();
|
|
156
|
+
if (currentNavigate) break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!currentNavigate) {
|
|
160
|
+
window.location.href = fullUrl;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
164
|
+
if (fullUrl === currentUrl) {
|
|
165
|
+
return;
|
|
125
166
|
}
|
|
126
|
-
|
|
167
|
+
window.history.pushState({}, "", fullUrl);
|
|
168
|
+
await currentNavigate(fullUrl, options);
|
|
127
169
|
},
|
|
128
170
|
[navigate]
|
|
171
|
+
// Include navigate in dependencies so it updates when context becomes available
|
|
129
172
|
);
|
|
130
173
|
const replace = useCallback(
|
|
131
174
|
async (url, options) => {
|
|
132
175
|
const fullUrl = url.startsWith("/") ? url : `/${url}`;
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
176
|
+
const getCurrentNavigate = () => {
|
|
177
|
+
if (navigateRef.current) return navigateRef.current;
|
|
178
|
+
if (navigate) return navigate;
|
|
179
|
+
if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
|
|
180
|
+
return window[ROUTER_NAVIGATE_KEY];
|
|
136
181
|
}
|
|
182
|
+
return null;
|
|
183
|
+
};
|
|
184
|
+
let currentNavigate = getCurrentNavigate();
|
|
185
|
+
if (typeof window === "undefined") {
|
|
137
186
|
return;
|
|
138
187
|
}
|
|
139
|
-
if (
|
|
140
|
-
|
|
188
|
+
if (!currentNavigate) {
|
|
189
|
+
for (let i = 0; i < 10; i++) {
|
|
190
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
191
|
+
currentNavigate = getCurrentNavigate();
|
|
192
|
+
if (currentNavigate) break;
|
|
193
|
+
}
|
|
141
194
|
}
|
|
142
|
-
|
|
195
|
+
if (!currentNavigate) {
|
|
196
|
+
window.location.replace(fullUrl);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
window.history.replaceState({}, "", fullUrl);
|
|
200
|
+
await currentNavigate(fullUrl, options);
|
|
143
201
|
},
|
|
144
202
|
[navigate]
|
|
145
203
|
);
|
|
@@ -150,13 +208,30 @@ function useRouter() {
|
|
|
150
208
|
}, []);
|
|
151
209
|
const refresh = useCallback(async () => {
|
|
152
210
|
const currentUrl = typeof window !== "undefined" ? window.location.pathname + window.location.search : routeData.pathname;
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
211
|
+
const getCurrentNavigate = () => {
|
|
212
|
+
if (navigateRef.current) return navigateRef.current;
|
|
213
|
+
if (navigate) return navigate;
|
|
214
|
+
if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
|
|
215
|
+
return window[ROUTER_NAVIGATE_KEY];
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
let currentNavigate = getCurrentNavigate();
|
|
220
|
+
if (typeof window === "undefined") {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!currentNavigate) {
|
|
224
|
+
for (let i = 0; i < 10; i++) {
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
226
|
+
currentNavigate = getCurrentNavigate();
|
|
227
|
+
if (currentNavigate) break;
|
|
156
228
|
}
|
|
229
|
+
}
|
|
230
|
+
if (!currentNavigate) {
|
|
231
|
+
window.location.reload();
|
|
157
232
|
return;
|
|
158
233
|
}
|
|
159
|
-
await
|
|
234
|
+
await currentNavigate(currentUrl, { revalidate: true });
|
|
160
235
|
}, [navigate, routeData.pathname]);
|
|
161
236
|
return {
|
|
162
237
|
push,
|
|
@@ -165,6 +240,7 @@ function useRouter() {
|
|
|
165
240
|
refresh,
|
|
166
241
|
pathname: routeData.pathname,
|
|
167
242
|
query: routeData.query,
|
|
243
|
+
searchParams: routeData.searchParams,
|
|
168
244
|
params: routeData.params
|
|
169
245
|
};
|
|
170
246
|
}
|
package/dist/react/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../modules/react/hooks/useBroadcastChannel/index.tsx","../../modules/react/hooks/usePageProps/index.ts","../../modules/react/hooks/useRouter/index.ts","../../modules/runtime/client/RouterContext.tsx","../../modules/runtime/client/constants.ts","../../modules/runtime/client/window-data.ts"],"sourcesContent":["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","import React, { useEffect, useState } from \"react\";\r\n\r\n/**\r\n * Hook to access page props and route parameters.\r\n *\r\n * Reads initial data from window.__FW_DATA__ set during SSR.\r\n * Automatically updates when `revalidate()` is called.\r\n *\r\n * @template P - Type for page props (default: any)\r\n * @template T - Type for route params (default: any)\r\n * @returns Object containing params and props\r\n *\r\n * @example\r\n * // With props type only\r\n * const { props } = usePageProps<{ title: string }>();\r\n *\r\n * @example\r\n * // With both props and params types\r\n * const { props, params } = usePageProps<{ title: string }, { id: string }>();\r\n */\r\nexport function usePageProps<P = any, T = any>(): { params: T, props: P } {\r\n const [state, setState] = useState<{ params: T, props: P }>(() => {\r\n // Initialize with current data if available\r\n if (typeof window !== \"undefined\" && (window as any)?.__FW_DATA__) {\r\n const data = (window as any).__FW_DATA__;\r\n return {\r\n params: data.params || {} as T,\r\n props: data.props || {} as P,\r\n };\r\n }\r\n return {\r\n params: {} as T,\r\n props: {} as P,\r\n };\r\n });\r\n\r\n useEffect(() => {\r\n // Listen for data refresh events (from revalidate() or navigation)\r\n const handleDataRefresh = () => {\r\n if ((window as any)?.__FW_DATA__) {\r\n const data = (window as any).__FW_DATA__;\r\n setState({\r\n params: data.params || {} as T,\r\n props: data.props || {} as P,\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 return { params: state.params as T, props: state.props as P };\r\n};\r\n","import { useState, useEffect, useCallback, useContext } from \"react\";\r\nimport { RouterContext } from \"../../../runtime/client/RouterContext\";\r\nimport { getWindowData } from \"../../../runtime/client/window-data\";\r\n\r\nexport interface Router {\r\n /**\r\n * Navigate to a new route.\r\n * @param url - The URL to navigate to (e.g., \"/about\" or \"/blog/[slug]\" with params)\r\n * @param options - Navigation options\r\n */\r\n push: (url: string, options?: { revalidate?: boolean }) => Promise<void>;\r\n \r\n /**\r\n * Replace the current route without adding to history.\r\n * @param url - The URL to navigate to\r\n * @param options - Navigation options\r\n */\r\n replace: (url: string, options?: { revalidate?: boolean }) => Promise<void>;\r\n \r\n /**\r\n * Go back in the browser history.\r\n */\r\n back: () => void;\r\n \r\n /**\r\n * Refresh the current route data by revalidating.\r\n */\r\n refresh: () => Promise<void>;\r\n \r\n /**\r\n * Current pathname (e.g., \"/blog/my-post\")\r\n */\r\n pathname: string;\r\n \r\n /**\r\n * Query parameters from the URL (e.g., ?id=123&name=test)\r\n */\r\n query: Record<string, string>;\r\n \r\n /**\r\n * Dynamic route parameters (e.g., { slug: \"my-post\" } for /blog/[slug])\r\n */\r\n params: Record<string, string>;\r\n}\r\n\r\n/**\r\n * Hook to access router functionality and current route information.\r\n * \r\n * Provides methods to navigate programmatically and access current route data.\r\n * \r\n * @returns Router object with navigation methods and route information\r\n * \r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const router = useRouter();\r\n * \r\n * const handleClick = () => {\r\n * router.push(\"/about\");\r\n * };\r\n * \r\n * return (\r\n * <div>\r\n * <p>Current path: {router.pathname}</p>\r\n * <p>Params: {JSON.stringify(router.params)}</p>\r\n * <button onClick={handleClick}>Go to About</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n * \r\n * @example\r\n * ```tsx\r\n * // Navigate with dynamic params\r\n * router.push(\"/blog/my-post\");\r\n * \r\n * // Replace current route\r\n * router.replace(\"/login\");\r\n * \r\n * // Refresh current route data\r\n * await router.refresh();\r\n * ```\r\n */\r\nexport function useRouter(): Router {\r\n // Try to get context, but don't throw if it's not available (SSR)\r\n const context = useContext(RouterContext);\r\n const navigate = context?.navigate;\r\n \r\n const [routeData, setRouteData] = useState(() => {\r\n // During SSR, return empty/default values\r\n if (typeof window === \"undefined\") {\r\n return {\r\n pathname: \"\",\r\n query: {},\r\n params: {},\r\n };\r\n }\r\n \r\n // On client, get data from window\r\n const data = getWindowData();\r\n return {\r\n pathname: data?.pathname || window.location.pathname,\r\n query: parseQueryString(window.location.search),\r\n params: data?.params || {},\r\n };\r\n });\r\n\r\n // Listen for route changes (only on client)\r\n useEffect(() => {\r\n if (typeof window === \"undefined\") return;\r\n \r\n const handleDataRefresh = () => {\r\n const data = getWindowData();\r\n const currentPathname = window.location.pathname;\r\n const currentSearch = window.location.search;\r\n \r\n setRouteData({\r\n pathname: data?.pathname || currentPathname,\r\n query: parseQueryString(currentSearch),\r\n params: data?.params || {},\r\n });\r\n };\r\n\r\n // Listen for navigation events\r\n window.addEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n \r\n // Also listen for popstate (browser back/forward)\r\n const handlePopState = () => {\r\n handleDataRefresh();\r\n };\r\n window.addEventListener(\"popstate\", handlePopState);\r\n\r\n return () => {\r\n window.removeEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n window.removeEventListener(\"popstate\", handlePopState);\r\n };\r\n }, []);\r\n\r\n const push = useCallback(\r\n async (url: string, options?: { revalidate?: boolean }) => {\r\n const fullUrl = url.startsWith(\"/\") ? url : `/${url}`;\r\n \r\n // During SSR or if context is not available, use window.location\r\n if (!navigate) {\r\n if (typeof window !== \"undefined\") {\r\n window.location.href = fullUrl;\r\n }\r\n return;\r\n }\r\n \r\n if (typeof window !== \"undefined\") {\r\n window.history.pushState({}, \"\", fullUrl);\r\n }\r\n await navigate(fullUrl, options);\r\n },\r\n [navigate]\r\n );\r\n\r\n const replace = useCallback(\r\n async (url: string, options?: { revalidate?: boolean }) => {\r\n const fullUrl = url.startsWith(\"/\") ? url : `/${url}`;\r\n \r\n // During SSR or if context is not available, use window.location\r\n if (!navigate) {\r\n if (typeof window !== \"undefined\") {\r\n window.location.replace(fullUrl);\r\n }\r\n return;\r\n }\r\n \r\n if (typeof window !== \"undefined\") {\r\n window.history.replaceState({}, \"\", fullUrl);\r\n }\r\n await navigate(fullUrl, options);\r\n },\r\n [navigate]\r\n );\r\n\r\n const back = useCallback(() => {\r\n if (typeof window !== \"undefined\") {\r\n window.history.back();\r\n }\r\n }, []);\r\n\r\n const refresh = useCallback(async () => {\r\n const currentUrl = typeof window !== \"undefined\" \r\n ? window.location.pathname + window.location.search \r\n : routeData.pathname;\r\n \r\n // During SSR or if context is not available, reload the page\r\n if (!navigate) {\r\n if (typeof window !== \"undefined\") {\r\n window.location.reload();\r\n }\r\n return;\r\n }\r\n \r\n await navigate(currentUrl, { revalidate: true });\r\n }, [navigate, routeData.pathname]);\r\n\r\n return {\r\n push,\r\n replace,\r\n back,\r\n refresh,\r\n pathname: routeData.pathname,\r\n query: routeData.query,\r\n params: routeData.params,\r\n };\r\n}\r\n\r\n/**\r\n * Parse query string into an object.\r\n * @param search - Query string (e.g., \"?id=123&name=test\")\r\n * @returns Object with query parameters\r\n */\r\nfunction parseQueryString(search: string): Record<string, string> {\r\n const params: Record<string, string> = {};\r\n if (!search || search.length === 0) return params;\r\n \r\n const queryString = search.startsWith(\"?\") ? search.slice(1) : search;\r\n const pairs = queryString.split(\"&\");\r\n \r\n for (const pair of pairs) {\r\n const [key, value] = pair.split(\"=\");\r\n if (key) {\r\n params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : \"\";\r\n }\r\n }\r\n \r\n return params;\r\n}\r\n","import { createContext, useContext } from \"react\";\r\n\r\nexport type NavigateFunction = (\r\n url: string,\r\n options?: { revalidate?: boolean; replace?: boolean }\r\n) => Promise<void>;\r\n\r\nexport interface RouterContextValue {\r\n navigate: NavigateFunction;\r\n}\r\n\r\nexport const RouterContext = createContext<RouterContextValue | null>(null);\r\n\r\nexport function useRouterContext(): RouterContextValue {\r\n const context = useContext(RouterContext);\r\n if (!context) {\r\n throw new Error(\r\n \"useRouter must be used within a RouterProvider. Make sure you're using it inside a Loly app.\"\r\n );\r\n }\r\n return context;\r\n}\r\n","// Client-side constants (hardcoded to avoid alias resolution issues in Rspack)\r\nexport const WINDOW_DATA_KEY = \"__FW_DATA__\";\r\nexport const APP_CONTAINER_ID = \"__app\";\r\n\r\n","import { WINDOW_DATA_KEY } from \"./constants\";\r\nimport type { InitialData } from \"./types\";\r\n\r\nexport function getWindowData(): InitialData | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return ((window as any)[WINDOW_DATA_KEY] as InitialData | undefined) ?? null;\r\n}\r\n\r\nexport function setWindowData(data: InitialData): void {\r\n (window as any)[WINDOW_DATA_KEY] = data;\r\n \r\n // Dispatch event for components to listen to (e.g., usePageProps, ThemeProvider)\r\n // This ensures components update when navigating in SPA mode\r\n if (typeof window !== \"undefined\") {\r\n window.dispatchEvent(\r\n new CustomEvent(\"fw-data-refresh\", {\r\n detail: { data },\r\n })\r\n );\r\n }\r\n}\r\n\r\nexport function getCurrentTheme(): string | null {\r\n return getWindowData()?.theme ?? null;\r\n}\r\n\r\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","useContext","useContext","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../../modules/react/hooks/useBroadcastChannel/index.tsx","../../modules/react/hooks/usePageProps/index.ts","../../modules/react/hooks/useRouter/index.ts","../../modules/runtime/client/RouterContext.tsx","../../modules/runtime/client/constants.ts","../../modules/runtime/client/window-data.ts"],"sourcesContent":["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","import React, { useEffect, useState } from \"react\";\r\n\r\n/**\r\n * Hook to access page props and route parameters.\r\n *\r\n * Reads initial data from window.__FW_DATA__ set during SSR.\r\n * Automatically updates when `revalidate()` is called.\r\n *\r\n * @template P - Type for page props (default: any)\r\n * @template T - Type for route params (default: any)\r\n * @returns Object containing params and props\r\n *\r\n * @example\r\n * // With props type only\r\n * const { props } = usePageProps<{ title: string }>();\r\n *\r\n * @example\r\n * // With both props and params types\r\n * const { props, params } = usePageProps<{ title: string }, { id: string }>();\r\n */\r\nexport function usePageProps<P = any, T = any>(): { params: T, props: P } {\r\n const [state, setState] = useState<{ params: T, props: P }>(() => {\r\n // Initialize with current data if available\r\n if (typeof window !== \"undefined\" && (window as any)?.__FW_DATA__) {\r\n const data = (window as any).__FW_DATA__;\r\n return {\r\n params: data.params || {} as T,\r\n props: data.props || {} as P,\r\n };\r\n }\r\n return {\r\n params: {} as T,\r\n props: {} as P,\r\n };\r\n });\r\n\r\n useEffect(() => {\r\n // Listen for data refresh events (from revalidate() or navigation)\r\n const handleDataRefresh = () => {\r\n if ((window as any)?.__FW_DATA__) {\r\n const data = (window as any).__FW_DATA__;\r\n setState({\r\n params: data.params || {} as T,\r\n props: data.props || {} as P,\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 return { params: state.params as T, props: state.props as P };\r\n};\r\n","import { useState, useEffect, useCallback, useContext, useRef } from \"react\";\r\nimport { RouterContext } from \"../../../runtime/client/RouterContext\";\r\nimport { getWindowData, getRouterData } from \"../../../runtime/client/window-data\";\r\nimport { ROUTER_NAVIGATE_KEY } from \"../../../runtime/client/constants\";\r\n\r\nexport interface Router {\r\n /**\r\n * Navigate to a new route.\r\n * @param url - The URL to navigate to (e.g., \"/about\" or \"/blog/[slug]\" with params)\r\n * @param options - Navigation options\r\n */\r\n push: (url: string, options?: { revalidate?: boolean }) => Promise<void>;\r\n \r\n /**\r\n * Replace the current route without adding to history.\r\n * @param url - The URL to navigate to\r\n * @param options - Navigation options\r\n */\r\n replace: (url: string, options?: { revalidate?: boolean }) => Promise<void>;\r\n \r\n /**\r\n * Go back in the browser history.\r\n */\r\n back: () => void;\r\n \r\n /**\r\n * Refresh the current route data by revalidating.\r\n */\r\n refresh: () => Promise<void>;\r\n \r\n /**\r\n * Current pathname (e.g., \"/blog/my-post\")\r\n */\r\n pathname: string;\r\n \r\n /**\r\n * Query parameters from the URL (e.g., ?id=123&name=test)\r\n * Alias for searchParams for backward compatibility\r\n */\r\n query: Record<string, string>;\r\n \r\n /**\r\n * Search parameters from the URL (e.g., ?id=123&name=test)\r\n */\r\n searchParams: Record<string, unknown>;\r\n \r\n /**\r\n * Dynamic route parameters (e.g., { slug: \"my-post\" } for /blog/[slug])\r\n */\r\n params: Record<string, string>;\r\n}\r\n\r\n/**\r\n * Hook to access router functionality and current route information.\r\n * \r\n * Provides methods to navigate programmatically and access current route data.\r\n * \r\n * @returns Router object with navigation methods and route information\r\n * \r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const router = useRouter();\r\n * \r\n * const handleClick = () => {\r\n * router.push(\"/about\");\r\n * };\r\n * \r\n * return (\r\n * <div>\r\n * <p>Current path: {router.pathname}</p>\r\n * <p>Params: {JSON.stringify(router.params)}</p>\r\n * <button onClick={handleClick}>Go to About</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n * \r\n * @example\r\n * ```tsx\r\n * // Navigate with dynamic params\r\n * router.push(\"/blog/my-post\");\r\n * \r\n * // Replace current route\r\n * router.replace(\"/login\");\r\n * \r\n * // Refresh current route data\r\n * await router.refresh();\r\n * ```\r\n */\r\nexport function useRouter(): Router {\r\n // Try to get context, but don't throw if it's not available (SSR)\r\n const context = useContext(RouterContext);\r\n const navigate = context?.navigate;\r\n \r\n // Use a ref to store navigate so we can access it in callbacks even if context updates\r\n // Initialize with current navigate value\r\n const navigateRef = useRef(navigate);\r\n \r\n // Update ref when navigate changes (this ensures we always have the latest value)\r\n useEffect(() => {\r\n navigateRef.current = navigate;\r\n }, [navigate]);\r\n \r\n const [routeData, setRouteData] = useState(() => {\r\n // During SSR, return empty/default values\r\n if (typeof window === \"undefined\") {\r\n return {\r\n pathname: \"\",\r\n query: {},\r\n searchParams: {},\r\n params: {},\r\n };\r\n }\r\n \r\n // On client, get data from window\r\n const data = getWindowData();\r\n const routerData = getRouterData();\r\n \r\n // Parse search params from URL if routerData is not available\r\n const searchParams = routerData?.searchParams || parseQueryString(window.location.search);\r\n \r\n return {\r\n pathname: routerData?.pathname || data?.pathname || window.location.pathname,\r\n query: searchParams as Record<string, string>, // For backward compatibility\r\n searchParams: searchParams,\r\n params: routerData?.params || data?.params || {},\r\n };\r\n });\r\n\r\n // Listen for route changes (only on client)\r\n useEffect(() => {\r\n if (typeof window === \"undefined\") return;\r\n \r\n const handleDataRefresh = () => {\r\n const data = getWindowData();\r\n const routerData = getRouterData();\r\n const currentPathname = window.location.pathname;\r\n const currentSearch = window.location.search;\r\n \r\n const searchParams = routerData?.searchParams || parseQueryString(currentSearch);\r\n \r\n setRouteData({\r\n pathname: routerData?.pathname || data?.pathname || currentPathname,\r\n query: searchParams as Record<string, string>, // For backward compatibility\r\n searchParams: searchParams,\r\n params: routerData?.params || data?.params || {},\r\n });\r\n };\r\n\r\n // Listen for navigation events\r\n window.addEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n window.addEventListener(\"fw-router-data-refresh\", handleDataRefresh);\r\n \r\n // Also listen for popstate (browser back/forward)\r\n const handlePopState = () => {\r\n handleDataRefresh();\r\n };\r\n window.addEventListener(\"popstate\", handlePopState);\r\n\r\n return () => {\r\n window.removeEventListener(\"fw-data-refresh\", handleDataRefresh);\r\n window.removeEventListener(\"fw-router-data-refresh\", handleDataRefresh);\r\n window.removeEventListener(\"popstate\", handlePopState);\r\n };\r\n }, []);\r\n\r\n const push = useCallback(\r\n async (url: string, options?: { revalidate?: boolean }) => {\r\n const fullUrl = url.startsWith(\"/\") ? url : `/${url}`;\r\n \r\n /**\r\n * SOLUTION: Multi-source navigate function resolution\r\n * \r\n * During React hydration, RouterContext may not be available immediately.\r\n * We try three sources in order:\r\n * 1. navigateRef.current - Most up-to-date, updated via useEffect\r\n * 2. navigate from context - Direct context access\r\n * 3. window.__LOLY_ROUTER_NAVIGATE__ - Global fallback exposed by AppShell\r\n * \r\n * This ensures SPA navigation works even during hydration timing issues.\r\n */\r\n const getCurrentNavigate = () => {\r\n if (navigateRef.current) return navigateRef.current;\r\n if (navigate) return navigate;\r\n if (typeof window !== \"undefined\" && (window as any)[ROUTER_NAVIGATE_KEY]) {\r\n return (window as any)[ROUTER_NAVIGATE_KEY];\r\n }\r\n return null;\r\n };\r\n \r\n let currentNavigate = getCurrentNavigate();\r\n \r\n if (typeof window === \"undefined\") {\r\n return; // SSR\r\n }\r\n \r\n // Wait for context during hydration (up to 100ms)\r\n if (!currentNavigate) {\r\n for (let i = 0; i < 10; i++) {\r\n await new Promise(resolve => setTimeout(resolve, 10));\r\n currentNavigate = getCurrentNavigate();\r\n if (currentNavigate) break;\r\n }\r\n }\r\n \r\n // Final fallback: full page reload if navigate is still unavailable\r\n if (!currentNavigate) {\r\n window.location.href = fullUrl;\r\n return;\r\n }\r\n \r\n // Check if we're already on this URL (same as link handler)\r\n const currentUrl = window.location.pathname + window.location.search;\r\n if (fullUrl === currentUrl) {\r\n return; // Already on this route, no need to navigate\r\n }\r\n \r\n // Update URL in browser history (same as link handler does)\r\n // This is done BEFORE navigation to match link behavior\r\n window.history.pushState({}, \"\", fullUrl);\r\n \r\n // Navigate using SPA navigation (same as link handler)\r\n // If navigation fails, navigate() will handle the reload internally\r\n await currentNavigate(fullUrl, options);\r\n },\r\n [navigate] // Include navigate in dependencies so it updates when context becomes available\r\n );\r\n\r\n const replace = useCallback(\r\n async (url: string, options?: { revalidate?: boolean }) => {\r\n const fullUrl = url.startsWith(\"/\") ? url : `/${url}`;\r\n \r\n const getCurrentNavigate = () => {\r\n if (navigateRef.current) return navigateRef.current;\r\n if (navigate) return navigate;\r\n if (typeof window !== \"undefined\" && (window as any)[ROUTER_NAVIGATE_KEY]) {\r\n return (window as any)[ROUTER_NAVIGATE_KEY];\r\n }\r\n return null;\r\n };\r\n \r\n let currentNavigate = getCurrentNavigate();\r\n \r\n if (typeof window === \"undefined\") {\r\n return;\r\n }\r\n \r\n if (!currentNavigate) {\r\n for (let i = 0; i < 10; i++) {\r\n await new Promise(resolve => setTimeout(resolve, 10));\r\n currentNavigate = getCurrentNavigate();\r\n if (currentNavigate) break;\r\n }\r\n }\r\n \r\n if (!currentNavigate) {\r\n window.location.replace(fullUrl);\r\n return;\r\n }\r\n \r\n // Update URL in browser history using replace (doesn't add to history)\r\n window.history.replaceState({}, \"\", fullUrl);\r\n \r\n // Navigate using SPA navigation\r\n await currentNavigate(fullUrl, options);\r\n },\r\n [navigate]\r\n );\r\n\r\n const back = useCallback(() => {\r\n if (typeof window !== \"undefined\") {\r\n window.history.back();\r\n }\r\n }, []);\r\n\r\n const refresh = useCallback(async () => {\r\n const currentUrl = typeof window !== \"undefined\" \r\n ? window.location.pathname + window.location.search \r\n : routeData.pathname;\r\n \r\n const getCurrentNavigate = () => {\r\n if (navigateRef.current) return navigateRef.current;\r\n if (navigate) return navigate;\r\n if (typeof window !== \"undefined\" && (window as any)[ROUTER_NAVIGATE_KEY]) {\r\n return (window as any)[ROUTER_NAVIGATE_KEY];\r\n }\r\n return null;\r\n };\r\n \r\n let currentNavigate = getCurrentNavigate();\r\n \r\n if (typeof window === \"undefined\") {\r\n return;\r\n }\r\n \r\n if (!currentNavigate) {\r\n for (let i = 0; i < 10; i++) {\r\n await new Promise(resolve => setTimeout(resolve, 10));\r\n currentNavigate = getCurrentNavigate();\r\n if (currentNavigate) break;\r\n }\r\n }\r\n \r\n if (!currentNavigate) {\r\n window.location.reload();\r\n return;\r\n }\r\n \r\n await currentNavigate(currentUrl, { revalidate: true });\r\n }, [navigate, routeData.pathname]);\r\n\r\n return {\r\n push,\r\n replace,\r\n back,\r\n refresh,\r\n pathname: routeData.pathname,\r\n query: routeData.query,\r\n searchParams: routeData.searchParams,\r\n params: routeData.params,\r\n };\r\n}\r\n\r\n/**\r\n * Parse query string into an object.\r\n * @param search - Query string (e.g., \"?id=123&name=test\")\r\n * @returns Object with query parameters\r\n */\r\nfunction parseQueryString(search: string): Record<string, string> {\r\n const params: Record<string, string> = {};\r\n if (!search || search.length === 0) return params;\r\n \r\n const queryString = search.startsWith(\"?\") ? search.slice(1) : search;\r\n const pairs = queryString.split(\"&\");\r\n \r\n for (const pair of pairs) {\r\n const [key, value] = pair.split(\"=\");\r\n if (key) {\r\n params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : \"\";\r\n }\r\n }\r\n \r\n return params;\r\n}\r\n","import { createContext, useContext } from \"react\";\r\n\r\nexport type NavigateFunction = (\r\n url: string,\r\n options?: { revalidate?: boolean; replace?: boolean }\r\n) => Promise<void>;\r\n\r\nexport interface RouterContextValue {\r\n navigate: NavigateFunction;\r\n}\r\n\r\nexport const RouterContext = createContext<RouterContextValue | null>(null);\r\n\r\nexport function useRouterContext(): RouterContextValue {\r\n const context = useContext(RouterContext);\r\n if (!context) {\r\n throw new Error(\r\n \"useRouter must be used within a RouterProvider. Make sure you're using it inside a Loly app.\"\r\n );\r\n }\r\n return context;\r\n}\r\n","// Client-side constants (hardcoded to avoid alias resolution issues in Rspack)\r\nexport const WINDOW_DATA_KEY = \"__FW_DATA__\";\r\nexport const ROUTER_DATA_KEY = \"__LOLY_ROUTER_DATA__\";\r\nexport const APP_CONTAINER_ID = \"__app\";\r\n// Global key for navigate function fallback (exposed by AppShell for hydration timing issues)\r\nexport const ROUTER_NAVIGATE_KEY = \"__LOLY_ROUTER_NAVIGATE__\";\r\n\r\n","import { WINDOW_DATA_KEY, ROUTER_DATA_KEY } from \"./constants\";\r\nimport type { InitialData, RouterData } from \"./types\";\r\n\r\nexport function getWindowData(): InitialData | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return ((window as any)[WINDOW_DATA_KEY] as InitialData | undefined) ?? null;\r\n}\r\n\r\nexport function getRouterData(): RouterData | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return ((window as any)[ROUTER_DATA_KEY] as RouterData | undefined) ?? null;\r\n}\r\n\r\nexport function setWindowData(data: InitialData): void {\r\n (window as any)[WINDOW_DATA_KEY] = data;\r\n \r\n // Dispatch event for components to listen to (e.g., usePageProps, ThemeProvider)\r\n // This ensures components update when navigating in SPA mode\r\n if (typeof window !== \"undefined\") {\r\n window.dispatchEvent(\r\n new CustomEvent(\"fw-data-refresh\", {\r\n detail: { data },\r\n })\r\n );\r\n }\r\n}\r\n\r\nexport function setRouterData(data: RouterData): void {\r\n (window as any)[ROUTER_DATA_KEY] = data;\r\n \r\n // Dispatch event for router data updates\r\n if (typeof window !== \"undefined\") {\r\n window.dispatchEvent(\r\n new CustomEvent(\"fw-router-data-refresh\", {\r\n detail: { data },\r\n })\r\n );\r\n }\r\n}\r\n\r\nexport function getCurrentTheme(): string | null {\r\n return getWindowData()?.theme ?? null;\r\n}\r\n\r\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","useContext","useContext","useEffect","useState"]}
|