@lolyjs/core 0.2.0-alpha.3 → 0.2.0-alpha.30

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.
Files changed (46) hide show
  1. package/README.md +1463 -761
  2. package/dist/{bootstrap-BiCQmSkx.d.mts → bootstrap-BfGTMUkj.d.mts} +19 -0
  3. package/dist/{bootstrap-BiCQmSkx.d.ts → bootstrap-BfGTMUkj.d.ts} +19 -0
  4. package/dist/cli.cjs +15701 -2448
  5. package/dist/cli.cjs.map +1 -1
  6. package/dist/cli.js +15704 -2441
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.cjs +17861 -4115
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.mts +323 -55
  11. package/dist/index.d.ts +323 -55
  12. package/dist/index.js +17982 -4227
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.types-B9j4OQft.d.mts +222 -0
  15. package/dist/index.types-B9j4OQft.d.ts +222 -0
  16. package/dist/react/cache.cjs +107 -32
  17. package/dist/react/cache.cjs.map +1 -1
  18. package/dist/react/cache.d.mts +29 -21
  19. package/dist/react/cache.d.ts +29 -21
  20. package/dist/react/cache.js +107 -32
  21. package/dist/react/cache.js.map +1 -1
  22. package/dist/react/components.cjs +11 -12
  23. package/dist/react/components.cjs.map +1 -1
  24. package/dist/react/components.js +11 -12
  25. package/dist/react/components.js.map +1 -1
  26. package/dist/react/hooks.cjs +124 -74
  27. package/dist/react/hooks.cjs.map +1 -1
  28. package/dist/react/hooks.d.mts +6 -24
  29. package/dist/react/hooks.d.ts +6 -24
  30. package/dist/react/hooks.js +122 -71
  31. package/dist/react/hooks.js.map +1 -1
  32. package/dist/react/sockets.cjs +5 -6
  33. package/dist/react/sockets.cjs.map +1 -1
  34. package/dist/react/sockets.js +5 -6
  35. package/dist/react/sockets.js.map +1 -1
  36. package/dist/react/themes.cjs +61 -18
  37. package/dist/react/themes.cjs.map +1 -1
  38. package/dist/react/themes.js +63 -20
  39. package/dist/react/themes.js.map +1 -1
  40. package/dist/runtime.cjs +531 -104
  41. package/dist/runtime.cjs.map +1 -1
  42. package/dist/runtime.d.mts +2 -2
  43. package/dist/runtime.d.ts +2 -2
  44. package/dist/runtime.js +531 -104
  45. package/dist/runtime.js.map +1 -1
  46. package/package.json +56 -14
