@amazon-devices/react-navigation__stack 8.0.9000000000 → 8.0.9000000002

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.
Files changed (81) hide show
  1. package/SystemModules/amzn__react-navigation__stack-8.private.modules.txt +39 -34
  2. package/kepler-compatibility.json +4 -2
  3. package/lib/module/index.js +0 -34
  4. package/lib/typescript/src/index.d.ts +1 -1
  5. package/lib/typescript/src/index.d.ts.map +1 -1
  6. package/oss-attribution/attribution.txt +76 -0
  7. package/oss-attribution/licenseInfos.json +27 -0
  8. package/package.json +1 -1
  9. package/src/index.tsx +0 -55
  10. package/SystemBundles/98ca5eeb45684e3e5615c766e1d3a956fee53d14cd6c2318098c23c210bd76d7.bundle +0 -13464
  11. package/SystemBundles/98ca5eeb45684e3e5615c766e1d3a956fee53d14cd6c2318098c23c210bd76d7.map +0 -4638
  12. package/lib/module/TransitionConfigs/CardStyleInterpolators.js +0 -394
  13. package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js +0 -262
  14. package/lib/module/TransitionConfigs/TransitionPresets.js +0 -156
  15. package/lib/module/TransitionConfigs/TransitionSpecs.js +0 -124
  16. package/lib/module/navigators/createStackNavigator.js +0 -71
  17. package/lib/module/types.js +0 -4
  18. package/lib/module/utils/CardAnimationContext.js +0 -5
  19. package/lib/module/utils/GestureHandlerRefContext.js +0 -5
  20. package/lib/module/utils/ModalPresentationContext.js +0 -5
  21. package/lib/module/utils/conditional.js +0 -28
  22. package/lib/module/utils/findLastIndex.js +0 -11
  23. package/lib/module/utils/gestureActivationCriteria.js +0 -60
  24. package/lib/module/utils/getDistanceForDirection.js +0 -15
  25. package/lib/module/utils/getInvertedMultiplier.js +0 -15
  26. package/lib/module/utils/getModalRoutesKeys.js +0 -12
  27. package/lib/module/utils/getShadowStyle.js +0 -24
  28. package/lib/module/utils/throttle.js +0 -14
  29. package/lib/module/utils/useCardAnimation.js +0 -12
  30. package/lib/module/utils/useGestureHandlerRef.js +0 -12
  31. package/lib/module/utils/useKeyboardManager.js +0 -90
  32. package/lib/module/views/GestureHandler.android.js +0 -4
  33. package/lib/module/views/GestureHandler.ios.js +0 -4
  34. package/lib/module/views/GestureHandler.js +0 -21
  35. package/lib/module/views/GestureHandler.kepler.js +0 -4
  36. package/lib/module/views/GestureHandlerNative.js +0 -18
  37. package/lib/module/views/Header/Header.js +0 -56
  38. package/lib/module/views/Header/HeaderContainer.js +0 -128
  39. package/lib/module/views/Header/HeaderSegment.js +0 -142
  40. package/lib/module/views/Screens.js +0 -42
  41. package/lib/module/views/Stack/Card.js +0 -433
  42. package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
  43. package/lib/module/views/Stack/CardContainer.js +0 -276
  44. package/lib/module/views/Stack/CardContent.js +0 -74
  45. package/lib/module/views/Stack/CardStack.js +0 -493
  46. package/lib/module/views/Stack/StackView.js +0 -339
  47. package/src/TransitionConfigs/CardStyleInterpolators.tsx +0 -460
  48. package/src/TransitionConfigs/HeaderStyleInterpolators.tsx +0 -286
  49. package/src/TransitionConfigs/TransitionPresets.tsx +0 -182
  50. package/src/TransitionConfigs/TransitionSpecs.tsx +0 -124
  51. package/src/navigators/createStackNavigator.tsx +0 -120
  52. package/src/types.tsx +0 -639
  53. package/src/utils/CardAnimationContext.tsx +0 -7
  54. package/src/utils/GestureHandlerRefContext.tsx +0 -5
  55. package/src/utils/ModalPresentationContext.tsx +0 -3
  56. package/src/utils/conditional.tsx +0 -33
  57. package/src/utils/findLastIndex.tsx +0 -9
  58. package/src/utils/gestureActivationCriteria.tsx +0 -70
  59. package/src/utils/getDistanceForDirection.tsx +0 -19
  60. package/src/utils/getInvertedMultiplier.tsx +0 -17
  61. package/src/utils/getModalRoutesKeys.ts +0 -21
  62. package/src/utils/getShadowStyle.tsx +0 -37
  63. package/src/utils/throttle.tsx +0 -16
  64. package/src/utils/useCardAnimation.tsx +0 -15
  65. package/src/utils/useGestureHandlerRef.tsx +0 -15
  66. package/src/utils/useKeyboardManager.tsx +0 -103
  67. package/src/views/GestureHandler.android.tsx +0 -1
  68. package/src/views/GestureHandler.ios.tsx +0 -1
  69. package/src/views/GestureHandler.kepler.tsx +0 -1
  70. package/src/views/GestureHandler.tsx +0 -23
  71. package/src/views/GestureHandlerNative.tsx +0 -23
  72. package/src/views/Header/Header.tsx +0 -73
  73. package/src/views/Header/HeaderContainer.tsx +0 -191
  74. package/src/views/Header/HeaderSegment.tsx +0 -203
  75. package/src/views/Screens.tsx +0 -45
  76. package/src/views/Stack/Card.tsx +0 -646
  77. package/src/views/Stack/CardA11yWrapper.tsx +0 -67
  78. package/src/views/Stack/CardContainer.tsx +0 -339
  79. package/src/views/Stack/CardContent.tsx +0 -92
  80. package/src/views/Stack/CardStack.tsx +0 -844
  81. package/src/views/Stack/StackView.tsx +0 -485
