@sigx/lynx-navigation 0.2.0 → 0.4.0

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 (193) hide show
  1. package/README.md +128 -8
  2. package/dist/components/EntryScope.d.ts +1 -1
  3. package/dist/components/EntryScope.d.ts.map +1 -1
  4. package/dist/components/Layer.d.ts +34 -0
  5. package/dist/components/Layer.d.ts.map +1 -0
  6. package/dist/components/Link.d.ts +2 -2
  7. package/dist/components/Link.d.ts.map +1 -1
  8. package/dist/components/NavigationRoot.d.ts +2 -2
  9. package/dist/components/NavigationRoot.d.ts.map +1 -1
  10. package/dist/components/Screen.d.ts +6 -6
  11. package/dist/components/Screen.d.ts.map +1 -1
  12. package/dist/components/Stack.d.ts +41 -16
  13. package/dist/components/Stack.d.ts.map +1 -1
  14. package/dist/components/TabBar.d.ts +19 -20
  15. package/dist/components/TabBar.d.ts.map +1 -1
  16. package/dist/components/Tabs.d.ts.map +1 -1
  17. package/dist/define-routes.d.ts +1 -1
  18. package/dist/define-routes.d.ts.map +1 -1
  19. package/dist/hooks/use-linking-nav.d.ts +3 -3
  20. package/dist/hooks/use-linking-nav.d.ts.map +1 -1
  21. package/dist/hooks/use-nav-internal.d.ts +21 -3
  22. package/dist/hooks/use-nav-internal.d.ts.map +1 -1
  23. package/dist/hooks/use-nav-serializer.d.ts +1 -1
  24. package/dist/hooks/use-nav-serializer.d.ts.map +1 -1
  25. package/dist/hooks/use-nav.d.ts +2 -2
  26. package/dist/hooks/use-nav.d.ts.map +1 -1
  27. package/dist/hooks/use-params.d.ts +1 -1
  28. package/dist/hooks/use-params.d.ts.map +1 -1
  29. package/dist/hooks/use-screen-chrome.d.ts +19 -0
  30. package/dist/hooks/use-screen-chrome.d.ts.map +1 -0
  31. package/dist/hooks/use-screen-options.d.ts +1 -1
  32. package/dist/hooks/use-screen-options.d.ts.map +1 -1
  33. package/dist/hooks/use-search.d.ts +1 -1
  34. package/dist/hooks/use-search.d.ts.map +1 -1
  35. package/dist/href.d.ts +2 -2
  36. package/dist/href.d.ts.map +1 -1
  37. package/dist/index.d.ts +33 -31
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1160 -29
  40. package/dist/index.js.map +1 -1
  41. package/dist/internal/layer-plan.d.ts +69 -0
  42. package/dist/internal/layer-plan.d.ts.map +1 -0
  43. package/dist/internal/screen-registry.d.ts +1 -1
  44. package/dist/internal/screen-registry.d.ts.map +1 -1
  45. package/dist/internal/screen-width.d.ts +9 -7
  46. package/dist/internal/screen-width.d.ts.map +1 -1
  47. package/dist/navigator/core.d.ts +5 -4
  48. package/dist/navigator/core.d.ts.map +1 -1
  49. package/dist/register.d.ts +1 -1
  50. package/dist/register.d.ts.map +1 -1
  51. package/dist/url/index.d.ts +6 -6
  52. package/dist/url/index.d.ts.map +1 -1
  53. package/dist/url/parse.d.ts +1 -1
  54. package/dist/url/parse.d.ts.map +1 -1
  55. package/dist/url/registry.d.ts +2 -2
  56. package/dist/url/registry.d.ts.map +1 -1
  57. package/dist/url/validate.d.ts +1 -1
  58. package/dist/url/validate.d.ts.map +1 -1
  59. package/package.json +11 -10
  60. package/src/components/Drawer.d.ts +55 -0
  61. package/src/components/EdgeBackHandle.d.ts +1 -0
  62. package/src/components/EdgeBackHandle.tsx +2 -2
  63. package/{dist/components/EntryScope.js → src/components/EntryScope.d.ts} +7 -15
  64. package/src/components/EntryScope.tsx +15 -4
  65. package/src/components/Header.d.ts +6 -0
  66. package/src/components/Header.tsx +3 -3
  67. package/src/components/Layer.d.ts +33 -0
  68. package/src/components/Layer.tsx +96 -0
  69. package/src/components/Link.d.ts +60 -0
  70. package/src/components/Link.tsx +4 -4
  71. package/src/components/NavigationRoot.d.ts +36 -0
  72. package/src/components/NavigationRoot.tsx +6 -6
  73. package/src/components/Screen.d.ts +97 -0
  74. package/src/components/Screen.tsx +13 -11
  75. package/src/components/Stack.d.ts +90 -0
  76. package/src/components/Stack.tsx +142 -98
  77. package/src/components/TabBar.d.ts +38 -0
  78. package/src/components/TabBar.tsx +22 -22
  79. package/src/components/Tabs.d.ts +109 -0
  80. package/src/components/Tabs.tsx +15 -1
  81. package/{dist/define-routes.js → src/define-routes.d.ts} +2 -4
  82. package/src/define-routes.ts +1 -1
  83. package/src/hooks/use-focus.d.ts +45 -0
  84. package/src/hooks/use-focus.ts +2 -2
  85. package/src/hooks/use-hardware-back.d.ts +37 -0
  86. package/src/hooks/use-hardware-back.ts +1 -1
  87. package/src/hooks/use-linking-nav.d.ts +91 -0
  88. package/src/hooks/use-linking-nav.ts +4 -4
  89. package/src/hooks/use-nav-internal.d.ts +91 -0
  90. package/src/hooks/use-nav-internal.ts +24 -3
  91. package/src/hooks/use-nav-serializer.d.ts +82 -0
  92. package/src/hooks/use-nav-serializer.ts +3 -3
  93. package/src/hooks/use-nav.d.ts +111 -0
  94. package/src/hooks/use-nav.ts +2 -2
  95. package/{dist/hooks/use-params.js → src/hooks/use-params.d.ts} +2 -6
  96. package/src/hooks/use-params.ts +2 -2
  97. package/src/hooks/use-screen-chrome.d.ts +18 -0
  98. package/src/hooks/use-screen-chrome.ts +122 -0
  99. package/src/hooks/use-screen-options.d.ts +2 -0
  100. package/src/hooks/use-screen-options.ts +3 -3
  101. package/{dist/hooks/use-search.js → src/hooks/use-search.d.ts} +2 -6
  102. package/src/hooks/use-search.ts +2 -2
  103. package/src/href.d.ts +54 -0
  104. package/src/href.ts +6 -6
  105. package/src/index.d.ts +39 -0
  106. package/src/index.ts +33 -31
  107. package/src/internal/layer-plan.d.ts +68 -0
  108. package/src/internal/layer-plan.ts +187 -0
  109. package/{dist/internal/screen-registry.js → src/internal/screen-registry.d.ts} +21 -32
  110. package/src/internal/screen-registry.ts +1 -1
  111. package/src/internal/screen-width.d.ts +17 -0
  112. package/src/internal/screen-width.ts +22 -14
  113. package/src/navigator/core.d.ts +96 -0
  114. package/src/navigator/core.ts +17 -6
  115. package/src/register.d.ts +37 -0
  116. package/src/register.ts +1 -1
  117. package/src/types.d.ts +217 -0
  118. package/src/url/build.d.ts +15 -0
  119. package/src/url/build.ts +2 -2
  120. package/src/url/compile.d.ts +34 -0
  121. package/src/url/format.d.ts +28 -0
  122. package/src/url/index.ts +6 -6
  123. package/src/url/parse.d.ts +20 -0
  124. package/src/url/parse.ts +6 -6
  125. package/{dist/url/registry.js → src/url/registry.d.ts} +12 -28
  126. package/src/url/registry.ts +3 -3
  127. package/src/url/validate.d.ts +23 -0
  128. package/src/url/validate.ts +1 -1
  129. package/dist/components/Drawer.js +0 -74
  130. package/dist/components/Drawer.js.map +0 -1
  131. package/dist/components/EdgeBackHandle.js +0 -144
  132. package/dist/components/EdgeBackHandle.js.map +0 -1
  133. package/dist/components/EntryScope.js.map +0 -1
  134. package/dist/components/Header.js +0 -103
  135. package/dist/components/Header.js.map +0 -1
  136. package/dist/components/Link.js +0 -51
  137. package/dist/components/Link.js.map +0 -1
  138. package/dist/components/NavigationRoot.js +0 -67
  139. package/dist/components/NavigationRoot.js.map +0 -1
  140. package/dist/components/Screen.js +0 -94
  141. package/dist/components/Screen.js.map +0 -1
  142. package/dist/components/ScreenContainer.d.ts +0 -18
  143. package/dist/components/ScreenContainer.d.ts.map +0 -1
  144. package/dist/components/ScreenContainer.js +0 -77
  145. package/dist/components/ScreenContainer.js.map +0 -1
  146. package/dist/components/Stack.js +0 -221
  147. package/dist/components/Stack.js.map +0 -1
  148. package/dist/components/TabBar.js +0 -63
  149. package/dist/components/TabBar.js.map +0 -1
  150. package/dist/components/Tabs.js +0 -154
  151. package/dist/components/Tabs.js.map +0 -1
  152. package/dist/define-routes.js.map +0 -1
  153. package/dist/hooks/use-focus.js +0 -87
  154. package/dist/hooks/use-focus.js.map +0 -1
  155. package/dist/hooks/use-hardware-back.js +0 -84
  156. package/dist/hooks/use-hardware-back.js.map +0 -1
  157. package/dist/hooks/use-linking-nav.js +0 -109
  158. package/dist/hooks/use-linking-nav.js.map +0 -1
  159. package/dist/hooks/use-nav-internal.js +0 -44
  160. package/dist/hooks/use-nav-internal.js.map +0 -1
  161. package/dist/hooks/use-nav-serializer.js +0 -181
  162. package/dist/hooks/use-nav-serializer.js.map +0 -1
  163. package/dist/hooks/use-nav.js +0 -11
  164. package/dist/hooks/use-nav.js.map +0 -1
  165. package/dist/hooks/use-params.js.map +0 -1
  166. package/dist/hooks/use-screen-options.js +0 -43
  167. package/dist/hooks/use-screen-options.js.map +0 -1
  168. package/dist/hooks/use-search.js.map +0 -1
  169. package/dist/href.js +0 -57
  170. package/dist/href.js.map +0 -1
  171. package/dist/internal/screen-registry.js.map +0 -1
  172. package/dist/internal/screen-width.js +0 -30
  173. package/dist/internal/screen-width.js.map +0 -1
  174. package/dist/navigator/core.js +0 -383
  175. package/dist/navigator/core.js.map +0 -1
  176. package/dist/register.js +0 -2
  177. package/dist/register.js.map +0 -1
  178. package/dist/types.js +0 -9
  179. package/dist/types.js.map +0 -1
  180. package/dist/url/build.js +0 -30
  181. package/dist/url/build.js.map +0 -1
  182. package/dist/url/compile.js +0 -83
  183. package/dist/url/compile.js.map +0 -1
  184. package/dist/url/format.js +0 -102
  185. package/dist/url/format.js.map +0 -1
  186. package/dist/url/index.js +0 -13
  187. package/dist/url/index.js.map +0 -1
  188. package/dist/url/parse.js +0 -94
  189. package/dist/url/parse.js.map +0 -1
  190. package/dist/url/registry.js.map +0 -1
  191. package/dist/url/validate.js +0 -37
  192. package/dist/url/validate.js.map +0 -1
  193. package/src/components/ScreenContainer.tsx +0 -114
