@sigx/lynx-navigation 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +355 -0
  3. package/dist/components/Drawer.d.ts +58 -0
  4. package/dist/components/Drawer.d.ts.map +1 -0
  5. package/dist/components/Drawer.js +76 -0
  6. package/dist/components/Drawer.js.map +1 -0
  7. package/dist/components/EdgeBackHandle.js +144 -0
  8. package/dist/components/EdgeBackHandle.js.map +1 -0
  9. package/dist/components/EntryScope.d.ts +26 -0
  10. package/dist/components/EntryScope.d.ts.map +1 -0
  11. package/dist/components/EntryScope.js +33 -0
  12. package/dist/components/EntryScope.js.map +1 -0
  13. package/dist/components/Header.d.ts +7 -0
  14. package/dist/components/Header.d.ts.map +1 -0
  15. package/dist/components/Header.js +103 -0
  16. package/dist/components/Header.js.map +1 -0
  17. package/dist/components/Link.js +1 -4
  18. package/dist/components/Link.js.map +1 -1
  19. package/dist/components/NavigationRoot.d.ts +1 -1
  20. package/dist/components/NavigationRoot.d.ts.map +1 -1
  21. package/dist/components/NavigationRoot.js +29 -3
  22. package/dist/components/NavigationRoot.js.map +1 -1
  23. package/dist/components/Screen.d.ts +98 -0
  24. package/dist/components/Screen.d.ts.map +1 -0
  25. package/dist/components/Screen.js +94 -0
  26. package/dist/components/Screen.js.map +1 -0
  27. package/dist/components/ScreenContainer.d.ts.map +1 -1
  28. package/dist/components/ScreenContainer.js +77 -0
  29. package/dist/components/ScreenContainer.js.map +1 -0
  30. package/dist/components/Stack.d.ts.map +1 -1
  31. package/dist/components/Stack.js +60 -24
  32. package/dist/components/Stack.js.map +1 -1
  33. package/dist/components/TabBar.d.ts +40 -0
  34. package/dist/components/TabBar.d.ts.map +1 -0
  35. package/dist/components/TabBar.js +63 -0
  36. package/dist/components/TabBar.js.map +1 -0
  37. package/dist/components/Tabs.d.ts +101 -0
  38. package/dist/components/Tabs.d.ts.map +1 -0
  39. package/dist/components/Tabs.js +135 -0
  40. package/dist/components/Tabs.js.map +1 -0
  41. package/dist/hooks/use-focus.d.ts +46 -0
  42. package/dist/hooks/use-focus.d.ts.map +1 -0
  43. package/dist/hooks/use-focus.js +77 -0
  44. package/dist/hooks/use-focus.js.map +1 -0
  45. package/dist/hooks/use-hardware-back.js +50 -0
  46. package/dist/hooks/use-hardware-back.js.map +1 -0
  47. package/dist/hooks/use-linking-nav.d.ts +92 -0
  48. package/dist/hooks/use-linking-nav.d.ts.map +1 -0
  49. package/dist/hooks/use-linking-nav.js +109 -0
  50. package/dist/hooks/use-linking-nav.js.map +1 -0
  51. package/dist/hooks/use-nav-internal.d.ts +38 -1
  52. package/dist/hooks/use-nav-internal.d.ts.map +1 -1
  53. package/dist/hooks/use-nav-internal.js +32 -0
  54. package/dist/hooks/use-nav-internal.js.map +1 -1
  55. package/dist/hooks/use-nav-serializer.d.ts +83 -0
  56. package/dist/hooks/use-nav-serializer.d.ts.map +1 -0
  57. package/dist/hooks/use-nav-serializer.js +181 -0
  58. package/dist/hooks/use-nav-serializer.js.map +1 -0
  59. package/dist/hooks/use-nav.js.map +1 -1
  60. package/dist/hooks/use-screen-options.d.ts +3 -0
  61. package/dist/hooks/use-screen-options.d.ts.map +1 -0
  62. package/dist/hooks/use-screen-options.js +43 -0
  63. package/dist/hooks/use-screen-options.js.map +1 -0
  64. package/dist/href.d.ts +16 -1
  65. package/dist/href.d.ts.map +1 -1
  66. package/dist/href.js +50 -7
  67. package/dist/href.js.map +1 -1
  68. package/dist/index.d.ts +18 -1
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +15 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/internal/screen-registry.d.ts +49 -0
  73. package/dist/internal/screen-registry.d.ts.map +1 -0
  74. package/dist/internal/screen-registry.js +59 -0
  75. package/dist/internal/screen-registry.js.map +1 -0
  76. package/dist/internal/screen-width.js +30 -0
  77. package/dist/internal/screen-width.js.map +1 -0
  78. package/dist/navigator/core.d.ts +20 -1
  79. package/dist/navigator/core.d.ts.map +1 -1
  80. package/dist/navigator/core.js +231 -36
  81. package/dist/navigator/core.js.map +1 -1
  82. package/dist/types.d.ts +56 -0
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/url/build.d.ts +16 -0
  85. package/dist/url/build.d.ts.map +1 -0
  86. package/dist/url/build.js +30 -0
  87. package/dist/url/build.js.map +1 -0
  88. package/dist/url/compile.d.ts +35 -0
  89. package/dist/url/compile.d.ts.map +1 -0
  90. package/dist/url/compile.js +83 -0
  91. package/dist/url/compile.js.map +1 -0
  92. package/dist/url/format.d.ts +26 -0
  93. package/dist/url/format.d.ts.map +1 -0
  94. package/dist/url/format.js +99 -0
  95. package/dist/url/format.js.map +1 -0
  96. package/dist/url/index.d.ts +13 -0
  97. package/dist/url/index.d.ts.map +1 -0
  98. package/dist/url/index.js +13 -0
  99. package/dist/url/index.js.map +1 -0
  100. package/dist/url/parse.d.ts +21 -0
  101. package/dist/url/parse.d.ts.map +1 -0
  102. package/dist/url/parse.js +90 -0
  103. package/dist/url/parse.js.map +1 -0
  104. package/dist/url/registry.d.ts +41 -0
  105. package/dist/url/registry.d.ts.map +1 -0
  106. package/dist/url/registry.js +56 -0
  107. package/dist/url/registry.js.map +1 -0
  108. package/dist/url/validate.d.ts +24 -0
  109. package/dist/url/validate.d.ts.map +1 -0
  110. package/dist/url/validate.js +37 -0
  111. package/dist/url/validate.js.map +1 -0
  112. package/package.json +44 -15
  113. package/src/components/Drawer.tsx +121 -0
  114. package/src/components/EdgeBackHandle.tsx +1 -1
  115. package/src/components/EntryScope.tsx +38 -0
  116. package/src/components/Header.tsx +124 -0
  117. package/src/components/NavigationRoot.tsx +9 -1
  118. package/src/components/Screen.tsx +116 -0
  119. package/src/components/ScreenContainer.tsx +14 -1
  120. package/src/components/Stack.tsx +21 -2
  121. package/src/components/TabBar.tsx +103 -0
  122. package/src/components/Tabs.tsx +212 -0
  123. package/src/hooks/use-focus.ts +77 -0
  124. package/src/hooks/use-linking-nav.ts +159 -0
  125. package/src/hooks/use-nav-internal.ts +48 -1
  126. package/src/hooks/use-nav-serializer.ts +239 -0
  127. package/src/hooks/use-screen-options.ts +48 -0
  128. package/src/href.ts +68 -11
  129. package/src/index.ts +29 -0
  130. package/src/internal/screen-registry.ts +89 -0
  131. package/src/navigator/core.ts +86 -4
  132. package/src/types.ts +56 -0
  133. package/src/url/build.ts +35 -0
  134. package/src/url/compile.ts +109 -0
  135. package/src/url/format.ts +92 -0
  136. package/src/url/index.ts +18 -0
  137. package/src/url/parse.ts +97 -0
  138. package/src/url/registry.ts +69 -0
  139. package/src/url/validate.ts +67 -0
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Andreas Ekdahl
3
+ Copyright (c) 2025-2026 Andreas Ekdahl
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # @sigx/lynx-navigation
2
+
3
+ Type-first native navigator for [SignalX](https://github.com/signalxjs) on
4
+ Lynx. Define routes once with `defineRoutes`, augment the `Register`
5
+ interface, and every navigator API — `useNav`, `useParams`, `useSearch`,
6
+ `<Link>`, `<Tabs.Screen>`, `<Drawer>` — picks up precise per-route
7
+ param/search inference.
8
+
9
+ The navigator ships native UI primitives (Stack, Tabs, Drawer, modal
10
+ presentation), focus hooks, deep-link integration, lazy routes, screen
11
+ options, and persistence — all reactive via sigx signals, all typed.
12
+
13
+ > **Status — 1.0 candidate.** Public surface is frozen; every export below
14
+ > is locked by the test suite in `__tests__/public-surface.test.ts`. The
15
+ > remaining work before flipping `private: false` is benchmarks against
16
+ > the legacy switch-based pattern and the publish flow.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pnpm add @sigx/lynx-navigation
22
+ ```
23
+
24
+ Peer-deps: `@sigx/lynx`, `@sigx/lynx-motion`. Optional but recommended:
25
+ [`@sigx/lynx-linking`](../lynx-linking) for deep-link wiring,
26
+ [`@sigx/lynx-storage`](../lynx-storage) for stack persistence.
27
+
28
+ ## Quick start
29
+
30
+ ```tsx
31
+ // src/routes.ts
32
+ import { defineRoutes } from '@sigx/lynx-navigation';
33
+ import { z } from 'zod';
34
+ import { Home } from './screens/Home';
35
+ import { Profile } from './screens/Profile';
36
+
37
+ export const routes = defineRoutes({
38
+ home: { component: Home },
39
+ profile: {
40
+ component: Profile,
41
+ params: z.object({ id: z.string() }),
42
+ path: '/users/:id',
43
+ },
44
+ });
45
+
46
+ declare module '@sigx/lynx-navigation' {
47
+ interface Register { routes: typeof routes }
48
+ }
49
+ ```
50
+
51
+ ```tsx
52
+ // src/App.tsx
53
+ import { NavigationRoot, Stack } from '@sigx/lynx-navigation';
54
+ import { routes } from './routes';
55
+
56
+ export const App = () => (
57
+ <NavigationRoot routes={routes} initialRoute="home">
58
+ <Stack />
59
+ </NavigationRoot>
60
+ );
61
+ ```
62
+
63
+ `<NavigationRoot>` creates a fresh navigator instance and provides it via
64
+ sigx's `defineProvide`, so multiple roots (or tests) get isolated state.
65
+ `<Stack>` renders the top entry, and during a push/pop the previous entry
66
+ too — driven by an MT-side `SharedValue` so per-frame interpolation never
67
+ crosses to BG.
68
+
69
+ ## API reference
70
+
71
+ ### `defineRoutes(routes)`
72
+
73
+ Locks in a typed route map. Each entry is a `RouteDefinition`:
74
+
75
+ ```ts
76
+ interface RouteDefinition<P = unknown, S = unknown> {
77
+ component: ComponentLike; // sigx component or lazy(...)
78
+ params?: StandardSchemaV1<P>; // zod / valibot / arktype / etc.
79
+ search?: StandardSchemaV1<S>;
80
+ path?: string; // for deep-link parsing
81
+ presentation?: Presentation; // 'card' (default) | 'modal' | 'fullScreen' | 'transparent-modal'
82
+ }
83
+ ```
84
+
85
+ Augment `Register.routes` once with the return value of `defineRoutes`
86
+ and every other API gets typed.
87
+
88
+ ### `<NavigationRoot>`
89
+
90
+ | Prop | Default | Notes |
91
+ |---|---|---|
92
+ | `routes` | required | Output of `defineRoutes(...)`. |
93
+ | `initialRoute` | first key | Starting route at the bottom of the stack. |
94
+ | `initialParams` / `initialSearch` | `{}` | Required when `initialRoute` declares a schema. |
95
+ | `animated` | `true` | Disable in tests so navigations commit synchronously. |
96
+ | `edgeSwipeEnabled` | `true` | iOS edge-swipe-back. |
97
+
98
+ ### `<Stack>`
99
+
100
+ Renders the topmost stack entry plus the entry beneath it during
101
+ transitions. No props — driven entirely by the navigator state.
102
+
103
+ ### `<Screen>`
104
+
105
+ Per-route slot container. Lets a screen declare its header / tab-bar item
106
+ JSX inline alongside its body:
107
+
108
+ ```tsx
109
+ const Profile = component(() => () => (
110
+ <Screen>
111
+ <Screen.Header>
112
+ <view><text>Custom header</text></view>
113
+ </Screen.Header>
114
+ <Screen.HeaderRight>
115
+ <text bindtap={save}>Save</text>
116
+ </Screen.HeaderRight>
117
+ <Screen.TabBarItem>
118
+ {({ active }) => <text style={{ opacity: active ? 1 : 0.6 }}>Me</text>}
119
+ </Screen.TabBarItem>
120
+
121
+ <view><text>profile body</text></view>
122
+ </Screen>
123
+ ));
124
+ ```
125
+
126
+ All sub-slots are optional. Anything not declared falls back to the
127
+ navigator's default chrome.
128
+
129
+ ### `<Header>`
130
+
131
+ Default navigator header. Reads the focused entry's `<Screen.Header>`
132
+ slot if set, otherwise renders `headerLeft | title | headerRight` with a
133
+ back button as the default `headerLeft` when `nav.canGoBack` is true.
134
+ Pulls `title` / `headerShown` / `gestureEnabled` from the focused
135
+ screen's `useScreenOptions(...)` registration.
136
+
137
+ ```tsx
138
+ <NavigationRoot routes={routes} initialRoute="home">
139
+ <Header />
140
+ <Stack />
141
+ </NavigationRoot>
142
+ ```
143
+
144
+ ### `<Tabs>` + `<Tabs.Screen>` + `<TabBar>`
145
+
146
+ Persistent tab navigator. Each tab keeps its own stack across switches.
147
+
148
+ ```tsx
149
+ <Tabs initialTab="home">
150
+ <Tabs.Screen name="home" component={HomeStack} label="Home" />
151
+ <Tabs.Screen name="profile" component={ProfileStack} label="Me"
152
+ accessibilityLabel="Profile tab" />
153
+ <TabBar />
154
+ </Tabs>
155
+ ```
156
+
157
+ `<TabBar>` is the default chrome — kebab-case `accessibility-*` props for
158
+ screen readers, opacity-based active marker, tap to switch. Pass
159
+ `renderTab={(info, ctx) => <view bindtap={ctx.onPress}>…</view>}` to
160
+ fully override per-item rendering.
161
+
162
+ `useTabs()` returns `{ active, setActive, tabs }` — reactive.
163
+
164
+ ### `<Drawer>`
165
+
166
+ Off-canvas sidebar navigator.
167
+
168
+ ```tsx
169
+ <Drawer sidebar={() => <view><text>Menu</text></view>}>
170
+ <Stack />
171
+ </Drawer>
172
+ ```
173
+
174
+ `useDrawer()` returns `{ isOpen, open(), close(), toggle() }`. The
175
+ sidebar is laid out absolutely on the left and toggled via `display`.
176
+ Gesture-driven open and slide-in animation are deferred to apps — wrap
177
+ your sidebar JSX in a motion component if you want it.
178
+
179
+ ### `useNav()`
180
+
181
+ ```ts
182
+ const nav = useNav();
183
+ // readonly + reactive
184
+ nav.current // top StackEntry
185
+ nav.stack // StackEntry[]
186
+ nav.canGoBack // boolean
187
+
188
+ // mutators
189
+ nav.push('profile', { id: 'alice' });
190
+ nav.push('profile', { id: 'alice' }, { tab: 'about' });
191
+ nav.replace('home');
192
+ nav.pop();
193
+ nav.pop(2);
194
+ nav.popTo('home');
195
+ nav.popToRoot();
196
+ nav.reset([{ name: 'home', params: {}, search: {} }]);
197
+ ```
198
+
199
+ Per-route overloaded — `params` is required iff the route declares a
200
+ `params` schema, and the value is type-checked against it.
201
+
202
+ ### `<Link>`
203
+
204
+ JSX flavor of `nav.push`:
205
+
206
+ ```tsx
207
+ <Link to="profile" params={{ id: 'alice' }} search={{ tab: 'about' }}>
208
+ Open Alice
209
+ </Link>
210
+ <Link to="home" replace>Reset</Link>
211
+ ```
212
+
213
+ Same per-route conditional typing as `nav.push`.
214
+
215
+ ### `useParams(name)` / `useSearch(name)`
216
+
217
+ Typed accessors for the *currently-mounted* route. Calling with the wrong
218
+ route name is a TS error.
219
+
220
+ ```ts
221
+ const { id } = useParams('profile'); // { id: string }
222
+ const { tab } = useSearch('profile'); // { tab: 'posts' | 'about' }
223
+ ```
224
+
225
+ ### `useFocusEffect(handler)` / `useIsFocused()`
226
+
227
+ `useIsFocused()` is a reactive boolean — `true` while this screen is the
228
+ visible top of its navigator. `useFocusEffect(() => () => cleanup)` runs
229
+ `handler` on focus and the returned function on blur. Use these to mount
230
+ side-effects (analytics, subscriptions, video playback) only while the
231
+ screen is visible.
232
+
233
+ ### `useHardwareBack(handler)`
234
+
235
+ Subscribe to Android system-back / iOS edge-swipe. Return `true` to
236
+ swallow the press, `false`/`undefined` to let the navigator handle it.
237
+
238
+ ### `useScreenOptions(options | () => options)`
239
+
240
+ Imperatively merge `ScreenOptions` (`title`, `headerShown`,
241
+ `gestureEnabled`) for the current screen. Pass a plain object for a
242
+ one-time merge; pass a function and every signal touched inside it is
243
+ tracked, so the options re-merge on change.
244
+
245
+ ```tsx
246
+ const Profile = component(() => {
247
+ const { id } = useParams('profile');
248
+ useScreenOptions(() => ({ title: `User ${id}` }));
249
+ return () => <view><text>profile</text></view>;
250
+ });
251
+ ```
252
+
253
+ ### `useLinkingNav(options?)`
254
+
255
+ Bridges `@sigx/lynx-linking` URL events into the navigator. Call once
256
+ inside a `<NavigationRoot>` subtree. Options:
257
+
258
+ | Key | Notes |
259
+ |---|---|
260
+ | `prefixes` | Schemes to strip before parsing (`'myapp://'`, `'https://myapp.com'`). |
261
+ | `onURL(url, nav)` | Intercept before default dispatch. Call `nav.push` yourself to handle. |
262
+ | `onUnmatched(url)` | Fired for URLs no route matches. Default: silent. |
263
+ | `replaceInitial` | Use `replace` for cold-start URLs (default `true`). |
264
+
265
+ ### `useNavSerializer(options)`
266
+
267
+ Persist the navigator's stack across launches. Adapter is yours to
268
+ implement — `@sigx/lynx-storage`, MMKV, AsyncStorage, anything.
269
+
270
+ ```ts
271
+ useNavSerializer({
272
+ storage: {
273
+ async load() { return JSON.parse(await Storage.get('nav')); },
274
+ async save(snap) { await Storage.set('nav', JSON.stringify(snap)); },
275
+ },
276
+ debounceMs: 250,
277
+ onRestored: (snap) => console.log('restored', snap.stack.length, 'entries'),
278
+ onRestoreError: (reason, err) => console.warn('restore failed', reason, err),
279
+ });
280
+ ```
281
+
282
+ Snapshots carry a `version` field (`NAV_SNAPSHOT_VERSION`) — bump it when
283
+ the schema changes and `onRestoreError` fires `'version'`.
284
+
285
+ ### `hrefFor(name, params?, search?)` / `parseHref(input, routes)`
286
+
287
+ Build and parse path-style URLs declared by each route's `path` template:
288
+
289
+ ```ts
290
+ const href = hrefFor('profile', { id: 'alice' }, { tab: 'posts' });
291
+ // → "/users/alice?tab=posts"
292
+
293
+ const parsed = parseHref('/users/bob?tab=about', routes);
294
+ // → { name: 'profile', params: { id: 'bob' }, search: { tab: 'about' } }
295
+ ```
296
+
297
+ ### Lazy routes
298
+
299
+ Routes can pass a `lazy(...)` component from `@sigx/lynx` (re-exports
300
+ `@sigx/runtime-core`'s `lazy` + `<Suspense>`). The navigator calls
301
+ `.preload()` on push so the chunk is fetched before the screen mounts:
302
+
303
+ ```tsx
304
+ import { lazy } from '@sigx/lynx';
305
+
306
+ export const routes = defineRoutes({
307
+ home: { component: Home },
308
+ profile: { component: lazy(() => import('./screens/Profile')) },
309
+ });
310
+ ```
311
+
312
+ Wrap your `<Stack>` in `<Suspense fallback={…}>` to show a fallback while
313
+ the chunk loads. The bundler (rspeedy/rspack) needs to produce
314
+ Lynx-loadable chunks for the layered MT-bundle — see the `examples/`
315
+ folder for a working setup.
316
+
317
+ ## Modal presentation
318
+
319
+ Set `presentation: 'modal' | 'fullScreen' | 'transparent-modal'` on a
320
+ route definition. Modals ship as stack entries with a different
321
+ transition (bottom-sheet style) — there's no separate `<Modal>`
322
+ navigator. Use `nav.pop()` to dismiss.
323
+
324
+ ## Testing
325
+
326
+ ```ts
327
+ import { render, act } from '@sigx/lynx-testing';
328
+
329
+ render(
330
+ <NavigationRoot routes={routes} initialRoute="home" animated={false}>
331
+ <Stack />
332
+ </NavigationRoot>,
333
+ );
334
+ ```
335
+
336
+ Pass `animated={false}` so navigations commit synchronously — `lynx-testing`
337
+ has no MT runtime so the slide-from-right transition never completes
338
+ otherwise. Then `act(() => nav.push(...))` immediately re-renders.
339
+
340
+ ## Runtime gotchas
341
+
342
+ - **`useMainThreadRef` will crash on BG.** Refs returned by sigx that bind
343
+ to MT-only host nodes blow up if you read them outside `runOnMainThread`.
344
+ - **`runOnBackground` closure capture.** Variables captured in the body
345
+ are snapshot at call time. Read signals via `.value` *inside* the body,
346
+ not at definition.
347
+ - **`SharedValue` writes must come from MT worklets.** The transition
348
+ layer enforces this — pushing to a `SharedValue` from BG silently no-ops.
349
+ - **Lynx has no `z-index`.** Layering is document-order. The navigator
350
+ renders the underneath entry first, then the top entry — overlap them
351
+ via `position: absolute` and an explicit offset.
352
+
353
+ ## License
354
+
355
+ MIT
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `<Drawer>` — minimal off-canvas drawer navigator.
3
+ *
4
+ * Usage:
5
+ *
6
+ * ```tsx
7
+ * <NavigationRoot routes={routes}>
8
+ * <Drawer
9
+ * sidebar={() => <view><text>Menu</text></view>}
10
+ * >
11
+ * <Stack />
12
+ * </Drawer>
13
+ * </NavigationRoot>
14
+ * ```
15
+ *
16
+ * `useDrawer()` from inside any descendant gives `{ isOpen, open(), close(),
17
+ * toggle() }`. The sidebar is laid out absolutely on the left and is
18
+ * visible whenever `isOpen` is true.
19
+ *
20
+ * Scope: this slice ships the state primitive + the bare-bones layout.
21
+ * Gesture-driven open (edge swipe from the left) and MTS slide-in are out
22
+ * of scope — the app shell can wrap its sidebar JSX in its own transition.
23
+ *
24
+ * Design note: the sidebar is passed as a render function via the
25
+ * `sidebar` slot prop rather than a `<Drawer.Sidebar>` child. Mixing
26
+ * "register-yourself-as-a-fill" children with the parent's own visible
27
+ * layout creates a feedback loop in sigx's reactive scope (the parent's
28
+ * render reads the fill, child's setup writes it, parent re-renders,
29
+ * child re-mounts, …). A scoped slot avoids that entirely and the API
30
+ * is identical at the call site.
31
+ *
32
+ * `default` slot is the main content (almost always a `<Stack>`).
33
+ */
34
+ import { type Define } from '@sigx/lynx';
35
+ /** Reactive controller returned by `useDrawer()`. */
36
+ export interface DrawerNav {
37
+ /** True when the drawer is currently visible. Reactive. */
38
+ readonly isOpen: boolean;
39
+ /** Opens the drawer. */
40
+ open(): void;
41
+ /** Closes the drawer. */
42
+ close(): void;
43
+ /** Toggles between open and closed. */
44
+ toggle(): void;
45
+ }
46
+ /**
47
+ * Access the enclosing Drawer navigator. Throws when called outside
48
+ * `<Drawer>`.
49
+ */
50
+ export declare const useDrawer: import("@sigx/runtime-core").InjectableFunction<DrawerNav>;
51
+ type DrawerProps = Define.Prop<'initialOpen', boolean> & Define.Slot<'sidebar'> & Define.Slot<'default'>;
52
+ export declare const Drawer: import("@sigx/runtime-core").ComponentFactory<DrawerProps, void, {
53
+ sidebar: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
54
+ } & {
55
+ default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
56
+ }>;
57
+ export {};
58
+ //# sourceMappingURL=Drawer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../src/components/Drawer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAKH,KAAK,MAAM,EAEd,MAAM,YAAY,CAAC;AAEpB,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,wBAAwB;IACxB,IAAI,IAAI,IAAI,CAAC;IACb,yBAAyB;IACzB,KAAK,IAAI,IAAI,CAAC;IACd,uCAAuC;IACvC,MAAM,IAAI,IAAI,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,4DAIpB,CAAC;AAEH,KAAK,WAAW,GACV,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GACtB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAE7B,eAAO,MAAM,MAAM;;;;EAmDjB,CAAC"}
@@ -0,0 +1,76 @@
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
10
+ * sidebar={() => <view><text>Menu</text></view>}
11
+ * >
12
+ * <Stack />
13
+ * </Drawer>
14
+ * </NavigationRoot>
15
+ * ```
16
+ *
17
+ * `useDrawer()` from inside any descendant gives `{ isOpen, open(), close(),
18
+ * toggle() }`. The sidebar is laid out absolutely on the left and is
19
+ * visible whenever `isOpen` is true.
20
+ *
21
+ * Scope: this slice ships the state primitive + the bare-bones layout.
22
+ * Gesture-driven open (edge swipe from the left) and MTS slide-in are out
23
+ * of scope — the app shell can wrap its sidebar JSX in its own transition.
24
+ *
25
+ * Design note: the sidebar is passed as a render function via the
26
+ * `sidebar` slot prop rather than a `<Drawer.Sidebar>` child. Mixing
27
+ * "register-yourself-as-a-fill" children with the parent's own visible
28
+ * layout creates a feedback loop in sigx's reactive scope (the parent's
29
+ * render reads the fill, child's setup writes it, parent re-renders,
30
+ * child re-mounts, …). A scoped slot avoids that entirely and the API
31
+ * is identical at the call site.
32
+ *
33
+ * `default` slot is the main content (almost always a `<Stack>`).
34
+ */
35
+ import { component, defineInjectable, defineProvide, signal, } from '@sigx/lynx';
36
+ /**
37
+ * Access the enclosing Drawer navigator. Throws when called outside
38
+ * `<Drawer>`.
39
+ */
40
+ export const useDrawer = defineInjectable(() => {
41
+ throw new Error('[lynx-navigation] useDrawer() called outside of a <Drawer> component.');
42
+ });
43
+ export const Drawer = component(({ props, slots }) => {
44
+ // `isOpenSig` uses the `{value}` wrapper pattern — sigx's `signal()` of
45
+ // a primitive returns a proxy that requires `.value` reads; wrapping in
46
+ // an object makes the proxy carry a mutable boolean.
47
+ const isOpenSig = signal({
48
+ value: props.initialOpen === true,
49
+ });
50
+ const nav = {
51
+ get isOpen() {
52
+ return isOpenSig.value;
53
+ },
54
+ open() {
55
+ isOpenSig.value = true;
56
+ },
57
+ close() {
58
+ isOpenSig.value = false;
59
+ },
60
+ toggle() {
61
+ isOpenSig.value = !isOpenSig.value;
62
+ },
63
+ };
64
+ defineProvide(useDrawer, () => nav);
65
+ return () => {
66
+ const open = isOpenSig.value;
67
+ return (_jsxs("view", { style: { width: '100%', height: '100%' }, children: [_jsx("view", { style: { width: '100%', height: '100%' }, children: slots.default?.() }), _jsx("view", { style: {
68
+ position: 'absolute',
69
+ left: 0,
70
+ top: 0,
71
+ bottom: 0,
72
+ display: open ? 'flex' : 'none',
73
+ }, children: slots.sidebar?.() })] }));
74
+ };
75
+ });
76
+ //# sourceMappingURL=Drawer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Drawer.js","sourceRoot":"","sources":["../../src/components/Drawer.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;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"}
@@ -0,0 +1,144 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component, Gesture, runOnBackground, useGestureDetector, useMainThreadRef, } from '@sigx/lynx';
3
+ import { withTiming } from '@sigx/lynx-motion';
4
+ import { useNavInternals } from '../hooks/use-nav-internal.js';
5
+ import { SCREEN_WIDTH } from '../internal/screen-width.js';
6
+ /**
7
+ * Edge-pan recognizer for iOS-style swipe-back. Mounts as an absolutely-
8
+ * positioned 20px-wide strip on the left edge of the active screen; only
9
+ * exists when `nav.canGoBack && !transition`.
10
+ *
11
+ * `Gesture.Pan().minDistance(MIN_DISTANCE)` lets quick taps pass through to
12
+ * whatever's behind the strip (back button, screen header, etc.). Only
13
+ * horizontal drags past the threshold activate the gesture.
14
+ *
15
+ * MT/BG split:
16
+ * - All gesture handlers run on MT. They write `progress.current.value`
17
+ * directly per frame (no per-frame bridge crossing) and dispatch
18
+ * `runOnBackground(...)` only at start/commit/cancel — three BG hops
19
+ * per gesture max.
20
+ * - The transition state machine on BG mounts the underneath
21
+ * `<ScreenContainer>` once `beginBackGesture` lands; the gesture's
22
+ * in-flight progress writes are picked up the moment the binding
23
+ * registers (Phase 0.5 polish: pre-mount underneath when canGoBack to
24
+ * eliminate the brief pre-mount latency).
25
+ *
26
+ * Implementation notes (matching `<Draggable>`):
27
+ * - Single `useMainThreadRef` holding an object — primitive refs don't
28
+ * survive worklet capture cleanly in some Lynx versions, while object
29
+ * refs do (the worklet runtime resolves the ref via the
30
+ * `_workletRefMap`).
31
+ * - `e: any` rather than `e: unknown` — type annotations are erased, but
32
+ * SWC's worklet transform has been observed to behave better with the
33
+ * looser annotation. Keeps us aligned with Draggable verbatim.
34
+ * - Empty `onBegin`: load-bearing on iOS — without a registered onBegin
35
+ * callback, `LynxPanGestureHandler` skips the begin path and onStart/
36
+ * onEnd never fire (per Draggable's notes).
37
+ */
38
+ /** Fraction of screen width past which a release commits the back nav. */
39
+ const COMMIT_TRANSLATION = 0.33;
40
+ /** px/sec horizontal speed past which a release commits, regardless of distance. */
41
+ const COMMIT_VELOCITY = 300;
42
+ /** Width of the touchable strip on the left edge of every screen. */
43
+ const EDGE_ZONE_WIDTH = 20;
44
+ /** Minimum movement before the gesture activates (lets taps pass through). */
45
+ const MIN_DISTANCE = 8;
46
+ const SNAP_DURATION_SEC = 0.18;
47
+ /**
48
+ * Pre-computed milliseconds for the BG-side `setTimeout`. Module-level so
49
+ * it's in scope for both the MT worklet (`withTiming` argument) and the BG
50
+ * callback wrapped by `runOnBackground` (`setTimeout` argument). Locals
51
+ * declared inside an MT worklet body are MT-only — the BG callback's
52
+ * closure can't see them, hence "ReferenceError: snapMs is not defined".
53
+ */
54
+ const SNAP_DURATION_MS = Math.round(SNAP_DURATION_SEC * 1000);
55
+ export const EdgeBackHandle = component(() => {
56
+ const ref = useMainThreadRef(null);
57
+ // Per-gesture transient state — captured as a plain closure object
58
+ // rather than a `useMainThreadRef`. Lynx's SWC worklet transform deep-
59
+ // copies plain objects into `_c` once at register time; mutations on MT
60
+ // persist across calls because the same `_c` is bound for the lifetime
61
+ // of the gesture registration. Using a `useMainThreadRef` here was
62
+ // crashing on iOS with `cannot read property 'current' of undefined`
63
+ // — the resolved-ref capture path looked up an empty
64
+ // `_workletRefMap` entry under a race I haven't fully tracked down.
65
+ // Plain object avoids that path entirely.
66
+ const state = {
67
+ startPageX: 0,
68
+ prevPageX: 0,
69
+ prevTime: 0,
70
+ velocity: 0,
71
+ };
72
+ const internals = useNavInternals();
73
+ const progress = internals.progress;
74
+ const beginBackGesture = internals.beginBackGesture;
75
+ const commitBackGesture = internals.commitBackGesture;
76
+ const cancelBackGesture = internals.cancelBackGesture;
77
+ const pan = Gesture.Pan()
78
+ .minDistance(MIN_DISTANCE)
79
+ .onBegin(() => {
80
+ 'main thread';
81
+ })
82
+ .onStart((e) => {
83
+ 'main thread';
84
+ const p = e && e.params;
85
+ const pageX = (p && p.pageX) || 0;
86
+ state.startPageX = pageX;
87
+ state.prevPageX = pageX;
88
+ state.prevTime = Date.now();
89
+ state.velocity = 0;
90
+ runOnBackground(() => {
91
+ beginBackGesture();
92
+ })();
93
+ })
94
+ .onUpdate((e) => {
95
+ 'main thread';
96
+ if (!progress)
97
+ return;
98
+ const p = e && e.params;
99
+ const pageX = (p && p.pageX) || 0;
100
+ const dx = pageX - state.startPageX;
101
+ const prog = Math.max(0, Math.min(1, dx / SCREEN_WIDTH));
102
+ progress.current.value = prog;
103
+ const now = Date.now();
104
+ const dt = now - state.prevTime;
105
+ if (dt > 0) {
106
+ state.velocity =
107
+ ((pageX - state.prevPageX) / dt) * 1000;
108
+ }
109
+ state.prevPageX = pageX;
110
+ state.prevTime = now;
111
+ })
112
+ .onEnd((e) => {
113
+ 'main thread';
114
+ if (!progress)
115
+ return;
116
+ const p = e && e.params;
117
+ const pageX = (p && p.pageX) || 0;
118
+ const dx = pageX - state.startPageX;
119
+ const fraction = dx / SCREEN_WIDTH;
120
+ const commit = fraction > COMMIT_TRANSLATION ||
121
+ state.velocity > COMMIT_VELOCITY;
122
+ if (commit) {
123
+ withTiming(progress, 1, { duration: SNAP_DURATION_SEC });
124
+ runOnBackground(() => {
125
+ setTimeout(() => commitBackGesture(), SNAP_DURATION_MS);
126
+ })();
127
+ }
128
+ else {
129
+ withTiming(progress, 0, { duration: SNAP_DURATION_SEC });
130
+ runOnBackground(() => {
131
+ setTimeout(() => cancelBackGesture(), SNAP_DURATION_MS);
132
+ })();
133
+ }
134
+ });
135
+ useGestureDetector(ref, pan);
136
+ return () => (_jsx("view", { "main-thread:ref": ref, style: {
137
+ position: 'absolute',
138
+ top: '0',
139
+ left: '0',
140
+ width: `${EDGE_ZONE_WIDTH}px`,
141
+ bottom: '0',
142
+ } }));
143
+ });
144
+ //# sourceMappingURL=EdgeBackHandle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EdgeBackHandle.js","sourceRoot":"","sources":["../../src/components/EdgeBackHandle.tsx"],"names":[],"mappings":"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"}