@sigmela/router 0.3.1 → 0.3.3

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.
@@ -6,7 +6,7 @@ import { useRouter } from "../RouterContext.js";
6
6
  import { ScreenStackItem } from 'react-native-screens';
7
7
  import { Pressable, StyleSheet, View, Text } from 'react-native';
8
8
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
9
- import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo } from 'react';
9
+ import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo, startTransition } from 'react';
10
10
  import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
11
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  const TIMING_CONFIG = {
@@ -19,9 +19,17 @@ const DrawerStackRenderer = /*#__PURE__*/memo(({
19
19
  }) => {
20
20
  const router = useRouter();
21
21
  const stackId = stack.getId();
22
- const subscribe = useCallback(cb => router.subscribeStack(stackId, cb), [router, stackId]);
23
- const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
24
- const history = useSyncExternalStore(subscribe, get, get);
22
+ const [history, setHistory] = useState(() => router.getStackHistory(stackId));
23
+ useEffect(() => {
24
+ const update = () => {
25
+ startTransition(() => {
26
+ setHistory(router.getStackHistory(stackId));
27
+ });
28
+ };
29
+ const unsub = router.subscribeStack(stackId, update);
30
+ update();
31
+ return unsub;
32
+ }, [router, stackId]);
25
33
  return /*#__PURE__*/_jsx(StackRenderer, {
26
34
  appearance: appearance,
27
35
  stackId: stackId,
@@ -4,13 +4,26 @@ import { ScreenStackItem } from "./ScreenStackItem/index.js";
4
4
  import { RouterContext } from "./RouterContext.js";
5
5
  import { ScreenStack } from "./ScreenStack/index.js";
6
6
  import { StyleSheet } from 'react-native';
7
- import { useSyncExternalStore, memo, useCallback, useEffect, useRef, useState } from 'react';
7
+ import { memo, useEffect, useRef, useState, startTransition } from 'react';
8
8
  import { jsx as _jsx } from "react/jsx-runtime";
9
9
  const EMPTY_HISTORY = [];
10
10
  function useStackHistory(router, stackId) {
11
- const subscribe = useCallback(cb => stackId ? router.subscribeStack(stackId, cb) : () => {}, [router, stackId]);
12
- const get = useCallback(() => stackId ? router.getStackHistory(stackId) : EMPTY_HISTORY, [router, stackId]);
13
- return useSyncExternalStore(subscribe, get, get);
11
+ const [history, setHistory] = useState(() => stackId ? router.getStackHistory(stackId) : EMPTY_HISTORY);
12
+ useEffect(() => {
13
+ if (!stackId) {
14
+ setHistory(EMPTY_HISTORY);
15
+ return;
16
+ }
17
+ const update = () => {
18
+ startTransition(() => {
19
+ setHistory(router.getStackHistory(stackId));
20
+ });
21
+ };
22
+ const unsub = router.subscribeStack(stackId, update);
23
+ update();
24
+ return unsub;
25
+ }, [router, stackId]);
26
+ return history;
14
27
  }
15
28
 
16
29
  /**
@@ -30,8 +43,10 @@ export const Navigation = /*#__PURE__*/memo(({
30
43
  }));
31
44
  useEffect(() => {
32
45
  return router.subscribeRoot(() => {
33
- setRoot({
34
- rootId: router.getRootStackId()
46
+ startTransition(() => {
47
+ setRoot({
48
+ rootId: router.getRootStackId()
49
+ });
35
50
  });
36
51
  });
37
52
  }, [router]);
@@ -4,33 +4,67 @@ import { ScreenStackItem } from "../ScreenStackItem/index.js";
4
4
  import { ScreenStack } from "../ScreenStack/index.js";
5
5
  import { SplitViewContext } from "./SplitViewContext.js";
6
6
  import { useRouter } from "../RouterContext.js";
7
- import { memo, useCallback, useSyncExternalStore, useMemo } from 'react';
7
+ import { memo, useMemo, useState, useEffect, startTransition } from 'react';
8
8
  import { StyleSheet } from 'react-native';
9
9
  import { jsx as _jsx } from "react/jsx-runtime";
10
10
  /**
11
- * On native (iPhone), SplitView renders primary and secondary screens
12
- * in a SINGLE ScreenStack to get native push/pop animations.
11
+ * On native, SplitView renders primary and secondary screens in a SINGLE
12
+ * ScreenStack to get native push/pop animations.
13
13
  *
14
14
  * The combined history is: [...primaryHistory, ...secondaryHistory]
15
15
  * This way, navigating from primary to secondary is a native push.
16
+ *
17
+ * On Android (Fabric) react-native-screens manages each ScreenStackItem via
18
+ * an Android Fragment. Fragment lifecycle (onCreateView) is asynchronous,
19
+ * but Fabric's layout pass is synchronous. If a new commit is dispatched
20
+ * while the previous Fragment transaction is still in flight, Fabric tries
21
+ * to insert child views into a Screen whose Fragment isn't attached yet:
22
+ *
23
+ * "addViewAt: Parent Screen does not have its Fragment attached"
24
+ *
25
+ * The fix follows the same pattern used in react-native-screens' own
26
+ * BottomTabsContainer example: subscribe via useEffect and apply updates
27
+ * inside `startTransition`. This marks the update as non-urgent, letting
28
+ * React defer the Fabric commit until pending Fragment transactions complete.
29
+ *
30
+ * See: https://github.com/software-mansion/react-native-screens/blob/ddd1a9e/
31
+ * apps/src/shared/gamma/containers/bottom-tabs/BottomTabsContainer.tsx
16
32
  */
17
33
  export const RenderSplitView = /*#__PURE__*/memo(({
18
34
  splitView,
19
35
  appearance
20
36
  }) => {
21
37
  const router = useRouter();
22
-
23
- // Subscribe to primary stack
24
38
  const primaryId = splitView.primary.getId();
25
- const subscribePrimary = useCallback(cb => router.subscribeStack(primaryId, cb), [router, primaryId]);
26
- const getPrimary = useCallback(() => router.getStackHistory(primaryId), [router, primaryId]);
27
- const primaryHistory = useSyncExternalStore(subscribePrimary, getPrimary, getPrimary);
28
-
29
- // Subscribe to secondary stack
30
39
  const secondaryId = splitView.secondary.getId();
31
- const subscribeSecondary = useCallback(cb => router.subscribeStack(secondaryId, cb), [router, secondaryId]);
32
- const getSecondary = useCallback(() => router.getStackHistory(secondaryId), [router, secondaryId]);
33
- const secondaryHistory = useSyncExternalStore(subscribeSecondary, getSecondary, getSecondary);
40
+
41
+ // Subscribe to both stacks via useEffect + startTransition.
42
+ // startTransition defers the Fabric commit so the FragmentManager has
43
+ // time to finish pending transactions between commits.
44
+ const [primaryHistory, setPrimaryHistory] = useState(() => router.getStackHistory(primaryId));
45
+ const [secondaryHistory, setSecondaryHistory] = useState(() => router.getStackHistory(secondaryId));
46
+ useEffect(() => {
47
+ const updatePrimary = () => {
48
+ startTransition(() => {
49
+ setPrimaryHistory(router.getStackHistory(primaryId));
50
+ });
51
+ };
52
+ const updateSecondary = () => {
53
+ startTransition(() => {
54
+ setSecondaryHistory(router.getStackHistory(secondaryId));
55
+ });
56
+ };
57
+ const unsub1 = router.subscribeStack(primaryId, updatePrimary);
58
+ const unsub2 = router.subscribeStack(secondaryId, updateSecondary);
59
+
60
+ // Sync in case the store changed between useState init and useEffect
61
+ updatePrimary();
62
+ updateSecondary();
63
+ return () => {
64
+ unsub1();
65
+ unsub2();
66
+ };
67
+ }, [router, primaryId, secondaryId]);
34
68
 
35
69
  // Fallback: if primary is empty, seed with first route
36
70
  const primaryHistoryToRender = useMemo(() => {
@@ -56,9 +90,6 @@ export const RenderSplitView = /*#__PURE__*/memo(({
56
90
  const combinedHistory = useMemo(() => {
57
91
  return [...primaryHistoryToRender, ...secondaryHistory];
58
92
  }, [primaryHistoryToRender, secondaryHistory]);
59
-
60
- // Use primary stack ID for the combined ScreenStack
61
- // (secondary items will animate as if pushed onto this stack)
62
93
  return /*#__PURE__*/_jsx(SplitViewContext.Provider, {
63
94
  value: splitView,
64
95
  children: /*#__PURE__*/_jsx(ScreenStack, {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import { memo, useCallback, useSyncExternalStore } from 'react';
3
+ import { memo, useEffect, useState, startTransition } from 'react';
4
4
  import { ScreenStackItem } from "./ScreenStackItem/index.js";
5
5
  import { ScreenStack } from "./ScreenStack/index.js";
6
6
  import { useRouter } from "./RouterContext.js";
@@ -13,15 +13,19 @@ export const StackRenderer = /*#__PURE__*/memo(({
13
13
  }) => {
14
14
  const router = useRouter();
15
15
  const hasHistoryProp = history != null;
16
- const subscribe = useCallback(cb => {
17
- if (hasHistoryProp) return () => {};
18
- return router.subscribeStack(stackId, cb);
16
+ const [internalHistory, setInternalHistory] = useState(() => hasHistoryProp ? history : router.getStackHistory(stackId));
17
+ useEffect(() => {
18
+ if (hasHistoryProp) return;
19
+ const update = () => {
20
+ startTransition(() => {
21
+ setInternalHistory(router.getStackHistory(stackId));
22
+ });
23
+ };
24
+ const unsub = router.subscribeStack(stackId, update);
25
+ update();
26
+ return unsub;
19
27
  }, [router, stackId, hasHistoryProp]);
20
- const get = useCallback(() => {
21
- if (hasHistoryProp) return history;
22
- return router.getStackHistory(stackId);
23
- }, [router, stackId, hasHistoryProp, history]);
24
- const historyForThisStack = useSyncExternalStore(subscribe, get, get);
28
+ const historyForThisStack = hasHistoryProp ? history : internalHistory;
25
29
  return /*#__PURE__*/_jsx(ScreenStack, {
26
30
  style: [styles.flex, appearance?.screen],
27
31
  children: historyForThisStack.map(item => /*#__PURE__*/_jsx(ScreenStackItem, {
@@ -3,9 +3,9 @@
3
3
  import { StackRenderer } from "../StackRenderer.js";
4
4
  import { TabBarContext } from "./TabBarContext.js";
5
5
  import { useRouter } from "../RouterContext.js";
6
- import { Tabs, ScreenStackItem } from 'react-native-screens';
6
+ import { Tabs } from 'react-native-screens';
7
7
  import { Platform, StyleSheet, View } from 'react-native';
8
- import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo } from 'react';
8
+ import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo, startTransition } from 'react';
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  const isImageSource = value => {
11
11
  if (value == null) return false;
@@ -136,9 +136,17 @@ const TabStackRenderer = /*#__PURE__*/memo(({
136
136
  }) => {
137
137
  const router = useRouter();
138
138
  const stackId = stack.getId();
139
- const subscribe = useCallback(cb => router.subscribeStack(stackId, cb), [router, stackId]);
140
- const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
141
- const history = useSyncExternalStore(subscribe, get, get);
139
+ const [history, setHistory] = useState(() => router.getStackHistory(stackId));
140
+ useEffect(() => {
141
+ const update = () => {
142
+ startTransition(() => {
143
+ setHistory(router.getStackHistory(stackId));
144
+ });
145
+ };
146
+ const unsub = router.subscribeStack(stackId, update);
147
+ update();
148
+ return () => unsub();
149
+ }, [router, stackId]);
142
150
  return /*#__PURE__*/_jsx(StackRenderer, {
143
151
  appearance: appearance,
144
152
  stackId: stackId,
@@ -190,81 +198,54 @@ export const RenderTabBar = /*#__PURE__*/memo(({
190
198
  const tabKey = event.nativeEvent.tabKey;
191
199
  const tabIndex = tabs.findIndex(route => route.tabKey === tabKey);
192
200
  if (tabIndex === -1) return;
193
- const targetTab = tabs[tabIndex];
194
- if (!targetTab) return;
195
- const targetStack = tabBar.stacks[targetTab.tabKey];
196
- const targetNode = tabBar.nodes[targetTab.tabKey];
197
201
 
198
- // Update TabBar UI state
202
+ // Keep native Tabs.Host focus state as the source of truth.
199
203
  if (tabIndex !== index) {
200
204
  tabBar.onIndexChange(tabIndex);
201
205
  }
202
-
203
- // Navigate to the target stack's first route if needed
206
+ }, [tabs, tabBar, index]);
207
+ const seedTabIfNeeded = useCallback(targetTab => {
208
+ if (!targetTab) return;
209
+ const targetStack = tabBar.stacks[targetTab.tabKey];
210
+ const targetNode = tabBar.nodes[targetTab.tabKey];
204
211
  if (targetStack) {
205
212
  const stackId = targetStack.getId();
206
213
  const stackHistory = router.getStackHistory(stackId);
207
- // Only navigate if stack is empty (first visit)
208
214
  if (stackHistory.length === 0) {
209
215
  const firstRoute = targetStack.getFirstRoute();
210
216
  if (firstRoute?.path) {
211
217
  router.navigate(firstRoute.path);
212
218
  }
213
219
  }
214
- } else if (targetNode) {
215
- // For nodes like SplitView, check if we need to seed it
220
+ return;
221
+ }
222
+ if (targetNode) {
216
223
  const nodeId = targetNode.getId?.();
217
- if (nodeId) {
218
- const nodeHistory = router.getStackHistory(nodeId);
219
- if (nodeHistory.length === 0) {
220
- const seed = targetNode.seed?.();
221
- if (seed?.path) {
222
- const prefix = targetTab.tabPrefix ?? '';
223
- const fullPath = prefix && !seed.path.startsWith(prefix) ? `${prefix}${seed.path.startsWith('/') ? '' : '/'}${seed.path}` : seed.path;
224
- router.navigate(fullPath);
225
- }
224
+ if (!nodeId) return;
225
+ const nodeHistory = router.getStackHistory(nodeId);
226
+ if (nodeHistory.length === 0) {
227
+ const seed = targetNode.seed?.();
228
+ if (seed?.path) {
229
+ const prefix = targetTab.tabPrefix ?? '';
230
+ const fullPath = prefix && !seed.path.startsWith(prefix) ? `${prefix}${seed.path.startsWith('/') ? '' : '/'}${seed.path}` : seed.path;
231
+ router.navigate(fullPath);
226
232
  }
227
233
  }
228
234
  }
229
- }, [tabs, tabBar, index, router]);
235
+ }, [tabBar.nodes, tabBar.stacks, router]);
236
+ useEffect(() => {
237
+ seedTabIfNeeded(tabs[index]);
238
+ }, [tabs, index, seedTabIfNeeded]);
230
239
  const onTabPress = useCallback(nextIndex => {
231
240
  const targetTab = tabs[nextIndex];
232
241
  if (!targetTab) return;
233
- const targetStack = tabBar.stacks[targetTab.tabKey];
234
- const targetNode = tabBar.nodes[targetTab.tabKey];
235
242
 
236
243
  // Update TabBar UI state
237
244
  if (nextIndex !== index) {
238
245
  tabBar.onIndexChange(nextIndex);
239
246
  }
240
-
241
- // Navigate to the target stack's first route if needed
242
- if (targetStack) {
243
- const stackId = targetStack.getId();
244
- const stackHistory = router.getStackHistory(stackId);
245
- // Only navigate if stack is empty (first visit)
246
- if (stackHistory.length === 0) {
247
- const firstRoute = targetStack.getFirstRoute();
248
- if (firstRoute?.path) {
249
- router.navigate(firstRoute.path);
250
- }
251
- }
252
- } else if (targetNode) {
253
- // For nodes like SplitView, check if we need to seed it
254
- const nodeId = targetNode.getId?.();
255
- if (nodeId) {
256
- const nodeHistory = router.getStackHistory(nodeId);
257
- if (nodeHistory.length === 0) {
258
- const seed = targetNode.seed?.();
259
- if (seed?.path) {
260
- const prefix = targetTab.tabPrefix ?? '';
261
- const fullPath = prefix && !seed.path.startsWith(prefix) ? `${prefix}${seed.path.startsWith('/') ? '' : '/'}${seed.path}` : seed.path;
262
- router.navigate(fullPath);
263
- }
264
- }
265
- }
266
- }
267
- }, [tabs, tabBar, index, router]);
247
+ seedTabIfNeeded(targetTab);
248
+ }, [tabs, tabBar, index, seedTabIfNeeded]);
268
249
  const containerProps = useMemo(() => ({
269
250
  tabBarBackgroundColor: backgroundColor,
270
251
  tabBarItemTitleFontFamily: title?.fontFamily,
@@ -339,83 +320,67 @@ export const RenderTabBar = /*#__PURE__*/memo(({
339
320
  }
340
321
  }, [tabs, index]);
341
322
  if (CustomTabBar) {
342
- return /*#__PURE__*/_jsx(ScreenStackItem, {
343
- screenId: "root-tabbar",
344
- headerConfig: {
345
- hidden: true
346
- },
347
- style: StyleSheet.absoluteFill,
348
- stackAnimation: "slide_from_right",
349
- children: /*#__PURE__*/_jsxs(TabBarContext.Provider, {
350
- value: tabBar,
351
- children: [/*#__PURE__*/_jsx(View, {
352
- style: styles.flex,
353
- children: tabs.filter(t => visited[t.tabKey]).map(tab => {
354
- const isActive = tab.tabKey === tabs[index]?.tabKey;
355
- const stackForTab = tabBar.stacks[tab.tabKey];
356
- const nodeForTab = tabBar.nodes[tab.tabKey];
357
- const ScreenForTab = tabBar.screens[tab.tabKey];
358
- return /*#__PURE__*/_jsx(View, {
359
- style: [styles.flex, !isActive && styles.hidden],
360
- children: stackForTab ? /*#__PURE__*/_jsx(TabStackRenderer, {
361
- appearance: appearance,
362
- stack: stackForTab
363
- }) : nodeForTab ? /*#__PURE__*/_jsx(TabNodeRenderer, {
364
- appearance: appearance,
365
- node: nodeForTab
366
- }) : ScreenForTab ? /*#__PURE__*/_jsx(ScreenForTab, {}) : null
367
- }, `tab-content-${tab.tabKey}`);
368
- })
369
- }), /*#__PURE__*/_jsx(CustomTabBar, {
370
- onTabPress: onTabPress,
371
- activeIndex: index,
372
- tabs: tabs
373
- })]
374
- })
375
- });
376
- }
377
- return /*#__PURE__*/_jsx(ScreenStackItem, {
378
- screenId: "root-tabbar",
379
- headerConfig: {
380
- hidden: true
381
- },
382
- style: StyleSheet.absoluteFill,
383
- stackAnimation: "slide_from_right",
384
- children: /*#__PURE__*/_jsx(TabBarContext.Provider, {
323
+ return /*#__PURE__*/_jsxs(TabBarContext.Provider, {
385
324
  value: tabBar,
386
- children: /*#__PURE__*/_jsx(Tabs.Host, {
387
- onNativeFocusChange: onNativeFocusChange,
388
- bottomAccessory: config.bottomAccessory,
389
- experimentalControlNavigationStateInJS: config.experimentalControlNavigationStateInJS,
390
- ...containerProps,
391
- children: tabs.map((tab, i) => {
392
- const isFocused = tab.tabKey === tabs[index]?.tabKey;
393
- const stack = tabBar.stacks[tab.tabKey];
394
- const node = tabBar.nodes[tab.tabKey];
395
- const Screen = tabBar.screens[tab.tabKey];
396
- const convertedIcon = tabIcons[i];
397
- const {
398
- icon: _icon,
399
- selectedIcon: _selectedIcon,
400
- tabPrefix: _prefix,
401
- ...tabScreenProps
402
- } = tab;
403
- return /*#__PURE__*/_jsx(Tabs.Screen, {
404
- ...tabScreenProps,
405
- scrollEdgeAppearance: iosAppearance,
406
- standardAppearance: iosAppearance,
407
- isFocused: isFocused,
408
- icon: convertedIcon?.icon,
409
- selectedIcon: convertedIcon?.selectedIcon,
410
- children: stack ? /*#__PURE__*/_jsx(TabStackRenderer, {
325
+ children: [/*#__PURE__*/_jsx(View, {
326
+ style: styles.flex,
327
+ children: tabs.filter(t => visited[t.tabKey]).map(tab => {
328
+ const isActive = tab.tabKey === tabs[index]?.tabKey;
329
+ const stackForTab = tabBar.stacks[tab.tabKey];
330
+ const nodeForTab = tabBar.nodes[tab.tabKey];
331
+ const ScreenForTab = tabBar.screens[tab.tabKey];
332
+ return /*#__PURE__*/_jsx(View, {
333
+ style: [styles.flex, !isActive && styles.hidden],
334
+ children: stackForTab ? /*#__PURE__*/_jsx(TabStackRenderer, {
411
335
  appearance: appearance,
412
- stack: stack
413
- }) : node ? /*#__PURE__*/_jsx(TabNodeRenderer, {
336
+ stack: stackForTab
337
+ }) : nodeForTab ? /*#__PURE__*/_jsx(TabNodeRenderer, {
414
338
  appearance: appearance,
415
- node: node
416
- }) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null
417
- }, tab.tabKey);
339
+ node: nodeForTab
340
+ }) : ScreenForTab ? /*#__PURE__*/_jsx(ScreenForTab, {}) : null
341
+ }, `tab-content-${tab.tabKey}`);
418
342
  })
343
+ }), /*#__PURE__*/_jsx(CustomTabBar, {
344
+ onTabPress: onTabPress,
345
+ activeIndex: index,
346
+ tabs: tabs
347
+ })]
348
+ });
349
+ }
350
+ return /*#__PURE__*/_jsx(TabBarContext.Provider, {
351
+ value: tabBar,
352
+ children: /*#__PURE__*/_jsx(Tabs.Host, {
353
+ onNativeFocusChange: onNativeFocusChange,
354
+ bottomAccessory: config.bottomAccessory,
355
+ experimentalControlNavigationStateInJS: config.experimentalControlNavigationStateInJS,
356
+ ...containerProps,
357
+ children: tabs.map((tab, i) => {
358
+ const isFocused = tab.tabKey === tabs[index]?.tabKey;
359
+ const stack = tabBar.stacks[tab.tabKey];
360
+ const node = tabBar.nodes[tab.tabKey];
361
+ const Screen = tabBar.screens[tab.tabKey];
362
+ const convertedIcon = tabIcons[i];
363
+ const {
364
+ icon: _icon,
365
+ selectedIcon: _selectedIcon,
366
+ tabPrefix: _prefix,
367
+ ...tabScreenProps
368
+ } = tab;
369
+ return /*#__PURE__*/_jsx(Tabs.Screen, {
370
+ ...tabScreenProps,
371
+ scrollEdgeAppearance: iosAppearance,
372
+ standardAppearance: iosAppearance,
373
+ isFocused: isFocused,
374
+ icon: convertedIcon?.icon,
375
+ selectedIcon: convertedIcon?.selectedIcon,
376
+ children: stack ? /*#__PURE__*/_jsx(TabStackRenderer, {
377
+ appearance: appearance,
378
+ stack: stack
379
+ }) : node ? /*#__PURE__*/_jsx(TabNodeRenderer, {
380
+ appearance: appearance,
381
+ node: node
382
+ }) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null
383
+ }, tab.tabKey);
419
384
  })
420
385
  })
421
386
  });
@@ -5,11 +5,27 @@ export interface RenderSplitViewProps {
5
5
  appearance?: NavigationAppearance;
6
6
  }
7
7
  /**
8
- * On native (iPhone), SplitView renders primary and secondary screens
9
- * in a SINGLE ScreenStack to get native push/pop animations.
8
+ * On native, SplitView renders primary and secondary screens in a SINGLE
9
+ * ScreenStack to get native push/pop animations.
10
10
  *
11
11
  * The combined history is: [...primaryHistory, ...secondaryHistory]
12
12
  * This way, navigating from primary to secondary is a native push.
13
+ *
14
+ * On Android (Fabric) react-native-screens manages each ScreenStackItem via
15
+ * an Android Fragment. Fragment lifecycle (onCreateView) is asynchronous,
16
+ * but Fabric's layout pass is synchronous. If a new commit is dispatched
17
+ * while the previous Fragment transaction is still in flight, Fabric tries
18
+ * to insert child views into a Screen whose Fragment isn't attached yet:
19
+ *
20
+ * "addViewAt: Parent Screen does not have its Fragment attached"
21
+ *
22
+ * The fix follows the same pattern used in react-native-screens' own
23
+ * BottomTabsContainer example: subscribe via useEffect and apply updates
24
+ * inside `startTransition`. This marks the update as non-urgent, letting
25
+ * React defer the Fabric commit until pending Fragment transactions complete.
26
+ *
27
+ * See: https://github.com/software-mansion/react-native-screens/blob/ddd1a9e/
28
+ * apps/src/shared/gamma/containers/bottom-tabs/BottomTabsContainer.tsx
13
29
  */
14
30
  export declare const RenderSplitView: import("react").NamedExoticComponent<RenderSplitViewProps>;
15
31
  //# sourceMappingURL=RenderSplitView.native.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigmela/router",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "React Native Router",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",