@sigmela/router 0.3.5 → 0.3.6
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.
- package/lib/module/Drawer/RenderDrawer.native.js +57 -67
- package/lib/module/Navigation.js +1 -1
- package/lib/module/SplitView/RenderSplitView.native.js +9 -24
- package/lib/module/TabBar/RenderTabBar.native.js +9 -7
- package/lib/typescript/src/SplitView/RenderSplitView.native.d.ts +3 -15
- package/package.json +1 -1
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
import { StackRenderer } from "../StackRenderer.js";
|
|
4
4
|
import { DrawerContext } from "./DrawerContext.js";
|
|
5
5
|
import { useRouter } from "../RouterContext.js";
|
|
6
|
-
import {
|
|
7
|
-
import { Platform, Pressable, StyleSheet, View, Text } from 'react-native';
|
|
6
|
+
import { Pressable, StyleSheet, View, Text } from 'react-native';
|
|
8
7
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
9
8
|
import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo, startTransition } from 'react';
|
|
10
9
|
const EMPTY_HISTORY = [];
|
|
@@ -21,9 +20,8 @@ const DrawerStackRenderer = /*#__PURE__*/memo(({
|
|
|
21
20
|
const router = useRouter();
|
|
22
21
|
const stackId = stack.getId();
|
|
23
22
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
const [history, setHistory] = useState(Platform.OS === 'android' ? EMPTY_HISTORY : () => router.getStackHistory(stackId));
|
|
23
|
+
// Start with empty history — useEffect populates via startTransition.
|
|
24
|
+
const [history, setHistory] = useState(EMPTY_HISTORY);
|
|
27
25
|
useEffect(() => {
|
|
28
26
|
const update = () => {
|
|
29
27
|
startTransition(() => {
|
|
@@ -137,69 +135,61 @@ export const RenderDrawer = /*#__PURE__*/memo(({
|
|
|
137
135
|
}
|
|
138
136
|
}, [tabs, index]);
|
|
139
137
|
const CustomDrawer = config.component;
|
|
140
|
-
return /*#__PURE__*/_jsx(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
onPress: () => onItemPress(i),
|
|
169
|
-
style: [styles.item, isActive && styles.itemActive],
|
|
170
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
171
|
-
style: [styles.itemText, isActive && styles.itemTextActive],
|
|
172
|
-
children: tab.title
|
|
173
|
-
})
|
|
174
|
-
}, tab.tabKey);
|
|
175
|
-
})
|
|
138
|
+
return /*#__PURE__*/_jsx(DrawerContext.Provider, {
|
|
139
|
+
value: drawer,
|
|
140
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
141
|
+
style: styles.container,
|
|
142
|
+
children: [/*#__PURE__*/_jsx(Animated.View, {
|
|
143
|
+
style: [styles.sidebar, {
|
|
144
|
+
width: drawerWidth
|
|
145
|
+
}, drawerStyle],
|
|
146
|
+
children: CustomDrawer ? /*#__PURE__*/_jsx(CustomDrawer, {
|
|
147
|
+
onItemPress: onItemPress,
|
|
148
|
+
activeIndex: index,
|
|
149
|
+
items: tabs,
|
|
150
|
+
isOpen: isOpen,
|
|
151
|
+
onClose: handleOverlayPress
|
|
152
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
153
|
+
style: [styles.sidebarContent, {
|
|
154
|
+
paddingTop: insets.top + 12
|
|
155
|
+
}],
|
|
156
|
+
children: tabs.map((tab, i) => {
|
|
157
|
+
const isActive = i === index;
|
|
158
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
159
|
+
onPress: () => onItemPress(i),
|
|
160
|
+
style: [styles.item, isActive && styles.itemActive],
|
|
161
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
162
|
+
style: [styles.itemText, isActive && styles.itemTextActive],
|
|
163
|
+
children: tab.title
|
|
164
|
+
})
|
|
165
|
+
}, tab.tabKey);
|
|
176
166
|
})
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
},
|
|
200
|
-
})
|
|
201
|
-
})
|
|
202
|
-
})
|
|
167
|
+
})
|
|
168
|
+
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
169
|
+
style: [styles.overlay, overlayStyle],
|
|
170
|
+
children: /*#__PURE__*/_jsx(Pressable, {
|
|
171
|
+
style: StyleSheet.absoluteFill,
|
|
172
|
+
onPress: handleOverlayPress
|
|
173
|
+
})
|
|
174
|
+
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
175
|
+
style: [styles.main, mainStyle],
|
|
176
|
+
children: tabs.filter(t => visited[t.tabKey]).map(tab => {
|
|
177
|
+
const isActive = tab.tabKey === tabs[index]?.tabKey;
|
|
178
|
+
const stackForTab = drawer.stacks[tab.tabKey];
|
|
179
|
+
const nodeForTab = drawer.nodes[tab.tabKey];
|
|
180
|
+
const ScreenForTab = drawer.screens[tab.tabKey];
|
|
181
|
+
return /*#__PURE__*/_jsx(View, {
|
|
182
|
+
style: [styles.flex, !isActive && styles.hidden],
|
|
183
|
+
children: stackForTab ? /*#__PURE__*/_jsx(DrawerStackRenderer, {
|
|
184
|
+
appearance: appearance,
|
|
185
|
+
stack: stackForTab
|
|
186
|
+
}) : nodeForTab ? /*#__PURE__*/_jsx(DrawerNodeRenderer, {
|
|
187
|
+
appearance: appearance,
|
|
188
|
+
node: nodeForTab
|
|
189
|
+
}) : ScreenForTab ? /*#__PURE__*/_jsx(ScreenForTab, {}) : null
|
|
190
|
+
}, `drawer-content-${tab.tabKey}`);
|
|
191
|
+
})
|
|
192
|
+
})]
|
|
203
193
|
})
|
|
204
194
|
});
|
|
205
195
|
});
|
package/lib/module/Navigation.js
CHANGED
|
@@ -8,7 +8,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 [history, setHistory] = useState(
|
|
11
|
+
const [history, setHistory] = useState(EMPTY_HISTORY);
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
if (!stackId) {
|
|
14
14
|
startTransition(() => {
|
|
@@ -5,7 +5,7 @@ import { ScreenStack } from "../ScreenStack/index.js";
|
|
|
5
5
|
import { SplitViewContext } from "./SplitViewContext.js";
|
|
6
6
|
import { useRouter } from "../RouterContext.js";
|
|
7
7
|
import { memo, useMemo, useRef, useState, useEffect, startTransition } from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { StyleSheet } from 'react-native';
|
|
9
9
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
10
|
const EMPTY_HISTORY = [];
|
|
11
11
|
/**
|
|
@@ -15,21 +15,9 @@ const EMPTY_HISTORY = [];
|
|
|
15
15
|
* The combined history is: [...primaryHistory, ...secondaryHistory]
|
|
16
16
|
* This way, navigating from primary to secondary is a native push.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* while the previous Fragment transaction is still in flight, Fabric tries
|
|
22
|
-
* to insert child views into a Screen whose Fragment isn't attached yet:
|
|
23
|
-
*
|
|
24
|
-
* "addViewAt: Parent Screen does not have its Fragment attached"
|
|
25
|
-
*
|
|
26
|
-
* The fix follows the same pattern used in react-native-screens' own
|
|
27
|
-
* BottomTabsContainer example: subscribe via useEffect and apply updates
|
|
28
|
-
* inside `startTransition`. This marks the update as non-urgent, letting
|
|
29
|
-
* React defer the Fabric commit until pending Fragment transactions complete.
|
|
30
|
-
*
|
|
31
|
-
* See: https://github.com/software-mansion/react-native-screens/blob/ddd1a9e/
|
|
32
|
-
* apps/src/shared/gamma/containers/bottom-tabs/BottomTabsContainer.tsx
|
|
18
|
+
* History starts empty and is populated via useEffect + startTransition,
|
|
19
|
+
* following the pattern from react-native-screens' BottomTabsContainer.
|
|
20
|
+
* This lets the parent Screen attach before child ScreenStackItems mount.
|
|
33
21
|
*/
|
|
34
22
|
export const RenderSplitView = /*#__PURE__*/memo(({
|
|
35
23
|
splitView,
|
|
@@ -39,13 +27,10 @@ export const RenderSplitView = /*#__PURE__*/memo(({
|
|
|
39
27
|
const primaryId = splitView.primary.getId();
|
|
40
28
|
const secondaryId = splitView.secondary.getId();
|
|
41
29
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const hydratedRef = useRef(!isAndroid);
|
|
47
|
-
const [primaryHistory, setPrimaryHistory] = useState(isAndroid ? EMPTY_HISTORY : () => router.getStackHistory(primaryId));
|
|
48
|
-
const [secondaryHistory, setSecondaryHistory] = useState(isAndroid ? EMPTY_HISTORY : () => router.getStackHistory(secondaryId));
|
|
30
|
+
// Start with empty history — useEffect populates via startTransition.
|
|
31
|
+
const hydratedRef = useRef(false);
|
|
32
|
+
const [primaryHistory, setPrimaryHistory] = useState(EMPTY_HISTORY);
|
|
33
|
+
const [secondaryHistory, setSecondaryHistory] = useState(EMPTY_HISTORY);
|
|
49
34
|
useEffect(() => {
|
|
50
35
|
hydratedRef.current = true;
|
|
51
36
|
const updatePrimary = () => {
|
|
@@ -71,7 +56,7 @@ export const RenderSplitView = /*#__PURE__*/memo(({
|
|
|
71
56
|
}, [router, primaryId, secondaryId]);
|
|
72
57
|
|
|
73
58
|
// Fallback: if primary is empty, seed with first route.
|
|
74
|
-
//
|
|
59
|
+
// Skip the seed during the initial empty mount (before useEffect
|
|
75
60
|
// hydrates history) so no ScreenStackItems mount before the parent Fragment attaches.
|
|
76
61
|
const primaryHistoryToRender = useMemo(() => {
|
|
77
62
|
if (primaryHistory.length > 0) {
|
|
@@ -138,9 +138,9 @@ const TabStackRenderer = /*#__PURE__*/memo(({
|
|
|
138
138
|
const router = useRouter();
|
|
139
139
|
const stackId = stack.getId();
|
|
140
140
|
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
const [history, setHistory] = useState(
|
|
141
|
+
// Start with empty history — useEffect populates via startTransition.
|
|
142
|
+
// This avoids mounting ScreenStackItems before parent Fragments attach.
|
|
143
|
+
const [history, setHistory] = useState(EMPTY_HISTORY);
|
|
144
144
|
useEffect(() => {
|
|
145
145
|
const update = () => {
|
|
146
146
|
startTransition(() => {
|
|
@@ -149,7 +149,7 @@ const TabStackRenderer = /*#__PURE__*/memo(({
|
|
|
149
149
|
};
|
|
150
150
|
const unsub = router.subscribeStack(stackId, update);
|
|
151
151
|
update();
|
|
152
|
-
return
|
|
152
|
+
return unsub;
|
|
153
153
|
}, [router, stackId]);
|
|
154
154
|
return /*#__PURE__*/_jsx(StackRenderer, {
|
|
155
155
|
appearance: appearance,
|
|
@@ -334,9 +334,11 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
334
334
|
useEffect(() => {
|
|
335
335
|
const key = tabs[index]?.tabKey;
|
|
336
336
|
if (key) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
startTransition(() => {
|
|
338
|
+
setVisited(prev => prev[key] ? prev : {
|
|
339
|
+
...prev,
|
|
340
|
+
[key]: true
|
|
341
|
+
});
|
|
340
342
|
});
|
|
341
343
|
}
|
|
342
344
|
}, [tabs, index]);
|
|
@@ -11,21 +11,9 @@ export interface RenderSplitViewProps {
|
|
|
11
11
|
* The combined history is: [...primaryHistory, ...secondaryHistory]
|
|
12
12
|
* This way, navigating from primary to secondary is a native push.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
14
|
+
* History starts empty and is populated via useEffect + startTransition,
|
|
15
|
+
* following the pattern from react-native-screens' BottomTabsContainer.
|
|
16
|
+
* This lets the parent Screen attach before child ScreenStackItems mount.
|
|
29
17
|
*/
|
|
30
18
|
export declare const RenderSplitView: import("react").NamedExoticComponent<RenderSplitViewProps>;
|
|
31
19
|
//# sourceMappingURL=RenderSplitView.native.d.ts.map
|