UNPKG

14.7 kBJavaScriptView Raw
1function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
3function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
4
5import { HeaderShownContext, SafeAreaProviderCompat } from '@react-navigation/elements';
6import { StackActions } from '@react-navigation/native';
7import * as React from 'react';
8import { StyleSheet, View } from 'react-native';
9import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
10import ModalPresentationContext from '../../utils/ModalPresentationContext';
11import { GestureHandlerRootView } from '../GestureHandler';
12import HeaderContainer from '../Header/HeaderContainer';
13import CardStack from './CardStack';
14const GestureHandlerWrapper = GestureHandlerRootView !== null && GestureHandlerRootView !== void 0 ? GestureHandlerRootView : View;
15/**
16 * Compare two arrays with primitive values as the content.
17 * We need to make sure that both values and order match.
18 */
19
20const isArrayEqual = (a, b) => a.length === b.length && a.every((it, index) => it === b[index]);
21
22export default class StackView extends React.Component {
23 constructor() {
24 super(...arguments);
25
26 _defineProperty(this, "state", {
27 routes: [],
28 previousRoutes: [],
29 previousDescriptors: {},
30 openingRouteKeys: [],
31 closingRouteKeys: [],
32 replacingRouteKeys: [],
33 descriptors: {}
34 });
35
36 _defineProperty(this, "getPreviousRoute", _ref => {
37 let {
38 route
39 } = _ref;
40 const {
41 closingRouteKeys,
42 replacingRouteKeys
43 } = this.state;
44 const routes = this.state.routes.filter(r => r.key === route.key || !closingRouteKeys.includes(r.key) && !replacingRouteKeys.includes(r.key));
45 const index = routes.findIndex(r => r.key === route.key);
46 return routes[index - 1];
47 });
48
49 _defineProperty(this, "renderScene", _ref2 => {
50 let {
51 route
52 } = _ref2;
53 const descriptor = this.state.descriptors[route.key] || this.props.descriptors[route.key];
54
55 if (!descriptor) {
56 return null;
57 }
58
59 return descriptor.render();
60 });
61
62 _defineProperty(this, "renderHeader", props => {
63 return /*#__PURE__*/React.createElement(HeaderContainer, props);
64 });
65
66 _defineProperty(this, "handleOpenRoute", _ref3 => {
67 let {
68 route
69 } = _ref3;
70 const {
71 state,
72 navigation
73 } = this.props;
74 const {
75 closingRouteKeys,
76 replacingRouteKeys
77 } = this.state;
78
79 if (closingRouteKeys.some(key => key === route.key) && replacingRouteKeys.every(key => key !== route.key) && state.routeNames.includes(route.name) && !state.routes.some(r => r.key === route.key)) {
80 // If route isn't present in current state, but was closing, assume that a close animation was cancelled
81 // So we need to add this route back to the state
82 navigation.navigate(route);
83 } else {
84 this.setState(state => ({
85 routes: state.replacingRouteKeys.length ? state.routes.filter(r => !state.replacingRouteKeys.includes(r.key)) : state.routes,
86 openingRouteKeys: state.openingRouteKeys.filter(key => key !== route.key),
87 closingRouteKeys: state.closingRouteKeys.filter(key => key !== route.key),
88 replacingRouteKeys: []
89 }));
90 }
91 });
92
93 _defineProperty(this, "handleCloseRoute", _ref4 => {
94 let {
95 route
96 } = _ref4;
97 const {
98 state,
99 navigation
100 } = this.props;
101
102 if (state.routes.some(r => r.key === route.key)) {
103 // If a route exists in state, trigger a pop
104 // This will happen in when the route was closed from the card component
105 // e.g. When the close animation triggered from a gesture ends
106 navigation.dispatch({ ...StackActions.pop(),
107 source: route.key,
108 target: state.key
109 });
110 } else {
111 // We need to clean up any state tracking the route and pop it immediately
112 this.setState(state => ({
113 routes: state.routes.filter(r => r.key !== route.key),
114 openingRouteKeys: state.openingRouteKeys.filter(key => key !== route.key),
115 closingRouteKeys: state.closingRouteKeys.filter(key => key !== route.key)
116 }));
117 }
118 });
119
120 _defineProperty(this, "handleTransitionStart", (_ref5, closing) => {
121 let {
122 route
123 } = _ref5;
124 return this.props.navigation.emit({
125 type: 'transitionStart',
126 data: {
127 closing
128 },
129 target: route.key
130 });
131 });
132
133 _defineProperty(this, "handleTransitionEnd", (_ref6, closing) => {
134 let {
135 route
136 } = _ref6;
137 return this.props.navigation.emit({
138 type: 'transitionEnd',
139 data: {
140 closing
141 },
142 target: route.key
143 });
144 });
145
146 _defineProperty(this, "handleGestureStart", _ref7 => {
147 let {
148 route
149 } = _ref7;
150 this.props.navigation.emit({
151 type: 'gestureStart',
152 target: route.key
153 });
154 });
155
156 _defineProperty(this, "handleGestureEnd", _ref8 => {
157 let {
158 route
159 } = _ref8;
160 this.props.navigation.emit({
161 type: 'gestureEnd',
162 target: route.key
163 });
164 });
165
166 _defineProperty(this, "handleGestureCancel", _ref9 => {
167 let {
168 route
169 } = _ref9;
170 this.props.navigation.emit({
171 type: 'gestureCancel',
172 target: route.key
173 });
174 });
175 }
176
177 static getDerivedStateFromProps(props, state) {
178 // If there was no change in routes, we don't need to compute anything
179 if ((props.state.routes === state.previousRoutes || isArrayEqual(props.state.routes.map(r => r.key), state.previousRoutes.map(r => r.key))) && state.routes.length) {
180 let routes = state.routes;
181 let previousRoutes = state.previousRoutes;
182 let descriptors = props.descriptors;
183 let previousDescriptors = state.previousDescriptors;
184
185 if (props.descriptors !== state.previousDescriptors) {
186 descriptors = state.routes.reduce((acc, route) => {
187 acc[route.key] = props.descriptors[route.key] || state.descriptors[route.key];
188 return acc;
189 }, {});
190 previousDescriptors = props.descriptors;
191 }
192
193 if (props.state.routes !== state.previousRoutes) {
194 // if any route objects have changed, we should update them
195 const map = props.state.routes.reduce((acc, route) => {
196 acc[route.key] = route;
197 return acc;
198 }, {});
199 routes = state.routes.map(route => map[route.key] || route);
200 previousRoutes = props.state.routes;
201 }
202
203 return {
204 routes,
205 previousRoutes,
206 descriptors,
207 previousDescriptors
208 };
209 } // Here we determine which routes were added or removed to animate them
210 // We keep a copy of the route being removed in local state to be able to animate it
211
212
213 let routes = props.state.index < props.state.routes.length - 1 ? // Remove any extra routes from the state
214 // The last visible route should be the focused route, i.e. at current index
215 props.state.routes.slice(0, props.state.index + 1) : props.state.routes; // Now we need to determine which routes were added and removed
216
217 let {
218 openingRouteKeys,
219 closingRouteKeys,
220 replacingRouteKeys,
221 previousRoutes
222 } = state;
223 const previousFocusedRoute = previousRoutes[previousRoutes.length - 1];
224 const nextFocusedRoute = routes[routes.length - 1];
225
226 const isAnimationEnabled = key => {
227 const descriptor = props.descriptors[key] || state.descriptors[key];
228 return descriptor ? descriptor.options.animationEnabled !== false : true;
229 };
230
231 const getAnimationTypeForReplace = key => {
232 var _descriptor$options$a;
233
234 const descriptor = props.descriptors[key] || state.descriptors[key];
235 return (_descriptor$options$a = descriptor.options.animationTypeForReplace) !== null && _descriptor$options$a !== void 0 ? _descriptor$options$a : 'push';
236 };
237
238 if (previousFocusedRoute && previousFocusedRoute.key !== nextFocusedRoute.key) {
239 // We only need to animate routes if the focused route changed
240 // Animating previous routes won't be visible coz the focused route is on top of everything
241 if (!previousRoutes.some(r => r.key === nextFocusedRoute.key)) {
242 // A new route has come to the focus, we treat this as a push
243 // A replace can also trigger this, the animation should look like push
244 if (isAnimationEnabled(nextFocusedRoute.key) && !openingRouteKeys.includes(nextFocusedRoute.key)) {
245 // In this case, we need to animate pushing the focused route
246 // We don't care about animating any other added routes because they won't be visible
247 openingRouteKeys = [...openingRouteKeys, nextFocusedRoute.key];
248 closingRouteKeys = closingRouteKeys.filter(key => key !== nextFocusedRoute.key);
249 replacingRouteKeys = replacingRouteKeys.filter(key => key !== nextFocusedRoute.key);
250
251 if (!routes.some(r => r.key === previousFocusedRoute.key)) {
252 // The previous focused route isn't present in state, we treat this as a replace
253 openingRouteKeys = openingRouteKeys.filter(key => key !== previousFocusedRoute.key);
254
255 if (getAnimationTypeForReplace(nextFocusedRoute.key) === 'pop') {
256 closingRouteKeys = [...closingRouteKeys, previousFocusedRoute.key]; // By default, new routes have a push animation, so we add it to `openingRouteKeys` before
257 // But since user configured it to animate the old screen like a pop, we need to add this without animation
258 // So remove it from `openingRouteKeys` which will remove the animation
259
260 openingRouteKeys = openingRouteKeys.filter(key => key !== nextFocusedRoute.key); // Keep the route being removed at the end to animate it out
261
262 routes = [...routes, previousFocusedRoute];
263 } else {
264 replacingRouteKeys = [...replacingRouteKeys, previousFocusedRoute.key];
265 closingRouteKeys = closingRouteKeys.filter(key => key !== previousFocusedRoute.key); // Keep the old route in the state because it's visible under the new route, and removing it will feel abrupt
266 // We need to insert it just before the focused one (the route being pushed)
267 // After the push animation is completed, routes being replaced will be removed completely
268
269 routes = routes.slice();
270 routes.splice(routes.length - 1, 0, previousFocusedRoute);
271 }
272 }
273 }
274 } else if (!routes.some(r => r.key === previousFocusedRoute.key)) {
275 // The previously focused route was removed, we treat this as a pop
276 if (isAnimationEnabled(previousFocusedRoute.key) && !closingRouteKeys.includes(previousFocusedRoute.key)) {
277 closingRouteKeys = [...closingRouteKeys, previousFocusedRoute.key]; // Sometimes a route can be closed before the opening animation finishes
278 // So we also need to remove it from the opening list
279
280 openingRouteKeys = openingRouteKeys.filter(key => key !== previousFocusedRoute.key);
281 replacingRouteKeys = replacingRouteKeys.filter(key => key !== previousFocusedRoute.key); // Keep a copy of route being removed in the state to be able to animate it
282
283 routes = [...routes, previousFocusedRoute];
284 }
285 } else {// Looks like some routes were re-arranged and no focused routes were added/removed
286 // i.e. the currently focused route already existed and the previously focused route still exists
287 // We don't know how to animate this
288 }
289 } else if (replacingRouteKeys.length || closingRouteKeys.length) {
290 // Keep the routes we are closing or replacing if animation is enabled for them
291 routes = routes.slice();
292 routes.splice(routes.length - 1, 0, ...state.routes.filter(_ref10 => {
293 let {
294 key
295 } = _ref10;
296 return isAnimationEnabled(key) ? replacingRouteKeys.includes(key) || closingRouteKeys.includes(key) : false;
297 }));
298 }
299
300 if (!routes.length) {
301 throw new Error('There should always be at least one route in the navigation state.');
302 }
303
304 const descriptors = routes.reduce((acc, route) => {
305 acc[route.key] = props.descriptors[route.key] || state.descriptors[route.key];
306 return acc;
307 }, {});
308 return {
309 routes,
310 previousRoutes: props.state.routes,
311 previousDescriptors: props.descriptors,
312 openingRouteKeys,
313 closingRouteKeys,
314 replacingRouteKeys,
315 descriptors
316 };
317 }
318
319 render() {
320 const {
321 state,
322 // eslint-disable-next-line @typescript-eslint/no-unused-vars
323 descriptors: _,
324 ...rest
325 } = this.props;
326 const {
327 routes,
328 descriptors,
329 openingRouteKeys,
330 closingRouteKeys
331 } = this.state;
332 return /*#__PURE__*/React.createElement(GestureHandlerWrapper, {
333 style: styles.container
334 }, /*#__PURE__*/React.createElement(SafeAreaProviderCompat, null, /*#__PURE__*/React.createElement(SafeAreaInsetsContext.Consumer, null, insets => /*#__PURE__*/React.createElement(ModalPresentationContext.Consumer, null, isParentModal => /*#__PURE__*/React.createElement(HeaderShownContext.Consumer, null, isParentHeaderShown => /*#__PURE__*/React.createElement(CardStack, _extends({
335 insets: insets,
336 isParentHeaderShown: isParentHeaderShown,
337 isParentModal: isParentModal,
338 getPreviousRoute: this.getPreviousRoute,
339 routes: routes,
340 openingRouteKeys: openingRouteKeys,
341 closingRouteKeys: closingRouteKeys,
342 onOpenRoute: this.handleOpenRoute,
343 onCloseRoute: this.handleCloseRoute,
344 onTransitionStart: this.handleTransitionStart,
345 onTransitionEnd: this.handleTransitionEnd,
346 renderHeader: this.renderHeader,
347 renderScene: this.renderScene,
348 state: state,
349 descriptors: descriptors,
350 onGestureStart: this.handleGestureStart,
351 onGestureEnd: this.handleGestureEnd,
352 onGestureCancel: this.handleGestureCancel
353 }, rest)))))));
354 }
355
356}
357const styles = StyleSheet.create({
358 container: {
359 flex: 1
360 }
361});
362//# sourceMappingURL=StackView.js.map
\No newline at end of file