@@ -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/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 { useRouter } from \"./useRouter\";\r\nexport type { Router } from \"./useRouter\";\r\n","import React, { useEffect, useState, useRef, useCallback } from \"react\";\r\n\r\nexport const useBroadcastChannel = (channelName: string) => {\r\n const [message, setMessage] = useState(null);\r\n const channelRef = useRef<BroadcastChannel | null>(null);\r\n\r\n useEffect(() => {\r\n // Create channel only once, inside useEffect\r\n if (!channelRef.current && typeof window !== \"undefined\") {\r\n channelRef.current = new BroadcastChannel(channelName);\r\n }\r\n\r\n const channel = channelRef.current;\r\n if (!channel) return;\r\n\r\n const handleMessage = (event: MessageEvent) => {\r\n setMessage(event.data);\r\n };\r\n\r\n channel.onmessage = handleMessage;\r\n\r\n // Clean up the channel when the component unmounts\r\n return () => {\r\n if (channelRef.current) {\r\n channelRef.current.close();\r\n channelRef.current = null;\r\n }\r\n };\r\n }, [channelName]);\r\n\r\n const sendMessage = useCallback((msg: unknown) => {\r\n if (channelRef.current) {\r\n channelRef.current.postMessage(msg);\r\n }\r\n }, []);\r\n\r\n return { message, sendMessage };\r\n};\r\n","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\nconst LAYOUT_PROPS_KEY = \"__FW_LAYOUT_PROPS__\";\r\n\r\nexport function getWindowData(): InitialData | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return (window[WINDOW_DATA_KEY] as InitialData | undefined) ?? null;\r\n}\r\n\r\n/**\r\n * Gets preserved layout props from window storage.\r\n * Layout props are preserved across SPA navigations when layout hooks are skipped.\r\n */\r\nexport function getPreservedLayoutProps(): Record<string, any> | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return ((window as any)[LAYOUT_PROPS_KEY] as Record<string, any> | undefined) ?? null;\r\n}\r\n\r\n/**\r\n * Sets preserved layout props in window storage.\r\n * These props are used when layout hooks are skipped in SPA navigation.\r\n */\r\nexport function setPreservedLayoutProps(props: Record<string, any> | null): void {\r\n if (typeof window === \"undefined\") {\r\n return;\r\n }\r\n if (props === null) {\r\n delete (window as any)[LAYOUT_PROPS_KEY];\r\n } else {\r\n (window as any)[LAYOUT_PROPS_KEY] = props;\r\n }\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[ROUTER_DATA_KEY] as RouterData | undefined) ?? null;\r\n}\r\n\r\nexport function setWindowData(data: InitialData): void {\r\n window[WINDOW_DATA_KEY] = data;\r\n \r\n // Dispatch event for components to listen to (e.g. 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[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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAgE;AAEzD,IAAM,sBAAsB,CAAC,gBAAwB;AAC1D,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,IAAI;AAC3C,QAAM,iBAAa,qBAAgC,IAAI;AAEvD,8BAAU,MAAM;AAEd,QAAI,CAAC,WAAW,WAAW,OAAO,WAAW,aAAa;AACxD,iBAAW,UAAU,IAAI,iBAAiB,WAAW;AAAA,IACvD;AAEA,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS;AAEd,UAAM,gBAAgB,CAAC,UAAwB;AAC7C,iBAAW,MAAM,IAAI;AAAA,IACvB;AAEA,YAAQ,YAAY;AAGpB,WAAO,MAAM;AACX,UAAI,WAAW,SAAS;AACtB,mBAAW,QAAQ,MAAM;AACzB,mBAAW,UAAU;AAAA,MACvB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,kBAAc,0BAAY,CAAC,QAAiB;AAChD,QAAI,WAAW,SAAS;AACtB,iBAAW,QAAQ,YAAY,GAAG;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,SAAS,YAAY;AAChC;;;ACrCA,IAAAA,gBAAqE;;;ACArE,IAAAC,gBAA0C;AAWnC,IAAM,oBAAgB,6BAAyC,IAAI;;;ACVnE,IAAM,kBAAkB;AACxB,IAAM,kBAAkB;AAGxB,IAAM,sBAAsB;;;ACA5B,SAAS,gBAAoC;AAClD,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AACA,SAAQ,OAAO,eAAe,KAAiC;AACjE;AA4BO,SAAS,gBAAmC;AACjD,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AACA,SAAQ,OAAO,eAAe,KAAgC;AAChE;;;AH+CO,SAAS,YAAoB;AAElC,QAAM,cAAU,0BAAW,aAAa;AACxC,QAAM,WAAW,SAAS;AAI1B,QAAM,kBAAc,sBAAO,QAAQ;AAGnC,+BAAU,MAAM;AACd,gBAAY,UAAU;AAAA,EACxB,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAS,MAAM;AAE/C,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAO,CAAC;AAAA,QACR,cAAc,CAAC;AAAA,QACf,QAAQ,CAAC;AAAA,MACX;AAAA,IACF;AAGA,UAAM,OAAO,cAAc;AAC3B,UAAM,aAAa,cAAc;AAGjC,UAAM,eAAe,YAAY,gBAAgB,iBAAiB,OAAO,SAAS,MAAM;AAExF,WAAO;AAAA,MACL,UAAU,YAAY,YAAY,MAAM,YAAY,OAAO,SAAS;AAAA,MACpE,OAAO;AAAA;AAAA,MACP;AAAA,MACA,QAAQ,YAAY,UAAU,MAAM,UAAU,CAAC;AAAA,IACjD;AAAA,EACF,CAAC;AAGD,+BAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,oBAAoB,MAAM;AAC9B,YAAM,OAAO,cAAc;AAC3B,YAAM,aAAa,cAAc;AACjC,YAAM,kBAAkB,OAAO,SAAS;AACxC,YAAM,gBAAgB,OAAO,SAAS;AAEtC,YAAM,eAAe,YAAY,gBAAgB,iBAAiB,aAAa;AAE/E,mBAAa;AAAA,QACX,UAAU,YAAY,YAAY,MAAM,YAAY;AAAA,QACpD,OAAO;AAAA;AAAA,QACP;AAAA,QACA,QAAQ,YAAY,UAAU,MAAM,UAAU,CAAC;AAAA,MACjD,CAAC;AAAA,IACH;AAGA,WAAO,iBAAiB,mBAAmB,iBAAiB;AAC5D,WAAO,iBAAiB,0BAA0B,iBAAiB;AAGnE,UAAM,iBAAiB,MAAM;AAC3B,wBAAkB;AAAA,IACpB;AACA,WAAO,iBAAiB,YAAY,cAAc;AAElD,WAAO,MAAM;AACX,aAAO,oBAAoB,mBAAmB,iBAAiB;AAC/D,aAAO,oBAAoB,0BAA0B,iBAAiB;AACtE,aAAO,oBAAoB,YAAY,cAAc;AAAA,IACvD;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAO;AAAA,IACX,OAAO,KAAa,YAAuC;AACzD,YAAM,UAAU,IAAI,WAAW,GAAG,IAAI,MAAM,IAAI,GAAG;AAanD,YAAM,qBAAqB,MAAM;AAC/B,YAAI,YAAY,QAAS,QAAO,YAAY;AAC5C,YAAI,SAAU,QAAO;AACrB,YAAI,OAAO,WAAW,eAAgB,OAAe,mBAAmB,GAAG;AACzE,iBAAQ,OAAe,mBAAmB;AAAA,QAC5C;AACA,eAAO;AAAA,MACT;AAEA,UAAI,kBAAkB,mBAAmB;AAEzC,UAAI,OAAO,WAAW,aAAa;AACjC;AAAA,MACF;AAGA,UAAI,CAAC,iBAAiB;AACpB,iBAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,gBAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACpD,4BAAkB,mBAAmB;AACrC,cAAI,gBAAiB;AAAA,QACvB;AAAA,MACF;AAGA,UAAI,CAAC,iBAAiB;AACpB,eAAO,SAAS,OAAO;AACvB;AAAA,MACF;AAGA,YAAM,aAAa,OAAO,SAAS,WAAW,OAAO,SAAS;AAC9D,UAAI,YAAY,YAAY;AAC1B;AAAA,MACF;AAIA,aAAO,QAAQ,UAAU,CAAC,GAAG,IAAI,OAAO;AAIxC,YAAM,gBAAgB,SAAS,OAAO;AAAA,IACxC;AAAA,IACA,CAAC,QAAQ;AAAA;AAAA,EACX;AAEA,QAAM,cAAU;AAAA,IACd,OAAO,KAAa,YAAuC;AACzD,YAAM,UAAU,IAAI,WAAW,GAAG,IAAI,MAAM,IAAI,GAAG;AAEnD,YAAM,qBAAqB,MAAM;AAC/B,YAAI,YAAY,QAAS,QAAO,YAAY;AAC5C,YAAI,SAAU,QAAO;AACrB,YAAI,OAAO,WAAW,eAAgB,OAAe,mBAAmB,GAAG;AACzE,iBAAQ,OAAe,mBAAmB;AAAA,QAC5C;AACA,eAAO;AAAA,MACT;AAEA,UAAI,kBAAkB,mBAAmB;AAEzC,UAAI,OAAO,WAAW,aAAa;AACjC;AAAA,MACF;AAEA,UAAI,CAAC,iBAAiB;AACpB,iBAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,gBAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACpD,4BAAkB,mBAAmB;AACrC,cAAI,gBAAiB;AAAA,QACvB;AAAA,MACF;AAEA,UAAI,CAAC,iBAAiB;AACpB,eAAO,SAAS,QAAQ,OAAO;AAC/B;AAAA,MACF;AAGA,aAAO,QAAQ,aAAa,CAAC,GAAG,IAAI,OAAO;AAG3C,YAAM,gBAAgB,SAAS,OAAO;AAAA,IACxC;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,WAAO,2BAAY,MAAM;AAC7B,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,QAAQ,KAAK;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,cAAU,2BAAY,YAAY;AACtC,UAAM,aAAa,OAAO,WAAW,cACjC,OAAO,SAAS,WAAW,OAAO,SAAS,SAC3C,UAAU;AAEd,UAAM,qBAAqB,MAAM;AAC/B,UAAI,YAAY,QAAS,QAAO,YAAY;AAC5C,UAAI,SAAU,QAAO;AACrB,UAAI,OAAO,WAAW,eAAgB,OAAe,mBAAmB,GAAG;AACzE,eAAQ,OAAe,mBAAmB;AAAA,MAC5C;AACA,aAAO;AAAA,IACT;AAEA,QAAI,kBAAkB,mBAAmB;AAEzC,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAEA,QAAI,CAAC,iBAAiB;AACpB,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,cAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACpD,0BAAkB,mBAAmB;AACrC,YAAI,gBAAiB;AAAA,MACvB;AAAA,IACF;AAEA,QAAI,CAAC,iBAAiB;AACpB,aAAO,SAAS,OAAO;AACvB;AAAA,IACF;AAEA,UAAM,gBAAgB,YAAY,EAAE,YAAY,KAAK,CAAC;AAAA,EACxD,GAAG,CAAC,UAAU,UAAU,QAAQ,CAAC;AAEjC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,UAAU;AAAA,IACpB,OAAO,UAAU;AAAA,IACjB,cAAc,UAAU;AAAA,IACxB,QAAQ,UAAU;AAAA,EACpB;AACF;AAOA,SAAS,iBAAiB,QAAwC;AAChE,QAAM,SAAiC,CAAC;AACxC,MAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;AAE3C,QAAM,cAAc,OAAO,WAAW,GAAG,IAAI,OAAO,MAAM,CAAC,IAAI;AAC/D,QAAM,QAAQ,YAAY,MAAM,GAAG;AAEnC,aAAW,QAAQ,OAAO;AACxB,UAAM,CAAC,KAAK,KAAK,IAAI,KAAK,MAAM,GAAG;AACnC,QAAI,KAAK;AACP,aAAO,mBAAmB,GAAG,CAAC,IAAI,QAAQ,mBAAmB,KAAK,IAAI;AAAA,IACxE;AAAA,EACF;AAEA,SAAO;AACT;","names":["import_react","import_react"]}
@@ -3,29 +3,6 @@ declare const useBroadcastChannel: (channelName: string) => {
3
3
  sendMessage: (msg: unknown) => void;
4
4
  };
