@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.
Files changed (193) hide show
  1. package/README.md +128 -8
  2. package/dist/components/EntryScope.d.ts +1 -1
  3. package/dist/components/EntryScope.d.ts.map +1 -1
  4. package/dist/components/Layer.d.ts +34 -0
  5. package/dist/components/Layer.d.ts.map +1 -0
  6. package/dist/components/Link.d.ts +2 -2
  7. package/dist/components/Link.d.ts.map +1 -1
  8. package/dist/components/NavigationRoot.d.ts +2 -2
  9. package/dist/components/NavigationRoot.d.ts.map +1 -1
  10. package/dist/components/Screen.d.ts +6 -6
  11. package/dist/components/Screen.d.ts.map +1 -1
  12. package/dist/components/Stack.d.ts +41 -16
  13. package/dist/components/Stack.d.ts.map +1 -1
  14. package/dist/components/TabBar.d.ts +19 -20
  15. package/dist/components/TabBar.d.ts.map +1 -1
  16. package/dist/components/Tabs.d.ts.map +1 -1
  17. package/dist/define-routes.d.ts +1 -1
  18. package/dist/define-routes.d.ts.map +1 -1
  19. package/dist/hooks/use-linking-nav.d.ts +3 -3
  20. package/dist/hooks/use-linking-nav.d.ts.map +1 -1
  21. package/dist/hooks/use-nav-internal.d.ts +21 -3
  22. package/dist/hooks/use-nav-internal.d.ts.map +1 -1
  23. package/dist/hooks/use-nav-serializer.d.ts +1 -1
  24. package/dist/hooks/use-nav-serializer.d.ts.map +1 -1
  25. package/dist/hooks/use-nav.d.ts +2 -2
  26. package/dist/hooks/use-nav.d.ts.map +1 -1
  27. package/dist/hooks/use-params.d.ts +1 -1
  28. package/dist/hooks/use-params.d.ts.map +1 -1
  29. package/dist/hooks/use-screen-chrome.d.ts +19 -0
  30. package/dist/hooks/use-screen-chrome.d.ts.map +1 -0
  31. package/dist/hooks/use-screen-options.d.ts +1 -1
  32. package/dist/hooks/use-screen-options.d.ts.map +1 -1
  33. package/dist/hooks/use-search.d.ts +1 -1
  34. package/dist/hooks/use-search.d.ts.map +1 -1
  35. package/dist/href.d.ts +2 -2
  36. package/dist/href.d.ts.map +1 -1
  37. package/dist/index.d.ts +33 -31
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1160 -29
  40. package/dist/index.js.map +1 -1
  41. package/dist/internal/layer-plan.d.ts +69 -0
  42. package/dist/internal/layer-plan.d.ts.map +1 -0
  43. package/dist/internal/screen-registry.d.ts +1 -1
  44. package/dist/internal/screen-registry.d.ts.map +1 -1
  45. package/dist/internal/screen-width.d.ts +9 -7
  46. package/dist/internal/screen-width.d.ts.map +1 -1
  47. package/dist/navigator/core.d.ts +5 -4
  48. package/dist/navigator/core.d.ts.map +1 -1
  49. package/dist/register.d.ts +1 -1
  50. package/dist/register.d.ts.map +1 -1
  51. package/dist/url/index.d.ts +6 -6
  52. package/dist/url/index.d.ts.map +1 -1
  53. package/dist/url/parse.d.ts +1 -1
  54. package/dist/url/parse.d.ts.map +1 -1
  55. package/dist/url/registry.d.ts +2 -2
  56. package/dist/url/registry.d.ts.map +1 -1
  57. package/dist/url/validate.d.ts +1 -1
  58. package/dist/url/validate.d.ts.map +1 -1
  59. package/package.json +11 -10
  60. package/src/components/Drawer.d.ts +55 -0
  61. package/src/components/EdgeBackHandle.d.ts +1 -0
  62. package/src/components/EdgeBackHandle.tsx +2 -2
  63. package/{dist/components/EntryScope.js → src/components/EntryScope.d.ts} +7 -15
  64. package/src/components/EntryScope.tsx +15 -4
  65. package/src/components/Header.d.ts +6 -0
  66. package/src/components/Header.tsx +3 -3
  67. package/src/components/Layer.d.ts +33 -0
  68. package/src/components/Layer.tsx +96 -0
  69. package/src/components/Link.d.ts +60 -0
  70. package/src/components/Link.tsx +4 -4
  71. package/src/components/NavigationRoot.d.ts +36 -0
  72. package/src/components/NavigationRoot.tsx +6 -6
  73. package/src/components/Screen.d.ts +97 -0
  74. package/src/components/Screen.tsx +13 -11
  75. package/src/components/Stack.d.ts +90 -0
  76. package/src/components/Stack.tsx +142 -98
  77. package/src/components/TabBar.d.ts +38 -0
  78. package/src/components/TabBar.tsx +22 -22
  79. package/src/components/Tabs.d.ts +109 -0
  80. package/src/components/Tabs.tsx +15 -1
  81. package/{dist/define-routes.js → src/define-routes.d.ts} +2 -4
  82. package/src/define-routes.ts +1 -1
  83. package/src/hooks/use-focus.d.ts +45 -0
  84. package/src/hooks/use-focus.ts +2 -2
  85. package/src/hooks/use-hardware-back.d.ts +37 -0
  86. package/src/hooks/use-hardware-back.ts +1 -1
  87. package/src/hooks/use-linking-nav.d.ts +91 -0
  88. package/src/hooks/use-linking-nav.ts +4 -4
  89. package/src/hooks/use-nav-internal.d.ts +91 -0
  90. package/src/hooks/use-nav-internal.ts +24 -3
  91. package/src/hooks/use-nav-serializer.d.ts +82 -0
  92. package/src/hooks/use-nav-serializer.ts +3 -3
  93. package/src/hooks/use-nav.d.ts +111 -0
  94. package/src/hooks/use-nav.ts +2 -2
  95. package/{dist/hooks/use-params.js → src/hooks/use-params.d.ts} +2 -6
  96. package/src/hooks/use-params.ts +2 -2
  97. package/src/hooks/use-screen-chrome.d.ts +18 -0
  98. package/src/hooks/use-screen-chrome.ts +122 -0
  99. package/src/hooks/use-screen-options.d.ts +2 -0
  100. package/src/hooks/use-screen-options.ts +3 -3
  101. package/{dist/hooks/use-search.js → src/hooks/use-search.d.ts} +2 -6
  102. package/src/hooks/use-search.ts +2 -2
  103. package/src/href.d.ts +54 -0
  104. package/src/href.ts +6 -6
  105. package/src/index.d.ts +39 -0
  106. package/src/index.ts +33 -31
  107. package/src/internal/layer-plan.d.ts +68 -0
  108. package/src/internal/layer-plan.ts +187 -0
  109. package/{dist/internal/screen-registry.js → src/internal/screen-registry.d.ts} +21 -32
  110. package/src/internal/screen-registry.ts +1 -1
  111. package/src/internal/screen-width.d.ts +17 -0
  112. package/src/internal/screen-width.ts +22 -14
  113. package/src/navigator/core.d.ts +96 -0
  114. package/src/navigator/core.ts +17 -6
  115. package/src/register.d.ts +37 -0
  116. package/src/register.ts +1 -1
  117. package/src/types.d.ts +217 -0
  118. package/src/url/build.d.ts +15 -0
  119. package/src/url/build.ts +2 -2
  120. package/src/url/compile.d.ts +34 -0
  121. package/src/url/format.d.ts +28 -0
  122. package/src/url/index.ts +6 -6
  123. package/src/url/parse.d.ts +20 -0
  124. package/src/url/parse.ts +6 -6
  125. package/{dist/url/registry.js → src/url/registry.d.ts} +12 -28
  126. package/src/url/registry.ts +3 -3
  127. package/src/url/validate.d.ts +23 -0
  128. package/src/url/validate.ts +1 -1
  129. package/dist/components/Drawer.js +0 -74
  130. package/dist/components/Drawer.js.map +0 -1
  131. package/dist/components/EdgeBackHandle.js +0 -144
  132. package/dist/components/EdgeBackHandle.js.map +0 -1
  133. package/dist/components/EntryScope.js.map +0 -1
  134. package/dist/components/Header.js +0 -103
  135. package/dist/components/Header.js.map +0 -1
  136. package/dist/components/Link.js +0 -51
  137. package/dist/components/Link.js.map +0 -1
  138. package/dist/components/NavigationRoot.js +0 -67
  139. package/dist/components/NavigationRoot.js.map +0 -1
  140. package/dist/components/Screen.js +0 -94
  141. package/dist/components/Screen.js.map +0 -1
  142. package/dist/components/ScreenContainer.d.ts +0 -18
  143. package/dist/components/ScreenContainer.d.ts.map +0 -1
  144. package/dist/components/ScreenContainer.js +0 -77
  145. package/dist/components/ScreenContainer.js.map +0 -1
  146. package/dist/components/Stack.js +0 -221
  147. package/dist/components/Stack.js.map +0 -1
  148. package/dist/components/TabBar.js +0 -63
  149. package/dist/components/TabBar.js.map +0 -1
  150. package/dist/components/Tabs.js +0 -154
  151. package/dist/components/Tabs.js.map +0 -1
  152. package/dist/define-routes.js.map +0 -1
  153. package/dist/hooks/use-focus.js +0 -87
  154. package/dist/hooks/use-focus.js.map +0 -1
  155. package/dist/hooks/use-hardware-back.js +0 -84
  156. package/dist/hooks/use-hardware-back.js.map +0 -1
  157. package/dist/hooks/use-linking-nav.js +0 -109
  158. package/dist/hooks/use-linking-nav.js.map +0 -1
  159. package/dist/hooks/use-nav-internal.js +0 -44
  160. package/dist/hooks/use-nav-internal.js.map +0 -1
  161. package/dist/hooks/use-nav-serializer.js +0 -181
  162. package/dist/hooks/use-nav-serializer.js.map +0 -1
  163. package/dist/hooks/use-nav.js +0 -11
  164. package/dist/hooks/use-nav.js.map +0 -1
  165. package/dist/hooks/use-params.js.map +0 -1
  166. package/dist/hooks/use-screen-options.js +0 -43
  167. package/dist/hooks/use-screen-options.js.map +0 -1
  168. package/dist/hooks/use-search.js.map +0 -1
  169. package/dist/href.js +0 -57
  170. package/dist/href.js.map +0 -1
  171. package/dist/internal/screen-registry.js.map +0 -1
  172. package/dist/internal/screen-width.js +0 -30
  173. package/dist/internal/screen-width.js.map +0 -1
  174. package/dist/navigator/core.js +0 -383
  175. package/dist/navigator/core.js.map +0 -1
  176. package/dist/register.js +0 -2
  177. package/dist/register.js.map +0 -1
  178. package/dist/types.js +0 -9
  179. package/dist/types.js.map +0 -1
  180. package/dist/url/build.js +0 -30
  181. package/dist/url/build.js.map +0 -1
  182. package/dist/url/compile.js +0 -83
  183. package/dist/url/compile.js.map +0 -1
  184. package/dist/url/format.js +0 -102
  185. package/dist/url/format.js.map +0 -1
  186. package/dist/url/index.js +0 -13
  187. package/dist/url/index.js.map +0 -1
  188. package/dist/url/parse.js +0 -94
  189. package/dist/url/parse.js.map +0 -1
  190. package/dist/url/registry.js.map +0 -1
  191. package/dist/url/validate.js +0 -37
  192. package/dist/url/validate.js.map +0 -1
  193. 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.js';
8
- import { getCompiledPath, getRouteRegistry } from './registry.js';
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.js';
9
- export { buildUrl } from './build.js';
10
- export { parseHrefImpl } from './parse.js';
11
- export { formatSearch, parseSearch } from './format.js';
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.js';
18
- export { validateSync, type ValidateOutcome } from './validate.js';
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.js';
14
- import type { RouteId } from '../register.js';
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.js';
17
- import { parseSearch } from './format.js';
18
- import { getCompiledPath, getRouteRegistry } from './registry.js';
19
- import { validateSync } from './validate.js';
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 { compilePath } from './compile.js';
15
- let current = null;
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
- const def = registry.routes[name];
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 {};
@@ -12,9 +12,9 @@
12
12
  * public API (test/integration use only).
13
13
  */
14
14
 
15
- import type { CompiledPath } from './compile.js';
16
- import { compilePath } from './compile.js';
17
- import type { RouteMap } from '../types.js';
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;
@@ -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.js';
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"}