@sigmela/router 0.1.2 → 0.2.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 +177 -833
- package/lib/module/Navigation.js +1 -10
- package/lib/module/NavigationStack.js +168 -19
- package/lib/module/Router.js +1508 -501
- package/lib/module/RouterContext.js +1 -1
- package/lib/module/ScreenStack/ScreenStack.web.js +343 -117
- package/lib/module/ScreenStack/ScreenStackContext.js +15 -0
- package/lib/module/ScreenStack/animationHelpers.js +72 -0
- package/lib/module/ScreenStackItem/ScreenStackItem.js +2 -1
- package/lib/module/ScreenStackItem/ScreenStackItem.web.js +76 -16
- package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +2 -1
- package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.web.js +1 -1
- package/lib/module/SplitView/RenderSplitView.native.js +85 -0
- package/lib/module/SplitView/RenderSplitView.web.js +79 -0
- package/lib/module/SplitView/SplitView.js +89 -0
- package/lib/module/SplitView/SplitViewContext.js +4 -0
- package/lib/module/SplitView/index.js +5 -0
- package/lib/module/SplitView/useSplitView.js +11 -0
- package/lib/module/StackRenderer.js +4 -2
- package/lib/module/TabBar/RenderTabBar.native.js +118 -33
- package/lib/module/TabBar/RenderTabBar.web.js +52 -47
- package/lib/module/TabBar/TabBar.js +117 -3
- package/lib/module/TabBar/index.js +4 -1
- package/lib/module/TabBar/useTabBarHeight.js +22 -0
- package/lib/module/index.js +3 -4
- package/lib/module/navigationNode.js +3 -0
- package/lib/module/styles.css +693 -28
- package/lib/typescript/src/NavigationStack.d.ts +25 -13
- package/lib/typescript/src/Router.d.ts +147 -34
- package/lib/typescript/src/RouterContext.d.ts +1 -1
- package/lib/typescript/src/ScreenStack/ScreenStack.web.d.ts +0 -2
- package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +22 -0
- package/lib/typescript/src/ScreenStack/animationHelpers.d.ts +6 -0
- package/lib/typescript/src/ScreenStackItem/ScreenStackItem.types.d.ts +5 -1
- package/lib/typescript/src/ScreenStackItem/ScreenStackItem.web.d.ts +1 -1
- package/lib/typescript/src/SplitView/RenderSplitView.native.d.ts +8 -0
- package/lib/typescript/src/SplitView/RenderSplitView.web.d.ts +8 -0
- package/lib/typescript/src/SplitView/SplitView.d.ts +31 -0
- package/lib/typescript/src/SplitView/SplitViewContext.d.ts +3 -0
- package/lib/typescript/src/SplitView/index.d.ts +5 -0
- package/lib/typescript/src/SplitView/useSplitView.d.ts +2 -0
- package/lib/typescript/src/StackRenderer.d.ts +2 -1
- package/lib/typescript/src/TabBar/TabBar.d.ts +27 -3
- package/lib/typescript/src/TabBar/index.d.ts +3 -0
- package/lib/typescript/src/TabBar/useTabBarHeight.d.ts +18 -0
- package/lib/typescript/src/createController.d.ts +1 -0
- package/lib/typescript/src/index.d.ts +4 -3
- package/lib/typescript/src/navigationNode.d.ts +41 -0
- package/lib/typescript/src/types.d.ts +21 -32
- package/package.json +6 -5
- package/lib/module/web/TransitionStack.js +0 -227
- package/lib/typescript/src/web/TransitionStack.d.ts +0 -21
|
@@ -13,7 +13,7 @@ export const useRouter = () => {
|
|
|
13
13
|
export const useCurrentRoute = () => {
|
|
14
14
|
const router = useRouter();
|
|
15
15
|
const subscribe = React.useCallback(cb => router.subscribe(cb), [router]);
|
|
16
|
-
const get = React.useCallback(() => router.
|
|
16
|
+
const get = React.useCallback(() => router.getActiveRoute(), [router]);
|
|
17
17
|
return React.useSyncExternalStore(subscribe, get, get);
|
|
18
18
|
};
|
|
19
19
|
export function useParams() {
|
|
@@ -1,139 +1,365 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
import { memo, useRef, useLayoutEffect, useMemo, useEffect, Children, isValidElement, Fragment } from 'react';
|
|
4
|
+
import { useTransitionMap } from 'react-transition-state';
|
|
5
|
+
import { ScreenStackItemsContext, ScreenStackAnimatingContext } from "./ScreenStackContext.js";
|
|
6
|
+
import { getPresentationTypeClass, computeAnimationType } from "./animationHelpers.js";
|
|
6
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
+
const devLog = (_, __) => {};
|
|
9
|
+
const isScreenStackItemElement = child => {
|
|
10
|
+
if (! /*#__PURE__*/isValidElement(child)) return false;
|
|
11
|
+
const anyProps = child.props;
|
|
12
|
+
return anyProps && typeof anyProps === 'object' && 'item' in anyProps;
|
|
13
|
+
};
|
|
14
|
+
const getItemKey = child => {
|
|
15
|
+
const anyChild = child;
|
|
16
|
+
const reactKey = anyChild.key ?? null;
|
|
17
|
+
if (typeof reactKey === 'string' && reactKey.length > 0 && !reactKey.startsWith('.')) {
|
|
18
|
+
return reactKey;
|
|
19
|
+
}
|
|
20
|
+
const item = anyChild.props?.item;
|
|
21
|
+
if (item?.key && typeof item.key === 'string') {
|
|
22
|
+
return item.key;
|
|
23
|
+
}
|
|
24
|
+
if (item?.routeId) {
|
|
25
|
+
return String(item.routeId);
|
|
26
|
+
}
|
|
27
|
+
throw new Error('[ScreenStack] ScreenStackItem is missing a stable key');
|
|
28
|
+
};
|
|
29
|
+
const computeDirection = (prev, current) => {
|
|
30
|
+
if (prev.length === 0 && current.length > 0) {
|
|
31
|
+
return 'forward';
|
|
32
|
+
}
|
|
33
|
+
if (current.length > prev.length) {
|
|
34
|
+
return 'forward';
|
|
35
|
+
}
|
|
36
|
+
if (current.length < prev.length) {
|
|
37
|
+
return 'back';
|
|
38
|
+
}
|
|
39
|
+
const prevTop = prev[prev.length - 1];
|
|
40
|
+
const currentTop = current[current.length - 1];
|
|
41
|
+
if (prevTop === currentTop) {
|
|
42
|
+
return 'forward';
|
|
43
|
+
}
|
|
44
|
+
const prevIndexOfCurrentTop = prev.indexOf(currentTop);
|
|
45
|
+
const prevIndexOfPrevTop = prev.indexOf(prevTop);
|
|
46
|
+
if (prevIndexOfCurrentTop !== -1 && prevIndexOfPrevTop !== -1 && prevIndexOfCurrentTop < prevIndexOfPrevTop) {
|
|
47
|
+
return 'back';
|
|
48
|
+
}
|
|
49
|
+
return 'forward';
|
|
50
|
+
};
|
|
7
51
|
export const ScreenStack = /*#__PURE__*/memo(props => {
|
|
8
52
|
const {
|
|
9
53
|
children,
|
|
10
54
|
transitionTime = 250,
|
|
11
|
-
type,
|
|
12
55
|
animated = true
|
|
13
56
|
} = props;
|
|
14
|
-
|
|
57
|
+
devLog('[ScreenStack] Render', {
|
|
58
|
+
transitionTime,
|
|
59
|
+
animated,
|
|
60
|
+
childrenExists: !!children
|
|
61
|
+
});
|
|
15
62
|
const containerRef = useRef(null);
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const key = child.key;
|
|
29
|
-
const element = /*#__PURE__*/cloneElement(child, {
|
|
30
|
-
phase: 'active'
|
|
63
|
+
const isInitialMountRef = useRef(true);
|
|
64
|
+
const prevKeysRef = useRef([]);
|
|
65
|
+
const lastDirectionRef = useRef('forward');
|
|
66
|
+
const childMapRef = useRef(new Map());
|
|
67
|
+
const stackChildren = useMemo(() => {
|
|
68
|
+
const stackItems = [];
|
|
69
|
+
Children.forEach(children, child => {
|
|
70
|
+
if (isScreenStackItemElement(child)) {
|
|
71
|
+
stackItems.push(child);
|
|
72
|
+
} else if (child != null) {
|
|
73
|
+
devLog('[ScreenStack] Non-ScreenStackItem child ignored', {
|
|
74
|
+
child
|
|
31
75
|
});
|
|
32
|
-
return [...prev, {
|
|
33
|
-
key,
|
|
34
|
-
phase: 'active',
|
|
35
|
-
element
|
|
36
|
-
}];
|
|
37
76
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
77
|
+
});
|
|
78
|
+
devLog('[ScreenStack] Parsed children', {
|
|
79
|
+
stackChildrenLength: stackItems.length
|
|
80
|
+
});
|
|
81
|
+
return stackItems;
|
|
82
|
+
}, [children]);
|
|
83
|
+
const routeKeys = useMemo(() => {
|
|
84
|
+
const keys = stackChildren.map(child => {
|
|
85
|
+
const item = child.props.item;
|
|
86
|
+
return item?.key || getItemKey(child);
|
|
87
|
+
});
|
|
88
|
+
devLog('[ScreenStack] routeKeys', keys);
|
|
89
|
+
return keys;
|
|
90
|
+
}, [stackChildren]);
|
|
91
|
+
const childMap = useMemo(() => {
|
|
92
|
+
const map = new Map(childMapRef.current);
|
|
93
|
+
for (const child of stackChildren) {
|
|
94
|
+
const item = child.props.item;
|
|
95
|
+
const key = item?.key || getItemKey(child);
|
|
96
|
+
map.set(key, child);
|
|
97
|
+
}
|
|
98
|
+
childMapRef.current = map;
|
|
99
|
+
devLog('[ScreenStack] childMap updated', {
|
|
100
|
+
size: map.size,
|
|
101
|
+
keys: Array.from(map.keys())
|
|
102
|
+
});
|
|
103
|
+
return map;
|
|
104
|
+
}, [stackChildren]);
|
|
105
|
+
const {
|
|
106
|
+
stateMap,
|
|
107
|
+
toggle,
|
|
108
|
+
setItem,
|
|
109
|
+
deleteItem
|
|
110
|
+
} = useTransitionMap({
|
|
111
|
+
timeout: transitionTime,
|
|
112
|
+
preEnter: true,
|
|
113
|
+
mountOnEnter: true,
|
|
114
|
+
unmountOnExit: false,
|
|
115
|
+
enter: animated,
|
|
116
|
+
exit: animated,
|
|
117
|
+
allowMultiple: true,
|
|
118
|
+
onStateChange: ({
|
|
119
|
+
key,
|
|
120
|
+
current
|
|
121
|
+
}) => {
|
|
122
|
+
devLog(`[ScreenStack] Transition state change for key ${key}:`, {
|
|
123
|
+
status: current.status,
|
|
124
|
+
isMounted: current.isMounted,
|
|
125
|
+
isEnter: current.isEnter,
|
|
126
|
+
isResolved: current.isResolved
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
devLog('[ScreenStack] Current transition states:', Array.from(stateMap.entries()).map(([key, state]) => ({
|
|
131
|
+
key,
|
|
132
|
+
status: state.status,
|
|
133
|
+
isMounted: state.isMounted,
|
|
134
|
+
isEnter: state.isEnter,
|
|
135
|
+
isResolved: state.isResolved
|
|
136
|
+
})));
|
|
137
|
+
const stateMapEntries = Array.from(stateMap.entries());
|
|
138
|
+
const direction = useMemo(() => {
|
|
139
|
+
const prevKeys = prevKeysRef.current;
|
|
140
|
+
const computed = computeDirection(prevKeys, routeKeys);
|
|
141
|
+
prevKeysRef.current = routeKeys;
|
|
142
|
+
return computed;
|
|
143
|
+
}, [routeKeys]);
|
|
144
|
+
devLog('[ScreenStack] Computed direction', {
|
|
145
|
+
prevKeys: prevKeysRef.current,
|
|
146
|
+
routeKeys,
|
|
147
|
+
direction
|
|
148
|
+
});
|
|
149
|
+
const isInitialPhase = isInitialMountRef.current;
|
|
150
|
+
const keysToRender = useMemo(() => {
|
|
151
|
+
const routeKeySet = new Set(routeKeys);
|
|
152
|
+
const exitingKeys = [];
|
|
153
|
+
for (const [key, state] of stateMapEntries) {
|
|
154
|
+
if (!state.isMounted) continue;
|
|
155
|
+
if (!routeKeySet.has(key)) {
|
|
156
|
+
exitingKeys.push(key);
|
|
49
157
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
158
|
+
}
|
|
159
|
+
const result = [...routeKeys, ...exitingKeys];
|
|
160
|
+
devLog('[ScreenStack] Keys to render:', {
|
|
161
|
+
result,
|
|
162
|
+
exitingKeys
|
|
163
|
+
});
|
|
164
|
+
return result;
|
|
165
|
+
}, [routeKeys, stateMapEntries]);
|
|
166
|
+
const containerClassName = useMemo(() => {
|
|
167
|
+
return 'screen-stack';
|
|
168
|
+
}, []);
|
|
169
|
+
useLayoutEffect(() => {
|
|
170
|
+
devLog('[ScreenStack] === LIFECYCLE EFFECT START ===', {
|
|
171
|
+
prevKeys: prevKeysRef.current,
|
|
172
|
+
routeKeys,
|
|
173
|
+
direction
|
|
174
|
+
});
|
|
175
|
+
const routeKeySet = new Set(routeKeys);
|
|
176
|
+
const existingKeySet = new Set();
|
|
177
|
+
for (const [key] of stateMapEntries) {
|
|
178
|
+
existingKeySet.add(key);
|
|
179
|
+
}
|
|
180
|
+
const newKeys = routeKeys.filter(key => !existingKeySet.has(key));
|
|
181
|
+
const removedKeys = [...existingKeySet].filter(key => !routeKeySet.has(key));
|
|
182
|
+
devLog('[ScreenStack] Lifecycle diff', {
|
|
183
|
+
newKeys,
|
|
184
|
+
removedKeys
|
|
185
|
+
});
|
|
186
|
+
for (const key of newKeys) {
|
|
187
|
+
devLog(`[ScreenStack] Adding item: ${key}`);
|
|
188
|
+
setItem(key);
|
|
189
|
+
devLog(`[ScreenStack] Entering item: ${key}`);
|
|
190
|
+
toggle(key, true);
|
|
191
|
+
}
|
|
192
|
+
for (const key of removedKeys) {
|
|
193
|
+
const state = stateMap.get(key);
|
|
194
|
+
if (state && state.isEnter) {
|
|
195
|
+
devLog(`[ScreenStack] Starting exit for item: ${key}`, {
|
|
196
|
+
status: state.status
|
|
197
|
+
});
|
|
198
|
+
toggle(key, false);
|
|
199
|
+
} else {
|
|
200
|
+
devLog(`[ScreenStack] Skip exit for item (not entered or missing): ${key}`, {
|
|
201
|
+
hasState: !!state,
|
|
202
|
+
status: state?.status,
|
|
203
|
+
isEnter: state?.isEnter
|
|
60
204
|
});
|
|
61
|
-
if (existed) {
|
|
62
|
-
const sameActive = existed.phase === 'active';
|
|
63
|
-
result.push(sameActive ? {
|
|
64
|
-
key,
|
|
65
|
-
phase: 'active',
|
|
66
|
-
element: nextEl
|
|
67
|
-
} : {
|
|
68
|
-
key,
|
|
69
|
-
phase: 'active',
|
|
70
|
-
element: nextEl
|
|
71
|
-
});
|
|
72
|
-
} else {
|
|
73
|
-
result.push({
|
|
74
|
-
key,
|
|
75
|
-
phase: 'active',
|
|
76
|
-
element: nextEl
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
for (const r of prev) {
|
|
81
|
-
if (!nextByKey.has(r.key)) {
|
|
82
|
-
const exitingEl = /*#__PURE__*/cloneElement(r.element, {
|
|
83
|
-
phase: 'exiting'
|
|
84
|
-
});
|
|
85
|
-
result.push({
|
|
86
|
-
key: r.key,
|
|
87
|
-
phase: 'exiting',
|
|
88
|
-
element: exitingEl
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
205
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
206
|
+
}
|
|
207
|
+
lastDirectionRef.current = direction;
|
|
208
|
+
devLog('[ScreenStack] === LIFECYCLE EFFECT END ===');
|
|
209
|
+
}, [routeKeys, direction, setItem, toggle, stateMapEntries, stateMap]);
|
|
95
210
|
useLayoutEffect(() => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
setRecords(prev => {
|
|
104
|
-
const idx = lastExitingIndexRef.current ?? -1;
|
|
105
|
-
if (idx < 0 || idx >= prev.length) return prev;
|
|
106
|
-
const candidate = prev[idx];
|
|
107
|
-
if (candidate?.phase !== 'exiting') return prev;
|
|
108
|
-
return [...prev.slice(0, idx), ...prev.slice(idx + 1)];
|
|
211
|
+
devLog('[ScreenStack] === CLEANUP EFFECT START ===');
|
|
212
|
+
const routeKeySet = new Set(routeKeys);
|
|
213
|
+
for (const [key, state] of stateMapEntries) {
|
|
214
|
+
if (!state.isMounted) {
|
|
215
|
+
devLog(`[ScreenStack] Cleanup unmounted item: ${key}`, {
|
|
216
|
+
status: state.status,
|
|
217
|
+
isResolved: state.isResolved
|
|
109
218
|
});
|
|
219
|
+
deleteItem(key);
|
|
220
|
+
childMapRef.current.delete(key);
|
|
221
|
+
continue;
|
|
110
222
|
}
|
|
223
|
+
const isInStack = routeKeySet.has(key);
|
|
224
|
+
const canCleanup = !isInStack && state.status === 'exited' && state.isResolved === true;
|
|
225
|
+
if (canCleanup) {
|
|
226
|
+
devLog(`[ScreenStack] Cleanup exited item: ${key}`, {
|
|
227
|
+
status: state.status,
|
|
228
|
+
isResolved: state.isResolved
|
|
229
|
+
});
|
|
230
|
+
deleteItem(key);
|
|
231
|
+
childMapRef.current.delete(key);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
devLog('[ScreenStack] === CLEANUP EFFECT END ===');
|
|
235
|
+
}, [routeKeys, stateMapEntries, deleteItem]);
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
if (!isInitialMountRef.current) return;
|
|
238
|
+
const hasMountedItem = stateMapEntries.some(([, st]) => st.isMounted);
|
|
239
|
+
|
|
240
|
+
// If the stack mounts empty, we still want the first pushed screen to animate.
|
|
241
|
+
// Mark initial mount as completed immediately in that case.
|
|
242
|
+
if (!hasMountedItem && routeKeys.length === 0) {
|
|
243
|
+
isInitialMountRef.current = false;
|
|
244
|
+
devLog('[ScreenStack] Initial mount completed (empty stack)');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (hasMountedItem) {
|
|
248
|
+
isInitialMountRef.current = false;
|
|
249
|
+
devLog('[ScreenStack] Initial mount completed');
|
|
250
|
+
}
|
|
251
|
+
}, [stateMapEntries, routeKeys.length]);
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
if (!containerRef.current) return;
|
|
254
|
+
const items = containerRef.current.querySelectorAll('.screen-stack-item');
|
|
255
|
+
if (items.length === 0) return;
|
|
256
|
+
devLog('[ScreenStack] DOM State after render:', {
|
|
257
|
+
containerClasses: containerRef.current.className,
|
|
258
|
+
containerDataAnimation: containerRef.current.dataset.animation,
|
|
259
|
+
containerDataDirection: containerRef.current.dataset.direction,
|
|
260
|
+
itemCount: items.length
|
|
111
261
|
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
262
|
+
});
|
|
263
|
+
const topKey = routeKeys[routeKeys.length - 1] ?? null;
|
|
264
|
+
const routeKeySet = useMemo(() => new Set(routeKeys), [routeKeys]);
|
|
265
|
+
const itemsContextValue = useMemo(() => {
|
|
266
|
+
const items = {};
|
|
267
|
+
for (let index = 0; index < keysToRender.length; index++) {
|
|
268
|
+
const key = keysToRender[index];
|
|
269
|
+
if (!key) continue;
|
|
270
|
+
const transitionState = stateMap.get(key);
|
|
271
|
+
const child = childMap.get(key);
|
|
272
|
+
if (!child) continue;
|
|
273
|
+
const item = child.props.item;
|
|
274
|
+
if (!item) continue;
|
|
275
|
+
const presentation = item.options?.stackPresentation ?? 'push';
|
|
276
|
+
const animated = item.options?.animated ?? true;
|
|
277
|
+
const isInStack = routeKeySet.has(key);
|
|
278
|
+
const isTop = isInStack && topKey !== null && key === topKey;
|
|
279
|
+
let phase;
|
|
280
|
+
if (!isInStack) {
|
|
281
|
+
phase = 'exiting';
|
|
282
|
+
} else if (isTop) {
|
|
283
|
+
phase = 'active';
|
|
284
|
+
} else {
|
|
285
|
+
phase = 'inactive';
|
|
286
|
+
}
|
|
287
|
+
const rawStatus = transitionState?.status || 'preEnter';
|
|
288
|
+
const status = isInitialPhase && (rawStatus === 'preEnter' || rawStatus === 'entering') ? 'entered' : rawStatus;
|
|
289
|
+
const routeIndex = routeKeys.indexOf(key);
|
|
290
|
+
const zIndex = routeIndex >= 0 ? routeIndex + 1 : keysToRender.length + index + 1;
|
|
291
|
+
const presentationType = getPresentationTypeClass(presentation);
|
|
292
|
+
const animationType = computeAnimationType(key, isInStack, isTop, direction, presentation, isInitialPhase, animated);
|
|
293
|
+
items[key] = {
|
|
294
|
+
presentationType,
|
|
295
|
+
animationType,
|
|
296
|
+
phase,
|
|
297
|
+
transitionStatus: status,
|
|
298
|
+
zIndex
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
for (let index = 0; index < routeKeys.length; index++) {
|
|
302
|
+
const key = routeKeys[index];
|
|
303
|
+
if (!key || items[key]) continue;
|
|
304
|
+
const child = childMap.get(key);
|
|
305
|
+
if (!child) continue;
|
|
306
|
+
const item = child.props.item;
|
|
307
|
+
if (!item) continue;
|
|
308
|
+
const presentation = item.options?.stackPresentation ?? 'push';
|
|
309
|
+
const animated = item.options?.animated ?? true;
|
|
310
|
+
const isInStack = routeKeySet.has(key);
|
|
311
|
+
const isTop = isInStack && topKey !== null && key === topKey;
|
|
312
|
+
let phase;
|
|
313
|
+
if (isTop) {
|
|
314
|
+
phase = 'active';
|
|
315
|
+
} else {
|
|
316
|
+
phase = 'inactive';
|
|
119
317
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
318
|
+
const presentationType = getPresentationTypeClass(presentation);
|
|
319
|
+
const animationType = isInitialPhase ? 'none' : computeAnimationType(key, isInStack, isTop, direction, presentation, isInitialPhase, animated);
|
|
320
|
+
items[key] = {
|
|
321
|
+
presentationType,
|
|
322
|
+
animationType,
|
|
323
|
+
phase,
|
|
324
|
+
transitionStatus: 'preEnter',
|
|
325
|
+
zIndex: index + 1
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
items
|
|
330
|
+
};
|
|
331
|
+
}, [keysToRender, stateMap, childMap, routeKeySet, topKey, isInitialPhase, routeKeys, direction]);
|
|
332
|
+
const animating = useMemo(() => {
|
|
333
|
+
return stateMapEntries.some(([, state]) => state.isMounted && (state.status === 'entering' || state.status === 'exiting' || state.status === 'preEnter' || state.status === 'preExit'));
|
|
334
|
+
}, [stateMapEntries]);
|
|
335
|
+
return /*#__PURE__*/_jsx(ScreenStackItemsContext.Provider, {
|
|
336
|
+
value: itemsContextValue,
|
|
337
|
+
children: /*#__PURE__*/_jsx(ScreenStackAnimatingContext.Provider, {
|
|
338
|
+
value: animating,
|
|
339
|
+
children: /*#__PURE__*/_jsx("div", {
|
|
340
|
+
ref: containerRef,
|
|
341
|
+
className: containerClassName + (animating ? ' animating' : ''),
|
|
342
|
+
children: keysToRender.map(key => {
|
|
343
|
+
const transitionState = stateMap.get(key);
|
|
344
|
+
if (!transitionState || !transitionState.isMounted) {
|
|
345
|
+
devLog(`[ScreenStack] Skipping ${key} - no state or not mounted`, {
|
|
346
|
+
hasState: !!transitionState,
|
|
347
|
+
isMounted: transitionState?.isMounted
|
|
348
|
+
});
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const child = childMap.get(key);
|
|
352
|
+
if (!child) {
|
|
353
|
+
devLog(`[ScreenStack] No child element for ${key}`, {
|
|
354
|
+
availableKeys: Array.from(childMap.keys())
|
|
355
|
+
});
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
return /*#__PURE__*/_jsx(Fragment, {
|
|
359
|
+
children: child
|
|
360
|
+
}, child.key || key);
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
})
|
|
138
364
|
});
|
|
139
365
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
export const ScreenStackItemsContext = /*#__PURE__*/createContext(null);
|
|
5
|
+
export const ScreenStackAnimatingContext = /*#__PURE__*/createContext(false);
|
|
6
|
+
export const useScreenStackItemsContext = () => {
|
|
7
|
+
const ctx = useContext(ScreenStackItemsContext);
|
|
8
|
+
if (!ctx) {
|
|
9
|
+
throw new Error('useScreenStackItemsContext must be used within ScreenStack');
|
|
10
|
+
}
|
|
11
|
+
return ctx;
|
|
12
|
+
};
|
|
13
|
+
export const useScreenStackAnimatingContext = () => {
|
|
14
|
+
return useContext(ScreenStackAnimatingContext);
|
|
15
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export function getPresentationTypeClass(presentation) {
|
|
4
|
+
switch (presentation) {
|
|
5
|
+
case 'push':
|
|
6
|
+
return 'push';
|
|
7
|
+
case 'modal':
|
|
8
|
+
return 'modal';
|
|
9
|
+
case 'transparentModal':
|
|
10
|
+
return 'transparent-modal';
|
|
11
|
+
case 'containedModal':
|
|
12
|
+
return 'contained-modal';
|
|
13
|
+
case 'containedTransparentModal':
|
|
14
|
+
return 'contained-transparent-modal';
|
|
15
|
+
case 'fullScreenModal':
|
|
16
|
+
return 'fullscreen-modal';
|
|
17
|
+
case 'formSheet':
|
|
18
|
+
return 'formsheet';
|
|
19
|
+
case 'pageSheet':
|
|
20
|
+
return 'pagesheet';
|
|
21
|
+
case 'sheet':
|
|
22
|
+
return 'sheet';
|
|
23
|
+
default:
|
|
24
|
+
return 'push';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function getAnimationTypeForPresentation(presentation, isEntering, direction) {
|
|
28
|
+
const suffix = isEntering ? 'enter' : 'exit';
|
|
29
|
+
const presentationClass = getPresentationTypeClass(presentation);
|
|
30
|
+
if (presentation === 'push') {
|
|
31
|
+
return direction === 'forward' ? `push-${suffix}` : `pop-${suffix}`;
|
|
32
|
+
}
|
|
33
|
+
return `${presentationClass}-${suffix}`;
|
|
34
|
+
}
|
|
35
|
+
export function computeAnimationType(_key, isInStack, isTop, direction, presentation, isInitialPhase, animated = true) {
|
|
36
|
+
if (!animated) {
|
|
37
|
+
return 'no-animate';
|
|
38
|
+
}
|
|
39
|
+
if (isInitialPhase) {
|
|
40
|
+
return 'none';
|
|
41
|
+
}
|
|
42
|
+
const isEntering = isInStack && isTop;
|
|
43
|
+
const isModalLike = ['modal', 'transparentModal', 'containedModal', 'containedTransparentModal', 'fullScreenModal', 'formSheet', 'pageSheet', 'sheet'].includes(presentation);
|
|
44
|
+
if (isModalLike) {
|
|
45
|
+
if (!isInStack) {
|
|
46
|
+
return getAnimationTypeForPresentation(presentation, false, direction);
|
|
47
|
+
}
|
|
48
|
+
if (isEntering) {
|
|
49
|
+
return getAnimationTypeForPresentation(presentation, true, direction);
|
|
50
|
+
}
|
|
51
|
+
return 'none';
|
|
52
|
+
}
|
|
53
|
+
if (!isInStack) {
|
|
54
|
+
if (direction === 'forward') {
|
|
55
|
+
return 'push-exit';
|
|
56
|
+
} else {
|
|
57
|
+
return 'pop-exit';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (isTop) {
|
|
61
|
+
if (direction === 'forward') {
|
|
62
|
+
return 'push-enter';
|
|
63
|
+
} else {
|
|
64
|
+
return 'pop-enter';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (direction === 'forward') {
|
|
68
|
+
return 'push-background';
|
|
69
|
+
} else {
|
|
70
|
+
return 'pop-background';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -59,7 +59,8 @@ export const ScreenStackItem = /*#__PURE__*/memo(({
|
|
|
59
59
|
children: /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
|
|
60
60
|
value: route,
|
|
61
61
|
children: /*#__PURE__*/_jsx(item.component, {
|
|
62
|
-
...item.passProps
|
|
62
|
+
...item.passProps,
|
|
63
|
+
appearance: appearance
|
|
63
64
|
})
|
|
64
65
|
})
|
|
65
66
|
}, item.key);
|