@lolyjs/core 0.2.0-alpha.1 → 0.2.0-alpha.11

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.
@@ -18,41 +18,213 @@ var useBroadcastChannel = (channelName) => {
18
18
  return { message, sendMessage };
19
19
  };
20
20
 
21
- // modules/react/hooks/usePageProps/index.ts
22
- import { useEffect as useEffect2, useState as useState2 } from "react";
23
- function usePageProps() {
24
- const [state, setState] = useState2(() => {
25
- if (typeof window !== "undefined" && window?.__FW_DATA__) {
26
- const data = window.__FW_DATA__;
21
+ // modules/react/hooks/useRouter/index.ts
22
+ import { useState as useState2, useEffect as useEffect2, useCallback, useContext as useContext2, useRef } from "react";
23
+
24
+ // modules/runtime/client/RouterContext.tsx
25
+ import { createContext, useContext } from "react";
26
+ var RouterContext = createContext(null);
27
+
28
+ // modules/runtime/client/constants.ts
29
+ var WINDOW_DATA_KEY = "__FW_DATA__";
30
+ var ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
31
+ var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
32
+
33
+ // modules/runtime/client/window-data.ts
34
+ function getWindowData() {
35
+ if (typeof window === "undefined") {
36
+ return null;
37
+ }
38
+ return window[WINDOW_DATA_KEY] ?? null;
39
+ }
40
+ function getRouterData() {
41
+ if (typeof window === "undefined") {
42
+ return null;
43
+ }
44
+ return window[ROUTER_DATA_KEY] ?? null;
45
+ }
46
+
47
+ // modules/react/hooks/useRouter/index.ts
48
+ function useRouter() {
49
+ const context = useContext2(RouterContext);
50
+ const navigate = context?.navigate;
51
+ const navigateRef = useRef(navigate);
52
+ useEffect2(() => {
53
+ navigateRef.current = navigate;
54
+ }, [navigate]);
55
+ const [routeData, setRouteData] = useState2(() => {
56
+ if (typeof window === "undefined") {
27
57
  return {
28
- params: data.params || {},
29
- props: data.props || {}
58
+ pathname: "",
59
+ query: {},
60
+ searchParams: {},
61
+ params: {}
30
62
  };
31
63
  }
64
+ const data = getWindowData();
65
+ const routerData = getRouterData();
66
+ const searchParams = routerData?.searchParams || parseQueryString(window.location.search);
32
67
  return {
33
- params: {},
34
- props: {}
68
+ pathname: routerData?.pathname || data?.pathname || window.location.pathname,
69
+ query: searchParams,
70
+ // For backward compatibility
71
+ searchParams,
72
+ params: routerData?.params || data?.params || {}
35
73
  };
36
74
  });
37
75
  useEffect2(() => {
76
+ if (typeof window === "undefined") return;
38
77
  const handleDataRefresh = () => {
39
- if (window?.__FW_DATA__) {
40
- const data = window.__FW_DATA__;
41
- setState({
42
- params: data.params || {},
43
- props: data.props || {}
44
- });
45
- }
78
+ const data = getWindowData();
79
+ const routerData = getRouterData();
80
+ const currentPathname = window.location.pathname;
81
+ const currentSearch = window.location.search;
82
+ const searchParams = routerData?.searchParams || parseQueryString(currentSearch);
83
+ setRouteData({
84
+ pathname: routerData?.pathname || data?.pathname || currentPathname,
85
+ query: searchParams,
86
+ // For backward compatibility
87
+ searchParams,
88
+ params: routerData?.params || data?.params || {}
89
+ });
46
90
  };
47
91
  window.addEventListener("fw-data-refresh", handleDataRefresh);
92
+ window.addEventListener("fw-router-data-refresh", handleDataRefresh);
93
+ const handlePopState = () => {
94
+ handleDataRefresh();
95
+ };
96
+ window.addEventListener("popstate", handlePopState);
48
97
  return () => {
49
98
  window.removeEventListener("fw-data-refresh", handleDataRefresh);
99
+ window.removeEventListener("fw-router-data-refresh", handleDataRefresh);
100
+ window.removeEventListener("popstate", handlePopState);
50
101
  };
51
102
  }, []);
52
- return { params: state.params, props: state.props };
103
+ const push = useCallback(
104
+ async (url, options) => {
105
+ const fullUrl = url.startsWith("/") ? url : `/${url}`;
106
+ const getCurrentNavigate = () => {
107
+ if (navigateRef.current) return navigateRef.current;
108
+ if (navigate) return navigate;
109
+ if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
110
+ return window[ROUTER_NAVIGATE_KEY];
111
+ }
112
+ return null;
113
+ };
114
+ let currentNavigate = getCurrentNavigate();
115
+ if (typeof window === "undefined") {
116
+ return;
117
+ }
118
+ if (!currentNavigate) {
119
+ for (let i = 0; i < 10; i++) {
120
+ await new Promise((resolve) => setTimeout(resolve, 10));
121
+ currentNavigate = getCurrentNavigate();
122
+ if (currentNavigate) break;
123
+ }
124
+ }
125
+ if (!currentNavigate) {
126
+ window.location.href = fullUrl;
127
+ return;
128
+ }
129
+ const currentUrl = window.location.pathname + window.location.search;
130
+ if (fullUrl === currentUrl) {
131
+ return;
132
+ }
133
+ window.history.pushState({}, "", fullUrl);
134
+ await currentNavigate(fullUrl, options);
135
+ },
136
+ [navigate]
137
+ // Include navigate in dependencies so it updates when context becomes available
138
+ );
139
+ const replace = useCallback(
140
+ async (url, options) => {
141
+ const fullUrl = url.startsWith("/") ? url : `/${url}`;
142
+ const getCurrentNavigate = () => {
143
+ if (navigateRef.current) return navigateRef.current;
144
+ if (navigate) return navigate;
145
+ if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
146
+ return window[ROUTER_NAVIGATE_KEY];
147
+ }
148
+ return null;
149
+ };
150
+ let currentNavigate = getCurrentNavigate();
151
+ if (typeof window === "undefined") {
152
+ return;
153
+ }
154
+ if (!currentNavigate) {
155
+ for (let i = 0; i < 10; i++) {
156
+ await new Promise((resolve) => setTimeout(resolve, 10));
157
+ currentNavigate = getCurrentNavigate();
158
+ if (currentNavigate) break;
159
+ }
160
+ }
161
+ if (!currentNavigate) {
162
+ window.location.replace(fullUrl);
163
+ return;
164
+ }
165
+ window.history.replaceState({}, "", fullUrl);
166
+ await currentNavigate(fullUrl, options);
167
+ },
168
+ [navigate]
169
+ );
170
+ const back = useCallback(() => {
171
+ if (typeof window !== "undefined") {
172
+ window.history.back();
173
+ }
174
+ }, []);
175
+ const refresh = useCallback(async () => {
176
+ const currentUrl = typeof window !== "undefined" ? window.location.pathname + window.location.search : routeData.pathname;
177
+ const getCurrentNavigate = () => {
178
+ if (navigateRef.current) return navigateRef.current;
179
+ if (navigate) return navigate;
180
+ if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
181
+ return window[ROUTER_NAVIGATE_KEY];
182
+ }
183
+ return null;
184
+ };
185
+ let currentNavigate = getCurrentNavigate();
186
+ if (typeof window === "undefined") {
187
+ return;
188
+ }
189
+ if (!currentNavigate) {
190
+ for (let i = 0; i < 10; i++) {
191
+ await new Promise((resolve) => setTimeout(resolve, 10));
192
+ currentNavigate = getCurrentNavigate();
193
+ if (currentNavigate) break;
194
+ }
195
+ }
196
+ if (!currentNavigate) {
197
+ window.location.reload();
198
+ return;
199
+ }
200
+ await currentNavigate(currentUrl, { revalidate: true });
201
+ }, [navigate, routeData.pathname]);
202
+ return {
203
+ push,
204
+ replace,
205
+ back,
206
+ refresh,
207
+ pathname: routeData.pathname,
208
+ query: routeData.query,
209
+ searchParams: routeData.searchParams,
210
+ params: routeData.params
211
+ };
212
+ }
213
+ function parseQueryString(search) {
214
+ const params = {};
215
+ if (!search || search.length === 0) return params;
216
+ const queryString = search.startsWith("?") ? search.slice(1) : search;
217
+ const pairs = queryString.split("&");
218
+ for (const pair of pairs) {
219
+ const [key, value] = pair.split("=");
220
+ if (key) {
221
+ params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : "";
222
+ }
223
+ }
224
+ return params;
53
225
  }
54
226
  export {
55
227
  useBroadcastChannel,
56
- usePageProps
228
+ useRouter
57
229
  };
58
230
  //# sourceMappingURL=hooks.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../modules/react/hooks/useBroadcastChannel/index.tsx","../../modules/react/hooks/usePageProps/index.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"],"mappings":";AAAA,SAAgB,WAAW,gBAAgB;AAEpC,IAAM,sBAAsB,CAAC,gBAAwB;AAC1D,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,UAAU,IAAI,iBAAiB,WAAW;AAEhD,YAAU,MAAM;AACd,UAAM,gBAAgB,CAAC,UAAwB;AAC7C,iBAAW,MAAM,IAAI;AAAA,IACvB;AAEA,YAAQ,YAAY;AAGpB,WAAO,MAAM;AACX,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,cAAc,CAAC,QAAiB;AACpC,YAAQ,YAAY,GAAG;AAAA,EACzB;AAEA,SAAO,EAAE,SAAS,YAAY;AAChC;;;ACxBA,SAAgB,aAAAA,YAAW,YAAAC,iBAAgB;AAoBpC,SAAS,eAA0D;AACxE,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAkC,MAAM;AAEhE,QAAI,OAAO,WAAW,eAAgB,QAAgB,aAAa;AACjE,YAAM,OAAQ,OAAe;AAC7B,aAAO;AAAA,QACL,QAAQ,KAAK,UAAU,CAAC;AAAA,QACxB,OAAO,KAAK,SAAS,CAAC;AAAA,MACxB;AAAA,IACF;AACA,WAAO;AAAA,MACL,QAAQ,CAAC;AAAA,MACT,OAAO,CAAC;AAAA,IACV;AAAA,EACF,CAAC;AAED,EAAAD,WAAU,MAAM;AAEd,UAAM,oBAAoB,MAAM;AAC9B,UAAK,QAAgB,aAAa;AAChC,cAAM,OAAQ,OAAe;AAC7B,iBAAS;AAAA,UACP,QAAQ,KAAK,UAAU,CAAC;AAAA,UACxB,OAAO,KAAK,SAAS,CAAC;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,iBAAiB,mBAAmB,iBAAiB;AAE5D,WAAO,MAAM;AACX,aAAO,oBAAoB,mBAAmB,iBAAiB;AAAA,IACjE;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,QAAQ,MAAM,QAAa,OAAO,MAAM,MAAW;AAC9D;","names":["useEffect","useState"]}
1
+ {"version":3,"sources":["../../modules/react/hooks/useBroadcastChannel/index.tsx","../../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 { 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":["useState","useEffect","useContext","useContext","useEffect","useState"]}
package/dist/runtime.cjs CHANGED
@@ -29,12 +29,23 @@ var import_client = require("react-dom/client");
29
29
 
30
30
  // modules/runtime/client/constants.ts
31
31
  var WINDOW_DATA_KEY = "__FW_DATA__";
32
+ var ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
32
33
  var APP_CONTAINER_ID = "__app";
34
+ var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
33
35
 
34
36
  // modules/runtime/client/window-data.ts
35
37
  function getWindowData() {
38
+ if (typeof window === "undefined") {
39
+ return null;
40
+ }
36
41
  return window[WINDOW_DATA_KEY] ?? null;
37
42
  }
43
+ function getRouterData() {
44
+ if (typeof window === "undefined") {
45
+ return null;
46
+ }
47
+ return window[ROUTER_DATA_KEY] ?? null;
48
+ }
38
49
  function setWindowData(data) {
39
50
  window[WINDOW_DATA_KEY] = data;
40
51
  if (typeof window !== "undefined") {
@@ -45,6 +56,16 @@ function setWindowData(data) {
45
56
  );
46
57
  }
47
58
  }
59
+ function setRouterData(data) {
60
+ window[ROUTER_DATA_KEY] = data;
61
+ if (typeof window !== "undefined") {
62
+ window.dispatchEvent(
63
+ new CustomEvent("fw-router-data-refresh", {
64
+ detail: { data }
65
+ })
66
+ );
67
+ }
68
+ }
48
69
  function getCurrentTheme() {
49
70
  return getWindowData()?.theme ?? null;
50
71
  }
@@ -109,7 +130,7 @@ function applyMetadata(md) {
109
130
  }
110
131
 
111
132
  // modules/runtime/client/AppShell.tsx
112
- var import_react = require("react");
133
+ var import_react2 = require("react");
113
134
 
114
135
  // modules/runtime/client/RouterView.tsx
115
136
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -306,6 +327,13 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
306
327
  error: true
307
328
  };
308
329
  setWindowData(windowData);
330
+ const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
331
+ const routerData = {
332
+ pathname: url.pathname,
333
+ params: json.params || {},
334
+ searchParams: Object.fromEntries(url.searchParams.entries())
335
+ };
336
+ setRouterData(routerData);
309
337
  setState({
310
338
  url: nextUrl,
311
339
  route: errorRoute,
@@ -352,6 +380,13 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
352
380
  error: false
353
381
  };
354
382
  setWindowData(windowData);
383
+ const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
384
+ const routerData = {
385
+ pathname: url.pathname,
386
+ params: {},
387
+ searchParams: Object.fromEntries(url.searchParams.entries())
388
+ };
389
+ setRouterData(routerData);
355
390
  if (notFoundRoute) {
356
391
  const components = await notFoundRoute.load();
357
392
  setState({
@@ -409,6 +444,13 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
409
444
  error: false
410
445
  };
411
446
  setWindowData(windowData);
447
+ const url = new URL(nextUrl, typeof window !== "undefined" ? window.location.origin : "http://localhost");
448
+ const routerData = {
449
+ pathname: url.pathname,
450
+ params: matched.params,
451
+ searchParams: Object.fromEntries(url.searchParams.entries())
452
+ };
453
+ setRouterData(routerData);
412
454
  const components = await matched.route.load();
413
455
  window.scrollTo({
414
456
  top: 0,
@@ -528,6 +570,10 @@ function createPopStateHandler(navigate2) {
528
570
  };
529
571
  }
530
572
 
573
+ // modules/runtime/client/RouterContext.tsx
574
+ var import_react = require("react");
575
+ var RouterContext = (0, import_react.createContext)(null);
576
+
531
577
  // modules/runtime/client/AppShell.tsx
532
578
  var import_jsx_runtime2 = require("react/jsx-runtime");
533
579
  function AppShell({
@@ -536,14 +582,14 @@ function AppShell({
536
582
  notFoundRoute,
537
583
  errorRoute
538
584
  }) {
539
- const [state, setState] = (0, import_react.useState)(initialState);
540
- const handlersRef = (0, import_react.useRef)({
585
+ const [state, setState] = (0, import_react2.useState)(initialState);
586
+ const handlersRef = (0, import_react2.useRef)({
541
587
  setState,
542
588
  routes,
543
589
  notFoundRoute,
544
590
  errorRoute
545
591
  });
546
- (0, import_react.useEffect)(() => {
592
+ (0, import_react2.useEffect)(() => {
547
593
  handlersRef.current = {
548
594
  setState,
549
595
  routes,
@@ -551,14 +597,30 @@ function AppShell({
551
597
  errorRoute
552
598
  };
553
599
  }, [routes, notFoundRoute, errorRoute]);
554
- (0, import_react.useEffect)(() => {
600
+ const handleNavigate = (0, import_react2.useCallback)(
601
+ async (nextUrl, options) => {
602
+ await navigate(nextUrl, handlersRef.current, {
603
+ revalidate: options?.revalidate
604
+ });
605
+ },
606
+ []
607
+ );
608
+ (0, import_react2.useEffect)(() => {
609
+ if (typeof window !== "undefined") {
610
+ window[ROUTER_NAVIGATE_KEY] = handleNavigate;
611
+ return () => {
612
+ delete window[ROUTER_NAVIGATE_KEY];
613
+ };
614
+ }
615
+ }, [handleNavigate]);
616
+ (0, import_react2.useEffect)(() => {
555
617
  let isMounted = true;
556
- async function handleNavigate(nextUrl, options) {
618
+ async function handleNavigateInternal(nextUrl, options) {
557
619
  if (!isMounted) return;
558
620
  await navigate(nextUrl, handlersRef.current, options);
559
621
  }
560
- const handleClick = createClickHandler(handleNavigate);
561
- const handlePopState = createPopStateHandler(handleNavigate);
622
+ const handleClick = createClickHandler(handleNavigateInternal);
623
+ const handlePopState = createPopStateHandler(handleNavigateInternal);
562
624
  window.addEventListener("click", handleClick, false);
563
625
  window.addEventListener("popstate", handlePopState, false);
564
626
  return () => {
@@ -567,11 +629,33 @@ function AppShell({
567
629
  window.removeEventListener("popstate", handlePopState, false);
568
630
  };
569
631
  }, []);
632
+ (0, import_react2.useEffect)(() => {
633
+ const handleDataRefresh = () => {
634
+ const freshData = window?.__FW_DATA__;
635
+ if (!freshData) return;
636
+ const currentPathname = window.location.pathname;
637
+ const freshPathname = freshData.pathname;
638
+ if (freshPathname === currentPathname) {
639
+ if (freshData.metadata !== void 0) {
640
+ applyMetadata(freshData.metadata);
641
+ }
642
+ setState((prevState) => ({
643
+ ...prevState,
644
+ props: freshData.props ?? prevState.props,
645
+ params: freshData.params ?? prevState.params
646
+ }));
647
+ }
648
+ };
649
+ window.addEventListener("fw-data-refresh", handleDataRefresh);
650
+ return () => {
651
+ window.removeEventListener("fw-data-refresh", handleDataRefresh);
652
+ };
653
+ }, [state.url]);
570
654
  const isError = state.route === errorRoute;
571
655
  const isNotFound = state.route === notFoundRoute;
572
656
  const routeType = isError ? "error" : isNotFound ? "notfound" : "normal";
573
657
  const routeKey = `${state.url}:${routeType}`;
574
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey);
658
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey) });
575
659
  }
576
660
 
577
661
  // modules/runtime/client/bootstrap.tsx
@@ -671,6 +755,16 @@ function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
671
755
  return;
672
756
  }
673
757
  const initialUrl = window.location.pathname + window.location.search;
758
+ let routerData = getRouterData();
759
+ if (!routerData) {
760
+ const url = new URL(initialUrl, window.location.origin);
761
+ routerData = {
762
+ pathname: url.pathname,
763
+ params: initialData?.params || {},
764
+ searchParams: Object.fromEntries(url.searchParams.entries())
765
+ };
766
+ setRouterData(routerData);
767
+ }
674
768
  try {
675
769
  const initialState = await loadInitialRoute(
676
770
  initialUrl,