@sigmela/router 0.0.11

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 (44) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +346 -0
  3. package/lib/module/Navigation.js +74 -0
  4. package/lib/module/NavigationStack.js +72 -0
  5. package/lib/module/Router.js +571 -0
  6. package/lib/module/RouterContext.js +33 -0
  7. package/lib/module/ScreenStackItem.js +61 -0
  8. package/lib/module/StackRenderer.js +29 -0
  9. package/lib/module/TabBar/RenderTabBar.js +122 -0
  10. package/lib/module/TabBar/TabBar.js +74 -0
  11. package/lib/module/TabBar/TabBarContext.js +4 -0
  12. package/lib/module/TabBar/useTabBar.js +11 -0
  13. package/lib/module/createController.js +5 -0
  14. package/lib/module/index.js +14 -0
  15. package/lib/module/package.json +1 -0
  16. package/lib/module/types.js +3 -0
  17. package/lib/typescript/package.json +1 -0
  18. package/lib/typescript/src/Navigation.d.ts +8 -0
  19. package/lib/typescript/src/NavigationStack.d.ts +30 -0
  20. package/lib/typescript/src/Router.d.ts +70 -0
  21. package/lib/typescript/src/RouterContext.d.ts +19 -0
  22. package/lib/typescript/src/ScreenStackItem.d.ts +12 -0
  23. package/lib/typescript/src/StackRenderer.d.ts +6 -0
  24. package/lib/typescript/src/TabBar/RenderTabBar.d.ts +8 -0
  25. package/lib/typescript/src/TabBar/TabBar.d.ts +43 -0
  26. package/lib/typescript/src/TabBar/TabBarContext.d.ts +3 -0
  27. package/lib/typescript/src/TabBar/useTabBar.d.ts +2 -0
  28. package/lib/typescript/src/createController.d.ts +14 -0
  29. package/lib/typescript/src/index.d.ts +15 -0
  30. package/lib/typescript/src/types.d.ts +244 -0
  31. package/package.json +166 -0
  32. package/src/Navigation.tsx +102 -0
  33. package/src/NavigationStack.ts +106 -0
  34. package/src/Router.ts +684 -0
  35. package/src/RouterContext.tsx +58 -0
  36. package/src/ScreenStackItem.tsx +64 -0
  37. package/src/StackRenderer.tsx +41 -0
  38. package/src/TabBar/RenderTabBar.tsx +154 -0
  39. package/src/TabBar/TabBar.ts +106 -0
  40. package/src/TabBar/TabBarContext.ts +4 -0
  41. package/src/TabBar/useTabBar.ts +10 -0
  42. package/src/createController.ts +27 -0
  43. package/src/index.ts +24 -0
  44. package/src/types.ts +272 -0
