@sigmela/router 0.2.7 → 0.3.0
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 +41 -0
- package/lib/module/Drawer/Drawer.js +250 -0
- package/lib/module/Drawer/DrawerContext.js +4 -0
- package/lib/module/Drawer/DrawerIcon.web.js +47 -0
- package/lib/module/Drawer/RenderDrawer.native.js +241 -0
- package/lib/module/Drawer/RenderDrawer.web.js +197 -0
- package/lib/module/Drawer/useDrawer.js +11 -0
- package/lib/module/Navigation.js +4 -2
- package/lib/module/NavigationStack.js +22 -5
- package/lib/module/Router.js +214 -60
- package/lib/module/RouterContext.js +1 -1
- package/lib/module/ScreenStack/ScreenStack.web.js +78 -12
- package/lib/module/ScreenStackItem/ScreenStackItem.js +7 -7
- package/lib/module/ScreenStackItem/ScreenStackItem.web.js +65 -6
- package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +4 -5
- package/lib/module/SplitView/RenderSplitView.web.js +5 -7
- package/lib/module/SplitView/SplitView.js +10 -2
- package/lib/module/StackRenderer.js +10 -4
- package/lib/module/TabBar/RenderTabBar.native.js +10 -9
- package/lib/module/TabBar/RenderTabBar.web.js +25 -2
- package/lib/module/TabBar/TabBar.js +8 -1
- package/lib/module/TabBar/TabIcon.web.js +12 -7
- package/lib/module/index.js +2 -0
- package/lib/module/styles.css +247 -106
- package/lib/typescript/src/Drawer/Drawer.d.ts +100 -0
- package/lib/typescript/src/Drawer/DrawerContext.d.ts +3 -0
- package/lib/typescript/src/Drawer/DrawerIcon.web.d.ts +7 -0
- package/lib/typescript/src/Drawer/RenderDrawer.native.d.ts +8 -0
- package/lib/typescript/src/Drawer/RenderDrawer.web.d.ts +8 -0
- package/lib/typescript/src/Drawer/useDrawer.d.ts +2 -0
- package/lib/typescript/src/NavigationStack.d.ts +6 -4
- package/lib/typescript/src/Router.d.ts +13 -0
- package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +1 -1
- package/lib/typescript/src/ScreenStack/animationHelpers.d.ts +1 -1
- package/lib/typescript/src/SplitView/SplitView.d.ts +2 -0
- package/lib/typescript/src/TabBar/TabBar.d.ts +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/types.d.ts +10 -1
- package/package.json +15 -4
package/README.md
CHANGED
|
@@ -152,6 +152,47 @@ Key methods:
|
|
|
152
152
|
- `addSheet(pathPattern, componentOrStack, options?)` (shorthand for `stackPresentation: 'sheet'`)
|
|
153
153
|
- `addStack(prefixOrStack, maybeStack?)` — compose nested stacks under a prefix
|
|
154
154
|
|
|
155
|
+
#### Provider Context
|
|
156
|
+
|
|
157
|
+
You can wrap an entire stack with a React context provider by passing a `provider` option:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { ThemeProvider } from './theme';
|
|
161
|
+
|
|
162
|
+
const stack = new NavigationStack({
|
|
163
|
+
header: { largeTitle: true },
|
|
164
|
+
provider: ThemeProvider,
|
|
165
|
+
})
|
|
166
|
+
.addScreen('/feed', FeedScreen)
|
|
167
|
+
.addScreen('/feed/:id', FeedItemScreen);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The `provider` component wraps the entire stack renderer, making the context available to all screens in the stack. This is useful for:
|
|
171
|
+
- **Theme providers**: Apply theme context to all screens
|
|
172
|
+
- **Auth providers**: Share authentication state across screens
|
|
173
|
+
- **Localization**: Provide i18n context to the entire stack
|
|
174
|
+
|
|
175
|
+
**Composing multiple providers:**
|
|
176
|
+
|
|
177
|
+
If you need multiple providers, create a composed component:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
const ComposedProvider = ({ children }) => (
|
|
181
|
+
<ThemeProvider>
|
|
182
|
+
<AuthProvider>
|
|
183
|
+
<I18nProvider>
|
|
184
|
+
{children}
|
|
185
|
+
</I18nProvider>
|
|
186
|
+
</AuthProvider>
|
|
187
|
+
</ThemeProvider>
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const stack = new NavigationStack({ provider: ComposedProvider })
|
|
191
|
+
.addScreen('/', HomeScreen);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Important:** The provider should be a stable reference (not an inline arrow function) to avoid unnecessary re-renders.
|
|
195
|
+
|
|
155
196
|
### Modal Stacks (Stack in Stack)
|
|
156
197
|
|
|
157
198
|
You can pass an entire `NavigationStack` to `addModal()` or `addSheet()` to create a multi-screen flow inside a modal:
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { RenderDrawer } from './RenderDrawer';
|
|
5
|
+
export class Drawer {
|
|
6
|
+
screens = {};
|
|
7
|
+
stacks = {};
|
|
8
|
+
nodes = {};
|
|
9
|
+
listeners = new Set();
|
|
10
|
+
openListeners = new Set();
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.drawerId = `drawer-${Math.random().toString(36).slice(2)}`;
|
|
13
|
+
this.width = options.width ?? 280;
|
|
14
|
+
this.state = {
|
|
15
|
+
tabs: [],
|
|
16
|
+
index: options.initialIndex ?? 0,
|
|
17
|
+
config: {
|
|
18
|
+
component: options.component
|
|
19
|
+
},
|
|
20
|
+
isOpen: false
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
getId() {
|
|
24
|
+
return this.drawerId;
|
|
25
|
+
}
|
|
26
|
+
addTab(tab) {
|
|
27
|
+
const sourcesCount = (tab.stack ? 1 : 0) + (tab.node ? 1 : 0) + (tab.screen ? 1 : 0);
|
|
28
|
+
if (sourcesCount !== 1) {
|
|
29
|
+
throw new Error(`Drawer.addTab: exactly one of { stack, node, screen } must be provided (got ${sourcesCount})`);
|
|
30
|
+
}
|
|
31
|
+
const {
|
|
32
|
+
key,
|
|
33
|
+
...rest
|
|
34
|
+
} = tab;
|
|
35
|
+
const nextIndex = this.state.tabs.length;
|
|
36
|
+
const tabKey = key ?? `drawer-item-${nextIndex}`;
|
|
37
|
+
const nextTabs = [...this.state.tabs, {
|
|
38
|
+
tabKey,
|
|
39
|
+
tabPrefix: tab.prefix,
|
|
40
|
+
title: rest.title,
|
|
41
|
+
icon: rest.icon,
|
|
42
|
+
selectedIcon: rest.selectedIcon,
|
|
43
|
+
badgeValue: rest.badgeValue
|
|
44
|
+
}];
|
|
45
|
+
this.setState({
|
|
46
|
+
tabs: nextTabs
|
|
47
|
+
});
|
|
48
|
+
if (tab.stack) {
|
|
49
|
+
this.stacks[tabKey] = tab.stack;
|
|
50
|
+
} else if (tab.node) {
|
|
51
|
+
this.nodes[tabKey] = tab.node;
|
|
52
|
+
} else if (tab.screen) {
|
|
53
|
+
this.screens[tabKey] = tab.screen;
|
|
54
|
+
}
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
setBadge(tabIndex, badge) {
|
|
58
|
+
this.setState({
|
|
59
|
+
tabs: this.state.tabs.map((item, index) => index === tabIndex ? {
|
|
60
|
+
...item,
|
|
61
|
+
badgeValue: badge ?? undefined
|
|
62
|
+
} : item)
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
setTabBarConfig(config) {
|
|
66
|
+
this.setState({
|
|
67
|
+
config: {
|
|
68
|
+
...this.state.config,
|
|
69
|
+
...config
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
onIndexChange(index) {
|
|
74
|
+
this.setState({
|
|
75
|
+
index
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---- Open/Close state ----
|
|
80
|
+
|
|
81
|
+
open() {
|
|
82
|
+
if (!this.state.isOpen) {
|
|
83
|
+
this.setState({
|
|
84
|
+
isOpen: true
|
|
85
|
+
});
|
|
86
|
+
this.notifyOpenListeners(true);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
close() {
|
|
90
|
+
if (this.state.isOpen) {
|
|
91
|
+
this.setState({
|
|
92
|
+
isOpen: false
|
|
93
|
+
});
|
|
94
|
+
this.notifyOpenListeners(false);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
toggle() {
|
|
98
|
+
if (this.state.isOpen) {
|
|
99
|
+
this.close();
|
|
100
|
+
} else {
|
|
101
|
+
this.open();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
getIsOpen() {
|
|
105
|
+
return this.state.isOpen;
|
|
106
|
+
}
|
|
107
|
+
subscribeOpenState(listener) {
|
|
108
|
+
this.openListeners.add(listener);
|
|
109
|
+
return () => {
|
|
110
|
+
this.openListeners.delete(listener);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
notifyOpenListeners(isOpen) {
|
|
114
|
+
for (const listener of Array.from(this.openListeners)) {
|
|
115
|
+
try {
|
|
116
|
+
listener(isOpen);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
if (__DEV__) {
|
|
119
|
+
console.error('[Drawer] openListener error', e);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- Standard TabBar-like methods ----
|
|
126
|
+
|
|
127
|
+
getState = () => {
|
|
128
|
+
return this.state;
|
|
129
|
+
};
|
|
130
|
+
setState(state) {
|
|
131
|
+
this.state = {
|
|
132
|
+
...this.state,
|
|
133
|
+
...state
|
|
134
|
+
};
|
|
135
|
+
this.notifyListeners();
|
|
136
|
+
}
|
|
137
|
+
notifyListeners() {
|
|
138
|
+
for (const listener of Array.from(this.listeners)) {
|
|
139
|
+
try {
|
|
140
|
+
listener();
|
|
141
|
+
} catch (e) {
|
|
142
|
+
if (__DEV__) {
|
|
143
|
+
console.error('[Drawer] listener error', e);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
subscribe(listener) {
|
|
149
|
+
this.listeners.add(listener);
|
|
150
|
+
return () => {
|
|
151
|
+
this.listeners.delete(listener);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
getTabs() {
|
|
155
|
+
return this.state.tabs.slice();
|
|
156
|
+
}
|
|
157
|
+
getInitialIndex() {
|
|
158
|
+
return this.state.index ?? 0;
|
|
159
|
+
}
|
|
160
|
+
getActiveChildId() {
|
|
161
|
+
const activeTab = this.state.tabs[this.state.index];
|
|
162
|
+
if (!activeTab) return undefined;
|
|
163
|
+
const node = this.nodes[activeTab.tabKey] ?? this.stacks[activeTab.tabKey];
|
|
164
|
+
return node?.getId();
|
|
165
|
+
}
|
|
166
|
+
switchToRoute(routeId) {
|
|
167
|
+
const idx = this.findTabIndexByRoute(routeId);
|
|
168
|
+
if (idx === -1) return;
|
|
169
|
+
if (idx === this.state.index) return;
|
|
170
|
+
this.setState({
|
|
171
|
+
index: idx
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
hasRoute(routeId) {
|
|
175
|
+
return this.findTabIndexByRoute(routeId) !== -1;
|
|
176
|
+
}
|
|
177
|
+
setActiveChildByRoute(routeId) {
|
|
178
|
+
this.switchToRoute(routeId);
|
|
179
|
+
}
|
|
180
|
+
getNodeRoutes() {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
getNodeChildren() {
|
|
184
|
+
const children = [];
|
|
185
|
+
for (let idx = 0; idx < this.state.tabs.length; idx++) {
|
|
186
|
+
const tab = this.state.tabs[idx];
|
|
187
|
+
const node = tab ? this.nodes[tab.tabKey] ?? this.stacks[tab.tabKey] : undefined;
|
|
188
|
+
if (node) {
|
|
189
|
+
children.push({
|
|
190
|
+
prefix: tab?.tabPrefix ?? '',
|
|
191
|
+
node,
|
|
192
|
+
onMatch: () => this.onIndexChange(idx)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return children;
|
|
197
|
+
}
|
|
198
|
+
getRenderer() {
|
|
199
|
+
const drawerInstance = this;
|
|
200
|
+
return function DrawerScreen(props) {
|
|
201
|
+
return /*#__PURE__*/React.createElement(RenderDrawer, {
|
|
202
|
+
drawer: drawerInstance,
|
|
203
|
+
appearance: props?.appearance
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
seed() {
|
|
208
|
+
const activeTab = this.state.tabs[this.state.index];
|
|
209
|
+
if (!activeTab) return null;
|
|
210
|
+
const node = this.nodes[activeTab.tabKey];
|
|
211
|
+
if (node) {
|
|
212
|
+
return node.seed?.() ?? null;
|
|
213
|
+
}
|
|
214
|
+
const stack = this.stacks[activeTab.tabKey];
|
|
215
|
+
if (!stack) return null;
|
|
216
|
+
const firstRoute = stack.getFirstRoute();
|
|
217
|
+
if (!firstRoute) return null;
|
|
218
|
+
return {
|
|
219
|
+
routeId: firstRoute.routeId,
|
|
220
|
+
path: firstRoute.path,
|
|
221
|
+
stackId: stack.getId()
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
findTabIndexByRoute(routeId) {
|
|
225
|
+
for (let i = 0; i < this.state.tabs.length; i++) {
|
|
226
|
+
const tab = this.state.tabs[i];
|
|
227
|
+
const node = tab ? this.nodes[tab.tabKey] ?? this.stacks[tab.tabKey] : undefined;
|
|
228
|
+
if (!node) continue;
|
|
229
|
+
const hasRoute = this.nodeHasRoute(node, routeId);
|
|
230
|
+
if (hasRoute) {
|
|
231
|
+
return i;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return -1;
|
|
235
|
+
}
|
|
236
|
+
nodeHasRoute(node, routeId) {
|
|
237
|
+
const routes = node.getNodeRoutes();
|
|
238
|
+
for (const r of routes) {
|
|
239
|
+
if (r.routeId === routeId) return true;
|
|
240
|
+
if (r.childNode) {
|
|
241
|
+
if (this.nodeHasRoute(r.childNode, routeId)) return true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const children = node.getNodeChildren();
|
|
245
|
+
for (const child of children) {
|
|
246
|
+
if (this.nodeHasRoute(child.node, routeId)) return true;
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { memo, useMemo } from 'react';
|
|
4
|
+
import { Image } from 'react-native';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
const resolveImageUri = source => {
|
|
7
|
+
if (!source) return undefined;
|
|
8
|
+
const resolved = typeof Image.resolveAssetSource === 'function' ? Image.resolveAssetSource(source) : undefined;
|
|
9
|
+
if (resolved?.uri) return resolved.uri;
|
|
10
|
+
if (Array.isArray(source)) {
|
|
11
|
+
const first = source[0];
|
|
12
|
+
if (first && typeof first === 'object' && 'uri' in first) return first.uri;
|
|
13
|
+
}
|
|
14
|
+
if (typeof source === 'object' && source && 'uri' in source) return source.uri;
|
|
15
|
+
return undefined;
|
|
16
|
+
};
|
|
17
|
+
export const DrawerIcon = /*#__PURE__*/memo(({
|
|
18
|
+
source,
|
|
19
|
+
tintColor
|
|
20
|
+
}) => {
|
|
21
|
+
const iconUri = resolveImageUri(source);
|
|
22
|
+
const useMask = Boolean(tintColor && iconUri);
|
|
23
|
+
const maskStyle = useMemo(() => {
|
|
24
|
+
if (!useMask || !iconUri) return undefined;
|
|
25
|
+
return {
|
|
26
|
+
backgroundColor: tintColor,
|
|
27
|
+
WebkitMaskImage: `url(${iconUri})`,
|
|
28
|
+
WebkitMaskSize: 'contain',
|
|
29
|
+
WebkitMaskRepeat: 'no-repeat',
|
|
30
|
+
WebkitMaskPosition: 'center',
|
|
31
|
+
maskImage: `url(${iconUri})`,
|
|
32
|
+
maskSize: 'contain',
|
|
33
|
+
maskRepeat: 'no-repeat',
|
|
34
|
+
maskPosition: 'center',
|
|
35
|
+
width: '100%',
|
|
36
|
+
height: '100%'
|
|
37
|
+
};
|
|
38
|
+
}, [tintColor, iconUri, useMask]);
|
|
39
|
+
if (maskStyle) {
|
|
40
|
+
return /*#__PURE__*/_jsx("div", {
|
|
41
|
+
style: maskStyle
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return /*#__PURE__*/_jsx(Image, {
|
|
45
|
+
source: source
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { StackRenderer } from "../StackRenderer.js";
|
|
4
|
+
import { DrawerContext } from "./DrawerContext.js";
|
|
5
|
+
import { useRouter } from "../RouterContext.js";
|
|
6
|
+
import { ScreenStackItem } from 'react-native-screens';
|
|
7
|
+
import { Pressable, StyleSheet, View, Text } from 'react-native';
|
|
8
|
+
import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo } from 'react';
|
|
9
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
const TIMING_CONFIG = {
|
|
12
|
+
duration: 300,
|
|
13
|
+
easing: Easing.bezier(0.22, 0.61, 0.36, 1)
|
|
14
|
+
};
|
|
15
|
+
const DrawerStackRenderer = /*#__PURE__*/memo(({
|
|
16
|
+
stack,
|
|
17
|
+
appearance
|
|
18
|
+
}) => {
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
const stackId = stack.getId();
|
|
21
|
+
const subscribe = useCallback(cb => router.subscribeStack(stackId, cb), [router, stackId]);
|
|
22
|
+
const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
|
|
23
|
+
const history = useSyncExternalStore(subscribe, get, get);
|
|
24
|
+
return /*#__PURE__*/_jsx(StackRenderer, {
|
|
25
|
+
appearance: appearance,
|
|
26
|
+
stackId: stackId,
|
|
27
|
+
history: history
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
DrawerStackRenderer.displayName = 'DrawerStackRenderer';
|
|
31
|
+
const DrawerNodeRenderer = /*#__PURE__*/memo(({
|
|
32
|
+
node,
|
|
33
|
+
appearance
|
|
34
|
+
}) => {
|
|
35
|
+
const Renderer = useMemo(() => node.getRenderer(), [node]);
|
|
36
|
+
return /*#__PURE__*/_jsx(Renderer, {
|
|
37
|
+
appearance: appearance
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
DrawerNodeRenderer.displayName = 'DrawerNodeRenderer';
|
|
41
|
+
export const RenderDrawer = /*#__PURE__*/memo(({
|
|
42
|
+
drawer,
|
|
43
|
+
appearance = {}
|
|
44
|
+
}) => {
|
|
45
|
+
const router = useRouter();
|
|
46
|
+
const drawerWidth = drawer.width;
|
|
47
|
+
const subscribe = useCallback(cb => drawer.subscribe(cb), [drawer]);
|
|
48
|
+
const snapshot = useSyncExternalStore(subscribe, drawer.getState, drawer.getState);
|
|
49
|
+
const {
|
|
50
|
+
tabs,
|
|
51
|
+
index,
|
|
52
|
+
config,
|
|
53
|
+
isOpen
|
|
54
|
+
} = snapshot;
|
|
55
|
+
|
|
56
|
+
// Reanimated shared value for drawer position
|
|
57
|
+
const translateX = useSharedValue(0);
|
|
58
|
+
const overlayOpacity = useSharedValue(0);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
translateX.value = withTiming(isOpen ? drawerWidth : 0, TIMING_CONFIG);
|
|
61
|
+
overlayOpacity.value = withTiming(isOpen ? 0.3 : 0, TIMING_CONFIG);
|
|
62
|
+
}, [isOpen, drawerWidth, translateX, overlayOpacity]);
|
|
63
|
+
const drawerStyle = useAnimatedStyle(() => ({
|
|
64
|
+
transform: [{
|
|
65
|
+
translateX: translateX.value - drawerWidth
|
|
66
|
+
}]
|
|
67
|
+
}));
|
|
68
|
+
const mainStyle = useAnimatedStyle(() => ({
|
|
69
|
+
transform: [{
|
|
70
|
+
translateX: translateX.value
|
|
71
|
+
}]
|
|
72
|
+
}));
|
|
73
|
+
const overlayStyle = useAnimatedStyle(() => ({
|
|
74
|
+
opacity: overlayOpacity.value,
|
|
75
|
+
pointerEvents: isOpen ? 'auto' : 'none'
|
|
76
|
+
}));
|
|
77
|
+
const onItemPress = useCallback(nextIndex => {
|
|
78
|
+
const targetTab = tabs[nextIndex];
|
|
79
|
+
if (!targetTab) return;
|
|
80
|
+
const targetStack = drawer.stacks[targetTab.tabKey];
|
|
81
|
+
const targetNode = drawer.nodes[targetTab.tabKey];
|
|
82
|
+
if (nextIndex !== index) {
|
|
83
|
+
drawer.onIndexChange(nextIndex);
|
|
84
|
+
}
|
|
85
|
+
if (targetStack) {
|
|
86
|
+
const stackId = targetStack.getId();
|
|
87
|
+
const stackHistory = router.getStackHistory(stackId);
|
|
88
|
+
if (stackHistory.length === 0) {
|
|
89
|
+
const firstRoute = targetStack.getFirstRoute();
|
|
90
|
+
if (firstRoute?.path) {
|
|
91
|
+
router.navigate(firstRoute.path);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else if (targetNode) {
|
|
95
|
+
const nodeId = targetNode.getId?.();
|
|
96
|
+
if (nodeId) {
|
|
97
|
+
const nodeHistory = router.getStackHistory(nodeId);
|
|
98
|
+
if (nodeHistory.length === 0) {
|
|
99
|
+
const seed = targetNode.seed?.();
|
|
100
|
+
if (seed?.path) {
|
|
101
|
+
const prefix = targetTab.tabPrefix ?? '';
|
|
102
|
+
const fullPath = prefix && !seed.path.startsWith(prefix) ? `${prefix}${seed.path.startsWith('/') ? '' : '/'}${seed.path}` : seed.path;
|
|
103
|
+
router.navigate(fullPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
drawer.close();
|
|
109
|
+
}, [tabs, drawer, index, router]);
|
|
110
|
+
const handleOverlayPress = useCallback(() => {
|
|
111
|
+
drawer.close();
|
|
112
|
+
}, [drawer]);
|
|
113
|
+
const [visited, setVisited] = useState({});
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const key = tabs[index]?.tabKey;
|
|
116
|
+
if (key) {
|
|
117
|
+
setVisited(prev => prev[key] ? prev : {
|
|
118
|
+
...prev,
|
|
119
|
+
[key]: true
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}, [tabs, index]);
|
|
123
|
+
const CustomDrawer = config.component;
|
|
124
|
+
return /*#__PURE__*/_jsx(ScreenStackItem, {
|
|
125
|
+
screenId: "root-drawer",
|
|
126
|
+
headerConfig: {
|
|
127
|
+
hidden: true
|
|
128
|
+
},
|
|
129
|
+
style: StyleSheet.absoluteFill,
|
|
130
|
+
stackAnimation: "slide_from_right",
|
|
131
|
+
children: /*#__PURE__*/_jsx(DrawerContext.Provider, {
|
|
132
|
+
value: drawer,
|
|
133
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
134
|
+
style: styles.container,
|
|
135
|
+
children: [/*#__PURE__*/_jsx(Animated.View, {
|
|
136
|
+
style: [styles.sidebar, {
|
|
137
|
+
width: drawerWidth
|
|
138
|
+
}, drawerStyle],
|
|
139
|
+
children: CustomDrawer ? /*#__PURE__*/_jsx(CustomDrawer, {
|
|
140
|
+
onItemPress: onItemPress,
|
|
141
|
+
activeIndex: index,
|
|
142
|
+
items: tabs,
|
|
143
|
+
isOpen: isOpen,
|
|
144
|
+
onClose: handleOverlayPress
|
|
145
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
146
|
+
style: styles.sidebarContent,
|
|
147
|
+
children: tabs.map((tab, i) => {
|
|
148
|
+
const isActive = i === index;
|
|
149
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
150
|
+
onPress: () => onItemPress(i),
|
|
151
|
+
style: [styles.item, isActive && styles.itemActive],
|
|
152
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
153
|
+
style: [styles.itemText, isActive && styles.itemTextActive],
|
|
154
|
+
children: tab.title
|
|
155
|
+
})
|
|
156
|
+
}, tab.tabKey);
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
160
|
+
style: [styles.overlay, overlayStyle],
|
|
161
|
+
children: /*#__PURE__*/_jsx(Pressable, {
|
|
162
|
+
style: StyleSheet.absoluteFill,
|
|
163
|
+
onPress: handleOverlayPress
|
|
164
|
+
})
|
|
165
|
+
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
166
|
+
style: [styles.main, mainStyle],
|
|
167
|
+
children: tabs.filter(t => visited[t.tabKey]).map(tab => {
|
|
168
|
+
const isActive = tab.tabKey === tabs[index]?.tabKey;
|
|
169
|
+
const stackForTab = drawer.stacks[tab.tabKey];
|
|
170
|
+
const nodeForTab = drawer.nodes[tab.tabKey];
|
|
171
|
+
const ScreenForTab = drawer.screens[tab.tabKey];
|
|
172
|
+
return /*#__PURE__*/_jsx(View, {
|
|
173
|
+
style: [styles.flex, !isActive && styles.hidden],
|
|
174
|
+
children: stackForTab ? /*#__PURE__*/_jsx(DrawerStackRenderer, {
|
|
175
|
+
appearance: appearance,
|
|
176
|
+
stack: stackForTab
|
|
177
|
+
}) : nodeForTab ? /*#__PURE__*/_jsx(DrawerNodeRenderer, {
|
|
178
|
+
appearance: appearance,
|
|
179
|
+
node: nodeForTab
|
|
180
|
+
}) : ScreenForTab ? /*#__PURE__*/_jsx(ScreenForTab, {}) : null
|
|
181
|
+
}, `drawer-content-${tab.tabKey}`);
|
|
182
|
+
})
|
|
183
|
+
})]
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
const styles = StyleSheet.create({
|
|
189
|
+
container: {
|
|
190
|
+
flex: 1,
|
|
191
|
+
flexDirection: 'row',
|
|
192
|
+
overflow: 'hidden'
|
|
193
|
+
},
|
|
194
|
+
sidebar: {
|
|
195
|
+
position: 'absolute',
|
|
196
|
+
top: 0,
|
|
197
|
+
bottom: 0,
|
|
198
|
+
left: 0,
|
|
199
|
+
zIndex: 10,
|
|
200
|
+
backgroundColor: '#ffffff'
|
|
201
|
+
},
|
|
202
|
+
sidebarContent: {
|
|
203
|
+
flex: 1,
|
|
204
|
+
paddingTop: 12
|
|
205
|
+
},
|
|
206
|
+
main: {
|
|
207
|
+
flex: 1,
|
|
208
|
+
width: '100%'
|
|
209
|
+
},
|
|
210
|
+
overlay: {
|
|
211
|
+
...StyleSheet.absoluteFillObject,
|
|
212
|
+
zIndex: 5,
|
|
213
|
+
backgroundColor: '#000000'
|
|
214
|
+
},
|
|
215
|
+
item: {
|
|
216
|
+
height: 44,
|
|
217
|
+
flexDirection: 'row',
|
|
218
|
+
alignItems: 'center',
|
|
219
|
+
paddingHorizontal: 12,
|
|
220
|
+
marginHorizontal: 12,
|
|
221
|
+
marginBottom: 4,
|
|
222
|
+
borderRadius: 10
|
|
223
|
+
},
|
|
224
|
+
itemActive: {
|
|
225
|
+
backgroundColor: 'rgba(201, 204, 209, 0.32)'
|
|
226
|
+
},
|
|
227
|
+
itemText: {
|
|
228
|
+
flex: 1,
|
|
229
|
+
fontSize: 14,
|
|
230
|
+
fontWeight: '600'
|
|
231
|
+
},
|
|
232
|
+
itemTextActive: {
|
|
233
|
+
fontWeight: '700'
|
|
234
|
+
},
|
|
235
|
+
flex: {
|
|
236
|
+
flex: 1
|
|
237
|
+
},
|
|
238
|
+
hidden: {
|
|
239
|
+
display: 'none'
|
|
240
|
+
}
|
|
241
|
+
});
|