@sigx/lynx-navigation 0.2.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.
- package/README.md +128 -8
- package/dist/components/EntryScope.d.ts.map +1 -1
- package/dist/components/EntryScope.js +8 -2
- package/dist/components/EntryScope.js.map +1 -1
- package/dist/components/Layer.d.ts +34 -0
- package/dist/components/Layer.d.ts.map +1 -0
- package/dist/components/Layer.js +66 -0
- package/dist/components/Layer.js.map +1 -0
- package/dist/components/Screen.d.ts +6 -6
- package/dist/components/Screen.d.ts.map +1 -1
- package/dist/components/Screen.js +13 -9
- package/dist/components/Screen.js.map +1 -1
- package/dist/components/Stack.d.ts +41 -16
- package/dist/components/Stack.d.ts.map +1 -1
- package/dist/components/Stack.js +90 -54
- package/dist/components/Stack.js.map +1 -1
- package/dist/components/TabBar.d.ts +18 -19
- package/dist/components/TabBar.d.ts.map +1 -1
- package/dist/components/TabBar.js +21 -21
- package/dist/components/TabBar.js.map +1 -1
- package/dist/components/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs.js +15 -1
- package/dist/components/Tabs.js.map +1 -1
- package/dist/hooks/use-linking-nav.js.map +1 -1
- package/dist/hooks/use-nav-internal.d.ts +19 -1
- package/dist/hooks/use-nav-internal.d.ts.map +1 -1
- package/dist/hooks/use-nav-internal.js +11 -0
- package/dist/hooks/use-nav-internal.js.map +1 -1
- package/dist/hooks/use-screen-chrome.d.ts +19 -0
- package/dist/hooks/use-screen-chrome.d.ts.map +1 -0
- package/dist/hooks/use-screen-chrome.js +102 -0
- package/dist/hooks/use-screen-chrome.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/layer-plan.d.ts +69 -0
- package/dist/internal/layer-plan.d.ts.map +1 -0
- package/dist/internal/layer-plan.js +102 -0
- package/dist/internal/layer-plan.js.map +1 -0
- package/dist/internal/screen-width.d.ts +9 -7
- package/dist/internal/screen-width.d.ts.map +1 -1
- package/dist/internal/screen-width.js +15 -13
- package/dist/internal/screen-width.js.map +1 -1
- package/dist/navigator/core.d.ts +2 -1
- package/dist/navigator/core.d.ts.map +1 -1
- package/dist/navigator/core.js +13 -2
- package/dist/navigator/core.js.map +1 -1
- package/dist/url/format.js.map +1 -1
- package/package.json +13 -11
- package/src/components/EntryScope.tsx +13 -2
- package/src/components/Layer.tsx +96 -0
- package/src/components/Screen.tsx +11 -9
- package/src/components/Stack.tsx +136 -92
- package/src/components/TabBar.tsx +21 -21
- package/src/components/Tabs.tsx +15 -1
- package/src/hooks/use-nav-internal.ts +22 -1
- package/src/hooks/use-screen-chrome.ts +122 -0
- package/src/index.ts +2 -0
- package/src/internal/layer-plan.ts +187 -0
- package/src/internal/screen-width.ts +22 -14
- package/src/navigator/core.ts +14 -3
- package/dist/components/ScreenContainer.d.ts +0 -18
- package/dist/components/ScreenContainer.d.ts.map +0 -1
- package/dist/components/ScreenContainer.js +0 -77
- package/dist/components/ScreenContainer.js.map +0 -1
- package/src/components/ScreenContainer.tsx +0 -114
package/README.md
CHANGED
|
@@ -148,17 +148,46 @@ Inside a tab body, `useNav()` resolves to the **innermost** navigator:
|
|
|
148
148
|
nav **and** every ancestor is focused (parent's current entry matches +
|
|
149
149
|
enclosing tab is active).
|
|
150
150
|
|
|
151
|
+
**Modal preservation.** When a `modal` / `fullScreen` /
|
|
152
|
+
`transparent-modal` push goes onto the root navigator (via escalation
|
|
153
|
+
from a per-tab stack), `<Stack>` keeps the underneath entry mounted
|
|
154
|
+
behind the overlay. Per-tab stack state, scroll positions, and in-flight
|
|
155
|
+
inputs all survive the modal lifecycle. Card pushes still replace the
|
|
156
|
+
underneath in the base layer — the user's mental model is "back
|
|
157
|
+
recreates the previous screen from history".
|
|
158
|
+
|
|
151
159
|
**Limitations (current slice)**:
|
|
152
160
|
|
|
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
161
|
- One global route registry — there's no per-tab whitelist yet. Deep-link
|
|
157
162
|
routing always pushes against the innermost nav of the caller site
|
|
158
163
|
(modal routes still escalate). A future slice may add `<Stack routes={…}>`.
|
|
159
164
|
- `useNavSerializer` snapshots one nav only — nested-tab stack state isn't
|
|
160
165
|
persisted across reload yet.
|
|
161
166
|
|
|
167
|
+
### Per-stack header chrome
|
|
168
|
+
|
|
169
|
+
`<Stack>` accepts a default slot — its children render *inside* the
|
|
170
|
+
stack's nav scope, above the active screen. That's how to give a
|
|
171
|
+
nested per-tab stack its own header, since the root-level `<Header />`
|
|
172
|
+
only tracks the root navigator:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
<Tabs.Screen name="trips" label="Trips">
|
|
176
|
+
<Stack initialRoute="tripsHome">
|
|
177
|
+
<Header /> {/* useNav() here resolves to the per-tab nav */}
|
|
178
|
+
</Stack>
|
|
179
|
+
</Tabs.Screen>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Without this, a `<Header />` placed as a sibling of `<Stack>` would
|
|
183
|
+
resolve `useNav()` to the *enclosing* nav and never react to pushes
|
|
184
|
+
inside the nested stack — the back button + title wouldn't update on
|
|
185
|
+
`nav.push('tripDetail', ...)`.
|
|
186
|
+
|
|
187
|
+
For a daisy-themed bar (height, padding, centred title, separator),
|
|
188
|
+
use `<NavHeader />` from `@sigx/lynx-daisyui` — same slot position,
|
|
189
|
+
same `useScreenChrome()` data source.
|
|
190
|
+
|
|
162
191
|
### `<Screen>`
|
|
163
192
|
|
|
164
193
|
Per-route slot container. Lets a screen declare its header / tab-bar item
|
|
@@ -185,13 +214,61 @@ const Profile = component(() => () => (
|
|
|
185
214
|
All sub-slots are optional. Anything not declared falls back to the
|
|
186
215
|
navigator's default chrome.
|
|
187
216
|
|
|
217
|
+
**Placement — `<Screen>` is a child component, return it from
|
|
218
|
+
render.** `<Screen>` and its slot-filler siblings (`<Screen.Header>`,
|
|
219
|
+
`<Screen.HeaderRight>`, etc.) each call `useScreenRegistry()` from
|
|
220
|
+
their *own* setup, which means they have to actually mount inside
|
|
221
|
+
your screen's `<EntryScope>` to find the registry — and that only
|
|
222
|
+
happens when JSX returned from your render function is reconciled
|
|
223
|
+
into the tree. Constructing `<Screen ... />` JSX inside setup without
|
|
224
|
+
returning it doesn't mount anything:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
// ✗ Won't take effect — the JSX is created but never rendered.
|
|
228
|
+
const Profile = component(() => {
|
|
229
|
+
<Screen title="Profile" />;
|
|
230
|
+
return () => <view>profile body</view>;
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ✓ Returned from render — gets mounted, registers its options.
|
|
234
|
+
const Profile = component(() => {
|
|
235
|
+
return () => (
|
|
236
|
+
<Screen title="Profile">
|
|
237
|
+
<view>profile body</view>
|
|
238
|
+
</Screen>
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
When `<Screen>` *does* mount outside of any `<EntryScope>` — typically
|
|
244
|
+
because the consumer placed it at the app root instead of inside a
|
|
245
|
+
route component — it throws:
|
|
246
|
+
|
|
247
|
+
> `[lynx-navigation] No screen registry in scope. `<Screen>` (and
|
|
248
|
+
> `<Screen.Header>`, etc.) must be used inside a route component
|
|
249
|
+
> rendered by `<Stack>`.`
|
|
250
|
+
|
|
251
|
+
If you need to write options *imperatively* at setup time (e.g. to
|
|
252
|
+
react to a hook result before the first render), reach for
|
|
253
|
+
`useScreenOptions(...)` — it calls `useScreenRegistry()` directly
|
|
254
|
+
from your own setup, where the EntryScope's registry is already
|
|
255
|
+
visible.
|
|
256
|
+
|
|
188
257
|
### `<Header>`
|
|
189
258
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
259
|
+
**Headless** default navigator header — bare `<view>`/`<text>` nodes,
|
|
260
|
+
no flex direction, no padding, no theme. Reads the focused entry's
|
|
261
|
+
`<Screen.Header>` slot if set, otherwise renders
|
|
262
|
+
`headerLeft | title | headerRight` with a back button as the default
|
|
263
|
+
`headerLeft` when `nav.canGoBack` is true. Pulls `title` /
|
|
264
|
+
`headerShown` / `gestureEnabled` from the focused screen's
|
|
265
|
+
`useScreenOptions(...)` registration (or declarative `<Screen title=…>`).
|
|
266
|
+
|
|
267
|
+
For a daisy-themed bar with sensible defaults (surface colour,
|
|
268
|
+
separator, fixed ~48dp height, centred title), use `<NavHeader />`
|
|
269
|
+
from `@sigx/lynx-daisyui` — same data source via `useScreenChrome()`.
|
|
270
|
+
Custom designs can build their own component on top of
|
|
271
|
+
`useScreenChrome()` without touching internals.
|
|
195
272
|
|
|
196
273
|
```tsx
|
|
197
274
|
<NavigationRoot routes={routes} initialRoute="home">
|
|
@@ -312,6 +389,49 @@ const Profile = component(() => {
|
|
|
312
389
|
});
|
|
313
390
|
```
|
|
314
391
|
|
|
392
|
+
Equivalent declarative form via `<Screen>`:
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
<Screen title={() => `User ${id}`} />
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
`<Screen>` only patches keys you actually pass — omitting `title`
|
|
399
|
+
won't clear a title set elsewhere on the same entry. Safe to use a
|
|
400
|
+
bare `<Screen>` purely to host `<Screen.HeaderRight>` slots without
|
|
401
|
+
worrying about wiping a sibling `useScreenOptions(...)` write.
|
|
402
|
+
|
|
403
|
+
### `useScreenChrome()`
|
|
404
|
+
|
|
405
|
+
Reactive read of the focused screen's options + slot fills, plus
|
|
406
|
+
navigation helpers a header would need (`canGoBack`, `pop`). The
|
|
407
|
+
public foundation for building custom header components without
|
|
408
|
+
touching internal modules — `<NavHeader />` in `@sigx/lynx-daisyui` is
|
|
409
|
+
built on this.
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
import { useScreenChrome } from '@sigx/lynx-navigation';
|
|
413
|
+
|
|
414
|
+
const MyHeader = component(() => {
|
|
415
|
+
const chrome = useScreenChrome();
|
|
416
|
+
return () => {
|
|
417
|
+
if (!chrome.headerShown) return null;
|
|
418
|
+
return (
|
|
419
|
+
<view class="my-header">
|
|
420
|
+
{chrome.canGoBack
|
|
421
|
+
? <view bindtap={chrome.pop}><text>‹ Back</text></view>
|
|
422
|
+
: null}
|
|
423
|
+
<text>{chrome.title}</text>
|
|
424
|
+
{chrome.headerRight?.()}
|
|
425
|
+
</view>
|
|
426
|
+
);
|
|
427
|
+
};
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Every property is a getter — reading it inside a render or `computed`
|
|
432
|
+
subscribes to the underlying signal, so the consumer re-renders when
|
|
433
|
+
title / slots change.
|
|
434
|
+
|
|
315
435
|
### `useLinkingNav(options?)`
|
|
316
436
|
|
|
317
437
|
Bridges `@sigx/lynx-linking` URL events into the navigator. Call once
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntryScope.d.ts","sourceRoot":"","sources":["../../src/components/EntryScope.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"EntryScope.d.ts","sourceRoot":"","sources":["../../src/components/EntryScope.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAQhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,KAAK,eAAe,GACd,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,GACtC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAE7B;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU;;EAgBrB,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { component, defineProvide, onUnmounted } from '@sigx/lynx';
|
|
2
|
-
import { useCurrentEntry, useNavInternals, useScreenRegistry } from '../hooks/use-nav-internal.js';
|
|
2
|
+
import { useCurrentEntry, useCurrentEntryOptional, useNavInternals, useScreenRegistry, } from '../hooks/use-nav-internal.js';
|
|
3
3
|
import { createScreenRegistry } from '../internal/screen-registry.js';
|
|
4
4
|
/**
|
|
5
5
|
* Provider wrapper for a single screen mount.
|
|
@@ -24,9 +24,15 @@ export const EntryScope = component(({ props, slots }) => {
|
|
|
24
24
|
const registry = createScreenRegistry(props.entry);
|
|
25
25
|
internals.screens.register(registry);
|
|
26
26
|
onUnmounted(() => {
|
|
27
|
-
|
|
27
|
+
// Pass the registry instance — `unregister` is identity-checked,
|
|
28
|
+
// so this is a no-op when a newer EntryScope has already taken
|
|
29
|
+
// over the same entry key (e.g. at the transition→idle handoff
|
|
30
|
+
// where the reconciler mounts the new EntryScope before
|
|
31
|
+
// unmounting the old).
|
|
32
|
+
internals.screens.unregister(registry);
|
|
28
33
|
});
|
|
29
34
|
defineProvide(useCurrentEntry, () => props.entry);
|
|
35
|
+
defineProvide(useCurrentEntryOptional, () => props.entry);
|
|
30
36
|
defineProvide(useScreenRegistry, () => registry);
|
|
31
37
|
return () => slots.default?.();
|
|
32
38
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntryScope.js","sourceRoot":"","sources":["../../src/components/EntryScope.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAChF,OAAO,
|
|
1
|
+
{"version":3,"file":"EntryScope.js","sourceRoot":"","sources":["../../src/components/EntryScope.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAChF,OAAO,EACH,eAAe,EACf,uBAAuB,EACvB,eAAe,EACf,iBAAiB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAOtE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAkB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;IACtE,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACnD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,WAAW,CAAC,GAAG,EAAE;QACb,iEAAiE;QACjE,+DAA+D;QAC/D,+DAA+D;QAC/D,wDAAwD;QACxD,uBAAuB;QACvB,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IACH,aAAa,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClD,aAAa,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1D,aAAa,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;AACnC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<Layer>` — one row in `<Stack>`'s layered render. Absolutely-
|
|
3
|
+
* positioned host view that fills the Stack's relative wrapper, with
|
|
4
|
+
* an optional MT-bound `translateX` / `translateY` animation driven
|
|
5
|
+
* by a `SharedValue<number>` from the navigator's transition state.
|
|
6
|
+
*
|
|
7
|
+
* `<Stack>` emits one `<Layer>` per entry returned by
|
|
8
|
+
* `computeLayers(...)`. Layer.key in the parent is
|
|
9
|
+
* `layer-${entry.key}-${animationVariant(animation)}` so that:
|
|
10
|
+
*
|
|
11
|
+
* - The same entry under the same animation state is preserved across
|
|
12
|
+
* renders (modal underneath stays mounted through the modal
|
|
13
|
+
* lifecycle; per-tab Stack state survives).
|
|
14
|
+
* - An entry transitioning between animated and static (e.g. a card
|
|
15
|
+
* top after its push transition completes) remounts so the
|
|
16
|
+
* `useAnimatedStyle` binding can be rebound — the underlying
|
|
17
|
+
* `useAnimatedStyle` is set-once at setup and can't switch its
|
|
18
|
+
* mapper at runtime.
|
|
19
|
+
*
|
|
20
|
+
* Layouts:
|
|
21
|
+
* - Host view is `position: absolute; top/right/bottom/left: 0;
|
|
22
|
+
* display: flex; flexDirection: column` so descendants that
|
|
23
|
+
* flex-fill (SafeAreaView, daisyui screens) get a sized parent.
|
|
24
|
+
* - No background. Screens own their own surface colour (typically
|
|
25
|
+
* via a daisy `bg-base-*` class on the screen body).
|
|
26
|
+
*/
|
|
27
|
+
import { type ComponentFactory, type Define } from '@sigx/lynx';
|
|
28
|
+
import type { LayerAnimation } from '../internal/layer-plan.js';
|
|
29
|
+
import type { RouteMap, StackEntry } from '../types.js';
|
|
30
|
+
export type LayerProps = Define.Prop<'entry', StackEntry, true> & Define.Prop<'routes', RouteMap, true>
|
|
31
|
+
/** When set, the host view animates per the transform spec. */
|
|
32
|
+
& Define.Prop<'animation', LayerAnimation | null, false>;
|
|
33
|
+
export declare const Layer: ComponentFactory<LayerProps, void, {}>;
|
|
34
|
+
//# sourceMappingURL=Layer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Layer.d.ts","sourceRoot":"","sources":["../../src/components/Layer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAIH,KAAK,gBAAgB,EACrB,KAAK,MAAM,EAEd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGxD,MAAM,MAAM,UAAU,GAChB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,GACtC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC;AACvC,+DAA+D;GAC7D,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC;AAE7D,eAAO,MAAM,KAAK,wCAkDhB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `<Layer>` — one row in `<Stack>`'s layered render. Absolutely-
|
|
4
|
+
* positioned host view that fills the Stack's relative wrapper, with
|
|
5
|
+
* an optional MT-bound `translateX` / `translateY` animation driven
|
|
6
|
+
* by a `SharedValue<number>` from the navigator's transition state.
|
|
7
|
+
*
|
|
8
|
+
* `<Stack>` emits one `<Layer>` per entry returned by
|
|
9
|
+
* `computeLayers(...)`. Layer.key in the parent is
|
|
10
|
+
* `layer-${entry.key}-${animationVariant(animation)}` so that:
|
|
11
|
+
*
|
|
12
|
+
* - The same entry under the same animation state is preserved across
|
|
13
|
+
* renders (modal underneath stays mounted through the modal
|
|
14
|
+
* lifecycle; per-tab Stack state survives).
|
|
15
|
+
* - An entry transitioning between animated and static (e.g. a card
|
|
16
|
+
* top after its push transition completes) remounts so the
|
|
17
|
+
* `useAnimatedStyle` binding can be rebound — the underlying
|
|
18
|
+
* `useAnimatedStyle` is set-once at setup and can't switch its
|
|
19
|
+
* mapper at runtime.
|
|
20
|
+
*
|
|
21
|
+
* Layouts:
|
|
22
|
+
* - Host view is `position: absolute; top/right/bottom/left: 0;
|
|
23
|
+
* display: flex; flexDirection: column` so descendants that
|
|
24
|
+
* flex-fill (SafeAreaView, daisyui screens) get a sized parent.
|
|
25
|
+
* - No background. Screens own their own surface colour (typically
|
|
26
|
+
* via a daisy `bg-base-*` class on the screen body).
|
|
27
|
+
*/
|
|
28
|
+
import { component, useMainThreadRef, useAnimatedStyle, } from '@sigx/lynx';
|
|
29
|
+
import { Suspense, isLazyComponent } from '@sigx/lynx';
|
|
30
|
+
import { EntryScope } from './EntryScope.js';
|
|
31
|
+
export const Layer = component(({ props }) => {
|
|
32
|
+
const ref = useMainThreadRef(null);
|
|
33
|
+
// `useAnimatedStyle` binds once at setup. Calling it conditionally
|
|
34
|
+
// is safe because setup runs once per mount and props.animation
|
|
35
|
+
// never changes for a given Layer instance — animation changes
|
|
36
|
+
// re-key the Layer at the parent, forcing a fresh mount.
|
|
37
|
+
if (props.animation) {
|
|
38
|
+
const a = props.animation;
|
|
39
|
+
useAnimatedStyle(ref, a.progress, a.axis, {
|
|
40
|
+
inputRange: [a.inputRange[0], a.inputRange[1]],
|
|
41
|
+
outputRange: [a.outputRange[0], a.outputRange[1]],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return () => {
|
|
45
|
+
const route = props.routes[props.entry.route];
|
|
46
|
+
if (!route)
|
|
47
|
+
return null;
|
|
48
|
+
const Comp = route.component;
|
|
49
|
+
if (typeof Comp !== 'function')
|
|
50
|
+
return null;
|
|
51
|
+
const entryParams = props.entry.params;
|
|
52
|
+
const body = isLazyComponent(Comp) && route.fallback
|
|
53
|
+
? (_jsx(Suspense, { fallback: route.fallback, children: _jsx(Comp, { ...entryParams }) }))
|
|
54
|
+
: _jsx(Comp, { ...entryParams });
|
|
55
|
+
return (_jsx("view", { "main-thread:ref": ref, style: {
|
|
56
|
+
position: 'absolute',
|
|
57
|
+
top: '0',
|
|
58
|
+
left: '0',
|
|
59
|
+
right: '0',
|
|
60
|
+
bottom: '0',
|
|
61
|
+
display: 'flex',
|
|
62
|
+
flexDirection: 'column',
|
|
63
|
+
}, children: _jsx(EntryScope, { entry: props.entry, children: body }, props.entry.key) }));
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=Layer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Layer.js","sourceRoot":"","sources":["../../src/components/Layer.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EACH,SAAS,EACT,gBAAgB,EAChB,gBAAgB,GAInB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAQ7C,MAAM,CAAC,MAAM,KAAK,GAAG,SAAS,CAAa,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;IACrD,MAAM,GAAG,GAAG,gBAAgB,CAA4B,IAAI,CAAC,CAAC;IAC9D,mEAAmE;IACnE,gEAAgE;IAChE,+DAA+D;IAC/D,yDAAyD;IACzD,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;QAC1B,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;YACtC,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC9C,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;SACpD,CAAC,CAAC;IACP,CAAC;IAED,OAAO,GAAG,EAAE;QACR,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,SAIlB,CAAC;QACF,IAAI,OAAO,IAAI,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,MAAiC,CAAC;QAClE,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ;YAChD,CAAC,CAAC,CACE,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAK,CAAC,QAAiB,YACvC,KAAC,IAAI,OAAK,WAAW,GAAI,GAClB,CACd;YACD,CAAC,CAAC,KAAC,IAAI,OAAK,WAAW,GAAI,CAAC;QAChC,OAAO,CACH,kCACqB,GAAG,EACpB,KAAK,EAAE;gBACH,QAAQ,EAAE,UAAU;gBACpB,GAAG,EAAE,GAAG;gBACR,IAAI,EAAE,GAAG;gBACT,KAAK,EAAE,GAAG;gBACV,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,MAAM;gBACf,aAAa,EAAE,QAAQ;aAC1B,YAED,KAAC,UAAU,IAAuB,KAAK,EAAE,KAAK,CAAC,KAAK,YAC/C,IAAI,IADQ,KAAK,CAAC,KAAK,CAAC,GAAG,CAEnB,GACV,CACV,CAAC;IACN,CAAC,CAAC;AACN,CAAC,CAAC,CAAC"}
|
|
@@ -50,9 +50,9 @@ type TabBarItemProps = Define.Slot<'default'>;
|
|
|
50
50
|
* call site shown in the file header.
|
|
51
51
|
*/
|
|
52
52
|
export declare const Screen: ((props: {
|
|
53
|
-
title?: string | (() => string) | undefined;
|
|
54
|
-
headerShown?: boolean | undefined;
|
|
55
53
|
gestureEnabled?: boolean | undefined;
|
|
54
|
+
headerShown?: boolean | undefined;
|
|
55
|
+
title?: string | (() => string) | undefined;
|
|
56
56
|
} & {} & {
|
|
57
57
|
slots?: Partial<{
|
|
58
58
|
default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
|
|
@@ -62,18 +62,18 @@ export declare const Screen: ((props: {
|
|
|
62
62
|
children?: any;
|
|
63
63
|
}) => import("@sigx/runtime-core").JSXElement) & {
|
|
64
64
|
__setup: import("@sigx/runtime-core").SetupFn<{
|
|
65
|
-
title?: string | (() => string) | undefined;
|
|
66
|
-
headerShown?: boolean | undefined;
|
|
67
65
|
gestureEnabled?: boolean | undefined;
|
|
66
|
+
headerShown?: boolean | undefined;
|
|
67
|
+
title?: string | (() => string) | undefined;
|
|
68
68
|
}, ScreenProps, void, {
|
|
69
69
|
default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
|
|
70
70
|
}>;
|
|
71
71
|
__name?: string;
|
|
72
72
|
__islandId?: string;
|
|
73
73
|
__props: {
|
|
74
|
-
title?: string | (() => string) | undefined;
|
|
75
|
-
headerShown?: boolean | undefined;
|
|
76
74
|
gestureEnabled?: boolean | undefined;
|
|
75
|
+
headerShown?: boolean | undefined;
|
|
76
|
+
title?: string | (() => string) | undefined;
|
|
77
77
|
};
|
|
78
78
|
__events: ScreenProps;
|
|
79
79
|
__ref: void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Screen.d.ts","sourceRoot":"","sources":["../../src/components/Screen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAA0B,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"Screen.d.ts","sourceRoot":"","sources":["../../src/components/Screen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAA0B,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAKjE,KAAK,WAAW,GACV,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC,GAC7C,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,GACtC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAiB7B,KAAK,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAoB9C;;;;;;;;;GASG;AACH,KAAK,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAmB9C;;;;GAIG;AACH,eAAO,MAAM,MAAM;;;4BA1EwB,MAAM;;;;;;;;;;;;gCAAN,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+E/C,CAAC"}
|
|
@@ -35,15 +35,19 @@ import { useScreenRegistry } from '../hooks/use-nav-internal.js';
|
|
|
35
35
|
import { mergeOptions, setSlot } from '../internal/screen-registry.js';
|
|
36
36
|
const ScreenRoot = component(({ props, slots }) => {
|
|
37
37
|
const registry = useScreenRegistry();
|
|
38
|
-
// Apply options whenever the component sets up.
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
// Apply options whenever the component sets up. Only set keys that
|
|
39
|
+
// were actually passed — `mergeOptions` treats `undefined` as "clear
|
|
40
|
+
// this key", so building the patch from raw `props.X` would wipe
|
|
41
|
+
// every option a previous `useScreenOptions(...)` (or another `<Screen>`)
|
|
42
|
+
// had set on this same entry.
|
|
43
|
+
const patch = {};
|
|
44
|
+
if (props.title !== undefined)
|
|
45
|
+
patch.title = props.title;
|
|
46
|
+
if (props.headerShown !== undefined)
|
|
47
|
+
patch.headerShown = props.headerShown;
|
|
48
|
+
if (props.gestureEnabled !== undefined)
|
|
49
|
+
patch.gestureEnabled = props.gestureEnabled;
|
|
50
|
+
mergeOptions(registry, patch);
|
|
47
51
|
return () => slots.default?.();
|
|
48
52
|
});
|
|
49
53
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Screen.js","sourceRoot":"","sources":["../../src/components/Screen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"Screen.js","sourceRoot":"","sources":["../../src/components/Screen.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Define } from '@sigx/lynx';
|
|
2
2
|
type StackProps =
|
|
3
3
|
/**
|
|
4
4
|
* Mint a nested navigator with this route at its base. When set, the
|
|
@@ -17,7 +17,19 @@ Define.Prop<'initialRoute', string>
|
|
|
17
17
|
/** Initial params for the nested-stack base entry. */
|
|
18
18
|
& Define.Prop<'initialParams', Record<string, unknown>>
|
|
19
19
|
/** Initial search for the nested-stack base entry. */
|
|
20
|
-
& Define.Prop<'initialSearch', Record<string, unknown
|
|
20
|
+
& Define.Prop<'initialSearch', Record<string, unknown>>
|
|
21
|
+
/**
|
|
22
|
+
* Optional chrome rendered *above* the active screen, **inside this
|
|
23
|
+
* Stack's nav scope**. The intended use is `<Header />`, which needs
|
|
24
|
+
* to resolve `useNav()` to the per-stack nav (not the enclosing one)
|
|
25
|
+
* so it can react to pushes inside this stack — e.g. show a back
|
|
26
|
+
* button when a card is pushed onto a per-tab stack.
|
|
27
|
+
*
|
|
28
|
+
* Without this, a `<Header />` placed as a sibling of `<Stack>`
|
|
29
|
+
* would see the enclosing nav and never update when pushes happen
|
|
30
|
+
* inside the nested stack.
|
|
31
|
+
*/
|
|
32
|
+
& Define.Slot<'default'>;
|
|
21
33
|
/**
|
|
22
34
|
* Stack navigator — renders the topmost stack entry's component at rest, or
|
|
23
35
|
* the top + underneath entries during a transition.
|
|
@@ -46,21 +58,34 @@ Define.Prop<'initialRoute', string>
|
|
|
46
58
|
* walks to root and overlays the whole UI. `replace` stays strictly local
|
|
47
59
|
* (asymmetric with `push`) so a modal `replace` never wipes the root stack.
|
|
48
60
|
*
|
|
49
|
-
* **Render strategy
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
61
|
+
* **Render strategy.** Stack always emits the same JSX shape — a
|
|
62
|
+
* relative wrapper containing one `<Layer>` per entry returned by
|
|
63
|
+
* `computeLayers(stack, transition, progress)`. Each Layer is an
|
|
64
|
+
* absolutely-positioned host view with optional MT-bound translate
|
|
65
|
+
* animation. The pure layer-plan function decides:
|
|
66
|
+
*
|
|
67
|
+
* - **Idle.** Topmost non-overlay base + any overlays above it. All
|
|
68
|
+
* static (no transform). Overlays (`modal` / `fullScreen` /
|
|
69
|
+
* `transparent-modal`) keep their underneath mounted; cards
|
|
70
|
+
* replace their underneath in the base layer.
|
|
71
|
+
* - **Card transition.** Both top and underneath animate (slide-in
|
|
72
|
+
* + parallax). After settle, idle rules apply — the underneath
|
|
73
|
+
* unmounts because the new top is the sole base.
|
|
74
|
+
* - **Overlay transition.** The full idle layer stack up through
|
|
75
|
+
* the underneath stays static; only the animated top has a
|
|
76
|
+
* transform. After settle, the overlay either joins the static
|
|
77
|
+
* idle stack (push) or unmounts (pop).
|
|
57
78
|
*
|
|
58
|
-
* `
|
|
59
|
-
*
|
|
60
|
-
* (
|
|
61
|
-
*
|
|
62
|
-
*
|
|
79
|
+
* Layer keys are `layer-${entry.key}-${animationVariant}`. The variant
|
|
80
|
+
* suffix forces a remount when an entry transitions from animated to
|
|
81
|
+
* static (or vice versa) — `useAnimatedStyle` binds once at setup and
|
|
82
|
+
* can't switch its mapper at runtime. Modal underneath layers never
|
|
83
|
+
* animate, so their key is stable across the modal lifecycle and the
|
|
84
|
+
* subtree's state (per-tab Stack navigators, scroll positions,
|
|
85
|
+
* in-flight inputs) survives.
|
|
63
86
|
*/
|
|
64
|
-
export declare const Stack: ComponentFactory<StackProps, void, {
|
|
87
|
+
export declare const Stack: import("@sigx/runtime-core").ComponentFactory<StackProps, void, {
|
|
88
|
+
default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
|
|
89
|
+
}>;
|
|
65
90
|
export {};
|
|
66
91
|
//# sourceMappingURL=Stack.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stack.d.ts","sourceRoot":"","sources":["../../src/components/Stack.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOH,KAAK,
|
|
1
|
+
{"version":3,"file":"Stack.d.ts","sourceRoot":"","sources":["../../src/components/Stack.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOH,KAAK,MAAM,EACd,MAAM,YAAY,CAAC;AAepB,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;AACvD;;;;;;;;;;GAUG;GACD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAI7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,eAAO,MAAM,KAAK;;EAuPhB,CAAC"}
|