@sigx/lynx-navigation 0.1.3 → 0.2.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.
package/README.md CHANGED
@@ -98,7 +98,66 @@ and every other API gets typed.
98
98
  ### `<Stack>`
99
99
 
100
100
  Renders the topmost stack entry plus the entry beneath it during
101
- transitions. No props driven entirely by the navigator state.
101
+ transitions. By default it binds to the enclosing navigator (the root one
102
+ under `<NavigationRoot>`).
103
+
104
+ Pass `initialRoute` to make `<Stack>` create its **own** nested navigator —
105
+ this is how per-tab stacks work. See [Per-tab nested stacks](#per-tab-nested-stacks).
106
+
107
+ ```tsx
108
+ <Stack initialRoute="tripsHome" />
109
+ <Stack initialRoute="profile" initialParams={{ id: 'me' }} />
110
+ ```
111
+
112
+ ### Per-tab nested stacks
113
+
114
+ Drop a `<Stack initialRoute=…>` inside each `<Tabs.Screen>` to give every
115
+ tab its own back-stack. Card-presentation pushes stay inside the tab;
116
+ modal / fullScreen / transparent-modal pushes **escalate** to the root
117
+ navigator so they overlay the entire tabs UI (TabBar included).
118
+
119
+ ```tsx
120
+ <NavigationRoot routes={routes} initialRoute="root">
121
+ <Header />
122
+ <Stack /> {/* root navigator — renders the `root` entry below */}
123
+ </NavigationRoot>
124
+
125
+ // `root` route renders this:
126
+ <Tabs initialTab="trips">
127
+ <Tabs.Screen name="trips" label="Trips">
128
+ <Stack initialRoute="tripsHome" />
129
+ </Tabs.Screen>
130
+ <Tabs.Screen name="map" label="Map">
131
+ <Stack initialRoute="mapHome" />
132
+ </Tabs.Screen>
133
+ <TabBar />
134
+ </Tabs>
135
+ ```
136
+
137
+ Inside a tab body, `useNav()` resolves to the **innermost** navigator:
138
+
139
+ - `nav.push('tripDetail', { tripId })` — `tripDetail` is a card route →
140
+ pushed onto the trips tab's stack, TabBar stays visible.
141
+ - `nav.push('newTrip')` — `newTrip` is `presentation: 'modal'` → walks up
142
+ `nav.parent` to root and pushes there, overlays the whole tabs UI.
143
+ - `nav.replace(...)` is **strictly local** and never escalates (asymmetric
144
+ with `push` by design — keeps the root stack stable).
145
+ - Hardware/edge back pops the focused inner nav first; only falls through
146
+ to root once the inner stack is empty.
147
+ - `useIsFocused()` is `true` only when the screen is the top of its own
148
+ nav **and** every ancestor is focused (parent's current entry matches +
149
+ enclosing tab is active).
150
+
151
+ **Limitations (current slice)**:
152
+
153
+ - Pushing a modal re-mounts the underlying root entry (the Tabs UI),
154
+ resetting inner-tab stack state. To preserve inner state across modals,
155
+ hoist app state out of screens.
156
+ - One global route registry — there's no per-tab whitelist yet. Deep-link
157
+ routing always pushes against the innermost nav of the caller site
158
+ (modal routes still escalate). A future slice may add `<Stack routes={…}>`.
159
+ - `useNavSerializer` snapshots one nav only — nested-tab stack state isn't
160
+ persisted across reload yet.
102
161
 
103
162
  ### `<Screen>`
104
163
 
@@ -143,7 +202,10 @@ screen's `useScreenOptions(...)` registration.
143
202
 
144
203
  ### `<Tabs>` + `<Tabs.Screen>` + `<TabBar>`
145
204
 
146
- Persistent tab navigator. Each tab keeps its own stack across switches.
205
+ Persistent tab navigator. Each tab body stays mounted (hidden via
206
+ `display: none`) so switching tabs preserves state. Drop a `<Stack
207
+ initialRoute=…>` inside a `<Tabs.Screen>` to give that tab its own
208
+ back-stack — see [Per-tab nested stacks](#per-tab-nested-stacks).
147
209
 
148
210
  ```tsx
149
211
  <Tabs initialTab="home">
@@ -1,21 +1,66 @@
1
- import { type ComponentFactory } from '@sigx/lynx';
1
+ import { type ComponentFactory, type Define } from '@sigx/lynx';
2
+ type StackProps =
3
+ /**
4
+ * Mint a nested navigator with this route at its base. When set, the
5
+ * `<Stack>` becomes the owner of a new `NavigatorState` and provides
6
+ * `useNav` / `useNavInternals` / `useNavRoutes` to its subtree, so
7
+ * `nav.push('card-route', …)` from inside the stack stays *inside* it
8
+ * (e.g. for per-tab stacks). Routes presented as `modal` / `fullScreen` /
9
+ * `transparent-modal` automatically escalate to the parent navigator
10
+ * via `nav.parent`, walking up until they reach the root — so modals
11
+ * still overlay the whole app.
12
+ *
13
+ * Omit to render the *enclosing* navigator's stack (the default — this
14
+ * is how `<NavigationRoot> → <Stack />` works).
15
+ */
16
+ Define.Prop<'initialRoute', string>
17
+ /** Initial params for the nested-stack base entry. */
18
+ & Define.Prop<'initialParams', Record<string, unknown>>
19
+ /** Initial search for the nested-stack base entry. */
20
+ & Define.Prop<'initialSearch', Record<string, unknown>>;
2
21
  /**
3
22
  * Stack navigator — renders the topmost stack entry's component at rest, or
4
23
  * the top + underneath entries during a transition.
5
24
  *
6
- * **Idle**: just the top entry, full-bleed, no transform. The screen
7
- * component mounts directly so it can use its own layout (no extra absolute
8
- * positioning that would break percentage heights).
25
+ * Two modes:
26
+ *
27
+ * **Bound** (no `initialRoute`): renders the enclosing navigator's stack.
28
+ * This is the shape used directly under `<NavigationRoot>` and is what
29
+ * single-stack apps want.
30
+ *
31
+ * **Nested-owner** (`initialRoute="…"`): mints a fresh `NavigatorState` with
32
+ * its own progress `SharedValue` and edge-back gesture, and provides
33
+ * `useNav` / `useNavInternals` / `useNavRoutes` to its subtree. `useNav()`
34
+ * inside this stack returns the nested nav; `nav.parent` points to the
35
+ * enclosing one. Per-tab stacks are the canonical use case:
36
+ *
37
+ * ```tsx
38
+ * <Tabs initialTab="trips">
39
+ * <Tabs.Screen name="trips"><Stack initialRoute="tripsHome" /></Tabs.Screen>
40
+ * <Tabs.Screen name="map"><Stack initialRoute="mapHome" /></Tabs.Screen>
41
+ * </Tabs>
42
+ * ```
43
+ *
44
+ * Modal/fullScreen pushes escalate up the parent chain automatically — so
45
+ * `nav.push('newTrip')` from inside Trips (where `newTrip` is `modal`)
46
+ * walks to root and overlays the whole UI. `replace` stays strictly local
47
+ * (asymmetric with `push`) so a modal `replace` never wipes the root stack.
9
48
  *
10
- * **Transitioning**: two `<ScreenContainer>` instances stacked absolutely,
11
- * each with an MT-driven `translateX` that reads from the navigator's
12
- * progress `SharedValue`. The host's BG thread doesn't tick per frame
13
- * `useAnimatedStyle` runs the interpolation entirely on MT.
49
+ * **Render strategy** (same in both modes):
50
+ * - **Idle**: just the top entry, full-bleed, no transform. The screen
51
+ * component mounts directly so it can use its own layout (no extra
52
+ * absolute positioning that would break percentage heights).
53
+ * - **Transitioning**: two `<ScreenContainer>` instances stacked
54
+ * absolutely, each with an MT-driven `translateX` that reads from the
55
+ * navigator's progress `SharedValue`. The host's BG thread doesn't tick
56
+ * per frame — `useAnimatedStyle` runs the interpolation entirely on MT.
14
57
  *
15
58
  * `key={top.key}` keeps the idle render's component instance stable across
16
59
  * unrelated re-renders. During transitions, composite keys
17
- * (`${entry.key}-${role}-${kind}`) ensure a fresh mount per role/kind pair so
18
- * the `useAnimatedStyle` binding is set with the right input/output ranges.
60
+ * (`${entry.key}-${role}-${kind}`) ensure a fresh mount per role/kind pair
61
+ * so the `useAnimatedStyle` binding is set with the right input/output
62
+ * ranges.
19
63
  */
20
- export declare const Stack: ComponentFactory<{}, void, unknown>;
64
+ export declare const Stack: ComponentFactory<StackProps, void, {}>;
65
+ export {};
21
66
  //# sourceMappingURL=Stack.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Stack.d.ts","sourceRoot":"","sources":["../../src/components/Stack.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,gBAAgB,EAAoB,MAAM,YAAY,CAAC;AAQhF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,KAAK,qCA2FhB,CAAC"}
1
+ {"version":3,"file":"Stack.d.ts","sourceRoot":"","sources":["../../src/components/Stack.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOH,KAAK,gBAAgB,EACrB,KAAK,MAAM,EAEd,MAAM,YAAY,CAAC;AAgBpB,KAAK,UAAU;AACX;;;;;;;;;;;;GAYG;AACD,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC;AACrC,sDAAsD;GACpD,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACvD,sDAAsD;GACpD,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAI5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,KAAK,wCA+NhB,CAAC"}
@@ -1,33 +1,179 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@sigx/lynx/jsx-runtime";
2
- import { component } from '@sigx/lynx';
2
+ import { component, defineProvide, effect, onUnmounted, untrack, useSharedValue, } from '@sigx/lynx';
3
3
  import { Suspense, isLazyComponent } from '@sigx/lynx';
4
+ import { createNavigatorState } from '../navigator/core.js';
4
5
  import { useNav } from '../hooks/use-nav.js';
5
- import { useNavInternals, useNavRoutes } from '../hooks/use-nav-internal.js';
6
+ import { useCurrentEntry, useNavInternals, useNavRoutes, } from '../hooks/use-nav-internal.js';
6
7
  import { ScreenContainer } from './ScreenContainer.js';
7
8
  import { EdgeBackHandle } from './EdgeBackHandle.js';
8
9
  import { EntryScope } from './EntryScope.js';
10
+ import { useTabScreenName, useTabs } from './Tabs.js';
11
+ let _nestedKeyCounter = 0;
9
12
  /**
10
13
  * Stack navigator — renders the topmost stack entry's component at rest, or
11
14
  * the top + underneath entries during a transition.
12
15
  *
13
- * **Idle**: just the top entry, full-bleed, no transform. The screen
14
- * component mounts directly so it can use its own layout (no extra absolute
15
- * positioning that would break percentage heights).
16
+ * Two modes:
16
17
  *
17
- * **Transitioning**: two `<ScreenContainer>` instances stacked absolutely,
18
- * each with an MT-driven `translateX` that reads from the navigator's
19
- * progress `SharedValue`. The host's BG thread doesn't tick per frame —
20
- * `useAnimatedStyle` runs the interpolation entirely on MT.
18
+ * **Bound** (no `initialRoute`): renders the enclosing navigator's stack.
19
+ * This is the shape used directly under `<NavigationRoot>` and is what
20
+ * single-stack apps want.
21
+ *
22
+ * **Nested-owner** (`initialRoute="…"`): mints a fresh `NavigatorState` with
23
+ * its own progress `SharedValue` and edge-back gesture, and provides
24
+ * `useNav` / `useNavInternals` / `useNavRoutes` to its subtree. `useNav()`
25
+ * inside this stack returns the nested nav; `nav.parent` points to the
26
+ * enclosing one. Per-tab stacks are the canonical use case:
27
+ *
28
+ * ```tsx
29
+ * <Tabs initialTab="trips">
30
+ * <Tabs.Screen name="trips"><Stack initialRoute="tripsHome" /></Tabs.Screen>
31
+ * <Tabs.Screen name="map"><Stack initialRoute="mapHome" /></Tabs.Screen>
32
+ * </Tabs>
33
+ * ```
34
+ *
35
+ * Modal/fullScreen pushes escalate up the parent chain automatically — so
36
+ * `nav.push('newTrip')` from inside Trips (where `newTrip` is `modal`)
37
+ * walks to root and overlays the whole UI. `replace` stays strictly local
38
+ * (asymmetric with `push`) so a modal `replace` never wipes the root stack.
39
+ *
40
+ * **Render strategy** (same in both modes):
41
+ * - **Idle**: just the top entry, full-bleed, no transform. The screen
42
+ * component mounts directly so it can use its own layout (no extra
43
+ * absolute positioning that would break percentage heights).
44
+ * - **Transitioning**: two `<ScreenContainer>` instances stacked
45
+ * absolutely, each with an MT-driven `translateX` that reads from the
46
+ * navigator's progress `SharedValue`. The host's BG thread doesn't tick
47
+ * per frame — `useAnimatedStyle` runs the interpolation entirely on MT.
21
48
  *
22
49
  * `key={top.key}` keeps the idle render's component instance stable across
23
50
  * unrelated re-renders. During transitions, composite keys
24
- * (`${entry.key}-${role}-${kind}`) ensure a fresh mount per role/kind pair so
25
- * the `useAnimatedStyle` binding is set with the right input/output ranges.
51
+ * (`${entry.key}-${role}-${kind}`) ensure a fresh mount per role/kind pair
52
+ * so the `useAnimatedStyle` binding is set with the right input/output
53
+ * ranges.
26
54
  */
27
- export const Stack = component(() => {
28
- const nav = useNav();
55
+ export const Stack = component(({ props }) => {
56
+ // Capture enclosing scope's nav + routes + internals BEFORE any of the
57
+ // defineProvide calls below override them for descendants. These are
58
+ // always the "outer" values regardless of whether this Stack is bound
59
+ // or nested-owner.
60
+ const parentNav = useNav();
29
61
  const routes = useNavRoutes();
30
- const internals = useNavInternals();
62
+ const parentInternals = useNavInternals();
63
+ // Decide mode at setup. `props.initialRoute` is captured once — the
64
+ // alternative (reactive switch between bound and nested-owner) would
65
+ // need to dispose and recreate the inner nav, which would lose all
66
+ // pushed state. Reasonable to pin it.
67
+ const initialName = props.initialRoute;
68
+ const isNested = typeof initialName === 'string' && initialName.length > 0;
69
+ let nav;
70
+ let internals;
71
+ if (isNested) {
72
+ if (!routes[initialName]) {
73
+ throw new Error(`[lynx-navigation] <Stack initialRoute='${initialName}'>: ` +
74
+ `route is not registered. Known routes: ` +
75
+ `${Object.keys(routes).join(', ') || '(none)'}`);
76
+ }
77
+ // Host entry — the parent's current top *when this Stack mounts*.
78
+ // Used by the focus chain so the nested nav is only "locally
79
+ // focused" while its host entry is still the top of the parent.
80
+ // Wrapped in try/catch because `<Stack initialRoute>` *may* be
81
+ // placed outside an EntryScope (e.g. directly under
82
+ // `<NavigationRoot>`); in that case there's no host-entry gate to
83
+ // apply and we just rely on `parent.isLocallyFocused`.
84
+ let hostEntryKey = null;
85
+ try {
86
+ hostEntryKey = useCurrentEntry().key;
87
+ }
88
+ catch {
89
+ hostEntryKey = null;
90
+ }
91
+ // Enclosing tab name (if any). Lets the focus chain gate on tab
92
+ // active state — Trips' inner stack reports `isLocallyFocused: false`
93
+ // while the user is on the Map tab, even though it's the top of
94
+ // its own stack.
95
+ let tabName = null;
96
+ let tabsHandle = null;
97
+ try {
98
+ tabName = useTabScreenName();
99
+ tabsHandle = useTabs();
100
+ }
101
+ catch {
102
+ tabName = null;
103
+ tabsHandle = null;
104
+ }
105
+ // Inherit animation enablement from the parent — if the root was
106
+ // created with `animated={false}` (tests), nested stacks should
107
+ // also commit instantly so test assertions don't have to wait on
108
+ // a SharedValue that won't tick.
109
+ const animationsEnabled = parentInternals.progress !== null;
110
+ const progressSv = useSharedValue(0);
111
+ const presentation = (routes[initialName].presentation ?? 'card');
112
+ // Counter-derived suffix keeps base-entry keys unique across
113
+ // concurrent nested stacks in a tab app. Plain `Math.random` would
114
+ // do but a counter is deterministic for test snapshots.
115
+ _nestedKeyCounter += 1;
116
+ const initial = {
117
+ key: `nested-${initialName}-${_nestedKeyCounter}`,
118
+ route: initialName,
119
+ params: props.initialParams ?? {},
120
+ search: props.initialSearch ?? {},
121
+ state: undefined,
122
+ presentation,
123
+ };
124
+ const navState = createNavigatorState({
125
+ routes,
126
+ initial,
127
+ progress: animationsEnabled ? progressSv : undefined,
128
+ parent: parentNav,
129
+ // Start un-focused; the effect below flips this once we observe
130
+ // the parent's current entry / tab-active state.
131
+ initialLocallyFocused: false,
132
+ });
133
+ nav = navState.nav;
134
+ internals = {
135
+ progress: animationsEnabled ? progressSv : null,
136
+ beginBackGesture: navState._gesture.beginBackGesture,
137
+ commitBackGesture: navState._gesture.commitBackGesture,
138
+ cancelBackGesture: navState._gesture.cancelBackGesture,
139
+ edgeSwipeEnabled:
140
+ // Gate on animationsEnabled too — if there's no progress
141
+ // SharedValue (e.g. parent is `animated={false}`), the edge
142
+ // swipe gesture would call `beginBackGesture()` with a null
143
+ // progress and leave the stack in an inconsistent state.
144
+ animationsEnabled && parentInternals.edgeSwipeEnabled,
145
+ screens: navState._screens,
146
+ };
147
+ // Reactive focus chain: this nav is locally focused iff
148
+ // 1. (no host entry captured) OR parent.current.key === hostEntryKey
149
+ // 2. parent.isLocallyFocused
150
+ // 3. (no enclosing tab) OR tabs.active === tabName
151
+ // Effect re-runs on any of those changing — parent's stack
152
+ // mutating, parent's own focus flipping, or the tab switching.
153
+ const focusRunner = effect(() => {
154
+ const hostMatch = hostEntryKey === null || parentNav.current.key === hostEntryKey;
155
+ const parentFocused = parentNav.isLocallyFocused;
156
+ const tabActive = tabName === null || tabsHandle === null
157
+ ? true
158
+ : tabsHandle.active === tabName;
159
+ const focused = hostMatch && parentFocused && tabActive;
160
+ // Write outside the read-tracking window — `_setLocallyFocused`
161
+ // bumps a signal that no consumer in *this* setup reads, but
162
+ // it's good hygiene anyway.
163
+ untrack(() => navState._setLocallyFocused(focused));
164
+ });
165
+ onUnmounted(() => {
166
+ focusRunner.stop();
167
+ parentNav._children.delete(nav);
168
+ });
169
+ defineProvide(useNav, () => nav);
170
+ defineProvide(useNavRoutes, () => routes);
171
+ defineProvide(useNavInternals, () => internals);
172
+ }
173
+ else {
174
+ nav = parentNav;
175
+ internals = parentInternals;
176
+ }
31
177
  return () => {
32
178
  const transition = nav.transition;
33
179
  const top = nav.current;
@@ -1 +1 @@
1
- {"version":3,"file":"Stack.js","sourceRoot":"","sources":["../../src/components/Stack.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Stack.js","sourceRoot":"","sources":["../../src/components/Stack.tsx"],"names":[],"mappings":"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"}
@@ -4,31 +4,32 @@
4
4
  * Usage:
5
5
  *
6
6
  * ```tsx
7
- * <NavigationRoot routes={routes}>
8
- * <Tabs initialTab="feed">
9
- * <Tabs.Screen name="feed" icon={<FeedIcon />} label="Feed">
10
- * <FeedView />
11
- * </Tabs.Screen>
12
- * <Tabs.Screen name="me" icon={<MeIcon />} label="Profile">
13
- * <ProfileView />
14
- * </Tabs.Screen>
15
- * </Tabs>
7
+ * <NavigationRoot routes={routes} initialRoute="root">
8
+ * <Stack />
16
9
  * </NavigationRoot>
17
- * ```
18
10
  *
19
- * Scope of this slice (v0.1): pure UI primitive. Each tab's body stays
20
- * mounted for state preservation (the inactive ones render with
21
- * `display: 'none'`). Active tab is reactive via `useTabs()`.
11
+ * // The route "root" component renders:
12
+ * <Tabs initialTab="feed">
13
+ * <Tabs.Screen name="feed" icon={<FeedIcon />} label="Feed">
14
+ * <Stack initialRoute="feedHome" />
15
+ * </Tabs.Screen>
16
+ * <Tabs.Screen name="me" icon={<MeIcon />} label="Profile">
17
+ * <Stack initialRoute="profileHome" />
18
+ * </Tabs.Screen>
19
+ * <TabBar />
20
+ * </Tabs>
21
+ * ```
22
22
  *
23
- * Out of scope (deferred to a nested-navigators slice):
24
- * - Per-tab `<Stack>` with its own navigator state machine
25
- * - `nav.parent` chain into the Tabs nav
26
- * - Named navigators (`useNav('root')`)
23
+ * Tab bodies stay mounted across switches (the inactive ones render with
24
+ * `display: 'none'`), so each tab's nested `<Stack>` keeps its history when
25
+ * the user flips back to it. The active tab is reactive via `useTabs()`.
27
26
  *
28
- * Those build on multi-navigator-state plumbing that isn't ready yet.
29
- * For now, the inner content of a `<Tabs.Screen>` shares the same nav as
30
- * its outer `<NavigationRoot>` usable for shallow tab apps, but full
31
- * nested routing comes later.
27
+ * Per-tab stacks: each `<Tabs.Screen>` can host a `<Stack initialRoute="…">`
28
+ * which mints its own navigator. `useNav()` inside that subtree resolves to
29
+ * the tab's stack, so `nav.push('card-route', …)` stays inside the tab.
30
+ * Routes presented as `modal` / `fullScreen` / `transparent-modal` escalate
31
+ * up `nav.parent` to the root navigator automatically — they overlay the
32
+ * tabs UI (TabBar included) and dismiss back into the originating tab.
32
33
  */
33
34
  import { type Define, type JSXElement } from '@sigx/lynx';
34
35
  /** Metadata about a registered `<Tabs.Screen>`. */
@@ -59,6 +60,14 @@ export interface TabsNav {
59
60
  * Access the enclosing Tabs navigator. Throws when called outside `<Tabs>`.
60
61
  */
61
62
  export declare const useTabs: import("@sigx/runtime-core").InjectableFunction<TabsNav>;
63
+ /**
64
+ * @internal
65
+ * Provided by each `<Tabs.Screen>` so a nested `<Stack initialRoute>` can
66
+ * discover *which* tab it's hosted by, and gate its focus state on that
67
+ * tab being active. Throws when called outside a `<Tabs.Screen>` body so
68
+ * the gate degrades to "always active" via the caller's try/catch.
69
+ */
70
+ export declare const useTabScreenName: import("@sigx/runtime-core").InjectableFunction<string>;
62
71
  type TabsProps = Define.Prop<'initialTab', string> & Define.Slot<'default'>;
63
72
  type TabsScreenProps = Define.Prop<'name', string, true> & Define.Prop<'icon', JSXElement> & Define.Prop<'label', string> & Define.Prop<'accessibilityLabel', string> & Define.Slot<'default'>;
64
73
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../../src/components/Tabs.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAQH,KAAK,MAAM,EACX,KAAK,UAAU,EAElB,MAAM,YAAY,CAAC;AAEpB,mDAAmD;AACnD,MAAM,WAAW,OAAO;IACpB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED,kDAAkD;AAClD,MAAM,WAAW,OAAO;IACpB,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,0DAIlB,CAAC;AAsBH,KAAK,SAAS,GACR,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,GACjC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAgE7B,KAAK,eAAe,GACd,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,GACjC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,GAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,GAC5B,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,MAAM,CAAC,GACzC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAoC7B;;;;;GAKG;AACH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAEf,CAAC"}
1
+ {"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../../src/components/Tabs.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAQH,KAAK,MAAM,EACX,KAAK,UAAU,EAElB,MAAM,YAAY,CAAC;AAEpB,mDAAmD;AACnD,MAAM,WAAW,OAAO;IACpB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED,kDAAkD;AAClD,MAAM,WAAW,OAAO;IACpB,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,0DAIlB,CAAC;AAsBH;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,yDAI3B,CAAC;AAEH,KAAK,SAAS,GACR,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,GACjC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAgE7B,KAAK,eAAe,GACd,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,GACjC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,GAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,GAC5B,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,MAAM,CAAC,GACzC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAwC7B;;;;;GAKG;AACH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAEf,CAAC"}
@@ -5,31 +5,32 @@ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
5
5
  * Usage:
6
6
  *
7
7
  * ```tsx
8
- * <NavigationRoot routes={routes}>
9
- * <Tabs initialTab="feed">
10
- * <Tabs.Screen name="feed" icon={<FeedIcon />} label="Feed">
11
- * <FeedView />
12
- * </Tabs.Screen>
13
- * <Tabs.Screen name="me" icon={<MeIcon />} label="Profile">
14
- * <ProfileView />
15
- * </Tabs.Screen>
16
- * </Tabs>
8
+ * <NavigationRoot routes={routes} initialRoute="root">
9
+ * <Stack />
17
10
  * </NavigationRoot>
18
- * ```
19
11
  *
20
- * Scope of this slice (v0.1): pure UI primitive. Each tab's body stays
21
- * mounted for state preservation (the inactive ones render with
22
- * `display: 'none'`). Active tab is reactive via `useTabs()`.
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
23
  *
24
- * Out of scope (deferred to a nested-navigators slice):
25
- * - Per-tab `<Stack>` with its own navigator state machine
26
- * - `nav.parent` chain into the Tabs nav
27
- * - Named navigators (`useNav('root')`)
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()`.
28
27
  *
29
- * Those build on multi-navigator-state plumbing that isn't ready yet.
30
- * For now, the inner content of a `<Tabs.Screen>` shares the same nav as
31
- * its outer `<NavigationRoot>` usable for shallow tab apps, but full
32
- * nested routing comes later.
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.
33
34
  */
34
35
  import { component, compound, defineInjectable, defineProvide, onUnmounted, signal, untrack, } from '@sigx/lynx';
35
36
  /**
@@ -41,6 +42,16 @@ export const useTabs = defineInjectable(() => {
41
42
  const useTabsRegistrar = defineInjectable(() => {
42
43
  throw new Error('[lynx-navigation] <Tabs.Screen> rendered outside a <Tabs> component.');
43
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
+ });
44
55
  const _Tabs = component(({ props, slots }) => {
45
56
  // Tabs are stored as a deeply-reactive proxy signal so `tabs` consumers
46
57
  // re-render when registration changes. `activeSignal` uses the wrapped
@@ -116,6 +127,9 @@ const TabsScreen = component(({ props, slots }) => {
116
127
  accessibilityLabel: props.accessibilityLabel,
117
128
  });
118
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);
119
133
  return () => {
120
134
  // `display: none` keeps the body mounted so per-tab state survives
121
135
  // tab switches. Read activeSignal here so re-activating triggers a
@@ -1 +1 @@
1
- {"version":3,"file":"Tabs.js","sourceRoot":"","sources":["../../src/components/Tabs.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Tabs.js","sourceRoot":"","sources":["../../src/components/Tabs.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EACH,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,MAAM,EACN,OAAO,GAIV,MAAM,YAAY,CAAC;AA4BpB;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,gBAAgB,CAAU,GAAG,EAAE;IAClD,MAAM,IAAI,KAAK,CACX,mEAAmE,CACtE,CAAC;AACN,CAAC,CAAC,CAAC;AAgBH,MAAM,gBAAgB,GAAG,gBAAgB,CAAgB,GAAG,EAAE;IAC1D,MAAM,IAAI,KAAK,CACX,sEAAsE,CACzE,CAAC;AACN,CAAC,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,CAAS,GAAG,EAAE;IAC1D,MAAM,IAAI,KAAK,CACX,2EAA2E,CAC9E,CAAC;AACN,CAAC,CAAC,CAAC;AAMH,MAAM,KAAK,GAAG,SAAS,CAAY,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;IACpD,wEAAwE;IACxE,uEAAuE;IACvE,kEAAkE;IAClE,gDAAgD;IAChD,MAAM,IAAI,GAAG,MAAM,CAAY,EAAE,CAAC,CAAC;IACnC,MAAM,YAAY,GAAqC,MAAM,CAAC;QAC1D,KAAK,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;KAClC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAkB;QAC7B,QAAQ,CAAC,IAAI;YACT,kEAAkE;YAClE,kEAAkE;YAClE,qDAAqD;YACrD,OAAO,CAAC,GAAG,EAAE;gBACT,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;gBACxD,IAAI,GAAG,KAAK,CAAC,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;oBAC3B,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;gBACtB,IAAI,YAAY,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;oBAC9B,YAAY,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;gBACnC,CAAC;YACL,CAAC,CAAC,CAAC;QACP,CAAC;QACD,UAAU,CAAC,IAAI;YACX,OAAO,CAAC,GAAG,EAAE;gBACT,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;gBACnD,IAAI,GAAG,KAAK,CAAC,CAAC;oBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;gBACpC,IAAI,YAAY,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;oBAC9B,YAAY,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;gBAC/C,CAAC;YACL,CAAC,CAAC,CAAC;QACP,CAAC;QACD,IAAI;QACJ,YAAY;KACf,CAAC;IAEF,MAAM,GAAG,GAAY;QACjB,IAAI,MAAM;YACN,kEAAkE;YAClE,gEAAgE;YAChE,mDAAmD;YACnD,OAAO,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC;QACpC,CAAC;QACD,SAAS,CAAC,IAAI;YACV,6DAA6D;YAC7D,gEAAgE;YAChE,6DAA6D;YAC7D,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,OAAO;YAC/C,YAAY,CAAC,KAAK,GAAG,IAAI,CAAC;QAC9B,CAAC;QACD,IAAI,IAAI;YACJ,OAAO,IAAI,CAAC;QAChB,CAAC;KACJ,CAAC;IAEF,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;IAClC,aAAa,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAEjD,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;AACnC,CAAC,CAAC,CAAC;AASH,MAAM,UAAU,GAAG,SAAS,CAAkB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;IAC/D,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,uEAAuE;IACvE,sEAAsE;IACtE,sEAAsE;IACtE,6DAA6D;IAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,SAAS,CAAC,QAAQ,CAAC;QACf,IAAI;QACJ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;KAC/C,CAAC,CAAC;IACH,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAE9C,wEAAwE;IACxE,gEAAgE;IAChE,aAAa,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAE5C,OAAO,GAAG,EAAE;QACR,mEAAmE;QACnE,mEAAmE;QACnE,mCAAmC;QACnC,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,KAAK,KAAK,IAAI,CAAC;QACrD,OAAO,CACH,eACI,KAAK,EAAE;gBACH,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;gBACjC,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,MAAM;aACjB,YAEA,KAAK,CAAC,OAAO,EAAE,EAAE,GACf,CACV,CAAC;IACN,CAAC,CAAC;AACN,CAAC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE;IAChC,MAAM,EAAE,UAAU;CACrB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-focus.d.ts","sourceRoot":"","sources":["../../src/hooks/use-focus.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,QAAQ,EAChB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,IAAI,QAAQ,CAAC,OAAO,CAAC,CAOhD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CA0BlE"}
1
+ {"version":3,"file":"use-focus.d.ts","sourceRoot":"","sources":["../../src/hooks/use-focus.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,QAAQ,EAChB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,IAAI,QAAQ,CAAC,OAAO,CAAC,CAahD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CA0BlE"}
@@ -27,7 +27,13 @@ export function useIsFocused() {
27
27
  // through `defineProvide` may carry reactive dependencies; we only care
28
28
  // about the immutable key of the entry this screen was mounted for.
29
29
  const myKey = useCurrentEntry().key;
30
- return computed(() => nav.current.key === myKey);
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);
31
37
  }
32
38
  /**
33
39
  * Run `cb` whenever this screen gains focus; run the returned cleanup when it
@@ -1 +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,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;AACrD,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"}
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"}
@@ -3,13 +3,20 @@
3
3
  *
4
4
  * Listens for `hardwareBackPress` events from `@sigx/lynx-linking`'s
5
5
  * `BackHandler` (which the native side dispatches from
6
- * `MainActivity.onBackPressed`). On press:
6
+ * `MainActivity.onBackPressed`). On press the handler walks to the
7
+ * deepest currently-focused navigator (per-tab `<Stack>`s register with
8
+ * their parent), then walks back up the `parent` chain looking for the
9
+ * first nav that `canGoBack`:
7
10
  *
8
- * - If `nav.canGoBack` → `nav.pop()`.
11
+ * - If any nav in the chain can go back → `nav.pop()` on that nav.
9
12
  * - Otherwise → `BackHandler.exitApp()` (Android: `moveTaskToBack(true)`,
10
13
  * keeps the bundle warm; iOS: rejects, since iOS doesn't permit
11
14
  * programmatic termination).
12
15
  *
16
+ * The traversal means you only need to call this once at the root — a
17
+ * back press from inside a tab pops that tab's nested stack first, only
18
+ * exiting the app once every level is at its base entry.
19
+ *
13
20
  * Call this once in any component under `<NavigationRoot>` (typically a
14
21
  * thin wrapper sibling to `<Stack />`). iOS doesn't fire the event so the
15
22
  * hook is a no-op there.
@@ -1 +1 @@
1
- {"version":3,"file":"use-hardware-back.d.ts","sourceRoot":"","sources":["../../src/hooks/use-hardware-back.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAgBtC"}
1
+ {"version":3,"file":"use-hardware-back.d.ts","sourceRoot":"","sources":["../../src/hooks/use-hardware-back.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,eAAe,IAAI,IAAI,CA2CtC"}