@sigx/lynx-navigation 0.4.0 → 0.4.1

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 (184) 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.js +168 -0
  32. package/dist/components/Tabs.js.map +1 -0
  33. package/dist/define-routes.d.ts +1 -1
  34. package/dist/define-routes.d.ts.map +1 -1
  35. package/{src/define-routes.d.ts → dist/define-routes.js} +4 -2
  36. package/dist/define-routes.js.map +1 -0
  37. package/dist/hooks/use-focus.js +87 -0
  38. package/dist/hooks/use-focus.js.map +1 -0
  39. package/dist/hooks/use-hardware-back.js +84 -0
  40. package/dist/hooks/use-hardware-back.js.map +1 -0
  41. package/dist/hooks/use-linking-nav.d.ts +3 -3
  42. package/dist/hooks/use-linking-nav.d.ts.map +1 -1
  43. package/dist/hooks/use-linking-nav.js +109 -0
  44. package/dist/hooks/use-linking-nav.js.map +1 -0
  45. package/dist/hooks/use-nav-internal.d.ts +2 -2
  46. package/dist/hooks/use-nav-internal.d.ts.map +1 -1
  47. package/dist/hooks/use-nav-internal.js +55 -0
  48. package/dist/hooks/use-nav-internal.js.map +1 -0
  49. package/dist/hooks/use-nav-serializer.d.ts +1 -1
  50. package/dist/hooks/use-nav-serializer.d.ts.map +1 -1
  51. package/dist/hooks/use-nav-serializer.js +181 -0
  52. package/dist/hooks/use-nav-serializer.js.map +1 -0
  53. package/dist/hooks/use-nav.d.ts +2 -2
  54. package/dist/hooks/use-nav.d.ts.map +1 -1
  55. package/dist/hooks/use-nav.js +11 -0
  56. package/dist/hooks/use-nav.js.map +1 -0
  57. package/dist/hooks/use-params.d.ts +1 -1
  58. package/dist/hooks/use-params.d.ts.map +1 -1
  59. package/{src/hooks/use-params.d.ts → dist/hooks/use-params.js} +6 -2
  60. package/dist/hooks/use-params.js.map +1 -0
  61. package/dist/hooks/use-screen-chrome.d.ts +1 -1
  62. package/dist/hooks/use-screen-chrome.d.ts.map +1 -1
  63. package/dist/hooks/use-screen-chrome.js +102 -0
  64. package/dist/hooks/use-screen-chrome.js.map +1 -0
  65. package/dist/hooks/use-screen-options.d.ts +1 -1
  66. package/dist/hooks/use-screen-options.d.ts.map +1 -1
  67. package/dist/hooks/use-screen-options.js +43 -0
  68. package/dist/hooks/use-screen-options.js.map +1 -0
  69. package/dist/hooks/use-search.d.ts +1 -1
  70. package/dist/hooks/use-search.d.ts.map +1 -1
  71. package/{src/hooks/use-search.d.ts → dist/hooks/use-search.js} +6 -2
  72. package/dist/hooks/use-search.js.map +1 -0
  73. package/dist/href.d.ts +2 -2
  74. package/dist/href.d.ts.map +1 -1
  75. package/dist/href.js +57 -0
  76. package/dist/href.js.map +1 -0
  77. package/dist/index.d.ts +33 -33
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +30 -1160
  80. package/dist/index.js.map +1 -1
  81. package/dist/internal/layer-plan.d.ts +1 -1
  82. package/dist/internal/layer-plan.d.ts.map +1 -1
  83. package/dist/internal/layer-plan.js +102 -0
  84. package/dist/internal/layer-plan.js.map +1 -0
  85. package/dist/internal/screen-registry.d.ts +1 -1
  86. package/dist/internal/screen-registry.d.ts.map +1 -1
  87. package/{src/internal/screen-registry.d.ts → dist/internal/screen-registry.js} +32 -21
  88. package/dist/internal/screen-registry.js.map +1 -0
  89. package/{src/internal/screen-width.d.ts → dist/internal/screen-width.js} +17 -2
  90. package/dist/internal/screen-width.js.map +1 -0
  91. package/dist/navigator/core.d.ts +3 -3
  92. package/dist/navigator/core.d.ts.map +1 -1
  93. package/dist/navigator/core.js +394 -0
  94. package/dist/navigator/core.js.map +1 -0
  95. package/dist/register.d.ts +1 -1
  96. package/dist/register.d.ts.map +1 -1
  97. package/dist/register.js +2 -0
  98. package/dist/register.js.map +1 -0
  99. package/dist/types.js +9 -0
  100. package/dist/types.js.map +1 -0
  101. package/dist/url/build.js +30 -0
  102. package/dist/url/build.js.map +1 -0
  103. package/dist/url/compile.js +83 -0
  104. package/dist/url/compile.js.map +1 -0
  105. package/dist/url/format.js +102 -0
  106. package/dist/url/format.js.map +1 -0
  107. package/dist/url/index.d.ts +6 -6
  108. package/dist/url/index.d.ts.map +1 -1
  109. package/dist/url/index.js +13 -0
  110. package/dist/url/index.js.map +1 -0
  111. package/dist/url/parse.d.ts +1 -1
  112. package/dist/url/parse.d.ts.map +1 -1
  113. package/dist/url/parse.js +94 -0
  114. package/dist/url/parse.js.map +1 -0
  115. package/dist/url/registry.d.ts +2 -2
  116. package/dist/url/registry.d.ts.map +1 -1
  117. package/{src/url/registry.d.ts → dist/url/registry.js} +28 -12
  118. package/dist/url/registry.js.map +1 -0
  119. package/dist/url/validate.d.ts +1 -1
  120. package/dist/url/validate.d.ts.map +1 -1
  121. package/dist/url/validate.js +37 -0
  122. package/dist/url/validate.js.map +1 -0
  123. package/package.json +13 -12
  124. package/src/components/EdgeBackHandle.tsx +2 -2
  125. package/src/components/EntryScope.tsx +3 -3
  126. package/src/components/Header.tsx +3 -3
  127. package/src/components/Layer.tsx +3 -3
  128. package/src/components/Link.tsx +4 -4
  129. package/src/components/NavigationRoot.tsx +6 -6
  130. package/src/components/Screen.tsx +3 -3
  131. package/src/components/Stack.tsx +8 -8
  132. package/src/components/TabBar.tsx +1 -1
  133. package/src/define-routes.ts +1 -1
  134. package/src/hooks/use-focus.ts +2 -2
  135. package/src/hooks/use-hardware-back.ts +1 -1
  136. package/src/hooks/use-linking-nav.ts +4 -4
  137. package/src/hooks/use-nav-internal.ts +2 -2
  138. package/src/hooks/use-nav-serializer.ts +3 -3
  139. package/src/hooks/use-nav.ts +2 -2
  140. package/src/hooks/use-params.ts +2 -2
  141. package/src/hooks/use-screen-chrome.ts +3 -3
  142. package/src/hooks/use-screen-options.ts +3 -3
  143. package/src/hooks/use-search.ts +2 -2
  144. package/src/href.ts +6 -6
  145. package/src/index.ts +33 -33
  146. package/src/internal/layer-plan.ts +2 -2
  147. package/src/internal/screen-registry.ts +1 -1
  148. package/src/navigator/core.ts +3 -3
  149. package/src/register.ts +1 -1
  150. package/src/url/build.ts +2 -2
  151. package/src/url/index.ts +6 -6
  152. package/src/url/parse.ts +6 -6
  153. package/src/url/registry.ts +3 -3
  154. package/src/url/validate.ts +1 -1
  155. package/src/components/Drawer.d.ts +0 -55
  156. package/src/components/EdgeBackHandle.d.ts +0 -1
  157. package/src/components/EntryScope.d.ts +0 -25
  158. package/src/components/Header.d.ts +0 -6
  159. package/src/components/Layer.d.ts +0 -33
  160. package/src/components/Link.d.ts +0 -60
  161. package/src/components/NavigationRoot.d.ts +0 -36
  162. package/src/components/Screen.d.ts +0 -97
  163. package/src/components/Stack.d.ts +0 -90
  164. package/src/components/TabBar.d.ts +0 -38
  165. package/src/components/Tabs.d.ts +0 -109
  166. package/src/hooks/use-focus.d.ts +0 -45
  167. package/src/hooks/use-hardware-back.d.ts +0 -37
  168. package/src/hooks/use-linking-nav.d.ts +0 -91
  169. package/src/hooks/use-nav-internal.d.ts +0 -91
  170. package/src/hooks/use-nav-serializer.d.ts +0 -82
  171. package/src/hooks/use-nav.d.ts +0 -111
  172. package/src/hooks/use-screen-chrome.d.ts +0 -18
  173. package/src/hooks/use-screen-options.d.ts +0 -2
  174. package/src/href.d.ts +0 -54
  175. package/src/index.d.ts +0 -39
  176. package/src/internal/layer-plan.d.ts +0 -68
  177. package/src/navigator/core.d.ts +0 -96
  178. package/src/register.d.ts +0 -37
  179. package/src/types.d.ts +0 -217
  180. package/src/url/build.d.ts +0 -15
  181. package/src/url/compile.d.ts +0 -34
  182. package/src/url/format.d.ts +0 -28
  183. package/src/url/parse.d.ts +0 -20
  184. package/src/url/validate.d.ts +0 -23