@@ -0,0 +1,111 @@
1
+ import type { RegisteredRoutes, RouteId, RouteParams, RouteSearch } from '../register';
2
+ import type { PopOptions, PushOptions, RouteRequiresParams, StackEntry, TransitionState } from '../types';
3
+ /**
4
+ * Subset of registered route names that declare a `params` schema (and so
5
+ * require a `params` argument when navigating).
6
+ *
7
+ * Computed via mapped-type filtering rather than a conditional inside the
8
+ * method signature: when a conditional like `RouteRequiresParams<R[K]>` is
9
+ * embedded inside a generic method parameter, TS evaluates it at definition
10
+ * time with K bound to the *whole* union of route ids — which distributes
11
+ * `RouteRequiresParams` over every route and collapses the result to
12
+ * `boolean`, breaking the conditional. Filtering once at the type level avoids
13
+ * the issue and produces clean overload candidates.
14
+ */
15
+ export type RoutesWithParams = {
16
+ [K in RouteId]: RouteRequiresParams<RegisteredRoutes[K]> extends true ? K : never;
17
+ }[RouteId];
18
+ /** Routes that don't declare a `params` schema. */
19
+ export type RoutesWithoutParams = Exclude<RouteId, RoutesWithParams>;
20
+ /**
21
+ * The navigator handle returned by `useNav()`.
22
+ *
23
+ * Read access (`current`, `stack`, `canGoBack`) is reactive — these properties
24
+ * are getters that read from the underlying stack signal, so accessing them
25
+ * inside a component's render function (or inside `effect` / `computed`) takes
26
+ * a reactive dependency. Mutating methods (`push`, `pop`, etc.) trigger the
27
+ * dependents to update.
28
+ *
29
+ * `push` and `replace` are split into two overloads — one for routes without a
30
+ * params schema (no params arg) and one for routes with a params schema
31
+ * (params required). See `RoutesWithParams` above for why this isn't a single
32
+ * conditional return type.
33
+ */
34
+ export interface Nav {
35
+ /** Push a route that has no params schema. */
36
+ push<K extends RoutesWithoutParams>(name: K, search?: RouteSearch<K>, options?: PushOptions): void;
37
+ /** Push a route that requires params. */
38
+ push<K extends RoutesWithParams>(name: K, params: RouteParams<K>, search?: RouteSearch<K>, options?: PushOptions): void;
39
+ /** Replace the top entry — same overload pattern as push. */
40
+ replace<K extends RoutesWithoutParams>(name: K, search?: RouteSearch<K>, options?: PushOptions): void;
41
+ replace<K extends RoutesWithParams>(name: K, params: RouteParams<K>, search?: RouteSearch<K>, options?: PushOptions): void;
42
+ /** Pop one or more entries off the top of the stack. */
43
+ pop(count?: number, options?: PopOptions): void;
44
+ /** Pop entries until the named route is at the top. */
45
+ popTo<K extends RouteId>(name: K): void;
46
+ /** Pop all the way to the root entry. */
47
+ popToRoot(): void;
48
+ /** Wholesale-replace the stack. */
49
+ reset(state: {
50
+ stack: ReadonlyArray<StackEntry>;
51
+ }): void;
52
+ /** Dismiss the topmost modal stack (no-op if none active). */
53
+ dismiss(): void;
54
+ /** Currently-focused entry. Reactive via property access. */
55
+ readonly current: StackEntry;
56
+ /** Full stack, top last. Reactive. */
57
+ readonly stack: ReadonlyArray<StackEntry>;
58
+ /** Whether the user can go back from the current entry. Reactive. */
59
+ readonly canGoBack: boolean;
60
+ /**
61
+ * Parent navigator (e.g. the root nav above a per-tab `<Stack>`), or null
62
+ * at the root. Set when a `<Stack>` mints its own navigator via
63
+ * `<Stack initialRoute="…">` — that stack's `useNav()` returns a nav
64
+ * whose `parent` is the enclosing nav.
65
+ *
66
+ * `push` calls for routes whose resolved presentation is non-`card`
67
+ * (`modal` / `fullScreen` / `transparent-modal`) escalate up the
68
+ * `parent` chain automatically — you don't normally need to reach
69
+ * through `parent` to present modals. `parent` is exposed as an escape
70
+ * hatch for power users (e.g. imperative `parent.pop()` from a child
71
+ * stack). Avoid pushing card routes onto `parent` directly — that
72
+ * defeats per-tab stack isolation.
73
+ */
74
+ readonly parent: Nav | null;
75
+ /**
76
+ * Whether this navigator is part of the currently-focused chain. True
77
+ * for the root nav at all times; for a nested nav (e.g. a per-tab
78
+ * stack), true only when its host entry is the top of `parent`, the
79
+ * parent itself is locally focused, and any extra gate (e.g. the
80
+ * enclosing tab is active) reports active.
81
+ *
82
+ * Reactive. `useIsFocused()` ANDs `nav.current.key === myKey` with
83
+ * `nav.isLocallyFocused`.
84
+ */
85
+ readonly isLocallyFocused: boolean;
86
+ /**
87
+ * @internal
88
+ * Set of child navigators (per-tab `<Stack>` instances) that have
89
+ * registered themselves under this nav. Used by `useHardwareBack` to
90
+ * find the deepest currently-focused nav and route the back press
91
+ * there before falling back up the chain.
92
+ *
93
+ * Not part of the public API — leading-underscore marks it as
94
+ * implementation detail.
95
+ */
96
+ readonly _children: Set<Nav>;
97
+ /**
98
+ * In-flight transition, or null when navigation is at rest. Reactive —
99
+ * `<Stack>` reads this to decide whether to render one screen or two
100
+ * (during a slide transition both the outgoing and incoming screens
101
+ * stay mounted with `useAnimatedStyle`-driven transforms).
102
+ */
103
+ readonly transition: TransitionState | null;
104
+ }
105
+ /**
106
+ * Access the innermost navigator. Provided by `<NavigationRoot>` via
107
+ * `defineProvide`. Throws when called outside a NavigationRoot subtree.
108
+ *
109
+ * Mirrors `@sigx/router`'s `useRouter` pattern (`packages/router/src/router.ts:30`).
110
+ */
111
+ export declare const useNav: import("@sigx/runtime-core").InjectableFunction<Nav>;
@@ -1,12 +1,12 @@
1
1
  import { defineInjectable } from '@sigx/lynx';