@@ -0,0 +1,571 @@
1
+ "use strict";
2
+
3
+ import { nanoid } from 'nanoid/non-secure';
4
+ import qs from 'query-string';
5
+
6
+ // Root transition option: allow string shorthand like 'fade'
7
+
8
+ function isTabBarLike(obj) {
9
+ return obj != null && typeof obj === 'object' && 'onIndexChange' in obj && 'getState' in obj && 'subscribe' in obj && 'stacks' in obj;
10
+ }
11
+ function isNavigationStackLike(obj) {
12
+ return obj != null && typeof obj === 'object' && 'getRoutes' in obj && 'getId' in obj;
13
+ }
14
+ const EMPTY_ARRAY = [];
15
+ export class Router {
16
+ tabBar = null;
17
+ root = null;
18
+ global = null;
19
+ listeners = new Set();
20
+ registry = [];
21
+ state = {
22
+ history: [],
23
+ activeTabIndex: undefined
24
+ };
25
+ // per-stack slices and listeners
26
+ stackSlices = new Map();
27
+ stackListeners = new Map();
28
+ activeTabListeners = new Set();
29
+ stackById = new Map();
30
+ routeById = new Map();
31
+ visibleRoute = null;
32
+ // Root structure listeners (TabBar ↔ NavigationStack changes)
33
+ rootListeners = new Set();
34
+ rootTransition = undefined;
35
+ constructor(config) {
36
+ this.routerScreenOptions = config.screenOptions;
37
+ if (isTabBarLike(config.root)) {
38
+ this.tabBar = config.root;
39
+ }
40
+ if (config.global) {
41
+ this.global = config.global;
42
+ }
43
+ this.root = config.root;
44
+ if (this.tabBar) {
45
+ this.state = {
46
+ history: [],
47
+ activeTabIndex: this.tabBar.getState().index
48
+ };
49
+ }
50
+ this.buildRegistry();
51
+ this.seedInitialHistory();
52
+ this.recomputeVisibleRoute();
53
+ }
54
+
55
+ // Public API
56
+ navigate = path => {
57
+ this.performNavigation(path, 'push');
58
+ };
59
+ replace = path => {
60
+ this.performNavigation(path, 'replace');
61
+ };
62
+ goBack = () => {
63
+ // Global layer wins
64
+ if (this.global) {
65
+ const gid = this.global.getId();
66
+ const gslice = this.getStackHistory(gid);
67
+ const gtop = gslice.length ? gslice[gslice.length - 1] : undefined;
68
+ if (gtop) {
69
+ this.applyHistoryChange('pop', gtop);
70
+ return;
71
+ }
72
+ }
73
+
74
+ // Tab layer
75
+ if (this.tabBar) {
76
+ const idx = this.getActiveTabIndex();
77
+ const state = this.tabBar.getState();
78
+ const route = state.tabs[idx];
79
+ if (!route) return;
80
+ const stack = this.tabBar.stacks[route.tabKey];
81
+ if (!stack) return;
82
+ const sid = stack.getId();
83
+ const slice = this.getStackHistory(sid);
84
+ if (slice.length > 1) {
85
+ const top = slice[slice.length - 1];
86
+ if (top) {
87
+ this.applyHistoryChange('pop', top);
88
+ }
89
+ }
90
+ return;
91
+ }
92
+
93
+ // Root layer
94
+ if (isNavigationStackLike(this.root)) {
95
+ const sid = this.root.getId();
96
+ const slice = this.getStackHistory(sid);
97
+ if (slice.length > 1) {
98
+ const top = slice[slice.length - 1];
99
+ if (top) {
100
+ this.applyHistoryChange('pop', top);
101
+ }
102
+ }
103
+ }
104
+ };
105
+ onTabIndexChange = index => {
106
+ if (this.tabBar) {
107
+ this.tabBar.onIndexChange(index);
108
+ this.setState({
109
+ activeTabIndex: index
110
+ });
111
+ this.emit(this.activeTabListeners);
112
+ this.recomputeVisibleRoute();
113
+ this.emit(this.listeners);
114
+ }
115
+ };
116
+ setActiveTabIndex = index => {
117
+ this.onTabIndexChange(index);
118
+ };
119
+ ensureTabSeed = index => {
120
+ if (!this.tabBar) return;
121
+ const state = this.tabBar.getState();
122
+ const route = state.tabs[index];
123
+ if (!route) return;
124
+ const key = route.tabKey;
125
+ const stack = this.tabBar.stacks[key];
126
+ if (!stack) return;
127
+ const hasAny = this.getStackHistory(stack.getId()).length > 0;
128
+ if (hasAny) return;
129
+ const first = stack.getFirstRoute();
130
+ if (!first) return;
131
+ const newItem = {
132
+ key: this.generateKey(),
133
+ scope: 'tab',
134
+ routeId: first.routeId,
135
+ component: first.component,
136
+ options: this.mergeOptions(first.options, stack.getId()),
137
+ params: {},
138
+ tabIndex: index,
139
+ stackId: stack.getId()
140
+ };
141
+ this.applyHistoryChange('push', newItem);
142
+ };
143
+ getState = () => {
144
+ return this.state;
145
+ };
146
+ subscribe(listener) {
147
+ this.listeners.add(listener);
148
+ return () => {
149
+ this.listeners.delete(listener);
150
+ };
151
+ }
152
+
153
+ // Per-stack subscriptions
154
+ getStackHistory = stackId => {
155
+ return this.stackSlices.get(stackId) ?? EMPTY_ARRAY;
156
+ };
157
+ subscribeStack = (stackId, cb) => {
158
+ if (!stackId) return () => {};
159
+ let set = this.stackListeners.get(stackId);
160
+ if (!set) {
161
+ set = new Set();
162
+ this.stackListeners.set(stackId, set);
163
+ }
164
+ set.add(cb);
165
+ return () => {
166
+ set.delete(cb);
167
+ };
168
+ };
169
+ getActiveTabIndex = () => {
170
+ return this.state.activeTabIndex ?? 0;
171
+ };
172
+ subscribeActiveTab = cb => {
173
+ this.activeTabListeners.add(cb);
174
+ return () => this.activeTabListeners.delete(cb);
175
+ };
176
+ getRootStackId() {
177
+ return isNavigationStackLike(this.root) ? this.root.getId() : undefined;
178
+ }
179
+ getGlobalStackId() {
180
+ return this.global?.getId();
181
+ }
182
+ hasTabBar() {
183
+ return !!this.tabBar;
184
+ }
185
+ subscribeRoot(listener) {
186
+ this.rootListeners.add(listener);
187
+ return () => this.rootListeners.delete(listener);
188
+ }
189
+ emitRootChange() {
190
+ this.rootListeners.forEach(l => l());
191
+ }
192
+ getRootTransition() {
193
+ return this.rootTransition;
194
+ }
195
+ setRoot(nextRoot, options) {
196
+ // Update root/tabBar references
197
+ this.tabBar = isTabBarLike(nextRoot) ? nextRoot : null;
198
+ this.root = nextRoot;
199
+
200
+ // Save requested transition (stackAnimation string)
201
+ this.rootTransition = options?.transition ?? undefined;
202
+
203
+ // If switching to TabBar, reset selected tab to the first one to avoid
204
+ // leaking previously selected tab across auth flow changes.
205
+ if (this.tabBar) {
206
+ this.tabBar.onIndexChange(0);
207
+ }
208
+
209
+ // Reset core structures (keep global reference as-is)
210
+ this.registry.length = 0;
211
+ this.stackSlices.clear();
212
+ this.stackById.clear();
213
+ this.routeById.clear();
214
+
215
+ // Reset state (activeTabIndex from tabBar if present)
216
+ this.state = {
217
+ history: [],
218
+ activeTabIndex: this.tabBar ? this.tabBar.getState().index : undefined
219
+ };
220
+
221
+ // Rebuild registry and seed new root
222
+ this.buildRegistry();
223
+ this.seedInitialHistory();
224
+ this.recomputeVisibleRoute();
225
+ this.emitRootChange();
226
+ this.emit(this.listeners);
227
+ }
228
+
229
+ // Visible route (global top if present, else active tab/root top)
230
+ getVisibleRoute = () => {
231
+ return this.visibleRoute;
232
+ };
233
+ recomputeVisibleRoute() {
234
+ // Global top
235
+ if (this.global) {
236
+ const gid = this.global.getId();
237
+ const gslice = this.getStackHistory(gid);
238
+ const gtop = gslice.length ? gslice[gslice.length - 1] : undefined;
239
+ if (gtop) {
240
+ const meta = this.routeById.get(gtop.routeId);
241
+ this.visibleRoute = meta ? {
242
+ ...meta,
243
+ routeId: gtop.routeId,
244
+ params: gtop.params,
245
+ query: gtop.query
246
+ } : {
247
+ routeId: gtop.routeId,
248
+ stackId: gtop.stackId,
249
+ params: gtop.params,
250
+ query: gtop.query,
251
+ scope: 'global'
252
+ };
253
+ return;
254
+ }
255
+ }
256
+
257
+ // TabBar
258
+ if (this.tabBar) {
259
+ const idx = this.getActiveTabIndex();
260
+ const state = this.tabBar.getState();
261
+ const route = state.tabs[idx];
262
+ if (route) {
263
+ const stack = this.tabBar.stacks[route.tabKey];
264
+ if (stack) {
265
+ const sid = stack.getId();
266
+ const slice = this.getStackHistory(sid);
267
+ const top = slice.length ? slice[slice.length - 1] : undefined;
268
+ if (top) {
269
+ const meta = this.routeById.get(top.routeId);
270
+ this.visibleRoute = meta ? {
271
+ ...meta,
272
+ routeId: top.routeId,
273
+ params: top.params,
274
+ query: top.query
275
+ } : {
276
+ routeId: top.routeId,
277
+ stackId: sid,
278
+ tabIndex: idx,
279
+ params: top.params,
280
+ query: top.query,
281
+ scope: 'tab'
282
+ };
283
+ return;
284
+ }
285
+ } else {
286
+ this.visibleRoute = {
287
+ routeId: `tab-screen-${idx}`,
288
+ tabIndex: idx,
289
+ scope: 'tab'
290
+ };
291
+ return;
292
+ }
293
+ }
294
+ }
295
+
296
+ // Root stack
297
+ if (this.root && isNavigationStackLike(this.root)) {
298
+ const sid = this.root.getId();
299
+ const slice = this.getStackHistory(sid);
300
+ const top = slice.length ? slice[slice.length - 1] : undefined;
301
+ if (top) {
302
+ const meta = this.routeById.get(top.routeId);
303
+ this.visibleRoute = meta ? {
304
+ ...meta,
305
+ routeId: top.routeId,
306
+ params: top.params,
307
+ query: top.query
308
+ } : {
309
+ routeId: top.routeId,
310
+ stackId: sid,
311
+ params: top.params,
312
+ query: top.query,
313
+ scope: 'root'
314
+ };
315
+ return;
316
+ }
317
+ }
318
+ this.visibleRoute = null;
319
+ }
320
+
321
+ // Internal navigation logic
322
+ performNavigation(path, action) {
323
+ const {
324
+ pathname,
325
+ query
326
+ } = this.parsePath(path);
327
+ const matched = this.matchRoute(pathname);
328
+ if (!matched) {
329
+ return;
330
+ }
331
+ if (matched.scope === 'tab' && this.tabBar && matched.tabIndex !== undefined) {
332
+ this.onTabIndexChange(matched.tabIndex);
333
+ }
334
+ const matchResult = matched.match(pathname);
335
+ const params = matchResult ? matchResult.params : undefined;
336
+
337
+ // Prevent duplicate push when navigating to the same screen already on top of its stack
338
+ if (action === 'push') {
339
+ const top = this.getTopForTarget(matched.stackId);
340
+ if (top && top.routeId === matched.routeId) {
341
+ const prev = top.params ? JSON.stringify(top.params) : '';
342
+ const next = params ? JSON.stringify(params) : '';
343
+ if (prev === next) {
344
+ return;
345
+ }
346
+ }
347
+ }
348
+
349
+ // If there's a controller, execute it first
350
+ if (matched.controller) {
351
+ const controllerInput = {
352
+ params,
353
+ query
354
+ };
355
+ const present = passProps => {
356
+ const newItem = this.createHistoryItem(matched, params, query, pathname, passProps);
357
+ this.applyHistoryChange(action, newItem);
358
+ };
359
+ matched.controller(controllerInput, present);
360
+ return;
361
+ }
362
+ const newItem = this.createHistoryItem(matched, params, query, pathname);
363
+ this.applyHistoryChange(action, newItem);
364
+ }
365
+ createHistoryItem(matched, params, query, pathname, passProps) {
366
+ return {
367
+ key: this.generateKey(),
368
+ scope: matched.scope,
369
+ routeId: matched.routeId,
370
+ component: matched.component,
371
+ options: this.mergeOptions(matched.options, matched.stackId),
372
+ params,
373
+ query: query,
374
+ passProps,
375
+ tabIndex: matched.tabIndex,
376
+ stackId: matched.stackId,
377
+ pattern: matched.path,
378
+ path: pathname
379
+ };
380
+ }
381
+
382
+ // Internal helpers
383
+ buildRegistry() {
384
+ this.registry.length = 0;
385
+ const addFromStack = (stack, scope, extras) => {
386
+ const stackId = stack.getId();
387
+ this.stackById.set(stackId, stack);
388
+ for (const r of stack.getRoutes()) {
389
+ this.registry.push({
390
+ routeId: r.routeId,
391
+ scope,
392
+ path: r.path,
393
+ match: r.match,
394
+ component: r.component,
395
+ controller: r.controller,
396
+ options: r.options,
397
+ tabIndex: extras.tabIndex,
398
+ stackId
399
+ });
400
+ this.routeById.set(r.routeId, {
401
+ path: r.path,
402
+ stackId,
403
+ tabIndex: extras.tabIndex,
404
+ scope
405
+ });
406
+ }
407
+ // init empty slice
408
+ if (!this.stackSlices.has(stackId)) this.stackSlices.set(stackId, EMPTY_ARRAY);
409
+ };
410
+ if (isNavigationStackLike(this.root)) {
411
+ addFromStack(this.root, 'root', {});
412
+ } else if (this.tabBar) {
413
+ const state = this.tabBar.getState();
414
+ state.tabs.forEach((tab, idx) => {
415
+ const stack = this.tabBar.stacks[tab.tabKey];
416
+ if (stack) {
417
+ addFromStack(stack, 'tab', {
418
+ tabIndex: idx
419
+ });
420
+ }
421
+ });
422
+ }
423
+ if (this.global) {
424
+ addFromStack(this.global, 'global', {});
425
+ }
426
+ }
427
+ seedInitialHistory() {
428
+ if (this.state.history.length > 0) return;
429
+ if (this.tabBar) {
430
+ const state = this.tabBar.getState();
431
+ const activeIdx = state.index ?? 0;
432
+ const route = state.tabs[activeIdx];
433
+ if (!route) return;
434
+ const stack = this.tabBar.stacks[route.tabKey];
435
+ if (stack) {
436
+ const first = stack.getFirstRoute();
437
+ if (first) {
438
+ const newItem = {
439
+ key: this.generateKey(),
440
+ scope: 'tab',
441
+ routeId: first.routeId,
442
+ component: first.component,
443
+ options: this.mergeOptions(first.options, stack.getId()),
444
+ params: {},
445
+ tabIndex: activeIdx,
446
+ stackId: stack.getId()
447
+ };
448
+ this.applyHistoryChange('push', newItem);
449
+ }
450
+ }
451
+ return;
452
+ }
453
+ if (isNavigationStackLike(this.root)) {
454
+ const first = this.root.getFirstRoute();
455
+ if (first) {
456
+ const newItem = {
457
+ key: this.generateKey(),
458
+ scope: 'root',
459
+ routeId: first.routeId,
460
+ component: first.component,
461
+ options: this.mergeOptions(first.options, this.root.getId()),
462
+ params: {},
463
+ stackId: this.root.getId()
464
+ };
465
+ this.applyHistoryChange('push', newItem);
466
+ }
467
+ }
468
+ }
469
+ matchRoute(path) {
470
+ for (const r of this.registry) {
471
+ if (r.match(path)) return r;
472
+ }
473
+ return undefined;
474
+ }
475
+ generateKey() {
476
+ return `route-${nanoid()}`;
477
+ }
478
+ parsePath(path) {
479
+ const parsed = qs.parseUrl(path);
480
+ return {
481
+ pathname: parsed.url,
482
+ query: parsed.query
483
+ };
484
+ }
485
+ applyHistoryChange(action, item) {
486
+ const sid = item.stackId;
487
+ if (action === 'push') {
488
+ this.setState({
489
+ history: [...this.state.history, item]
490
+ });
491
+ const prevSlice = this.stackSlices.get(sid) ?? EMPTY_ARRAY;
492
+ const nextSlice = [...prevSlice, item];
493
+ this.stackSlices.set(sid, nextSlice);
494
+ this.emit(this.stackListeners.get(sid));
495
+ this.recomputeVisibleRoute();
496
+ this.emit(this.listeners);
497
+ } else if (action === 'replace') {
498
+ const prevTop = this.state.history[this.state.history.length - 1];
499
+ const prevSid = prevTop?.stackId;
500
+ this.setState({
501
+ history: [...this.state.history.slice(0, -1), item]
502
+ });
503
+ if (prevSid && prevSid !== sid) {
504
+ const prevSlice = this.stackSlices.get(prevSid) ?? EMPTY_ARRAY;
505
+ const trimmed = prevSlice.slice(0, -1);
506
+ this.stackSlices.set(prevSid, trimmed);
507
+ this.emit(this.stackListeners.get(prevSid));
508
+ const newSlice = this.stackSlices.get(sid) ?? EMPTY_ARRAY;
509
+ const appended = [...newSlice, item];
510
+ this.stackSlices.set(sid, appended);
511
+ this.emit(this.stackListeners.get(sid));
512
+ } else {
513
+ const prevSlice = this.stackSlices.get(sid) ?? EMPTY_ARRAY;
514
+ const nextSlice = prevSlice.length ? [...prevSlice.slice(0, -1), item] : [item];
515
+ this.stackSlices.set(sid, nextSlice);
516
+ this.emit(this.stackListeners.get(sid));
517
+ }
518
+ this.recomputeVisibleRoute();
519
+ this.emit(this.listeners);
520
+ } else if (action === 'pop') {
521
+ // Remove specific item by key from global history
522
+ const nextHist = this.state.history.filter(h => h.key !== item.key);
523
+ this.setState({
524
+ history: nextHist
525
+ });
526
+
527
+ // Update slice only if the last item matches the popped one
528
+ const prevSlice = this.stackSlices.get(sid) ?? EMPTY_ARRAY;
529
+ const last = prevSlice.length ? prevSlice[prevSlice.length - 1] : undefined;
530
+ if (last && last.key === item.key) {
531
+ const nextSlice = prevSlice.slice(0, -1);
532
+ this.stackSlices.set(sid, nextSlice);
533
+ this.emit(this.stackListeners.get(sid));
534
+ }
535
+ this.recomputeVisibleRoute();
536
+ this.emit(this.listeners);
537
+ }
538
+ }
539
+ setState(next) {
540
+ const prev = this.state;
541
+ const nextState = {
542
+ history: next.history ?? prev.history,
543
+ activeTabIndex: next.activeTabIndex ?? prev.activeTabIndex
544
+ };
545
+ this.state = nextState;
546
+ // Callers will emit updates explicitly.
547
+ }
548
+ emit(set) {
549
+ if (!set) return;
550
+ set.forEach(l => l());
551
+ }
552
+ getTopForTarget(stackId) {
553
+ if (!stackId) return undefined;
554
+ const slice = this.stackSlices.get(stackId) ?? EMPTY_ARRAY;
555
+ return slice.length ? slice[slice.length - 1] : undefined;
556
+ }
557
+ mergeOptions(routeOptions, stackId) {
558
+ const stackDefaults = stackId ? this.findStackById(stackId)?.getDefaultOptions() : undefined;
559
+ const routerDefaults = this.routerScreenOptions;
560
+ if (!routerDefaults && !stackDefaults && !routeOptions) return undefined;
561
+ const merged = {
562
+ ...(stackDefaults ?? {}),
563
+ ...(routeOptions ?? {}),
564
+ ...(routerDefaults ?? {})
565
+ };
566
+ return merged;
567
+ }
568
+ findStackById(stackId) {
569
+ return this.stackById.get(stackId);
570
+ }
571
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ import React, { createContext, useContext } from 'react';
4
+ export const RouterContext = /*#__PURE__*/createContext(null);
5
+ export const RouteLocalContext = /*#__PURE__*/createContext(null);
6
+ export const useRouter = () => {
7
+ const ctx = useContext(RouterContext);
8
+ if (ctx == null) {
9
+ throw new Error('useRouter must be used within RouterContext.Provider');
10
+ }
11
+ return ctx;
12
+ };
13
+ export const useCurrentRoute = () => {
14
+ const router = useRouter();
15
+ const subscribe = React.useCallback(cb => router.subscribe(cb), [router]);
16
+ const get = React.useCallback(() => router.getVisibleRoute(), [router]);
17
+ return React.useSyncExternalStore(subscribe, get, get);
18
+ };
19
+ export function useParams() {
20
+ const local = React.useContext(RouteLocalContext);
21
+ return local?.params ?? {};
22
+ }
23
+ export function useQueryParams() {
24
+ const local = React.useContext(RouteLocalContext);
25
+ return local?.query ?? {};
26
+ }
27
+ export function useRoute() {
28
+ const local = React.useContext(RouteLocalContext);
29
+ if (!local) {
30
+ throw new Error('useRoute must be used within RouterLocalContext.Provider');
31
+ }
32
+ return local;
33
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ import { ScreenStackItem as RNSScreenStackItem } from 'react-native-screens';
4
+ import { RouteLocalContext, useRouter } from "./RouterContext.js";
5
+ import { StyleSheet } from 'react-native';
6
+ import { memo } from 'react';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ export const ScreenStackItem = /*#__PURE__*/memo(({
9
+ item,
10
+ stackId,
11
+ stackAnimation,
12
+ screenStyle
13
+ }) => {
14
+ const router = useRouter();
15
+ const onDismissed = () => {
16
+ if (stackId) {
17
+ const history = router.getStackHistory(stackId);
18
+ const topKey = history.length ? history[history.length - 1]?.key : null;
19
+ if (topKey && topKey === item.key) {
20
+ router.goBack();
21
+ }
22
+ }
23
+ };
24
+ const value = {
25
+ presentation: item.options?.stackPresentation ?? 'push',
26
+ params: item.params,
27
+ query: item.query,
28
+ pattern: item.pattern,
29
+ path: item.path
30
+ };
31
+ const {
32
+ header,
33
+ ...screenProps
34
+ } = item.options || {};
35
+ // Merge global header appearance with per-screen header options
36
+ // const mergedHeader = {
37
+ // ...(headerAppearance ?? {}),
38
+ // ...(header ?? {}),
39
+ // } as any;
40
+ // Hide header by default if not specified
41
+ const headerConfig = header ?? {
42
+ hidden: true
43
+ };
44
+
45
+ // console.log('headerConfig', headerAppearance, item.key);
46
+ return /*#__PURE__*/_jsx(RNSScreenStackItem, {
47
+ screenId: item.key,
48
+ onDismissed: onDismissed,
49
+ style: StyleSheet.absoluteFill,
50
+ contentStyle: screenStyle,
51
+ headerConfig: headerConfig,
52
+ ...screenProps,
53
+ stackAnimation: stackAnimation ?? item.options?.stackAnimation,
54
+ children: /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
55
+ value: value,
56
+ children: /*#__PURE__*/_jsx(item.component, {
57
+ ...(item.passProps || {})
58
+ })
59
+ })
60
+ }, item.key);
61
+ });
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ import { memo, useCallback, useSyncExternalStore } from 'react';
4
+ import { ScreenStackItem } from "./ScreenStackItem.js";
5
+ import { ScreenStack } from 'react-native-screens';
6
+ import { useRouter } from "./RouterContext.js";
7
+ import { StyleSheet } from 'react-native';
8
+ import { jsx as _jsx } from "react/jsx-runtime";
9
+ export const StackRenderer = /*#__PURE__*/memo(({
10
+ stack
11
+ }) => {
12
+ const router = useRouter();
13
+ const stackId = stack.getId();
14
+ const subscribe = useCallback(cb => router.subscribeStack(stackId, cb), [router, stackId]);
15
+ const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
16
+ const historyForThisStack = useSyncExternalStore(subscribe, get, get);
17
+ return /*#__PURE__*/_jsx(ScreenStack, {
18
+ style: styles.flex,
19
+ children: historyForThisStack.map(item => /*#__PURE__*/_jsx(ScreenStackItem, {
20
+ item: item,
21
+ stackId: stackId
22
+ }, item.key))
23
+ });
24
+ });
25
+ const styles = StyleSheet.create({
26
+ flex: {
27
+ flex: 1
28
+ }
29
+ });