@@ -0,0 +1,168 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ /**
3
+ * `<Tabs>` — Lynx tab navigator.
4
+ *
5
+ * Usage:
6
+ *
7
+ * ```tsx
8
+ * <NavigationRoot routes={routes} initialRoute="root">
9
+ * <Stack />
10
+ * </NavigationRoot>
11
+ *
12
+ * // The route "root" component renders:
13
+ * <Tabs initialTab="feed">
14
+ * <Tabs.Screen name="feed" icon={<FeedIcon />} label="Feed">
15
+ * <Stack initialRoute="feedHome" />
16
+ * </Tabs.Screen>
17
+ * <Tabs.Screen name="me" icon={<MeIcon />} label="Profile">
18
+ * <Stack initialRoute="profileHome" />
19
+ * </Tabs.Screen>
20
+ * <TabBar />
21
+ * </Tabs>
22
+ * ```
23
+ *
24
+ * Tab bodies stay mounted across switches (the inactive ones render with
25
+ * `display: 'none'`), so each tab's nested `<Stack>` keeps its history when
26
+ * the user flips back to it. The active tab is reactive via `useTabs()`.
27
+ *
28
+ * Per-tab stacks: each `<Tabs.Screen>` can host a `<Stack initialRoute="…">`
29
+ * which mints its own navigator. `useNav()` inside that subtree resolves to
30
+ * the tab's stack, so `nav.push('card-route', …)` stays inside the tab.
31
+ * Routes presented as `modal` / `fullScreen` / `transparent-modal` escalate
32
+ * up `nav.parent` to the root navigator automatically — they overlay the
33
+ * tabs UI (TabBar included) and dismiss back into the originating tab.
34
+ */
35
+ import { component, compound, defineInjectable, defineProvide, onUnmounted, signal, untrack, } from '@sigx/lynx';
36
+ /**
37
+ * Access the enclosing Tabs navigator. Throws when called outside `<Tabs>`.
38
+ */
39
+ export const useTabs = defineInjectable(() => {
40
+ throw new Error('[lynx-navigation] useTabs() called outside of a <Tabs> component.');
41
+ });
42
+ const useTabsRegistrar = defineInjectable(() => {
43
+ throw new Error('[lynx-navigation] <Tabs.Screen> rendered outside a <Tabs> component.');
44
+ });
45
+ /**
46
+ * @internal
47
+ * Provided by each `<Tabs.Screen>` so a nested `<Stack initialRoute>` can
48
+ * discover *which* tab it's hosted by, and gate its focus state on that
49
+ * tab being active. Throws when called outside a `<Tabs.Screen>` body so
50
+ * the gate degrades to "always active" via the caller's try/catch.
51
+ */
52
+ export const useTabScreenName = defineInjectable(() => {
53
+ throw new Error('[lynx-navigation] useTabScreenName() called outside a <Tabs.Screen> body.');
54
+ });
55
+ const _Tabs = component(({ props, slots }) => {
56
+ // Tabs are stored as a deeply-reactive proxy signal so `tabs` consumers
57
+ // re-render when registration changes. `activeSignal` uses the wrapped
58
+ // `{value}` pattern so we can write a `string | null` without the
59
+ // proxy treating the inner string as an object.
60
+ const tabs = signal([]);
61
+ const activeSignal = signal({
62
+ value: props.initialTab ?? null,
63
+ });
64
+ const registrar = {
65
+ register(info) {
66
+ // Wrap in untrack so registration writes inside `<Tabs.Screen>`'s
67
+ // setup phase don't notify the same setup effect that issued them
68
+ // — sigx's setup runs in a tracked scope by default.
69
+ untrack(() => {
70
+ const idx = tabs.findIndex((t) => t.name === info.name);
71
+ if (idx === -1)
72
+ tabs.push(info);
73
+ else
74
+ tabs[idx] = info;
75
+ if (activeSignal.value === null) {
76
+ activeSignal.value = info.name;
77
+ }
78
+ });
79
+ },
80
+ unregister(name) {
81
+ untrack(() => {
82
+ const idx = tabs.findIndex((t) => t.name === name);
83
+ if (idx !== -1)
84
+ tabs.splice(idx, 1);
85
+ if (activeSignal.value === name) {
86
+ activeSignal.value = tabs[0]?.name ?? null;
87
+ }
88
+ });
89
+ },
90
+ tabs,
91
+ activeSignal,
92
+ };
93
+ const nav = {
94
+ get active() {
95
+ // Empty-tabs state is rare in practice (no <Tabs.Screen> yet) but
96
+ // possible during initial render; expose '' rather than null so
97
+ // consumers can compare strings without narrowing.
98
+ return activeSignal.value ?? '';
99
+ },
100
+ setActive(name) {
101
+ // Silently ignore unknown names rather than writing them and
102
+ // hiding every tab body. Surfacing as a no-op gives consumers a
103
+ // predictable failure mode for typos / dynamic name sources.
104
+ if (!tabs.some((t) => t.name === name))
105
+ return;
106
+ activeSignal.value = name;
107
+ },
108
+ get tabs() {
109
+ return tabs;
110
+ },
111
+ };
112
+ defineProvide(useTabs, () => nav);
113
+ defineProvide(useTabsRegistrar, () => registrar);
114
+ return () => slots.default?.();
115
+ });
116
+ const TabsScreen = component(({ props, slots }) => {
117
+ const registrar = useTabsRegistrar();
118
+ // Capture `name` once at setup. Props is reactive in sigx, but using a
119
+ // changing `name` for an already-registered screen would be ambiguous
120
+ // (rename vs re-register?) — pin it and require callers to remount on
121
+ // identity change. This matches React Navigation's contract.
122
+ const name = props.name;
123
+ registrar.register({
124
+ name,
125
+ icon: props.icon,
126
+ label: props.label,
127
+ accessibilityLabel: props.accessibilityLabel,
128
+ });
129
+ onUnmounted(() => registrar.unregister(name));
130
+ // Expose this screen's tab name so a nested `<Stack initialRoute>` body
131
+ // can gate its locally-focused state on `tabs.active === name`.
132
+ defineProvide(useTabScreenName, () => name);
133
+ return () => {
134
+ // `display: none` keeps the body mounted so per-tab state survives
135
+ // tab switches. Read activeSignal here so re-activating triggers a
136
+ // re-render with display restored.
137
+ //
138
+ // Flex-fill long-form (`flex-grow/shrink/basis`) instead of
139
+ // `height: '100%'`. The percentage form only resolves against an
140
+ // explicit parent height, which means consumers had to wrap us
141
+ // in a `flexFill + height: '100%'` view to make us visible — and
142
+ // every Lynx app got that wrong (myself included) until we hit
143
+ // it on the showcase. With flex-fill we just take whatever space
144
+ // our parent flex container gives us; the parent only needs to
145
+ // be a flex column with a known height (e.g. SafeAreaView, which
146
+ // now defaults to that).
147
+ const active = registrar.activeSignal.value === name;
148
+ return (_jsx("view", { style: {
149
+ display: active ? 'flex' : 'none',
150
+ flexDirection: 'column',
151
+ width: '100%',
152
+ flexGrow: 1,
153
+ flexShrink: 1,
154
+ flexBasis: 0,
155
+ minHeight: 0,
156
+ }, children: slots.default?.() }));
157
+ };
158
+ });
159
+ /**
160
+ * Compound export. `Tabs` is the parent component; `Tabs.Screen` registers
161
+ * an individual tab. Matches the `Screen` / `Screen.Header` shape used
162
+ * elsewhere in this package and the daisyui `Modal` / `Modal.Header`
163
+ * convention.
164
+ */
165
+ export const Tabs = compound(_Tabs, {
166
+ Screen: TabsScreen,
167
+ });
168
+ //# sourceMappingURL=Tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tabs.js","sourceRoot":"","sources":["../../src/components/Tabs.tsx"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { RouteMap } from './types';
1
+ import type { RouteMap } from './types.js';
2
2
  /**
3
3
  * Define a typed route registry.
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"define-routes.d.ts","sourceRoot":"","sources":["../src/define-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEnE"}
1
+ {"version":3,"file":"define-routes.d.ts","sourceRoot":"","sources":["../src/define-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEnE"}
@@ -1,4 +1,3 @@
1
- import type { RouteMap } from './types';
2
1
  /**
3
2
  * Define a typed route registry.
4
3
  *
@@ -27,4 +26,7 @@ import type { RouteMap } from './types';
27
26
  * }
28
27
  * ```
29
28
  */
