@sigmela/router 0.3.2 → 0.3.4

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,
@@ -116,9 +124,11 @@ export const RenderDrawer = /*#__PURE__*/memo(({
116
124
  useEffect(() => {
117
125
  const key = tabs[index]?.tabKey;
118
126
  if (key) {
119
- setVisited(prev => prev[key] ? prev : {
120
- ...prev,
121
- [key]: true
127
+ startTransition(() => {
128
+ setVisited(prev => prev[key] ? prev : {
129
+ ...prev,
130
+ [key]: true
131
+ });
122
132
  });
123
133
  }
124
134
  }, [tabs, index]);
@@ -4,13 +4,28 @@ 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
+ startTransition(() => {
15
+ setHistory(EMPTY_HISTORY);
16
+ });
17
+ return;
18
+ }
19
+ const update = () => {
20
+ startTransition(() => {
21
+ setHistory(router.getStackHistory(stackId));
22
+ });
23
+ };
24
+ const unsub = router.subscribeStack(stackId, update);
25
+ update();
26
+ return unsub;
27
+ }, [router, stackId]);
28
+ return history;
14
29
  }
15
30
 
16
31
  /**
@@ -30,8 +45,10 @@ export const Navigation = /*#__PURE__*/memo(({
30
45
  }));
31
46
  useEffect(() => {
32
47
  return router.subscribeRoot(() => {
33
- setRoot({
34
- rootId: router.getRootStackId()
48
+ startTransition(() => {
49
+ setRoot({
50
+ rootId: router.getRootStackId()
51
+ });
35
52
  });
36
53
  });
37
54
  }, [router]);
@@ -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, {
@@ -5,7 +5,7 @@ import { TabBarContext } from "./TabBarContext.js";
5
5
  import { useRouter } from "../RouterContext.js";
6
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,
@@ -168,6 +176,20 @@ export const RenderTabBar = /*#__PURE__*/memo(({
168
176
  index,
169
177
  config
170
178
  } = snapshot;
179
+
180
+ // Local focus state driven by startTransition so that isFocused changes
181
+ // are deferred until the native Fragment lifecycle completes (Android Fabric).
182
+ const [focusedTabKey, setFocusedTabKey] = useState(() => tabs[index]?.tabKey);
183
+
184
+ // Sync focusedTabKey when index changes from external sources (programmatic navigation).
185
+ useEffect(() => {
186
+ const key = tabs[index]?.tabKey;
187
+ if (key && key !== focusedTabKey) {
188
+ startTransition(() => {
189
+ setFocusedTabKey(key);
190
+ });
191
+ }
192
+ }, [tabs, index, focusedTabKey]);
171
193
  const {
172
194
  iconColor,
173
195
  iconColorActive,
@@ -191,7 +213,10 @@ export const RenderTabBar = /*#__PURE__*/memo(({
191
213
  const tabIndex = tabs.findIndex(route => route.tabKey === tabKey);
192
214
  if (tabIndex === -1) return;
193
215
 
194
- // Keep native Tabs.Host focus state as the source of truth.
216
+ // Defer isFocused update so the native Fragment has time to attach.
217
+ startTransition(() => {
218
+ setFocusedTabKey(tabKey);
219
+ });
195
220
  if (tabIndex !== index) {
196
221
  tabBar.onIndexChange(tabIndex);
197
222
  }
@@ -347,7 +372,7 @@ export const RenderTabBar = /*#__PURE__*/memo(({
347
372
  experimentalControlNavigationStateInJS: config.experimentalControlNavigationStateInJS,
348
373
  ...containerProps,
349
374
  children: tabs.map((tab, i) => {
350
- const isFocused = tab.tabKey === tabs[index]?.tabKey;
375
+ const isFocused = tab.tabKey === focusedTabKey;
351
376
  const stack = tabBar.stacks[tab.tabKey];
352
377
  const node = tabBar.nodes[tab.tabKey];
353
378
  const Screen = tabBar.screens[tab.tabKey];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigmela/router",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "React Native Router",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",