5
5
 
6
- /**
7
- * Hook to access page props and route parameters.
8
- *
9
- * Reads initial data from window.__FW_DATA__ set during SSR.
10
- * Automatically updates when `revalidate()` is called.
11
- *
12
- * @template P - Type for page props (default: any)
13
- * @template T - Type for route params (default: any)
14
- * @returns Object containing params and props
15
- *
16
- * @example
17
- * // With props type only
18
- * const { props } = usePageProps<{ title: string }>();
19
- *
20
- * @example
21
- * // With both props and params types
22
- * const { props, params } = usePageProps<{ title: string }, { id: string }>();
23
- */
24
- declare function usePageProps<P = any, T = any>(): {
25
- params: T;
26
- props: P;
27
- };
28
-
29
6
  interface Router {
30
7
  /**
31
8
  * Navigate to a new route.
@@ -57,8 +34,13 @@ interface Router {
57
34
  pathname: string;
58
35
  /**
59
36
  * Query parameters from the URL (e.g., ?id=123&name=test)
37
+ * Alias for searchParams for backward compatibility
60
38
  */
61
39
  query: Record<string, string>;
40
+ /**
41
+ * Search parameters from the URL (e.g., ?id=123&name=test)
42
+ */
43
+ searchParams: Record<string, unknown>;
62
44
  /**
63
45
  * Dynamic route parameters (e.g., { slug: "my-post" } for /blog/[slug])
64
46
  */
@@ -104,4 +86,4 @@ interface Router {
104
86
  */
105
87
  declare function useRouter(): Router;
106
88
 
107
- export { type Router, useBroadcastChannel, usePageProps, useRouter };
89
+ export { type Router, useBroadcastChannel, useRouter };
@@ -3,29 +3,6 @@ declare const useBroadcastChannel: (channelName: string) => {
3
3
  sendMessage: (msg: unknown) => void;
4
4
  };
5
5
 
6
- /**
7
- * Hook to access page props and route parameters.
8
- *
9
- * Reads initial data from window.__FW_DATA__ set during SSR.
10
- * Automatically updates when `revalidate()` is called.
11
- *
12
- * @template P - Type for page props (default: any)
13
- * @template T - Type for route params (default: any)
14
- * @returns Object containing params and props
15
- *
16
- * @example
17
- * // With props type only
18
- * const { props } = usePageProps<{ title: string }>();
19
- *
20
- * @example
21
- * // With both props and params types
22
- * const { props, params } = usePageProps<{ title: string }, { id: string }>();
23
- */
24
- declare function usePageProps<P = any, T = any>(): {
25
- params: T;
26
- props: P;
27
- };
28
-
29
6
  interface Router {
30
7
  /**
31
8
  * Navigate to a new route.
@@ -57,8 +34,13 @@ interface Router {
57
34
  pathname: string;
58
35
  /**
59
36
  * Query parameters from the URL (e.g., ?id=123&name=test)
37
+ * Alias for searchParams for backward compatibility
60
38
  */
61
39
  query: Record<string, string>;
40
+ /**
41
+ * Search parameters from the URL (e.g., ?id=123&name=test)
42
+ */
43
+ searchParams: Record<string, unknown>;
62
44
  /**
63
45
  * Dynamic route parameters (e.g., { slug: "my-post" } for /blog/[slug])
64
46
  */
@@ -104,4 +86,4 @@ interface Router {
104
86
  */
105
87
  declare function useRouter(): Router;
106
88
 
107
- export { type Router, useBroadcastChannel, usePageProps, useRouter };
89
+ export { type Router, useBroadcastChannel, useRouter };
@@ -1,59 +1,35 @@
1
1
  // modules/react/hooks/useBroadcastChannel/index.tsx
2
- import { useEffect, useState } from "react";
2
+ import { useEffect, useState, useRef, useCallback } from "react";
3
3
  var useBroadcastChannel = (channelName) => {
4
4
  const [message, setMessage] = useState(null);
5
- const channel = new BroadcastChannel(channelName);
5
+ const channelRef = useRef(null);
6
6
  useEffect(() => {
7
+ if (!channelRef.current && typeof window !== "undefined") {
8
+ channelRef.current = new BroadcastChannel(channelName);
9
+ }
10
+ const channel = channelRef.current;
11
+ if (!channel) return;
7
12
  const handleMessage = (event) => {
8
13
  setMessage(event.data);
9
14
  };
10
15
  channel.onmessage = handleMessage;
11
16
  return () => {
12
- channel.close();
13
- };
14
- }, [channel]);
15
- const sendMessage = (msg) => {
16
- channel.postMessage(msg);
17
- };
18
- return { message, sendMessage };
19
- };
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__;
27
- return {
28
- params: data.params || {},
29
- props: data.props || {}
30
- };
31
- }
32
- return {
33
- params: {},
34
- props: {}
35
- };
36
- });
37
- useEffect2(() => {
38
- const handleDataRefresh = () => {
39
- if (window?.__FW_DATA__) {
40
- const data = window.__FW_DATA__;
41
- setState({
42
- params: data.params || {},
43
- props: data.props || {}
44
- });
17
+ if (channelRef.current) {
18
+ channelRef.current.close();
19
+ channelRef.current = null;
45
20
  }
46
21
  };
47
- window.addEventListener("fw-data-refresh", handleDataRefresh);
48
- return () => {
49
- window.removeEventListener("fw-data-refresh", handleDataRefresh);
50
- };
22
+ }, [channelName]);
23
+ const sendMessage = useCallback((msg) => {
24
+ if (channelRef.current) {
25
+ channelRef.current.postMessage(msg);
26
+ }
51
27
  }, []);
52
- return { params: state.params, props: state.props };
53
- }
28
+ return { message, sendMessage };
29
+ };
54
30
 