30
- export declare function defineRoutes<const T extends RouteMap>(routes: T): T;
29
+ export function defineRoutes(routes) {
30
+ return routes;
31
+ }
32
+ //# sourceMappingURL=define-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-routes.js","sourceRoot":"","sources":["../src/define-routes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,YAAY,CAA2B,MAAS;IAC5D,OAAO,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,87 @@
1
+ import { computed, effect, onUnmounted, untrack, } from '@sigx/lynx';
2
+ import { useNav } from './use-nav.js';
3
+ import { useCurrentEntry } from './use-nav-internal.js';
4
+ /**
5
+ * Reactive "is this screen the focused entry?" signal.
6
+ *
7
+ * Must be called from inside a component rendered as a route by `<Stack>` (or
8
+ * any other navigator that uses `<EntryScope>`); throws otherwise. The
9
+ * returned `Computed` reads `nav.current.key` and compares it to the entry
10
+ * the calling screen was mounted for, so any nav mutation that changes the
11
+ * top entry flips the value.
12
+ *
13
+ * Note: screens stay mounted when something is pushed on top of them — they
14
+ * just lose focus. Pop the new top off and they regain focus.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const Profile = component(() => {
19
+ * const isFocused = useIsFocused();
20
+ * return () => <text>{isFocused.value ? 'visible' : 'hidden'}</text>;
21
+ * });
22
+ * ```
23
+ */
24
+ export function useIsFocused() {
25
+ const nav = useNav();
26
+ // Capture the entry's key once at setup. The entry object provided
27
+ // through `defineProvide` may carry reactive dependencies; we only care
28
+ // about the immutable key of the entry this screen was mounted for.
29
+ const myKey = useCurrentEntry().key;
30
+ // AND in `nav.isLocallyFocused` so a screen in a nested stack (e.g. a
31
+ // per-tab `<Stack>`) reports unfocused when its enclosing tab is
32
+ // inactive, or when a modal on the root nav covers everything — even
33
+ // though it's still the top of its own (paused) stack. Root nav's
34
+ // `isLocallyFocused` is permanently true, so this reduces to the
35
+ // previous behavior for un-nested apps.
36
+ return computed(() => nav.current.key === myKey && nav.isLocallyFocused);
37
+ }
38
+ /**
39
+ * Run `cb` whenever this screen gains focus; run the returned cleanup when it
40
+ * loses focus or unmounts. Mirrors React Navigation's `useFocusEffect`.
41
+ *
42
+ * Lifecycle:
43
+ * - cb runs immediately if the screen is already focused at mount.
44
+ * - When the screen loses focus (something pushed on top), cleanup runs.
45
+ * - When focus returns (the cover is popped), `cb` runs again — yielding a
46
+ * fresh cleanup for the next blur.
47
+ * - On unmount, cleanup runs once if still focused.
48
+ *
49
+ * Common uses: subscribe to a data source while visible, track an analytics
50
+ * "screen view" event, start/stop a polling loop.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * useFocusEffect(() => {
55
+ * const id = setInterval(refresh, 5000);
56
+ * return () => clearInterval(id);
57
+ * });
58
+ * ```
59
+ */
60
+ export function useFocusEffect(cb) {
61
+ const isFocused = useIsFocused();
62
+ let cleanup;
63
+ const runner = effect(() => {
64
+ const focused = isFocused.value;
65
+ // Always tear down any previous focus session before starting a new
66
+ // one (or before going dormant on blur). Wrap `cb` in `untrack` so
67
+ // signals read inside the user-provided callback can't retrigger the
68
+ // outer effect and stack subscriptions.
69
+ if (typeof cleanup === 'function') {
70
+ const fn = cleanup;
71
+ cleanup = undefined;
72
+ fn();
73
+ }
74
+ if (focused) {
75
+ cleanup = untrack(() => cb());
76
+ }
77
+ });
78
+ onUnmounted(() => {
79
+ if (typeof cleanup === 'function') {
80
+ const fn = cleanup;
81
+ cleanup = undefined;
82
+ fn();
83
+ }
84
+ runner.stop();
85
+ });
86
+ }
87
+ //# sourceMappingURL=use-focus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-focus.js","sourceRoot":"","sources":["../../src/hooks/use-focus.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,QAAQ,EACR,MAAM,EACN,WAAW,EACX,OAAO,GAEV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,YAAY;IACxB,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,mEAAmE;IACnE,wEAAwE;IACxE,oEAAoE;IACpE,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC,GAAG,CAAC;IACpC,sEAAsE;IACtE,iEAAiE;IACjE,qEAAqE;IACrE,kEAAkE;IAClE,iEAAiE;IACjE,wCAAwC;IACxC,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,KAAK,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,cAAc,CAAC,EAA6B;IACxD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,IAAI,OAA4B,CAAC;IACjC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,EAAE;QACvB,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC;QAChC,oEAAoE;QACpE,mEAAmE;QACnE,qEAAqE;QACrE,wCAAwC;QACxC,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;YAChC,MAAM,EAAE,GAAG,OAAO,CAAC;YACnB,OAAO,GAAG,SAAS,CAAC;YACpB,EAAE,EAAE,CAAC;QACT,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACV,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,WAAW,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;YAChC,MAAM,EAAE,GAAG,OAAO,CAAC;YACnB,OAAO,GAAG,SAAS,CAAC;YACpB,EAAE,EAAE,CAAC;QACT,CAAC;QACD,MAAM,CAAC,IAAI,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,84 @@
1
+ import { onMounted } from '@sigx/lynx';
2
+ import { BackHandler } from '@sigx/lynx-linking';
3
+ import { useNav } from './use-nav.js';
4
+ /**
5
+ * Wire the Android hardware back button to the active navigator.
6
+ *
7
+ * Listens for `hardwareBackPress` events from `@sigx/lynx-linking`'s
8
+ * `BackHandler` (which the native side dispatches from
9
+ * `MainActivity.onBackPressed`). On press the handler walks to the
10
+ * deepest currently-focused navigator (per-tab `<Stack>`s register with
11
+ * their parent), then walks back up the `parent` chain looking for the
12
+ * first nav that `canGoBack`:
13
+ *
14
+ * - If any nav in the chain can go back → `nav.pop()` on that nav.
15
+ * - Otherwise → `BackHandler.exitApp()` (Android: `moveTaskToBack(true)`,
16
+ * keeps the bundle warm; iOS: rejects, since iOS doesn't permit
17
+ * programmatic termination).
18
+ *
19
+ * The traversal means you only need to call this once at the root — a
20
+ * back press from inside a tab pops that tab's nested stack first, only
21
+ * exiting the app once every level is at its base entry.
22
+ *
23
+ * Call this once in any component under `<NavigationRoot>` (typically a
24
+ * thin wrapper sibling to `<Stack />`). iOS doesn't fire the event so the
25
+ * hook is a no-op there.
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * const BackHandlerWiring = component(() => {
30
+ * useHardwareBack();
31
+ * return () => null;
32
+ * });
33
+ *
34
+ * <NavigationRoot routes={routes}>
35
+ * <BackHandlerWiring />
36
+ * <Stack />
37
+ * </NavigationRoot>
38
+ * ```
39
+ */
40
+ export function useHardwareBack() {
41
+ const nav = useNav();
42
+ onMounted(() => {
43
+ const sub = BackHandler.addEventListener(() => {
44
+ // Walk down to the deepest focused nav. Per-tab `<Stack>`s
45
+ // register themselves via `parent._children.add(nav)`; only one
46
+ // child per level is `isLocallyFocused` at a time, so the
47
+ // traversal is unambiguous. Falls back to the starting nav if
48
+ // no nested stacks are wired up.
49
+ let active = nav;
50
+ // Loop instead of recursion so a deeply-nested tree doesn't blow
51
+ // the stack on a synchronous back press.
52
+ outer: while (active._children.size > 0) {
53
+ for (const child of active._children) {
54
+ if (child.isLocallyFocused) {
55
+ active = child;
56
+ continue outer;
57
+ }
58
+ }
59
+ // No focused child at this level — stop drilling.
60
+ break;
61
+ }
62
+ // Walk back up the chain looking for the first nav that has
63
+ // something to pop. This is what makes "back press in trips
64
+ // tab with empty inner stack" fall through to root (which might
65
+ // have a modal on top) before exiting.
66
+ let cur = active;
67
+ while (cur) {
68
+ if (cur.canGoBack) {
69
+ cur.pop();
70
+ return true;
71
+ }
72
+ cur = cur.parent;
73
+ }
74
+ // At the root with nothing to pop — leave the app. Promise is
75
+ // fire-and-forget; we don't await because we want the back
76
+ // press to feel instant (Android starts the move-to-back
77
+ // transition immediately).
78
+ void BackHandler.exitApp();
79
+ return true;
80
+ });
81
+ return () => sub.remove();
82
+ });
83
+ }
84
+ //# sourceMappingURL=use-hardware-back.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-hardware-back.js","sourceRoot":"","sources":["../../src/hooks/use-hardware-back.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAY,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,eAAe;IAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,GAAG,EAAE;QACX,MAAM,GAAG,GAAG,WAAW,CAAC,gBAAgB,CAAC,GAAG,EAAE;YAC1C,2DAA2D;YAC3D,gEAAgE;YAChE,0DAA0D;YAC1D,8DAA8D;YAC9D,iCAAiC;YACjC,IAAI,MAAM,GAAQ,GAAG,CAAC;YACtB,iEAAiE;YACjE,yCAAyC;YACzC,KAAK,EAAE,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACtC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;oBACnC,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;wBACzB,MAAM,GAAG,KAAK,CAAC;wBACf,SAAS,KAAK,CAAC;oBACnB,CAAC;gBACL,CAAC;gBACD,kDAAkD;gBAClD,MAAM;YACV,CAAC;YACD,4DAA4D;YAC5D,4DAA4D;YAC5D,gEAAgE;YAChE,uCAAuC;YACvC,IAAI,GAAG,GAAe,MAAM,CAAC;YAC7B,OAAO,GAAG,EAAE,CAAC;gBACT,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;oBAChB,GAAG,CAAC,GAAG,EAAE,CAAC;oBACV,OAAO,IAAI,CAAC;gBAChB,CAAC;gBACD,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC;YACrB,CAAC;YACD,8DAA8D;YAC9D,2DAA2D;YAC3D,yDAAyD;YACzD,2BAA2B;YAC3B,KAAK,WAAW,CAAC,OAAO,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;IAC9B,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -1,6 +1,6 @@
1
- import { type Href } from '../href';
2
- import { type Nav } from './use-nav';
3
- import type { RouteMap } from '../types';
1
+ import { type Href } from '../href.js';
2
+ import { type Nav } from './use-nav.js';
3
+ import type { RouteMap } from '../types.js';
4
4
  export interface UseLinkingNavOptions {
5
5
  /**
6
6
  * Schemes/prefixes to strip before parsing. Matched in order; the first
@@ -1 +1 @@
1
- {"version":3,"file":"use-linking-nav.d.ts","sourceRoot":"","sources":["../../src/hooks/use-linking-nav.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAU,KAAK,GAAG,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,WAAW,oBAAoB;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;IAExC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpC;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,aAAa,CAAC,IAAI,GAAE,oBAAyB,GAAG,IAAI,CA0BnE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CASrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC3B,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,QAAQ,EAChB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,MAAM,GAAG,SAAS,GACzB,IAAI,CAkBN"}
1
+ {"version":3,"file":"use-linking-nav.d.ts","sourceRoot":"","sources":["../../src/hooks/use-linking-nav.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAU,KAAK,GAAG,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,oBAAoB;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;IAExC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpC;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,aAAa,CAAC,IAAI,GAAE,oBAAyB,GAAG,IAAI,CA0BnE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CASrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC3B,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,QAAQ,EAChB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,MAAM,GAAG,SAAS,GACzB,IAAI,CAkBN"}
@@ -0,0 +1,109 @@
1
+ import { onMounted } from '@sigx/lynx';
2
+ import { Linking } from '@sigx/lynx-linking';
3
+ import { parseHref } from '../href.js';
4
+ import { useNav } from './use-nav.js';
5
+ import { useNavRoutes } from './use-nav-internal.js';
6
+ /**
7
+ * Bridge `@sigx/lynx-linking` URL events into a `@sigx/lynx-navigation`
8
+ * navigator. Call once inside a `<NavigationRoot>` subtree.
9
+ *
10
+ * Handles both delivery modes:
11
+ * - **cold start** — `Linking.getInitialURL()` is read on mount and, if
12
+ * present, dispatched (replacing the initial route by default).
13
+ * - **warm start** — `Linking.addEventListener('url', ...)` subscribes for
14
+ * URLs delivered while the app is already running; each one is pushed.
15
+ *
16
+ * URL → route dispatch goes through `parseHref`, which matches the URL's
17
+ * pathname against the route registry seeded by `<NavigationRoot>`. Routes
18
+ * without a `path` template are never matched by deep links — only typed
19
+ * `<Link>` / `nav.push` calls reach them.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * import { useLinkingNav } from '@sigx/lynx-navigation';
24
+ *
25
+ * const DeepLinks = component(() => {
26
+ * useLinkingNav({
27
+ * prefixes: ['myapp://', 'https://myapp.com'],
28
+ * onUnmatched: (url) => console.warn('Unknown deep link:', url),
29
+ * });
30
+ * return () => null;
31
+ * });
32
+ *
33
+ * <NavigationRoot routes={routes}>
34
+ * <DeepLinks />
35
+ * <Stack />
36
+ * </NavigationRoot>
37
+ * ```
38
+ */
39
+ export function useLinkingNav(opts = {}) {
40
+ const nav = useNav();
41
+ const routes = useNavRoutes();
42
+ const dispatch = (url, kind) => {
43
+ if (opts.onURL) {
44
+ opts.onURL(url, nav);
45
+ return;
46
+ }
47
+ const stripped = _stripPrefix(url, opts.prefixes);
48
+ const href = parseHref(stripped);
49
+ if (!href) {
50
+ opts.onUnmatched?.(url);
51
+ return;
52
+ }
53
+ _navigateToHref(nav, routes, href, kind);
54
+ };
55
+ onMounted(() => {
56
+ const initial = Linking.getInitialURL();
57
+ if (initial) {
58
+ dispatch(initial, opts.replaceInitial === false ? 'push' : 'replace');
59
+ }
60
+ const sub = Linking.addEventListener('url', (e) => dispatch(e.url, 'push'));
61
+ return () => sub.remove();
62
+ });
63
+ }
64
+ /**
65
+ * Strip the first matching prefix from `url`, returning a pathname-like
66
+ * string. If no prefixes are provided, or none match, the original URL is
67
+ * returned unchanged so `parseHref` can still handle scheme-prefixed forms
68
+ * via `@sigx/lynx-linking`'s `parse`.
69
+ *
70
+ * Exported for unit testing — not part of the package public API.
71
+ */
72
+ export function _stripPrefix(url, prefixes) {
73
+ if (!prefixes || prefixes.length === 0)
74
+ return url;
75
+ for (const prefix of prefixes) {
76
+ if (url.startsWith(prefix)) {
77
+ const rest = url.slice(prefix.length);
78
+ return rest.startsWith('/') ? rest : `/${rest}`;
79
+ }
80
+ }
81
+ return url;
82
+ }
83
+ /**
84
+ * Call the right `nav.push` / `nav.replace` overload for `href`. The
85
+ * overloads differ in positional layout: routes with a params schema take
86
+ * `(name, params, search?, options?)`; routes without take `(name, search?,
87
+ * options?)`. Calling the wrong shape silently shifts `search` into the
88
+ * `options` slot, so we look the route up in the registry and branch.
89
+ *
90
+ * Exported for unit testing — not part of the package public API.
91
+ */
92
+ export function _navigateToHref(nav, routes, href, kind) {
93
+ const def = routes[href.route];
94
+ // Defensive: `parseHref` already validated against the registry, so this
95
+ // really shouldn't happen — but if the registry was cleared between
96
+ // parse and dispatch (multi-NavigationRoot scenarios), bail rather than
97
+ // throw.
98
+ if (!def)
99
+ return;
100
+ const hasParams = !!def.params;
101
+ const action = kind === 'replace' ? nav.replace : nav.push;
102
+ if (hasParams) {
103
+ action(href.route, href.params, href.search);
104
+ }
105
+ else {
106
+ action(href.route, href.search);
107
+ }
108
+ }
109
+ //# sourceMappingURL=use-linking-nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-linking-nav.js","sourceRoot":"","sources":["../../src/hooks/use-linking-nav.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { type SharedValue } from '@sigx/lynx';
2
- import type { ScreenRegistry } from '../internal/screen-registry';
3
- import type { RouteMap, StackEntry } from '../types';
2
+ import type { ScreenRegistry } from '../internal/screen-registry.js';
3
+ import type { RouteMap, StackEntry } from '../types.js';
4
4
  /**
5
5
  * Internal injectable: the `StackEntry` the calling screen was rendered for.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"use-nav-internal.d.ts","sourceRoot":"","sources":["../../src/hooks/use-nav-internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAErD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,uFAI1B,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,8FAEnC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,2DAIvB,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IACzB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C;;;;OAIG;IACH,gBAAgB,IAAI,IAAI,CAAC;IACzB,iEAAiE;IACjE,iBAAiB,IAAI,IAAI,CAAC;IAC1B,iEAAiE;IACjE,iBAAiB,IAAI,IAAI,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE;QACd,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;QACzC;;;;;;WAMG;QACH,UAAU,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;QAC3C,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;KACrD,CAAC;CACL;AAED,eAAO,MAAM,eAAe,+DAI1B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,iEAI5B,CAAC"}
1
+ {"version":3,"file":"use-nav-internal.d.ts","sourceRoot":"","sources":["../../src/hooks/use-nav-internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAExD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,uFAI1B,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,8FAEnC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,2DAIvB,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IACzB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C;;;;OAIG;IACH,gBAAgB,IAAI,IAAI,CAAC;IACzB,iEAAiE;IACjE,iBAAiB,IAAI,IAAI,CAAC;IAC1B,iEAAiE;IACjE,iBAAiB,IAAI,IAAI,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE;QACd,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;QACzC;;;;;;WAMG;QACH,UAAU,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;QAC3C,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;KACrD,CAAC;CACL;AAED,eAAO,MAAM,eAAe,+DAI1B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,iEAI5B,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { defineInjectable } from '@sigx/lynx';
2
+ /**
3
+ * Internal injectable: the `StackEntry` the calling screen was rendered for.
4
+ *
5
+ * Provided by `<EntryScope>` which `<Stack>` and `<ScreenContainer>` wrap
6
+ * around each screen component mount. Screens use this to derive their own
7
+ * focus state (`useIsFocused`, `useFocusEffect`) without having to track
8
+ * `entry.key` themselves.
9
+ *
10
+ * Default throws so calling `useIsFocused()` outside a screen mounted by a
11
+ * navigator surfaces a clear error rather than silently returning `false`.
12
+ */
13
+ export const useCurrentEntry = defineInjectable(() => {
14
+ throw new Error('[lynx-navigation] No screen entry in scope. `useIsFocused` / `useFocusEffect` must be called from a component rendered as a route by <Stack>.');
15
+ });
16
+ /**
17
+ * Soft companion to {@link useCurrentEntry} — returns the current scope's
18
+ * entry if any, `null` when called outside an `<EntryScope>` instead of
19
+ * throwing. Provided alongside the strict version by `<EntryScope>`.
20
+ *
21
+ * Used by chrome consumers (`useScreenChrome`) where "no scoped entry"
22
+ * is a legitimate state (a Stack chrome slot lives outside the screen's
23
+ * EntryScope) and the caller wants to soft-fallback to the navigator's
24
+ * destination entry rather than crash.
25
+ */
26
+ export const useCurrentEntryOptional = defineInjectable(() => null);
27
+ /**
28
+ * Internal injectable: the route registry passed into `<NavigationRoot>`.
29
+ * Components (Stack, Screen) read this to look up route definitions by name.
30
+ *
31
+ * Not exported from the package barrel — use `useNav()` for navigation, and
32
+ * the registry is implicit from `<NavigationRoot routes={...}>`.
33
+ */
34
+ export const useNavRoutes = defineInjectable(() => {
35
+ throw new Error('[lynx-navigation] No <NavigationRoot> found in the component tree.');
36
+ });
37
+ export const useNavInternals = defineInjectable(() => {
38
+ throw new Error('[lynx-navigation] No <NavigationRoot> found in the component tree.');
39
+ });
40
+ /**
41
+ * Internal injectable: the calling screen's `ScreenRegistry`.
42
+ *
43
+ * Provided by `<EntryScope>` alongside `useCurrentEntry`. The `<Screen>`
44
+ * component and its slot-filling sub-components write options and slot
45
+ * fills here; the navigator's persistent chrome (HeaderBar, TabBar — later
46
+ * slices) reads from this registry via `getScreenRegistry(key)` on the
47
+ * navigator state, which keys into a cross-entry map.
48
+ *
49
+ * Throws when used outside an EntryScope so calling `<Screen>` at the app
50
+ * root surfaces a clear error rather than silently no-op'ing.
51
+ */
52
+ export const useScreenRegistry = defineInjectable(() => {
53
+ throw new Error('[lynx-navigation] No screen registry in scope. `<Screen>` (and `<Screen.Header>`, etc.) must be used inside a route component rendered by `<Stack>`.');
54
+ });
55
+ //# sourceMappingURL=use-nav-internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-nav-internal.js","sourceRoot":"","sources":["../../src/hooks/use-nav-internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAoB,MAAM,YAAY,CAAC;AAIhE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,gBAAgB,CAAa,GAAG,EAAE;IAC7D,MAAM,IAAI,KAAK,CACX,+IAA+I,CAClJ,CAAC;AACN,CAAC,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,gBAAgB,CACnD,GAAG,EAAE,CAAC,IAAI,CACb,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAW,GAAG,EAAE;IACxD,MAAM,IAAI,KAAK,CACX,oEAAoE,CACvE,CAAC;AACN,CAAC,CAAC,CAAC;AA8CH,MAAM,CAAC,MAAM,eAAe,GAAG,gBAAgB,CAAe,GAAG,EAAE;IAC/D,MAAM,IAAI,KAAK,CACX,oEAAoE,CACvE,CAAC;AACN,CAAC,CAAC,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,CAAiB,GAAG,EAAE;IACnE,MAAM,IAAI,KAAK,CACX,sJAAsJ,CACzJ,CAAC;AACN,CAAC,CAAC,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { StackEntry } from '../types';
1
+ import type { StackEntry } from '../types.js';
2
2
  /**
3
3
  * Plain JSON snapshot of a navigator. The whole point of holding navigation
4
4
  * state in signals is that this is a one-liner — `JSON.stringify(nav.stack)`.
@@ -1 +1 @@
1
- {"version":3,"file":"use-nav-serializer.d.ts","sourceRoot":"","sources":["../../src/hooks/use-nav-serializer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAC9B,IAAI,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,WAAW,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,uBAAuB;IACpC,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,eAAe,GAAG,YAAY,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1G;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAsIvE"}
1
+ {"version":3,"file":"use-nav-serializer.d.ts","sourceRoot":"","sources":["../../src/hooks/use-nav-serializer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAC9B,IAAI,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,WAAW,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,uBAAuB;IACpC,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,eAAe,GAAG,YAAY,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1G;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAsIvE"}