2
- import type { RegisteredRoutes, RouteId, RouteParams, RouteSearch } from '../register.js';
2
+ import type { RegisteredRoutes, RouteId, RouteParams, RouteSearch } from '../register';
3
3
  import type {
4
4
  PopOptions,
5
5
  PushOptions,
6
6
  RouteRequiresParams,
7
7
  StackEntry,
8
8
  TransitionState,
9
- } from '../types.js';
9
+ } from '../types';
10
10
 
11
11
  /**
12
12
  * Subset of registered route names that declare a `params` schema (and so
@@ -1,4 +1,4 @@
1
- import { useNav } from './use-nav.js';
1
+ import type { RouteId, RouteParams } from '../register';
2
2
  /**
3
3
  * Read the typed params for the current screen, asserted against the named
4
4
  * route from the registry.
@@ -15,8 +15,4 @@ import { useNav } from './use-nav.js';
15
15
  * update for the same mounted screen" path in v0.1, so a snapshot at setup
16
16
  * time is correct.
17
17
  */
18
- export function useParams(_name) {
19
- const nav = useNav();
20
- return nav.current.params;
21
- }
22
- //# sourceMappingURL=use-params.js.map
18
+ export declare function useParams<K extends RouteId>(_name: K): RouteParams<K>;
@@ -1,5 +1,5 @@
1
- import type { RouteId, RouteParams } from '../register.js';
2
- import { useNav } from './use-nav.js';
1
+ import type { RouteId, RouteParams } from '../register';
2
+ import { useNav } from './use-nav';
3
3
 
