@sigx/lynx-navigation 0.1.2 → 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 +64 -2
- package/dist/components/Stack.d.ts +56 -11
- package/dist/components/Stack.d.ts.map +1 -1
- package/dist/components/Stack.js +160 -14
- package/dist/components/Stack.js.map +1 -1
- package/dist/components/Tabs.d.ts +30 -21
- package/dist/components/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs.js +35 -21
- package/dist/components/Tabs.js.map +1 -1
- package/dist/hooks/use-focus.d.ts.map +1 -1
- package/dist/hooks/use-focus.js +7 -1
- package/dist/hooks/use-focus.js.map +1 -1
- package/dist/hooks/use-hardware-back.d.ts +9 -2
- package/dist/hooks/use-hardware-back.d.ts.map +1 -1
- package/dist/hooks/use-hardware-back.js +42 -8
- package/dist/hooks/use-hardware-back.js.map +1 -1
- package/dist/hooks/use-nav.d.ts +36 -1
- package/dist/hooks/use-nav.d.ts.map +1 -1
- package/dist/hooks/use-nav.js.map +1 -1
- package/dist/navigator/core.d.ts +26 -0
- package/dist/navigator/core.d.ts.map +1 -1
- package/dist/navigator/core.js +44 -5
- package/dist/navigator/core.js.map +1 -1
- package/package.json +4 -4
- package/src/components/Stack.tsx +212 -15
- package/src/components/Tabs.tsx +39 -21
- package/src/hooks/use-focus.ts +7 -1
- package/src/hooks/use-hardware-back.ts +43 -9
- package/src/hooks/use-nav.ts +38 -1
- package/src/navigator/core.ts +73 -4
|
@@ -6,13 +6,20 @@ import { useNav } from './use-nav.js';
|
|
|
6
6
|
*
|
|
7
7
|
* Listens for `hardwareBackPress` events from `@sigx/lynx-linking`'s
|
|
8
8
|
* `BackHandler` (which the native side dispatches from
|
|
9
|
-
* `MainActivity.onBackPressed`). On press
|
|
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`:
|
|
10
13
|
*
|
|
11
|
-
* - If
|
|
14
|
+
* - If any nav in the chain can go back → `nav.pop()` on that nav.
|
|
12
15
|
* - Otherwise → `BackHandler.exitApp()` (Android: `moveTaskToBack(true)`,
|
|
13
16
|
* keeps the bundle warm; iOS: rejects, since iOS doesn't permit
|
|
14
17
|
* programmatic termination).
|
|
15
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
|
+
*
|
|
16
23
|
* Call this once in any component under `<NavigationRoot>` (typically a
|
|
17
24
|
* thin wrapper sibling to `<Stack />`). iOS doesn't fire the event so the
|
|
18
25
|
* hook is a no-op there.
|
|
@@ -34,13 +41,40 @@ export function useHardwareBack() {
|
|
|
34
41
|
const nav = useNav();
|
|
35
42
|
onMounted(() => {
|
|
36
43
|
const sub = BackHandler.addEventListener(() => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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;
|
|
40
73
|
}
|
|
41
|
-
// At the root — leave the app. Promise is
|
|
42
|
-
// don't await because we want the back
|
|
43
|
-
// (Android starts the move-to-back
|
|
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).
|
|
44
78
|
void BackHandler.exitApp();
|
|
45
79
|
return true;
|
|
46
80
|
});
|
|
@@ -1 +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,
|
|
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"}
|
package/dist/hooks/use-nav.d.ts
CHANGED
|
@@ -57,8 +57,43 @@ export interface Nav {
|
|
|
57
57
|
readonly stack: ReadonlyArray<StackEntry>;
|
|
58
58
|
/** Whether the user can go back from the current entry. Reactive. */
|
|
59
59
|
readonly canGoBack: boolean;
|
|
60
|
-
/**
|
|
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
|
+
*/
|
|
61
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>;
|
|
62
97
|
/**
|
|
63
98
|
* In-flight transition, or null when navigation is at rest. Reactive —
|
|
64
99
|
* `<Stack>` reads this to decide whether to render one screen or two
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-nav.d.ts","sourceRoot":"","sources":["../../src/hooks/use-nav.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1F,OAAO,KAAK,EACR,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,UAAU,EACV,eAAe,EAClB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,gBAAgB,GAAG;KAC1B,CAAC,IAAI,OAAO,GAAG,mBAAmB,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,KAAK;CACpF,CAAC,OAAO,CAAC,CAAC;AAEX,mDAAmD;AACnD,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;AAErE;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,GAAG;IAChB,8CAA8C;IAC9C,IAAI,CAAC,CAAC,SAAS,mBAAmB,EAC9B,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,WAAW,GACtB,IAAI,CAAC;IACR,yCAAyC;IACzC,IAAI,CAAC,CAAC,SAAS,gBAAgB,EAC3B,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,WAAW,GACtB,IAAI,CAAC;IAER,6DAA6D;IAC7D,OAAO,CAAC,CAAC,SAAS,mBAAmB,EACjC,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,WAAW,GACtB,IAAI,CAAC;IACR,OAAO,CAAC,CAAC,SAAS,gBAAgB,EAC9B,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,WAAW,GACtB,IAAI,CAAC;IAER,wDAAwD;IACxD,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAEhD,uDAAuD;IACvD,KAAK,CAAC,CAAC,SAAS,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAExC,yCAAyC;IACzC,SAAS,IAAI,IAAI,CAAC;IAElB,mCAAmC;IACnC,KAAK,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IAEzD,8DAA8D;IAC9D,OAAO,IAAI,IAAI,CAAC;IAEhB,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAE7B,sCAAsC;IACtC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAE1C,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAE5B
|
|
1
|
+
{"version":3,"file":"use-nav.d.ts","sourceRoot":"","sources":["../../src/hooks/use-nav.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1F,OAAO,KAAK,EACR,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,UAAU,EACV,eAAe,EAClB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,gBAAgB,GAAG;KAC1B,CAAC,IAAI,OAAO,GAAG,mBAAmB,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,KAAK;CACpF,CAAC,OAAO,CAAC,CAAC;AAEX,mDAAmD;AACnD,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;AAErE;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,GAAG;IAChB,8CAA8C;IAC9C,IAAI,CAAC,CAAC,SAAS,mBAAmB,EAC9B,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,WAAW,GACtB,IAAI,CAAC;IACR,yCAAyC;IACzC,IAAI,CAAC,CAAC,SAAS,gBAAgB,EAC3B,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,WAAW,GACtB,IAAI,CAAC;IAER,6DAA6D;IAC7D,OAAO,CAAC,CAAC,SAAS,mBAAmB,EACjC,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,WAAW,GACtB,IAAI,CAAC;IACR,OAAO,CAAC,CAAC,SAAS,gBAAgB,EAC9B,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,WAAW,GACtB,IAAI,CAAC;IAER,wDAAwD;IACxD,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAEhD,uDAAuD;IACvD,KAAK,CAAC,CAAC,SAAS,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAExC,yCAAyC;IACzC,SAAS,IAAI,IAAI,CAAC;IAElB,mCAAmC;IACnC,KAAK,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IAEzD,8DAA8D;IAC9D,OAAO,IAAI,IAAI,CAAC;IAEhB,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAE7B,sCAAsC;IACtC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAE1C,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAE5B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC;IAE5B;;;;;;;;;OASG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IAEnC;;;;;;;;;OASG;IACH,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAE7B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;CAC/C;AAED;;;;;GAKG;AACH,eAAO,MAAM,MAAM,sDAIjB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-nav.js","sourceRoot":"","sources":["../../src/hooks/use-nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"use-nav.js","sourceRoot":"","sources":["../../src/hooks/use-nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAgJ9C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,gBAAgB,CAAM,GAAG,EAAE;IAC7C,MAAM,IAAI,KAAK,CACX,6FAA6F,CAChG,CAAC;AACN,CAAC,CAAC,CAAC"}
|
package/dist/navigator/core.d.ts
CHANGED
|
@@ -48,6 +48,14 @@ export interface NavigatorState {
|
|
|
48
48
|
unregister(entryKey: string): void;
|
|
49
49
|
get(entryKey: string): ScreenRegistry | undefined;
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Internal: set `nav.isLocallyFocused` from outside.
|
|
53
|
+
*
|
|
54
|
+
* `<Stack>` calls this when its host entry's locally-focused state
|
|
55
|
+
* changes (top of parent + parent focused + enclosing tab active). For
|
|
56
|
+
* the root nav this stays `true` for the lifetime of the navigator.
|
|
57
|
+
*/
|
|
58
|
+
readonly _setLocallyFocused: (focused: boolean) => void;
|
|
51
59
|
}
|
|
52
60
|
export interface CreateNavigatorOptions {
|
|
53
61
|
routes: RouteMap;
|
|
@@ -60,6 +68,24 @@ export interface CreateNavigatorOptions {
|
|
|
60
68
|
* that don't have an MT runtime.
|
|
61
69
|
*/
|
|
62
70
|
progress?: SharedValue<number>;
|
|
71
|
+
/**
|
|
72
|
+
* Parent navigator. Set when this navigator is nested under another
|
|
73
|
+
* (e.g. a per-tab `<Stack initialRoute>` under root). Drives the
|
|
74
|
+
* `nav.parent` getter and the modal-escalation behaviour of `push`:
|
|
75
|
+
* a push of a route whose resolved presentation is not `'card'`
|
|
76
|
+
* recurses via `parent.push(...)`, walking up the chain until it
|
|
77
|
+
* lands on a navigator with no parent (the root).
|
|
78
|
+
*
|
|
79
|
+
* Leave undefined for the root navigator.
|
|
80
|
+
*/
|
|
81
|
+
parent?: Nav | null;
|
|
82
|
+
/**
|
|
83
|
+
* Whether this navigator is considered "locally focused" at creation
|
|
84
|
+
* time. Defaults to true for the root nav; nested stacks pass `false`
|
|
85
|
+
* here and then flip the flag via `_setLocallyFocused` once their
|
|
86
|
+
* host-entry/tab-active state is computed.
|
|
87
|
+
*/
|
|
88
|
+
initialLocallyFocused?: boolean;
|
|
63
89
|
}
|
|
64
90
|
/**
|
|
65
91
|
* Create a navigator. Returns the public `nav` handle plus the routes map.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/navigator/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,WAAW,EACnB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAIR,QAAQ,EACR,UAAU,EAEb,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE;QACf,gBAAgB,IAAI,IAAI,CAAC;QACzB,iBAAiB,IAAI,IAAI,CAAC;QAC1B,iBAAiB,IAAI,IAAI,CAAC;KAC7B,CAAC;IACF;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,QAAQ,EAAE;QACf,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;QACzC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;KACrD,CAAC;
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/navigator/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,WAAW,EACnB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAIR,QAAQ,EACR,UAAU,EAEb,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE;QACf,gBAAgB,IAAI,IAAI,CAAC;QACzB,iBAAiB,IAAI,IAAI,CAAC;QAC1B,iBAAiB,IAAI,IAAI,CAAC;KAC7B,CAAC;IACF;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,QAAQ,EAAE;QACf,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;QACzC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;KACrD,CAAC;IACF;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3D;AAuED,MAAM,WAAW,sBAAsB;IACnC,MAAM,EAAE,QAAQ,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;IACpB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,sBAAsB,GAAG,cAAc,CA0TjF"}
|
package/dist/navigator/core.js
CHANGED
|
@@ -56,8 +56,12 @@ function unpackArgs(name, args, routes) {
|
|
|
56
56
|
* can subscribe to it.
|
|
57
57
|
*/
|
|
58
58
|
export function createNavigatorState(opts) {
|
|
59
|
-
const { routes, initial, progress } = opts;
|
|
59
|
+
const { routes, initial, progress, parent = null } = opts;
|
|
60
60
|
const stackSignal = signal([initial]);
|
|
61
|
+
const focusedBox = signal({
|
|
62
|
+
value: opts.initialLocallyFocused ?? true,
|
|
63
|
+
});
|
|
64
|
+
const children = new Set();
|
|
61
65
|
// `signal(null)` would wrap as a primitive (no `$set`), so wrap in an
|
|
62
66
|
// object to get the standard `{ value }`-style API. Reading `.value`
|
|
63
67
|
// tracks; writing triggers re-render of `<Stack>`.
|
|
@@ -124,13 +128,29 @@ export function createNavigatorState(opts) {
|
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
130
|
const push = ((name, ...args) => {
|
|
127
|
-
if (isTransitioning())
|
|
128
|
-
return;
|
|
129
|
-
const { params, search, options } = unpackArgs(name, args, routes);
|
|
130
131
|
if (!routes[name]) {
|
|
131
132
|
throw new Error(`[lynx-navigation] push('${name}'): route is not registered. ` +
|
|
132
133
|
`Known routes: ${Object.keys(routes).join(', ') || '(none)'}`);
|
|
133
134
|
}
|
|
135
|
+
const { params, search, options } = unpackArgs(name, args, routes);
|
|
136
|
+
// Escalate non-card presentations up the parent chain. Modals,
|
|
137
|
+
// fullScreen, and transparent-modal routes belong on the root
|
|
138
|
+
// navigator so they overlay tab UI and persistent chrome. We resolve
|
|
139
|
+
// the presentation the same way `makeEntry` does so the escalation
|
|
140
|
+
// decision matches what would actually be shown.
|
|
141
|
+
const resolvedPresentation = (options?.presentation ?? routes[name].presentation ?? 'card');
|
|
142
|
+
if (resolvedPresentation !== 'card' && parent) {
|
|
143
|
+
// Walk straight to the root — every navigator with a parent
|
|
144
|
+
// delegates non-card pushes upward, so a chain of any depth
|
|
145
|
+
// collapses to a single push on the topmost nav.
|
|
146
|
+
// Forward original args verbatim so overloads (`push(name)`,
|
|
147
|
+
// `push(name, params)`, `push(name, params, search)`,
|
|
148
|
+
// `push(name, params, search, options)`) keep their meaning.
|
|
149
|
+
parent.push(name, ...args);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (isTransitioning())
|
|
153
|
+
return;
|
|
134
154
|
preloadRouteComponent(routes[name].component);
|
|
135
155
|
const newEntry = makeEntry(name, params, search, options, routes);
|
|
136
156
|
const cur = getStack();
|
|
@@ -288,17 +308,36 @@ export function createNavigatorState(opts) {
|
|
|
288
308
|
return stackSignal.length > 1;
|
|
289
309
|
},
|
|
290
310
|
get parent() {
|
|
291
|
-
return
|
|
311
|
+
return parent;
|
|
312
|
+
},
|
|
313
|
+
get isLocallyFocused() {
|
|
314
|
+
return focusedBox.value;
|
|
315
|
+
},
|
|
316
|
+
get _children() {
|
|
317
|
+
return children;
|
|
292
318
|
},
|
|
293
319
|
get transition() {
|
|
294
320
|
return transitionBox.value;
|
|
295
321
|
},
|
|
296
322
|
};
|
|
323
|
+
if (parent) {
|
|
324
|
+
// Register with parent so root-level traversals (hardware back,
|
|
325
|
+
// future deepest-focused queries) can reach this nav. The matching
|
|
326
|
+
// `_children.delete(nav)` happens when the owning `<Stack>` unmounts;
|
|
327
|
+
// see Stack.tsx.
|
|
328
|
+
parent._children.add(nav);
|
|
329
|
+
}
|
|
330
|
+
function setLocallyFocused(focused) {
|
|
331
|
+
if (focusedBox.value === focused)
|
|
332
|
+
return;
|
|
333
|
+
focusedBox.value = focused;
|
|
334
|
+
}
|
|
297
335
|
return {
|
|
298
336
|
nav,
|
|
299
337
|
routes,
|
|
300
338
|
_gesture: { beginBackGesture, commitBackGesture, cancelBackGesture },
|
|
301
339
|
_screens: createScreenRegistries(),
|
|
340
|
+
_setLocallyFocused: setLocallyFocused,
|
|
302
341
|
};
|
|
303
342
|
}
|
|
304
343
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/navigator/core.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,eAAe,EACf,MAAM,EACN,OAAO,GAGV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/navigator/core.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigx/lynx-navigation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Type-first native navigator for sigx-lynx — Stack, Tabs, Drawer, modals, lazy routes, deep links",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@sigx/lynx-motion": "^0.1.1",
|
|
20
19
|
"@sigx/lynx": "^0.1.4",
|
|
20
|
+
"@sigx/lynx-motion": "^0.1.2",
|
|
21
21
|
"@sigx/lynx-linking": "^0.1.2"
|
|
22
22
|
},
|
|
23
23
|
"peerDependenciesMeta": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"vitest": "^4.1.6",
|
|
31
31
|
"@sigx/lynx": "^0.1.4",
|
|
32
32
|
"@sigx/lynx-linking": "^0.1.2",
|
|
33
|
-
"@sigx/lynx-
|
|
34
|
-
"@sigx/lynx-
|
|
33
|
+
"@sigx/lynx-motion": "^0.1.2",
|
|
34
|
+
"@sigx/lynx-testing": "^0.2.6"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"sigx",
|
package/src/components/Stack.tsx
CHANGED
|
@@ -1,33 +1,230 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
component,
|
|
3
|
+
defineProvide,
|
|
4
|
+
effect,
|
|
5
|
+
onUnmounted,
|
|
6
|
+
untrack,
|
|
7
|
+
useSharedValue,
|
|
8
|
+
type ComponentFactory,
|
|
9
|
+
type Define,
|
|
10
|
+
type SharedValue,
|
|
11
|
+
} from '@sigx/lynx';
|
|
2
12
|
import { Suspense, isLazyComponent } from '@sigx/lynx';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
13
|
+
import { createNavigatorState } from '../navigator/core.js';
|
|
14
|
+
import { useNav, type Nav } from '../hooks/use-nav.js';
|
|
15
|
+
import {
|
|
16
|
+
useCurrentEntry,
|
|
17
|
+
useNavInternals,
|
|
18
|
+
useNavRoutes,
|
|
19
|
+
type NavInternals,
|
|
20
|
+
} from '../hooks/use-nav-internal.js';
|
|
21
|
+
import type { Presentation, StackEntry } from '../types.js';
|
|
5
22
|
import { ScreenContainer } from './ScreenContainer.js';
|
|
6
23
|
import { EdgeBackHandle } from './EdgeBackHandle.js';
|
|
7
24
|
import { EntryScope } from './EntryScope.js';
|
|
25
|
+
import { useTabScreenName, useTabs } from './Tabs.js';
|
|
26
|
+
|
|
27
|
+
type StackProps =
|
|
28
|
+
/**
|
|
29
|
+
* Mint a nested navigator with this route at its base. When set, the
|
|
30
|
+
* `<Stack>` becomes the owner of a new `NavigatorState` and provides
|
|
31
|
+
* `useNav` / `useNavInternals` / `useNavRoutes` to its subtree, so
|
|
32
|
+
* `nav.push('card-route', …)` from inside the stack stays *inside* it
|
|
33
|
+
* (e.g. for per-tab stacks). Routes presented as `modal` / `fullScreen` /
|
|
34
|
+
* `transparent-modal` automatically escalate to the parent navigator
|
|
35
|
+
* via `nav.parent`, walking up until they reach the root — so modals
|
|
36
|
+
* still overlay the whole app.
|
|
37
|
+
*
|
|
38
|
+
* Omit to render the *enclosing* navigator's stack (the default — this
|
|
39
|
+
* is how `<NavigationRoot> → <Stack />` works).
|
|
40
|
+
*/
|
|
41
|
+
& Define.Prop<'initialRoute', string>
|
|
42
|
+
/** Initial params for the nested-stack base entry. */
|
|
43
|
+
& Define.Prop<'initialParams', Record<string, unknown>>
|
|
44
|
+
/** Initial search for the nested-stack base entry. */
|
|
45
|
+
& Define.Prop<'initialSearch', Record<string, unknown>>;
|
|
46
|
+
|
|
47
|
+
let _nestedKeyCounter = 0;
|
|
8
48
|
|
|
9
49
|
/**
|
|
10
50
|
* Stack navigator — renders the topmost stack entry's component at rest, or
|
|
11
51
|
* the top + underneath entries during a transition.
|
|
12
52
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
53
|
+
* Two modes:
|
|
54
|
+
*
|
|
55
|
+
* **Bound** (no `initialRoute`): renders the enclosing navigator's stack.
|
|
56
|
+
* This is the shape used directly under `<NavigationRoot>` and is what
|
|
57
|
+
* single-stack apps want.
|
|
58
|
+
*
|
|
59
|
+
* **Nested-owner** (`initialRoute="…"`): mints a fresh `NavigatorState` with
|
|
60
|
+
* its own progress `SharedValue` and edge-back gesture, and provides
|
|
61
|
+
* `useNav` / `useNavInternals` / `useNavRoutes` to its subtree. `useNav()`
|
|
62
|
+
* inside this stack returns the nested nav; `nav.parent` points to the
|
|
63
|
+
* enclosing one. Per-tab stacks are the canonical use case:
|
|
64
|
+
*
|
|
65
|
+
* ```tsx
|
|
66
|
+
* <Tabs initialTab="trips">
|
|
67
|
+
* <Tabs.Screen name="trips"><Stack initialRoute="tripsHome" /></Tabs.Screen>
|
|
68
|
+
* <Tabs.Screen name="map"><Stack initialRoute="mapHome" /></Tabs.Screen>
|
|
69
|
+
* </Tabs>
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* Modal/fullScreen pushes escalate up the parent chain automatically — so
|
|
73
|
+
* `nav.push('newTrip')` from inside Trips (where `newTrip` is `modal`)
|
|
74
|
+
* walks to root and overlays the whole UI. `replace` stays strictly local
|
|
75
|
+
* (asymmetric with `push`) so a modal `replace` never wipes the root stack.
|
|
16
76
|
*
|
|
17
|
-
* **
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
77
|
+
* **Render strategy** (same in both modes):
|
|
78
|
+
* - **Idle**: just the top entry, full-bleed, no transform. The screen
|
|
79
|
+
* component mounts directly so it can use its own layout (no extra
|
|
80
|
+
* absolute positioning that would break percentage heights).
|
|
81
|
+
* - **Transitioning**: two `<ScreenContainer>` instances stacked
|
|
82
|
+
* absolutely, each with an MT-driven `translateX` that reads from the
|
|
83
|
+
* navigator's progress `SharedValue`. The host's BG thread doesn't tick
|
|
84
|
+
* per frame — `useAnimatedStyle` runs the interpolation entirely on MT.
|
|
21
85
|
*
|
|
22
86
|
* `key={top.key}` keeps the idle render's component instance stable across
|
|
23
87
|
* unrelated re-renders. During transitions, composite keys
|
|
24
|
-
* (`${entry.key}-${role}-${kind}`) ensure a fresh mount per role/kind pair
|
|
25
|
-
* the `useAnimatedStyle` binding is set with the right input/output
|
|
88
|
+
* (`${entry.key}-${role}-${kind}`) ensure a fresh mount per role/kind pair
|
|
89
|
+
* so the `useAnimatedStyle` binding is set with the right input/output
|
|
90
|
+
* ranges.
|
|
26
91
|
*/
|
|
27
|
-
export const Stack = component(() => {
|
|
28
|
-
|
|
92
|
+
export const Stack = component<StackProps>(({ props }) => {
|
|
93
|
+
// Capture enclosing scope's nav + routes + internals BEFORE any of the
|
|
94
|
+
// defineProvide calls below override them for descendants. These are
|
|
95
|
+
// always the "outer" values regardless of whether this Stack is bound
|
|
96
|
+
// or nested-owner.
|
|
97
|
+
const parentNav = useNav();
|
|
29
98
|
const routes = useNavRoutes();
|
|
30
|
-
const
|
|
99
|
+
const parentInternals = useNavInternals();
|
|
100
|
+
|
|
101
|
+
// Decide mode at setup. `props.initialRoute` is captured once — the
|
|
102
|
+
// alternative (reactive switch between bound and nested-owner) would
|
|
103
|
+
// need to dispose and recreate the inner nav, which would lose all
|
|
104
|
+
// pushed state. Reasonable to pin it.
|
|
105
|
+
const initialName = props.initialRoute;
|
|
106
|
+
const isNested = typeof initialName === 'string' && initialName.length > 0;
|
|
107
|
+
|
|
108
|
+
let nav: Nav;
|
|
109
|
+
let internals: NavInternals;
|
|
110
|
+
|
|
111
|
+
if (isNested) {
|
|
112
|
+
if (!routes[initialName]) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`[lynx-navigation] <Stack initialRoute='${initialName}'>: ` +
|
|
115
|
+
`route is not registered. Known routes: ` +
|
|
116
|
+
`${Object.keys(routes).join(', ') || '(none)'}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Host entry — the parent's current top *when this Stack mounts*.
|
|
121
|
+
// Used by the focus chain so the nested nav is only "locally
|
|
122
|
+
// focused" while its host entry is still the top of the parent.
|
|
123
|
+
// Wrapped in try/catch because `<Stack initialRoute>` *may* be
|
|
124
|
+
// placed outside an EntryScope (e.g. directly under
|
|
125
|
+
// `<NavigationRoot>`); in that case there's no host-entry gate to
|
|
126
|
+
// apply and we just rely on `parent.isLocallyFocused`.
|
|
127
|
+
let hostEntryKey: string | null = null;
|
|
128
|
+
try {
|
|
129
|
+
hostEntryKey = useCurrentEntry().key;
|
|
130
|
+
} catch {
|
|
131
|
+
hostEntryKey = null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Enclosing tab name (if any). Lets the focus chain gate on tab
|
|
135
|
+
// active state — Trips' inner stack reports `isLocallyFocused: false`
|
|
136
|
+
// while the user is on the Map tab, even though it's the top of
|
|
137
|
+
// its own stack.
|
|
138
|
+
let tabName: string | null = null;
|
|
139
|
+
let tabsHandle: ReturnType<typeof useTabs> | null = null;
|
|
140
|
+
try {
|
|
141
|
+
tabName = useTabScreenName();
|
|
142
|
+
tabsHandle = useTabs();
|
|
143
|
+
} catch {
|
|
144
|
+
tabName = null;
|
|
145
|
+
tabsHandle = null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Inherit animation enablement from the parent — if the root was
|
|
149
|
+
// created with `animated={false}` (tests), nested stacks should
|
|
150
|
+
// also commit instantly so test assertions don't have to wait on
|
|
151
|
+
// a SharedValue that won't tick.
|
|
152
|
+
const animationsEnabled = parentInternals.progress !== null;
|
|
153
|
+
const progressSv = useSharedValue(0);
|
|
154
|
+
|
|
155
|
+
const presentation =
|
|
156
|
+
(routes[initialName].presentation ?? 'card') as Presentation;
|
|
157
|
+
// Counter-derived suffix keeps base-entry keys unique across
|
|
158
|
+
// concurrent nested stacks in a tab app. Plain `Math.random` would
|
|
159
|
+
// do but a counter is deterministic for test snapshots.
|
|
160
|
+
_nestedKeyCounter += 1;
|
|
161
|
+
const initial: StackEntry = {
|
|
162
|
+
key: `nested-${initialName}-${_nestedKeyCounter}`,
|
|
163
|
+
route: initialName,
|
|
164
|
+
params: props.initialParams ?? {},
|
|
165
|
+
search: props.initialSearch ?? {},
|
|
166
|
+
state: undefined,
|
|
167
|
+
presentation,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const navState = createNavigatorState({
|
|
171
|
+
routes,
|
|
172
|
+
initial,
|
|
173
|
+
progress: animationsEnabled ? progressSv : undefined,
|
|
174
|
+
parent: parentNav,
|
|
175
|
+
// Start un-focused; the effect below flips this once we observe
|
|
176
|
+
// the parent's current entry / tab-active state.
|
|
177
|
+
initialLocallyFocused: false,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
nav = navState.nav;
|
|
181
|
+
internals = {
|
|
182
|
+
progress: animationsEnabled ? progressSv : null,
|
|
183
|
+
beginBackGesture: navState._gesture.beginBackGesture,
|
|
184
|
+
commitBackGesture: navState._gesture.commitBackGesture,
|
|
185
|
+
cancelBackGesture: navState._gesture.cancelBackGesture,
|
|
186
|
+
edgeSwipeEnabled:
|
|
187
|
+
// Gate on animationsEnabled too — if there's no progress
|
|
188
|
+
// SharedValue (e.g. parent is `animated={false}`), the edge
|
|
189
|
+
// swipe gesture would call `beginBackGesture()` with a null
|
|
190
|
+
// progress and leave the stack in an inconsistent state.
|
|
191
|
+
animationsEnabled && parentInternals.edgeSwipeEnabled,
|
|
192
|
+
screens: navState._screens,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Reactive focus chain: this nav is locally focused iff
|
|
196
|
+
// 1. (no host entry captured) OR parent.current.key === hostEntryKey
|
|
197
|
+
// 2. parent.isLocallyFocused
|
|
198
|
+
// 3. (no enclosing tab) OR tabs.active === tabName
|
|
199
|
+
// Effect re-runs on any of those changing — parent's stack
|
|
200
|
+
// mutating, parent's own focus flipping, or the tab switching.
|
|
201
|
+
const focusRunner = effect(() => {
|
|
202
|
+
const hostMatch =
|
|
203
|
+
hostEntryKey === null || parentNav.current.key === hostEntryKey;
|
|
204
|
+
const parentFocused = parentNav.isLocallyFocused;
|
|
205
|
+
const tabActive =
|
|
206
|
+
tabName === null || tabsHandle === null
|
|
207
|
+
? true
|
|
208
|
+
: tabsHandle.active === tabName;
|
|
209
|
+
const focused = hostMatch && parentFocused && tabActive;
|
|
210
|
+
// Write outside the read-tracking window — `_setLocallyFocused`
|
|
211
|
+
// bumps a signal that no consumer in *this* setup reads, but
|
|
212
|
+
// it's good hygiene anyway.
|
|
213
|
+
untrack(() => navState._setLocallyFocused(focused));
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
onUnmounted(() => {
|
|
217
|
+
focusRunner.stop();
|
|
218
|
+
parentNav._children.delete(nav);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
defineProvide(useNav, () => nav);
|
|
222
|
+
defineProvide(useNavRoutes, () => routes);
|
|
223
|
+
defineProvide(useNavInternals, () => internals);
|
|
224
|
+
} else {
|
|
225
|
+
nav = parentNav;
|
|
226
|
+
internals = parentInternals;
|
|
227
|
+
}
|
|
31
228
|
|
|
32
229
|
return () => {
|
|
33
230
|
const transition = nav.transition;
|