55
31
  // modules/react/hooks/useRouter/index.ts
56
- import { useState as useState3, useEffect as useEffect3, useCallback, useContext as useContext2 } from "react";
32
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useContext as useContext2, useRef as useRef2 } from "react";
57
33
 
58
34
  // modules/runtime/client/RouterContext.tsx
59
35
  import { createContext, useContext } from "react";
@@ -61,6 +37,8 @@ var RouterContext = createContext(null);
61
37
 
62
38
  // modules/runtime/client/constants.ts
63
39
  var WINDOW_DATA_KEY = "__FW_DATA__";
40
+ var ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
41
+ var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
64
42
 
65
43
  // modules/runtime/client/window-data.ts
66
44
  function getWindowData() {
@@ -69,94 +47,167 @@ function getWindowData() {
69
47
  }
70
48
  return window[WINDOW_DATA_KEY] ?? null;
71
49
  }
50
+ function getRouterData() {
51
+ if (typeof window === "undefined") {
52
+ return null;
53
+ }
54
+ return window[ROUTER_DATA_KEY] ?? null;
55
+ }
72
56
 
73
57
  // modules/react/hooks/useRouter/index.ts
74
58
  function useRouter() {
75
59
  const context = useContext2(RouterContext);
76
60
  const navigate = context?.navigate;
77
- const [routeData, setRouteData] = useState3(() => {
61
+ const navigateRef = useRef2(navigate);
62
+ useEffect2(() => {
63
+ navigateRef.current = navigate;
64
+ }, [navigate]);
65
+ const [routeData, setRouteData] = useState2(() => {
78
66
  if (typeof window === "undefined") {
79
67
  return {
80
68
  pathname: "",
81
69
  query: {},
70
+ searchParams: {},
82
71
  params: {}
83
72
  };
84
73
  }
85
74
  const data = getWindowData();
75
+ const routerData = getRouterData();
76
+ const searchParams = routerData?.searchParams || parseQueryString(window.location.search);
86
77
  return {
87
- pathname: data?.pathname || window.location.pathname,
88
- query: parseQueryString(window.location.search),
89
- params: data?.params || {}
78
+ pathname: routerData?.pathname || data?.pathname || window.location.pathname,
79
+ query: searchParams,
80
+ // For backward compatibility
81
+ searchParams,
82
+ params: routerData?.params || data?.params || {}
90
83
  };
91
84
  });
92
- useEffect3(() => {
85
+ useEffect2(() => {
93
86
  if (typeof window === "undefined") return;
94
87
  const handleDataRefresh = () => {
95
88
  const data = getWindowData();
89
+ const routerData = getRouterData();
96
90
  const currentPathname = window.location.pathname;
97
91
  const currentSearch = window.location.search;
92
+ const searchParams = routerData?.searchParams || parseQueryString(currentSearch);
98
93
  setRouteData({
99
- pathname: data?.pathname || currentPathname,
100
- query: parseQueryString(currentSearch),
101
- params: data?.params || {}
94
+ pathname: routerData?.pathname || data?.pathname || currentPathname,
95
+ query: searchParams,
96
+ // For backward compatibility
97
+ searchParams,
98
+ params: routerData?.params || data?.params || {}
102
99
  });
103
100
  };
104
101
  window.addEventListener("fw-data-refresh", handleDataRefresh);
102
+ window.addEventListener("fw-router-data-refresh", handleDataRefresh);
105
103
  const handlePopState = () => {
106
104
  handleDataRefresh();
107
105
  };
108
106
  window.addEventListener("popstate", handlePopState);
109
107
  return () => {
110
108
  window.removeEventListener("fw-data-refresh", handleDataRefresh);
109
+ window.removeEventListener("fw-router-data-refresh", handleDataRefresh);
111
110
  window.removeEventListener("popstate", handlePopState);
112
111
  };
113
112
  }, []);
114
- const push = useCallback(
113
+ const push = useCallback2(
115
114
  async (url, options) => {
116
115
  const fullUrl = url.startsWith("/") ? url : `/${url}`;
117
- if (!navigate) {
118
- if (typeof window !== "undefined") {
119
- window.location.href = fullUrl;
116
+ const getCurrentNavigate = () => {
117
+ if (navigateRef.current) return navigateRef.current;
118
+ if (navigate) return navigate;
119
+ if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
120
+ return window[ROUTER_NAVIGATE_KEY];
120
121
  }
122
+ return null;
123
+ };
124
+ let currentNavigate = getCurrentNavigate();
125
+ if (typeof window === "undefined") {
126
+ return;
127
+ }
128
+ if (!currentNavigate) {
129
+ for (let i = 0; i < 10; i++) {
130
+ await new Promise((resolve) => setTimeout(resolve, 10));
131
+ currentNavigate = getCurrentNavigate();
132
+ if (currentNavigate) break;
133
+ }
134
+ }
135
+ if (!currentNavigate) {
136
+ window.location.href = fullUrl;
121
137
  return;
122
138
  }
123
- if (typeof window !== "undefined") {
124
- window.history.pushState({}, "", fullUrl);
139
+ const currentUrl = window.location.pathname + window.location.search;
140
+ if (fullUrl === currentUrl) {
141
+ return;
125
142
  }
126
- await navigate(fullUrl, options);
143
+ window.history.pushState({}, "", fullUrl);
144
+ await currentNavigate(fullUrl, options);
127
145
  },
128
146
  [navigate]
147
+ // Include navigate in dependencies so it updates when context becomes available
129
148
  );
130
- const replace = useCallback(
149
+ const replace = useCallback2(
131
150
  async (url, options) => {
132
151
  const fullUrl = url.startsWith("/") ? url : `/${url}`;
133
- if (!navigate) {
134
- if (typeof window !== "undefined") {
135
- window.location.replace(fullUrl);
152
+ const getCurrentNavigate = () => {
153
+ if (navigateRef.current) return navigateRef.current;
154
+ if (navigate) return navigate;
155
+ if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
156
+ return window[ROUTER_NAVIGATE_KEY];
136
157
  }
158
+ return null;
159
+ };
160
+ let currentNavigate = getCurrentNavigate();
161
+ if (typeof window === "undefined") {
137
162
  return;
138
163
  }
139
- if (typeof window !== "undefined") {
140
- window.history.replaceState({}, "", fullUrl);
164
+ if (!currentNavigate) {
165
+ for (let i = 0; i < 10; i++) {
166
+ await new Promise((resolve) => setTimeout(resolve, 10));
167
+ currentNavigate = getCurrentNavigate();
168
+ if (currentNavigate) break;
169
+ }
170
+ }
171
+ if (!currentNavigate) {
172
+ window.location.replace(fullUrl);
173
+ return;
141
174
  }
142
- await navigate(fullUrl, options);
175
+ window.history.replaceState({}, "", fullUrl);
176
+ await currentNavigate(fullUrl, options);
143
177
  },
144
178
  [navigate]
145
179
  );
146
- const back = useCallback(() => {
180
+ const back = useCallback2(() => {
147
181
  if (typeof window !== "undefined") {
148
182
  window.history.back();
149
183
  }
150
184
  }, []);
151
- const refresh = useCallback(async () => {
185
+ const refresh = useCallback2(async () => {
152
186
  const currentUrl = typeof window !== "undefined" ? window.location.pathname + window.location.search : routeData.pathname;
153
- if (!navigate) {
154
- if (typeof window !== "undefined") {
155
- window.location.reload();
187
+ const getCurrentNavigate = () => {
188
+ if (navigateRef.current) return navigateRef.current;
189
+ if (navigate) return navigate;
190
+ if (typeof window !== "undefined" && window[ROUTER_NAVIGATE_KEY]) {
191
+ return window[ROUTER_NAVIGATE_KEY];
156
192
  }
193
+ return null;
194
+ };
195
+ let currentNavigate = getCurrentNavigate();
196
+ if (typeof window === "undefined") {
197
+ return;
198
+ }
199
+ if (!currentNavigate) {
200
+ for (let i = 0; i < 10; i++) {
201
+ await new Promise((resolve) => setTimeout(resolve, 10));
202
+ currentNavigate = getCurrentNavigate();
203
+ if (currentNavigate) break;
204
+ }
205
+ }
206
+ if (!currentNavigate) {
207
+ window.location.reload();
157
208
  return;
158
209
  }
159
- await navigate(currentUrl, { revalidate: true });
210
+ await currentNavigate(currentUrl, { revalidate: true });
160
211
  }, [navigate, routeData.pathname]);
161
212
  return {
162
213
  push,
@@ -165,6 +216,7 @@ function useRouter() {
165
216
  refresh,
166
217
  pathname: routeData.pathname,
167
218
  query: routeData.query,
219
+ searchParams: routeData.searchParams,
168
220
  params: routeData.params
169
221
  };
170
222
  }
@@ -183,7 +235,6 @@ function parseQueryString(search) {
183
235
  }
184
236
  export {
185
237
  useBroadcastChannel,
186
- usePageProps,
187
238
  useRouter
188
239
  };
189
240
  //# sourceMappingURL=hooks.js.map
@@ -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/useRouter/index.ts","../../modules/runtime/client/RouterContext.tsx","../../modules/runtime/client/constants.ts","../../modules/runtime/client/window-data.ts"],"sourcesContent":["import React, { useEffect, useState, useRef, useCallback } from \"react\";\r\n\r\nexport const useBroadcastChannel = (channelName: string) => {\r\n const [message, setMessage] = useState(null);\r\n const channelRef = useRef<BroadcastChannel | null>(null);\r\n\r\n useEffect(() => {\r\n // Create channel only once, inside useEffect\r\n if (!channelRef.current && typeof window !== \"undefined\") {\r\n channelRef.current = new BroadcastChannel(channelName);\r\n }\r\n\r\n const channel = channelRef.current;\r\n if (!channel) return;\r\n\r\n const handleMessage = (event: MessageEvent) => {\r\n setMessage(event.data);\r\n };\r\n\r\n channel.onmessage = handleMessage;\r\n\r\n // Clean up the channel when the component unmounts\r\n return () => {\r\n if (channelRef.current) {\r\n channelRef.current.close();\r\n channelRef.current = null;\r\n }\r\n };\r\n }, [channelName]);\r\n\r\n const sendMessage = useCallback((msg: unknown) => {\r\n if (channelRef.current) {\r\n channelRef.current.postMessage(msg);\r\n }\r\n }, []);\r\n\r\n return { message, sendMessage };\r\n};\r\n","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\nconst LAYOUT_PROPS_KEY = \"__FW_LAYOUT_PROPS__\";\r\n\r\nexport function getWindowData(): InitialData | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return (window[WINDOW_DATA_KEY] as InitialData | undefined) ?? null;\r\n}\r\n\r\n/**\r\n * Gets preserved layout props from window storage.\r\n * Layout props are preserved across SPA navigations when layout hooks are skipped.\r\n */\r\nexport function getPreservedLayoutProps(): Record<string, any> | null {\r\n if (typeof window === \"undefined\") {\r\n return null;\r\n }\r\n return ((window as any)[LAYOUT_PROPS_KEY] as Record<string, any> | undefined) ?? null;\r\n}\r\n\r\n/**\r\n * Sets preserved layout props in window storage.\r\n * These props are used when layout hooks are skipped in SPA navigation.\r\n */\r\nexport function setPreservedLayoutProps(props: Record<string, any> | null): void {\r\n if (typeof window === \"undefined\") {\r\n return;\r\n }\r\n if (props === null) {\r\n delete (window as any)[LAYOUT_PROPS_KEY];\r\n } else {\r\n (window as any)[LAYOUT_PROPS_KEY] = props;\r\n }\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[ROUTER_DATA_KEY] as RouterData | undefined) ?? null;\r\n}\r\n\r\nexport function setWindowData(data: InitialData): void {\r\n window[WINDOW_DATA_KEY] = data;\r\n \r\n // Dispatch event for components to listen to (e.g. 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[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","useCallback","useContext","useRef","useContext","useRef","useEffect","useState","useCallback"]}
@@ -25,14 +25,13 @@ __export(sockets_exports, {
25
25
  module.exports = __toCommonJS(sockets_exports);
26
26
  var import_socket = require("socket.io-client");
27
27
  var lolySocket = (namespace, opts) => {
28
- const wsBaseUrl = process.env.PUBLIC_WS_BASE_URL;
29
- const baseUrl = wsBaseUrl && wsBaseUrl.trim() !== "" ? wsBaseUrl : window.location.origin;
30
- if (!wsBaseUrl || wsBaseUrl.trim() === "") {
31
- console.warn("[loly:socket] PUBLIC_WS_BASE_URL is not set, using window.location.origin.");
28
+ if (typeof window === "undefined") {
29
+ throw new Error(
30
+ "[loly:socket] lolySocket can only be called on the client side."
31
+ );
32
32
  }
33
33
  const normalizedNamespace = namespace.startsWith("/") ? namespace : `/${namespace}`;
34
- const fullUrl = `${baseUrl}${normalizedNamespace}`;
35
- const socket = (0, import_socket.io)(fullUrl, {
34
+ const socket = (0, import_socket.io)(normalizedNamespace, {
36
35
  path: "/wss",
37
36
  transports: ["websocket", "polling"],
38
37
  autoConnect: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../modules/react/sockets/index.ts"],"sourcesContent":["import { io, ManagerOptions, Socket, SocketOptions } from \"socket.io-client\";\r\n\r\n/**\r\n * Creates a Socket.IO client connection to a specific namespace.\r\n * \r\n * This helper function simplifies Socket.IO client setup by handling:\r\n * - Namespace normalization (ensures leading slash)\r\n * - Base URL resolution (from environment or current origin)\r\n * - Default Socket.IO configuration (path: '/wss', transports: ['websocket', 'polling'])\r\n * \r\n * @param namespace - The namespace to connect to (e.g., '/chat' or 'chat').\r\n * The namespace will be normalized to always start with '/'.\r\n * Must match the namespace pattern defined in your server's WSS routes.\r\n * @param opts - Optional Socket.IO client options that will override the defaults.\r\n * \r\n * @returns A Socket.IO client instance connected to the specified namespace.\r\n * \r\n * @example\r\n * ```ts\r\n * const socket = lolySocket('/chat');\r\n * socket.on('message', (data) => {\r\n * console.log('Received:', data);\r\n * });\r\n * socket.emit('message', { text: 'Hello' });\r\n * ```\r\n */\r\nexport const lolySocket = (\r\n namespace: string,\r\n opts?: Partial<ManagerOptions & SocketOptions>\r\n): Socket => {\r\n // @ts-ignore - process.env.PUBLIC_WS_BASE_URL is replaced by DefinePlugin at build time with literal value\r\n // DefinePlugin replaces process.env.PUBLIC_WS_BASE_URL with the actual value (or empty string if not set)\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const wsBaseUrl: string | undefined = (process as any).env.PUBLIC_WS_BASE_URL;\r\n const baseUrl = (wsBaseUrl && wsBaseUrl.trim() !== \"\") ? wsBaseUrl : window.location.origin;\r\n\r\n if (!wsBaseUrl || wsBaseUrl.trim() === \"\") {\r\n console.warn(\"[loly:socket] PUBLIC_WS_BASE_URL is not set, using window.location.origin.\");\r\n }\r\n\r\n // Normalize namespace to always start with '/'\r\n const normalizedNamespace = namespace.startsWith(\"/\") ? namespace : `/${namespace}`;\r\n\r\n // In Socket.IO, when using a custom path, the namespace is specified in the URL:\r\n // baseUrl + namespace. The path '/wss' is the HTTP route where Socket.IO listens.\r\n const fullUrl = `${baseUrl}${normalizedNamespace}`;\r\n\r\n const socket = io(fullUrl, {\r\n path: \"/wss\",\r\n transports: [\"websocket\", \"polling\"],\r\n autoConnect: true,\r\n ...opts,\r\n });\r\n\r\n return socket;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA0D;AA0BnD,IAAM,aAAa,CACxB,WACA,SACW;AAIX,QAAM,YAAiC,QAAgB,IAAI;AAC3D,QAAM,UAAW,aAAa,UAAU,KAAK,MAAM,KAAM,YAAY,OAAO,SAAS;AAErF,MAAI,CAAC,aAAa,UAAU,KAAK,MAAM,IAAI;AACzC,YAAQ,KAAK,4EAA4E;AAAA,EAC3F;AAGA,QAAM,sBAAsB,UAAU,WAAW,GAAG,IAAI,YAAY,IAAI,SAAS;AAIjF,QAAM,UAAU,GAAG,OAAO,GAAG,mBAAmB;AAEhD,QAAM,aAAS,kBAAG,SAAS;AAAA,IACzB,MAAM;AAAA,IACN,YAAY,CAAC,aAAa,SAAS;AAAA,IACnC,aAAa;AAAA,IACb,GAAG;AAAA,EACL,CAAC;AAED,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../modules/react/sockets/index.ts"],"sourcesContent":["import { io, ManagerOptions, Socket, SocketOptions } from \"socket.io-client\";\r\n\r\n/**\r\n * Creates a Socket.IO client connection to a specific namespace.\r\n * \r\n * This helper function simplifies Socket.IO client setup by handling:\r\n * - Namespace normalization (ensures leading slash)\r\n * - Base URL resolution (from environment or current origin)\r\n * - Default Socket.IO configuration (path: '/wss', transports: ['websocket', 'polling'])\r\n * \r\n * @param namespace - The namespace to connect to (e.g., '/chat' or 'chat').\r\n * The namespace will be normalized to always start with '/'.\r\n * Must match the namespace pattern defined in your server's WSS routes.\r\n * @param opts - Optional Socket.IO client options that will override the defaults.\r\n * \r\n * @returns A Socket.IO client instance connected to the specified namespace.\r\n * \r\n * @example\r\n * ```ts\r\n * const socket = lolySocket('/chat');\r\n * socket.on('message', (data) => {\r\n * console.log('Received:', data);\r\n * });\r\n * socket.emit('message', { text: 'Hello' });\r\n * ```\r\n */\r\nexport const lolySocket = (\r\n namespace: string,\r\n opts?: Partial<ManagerOptions & SocketOptions>\r\n): Socket => {\r\n // SIMPLIFICADO: Siempre usar el origin actual en el navegador\r\n // Socket.IO maneja el namespace automáticamente\r\n if (typeof window === \"undefined\") {\r\n throw new Error(\r\n \"[loly:socket] lolySocket can only be called on the client side.\"\r\n );\r\n }\r\n\r\n const normalizedNamespace = namespace.startsWith(\"/\") ? namespace : `/${namespace}`;\r\n \r\n // Socket.IO: io(namespace, { path: '/wss' })\r\n // Socket.IO usa window.location.origin automáticamente\r\n // El path '/wss' es el endpoint del engine de Socket.IO\r\n // El namespace se maneja en el handshake\r\n\r\n const socket = io(normalizedNamespace, {\r\n path: \"/wss\",\r\n transports: [\"websocket\", \"polling\"],\r\n autoConnect: true,\r\n ...opts,\r\n });\r\n\r\n return socket;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA0D;AA0BnD,IAAM,aAAa,CACxB,WACA,SACW;AAGX,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,sBAAsB,UAAU,WAAW,GAAG,IAAI,YAAY,IAAI,SAAS;AAOjF,QAAM,aAAS,kBAAG,qBAAqB;AAAA,IACrC,MAAM;AAAA,IACN,YAAY,CAAC,aAAa,SAAS;AAAA,IACnC,aAAa;AAAA,IACb,GAAG;AAAA,EACL,CAAC;AAED,SAAO;AACT;","names":[]}