4
4
  /**
5
5
  * Read the typed params for the current screen, asserted against the named
@@ -0,0 +1,18 @@
1
+ import type { ScreenSlotFills } from '../types';
2
+ export interface ScreenChrome {
3
+ /** Resolved screen title — `options.title` (string or getter) or the route name as fallback. Reactive. */
4
+ readonly title: string;
5
+ /** Whether the header should render. Defaults to true unless the screen set `headerShown: false`. Reactive. */
6
+ readonly headerShown: boolean;
7
+ /** True when the current stack has more than one entry — i.e. there's something to pop back to. Reactive. */
8
+ readonly canGoBack: boolean;
9
+ /** Pop the top entry. No-op when `!canGoBack`. */
10
+ pop(): void;
11
+ /** Full header override slot, if `<Screen.Header>` was set. Render its return value in place of the default layout. */
12
+ readonly header: ScreenSlotFills['header'] | undefined;
13
+ /** Left-aligned slot (typically a back button). Reactive. */
14
+ readonly headerLeft: ScreenSlotFills['headerLeft'] | undefined;
15
+ /** Right-aligned slot (typically actions). Reactive. */
16
+ readonly headerRight: ScreenSlotFills['headerRight'] | undefined;
17
+ }
18
+ export declare function useScreenChrome(): ScreenChrome;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `useScreenChrome` — reactive read of the currently-focused screen's
3
+ * options + slot fills, plus navigation helpers a header would need
4
+ * (canGoBack, pop).
5
+ *
6
+ * The built-in `<Header />` reads this same data via internal hooks.
7
+ * `useScreenChrome` exposes it as a public API so theme packages
8
+ * (`@sigx/lynx-daisyui`, custom designs) can build their own header
9
+ * components without depending on internal modules.
10
+ *
11
+ * Resolution rules:
12
+ *
13
+ * - **Inside a screen body** (i.e. inside an EntryScope whose entry is
14
+ * on the nearest `useNav()`'s stack), bind to **this entry's**
15
+ * registry. Useful for modal screens that render their own
16
+ * NavHeader inside — the chrome slides with the sheet.
17
+ * - **Outside any matching EntryScope** (slot of `<Stack>`, persistent
18
+ * root-level Header, etc.), bind to the *destination* entry of the
19
+ * current nav state — what the navigator is settling on once the
20
+ * in-flight transition completes. Push: the new top (already at
21
+ * nav.current). Pop: the entry being revealed
22
+ * (`transition.underneathEntry`), *not* the one being animated off.
23
+ * Using the destination means the bar reflects what the user is
24
+ * navigating *to*, immediately, with no end-of-animation snap.
25
+ *
26
+ * Every property is a getter — reading inside a render / `computed`
27
+ * subscribes to the underlying signal, so consumers re-render when
28
+ * title / slots change.
29
+ */
30
+ import { useNav } from './use-nav';
31
+ import { useCurrentEntryOptional, useNavInternals } from './use-nav-internal';
32
+ import type { ScreenSlotFills, StackEntry } from '../types';
33
+
34
+ export interface ScreenChrome {
35
+ /** Resolved screen title — `options.title` (string or getter) or the route name as fallback. Reactive. */
36
+ readonly title: string;
37
+ /** Whether the header should render. Defaults to true unless the screen set `headerShown: false`. Reactive. */
38
+ readonly headerShown: boolean;
39
+ /** True when the current stack has more than one entry — i.e. there's something to pop back to. Reactive. */
40
+ readonly canGoBack: boolean;
41
+ /** Pop the top entry. No-op when `!canGoBack`. */
42
+ pop(): void;
43
+ /** Full header override slot, if `<Screen.Header>` was set. Render its return value in place of the default layout. */
44
+ readonly header: ScreenSlotFills['header'] | undefined;
45
+ /** Left-aligned slot (typically a back button). Reactive. */
46
+ readonly headerLeft: ScreenSlotFills['headerLeft'] | undefined;
47
+ /** Right-aligned slot (typically actions). Reactive. */
48
+ readonly headerRight: ScreenSlotFills['headerRight'] | undefined;
49
+ }
50
+
51
+ export function useScreenChrome(): ScreenChrome {
52
+ const nav = useNav();
53
+ const internals = useNavInternals();
54
+
55
+ // The candidate "scoped" entry, if we happen to be rendered inside
56
+ // an EntryScope. May belong to a DIFFERENT nav than `nav` — e.g.
57
+ // when NavHeader is placed in a per-tab `<Stack>`'s chrome slot,
58
+ // it sees the outer (root) EntryScope's entry but its `useNav()`
59
+ // returns the inner per-tab nav. We only honor the pin when the
60
+ // entry is actually on this nav's stack; otherwise we're crossing
61
+ // scopes and the destination-entry path is correct.
62
+ //
63
+ // `useCurrentEntryOptional` is the soft companion to
64
+ // `useCurrentEntry` — it returns `null` outside any EntryScope
65
+ // rather than throwing, which is the right semantic for a chrome
66
+ // consumer that *might* be a Stack slot.
67
+ const candidate: StackEntry | null = useCurrentEntryOptional();
68
+
69
+ const getDestinationEntry = (): StackEntry => {
70
+ const t = nav.transition;
71
+ if (t) {
72
+ return t.kind === 'pop' ? t.underneathEntry : t.topEntry;
73
+ }
74
+ return nav.current;
75
+ };
76
+
77
+ const getEntry = (): StackEntry => {
78
+ if (candidate) {
79
+ const stack = nav.stack;
80
+ if (stack.some((e) => e.key === candidate!.key)) {
81
+ return candidate;
82
+ }
83
+ }
84
+ return getDestinationEntry();
85
+ };
86
+
87
+ return {
88
+ get title() {
89
+ const entry = getEntry();
90
+ const reg = internals.screens.get(entry.key);
91
+ const t = reg?.options.title;
92
+ if (typeof t === 'function') return t();
93
+ if (typeof t === 'string') return t;
94
+ return entry.route;
95
+ },
96
+ get headerShown() {
97
+ const reg = internals.screens.get(getEntry().key);
98
+ return reg?.options.headerShown !== false;
99
+ },
100
+ get canGoBack() {
101
+ const entry = getEntry();
102
+ const stack = nav.stack;
103
+ const idx = stack.findIndex((e) => e.key === entry.key);
104
+ return idx > 0;
105
+ },
106
+ pop() {
107
+ nav.pop();
108
+ },
109
+ get header() {
110
+ const reg = internals.screens.get(getEntry().key);
111
+ return reg?.slots.header;
112
+ },
113
+ get headerLeft() {
114
+ const reg = internals.screens.get(getEntry().key);
115
+ return reg?.slots.headerLeft;
116
+ },
117
+ get headerRight() {
118
+ const reg = internals.screens.get(getEntry().key);
119
+ return reg?.slots.headerRight;
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,2 @@
1
+ import type { ScreenOptions } from '../types';
2
+ export declare function useScreenOptions(optionsOrFn: ScreenOptions | (() => ScreenOptions)): void;
@@ -22,9 +22,9 @@
22
22
  * pass a getter pay for the subscription; hosts that pass an object don't.
23
23
  */
24
24
  import { effect, onUnmounted } from '@sigx/lynx';
25
- import { useScreenRegistry } from './use-nav-internal.js';
26
- import { mergeOptions } from '../internal/screen-registry.js';
27
- import type { ScreenOptions } from '../types.js';
25
+ import { useScreenRegistry } from './use-nav-internal';
26
+ import { mergeOptions } from '../internal/screen-registry';
27
+ import type { ScreenOptions } from '../types';
28
28
 
29
29
  export function useScreenOptions(
30
30
  optionsOrFn: ScreenOptions | (() => ScreenOptions),
@@ -1,4 +1,4 @@
1
- import { useNav } from './use-nav.js';
1
+ import type { RouteId, RouteSearch } from '../register';
2
2
  /**
3
3
  * Read the typed search/query params for the current screen, asserted against
4
4
  * the named route from the registry.
@@ -7,8 +7,4 @@ import { useNav } from './use-nav.js';
7
7
  * reactivity story — each navigation triggers a remount via the entry-keyed
8
8
  * Stack, so a setup-time snapshot is sufficient.
9
9
  */
10
- export function useSearch(_name) {
11
- const nav = useNav();
12
- return nav.current.search;
13
- }
14
- //# sourceMappingURL=use-search.js.map
10
+ export declare function useSearch<K extends RouteId>(_name: K): RouteSearch<K>;
@@ -1,5 +1,5 @@
1
- import type { RouteId, RouteSearch } from '../register.js';
2
- import { useNav } from './use-nav.js';
1
+ import type { RouteId, RouteSearch } from '../register';
2
+ import { useNav } from './use-nav';
3
3
 
4
4
  /**
5
5
  * Read the typed search/query params for the current screen, asserted against
package/src/href.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { RouteId, RouteParams, RouteSearch } from './register';
2
+ import type { RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav';
3
+ /**
4
+ * A typed reference to a navigation target — what `<Link to={...}>` consumes
5
+ * and what `hrefFor()` produces.
6
+ *
7
+ * Holds both the typed pieces (route name, params, search) and the serialized
8
+ * URL form (when the route declares a `path` template). Either side can drive
9
+ * navigation — typed for in-app links, URL for deep links / sharing.
10
+ */
11
+ export interface Href<K extends RouteId = RouteId> {
12
+ readonly route: K;
13
+ readonly params: RouteParams<K>;
14
+ readonly search: RouteSearch<K>;
15
+ /** URL form. `null` when the route declares no `path` template. */
16
+ readonly url: string | null;
17
+ }
18
+ /**
19
+ * Build a typed Href for a given route. Validates params against the route's
20
+ * schema at runtime; type-checks them at compile time.
21
+ *
22
+ * Overloaded the same way as `nav.push` — one signature for routes without a
23
+ * params schema, one for routes that require params. See `RoutesWithParams`
24
+ * in `./hooks/use-nav.js` for why this isn't expressed as a single conditional.
25
+ *
26
+ * Requires a `<NavigationRoot>` (or an explicit `_setRouteRegistry` call) to
27
+ * have run — the URL form is built against the active route registry. The
28
+ * typed pieces (`route`, `params`, `search`) are returned regardless; `url`
29
+ * is `null` when the route declares no `path` template.
30
+ *
31
+ * Schema validation errors throw — pass already-validated values from
32
+ * `useParams` / `useSearch` to round-trip safely, or wrap in try/catch when
33
+ * building hrefs from external input.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * hrefFor('profile', { id: '42' }); // → { route, params, search: {}, url: '/users/42' }
38
+ * hrefFor('profile', { id: '42' }, { tab: 'about' });
39
+ * hrefFor('home'); // params arg omitted (no schema)
40
+ * ```
41
+ */
42
+ export declare function hrefFor<K extends RoutesWithoutParams>(name: K, search?: RouteSearch<K>): Href<K>;
43
+ export declare function hrefFor<K extends RoutesWithParams>(name: K, params: RouteParams<K>, search?: RouteSearch<K>): Href<K>;
44
+ /**
45
+ * Parse a URL string into a typed Href against the registered routes.
46
+ * Returns `null` if no route's `path` template matches the URL or if the
47
+ * extracted params/search fail the route's schema validation.
48
+ *
49
+ * Accepts both absolute (`myapp://host/path?q`) and pathname-only
50
+ * (`/path?q`) forms — the pathname is matched against each route's
51
+ * compiled template. Iteration order is the registration order; first match
52
+ * wins.
53
+ */
54
+ export declare function parseHref(url: string): Href | null;
package/src/href.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { RouteId, RouteParams, RouteSearch } from './register.js';
2
- import type { RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav.js';
3
- import { buildUrl } from './url/build.js';
4
- import { parseHrefImpl } from './url/parse.js';
5
- import { getRouteRegistry } from './url/registry.js';
6
- import { validateSync } from './url/validate.js';
1
+ import type { RouteId, RouteParams, RouteSearch } from './register';
2
+ import type { RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav';
3
+ import { buildUrl } from './url/build';
4
+ import { parseHrefImpl } from './url/parse';
5
+ import { getRouteRegistry } from './url/registry';
6
+ import { validateSync } from './url/validate';
7
7
 
8
8
  /**
9
9
  * A typed reference to a navigation target — what `<Link to={...}>` consumes
package/src/index.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @sigx/lynx-navigation — type-first native stack router.
3
+ *
4
+ * Phase 0.1 (current): typed registry, stack runtime, NavigationRoot + Stack.
5
+ * Coming next: Screen with slot-based header API, MTS transitions, Tabs.
6
+ */
7
+ export { defineRoutes } from './define-routes';
8
+ export type { Register, RegisteredRoutes, RouteId, RouteParams, RouteSearch } from './register';
9
+ export { useNav } from './hooks/use-nav';
10
+ export type { Nav, RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav';
11
+ export { useParams } from './hooks/use-params';
12
+ export { useSearch } from './hooks/use-search';
13
+ export { useHardwareBack } from './hooks/use-hardware-back';
14
+ export { useLinkingNav } from './hooks/use-linking-nav';
15
+ export type { UseLinkingNavOptions } from './hooks/use-linking-nav';
16
+ export { useIsFocused, useFocusEffect } from './hooks/use-focus';
17
+ export { useScreenOptions } from './hooks/use-screen-options';
18
+ export { useScreenChrome } from './hooks/use-screen-chrome';
19
+ export type { ScreenChrome } from './hooks/use-screen-chrome';
20
+ export { useNavSerializer, NAV_SNAPSHOT_VERSION, } from './hooks/use-nav-serializer';
21
+ export type { NavSnapshot, NavStorageAdapter, UseNavSerializerOptions, } from './hooks/use-nav-serializer';
22
+ export { hrefFor, parseHref } from './href';
23
+ export type { Href } from './href';
24
+ export { _setRouteRegistry, _clearRouteRegistry } from './url/registry';
25
+ export { compilePath } from './url/compile';
26
+ export type { CompiledPath } from './url/compile';
27
+ export { NavigationRoot } from './components/NavigationRoot';
28
+ export { Stack } from './components/Stack';
29
+ export { Screen } from './components/Screen';
30
+ export { Header } from './components/Header';
31
+ export { Tabs, useTabs } from './components/Tabs';
32
+ export type { TabInfo, TabsNav } from './components/Tabs';
33
+ export { TabBar } from './components/TabBar';
34
+ export type { TabRenderContext } from './components/TabBar';
35
+ export { Drawer, useDrawer } from './components/Drawer';
36
+ export type { DrawerNav } from './components/Drawer';
37
+ export { Link } from './components/Link';
38
+ export type { LinkProps } from './components/Link';
39
+ export type { ComponentLike, EmptyParams, InferOutput, ParamsOf, PopOptions, Presentation, PushOptions, RouteDefinition, RouteMap, RouteRequiresParams, ScreenOptions, ScreenSlotFills, SearchOf, StackEntry, StandardSchemaV1, TransitionKind, TransitionRole, TransitionState, } from './types';
package/src/index.ts CHANGED
@@ -5,46 +5,48 @@
5
5
  * Coming next: Screen with slot-based header API, MTS transitions, Tabs.
6
6
  */
7
7
 
8
- export { defineRoutes } from './define-routes.js';
9
- export type { Register, RegisteredRoutes, RouteId, RouteParams, RouteSearch } from './register.js';
10
- export { useNav } from './hooks/use-nav.js';
11
- export type { Nav, RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav.js';
12
- export { useParams } from './hooks/use-params.js';
13
- export { useSearch } from './hooks/use-search.js';
14
- export { useHardwareBack } from './hooks/use-hardware-back.js';
15
- export { useLinkingNav } from './hooks/use-linking-nav.js';
16
- export type { UseLinkingNavOptions } from './hooks/use-linking-nav.js';
17
- export { useIsFocused, useFocusEffect } from './hooks/use-focus.js';
18
- export { useScreenOptions } from './hooks/use-screen-options.js';
8
+ export { defineRoutes } from './define-routes';
9
+ export type { Register, RegisteredRoutes, RouteId, RouteParams, RouteSearch } from './register';
10
+ export { useNav } from './hooks/use-nav';
11
+ export type { Nav, RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav';
12
+ export { useParams } from './hooks/use-params';
13
+ export { useSearch } from './hooks/use-search';
14
+ export { useHardwareBack } from './hooks/use-hardware-back';
15
+ export { useLinkingNav } from './hooks/use-linking-nav';
16
+ export type { UseLinkingNavOptions } from './hooks/use-linking-nav';
17
+ export { useIsFocused, useFocusEffect } from './hooks/use-focus';
18
+ export { useScreenOptions } from './hooks/use-screen-options';
19
+ export { useScreenChrome } from './hooks/use-screen-chrome';
20
+ export type { ScreenChrome } from './hooks/use-screen-chrome';
19
21
  export {
20
22
  useNavSerializer,
21
23
  NAV_SNAPSHOT_VERSION,
22
- } from './hooks/use-nav-serializer.js';
24
+ } from './hooks/use-nav-serializer';
23
25
  export type {
24
26
  NavSnapshot,
25
27
  NavStorageAdapter,
26
28
  UseNavSerializerOptions,
27
- } from './hooks/use-nav-serializer.js';
28
- export { hrefFor, parseHref } from './href.js';
29
- export type { Href } from './href.js';
29
+ } from './hooks/use-nav-serializer';
30
+ export { hrefFor, parseHref } from './href';
31
+ export type { Href } from './href';
30
32
  // URL bridge internals: `_setRouteRegistry` is a leading-underscore export —
31
33
  // intended for tests, deep-link bootstrap before a NavigationRoot mounts, and
32
34
  // any other integration that needs to seed the registry imperatively.
33
- export { _setRouteRegistry, _clearRouteRegistry } from './url/registry.js';
34
- export { compilePath } from './url/compile.js';
35
- export type { CompiledPath } from './url/compile.js';
36
- export { NavigationRoot } from './components/NavigationRoot.js';
37
- export { Stack } from './components/Stack.js';
38
- export { Screen } from './components/Screen.js';
39
- export { Header } from './components/Header.js';
40
- export { Tabs, useTabs } from './components/Tabs.js';
41
- export type { TabInfo, TabsNav } from './components/Tabs.js';
42
- export { TabBar } from './components/TabBar.js';
43
- export type { TabRenderContext } from './components/TabBar.js';
44
- export { Drawer, useDrawer } from './components/Drawer.js';
45
- export type { DrawerNav } from './components/Drawer.js';
46
- export { Link } from './components/Link.js';
47
- export type { LinkProps } from './components/Link.js';
35
+ export { _setRouteRegistry, _clearRouteRegistry } from './url/registry';
36
+ export { compilePath } from './url/compile';
37
+ export type { CompiledPath } from './url/compile';
38
+ export { NavigationRoot } from './components/NavigationRoot';
39
+ export { Stack } from './components/Stack';
40
+ export { Screen } from './components/Screen';
41
+ export { Header } from './components/Header';
42
+ export { Tabs, useTabs } from './components/Tabs';
43
+ export type { TabInfo, TabsNav } from './components/Tabs';
44
+ export { TabBar } from './components/TabBar';
45
+ export type { TabRenderContext } from './components/TabBar';
46
+ export { Drawer, useDrawer } from './components/Drawer';
47
+ export type { DrawerNav } from './components/Drawer';
48
+ export { Link } from './components/Link';
49
+ export type { LinkProps } from './components/Link';
48
50
  export type {
49
51
  ComponentLike,
50
52
  EmptyParams,
@@ -64,4 +66,4 @@ export type {
64
66
  TransitionKind,
65
67
  TransitionRole,
66
68
  TransitionState,
67
- } from './types.js';
69
+ } from './types';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Pure layer-plan computation for `<Stack>`'s render.
3
+ *
4
+ * Given (stack, transition, progress), produces an ordered list of
5
+ * `Layer`s — each is an entry to render plus an optional transform
6
+ * spec for animation. The Stack render emits one absolutely-positioned
7
+ * `<view>` per layer, stacked bottom-to-top in document order.
8
+ *
9
+ * Why this is its own module: the layer-selection logic is the only
10
+ * non-obvious part of the navigator's render path, and the rules are
11
+ * easier to read (and unit-test) as a pure function over the
12
+ * navigator's state than as inline render branches.
13
+ *
14
+ * Rules:
15
+ *
16
+ * - **Idle (no transition).** Render the topmost non-overlay entry
17
+ * as the base, plus every overlay entry above it. Overlays
18
+ * (`modal` / `fullScreen` / `transparent-modal`) keep their
19
+ * underneath mounted; cards replace their underneath in the base
20
+ * layer.
21
+ *
22
+ * - **Card transition.** Two layers: the underneath entry (animated
23
+ * with the parallax-card-underneath spec) and the top entry
24
+ * (animated with the slide-in-from-right spec). After the
25
+ * transition completes, the idle rule kicks in — the underneath
26
+ * unmounts because the new top becomes the sole base.
27
+ *
28
+ * - **Overlay transition.** The full idle layer stack up through the
29
+ * underneath entry stays static (no transform). The animated top
30
+ * is the only layer with a transform. After the transition, the
31
+ * overlay either joins the static idle stack (push) or unmounts
32
+ * (pop).
33
+ *
34
+ * The Layer.key for the Stack render is
35
+ * `layer-${entry.key}-${animVariant(layer.animation)}`. The variant
36
+ * suffix forces a remount when an entry transitions from animated to
37
+ * static (or vice versa) — `useAnimatedStyle` can't re-bind mid-life,
38
+ * so we get a fresh `useAnimatedStyle` call per animation state.
39
+ * Modal underneath layers never animate, so they stay statically
40
+ * keyed across the modal lifecycle and their state (per-tab Stack,
41
+ * scroll, in-flight inputs) survives.
42
+ */
43
+ import type { SharedValue } from '@sigx/lynx';
44
+ import type { Presentation, StackEntry, TransitionState } from '../types';
45
+ export type LayerAnimation = {
46
+ axis: 'translateX' | 'translateY';
47
+ inputRange: readonly [number, number];
48
+ outputRange: readonly [number, number];
49
+ progress: SharedValue<number>;
50
+ };
51
+ export interface Layer {
52
+ /** The entry whose component renders inside this layer. */
53
+ readonly entry: StackEntry;
54
+ /** When non-null, the layer's host view binds a `useAnimatedStyle` mapper. */
55
+ readonly animation: LayerAnimation | null;
56
+ }
57
+ export declare function isOverlayPresentation(p: Presentation): boolean;
58
+ /**
59
+ * Suffix used in a layer's render key. Stable for the layer's
60
+ * lifetime (same entry, same animation kind) and changes when the
61
+ * animation transitions on/off so the Layer remounts and rebinds.
62
+ */
63
+ export declare function animationVariant(animation: LayerAnimation | null): string;
64
+ /**
65
+ * Compute the visible-layer list for one render of `<Stack>`. Pure —
66
+ * unit-testable independently of the renderer.
67
+ */
68
+ export declare function computeLayers(stack: readonly StackEntry[], transition: TransitionState | null, progress: SharedValue<number> | null): Layer[];