@sigx/lynx-navigation 0.4.0 → 0.4.2

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 (187) hide show
  1. package/dist/components/Drawer.js +74 -0
  2. package/dist/components/Drawer.js.map +1 -0
  3. package/dist/components/EdgeBackHandle.js +144 -0
  4. package/dist/components/EdgeBackHandle.js.map +1 -0
  5. package/dist/components/EntryScope.d.ts +1 -1
  6. package/dist/components/EntryScope.d.ts.map +1 -1
  7. package/dist/components/EntryScope.js +39 -0
  8. package/dist/components/EntryScope.js.map +1 -0
  9. package/dist/components/Header.js +103 -0
  10. package/dist/components/Header.js.map +1 -0
  11. package/dist/components/Layer.d.ts +2 -2
  12. package/dist/components/Layer.d.ts.map +1 -1
  13. package/dist/components/Layer.js +66 -0
  14. package/dist/components/Layer.js.map +1 -0
  15. package/dist/components/Link.d.ts +2 -2
  16. package/dist/components/Link.d.ts.map +1 -1
  17. package/dist/components/Link.js +51 -0
  18. package/dist/components/Link.js.map +1 -0
  19. package/dist/components/NavigationRoot.d.ts +2 -2
  20. package/dist/components/NavigationRoot.d.ts.map +1 -1
  21. package/dist/components/NavigationRoot.js +67 -0
  22. package/dist/components/NavigationRoot.js.map +1 -0
  23. package/dist/components/Screen.js +98 -0
  24. package/dist/components/Screen.js.map +1 -0
  25. package/dist/components/Stack.js +257 -0
  26. package/dist/components/Stack.js.map +1 -0
  27. package/dist/components/TabBar.d.ts +1 -1
  28. package/dist/components/TabBar.d.ts.map +1 -1
  29. package/dist/components/TabBar.js +63 -0
  30. package/dist/components/TabBar.js.map +1 -0
  31. package/dist/components/Tabs.d.ts +8 -3
  32. package/dist/components/Tabs.d.ts.map +1 -1
  33. package/dist/components/Tabs.js +168 -0
  34. package/dist/components/Tabs.js.map +1 -0
  35. package/dist/define-routes.d.ts +1 -1
  36. package/dist/define-routes.d.ts.map +1 -1
  37. package/{src/define-routes.d.ts → dist/define-routes.js} +4 -2
  38. package/dist/define-routes.js.map +1 -0
  39. package/dist/hooks/use-focus.js +87 -0
  40. package/dist/hooks/use-focus.js.map +1 -0
  41. package/dist/hooks/use-hardware-back.js +84 -0
  42. package/dist/hooks/use-hardware-back.js.map +1 -0
  43. package/dist/hooks/use-linking-nav.d.ts +3 -3
  44. package/dist/hooks/use-linking-nav.d.ts.map +1 -1
  45. package/dist/hooks/use-linking-nav.js +109 -0
  46. package/dist/hooks/use-linking-nav.js.map +1 -0
  47. package/dist/hooks/use-nav-internal.d.ts +2 -2
  48. package/dist/hooks/use-nav-internal.d.ts.map +1 -1
  49. package/dist/hooks/use-nav-internal.js +55 -0
  50. package/dist/hooks/use-nav-internal.js.map +1 -0
  51. package/dist/hooks/use-nav-serializer.d.ts +1 -1
  52. package/dist/hooks/use-nav-serializer.d.ts.map +1 -1
  53. package/dist/hooks/use-nav-serializer.js +181 -0
  54. package/dist/hooks/use-nav-serializer.js.map +1 -0
  55. package/dist/hooks/use-nav.d.ts +2 -2
  56. package/dist/hooks/use-nav.d.ts.map +1 -1
  57. package/dist/hooks/use-nav.js +11 -0
  58. package/dist/hooks/use-nav.js.map +1 -0
  59. package/dist/hooks/use-params.d.ts +1 -1
  60. package/dist/hooks/use-params.d.ts.map +1 -1
  61. package/{src/hooks/use-params.d.ts → dist/hooks/use-params.js} +6 -2
  62. package/dist/hooks/use-params.js.map +1 -0
  63. package/dist/hooks/use-screen-chrome.d.ts +1 -1
  64. package/dist/hooks/use-screen-chrome.d.ts.map +1 -1
  65. package/dist/hooks/use-screen-chrome.js +102 -0
  66. package/dist/hooks/use-screen-chrome.js.map +1 -0
  67. package/dist/hooks/use-screen-options.d.ts +1 -1
  68. package/dist/hooks/use-screen-options.d.ts.map +1 -1
  69. package/dist/hooks/use-screen-options.js +43 -0
  70. package/dist/hooks/use-screen-options.js.map +1 -0
  71. package/dist/hooks/use-search.d.ts +1 -1
  72. package/dist/hooks/use-search.d.ts.map +1 -1
  73. package/{src/hooks/use-search.d.ts → dist/hooks/use-search.js} +6 -2
  74. package/dist/hooks/use-search.js.map +1 -0
  75. package/dist/href.d.ts +2 -2
  76. package/dist/href.d.ts.map +1 -1
  77. package/dist/href.js +57 -0
  78. package/dist/href.js.map +1 -0
  79. package/dist/index.d.ts +33 -33
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +30 -1160
  82. package/dist/index.js.map +1 -1
  83. package/dist/internal/layer-plan.d.ts +1 -1
  84. package/dist/internal/layer-plan.d.ts.map +1 -1
  85. package/dist/internal/layer-plan.js +102 -0
  86. package/dist/internal/layer-plan.js.map +1 -0
  87. package/dist/internal/screen-registry.d.ts +1 -1
  88. package/dist/internal/screen-registry.d.ts.map +1 -1
  89. package/{src/internal/screen-registry.d.ts → dist/internal/screen-registry.js} +32 -21
  90. package/dist/internal/screen-registry.js.map +1 -0
  91. package/{src/internal/screen-width.d.ts → dist/internal/screen-width.js} +17 -2
  92. package/dist/internal/screen-width.js.map +1 -0
  93. package/dist/navigator/core.d.ts +3 -3
  94. package/dist/navigator/core.d.ts.map +1 -1
  95. package/dist/navigator/core.js +394 -0
  96. package/dist/navigator/core.js.map +1 -0
  97. package/dist/register.d.ts +1 -1
  98. package/dist/register.d.ts.map +1 -1
  99. package/dist/register.js +2 -0
  100. package/dist/register.js.map +1 -0
  101. package/dist/types.js +9 -0
  102. package/dist/types.js.map +1 -0
  103. package/dist/url/build.js +30 -0
  104. package/dist/url/build.js.map +1 -0
  105. package/dist/url/compile.js +83 -0
  106. package/dist/url/compile.js.map +1 -0
  107. package/dist/url/format.js +102 -0
  108. package/dist/url/format.js.map +1 -0
  109. package/dist/url/index.d.ts +6 -6
  110. package/dist/url/index.d.ts.map +1 -1
  111. package/dist/url/index.js +13 -0
  112. package/dist/url/index.js.map +1 -0
  113. package/dist/url/parse.d.ts +1 -1
  114. package/dist/url/parse.d.ts.map +1 -1
  115. package/dist/url/parse.js +94 -0
  116. package/dist/url/parse.js.map +1 -0
  117. package/dist/url/registry.d.ts +2 -2
  118. package/dist/url/registry.d.ts.map +1 -1
  119. package/{src/url/registry.d.ts → dist/url/registry.js} +28 -12
  120. package/dist/url/registry.js.map +1 -0
  121. package/dist/url/validate.d.ts +1 -1
  122. package/dist/url/validate.d.ts.map +1 -1
  123. package/dist/url/validate.js +37 -0
  124. package/dist/url/validate.js.map +1 -0
  125. package/package.json +16 -13
  126. package/src/components/EdgeBackHandle.tsx +2 -2
  127. package/src/components/EntryScope.tsx +3 -3
  128. package/src/components/Header.tsx +3 -3
  129. package/src/components/Layer.tsx +3 -3
  130. package/src/components/Link.tsx +4 -4
  131. package/src/components/NavigationRoot.tsx +6 -6
  132. package/src/components/Screen.tsx +3 -3
  133. package/src/components/Stack.tsx +8 -8
  134. package/src/components/TabBar.tsx +1 -1
  135. package/src/components/Tabs.tsx +8 -3
  136. package/src/define-routes.ts +1 -1
  137. package/src/hooks/use-focus.ts +2 -2
  138. package/src/hooks/use-hardware-back.ts +1 -1
  139. package/src/hooks/use-linking-nav.ts +4 -4
  140. package/src/hooks/use-nav-internal.ts +2 -2
  141. package/src/hooks/use-nav-serializer.ts +3 -3
  142. package/src/hooks/use-nav.ts +2 -2
  143. package/src/hooks/use-params.ts +2 -2
  144. package/src/hooks/use-screen-chrome.ts +3 -3
  145. package/src/hooks/use-screen-options.ts +3 -3
  146. package/src/hooks/use-search.ts +2 -2
  147. package/src/href.ts +6 -6
  148. package/src/index.ts +33 -33
  149. package/src/internal/layer-plan.ts +2 -2
  150. package/src/internal/screen-registry.ts +1 -1
  151. package/src/navigator/core.ts +3 -3
  152. package/src/register.ts +1 -1
  153. package/src/url/build.ts +2 -2
  154. package/src/url/index.ts +6 -6
  155. package/src/url/parse.ts +6 -6
  156. package/src/url/registry.ts +3 -3
  157. package/src/url/validate.ts +1 -1
  158. package/src/components/Drawer.d.ts +0 -55
  159. package/src/components/EdgeBackHandle.d.ts +0 -1
  160. package/src/components/EntryScope.d.ts +0 -25
  161. package/src/components/Header.d.ts +0 -6
  162. package/src/components/Layer.d.ts +0 -33
  163. package/src/components/Link.d.ts +0 -60
  164. package/src/components/NavigationRoot.d.ts +0 -36
  165. package/src/components/Screen.d.ts +0 -97
  166. package/src/components/Stack.d.ts +0 -90
  167. package/src/components/TabBar.d.ts +0 -38
  168. package/src/components/Tabs.d.ts +0 -109
  169. package/src/hooks/use-focus.d.ts +0 -45
  170. package/src/hooks/use-hardware-back.d.ts +0 -37
  171. package/src/hooks/use-linking-nav.d.ts +0 -91
  172. package/src/hooks/use-nav-internal.d.ts +0 -91
  173. package/src/hooks/use-nav-serializer.d.ts +0 -82
  174. package/src/hooks/use-nav.d.ts +0 -111
  175. package/src/hooks/use-screen-chrome.d.ts +0 -18
  176. package/src/hooks/use-screen-options.d.ts +0 -2
  177. package/src/href.d.ts +0 -54
  178. package/src/index.d.ts +0 -39
  179. package/src/internal/layer-plan.d.ts +0 -68
  180. package/src/navigator/core.d.ts +0 -96
  181. package/src/register.d.ts +0 -37
  182. package/src/types.d.ts +0 -217
  183. package/src/url/build.d.ts +0 -15
  184. package/src/url/compile.d.ts +0 -34
  185. package/src/url/format.d.ts +0 -28
  186. package/src/url/parse.d.ts +0 -20
  187. package/src/url/validate.d.ts +0 -23
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/define-routes.ts","../src/hooks/use-nav.ts","../src/hooks/use-params.ts","../src/hooks/use-search.ts","../src/hooks/use-hardware-back.ts","../src/url/format.ts","../src/url/compile.ts","../src/url/registry.ts","../src/url/build.ts","../src/url/validate.ts","../src/url/parse.ts","../src/href.ts","../src/hooks/use-nav-internal.ts","../src/hooks/use-linking-nav.ts","../src/hooks/use-focus.ts","../src/internal/screen-registry.ts","../src/hooks/use-screen-options.ts","../src/hooks/use-screen-chrome.ts","../src/hooks/use-nav-serializer.ts","../src/navigator/core.ts","../src/components/NavigationRoot.tsx","../src/internal/screen-width.ts","../src/internal/layer-plan.ts","../src/components/EdgeBackHandle.tsx","../src/components/EntryScope.tsx","../src/components/Layer.tsx","../src/components/Tabs.tsx","../src/components/Stack.tsx","../src/components/Screen.tsx","../src/components/Header.tsx","../src/components/TabBar.tsx","../src/components/Drawer.tsx","../src/components/Link.tsx"],"sourcesContent":["import type { RouteMap } from './types';\n\n/**\n * Define a typed route registry.\n *\n * Returns the input verbatim at runtime — the function exists for TypeScript\n * inference. The returned type is narrowed to the literal route map so that\n * downstream APIs (`useNav`, `useParams`, `<Link>`) can extract route names\n * and params/search schemas precisely.\n *\n * @example\n * ```ts\n * import { defineRoutes } from '@sigx/lynx-navigation';\n * import { z } from 'zod';\n *\n * export const routes = defineRoutes({\n * home: { component: lazy(() => import('./Home')) },\n * profile: {\n * params: z.object({ id: z.string() }),\n * component: lazy(() => import('./Profile')),\n * path: '/users/:id',\n * },\n * });\n *\n * // Then in app entry:\n * declare module '@sigx/lynx-navigation' {\n * interface Register { routes: typeof routes }\n * }\n * ```\n */\nexport function defineRoutes<const T extends RouteMap>(routes: T): T {\n return routes;\n}\n","import { defineInjectable } from '@sigx/lynx';\nimport type { RegisteredRoutes, RouteId, RouteParams, RouteSearch } from '../register';\nimport type {\n PopOptions,\n PushOptions,\n RouteRequiresParams,\n StackEntry,\n TransitionState,\n} from '../types';\n\n/**\n * Subset of registered route names that declare a `params` schema (and so\n * require a `params` argument when navigating).\n *\n * Computed via mapped-type filtering rather than a conditional inside the\n * method signature: when a conditional like `RouteRequiresParams<R[K]>` is\n * embedded inside a generic method parameter, TS evaluates it at definition\n * time with K bound to the *whole* union of route ids — which distributes\n * `RouteRequiresParams` over every route and collapses the result to\n * `boolean`, breaking the conditional. Filtering once at the type level avoids\n * the issue and produces clean overload candidates.\n */\nexport type RoutesWithParams = {\n [K in RouteId]: RouteRequiresParams<RegisteredRoutes[K]> extends true ? K : never;\n}[RouteId];\n\n/** Routes that don't declare a `params` schema. */\nexport type RoutesWithoutParams = Exclude<RouteId, RoutesWithParams>;\n\n/**\n * The navigator handle returned by `useNav()`.\n *\n * Read access (`current`, `stack`, `canGoBack`) is reactive — these properties\n * are getters that read from the underlying stack signal, so accessing them\n * inside a component's render function (or inside `effect` / `computed`) takes\n * a reactive dependency. Mutating methods (`push`, `pop`, etc.) trigger the\n * dependents to update.\n *\n * `push` and `replace` are split into two overloads — one for routes without a\n * params schema (no params arg) and one for routes with a params schema\n * (params required). See `RoutesWithParams` above for why this isn't a single\n * conditional return type.\n */\nexport interface Nav {\n /** Push a route that has no params schema. */\n push<K extends RoutesWithoutParams>(\n name: K,\n search?: RouteSearch<K>,\n options?: PushOptions,\n ): void;\n /** Push a route that requires params. */\n push<K extends RoutesWithParams>(\n name: K,\n params: RouteParams<K>,\n search?: RouteSearch<K>,\n options?: PushOptions,\n ): void;\n\n /** Replace the top entry — same overload pattern as push. */\n replace<K extends RoutesWithoutParams>(\n name: K,\n search?: RouteSearch<K>,\n options?: PushOptions,\n ): void;\n replace<K extends RoutesWithParams>(\n name: K,\n params: RouteParams<K>,\n search?: RouteSearch<K>,\n options?: PushOptions,\n ): void;\n\n /** Pop one or more entries off the top of the stack. */\n pop(count?: number, options?: PopOptions): void;\n\n /** Pop entries until the named route is at the top. */\n popTo<K extends RouteId>(name: K): void;\n\n /** Pop all the way to the root entry. */\n popToRoot(): void;\n\n /** Wholesale-replace the stack. */\n reset(state: { stack: ReadonlyArray<StackEntry> }): void;\n\n /** Dismiss the topmost modal stack (no-op if none active). */\n dismiss(): void;\n\n /** Currently-focused entry. Reactive via property access. */\n readonly current: StackEntry;\n\n /** Full stack, top last. Reactive. */\n readonly stack: ReadonlyArray<StackEntry>;\n\n /** Whether the user can go back from the current entry. Reactive. */\n readonly canGoBack: boolean;\n\n /**\n * Parent navigator (e.g. the root nav above a per-tab `<Stack>`), or null\n * at the root. Set when a `<Stack>` mints its own navigator via\n * `<Stack initialRoute=\"…\">` — that stack's `useNav()` returns a nav\n * whose `parent` is the enclosing nav.\n *\n * `push` calls for routes whose resolved presentation is non-`card`\n * (`modal` / `fullScreen` / `transparent-modal`) escalate up the\n * `parent` chain automatically — you don't normally need to reach\n * through `parent` to present modals. `parent` is exposed as an escape\n * hatch for power users (e.g. imperative `parent.pop()` from a child\n * stack). Avoid pushing card routes onto `parent` directly — that\n * defeats per-tab stack isolation.\n */\n readonly parent: Nav | null;\n\n /**\n * Whether this navigator is part of the currently-focused chain. True\n * for the root nav at all times; for a nested nav (e.g. a per-tab\n * stack), true only when its host entry is the top of `parent`, the\n * parent itself is locally focused, and any extra gate (e.g. the\n * enclosing tab is active) reports active.\n *\n * Reactive. `useIsFocused()` ANDs `nav.current.key === myKey` with\n * `nav.isLocallyFocused`.\n */\n readonly isLocallyFocused: boolean;\n\n /**\n * @internal\n * Set of child navigators (per-tab `<Stack>` instances) that have\n * registered themselves under this nav. Used by `useHardwareBack` to\n * find the deepest currently-focused nav and route the back press\n * there before falling back up the chain.\n *\n * Not part of the public API — leading-underscore marks it as\n * implementation detail.\n */\n readonly _children: Set<Nav>;\n\n /**\n * In-flight transition, or null when navigation is at rest. Reactive —\n * `<Stack>` reads this to decide whether to render one screen or two\n * (during a slide transition both the outgoing and incoming screens\n * stay mounted with `useAnimatedStyle`-driven transforms).\n */\n readonly transition: TransitionState | null;\n}\n\n/**\n * Access the innermost navigator. Provided by `<NavigationRoot>` via\n * `defineProvide`. Throws when called outside a NavigationRoot subtree.\n *\n * Mirrors `@sigx/router`'s `useRouter` pattern (`packages/router/src/router.ts:30`).\n */\nexport const useNav = defineInjectable<Nav>(() => {\n throw new Error(\n '[lynx-navigation] useNav() called but no <NavigationRoot> is mounted in the component tree.',\n );\n});\n","import type { RouteId, RouteParams } from '../register';\nimport { useNav } from './use-nav';\n\n/**\n * Read the typed params for the current screen, asserted against the named\n * route from the registry.\n *\n * Returns the current entry's params snapshot. The `name` arg is the type\n * discriminator at compile time; we don't currently runtime-check that the\n * caller's route matches the active entry — the dev-mode warning lands in a\n * later slice along with schema validation.\n *\n * **Reactivity**: each `nav.push` / `replace` produces a new entry with a\n * fresh `key`. `<Stack>` keys the rendered component on `entry.key`, so the\n * screen component fully remounts on every navigation — useParams runs again\n * during the new mount and reads the new params. There is no \"in-place params\n * update for the same mounted screen\" path in v0.1, so a snapshot at setup\n * time is correct.\n */\nexport function useParams<K extends RouteId>(_name: K): RouteParams<K> {\n const nav = useNav();\n return nav.current.params as RouteParams<K>;\n}\n","import type { RouteId, RouteSearch } from '../register';\nimport { useNav } from './use-nav';\n\n/**\n * Read the typed search/query params for the current screen, asserted against\n * the named route from the registry.\n *\n * Returns the current entry's search snapshot. See `useParams` for the\n * reactivity story — each navigation triggers a remount via the entry-keyed\n * Stack, so a setup-time snapshot is sufficient.\n */\nexport function useSearch<K extends RouteId>(_name: K): RouteSearch<K> {\n const nav = useNav();\n return nav.current.search as RouteSearch<K>;\n}\n","import { onMounted } from '@sigx/lynx';\nimport { BackHandler } from '@sigx/lynx-linking';\nimport { useNav, type Nav } from './use-nav';\n\n/**\n * Wire the Android hardware back button to the active navigator.\n *\n * Listens for `hardwareBackPress` events from `@sigx/lynx-linking`'s\n * `BackHandler` (which the native side dispatches from\n * `MainActivity.onBackPressed`). On press the handler walks to the\n * deepest currently-focused navigator (per-tab `<Stack>`s register with\n * their parent), then walks back up the `parent` chain looking for the\n * first nav that `canGoBack`:\n *\n * - If any nav in the chain can go back → `nav.pop()` on that nav.\n * - Otherwise → `BackHandler.exitApp()` (Android: `moveTaskToBack(true)`,\n * keeps the bundle warm; iOS: rejects, since iOS doesn't permit\n * programmatic termination).\n *\n * The traversal means you only need to call this once at the root — a\n * back press from inside a tab pops that tab's nested stack first, only\n * exiting the app once every level is at its base entry.\n *\n * Call this once in any component under `<NavigationRoot>` (typically a\n * thin wrapper sibling to `<Stack />`). iOS doesn't fire the event so the\n * hook is a no-op there.\n *\n * @example\n * ```tsx\n * const BackHandlerWiring = component(() => {\n * useHardwareBack();\n * return () => null;\n * });\n *\n * <NavigationRoot routes={routes}>\n * <BackHandlerWiring />\n * <Stack />\n * </NavigationRoot>\n * ```\n */\nexport function useHardwareBack(): void {\n const nav = useNav();\n onMounted(() => {\n const sub = BackHandler.addEventListener(() => {\n // Walk down to the deepest focused nav. Per-tab `<Stack>`s\n // register themselves via `parent._children.add(nav)`; only one\n // child per level is `isLocallyFocused` at a time, so the\n // traversal is unambiguous. Falls back to the starting nav if\n // no nested stacks are wired up.\n let active: Nav = nav;\n // Loop instead of recursion so a deeply-nested tree doesn't blow\n // the stack on a synchronous back press.\n outer: while (active._children.size > 0) {\n for (const child of active._children) {\n if (child.isLocallyFocused) {\n active = child;\n continue outer;\n }\n }\n // No focused child at this level — stop drilling.\n break;\n }\n // Walk back up the chain looking for the first nav that has\n // something to pop. This is what makes \"back press in trips\n // tab with empty inner stack\" fall through to root (which might\n // have a modal on top) before exiting.\n let cur: Nav | null = active;\n while (cur) {\n if (cur.canGoBack) {\n cur.pop();\n return true;\n }\n cur = cur.parent;\n }\n // At the root with nothing to pop — leave the app. Promise is\n // fire-and-forget; we don't await because we want the back\n // press to feel instant (Android starts the move-to-back\n // transition immediately).\n void BackHandler.exitApp();\n return true;\n });\n return () => sub.remove();\n });\n}\n","/**\n * Format helpers: typed params/search → URL string.\n *\n * Used by `hrefFor()` to render the `url` field of an Href.\n */\n\n/**\n * Serialize an object as a `key=value&key=value` querystring.\n *\n * Keys are sorted to make the output deterministic (useful for tests and\n * persistence diffs). `undefined`/`null` values are skipped. Non-primitive\n * values are JSON-stringified on the way out — `parseSearch` returns the\n * raw string and leaves any JSON decoding to the route's `search` schema\n * (e.g. a Zod `transform`), so the round-trip is intentionally one-way at\n * this layer.\n *\n * Returns `''` (empty string) when there are no entries — callers join with\n * `?` only when the result is non-empty.\n */\nexport function formatSearch(search: Record<string, unknown> | undefined): string {\n if (!search) return '';\n const keys = Object.keys(search).sort();\n const parts: string[] = [];\n for (const key of keys) {\n const value = search[key];\n if (value === undefined || value === null) continue;\n const encoded = encodeURIComponent(key);\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item === undefined || item === null) continue;\n parts.push(`${encoded}=${encodeURIComponent(serializeScalar(item))}`);\n }\n } else {\n parts.push(`${encoded}=${encodeURIComponent(serializeScalar(value))}`);\n }\n }\n return parts.join('&');\n}\n\n/**\n * Parse a `key=value&key=value` querystring into a string-keyed bag. Values\n * are decoded but kept as strings — typed coercion happens in the route's\n * `search` schema (e.g. Zod's `z.coerce.number()`).\n *\n * Multiple occurrences of the same key produce an array. Schemas that don't\n * expect arrays will reject this — that's the right failure mode.\n */\nexport function parseSearch(query: string): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n if (!query) return result;\n // Strip leading `?` if present (formatHref doesn't include it but callers\n // sometimes pass `?a=1`).\n const cleaned = query.startsWith('?') ? query.slice(1) : query;\n if (!cleaned) return result;\n for (const pair of cleaned.split('&')) {\n if (!pair) continue;\n const eqIdx = pair.indexOf('=');\n const rawKey = eqIdx === -1 ? pair : pair.slice(0, eqIdx);\n const rawValue = eqIdx === -1 ? '' : pair.slice(eqIdx + 1);\n const key = safeDecode(rawKey);\n const value = safeDecode(rawValue);\n const existing = result[key];\n if (existing === undefined) {\n result[key] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n }\n return result;\n}\n\nfunction serializeScalar(v: unknown): string {\n switch (typeof v) {\n case 'string': return v;\n case 'number':\n case 'boolean':\n case 'bigint':\n return String(v);\n default:\n // Objects/arrays/etc — JSON. Schemas can decide how to interpret.\n return JSON.stringify(v);\n }\n}\n\nfunction safeDecode(s: string): string {\n try {\n return decodeURIComponent(s.replace(/\\+/g, ' '));\n } catch {\n // Malformed % escape — fall back to the raw text rather than throwing\n // from a navigation hot path. The schema will reject if it matters.\n return s;\n }\n}\n","/**\n * Path template compiler.\n *\n * Turns a route's `path` (e.g. `/users/:id/posts/:postId`) into a compiled\n * object that can both match a URL pathname against it and format a typed\n * params object back into a URL.\n *\n * Supported syntax (intentionally minimal for v1):\n * - Literal segments: `/users`, `/users/me`\n * - Named params: `:id` (matches `[^/]+`)\n * - Trailing slashes tolerated on match\n *\n * Out of scope for v1 (future-compatible — additions won't break v1 paths):\n * - Wildcards `*`\n * - Optional params `:id?`\n * - Typed/constrained params `:id<number>` or `:id(\\\\d+)`\n */\n\n/** Result of compiling a path template — used by parse + format. */\nexport interface CompiledPath {\n readonly source: string;\n readonly paramNames: readonly string[];\n /** Regex that matches a URL pathname. Captures are param values in order. */\n readonly regex: RegExp;\n /**\n * Render a URL pathname for this template given param values. Each value\n * is `encodeURIComponent`-encoded. Throws if a required `:name` is missing.\n */\n format(params: Record<string, string | number>): string;\n}\n\nconst PARAM_RE = /:([A-Za-z_][A-Za-z0-9_]*)/g;\n\n/**\n * Compile a path template. Throws on malformed input (duplicate param names,\n * unexpected `:` syntax). Pure — safe to memoize.\n */\nexport function compilePath(template: string): CompiledPath {\n if (typeof template !== 'string') {\n throw new TypeError(`compilePath: expected string, got ${typeof template}`);\n }\n if (template.length === 0) {\n throw new Error('compilePath: path template must not be empty');\n }\n // Normalize: ensure leading `/`. Trailing slashes are tolerated on match,\n // but the canonical formatted output preserves the template's trailing\n // slash policy.\n const normalized = template.startsWith('/') ? template : `/${template}`;\n\n const paramNames: string[] = [];\n // Build the regex by replacing :name with a capture group. We escape the\n // surrounding literal text so paths with regex-special chars (`.`, `+`)\n // match literally — only `:name` is treated as a placeholder.\n let lastIndex = 0;\n let pattern = '';\n PARAM_RE.lastIndex = 0;\n for (let m = PARAM_RE.exec(normalized); m !== null; m = PARAM_RE.exec(normalized)) {\n const name = m[1];\n if (paramNames.includes(name)) {\n throw new Error(\n `compilePath: duplicate param name ':${name}' in '${template}'`,\n );\n }\n paramNames.push(name);\n pattern += escapeRegex(normalized.slice(lastIndex, m.index));\n pattern += '([^/]+)';\n lastIndex = m.index + m[0].length;\n }\n pattern += escapeRegex(normalized.slice(lastIndex));\n\n // Trim a trailing slash from the pattern so `/users/` and `/users` both\n // match `/users/:?`. We keep the formatter's output as-templated.\n const matchPattern = pattern.endsWith('/') ? pattern.slice(0, -1) : pattern;\n const regex = new RegExp(`^${matchPattern}/?$`);\n\n return {\n source: template,\n paramNames,\n regex,\n format(params: Record<string, string | number>): string {\n let out = '';\n let i = 0;\n PARAM_RE.lastIndex = 0;\n for (\n let m = PARAM_RE.exec(normalized);\n m !== null;\n m = PARAM_RE.exec(normalized)\n ) {\n const name = m[1];\n const value = params[name];\n if (value === undefined || value === null) {\n throw new Error(\n `compilePath.format: missing required param ':${name}' for '${template}'`,\n );\n }\n out += normalized.slice(i, m.index);\n out += encodeURIComponent(String(value));\n i = m.index + m[0].length;\n }\n out += normalized.slice(i);\n return out;\n },\n };\n}\n\nconst REGEX_SPECIALS = /[.*+?^${}()|[\\]\\\\]/g;\nfunction escapeRegex(s: string): string {\n return s.replace(REGEX_SPECIALS, '\\\\$&');\n}\n","/**\n * Module-level route registry for the URL bridge.\n *\n * `hrefFor()` and `parseHref()` are designed to be callable from anywhere —\n * including outside the component tree (e.g. deep-link bootstrapping in\n * native bridge code). They can't reach the in-tree `useNavRoutes` injectable\n * directly, so we mirror the active routes here.\n *\n * `<NavigationRoot>` sets this synchronously during setup. Tests that exercise\n * the URL helpers without a NavigationRoot can call `_setRouteRegistry`\n * directly. The leading underscore is a convention: not part of the supported\n * public API (test/integration use only).\n */\n\nimport type { CompiledPath } from './compile';\nimport { compilePath } from './compile';\nimport type { RouteMap } from '../types';\n\ninterface RegistryState {\n readonly routes: RouteMap;\n /** Lazy-compiled paths keyed by route name. */\n readonly compiled: Map<string, CompiledPath>;\n}\n\nlet current: RegistryState | null = null;\n\n/**\n * Set the active route registry. Called by `<NavigationRoot>` on setup and\n * available to tests/bootstrap code as `_setRouteRegistry`.\n *\n * Last write wins — multi-root apps and rapid mount/unmount cycles in tests\n * always see the most recent `<NavigationRoot>`'s routes. If you need a\n * specific registry for a one-off call, pass it explicitly to the helper\n * (parseHrefWithRoutes / hrefForWithRoutes — currently internal).\n */\nexport function _setRouteRegistry(routes: RouteMap): void {\n current = { routes, compiled: new Map() };\n}\n\n/** Clear the registry. Mainly for tests that want to assert the unset path. */\nexport function _clearRouteRegistry(): void {\n current = null;\n}\n\n/** Get the active registry or throw a friendly error if none is set. */\nexport function getRouteRegistry(): RegistryState {\n if (!current) {\n throw new Error(\n '[lynx-navigation] No route registry set — render a <NavigationRoot> first, or call _setRouteRegistry() for tests.',\n );\n }\n return current;\n}\n\n/**\n * Look up (or lazily compile) the path template for a route name. Returns\n * `null` when the route exists but declares no `path`.\n */\nexport function getCompiledPath(registry: RegistryState, name: string): CompiledPath | null {\n const def = registry.routes[name];\n if (!def) return null;\n if (!def.path) return null;\n let compiled = registry.compiled.get(name);\n if (!compiled) {\n compiled = compilePath(def.path);\n registry.compiled.set(name, compiled);\n }\n return compiled;\n}\n","/**\n * Typed → URL: build the `url` field of an Href from a route's params/search.\n *\n * Mirror of parse.ts. Used by `hrefFor()` after schema validation succeeds.\n */\n\nimport { formatSearch } from './format';\nimport { getCompiledPath, getRouteRegistry } from './registry';\n\n/**\n * Build the URL form of a route + params + search, or `null` if the route\n * declares no `path` template (typed navigation still works — only deep-link\n * serialization is unavailable).\n *\n * Params must already be valid for the route's schema (callers run this after\n * `validateSync`). Search values are stringified as-is — schema-validated\n * inputs survive round-tripping because `parseHref` re-runs the same schema.\n */\nexport function buildUrl(\n routeName: string,\n params: Record<string, unknown> | undefined,\n search: Record<string, unknown> | undefined,\n): string | null {\n const registry = getRouteRegistry();\n const compiled = getCompiledPath(registry, routeName);\n if (!compiled) return null;\n const formatted = compiled.format(\n // The compiler accepts string|number; we widen to unknown and let it\n // String()-coerce. Booleans/null get filtered by the missing-param\n // check, which is what we want — boolean path params don't exist.\n (params ?? {}) as Record<string, string | number>,\n );\n const querystring = formatSearch(search);\n return querystring.length > 0 ? `${formatted}?${querystring}` : formatted;\n}\n","/**\n * Standard Schema validation helper (sync only).\n *\n * `hrefFor` and `parseHref` run on hot paths (link rendering, deep-link\n * resolution) so we restrict to sync validators. Zod/Valibot/ArkType are all\n * sync, which covers the common case. Async validators throw a clear error.\n */\n\nimport type { StandardSchemaV1 } from '../types';\n\n/**\n * Extended runtime view of a Standard Schema — adds the `validate` function\n * that the spec mandates but `types.ts`'s minimal type omits (for test-fixture\n * ergonomics). Treat any schema-shaped object as potentially carrying it.\n */\ninterface StandardSchemaRuntime {\n readonly '~standard': {\n readonly version: 1;\n readonly vendor: string;\n readonly validate?: (input: unknown) => StandardResult | PromiseLike<StandardResult>;\n };\n}\n\ntype StandardResult =\n | { readonly value: unknown; readonly issues?: undefined }\n | { readonly issues: ReadonlyArray<{ readonly message: string }> };\n\n/** Outcome of a sync validation call — discriminated for explicit handling. */\nexport type ValidateOutcome =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly issues: ReadonlyArray<string> };\n\n/**\n * Run a Standard Schema's `validate` synchronously. When the schema lacks a\n * `validate` function (e.g. our test `fakeSchema`), passthrough — assume the\n * input is already in the correct shape. This is a deliberate ergonomic\n * choice so the type-spike fixtures stay terse.\n */\nexport function validateSync(\n schema: StandardSchemaV1 | undefined,\n input: unknown,\n): ValidateOutcome {\n if (!schema) return { ok: true, value: input };\n const validate = (schema as StandardSchemaRuntime)['~standard']?.validate;\n if (!validate) return { ok: true, value: input };\n const result = validate(input);\n if (isPromiseLike(result)) {\n throw new Error(\n '[lynx-navigation] Async schema validation is not supported on the URL bridge — use a sync validator (Zod/Valibot/ArkType are all sync).',\n );\n }\n if (result.issues !== undefined && result.issues.length > 0) {\n return {\n ok: false,\n issues: result.issues.map((i) => i.message),\n };\n }\n return { ok: true, value: (result as { value: unknown }).value };\n}\n\nfunction isPromiseLike<T>(v: unknown): v is PromiseLike<T> {\n return (\n v !== null\n && typeof v === 'object'\n && typeof (v as { then?: unknown }).then === 'function'\n );\n}\n","/**\n * URL → typed Href parser.\n *\n * Walks every registered route with a `path`, tries to match its compiled\n * regex against the URL's pathname, and on a hit validates the extracted\n * params + search through the route's Standard Schema. First match wins;\n * iteration order follows `Object.keys` of the registered routes map.\n *\n * Validation failures return `null` rather than throwing — deep-link handlers\n * fall back to the initial route on a bad URL instead of crashing the app.\n */\n\nimport type { Href } from '../href';\nimport type { RouteId } from '../register';\nimport { parse as parseUrl } from '@sigx/lynx-linking';\nimport type { CompiledPath } from './compile';\nimport { parseSearch } from './format';\nimport { getCompiledPath, getRouteRegistry } from './registry';\nimport { validateSync } from './validate';\n\n/**\n * Parse a URL string against the active route registry.\n *\n * Accepts both absolute URLs (`myapp://host/users/42?tab=about`) and\n * pathname-only forms (`/users/42?tab=about`). Returns `null` if no route's\n * `path` matches the URL or if schema validation rejects the extracted bits.\n */\nexport function parseHrefImpl(url: string): Href | null {\n if (typeof url !== 'string' || url.length === 0) return null;\n\n // Use lynx-linking's parser for the scheme/host split — but accept paths\n // that don't have a scheme too (raw `/users/42` is the common case from\n // in-app routing).\n const { pathname, query } = splitPathAndQuery(url);\n if (!pathname) return null;\n\n const registry = getRouteRegistry();\n const rawSearch = parseSearch(query);\n\n for (const name of Object.keys(registry.routes)) {\n const compiled = getCompiledPath(registry, name);\n if (!compiled) continue;\n const match = compiled.regex.exec(pathname);\n if (!match) continue;\n\n const rawParams = extractParams(compiled, match);\n const def = registry.routes[name];\n\n const paramsOutcome = validateSync(def.params, rawParams);\n if (!paramsOutcome.ok) continue;\n const searchOutcome = validateSync(def.search, rawSearch);\n if (!searchOutcome.ok) continue;\n\n return {\n route: name as RouteId,\n params: paramsOutcome.value as Record<string, never>,\n search: searchOutcome.value as Record<string, never>,\n url,\n };\n }\n return null;\n}\n\nfunction extractParams(\n compiled: CompiledPath,\n match: RegExpExecArray,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (let i = 0; i < compiled.paramNames.length; i++) {\n const raw = match[i + 1];\n try {\n out[compiled.paramNames[i]] = decodeURIComponent(raw);\n } catch {\n out[compiled.paramNames[i]] = raw;\n }\n }\n return out;\n}\n\nfunction splitPathAndQuery(url: string): { pathname: string; query: string } {\n // Drop the fragment before any path/query work — `#…` is a client-side\n // anchor that must not leak into the route pathname or query values.\n const hashIdx = url.indexOf('#');\n const noHash = hashIdx >= 0 ? url.slice(0, hashIdx) : url;\n\n // If the URL has a scheme, defer to lynx-linking's parser — handles\n // `myapp://host/path?q` correctly. Otherwise treat the whole thing as\n // pathname+query.\n const hasScheme = /^[a-zA-Z][a-zA-Z0-9+.\\-]*:/.test(noHash);\n if (hasScheme) {\n const parsed = parseUrl(noHash);\n // Reconstruct the query string from the already-parsed bag. We have\n // to do this because parseUrl decoded keys/values, but parseSearch\n // expects encoded form. Simpler: split the original ourselves.\n const qIdx = noHash.indexOf('?');\n const query = qIdx >= 0 ? noHash.slice(qIdx + 1) : '';\n return { pathname: parsed.path, query };\n }\n const qIdx = noHash.indexOf('?');\n if (qIdx === -1) return { pathname: noHash, query: '' };\n return { pathname: noHash.slice(0, qIdx), query: noHash.slice(qIdx + 1) };\n}\n","import type { RouteId, RouteParams, RouteSearch } from './register';\nimport type { RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav';\nimport { buildUrl } from './url/build';\nimport { parseHrefImpl } from './url/parse';\nimport { getRouteRegistry } from './url/registry';\nimport { validateSync } from './url/validate';\n\n/**\n * A typed reference to a navigation target — what `<Link to={...}>` consumes\n * and what `hrefFor()` produces.\n *\n * Holds both the typed pieces (route name, params, search) and the serialized\n * URL form (when the route declares a `path` template). Either side can drive\n * navigation — typed for in-app links, URL for deep links / sharing.\n */\nexport interface Href<K extends RouteId = RouteId> {\n readonly route: K;\n readonly params: RouteParams<K>;\n readonly search: RouteSearch<K>;\n /** URL form. `null` when the route declares no `path` template. */\n readonly url: string | null;\n}\n\n/**\n * Build a typed Href for a given route. Validates params against the route's\n * schema at runtime; type-checks them at compile time.\n *\n * Overloaded the same way as `nav.push` — one signature for routes without a\n * params schema, one for routes that require params. See `RoutesWithParams`\n * in `./hooks/use-nav.js` for why this isn't expressed as a single conditional.\n *\n * Requires a `<NavigationRoot>` (or an explicit `_setRouteRegistry` call) to\n * have run — the URL form is built against the active route registry. The\n * typed pieces (`route`, `params`, `search`) are returned regardless; `url`\n * is `null` when the route declares no `path` template.\n *\n * Schema validation errors throw — pass already-validated values from\n * `useParams` / `useSearch` to round-trip safely, or wrap in try/catch when\n * building hrefs from external input.\n *\n * @example\n * ```ts\n * hrefFor('profile', { id: '42' }); // → { route, params, search: {}, url: '/users/42' }\n * hrefFor('profile', { id: '42' }, { tab: 'about' });\n * hrefFor('home'); // params arg omitted (no schema)\n * ```\n */\nexport function hrefFor<K extends RoutesWithoutParams>(name: K, search?: RouteSearch<K>): Href<K>;\nexport function hrefFor<K extends RoutesWithParams>(\n name: K,\n params: RouteParams<K>,\n search?: RouteSearch<K>,\n): Href<K>;\nexport function hrefFor(name: string, ...args: unknown[]): Href {\n const registry = getRouteRegistry();\n const def = registry.routes[name];\n if (!def) {\n throw new Error(\n `[lynx-navigation] hrefFor('${name}'): route is not in the active registry.`,\n );\n }\n\n // Disambiguate the overloads: routes with a params schema get\n // (name, params, search?); routes without get (name, search?). The\n // typed overloads enforce this at compile time, but at runtime we need\n // to peek at the schema presence to know how to interpret `args`.\n const hasParamsSchema = !!def.params;\n let rawParams: Record<string, unknown> | undefined;\n let rawSearch: Record<string, unknown> | undefined;\n if (hasParamsSchema) {\n rawParams = args[0] as Record<string, unknown> | undefined;\n rawSearch = args[1] as Record<string, unknown> | undefined;\n } else {\n rawParams = undefined;\n rawSearch = args[0] as Record<string, unknown> | undefined;\n }\n\n const paramsOutcome = validateSync(def.params, rawParams ?? {});\n if (!paramsOutcome.ok) {\n throw new Error(\n `[lynx-navigation] hrefFor('${name}'): params validation failed — ${paramsOutcome.issues.join('; ')}`,\n );\n }\n const searchOutcome = validateSync(def.search, rawSearch ?? {});\n if (!searchOutcome.ok) {\n throw new Error(\n `[lynx-navigation] hrefFor('${name}'): search validation failed — ${searchOutcome.issues.join('; ')}`,\n );\n }\n\n const params = paramsOutcome.value as Record<string, unknown>;\n const search = searchOutcome.value as Record<string, unknown>;\n const url = buildUrl(name, params, search);\n\n return {\n route: name as RouteId,\n params: params as never,\n search: search as never,\n url,\n };\n}\n\n/**\n * Parse a URL string into a typed Href against the registered routes.\n * Returns `null` if no route's `path` template matches the URL or if the\n * extracted params/search fail the route's schema validation.\n *\n * Accepts both absolute (`myapp://host/path?q`) and pathname-only\n * (`/path?q`) forms — the pathname is matched against each route's\n * compiled template. Iteration order is the registration order; first match\n * wins.\n */\nexport function parseHref(url: string): Href | null {\n return parseHrefImpl(url);\n}\n","import { defineInjectable, type SharedValue } from '@sigx/lynx';\nimport type { ScreenRegistry } from '../internal/screen-registry';\nimport type { RouteMap, StackEntry } from '../types';\n\n/**\n * Internal injectable: the `StackEntry` the calling screen was rendered for.\n *\n * Provided by `<EntryScope>` which `<Stack>` and `<ScreenContainer>` wrap\n * around each screen component mount. Screens use this to derive their own\n * focus state (`useIsFocused`, `useFocusEffect`) without having to track\n * `entry.key` themselves.\n *\n * Default throws so calling `useIsFocused()` outside a screen mounted by a\n * navigator surfaces a clear error rather than silently returning `false`.\n */\nexport const useCurrentEntry = defineInjectable<StackEntry>(() => {\n throw new Error(\n '[lynx-navigation] No screen entry in scope. `useIsFocused` / `useFocusEffect` must be called from a component rendered as a route by <Stack>.',\n );\n});\n\n/**\n * Soft companion to {@link useCurrentEntry} — returns the current scope's\n * entry if any, `null` when called outside an `<EntryScope>` instead of\n * throwing. Provided alongside the strict version by `<EntryScope>`.\n *\n * Used by chrome consumers (`useScreenChrome`) where \"no scoped entry\"\n * is a legitimate state (a Stack chrome slot lives outside the screen's\n * EntryScope) and the caller wants to soft-fallback to the navigator's\n * destination entry rather than crash.\n */\nexport const useCurrentEntryOptional = defineInjectable<StackEntry | null>(\n () => null,\n);\n\n/**\n * Internal injectable: the route registry passed into `<NavigationRoot>`.\n * Components (Stack, Screen) read this to look up route definitions by name.\n *\n * Not exported from the package barrel — use `useNav()` for navigation, and\n * the registry is implicit from `<NavigationRoot routes={...}>`.\n */\nexport const useNavRoutes = defineInjectable<RouteMap>(() => {\n throw new Error(\n '[lynx-navigation] No <NavigationRoot> found in the component tree.',\n );\n});\n\n/**\n * Internal injectable: low-level navigator handles used by the edge-back\n * gesture. Holds the progress SharedValue (so gesture worklets can write it\n * directly on MT) plus BG-side begin/commit/cancel functions invoked via\n * `runOnBackground` from gesture worklets.\n *\n * `progress` is `null` when the navigator was created with `animated={false}`\n * (e.g. tests). `beginBackGesture` is also a no-op in that case.\n */\nexport interface NavInternals {\n /** MT-driven transition progress; null when animations are disabled. */\n readonly progress: SharedValue<number> | null;\n /**\n * Set transition state for a gesture-driven pop. Does not start any\n * automatic animation — the gesture worklet writes `progress` directly\n * per frame, then animates to the commit/cancel endpoint on release.\n */\n beginBackGesture(): void;\n /** Commit the back gesture: pop top entry + clear transition. */\n commitBackGesture(): void;\n /** Cancel the back gesture: clear transition without popping. */\n cancelBackGesture(): void;\n /** Whether the user opted into the edge-swipe-back gesture. */\n readonly edgeSwipeEnabled: boolean;\n /**\n * Cross-entry screen registry controller. `<EntryScope>` calls\n * `register` on mount and `unregister` on unmount. Persistent chrome\n * (HeaderBar / TabBar — later slices) calls `get(entryKey)` to read\n * the focused screen's options + slot fills without remounting itself.\n */\n readonly screens: {\n register(registry: ScreenRegistry): void;\n /**\n * Identity-checked: only removes the entry if `registry` is the\n * one currently registered under its `entry.key`. A no-op when\n * a newer registry has already taken that slot (which happens\n * at the transition→idle handoff, where a fresh `<EntryScope>`\n * for the same entry mounts before the old one's unmount fires).\n */\n unregister(registry: ScreenRegistry): void;\n get(entryKey: string): ScreenRegistry | undefined;\n };\n}\n\nexport const useNavInternals = defineInjectable<NavInternals>(() => {\n throw new Error(\n '[lynx-navigation] No <NavigationRoot> found in the component tree.',\n );\n});\n\n/**\n * Internal injectable: the calling screen's `ScreenRegistry`.\n *\n * Provided by `<EntryScope>` alongside `useCurrentEntry`. The `<Screen>`\n * component and its slot-filling sub-components write options and slot\n * fills here; the navigator's persistent chrome (HeaderBar, TabBar — later\n * slices) reads from this registry via `getScreenRegistry(key)` on the\n * navigator state, which keys into a cross-entry map.\n *\n * Throws when used outside an EntryScope so calling `<Screen>` at the app\n * root surfaces a clear error rather than silently no-op'ing.\n */\nexport const useScreenRegistry = defineInjectable<ScreenRegistry>(() => {\n throw new Error(\n '[lynx-navigation] No screen registry in scope. `<Screen>` (and `<Screen.Header>`, etc.) must be used inside a route component rendered by `<Stack>`.',\n );\n});\n","import { onMounted } from '@sigx/lynx';\nimport { Linking } from '@sigx/lynx-linking';\nimport { parseHref, type Href } from '../href';\nimport { useNav, type Nav } from './use-nav';\nimport { useNavRoutes } from './use-nav-internal';\nimport type { RouteMap } from '../types';\n\nexport interface UseLinkingNavOptions {\n /**\n * Schemes/prefixes to strip before parsing. Matched in order; the first\n * match wins. Example: `['myapp://', 'https://myapp.com']` lets\n * `https://myapp.com/users/42` parse against the same routes as\n * `/users/42`.\n *\n * After stripping, a leading `/` is added if missing so the result is a\n * valid pathname.\n */\n prefixes?: string[];\n\n /**\n * Custom handler invoked instead of the default dispatch. Use this when\n * you need to intercept (e.g. for auth callbacks, analytics) before\n * routing. If you call `nav.push` / `nav.replace` from here, the default\n * dispatch is skipped — return `void`.\n */\n onURL?: (url: string, nav: Nav) => void;\n\n /**\n * Called when an incoming URL doesn't match any registered route's `path`\n * template (or fails schema validation). Defaults to a no-op so unknown\n * URLs are dropped silently. Use this to surface \"page not found\" UX or\n * to forward to a catch-all route.\n */\n onUnmatched?: (url: string) => void;\n\n /**\n * Whether to use `nav.replace` instead of `nav.push` for the cold-start\n * initial URL. Defaults to `true` — restoring an app into a deep link\n * shouldn't leave a stray \"initial route\" entry beneath it that the back\n * button can return to.\n *\n * Runtime URLs (from `addEventListener`) always `push`.\n */\n replaceInitial?: boolean;\n}\n\n/**\n * Bridge `@sigx/lynx-linking` URL events into a `@sigx/lynx-navigation`\n * navigator. Call once inside a `<NavigationRoot>` subtree.\n *\n * Handles both delivery modes:\n * - **cold start** — `Linking.getInitialURL()` is read on mount and, if\n * present, dispatched (replacing the initial route by default).\n * - **warm start** — `Linking.addEventListener('url', ...)` subscribes for\n * URLs delivered while the app is already running; each one is pushed.\n *\n * URL → route dispatch goes through `parseHref`, which matches the URL's\n * pathname against the route registry seeded by `<NavigationRoot>`. Routes\n * without a `path` template are never matched by deep links — only typed\n * `<Link>` / `nav.push` calls reach them.\n *\n * @example\n * ```tsx\n * import { useLinkingNav } from '@sigx/lynx-navigation';\n *\n * const DeepLinks = component(() => {\n * useLinkingNav({\n * prefixes: ['myapp://', 'https://myapp.com'],\n * onUnmatched: (url) => console.warn('Unknown deep link:', url),\n * });\n * return () => null;\n * });\n *\n * <NavigationRoot routes={routes}>\n * <DeepLinks />\n * <Stack />\n * </NavigationRoot>\n * ```\n */\nexport function useLinkingNav(opts: UseLinkingNavOptions = {}): void {\n const nav = useNav();\n const routes = useNavRoutes();\n\n const dispatch = (url: string, kind: 'push' | 'replace'): void => {\n if (opts.onURL) {\n opts.onURL(url, nav);\n return;\n }\n const stripped = _stripPrefix(url, opts.prefixes);\n const href = parseHref(stripped);\n if (!href) {\n opts.onUnmatched?.(url);\n return;\n }\n _navigateToHref(nav, routes, href, kind);\n };\n\n onMounted(() => {\n const initial = Linking.getInitialURL();\n if (initial) {\n dispatch(initial, opts.replaceInitial === false ? 'push' : 'replace');\n }\n const sub = Linking.addEventListener('url', (e) => dispatch(e.url, 'push'));\n return () => sub.remove();\n });\n}\n\n/**\n * Strip the first matching prefix from `url`, returning a pathname-like\n * string. If no prefixes are provided, or none match, the original URL is\n * returned unchanged so `parseHref` can still handle scheme-prefixed forms\n * via `@sigx/lynx-linking`'s `parse`.\n *\n * Exported for unit testing — not part of the package public API.\n */\nexport function _stripPrefix(url: string, prefixes?: string[]): string {\n if (!prefixes || prefixes.length === 0) return url;\n for (const prefix of prefixes) {\n if (url.startsWith(prefix)) {\n const rest = url.slice(prefix.length);\n return rest.startsWith('/') ? rest : `/${rest}`;\n }\n }\n return url;\n}\n\n/**\n * Call the right `nav.push` / `nav.replace` overload for `href`. The\n * overloads differ in positional layout: routes with a params schema take\n * `(name, params, search?, options?)`; routes without take `(name, search?,\n * options?)`. Calling the wrong shape silently shifts `search` into the\n * `options` slot, so we look the route up in the registry and branch.\n *\n * Exported for unit testing — not part of the package public API.\n */\nexport function _navigateToHref(\n nav: Nav,\n routes: RouteMap,\n href: Href,\n kind: 'push' | 'replace',\n): void {\n const def = routes[href.route];\n // Defensive: `parseHref` already validated against the registry, so this\n // really shouldn't happen — but if the registry was cleared between\n // parse and dispatch (multi-NavigationRoot scenarios), bail rather than\n // throw.\n if (!def) return;\n const hasParams = !!def.params;\n const action = kind === 'replace' ? nav.replace : nav.push;\n if (hasParams) {\n (action as (n: string, p: unknown, s?: unknown) => void)(\n href.route,\n href.params,\n href.search,\n );\n } else {\n (action as (n: string, s?: unknown) => void)(href.route, href.search);\n }\n}\n","import {\n computed,\n effect,\n onUnmounted,\n untrack,\n type Computed,\n} from '@sigx/lynx';\nimport { useNav } from './use-nav';\nimport { useCurrentEntry } from './use-nav-internal';\n\n/**\n * Reactive \"is this screen the focused entry?\" signal.\n *\n * Must be called from inside a component rendered as a route by `<Stack>` (or\n * any other navigator that uses `<EntryScope>`); throws otherwise. The\n * returned `Computed` reads `nav.current.key` and compares it to the entry\n * the calling screen was mounted for, so any nav mutation that changes the\n * top entry flips the value.\n *\n * Note: screens stay mounted when something is pushed on top of them — they\n * just lose focus. Pop the new top off and they regain focus.\n *\n * @example\n * ```tsx\n * const Profile = component(() => {\n * const isFocused = useIsFocused();\n * return () => <text>{isFocused.value ? 'visible' : 'hidden'}</text>;\n * });\n * ```\n */\nexport function useIsFocused(): Computed<boolean> {\n const nav = useNav();\n // Capture the entry's key once at setup. The entry object provided\n // through `defineProvide` may carry reactive dependencies; we only care\n // about the immutable key of the entry this screen was mounted for.\n const myKey = useCurrentEntry().key;\n // AND in `nav.isLocallyFocused` so a screen in a nested stack (e.g. a\n // per-tab `<Stack>`) reports unfocused when its enclosing tab is\n // inactive, or when a modal on the root nav covers everything — even\n // though it's still the top of its own (paused) stack. Root nav's\n // `isLocallyFocused` is permanently true, so this reduces to the\n // previous behavior for un-nested apps.\n return computed(() => nav.current.key === myKey && nav.isLocallyFocused);\n}\n\n/**\n * Run `cb` whenever this screen gains focus; run the returned cleanup when it\n * loses focus or unmounts. Mirrors React Navigation's `useFocusEffect`.\n *\n * Lifecycle:\n * - cb runs immediately if the screen is already focused at mount.\n * - When the screen loses focus (something pushed on top), cleanup runs.\n * - When focus returns (the cover is popped), `cb` runs again — yielding a\n * fresh cleanup for the next blur.\n * - On unmount, cleanup runs once if still focused.\n *\n * Common uses: subscribe to a data source while visible, track an analytics\n * \"screen view\" event, start/stop a polling loop.\n *\n * @example\n * ```tsx\n * useFocusEffect(() => {\n * const id = setInterval(refresh, 5000);\n * return () => clearInterval(id);\n * });\n * ```\n */\nexport function useFocusEffect(cb: () => void | (() => void)): void {\n const isFocused = useIsFocused();\n let cleanup: (() => void) | void;\n const runner = effect(() => {\n const focused = isFocused.value;\n // Always tear down any previous focus session before starting a new\n // one (or before going dormant on blur). Wrap `cb` in `untrack` so\n // signals read inside the user-provided callback can't retrigger the\n // outer effect and stack subscriptions.\n if (typeof cleanup === 'function') {\n const fn = cleanup;\n cleanup = undefined;\n fn();\n }\n if (focused) {\n cleanup = untrack(() => cb());\n }\n });\n onUnmounted(() => {\n if (typeof cleanup === 'function') {\n const fn = cleanup;\n cleanup = undefined;\n fn();\n }\n runner.stop();\n });\n}\n","/**\n * Per-entry registry of `<Screen>` options + slot fills.\n *\n * Each `<EntryScope>` allocates one of these on mount and provides it to\n * descendants via `defineProvide(useScreenRegistry, ...)`. The Screen\n * component and its sub-components (`<Screen.Header>`, `<Screen.HeaderLeft>`,\n * `<Screen.HeaderRight>`, `<Screen.TabBarItem>`) write into the registry as\n * they mount.\n *\n * Reads track because options/slots are stored in signals — when a child\n * re-renders and registers a new slot fill, the navigator-side consumer\n * (HeaderBar / TabBar, shipped in later slices) reactively updates.\n *\n * Cross-entry lookup is exposed via the navigator's `getScreenRegistry(key)`\n * so a persistent HeaderBar can read slots from the currently-focused entry\n * without needing to be itself remounted on each navigation.\n */\nimport { signal, type Signal } from '@sigx/lynx';\nimport type {\n ScreenOptions,\n ScreenSlotFills,\n StackEntry,\n} from '../types';\n\n/**\n * Reactive container for one screen's options and slot fills.\n *\n * `options` and `slots` are deeply-reactive object signals (sigx's `signal()`\n * of an object returns a Proxy that tracks per-key reads and notifies\n * per-key writes). Writers assign individual keys; readers subscribe to the\n * keys they actually use — no whole-object reads, no read/write cycles in\n * setup.\n */\nexport interface ScreenRegistry {\n readonly entry: StackEntry;\n /** Reactive ScreenOptions — written per-key by `<Screen>`. */\n readonly options: Signal<ScreenOptions>;\n /** Reactive ScreenSlotFills — written per-key by `<Screen.Header>` et al. */\n readonly slots: Signal<ScreenSlotFills>;\n}\n\n/** Create a fresh registry for an entry. Options and slots start empty. */\nexport function createScreenRegistry(entry: StackEntry): ScreenRegistry {\n return {\n entry,\n options: signal<ScreenOptions>({}),\n slots: signal<ScreenSlotFills>({}),\n };\n}\n\n/**\n * Set a single slot fill on a registry. Pass `undefined` to clear.\n * Per-key write on the proxy — does not read other keys, so it can't loop\n * with effects that read different slot keys.\n */\nexport function setSlot<K extends keyof ScreenSlotFills>(\n registry: ScreenRegistry,\n name: K,\n fill: ScreenSlotFills[K] | undefined,\n): void {\n if (fill === undefined) {\n // Assigning undefined keeps the key around in the proxy; explicit\n // delete is what consumers checking `name in slots` expect.\n delete registry.slots[name];\n return;\n }\n (registry.slots as ScreenSlotFills)[name] = fill;\n}\n\n/**\n * Merge partial options into a registry. Each option key is written\n * independently on the proxy — `undefined` keys clear that option.\n */\nexport function mergeOptions(\n registry: ScreenRegistry,\n patch: ScreenOptions,\n): void {\n for (const key of Object.keys(patch) as (keyof ScreenOptions)[]) {\n const v = patch[key];\n if (v === undefined) {\n delete registry.options[key];\n } else {\n // Property-level assignment on a deeply-reactive proxy: notifies\n // only subscribers of this specific key, never reads the whole\n // options object, so it can't trigger the setup that wrote it.\n (registry.options as unknown as Record<string, unknown>)[key] = v;\n }\n }\n}\n","/**\n * `useScreenOptions` — imperative merge into the current entry's options.\n *\n * Use this when options need to be set from an effect rather than declared\n * statically via `<Screen title=…>`. The canonical case is \"title becomes\n * known after a fetch\":\n *\n * ```ts\n * const user = useFetchUser(id);\n * useScreenOptions(() => ({\n * title: user.value?.displayName ?? 'Loading…',\n * }));\n * ```\n *\n * The callback runs in a tracked `effect` — any signals it reads cause it\n * to re-run and re-merge. This is strictly additive: returning a partial\n * options object only touches the keys it sets, and returning `undefined`\n * for a key clears it.\n *\n * Static usage where the options never change can pass a plain object and\n * skip the effect — internally we detect that and merge once. Hosts that\n * pass a getter pay for the subscription; hosts that pass an object don't.\n */\nimport { effect, onUnmounted } from '@sigx/lynx';\nimport { useScreenRegistry } from './use-nav-internal';\nimport { mergeOptions } from '../internal/screen-registry';\nimport type { ScreenOptions } from '../types';\n\nexport function useScreenOptions(\n optionsOrFn: ScreenOptions | (() => ScreenOptions),\n): void {\n const registry = useScreenRegistry();\n\n if (typeof optionsOrFn !== 'function') {\n mergeOptions(registry, optionsOrFn);\n return;\n }\n\n // Reactive path: every signal touched inside the getter is tracked, so\n // the merge re-runs when any of them change. `mergeOptions` does per-key\n // writes on a deeply-reactive proxy, so consumers (HeaderBar) only\n // re-render the parts they actually read.\n const runner = effect(() => {\n const next = optionsOrFn();\n mergeOptions(registry, next);\n });\n onUnmounted(() => runner.stop());\n}\n","/**\n * `useScreenChrome` — reactive read of the currently-focused screen's\n * options + slot fills, plus navigation helpers a header would need\n * (canGoBack, pop).\n *\n * The built-in `<Header />` reads this same data via internal hooks.\n * `useScreenChrome` exposes it as a public API so theme packages\n * (`@sigx/lynx-daisyui`, custom designs) can build their own header\n * components without depending on internal modules.\n *\n * Resolution rules:\n *\n * - **Inside a screen body** (i.e. inside an EntryScope whose entry is\n * on the nearest `useNav()`'s stack), bind to **this entry's**\n * registry. Useful for modal screens that render their own\n * NavHeader inside — the chrome slides with the sheet.\n * - **Outside any matching EntryScope** (slot of `<Stack>`, persistent\n * root-level Header, etc.), bind to the *destination* entry of the\n * current nav state — what the navigator is settling on once the\n * in-flight transition completes. Push: the new top (already at\n * nav.current). Pop: the entry being revealed\n * (`transition.underneathEntry`), *not* the one being animated off.\n * Using the destination means the bar reflects what the user is\n * navigating *to*, immediately, with no end-of-animation snap.\n *\n * Every property is a getter — reading inside a render / `computed`\n * subscribes to the underlying signal, so consumers re-render when\n * title / slots change.\n */\nimport { useNav } from './use-nav';\nimport { useCurrentEntryOptional, useNavInternals } from './use-nav-internal';\nimport type { ScreenSlotFills, StackEntry } from '../types';\n\nexport interface ScreenChrome {\n /** Resolved screen title — `options.title` (string or getter) or the route name as fallback. Reactive. */\n readonly title: string;\n /** Whether the header should render. Defaults to true unless the screen set `headerShown: false`. Reactive. */\n readonly headerShown: boolean;\n /** True when the current stack has more than one entry — i.e. there's something to pop back to. Reactive. */\n readonly canGoBack: boolean;\n /** Pop the top entry. No-op when `!canGoBack`. */\n pop(): void;\n /** Full header override slot, if `<Screen.Header>` was set. Render its return value in place of the default layout. */\n readonly header: ScreenSlotFills['header'] | undefined;\n /** Left-aligned slot (typically a back button). Reactive. */\n readonly headerLeft: ScreenSlotFills['headerLeft'] | undefined;\n /** Right-aligned slot (typically actions). Reactive. */\n readonly headerRight: ScreenSlotFills['headerRight'] | undefined;\n}\n\nexport function useScreenChrome(): ScreenChrome {\n const nav = useNav();\n const internals = useNavInternals();\n\n // The candidate \"scoped\" entry, if we happen to be rendered inside\n // an EntryScope. May belong to a DIFFERENT nav than `nav` — e.g.\n // when NavHeader is placed in a per-tab `<Stack>`'s chrome slot,\n // it sees the outer (root) EntryScope's entry but its `useNav()`\n // returns the inner per-tab nav. We only honor the pin when the\n // entry is actually on this nav's stack; otherwise we're crossing\n // scopes and the destination-entry path is correct.\n //\n // `useCurrentEntryOptional` is the soft companion to\n // `useCurrentEntry` — it returns `null` outside any EntryScope\n // rather than throwing, which is the right semantic for a chrome\n // consumer that *might* be a Stack slot.\n const candidate: StackEntry | null = useCurrentEntryOptional();\n\n const getDestinationEntry = (): StackEntry => {\n const t = nav.transition;\n if (t) {\n return t.kind === 'pop' ? t.underneathEntry : t.topEntry;\n }\n return nav.current;\n };\n\n const getEntry = (): StackEntry => {\n if (candidate) {\n const stack = nav.stack;\n if (stack.some((e) => e.key === candidate!.key)) {\n return candidate;\n }\n }\n return getDestinationEntry();\n };\n\n return {\n get title() {\n const entry = getEntry();\n const reg = internals.screens.get(entry.key);\n const t = reg?.options.title;\n if (typeof t === 'function') return t();\n if (typeof t === 'string') return t;\n return entry.route;\n },\n get headerShown() {\n const reg = internals.screens.get(getEntry().key);\n return reg?.options.headerShown !== false;\n },\n get canGoBack() {\n const entry = getEntry();\n const stack = nav.stack;\n const idx = stack.findIndex((e) => e.key === entry.key);\n return idx > 0;\n },\n pop() {\n nav.pop();\n },\n get header() {\n const reg = internals.screens.get(getEntry().key);\n return reg?.slots.header;\n },\n get headerLeft() {\n const reg = internals.screens.get(getEntry().key);\n return reg?.slots.headerLeft;\n },\n get headerRight() {\n const reg = internals.screens.get(getEntry().key);\n return reg?.slots.headerRight;\n },\n };\n}\n","import { effect, onMounted, onUnmounted } from '@sigx/lynx';\nimport { useNav } from './use-nav';\nimport { useNavRoutes } from './use-nav-internal';\nimport type { StackEntry } from '../types';\n\n/**\n * Plain JSON snapshot of a navigator. The whole point of holding navigation\n * state in signals is that this is a one-liner — `JSON.stringify(nav.stack)`.\n *\n * Shape is deliberately minimal:\n *\n * {\n * version: 1,\n * stack: [ { key, route, params, search, state, presentation }, ... ],\n * }\n *\n * `version` lets future schema migrations (or hard breakage) reject old\n * snapshots cleanly rather than restoring incompatible state.\n *\n * Per spec resolved-decisions: only the root navigator is persisted in v1.\n * Per-tab / nested-navigator stacks are deferred until the nested-navigators\n * follow-up slice lands.\n */\nexport interface NavSnapshot {\n version: number;\n stack: StackEntry[];\n}\n\nexport const NAV_SNAPSHOT_VERSION = 1;\n\n/**\n * Adapter contract for `useNavSerializer`. Implementations bridge to whatever\n * storage backend the host app uses — `@sigx/lynx-storage`, `localStorage`,\n * an MMKV bridge, etc. Both methods may be async; the hook awaits load before\n * applying anything to the stack and fires save in a debounced manner.\n *\n * - `load()` returns `null` (or rejects) when no snapshot exists, when the\n * stored payload is malformed, or when the host opts not to restore on\n * this launch.\n * - `save(snapshot)` persists the latest stack. The hook drops save errors\n * on the floor — losing a write is preferable to crashing the navigator.\n */\nexport interface NavStorageAdapter {\n load(): Promise<NavSnapshot | null> | NavSnapshot | null;\n save(snapshot: NavSnapshot): Promise<void> | void;\n}\n\nexport interface UseNavSerializerOptions {\n storage: NavStorageAdapter;\n /**\n * Trailing-edge debounce in ms before pushing a stack change to storage.\n * Defaults to 250ms — quick enough that a force-quit one tick after a\n * push is recoverable, slow enough that rapid `pop/push` flurries\n * coalesce into one write.\n */\n debounceMs?: number;\n /**\n * Optional callback after a successful restore — lets the host run\n * post-restore wiring (analytics, focus shifts, etc.) only when we\n * actually applied state, not on every mount.\n */\n onRestored?: (snapshot: NavSnapshot) => void;\n /**\n * Optional callback when a snapshot is rejected (validation failed or\n * load threw). Defaults to silent. Useful for logging during migration.\n */\n onRestoreError?: (reason: 'version' | 'shape' | 'unknown-route' | 'load-threw', err?: unknown) => void;\n}\n\n/**\n * Wire a navigator's stack to a storage adapter.\n *\n * On mount:\n * 1. Call `storage.load()`.\n * 2. Validate the snapshot (version match, every entry's route still\n * registered).\n * 3. On success, `nav.reset({ stack })` to apply.\n * 4. On any failure, leave the stack alone (initial route remains).\n *\n * Then subscribe to `nav.stack` and call `storage.save(snapshot)` debounced.\n *\n * Why we don't validate `params` / `search` against schemas here: schemas\n * are part of the route definition, and re-running them across all entries\n * on every launch costs more than it's worth. The contract is \"entries were\n * validated when they were pushed; if the schema has since changed in a\n * breaking way, bump `version` to reject old snapshots wholesale.\" Callers\n * who want a stricter check can run their own validation in\n * `storage.load()` and return `null` on mismatch.\n */\nexport function useNavSerializer(options: UseNavSerializerOptions): void {\n const nav = useNav();\n const routes = useNavRoutes();\n const debounceMs = options.debounceMs ?? 250;\n const onRestored = options.onRestored;\n const onErr = options.onRestoreError;\n\n // Mutable mount/state flags. Plain closure vars (no signals) — we don't\n // want any of this driving a render and we don't want it tracked by the\n // save-effect below.\n let mounted = true;\n let restoreDone = false;\n let pendingTimer: ReturnType<typeof setTimeout> | null = null;\n let stopEffect: (() => void) | null = null;\n\n onMounted(() => {\n // Kick off the load synchronously — adapters that return a value\n // immediately (sync stores, test doubles) hit the resolve branch on\n // the same tick. Promise adapters resolve on the microtask queue;\n // the `mounted` guard catches teardown races.\n Promise.resolve()\n .then(() => options.storage.load())\n .then((snap) => {\n if (!mounted) return;\n if (snap == null) {\n restoreDone = true;\n startSaveEffect();\n return;\n }\n if (!isValidShape(snap)) {\n onErr?.('shape');\n restoreDone = true;\n startSaveEffect();\n return;\n }\n if (snap.version !== NAV_SNAPSHOT_VERSION) {\n onErr?.('version');\n restoreDone = true;\n startSaveEffect();\n return;\n }\n // Drop the snapshot if any entry references a route the app\n // no longer knows about — partial restoration is worse than\n // no restoration (could leave the user stranded on a screen\n // whose params won't validate when read by `useParams`).\n for (const entry of snap.stack) {\n if (!routes[entry.route]) {\n onErr?.('unknown-route');\n restoreDone = true;\n startSaveEffect();\n return;\n }\n }\n if (snap.stack.length === 0) {\n onErr?.('shape');\n restoreDone = true;\n startSaveEffect();\n return;\n }\n nav.reset({ stack: snap.stack });\n onRestored?.(snap);\n restoreDone = true;\n startSaveEffect();\n })\n .catch((err) => {\n if (!mounted) return;\n onErr?.('load-threw', err);\n restoreDone = true;\n startSaveEffect();\n });\n });\n\n function startSaveEffect() {\n if (!mounted || stopEffect) return;\n // The first effect run is just the initial subscription read — it\n // happens immediately when `effect()` is called, before any user\n // navigation, and represents the stack-as-restored (or the initial\n // route when there was nothing to restore). Either way, we don't\n // want to persist it: in the restore case it would race with the\n // adapter that just supplied this state, and in the fresh case\n // it's redundant.\n let firstRun = true;\n const runner = effect(() => {\n const stack = nav.stack;\n const snapshot: NavSnapshot = {\n version: NAV_SNAPSHOT_VERSION,\n stack: stack.map((e) => ({\n key: e.key,\n route: e.route,\n params: e.params,\n search: e.search,\n state: e.state,\n presentation: e.presentation,\n })),\n };\n if (firstRun) {\n firstRun = false;\n return;\n }\n schedule(snapshot);\n });\n stopEffect = () => runner.stop();\n }\n\n function schedule(snapshot: NavSnapshot) {\n if (pendingTimer != null) clearTimeout(pendingTimer);\n pendingTimer = setTimeout(() => {\n pendingTimer = null;\n try {\n const r = options.storage.save(snapshot);\n if (r && typeof (r as Promise<void>).catch === 'function') {\n (r as Promise<void>).catch(() => {\n // Save errors are intentionally swallowed — see the\n // hook doc-comment. Hosts that need visibility can\n // wrap their adapter.\n });\n }\n } catch {\n // Same rationale.\n }\n }, debounceMs);\n }\n\n onUnmounted(() => {\n mounted = false;\n if (pendingTimer != null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n if (stopEffect) {\n stopEffect();\n stopEffect = null;\n }\n });\n}\n\nfunction isValidShape(s: unknown): s is NavSnapshot {\n if (!s || typeof s !== 'object') return false;\n const obj = s as { version?: unknown; stack?: unknown };\n if (typeof obj.version !== 'number') return false;\n if (!Array.isArray(obj.stack)) return false;\n for (const entry of obj.stack) {\n if (!entry || typeof entry !== 'object') return false;\n const e = entry as Record<string, unknown>;\n if (typeof e.key !== 'string') return false;\n if (typeof e.route !== 'string') return false;\n if (typeof e.presentation !== 'string') return false;\n }\n return true;\n}\n","import {\n runOnMainThread,\n signal,\n untrack,\n type Signal,\n type SharedValue,\n} from '@sigx/lynx';\nimport { isLazyComponent } from '@sigx/lynx';\nimport { withTiming } from '@sigx/lynx-motion';\nimport type { Nav } from '../hooks/use-nav';\nimport type { ScreenRegistry } from '../internal/screen-registry';\nimport type {\n PopOptions,\n Presentation,\n PushOptions,\n RouteMap,\n StackEntry,\n TransitionState,\n} from '../types';\n\n/**\n * The reactive backing state for one navigator instance.\n *\n * Two reactive signals drive the public surface:\n * - `stack` is the entry array (read via `nav.stack` / `nav.current`).\n * - `transition` is non-null only while a push/pop animation is in flight;\n * `<Stack>` reads it to decide whether to render one screen or two.\n *\n * Pop is committed *after* its slide animation completes — `nav.canGoBack`\n * stays true during the slide, then flips when the entry actually leaves the\n * stack. Push commits its stack mutation immediately and animates the new\n * entry in.\n */\nexport interface NavigatorState {\n readonly nav: Nav;\n readonly routes: RouteMap;\n /**\n * Internal: BG-side gesture-back controller used by `<EdgeBackHandle>`.\n * The `progress` SharedValue is wired here so a gesture worklet can write\n * it directly on MT; the begin/commit/cancel methods set the transition\n * state appropriately without driving their own auto-animation (the\n * gesture worklet is in charge of that).\n */\n readonly _gesture: {\n beginBackGesture(): void;\n commitBackGesture(): void;\n cancelBackGesture(): void;\n };\n /**\n * Internal: cross-entry `<Screen>` registry lookup.\n *\n * Each `<EntryScope>` registers its `ScreenRegistry` here on mount and\n * removes it on unmount. The navigator's persistent chrome (HeaderBar /\n * TabBar, shipped in later slices) calls `getScreenRegistry(entry.key)`\n * to read the currently-focused screen's options/slot fills without\n * being itself remounted on each navigation.\n *\n * Returns `undefined` when no screen for that key has mounted yet (or\n * after it has unmounted) — consumers must tolerate this and render\n * defaults.\n */\n readonly _screens: {\n register(registry: ScreenRegistry): void;\n /** Identity-checked: no-op when a newer registry has taken the slot. */\n unregister(registry: ScreenRegistry): void;\n get(entryKey: string): ScreenRegistry | undefined;\n };\n /**\n * Internal: set `nav.isLocallyFocused` from outside.\n *\n * `<Stack>` calls this when its host entry's locally-focused state\n * changes (top of parent + parent focused + enclosing tab active). For\n * the root nav this stays `true` for the lifetime of the navigator.\n */\n readonly _setLocallyFocused: (focused: boolean) => void;\n}\n\n/**\n * Slide-from-right transition timing. Kept as constants so screen options\n * can override per-screen later (Phase 0.5). Duration is in seconds — that's\n * what `@sigx/lynx-motion`'s `withTiming` expects (per `with-timing.ts`).\n */\nconst TRANSITION_DURATION_SEC = 0.28;\n\n/**\n * Kick off a lazy component's chunk fetch when its route is navigated to.\n *\n * Lazy routes (`component: lazy(() => import('./Heavy.js'))`) start loading\n * the moment `push`/`replace` is called rather than waiting until render\n * tries to instantiate them — by the time `<Stack>` swaps screens the chunk\n * is usually already resolved, so the user sees the screen instead of the\n * `<Suspense fallback>`. Fire-and-forget: errors here surface through\n * `<Suspense>` at render time.\n */\nfunction preloadRouteComponent(component: unknown): void {\n if (isLazyComponent(component)) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n component.preload().catch(() => {});\n }\n}\n\nlet entryKeyCounter = 0;\nfunction nextEntryKey(): string {\n entryKeyCounter += 1;\n return `entry-${entryKeyCounter}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction makeEntry(\n name: string,\n params: unknown,\n search: unknown,\n options: PushOptions | undefined,\n routes: RouteMap,\n): StackEntry {\n const route = routes[name];\n const presentation: Presentation =\n options?.presentation ?? route?.presentation ?? 'card';\n return {\n key: nextEntryKey(),\n route: name,\n params: (params ?? {}) as Record<string, unknown>,\n search: (search ?? {}) as Record<string, unknown>,\n state: options?.state,\n presentation,\n };\n}\n\nfunction unpackArgs(\n name: string,\n args: unknown[],\n routes: RouteMap,\n): { params: unknown; search: unknown; options: PushOptions | undefined } {\n const route = routes[name];\n const requiresParams = !!route?.params;\n if (requiresParams) {\n const [params, search, options] = args as [\n unknown,\n unknown,\n PushOptions | undefined,\n ];\n return { params, search, options };\n }\n const [search, options] = args as [unknown, PushOptions | undefined];\n return { params: undefined, search, options };\n}\n\nexport interface CreateNavigatorOptions {\n routes: RouteMap;\n initial: StackEntry;\n /**\n * SharedValue driving push/pop transition progress. Created in\n * `<NavigationRoot>` setup via `useSharedValue(0)` so the bridge\n * plumbing is wired (SharedValue is an MT-bridged ref). When undefined,\n * navigations are instant — used by tests against `@sigx/lynx-testing`\n * that don't have an MT runtime.\n */\n progress?: SharedValue<number>;\n /**\n * Parent navigator. Set when this navigator is nested under another\n * (e.g. a per-tab `<Stack initialRoute>` under root). Drives the\n * `nav.parent` getter and the modal-escalation behaviour of `push`:\n * a push of a route whose resolved presentation is not `'card'`\n * recurses via `parent.push(...)`, walking up the chain until it\n * lands on a navigator with no parent (the root).\n *\n * Leave undefined for the root navigator.\n */\n parent?: Nav | null;\n /**\n * Whether this navigator is considered \"locally focused\" at creation\n * time. Defaults to true for the root nav; nested stacks pass `false`\n * here and then flip the flag via `_setLocallyFocused` once their\n * host-entry/tab-active state is computed.\n */\n initialLocallyFocused?: boolean;\n}\n\n/**\n * Create a navigator. Returns the public `nav` handle plus the routes map.\n * The transition signal lives on `nav` (via `nav.transition`) so `<Stack>`\n * can subscribe to it.\n */\nexport function createNavigatorState(opts: CreateNavigatorOptions): NavigatorState {\n const { routes, initial, progress, parent = null } = opts;\n\n const stackSignal: Signal<StackEntry[]> = signal<StackEntry[]>([initial]);\n const focusedBox: Signal<{ value: boolean }> = signal<{ value: boolean }>({\n value: opts.initialLocallyFocused ?? true,\n });\n const children = new Set<Nav>();\n // `signal(null)` would wrap as a primitive (no `$set`), so wrap in an\n // object to get the standard `{ value }`-style API. Reading `.value`\n // tracks; writing triggers re-render of `<Stack>`.\n const transitionBox: Signal<{ value: TransitionState | null }> = signal<{\n value: TransitionState | null;\n }>({ value: null });\n\n function getStack(): StackEntry[] {\n return stackSignal;\n }\n function setStack(next: StackEntry[]): void {\n stackSignal.$set(next);\n }\n function setTransition(next: TransitionState | null): void {\n transitionBox.value = next;\n }\n\n /**\n * Whether a transition is currently in flight. Used to no-op concurrent\n * navigation calls — keeps the state machine simple. A queued/aborted\n * model is a v0.3 polish item.\n */\n function isTransitioning(): boolean {\n return transitionBox.value !== null;\n }\n\n /**\n * Run the slide animation by hopping a worklet onto the main thread that\n * resets `progress` to 0 and starts a `withTiming` to the target. Then\n * wait the animation duration on BG so we can fire the completion\n * callback (clear transition / commit the popped entry) when the visual\n * animation is done.\n *\n * Why the SV reset lives *inside* the worklet (not on BG before the call):\n * the BG-side render ops (Stack re-render mounting the two\n * `ScreenContainer`s with their `useAnimatedStyle` bindings) and a BG-side\n * SV write (`progress.value = 0`) travel different bridge channels. On\n * subsequent navigations, MT can register the new bindings before the\n * BG-side reset arrives — the bindings snapshot sv at its previous\n * end-state (`1`), and `withTiming(sv, 1, ...)` then animates from 1→1\n * (no visible motion). Resetting inside the worklet guarantees the order\n * `bindings register → sv resets → withTiming starts` happens atomically\n * on MT.\n *\n * Why we don't `await` the worklet's Promise: `withTiming` returns a\n * Promise on MT, but Promises don't serialize across the BG/MT bridge —\n * `runOnMainThread`'s callback fires the moment the worklet *returns*\n * (synchronously, with `undefined` since the Promise can't cross), not\n * when the underlying animation finishes. We time the BG-side wait\n * against the duration we passed to MT instead.\n */\n async function animateProgress(\n target: number,\n durationSec: number,\n ): Promise<void> {\n if (!progress) return;\n const sv = progress;\n const runner = runOnMainThread((t: number, d: number) => {\n 'main thread';\n // MT-side direct write — `sv.value` is a BG-side getter/setter\n // that emits a \"read-only on BG\" warning when set; the actual\n // MT field (which `withTiming`'s animate() reads as the start\n // value) is `sv.current.value`. See `packages/lynx-runtime/src/\n // animated/shared-value.ts:14-44`.\n sv.current.value = 0;\n withTiming(sv, t, { duration: d });\n });\n runner(target, durationSec);\n await new Promise<void>((resolve) => {\n setTimeout(resolve, Math.round(durationSec * 1000));\n });\n }\n\n const push: Nav['push'] = ((name: string, ...args: unknown[]) => {\n if (!routes[name]) {\n throw new Error(\n `[lynx-navigation] push('${name}'): route is not registered. ` +\n `Known routes: ${Object.keys(routes).join(', ') || '(none)'}`,\n );\n }\n const { params, search, options } = unpackArgs(name, args, routes);\n\n // Escalate non-card presentations up the parent chain. Modals,\n // fullScreen, and transparent-modal routes belong on the root\n // navigator so they overlay tab UI and persistent chrome. We resolve\n // the presentation the same way `makeEntry` does so the escalation\n // decision matches what would actually be shown.\n const resolvedPresentation =\n (options?.presentation ?? routes[name].presentation ?? 'card') as Presentation;\n if (resolvedPresentation !== 'card' && parent) {\n // Walk straight to the root — every navigator with a parent\n // delegates non-card pushes upward, so a chain of any depth\n // collapses to a single push on the topmost nav.\n // Forward original args verbatim so overloads (`push(name)`,\n // `push(name, params)`, `push(name, params, search)`,\n // `push(name, params, search, options)`) keep their meaning.\n (parent.push as (n: string, ...a: unknown[]) => void)(name, ...args);\n return;\n }\n\n if (isTransitioning()) return;\n preloadRouteComponent(routes[name].component);\n const newEntry = makeEntry(name, params, search, options, routes);\n const cur = getStack();\n const prevTop = cur[cur.length - 1];\n\n // Append eagerly — UX-wise the user just initiated a forward nav, so\n // the new entry should be queryable immediately (`nav.current` =\n // newEntry). The slide animation overlays the visual transition.\n setStack([...cur, newEntry]);\n\n const animated = options?.animated !== false && !!progress;\n if (!animated) return;\n\n setTransition({\n kind: 'push',\n topEntry: newEntry,\n underneathEntry: prevTop,\n progress,\n });\n\n animateProgress(1, TRANSITION_DURATION_SEC).then(\n () => setTransition(null),\n () => setTransition(null), // best-effort cleanup on animation rejection\n );\n }) as Nav['push'];\n\n const replace: Nav['replace'] = ((name: string, ...args: unknown[]) => {\n if (isTransitioning()) return;\n const { params, search, options } = unpackArgs(name, args, routes);\n if (!routes[name]) {\n throw new Error(\n `[lynx-navigation] replace('${name}'): route is not registered.`,\n );\n }\n preloadRouteComponent(routes[name].component);\n const entry = makeEntry(name, params, search, options, routes);\n const cur = getStack();\n // Replace doesn't animate in v1 — it's a swap, not a forward/back nav.\n // Adding a fade-or-slide variant is a screen-option in Phase 0.5.\n setStack([...cur.slice(0, cur.length - 1), entry]);\n }) as Nav['replace'];\n\n function pop(count: number = 1, options?: PopOptions): void {\n if (isTransitioning()) return;\n const cur = getStack();\n const target = Math.max(1, cur.length - Math.max(1, count));\n if (target === cur.length) return;\n\n const animated =\n options?.animated !== false && !!progress && count === 1 && cur.length >= 2;\n if (!animated) {\n setStack(cur.slice(0, target));\n return;\n }\n\n // Single-step animated pop: keep the popped entry on the stack until\n // the slide finishes, so `<Stack>` can render both screens during the\n // animation. The stack mutation happens on completion.\n const popping = cur[cur.length - 1];\n const next = cur[cur.length - 2];\n setTransition({\n kind: 'pop',\n topEntry: popping,\n underneathEntry: next,\n progress,\n });\n\n animateProgress(1, TRANSITION_DURATION_SEC).then(\n () => {\n setStack(cur.slice(0, cur.length - 1));\n setTransition(null);\n },\n () => {\n // On animation failure, snap to the destination state anyway —\n // leaving the popped entry rendered would be more confusing\n // than skipping the animation.\n setStack(cur.slice(0, cur.length - 1));\n setTransition(null);\n },\n );\n }\n\n function popTo(name: string): void {\n if (isTransitioning()) return;\n const cur = getStack();\n for (let i = cur.length - 1; i >= 0; i--) {\n if (cur[i].route === name) {\n if (i === cur.length - 1) return;\n setStack(cur.slice(0, i + 1));\n return;\n }\n }\n }\n\n function popToRoot(): void {\n if (isTransitioning()) return;\n const cur = getStack();\n if (cur.length <= 1) return;\n setStack([cur[0]]);\n }\n\n function reset(state: { stack: ReadonlyArray<StackEntry> }): void {\n if (state.stack.length === 0) {\n throw new Error('[lynx-navigation] reset() called with empty stack.');\n }\n setStack([...state.stack]);\n setTransition(null);\n }\n\n function dismiss(): void {\n if (isTransitioning()) return;\n const cur = getStack();\n let i = cur.length - 1;\n while (i > 0 && cur[i].presentation !== 'card') {\n i--;\n }\n if (i < cur.length - 1) {\n setStack(cur.slice(0, i + 1));\n }\n }\n\n /**\n * Set up a gesture-driven pop transition. Same shape as `pop()` sets but\n * does NOT call `animateProgress` — the gesture worklet writes the\n * progress SV directly per frame, then animates to commit/cancel\n * endpoints on release before invoking `commitBackGesture` or\n * `cancelBackGesture` via `runOnBackground`.\n */\n function beginBackGesture(): void {\n if (isTransitioning()) return;\n const cur = getStack();\n if (cur.length < 2) return;\n const popping = cur[cur.length - 1];\n const next = cur[cur.length - 2];\n setTransition({\n kind: 'pop',\n topEntry: popping,\n underneathEntry: next,\n progress: progress as unknown,\n });\n }\n\n function commitBackGesture(): void {\n const cur = getStack();\n if (cur.length >= 2) {\n setStack(cur.slice(0, cur.length - 1));\n }\n setTransition(null);\n }\n\n function cancelBackGesture(): void {\n setTransition(null);\n }\n\n const nav: Nav = {\n push,\n replace,\n pop,\n popTo,\n popToRoot,\n reset,\n dismiss,\n get current() {\n return stackSignal[stackSignal.length - 1];\n },\n get stack() {\n return stackSignal;\n },\n get canGoBack() {\n return stackSignal.length > 1;\n },\n get parent() {\n return parent;\n },\n get isLocallyFocused() {\n return focusedBox.value;\n },\n get _children() {\n return children;\n },\n get transition() {\n return transitionBox.value;\n },\n };\n\n if (parent) {\n // Register with parent so root-level traversals (hardware back,\n // future deepest-focused queries) can reach this nav. The matching\n // `_children.delete(nav)` happens when the owning `<Stack>` unmounts;\n // see Stack.tsx.\n parent._children.add(nav);\n }\n\n function setLocallyFocused(focused: boolean): void {\n if (focusedBox.value === focused) return;\n focusedBox.value = focused;\n }\n\n return {\n nav,\n routes,\n _gesture: { beginBackGesture, commitBackGesture, cancelBackGesture },\n _screens: createScreenRegistries(),\n _setLocallyFocused: setLocallyFocused,\n };\n}\n\n/**\n * Map-backed `_screens` controller. Pulled out as a tiny factory so test\n * tooling can call it directly when asserting registry behaviour without\n * standing up an entire navigator.\n *\n * Not reactive — `<EntryScope>` registers once at setup and unregisters at\n * unmount, so reads from the navigator's chrome are point-in-time lookups,\n * and the registry's own internal signals carry the reactive payload.\n */\nfunction createScreenRegistries(): NavigatorState['_screens'] {\n const byKey = new Map<string, ScreenRegistry>();\n // Reactive version tick — bumped on every register/unregister so consumers\n // (HeaderBar's computeds) re-evaluate their lookups when entries come and\n // go. `Map.get` itself isn't tracked, so without this a chrome component\n // that renders before its target entry mounts would never see the late\n // arrival of the registry.\n const version = signal({ v: 0 });\n return {\n register(reg: ScreenRegistry) {\n byKey.set(reg.entry.key, reg);\n // `register` is called from `<EntryScope>` setup, which itself\n // runs inside a tracked scope. Read-then-write on `version`\n // would self-loop, so we untrack the bump.\n untrack(() => { version.v = version.v + 1; });\n },\n // Identity-checked unregister: deletes the entry only if the\n // currently-registered registry is the *same instance* the caller\n // holds. Without this, the transition→idle handoff (which can\n // mount a new `<EntryScope>` for the same entry-key before the\n // old one unmounts) would let the old scope's `onUnmounted` wipe\n // out the fresh registry — leaving `screens.get(key)` returning\n // undefined and chrome consumers (NavHeader) falling back to the\n // route-name as title with all slot fills gone.\n unregister(reg: ScreenRegistry) {\n const cur = byKey.get(reg.entry.key);\n if (cur !== reg) return;\n byKey.delete(reg.entry.key);\n untrack(() => { version.v = version.v + 1; });\n },\n get(key: string) {\n // Touch the version signal so the caller's reactive scope\n // re-runs on the next register/unregister. The actual returned\n // value still comes from the plain Map — registries themselves\n // are signal-backed, so once a caller has one in hand they\n // track the bits they care about (options/slots) directly.\n void version.v;\n return byKey.get(key);\n },\n };\n}\n","import { component, defineProvide, useSharedValue, type Define } from '@sigx/lynx';\nimport { createNavigatorState } from '../navigator/core';\nimport { useNav } from '../hooks/use-nav';\nimport { useNavInternals, useNavRoutes } from '../hooks/use-nav-internal';\nimport type { RouteId } from '../register';\nimport type { Presentation, RouteMap, StackEntry } from '../types';\nimport { _setRouteRegistry } from '../url/registry';\n\ntype NavigationRootProps =\n & Define.Prop<'routes', RouteMap, true>\n & Define.Prop<'initialRoute', RouteId>\n & Define.Prop<'initialParams', Record<string, unknown>>\n & Define.Prop<'initialSearch', Record<string, unknown>>\n /**\n * Enable slide-from-right transitions on push/pop. Defaults to true.\n * Tests against `@sigx/lynx-testing` (which doesn't have an MT runtime)\n * should pass `animated={false}` so navigations commit synchronously.\n */\n & Define.Prop<'animated', boolean>\n /**\n * Enable the iOS-style edge-swipe-back gesture. Defaults to true. Set\n * to false if it conflicts with screen content on the leftmost 20px,\n * or while debugging gesture issues.\n */\n & Define.Prop<'edgeSwipeEnabled', boolean>\n & Define.Slot<'default'>;\n\n/**\n * Root of a navigator subtree.\n *\n * Creates a fresh `NavigatorState` from `routes` and provides it via\n * `defineProvide`, so descendant `<Stack>` / `<Screen>` components and any\n * `useNav()` / `useParams()` calls resolve through this instance.\n *\n * The bottom-of-stack entry is built from `initialRoute` (defaults to the\n * first key in `routes`). For routes that declare a params schema, you must\n * pass `initialParams` matching that schema.\n *\n * Mirrors the install pattern of `@sigx/router` (see\n * `packages/router/src/router.ts:519-528`), but at component scope rather than\n * `app.use(router)` — no app-wide singleton, so multi-navigator apps and\n * tests get isolated state for free.\n */\nexport const NavigationRoot = component<NavigationRootProps>(({ props, slots }) => {\n const routes = props.routes;\n const initialName: string = props.initialRoute ?? Object.keys(routes)[0];\n if (!routes[initialName]) {\n throw new Error(\n `[lynx-navigation] <NavigationRoot> initialRoute='${initialName}' is not in the routes registry.`,\n );\n }\n // Publish the active route registry to the URL bridge so module-level\n // `hrefFor` / `parseHref` callers (deep-link handlers, anything outside\n // the component tree) resolve against this navigator's routes. Last\n // mount wins — multi-root apps that need isolation should call the\n // URL helpers with explicit context (TBD post-1.0).\n _setRouteRegistry(routes);\n const initialPresentation: Presentation = routes[initialName].presentation ?? 'card';\n const initial: StackEntry = {\n key: 'root',\n route: initialName,\n params: props.initialParams ?? {},\n search: props.initialSearch ?? {},\n state: undefined,\n presentation: initialPresentation,\n };\n\n // SharedValue driving the slide-from-right push/pop transition. Created\n // unconditionally (hooks must be) but only forwarded into the navigator\n // when animations are enabled — `createNavigatorState` falls back to\n // instant swaps when `progress` is undefined.\n const progressSv = useSharedValue(0);\n const animationsEnabled = props.animated !== false;\n const navState = createNavigatorState({\n routes,\n initial,\n progress: animationsEnabled ? progressSv : undefined,\n });\n\n defineProvide(useNav, () => navState.nav);\n defineProvide(useNavRoutes, () => navState.routes);\n const edgeSwipeEnabled = props.edgeSwipeEnabled !== false;\n defineProvide(useNavInternals, () => ({\n progress: animationsEnabled ? progressSv : null,\n beginBackGesture: navState._gesture.beginBackGesture,\n commitBackGesture: navState._gesture.commitBackGesture,\n cancelBackGesture: navState._gesture.cancelBackGesture,\n edgeSwipeEnabled,\n screens: navState._screens,\n }));\n\n return () => slots.default?.();\n});\n","/**\n * Logical screen dimensions (in dp) read from `lynx.SystemInfo` at module\n * load. Falls back to typical phone values if SystemInfo isn't available —\n * module load happens BG-side after the bundle initializes, by which time\n * `lynx.SystemInfo` is populated, so the fallback only fires in tests /\n * SSR / non-Lynx hosts.\n *\n * Used by:\n * - `<ScreenContainer>` for the slide-from-right (translateX) and\n * slide-from-bottom (translateY, modal) transform output ranges.\n * - `<EdgeBackHandle>` for the gesture commit threshold (`dx / width`).\n *\n * Both must agree, otherwise the commit threshold and the animation\n * geometry won't line up. Single shared module avoids drift.\n */\n\ndeclare const lynx:\n | {\n SystemInfo?: {\n pixelWidth?: number;\n pixelHeight?: number;\n pixelRatio?: number;\n };\n }\n | undefined;\n\nfunction readDp(prop: 'pixelWidth' | 'pixelHeight', fallback: number): number {\n try {\n const info = typeof lynx !== 'undefined' ? lynx?.SystemInfo : undefined;\n const px = info?.[prop];\n const pr = info?.pixelRatio || 1;\n if (typeof px === 'number' && px > 0) {\n return Math.round(px / pr);\n }\n } catch {\n // Lynx globals not present (test env / SSR) — use fallback.\n }\n return fallback;\n}\n\nexport const SCREEN_WIDTH = readDp('pixelWidth', 400);\nexport const SCREEN_HEIGHT = readDp('pixelHeight', 800);\n","/**\n * Pure layer-plan computation for `<Stack>`'s render.\n *\n * Given (stack, transition, progress), produces an ordered list of\n * `Layer`s — each is an entry to render plus an optional transform\n * spec for animation. The Stack render emits one absolutely-positioned\n * `<view>` per layer, stacked bottom-to-top in document order.\n *\n * Why this is its own module: the layer-selection logic is the only\n * non-obvious part of the navigator's render path, and the rules are\n * easier to read (and unit-test) as a pure function over the\n * navigator's state than as inline render branches.\n *\n * Rules:\n *\n * - **Idle (no transition).** Render the topmost non-overlay entry\n * as the base, plus every overlay entry above it. Overlays\n * (`modal` / `fullScreen` / `transparent-modal`) keep their\n * underneath mounted; cards replace their underneath in the base\n * layer.\n *\n * - **Card transition.** Two layers: the underneath entry (animated\n * with the parallax-card-underneath spec) and the top entry\n * (animated with the slide-in-from-right spec). After the\n * transition completes, the idle rule kicks in — the underneath\n * unmounts because the new top becomes the sole base.\n *\n * - **Overlay transition.** The full idle layer stack up through the\n * underneath entry stays static (no transform). The animated top\n * is the only layer with a transform. After the transition, the\n * overlay either joins the static idle stack (push) or unmounts\n * (pop).\n *\n * The Layer.key for the Stack render is\n * `layer-${entry.key}-${animVariant(layer.animation)}`. The variant\n * suffix forces a remount when an entry transitions from animated to\n * static (or vice versa) — `useAnimatedStyle` can't re-bind mid-life,\n * so we get a fresh `useAnimatedStyle` call per animation state.\n * Modal underneath layers never animate, so they stay statically\n * keyed across the modal lifecycle and their state (per-tab Stack,\n * scroll, in-flight inputs) survives.\n */\nimport type { SharedValue } from '@sigx/lynx';\nimport { SCREEN_HEIGHT, SCREEN_WIDTH } from './screen-width';\nimport type {\n Presentation,\n StackEntry,\n TransitionKind,\n TransitionState,\n} from '../types';\n\nconst PARALLAX_FACTOR = 0.3;\n\nexport type LayerAnimation = {\n axis: 'translateX' | 'translateY';\n inputRange: readonly [number, number];\n outputRange: readonly [number, number];\n progress: SharedValue<number>;\n};\n\nexport interface Layer {\n /** The entry whose component renders inside this layer. */\n readonly entry: StackEntry;\n /** When non-null, the layer's host view binds a `useAnimatedStyle` mapper. */\n readonly animation: LayerAnimation | null;\n}\n\nexport function isOverlayPresentation(p: Presentation): boolean {\n return p === 'modal' || p === 'fullScreen' || p === 'transparent-modal';\n}\n\n/**\n * Suffix used in a layer's render key. Stable for the layer's\n * lifetime (same entry, same animation kind) and changes when the\n * animation transitions on/off so the Layer remounts and rebinds.\n */\nexport function animationVariant(animation: LayerAnimation | null): string {\n if (!animation) return 'static';\n // Output range alone identifies the transition shape — different\n // animations (card-top vs card-underneath vs overlay-top, push vs\n // pop) all land on different range tuples.\n return `${animation.axis}:${animation.outputRange[0]}->${animation.outputRange[1]}`;\n}\n\n/**\n * Card-presentation transition transforms. `role='top'` is the entry\n * being pushed/popped; `role='underneath'` is the one parallaxing.\n */\nfunction cardAnimation(\n role: 'top' | 'underneath',\n kind: TransitionKind,\n progress: SharedValue<number>,\n): LayerAnimation {\n if (kind === 'push') {\n if (role === 'top') {\n return { axis: 'translateX', inputRange: [0, 1], outputRange: [SCREEN_WIDTH, 0], progress };\n }\n return { axis: 'translateX', inputRange: [0, 1], outputRange: [0, -PARALLAX_FACTOR * SCREEN_WIDTH], progress };\n }\n // pop\n if (role === 'top') {\n return { axis: 'translateX', inputRange: [0, 1], outputRange: [0, SCREEN_WIDTH], progress };\n }\n return { axis: 'translateX', inputRange: [0, 1], outputRange: [-PARALLAX_FACTOR * SCREEN_WIDTH, 0], progress };\n}\n\n/**\n * Overlay-presentation transition transform for the animated top.\n * The underneath of an overlay transition does not animate (modal\n * doesn't reposition its background); we render it as a static layer\n * instead, so this function only produces the top's transform.\n */\nfunction overlayTopAnimation(\n kind: TransitionKind,\n progress: SharedValue<number>,\n): LayerAnimation {\n if (kind === 'push') {\n return { axis: 'translateY', inputRange: [0, 1], outputRange: [SCREEN_HEIGHT, 0], progress };\n }\n return { axis: 'translateY', inputRange: [0, 1], outputRange: [0, SCREEN_HEIGHT], progress };\n}\n\n/**\n * Compute the visible-layer list for one render of `<Stack>`. Pure —\n * unit-testable independently of the renderer.\n */\nexport function computeLayers(\n stack: readonly StackEntry[],\n transition: TransitionState | null,\n progress: SharedValue<number> | null,\n): Layer[] {\n if (!transition) {\n // Idle: topmost non-overlay base + any overlays above it.\n let baseIdx = stack.length - 1;\n while (baseIdx > 0 && isOverlayPresentation(stack[baseIdx].presentation)) {\n baseIdx -= 1;\n }\n return stack.slice(baseIdx).map((entry) => ({ entry, animation: null }));\n }\n\n // A transition is in flight. `progress` may still be null when\n // animations are disabled — produce static layers in that case\n // (the animation never plays; the transition timer just ticks).\n const isOverlay = isOverlayPresentation(transition.topEntry.presentation);\n if (!isOverlay) {\n // Card transition: just the two participating entries, both\n // animated (parallax underneath + slide top).\n return [\n {\n entry: transition.underneathEntry,\n animation: progress ? cardAnimation('underneath', transition.kind, progress) : null,\n },\n {\n entry: transition.topEntry,\n animation: progress ? cardAnimation('top', transition.kind, progress) : null,\n },\n ];\n }\n\n // Overlay transition: render the full idle layer stack up through\n // the underneath entry (all static — they don't animate) plus the\n // animated top.\n const underneathIdx = stack.findIndex(\n (e) => e.key === transition.underneathEntry.key,\n );\n // If the underneath isn't in the stack (e.g. mid-pop where the\n // stack mutation already removed an entry), fall back to the\n // current top of the stack.\n const lastStaticIdx = underneathIdx >= 0 ? underneathIdx : stack.length - 1;\n\n let baseIdx = lastStaticIdx;\n while (baseIdx > 0 && isOverlayPresentation(stack[baseIdx].presentation)) {\n baseIdx -= 1;\n }\n\n const staticLayers: Layer[] = stack\n .slice(baseIdx, lastStaticIdx + 1)\n .map((entry) => ({ entry, animation: null }));\n\n return [\n ...staticLayers,\n {\n entry: transition.topEntry,\n animation: progress ? overlayTopAnimation(transition.kind, progress) : null,\n },\n ];\n}\n","import {\n component,\n Gesture,\n runOnBackground,\n useGestureDetector,\n useMainThreadRef,\n type MainThread,\n} from '@sigx/lynx';\nimport { withTiming } from '@sigx/lynx-motion';\nimport { useNavInternals } from '../hooks/use-nav-internal';\nimport { SCREEN_WIDTH } from '../internal/screen-width';\n\n/**\n * Edge-pan recognizer for iOS-style swipe-back. Mounts as an absolutely-\n * positioned 20px-wide strip on the left edge of the active screen; only\n * exists when `nav.canGoBack && !transition`.\n *\n * `Gesture.Pan().minDistance(MIN_DISTANCE)` lets quick taps pass through to\n * whatever's behind the strip (back button, screen header, etc.). Only\n * horizontal drags past the threshold activate the gesture.\n *\n * MT/BG split:\n * - All gesture handlers run on MT. They write `progress.current.value`\n * directly per frame (no per-frame bridge crossing) and dispatch\n * `runOnBackground(...)` only at start/commit/cancel — three BG hops\n * per gesture max.\n * - The transition state machine on BG mounts the underneath\n * `<ScreenContainer>` once `beginBackGesture` lands; the gesture's\n * in-flight progress writes are picked up the moment the binding\n * registers (Phase 0.5 polish: pre-mount underneath when canGoBack to\n * eliminate the brief pre-mount latency).\n *\n * Implementation notes (matching `<Draggable>`):\n * - Single `useMainThreadRef` holding an object — primitive refs don't\n * survive worklet capture cleanly in some Lynx versions, while object\n * refs do (the worklet runtime resolves the ref via the\n * `_workletRefMap`).\n * - `e: any` rather than `e: unknown` — type annotations are erased, but\n * SWC's worklet transform has been observed to behave better with the\n * looser annotation. Keeps us aligned with Draggable verbatim.\n * - Empty `onBegin`: load-bearing on iOS — without a registered onBegin\n * callback, `LynxPanGestureHandler` skips the begin path and onStart/\n * onEnd never fire (per Draggable's notes).\n */\n\n/** Fraction of screen width past which a release commits the back nav. */\nconst COMMIT_TRANSLATION = 0.33;\n/** px/sec horizontal speed past which a release commits, regardless of distance. */\nconst COMMIT_VELOCITY = 300;\n/** Width of the touchable strip on the left edge of every screen. */\nconst EDGE_ZONE_WIDTH = 20;\n/** Minimum movement before the gesture activates (lets taps pass through). */\nconst MIN_DISTANCE = 8;\nconst SNAP_DURATION_SEC = 0.18;\n/**\n * Pre-computed milliseconds for the BG-side `setTimeout`. Module-level so\n * it's in scope for both the MT worklet (`withTiming` argument) and the BG\n * callback wrapped by `runOnBackground` (`setTimeout` argument). Locals\n * declared inside an MT worklet body are MT-only — the BG callback's\n * closure can't see them, hence \"ReferenceError: snapMs is not defined\".\n */\nconst SNAP_DURATION_MS = Math.round(SNAP_DURATION_SEC * 1000);\n\nexport const EdgeBackHandle = component(() => {\n const ref = useMainThreadRef<MainThread.Element | null>(null);\n // Per-gesture transient state — captured as a plain closure object\n // rather than a `useMainThreadRef`. Lynx's SWC worklet transform deep-\n // copies plain objects into `_c` once at register time; mutations on MT\n // persist across calls because the same `_c` is bound for the lifetime\n // of the gesture registration. Using a `useMainThreadRef` here was\n // crashing on iOS with `cannot read property 'current' of undefined`\n // — the resolved-ref capture path looked up an empty\n // `_workletRefMap` entry under a race I haven't fully tracked down.\n // Plain object avoids that path entirely.\n const state = {\n startPageX: 0,\n prevPageX: 0,\n prevTime: 0,\n velocity: 0,\n };\n\n const internals = useNavInternals();\n const progress = internals.progress;\n const beginBackGesture = internals.beginBackGesture;\n const commitBackGesture = internals.commitBackGesture;\n const cancelBackGesture = internals.cancelBackGesture;\n\n const pan = Gesture.Pan()\n .minDistance(MIN_DISTANCE)\n .onBegin(() => {\n 'main thread';\n })\n .onStart((e: any) => {\n 'main thread';\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n state.startPageX = pageX;\n state.prevPageX = pageX;\n state.prevTime = Date.now();\n state.velocity = 0;\n runOnBackground(() => {\n beginBackGesture();\n })();\n })\n .onUpdate((e: any) => {\n 'main thread';\n if (!progress) return;\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n const dx = pageX - state.startPageX;\n const prog = Math.max(0, Math.min(1, dx / SCREEN_WIDTH));\n progress.current.value = prog;\n\n const now = Date.now();\n const dt = now - state.prevTime;\n if (dt > 0) {\n state.velocity =\n ((pageX - state.prevPageX) / dt) * 1000;\n }\n state.prevPageX = pageX;\n state.prevTime = now;\n })\n .onEnd((e: any) => {\n 'main thread';\n if (!progress) return;\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n const dx = pageX - state.startPageX;\n const fraction = dx / SCREEN_WIDTH;\n const commit =\n fraction > COMMIT_TRANSLATION ||\n state.velocity > COMMIT_VELOCITY;\n\n if (commit) {\n withTiming(progress, 1, { duration: SNAP_DURATION_SEC });\n runOnBackground(() => {\n setTimeout(() => commitBackGesture(), SNAP_DURATION_MS);\n })();\n } else {\n withTiming(progress, 0, { duration: SNAP_DURATION_SEC });\n runOnBackground(() => {\n setTimeout(() => cancelBackGesture(), SNAP_DURATION_MS);\n })();\n }\n });\n\n useGestureDetector(ref, pan);\n\n return () => (\n <view\n main-thread:ref={ref}\n style={{\n position: 'absolute',\n top: '0',\n left: '0',\n width: `${EDGE_ZONE_WIDTH}px`,\n bottom: '0',\n }}\n />\n );\n});\n","import { component, defineProvide, onUnmounted, type Define } from '@sigx/lynx';\nimport {\n useCurrentEntry,\n useCurrentEntryOptional,\n useNavInternals,\n useScreenRegistry,\n} from '../hooks/use-nav-internal';\nimport { createScreenRegistry } from '../internal/screen-registry';\nimport type { StackEntry } from '../types';\n\ntype EntryScopeProps =\n & Define.Prop<'entry', StackEntry, true>\n & Define.Slot<'default'>;\n\n/**\n * Provider wrapper for a single screen mount.\n *\n * `<Stack>` and `<ScreenContainer>` instantiate this around each route\n * component so calls to `useIsFocused()` / `useFocusEffect()` /\n * `<Screen>` inside that screen resolve through `useCurrentEntry()` and\n * `useScreenRegistry()` to the entry it was rendered for. Without this\n * wrapper there'd be no per-screen way to know \"which stack entry am I?\"\n * — the navigator only knows what's currently on top.\n *\n * Also allocates a fresh `ScreenRegistry` per entry and publishes it to\n * the navigator's cross-entry registry map, so persistent chrome (HeaderBar\n * / TabBar — later slices) can read the focused entry's options + slot\n * fills without remounting itself.\n *\n * Renders the default slot directly; no extra layout element is inserted,\n * so this is layout-neutral for the screen it wraps.\n */\nexport const EntryScope = component<EntryScopeProps>(({ props, slots }) => {\n const internals = useNavInternals();\n const registry = createScreenRegistry(props.entry);\n internals.screens.register(registry);\n onUnmounted(() => {\n // Pass the registry instance — `unregister` is identity-checked,\n // so this is a no-op when a newer EntryScope has already taken\n // over the same entry key (e.g. at the transition→idle handoff\n // where the reconciler mounts the new EntryScope before\n // unmounting the old).\n internals.screens.unregister(registry);\n });\n defineProvide(useCurrentEntry, () => props.entry);\n defineProvide(useCurrentEntryOptional, () => props.entry);\n defineProvide(useScreenRegistry, () => registry);\n return () => slots.default?.();\n});\n","/**\n * `<Layer>` — one row in `<Stack>`'s layered render. Absolutely-\n * positioned host view that fills the Stack's relative wrapper, with\n * an optional MT-bound `translateX` / `translateY` animation driven\n * by a `SharedValue<number>` from the navigator's transition state.\n *\n * `<Stack>` emits one `<Layer>` per entry returned by\n * `computeLayers(...)`. Layer.key in the parent is\n * `layer-${entry.key}-${animationVariant(animation)}` so that:\n *\n * - The same entry under the same animation state is preserved across\n * renders (modal underneath stays mounted through the modal\n * lifecycle; per-tab Stack state survives).\n * - An entry transitioning between animated and static (e.g. a card\n * top after its push transition completes) remounts so the\n * `useAnimatedStyle` binding can be rebound — the underlying\n * `useAnimatedStyle` is set-once at setup and can't switch its\n * mapper at runtime.\n *\n * Layouts:\n * - Host view is `position: absolute; top/right/bottom/left: 0;\n * display: flex; flexDirection: column` so descendants that\n * flex-fill (SafeAreaView, daisyui screens) get a sized parent.\n * - No background. Screens own their own surface colour (typically\n * via a daisy `bg-base-*` class on the screen body).\n */\nimport {\n component,\n useMainThreadRef,\n useAnimatedStyle,\n type ComponentFactory,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\nimport { Suspense, isLazyComponent } from '@sigx/lynx';\nimport type { LayerAnimation } from '../internal/layer-plan';\nimport type { RouteMap, StackEntry } from '../types';\nimport { EntryScope } from './EntryScope';\n\nexport type LayerProps =\n & Define.Prop<'entry', StackEntry, true>\n & Define.Prop<'routes', RouteMap, true>\n /** When set, the host view animates per the transform spec. */\n & Define.Prop<'animation', LayerAnimation | null, false>;\n\nexport const Layer = component<LayerProps>(({ props }) => {\n const ref = useMainThreadRef<MainThread.Element | null>(null);\n // `useAnimatedStyle` binds once at setup. Calling it conditionally\n // is safe because setup runs once per mount and props.animation\n // never changes for a given Layer instance — animation changes\n // re-key the Layer at the parent, forcing a fresh mount.\n if (props.animation) {\n const a = props.animation;\n useAnimatedStyle(ref, a.progress, a.axis, {\n inputRange: [a.inputRange[0], a.inputRange[1]],\n outputRange: [a.outputRange[0], a.outputRange[1]],\n });\n }\n\n return () => {\n const route = props.routes[props.entry.route];\n if (!route) return null;\n const Comp = route.component as unknown as ComponentFactory<\n Record<string, unknown>,\n unknown,\n unknown\n >;\n if (typeof Comp !== 'function') return null;\n const entryParams = props.entry.params as Record<string, unknown>;\n const body = isLazyComponent(Comp) && route.fallback\n ? (\n <Suspense fallback={route.fallback as never}>\n <Comp {...entryParams} />\n </Suspense>\n )\n : <Comp {...entryParams} />;\n return (\n <view\n main-thread:ref={ref}\n style={{\n position: 'absolute',\n top: '0',\n left: '0',\n right: '0',\n bottom: '0',\n display: 'flex',\n flexDirection: 'column',\n }}\n >\n <EntryScope key={props.entry.key} entry={props.entry}>\n {body}\n </EntryScope>\n </view>\n );\n };\n});\n","/**\n * `<Tabs>` — Lynx tab navigator.\n *\n * Usage:\n *\n * ```tsx\n * <NavigationRoot routes={routes} initialRoute=\"root\">\n * <Stack />\n * </NavigationRoot>\n *\n * // The route \"root\" component renders:\n * <Tabs initialTab=\"feed\">\n * <Tabs.Screen name=\"feed\" icon={<FeedIcon />} label=\"Feed\">\n * <Stack initialRoute=\"feedHome\" />\n * </Tabs.Screen>\n * <Tabs.Screen name=\"me\" icon={<MeIcon />} label=\"Profile\">\n * <Stack initialRoute=\"profileHome\" />\n * </Tabs.Screen>\n * <TabBar />\n * </Tabs>\n * ```\n *\n * Tab bodies stay mounted across switches (the inactive ones render with\n * `display: 'none'`), so each tab's nested `<Stack>` keeps its history when\n * the user flips back to it. The active tab is reactive via `useTabs()`.\n *\n * Per-tab stacks: each `<Tabs.Screen>` can host a `<Stack initialRoute=\"…\">`\n * which mints its own navigator. `useNav()` inside that subtree resolves to\n * the tab's stack, so `nav.push('card-route', …)` stays inside the tab.\n * Routes presented as `modal` / `fullScreen` / `transparent-modal` escalate\n * up `nav.parent` to the root navigator automatically — they overlay the\n * tabs UI (TabBar included) and dismiss back into the originating tab.\n */\nimport {\n component,\n compound,\n defineInjectable,\n defineProvide,\n onUnmounted,\n signal,\n untrack,\n type Define,\n type JSXElement,\n type Signal,\n} from '@sigx/lynx';\n\n/** Metadata about a registered `<Tabs.Screen>`. */\nexport interface TabInfo {\n /** Stable tab id, used by `setActive`. */\n readonly name: string;\n /** Optional icon node — passed through to the default tab bar. */\n readonly icon?: JSXElement;\n /** Optional human-readable label. Defaults to `name`. */\n readonly label?: string;\n /**\n * Accessibility label announced by screen readers. Falls back to\n * `label`, then `name`. Surfaced as `accessibility-label` on the\n * default `<TabBar>` button.\n */\n readonly accessibilityLabel?: string;\n}\n\n/** Reactive controller exposed by `useTabs()`. */\nexport interface TabsNav {\n /** Currently-active tab name. Reactive — accessing inside render/effect tracks. */\n readonly active: string;\n /** Switch the active tab. Triggers reactive updates in any consumer. */\n setActive(name: string): void;\n /** Snapshot of registered tabs in registration order. Reactive. */\n readonly tabs: ReadonlyArray<TabInfo>;\n}\n\n/**\n * Access the enclosing Tabs navigator. Throws when called outside `<Tabs>`.\n */\nexport const useTabs = defineInjectable<TabsNav>(() => {\n throw new Error(\n '[lynx-navigation] useTabs() called outside of a <Tabs> component.',\n );\n});\n\n/**\n * Internal registrar used by `<Tabs.Screen>` to announce itself to the\n * parent `<Tabs>`. Splitting registration off the public `useTabs()` keeps\n * the public surface read-only.\n */\ninterface TabsRegistrar {\n register(info: TabInfo): void;\n unregister(name: string): void;\n /** Reactive list — mirrors `TabsNav.tabs`, used by `<Tabs.Screen>` to\n * decide whether it's the active tab. */\n readonly tabs: Signal<TabInfo[]>;\n readonly activeSignal: Signal<{ value: string | null }>;\n}\n\nconst useTabsRegistrar = defineInjectable<TabsRegistrar>(() => {\n throw new Error(\n '[lynx-navigation] <Tabs.Screen> rendered outside a <Tabs> component.',\n );\n});\n\n/**\n * @internal\n * Provided by each `<Tabs.Screen>` so a nested `<Stack initialRoute>` can\n * discover *which* tab it's hosted by, and gate its focus state on that\n * tab being active. Throws when called outside a `<Tabs.Screen>` body so\n * the gate degrades to \"always active\" via the caller's try/catch.\n */\nexport const useTabScreenName = defineInjectable<string>(() => {\n throw new Error(\n '[lynx-navigation] useTabScreenName() called outside a <Tabs.Screen> body.',\n );\n});\n\ntype TabsProps =\n & Define.Prop<'initialTab', string>\n & Define.Slot<'default'>;\n\nconst _Tabs = component<TabsProps>(({ props, slots }) => {\n // Tabs are stored as a deeply-reactive proxy signal so `tabs` consumers\n // re-render when registration changes. `activeSignal` uses the wrapped\n // `{value}` pattern so we can write a `string | null` without the\n // proxy treating the inner string as an object.\n const tabs = signal<TabInfo[]>([]);\n const activeSignal: Signal<{ value: string | null }> = signal({\n value: props.initialTab ?? null,\n });\n\n const registrar: TabsRegistrar = {\n register(info) {\n // Wrap in untrack so registration writes inside `<Tabs.Screen>`'s\n // setup phase don't notify the same setup effect that issued them\n // — sigx's setup runs in a tracked scope by default.\n untrack(() => {\n const idx = tabs.findIndex((t) => t.name === info.name);\n if (idx === -1) tabs.push(info);\n else tabs[idx] = info;\n if (activeSignal.value === null) {\n activeSignal.value = info.name;\n }\n });\n },\n unregister(name) {\n untrack(() => {\n const idx = tabs.findIndex((t) => t.name === name);\n if (idx !== -1) tabs.splice(idx, 1);\n if (activeSignal.value === name) {\n activeSignal.value = tabs[0]?.name ?? null;\n }\n });\n },\n tabs,\n activeSignal,\n };\n\n const nav: TabsNav = {\n get active() {\n // Empty-tabs state is rare in practice (no <Tabs.Screen> yet) but\n // possible during initial render; expose '' rather than null so\n // consumers can compare strings without narrowing.\n return activeSignal.value ?? '';\n },\n setActive(name) {\n // Silently ignore unknown names rather than writing them and\n // hiding every tab body. Surfacing as a no-op gives consumers a\n // predictable failure mode for typos / dynamic name sources.\n if (!tabs.some((t) => t.name === name)) return;\n activeSignal.value = name;\n },\n get tabs() {\n return tabs;\n },\n };\n\n defineProvide(useTabs, () => nav);\n defineProvide(useTabsRegistrar, () => registrar);\n\n return () => slots.default?.();\n});\n\ntype TabsScreenProps =\n & Define.Prop<'name', string, true>\n & Define.Prop<'icon', JSXElement>\n & Define.Prop<'label', string>\n & Define.Prop<'accessibilityLabel', string>\n & Define.Slot<'default'>;\n\nconst TabsScreen = component<TabsScreenProps>(({ props, slots }) => {\n const registrar = useTabsRegistrar();\n // Capture `name` once at setup. Props is reactive in sigx, but using a\n // changing `name` for an already-registered screen would be ambiguous\n // (rename vs re-register?) — pin it and require callers to remount on\n // identity change. This matches React Navigation's contract.\n const name = props.name;\n registrar.register({\n name,\n icon: props.icon,\n label: props.label,\n accessibilityLabel: props.accessibilityLabel,\n });\n onUnmounted(() => registrar.unregister(name));\n\n // Expose this screen's tab name so a nested `<Stack initialRoute>` body\n // can gate its locally-focused state on `tabs.active === name`.\n defineProvide(useTabScreenName, () => name);\n\n return () => {\n // `display: none` keeps the body mounted so per-tab state survives\n // tab switches. Read activeSignal here so re-activating triggers a\n // re-render with display restored.\n //\n // Flex-fill long-form (`flex-grow/shrink/basis`) instead of\n // `height: '100%'`. The percentage form only resolves against an\n // explicit parent height, which means consumers had to wrap us\n // in a `flexFill + height: '100%'` view to make us visible — and\n // every Lynx app got that wrong (myself included) until we hit\n // it on the showcase. With flex-fill we just take whatever space\n // our parent flex container gives us; the parent only needs to\n // be a flex column with a known height (e.g. SafeAreaView, which\n // now defaults to that).\n const active = registrar.activeSignal.value === name;\n return (\n <view\n style={{\n display: active ? 'flex' : 'none',\n flexDirection: 'column',\n width: '100%',\n flexGrow: 1,\n flexShrink: 1,\n flexBasis: 0,\n minHeight: 0,\n }}\n >\n {slots.default?.()}\n </view>\n );\n };\n});\n\n/**\n * Compound export. `Tabs` is the parent component; `Tabs.Screen` registers\n * an individual tab. Matches the `Screen` / `Screen.Header` shape used\n * elsewhere in this package and the daisyui `Modal` / `Modal.Header`\n * convention.\n */\nexport const Tabs = compound(_Tabs, {\n Screen: TabsScreen,\n});\n","import {\n component,\n defineProvide,\n effect,\n onUnmounted,\n untrack,\n useSharedValue,\n type Define,\n} from '@sigx/lynx';\nimport { createNavigatorState } from '../navigator/core';\nimport { useNav, type Nav } from '../hooks/use-nav';\nimport {\n useCurrentEntry,\n useNavInternals,\n useNavRoutes,\n type NavInternals,\n} from '../hooks/use-nav-internal';\nimport type { Presentation, StackEntry } from '../types';\nimport { animationVariant, computeLayers, isOverlayPresentation } from '../internal/layer-plan';\nimport { EdgeBackHandle } from './EdgeBackHandle';\nimport { Layer } from './Layer';\nimport { useTabScreenName, useTabs } from './Tabs';\n\ntype StackProps =\n /**\n * Mint a nested navigator with this route at its base. When set, the\n * `<Stack>` becomes the owner of a new `NavigatorState` and provides\n * `useNav` / `useNavInternals` / `useNavRoutes` to its subtree, so\n * `nav.push('card-route', …)` from inside the stack stays *inside* it\n * (e.g. for per-tab stacks). Routes presented as `modal` / `fullScreen` /\n * `transparent-modal` automatically escalate to the parent navigator\n * via `nav.parent`, walking up until they reach the root — so modals\n * still overlay the whole app.\n *\n * Omit to render the *enclosing* navigator's stack (the default — this\n * is how `<NavigationRoot> → <Stack />` works).\n */\n & Define.Prop<'initialRoute', string>\n /** Initial params for the nested-stack base entry. */\n & Define.Prop<'initialParams', Record<string, unknown>>\n /** Initial search for the nested-stack base entry. */\n & Define.Prop<'initialSearch', Record<string, unknown>>\n /**\n * Optional chrome rendered *above* the active screen, **inside this\n * Stack's nav scope**. The intended use is `<Header />`, which needs\n * to resolve `useNav()` to the per-stack nav (not the enclosing one)\n * so it can react to pushes inside this stack — e.g. show a back\n * button when a card is pushed onto a per-tab stack.\n *\n * Without this, a `<Header />` placed as a sibling of `<Stack>`\n * would see the enclosing nav and never update when pushes happen\n * inside the nested stack.\n */\n & Define.Slot<'default'>;\n\nlet _nestedKeyCounter = 0;\n\n/**\n * Stack navigator — renders the topmost stack entry's component at rest, or\n * the top + underneath entries during a transition.\n *\n * Two modes:\n *\n * **Bound** (no `initialRoute`): renders the enclosing navigator's stack.\n * This is the shape used directly under `<NavigationRoot>` and is what\n * single-stack apps want.\n *\n * **Nested-owner** (`initialRoute=\"…\"`): mints a fresh `NavigatorState` with\n * its own progress `SharedValue` and edge-back gesture, and provides\n * `useNav` / `useNavInternals` / `useNavRoutes` to its subtree. `useNav()`\n * inside this stack returns the nested nav; `nav.parent` points to the\n * enclosing one. Per-tab stacks are the canonical use case:\n *\n * ```tsx\n * <Tabs initialTab=\"trips\">\n * <Tabs.Screen name=\"trips\"><Stack initialRoute=\"tripsHome\" /></Tabs.Screen>\n * <Tabs.Screen name=\"map\"><Stack initialRoute=\"mapHome\" /></Tabs.Screen>\n * </Tabs>\n * ```\n *\n * Modal/fullScreen pushes escalate up the parent chain automatically — so\n * `nav.push('newTrip')` from inside Trips (where `newTrip` is `modal`)\n * walks to root and overlays the whole UI. `replace` stays strictly local\n * (asymmetric with `push`) so a modal `replace` never wipes the root stack.\n *\n * **Render strategy.** Stack always emits the same JSX shape — a\n * relative wrapper containing one `<Layer>` per entry returned by\n * `computeLayers(stack, transition, progress)`. Each Layer is an\n * absolutely-positioned host view with optional MT-bound translate\n * animation. The pure layer-plan function decides:\n *\n * - **Idle.** Topmost non-overlay base + any overlays above it. All\n * static (no transform). Overlays (`modal` / `fullScreen` /\n * `transparent-modal`) keep their underneath mounted; cards\n * replace their underneath in the base layer.\n * - **Card transition.** Both top and underneath animate (slide-in\n * + parallax). After settle, idle rules apply — the underneath\n * unmounts because the new top is the sole base.\n * - **Overlay transition.** The full idle layer stack up through\n * the underneath stays static; only the animated top has a\n * transform. After settle, the overlay either joins the static\n * idle stack (push) or unmounts (pop).\n *\n * Layer keys are `layer-${entry.key}-${animationVariant}`. The variant\n * suffix forces a remount when an entry transitions from animated to\n * static (or vice versa) — `useAnimatedStyle` binds once at setup and\n * can't switch its mapper at runtime. Modal underneath layers never\n * animate, so their key is stable across the modal lifecycle and the\n * subtree's state (per-tab Stack navigators, scroll positions,\n * in-flight inputs) survives.\n */\nexport const Stack = component<StackProps>(({ props, slots }) => {\n // Capture enclosing scope's nav + routes + internals BEFORE any of the\n // defineProvide calls below override them for descendants. These are\n // always the \"outer\" values regardless of whether this Stack is bound\n // or nested-owner.\n const parentNav = useNav();\n const routes = useNavRoutes();\n const parentInternals = useNavInternals();\n\n // Decide mode at setup. `props.initialRoute` is captured once — the\n // alternative (reactive switch between bound and nested-owner) would\n // need to dispose and recreate the inner nav, which would lose all\n // pushed state. Reasonable to pin it.\n const initialName = props.initialRoute;\n const isNested = typeof initialName === 'string' && initialName.length > 0;\n\n let nav: Nav;\n let internals: NavInternals;\n\n if (isNested) {\n if (!routes[initialName]) {\n throw new Error(\n `[lynx-navigation] <Stack initialRoute='${initialName}'>: ` +\n `route is not registered. Known routes: ` +\n `${Object.keys(routes).join(', ') || '(none)'}`,\n );\n }\n\n // Host entry — the parent's current top *when this Stack mounts*.\n // Used by the focus chain so the nested nav is only \"locally\n // focused\" while its host entry is still the top of the parent.\n // Wrapped in try/catch because `<Stack initialRoute>` *may* be\n // placed outside an EntryScope (e.g. directly under\n // `<NavigationRoot>`); in that case there's no host-entry gate to\n // apply and we just rely on `parent.isLocallyFocused`.\n let hostEntryKey: string | null = null;\n try {\n hostEntryKey = useCurrentEntry().key;\n } catch {\n hostEntryKey = null;\n }\n\n // Enclosing tab name (if any). Lets the focus chain gate on tab\n // active state — Trips' inner stack reports `isLocallyFocused: false`\n // while the user is on the Map tab, even though it's the top of\n // its own stack.\n let tabName: string | null = null;\n let tabsHandle: ReturnType<typeof useTabs> | null = null;\n try {\n tabName = useTabScreenName();\n tabsHandle = useTabs();\n } catch {\n tabName = null;\n tabsHandle = null;\n }\n\n // Inherit animation enablement from the parent — if the root was\n // created with `animated={false}` (tests), nested stacks should\n // also commit instantly so test assertions don't have to wait on\n // a SharedValue that won't tick.\n const animationsEnabled = parentInternals.progress !== null;\n const progressSv = useSharedValue(0);\n\n const presentation =\n (routes[initialName].presentation ?? 'card') as Presentation;\n // Counter-derived suffix keeps base-entry keys unique across\n // concurrent nested stacks in a tab app. Plain `Math.random` would\n // do but a counter is deterministic for test snapshots.\n _nestedKeyCounter += 1;\n const initial: StackEntry = {\n key: `nested-${initialName}-${_nestedKeyCounter}`,\n route: initialName,\n params: props.initialParams ?? {},\n search: props.initialSearch ?? {},\n state: undefined,\n presentation,\n };\n\n const navState = createNavigatorState({\n routes,\n initial,\n progress: animationsEnabled ? progressSv : undefined,\n parent: parentNav,\n // Start un-focused; the effect below flips this once we observe\n // the parent's current entry / tab-active state.\n initialLocallyFocused: false,\n });\n\n nav = navState.nav;\n internals = {\n progress: animationsEnabled ? progressSv : null,\n beginBackGesture: navState._gesture.beginBackGesture,\n commitBackGesture: navState._gesture.commitBackGesture,\n cancelBackGesture: navState._gesture.cancelBackGesture,\n edgeSwipeEnabled:\n // Gate on animationsEnabled too — if there's no progress\n // SharedValue (e.g. parent is `animated={false}`), the edge\n // swipe gesture would call `beginBackGesture()` with a null\n // progress and leave the stack in an inconsistent state.\n animationsEnabled && parentInternals.edgeSwipeEnabled,\n screens: navState._screens,\n };\n\n // Reactive focus chain: this nav is locally focused iff\n // 1. (no host entry captured) OR parent.current.key === hostEntryKey\n // 2. parent.isLocallyFocused\n // 3. (no enclosing tab) OR tabs.active === tabName\n // Effect re-runs on any of those changing — parent's stack\n // mutating, parent's own focus flipping, or the tab switching.\n const focusRunner = effect(() => {\n const hostMatch =\n hostEntryKey === null || parentNav.current.key === hostEntryKey;\n const parentFocused = parentNav.isLocallyFocused;\n const tabActive =\n tabName === null || tabsHandle === null\n ? true\n : tabsHandle.active === tabName;\n const focused = hostMatch && parentFocused && tabActive;\n // Write outside the read-tracking window — `_setLocallyFocused`\n // bumps a signal that no consumer in *this* setup reads, but\n // it's good hygiene anyway.\n untrack(() => navState._setLocallyFocused(focused));\n });\n\n onUnmounted(() => {\n focusRunner.stop();\n parentNav._children.delete(nav);\n });\n\n defineProvide(useNav, () => nav);\n defineProvide(useNavRoutes, () => routes);\n defineProvide(useNavInternals, () => internals);\n } else {\n nav = parentNav;\n internals = parentInternals;\n }\n\n // Per-stack chrome (slots.default) renders *inside* this Stack's\n // nav scope so a `<Header />` placed there resolves `useNav()` to\n // the per-stack nav. Wrapping the active body in a flex-column\n // with the slot above does that without disturbing layer-fill\n // semantics — the slot takes natural height, the body keeps\n // flex-fill.\n const flexColumnFill = {\n flexGrow: 1,\n flexShrink: 1,\n flexBasis: 0,\n minHeight: 0,\n display: 'flex',\n flexDirection: 'column',\n } as const;\n\n return () => {\n const chrome = slots.default?.();\n const layers = computeLayers(nav.stack, nav.transition, internals.progress);\n\n const renderLayerNode = (layer: typeof layers[number] | undefined) =>\n layer ? (\n <Layer\n key={`layer-${layer.entry.key}-${animationVariant(layer.animation)}`}\n entry={layer.entry}\n routes={routes}\n animation={layer.animation}\n />\n ) : null;\n // sigx's reconciler treats a single array-valued JSX child as\n // one \"slot\": when the array's *length* changes between\n // renders, keyed children inside can be remounted even if\n // their keys are stable. To make stacked-overlay state\n // preservation work (modal A still mounted after modal B\n // pushes on top), each layer is emitted as its own separate\n // JSX child slot rather than as an array. The slots are\n // position-stable across renders — the only thing that\n // changes is a slot turning from `null` to a Layer (mount) or\n // vice versa (unmount). MAX_LAYERS caps the supported stack\n // depth; in practice apps rarely stack more than 2-3 overlays.\n // If you hit the cap, increase the constant — the unrolled\n // shape is just verbose, not algorithmically limited.\n\n // Edge-swipe handle on top, gated on:\n // - `internals.edgeSwipeEnabled` — opt-out flag (also off\n // when the navigator has no progress SharedValue, i.e.\n // animations disabled — no in-flight gesture to animate).\n // - `nav.canGoBack` — something to pop back to.\n // - `!nav.transition` — no animation already running.\n // - The current top is a card (not an overlay). Edge-swipe\n // is the iOS-style horizontal pop gesture for card stacks;\n // using it to dismiss a modal would be the wrong axis +\n // the wrong dismissal semantic.\n //\n // The handle only intercepts touches in the leftmost 20px and\n // ignores small drags, so placing it last (highest z) doesn't\n // disturb screen touches.\n const top = nav.current;\n const edgeHandle = (\n internals.edgeSwipeEnabled\n && nav.canGoBack\n && !nav.transition\n && !isOverlayPresentation(top.presentation)\n )\n ? <EdgeBackHandle key=\"edge-back\" />\n : null;\n\n const body = (\n <view\n style={{\n position: 'relative',\n width: '100%',\n // Flex-fill so the layer container has a real\n // height — `<Layer>`s anchor via `position:\n // absolute; top/right/bottom/left: 0`, which\n // needs a sized relative parent.\n ...flexColumnFill,\n // Clip any animated layer that translates off-\n // screen so the slide doesn't bleed past the\n // Stack's bounds.\n overflow: 'hidden',\n }}\n >\n {renderLayerNode(layers[0])}\n {renderLayerNode(layers[1])}\n {renderLayerNode(layers[2])}\n {renderLayerNode(layers[3])}\n {renderLayerNode(layers[4])}\n {renderLayerNode(layers[5])}\n {renderLayerNode(layers[6])}\n {renderLayerNode(layers[7])}\n {renderLayerNode(layers[8])}\n {renderLayerNode(layers[9])}\n {renderLayerNode(layers[10])}\n {renderLayerNode(layers[11])}\n {renderLayerNode(layers[12])}\n {renderLayerNode(layers[13])}\n {renderLayerNode(layers[14])}\n {renderLayerNode(layers[15])}\n {edgeHandle}\n </view>\n );\n\n if (chrome == null) return body as never;\n return (\n <view style={flexColumnFill}>\n {chrome}\n <view style={flexColumnFill}>{body}</view>\n </view>\n );\n };\n});\n","/**\n * `<Screen>` — declarative per-screen options + slot fills.\n *\n * Usage:\n *\n * ```tsx\n * const ProfileScreen = component(() => () => (\n * <Screen title=\"Profile\" headerShown gestureEnabled>\n * <Screen.HeaderRight>\n * <text bindtap={onEdit}>Edit</text>\n * </Screen.HeaderRight>\n * <view>body…</view>\n * </Screen>\n * ));\n * ```\n *\n * `<Screen>` itself renders its `default` slot inline — so the body lives\n * where you'd expect with no extra layout wrapper. The sub-components\n * (`Screen.Header`, `Screen.HeaderLeft`, `Screen.HeaderRight`,\n * `Screen.TabBarItem`) render `null` and write into the entry's\n * `ScreenRegistry`. The navigator's persistent chrome reads from there.\n *\n * Note: `<Screen.TabBarItem>` registers a scoped slot fill on the entry's\n * `ScreenRegistry`, but the built-in `<TabBar>` doesn't read it yet — the\n * fill is exposed for custom tab-bar renderers (pass `renderTab` and look\n * up the active entry's registry yourself).\n *\n * Sub-component placement inside `<Screen>` is conventional — sigx scopes\n * are by component tree, so they work anywhere under the same EntryScope.\n * Placing them as direct children of `<Screen>` keeps the call site\n * declarative and grep-friendly.\n */\nimport { component, onUnmounted, type Define } from '@sigx/lynx';\nimport { useScreenRegistry } from '../hooks/use-nav-internal';\nimport { mergeOptions, setSlot } from '../internal/screen-registry';\nimport type { ScreenOptions } from '../types';\n\ntype ScreenProps =\n & Define.Prop<'title', string | (() => string)>\n & Define.Prop<'headerShown', boolean>\n & Define.Prop<'gestureEnabled', boolean>\n & Define.Slot<'default'>;\n\nconst ScreenRoot = component<ScreenProps>(({ props, slots }) => {\n const registry = useScreenRegistry();\n // Apply options whenever the component sets up. Only set keys that\n // were actually passed — `mergeOptions` treats `undefined` as \"clear\n // this key\", so building the patch from raw `props.X` would wipe\n // every option a previous `useScreenOptions(...)` (or another `<Screen>`)\n // had set on this same entry.\n const patch: ScreenOptions = {};\n if (props.title !== undefined) patch.title = props.title;\n if (props.headerShown !== undefined) patch.headerShown = props.headerShown;\n if (props.gestureEnabled !== undefined) patch.gestureEnabled = props.gestureEnabled;\n mergeOptions(registry, patch);\n return () => slots.default?.();\n});\n\ntype SimpleSlotProps = Define.Slot<'default'>;\n\n/**\n * Build a sub-component that registers its `default` slot under `name` on\n * the current screen's registry. Unmount removes the fill so navigating\n * away from a screen with a `<Screen.HeaderRight>` clears that action.\n */\nfunction makeSlotFiller(name: 'header' | 'headerLeft' | 'headerRight') {\n return component<SimpleSlotProps>(({ slots }) => {\n const registry = useScreenRegistry();\n setSlot(registry, name, () => slots.default?.());\n onUnmounted(() => setSlot(registry, name, undefined));\n return () => null;\n });\n}\n\nconst Header = makeSlotFiller('header');\nconst HeaderLeft = makeSlotFiller('headerLeft');\nconst HeaderRight = makeSlotFiller('headerRight');\n\n/**\n * `<Screen.TabBarItem>` — scoped slot. The default slot is a function that\n * receives `{ active }`; whatever it returns is the tab-bar item content.\n *\n * Sigx's `Define.Slot<'default', { active: boolean }>` would express this\n * directly on the component, but since `<Screen.TabBarItem>`'s parent\n * (the user's tree, not the navigator) doesn't actually pass `active`, we\n * accept a plain default slot whose body is itself a function. The\n * navigator's TabBar invokes that function with the active flag.\n */\ntype TabBarItemProps = Define.Slot<'default'>;\n\nconst TabBarItem = component<TabBarItemProps>(({ slots }) => {\n const registry = useScreenRegistry();\n setSlot(registry, 'tabBarItem', (ctx) => {\n const out = slots.default?.();\n // Children may be a render function `({active}) => JSX` or plain\n // JSX (in which case `active` is ignored). Normalise to a value.\n if (typeof out === 'function') return (out as (c: typeof ctx) => unknown)(ctx);\n if (Array.isArray(out)) {\n const first = out[0];\n if (typeof first === 'function') return (first as (c: typeof ctx) => unknown)(ctx);\n }\n return out;\n });\n onUnmounted(() => setSlot(registry, 'tabBarItem', undefined));\n return () => null;\n});\n\n/**\n * Compound export. `Screen` is callable as a JSX element and exposes the\n * sub-components as properties (`Screen.Header`, etc.) for the declarative\n * call site shown in the file header.\n */\nexport const Screen = Object.assign(ScreenRoot, {\n Header,\n HeaderLeft,\n HeaderRight,\n TabBarItem,\n});\n","/**\n * `<Header>` — default navigator header chrome.\n *\n * Reads from the currently-focused entry's `ScreenRegistry`:\n *\n * - If `slots.header` is set, render that (full override).\n * - Else render the default layout: headerLeft (back button when\n * `nav.canGoBack`), title (from `options.title`, or the route name as\n * a fallback), headerRight.\n *\n * Persistent: the Header component itself is mounted once near the root and\n * stays mounted across navigations — it reactively switches its content\n * when `nav.current` changes, rather than being remounted per screen. That\n * matters because mounting cost adds to perceived transition latency.\n *\n * Header chrome is opt-in. Consumers place `<Header />` inside\n * `<NavigationRoot>` above `<Stack />`. We don't auto-inject because:\n * - app shells vary (some want the header inside a `<SafeArea>`, some\n * want a custom toolbar, some want no header at all in tabs).\n * - making it opt-in keeps `<Stack>`'s contract narrow.\n */\nimport { component, computed } from '@sigx/lynx';\nimport { useNav } from '../hooks/use-nav';\nimport { useNavInternals } from '../hooks/use-nav-internal';\nimport type { ScreenOptions, ScreenSlotFills, StackEntry } from '../types';\n\n/**\n * Resolve a title (string or getter) to a plain string.\n *\n * Getter is the more general case; the `<Screen title={() => state.value}>`\n * call site is how reactive titles work. A plain string is wrapped in a\n * trivial closure so consumers always handle one shape.\n */\nfunction resolveTitle(t: ScreenOptions['title'], routeName: string): string {\n if (typeof t === 'function') return t();\n if (typeof t === 'string') return t;\n return routeName;\n}\n\n/**\n * Default back-button rendering. Plain `<text>` with a tap handler — apps\n * that want an icon or a custom design override via\n * `<Screen.HeaderLeft>`. Kept minimal because there's no shared icon\n * primitive at the navigation layer.\n */\nconst DefaultBackButton = component<{ onPress: () => void } & {}>(({ props }) => {\n return () => (\n <view\n bindtap={() => props.onPress()}\n accessibility-element={true}\n accessibility-label=\"Back\"\n accessibility-trait=\"button\"\n >\n <text>‹ Back</text>\n </view>\n );\n});\n\nconst DefaultTitle = component<{ text: string } & {}>(({ props }) => {\n return () => (\n <view>\n <text>{props.text}</text>\n </view>\n );\n});\n\n/**\n * Persistent header chrome. Mount once above `<Stack>`; reactively follows\n * the focused entry. No props in v1 — styling is a host-app concern,\n * arrived at through the slot fills.\n */\nexport const Header = component(() => {\n const nav = useNav();\n const internals = useNavInternals();\n\n // Snapshot computeds — each one reads only what it needs so the header\n // doesn't re-run wholesale on every signal touch. The slot-fill thunks\n // captured by `<Screen.Header>` etc. are themselves reactive (they\n // execute on every render of the consumer's tree), so re-running the\n // outer template is enough to pick up downstream updates.\n const currentEntry = computed<StackEntry>(() => nav.current);\n\n const headerSlot = computed<ScreenSlotFills['header'] | undefined>(() => {\n const reg = internals.screens.get(currentEntry.value.key);\n return reg?.slots.header;\n });\n const headerLeftSlot = computed<ScreenSlotFills['headerLeft'] | undefined>(() => {\n const reg = internals.screens.get(currentEntry.value.key);\n return reg?.slots.headerLeft;\n });\n const headerRightSlot = computed<ScreenSlotFills['headerRight'] | undefined>(() => {\n const reg = internals.screens.get(currentEntry.value.key);\n return reg?.slots.headerRight;\n });\n const headerShown = computed<boolean>(() => {\n const reg = internals.screens.get(currentEntry.value.key);\n // Default true — most screens want a header. Opting out is one prop\n // on `<Screen>`.\n return reg?.options.headerShown !== false;\n });\n const titleText = computed<string>(() => {\n const reg = internals.screens.get(currentEntry.value.key);\n return resolveTitle(reg?.options.title, currentEntry.value.route);\n });\n\n return () => {\n if (!headerShown.value) return null;\n // Full-override path: `<Screen.Header>` supplied its own content,\n // we render that and skip the default layout entirely.\n const override = headerSlot.value;\n if (override) return override();\n\n return (\n <view>\n <view>\n {headerLeftSlot.value\n ? headerLeftSlot.value()\n : nav.canGoBack\n ? <DefaultBackButton onPress={() => nav.pop()} />\n : null}\n </view>\n <DefaultTitle text={titleText.value} />\n <view>\n {headerRightSlot.value ? headerRightSlot.value() : null}\n </view>\n </view>\n );\n };\n});\n","/**\n * `<TabBar>` — headless default chrome for `<Tabs>`.\n *\n * Renders the active-tab buttons reading from the enclosing `useTabs()`\n * navigator. Intentionally **unstyled** — this lives in the (theme-less)\n * navigation package, so it ships pure structure + accessibility wiring.\n * Themed chrome belongs in a UI-kit package: see `<NavTabBar />` in\n * `@sigx/lynx-daisyui` for the daisy-themed equivalent.\n *\n * Use this directly only if you want to handle styling yourself via the\n * `renderTab` prop. For a \"looks like a tab bar out of the box\" component,\n * pull `<NavTabBar />` from `@sigx/lynx-daisyui` (or your own UI kit).\n *\n * Customization:\n * - `renderTab`: a function `(info, ctx) => JSX` that fully replaces the\n * default button rendering for each tab. `ctx.active` tells the\n * consumer whether this tab is currently focused; `ctx.onPress`\n * activates the tab. **Recommended** for any visual treatment.\n *\n * Accessibility (baked into the default button — the one structural\n * concern this component keeps):\n * - `accessibility-label` from `info.accessibilityLabel ?? info.label ?? info.name`.\n * - `accessibility-element=\"true\"` so screen readers see the whole pill.\n * - `accessibility-trait=\"button\"` and a `selected` flag on the active\n * one so VoiceOver/TalkBack announces focus state on tab switch.\n */\nimport {\n component,\n type Define,\n type JSXElement,\n} from '@sigx/lynx';\nimport { useTabs, type TabInfo } from './Tabs';\n\n/** Rendering context passed to a `renderTab` consumer. */\nexport interface TabRenderContext {\n /** True when this tab is currently active. Reactive — re-runs render on change. */\n readonly active: boolean;\n /** Activates this tab. Use as a `bindtap` handler on the rendered node. */\n onPress(): void;\n}\n\ntype TabBarProps =\n & Define.Prop<'renderTab', (info: TabInfo, ctx: TabRenderContext) => JSXElement>;\n\n/**\n * Default per-tab button. Plain `<view>` with a `<text>` inside, an\n * `accessibility-*` cluster for screen readers, and a tap handler. No\n * styling beyond a minimal active-state opacity hint — consumers that\n * want branded chrome pass `renderTab` or use a UI-kit-provided tab bar\n * (e.g. `<NavTabBar />` from `@sigx/lynx-daisyui`).\n */\nconst DefaultTabButton = component<\n & Define.Prop<'info', TabInfo, true>\n & Define.Prop<'active', boolean, true>\n & Define.Prop<'onPress', () => void, true>\n>(({ props }) => {\n return () => {\n const label = props.info.label ?? props.info.name;\n const a11y = props.info.accessibilityLabel ?? label;\n return (\n <view\n bindtap={() => props.onPress()}\n accessibility-element={true}\n accessibility-label={a11y}\n accessibility-trait=\"button\"\n accessibility-status={props.active ? 'selected' : undefined}\n style={{ opacity: props.active ? 1 : 0.6 }}\n >\n {props.info.icon ?? null}\n <text>{label}</text>\n </view>\n );\n };\n});\n\nexport const TabBar = component<TabBarProps>(({ props }) => {\n const nav = useTabs();\n return () => {\n // Reading `nav.tabs` and `nav.active` here ties this render to both\n // the registration list and the active signal — switching active or\n // adding/removing a `<Tabs.Screen>` updates the bar reactively.\n const tabs = nav.tabs;\n const active = nav.active;\n const renderer = props.renderTab;\n return (\n <view accessibility-element={false}>\n {tabs.map((info) => {\n const isActive = info.name === active;\n const onPress = () => nav.setActive(info.name);\n if (renderer) {\n return renderer(info, { active: isActive, onPress });\n }\n return (\n <DefaultTabButton\n info={info}\n active={isActive}\n onPress={onPress}\n />\n );\n })}\n </view>\n );\n };\n});\n","/**\n * `<Drawer>` — minimal off-canvas drawer navigator.\n *\n * Usage:\n *\n * ```tsx\n * <NavigationRoot routes={routes}>\n * <Drawer slots={{ sidebar: () => <view><text>Menu</text></view> }}>\n * <Stack />\n * </Drawer>\n * </NavigationRoot>\n * ```\n *\n * `useDrawer()` from inside any descendant gives `{ isOpen, open(), close(),\n * toggle() }`. The sidebar is laid out absolutely on the left and is\n * visible whenever `isOpen` is true.\n *\n * Scope: this slice ships the state primitive + the bare-bones layout.\n * Gesture-driven open (edge swipe from the left) and MTS slide-in are out\n * of scope — the app shell can wrap its sidebar JSX in its own transition.\n *\n * Design note: the sidebar lives in a named slot (`sidebar`) rather than\n * a render-prop or a `<Drawer.Sidebar>` child. Mixing\n * \"register-yourself-as-a-fill\" children with the parent's own visible\n * layout creates a feedback loop in sigx's reactive scope (the parent's\n * render reads the fill, child's setup writes it, parent re-renders,\n * child re-mounts, …). A scoped slot avoids that entirely and keeps the\n * call site declarative.\n *\n * `default` slot is the main content (almost always a `<Stack>`).\n */\nimport {\n component,\n defineInjectable,\n defineProvide,\n signal,\n type Define,\n type Signal,\n} from '@sigx/lynx';\n\n/** Reactive controller returned by `useDrawer()`. */\nexport interface DrawerNav {\n /** True when the drawer is currently visible. Reactive. */\n readonly isOpen: boolean;\n /** Opens the drawer. */\n open(): void;\n /** Closes the drawer. */\n close(): void;\n /** Toggles between open and closed. */\n toggle(): void;\n}\n\n/**\n * Access the enclosing Drawer navigator. Throws when called outside\n * `<Drawer>`.\n */\nexport const useDrawer = defineInjectable<DrawerNav>(() => {\n throw new Error(\n '[lynx-navigation] useDrawer() called outside of a <Drawer> component.',\n );\n});\n\ntype DrawerProps =\n & Define.Prop<'initialOpen', boolean>\n & Define.Slot<'sidebar'>\n & Define.Slot<'default'>;\n\nexport const Drawer = component<DrawerProps>(({ props, slots }) => {\n // `isOpenSig` uses the `{value}` wrapper pattern — sigx's `signal()` of\n // a primitive returns a proxy that requires `.value` reads; wrapping in\n // an object makes the proxy carry a mutable boolean.\n const isOpenSig: Signal<{ value: boolean }> = signal({\n value: props.initialOpen === true,\n });\n\n const nav: DrawerNav = {\n get isOpen() {\n return isOpenSig.value;\n },\n open() {\n isOpenSig.value = true;\n },\n close() {\n isOpenSig.value = false;\n },\n toggle() {\n isOpenSig.value = !isOpenSig.value;\n },\n };\n\n defineProvide(useDrawer, () => nav);\n\n return () => {\n const open = isOpenSig.value;\n return (\n <view style={{ width: '100%', height: '100%' }}>\n {/* Main content fills the whole parent. */}\n <view style={{ width: '100%', height: '100%' }}>\n {slots.default?.()}\n </view>\n\n {/* Sidebar is overlaid; toggled via `display`. Apps that\n want an animated slide-in wrap the sidebar themselves\n — the navigator just controls visibility. */}\n <view\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n bottom: 0,\n display: open ? 'flex' : 'none',\n }}\n >\n {slots.sidebar?.()}\n </view>\n </view>\n );\n };\n});\n","import { component, type Define } from '@sigx/lynx';\nimport { useNav } from '../hooks/use-nav';\nimport { useNavRoutes } from '../hooks/use-nav-internal';\nimport type { RouteId, RouteParams, RouteSearch } from '../register';\nimport type { RoutesWithParams } from '../hooks/use-nav';\n\n/**\n * Per-route conditional props for `<Link>`.\n *\n * Mapped over `RouteId`, then indexed by `RouteId` to flatten into a union.\n * Each branch enforces the `params`-required-iff-route-has-schema rule:\n *\n * - `<Link to=\"profile\" />` → TS error (profile requires params)\n * - `<Link to=\"profile\" params={...} />` → ok\n * - `<Link to=\"home\" />` → ok (home has no params)\n * - `<Link to=\"home\" params={...} />` → TS error (home accepts no params)\n *\n * Same per-route discrimination as `nav.push`, expressed as a JSX-friendly\n * union rather than overloads.\n */\ntype LinkPropsByRoute = {\n [K in RouteId]: K extends RoutesWithParams\n ? { to: K; params: RouteParams<K>; search?: RouteSearch<K> }\n : { to: K; params?: undefined; search?: RouteSearch<K> };\n}[RouteId];\n\n/**\n * Public type for `<Link>`'s props. The conditional `LinkPropsByRoute` carries\n * the typed `to`/`params`/`search` triple; the rest are simple optionals.\n *\n * `children` is declared explicitly because the public type is exposed via a\n * type-cast (so JSX sees a function-shaped signature). The cast strips sigx's\n * built-in `Define.Slot<'default'>` → JSX-children wiring, so we add a\n * permissive `children?` slot ourselves.\n */\nexport type LinkProps = LinkPropsByRoute & {\n /** Use `replace` instead of `push` (no new history entry). */\n replace?: boolean;\n /** Link content rendered inside the tappable container. */\n children?: unknown;\n};\n\n/**\n * Loose internal props used by the component implementation. The runtime\n * doesn't enforce the per-route conditional — that's a TS-only constraint\n * surfaced via the public `LinkProps` type. The cast at the bottom of this\n * file rewires the export to the strict type.\n */\ntype LinkPropsLoose =\n & Define.Prop<'to', string, true>\n & Define.Prop<'params', Record<string, unknown> | undefined>\n & Define.Prop<'search', Record<string, unknown> | undefined>\n & Define.Prop<'replace', boolean>\n & Define.Slot<'default'>;\n\nconst LinkImpl = component<LinkPropsLoose>(({ props, slots }) => {\n const nav = useNav();\n const routes = useNavRoutes();\n\n const handlePress = (): void => {\n const route = props.to;\n const routeDef = routes[route];\n if (!routeDef) {\n // Defensive: prop was typed against the registry, so this shouldn't\n // happen at runtime — but if it does (e.g. a stale Link survived a\n // route removal), surface a clear error rather than crashing on\n // the navigator's lookup.\n throw new Error(\n `[lynx-navigation] <Link to='${route}'>: route is not registered.`,\n );\n }\n const hasParams = !!routeDef.params;\n const action = props.replace ? nav.replace : nav.push;\n // Branch on whether the route declares a params schema so positional\n // args land correctly: routes-with-params shift everything one slot\n // (push/replace overload signatures `(name, params, search?, options?)`\n // vs `(name, search?, options?)`). Calling the wrong shape silently\n // puts `search` into the `options` slot.\n if (hasParams) {\n (action as (n: string, p: unknown, s?: unknown) => void)(\n route,\n props.params,\n props.search,\n );\n } else {\n (action as (n: string, s?: unknown) => void)(route, props.search);\n }\n };\n\n return () => (\n // `bindtap` is correct here because the underlying element is a Lynx\n // native `<view>` — sigx component-level events would use `onPress`.\n <view bindtap={handlePress}>{slots.default?.()}</view>\n );\n}, { name: 'Link' });\n\n/**\n * Declarative navigation. Same typing as `nav.push` — pass `params` only when\n * the route declares a schema. Wraps a `<view>` that fires `nav.push` (or\n * `nav.replace` if `replace` is set) on tap.\n *\n * @example\n * ```tsx\n * <Link to=\"home\">Home</Link>\n * <Link to=\"profile\" params={{ id: '42' }}>View profile</Link>\n * <Link to=\"profile\" params={{ id: '42' }} search={{ tab: 'about' }}>About</Link>\n * <Link to=\"settings\" replace>Settings (no back)</Link>\n * ```\n *\n * The cast widens the inferred prop type from the loose impl to the strict\n * `LinkProps` so JSX usage gets per-route discrimination. Runtime is identical.\n */\nexport const Link = LinkImpl as unknown as (props: LinkProps) => unknown;\n"],"mappings":";;;;;AA8BA,SAAgB,EAAuC,GAAc;CACjE,OAAO;;;;ACuHX,IAAa,IAAS,QAA4B;CAC9C,MAAU,MACN,8FACH;EACH;;;ACvIF,SAAgB,EAA6B,GAA0B;CAEnE,OADY,GACL,CAAI,QAAQ;;;;ACVvB,SAAgB,GAA6B,GAA0B;CAEnE,OADY,GACL,CAAI,QAAQ;;;;AC2BvB,SAAgB,KAAwB;CACpC,IAAM,IAAM,GAAQ;CACpB,QAAgB;EACZ,IAAM,IAAM,EAAY,uBAAuB;GAM3C,IAAI,IAAc;GAGlB,OAAO,OAAO,EAAO,UAAU,OAAO,IAAG;IACrC,KAAK,IAAM,KAAS,EAAO,WACvB,IAAI,EAAM,kBAAkB;KACxB,IAAS;KACT,SAAS;;IAIjB;;GAMJ,IAAI,IAAkB;GACtB,OAAO,IAAK;IACR,IAAI,EAAI,WAEJ,OADA,EAAI,KAAK,EACF;IAEX,IAAM,EAAI;;GAOd,OADA,EAAiB,SAAS,EACnB;IACT;EACF,aAAa,EAAI,QAAQ;GAC3B;;;;AC/DN,SAAgB,GAAa,GAAqD;CAC9E,IAAI,CAAC,GAAQ,OAAO;CACpB,IAAM,IAAO,OAAO,KAAK,EAAO,CAAC,MAAM,EACjC,IAAkB,EAAE;CAC1B,KAAK,IAAM,KAAO,GAAM;EACpB,IAAM,IAAQ,EAAO;EACrB,IAAI,KAAiC,MAAM;EAC3C,IAAM,IAAU,mBAAmB,EAAI;EACvC,IAAI,MAAM,QAAQ,EAAM,EACpB,KAAK,IAAM,KAAQ,GACX,KAA+B,QACnC,EAAM,KAAK,GAAG,EAAQ,GAAG,mBAAmB,GAAgB,EAAK,CAAC,GAAG;OAGzE,EAAM,KAAK,GAAG,EAAQ,GAAG,mBAAmB,GAAgB,EAAM,CAAC,GAAG;;CAG9E,OAAO,EAAM,KAAK,IAAI;;AAW1B,SAAgB,GAAY,GAAkD;CAC1E,IAAM,IAA4C,EAAE;CACpD,IAAI,CAAC,GAAO,OAAO;CAGnB,IAAM,IAAU,EAAM,WAAW,IAAI,GAAG,EAAM,MAAM,EAAE,GAAG;CACzD,IAAI,CAAC,GAAS,OAAO;CACrB,KAAK,IAAM,KAAQ,EAAQ,MAAM,IAAI,EAAE;EACnC,IAAI,CAAC,GAAM;EACX,IAAM,IAAQ,EAAK,QAAQ,IAAI,EACzB,IAAS,MAAU,KAAK,IAAO,EAAK,MAAM,GAAG,EAAM,EACnD,IAAW,MAAU,KAAK,KAAK,EAAK,MAAM,IAAQ,EAAE,EACpD,IAAM,GAAW,EAAO,EACxB,IAAQ,GAAW,EAAS,EAC5B,IAAW,EAAO;EACxB,AAAI,MAAa,KAAA,IACb,EAAO,KAAO,IACP,MAAM,QAAQ,EAAS,GAC9B,EAAS,KAAK,EAAM,GAEpB,EAAO,KAAO,CAAC,GAAU,EAAM;;CAGvC,OAAO;;AAGX,SAAS,GAAgB,GAAoB;CACzC,QAAQ,OAAO,GAAf;EACI,KAAK,UAAU,OAAO;EACtB,KAAK;EACL,KAAK;EACL,KAAK,UACD,OAAO,OAAO,EAAE;EACpB,SAEI,OAAO,KAAK,UAAU,EAAE;;;AAIpC,SAAS,GAAW,GAAmB;CACnC,IAAI;EACA,OAAO,mBAAmB,EAAE,QAAQ,OAAO,IAAI,CAAC;SAC5C;EAGJ,OAAO;;;;;AC7Df,IAAM,IAAW;AAMjB,SAAgB,EAAY,GAAgC;CACxD,IAAI,OAAO,KAAa,UACpB,MAAU,UAAU,qCAAqC,OAAO,IAAW;CAE/E,IAAI,EAAS,WAAW,GACpB,MAAU,MAAM,+CAA+C;CAKnE,IAAM,IAAa,EAAS,WAAW,IAAI,GAAG,IAAW,IAAI,KAEvD,IAAuB,EAAE,EAI3B,IAAY,GACZ,IAAU;CACd,EAAS,YAAY;CACrB,KAAK,IAAI,IAAI,EAAS,KAAK,EAAW,EAAE,MAAM,MAAM,IAAI,EAAS,KAAK,EAAW,EAAE;EAC/E,IAAM,IAAO,EAAE;EACf,IAAI,EAAW,SAAS,EAAK,EACzB,MAAU,MACN,uCAAuC,EAAK,QAAQ,EAAS,GAChE;EAKL,AAHA,EAAW,KAAK,EAAK,EACrB,KAAW,EAAY,EAAW,MAAM,GAAW,EAAE,MAAM,CAAC,EAC5D,KAAW,WACX,IAAY,EAAE,QAAQ,EAAE,GAAG;;CAE/B,KAAW,EAAY,EAAW,MAAM,EAAU,CAAC;CAInD,IAAM,IAAe,EAAQ,SAAS,IAAI,GAAG,EAAQ,MAAM,GAAG,GAAG,GAAG;CAGpE,OAAO;EACH,QAAQ;EACR;EACA,OALc,OAAO,IAAI,EAAa,KAKtC;EACA,OAAO,GAAiD;GACpD,IAAI,IAAM,IACN,IAAI;GACR,EAAS,YAAY;GACrB,KACI,IAAI,IAAI,EAAS,KAAK,EAAW,EACjC,MAAM,MACN,IAAI,EAAS,KAAK,EAAW,EAC/B;IACE,IAAM,IAAO,EAAE,IACT,IAAQ,EAAO;IACrB,IAAI,KAAiC,MACjC,MAAU,MACN,gDAAgD,EAAK,SAAS,EAAS,GAC1E;IAIL,AAFA,KAAO,EAAW,MAAM,GAAG,EAAE,MAAM,EACnC,KAAO,mBAAmB,OAAO,EAAM,CAAC,EACxC,IAAI,EAAE,QAAQ,EAAE,GAAG;;GAGvB,OADA,KAAO,EAAW,MAAM,EAAE,EACnB;;EAEd;;AAGL,IAAM,KAAiB;AACvB,SAAS,EAAY,GAAmB;CACpC,OAAO,EAAE,QAAQ,IAAgB,OAAO;;;;ACnF5C,IAAI,IAAgC;AAWpC,SAAgB,EAAkB,GAAwB;CACtD,IAAU;EAAE;EAAQ,0BAAU,IAAI,KAAK;EAAE;;AAI7C,SAAgB,KAA4B;CACxC,IAAU;;AAId,SAAgB,IAAkC;CAC9C,IAAI,CAAC,GACD,MAAU,MACN,oHACH;CAEL,OAAO;;AAOX,SAAgB,GAAgB,GAAyB,GAAmC;CACxF,IAAM,IAAM,EAAS,OAAO;CAE5B,IADI,CAAC,KACD,CAAC,EAAI,MAAM,OAAO;CACtB,IAAI,IAAW,EAAS,SAAS,IAAI,EAAK;CAK1C,OAJK,MACD,IAAW,EAAY,EAAI,KAAK,EAChC,EAAS,SAAS,IAAI,GAAM,EAAS,GAElC;;;;ACjDX,SAAgB,GACZ,GACA,GACA,GACa;CAEb,IAAM,IAAW,GADA,GACgB,EAAU,EAAU;CACrD,IAAI,CAAC,GAAU,OAAO;CACtB,IAAM,IAAY,EAAS,OAItB,KAAU,EAAE,CAChB,EACK,IAAc,GAAa,EAAO;CACxC,OAAO,EAAY,SAAS,IAAI,GAAG,EAAU,GAAG,MAAgB;;;;ACKpE,SAAgB,EACZ,GACA,GACe;CACf,IAAI,CAAC,GAAQ,OAAO;EAAE,IAAI;EAAM,OAAO;EAAO;CAC9C,IAAM,IAAY,EAAiC,cAAc;CACjE,IAAI,CAAC,GAAU,OAAO;EAAE,IAAI;EAAM,OAAO;EAAO;CAChD,IAAM,IAAS,EAAS,EAAM;CAC9B,IAAI,GAAc,EAAO,EACrB,MAAU,MACN,0IACH;CAQL,OANI,EAAO,WAAW,KAAA,KAAa,EAAO,OAAO,SAAS,IAC/C;EACH,IAAI;EACJ,QAAQ,EAAO,OAAO,KAAK,MAAM,EAAE,QAAQ;EAC9C,GAEE;EAAE,IAAI;EAAM,OAAQ,EAA8B;EAAO;;AAGpE,SAAS,GAAiB,GAAiC;CACvD,OAEO,OAAO,KAAM,cADhB,KAEG,OAAQ,EAAyB,QAAS;;;;ACrCrD,SAAgB,GAAc,GAA0B;CACpD,IAAI,OAAO,KAAQ,YAAY,EAAI,WAAW,GAAG,OAAO;CAKxD,IAAM,EAAE,aAAU,aAAU,GAAkB,EAAI;CAClD,IAAI,CAAC,GAAU,OAAO;CAEtB,IAAM,IAAW,GAAkB,EAC7B,IAAY,GAAY,EAAM;CAEpC,KAAK,IAAM,KAAQ,OAAO,KAAK,EAAS,OAAO,EAAE;EAC7C,IAAM,IAAW,GAAgB,GAAU,EAAK;EAChD,IAAI,CAAC,GAAU;EACf,IAAM,IAAQ,EAAS,MAAM,KAAK,EAAS;EAC3C,IAAI,CAAC,GAAO;EAEZ,IAAM,IAAY,GAAc,GAAU,EAAM,EAC1C,IAAM,EAAS,OAAO,IAEtB,IAAgB,EAAa,EAAI,QAAQ,EAAU;EACzD,IAAI,CAAC,EAAc,IAAI;EACvB,IAAM,IAAgB,EAAa,EAAI,QAAQ,EAAU;EACpD,MAAc,IAEnB,OAAO;GACH,OAAO;GACP,QAAQ,EAAc;GACtB,QAAQ,EAAc;GACtB;GACH;;CAEL,OAAO;;AAGX,SAAS,GACL,GACA,GACsB;CACtB,IAAM,IAA8B,EAAE;CACtC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAS,WAAW,QAAQ,KAAK;EACjD,IAAM,IAAM,EAAM,IAAI;EACtB,IAAI;GACA,EAAI,EAAS,WAAW,MAAM,mBAAmB,EAAI;UACjD;GACJ,EAAI,EAAS,WAAW,MAAM;;;CAGtC,OAAO;;AAGX,SAAS,GAAkB,GAAkD;CAGzE,IAAM,IAAU,EAAI,QAAQ,IAAI,EAC1B,IAAS,KAAW,IAAI,EAAI,MAAM,GAAG,EAAQ,GAAG;CAMtD,IADkB,6BAA6B,KAAK,EAChD,EAAW;EACX,IAAM,IAAS,GAAS,EAAO,EAIzB,IAAO,EAAO,QAAQ,IAAI,EAC1B,IAAQ,KAAQ,IAAI,EAAO,MAAM,IAAO,EAAE,GAAG;EACnD,OAAO;GAAE,UAAU,EAAO;GAAM;GAAO;;CAE3C,IAAM,IAAO,EAAO,QAAQ,IAAI;CAEhC,OADI,MAAS,KAAW;EAAE,UAAU;EAAQ,OAAO;EAAI,GAChD;EAAE,UAAU,EAAO,MAAM,GAAG,EAAK;EAAE,OAAO,EAAO,MAAM,IAAO,EAAE;EAAE;;;;AC/C7E,SAAgB,GAAQ,GAAc,GAAG,GAAuB;CAE5D,IAAM,IADW,GACL,CAAS,OAAO;CAC5B,IAAI,CAAC,GACD,MAAU,MACN,8BAA8B,EAAK,0CACtC;CAOL,IAAM,IAAkB,CAAC,CAAC,EAAI,QAC1B,GACA;CACJ,AAAI,KACA,IAAY,EAAK,IACjB,IAAY,EAAK,OAEjB,IAAY,KAAA,GACZ,IAAY,EAAK;CAGrB,IAAM,IAAgB,EAAa,EAAI,QAAQ,KAAa,EAAE,CAAC;CAC/D,IAAI,CAAC,EAAc,IACf,MAAU,MACN,8BAA8B,EAAK,iCAAiC,EAAc,OAAO,KAAK,KAAK,GACtG;CAEL,IAAM,IAAgB,EAAa,EAAI,QAAQ,KAAa,EAAE,CAAC;CAC/D,IAAI,CAAC,EAAc,IACf,MAAU,MACN,8BAA8B,EAAK,iCAAiC,EAAc,OAAO,KAAK,KAAK,GACtG;CAGL,IAAM,IAAS,EAAc,OACvB,IAAS,EAAc;CAG7B,OAAO;EACH,OAAO;EACC;EACA;EACR,KANQ,GAAS,GAAM,GAAQ,EAM/B;EACH;;AAaL,SAAgB,GAAU,GAA0B;CAChD,OAAO,GAAc,EAAI;;;;AClG7B,IAAa,IAAkB,QAAmC;CAC9D,MAAU,MACN,gJACH;EACH,EAYW,KAA0B,QAC7B,KACT,EASY,IAAe,QAAiC;CACzD,MAAU,MACN,qEACH;EACH,EA8CW,IAAkB,QAAqC;CAChE,MAAU,MACN,qEACH;EACH,EAcW,IAAoB,QAAuC;CACpE,MAAU,MACN,uJACH;EACH;;;ACnCF,SAAgB,GAAc,IAA6B,EAAE,EAAQ;CACjE,IAAM,IAAM,GAAQ,EACd,IAAS,GAAc,EAEvB,KAAY,GAAa,MAAmC;EAC9D,IAAI,EAAK,OAAO;GACZ,EAAK,MAAM,GAAK,EAAI;GACpB;;EAGJ,IAAM,IAAO,GADI,GAAa,GAAK,EAAK,SACjB,CAAS;EAChC,IAAI,CAAC,GAAM;GACP,EAAK,cAAc,EAAI;GACvB;;EAEJ,GAAgB,GAAK,GAAQ,GAAM,EAAK;;CAG5C,QAAgB;EACZ,IAAM,IAAU,EAAQ,eAAe;EACvC,AAAI,KACA,EAAS,GAAS,EAAK,mBAAmB,KAAQ,SAAS,UAAU;EAEzE,IAAM,IAAM,EAAQ,iBAAiB,QAAQ,MAAM,EAAS,EAAE,KAAK,OAAO,CAAC;EAC3E,aAAa,EAAI,QAAQ;GAC3B;;AAWN,SAAgB,GAAa,GAAa,GAA6B;CACnE,IAAI,CAAC,KAAY,EAAS,WAAW,GAAG,OAAO;CAC/C,KAAK,IAAM,KAAU,GACjB,IAAI,EAAI,WAAW,EAAO,EAAE;EACxB,IAAM,IAAO,EAAI,MAAM,EAAO,OAAO;EACrC,OAAO,EAAK,WAAW,IAAI,GAAG,IAAO,IAAI;;CAGjD,OAAO;;AAYX,SAAgB,GACZ,GACA,GACA,GACA,GACI;CACJ,IAAM,IAAM,EAAO,EAAK;CAKxB,IAAI,CAAC,GAAK;CACV,IAAM,IAAY,CAAC,CAAC,EAAI,QAClB,IAAS,MAAS,YAAY,EAAI,UAAU,EAAI;CACtD,AAAI,IACA,EACI,EAAK,OACL,EAAK,QACL,EAAK,OACR,GAED,EAA6C,EAAK,OAAO,EAAK,OAAO;;;;AC9H7E,SAAgB,IAAkC;CAC9C,IAAM,IAAM,GAAQ,EAId,IAAQ,GAAiB,CAAC;CAOhC,OAAO,QAAe,EAAI,QAAQ,QAAQ,KAAS,EAAI,iBAAiB;;AAyB5E,SAAgB,GAAe,GAAqC;CAChE,IAAM,IAAY,GAAc,EAC5B,GACE,IAAS,QAAa;EACxB,IAAM,IAAU,EAAU;EAK1B,IAAI,OAAO,KAAY,YAAY;GAC/B,IAAM,IAAK;GAEX,AADA,IAAU,KAAA,GACV,GAAI;;EAER,AAAI,MACA,IAAU,QAAc,GAAI,CAAC;GAEnC;CACF,QAAkB;EACd,IAAI,OAAO,KAAY,YAAY;GAC/B,IAAM,IAAK;GAEX,AADA,IAAU,KAAA,GACV,GAAI;;EAER,EAAO,MAAM;GACf;;;;AClDN,SAAgB,GAAqB,GAAmC;CACpE,OAAO;EACH;EACA,SAAS,EAAsB,EAAE,CAAC;EAClC,OAAO,EAAwB,EAAE,CAAC;EACrC;;AAQL,SAAgB,EACZ,GACA,GACA,GACI;CACJ,IAAI,MAAS,KAAA,GAAW;EAGpB,OAAO,EAAS,MAAM;EACtB;;CAEJ,EAAU,MAA0B,KAAQ;;AAOhD,SAAgB,EACZ,GACA,GACI;CACJ,KAAK,IAAM,KAAO,OAAO,KAAK,EAAM,EAA6B;EAC7D,IAAM,IAAI,EAAM;EAChB,AAAI,MAAM,KAAA,IACN,OAAO,EAAS,QAAQ,KAKxB,EAAU,QAA+C,KAAO;;;;;ACzD5E,SAAgB,GACZ,GACI;CACJ,IAAM,IAAW,GAAmB;CAEpC,IAAI,OAAO,KAAgB,YAAY;EACnC,EAAa,GAAU,EAAY;EACnC;;CAOJ,IAAM,IAAS,QAAa;EAExB,EAAa,GADA,GACU,CAAK;GAC9B;CACF,QAAkB,EAAO,MAAM,CAAC;;;;ACIpC,SAAgB,KAAgC;CAC5C,IAAM,IAAM,GAAQ,EACd,IAAY,GAAiB,EAc7B,IAA+B,IAAyB,EAExD,UAAwC;EAC1C,IAAM,IAAI,EAAI;EAId,OAHI,IACO,EAAE,SAAS,QAAQ,EAAE,kBAAkB,EAAE,WAE7C,EAAI;IAGT,UACE,KACc,EAAI,MACR,MAAM,MAAM,EAAE,QAAQ,EAAW,IAAI,GACpC,IAGR,GAAqB;CAGhC,OAAO;EACH,IAAI,QAAQ;GACR,IAAM,IAAQ,GAAU,EAElB,IADM,EAAU,QAAQ,IAAI,EAAM,IAC9B,EAAK,QAAQ;GAGvB,OAFI,OAAO,KAAM,aAAmB,GAAG,GACnC,OAAO,KAAM,WAAiB,IAC3B,EAAM;;EAEjB,IAAI,cAAc;GAEd,OADY,EAAU,QAAQ,IAAI,GAAU,CAAC,IACtC,EAAK,QAAQ,gBAAgB;;EAExC,IAAI,YAAY;GACZ,IAAM,IAAQ,GAAU;GAGxB,OAFc,EAAI,MACA,WAAW,MAAM,EAAE,QAAQ,EAAM,IAC5C,GAAM;;EAEjB,MAAM;GACF,EAAI,KAAK;;EAEb,IAAI,SAAS;GAET,OADY,EAAU,QAAQ,IAAI,GAAU,CAAC,IACtC,EAAK,MAAM;;EAEtB,IAAI,aAAa;GAEb,OADY,EAAU,QAAQ,IAAI,GAAU,CAAC,IACtC,EAAK,MAAM;;EAEtB,IAAI,cAAc;GAEd,OADY,EAAU,QAAQ,IAAI,GAAU,CAAC,IACtC,EAAK,MAAM;;EAEzB;;;;AC5FL,IAAa,KAAuB;AA6DpC,SAAgB,GAAiB,GAAwC;CACrE,IAAM,IAAM,GAAQ,EACd,IAAS,GAAc,EACvB,IAAa,EAAQ,cAAc,KACnC,IAAa,EAAQ,YACrB,IAAQ,EAAQ,gBAKlB,IAAU,IAEV,IAAqD,MACrD,IAAkC;CAEtC,QAAgB;EAKZ,QAAQ,SAAS,CACZ,WAAW,EAAQ,QAAQ,MAAM,CAAC,CAClC,MAAM,MAAS;GACP,OACL;QAAI,KAAQ,MAAM;KAEd,GAAiB;KACjB;;IAEJ,IAAI,CAAC,GAAa,EAAK,EAAE;KAGrB,AAFA,IAAQ,QAAQ,EAEhB,GAAiB;KACjB;;IAEJ,IAAI,EAAK,YAAA,GAAkC;KAGvC,AAFA,IAAQ,UAAU,EAElB,GAAiB;KACjB;;IAMJ,KAAK,IAAM,KAAS,EAAK,OACrB,IAAI,CAAC,EAAO,EAAM,QAAQ;KAGtB,AAFA,IAAQ,gBAAgB,EAExB,GAAiB;KACjB;;IAGR,IAAI,EAAK,MAAM,WAAW,GAAG;KAGzB,AAFA,IAAQ,QAAQ,EAEhB,GAAiB;KACjB;;IAKJ,AAHA,EAAI,MAAM,EAAE,OAAO,EAAK,OAAO,CAAC,EAChC,IAAa,EAAK,EAElB,GAAiB;;IACnB,CACD,OAAO,MAAQ;GACP,MACL,IAAQ,cAAc,EAAI,EAE1B,GAAiB;IACnB;GACR;CAEF,SAAS,IAAkB;EACvB,IAAI,CAAC,KAAW,GAAY;EAQ5B,IAAI,IAAW,IACT,IAAS,QAAa;GAExB,IAAM,IAAwB;IAC1B,SAAA;IACA,OAHU,EAAI,MAGD,KAAK,OAAO;KACrB,KAAK,EAAE;KACP,OAAO,EAAE;KACT,QAAQ,EAAE;KACV,QAAQ,EAAE;KACV,OAAO,EAAE;KACT,cAAc,EAAE;KACnB,EAAE;IACN;GACD,IAAI,GAAU;IACV,IAAW;IACX;;GAEJ,EAAS,EAAS;IACpB;EACF,UAAmB,EAAO,MAAM;;CAGpC,SAAS,EAAS,GAAuB;EAErC,AADI,KAAgB,QAAM,aAAa,EAAa,EACpD,IAAe,iBAAiB;GAC5B,IAAe;GACf,IAAI;IACA,IAAM,IAAI,EAAQ,QAAQ,KAAK,EAAS;IACxC,AAAI,KAAK,OAAQ,EAAoB,SAAU,cAC3C,EAAqB,YAAY,GAI/B;WAEF;KAGT,EAAW;;CAGlB,QAAkB;EAMd,AALA,IAAU,IACN,KAAgB,SAChB,aAAa,EAAa,EAC1B,IAAe,OAEnB,AAEI,OADA,GAAY,EACC;GAEnB;;AAGN,SAAS,GAAa,GAA8B;CAChD,IAAI,CAAC,KAAK,OAAO,KAAM,UAAU,OAAO;CACxC,IAAM,IAAM;CAEZ,IADI,OAAO,EAAI,WAAY,YACvB,CAAC,MAAM,QAAQ,EAAI,MAAM,EAAE,OAAO;CACtC,KAAK,IAAM,KAAS,EAAI,OAAO;EAC3B,IAAI,CAAC,KAAS,OAAO,KAAU,UAAU,OAAO;EAChD,IAAM,IAAI;EAGV,IAFI,OAAO,EAAE,OAAQ,YACjB,OAAO,EAAE,SAAU,YACnB,OAAO,EAAE,gBAAiB,UAAU,OAAO;;CAEnD,OAAO;;;;AC3JX,IAAM,IAA0B;AAYhC,SAAS,EAAsB,GAA0B;CACrD,AAAI,EAAgB,EAAU,IAE1B,EAAU,SAAS,CAAC,YAAY,GAAG;;AAI3C,IAAI,IAAkB;AACtB,SAAS,KAAuB;CAE5B,OADA,KAAmB,GACZ,SAAS,EAAgB,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;;AAG7E,SAAS,EACL,GACA,GACA,GACA,GACA,GACU;CACV,IAAM,IAAQ,EAAO,IACf,IACF,GAAS,gBAAgB,GAAO,gBAAgB;CACpD,OAAO;EACH,KAAK,IAAc;EACnB,OAAO;EACP,QAAS,KAAU,EAAE;EACrB,QAAS,KAAU,EAAE;EACrB,OAAO,GAAS;EAChB;EACH;;AAGL,SAAS,EACL,GACA,GACA,GACsE;CAGtE,IAFc,EAAO,IACW,QACZ;EAChB,IAAM,CAAC,GAAQ,GAAQ,KAAW;EAKlC,OAAO;GAAE;GAAQ;GAAQ;GAAS;;CAEtC,IAAM,CAAC,GAAQ,KAAW;CAC1B,OAAO;EAAE,QAAQ,KAAA;EAAW;EAAQ;EAAS;;AAuCjD,SAAgB,EAAqB,GAA8C;CAC/E,IAAM,EAAE,WAAQ,YAAS,aAAU,YAAS,SAAS,GAE/C,IAAoC,EAAqB,CAAC,EAAQ,CAAC,EACnE,IAAyC,EAA2B,EACtE,OAAO,EAAK,yBAAyB,IACxC,CAAC,EACI,oBAAW,IAAI,KAAU,EAIzB,IAA2D,EAE9D,EAAE,OAAO,MAAM,CAAC;CAEnB,SAAS,IAAyB;EAC9B,OAAO;;CAEX,SAAS,EAAS,GAA0B;EACxC,EAAY,KAAK,EAAK;;CAE1B,SAAS,EAAc,GAAoC;EACvD,EAAc,QAAQ;;CAQ1B,SAAS,IAA2B;EAChC,OAAO,EAAc,UAAU;;CA4BnC,eAAe,EACX,GACA,GACa;EACb,IAAI,CAAC,GAAU;EACf,IAAM,IAAK;EAYX,AADA,GAVgC,GAAW,MAAc;AACrD;GAOA,AADA,EAAG,QAAQ,QAAQ,GACnB,EAAW,GAAI,GAAG,EAAE,UAAU,GAAG,CAAC;IAEtC,CAAO,GAAQ,EAAY,EAC3B,MAAM,IAAI,SAAe,MAAY;GACjC,WAAW,GAAS,KAAK,MAAM,IAAc,IAAK,CAAC;IACrD;;CAGN,IAAM,MAAsB,GAAc,GAAG,MAAoB;EAC7D,IAAI,CAAC,EAAO,IACR,MAAU,MACN,2BAA2B,EAAK,6CACX,OAAO,KAAK,EAAO,CAAC,KAAK,KAAK,IAAI,WAC1D;EAEL,IAAM,EAAE,WAAQ,WAAQ,eAAY,EAAW,GAAM,GAAM,EAAO;EASlE,KADK,GAAS,gBAAgB,EAAO,GAAM,gBAAgB,YAC9B,UAAU,GAAQ;GAO3C,EAAQ,KAA8C,GAAM,GAAG,EAAK;GACpE;;EAGJ,IAAI,GAAiB,EAAE;EACvB,EAAsB,EAAO,GAAM,UAAU;EAC7C,IAAM,IAAW,EAAU,GAAM,GAAQ,GAAQ,GAAS,EAAO,EAC3D,IAAM,GAAU,EAChB,IAAU,EAAI,EAAI,SAAS;EAKjC,EAAS,CAAC,GAAG,GAAK,EAAS,CAAC,EAEX,GAAS,aAAa,MAAW,MAGlD,EAAc;GACV,MAAM;GACN,UAAU;GACV,iBAAiB;GACjB;GACH,CAAC,EAEF,EAAgB,GAAG,EAAwB,CAAC,WAClC,EAAc,KAAK,QACnB,EAAc,KAAK,CAC5B;KAGC,MAA4B,GAAc,GAAG,MAAoB;EACnE,IAAI,GAAiB,EAAE;EACvB,IAAM,EAAE,WAAQ,WAAQ,eAAY,EAAW,GAAM,GAAM,EAAO;EAClE,IAAI,CAAC,EAAO,IACR,MAAU,MACN,8BAA8B,EAAK,8BACtC;EAEL,EAAsB,EAAO,GAAM,UAAU;EAC7C,IAAM,IAAQ,EAAU,GAAM,GAAQ,GAAQ,GAAS,EAAO,EACxD,IAAM,GAAU;EAGtB,EAAS,CAAC,GAAG,EAAI,MAAM,GAAG,EAAI,SAAS,EAAE,EAAE,EAAM,CAAC;;CAGtD,SAAS,EAAI,IAAgB,GAAG,GAA4B;EACxD,IAAI,GAAiB,EAAE;EACvB,IAAM,IAAM,GAAU,EAChB,IAAS,KAAK,IAAI,GAAG,EAAI,SAAS,KAAK,IAAI,GAAG,EAAM,CAAC;EAC3D,IAAI,MAAW,EAAI,QAAQ;EAI3B,IAAI,EADA,GAAS,aAAa,MAAW,KAAY,MAAU,KAAK,EAAI,UAAU,IAC/D;GACX,EAAS,EAAI,MAAM,GAAG,EAAO,CAAC;GAC9B;;EAMJ,IAAM,IAAU,EAAI,EAAI,SAAS,IAC3B,IAAO,EAAI,EAAI,SAAS;EAQ9B,AAPA,EAAc;GACV,MAAM;GACN,UAAU;GACV,iBAAiB;GACjB;GACH,CAAC,EAEF,EAAgB,GAAG,EAAwB,CAAC,WAClC;GAEF,AADA,EAAS,EAAI,MAAM,GAAG,EAAI,SAAS,EAAE,CAAC,EACtC,EAAc,KAAK;WAEjB;GAKF,AADA,EAAS,EAAI,MAAM,GAAG,EAAI,SAAS,EAAE,CAAC,EACtC,EAAc,KAAK;IAE1B;;CAGL,SAAS,EAAM,GAAoB;EAC/B,IAAI,GAAiB,EAAE;EACvB,IAAM,IAAM,GAAU;EACtB,KAAK,IAAI,IAAI,EAAI,SAAS,GAAG,KAAK,GAAG,KACjC,IAAI,EAAI,GAAG,UAAU,GAAM;GACvB,IAAI,MAAM,EAAI,SAAS,GAAG;GAC1B,EAAS,EAAI,MAAM,GAAG,IAAI,EAAE,CAAC;GAC7B;;;CAKZ,SAAS,IAAkB;EACvB,IAAI,GAAiB,EAAE;EACvB,IAAM,IAAM,GAAU;EAClB,EAAI,UAAU,KAClB,EAAS,CAAC,EAAI,GAAG,CAAC;;CAGtB,SAAS,GAAM,GAAmD;EAC9D,IAAI,EAAM,MAAM,WAAW,GACvB,MAAU,MAAM,qDAAqD;EAGzE,AADA,EAAS,CAAC,GAAG,EAAM,MAAM,CAAC,EAC1B,EAAc,KAAK;;CAGvB,SAAS,IAAgB;EACrB,IAAI,GAAiB,EAAE;EACvB,IAAM,IAAM,GAAU,EAClB,IAAI,EAAI,SAAS;EACrB,OAAO,IAAI,KAAK,EAAI,GAAG,iBAAiB,SACpC;EAEJ,AAAI,IAAI,EAAI,SAAS,KACjB,EAAS,EAAI,MAAM,GAAG,IAAI,EAAE,CAAC;;CAWrC,SAAS,IAAyB;EAC9B,IAAI,GAAiB,EAAE;EACvB,IAAM,IAAM,GAAU;EACtB,IAAI,EAAI,SAAS,GAAG;EACpB,IAAM,IAAU,EAAI,EAAI,SAAS,IAC3B,IAAO,EAAI,EAAI,SAAS;EAC9B,EAAc;GACV,MAAM;GACN,UAAU;GACV,iBAAiB;GACP;GACb,CAAC;;CAGN,SAAS,IAA0B;EAC/B,IAAM,IAAM,GAAU;EAItB,AAHI,EAAI,UAAU,KACd,EAAS,EAAI,MAAM,GAAG,EAAI,SAAS,EAAE,CAAC,EAE1C,EAAc,KAAK;;CAGvB,SAAS,IAA0B;EAC/B,EAAc,KAAK;;CAGvB,IAAM,IAAW;EACb;EACA;EACA;EACA;EACA;EACA;EACA;EACA,IAAI,UAAU;GACV,OAAO,EAAY,EAAY,SAAS;;EAE5C,IAAI,QAAQ;GACR,OAAO;;EAEX,IAAI,YAAY;GACZ,OAAO,EAAY,SAAS;;EAEhC,IAAI,SAAS;GACT,OAAO;;EAEX,IAAI,mBAAmB;GACnB,OAAO,EAAW;;EAEtB,IAAI,YAAY;GACZ,OAAO;;EAEX,IAAI,aAAa;GACb,OAAO,EAAc;;EAE5B;CAED,AAAI,KAKA,EAAO,UAAU,IAAI,EAAI;CAG7B,SAAS,GAAkB,GAAwB;EAC3C,EAAW,UAAU,MACzB,EAAW,QAAQ;;CAGvB,OAAO;EACH;EACA;EACA,UAAU;GAAE;GAAkB;GAAmB;GAAmB;EACpE,UAAU,IAAwB;EAClC,oBAAoB;EACvB;;AAYL,SAAS,KAAqD;CAC1D,IAAM,oBAAQ,IAAI,KAA6B,EAMzC,IAAU,EAAO,EAAE,GAAG,GAAG,CAAC;CAChC,OAAO;EACH,SAAS,GAAqB;GAK1B,AAJA,EAAM,IAAI,EAAI,MAAM,KAAK,EAAI,EAI7B,QAAc;IAAE,EAAQ,KAAgB;KAAK;;EAUjD,WAAW,GAAqB;GAChB,EAAM,IAAI,EAAI,MAAM,IAC5B,KAAQ,MACZ,EAAM,OAAO,EAAI,MAAM,IAAI,EAC3B,QAAc;IAAE,EAAQ,KAAgB;KAAK;;EAEjD,IAAI,GAAa;GAOb,OADA,EAAa,GACN,EAAM,IAAI,EAAI;;EAE5B;;;;ACvfL,IAAa,KAAiB,GAAgC,EAAE,UAAO,eAAY;CAC/E,IAAM,IAAS,EAAM,QACf,IAAsB,EAAM,gBAAgB,OAAO,KAAK,EAAO,CAAC;CACtE,IAAI,CAAC,EAAO,IACR,MAAU,MACN,oDAAoD,EAAY,kCACnE;CAOL,EAAkB,EAAO;CACzB,IAAM,IAAoC,EAAO,GAAa,gBAAgB,QACxE,IAAsB;EACxB,KAAK;EACL,OAAO;EACP,QAAQ,EAAM,iBAAiB,EAAE;EACjC,QAAQ,EAAM,iBAAiB,EAAE;EACjC,OAAO,KAAA;EACP,cAAc;EACjB,EAMK,IAAa,EAAe,EAAE,EAC9B,IAAoB,EAAM,aAAa,IACvC,IAAW,EAAqB;EAClC;EACA;EACA,UAAU,IAAoB,IAAa,KAAA;EAC9C,CAAC;CAGF,AADA,EAAc,SAAc,EAAS,IAAI,EACzC,EAAc,SAAoB,EAAS,OAAO;CAClD,IAAM,IAAmB,EAAM,qBAAqB;CAUpD,OATA,EAAc,UAAwB;EAClC,UAAU,IAAoB,IAAa;EAC3C,kBAAkB,EAAS,SAAS;EACpC,mBAAmB,EAAS,SAAS;EACrC,mBAAmB,EAAS,SAAS;EACrC;EACA,SAAS,EAAS;EACrB,EAAE,QAEU,EAAM,WAAW;EAChC;;;AClEF,SAAS,EAAO,GAAoC,GAA0B;CAC1E,IAAI;EACA,IAAM,IAAO,OAAO,OAAS,MAAc,MAAM,aAAa,KAAA,GACxD,IAAK,IAAO,IACZ,IAAK,GAAM,cAAc;EAC/B,IAAI,OAAO,KAAO,YAAY,IAAK,GAC/B,OAAO,KAAK,MAAM,IAAK,EAAG;SAE1B;CAGR,OAAO;;AAGX,IAAa,IAAe,EAAO,cAAc,IAAI,EACxC,KAAgB,EAAO,eAAe,IAAI,ECUjD,KAAkB;AAgBxB,SAAgB,EAAsB,GAA0B;CAC5D,OAAO,MAAM,WAAW,MAAM,gBAAgB,MAAM;;AAQxD,SAAgB,GAAiB,GAA0C;CAKvE,OAJK,IAIE,GAAG,EAAU,KAAK,GAAG,EAAU,YAAY,GAAG,IAAI,EAAU,YAAY,OAJxD;;AAW3B,SAAS,GACL,GACA,GACA,GACc;CAWd,OAVI,MAAS,SACL,MAAS,QACF;EAAE,MAAM;EAAc,YAAY,CAAC,GAAG,EAAE;EAAE,aAAa,CAAC,GAAc,EAAE;EAAE;EAAU,GAExF;EAAE,MAAM;EAAc,YAAY,CAAC,GAAG,EAAE;EAAE,aAAa,CAAC,GAAG,CAAC,KAAkB,EAAa;EAAE;EAAU,GAG9G,MAAS,QACF;EAAE,MAAM;EAAc,YAAY,CAAC,GAAG,EAAE;EAAE,aAAa,CAAC,GAAG,EAAa;EAAE;EAAU,GAExF;EAAE,MAAM;EAAc,YAAY,CAAC,GAAG,EAAE;EAAE,aAAa,CAAC,CAAC,KAAkB,GAAc,EAAE;EAAE;EAAU;;AASlH,SAAS,GACL,GACA,GACc;CAId,OAHI,MAAS,SACF;EAAE,MAAM;EAAc,YAAY,CAAC,GAAG,EAAE;EAAE,aAAa,CAAC,IAAe,EAAE;EAAE;EAAU,GAEzF;EAAE,MAAM;EAAc,YAAY,CAAC,GAAG,EAAE;EAAE,aAAa,CAAC,GAAG,GAAc;EAAE;EAAU;;AAOhG,SAAgB,GACZ,GACA,GACA,GACO;CACP,IAAI,CAAC,GAAY;EAEb,IAAI,IAAU,EAAM,SAAS;EAC7B,OAAO,IAAU,KAAK,EAAsB,EAAM,GAAS,aAAa,GACpE;EAEJ,OAAO,EAAM,MAAM,EAAQ,CAAC,KAAK,OAAW;GAAE;GAAO,WAAW;GAAM,EAAE;;CAO5E,IAAI,CADc,EAAsB,EAAW,SAAS,aACvD,EAGD,OAAO,CACH;EACI,OAAO,EAAW;EAClB,WAAW,IAAW,GAAc,cAAc,EAAW,MAAM,EAAS,GAAG;EAClF,EACD;EACI,OAAO,EAAW;EAClB,WAAW,IAAW,GAAc,OAAO,EAAW,MAAM,EAAS,GAAG;EAC3E,CACJ;CAML,IAAM,IAAgB,EAAM,WACvB,MAAM,EAAE,QAAQ,EAAW,gBAAgB,IAC/C,EAIK,IAAgB,KAAiB,IAAI,IAAgB,EAAM,SAAS,GAEtE,IAAU;CACd,OAAO,IAAU,KAAK,EAAsB,EAAM,GAAS,aAAa,GACpE;CAOJ,OAAO,CACH,GAL0B,EACzB,MAAM,GAAS,IAAgB,EAAE,CACjC,KAAK,OAAW;EAAE;EAAO,WAAW;EAAM,EAGxC,EACH;EACI,OAAO,EAAW;EAClB,WAAW,IAAW,GAAoB,EAAW,MAAM,EAAS,GAAG;EAC1E,CACJ;;;;AC3IL,IAAM,KAAqB,KAErB,KAAkB,KAElB,KAAkB,IAElB,KAAe,GACf,IAAoB,KAQpB,IAAmB,KAAK,MAAM,IAAoB,IAAK,EAEhD,KAAiB,QAAgB;CAC1C,IAAM,IAAM,EAA4C,KAAK,EAUvD,IAAQ;EACV,YAAY;EACZ,WAAW;EACX,UAAU;EACV,UAAU;EACb,EAEK,IAAY,GAAiB,EAC7B,IAAW,EAAU,UACrB,IAAmB,EAAU,kBAC7B,IAAoB,EAAU,mBAC9B,IAAoB,EAAU;CA+DpC,OAFA,EAAmB,GA3DP,EAAQ,KAAK,CACpB,YAAY,GAAa,CACzB,cAAc;AACX;GACF,CACD,SAAS,MAAW;AACjB;EACA,IAAM,IAAI,KAAK,EAAE,QACX,IAAS,KAAK,EAAE,SAAU;EAKhC,AAJA,EAAM,aAAa,GACnB,EAAM,YAAY,GAClB,EAAM,WAAW,KAAK,KAAK,EAC3B,EAAM,WAAW,GACjB,QAAsB;GAClB,GAAkB;IACpB,EAAE;GACN,CACD,UAAU,MAAW;AAClB;EACA,IAAI,CAAC,GAAU;EACf,IAAM,IAAI,KAAK,EAAE,QACX,IAAS,KAAK,EAAE,SAAU,GAC1B,IAAK,IAAQ,EAAM,YACnB,IAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAK,EAAa,CAAC;EACxD,EAAS,QAAQ,QAAQ;EAEzB,IAAM,IAAM,KAAK,KAAK,EAChB,IAAK,IAAM,EAAM;EAMvB,AALI,IAAK,MACL,EAAM,YACA,IAAQ,EAAM,aAAa,IAAM,MAE3C,EAAM,YAAY,GAClB,EAAM,WAAW;GACnB,CACD,OAAO,MAAW;AACf;EACA,IAAI,CAAC,GAAU;EACf,IAAM,IAAI,KAAK,EAAE;EAQjB,EAPe,KAAK,EAAE,SAAU,KACb,EAAM,cACH,IAEP,MACX,EAAM,WAAW,MAGjB,EAAW,GAAU,GAAG,EAAE,UAAU,GAAmB,CAAC,EACxD,QAAsB;GAClB,iBAAiB,GAAmB,EAAE,EAAiB;IACzD,EAAE,KAEJ,EAAW,GAAU,GAAG,EAAE,UAAU,GAAmB,CAAC,EACxD,QAAsB;GAClB,iBAAiB,GAAmB,EAAE,EAAiB;IACzD,EAAE;GAIQ,CAAI,QAGxB,kBAAC,QAAD;EACI,mBAAiB;EACjB,OAAO;GACH,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO,GAAG,GAAgB;GAC1B,QAAQ;GACX;EACH,CAAA;EAER,EChIW,KAAa,GAA4B,EAAE,UAAO,eAAY;CACvE,IAAM,IAAY,GAAiB,EAC7B,IAAW,GAAqB,EAAM,MAAM;CAalD,OAZA,EAAU,QAAQ,SAAS,EAAS,EACpC,QAAkB;EAMd,EAAU,QAAQ,WAAW,EAAS;GACxC,EACF,EAAc,SAAuB,EAAM,MAAM,EACjD,EAAc,UAA+B,EAAM,MAAM,EACzD,EAAc,SAAyB,EAAS,QACnC,EAAM,WAAW;EAChC,ECHW,KAAQ,GAAuB,EAAE,eAAY;CACtD,IAAM,IAAM,EAA4C,KAAK;CAK7D,IAAI,EAAM,WAAW;EACjB,IAAM,IAAI,EAAM;EAChB,EAAiB,GAAK,EAAE,UAAU,EAAE,MAAM;GACtC,YAAY,CAAC,EAAE,WAAW,IAAI,EAAE,WAAW,GAAG;GAC9C,aAAa,CAAC,EAAE,YAAY,IAAI,EAAE,YAAY,GAAG;GACpD,CAAC;;CAGN,aAAa;EACT,IAAM,IAAQ,EAAM,OAAO,EAAM,MAAM;EACvC,IAAI,CAAC,GAAO,OAAO;EACnB,IAAM,IAAO,EAAM;EAKnB,IAAI,OAAO,KAAS,YAAY,OAAO;EACvC,IAAM,IAAc,EAAM,MAAM,QAC1B,IAAO,EAAgB,EAAK,IAAI,EAAM,WAEpC,kBAAC,GAAD;GAAU,UAAU,EAAM;aACtB,kBAAC,GAAD,EAAM,GAAI,GAAe,CAAA;GAClB,CAAA,GAEb,kBAAC,GAAD,EAAM,GAAI,GAAe,CAAA;EAC/B,OACI,kBAAC,QAAD;GACI,mBAAiB;GACjB,OAAO;IACH,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,eAAe;IAClB;aAED,kBAAC,IAAD;IAAkC,OAAO,EAAM;cAC1C;IACQ,EAFI,EAAM,MAAM,IAEhB;GACV,CAAA;;EAGjB,ECpBW,IAAU,QAAgC;CACnD,MAAU,MACN,oEACH;EACH,EAgBI,KAAmB,QAAsC;CAC3D,MAAU,MACN,uEACH;EACH,EASW,KAAmB,QAA+B;CAC3D,MAAU,MACN,4EACH;EACH,EAqIW,KAAO,EA/HN,GAAsB,EAAE,UAAO,eAAY;CAKrD,IAAM,IAAO,EAAkB,EAAE,CAAC,EAC5B,IAAiD,EAAO,EAC1D,OAAO,EAAM,cAAc,MAC9B,CAAC,EAEI,IAA2B;EAC7B,SAAS,GAAM;GAIX,QAAc;IACV,IAAM,IAAM,EAAK,WAAW,MAAM,EAAE,SAAS,EAAK,KAAK;IAGvD,AAFI,MAAQ,KAAI,EAAK,KAAK,EAAK,GAC1B,EAAK,KAAO,GACb,EAAa,UAAU,SACvB,EAAa,QAAQ,EAAK;KAEhC;;EAEN,WAAW,GAAM;GACb,QAAc;IACV,IAAM,IAAM,EAAK,WAAW,MAAM,EAAE,SAAS,EAAK;IAElD,AADI,MAAQ,MAAI,EAAK,OAAO,GAAK,EAAE,EAC/B,EAAa,UAAU,MACvB,EAAa,QAAQ,EAAK,IAAI,QAAQ;KAE5C;;EAEN;EACA;EACH,EAEK,IAAe;EACjB,IAAI,SAAS;GAIT,OAAO,EAAa,SAAS;;EAEjC,UAAU,GAAM;GAIP,EAAK,MAAM,MAAM,EAAE,SAAS,EAAK,KACtC,EAAa,QAAQ;;EAEzB,IAAI,OAAO;GACP,OAAO;;EAEd;CAKD,OAHA,EAAc,SAAe,EAAI,EACjC,EAAc,UAAwB,EAAU,QAEnC,EAAM,WAAW;EAoEL,EAAO,EAChC,QA3De,GAA4B,EAAE,UAAO,eAAY;CAChE,IAAM,IAAY,IAAkB,EAK9B,IAAO,EAAM;CAanB,OAZA,EAAU,SAAS;EACf;EACA,MAAM,EAAM;EACZ,OAAO,EAAM;EACb,oBAAoB,EAAM;EAC7B,CAAC,EACF,QAAkB,EAAU,WAAW,EAAK,CAAC,EAI7C,EAAc,UAAwB,EAAK,QAkBnC,kBAAC,QAAD;EACI,OAAO;GACH,SAJG,EAAU,aAAa,UAAU,IAIlB,SAAS;GAC3B,eAAe;GACf,OAAO;GACP,UAAU;GACV,YAAY;GACZ,WAAW;GACX,WAAW;GACd;YAEA,EAAM,WAAW;EACf,CAAA;EAYP,EACX,CAAC,EChME,KAAoB,GAwDX,KAAQ,GAAuB,EAAE,UAAO,eAAY;CAK7D,IAAM,IAAY,GAAQ,EACpB,IAAS,GAAc,EACvB,IAAkB,GAAiB,EAMnC,IAAc,EAAM,cACpB,IAAW,OAAO,KAAgB,YAAY,EAAY,SAAS,GAErE,GACA;CAEJ,IAAI,GAAU;EACV,IAAI,CAAC,EAAO,IACR,MAAU,MACN,0CAA0C,EAAY,6CAE/C,OAAO,KAAK,EAAO,CAAC,KAAK,KAAK,IAAI,WAC5C;EAUL,IAAI,IAA8B;EAClC,IAAI;GACA,IAAe,GAAiB,CAAC;UAC7B;GACJ,IAAe;;EAOnB,IAAI,IAAyB,MACzB,IAAgD;EACpD,IAAI;GAEA,AADA,IAAU,IAAkB,EAC5B,IAAa,GAAS;UAClB;GAEJ,AADA,IAAU,MACV,IAAa;;EAOjB,IAAM,IAAoB,EAAgB,aAAa,MACjD,IAAa,EAAe,EAAE,EAE9B,IACD,EAAO,GAAa,gBAAgB;EAIzC,MAAqB;EAUrB,IAAM,IAAW,EAAqB;GAClC;GACA,SAAA;IAVA,KAAK,UAAU,EAAY,GAAG;IAC9B,OAAO;IACP,QAAQ,EAAM,iBAAiB,EAAE;IACjC,QAAQ,EAAM,iBAAiB,EAAE;IACjC,OAAO,KAAA;IACP;IAKA;GACA,UAAU,IAAoB,IAAa,KAAA;GAC3C,QAAQ;GAGR,uBAAuB;GAC1B,CAAC;EAGF,AADA,IAAM,EAAS,KACf,IAAY;GACR,UAAU,IAAoB,IAAa;GAC3C,kBAAkB,EAAS,SAAS;GACpC,mBAAmB,EAAS,SAAS;GACrC,mBAAmB,EAAS,SAAS;GACrC,kBAKI,KAAqB,EAAgB;GACzC,SAAS,EAAS;GACrB;EAQD,IAAM,IAAc,QAAa;GAC7B,IAAM,IACF,MAAiB,QAAQ,EAAU,QAAQ,QAAQ,GACjD,IAAgB,EAAU,kBAC1B,IACF,MAAY,QAAQ,MAAe,OAC7B,KACA,EAAW,WAAW,GAC1B,IAAU,KAAa,KAAiB;GAI9C,QAAc,EAAS,mBAAmB,EAAQ,CAAC;IACrD;EASF,AAPA,QAAkB;GAEd,AADA,EAAY,MAAM,EAClB,EAAU,UAAU,OAAO,EAAI;IACjC,EAEF,EAAc,SAAc,EAAI,EAChC,EAAc,SAAoB,EAAO,EACzC,EAAc,SAAuB,EAAU;QAG/C,AADA,IAAM,GACN,IAAY;CAShB,IAAM,IAAiB;EACnB,UAAU;EACV,YAAY;EACZ,WAAW;EACX,WAAW;EACX,SAAS;EACT,eAAe;EAClB;CAED,aAAa;EACT,IAAM,IAAS,EAAM,WAAW,EAC1B,IAAS,GAAc,EAAI,OAAO,EAAI,YAAY,EAAU,SAAS,EAErE,KAAmB,MACrB,IACI,kBAAC,IAAD;GAEI,OAAO,EAAM;GACL;GACR,WAAW,EAAM;GACnB,EAJO,SAAS,EAAM,MAAM,IAAI,GAAG,GAAiB,EAAM,UAAU,GAIpE,GACF,MA6BF,IAAM,EAAI,SACV,IACF,EAAU,oBACP,EAAI,aACJ,CAAC,EAAI,cACL,CAAC,EAAsB,EAAI,aAAa,GAEzC,kBAAC,IAAD,EAAkC,EAAd,YAAc,GAClC,MAEA,IACF,kBAAC,QAAD;GACI,OAAO;IACH,UAAU;IACV,OAAO;IAKP,GAAG;IAIH,UAAU;IACb;aAbL;IAeK,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,GAAG;IAC1B,EAAgB,EAAO,IAAI;IAC3B,EAAgB,EAAO,IAAI;IAC3B,EAAgB,EAAO,IAAI;IAC3B,EAAgB,EAAO,IAAI;IAC3B,EAAgB,EAAO,IAAI;IAC3B,EAAgB,EAAO,IAAI;IAC3B;IACE;;EAIX,OADI,KAAU,OAAa,IAEvB,kBAAC,QAAD;GAAM,OAAO;aAAb,CACK,GACD,kBAAC,QAAD;IAAM,OAAO;cAAiB;IAAY,CAAA,CACvC;;;EAGjB,EC3TI,KAAa,GAAwB,EAAE,UAAO,eAAY;CAC5D,IAAM,IAAW,GAAmB,EAM9B,IAAuB,EAAE;CAK/B,OAJI,EAAM,UAAU,KAAA,MAAW,EAAM,QAAQ,EAAM,QAC/C,EAAM,gBAAgB,KAAA,MAAW,EAAM,cAAc,EAAM,cAC3D,EAAM,mBAAmB,KAAA,MAAW,EAAM,iBAAiB,EAAM,iBACrE,EAAa,GAAU,EAAM,QAChB,EAAM,WAAW;EAChC;AASF,SAAS,EAAe,GAA+C;CACnE,OAAO,GAA4B,EAAE,eAAY;EAC7C,IAAM,IAAW,GAAmB;EAGpC,OAFA,EAAQ,GAAU,SAAY,EAAM,WAAW,CAAC,EAChD,QAAkB,EAAQ,GAAU,GAAM,KAAA,EAAU,CAAC,QACxC;GACf;;AAGN,IAAM,KAAS,EAAe,SAAS,EACjC,KAAa,EAAe,aAAa,EACzC,KAAc,EAAe,cAAc,EAc3C,KAAa,GAA4B,EAAE,eAAY;CACzD,IAAM,IAAW,GAAmB;CAapC,OAZA,EAAQ,GAAU,eAAe,MAAQ;EACrC,IAAM,IAAM,EAAM,WAAW;EAG7B,IAAI,OAAO,KAAQ,YAAY,OAAQ,EAAmC,EAAI;EAC9E,IAAI,MAAM,QAAQ,EAAI,EAAE;GACpB,IAAM,IAAQ,EAAI;GAClB,IAAI,OAAO,KAAU,YAAY,OAAQ,EAAqC,EAAI;;EAEtF,OAAO;GACT,EACF,QAAkB,EAAQ,GAAU,cAAc,KAAA,EAAU,CAAC,QAChD;EACf,EAOW,KAAS,OAAO,OAAO,IAAY;CAC5C,QAAA;CACA;CACA;CACA;CACH,CAAC;;;ACpFF,SAAS,GAAa,GAA2B,GAA2B;CAGxE,OAFI,OAAO,KAAM,aAAmB,GAAG,GACnC,OAAO,KAAM,WAAiB,IAC3B;;AASX,IAAM,KAAoB,GAAyC,EAAE,qBAE7D,kBAAC,QAAD;CACI,eAAe,EAAM,SAAS;CAC9B,yBAAuB;CACvB,uBAAoB;CACpB,uBAAoB;WAEpB,kBAAC,QAAD,EAAA,UAAM,UAAa,CAAA;CAChB,CAAA,CAEb,EAEI,KAAe,GAAkC,EAAE,qBAEjD,kBAAC,QAAD,EAAA,UACI,kBAAC,QAAD,EAAA,UAAO,EAAM,MAAY,CAAA,EACtB,CAAA,CAEb,EAOW,KAAS,QAAgB;CAClC,IAAM,IAAM,GAAQ,EACd,IAAY,GAAiB,EAO7B,IAAe,QAA2B,EAAI,QAAQ,EAEtD,IAAa,QACH,EAAU,QAAQ,IAAI,EAAa,MAAM,IAC9C,EAAK,MAAM,OACpB,EACI,IAAiB,QACP,EAAU,QAAQ,IAAI,EAAa,MAAM,IAC9C,EAAK,MAAM,WACpB,EACI,IAAkB,QACR,EAAU,QAAQ,IAAI,EAAa,MAAM,IAC9C,EAAK,MAAM,YACpB,EACI,IAAc,QACJ,EAAU,QAAQ,IAAI,EAAa,MAAM,IAG9C,EAAK,QAAQ,gBAAgB,GACtC,EACI,IAAY,QAEP,GADK,EAAU,QAAQ,IAAI,EAAa,MAAM,IACjC,EAAK,QAAQ,OAAO,EAAa,MAAM,MAAM,CACnE;CAEF,aAAa;EACT,IAAI,CAAC,EAAY,OAAO,OAAO;EAG/B,IAAM,IAAW,EAAW;EAG5B,OAFI,IAAiB,GAAU,GAG3B,kBAAC,QAAD,EAAA,UAAA;GACI,kBAAC,QAAD,EAAA,UACK,EAAe,QACV,EAAe,OAAO,GACtB,EAAI,YACA,kBAAC,IAAD,EAAmB,eAAe,EAAI,KAAK,EAAI,CAAA,GAC/C,MACP,CAAA;GACP,kBAAC,IAAD,EAAc,MAAM,EAAU,OAAS,CAAA;GACvC,kBAAC,QAAD,EAAA,UACK,EAAgB,QAAQ,EAAgB,OAAO,GAAG,MAChD,CAAA;GACJ,EAAA,CAAA;;EAGjB,EC7EI,KAAmB,GAItB,EAAE,qBACY;CACT,IAAM,IAAQ,EAAM,KAAK,SAAS,EAAM,KAAK;CAE7C,OACI,kBAAC,QAAD;EACI,eAAe,EAAM,SAAS;EAC9B,yBAAuB;EACvB,uBALK,EAAM,KAAK,sBAAsB;EAMtC,uBAAoB;EACpB,wBAAsB,EAAM,SAAS,aAAa,KAAA;EAClD,OAAO,EAAE,SAAS,EAAM,SAAS,IAAI,IAAK;YAN9C,CAQK,EAAM,KAAK,QAAQ,MACpB,kBAAC,QAAD,EAAA,UAAO,GAAa,CAAA,CACjB;;EAGjB,EAEW,KAAS,GAAwB,EAAE,eAAY;CACxD,IAAM,IAAM,GAAS;CACrB,aAAa;EAIT,IAAM,IAAO,EAAI,MACX,IAAS,EAAI,QACb,IAAW,EAAM;EACvB,OACI,kBAAC,QAAD;GAAM,yBAAuB;aACxB,EAAK,KAAK,MAAS;IAChB,IAAM,IAAW,EAAK,SAAS,GACzB,UAAgB,EAAI,UAAU,EAAK,KAAK;IAI9C,OAHI,IACO,EAAS,GAAM;KAAE,QAAQ;KAAU;KAAS,CAAC,GAGpD,kBAAC,IAAD;KACU;KACN,QAAQ;KACC;KACX,CAAA;KAER;GACC,CAAA;;EAGjB,EC/CW,KAAY,QAAkC;CACvD,MAAU,MACN,wEACH;EACH,EAOW,KAAS,GAAwB,EAAE,UAAO,eAAY;CAI/D,IAAM,IAAwC,EAAO,EACjD,OAAO,EAAM,gBAAgB,IAChC,CAAC,EAEI,IAAiB;EACnB,IAAI,SAAS;GACT,OAAO,EAAU;;EAErB,OAAO;GACH,EAAU,QAAQ;;EAEtB,QAAQ;GACJ,EAAU,QAAQ;;EAEtB,SAAS;GACL,EAAU,QAAQ,CAAC,EAAU;;EAEpC;CAID,OAFA,EAAc,UAAiB,EAAI,QAEtB;EACT,IAAM,IAAO,EAAU;EACvB,OACI,kBAAC,QAAD;GAAM,OAAO;IAAE,OAAO;IAAQ,QAAQ;IAAQ;aAA9C,CAEI,kBAAC,QAAD;IAAM,OAAO;KAAE,OAAO;KAAQ,QAAQ;KAAQ;cACzC,EAAM,WAAW;IACf,CAAA,EAKP,kBAAC,QAAD;IACI,OAAO;KACH,UAAU;KACV,MAAM;KACN,KAAK;KACL,QAAQ;KACR,SAAS,IAAO,SAAS;KAC5B;cAEA,EAAM,WAAW;IACf,CAAA,CACJ;;;EAGjB,ECNW,KAzDI,GAA2B,EAAE,UAAO,eAAY;CAC7D,IAAM,IAAM,GAAQ,EACd,IAAS,GAAc,EAEvB,UAA0B;EAC5B,IAAM,IAAQ,EAAM,IACd,IAAW,EAAO;EACxB,IAAI,CAAC,GAKD,MAAU,MACN,+BAA+B,EAAM,8BACxC;EAEL,IAAM,IAAY,CAAC,CAAC,EAAS,QACvB,IAAS,EAAM,UAAU,EAAI,UAAU,EAAI;EAMjD,AAAI,IACA,EACI,GACA,EAAM,QACN,EAAM,OACT,GAED,EAA6C,GAAO,EAAM,OAAO;;CAIzE,aAGI,kBAAC,QAAD;EAAM,SAAS;YAAc,EAAM,WAAW;EAAQ,CAAA;GAE3D,EAAE,MAAM,QAAQ,CAkBC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EACH,gBAAgB,EAChB,oBAAoB,GACvB,MAAM,+BAA+B,CAAC;AAMvC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE/C,6EAA6E;AAC7E,8EAA8E;AAC9E,sEAAsE;AACtE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEhD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC"}
@@ -41,7 +41,7 @@
41
41
  * scroll, in-flight inputs) survives.
42
42
  */
43
43
  import type { SharedValue } from '@sigx/lynx';
44
- import type { Presentation, StackEntry, TransitionState } from '../types';
44
+ import type { Presentation, StackEntry, TransitionState } from '../types.js';
45
45
  export type LayerAnimation = {
46
46
  axis: 'translateX' | 'translateY';
47
47
  inputRange: readonly [number, number];
@@ -1 +1 @@
1
- {"version":3,"file":"layer-plan.d.ts","sourceRoot":"","sources":["../../src/internal/layer-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EACR,YAAY,EACZ,UAAU,EAEV,eAAe,EAClB,MAAM,UAAU,CAAC;AAIlB,MAAM,MAAM,cAAc,GAAG;IACzB,IAAI,EAAE,YAAY,GAAG,YAAY,CAAC;IAClC,UAAU,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,WAAW,KAAK;IAClB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAAC;CAC7C;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,GAAG,MAAM,CAMzE;AAwCD;;;GAGG;AACH,wBAAgB,aAAa,CACzB,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,UAAU,EAAE,eAAe,GAAG,IAAI,EAClC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,GACrC,KAAK,EAAE,CAwDT"}
1
+ {"version":3,"file":"layer-plan.d.ts","sourceRoot":"","sources":["../../src/internal/layer-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EACR,YAAY,EACZ,UAAU,EAEV,eAAe,EAClB,MAAM,aAAa,CAAC;AAIrB,MAAM,MAAM,cAAc,GAAG;IACzB,IAAI,EAAE,YAAY,GAAG,YAAY,CAAC;IAClC,UAAU,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,WAAW,KAAK;IAClB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAAC;CAC7C;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,GAAG,MAAM,CAMzE;AAwCD;;;GAGG;AACH,wBAAgB,aAAa,CACzB,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,UAAU,EAAE,eAAe,GAAG,IAAI,EAClC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,GACrC,KAAK,EAAE,CAwDT"}