@@ -1,844 +0,0 @@
1
- import {
2
- getDefaultHeaderHeight,
3
- SafeAreaProviderCompat,
4
- } from '@amazon-devices/react-navigation__elements';
5
- import type {
6
- LocaleDirection,
7
- ParamListBase,
8
- Route,
9
- StackNavigationState,
10
- } from '@amazon-devices/react-navigation__native';
11
- import * as React from 'react';
12
- import {
13
- Animated,
14
- type LayoutChangeEvent,
15
- Platform,
16
- StyleSheet,
17
- View,
18
- } from 'react-native';
19
- import type { EdgeInsets } from '@amazon-devices/react-native-safe-area-context';
20
-
21
- import {
22
- forModalPresentationIOS,
23
- forNoAnimation as forNoAnimationCard,
24
- } from '../../TransitionConfigs/CardStyleInterpolators';
25
- import {
26
- BottomSheetAndroid,
27
- DefaultTransition,
28
- FadeFromBottomAndroid,
29
- FadeFromRightAndroid,
30
- ModalFadeTransition,
31
- ModalSlideFromBottomIOS,
32
- ModalTransition,
33
- RevealFromBottomAndroid,
34
- ScaleFromCenterAndroid,
35
- SlideFromLeftIOS,
36
- SlideFromRightIOS,
37
- } from '../../TransitionConfigs/TransitionPresets';
38
- import type {
39
- Layout,
40
- Scene,
41
- StackAnimationName,
42
- StackCardStyleInterpolator,
43
- StackDescriptorMap,
44
- StackHeaderMode,
45
- StackNavigationOptions,
46
- TransitionPreset,
47
- } from '../../types';
48
- import { findLastIndex } from '../../utils/findLastIndex';
49
- import { getDistanceForDirection } from '../../utils/getDistanceForDirection';
50
- import { getModalRouteKeys } from '../../utils/getModalRoutesKeys';
51
- import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
52
- import { MaybeScreen, MaybeScreenContainer } from '../Screens';
53
- import { CardContainer } from './CardContainer';
54
-
55
- type GestureValues = {
56
- [key: string]: Animated.Value;
57
- };
58
-
59
- type Props = {
60
- direction: LocaleDirection;
61
- insets: EdgeInsets;
62
- state: StackNavigationState<ParamListBase>;
63
- descriptors: StackDescriptorMap;
64
- preloadedDescriptors: StackDescriptorMap;
65
- routes: Route<string>[];
66
- openingRouteKeys: string[];
67
- closingRouteKeys: string[];
68
- onOpenRoute: (props: { route: Route<string> }) => void;
69
- onCloseRoute: (props: { route: Route<string> }) => void;
70
- getPreviousRoute: (props: {
71
- route: Route<string>;
72
- }) => Route<string> | undefined;
73
- renderHeader: (props: HeaderContainerProps) => React.ReactNode;
74
- isParentHeaderShown: boolean;
75
- isParentModal: boolean;
76
- onTransitionStart: (
77
- props: { route: Route<string> },
78
- closing: boolean
79
- ) => void;
80
- onTransitionEnd: (props: { route: Route<string> }, closing: boolean) => void;
81
- onGestureStart: (props: { route: Route<string> }) => void;
82
- onGestureEnd: (props: { route: Route<string> }) => void;
83
- onGestureCancel: (props: { route: Route<string> }) => void;
84
- detachInactiveScreens?: boolean;
85
- };
86
-
87
- type State = {
88
- routes: Route<string>[];
89
- descriptors: StackDescriptorMap;
90
- scenes: Scene[];
91
- gestures: GestureValues;
92
- layout: Layout;
93
- activeStates: (0 | 1 | Animated.AnimatedInterpolation<0 | 1>)[];
94
- headerHeights: Record<string, number>;
95
- };
96
-
97
- const NAMED_TRANSITIONS_PRESETS = {
98
- default: DefaultTransition,
99
- fade: ModalFadeTransition,
100
- fade_from_bottom: FadeFromBottomAndroid,
101
- fade_from_right: FadeFromRightAndroid,
102
- none: DefaultTransition,
103
- reveal_from_bottom: RevealFromBottomAndroid,
104
- scale_from_center: ScaleFromCenterAndroid,
105
- slide_from_left: SlideFromLeftIOS,
106
- slide_from_right: SlideFromRightIOS,
107
- slide_from_bottom: Platform.select({
108
- ios: ModalSlideFromBottomIOS,
109
- default: BottomSheetAndroid,
110
- }),
111
- } as const satisfies Record<StackAnimationName, TransitionPreset>;
112
-
113
- const EPSILON = 1e-5;
114
-
115
- const STATE_INACTIVE = 0;
116
- const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
117
- const STATE_ON_TOP = 2;
118
-
119
- const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
120
-
121
- const getInterpolationIndex = (scenes: Scene[], index: number) => {
122
- const { cardStyleInterpolator } = scenes[index].descriptor.options;
123
-
124
- // Start from current card and count backwards the number of cards with same interpolation
125
- let interpolationIndex = 0;
126
-
127
- for (let i = index - 1; i >= 0; i--) {
128
- const cardStyleInterpolatorCurrent =
129
- scenes[i]?.descriptor.options.cardStyleInterpolator;
130
-
131
- if (cardStyleInterpolatorCurrent !== cardStyleInterpolator) {
132
- break;
133
- }
134
-
135
- interpolationIndex++;
136
- }
137
-
138
- return interpolationIndex;
139
- };
140
-
141
- const getIsModalPresentation = (
142
- cardStyleInterpolator: StackCardStyleInterpolator
143
- ) => {
144
- return (
145
- cardStyleInterpolator === forModalPresentationIOS ||
146
- // Handle custom modal presentation interpolators as well
147
- cardStyleInterpolator.name === 'forModalPresentationIOS'
148
- );
149
- };
150
-
151
- const getIsModal = (
152
- scene: Scene,
153
- interpolationIndex: number,
154
- isParentModal: boolean
155
- ) => {
156
- if (isParentModal) {
157
- return true;
158
- }
159
-
160
- const { cardStyleInterpolator } = scene.descriptor.options;
161
- const isModalPresentation = getIsModalPresentation(cardStyleInterpolator);
162
- const isModal = isModalPresentation && interpolationIndex !== 0;
163
-
164
- return isModal;
165
- };
166
-
167
- const getHeaderHeights = (
168
- scenes: Scene[],
169
- insets: EdgeInsets,
170
- isParentHeaderShown: boolean,
171
- isParentModal: boolean,
172
- layout: Layout,
173
- previous: Record<string, number>
174
- ) => {
175
- return scenes.reduce<Record<string, number>>((acc, curr, index) => {
176
- const {
177
- headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
178
- headerStyle,
179
- } = curr.descriptor.options;
180
-
181
- const style = StyleSheet.flatten(headerStyle || {});
182
-
183
- const height =
184
- 'height' in style && typeof style.height === 'number'
185
- ? style.height
186
- : previous[curr.route.key];
187
-
188
- const interpolationIndex = getInterpolationIndex(scenes, index);
189
- const isModal = getIsModal(curr, interpolationIndex, isParentModal);
190
-
191
- acc[curr.route.key] =
192
- typeof height === 'number'
193
- ? height
194
- : getDefaultHeaderHeight(layout, isModal, headerStatusBarHeight);
195
-
196
- return acc;
197
- }, {});
198
- };
199
-
200
- const getDistanceFromOptions = (
201
- layout: Layout,
202
- options: StackNavigationOptions | undefined,
203
- isRTL: boolean
204
- ) => {
205
- if (options?.gestureDirection) {
206
- return getDistanceForDirection(layout, options.gestureDirection, isRTL);
207
- }
208
-
209
- const defaultGestureDirection =
210
- options?.presentation === 'modal'
211
- ? ModalTransition.gestureDirection
212
- : DefaultTransition.gestureDirection;
213
-
214
- const gestureDirection = options?.animation
215
- ? NAMED_TRANSITIONS_PRESETS[options?.animation]?.gestureDirection
216
- : defaultGestureDirection;
217
-
218
- return getDistanceForDirection(layout, gestureDirection, isRTL);
219
- };
220
-
221
- const getProgressFromGesture = (
222
- gesture: Animated.Value,
223
- layout: Layout,
224
- options: StackNavigationOptions | undefined,
225
- isRTL: boolean
226
- ) => {
227
- const distance = getDistanceFromOptions(
228
- {
229
- // Make sure that we have a non-zero distance, otherwise there will be incorrect progress
230
- // This causes blank screen on web if it was previously inside container with display: none
231
- width: Math.max(1, layout.width),
232
- height: Math.max(1, layout.height),
233
- },
234
- options,
235
- isRTL
236
- );
237
-
238
- if (distance > 0) {
239
- return gesture.interpolate({
240
- inputRange: [0, distance],
241
- outputRange: [1, 0],
242
- });
243
- }
244
-
245
- return gesture.interpolate({
246
- inputRange: [distance, 0],
247
- outputRange: [0, 1],
248
- });
249
- };
250
-
251
- function getDefaultAnimation(animation: StackAnimationName | undefined) {
252
- // Disable screen transition animation by default on web, windows and macos to match the native behavior
253
- const excludedPlatforms =
254
- Platform.OS !== 'web' &&
255
- Platform.OS !== 'windows' &&
256
- Platform.OS !== 'macos';
257
-
258
- return animation ?? (excludedPlatforms ? 'default' : 'none');
259
- }
260
-
261
- export function getAnimationEnabled(animation: StackAnimationName | undefined) {
262
- return getDefaultAnimation(animation) !== 'none';
263
- }
264
-
265
- export class CardStack extends React.Component<Props, State> {
266
- static getDerivedStateFromProps(
267
- props: Props,
268
- state: State
269
- ): Partial<State> | null {
270
- if (
271
- props.routes === state.routes &&
272
- props.descriptors === state.descriptors
273
- ) {
274
- return null;
275
- }
276
-
277
- const gestures = [
278
- ...props.routes,
279
- ...props.state.preloadedRoutes,
280
- ].reduce<GestureValues>((acc, curr) => {
281
- const descriptor =
282
- props.descriptors[curr.key] || props.preloadedDescriptors[curr.key];
283
- const { animation } = descriptor?.options || {};
284
-
285
- acc[curr.key] =
286
- state.gestures[curr.key] ||
287
- new Animated.Value(
288
- (props.openingRouteKeys.includes(curr.key) &&
289
- getAnimationEnabled(animation)) ||
290
- props.state.preloadedRoutes.includes(curr)
291
- ? getDistanceFromOptions(
292
- state.layout,
293
- descriptor?.options,
294
- props.direction === 'rtl'
295
- )
296
- : 0
297
- );
298
-
299
- return acc;
300
- }, {});
301
-
302
- const modalRouteKeys = getModalRouteKeys(
303
- [...props.routes, ...props.state.preloadedRoutes],
304
- {
305
- ...props.descriptors,
306
- ...props.preloadedDescriptors,
307
- }
308
- );
309
-
310
- const scenes = [...props.routes, ...props.state.preloadedRoutes].map(
311
- (route, index, self) => {
312
- // For preloaded screens, we don't care about the previous and the next screen
313
- const isPreloaded = props.state.preloadedRoutes.includes(route);
314
- const previousRoute = isPreloaded ? undefined : self[index - 1];
315
- const nextRoute = isPreloaded ? undefined : self[index + 1];
316
-
317
- const oldScene = state.scenes[index];
318
-
319
- const currentGesture = gestures[route.key];
320
- const previousGesture = previousRoute
321
- ? gestures[previousRoute.key]
322
- : undefined;
323
- const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
324
-
325
- const descriptor =
326
- (isPreloaded ? props.preloadedDescriptors : props.descriptors)[
327
- route.key
328
- ] ||
329
- state.descriptors[route.key] ||
330
- (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
331
-
332
- const nextOptions =
333
- nextRoute &&
334
- (
335
- props.descriptors[nextRoute?.key] ||
336
- state.descriptors[nextRoute?.key]
337
- )?.options;
338
-
339
- const previousOptions =
340
- previousRoute &&
341
- (
342
- props.descriptors[previousRoute?.key] ||
343
- state.descriptors[previousRoute?.key]
344
- )?.options;
345
-
346
- // When a screen is not the last, it should use next screen's transition config
347
- // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
348
- // For example combining a slide and a modal transition would look wrong otherwise
349
- // With this approach, combining different transition styles in the same navigator mostly looks right
350
- // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
351
- // but the majority of the transitions look alright
352
- const optionsForTransitionConfig =
353
- index !== self.length - 1 &&
354
- nextOptions &&
355
- nextOptions?.presentation !== 'transparentModal'
356
- ? nextOptions
357
- : descriptor.options;
358
-
359
- // Assume modal if there are already modal screens in the stack
360
- // or current screen is a modal when no presentation is specified
361
- const isModal = modalRouteKeys.includes(route.key);
362
-
363
- const animation = getDefaultAnimation(
364
- optionsForTransitionConfig.animation
365
- );
366
-
367
- const isAnimationEnabled = getAnimationEnabled(animation);
368
-
369
- const transitionPreset =
370
- animation !== 'default'
371
- ? NAMED_TRANSITIONS_PRESETS[animation]
372
- : optionsForTransitionConfig.presentation === 'transparentModal'
373
- ? ModalFadeTransition
374
- : optionsForTransitionConfig.presentation === 'modal' || isModal
375
- ? ModalTransition
376
- : DefaultTransition;
377
-
378
- const {
379
- gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
380
- gestureDirection = transitionPreset.gestureDirection,
381
- transitionSpec = transitionPreset.transitionSpec,
382
- cardStyleInterpolator = isAnimationEnabled
383
- ? transitionPreset.cardStyleInterpolator
384
- : forNoAnimationCard,
385
- headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
386
- cardOverlayEnabled = (Platform.OS !== 'ios' &&
387
- optionsForTransitionConfig.presentation !== 'transparentModal') ||
388
- getIsModalPresentation(cardStyleInterpolator),
389
- } = optionsForTransitionConfig;
390
-
391
- const headerMode: StackHeaderMode =
392
- descriptor.options.headerMode ??
393
- (!(
394
- optionsForTransitionConfig.presentation === 'modal' ||
395
- optionsForTransitionConfig.presentation === 'transparentModal' ||
396
- nextOptions?.presentation === 'modal' ||
397
- nextOptions?.presentation === 'transparentModal' ||
398
- getIsModalPresentation(cardStyleInterpolator)
399
- ) &&
400
- Platform.OS === 'ios' &&
401
- descriptor.options.header === undefined
402
- ? 'float'
403
- : 'screen');
404
-
405
- const isRTL = props.direction === 'rtl';
406
-
407
- const scene = {
408
- route,
409
- descriptor: {
410
- ...descriptor,
411
- options: {
412
- ...descriptor.options,
413
- animation,
414
- cardOverlayEnabled,
415
- cardStyleInterpolator,
416
- gestureDirection,
417
- gestureEnabled,
418
- headerStyleInterpolator,
419
- transitionSpec,
420
- headerMode,
421
- },
422
- },
423
- progress: {
424
- current: getProgressFromGesture(
425
- currentGesture,
426
- state.layout,
427
- descriptor.options,
428
- isRTL
429
- ),
430
- next:
431
- nextGesture && nextOptions?.presentation !== 'transparentModal'
432
- ? getProgressFromGesture(
433
- nextGesture,
434
- state.layout,
435
- nextOptions,
436
- isRTL
437
- )
438
- : undefined,
439
- previous: previousGesture
440
- ? getProgressFromGesture(
441
- previousGesture,
442
- state.layout,
443
- previousOptions,
444
- isRTL
445
- )
446
- : undefined,
447
- },
448
- __memo: [
449
- state.layout,
450
- descriptor,
451
- nextOptions,
452
- previousOptions,
453
- currentGesture,
454
- nextGesture,
455
- previousGesture,
456
- ],
457
- };
458
-
459
- if (
460
- oldScene &&
461
- scene.__memo.every((it, i) => {
462
- // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
463
- return oldScene.__memo[i] === it;
464
- })
465
- ) {
466
- return oldScene;
467
- }
468
-
469
- return scene;
470
- }
471
- );
472
-
473
- let activeStates = state.activeStates;
474
-
475
- if (props.routes.length !== state.routes.length) {
476
- let activeScreensLimit = 1;
477
-
478
- for (let i = props.routes.length - 1; i >= 0; i--) {
479
- const { options } = scenes[i].descriptor;
480
-
481
- const {
482
- // By default, we don't want to detach the previous screen of the active one for modals
483
- detachPreviousScreen = options.presentation === 'transparentModal'
484
- ? false
485
- : getIsModalPresentation(options.cardStyleInterpolator)
486
- ? i !==
487
- findLastIndex(scenes, (scene) => {
488
- const { cardStyleInterpolator } = scene.descriptor.options;
489
-
490
- return (
491
- cardStyleInterpolator === forModalPresentationIOS ||
492
- cardStyleInterpolator?.name === 'forModalPresentationIOS'
493
- );
494
- })
495
- : true,
496
- } = options;
497
-
498
- if (detachPreviousScreen === false) {
499
- activeScreensLimit++;
500
- } else {
501
- // Check at least last 2 screens before stopping
502
- // This will make sure that screen isn't detached when another screen is animating on top of the transparent one
503
- // e.g. opaque -> transparent -> opaque
504
- if (i <= props.routes.length - 2) {
505
- break;
506
- }
507
- }
508
- }
509
-
510
- activeStates = props.routes.map((_, index, self) => {
511
- // The activity state represents state of the screen:
512
- // 0 - inactive, the screen is detached
513
- // 1 - transitioning or below the top screen, the screen is mounted but interaction is disabled
514
- // 2 - on top of the stack, the screen is mounted and interaction is enabled
515
- let activityState:
516
- | Animated.AnimatedInterpolation<0 | 1 | 2>
517
- | 0
518
- | 1
519
- | 2;
520
-
521
- const lastActiveState = state.activeStates[index];
522
- const activeAfterTransition = index >= self.length - activeScreensLimit;
523
-
524
- if (lastActiveState === STATE_INACTIVE && !activeAfterTransition) {
525
- // screen was inactive before and it will still be inactive after the transition
526
- activityState = STATE_INACTIVE;
527
- } else {
528
- const sceneForActivity = scenes[self.length - 1];
529
- const outputValue =
530
- index === self.length - 1
531
- ? STATE_ON_TOP // the screen is on top after the transition
532
- : activeAfterTransition
533
- ? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
534
- : STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
535
-
536
- activityState = sceneForActivity
537
- ? sceneForActivity.progress.current.interpolate({
538
- inputRange: [0, 1 - EPSILON, 1],
539
- outputRange: [1, 1, outputValue],
540
- extrapolate: 'clamp',
541
- })
542
- : STATE_TRANSITIONING_OR_BELOW_TOP;
543
- }
544
-
545
- return activityState;
546
- });
547
- }
548
-
549
- return {
550
- routes: props.routes,
551
- scenes,
552
- gestures,
553
- descriptors: props.descriptors,
554
- activeStates,
555
- headerHeights: getHeaderHeights(
556
- scenes,
557
- props.insets,
558
- props.isParentHeaderShown,
559
- props.isParentModal,
560
- state.layout,
561
- state.headerHeights
562
- ),
563
- };
564
- }
565
-
566
- constructor(props: Props) {
567
- super(props);
568
-
569
- this.state = {
570
- routes: [],
571
- scenes: [],
572
- gestures: {},
573
- layout: SafeAreaProviderCompat.initialMetrics.frame,
574
- descriptors: this.props.descriptors,
575
- activeStates: [],
576
- // Used when card's header is null and mode is float to make transition
577
- // between screens with headers and those without headers smooth.
578
- // This is not a great heuristic here. We don't know synchronously
579
- // on mount what the header height is so we have just used the most
580
- // common cases here.
581
- headerHeights: {},
582
- };
583
- }
584
-
585
- private handleLayout = (e: LayoutChangeEvent) => {
586
- const { height, width } = e.nativeEvent.layout;
587
-
588
- const layout = { width, height };
589
-
590
- this.setState((state, props) => {
591
- if (height === state.layout.height && width === state.layout.width) {
592
- return null;
593
- }
594
-
595
- return {
596
- layout,
597
- headerHeights: getHeaderHeights(
598
- state.scenes,
599
- props.insets,
600
- props.isParentHeaderShown,
601
- props.isParentModal,
602
- layout,
603
- state.headerHeights
604
- ),
605
- };
606
- });
607
- };
608
-
609
- private handleHeaderLayout = ({
610
- route,
611
- height,
612
- }: {
613
- route: Route<string>;
614
- height: number;
615
- }) => {
616
- this.setState(({ headerHeights }) => {
617
- const previousHeight = headerHeights[route.key];
618
-
619
- if (previousHeight === height) {
620
- return null;
621
- }
622
-
623
- return {
624
- headerHeights: {
625
- ...headerHeights,
626
- [route.key]: height,
627
- },
628
- };
629
- });
630
- };
631
-
632
- private getFocusedRoute = () => {
633
- const { state } = this.props;
634
-
635
- return state.routes[state.index];
636
- };
637
-
638
- private getPreviousScene = ({ route }: { route: Route<string> }) => {
639
- const { getPreviousRoute } = this.props;
640
- const { scenes } = this.state;
641
-
642
- const previousRoute = getPreviousRoute({ route });
643
-
644
- if (previousRoute) {
645
- const previousScene = scenes.find(
646
- (scene) => scene.descriptor.route.key === previousRoute.key
647
- );
648
-
649
- return previousScene;
650
- }
651
-
652
- return undefined;
653
- };
654
-
655
- render() {
656
- const {
657
- insets,
658
- state,
659
- routes,
660
- openingRouteKeys,
661
- closingRouteKeys,
662
- onOpenRoute,
663
- onCloseRoute,
664
- renderHeader,
665
- isParentHeaderShown,
666
- isParentModal,
667
- onTransitionStart,
668
- onTransitionEnd,
669
- onGestureStart,
670
- onGestureEnd,
671
- onGestureCancel,
672
- detachInactiveScreens = Platform.OS === 'web' ||
673
- Platform.OS === 'android' ||
674
- Platform.OS === 'ios' ||
675
- // @ts-ignore - accept kepler as a platform
676
- Platform.OS === 'kepler',
677
- } = this.props;
678
-
679
- const { scenes, layout, gestures, activeStates, headerHeights } =
680
- this.state;
681
-
682
- const focusedRoute = state.routes[state.index];
683
- const focusedHeaderHeight = headerHeights[focusedRoute.key];
684
-
685
- const isFloatHeaderAbsolute = this.state.scenes.slice(-2).some((scene) => {
686
- const options = scene.descriptor.options ?? {};
687
- const { headerMode, headerTransparent, headerShown = true } = options;
688
-
689
- if (
690
- headerTransparent ||
691
- headerShown === false ||
692
- headerMode === 'screen'
693
- ) {
694
- return true;
695
- }
696
-
697
- return false;
698
- });
699
-
700
- return (
701
- <View style={styles.container}>
702
- {renderHeader({
703
- mode: 'float',
704
- layout,
705
- scenes,
706
- getPreviousScene: this.getPreviousScene,
707
- getFocusedRoute: this.getFocusedRoute,
708
- onContentHeightChange: this.handleHeaderLayout,
709
- style: [
710
- styles.floating,
711
- isFloatHeaderAbsolute && [
712
- // Without this, the header buttons won't be touchable on Android when headerTransparent: true
713
- { height: focusedHeaderHeight },
714
- styles.absolute,
715
- ],
716
- ],
717
- })}
718
- <MaybeScreenContainer
719
- enabled={detachInactiveScreens}
720
- style={styles.container}
721
- onLayout={this.handleLayout}
722
- >
723
- {[...routes, ...state.preloadedRoutes].map((route, index) => {
724
- const focused = focusedRoute.key === route.key;
725
- const gesture = gestures[route.key];
726
- const scene = scenes[index];
727
- // It is possible that for a short period the route appears in both arrays.
728
- // Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
729
- // However, due to the router action, it immediately populates the `preloadedRoutes` array.
730
- // Practically, the logic below takes care that it is rendered only once.
731
- const isPreloaded =
732
- state.preloadedRoutes.includes(route) && !routes.includes(route);
733
- if (
734
- state.preloadedRoutes.includes(route) &&
735
- routes.includes(route) &&
736
- index >= routes.length
737
- ) {
738
- return null;
739
- }
740
-
741
- const {
742
- headerShown = true,
743
- headerTransparent,
744
- freezeOnBlur,
745
- autoHideHomeIndicator,
746
- } = scene.descriptor.options;
747
-
748
- const safeAreaInsetTop = insets.top;
749
- const safeAreaInsetRight = insets.right;
750
- const safeAreaInsetBottom = insets.bottom;
751
- const safeAreaInsetLeft = insets.left;
752
-
753
- const headerHeight =
754
- headerShown !== false ? headerHeights[route.key] : 0;
755
-
756
- // Start from current card and count backwards the number of cards with same interpolation
757
- const interpolationIndex = getInterpolationIndex(scenes, index);
758
- const isModal = getIsModal(
759
- scene,
760
- interpolationIndex,
761
- isParentModal
762
- );
763
-
764
- const isNextScreenTransparent =
765
- scenes[index + 1]?.descriptor.options.presentation ===
766
- 'transparentModal';
767
-
768
- const detachCurrentScreen =
769
- scenes[index + 1]?.descriptor.options.detachPreviousScreen !==
770
- false;
771
-
772
- const activityState = isPreloaded
773
- ? STATE_INACTIVE
774
- : activeStates[index];
775
-
776
- return (
777
- <MaybeScreen
778
- key={route.key}
779
- style={[StyleSheet.absoluteFill]}
780
- enabled={detachInactiveScreens}
781
- active={activityState}
782
- freezeOnBlur={freezeOnBlur}
783
- shouldFreeze={activityState === STATE_INACTIVE && !isPreloaded}
784
- homeIndicatorHidden={autoHideHomeIndicator}
785
- pointerEvents="box-none"
786
- >
787
- <CardContainer
788
- index={index}
789
- interpolationIndex={interpolationIndex}
790
- modal={isModal}
791
- active={index === routes.length - 1}
792
- focused={focused}
793
- opening={openingRouteKeys.includes(route.key)}
794
- closing={closingRouteKeys.includes(route.key)}
795
- layout={layout}
796
- gesture={gesture}
797
- scene={scene}
798
- safeAreaInsetTop={safeAreaInsetTop}
799
- safeAreaInsetRight={safeAreaInsetRight}
800
- safeAreaInsetBottom={safeAreaInsetBottom}
801
- safeAreaInsetLeft={safeAreaInsetLeft}
802
- onGestureStart={onGestureStart}
803
- onGestureCancel={onGestureCancel}
804
- onGestureEnd={onGestureEnd}
805
- headerHeight={headerHeight}
806
- isParentHeaderShown={isParentHeaderShown}
807
- onHeaderHeightChange={this.handleHeaderLayout}
808
- getPreviousScene={this.getPreviousScene}
809
- getFocusedRoute={this.getFocusedRoute}
810
- hasAbsoluteFloatHeader={
811
- isFloatHeaderAbsolute && !headerTransparent
812
- }
813
- renderHeader={renderHeader}
814
- onOpenRoute={onOpenRoute}
815
- onCloseRoute={onCloseRoute}
816
- onTransitionStart={onTransitionStart}
817
- onTransitionEnd={onTransitionEnd}
818
- isNextScreenTransparent={isNextScreenTransparent}
819
- detachCurrentScreen={detachCurrentScreen}
820
- preloaded={isPreloaded}
821
- />
822
- </MaybeScreen>
823
- );
824
- })}
825
- </MaybeScreenContainer>
826
- </View>
827
- );
828
- }
829
- }
830
-
831
- const styles = StyleSheet.create({
832
- container: {
833
- flex: 1,
834
- },
835
- absolute: {
836
- position: 'absolute',
837
- top: 0,
838
- start: 0,
839
- end: 0,
840
- },
841
- floating: {
842
- zIndex: 1,
843
- },
844
- });