@sigx/lynx-navigation 0.2.0 → 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 +128 -8
- 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 +41 -16
- 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.map +1 -1
- package/dist/define-routes.d.ts +1 -1
- package/dist/define-routes.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 +2 -2
- 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 +5 -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 +142 -98
- 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 +15 -1
- package/{dist/define-routes.js → src/define-routes.d.ts} +2 -4
- package/src/define-routes.ts +1 -1
- package/src/hooks/use-focus.d.ts +45 -0
- package/src/hooks/use-focus.ts +2 -2
- package/src/hooks/use-hardware-back.d.ts +37 -0
- package/src/hooks/use-hardware-back.ts +1 -1
- 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 +2 -2
- 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 +17 -6
- 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 -221
- 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 -154
- package/dist/components/Tabs.js.map +0 -1
- package/dist/define-routes.js.map +0 -1
- package/dist/hooks/use-focus.js +0 -87
- package/dist/hooks/use-focus.js.map +0 -1
- package/dist/hooks/use-hardware-back.js +0 -84
- 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 -383
- 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/types.d.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for @sigx/lynx-navigation.
|
|
3
|
+
*
|
|
4
|
+
* The type machinery here is the differentiating DX: route names, params, and
|
|
5
|
+
* search are all inferred end-to-end from the user's `defineRoutes` call so
|
|
6
|
+
* `nav.push('profile', { id: 42 })` is a TS error if `id` is typed as string.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Minimal Standard Schema spec subset — see https://standardschema.dev.
|
|
10
|
+
* Inlined so we don't depend on `@standard-schema/spec` for the type spike.
|
|
11
|
+
* Compatible with Zod, Valibot, ArkType, etc.
|
|
12
|
+
*/
|
|
13
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
14
|
+
readonly '~standard': {
|
|
15
|
+
readonly version: 1;
|
|
16
|
+
readonly vendor: string;
|
|
17
|
+
readonly types?: {
|
|
18
|
+
readonly input: Input;
|
|
19
|
+
readonly output: Output;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Infer the validated output type of a Standard Schema, falling back to
|
|
25
|
+
* `unknown` for non-schema values.
|
|
26
|
+
*/
|
|
27
|
+
export type InferOutput<S> = S extends StandardSchemaV1<unknown, infer O> ? O : unknown;
|
|
28
|
+
/** Empty record — what `ParamsOf` returns when a route declares no schema. */
|
|
29
|
+
export type EmptyParams = Record<string, never>;
|
|
30
|
+
/**
|
|
31
|
+
* How a route entry is presented on the stack.
|
|
32
|
+
* `card` is the default push; `modal`/`fullScreen` slide up; `transparent-modal`
|
|
33
|
+
* preserves the underlying screen visible (e.g. for popovers).
|
|
34
|
+
*/
|
|
35
|
+
export type Presentation = 'card' | 'modal' | 'fullScreen' | 'transparent-modal';
|
|
36
|
+
/**
|
|
37
|
+
* A route definition entry.
|
|
38
|
+
*
|
|
39
|
+
* Users construct this via `defineRoutes({...})`. The `params` and `search`
|
|
40
|
+
* schemas drive runtime validation AND TS inference for `useParams`,
|
|
41
|
+
* `useSearch`, `nav.push`, `<Link>`, etc.
|
|
42
|
+
*
|
|
43
|
+
* `component` accepts an eager component factory or a lazy import — both shapes
|
|
44
|
+
* resolve through sigx's `<Suspense>` boundary at render time.
|
|
45
|
+
*/
|
|
46
|
+
export interface RouteDefinition<Params extends StandardSchemaV1 | undefined = StandardSchemaV1 | undefined, Search extends StandardSchemaV1 | undefined = StandardSchemaV1 | undefined> {
|
|
47
|
+
/** Component factory or lazy importer. */
|
|
48
|
+
component: ComponentLike;
|
|
49
|
+
/**
|
|
50
|
+
* Fallback shown while a lazy `component` is loading.
|
|
51
|
+
*
|
|
52
|
+
* Set this only on routes whose `component` was created with `lazy(...)`.
|
|
53
|
+
* The fallback is rendered inside a `<Suspense>` boundary wrapping the
|
|
54
|
+
* screen mount, so the user sees this UI while the screen's chunk is
|
|
55
|
+
* being fetched. When omitted, lazy routes still work — the caller is
|
|
56
|
+
* responsible for placing its own `<Suspense>` boundary (e.g. above the
|
|
57
|
+
* `<NavigationRoot>` or inside the screen component).
|
|
58
|
+
*
|
|
59
|
+
* Accepts a component factory (`MyLoadingScreen`) or a function returning
|
|
60
|
+
* JSX (`() => <Spinner />`). Eager routes ignore this field.
|
|
61
|
+
*/
|
|
62
|
+
fallback?: ComponentLike | (() => unknown);
|
|
63
|
+
/** Standard-Schema validator for path params. Optional. */
|
|
64
|
+
params?: Params;
|
|
65
|
+
/** Standard-Schema validator for query/search params. Optional. */
|
|
66
|
+
search?: Search;
|
|
67
|
+
/** Optional URL pattern for deep-link serialization (e.g. `/users/:id`). */
|
|
68
|
+
path?: string;
|
|
69
|
+
/** Default presentation when this route is pushed. */
|
|
70
|
+
presentation?: Presentation;
|
|
71
|
+
/** Nested routes — share the URL/path namespace and may inherit options. */
|
|
72
|
+
children?: Record<string, RouteDefinition>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The component shape we accept on a route. Kept structural so we don't pull
|
|
76
|
+
* `ComponentFactory` from sigx at type level (avoids a hard dep on sigx purely
|
|
77
|
+
* for types in the spike). Refined to a real ComponentFactory in Phase 0.1
|
|
78
|
+
* runtime work.
|
|
79
|
+
*/
|
|
80
|
+
export type ComponentLike = ((...args: any[]) => unknown) | (() => Promise<{
|
|
81
|
+
default: (...args: any[]) => unknown;
|
|
82
|
+
}>);
|
|
83
|
+
/**
|
|
84
|
+
* Map of route definitions, as returned by `defineRoutes`. Keys are route
|
|
85
|
+
* names; values are typed RouteDefinitions.
|
|
86
|
+
*/
|
|
87
|
+
export type RouteMap = Record<string, RouteDefinition>;
|
|
88
|
+
/**
|
|
89
|
+
* Extract params type from a single RouteDefinition.
|
|
90
|
+
* Falls back to `EmptyParams` when the route declares no schema.
|
|
91
|
+
*
|
|
92
|
+
* We use a structural `params: infer S` match (without an `extends
|
|
93
|
+
* StandardSchemaV1` constraint on `S`) because TS conditional types treat the
|
|
94
|
+
* generic-defaulted `StandardSchemaV1<unknown, unknown>` as invariant in this
|
|
95
|
+
* position — a schema typed `StandardSchemaV1<{id:string}>` does not match
|
|
96
|
+
* `extends StandardSchemaV1` reliably under `<const T>` inference. `InferOutput`
|
|
97
|
+
* gracefully handles non-schema `S` by returning `unknown`.
|
|
98
|
+
*/
|
|
99
|
+
export type ParamsOf<R> = R extends {
|
|
100
|
+
params: infer S;
|
|
101
|
+
} ? InferOutput<S> : EmptyParams;
|
|
102
|
+
/**
|
|
103
|
+
* Extract search type from a single RouteDefinition.
|
|
104
|
+
* Falls back to `EmptyParams` when the route declares no schema.
|
|
105
|
+
*/
|
|
106
|
+
export type SearchOf<R> = R extends {
|
|
107
|
+
search: infer S;
|
|
108
|
+
} ? InferOutput<S> : EmptyParams;
|
|
109
|
+
/**
|
|
110
|
+
* Whether a route requires a `params` argument when calling `nav.push` etc.
|
|
111
|
+
* True iff the route definition has a `params` field.
|
|
112
|
+
*/
|
|
113
|
+
export type RouteRequiresParams<R> = R extends {
|
|
114
|
+
params: object;
|
|
115
|
+
} ? true : false;
|
|
116
|
+
/**
|
|
117
|
+
* Per-entry state stored on the stack signal.
|
|
118
|
+
*
|
|
119
|
+
* `key` is unique per entry — needed because the same route can appear more
|
|
120
|
+
* than once (e.g. profile A → message → profile A again). Focus state and
|
|
121
|
+
* scroll position are keyed by `key`, not by route name.
|
|
122
|
+
*/
|
|
123
|
+
export interface StackEntry<R extends string = string, P = unknown, S = unknown> {
|
|
124
|
+
readonly key: string;
|
|
125
|
+
readonly route: R;
|
|
126
|
+
readonly params: P;
|
|
127
|
+
readonly search: S;
|
|
128
|
+
/** User state — survives suspend/restore. */
|
|
129
|
+
state: unknown;
|
|
130
|
+
readonly presentation: Presentation;
|
|
131
|
+
}
|
|
132
|
+
/** Options accepted by `nav.push` / `nav.replace`. */
|
|
133
|
+
export interface PushOptions {
|
|
134
|
+
/** Override the route's default presentation for this navigation. */
|
|
135
|
+
presentation?: Presentation;
|
|
136
|
+
/** User state to attach to the new entry. Survives suspend/restore. */
|
|
137
|
+
state?: unknown;
|
|
138
|
+
/**
|
|
139
|
+
* Skip the slide animation (instant swap). Defaults to true on platforms
|
|
140
|
+
* where `useAnimatedStyle` isn't available (test renderer); defaults to
|
|
141
|
+
* false on real Lynx. Tests can force `false` to keep assertions
|
|
142
|
+
* deterministic.
|
|
143
|
+
*/
|
|
144
|
+
animated?: boolean;
|
|
145
|
+
}
|
|
146
|
+
/** Options accepted by `nav.pop`. */
|
|
147
|
+
export interface PopOptions {
|
|
148
|
+
/** Skip the slide animation (instant swap). See `PushOptions.animated`. */
|
|
149
|
+
animated?: boolean;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Direction of an in-flight transition.
|
|
153
|
+
* - `push`: a new entry is animating in (progress 0 → 1).
|
|
154
|
+
* - `pop`: the current top is animating out (progress 0 → 1, then committed).
|
|
155
|
+
*/
|
|
156
|
+
export type TransitionKind = 'push' | 'pop';
|
|
157
|
+
/** Role of a screen during a transition — determines its transform formula. */
|
|
158
|
+
export type TransitionRole = 'top' | 'underneath';
|
|
159
|
+
/**
|
|
160
|
+
* Snapshot of an in-flight transition. Stored on the navigator state so the
|
|
161
|
+
* `<Stack>` component knows to render two entries (`topEntry` above
|
|
162
|
+
* `underneathEntry`) and bind their transforms to `progress`.
|
|
163
|
+
*
|
|
164
|
+
* `progress` is a `SharedValue<number>` (re-exported as `unknown` here to
|
|
165
|
+
* avoid a hard dep on `@sigx/lynx`'s SharedValue type at the contract level —
|
|
166
|
+
* the runtime `<Stack>` casts as needed). The value runs 0 → 1 in both push
|
|
167
|
+
* and pop, with the role/kind pair determining the visual direction.
|
|
168
|
+
*/
|
|
169
|
+
export interface TransitionState {
|
|
170
|
+
readonly kind: TransitionKind;
|
|
171
|
+
readonly topEntry: StackEntry;
|
|
172
|
+
readonly underneathEntry: StackEntry;
|
|
173
|
+
/** Animation progress signal — typed loosely; cast at the runtime boundary. */
|
|
174
|
+
readonly progress: unknown;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Per-screen display options written by `<Screen>` into its entry's registry.
|
|
178
|
+
*
|
|
179
|
+
* Read by persistent navigator chrome (the `<HeaderBar>` shipped in the
|
|
180
|
+
* `header` slice; `<TabBar>` later). All fields are optional — consumers
|
|
181
|
+
* apply sensible defaults (headerShown defaults to true, gestureEnabled to
|
|
182
|
+
* true, title falls back to the route name).
|
|
183
|
+
*
|
|
184
|
+
* `title` accepts a function so the header can be derived from reactive
|
|
185
|
+
* state (e.g. a user's display name signal). Plain strings are wrapped in
|
|
186
|
+
* a thunk by consumers when read.
|
|
187
|
+
*/
|
|
188
|
+
export interface ScreenOptions {
|
|
189
|
+
/** Header title. Either a static string or a getter (re-tracked each render). */
|
|
190
|
+
title?: string | (() => string);
|
|
191
|
+
/** When false, the navigator's header is hidden for this screen. Default true. */
|
|
192
|
+
headerShown?: boolean;
|
|
193
|
+
/** When false, the iOS edge-swipe-back gesture is disabled for this screen. Default true. */
|
|
194
|
+
gestureEnabled?: boolean;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Slot fills written by `<Screen.Header>` / `<Screen.HeaderLeft>` /
|
|
198
|
+
* `<Screen.HeaderRight>` / `<Screen.TabBarItem>`.
|
|
199
|
+
*
|
|
200
|
+
* Each fill is the rendered output of that sub-component's `default` slot,
|
|
201
|
+
* captured as a thunk so the navigator's persistent chrome can call it at
|
|
202
|
+
* render time. `tabBarItem` is a scoped slot — the consumer passes
|
|
203
|
+
* `{ active }` so the same screen's tab-bar item can style itself
|
|
204
|
+
* differently when focused vs. not.
|
|
205
|
+
*/
|
|
206
|
+
export interface ScreenSlotFills {
|
|
207
|
+
/** Full header replacement. When set, takes precedence over title + headerLeft/Right. */
|
|
208
|
+
header?: () => unknown;
|
|
209
|
+
/** Left-side header content (typically back arrow override). */
|
|
210
|
+
headerLeft?: () => unknown;
|
|
211
|
+
/** Right-side header content (typically action buttons). */
|
|
212
|
+
headerRight?: () => unknown;
|
|
213
|
+
/** Tab-bar item — scoped slot receives `{ active }` indicating focus. */
|
|
214
|
+
tabBarItem?: (ctx: {
|
|
215
|
+
active: boolean;
|
|
216
|
+
}) => unknown;
|
|
217
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed → URL: build the `url` field of an Href from a route's params/search.
|
|
3
|
+
*
|
|
4
|
+
* Mirror of parse.ts. Used by `hrefFor()` after schema validation succeeds.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Build the URL form of a route + params + search, or `null` if the route
|
|
8
|
+
* declares no `path` template (typed navigation still works — only deep-link
|
|
9
|
+
* serialization is unavailable).
|
|
10
|
+
*
|
|
11
|
+
* Params must already be valid for the route's schema (callers run this after
|
|
12
|
+
* `validateSync`). Search values are stringified as-is — schema-validated
|
|
13
|
+
* inputs survive round-tripping because `parseHref` re-runs the same schema.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildUrl(routeName: string, params: Record<string, unknown> | undefined, search: Record<string, unknown> | undefined): string | null;
|
package/src/url/build.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Mirror of parse.ts. Used by `hrefFor()` after schema validation succeeds.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { formatSearch } from './format
|
|
8
|
-
import { getCompiledPath, getRouteRegistry } from './registry
|
|
7
|
+
import { formatSearch } from './format';
|
|
8
|
+
import { getCompiledPath, getRouteRegistry } from './registry';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Build the URL form of a route + params + search, or `null` if the route
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path template compiler.
|
|
3
|
+
*
|
|
4
|
+
* Turns a route's `path` (e.g. `/users/:id/posts/:postId`) into a compiled
|
|
5
|
+
* object that can both match a URL pathname against it and format a typed
|
|
6
|
+
* params object back into a URL.
|
|
7
|
+
*
|
|
8
|
+
* Supported syntax (intentionally minimal for v1):
|
|
9
|
+
* - Literal segments: `/users`, `/users/me`
|
|
10
|
+
* - Named params: `:id` (matches `[^/]+`)
|
|
11
|
+
* - Trailing slashes tolerated on match
|
|
12
|
+
*
|
|
13
|
+
* Out of scope for v1 (future-compatible — additions won't break v1 paths):
|
|
14
|
+
* - Wildcards `*`
|
|
15
|
+
* - Optional params `:id?`
|
|
16
|
+
* - Typed/constrained params `:id<number>` or `:id(\\d+)`
|
|
17
|
+
*/
|
|
18
|
+
/** Result of compiling a path template — used by parse + format. */
|
|
19
|
+
export interface CompiledPath {
|
|
20
|
+
readonly source: string;
|
|
21
|
+
readonly paramNames: readonly string[];
|
|
22
|
+
/** Regex that matches a URL pathname. Captures are param values in order. */
|
|
23
|
+
readonly regex: RegExp;
|
|
24
|
+
/**
|
|
25
|
+
* Render a URL pathname for this template given param values. Each value
|
|
26
|
+
* is `encodeURIComponent`-encoded. Throws if a required `:name` is missing.
|
|
27
|
+
*/
|
|
28
|
+
format(params: Record<string, string | number>): string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compile a path template. Throws on malformed input (duplicate param names,
|
|
32
|
+
* unexpected `:` syntax). Pure — safe to memoize.
|
|
33
|
+
*/
|
|
34
|
+
export declare function compilePath(template: string): CompiledPath;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format helpers: typed params/search → URL string.
|
|
3
|
+
*
|
|
4
|
+
* Used by `hrefFor()` to render the `url` field of an Href.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Serialize an object as a `key=value&key=value` querystring.
|
|
8
|
+
*
|
|
9
|
+
* Keys are sorted to make the output deterministic (useful for tests and
|
|
10
|
+
* persistence diffs). `undefined`/`null` values are skipped. Non-primitive
|
|
11
|
+
* values are JSON-stringified on the way out — `parseSearch` returns the
|
|
12
|
+
* raw string and leaves any JSON decoding to the route's `search` schema
|
|
13
|
+
* (e.g. a Zod `transform`), so the round-trip is intentionally one-way at
|
|
14
|
+
* this layer.
|
|
15
|
+
*
|
|
16
|
+
* Returns `''` (empty string) when there are no entries — callers join with
|
|
17
|
+
* `?` only when the result is non-empty.
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatSearch(search: Record<string, unknown> | undefined): string;
|
|
20
|
+
/**
|
|
21
|
+
* Parse a `key=value&key=value` querystring into a string-keyed bag. Values
|
|
22
|
+
* are decoded but kept as strings — typed coercion happens in the route's
|
|
23
|
+
* `search` schema (e.g. Zod's `z.coerce.number()`).
|
|
24
|
+
*
|
|
25
|
+
* Multiple occurrences of the same key produce an array. Schemas that don't
|
|
26
|
+
* expect arrays will reject this — that's the right failure mode.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseSearch(query: string): Record<string, string | string[]>;
|
package/src/url/index.ts
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* `parseHref` in ../href.ts plus `_setRouteRegistry` for tests/bootstrap.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export { compilePath, type CompiledPath } from './compile
|
|
9
|
-
export { buildUrl } from './build
|
|
10
|
-
export { parseHrefImpl } from './parse
|
|
11
|
-
export { formatSearch, parseSearch } from './format
|
|
8
|
+
export { compilePath, type CompiledPath } from './compile';
|
|
9
|
+
export { buildUrl } from './build';
|
|
10
|
+
export { parseHrefImpl } from './parse';
|
|
11
|
+
export { formatSearch, parseSearch } from './format';
|
|
12
12
|
export {
|
|
13
13
|
_setRouteRegistry,
|
|
14
14
|
_clearRouteRegistry,
|
|
15
15
|
getRouteRegistry,
|
|
16
16
|
getCompiledPath,
|
|
17
|
-
} from './registry
|
|
18
|
-
export { validateSync, type ValidateOutcome } from './validate
|
|
17
|
+
} from './registry';
|
|
18
|
+
export { validateSync, type ValidateOutcome } from './validate';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL → typed Href parser.
|
|
3
|
+
*
|
|
4
|
+
* Walks every registered route with a `path`, tries to match its compiled
|
|
5
|
+
* regex against the URL's pathname, and on a hit validates the extracted
|
|
6
|
+
* params + search through the route's Standard Schema. First match wins;
|
|
7
|
+
* iteration order follows `Object.keys` of the registered routes map.
|
|
8
|
+
*
|
|
9
|
+
* Validation failures return `null` rather than throwing — deep-link handlers
|
|
10
|
+
* fall back to the initial route on a bad URL instead of crashing the app.
|
|
11
|
+
*/
|
|
12
|
+
import type { Href } from '../href';
|
|
13
|
+
/**
|
|
14
|
+
* Parse a URL string against the active route registry.
|
|
15
|
+
*
|
|
16
|
+
* Accepts both absolute URLs (`myapp://host/users/42?tab=about`) and
|
|
17
|
+
* pathname-only forms (`/users/42?tab=about`). Returns `null` if no route's
|
|
18
|
+
* `path` matches the URL or if schema validation rejects the extracted bits.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseHrefImpl(url: string): Href | null;
|
package/src/url/parse.ts
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* fall back to the initial route on a bad URL instead of crashing the app.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { Href } from '../href
|
|
14
|
-
import type { RouteId } from '../register
|
|
13
|
+
import type { Href } from '../href';
|
|
14
|
+
import type { RouteId } from '../register';
|
|
15
15
|
import { parse as parseUrl } from '@sigx/lynx-linking';
|
|
16
|
-
import type { CompiledPath } from './compile
|
|
17
|
-
import { parseSearch } from './format
|
|
18
|
-
import { getCompiledPath, getRouteRegistry } from './registry
|
|
19
|
-
import { validateSync } from './validate
|
|
16
|
+
import type { CompiledPath } from './compile';
|
|
17
|
+
import { parseSearch } from './format';
|
|
18
|
+
import { getCompiledPath, getRouteRegistry } from './registry';
|
|
19
|
+
import { validateSync } from './validate';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Parse a URL string against the active route registry.
|
|
@@ -11,8 +11,13 @@
|
|
|
11
11
|
* directly. The leading underscore is a convention: not part of the supported
|
|
12
12
|
* public API (test/integration use only).
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
15
|
-
|
|
14
|
+
import type { CompiledPath } from './compile';
|
|
15
|
+
import type { RouteMap } from '../types';
|
|
16
|
+
interface RegistryState {
|
|
17
|
+
readonly routes: RouteMap;
|
|
18
|
+
/** Lazy-compiled paths keyed by route name. */
|
|
19
|
+
readonly compiled: Map<string, CompiledPath>;
|
|
20
|
+
}
|
|
16
21
|
/**
|
|
17
22
|
* Set the active route registry. Called by `<NavigationRoot>` on setup and
|
|
18
23
|
* available to tests/bootstrap code as `_setRouteRegistry`.
|
|
@@ -22,35 +27,14 @@ let current = null;
|
|
|
22
27
|
* specific registry for a one-off call, pass it explicitly to the helper
|
|
23
28
|
* (parseHrefWithRoutes / hrefForWithRoutes — currently internal).
|
|
24
29
|
*/
|
|
25
|
-
export function _setRouteRegistry(routes)
|
|
26
|
-
current = { routes, compiled: new Map() };
|
|
27
|
-
}
|
|
30
|
+
export declare function _setRouteRegistry(routes: RouteMap): void;
|
|
28
31
|
/** Clear the registry. Mainly for tests that want to assert the unset path. */
|
|
29
|
-
export function _clearRouteRegistry()
|
|
30
|
-
current = null;
|
|
31
|
-
}
|
|
32
|
+
export declare function _clearRouteRegistry(): void;
|
|
32
33
|
/** Get the active registry or throw a friendly error if none is set. */
|
|
33
|
-
export function getRouteRegistry()
|
|
34
|
-
if (!current) {
|
|
35
|
-
throw new Error('[lynx-navigation] No route registry set — render a <NavigationRoot> first, or call _setRouteRegistry() for tests.');
|
|
36
|
-
}
|
|
37
|
-
return current;
|
|
38
|
-
}
|
|
34
|
+
export declare function getRouteRegistry(): RegistryState;
|
|
39
35
|
/**
|
|
40
36
|
* Look up (or lazily compile) the path template for a route name. Returns
|
|
41
37
|
* `null` when the route exists but declares no `path`.
|
|
42
38
|
*/
|
|
43
|
-
export function getCompiledPath(registry, name)
|
|
44
|
-
|
|
45
|
-
if (!def)
|
|
46
|
-
return null;
|
|
47
|
-
if (!def.path)
|
|
48
|
-
return null;
|
|
49
|
-
let compiled = registry.compiled.get(name);
|
|
50
|
-
if (!compiled) {
|
|
51
|
-
compiled = compilePath(def.path);
|
|
52
|
-
registry.compiled.set(name, compiled);
|
|
53
|
-
}
|
|
54
|
-
return compiled;
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=registry.js.map
|
|
39
|
+
export declare function getCompiledPath(registry: RegistryState, name: string): CompiledPath | null;
|
|
40
|
+
export {};
|
package/src/url/registry.ts
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* public API (test/integration use only).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type { CompiledPath } from './compile
|
|
16
|
-
import { compilePath } from './compile
|
|
17
|
-
import type { RouteMap } from '../types
|
|
15
|
+
import type { CompiledPath } from './compile';
|
|
16
|
+
import { compilePath } from './compile';
|
|
17
|
+
import type { RouteMap } from '../types';
|
|
18
18
|
|
|
19
19
|
interface RegistryState {
|
|
20
20
|
readonly routes: RouteMap;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard Schema validation helper (sync only).
|
|
3
|
+
*
|
|
4
|
+
* `hrefFor` and `parseHref` run on hot paths (link rendering, deep-link
|
|
5
|
+
* resolution) so we restrict to sync validators. Zod/Valibot/ArkType are all
|
|
6
|
+
* sync, which covers the common case. Async validators throw a clear error.
|
|
7
|
+
*/
|
|
8
|
+
import type { StandardSchemaV1 } from '../types';
|
|
9
|
+
/** Outcome of a sync validation call — discriminated for explicit handling. */
|
|
10
|
+
export type ValidateOutcome = {
|
|
11
|
+
readonly ok: true;
|
|
12
|
+
readonly value: unknown;
|
|
13
|
+
} | {
|
|
14
|
+
readonly ok: false;
|
|
15
|
+
readonly issues: ReadonlyArray<string>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Run a Standard Schema's `validate` synchronously. When the schema lacks a
|
|
19
|
+
* `validate` function (e.g. our test `fakeSchema`), passthrough — assume the
|
|
20
|
+
* input is already in the correct shape. This is a deliberate ergonomic
|
|
21
|
+
* choice so the type-spike fixtures stay terse.
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateSync(schema: StandardSchemaV1 | undefined, input: unknown): ValidateOutcome;
|
package/src/url/validate.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* sync, which covers the common case. Async validators throw a clear error.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { StandardSchemaV1 } from '../types
|
|
9
|
+
import type { StandardSchemaV1 } from '../types';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Extended runtime view of a Standard Schema — adds the `validate` function
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "@sigx/lynx/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* `<Drawer>` — minimal off-canvas drawer navigator.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
*
|
|
7
|
-
* ```tsx
|
|
8
|
-
* <NavigationRoot routes={routes}>
|
|
9
|
-
* <Drawer slots={{ sidebar: () => <view><text>Menu</text></view> }}>
|
|
10
|
-
* <Stack />
|
|
11
|
-
* </Drawer>
|
|
12
|
-
* </NavigationRoot>
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* `useDrawer()` from inside any descendant gives `{ isOpen, open(), close(),
|
|
16
|
-
* toggle() }`. The sidebar is laid out absolutely on the left and is
|
|
17
|
-
* visible whenever `isOpen` is true.
|
|
18
|
-
*
|
|
19
|
-
* Scope: this slice ships the state primitive + the bare-bones layout.
|
|
20
|
-
* Gesture-driven open (edge swipe from the left) and MTS slide-in are out
|
|
21
|
-
* of scope — the app shell can wrap its sidebar JSX in its own transition.
|
|
22
|
-
*
|
|
23
|
-
* Design note: the sidebar lives in a named slot (`sidebar`) rather than
|
|
24
|
-
* a render-prop or a `<Drawer.Sidebar>` child. Mixing
|
|
25
|
-
* "register-yourself-as-a-fill" children with the parent's own visible
|
|
26
|
-
* layout creates a feedback loop in sigx's reactive scope (the parent's
|
|
27
|
-
* render reads the fill, child's setup writes it, parent re-renders,
|
|
28
|
-
* child re-mounts, …). A scoped slot avoids that entirely and keeps the
|
|
29
|
-
* call site declarative.
|
|
30
|
-
*
|
|
31
|
-
* `default` slot is the main content (almost always a `<Stack>`).
|
|
32
|
-
*/
|
|
33
|
-
import { component, defineInjectable, defineProvide, signal, } from '@sigx/lynx';
|
|
34
|
-
/**
|
|
35
|
-
* Access the enclosing Drawer navigator. Throws when called outside
|
|
36
|
-
* `<Drawer>`.
|
|
37
|
-
*/
|
|
38
|
-
export const useDrawer = defineInjectable(() => {
|
|
39
|
-
throw new Error('[lynx-navigation] useDrawer() called outside of a <Drawer> component.');
|
|
40
|
-
});
|
|
41
|
-
export const Drawer = component(({ props, slots }) => {
|
|
42
|
-
// `isOpenSig` uses the `{value}` wrapper pattern — sigx's `signal()` of
|
|
43
|
-
// a primitive returns a proxy that requires `.value` reads; wrapping in
|
|
44
|
-
// an object makes the proxy carry a mutable boolean.
|
|
45
|
-
const isOpenSig = signal({
|
|
46
|
-
value: props.initialOpen === true,
|
|
47
|
-
});
|
|
48
|
-
const nav = {
|
|
49
|
-
get isOpen() {
|
|
50
|
-
return isOpenSig.value;
|
|
51
|
-
},
|
|
52
|
-
open() {
|
|
53
|
-
isOpenSig.value = true;
|
|
54
|
-
},
|
|
55
|
-
close() {
|
|
56
|
-
isOpenSig.value = false;
|
|
57
|
-
},
|
|
58
|
-
toggle() {
|
|
59
|
-
isOpenSig.value = !isOpenSig.value;
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
defineProvide(useDrawer, () => nav);
|
|
63
|
-
return () => {
|
|
64
|
-
const open = isOpenSig.value;
|
|
65
|
-
return (_jsxs("view", { style: { width: '100%', height: '100%' }, children: [_jsx("view", { style: { width: '100%', height: '100%' }, children: slots.default?.() }), _jsx("view", { style: {
|
|
66
|
-
position: 'absolute',
|
|
67
|
-
left: 0,
|
|
68
|
-
top: 0,
|
|
69
|
-
bottom: 0,
|
|
70
|
-
display: open ? 'flex' : 'none',
|
|
71
|
-
}, children: slots.sidebar?.() })] }));
|
|
72
|
-
};
|
|
73
|
-
});
|
|
74
|
-
//# sourceMappingURL=Drawer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.js","sourceRoot":"","sources":["../../src/components/Drawer.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EACH,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,MAAM,GAGT,MAAM,YAAY,CAAC;AAcpB;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,gBAAgB,CAAY,GAAG,EAAE;IACtD,MAAM,IAAI,KAAK,CACX,uEAAuE,CAC1E,CAAC;AACN,CAAC,CAAC,CAAC;AAOH,MAAM,CAAC,MAAM,MAAM,GAAG,SAAS,CAAc,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;IAC9D,wEAAwE;IACxE,wEAAwE;IACxE,qDAAqD;IACrD,MAAM,SAAS,GAA+B,MAAM,CAAC;QACjD,KAAK,EAAE,KAAK,CAAC,WAAW,KAAK,IAAI;KACpC,CAAC,CAAC;IAEH,MAAM,GAAG,GAAc;QACnB,IAAI,MAAM;YACN,OAAO,SAAS,CAAC,KAAK,CAAC;QAC3B,CAAC;QACD,IAAI;YACA,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC;QAC3B,CAAC;QACD,KAAK;YACD,SAAS,CAAC,KAAK,GAAG,KAAK,CAAC;QAC5B,CAAC;QACD,MAAM;YACF,SAAS,CAAC,KAAK,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC;QACvC,CAAC;KACJ,CAAC;IAEF,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;IAEpC,OAAO,GAAG,EAAE;QACR,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC;QAC7B,OAAO,CACH,gBAAM,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAE1C,eAAM,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YACzC,KAAK,CAAC,OAAO,EAAE,EAAE,GACf,EAKP,eACI,KAAK,EAAE;wBACH,QAAQ,EAAE,UAAU;wBACpB,IAAI,EAAE,CAAC;wBACP,GAAG,EAAE,CAAC;wBACN,MAAM,EAAE,CAAC;wBACT,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;qBAClC,YAEA,KAAK,CAAC,OAAO,EAAE,EAAE,GACf,IACJ,CACV,CAAC;IACN,CAAC,CAAC;AACN,CAAC,CAAC,CAAC"}
|