@sigmela/router 0.3.0 → 0.3.2
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/README.md +107 -1
- package/lib/module/Drawer/RenderDrawer.native.js +6 -3
- package/lib/module/SplitView/RenderSplitView.native.js +47 -16
- package/lib/module/TabBar/RenderTabBar.native.js +116 -129
- package/lib/module/styles.css +10 -1
- package/lib/typescript/src/SplitView/RenderSplitView.native.d.ts +18 -2
- package/lib/typescript/src/TabBar/TabBar.d.ts +9 -1
- package/lib/typescript/src/types.d.ts +11 -2
- package/package.json +14 -9
package/README.md
CHANGED
|
@@ -8,9 +8,11 @@ This library is **URL-first**: you navigate by **paths** (`/users/42?tab=posts`)
|
|
|
8
8
|
|
|
9
9
|
- **Stacks**: predictable stack-based navigation
|
|
10
10
|
- **Tabs**: `TabBar` with native + web renderers (or custom tab bar)
|
|
11
|
+
- **Drawer**: side-panel navigation (`Drawer`)
|
|
11
12
|
- **Split view**: master/details navigation (`SplitView`)
|
|
12
13
|
- **Modals & sheets**: via `stackPresentation` (`modal`, `sheet`, …)
|
|
13
14
|
- **Controllers**: async/guarded navigation (only present when ready)
|
|
15
|
+
- **Appearance**: global styling via `NavigationAppearance` (tab bar colors, fonts, blur effects, etc.)
|
|
14
16
|
- **Web History integration**: keeps Router state in sync with `pushState`, `replaceState`, `popstate`
|
|
15
17
|
- **Dynamic root**: swap root navigation tree at runtime (`router.setRoot`)
|
|
16
18
|
- **Type-safe hooks**: `useParams`, `useQueryParams`, `useRoute`, `useCurrentRoute`
|
|
@@ -27,7 +29,7 @@ yarn add @sigmela/native-sheet
|
|
|
27
29
|
|
|
28
30
|
- `react`
|
|
29
31
|
- `react-native`
|
|
30
|
-
- `react-native-screens` (>= `4.
|
|
32
|
+
- `react-native-screens` (>= `4.24.0`)
|
|
31
33
|
- `@sigmela/native-sheet` (>= `0.0.1`) — only if you use sheets
|
|
32
34
|
|
|
33
35
|
### Web CSS
|
|
@@ -292,10 +294,45 @@ Key methods:
|
|
|
292
294
|
Notes:
|
|
293
295
|
- Exactly one of `stack`, `node`, `screen` must be provided.
|
|
294
296
|
- Use `prefix` to mount a tab's routes under a base path (e.g. `/mail`).
|
|
297
|
+
- All `TabsScreenProps` from `react-native-screens` are forwarded to native. This includes lifecycle events (`onWillAppear`, `onDidAppear`, `onWillDisappear`, `onDidDisappear`), accessibility props (`testID`, `accessibilityLabel`, `tabBarItemTestID`, `tabBarItemAccessibilityLabel`), `orientation`, `systemItem`, `freezeContents`, `placeholder`, `scrollEdgeEffects`, badge styling, and more.
|
|
298
|
+
|
|
299
|
+
#### `setTabBarConfig()`
|
|
300
|
+
|
|
301
|
+
Runtime tab bar configuration:
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
tabBar.setTabBarConfig({
|
|
305
|
+
bottomAccessory: (environment) => <MiniPlayer layout={environment} />, // iOS 26+
|
|
306
|
+
experimentalControlNavigationStateInJS: true,
|
|
307
|
+
});
|
|
308
|
+
```
|
|
295
309
|
|
|
296
310
|
Web behavior note:
|
|
297
311
|
- The built-in **web** tab bar renderer resets Router history on tab switch (to keep URL and Router state consistent) using `router.reset(firstRoutePath)`.
|
|
298
312
|
|
|
313
|
+
### `Drawer`
|
|
314
|
+
|
|
315
|
+
`Drawer` provides side-panel navigation, similar to `TabBar` but with a slide-out panel.
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
import { Drawer, NavigationStack } from '@sigmela/router';
|
|
319
|
+
|
|
320
|
+
const homeStack = new NavigationStack().addScreen('/', HomeScreen);
|
|
321
|
+
const settingsStack = new NavigationStack().addScreen('/settings', SettingsScreen);
|
|
322
|
+
|
|
323
|
+
const drawer = new Drawer({ width: 280 })
|
|
324
|
+
.addTab({ key: 'home', stack: homeStack, title: 'Home' })
|
|
325
|
+
.addTab({ key: 'settings', stack: settingsStack, title: 'Settings' });
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Key methods:
|
|
329
|
+
- `addTab({ key, stack?, node?, screen?, prefix?, title?, icon?, ... })`
|
|
330
|
+
- `open()`, `close()`, `toggle()` — manage drawer state
|
|
331
|
+
- `getIsOpen()` — current open state
|
|
332
|
+
- `subscribeOpenState(listener)` — subscribe to open/close changes
|
|
333
|
+
- `onIndexChange(index)` — switch active tab
|
|
334
|
+
- `setBadge(index, badge | null)`
|
|
335
|
+
|
|
299
336
|
### `SplitView`
|
|
300
337
|
|
|
301
338
|
`SplitView` renders **two stacks**: `primary` and `secondary`.
|
|
@@ -350,6 +387,54 @@ stack.addScreen('/users/:userId', UserDetails);
|
|
|
350
387
|
|
|
351
388
|
If you never call `present()`, the screen is not pushed/replaced.
|
|
352
389
|
|
|
390
|
+
## Appearance
|
|
391
|
+
|
|
392
|
+
Pass `NavigationAppearance` to `<Navigation>` to customize styling globally:
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
import { Navigation, type NavigationAppearance } from '@sigmela/router';
|
|
396
|
+
|
|
397
|
+
const appearance: NavigationAppearance = {
|
|
398
|
+
tabBar: {
|
|
399
|
+
backgroundColor: '#ffffff',
|
|
400
|
+
iconColor: '#999999',
|
|
401
|
+
iconColorActive: '#007AFF',
|
|
402
|
+
badgeBackgroundColor: '#FF3B30',
|
|
403
|
+
iOSShadowColor: '#00000020',
|
|
404
|
+
title: {
|
|
405
|
+
fontFamily: 'Inter',
|
|
406
|
+
fontSize: 10,
|
|
407
|
+
color: '#999999',
|
|
408
|
+
activeColor: '#007AFF',
|
|
409
|
+
activeFontSize: 12, // Android: active tab title font size
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
// Android-specific
|
|
413
|
+
androidActiveIndicatorEnabled: true,
|
|
414
|
+
androidActiveIndicatorColor: '#007AFF20',
|
|
415
|
+
androidRippleColor: '#007AFF10',
|
|
416
|
+
labelVisibilityMode: 'labeled',
|
|
417
|
+
|
|
418
|
+
// Tab bar behavior
|
|
419
|
+
hidden: false, // hide/show the tab bar
|
|
420
|
+
tintColor: '#007AFF', // iOS: selected tab tint + glow color
|
|
421
|
+
controllerMode: 'automatic', // iOS 18+: 'automatic' | 'tabBar' | 'tabSidebar'
|
|
422
|
+
minimizeBehavior: 'automatic', // iOS 26+: 'automatic' | 'never' | 'onScrollDown' | 'onScrollUp'
|
|
423
|
+
nativeContainerBackgroundColor: '#fff', // native container background
|
|
424
|
+
iOSBlurEffect: 'systemDefault', // iOS: tab bar blur effect
|
|
425
|
+
},
|
|
426
|
+
header: { /* ScreenStackHeaderConfigProps */ },
|
|
427
|
+
sheet: {
|
|
428
|
+
cornerRadius: 16,
|
|
429
|
+
backgroundColor: '#ffffff',
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
export default function App() {
|
|
434
|
+
return <Navigation router={router} appearance={appearance} />;
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
353
438
|
## Hooks
|
|
354
439
|
|
|
355
440
|
### `useRouter()`
|
|
@@ -410,6 +495,27 @@ function ScreenInsideTabs() {
|
|
|
410
495
|
}
|
|
411
496
|
```
|
|
412
497
|
|
|
498
|
+
### `useTabBarHeight()`
|
|
499
|
+
|
|
500
|
+
Returns the tab bar height constant (`57`). Useful for bottom padding.
|
|
501
|
+
|
|
502
|
+
### `useDrawer()`
|
|
503
|
+
|
|
504
|
+
Returns the nearest `Drawer` from context (only inside drawer screens).
|
|
505
|
+
|
|
506
|
+
```tsx
|
|
507
|
+
import { useDrawer } from '@sigmela/router';
|
|
508
|
+
|
|
509
|
+
function ScreenInsideDrawer() {
|
|
510
|
+
const drawer = useDrawer();
|
|
511
|
+
return <Button title="Open menu" onPress={() => drawer.open()} />;
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### `useSplitView()`
|
|
516
|
+
|
|
517
|
+
Returns the nearest `SplitView` from context (only inside split view screens).
|
|
518
|
+
|
|
413
519
|
## Web integration
|
|
414
520
|
|
|
415
521
|
### History API syncing
|
|
@@ -5,6 +5,7 @@ import { DrawerContext } from "./DrawerContext.js";
|
|
|
5
5
|
import { useRouter } from "../RouterContext.js";
|
|
6
6
|
import { ScreenStackItem } from 'react-native-screens';
|
|
7
7
|
import { Pressable, StyleSheet, View, Text } from 'react-native';
|
|
8
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
8
9
|
import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo } from 'react';
|
|
9
10
|
import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
|
|
10
11
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -43,6 +44,7 @@ export const RenderDrawer = /*#__PURE__*/memo(({
|
|
|
43
44
|
appearance = {}
|
|
44
45
|
}) => {
|
|
45
46
|
const router = useRouter();
|
|
47
|
+
const insets = useSafeAreaInsets();
|
|
46
48
|
const drawerWidth = drawer.width;
|
|
47
49
|
const subscribe = useCallback(cb => drawer.subscribe(cb), [drawer]);
|
|
48
50
|
const snapshot = useSyncExternalStore(subscribe, drawer.getState, drawer.getState);
|
|
@@ -143,7 +145,9 @@ export const RenderDrawer = /*#__PURE__*/memo(({
|
|
|
143
145
|
isOpen: isOpen,
|
|
144
146
|
onClose: handleOverlayPress
|
|
145
147
|
}) : /*#__PURE__*/_jsx(View, {
|
|
146
|
-
style: styles.sidebarContent,
|
|
148
|
+
style: [styles.sidebarContent, {
|
|
149
|
+
paddingTop: insets.top + 12
|
|
150
|
+
}],
|
|
147
151
|
children: tabs.map((tab, i) => {
|
|
148
152
|
const isActive = i === index;
|
|
149
153
|
return /*#__PURE__*/_jsx(Pressable, {
|
|
@@ -200,8 +204,7 @@ const styles = StyleSheet.create({
|
|
|
200
204
|
backgroundColor: '#ffffff'
|
|
201
205
|
},
|
|
202
206
|
sidebarContent: {
|
|
203
|
-
flex: 1
|
|
204
|
-
paddingTop: 12
|
|
207
|
+
flex: 1
|
|
205
208
|
},
|
|
206
209
|
main: {
|
|
207
210
|
flex: 1,
|
|
@@ -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,
|
|
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
|
|
12
|
-
*
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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, {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { StackRenderer } from "../StackRenderer.js";
|
|
4
4
|
import { TabBarContext } from "./TabBarContext.js";
|
|
5
5
|
import { useRouter } from "../RouterContext.js";
|
|
6
|
-
import {
|
|
6
|
+
import { Tabs } from 'react-native-screens';
|
|
7
7
|
import { Platform, StyleSheet, View } from 'react-native';
|
|
8
8
|
import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo } from 'react';
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -178,91 +178,71 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
178
178
|
title,
|
|
179
179
|
backgroundColor,
|
|
180
180
|
badgeBackgroundColor,
|
|
181
|
-
iOSShadowColor
|
|
181
|
+
iOSShadowColor,
|
|
182
|
+
hidden,
|
|
183
|
+
tintColor,
|
|
184
|
+
controllerMode,
|
|
185
|
+
minimizeBehavior,
|
|
186
|
+
nativeContainerBackgroundColor,
|
|
187
|
+
iOSBlurEffect
|
|
182
188
|
} = appearance?.tabBar ?? {};
|
|
183
189
|
const onNativeFocusChange = useCallback(event => {
|
|
184
190
|
const tabKey = event.nativeEvent.tabKey;
|
|
185
191
|
const tabIndex = tabs.findIndex(route => route.tabKey === tabKey);
|
|
186
192
|
if (tabIndex === -1) return;
|
|
187
|
-
const targetTab = tabs[tabIndex];
|
|
188
|
-
if (!targetTab) return;
|
|
189
|
-
const targetStack = tabBar.stacks[targetTab.tabKey];
|
|
190
|
-
const targetNode = tabBar.nodes[targetTab.tabKey];
|
|
191
193
|
|
|
192
|
-
//
|
|
194
|
+
// Keep native Tabs.Host focus state as the source of truth.
|
|
193
195
|
if (tabIndex !== index) {
|
|
194
196
|
tabBar.onIndexChange(tabIndex);
|
|
195
197
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
}, [tabs, tabBar, index]);
|
|
199
|
+
const seedTabIfNeeded = useCallback(targetTab => {
|
|
200
|
+
if (!targetTab) return;
|
|
201
|
+
const targetStack = tabBar.stacks[targetTab.tabKey];
|
|
202
|
+
const targetNode = tabBar.nodes[targetTab.tabKey];
|
|
198
203
|
if (targetStack) {
|
|
199
204
|
const stackId = targetStack.getId();
|
|
200
205
|
const stackHistory = router.getStackHistory(stackId);
|
|
201
|
-
// Only navigate if stack is empty (first visit)
|
|
202
206
|
if (stackHistory.length === 0) {
|
|
203
207
|
const firstRoute = targetStack.getFirstRoute();
|
|
204
208
|
if (firstRoute?.path) {
|
|
205
209
|
router.navigate(firstRoute.path);
|
|
206
210
|
}
|
|
207
211
|
}
|
|
208
|
-
|
|
209
|
-
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (targetNode) {
|
|
210
215
|
const nodeId = targetNode.getId?.();
|
|
211
|
-
if (nodeId)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
216
|
+
if (!nodeId) return;
|
|
217
|
+
const nodeHistory = router.getStackHistory(nodeId);
|
|
218
|
+
if (nodeHistory.length === 0) {
|
|
219
|
+
const seed = targetNode.seed?.();
|
|
220
|
+
if (seed?.path) {
|
|
221
|
+
const prefix = targetTab.tabPrefix ?? '';
|
|
222
|
+
const fullPath = prefix && !seed.path.startsWith(prefix) ? `${prefix}${seed.path.startsWith('/') ? '' : '/'}${seed.path}` : seed.path;
|
|
223
|
+
router.navigate(fullPath);
|
|
220
224
|
}
|
|
221
225
|
}
|
|
222
226
|
}
|
|
223
|
-
}, [
|
|
227
|
+
}, [tabBar.nodes, tabBar.stacks, router]);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
seedTabIfNeeded(tabs[index]);
|
|
230
|
+
}, [tabs, index, seedTabIfNeeded]);
|
|
224
231
|
const onTabPress = useCallback(nextIndex => {
|
|
225
232
|
const targetTab = tabs[nextIndex];
|
|
226
233
|
if (!targetTab) return;
|
|
227
|
-
const targetStack = tabBar.stacks[targetTab.tabKey];
|
|
228
|
-
const targetNode = tabBar.nodes[targetTab.tabKey];
|
|
229
234
|
|
|
230
235
|
// Update TabBar UI state
|
|
231
236
|
if (nextIndex !== index) {
|
|
232
237
|
tabBar.onIndexChange(nextIndex);
|
|
233
238
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (targetStack) {
|
|
237
|
-
const stackId = targetStack.getId();
|
|
238
|
-
const stackHistory = router.getStackHistory(stackId);
|
|
239
|
-
// Only navigate if stack is empty (first visit)
|
|
240
|
-
if (stackHistory.length === 0) {
|
|
241
|
-
const firstRoute = targetStack.getFirstRoute();
|
|
242
|
-
if (firstRoute?.path) {
|
|
243
|
-
router.navigate(firstRoute.path);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
} else if (targetNode) {
|
|
247
|
-
// For nodes like SplitView, check if we need to seed it
|
|
248
|
-
const nodeId = targetNode.getId?.();
|
|
249
|
-
if (nodeId) {
|
|
250
|
-
const nodeHistory = router.getStackHistory(nodeId);
|
|
251
|
-
if (nodeHistory.length === 0) {
|
|
252
|
-
const seed = targetNode.seed?.();
|
|
253
|
-
if (seed?.path) {
|
|
254
|
-
const prefix = targetTab.tabPrefix ?? '';
|
|
255
|
-
const fullPath = prefix && !seed.path.startsWith(prefix) ? `${prefix}${seed.path.startsWith('/') ? '' : '/'}${seed.path}` : seed.path;
|
|
256
|
-
router.navigate(fullPath);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}, [tabs, tabBar, index, router]);
|
|
239
|
+
seedTabIfNeeded(targetTab);
|
|
240
|
+
}, [tabs, tabBar, index, seedTabIfNeeded]);
|
|
262
241
|
const containerProps = useMemo(() => ({
|
|
263
242
|
tabBarBackgroundColor: backgroundColor,
|
|
264
243
|
tabBarItemTitleFontFamily: title?.fontFamily,
|
|
265
244
|
tabBarItemTitleFontSize: title?.fontSize,
|
|
245
|
+
tabBarItemTitleFontSizeActive: title?.activeFontSize,
|
|
266
246
|
tabBarItemTitleFontWeight: title?.fontWeight,
|
|
267
247
|
tabBarItemTitleFontStyle: title?.fontStyle,
|
|
268
248
|
tabBarItemTitleFontColor: title?.color,
|
|
@@ -272,35 +252,53 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
272
252
|
tabBarItemActiveIndicatorColor: androidActiveIndicatorColor,
|
|
273
253
|
tabBarItemActiveIndicatorEnabled: androidActiveIndicatorEnabled,
|
|
274
254
|
tabBarItemRippleColor: androidRippleColor,
|
|
275
|
-
tabBarItemLabelVisibilityMode: labelVisibilityMode
|
|
276
|
-
|
|
277
|
-
|
|
255
|
+
tabBarItemLabelVisibilityMode: labelVisibilityMode,
|
|
256
|
+
tabBarHidden: hidden,
|
|
257
|
+
tabBarTintColor: tintColor,
|
|
258
|
+
tabBarControllerMode: controllerMode,
|
|
259
|
+
tabBarMinimizeBehavior: minimizeBehavior,
|
|
260
|
+
nativeContainerStyle: nativeContainerBackgroundColor ? {
|
|
261
|
+
backgroundColor: nativeContainerBackgroundColor
|
|
262
|
+
} : undefined
|
|
263
|
+
}), [backgroundColor, title?.fontFamily, title?.fontSize, title?.activeFontSize, title?.fontWeight, title?.fontStyle, title?.color, title?.activeColor, iconColor, iconColorActive, androidActiveIndicatorColor, androidActiveIndicatorEnabled, androidRippleColor, labelVisibilityMode, hidden, tintColor, controllerMode, minimizeBehavior, nativeContainerBackgroundColor]);
|
|
264
|
+
const iosNormalState = useMemo(() => ({
|
|
278
265
|
tabBarItemTitleFontFamily: title?.fontFamily,
|
|
279
266
|
tabBarItemTitleFontSize: title?.fontSize,
|
|
280
267
|
tabBarItemTitleFontWeight: title?.fontWeight,
|
|
281
268
|
tabBarItemTitleFontStyle: title?.fontStyle,
|
|
282
269
|
tabBarItemTitleFontColor: title?.color,
|
|
283
270
|
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
284
|
-
tabBarItemTitleFontColorActive: title?.activeColor ?? title?.color,
|
|
285
|
-
tabBarItemIconColorActive: iconColorActive,
|
|
286
271
|
tabBarItemIconColor: iconColor
|
|
272
|
+
}), [title?.fontFamily, title?.fontSize, title?.fontWeight, title?.fontStyle, title?.color, badgeBackgroundColor, iconColor]);
|
|
273
|
+
const iosSelectedState = useMemo(() => ({
|
|
274
|
+
tabBarItemTitleFontFamily: title?.fontFamily,
|
|
275
|
+
tabBarItemTitleFontSize: title?.fontSize,
|
|
276
|
+
tabBarItemTitleFontWeight: title?.fontWeight,
|
|
277
|
+
tabBarItemTitleFontStyle: title?.fontStyle,
|
|
278
|
+
tabBarItemTitleFontColor: title?.activeColor ?? title?.color,
|
|
279
|
+
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
280
|
+
tabBarItemIconColor: iconColorActive ?? iconColor
|
|
287
281
|
}), [title?.fontFamily, title?.fontSize, title?.fontWeight, title?.fontStyle, title?.color, title?.activeColor, badgeBackgroundColor, iconColorActive, iconColor]);
|
|
288
282
|
const iosAppearance = useMemo(() => Platform.select({
|
|
289
283
|
default: undefined,
|
|
290
284
|
ios: {
|
|
291
285
|
tabBarBackgroundColor: backgroundColor,
|
|
292
286
|
tabBarShadowColor: iOSShadowColor,
|
|
287
|
+
tabBarBlurEffect: iOSBlurEffect,
|
|
293
288
|
compactInline: {
|
|
294
|
-
normal:
|
|
289
|
+
normal: iosNormalState,
|
|
290
|
+
selected: iosSelectedState
|
|
295
291
|
},
|
|
296
292
|
stacked: {
|
|
297
|
-
normal:
|
|
293
|
+
normal: iosNormalState,
|
|
294
|
+
selected: iosSelectedState
|
|
298
295
|
},
|
|
299
296
|
inline: {
|
|
300
|
-
normal:
|
|
297
|
+
normal: iosNormalState,
|
|
298
|
+
selected: iosSelectedState
|
|
301
299
|
}
|
|
302
300
|
}
|
|
303
|
-
}), [backgroundColor, iOSShadowColor,
|
|
301
|
+
}), [backgroundColor, iOSShadowColor, iOSBlurEffect, iosNormalState, iosSelectedState]);
|
|
304
302
|
const CustomTabBar = config.component;
|
|
305
303
|
const tabIcons = useMemo(() => tabs.map(tab => getTabIcon(tab)), [tabs]);
|
|
306
304
|
const [visited, setVisited] = useState({});
|
|
@@ -314,78 +312,67 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
314
312
|
}
|
|
315
313
|
}, [tabs, index]);
|
|
316
314
|
if (CustomTabBar) {
|
|
317
|
-
return /*#__PURE__*/
|
|
318
|
-
screenId: "root-tabbar",
|
|
319
|
-
headerConfig: {
|
|
320
|
-
hidden: true
|
|
321
|
-
},
|
|
322
|
-
style: StyleSheet.absoluteFill,
|
|
323
|
-
stackAnimation: "slide_from_right",
|
|
324
|
-
children: /*#__PURE__*/_jsxs(TabBarContext.Provider, {
|
|
325
|
-
value: tabBar,
|
|
326
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
327
|
-
style: styles.flex,
|
|
328
|
-
children: tabs.filter(t => visited[t.tabKey]).map(tab => {
|
|
329
|
-
const isActive = tab.tabKey === tabs[index]?.tabKey;
|
|
330
|
-
const stackForTab = tabBar.stacks[tab.tabKey];
|
|
331
|
-
const nodeForTab = tabBar.nodes[tab.tabKey];
|
|
332
|
-
const ScreenForTab = tabBar.screens[tab.tabKey];
|
|
333
|
-
return /*#__PURE__*/_jsx(View, {
|
|
334
|
-
style: [styles.flex, !isActive && styles.hidden],
|
|
335
|
-
children: stackForTab ? /*#__PURE__*/_jsx(TabStackRenderer, {
|
|
336
|
-
appearance: appearance,
|
|
337
|
-
stack: stackForTab
|
|
338
|
-
}) : nodeForTab ? /*#__PURE__*/_jsx(TabNodeRenderer, {
|
|
339
|
-
appearance: appearance,
|
|
340
|
-
node: nodeForTab
|
|
341
|
-
}) : ScreenForTab ? /*#__PURE__*/_jsx(ScreenForTab, {}) : null
|
|
342
|
-
}, `tab-content-${tab.tabKey}`);
|
|
343
|
-
})
|
|
344
|
-
}), /*#__PURE__*/_jsx(CustomTabBar, {
|
|
345
|
-
onTabPress: onTabPress,
|
|
346
|
-
activeIndex: index,
|
|
347
|
-
tabs: tabs
|
|
348
|
-
})]
|
|
349
|
-
})
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
return /*#__PURE__*/_jsx(ScreenStackItem, {
|
|
353
|
-
screenId: "root-tabbar",
|
|
354
|
-
headerConfig: {
|
|
355
|
-
hidden: true
|
|
356
|
-
},
|
|
357
|
-
style: StyleSheet.absoluteFill,
|
|
358
|
-
stackAnimation: "slide_from_right",
|
|
359
|
-
children: /*#__PURE__*/_jsx(TabBarContext.Provider, {
|
|
315
|
+
return /*#__PURE__*/_jsxs(TabBarContext.Provider, {
|
|
360
316
|
value: tabBar,
|
|
361
|
-
children: /*#__PURE__*/_jsx(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
scrollEdgeAppearance: iosAppearance,
|
|
372
|
-
standardAppearance: iosAppearance,
|
|
373
|
-
isFocused: isFocused,
|
|
374
|
-
tabKey: tab.tabKey,
|
|
375
|
-
title: tab.title,
|
|
376
|
-
badgeValue: tab.badgeValue,
|
|
377
|
-
specialEffects: tab.specialEffects,
|
|
378
|
-
icon: icon?.icon,
|
|
379
|
-
selectedIcon: icon?.selectedIcon,
|
|
380
|
-
children: stack ? /*#__PURE__*/_jsx(TabStackRenderer, {
|
|
317
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
318
|
+
style: styles.flex,
|
|
319
|
+
children: tabs.filter(t => visited[t.tabKey]).map(tab => {
|
|
320
|
+
const isActive = tab.tabKey === tabs[index]?.tabKey;
|
|
321
|
+
const stackForTab = tabBar.stacks[tab.tabKey];
|
|
322
|
+
const nodeForTab = tabBar.nodes[tab.tabKey];
|
|
323
|
+
const ScreenForTab = tabBar.screens[tab.tabKey];
|
|
324
|
+
return /*#__PURE__*/_jsx(View, {
|
|
325
|
+
style: [styles.flex, !isActive && styles.hidden],
|
|
326
|
+
children: stackForTab ? /*#__PURE__*/_jsx(TabStackRenderer, {
|
|
381
327
|
appearance: appearance,
|
|
382
|
-
stack:
|
|
383
|
-
}) :
|
|
328
|
+
stack: stackForTab
|
|
329
|
+
}) : nodeForTab ? /*#__PURE__*/_jsx(TabNodeRenderer, {
|
|
384
330
|
appearance: appearance,
|
|
385
|
-
node:
|
|
386
|
-
}) :
|
|
387
|
-
}, tab.tabKey);
|
|
331
|
+
node: nodeForTab
|
|
332
|
+
}) : ScreenForTab ? /*#__PURE__*/_jsx(ScreenForTab, {}) : null
|
|
333
|
+
}, `tab-content-${tab.tabKey}`);
|
|
388
334
|
})
|
|
335
|
+
}), /*#__PURE__*/_jsx(CustomTabBar, {
|
|
336
|
+
onTabPress: onTabPress,
|
|
337
|
+
activeIndex: index,
|
|
338
|
+
tabs: tabs
|
|
339
|
+
})]
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return /*#__PURE__*/_jsx(TabBarContext.Provider, {
|
|
343
|
+
value: tabBar,
|
|
344
|
+
children: /*#__PURE__*/_jsx(Tabs.Host, {
|
|
345
|
+
onNativeFocusChange: onNativeFocusChange,
|
|
346
|
+
bottomAccessory: config.bottomAccessory,
|
|
347
|
+
experimentalControlNavigationStateInJS: config.experimentalControlNavigationStateInJS,
|
|
348
|
+
...containerProps,
|
|
349
|
+
children: tabs.map((tab, i) => {
|
|
350
|
+
const isFocused = tab.tabKey === tabs[index]?.tabKey;
|
|
351
|
+
const stack = tabBar.stacks[tab.tabKey];
|
|
352
|
+
const node = tabBar.nodes[tab.tabKey];
|
|
353
|
+
const Screen = tabBar.screens[tab.tabKey];
|
|
354
|
+
const convertedIcon = tabIcons[i];
|
|
355
|
+
const {
|
|
356
|
+
icon: _icon,
|
|
357
|
+
selectedIcon: _selectedIcon,
|
|
358
|
+
tabPrefix: _prefix,
|
|
359
|
+
...tabScreenProps
|
|
360
|
+
} = tab;
|
|
361
|
+
return /*#__PURE__*/_jsx(Tabs.Screen, {
|
|
362
|
+
...tabScreenProps,
|
|
363
|
+
scrollEdgeAppearance: iosAppearance,
|
|
364
|
+
standardAppearance: iosAppearance,
|
|
365
|
+
isFocused: isFocused,
|
|
366
|
+
icon: convertedIcon?.icon,
|
|
367
|
+
selectedIcon: convertedIcon?.selectedIcon,
|
|
368
|
+
children: stack ? /*#__PURE__*/_jsx(TabStackRenderer, {
|
|
369
|
+
appearance: appearance,
|
|
370
|
+
stack: stack
|
|
371
|
+
}) : node ? /*#__PURE__*/_jsx(TabNodeRenderer, {
|
|
372
|
+
appearance: appearance,
|
|
373
|
+
node: node
|
|
374
|
+
}) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null
|
|
375
|
+
}, tab.tabKey);
|
|
389
376
|
})
|
|
390
377
|
})
|
|
391
378
|
});
|
package/lib/module/styles.css
CHANGED
|
@@ -1094,9 +1094,18 @@
|
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
1096
|
/* ==================== DESKTOP DRAWER (>= 641px) ==================== */
|
|
1097
|
+
/* Inverted logic: sidebar is visible by default, data-drawer-open='true' hides it.
|
|
1098
|
+
This way toggle() works on both viewports with a single boolean:
|
|
1099
|
+
isOpen=false (default) → mobile: hidden, desktop: visible
|
|
1100
|
+
isOpen=true (toggled) → mobile: visible, desktop: hidden */
|
|
1097
1101
|
@media (min-width: 641px) {
|
|
1098
|
-
/* On desktop, drawer is always visible — data-drawer-open is irrelevant */
|
|
1099
1102
|
.drawer-sidebar {
|
|
1100
1103
|
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
|
1104
|
+
transition: margin-left var(--drawer-transition);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.drawer-container[data-drawer-open='true'] .drawer-sidebar {
|
|
1108
|
+
margin-left: calc(-1 * var(--drawer-width));
|
|
1109
|
+
border-right: none;
|
|
1101
1110
|
}
|
|
1102
1111
|
}
|
|
@@ -5,11 +5,27 @@ export interface RenderSplitViewProps {
|
|
|
5
5
|
appearance?: NavigationAppearance;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* On native
|
|
9
|
-
*
|
|
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
|
|
@@ -4,7 +4,7 @@ import type { ComponentType } from 'react';
|
|
|
4
4
|
import type { TabItem } from '../types';
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import type { NavigationNode, NodeChild, NodeRoute } from '../navigationNode';
|
|
7
|
-
import type { PlatformIcon } from 'react-native-screens';
|
|
7
|
+
import type { PlatformIcon, TabAccessoryComponentFactory } from 'react-native-screens';
|
|
8
8
|
type LegacyIOSIconShape = {
|
|
9
9
|
sfSymbolName: string;
|
|
10
10
|
} | {
|
|
@@ -52,6 +52,14 @@ type TabBarConfig = Omit<InternalTabItem, 'tabKey' | 'key'> & {
|
|
|
52
52
|
* Custom tab bar component (UI). Kept for compatibility.
|
|
53
53
|
*/
|
|
54
54
|
component?: ComponentType<TabBarProps>;
|
|
55
|
+
/**
|
|
56
|
+
* iOS 26+ bottom accessory factory.
|
|
57
|
+
*/
|
|
58
|
+
bottomAccessory?: TabAccessoryComponentFactory;
|
|
59
|
+
/**
|
|
60
|
+
* Experimental: control navigation state in JS.
|
|
61
|
+
*/
|
|
62
|
+
experimentalControlNavigationStateInJS?: boolean;
|
|
55
63
|
};
|
|
56
64
|
type TabBarOptions = {
|
|
57
65
|
component?: ComponentType<TabBarProps>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ColorValue, StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
|
-
import type {
|
|
2
|
+
import type { TabsScreenProps, ScreenProps as RNSScreenProps, ScreenStackHeaderConfigProps, TabBarItemLabelVisibilityMode, TabBarMinimizeBehavior, TabBarControllerMode, TabAccessoryComponentFactory, TabsScreenBlurEffect } from 'react-native-screens';
|
|
3
3
|
export type StackPresentationTypes = 'push' | 'modal' | 'modalRight' | 'transparentModal' | 'containedModal' | 'containedTransparentModal' | 'fullScreenModal' | 'formSheet' | 'pageSheet' | 'sheet';
|
|
4
4
|
/**
|
|
5
5
|
* Presentations that behave like modals (overlay on top of content).
|
|
@@ -9,7 +9,7 @@ export declare const MODAL_LIKE_PRESENTATIONS: ReadonlySet<StackPresentationType
|
|
|
9
9
|
* Check if a presentation type is modal-like (renders as overlay).
|
|
10
10
|
*/
|
|
11
11
|
export declare function isModalLikePresentation(presentation: StackPresentationTypes | undefined): boolean;
|
|
12
|
-
export type TabItem = Omit<
|
|
12
|
+
export type TabItem = Omit<TabsScreenProps, 'isFocused' | 'children'>;
|
|
13
13
|
export type NavigationState<Route extends TabItem> = {
|
|
14
14
|
index: number;
|
|
15
15
|
routes: Route[];
|
|
@@ -98,6 +98,8 @@ export type CompiledRoute = {
|
|
|
98
98
|
};
|
|
99
99
|
export type TabBarConfig = {
|
|
100
100
|
tabBarMinimizeBehavior?: TabBarMinimizeBehavior;
|
|
101
|
+
bottomAccessory?: TabAccessoryComponentFactory;
|
|
102
|
+
experimentalControlNavigationStateInJS?: boolean;
|
|
101
103
|
};
|
|
102
104
|
export type SheetAppearance = {
|
|
103
105
|
androidFullScreenTopInset?: number;
|
|
@@ -115,6 +117,12 @@ export interface NavigationAppearance {
|
|
|
115
117
|
androidRippleColor?: ColorValue;
|
|
116
118
|
labelVisibilityMode?: TabBarItemLabelVisibilityMode;
|
|
117
119
|
iOSShadowColor?: ColorValue;
|
|
120
|
+
hidden?: boolean;
|
|
121
|
+
tintColor?: ColorValue;
|
|
122
|
+
controllerMode?: TabBarControllerMode;
|
|
123
|
+
minimizeBehavior?: TabBarMinimizeBehavior;
|
|
124
|
+
nativeContainerBackgroundColor?: ColorValue;
|
|
125
|
+
iOSBlurEffect?: TabsScreenBlurEffect;
|
|
118
126
|
title: {
|
|
119
127
|
fontFamily?: TextStyle['fontFamily'];
|
|
120
128
|
fontSize?: TextStyle['fontSize'];
|
|
@@ -122,6 +130,7 @@ export interface NavigationAppearance {
|
|
|
122
130
|
fontStyle?: TextStyle['fontStyle'];
|
|
123
131
|
color?: TextStyle['color'];
|
|
124
132
|
activeColor?: TextStyle['color'];
|
|
133
|
+
activeFontSize?: TextStyle['fontSize'];
|
|
125
134
|
};
|
|
126
135
|
};
|
|
127
136
|
screen?: StyleProp<ViewStyle>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigmela/router",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "React Native Router",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
"@eslint/eslintrc": "^3.3.1",
|
|
72
72
|
"@eslint/js": "^9.35.0",
|
|
73
73
|
"@evilmartians/lefthook": "^1.12.3",
|
|
74
|
-
"@react-native/babel-preset": "0.
|
|
75
|
-
"@react-native/eslint-config": "^0.
|
|
74
|
+
"@react-native/babel-preset": "0.83.2",
|
|
75
|
+
"@react-native/eslint-config": "^0.83.2",
|
|
76
76
|
"@release-it/conventional-changelog": "^10.0.1",
|
|
77
77
|
"@sigmela/native-sheet": "^0.0.1",
|
|
78
78
|
"@types/jest": "^29.5.14",
|
|
@@ -83,12 +83,13 @@
|
|
|
83
83
|
"eslint-config-prettier": "^10.1.8",
|
|
84
84
|
"eslint-plugin-prettier": "^5.5.4",
|
|
85
85
|
"jest": "^29.7.0",
|
|
86
|
-
"prettier": "^3.
|
|
87
|
-
"react": "19.
|
|
88
|
-
"react-native": "0.
|
|
89
|
-
"react-native-builder-bob": "^0.40.
|
|
86
|
+
"prettier": "^3.8.1",
|
|
87
|
+
"react": "19.2.0",
|
|
88
|
+
"react-native": "0.83.2",
|
|
89
|
+
"react-native-builder-bob": "^0.40.18",
|
|
90
90
|
"react-native-reanimated": "^4.2.2",
|
|
91
|
-
"react-native-
|
|
91
|
+
"react-native-safe-area-context": "^5.7.0",
|
|
92
|
+
"react-native-screens": "^4.24.0",
|
|
92
93
|
"react-native-worklets": "^0.7.4",
|
|
93
94
|
"release-it": "^19.0.4",
|
|
94
95
|
"typescript": "^5.9.2"
|
|
@@ -98,6 +99,7 @@
|
|
|
98
99
|
"react": "*",
|
|
99
100
|
"react-native": ">=0.72.0",
|
|
100
101
|
"react-native-reanimated": ">=3.0.0",
|
|
102
|
+
"react-native-safe-area-context": ">=4.0.0",
|
|
101
103
|
"react-native-screens": ">=4.18.0"
|
|
102
104
|
},
|
|
103
105
|
"peerDependenciesMeta": {
|
|
@@ -106,6 +108,9 @@
|
|
|
106
108
|
},
|
|
107
109
|
"react-native-reanimated": {
|
|
108
110
|
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"react-native-safe-area-context": {
|
|
113
|
+
"optional": true
|
|
109
114
|
}
|
|
110
115
|
},
|
|
111
116
|
"workspaces": [
|
|
@@ -182,6 +187,6 @@
|
|
|
182
187
|
"nanoid": "^5.1.6",
|
|
183
188
|
"path-to-regexp": "^8.3.0",
|
|
184
189
|
"query-string": "^9.3.1",
|
|
185
|
-
"react-transition-state": "^2.3.
|
|
190
|
+
"react-transition-state": "^2.3.3"
|
|
186
191
|
}
|
|
187
192
|
}
|