{"version":3,"sources":["../src/client/router.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type RefObject } from \"react\";\nimport { createFarmRouter, type FarmRouter, type FarmRouterRouteInput } from \"../router\";\nimport {\n  getInstalledFarmSPARouter,\n  getRouter as getSPARouter,\n  readPageState,\n  type FarmNavigationBlockerContext,\n  type FarmNavigationState,\n} from \"./spa-router\";\nimport { notifyHistoryChange, subscribeHistoryChange } from \"./history-sync\";\nimport { getFarmI18nClientState } from \"../i18n/client-runtime\";\nimport { _resolveCurrentRequest } from \"../server/request-bridge\";\nimport { stripFarmLocaleFromPathname } from \"../i18n/routing\";\nimport {\n  applyFarmBasePath,\n  getFarmBasePath,\n  normalizeFarmBasePath,\n  stripFarmBasePath,\n} from \"../base-path\";\n\ninterface RouterState {\n  pathname: string;\n  searchParams: URLSearchParams;\n  params: Record<string, string>;\n  pageState: unknown;\n}\n\nexport interface UseRouterOptions {\n  basePath?: string;\n  routes?: FarmRouterRouteInput[];\n}\n\nexport interface UseBlockerOptions {\n  when: boolean | ((context: FarmNavigationBlockerContext) => boolean);\n  message?: string;\n  shouldBlock?: (context: FarmNavigationBlockerContext) => boolean | Promise<boolean>;\n}\n\nexport interface UseBlockerReturn {\n  active: boolean;\n}\n\nfunction shouldBlockUnload(options: UseBlockerOptions): boolean {\n  const path = window.location.pathname + window.location.search;\n  const context: FarmNavigationBlockerContext = {\n    from: path,\n    to: path,\n    action: \"replace\",\n  };\n  const when = typeof options.when === \"function\" ? options.when(context) : options.when;\n  if (!when) return false;\n  if (!options.shouldBlock) return true;\n\n  try {\n    const result = options.shouldBlock(context);\n    if (typeof result === \"boolean\") return result;\n    void result.catch(() => undefined);\n    return true;\n  } catch {\n    return true;\n  }\n}\n\n/**\n * Hook for accessing router state and navigation\n */\n/**\n * Imperative navigation for useRouter. Delegates to the installed SPA router\n * so blockers run before the URL changes, history state is written by the\n * router, and the action is labelled push/replace rather than pop. Without a\n * Farm router (embedded usage) the URL is written directly and announced on\n * the shared history channel, whose no-router fallback is the nuqs-style\n * synthetic popstate this code previously hand-rolled.\n */\nfunction navigateViaRouter(href: string, basePath: string, replace: boolean): void {\n  if (typeof window === \"undefined\") return;\n\n  const url = applyFarmBasePath(href, basePath);\n  const spaRouter = getInstalledFarmSPARouter();\n  if (spaRouter) {\n    void spaRouter.navigate(url, { replace });\n    return;\n  }\n\n  if (replace) {\n    window.history.replaceState(null, \"\", url);\n  } else {\n    window.history.pushState(null, \"\", url);\n  }\n  notifyHistoryChange(\"url-search\");\n}\n\nexport function useRouter(options: UseRouterOptions = {}) {\n  const basePath =\n    options.basePath === undefined ? getFarmBasePath() : normalizeFarmBasePath(options.basePath);\n  const routes = options.routes;\n  const routeKey =\n    routes?.map((route) => (typeof route === \"string\" ? route : route.path)).join(\"\\n\") || \"\";\n  const routeMatcher = useMemo(\n    () => (routeKey ? createFarmRouter(routeKey.split(\"\\n\")) : null),\n    [routeKey],\n  );\n  const [state, setState] = useState<RouterState>(() => {\n    return readRouterState(basePath, routeMatcher);\n  });\n\n  useEffect(() => {\n    if (typeof window === \"undefined\") return;\n\n    const handlePopState = () => {\n      setState(readRouterState(basePath, routeMatcher));\n    };\n\n    handlePopState();\n    const unsubscribe = subscribeHistoryChange(handlePopState);\n    return unsubscribe;\n  }, [basePath, routeMatcher]);\n\n  const push = (href: string) => {\n    navigateViaRouter(href, basePath, false);\n  };\n\n  const replace = (href: string) => {\n    navigateViaRouter(href, basePath, true);\n  };\n\n  const pushState = <TState>(pageState: TState, href?: string) => {\n    if (typeof window === \"undefined\") return;\n    getSPARouter().pushState(pageState, resolveClientHref(href, basePath));\n  };\n\n  const replaceState = <TState>(pageState: TState, href?: string) => {\n    if (typeof window === \"undefined\") return;\n    getSPARouter().replaceState(pageState, resolveClientHref(href, basePath));\n  };\n\n  const back = () => {\n    if (typeof window !== \"undefined\") {\n      window.history.back();\n    }\n  };\n\n  const forward = () => {\n    if (typeof window !== \"undefined\") {\n      window.history.forward();\n    }\n  };\n\n  return {\n    ...state,\n    push,\n    replace,\n    pushState,\n    replaceState,\n    back,\n    forward,\n  };\n}\n\nexport function usePageState<TState = unknown>(): TState | null {\n  const [state, setState] = useState<TState | null>(() => readPageState<TState>());\n\n  useEffect(() => {\n    if (typeof window === \"undefined\") return;\n\n    const handlePopState = () => {\n      setState(readPageState<TState>());\n    };\n\n    handlePopState();\n    return subscribeHistoryChange(handlePopState);\n  }, []);\n\n  return state;\n}\n\nexport function useNavigation(): FarmNavigationState {\n  const [state, setState] = useState<FarmNavigationState>(() =>\n    getSPARouter().getNavigationState(),\n  );\n\n  useEffect(() => {\n    if (typeof window === \"undefined\") return;\n    return getSPARouter().subscribeNavigation(setState);\n  }, []);\n\n  return state;\n}\n\nexport function useBlocker(options: UseBlockerOptions): UseBlockerReturn {\n  const optionsRef = useRef(options);\n  optionsRef.current = options;\n  const active = Boolean(options.when);\n\n  useEffect(() => {\n    if (typeof window === \"undefined\" || !active) {\n      return;\n    }\n\n    return getSPARouter().addBlocker(\n      async (context) => {\n        const current = optionsRef.current;\n        const when = typeof current.when === \"function\" ? current.when(context) : current.when;\n\n        if (!when) return false;\n\n        if (current.shouldBlock && !(await current.shouldBlock(context))) {\n          return false;\n        }\n\n        if (current.message && typeof window.confirm === \"function\") {\n          return !window.confirm(current.message);\n        }\n\n        return true;\n      },\n      () => shouldBlockUnload(optionsRef.current),\n    );\n  }, [active]);\n\n  return { active };\n}\n\nexport function useScrollRestoration<TElement extends HTMLElement = HTMLElement>(\n  key: string,\n): RefObject<TElement | null> {\n  const ref = useRef<TElement>(null);\n\n  useEffect(() => {\n    const element = ref.current;\n    if (!element) return;\n    return getSPARouter().registerScrollElement(key, element);\n  }, [key]);\n\n  return ref;\n}\n\nfunction readRouterState(basePath: string, routeMatcher: FarmRouter | null): RouterState {\n  const url = readCurrentUrl();\n  if (!url) {\n    return {\n      pathname: \"/\",\n      searchParams: new URLSearchParams(),\n      params: {},\n      pageState: null,\n    };\n  }\n\n  const pathname = normalizeClientPathname(url.pathname, basePath);\n  const i18n = getFarmI18nClientState();\n  const routePathname = i18n ? stripFarmLocaleFromPathname(pathname, i18n) : pathname;\n  return {\n    pathname,\n    searchParams: url.searchParams,\n    params: routeMatcher?.match(routePathname)?.params || {},\n    pageState: typeof window === \"undefined\" ? null : readPageState(),\n  };\n}\n\n/**\n * The URL being rendered, on either side of hydration.\n *\n * On the server this came back as `/` with no search params, so a component\n * reading the path or the query string rendered one thing on the server and\n * another in the browser, which React reports as a hydration mismatch and\n * leaves the route without useful server rendered markup.\n */\nfunction readCurrentUrl(): URL | undefined {\n  if (typeof window !== \"undefined\") {\n    try {\n      return new URL(window.location.href);\n    } catch {\n      return undefined;\n    }\n  }\n\n  const request = _resolveCurrentRequest();\n  if (!request) return undefined;\n  try {\n    return new URL(request.url);\n  } catch {\n    return undefined;\n  }\n}\n\nfunction normalizeClientPathname(pathname: string, basePath: string) {\n  return stripFarmBasePath(pathname, basePath);\n}\n\nfunction resolveClientHref(href: string | undefined, basePath: string): string | undefined {\n  if (!href) return undefined;\n  return applyFarmBasePath(href, basePath);\n}\n"],"mappings":"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","names":[]}