@sigx/lynx-navigation 0.1.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -7
- package/dist/components/EntryScope.d.ts +1 -1
- package/dist/components/EntryScope.d.ts.map +1 -1
- package/dist/components/Layer.d.ts +34 -0
- package/dist/components/Layer.d.ts.map +1 -0
- package/dist/components/Link.d.ts +2 -2
- package/dist/components/Link.d.ts.map +1 -1
- package/dist/components/NavigationRoot.d.ts +2 -2
- package/dist/components/NavigationRoot.d.ts.map +1 -1
- package/dist/components/Screen.d.ts +6 -6
- package/dist/components/Screen.d.ts.map +1 -1
- package/dist/components/Stack.d.ts +83 -13
- package/dist/components/Stack.d.ts.map +1 -1
- package/dist/components/TabBar.d.ts +19 -20
- package/dist/components/TabBar.d.ts.map +1 -1
- package/dist/components/Tabs.d.ts +30 -21
- package/dist/components/Tabs.d.ts.map +1 -1
- package/dist/define-routes.d.ts +1 -1
- package/dist/define-routes.d.ts.map +1 -1
- package/dist/hooks/use-focus.d.ts.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-linking-nav.d.ts +3 -3
- package/dist/hooks/use-linking-nav.d.ts.map +1 -1
- package/dist/hooks/use-nav-internal.d.ts +21 -3
- package/dist/hooks/use-nav-internal.d.ts.map +1 -1
- package/dist/hooks/use-nav-serializer.d.ts +1 -1
- package/dist/hooks/use-nav-serializer.d.ts.map +1 -1
- package/dist/hooks/use-nav.d.ts +38 -3
- package/dist/hooks/use-nav.d.ts.map +1 -1
- package/dist/hooks/use-params.d.ts +1 -1
- package/dist/hooks/use-params.d.ts.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-options.d.ts +1 -1
- package/dist/hooks/use-screen-options.d.ts.map +1 -1
- package/dist/hooks/use-search.d.ts +1 -1
- package/dist/hooks/use-search.d.ts.map +1 -1
- package/dist/href.d.ts +2 -2
- package/dist/href.d.ts.map +1 -1
- package/dist/index.d.ts +33 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1160 -29
- 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/screen-registry.d.ts +1 -1
- package/dist/internal/screen-registry.d.ts.map +1 -1
- package/dist/internal/screen-width.d.ts +9 -7
- package/dist/internal/screen-width.d.ts.map +1 -1
- package/dist/navigator/core.d.ts +31 -4
- package/dist/navigator/core.d.ts.map +1 -1
- package/dist/register.d.ts +1 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/url/index.d.ts +6 -6
- package/dist/url/index.d.ts.map +1 -1
- package/dist/url/parse.d.ts +1 -1
- package/dist/url/parse.d.ts.map +1 -1
- package/dist/url/registry.d.ts +2 -2
- package/dist/url/registry.d.ts.map +1 -1
- package/dist/url/validate.d.ts +1 -1
- package/dist/url/validate.d.ts.map +1 -1
- package/package.json +11 -10
- package/src/components/Drawer.d.ts +55 -0
- package/src/components/EdgeBackHandle.d.ts +1 -0
- package/src/components/EdgeBackHandle.tsx +2 -2
- package/{dist/components/EntryScope.js → src/components/EntryScope.d.ts} +7 -15
- package/src/components/EntryScope.tsx +15 -4
- package/src/components/Header.d.ts +6 -0
- package/src/components/Header.tsx +3 -3
- package/src/components/Layer.d.ts +33 -0
- package/src/components/Layer.tsx +96 -0
- package/src/components/Link.d.ts +60 -0
- package/src/components/Link.tsx +4 -4
- package/src/components/NavigationRoot.d.ts +36 -0
- package/src/components/NavigationRoot.tsx +6 -6
- package/src/components/Screen.d.ts +97 -0
- package/src/components/Screen.tsx +13 -11
- package/src/components/Stack.d.ts +90 -0
- package/src/components/Stack.tsx +333 -92
- package/src/components/TabBar.d.ts +38 -0
- package/src/components/TabBar.tsx +22 -22
- package/src/components/Tabs.d.ts +109 -0
- package/src/components/Tabs.tsx +54 -22
- package/{dist/define-routes.js → src/define-routes.d.ts} +2 -4
- package/src/define-routes.ts +1 -1
- package/{dist/hooks/use-focus.js → src/hooks/use-focus.d.ts} +3 -39
- package/src/hooks/use-focus.ts +9 -3
- package/src/hooks/use-hardware-back.d.ts +37 -0
- package/src/hooks/use-hardware-back.ts +43 -9
- package/src/hooks/use-linking-nav.d.ts +91 -0
- package/src/hooks/use-linking-nav.ts +4 -4
- package/src/hooks/use-nav-internal.d.ts +91 -0
- package/src/hooks/use-nav-internal.ts +24 -3
- package/src/hooks/use-nav-serializer.d.ts +82 -0
- package/src/hooks/use-nav-serializer.ts +3 -3
- package/src/hooks/use-nav.d.ts +111 -0
- package/src/hooks/use-nav.ts +40 -3
- package/{dist/hooks/use-params.js → src/hooks/use-params.d.ts} +2 -6
- package/src/hooks/use-params.ts +2 -2
- package/src/hooks/use-screen-chrome.d.ts +18 -0
- package/src/hooks/use-screen-chrome.ts +122 -0
- package/src/hooks/use-screen-options.d.ts +2 -0
- package/src/hooks/use-screen-options.ts +3 -3
- package/{dist/hooks/use-search.js → src/hooks/use-search.d.ts} +2 -6
- package/src/hooks/use-search.ts +2 -2
- package/src/href.d.ts +54 -0
- package/src/href.ts +6 -6
- package/src/index.d.ts +39 -0
- package/src/index.ts +33 -31
- package/src/internal/layer-plan.d.ts +68 -0
- package/src/internal/layer-plan.ts +187 -0
- package/{dist/internal/screen-registry.js → src/internal/screen-registry.d.ts} +21 -32
- package/src/internal/screen-registry.ts +1 -1
- package/src/internal/screen-width.d.ts +17 -0
- package/src/internal/screen-width.ts +22 -14
- package/src/navigator/core.d.ts +96 -0
- package/src/navigator/core.ts +90 -10
- package/src/register.d.ts +37 -0
- package/src/register.ts +1 -1
- package/src/types.d.ts +217 -0
- package/src/url/build.d.ts +15 -0
- package/src/url/build.ts +2 -2
- package/src/url/compile.d.ts +34 -0
- package/src/url/format.d.ts +28 -0
- package/src/url/index.ts +6 -6
- package/src/url/parse.d.ts +20 -0
- package/src/url/parse.ts +6 -6
- package/{dist/url/registry.js → src/url/registry.d.ts} +12 -28
- package/src/url/registry.ts +3 -3
- package/src/url/validate.d.ts +23 -0
- package/src/url/validate.ts +1 -1
- package/dist/components/Drawer.js +0 -74
- package/dist/components/Drawer.js.map +0 -1
- package/dist/components/EdgeBackHandle.js +0 -144
- package/dist/components/EdgeBackHandle.js.map +0 -1
- package/dist/components/EntryScope.js.map +0 -1
- package/dist/components/Header.js +0 -103
- package/dist/components/Header.js.map +0 -1
- package/dist/components/Link.js +0 -51
- package/dist/components/Link.js.map +0 -1
- package/dist/components/NavigationRoot.js +0 -67
- package/dist/components/NavigationRoot.js.map +0 -1
- package/dist/components/Screen.js +0 -94
- package/dist/components/Screen.js.map +0 -1
- 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/dist/components/Stack.js +0 -75
- package/dist/components/Stack.js.map +0 -1
- package/dist/components/TabBar.js +0 -63
- package/dist/components/TabBar.js.map +0 -1
- package/dist/components/Tabs.js +0 -140
- package/dist/components/Tabs.js.map +0 -1
- package/dist/define-routes.js.map +0 -1
- package/dist/hooks/use-focus.js.map +0 -1
- package/dist/hooks/use-hardware-back.js +0 -50
- package/dist/hooks/use-hardware-back.js.map +0 -1
- package/dist/hooks/use-linking-nav.js +0 -109
- package/dist/hooks/use-linking-nav.js.map +0 -1
- package/dist/hooks/use-nav-internal.js +0 -44
- package/dist/hooks/use-nav-internal.js.map +0 -1
- package/dist/hooks/use-nav-serializer.js +0 -181
- package/dist/hooks/use-nav-serializer.js.map +0 -1
- package/dist/hooks/use-nav.js +0 -11
- package/dist/hooks/use-nav.js.map +0 -1
- package/dist/hooks/use-params.js.map +0 -1
- package/dist/hooks/use-screen-options.js +0 -43
- package/dist/hooks/use-screen-options.js.map +0 -1
- package/dist/hooks/use-search.js.map +0 -1
- package/dist/href.js +0 -57
- package/dist/href.js.map +0 -1
- package/dist/internal/screen-registry.js.map +0 -1
- package/dist/internal/screen-width.js +0 -30
- package/dist/internal/screen-width.js.map +0 -1
- package/dist/navigator/core.js +0 -344
- package/dist/navigator/core.js.map +0 -1
- package/dist/register.js +0 -2
- package/dist/register.js.map +0 -1
- package/dist/types.js +0 -9
- package/dist/types.js.map +0 -1
- package/dist/url/build.js +0 -30
- package/dist/url/build.js.map +0 -1
- package/dist/url/compile.js +0 -83
- package/dist/url/compile.js.map +0 -1
- package/dist/url/format.js +0 -102
- package/dist/url/format.js.map +0 -1
- package/dist/url/index.js +0 -13
- package/dist/url/index.js.map +0 -1
- package/dist/url/parse.js +0 -94
- package/dist/url/parse.js.map +0 -1
- package/dist/url/registry.js.map +0 -1
- package/dist/url/validate.js +0 -37
- package/dist/url/validate.js.map +0 -1
- package/src/components/ScreenContainer.tsx +0 -114
package/src/index.ts
CHANGED
|
@@ -5,46 +5,48 @@
|
|
|
5
5
|
* Coming next: Screen with slot-based header API, MTS transitions, Tabs.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export { defineRoutes } from './define-routes
|
|
9
|
-
export type { Register, RegisteredRoutes, RouteId, RouteParams, RouteSearch } from './register
|
|
10
|
-
export { useNav } from './hooks/use-nav
|
|
11
|
-
export type { Nav, RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav
|
|
12
|
-
export { useParams } from './hooks/use-params
|
|
13
|
-
export { useSearch } from './hooks/use-search
|
|
14
|
-
export { useHardwareBack } from './hooks/use-hardware-back
|
|
15
|
-
export { useLinkingNav } from './hooks/use-linking-nav
|
|
16
|
-
export type { UseLinkingNavOptions } from './hooks/use-linking-nav
|
|
17
|
-
export { useIsFocused, useFocusEffect } from './hooks/use-focus
|
|
18
|
-
export { useScreenOptions } from './hooks/use-screen-options
|
|
8
|
+
export { defineRoutes } from './define-routes';
|
|
9
|
+
export type { Register, RegisteredRoutes, RouteId, RouteParams, RouteSearch } from './register';
|
|
10
|
+
export { useNav } from './hooks/use-nav';
|
|
11
|
+
export type { Nav, RoutesWithoutParams, RoutesWithParams } from './hooks/use-nav';
|
|
12
|
+
export { useParams } from './hooks/use-params';
|
|
13
|
+
export { useSearch } from './hooks/use-search';
|
|
14
|
+
export { useHardwareBack } from './hooks/use-hardware-back';
|
|
15
|
+
export { useLinkingNav } from './hooks/use-linking-nav';
|
|
16
|
+
export type { UseLinkingNavOptions } from './hooks/use-linking-nav';
|
|
17
|
+
export { useIsFocused, useFocusEffect } from './hooks/use-focus';
|
|
18
|
+
export { useScreenOptions } from './hooks/use-screen-options';
|
|
19
|
+
export { useScreenChrome } from './hooks/use-screen-chrome';
|
|
20
|
+
export type { ScreenChrome } from './hooks/use-screen-chrome';
|
|
19
21
|
export {
|
|
20
22
|
useNavSerializer,
|
|
21
23
|
NAV_SNAPSHOT_VERSION,
|
|
22
|
-
} from './hooks/use-nav-serializer
|
|
24
|
+
} from './hooks/use-nav-serializer';
|
|
23
25
|
export type {
|
|
24
26
|
NavSnapshot,
|
|
25
27
|
NavStorageAdapter,
|
|
26
28
|
UseNavSerializerOptions,
|
|
27
|
-
} from './hooks/use-nav-serializer
|
|
28
|
-
export { hrefFor, parseHref } from './href
|
|
29
|
-
export type { Href } from './href
|
|
29
|
+
} from './hooks/use-nav-serializer';
|
|
30
|
+
export { hrefFor, parseHref } from './href';
|
|
31
|
+
export type { Href } from './href';
|
|
30
32
|
// URL bridge internals: `_setRouteRegistry` is a leading-underscore export —
|
|
31
33
|
// intended for tests, deep-link bootstrap before a NavigationRoot mounts, and
|
|
32
34
|
// any other integration that needs to seed the registry imperatively.
|
|
33
|
-
export { _setRouteRegistry, _clearRouteRegistry } from './url/registry
|
|
34
|
-
export { compilePath } from './url/compile
|
|
35
|
-
export type { CompiledPath } from './url/compile
|
|
36
|
-
export { NavigationRoot } from './components/NavigationRoot
|
|
37
|
-
export { Stack } from './components/Stack
|
|
38
|
-
export { Screen } from './components/Screen
|
|
39
|
-
export { Header } from './components/Header
|
|
40
|
-
export { Tabs, useTabs } from './components/Tabs
|
|
41
|
-
export type { TabInfo, TabsNav } from './components/Tabs
|
|
42
|
-
export { TabBar } from './components/TabBar
|
|
43
|
-
export type { TabRenderContext } from './components/TabBar
|
|
44
|
-
export { Drawer, useDrawer } from './components/Drawer
|
|
45
|
-
export type { DrawerNav } from './components/Drawer
|
|
46
|
-
export { Link } from './components/Link
|
|
47
|
-
export type { LinkProps } from './components/Link
|
|
35
|
+
export { _setRouteRegistry, _clearRouteRegistry } from './url/registry';
|
|
36
|
+
export { compilePath } from './url/compile';
|
|
37
|
+
export type { CompiledPath } from './url/compile';
|
|
38
|
+
export { NavigationRoot } from './components/NavigationRoot';
|
|
39
|
+
export { Stack } from './components/Stack';
|
|
40
|
+
export { Screen } from './components/Screen';
|
|
41
|
+
export { Header } from './components/Header';
|
|
42
|
+
export { Tabs, useTabs } from './components/Tabs';
|
|
43
|
+
export type { TabInfo, TabsNav } from './components/Tabs';
|
|
44
|
+
export { TabBar } from './components/TabBar';
|
|
45
|
+
export type { TabRenderContext } from './components/TabBar';
|
|
46
|
+
export { Drawer, useDrawer } from './components/Drawer';
|
|
47
|
+
export type { DrawerNav } from './components/Drawer';
|
|
48
|
+
export { Link } from './components/Link';
|
|
49
|
+
export type { LinkProps } from './components/Link';
|
|
48
50
|
export type {
|
|
49
51
|
ComponentLike,
|
|
50
52
|
EmptyParams,
|
|
@@ -64,4 +66,4 @@ export type {
|
|
|
64
66
|
TransitionKind,
|
|
65
67
|
TransitionRole,
|
|
66
68
|
TransitionState,
|
|
67
|
-
} from './types
|
|
69
|
+
} from './types';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure layer-plan computation for `<Stack>`'s render.
|
|
3
|
+
*
|
|
4
|
+
* Given (stack, transition, progress), produces an ordered list of
|
|
5
|
+
* `Layer`s — each is an entry to render plus an optional transform
|
|
6
|
+
* spec for animation. The Stack render emits one absolutely-positioned
|
|
7
|
+
* `<view>` per layer, stacked bottom-to-top in document order.
|
|
8
|
+
*
|
|
9
|
+
* Why this is its own module: the layer-selection logic is the only
|
|
10
|
+
* non-obvious part of the navigator's render path, and the rules are
|
|
11
|
+
* easier to read (and unit-test) as a pure function over the
|
|
12
|
+
* navigator's state than as inline render branches.
|
|
13
|
+
*
|
|
14
|
+
* Rules:
|
|
15
|
+
*
|
|
16
|
+
* - **Idle (no transition).** Render the topmost non-overlay entry
|
|
17
|
+
* as the base, plus every overlay entry above it. Overlays
|
|
18
|
+
* (`modal` / `fullScreen` / `transparent-modal`) keep their
|
|
19
|
+
* underneath mounted; cards replace their underneath in the base
|
|
20
|
+
* layer.
|
|
21
|
+
*
|
|
22
|
+
* - **Card transition.** Two layers: the underneath entry (animated
|
|
23
|
+
* with the parallax-card-underneath spec) and the top entry
|
|
24
|
+
* (animated with the slide-in-from-right spec). After the
|
|
25
|
+
* transition completes, the idle rule kicks in — the underneath
|
|
26
|
+
* unmounts because the new top becomes the sole base.
|
|
27
|
+
*
|
|
28
|
+
* - **Overlay transition.** The full idle layer stack up through the
|
|
29
|
+
* underneath entry stays static (no transform). The animated top
|
|
30
|
+
* is the only layer with a transform. After the transition, the
|
|
31
|
+
* overlay either joins the static idle stack (push) or unmounts
|
|
32
|
+
* (pop).
|
|
33
|
+
*
|
|
34
|
+
* The Layer.key for the Stack render is
|
|
35
|
+
* `layer-${entry.key}-${animVariant(layer.animation)}`. The variant
|
|
36
|
+
* suffix forces a remount when an entry transitions from animated to
|
|
37
|
+
* static (or vice versa) — `useAnimatedStyle` can't re-bind mid-life,
|
|
38
|
+
* so we get a fresh `useAnimatedStyle` call per animation state.
|
|
39
|
+
* Modal underneath layers never animate, so they stay statically
|
|
40
|
+
* keyed across the modal lifecycle and their state (per-tab Stack,
|
|
41
|
+
* scroll, in-flight inputs) survives.
|
|
42
|
+
*/
|
|
43
|
+
import type { SharedValue } from '@sigx/lynx';
|
|
44
|
+
import type { Presentation, StackEntry, TransitionState } from '../types';
|
|
45
|
+
export type LayerAnimation = {
|
|
46
|
+
axis: 'translateX' | 'translateY';
|
|
47
|
+
inputRange: readonly [number, number];
|
|
48
|
+
outputRange: readonly [number, number];
|
|
49
|
+
progress: SharedValue<number>;
|
|
50
|
+
};
|
|
51
|
+
export interface Layer {
|
|
52
|
+
/** The entry whose component renders inside this layer. */
|
|
53
|
+
readonly entry: StackEntry;
|
|
54
|
+
/** When non-null, the layer's host view binds a `useAnimatedStyle` mapper. */
|
|
55
|
+
readonly animation: LayerAnimation | null;
|
|
56
|
+
}
|
|
57
|
+
export declare function isOverlayPresentation(p: Presentation): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Suffix used in a layer's render key. Stable for the layer's
|
|
60
|
+
* lifetime (same entry, same animation kind) and changes when the
|
|
61
|
+
* animation transitions on/off so the Layer remounts and rebinds.
|
|
62
|
+
*/
|
|
63
|
+
export declare function animationVariant(animation: LayerAnimation | null): string;
|
|
64
|
+
/**
|
|
65
|
+
* Compute the visible-layer list for one render of `<Stack>`. Pure —
|
|
66
|
+
* unit-testable independently of the renderer.
|
|
67
|
+
*/
|
|
68
|
+
export declare function computeLayers(stack: readonly StackEntry[], transition: TransitionState | null, progress: SharedValue<number> | null): Layer[];
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure layer-plan computation for `<Stack>`'s render.
|
|
3
|
+
*
|
|
4
|
+
* Given (stack, transition, progress), produces an ordered list of
|
|
5
|
+
* `Layer`s — each is an entry to render plus an optional transform
|
|
6
|
+
* spec for animation. The Stack render emits one absolutely-positioned
|
|
7
|
+
* `<view>` per layer, stacked bottom-to-top in document order.
|
|
8
|
+
*
|
|
9
|
+
* Why this is its own module: the layer-selection logic is the only
|
|
10
|
+
* non-obvious part of the navigator's render path, and the rules are
|
|
11
|
+
* easier to read (and unit-test) as a pure function over the
|
|
12
|
+
* navigator's state than as inline render branches.
|
|
13
|
+
*
|
|
14
|
+
* Rules:
|
|
15
|
+
*
|
|
16
|
+
* - **Idle (no transition).** Render the topmost non-overlay entry
|
|
17
|
+
* as the base, plus every overlay entry above it. Overlays
|
|
18
|
+
* (`modal` / `fullScreen` / `transparent-modal`) keep their
|
|
19
|
+
* underneath mounted; cards replace their underneath in the base
|
|
20
|
+
* layer.
|
|
21
|
+
*
|
|
22
|
+
* - **Card transition.** Two layers: the underneath entry (animated
|
|
23
|
+
* with the parallax-card-underneath spec) and the top entry
|
|
24
|
+
* (animated with the slide-in-from-right spec). After the
|
|
25
|
+
* transition completes, the idle rule kicks in — the underneath
|
|
26
|
+
* unmounts because the new top becomes the sole base.
|
|
27
|
+
*
|
|
28
|
+
* - **Overlay transition.** The full idle layer stack up through the
|
|
29
|
+
* underneath entry stays static (no transform). The animated top
|
|
30
|
+
* is the only layer with a transform. After the transition, the
|
|
31
|
+
* overlay either joins the static idle stack (push) or unmounts
|
|
32
|
+
* (pop).
|
|
33
|
+
*
|
|
34
|
+
* The Layer.key for the Stack render is
|
|
35
|
+
* `layer-${entry.key}-${animVariant(layer.animation)}`. The variant
|
|
36
|
+
* suffix forces a remount when an entry transitions from animated to
|
|
37
|
+
* static (or vice versa) — `useAnimatedStyle` can't re-bind mid-life,
|
|
38
|
+
* so we get a fresh `useAnimatedStyle` call per animation state.
|
|
39
|
+
* Modal underneath layers never animate, so they stay statically
|
|
40
|
+
* keyed across the modal lifecycle and their state (per-tab Stack,
|
|
41
|
+
* scroll, in-flight inputs) survives.
|
|
42
|
+
*/
|
|
43
|
+
import type { SharedValue } from '@sigx/lynx';
|
|
44
|
+
import { SCREEN_HEIGHT, SCREEN_WIDTH } from './screen-width';
|
|
45
|
+
import type {
|
|
46
|
+
Presentation,
|
|
47
|
+
StackEntry,
|
|
48
|
+
TransitionKind,
|
|
49
|
+
TransitionState,
|
|
50
|
+
} from '../types';
|
|
51
|
+
|
|
52
|
+
const PARALLAX_FACTOR = 0.3;
|
|
53
|
+
|
|
54
|
+
export type LayerAnimation = {
|
|
55
|
+
axis: 'translateX' | 'translateY';
|
|
56
|
+
inputRange: readonly [number, number];
|
|
57
|
+
outputRange: readonly [number, number];
|
|
58
|
+
progress: SharedValue<number>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export interface Layer {
|
|
62
|
+
/** The entry whose component renders inside this layer. */
|
|
63
|
+
readonly entry: StackEntry;
|
|
64
|
+
/** When non-null, the layer's host view binds a `useAnimatedStyle` mapper. */
|
|
65
|
+
readonly animation: LayerAnimation | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function isOverlayPresentation(p: Presentation): boolean {
|
|
69
|
+
return p === 'modal' || p === 'fullScreen' || p === 'transparent-modal';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Suffix used in a layer's render key. Stable for the layer's
|
|
74
|
+
* lifetime (same entry, same animation kind) and changes when the
|
|
75
|
+
* animation transitions on/off so the Layer remounts and rebinds.
|
|
76
|
+
*/
|
|
77
|
+
export function animationVariant(animation: LayerAnimation | null): string {
|
|
78
|
+
if (!animation) return 'static';
|
|
79
|
+
// Output range alone identifies the transition shape — different
|
|
80
|
+
// animations (card-top vs card-underneath vs overlay-top, push vs
|
|
81
|
+
// pop) all land on different range tuples.
|
|
82
|
+
return `${animation.axis}:${animation.outputRange[0]}->${animation.outputRange[1]}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Card-presentation transition transforms. `role='top'` is the entry
|
|
87
|
+
* being pushed/popped; `role='underneath'` is the one parallaxing.
|
|
88
|
+
*/
|
|
89
|
+
function cardAnimation(
|
|
90
|
+
role: 'top' | 'underneath',
|
|
91
|
+
kind: TransitionKind,
|
|
92
|
+
progress: SharedValue<number>,
|
|
93
|
+
): LayerAnimation {
|
|
94
|
+
if (kind === 'push') {
|
|
95
|
+
if (role === 'top') {
|
|
96
|
+
return { axis: 'translateX', inputRange: [0, 1], outputRange: [SCREEN_WIDTH, 0], progress };
|
|
97
|
+
}
|
|
98
|
+
return { axis: 'translateX', inputRange: [0, 1], outputRange: [0, -PARALLAX_FACTOR * SCREEN_WIDTH], progress };
|
|
99
|
+
}
|
|
100
|
+
// pop
|
|
101
|
+
if (role === 'top') {
|
|
102
|
+
return { axis: 'translateX', inputRange: [0, 1], outputRange: [0, SCREEN_WIDTH], progress };
|
|
103
|
+
}
|
|
104
|
+
return { axis: 'translateX', inputRange: [0, 1], outputRange: [-PARALLAX_FACTOR * SCREEN_WIDTH, 0], progress };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Overlay-presentation transition transform for the animated top.
|
|
109
|
+
* The underneath of an overlay transition does not animate (modal
|
|
110
|
+
* doesn't reposition its background); we render it as a static layer
|
|
111
|
+
* instead, so this function only produces the top's transform.
|
|
112
|
+
*/
|
|
113
|
+
function overlayTopAnimation(
|
|
114
|
+
kind: TransitionKind,
|
|
115
|
+
progress: SharedValue<number>,
|
|
116
|
+
): LayerAnimation {
|
|
117
|
+
if (kind === 'push') {
|
|
118
|
+
return { axis: 'translateY', inputRange: [0, 1], outputRange: [SCREEN_HEIGHT, 0], progress };
|
|
119
|
+
}
|
|
120
|
+
return { axis: 'translateY', inputRange: [0, 1], outputRange: [0, SCREEN_HEIGHT], progress };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compute the visible-layer list for one render of `<Stack>`. Pure —
|
|
125
|
+
* unit-testable independently of the renderer.
|
|
126
|
+
*/
|
|
127
|
+
export function computeLayers(
|
|
128
|
+
stack: readonly StackEntry[],
|
|
129
|
+
transition: TransitionState | null,
|
|
130
|
+
progress: SharedValue<number> | null,
|
|
131
|
+
): Layer[] {
|
|
132
|
+
if (!transition) {
|
|
133
|
+
// Idle: topmost non-overlay base + any overlays above it.
|
|
134
|
+
let baseIdx = stack.length - 1;
|
|
135
|
+
while (baseIdx > 0 && isOverlayPresentation(stack[baseIdx].presentation)) {
|
|
136
|
+
baseIdx -= 1;
|
|
137
|
+
}
|
|
138
|
+
return stack.slice(baseIdx).map((entry) => ({ entry, animation: null }));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// A transition is in flight. `progress` may still be null when
|
|
142
|
+
// animations are disabled — produce static layers in that case
|
|
143
|
+
// (the animation never plays; the transition timer just ticks).
|
|
144
|
+
const isOverlay = isOverlayPresentation(transition.topEntry.presentation);
|
|
145
|
+
if (!isOverlay) {
|
|
146
|
+
// Card transition: just the two participating entries, both
|
|
147
|
+
// animated (parallax underneath + slide top).
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
entry: transition.underneathEntry,
|
|
151
|
+
animation: progress ? cardAnimation('underneath', transition.kind, progress) : null,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
entry: transition.topEntry,
|
|
155
|
+
animation: progress ? cardAnimation('top', transition.kind, progress) : null,
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Overlay transition: render the full idle layer stack up through
|
|
161
|
+
// the underneath entry (all static — they don't animate) plus the
|
|
162
|
+
// animated top.
|
|
163
|
+
const underneathIdx = stack.findIndex(
|
|
164
|
+
(e) => e.key === transition.underneathEntry.key,
|
|
165
|
+
);
|
|
166
|
+
// If the underneath isn't in the stack (e.g. mid-pop where the
|
|
167
|
+
// stack mutation already removed an entry), fall back to the
|
|
168
|
+
// current top of the stack.
|
|
169
|
+
const lastStaticIdx = underneathIdx >= 0 ? underneathIdx : stack.length - 1;
|
|
170
|
+
|
|
171
|
+
let baseIdx = lastStaticIdx;
|
|
172
|
+
while (baseIdx > 0 && isOverlayPresentation(stack[baseIdx].presentation)) {
|
|
173
|
+
baseIdx -= 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const staticLayers: Layer[] = stack
|
|
177
|
+
.slice(baseIdx, lastStaticIdx + 1)
|
|
178
|
+
.map((entry) => ({ entry, animation: null }));
|
|
179
|
+
|
|
180
|
+
return [
|
|
181
|
+
...staticLayers,
|
|
182
|
+
{
|
|
183
|
+
entry: transition.topEntry,
|
|
184
|
+
animation: progress ? overlayTopAnimation(transition.kind, progress) : null,
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
}
|
|
@@ -15,45 +15,34 @@
|
|
|
15
15
|
* so a persistent HeaderBar can read slots from the currently-focused entry
|
|
16
16
|
* without needing to be itself remounted on each navigation.
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
import { type Signal } from '@sigx/lynx';
|
|
19
|
+
import type { ScreenOptions, ScreenSlotFills, StackEntry } from '../types';
|
|
20
|
+
/**
|
|
21
|
+
* Reactive container for one screen's options and slot fills.
|
|
22
|
+
*
|
|
23
|
+
* `options` and `slots` are deeply-reactive object signals (sigx's `signal()`
|
|
24
|
+
* of an object returns a Proxy that tracks per-key reads and notifies
|
|
25
|
+
* per-key writes). Writers assign individual keys; readers subscribe to the
|
|
26
|
+
* keys they actually use — no whole-object reads, no read/write cycles in
|
|
27
|
+
* setup.
|
|
28
|
+
*/
|
|
29
|
+
export interface ScreenRegistry {
|
|
30
|
+
readonly entry: StackEntry;
|
|
31
|
+
/** Reactive ScreenOptions — written per-key by `<Screen>`. */
|
|
32
|
+
readonly options: Signal<ScreenOptions>;
|
|
33
|
+
/** Reactive ScreenSlotFills — written per-key by `<Screen.Header>` et al. */
|
|
34
|
+
readonly slots: Signal<ScreenSlotFills>;
|
|
26
35
|
}
|
|
36
|
+
/** Create a fresh registry for an entry. Options and slots start empty. */
|
|
37
|
+
export declare function createScreenRegistry(entry: StackEntry): ScreenRegistry;
|
|
27
38
|
/**
|
|
28
39
|
* Set a single slot fill on a registry. Pass `undefined` to clear.
|
|
29
40
|
* Per-key write on the proxy — does not read other keys, so it can't loop
|
|
30
41
|
* with effects that read different slot keys.
|
|
31
42
|
*/
|
|
32
|
-
export function setSlot(registry, name, fill)
|
|
33
|
-
if (fill === undefined) {
|
|
34
|
-
// Assigning undefined keeps the key around in the proxy; explicit
|
|
35
|
-
// delete is what consumers checking `name in slots` expect.
|
|
36
|
-
delete registry.slots[name];
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
registry.slots[name] = fill;
|
|
40
|
-
}
|
|
43
|
+
export declare function setSlot<K extends keyof ScreenSlotFills>(registry: ScreenRegistry, name: K, fill: ScreenSlotFills[K] | undefined): void;
|
|
41
44
|
/**
|
|
42
45
|
* Merge partial options into a registry. Each option key is written
|
|
43
46
|
* independently on the proxy — `undefined` keys clear that option.
|
|
44
47
|
*/
|
|
45
|
-
export function mergeOptions(registry, patch)
|
|
46
|
-
for (const key of Object.keys(patch)) {
|
|
47
|
-
const v = patch[key];
|
|
48
|
-
if (v === undefined) {
|
|
49
|
-
delete registry.options[key];
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
// Property-level assignment on a deeply-reactive proxy: notifies
|
|
53
|
-
// only subscribers of this specific key, never reads the whole
|
|
54
|
-
// options object, so it can't trigger the setup that wrote it.
|
|
55
|
-
registry.options[key] = v;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
//# sourceMappingURL=screen-registry.js.map
|
|
48
|
+
export declare function mergeOptions(registry: ScreenRegistry, patch: ScreenOptions): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logical screen dimensions (in dp) read from `lynx.SystemInfo` at module
|
|
3
|
+
* load. Falls back to typical phone values if SystemInfo isn't available —
|
|
4
|
+
* module load happens BG-side after the bundle initializes, by which time
|
|
5
|
+
* `lynx.SystemInfo` is populated, so the fallback only fires in tests /
|
|
6
|
+
* SSR / non-Lynx hosts.
|
|
7
|
+
*
|
|
8
|
+
* Used by:
|
|
9
|
+
* - `<ScreenContainer>` for the slide-from-right (translateX) and
|
|
10
|
+
* slide-from-bottom (translateY, modal) transform output ranges.
|
|
11
|
+
* - `<EdgeBackHandle>` for the gesture commit threshold (`dx / width`).
|
|
12
|
+
*
|
|
13
|
+
* Both must agree, otherwise the commit threshold and the animation
|
|
14
|
+
* geometry won't line up. Single shared module avoids drift.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SCREEN_WIDTH: number;
|
|
17
|
+
export declare const SCREEN_HEIGHT: number;
|
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Logical screen
|
|
3
|
-
* Falls back to
|
|
4
|
-
* load happens BG-side after the bundle initializes, by which time
|
|
5
|
-
* `lynx.SystemInfo` is populated, so the fallback only fires in tests /
|
|
6
|
-
* non-Lynx hosts.
|
|
2
|
+
* Logical screen dimensions (in dp) read from `lynx.SystemInfo` at module
|
|
3
|
+
* load. Falls back to typical phone values if SystemInfo isn't available —
|
|
4
|
+
* module load happens BG-side after the bundle initializes, by which time
|
|
5
|
+
* `lynx.SystemInfo` is populated, so the fallback only fires in tests /
|
|
6
|
+
* SSR / non-Lynx hosts.
|
|
7
7
|
*
|
|
8
8
|
* Used by:
|
|
9
|
-
* - `<ScreenContainer>` for the slide-from-right
|
|
9
|
+
* - `<ScreenContainer>` for the slide-from-right (translateX) and
|
|
10
|
+
* slide-from-bottom (translateY, modal) transform output ranges.
|
|
10
11
|
* - `<EdgeBackHandle>` for the gesture commit threshold (`dx / width`).
|
|
11
12
|
*
|
|
12
13
|
* Both must agree, otherwise the commit threshold and the animation
|
|
13
|
-
* geometry won't line up. Single shared
|
|
14
|
+
* geometry won't line up. Single shared module avoids drift.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
declare const lynx:
|
|
17
|
-
| {
|
|
18
|
+
| {
|
|
19
|
+
SystemInfo?: {
|
|
20
|
+
pixelWidth?: number;
|
|
21
|
+
pixelHeight?: number;
|
|
22
|
+
pixelRatio?: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
18
25
|
| undefined;
|
|
19
26
|
|
|
20
|
-
function
|
|
27
|
+
function readDp(prop: 'pixelWidth' | 'pixelHeight', fallback: number): number {
|
|
21
28
|
try {
|
|
22
29
|
const info = typeof lynx !== 'undefined' ? lynx?.SystemInfo : undefined;
|
|
23
|
-
const
|
|
30
|
+
const px = info?.[prop];
|
|
24
31
|
const pr = info?.pixelRatio || 1;
|
|
25
|
-
if (typeof
|
|
26
|
-
return Math.round(
|
|
32
|
+
if (typeof px === 'number' && px > 0) {
|
|
33
|
+
return Math.round(px / pr);
|
|
27
34
|
}
|
|
28
35
|
} catch {
|
|
29
36
|
// Lynx globals not present (test env / SSR) — use fallback.
|
|
30
37
|
}
|
|
31
|
-
return
|
|
38
|
+
return fallback;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
export const SCREEN_WIDTH =
|
|
41
|
+
export const SCREEN_WIDTH = readDp('pixelWidth', 400);
|
|
42
|
+
export const SCREEN_HEIGHT = readDp('pixelHeight', 800);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type SharedValue } from '@sigx/lynx';
|
|
2
|
+
import type { Nav } from '../hooks/use-nav';
|
|
3
|
+
import type { ScreenRegistry } from '../internal/screen-registry';
|
|
4
|
+
import type { RouteMap, StackEntry } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* The reactive backing state for one navigator instance.
|
|
7
|
+
*
|
|
8
|
+
* Two reactive signals drive the public surface:
|
|
9
|
+
* - `stack` is the entry array (read via `nav.stack` / `nav.current`).
|
|
10
|
+
* - `transition` is non-null only while a push/pop animation is in flight;
|
|
11
|
+
* `<Stack>` reads it to decide whether to render one screen or two.
|
|
12
|
+
*
|
|
13
|
+
* Pop is committed *after* its slide animation completes — `nav.canGoBack`
|
|
14
|
+
* stays true during the slide, then flips when the entry actually leaves the
|
|
15
|
+
* stack. Push commits its stack mutation immediately and animates the new
|
|
16
|
+
* entry in.
|
|
17
|
+
*/
|
|
18
|
+
export interface NavigatorState {
|
|
19
|
+
readonly nav: Nav;
|
|
20
|
+
readonly routes: RouteMap;
|
|
21
|
+
/**
|
|
22
|
+
* Internal: BG-side gesture-back controller used by `<EdgeBackHandle>`.
|
|
23
|
+
* The `progress` SharedValue is wired here so a gesture worklet can write
|
|
24
|
+
* it directly on MT; the begin/commit/cancel methods set the transition
|
|
25
|
+
* state appropriately without driving their own auto-animation (the
|
|
26
|
+
* gesture worklet is in charge of that).
|
|
27
|
+
*/
|
|
28
|
+
readonly _gesture: {
|
|
29
|
+
beginBackGesture(): void;
|
|
30
|
+
commitBackGesture(): void;
|
|
31
|
+
cancelBackGesture(): void;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Internal: cross-entry `<Screen>` registry lookup.
|
|
35
|
+
*
|
|
36
|
+
* Each `<EntryScope>` registers its `ScreenRegistry` here on mount and
|
|
37
|
+
* removes it on unmount. The navigator's persistent chrome (HeaderBar /
|
|
38
|
+
* TabBar, shipped in later slices) calls `getScreenRegistry(entry.key)`
|
|
39
|
+
* to read the currently-focused screen's options/slot fills without
|
|
40
|
+
* being itself remounted on each navigation.
|
|
41
|
+
*
|
|
42
|
+
* Returns `undefined` when no screen for that key has mounted yet (or
|
|
43
|
+
* after it has unmounted) — consumers must tolerate this and render
|
|
44
|
+
* defaults.
|
|
45
|
+
*/
|
|
46
|
+
readonly _screens: {
|
|
47
|
+
register(registry: ScreenRegistry): void;
|
|
48
|
+
/** Identity-checked: no-op when a newer registry has taken the slot. */
|
|
49
|
+
unregister(registry: ScreenRegistry): void;
|
|
50
|
+
get(entryKey: string): ScreenRegistry | undefined;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Internal: set `nav.isLocallyFocused` from outside.
|
|
54
|
+
*
|
|
55
|
+
* `<Stack>` calls this when its host entry's locally-focused state
|
|
56
|
+
* changes (top of parent + parent focused + enclosing tab active). For
|
|
57
|
+
* the root nav this stays `true` for the lifetime of the navigator.
|
|
58
|
+
*/
|
|
59
|
+
readonly _setLocallyFocused: (focused: boolean) => void;
|
|
60
|
+
}
|
|
61
|
+
export interface CreateNavigatorOptions {
|
|
62
|
+
routes: RouteMap;
|
|
63
|
+
initial: StackEntry;
|
|
64
|
+
/**
|
|
65
|
+
* SharedValue driving push/pop transition progress. Created in
|
|
66
|
+
* `<NavigationRoot>` setup via `useSharedValue(0)` so the bridge
|
|
67
|
+
* plumbing is wired (SharedValue is an MT-bridged ref). When undefined,
|
|
68
|
+
* navigations are instant — used by tests against `@sigx/lynx-testing`
|
|
69
|
+
* that don't have an MT runtime.
|
|
70
|
+
*/
|
|
71
|
+
progress?: SharedValue<number>;
|
|
72
|
+
/**
|
|
73
|
+
* Parent navigator. Set when this navigator is nested under another
|
|
74
|
+
* (e.g. a per-tab `<Stack initialRoute>` under root). Drives the
|
|
75
|
+
* `nav.parent` getter and the modal-escalation behaviour of `push`:
|
|
76
|
+
* a push of a route whose resolved presentation is not `'card'`
|
|
77
|
+
* recurses via `parent.push(...)`, walking up the chain until it
|
|
78
|
+
* lands on a navigator with no parent (the root).
|
|
79
|
+
*
|
|
80
|
+
* Leave undefined for the root navigator.
|
|
81
|
+
*/
|
|
82
|
+
parent?: Nav | null;
|
|
83
|
+
/**
|
|
84
|
+
* Whether this navigator is considered "locally focused" at creation
|
|
85
|
+
* time. Defaults to true for the root nav; nested stacks pass `false`
|
|
86
|
+
* here and then flip the flag via `_setLocallyFocused` once their
|
|
87
|
+
* host-entry/tab-active state is computed.
|
|
88
|
+
*/
|
|
89
|
+
initialLocallyFocused?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a navigator. Returns the public `nav` handle plus the routes map.
|
|
93
|
+
* The transition signal lives on `nav` (via `nav.transition`) so `<Stack>`
|
|
94
|
+
* can subscribe to it.
|
|
95
|
+
*/
|
|
96
|
+
export declare function createNavigatorState(opts: CreateNavigatorOptions): NavigatorState;
|