@react-navigation/stack 7.0.12 → 7.0.14

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.
@@ -1,18 +1,5 @@
1
1
  "use strict";
2
2
 
3
- function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
4
- function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
5
- function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
6
- function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
7
- function _possibleConstructorReturn(t, e) { if (e && ("object" == typeof e || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
8
- function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
9
- function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); }
10
- function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
11
- function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
12
- function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
13
- function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
14
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
15
- function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
16
3
  import { getDefaultHeaderHeight, SafeAreaProviderCompat } from '@react-navigation/elements';
17
4
  import * as React from 'react';
18
5
  import { Animated, Platform, StyleSheet, View } from 'react-native';
@@ -118,79 +105,110 @@ const getProgressFromGesture = (gesture, layout, descriptor, isRTL) => {
118
105
  outputRange: [0, 1]
119
106
  });
120
107
  };
121
- export let CardStack = /*#__PURE__*/function (_React$Component) {
122
- function CardStack(_props) {
123
- var _this;
124
- _classCallCheck(this, CardStack);
125
- _this = _callSuper(this, CardStack, [_props]);
126
- _defineProperty(_this, "handleLayout", e => {
127
- const {
128
- height,
129
- width
130
- } = e.nativeEvent.layout;
131
- const layout = {
132
- width,
133
- height
134
- };
135
- _this.setState((state, props) => {
136
- if (height === state.layout.height && width === state.layout.width) {
137
- return null;
138
- }
139
- return {
140
- layout,
141
- headerHeights: getHeaderHeights(state.scenes, props.insets, props.isParentHeaderShown, props.isParentModal, layout, state.headerHeights)
142
- };
143
- });
144
- });
145
- _defineProperty(_this, "handleHeaderLayout", ({
146
- route,
147
- height
148
- }) => {
149
- _this.setState(({
150
- headerHeights
151
- }) => {
152
- const previousHeight = headerHeights[route.key];
153
- if (previousHeight === height) {
154
- return null;
155
- }
156
- return {
157
- headerHeights: {
158
- ...headerHeights,
159
- [route.key]: height
160
- }
161
- };
162
- });
163
- });
164
- _defineProperty(_this, "getFocusedRoute", () => {
108
+ export class CardStack extends React.Component {
109
+ static getDerivedStateFromProps(props, state) {
110
+ if (props.routes === state.routes && props.descriptors === state.descriptors) {
111
+ return null;
112
+ }
113
+ const gestures = [...props.routes, ...props.state.preloadedRoutes].reduce((acc, curr) => {
114
+ const descriptor = props.descriptors[curr.key] || props.preloadedDescriptors[curr.key];
165
115
  const {
166
- state
167
- } = _this.props;
168
- return state.routes[state.index];
116
+ animation
117
+ } = descriptor?.options || {};
118
+ acc[curr.key] = state.gestures[curr.key] || new Animated.Value(props.openingRouteKeys.includes(curr.key) && animation !== 'none' || props.state.preloadedRoutes.includes(curr) ? getDistanceFromOptions(state.layout, descriptor, props.direction === 'rtl') : 0);
119
+ return acc;
120
+ }, {});
121
+ const modalRouteKeys = getModalRouteKeys([...props.routes, ...props.state.preloadedRoutes], {
122
+ ...props.descriptors,
123
+ ...props.preloadedDescriptors
169
124
  });
170
- _defineProperty(_this, "getPreviousScene", ({
171
- route
172
- }) => {
173
- const {
174
- getPreviousRoute
175
- } = _this.props;
125
+ const scenes = [...props.routes, ...props.state.preloadedRoutes].map((route, index, self) => {
126
+ // For preloaded screens, we don't care about the previous and the next screen
127
+ const isPreloaded = props.state.preloadedRoutes.includes(route);
128
+ const previousRoute = isPreloaded ? undefined : self[index - 1];
129
+ const nextRoute = isPreloaded ? undefined : self[index + 1];
130
+ const oldScene = state.scenes[index];
131
+ const currentGesture = gestures[route.key];
132
+ const previousGesture = previousRoute ? gestures[previousRoute.key] : undefined;
133
+ const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
134
+ const descriptor = (isPreloaded ? props.preloadedDescriptors : props.descriptors)[route.key] || state.descriptors[route.key] || (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
135
+ const nextDescriptor = nextRoute && (props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key]);
136
+ const previousDescriptor = previousRoute && (props.descriptors[previousRoute?.key] || state.descriptors[previousRoute?.key]);
137
+
138
+ // When a screen is not the last, it should use next screen's transition config
139
+ // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
140
+ // For example combining a slide and a modal transition would look wrong otherwise
141
+ // With this approach, combining different transition styles in the same navigator mostly looks right
142
+ // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
143
+ // but the majority of the transitions look alright
144
+ const optionsForTransitionConfig = index !== self.length - 1 && nextDescriptor && nextDescriptor.options.presentation !== 'transparentModal' ? nextDescriptor.options : descriptor.options;
145
+
146
+ // Assume modal if there are already modal screens in the stack
147
+ // or current screen is a modal when no presentation is specified
148
+ const isModal = modalRouteKeys.includes(route.key);
149
+
150
+ // Disable screen transition animation by default on web, windows and macos to match the native behavior
151
+ const excludedPlatforms = Platform.OS !== 'web' && Platform.OS !== 'windows' && Platform.OS !== 'macos';
152
+ const animation = optionsForTransitionConfig.animation ?? (excludedPlatforms ? 'default' : 'none');
153
+ const isAnimationEnabled = animation !== 'none';
154
+ const transitionPreset = animation !== 'default' ? NAMED_TRANSITIONS_PRESETS[animation] : isModal || optionsForTransitionConfig.presentation === 'modal' ? ModalTransition : optionsForTransitionConfig.presentation === 'transparentModal' ? ModalFadeTransition : DefaultTransition;
176
155
  const {
177
- scenes
178
- } = _this.state;
179
- const previousRoute = getPreviousRoute({
180
- route
181
- });
182
- if (previousRoute) {
183
- const previousScene = scenes.find(scene => scene.descriptor.route.key === previousRoute.key);
184
- return previousScene;
156
+ gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
157
+ gestureDirection = transitionPreset.gestureDirection,
158
+ transitionSpec = transitionPreset.transitionSpec,
159
+ cardStyleInterpolator = isAnimationEnabled ? transitionPreset.cardStyleInterpolator : forNoAnimationCard,
160
+ headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
161
+ cardOverlayEnabled = Platform.OS !== 'ios' && optionsForTransitionConfig.presentation !== 'transparentModal' || getIsModalPresentation(cardStyleInterpolator)
162
+ } = optionsForTransitionConfig;
163
+ const headerMode = descriptor.options.headerMode ?? (!(optionsForTransitionConfig.presentation === 'modal' || optionsForTransitionConfig.presentation === 'transparentModal' || nextDescriptor?.options.presentation === 'modal' || nextDescriptor?.options.presentation === 'transparentModal' || getIsModalPresentation(cardStyleInterpolator)) && Platform.OS === 'ios' && descriptor.options.header === undefined ? 'float' : 'screen');
164
+ const isRTL = props.direction === 'rtl';
165
+ const scene = {
166
+ route,
167
+ descriptor: {
168
+ ...descriptor,
169
+ options: {
170
+ ...descriptor.options,
171
+ animation,
172
+ cardOverlayEnabled,
173
+ cardStyleInterpolator,
174
+ gestureDirection,
175
+ gestureEnabled,
176
+ headerStyleInterpolator,
177
+ transitionSpec,
178
+ headerMode
179
+ }
180
+ },
181
+ progress: {
182
+ current: getProgressFromGesture(currentGesture, state.layout, descriptor, isRTL),
183
+ next: nextGesture && nextDescriptor?.options.presentation !== 'transparentModal' ? getProgressFromGesture(nextGesture, state.layout, nextDescriptor, isRTL) : undefined,
184
+ previous: previousGesture ? getProgressFromGesture(previousGesture, state.layout, previousDescriptor, isRTL) : undefined
185
+ },
186
+ __memo: [state.layout, descriptor, nextDescriptor, previousDescriptor, currentGesture, nextGesture, previousGesture]
187
+ };
188
+ if (oldScene && scene.__memo.every((it, i) => {
189
+ // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
190
+ return oldScene.__memo[i] === it;
191
+ })) {
192
+ return oldScene;
185
193
  }
186
- return undefined;
194
+ return scene;
187
195
  });
188
- _this.state = {
196
+ return {
197
+ routes: props.routes,
198
+ scenes,
199
+ gestures,
200
+ descriptors: props.descriptors,
201
+ headerHeights: getHeaderHeights(scenes, props.insets, props.isParentHeaderShown, props.isParentModal, state.layout, state.headerHeights)
202
+ };
203
+ }
204
+ constructor(props) {
205
+ super(props);
206
+ this.state = {
189
207
  routes: [],
190
208
  scenes: [],
191
209
  gestures: {},
192
210
  layout: SafeAreaProviderCompat.initialMetrics.frame,
193
- descriptors: _this.props.descriptors,
211
+ descriptors: this.props.descriptors,
194
212
  // Used when card's header is null and mode is float to make transition
195
213
  // between screens with headers and those without headers smooth.
196
214
  // This is not a great heuristic here. We don't know synchronously
@@ -198,289 +216,249 @@ export let CardStack = /*#__PURE__*/function (_React$Component) {
198
216
  // common cases here.
199
217
  headerHeights: {}
200
218
  };
201
- return _this;
202
219
  }
203
- _inherits(CardStack, _React$Component);
204
- return _createClass(CardStack, [{
205
- key: "render",
206
- value: function render() {
207
- const {
208
- insets,
209
- state,
210
- routes,
211
- openingRouteKeys,
212
- closingRouteKeys,
213
- onOpenRoute,
214
- onCloseRoute,
215
- renderHeader,
216
- isParentHeaderShown,
217
- isParentModal,
218
- onTransitionStart,
219
- onTransitionEnd,
220
- onGestureStart,
221
- onGestureEnd,
222
- onGestureCancel,
223
- detachInactiveScreens = Platform.OS === 'web' || Platform.OS === 'android' || Platform.OS === 'ios'
224
- } = this.props;
225
- const {
226
- scenes,
220
+ handleLayout = e => {
221
+ const {
222
+ height,
223
+ width
224
+ } = e.nativeEvent.layout;
225
+ const layout = {
226
+ width,
227
+ height
228
+ };
229
+ this.setState((state, props) => {
230
+ if (height === state.layout.height && width === state.layout.width) {
231
+ return null;
232
+ }
233
+ return {
227
234
  layout,
228
- gestures,
229
- headerHeights
230
- } = this.state;
231
- const focusedRoute = state.routes[state.index];
232
- const focusedHeaderHeight = headerHeights[focusedRoute.key];
233
- const isFloatHeaderAbsolute = this.state.scenes.slice(-2).some(scene => {
234
- const options = scene.descriptor.options ?? {};
235
- const {
236
- headerMode,
237
- headerTransparent,
238
- headerShown = true
239
- } = options;
240
- if (headerTransparent || headerShown === false || headerMode === 'screen') {
241
- return true;
235
+ headerHeights: getHeaderHeights(state.scenes, props.insets, props.isParentHeaderShown, props.isParentModal, layout, state.headerHeights)
236
+ };
237
+ });
238
+ };
239
+ handleHeaderLayout = ({
240
+ route,
241
+ height
242
+ }) => {
243
+ this.setState(({
244
+ headerHeights
245
+ }) => {
246
+ const previousHeight = headerHeights[route.key];
247
+ if (previousHeight === height) {
248
+ return null;
249
+ }
250
+ return {
251
+ headerHeights: {
252
+ ...headerHeights,
253
+ [route.key]: height
242
254
  }
243
- return false;
244
- });
245
- let activeScreensLimit = 1;
246
- for (let i = scenes.length - 1; i >= 0; i--) {
247
- const {
248
- options
249
- } = scenes[i].descriptor;
250
- const {
251
- // By default, we don't want to detach the previous screen of the active one for modals
252
- detachPreviousScreen = options.presentation === 'transparentModal' ? false : getIsModalPresentation(options.cardStyleInterpolator) ? i !== findLastIndex(scenes, scene => {
253
- const {
254
- cardStyleInterpolator
255
- } = scene.descriptor.options;
256
- return cardStyleInterpolator === forModalPresentationIOS || cardStyleInterpolator?.name === 'forModalPresentationIOS';
257
- }) : true
258
- } = options;
259
- if (detachPreviousScreen === false) {
260
- activeScreensLimit++;
261
- } else {
262
- // Check at least last 2 screens before stopping
263
- // This will make sure that screen isn't detached when another screen is animating on top of the transparent one
264
- // For example, (Opaque -> Transparent -> Opaque)
265
- if (i <= scenes.length - 2) {
266
- break;
267
- }
255
+ };
256
+ });
257
+ };
258
+ getFocusedRoute = () => {
259
+ const {
260
+ state
261
+ } = this.props;
262
+ return state.routes[state.index];
263
+ };
264
+ getPreviousScene = ({
265
+ route
266
+ }) => {
267
+ const {
268
+ getPreviousRoute
269
+ } = this.props;
270
+ const {
271
+ scenes
272
+ } = this.state;
273
+ const previousRoute = getPreviousRoute({
274
+ route
275
+ });
276
+ if (previousRoute) {
277
+ const previousScene = scenes.find(scene => scene.descriptor.route.key === previousRoute.key);
278
+ return previousScene;
279
+ }
280
+ return undefined;
281
+ };
282
+ render() {
283
+ const {
284
+ insets,
285
+ state,
286
+ routes,
287
+ openingRouteKeys,
288
+ closingRouteKeys,
289
+ onOpenRoute,
290
+ onCloseRoute,
291
+ renderHeader,
292
+ isParentHeaderShown,
293
+ isParentModal,
294
+ onTransitionStart,
295
+ onTransitionEnd,
296
+ onGestureStart,
297
+ onGestureEnd,
298
+ onGestureCancel,
299
+ detachInactiveScreens = Platform.OS === 'web' || Platform.OS === 'android' || Platform.OS === 'ios'
300
+ } = this.props;
301
+ const {
302
+ scenes,
303
+ layout,
304
+ gestures,
305
+ headerHeights
306
+ } = this.state;
307
+ const focusedRoute = state.routes[state.index];
308
+ const focusedHeaderHeight = headerHeights[focusedRoute.key];
309
+ const isFloatHeaderAbsolute = this.state.scenes.slice(-2).some(scene => {
310
+ const options = scene.descriptor.options ?? {};
311
+ const {
312
+ headerMode,
313
+ headerTransparent,
314
+ headerShown = true
315
+ } = options;
316
+ if (headerTransparent || headerShown === false || headerMode === 'screen') {
317
+ return true;
318
+ }
319
+ return false;
320
+ });
321
+ let activeScreensLimit = 1;
322
+ for (let i = scenes.length - 1; i >= 0; i--) {
323
+ const {
324
+ options
325
+ } = scenes[i].descriptor;
326
+ const {
327
+ // By default, we don't want to detach the previous screen of the active one for modals
328
+ detachPreviousScreen = options.presentation === 'transparentModal' ? false : getIsModalPresentation(options.cardStyleInterpolator) ? i !== findLastIndex(scenes, scene => {
329
+ const {
330
+ cardStyleInterpolator
331
+ } = scene.descriptor.options;
332
+ return cardStyleInterpolator === forModalPresentationIOS || cardStyleInterpolator?.name === 'forModalPresentationIOS';
333
+ }) : true
334
+ } = options;
335
+ if (detachPreviousScreen === false) {
336
+ activeScreensLimit++;
337
+ } else {
338
+ // Check at least last 2 screens before stopping
339
+ // This will make sure that screen isn't detached when another screen is animating on top of the transparent one
340
+ // For example, (Opaque -> Transparent -> Opaque)
341
+ if (i <= scenes.length - 2) {
342
+ break;
268
343
  }
269
344
  }
270
- const floatingHeader = /*#__PURE__*/_jsx(React.Fragment, {
271
- children: renderHeader({
272
- mode: 'float',
273
- layout,
274
- scenes,
275
- getPreviousScene: this.getPreviousScene,
276
- getFocusedRoute: this.getFocusedRoute,
277
- onContentHeightChange: this.handleHeaderLayout,
278
- style: [styles.floating, isFloatHeaderAbsolute && [
279
- // Without this, the header buttons won't be touchable on Android when headerTransparent: true
280
- {
281
- height: focusedHeaderHeight
282
- }, styles.absolute]]
283
- })
284
- }, "header");
285
- return /*#__PURE__*/_jsxs(View, {
286
- style: styles.container,
287
- children: [isFloatHeaderAbsolute ? null : floatingHeader, /*#__PURE__*/_jsx(MaybeScreenContainer, {
288
- enabled: detachInactiveScreens,
289
- style: styles.container,
290
- onLayout: this.handleLayout,
291
- children: [...routes, ...state.preloadedRoutes].map((route, index) => {
292
- const focused = focusedRoute.key === route.key;
293
- const gesture = gestures[route.key];
294
- const scene = scenes[index];
295
- // It is possible that for a short period the route appears in both arrays.
296
- // Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
297
- // However, due to the router action, it immediately populates the `preloadedRoutes` array.
298
- // Practically, the logic below takes care that it is rendered only once.
299
- const isPreloaded = state.preloadedRoutes.includes(route) && !routes.includes(route);
300
- if (state.preloadedRoutes.includes(route) && routes.includes(route) && index >= routes.length) {
301
- return null;
302
- }
303
-
304
- // For the screens that shouldn't be active, the value is 0
305
- // For those that should be active, but are not the top screen, the value is 1
306
- // For those on top of the stack and with interaction enabled, the value is 2
307
- // For the old implementation, it stays the same it was
308
- let isScreenActive = 1;
309
- if (index < routes.length - activeScreensLimit - 1 || isPreloaded) {
310
- // screen should be inactive because it is too deep in the stack
311
- isScreenActive = STATE_INACTIVE;
312
- } else {
313
- const sceneForActivity = scenes[routes.length - 1];
314
- const outputValue = index === routes.length - 1 ? STATE_ON_TOP // the screen is on top after the transition
315
- : index >= routes.length - activeScreensLimit ? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
316
- : STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
317
- isScreenActive = sceneForActivity ? sceneForActivity.progress.current.interpolate({
318
- inputRange: [0, 1 - EPSILON, 1],
319
- outputRange: [1, 1, outputValue],
320
- extrapolate: 'clamp'
321
- }) : STATE_TRANSITIONING_OR_BELOW_TOP;
322
- }
323
- const {
324
- headerShown = true,
325
- headerTransparent,
326
- freezeOnBlur,
327
- autoHideHomeIndicator
328
- } = scene.descriptor.options;
329
- const safeAreaInsetTop = insets.top;
330
- const safeAreaInsetRight = insets.right;
331
- const safeAreaInsetBottom = insets.bottom;
332
- const safeAreaInsetLeft = insets.left;
333
- const headerHeight = headerShown !== false ? headerHeights[route.key] : 0;
334
-
335
- // Start from current card and count backwards the number of cards with same interpolation
336
- const interpolationIndex = getInterpolationIndex(scenes, index);
337
- const isModal = getIsModal(scene, interpolationIndex, isParentModal);
338
- const isNextScreenTransparent = scenes[index + 1]?.descriptor.options.presentation === 'transparentModal';
339
- const detachCurrentScreen = scenes[index + 1]?.descriptor.options.detachPreviousScreen !== false;
340
- return /*#__PURE__*/_jsx(MaybeScreen, {
341
- style: [StyleSheet.absoluteFill],
342
- enabled: detachInactiveScreens,
343
- active: isScreenActive,
344
- freezeOnBlur: freezeOnBlur,
345
- homeIndicatorHidden: autoHideHomeIndicator,
346
- pointerEvents: "box-none",
347
- children: /*#__PURE__*/_jsx(CardContainer, {
348
- index: index,
349
- interpolationIndex: interpolationIndex,
350
- modal: isModal,
351
- active: index === routes.length - 1,
352
- focused: focused,
353
- opening: openingRouteKeys.includes(route.key),
354
- closing: closingRouteKeys.includes(route.key),
355
- layout: layout,
356
- gesture: gesture,
357
- scene: scene,
358
- safeAreaInsetTop: safeAreaInsetTop,
359
- safeAreaInsetRight: safeAreaInsetRight,
360
- safeAreaInsetBottom: safeAreaInsetBottom,
361
- safeAreaInsetLeft: safeAreaInsetLeft,
362
- onGestureStart: onGestureStart,
363
- onGestureCancel: onGestureCancel,
364
- onGestureEnd: onGestureEnd,
365
- headerHeight: headerHeight,
366
- isParentHeaderShown: isParentHeaderShown,
367
- onHeaderHeightChange: this.handleHeaderLayout,
368
- getPreviousScene: this.getPreviousScene,
369
- getFocusedRoute: this.getFocusedRoute,
370
- hasAbsoluteFloatHeader: isFloatHeaderAbsolute && !headerTransparent,
371
- renderHeader: renderHeader,
372
- onOpenRoute: onOpenRoute,
373
- onCloseRoute: onCloseRoute,
374
- onTransitionStart: onTransitionStart,
375
- onTransitionEnd: onTransitionEnd,
376
- isNextScreenTransparent: isNextScreenTransparent,
377
- detachCurrentScreen: detachCurrentScreen,
378
- preloaded: isPreloaded
379
- })
380
- }, route.key);
381
- })
382
- }), isFloatHeaderAbsolute ? floatingHeader : null]
383
- });
384
345
  }
385
- }], [{
386
- key: "getDerivedStateFromProps",
387
- value: function getDerivedStateFromProps(props, state) {
388
- if (props.routes === state.routes && props.descriptors === state.descriptors) {
389
- return null;
390
- }
391
- const gestures = [...props.routes, ...props.state.preloadedRoutes].reduce((acc, curr) => {
392
- const descriptor = props.descriptors[curr.key] || props.preloadedDescriptors[curr.key];
393
- const {
394
- animation
395
- } = descriptor?.options || {};
396
- acc[curr.key] = state.gestures[curr.key] || new Animated.Value(props.openingRouteKeys.includes(curr.key) && animation !== 'none' || props.state.preloadedRoutes.includes(curr) ? getDistanceFromOptions(state.layout, descriptor, props.direction === 'rtl') : 0);
397
- return acc;
398
- }, {});
399
- const modalRouteKeys = getModalRouteKeys([...props.routes, ...props.state.preloadedRoutes], {
400
- ...props.descriptors,
401
- ...props.preloadedDescriptors
402
- });
403
- const scenes = [...props.routes, ...props.state.preloadedRoutes].map((route, index, self) => {
404
- // For preloaded screens, we don't care about the previous and the next screen
405
- const isPreloaded = props.state.preloadedRoutes.includes(route);
406
- const previousRoute = isPreloaded ? undefined : self[index - 1];
407
- const nextRoute = isPreloaded ? undefined : self[index + 1];
408
- const oldScene = state.scenes[index];
409
- const currentGesture = gestures[route.key];
410
- const previousGesture = previousRoute ? gestures[previousRoute.key] : undefined;
411
- const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
412
- const descriptor = (isPreloaded ? props.preloadedDescriptors : props.descriptors)[route.key] || state.descriptors[route.key] || (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
413
- const nextDescriptor = nextRoute && (props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key]);
414
- const previousDescriptor = previousRoute && (props.descriptors[previousRoute?.key] || state.descriptors[previousRoute?.key]);
415
-
416
- // When a screen is not the last, it should use next screen's transition config
417
- // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
418
- // For example combining a slide and a modal transition would look wrong otherwise
419
- // With this approach, combining different transition styles in the same navigator mostly looks right
420
- // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
421
- // but the majority of the transitions look alright
422
- const optionsForTransitionConfig = index !== self.length - 1 && nextDescriptor && nextDescriptor.options.presentation !== 'transparentModal' ? nextDescriptor.options : descriptor.options;
346
+ const floatingHeader = /*#__PURE__*/_jsx(React.Fragment, {
347
+ children: renderHeader({
348
+ mode: 'float',
349
+ layout,
350
+ scenes,
351
+ getPreviousScene: this.getPreviousScene,
352
+ getFocusedRoute: this.getFocusedRoute,
353
+ onContentHeightChange: this.handleHeaderLayout,
354
+ style: [styles.floating, isFloatHeaderAbsolute && [
355
+ // Without this, the header buttons won't be touchable on Android when headerTransparent: true
356
+ {
357
+ height: focusedHeaderHeight
358
+ }, styles.absolute]]
359
+ })
360
+ }, "header");
361
+ return /*#__PURE__*/_jsxs(View, {
362
+ style: styles.container,
363
+ children: [isFloatHeaderAbsolute ? null : floatingHeader, /*#__PURE__*/_jsx(MaybeScreenContainer, {
364
+ enabled: detachInactiveScreens,
365
+ style: styles.container,
366
+ onLayout: this.handleLayout,
367
+ children: [...routes, ...state.preloadedRoutes].map((route, index) => {
368
+ const focused = focusedRoute.key === route.key;
369
+ const gesture = gestures[route.key];
370
+ const scene = scenes[index];
371
+ // It is possible that for a short period the route appears in both arrays.
372
+ // Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
373
+ // However, due to the router action, it immediately populates the `preloadedRoutes` array.
374
+ // Practically, the logic below takes care that it is rendered only once.
375
+ const isPreloaded = state.preloadedRoutes.includes(route) && !routes.includes(route);
376
+ if (state.preloadedRoutes.includes(route) && routes.includes(route) && index >= routes.length) {
377
+ return null;
378
+ }
423
379
 
424
- // Assume modal if there are already modal screens in the stack
425
- // or current screen is a modal when no presentation is specified
426
- const isModal = modalRouteKeys.includes(route.key);
380
+ // For the screens that shouldn't be active, the value is 0
381
+ // For those that should be active, but are not the top screen, the value is 1
382
+ // For those on top of the stack and with interaction enabled, the value is 2
383
+ // For the old implementation, it stays the same it was
384
+ let isScreenActive = 1;
385
+ if (index < routes.length - activeScreensLimit - 1 || isPreloaded) {
386
+ // screen should be inactive because it is too deep in the stack
387
+ isScreenActive = STATE_INACTIVE;
388
+ } else {
389
+ const sceneForActivity = scenes[routes.length - 1];
390
+ const outputValue = index === routes.length - 1 ? STATE_ON_TOP // the screen is on top after the transition
391
+ : index >= routes.length - activeScreensLimit ? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
392
+ : STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
393
+ isScreenActive = sceneForActivity ? sceneForActivity.progress.current.interpolate({
394
+ inputRange: [0, 1 - EPSILON, 1],
395
+ outputRange: [1, 1, outputValue],
396
+ extrapolate: 'clamp'
397
+ }) : STATE_TRANSITIONING_OR_BELOW_TOP;
398
+ }
399
+ const {
400
+ headerShown = true,
401
+ headerTransparent,
402
+ freezeOnBlur,
403
+ autoHideHomeIndicator
404
+ } = scene.descriptor.options;
405
+ const safeAreaInsetTop = insets.top;
406
+ const safeAreaInsetRight = insets.right;
407
+ const safeAreaInsetBottom = insets.bottom;
408
+ const safeAreaInsetLeft = insets.left;
409
+ const headerHeight = headerShown !== false ? headerHeights[route.key] : 0;
427
410
 
428
- // Disable screen transition animation by default on web, windows and macos to match the native behavior
429
- const excludedPlatforms = Platform.OS !== 'web' && Platform.OS !== 'windows' && Platform.OS !== 'macos';
430
- const animation = optionsForTransitionConfig.animation ?? (excludedPlatforms ? 'default' : 'none');
431
- const isAnimationEnabled = animation !== 'none';
432
- const transitionPreset = animation !== 'default' ? NAMED_TRANSITIONS_PRESETS[animation] : isModal || optionsForTransitionConfig.presentation === 'modal' ? ModalTransition : optionsForTransitionConfig.presentation === 'transparentModal' ? ModalFadeTransition : DefaultTransition;
433
- const {
434
- gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
435
- gestureDirection = transitionPreset.gestureDirection,
436
- transitionSpec = transitionPreset.transitionSpec,
437
- cardStyleInterpolator = isAnimationEnabled ? transitionPreset.cardStyleInterpolator : forNoAnimationCard,
438
- headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
439
- cardOverlayEnabled = Platform.OS !== 'ios' && optionsForTransitionConfig.presentation !== 'transparentModal' || getIsModalPresentation(cardStyleInterpolator)
440
- } = optionsForTransitionConfig;
441
- const headerMode = descriptor.options.headerMode ?? (!(optionsForTransitionConfig.presentation === 'modal' || optionsForTransitionConfig.presentation === 'transparentModal' || nextDescriptor?.options.presentation === 'modal' || nextDescriptor?.options.presentation === 'transparentModal' || getIsModalPresentation(cardStyleInterpolator)) && Platform.OS === 'ios' && descriptor.options.header === undefined ? 'float' : 'screen');
442
- const isRTL = props.direction === 'rtl';
443
- const scene = {
444
- route,
445
- descriptor: {
446
- ...descriptor,
447
- options: {
448
- ...descriptor.options,
449
- animation,
450
- cardOverlayEnabled,
451
- cardStyleInterpolator,
452
- gestureDirection,
453
- gestureEnabled,
454
- headerStyleInterpolator,
455
- transitionSpec,
456
- headerMode
457
- }
458
- },
459
- progress: {
460
- current: getProgressFromGesture(currentGesture, state.layout, descriptor, isRTL),
461
- next: nextGesture && nextDescriptor?.options.presentation !== 'transparentModal' ? getProgressFromGesture(nextGesture, state.layout, nextDescriptor, isRTL) : undefined,
462
- previous: previousGesture ? getProgressFromGesture(previousGesture, state.layout, previousDescriptor, isRTL) : undefined
463
- },
464
- __memo: [state.layout, descriptor, nextDescriptor, previousDescriptor, currentGesture, nextGesture, previousGesture]
465
- };
466
- if (oldScene && scene.__memo.every((it, i) => {
467
- // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
468
- return oldScene.__memo[i] === it;
469
- })) {
470
- return oldScene;
471
- }
472
- return scene;
473
- });
474
- return {
475
- routes: props.routes,
476
- scenes,
477
- gestures,
478
- descriptors: props.descriptors,
479
- headerHeights: getHeaderHeights(scenes, props.insets, props.isParentHeaderShown, props.isParentModal, state.layout, state.headerHeights)
480
- };
481
- }
482
- }]);
483
- }(React.Component);
411
+ // Start from current card and count backwards the number of cards with same interpolation
412
+ const interpolationIndex = getInterpolationIndex(scenes, index);
413
+ const isModal = getIsModal(scene, interpolationIndex, isParentModal);
414
+ const isNextScreenTransparent = scenes[index + 1]?.descriptor.options.presentation === 'transparentModal';
415
+ const detachCurrentScreen = scenes[index + 1]?.descriptor.options.detachPreviousScreen !== false;
416
+ return /*#__PURE__*/_jsx(MaybeScreen, {
417
+ style: [StyleSheet.absoluteFill],
418
+ enabled: detachInactiveScreens,
419
+ active: isScreenActive,
420
+ freezeOnBlur: freezeOnBlur,
421
+ homeIndicatorHidden: autoHideHomeIndicator,
422
+ pointerEvents: "box-none",
423
+ children: /*#__PURE__*/_jsx(CardContainer, {
424
+ index: index,
425
+ interpolationIndex: interpolationIndex,
426
+ modal: isModal,
427
+ active: index === routes.length - 1,
428
+ focused: focused,
429
+ opening: openingRouteKeys.includes(route.key),
430
+ closing: closingRouteKeys.includes(route.key),
431
+ layout: layout,
432
+ gesture: gesture,
433
+ scene: scene,
434
+ safeAreaInsetTop: safeAreaInsetTop,
435
+ safeAreaInsetRight: safeAreaInsetRight,
436
+ safeAreaInsetBottom: safeAreaInsetBottom,
437
+ safeAreaInsetLeft: safeAreaInsetLeft,
438
+ onGestureStart: onGestureStart,
439
+ onGestureCancel: onGestureCancel,
440
+ onGestureEnd: onGestureEnd,
441
+ headerHeight: headerHeight,
442
+ isParentHeaderShown: isParentHeaderShown,
443
+ onHeaderHeightChange: this.handleHeaderLayout,
444
+ getPreviousScene: this.getPreviousScene,
445
+ getFocusedRoute: this.getFocusedRoute,
446
+ hasAbsoluteFloatHeader: isFloatHeaderAbsolute && !headerTransparent,
447
+ renderHeader: renderHeader,
448
+ onOpenRoute: onOpenRoute,
449
+ onCloseRoute: onCloseRoute,
450
+ onTransitionStart: onTransitionStart,
451
+ onTransitionEnd: onTransitionEnd,
452
+ isNextScreenTransparent: isNextScreenTransparent,
453
+ detachCurrentScreen: detachCurrentScreen,
454
+ preloaded: isPreloaded
455
+ })
456
+ }, route.key);
457
+ })
458
+ }), isFloatHeaderAbsolute ? floatingHeader : null]
459
+ });
460
+ }
461
+ }
484
462
  const styles = StyleSheet.create({
485
463
  container: {
486
464
  flex: 1