@react-navigation/stack 7.4.4 → 7.4.6
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/lib/module/views/Stack/Card.js +65 -81
- package/lib/module/views/Stack/Card.js.map +1 -1
- package/lib/module/views/Stack/CardA11yWrapper.js +44 -0
- package/lib/module/views/Stack/CardA11yWrapper.js.map +1 -0
- package/lib/module/views/Stack/CardContainer.js +71 -70
- package/lib/module/views/Stack/CardContainer.js.map +1 -1
- package/lib/module/views/Stack/{CardSheet.js → CardContent.js} +5 -13
- package/lib/module/views/Stack/CardContent.js.map +1 -0
- package/lib/module/views/Stack/CardStack.js +26 -21
- package/lib/module/views/Stack/CardStack.js.map +1 -1
- package/lib/module/views/Stack/StackView.js +2 -2
- package/lib/module/views/Stack/StackView.js.map +1 -1
- package/lib/typescript/src/views/Stack/Card.d.ts +5 -5
- package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +15 -0
- package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +1 -0
- package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContent.d.ts +13 -0
- package/lib/typescript/src/views/Stack/CardContent.d.ts.map +1 -0
- package/lib/typescript/src/views/Stack/CardStack.d.ts +2 -1
- package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/views/Stack/Card.tsx +96 -111
- package/src/views/Stack/CardA11yWrapper.tsx +65 -0
- package/src/views/Stack/CardContainer.tsx +92 -98
- package/src/views/Stack/CardContent.tsx +92 -0
- package/src/views/Stack/CardStack.tsx +51 -42
- package/src/views/Stack/StackView.tsx +2 -2
- package/lib/module/views/Stack/CardSheet.js.map +0 -1
- package/lib/typescript/src/views/Stack/CardSheet.d.ts +0 -14
- package/lib/typescript/src/views/Stack/CardSheet.d.ts.map +0 -1
- package/src/views/Stack/CardSheet.tsx +0 -106
package/src/views/Stack/Card.tsx
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
type StyleProp,
|
|
9
9
|
StyleSheet,
|
|
10
10
|
View,
|
|
11
|
-
type ViewProps,
|
|
12
11
|
type ViewStyle,
|
|
13
12
|
} from 'react-native';
|
|
14
13
|
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
@@ -30,9 +29,10 @@ import {
|
|
|
30
29
|
PanGestureHandler,
|
|
31
30
|
type PanGestureHandlerGestureEvent,
|
|
32
31
|
} from '../GestureHandler';
|
|
33
|
-
import {
|
|
32
|
+
import { CardContent } from './CardContent';
|
|
34
33
|
|
|
35
|
-
type Props =
|
|
34
|
+
type Props = {
|
|
35
|
+
animated: boolean;
|
|
36
36
|
interpolationIndex: number;
|
|
37
37
|
opening: boolean;
|
|
38
38
|
closing: boolean;
|
|
@@ -181,11 +181,11 @@ export class Card extends React.Component<Props> {
|
|
|
181
181
|
|
|
182
182
|
private isSwiping = new Animated.Value(FALSE);
|
|
183
183
|
|
|
184
|
-
private
|
|
184
|
+
private lastToValue: number | undefined;
|
|
185
185
|
|
|
186
|
+
private interactionHandle: number | undefined;
|
|
186
187
|
private pendingGestureCallback: number | undefined;
|
|
187
|
-
|
|
188
|
-
private lastToValue: number | undefined;
|
|
188
|
+
private animationHandle: number | undefined;
|
|
189
189
|
|
|
190
190
|
private animate = ({
|
|
191
191
|
closing,
|
|
@@ -194,7 +194,7 @@ export class Card extends React.Component<Props> {
|
|
|
194
194
|
closing: boolean;
|
|
195
195
|
velocity?: number;
|
|
196
196
|
}) => {
|
|
197
|
-
const { transitionSpec, onOpen, onClose, onTransition, gesture } =
|
|
197
|
+
const { animated, transitionSpec, onOpen, onClose, onTransition, gesture } =
|
|
198
198
|
this.props;
|
|
199
199
|
|
|
200
200
|
const toValue = this.getAnimateToValue({
|
|
@@ -211,36 +211,50 @@ export class Card extends React.Component<Props> {
|
|
|
211
211
|
const animation =
|
|
212
212
|
spec.animation === 'spring' ? Animated.spring : Animated.timing;
|
|
213
213
|
|
|
214
|
-
this.setPointerEventsEnabled(!closing);
|
|
215
|
-
this.handleStartInteraction();
|
|
216
|
-
|
|
217
214
|
clearTimeout(this.pendingGestureCallback);
|
|
218
215
|
|
|
216
|
+
if (this.animationHandle !== undefined) {
|
|
217
|
+
cancelAnimationFrame(this.animationHandle);
|
|
218
|
+
}
|
|
219
|
+
|
|
219
220
|
onTransition?.({ closing, gesture: velocity !== undefined });
|
|
220
|
-
animation(gesture, {
|
|
221
|
-
...spec.config,
|
|
222
|
-
velocity,
|
|
223
|
-
toValue,
|
|
224
|
-
useNativeDriver,
|
|
225
|
-
isInteraction: false,
|
|
226
|
-
}).start(({ finished }) => {
|
|
227
|
-
this.handleEndInteraction();
|
|
228
|
-
|
|
229
|
-
clearTimeout(this.pendingGestureCallback);
|
|
230
|
-
|
|
231
|
-
if (finished) {
|
|
232
|
-
if (closing) {
|
|
233
|
-
onClose();
|
|
234
|
-
} else {
|
|
235
|
-
onOpen();
|
|
236
|
-
}
|
|
237
221
|
|
|
222
|
+
const onFinish = () => {
|
|
223
|
+
if (closing) {
|
|
224
|
+
onClose();
|
|
225
|
+
} else {
|
|
226
|
+
onOpen();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.animationHandle = requestAnimationFrame(() => {
|
|
238
230
|
if (this.isCurrentlyMounted) {
|
|
239
231
|
// Make sure to re-open screen if it wasn't removed
|
|
240
232
|
this.forceUpdate();
|
|
241
233
|
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (animated) {
|
|
238
|
+
this.handleStartInteraction();
|
|
239
|
+
|
|
240
|
+
animation(gesture, {
|
|
241
|
+
...spec.config,
|
|
242
|
+
velocity,
|
|
243
|
+
toValue,
|
|
244
|
+
useNativeDriver,
|
|
245
|
+
isInteraction: false,
|
|
246
|
+
}).start(({ finished }) => {
|
|
247
|
+
this.handleEndInteraction();
|
|
248
|
+
|
|
249
|
+
clearTimeout(this.pendingGestureCallback);
|
|
250
|
+
|
|
251
|
+
if (finished) {
|
|
252
|
+
onFinish();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
onFinish();
|
|
257
|
+
}
|
|
244
258
|
};
|
|
245
259
|
|
|
246
260
|
private getAnimateToValue = ({
|
|
@@ -267,12 +281,6 @@ export class Card extends React.Component<Props> {
|
|
|
267
281
|
);
|
|
268
282
|
};
|
|
269
283
|
|
|
270
|
-
private setPointerEventsEnabled = (enabled: boolean) => {
|
|
271
|
-
const pointerEvents = enabled ? 'box-none' : 'none';
|
|
272
|
-
|
|
273
|
-
this.ref.current?.setPointerEvents(pointerEvents);
|
|
274
|
-
};
|
|
275
|
-
|
|
276
284
|
private handleStartInteraction = () => {
|
|
277
285
|
if (this.interactionHandle === undefined) {
|
|
278
286
|
this.interactionHandle = InteractionManager.createInteractionHandle();
|
|
@@ -462,8 +470,6 @@ export class Card extends React.Component<Props> {
|
|
|
462
470
|
}
|
|
463
471
|
}
|
|
464
472
|
|
|
465
|
-
private ref = React.createRef<CardSheetRef>();
|
|
466
|
-
|
|
467
473
|
render() {
|
|
468
474
|
const {
|
|
469
475
|
styleInterpolator,
|
|
@@ -482,20 +488,6 @@ export class Card extends React.Component<Props> {
|
|
|
482
488
|
children,
|
|
483
489
|
containerStyle: customContainerStyle,
|
|
484
490
|
contentStyle,
|
|
485
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
486
|
-
closing,
|
|
487
|
-
direction,
|
|
488
|
-
gestureResponseDistance,
|
|
489
|
-
gestureVelocityImpact,
|
|
490
|
-
onClose,
|
|
491
|
-
onGestureBegin,
|
|
492
|
-
onGestureCanceled,
|
|
493
|
-
onGestureEnd,
|
|
494
|
-
onOpen,
|
|
495
|
-
onTransition,
|
|
496
|
-
transitionSpec,
|
|
497
|
-
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
498
|
-
...rest
|
|
499
491
|
} = this.props;
|
|
500
492
|
|
|
501
493
|
const interpolationProps = this.getCardAnimation(
|
|
@@ -540,72 +532,65 @@ export class Card extends React.Component<Props> {
|
|
|
540
532
|
|
|
541
533
|
return (
|
|
542
534
|
<CardAnimationContext.Provider value={interpolationProps}>
|
|
535
|
+
{Platform.OS !== 'web' ? (
|
|
536
|
+
<Animated.View
|
|
537
|
+
style={{
|
|
538
|
+
// This is a dummy style that doesn't actually change anything visually.
|
|
539
|
+
// Animated needs the animated value to be used somewhere, otherwise things don't update properly.
|
|
540
|
+
// If we disable animations and hide header, it could end up making the value unused.
|
|
541
|
+
// So we have this dummy style that will always be used regardless of what else changed.
|
|
542
|
+
opacity: current,
|
|
543
|
+
}}
|
|
544
|
+
// Make sure that this view isn't removed. If this view is removed, our style with animated value won't apply
|
|
545
|
+
collapsable={false}
|
|
546
|
+
/>
|
|
547
|
+
) : null}
|
|
548
|
+
{overlayEnabled ? (
|
|
549
|
+
<View pointerEvents="box-none" style={StyleSheet.absoluteFill}>
|
|
550
|
+
{overlay({ style: overlayStyle })}
|
|
551
|
+
</View>
|
|
552
|
+
) : null}
|
|
543
553
|
<Animated.View
|
|
544
|
-
style={
|
|
545
|
-
// This is a dummy style that doesn't actually change anything visually.
|
|
546
|
-
// Animated needs the animated value to be used somewhere, otherwise things don't update properly.
|
|
547
|
-
// If we disable animations and hide header, it could end up making the value unused.
|
|
548
|
-
// So we have this dummy style that will always be used regardless of what else changed.
|
|
549
|
-
opacity: current,
|
|
550
|
-
}}
|
|
551
|
-
// Make sure that this view isn't removed. If this view is removed, our style with animated value won't apply
|
|
552
|
-
collapsable={false}
|
|
553
|
-
/>
|
|
554
|
-
<View
|
|
554
|
+
style={[styles.container, containerStyle, customContainerStyle]}
|
|
555
555
|
pointerEvents="box-none"
|
|
556
|
-
// Make sure this view is not removed on the new architecture, as it causes focus loss during navigation on Android.
|
|
557
|
-
// This can happen when the view flattening results in different trees - due to `overflow` style changing in a parent.
|
|
558
|
-
collapsable={false}
|
|
559
|
-
{...rest}
|
|
560
556
|
>
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
<Animated.View
|
|
567
|
-
style={[styles.container, containerStyle, customContainerStyle]}
|
|
568
|
-
pointerEvents="box-none"
|
|
557
|
+
<PanGestureHandler
|
|
558
|
+
enabled={layout.width !== 0 && gestureEnabled}
|
|
559
|
+
onGestureEvent={handleGestureEvent}
|
|
560
|
+
onHandlerStateChange={this.handleGestureStateChange}
|
|
561
|
+
{...this.gestureActivationCriteria()}
|
|
569
562
|
>
|
|
570
|
-
<
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
onHandlerStateChange={this.handleGestureStateChange}
|
|
574
|
-
{...this.gestureActivationCriteria()}
|
|
563
|
+
<Animated.View
|
|
564
|
+
needsOffscreenAlphaCompositing={hasOpacityStyle(cardStyle)}
|
|
565
|
+
style={[styles.container, cardStyle]}
|
|
575
566
|
>
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
567
|
+
{shadowEnabled && shadowStyle && !isTransparent ? (
|
|
568
|
+
<Animated.View
|
|
569
|
+
style={[
|
|
570
|
+
styles.shadow,
|
|
571
|
+
gestureDirection === 'horizontal'
|
|
572
|
+
? [styles.shadowHorizontal, styles.shadowStart]
|
|
573
|
+
: gestureDirection === 'horizontal-inverted'
|
|
574
|
+
? [styles.shadowHorizontal, styles.shadowEnd]
|
|
575
|
+
: gestureDirection === 'vertical'
|
|
576
|
+
? [styles.shadowVertical, styles.shadowTop]
|
|
577
|
+
: [styles.shadowVertical, styles.shadowBottom],
|
|
578
|
+
{ backgroundColor },
|
|
579
|
+
shadowStyle,
|
|
580
|
+
]}
|
|
581
|
+
pointerEvents="none"
|
|
582
|
+
/>
|
|
583
|
+
) : null}
|
|
584
|
+
<CardContent
|
|
585
|
+
enabled={pageOverflowEnabled}
|
|
586
|
+
layout={layout}
|
|
587
|
+
style={contentStyle}
|
|
579
588
|
>
|
|
580
|
-
{
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
? [styles.shadowHorizontal, styles.shadowStart]
|
|
586
|
-
: gestureDirection === 'horizontal-inverted'
|
|
587
|
-
? [styles.shadowHorizontal, styles.shadowEnd]
|
|
588
|
-
: gestureDirection === 'vertical'
|
|
589
|
-
? [styles.shadowVertical, styles.shadowTop]
|
|
590
|
-
: [styles.shadowVertical, styles.shadowBottom],
|
|
591
|
-
{ backgroundColor },
|
|
592
|
-
shadowStyle,
|
|
593
|
-
]}
|
|
594
|
-
pointerEvents="none"
|
|
595
|
-
/>
|
|
596
|
-
) : null}
|
|
597
|
-
<CardSheet
|
|
598
|
-
ref={this.ref}
|
|
599
|
-
enabled={pageOverflowEnabled}
|
|
600
|
-
layout={layout}
|
|
601
|
-
style={contentStyle}
|
|
602
|
-
>
|
|
603
|
-
{children}
|
|
604
|
-
</CardSheet>
|
|
605
|
-
</Animated.View>
|
|
606
|
-
</PanGestureHandler>
|
|
607
|
-
</Animated.View>
|
|
608
|
-
</View>
|
|
589
|
+
{children}
|
|
590
|
+
</CardContent>
|
|
591
|
+
</Animated.View>
|
|
592
|
+
</PanGestureHandler>
|
|
593
|
+
</Animated.View>
|
|
609
594
|
</CardAnimationContext.Provider>
|
|
610
595
|
);
|
|
611
596
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Platform, StyleSheet, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
focused: boolean;
|
|
6
|
+
active: boolean;
|
|
7
|
+
animated: boolean;
|
|
8
|
+
isNextScreenTransparent: boolean;
|
|
9
|
+
detachCurrentScreen: boolean;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type CardA11yWrapperRef = { setInert: (value: boolean) => void };
|
|
14
|
+
|
|
15
|
+
export const CardA11yWrapper = React.forwardRef(
|
|
16
|
+
(
|
|
17
|
+
{
|
|
18
|
+
focused,
|
|
19
|
+
active,
|
|
20
|
+
animated,
|
|
21
|
+
isNextScreenTransparent,
|
|
22
|
+
detachCurrentScreen,
|
|
23
|
+
children,
|
|
24
|
+
}: Props,
|
|
25
|
+
ref: React.Ref<CardA11yWrapperRef>
|
|
26
|
+
) => {
|
|
27
|
+
// Manage this in separate component to avoid re-rendering card during gestures
|
|
28
|
+
// Otherwise the gesture animation will be interrupted as state hasn't updated yet
|
|
29
|
+
const [inert, setInert] = React.useState(false);
|
|
30
|
+
|
|
31
|
+
React.useImperativeHandle(ref, () => ({ setInert }), []);
|
|
32
|
+
|
|
33
|
+
const isHidden =
|
|
34
|
+
!animated &&
|
|
35
|
+
isNextScreenTransparent === false &&
|
|
36
|
+
detachCurrentScreen !== false &&
|
|
37
|
+
!focused;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View
|
|
41
|
+
aria-hidden={!focused}
|
|
42
|
+
pointerEvents={(animated ? inert : !focused) ? 'none' : 'box-none'}
|
|
43
|
+
style={{
|
|
44
|
+
...StyleSheet.absoluteFillObject,
|
|
45
|
+
// This is necessary to avoid unfocused larger pages increasing scroll area
|
|
46
|
+
// The issue can be seen on the web when a smaller screen is pushed over a larger one
|
|
47
|
+
overflow: active ? undefined : 'hidden',
|
|
48
|
+
// We use visibility on web
|
|
49
|
+
display: Platform.OS !== 'web' && isHidden ? 'none' : 'flex',
|
|
50
|
+
// Hide unfocused screens when animation isn't enabled
|
|
51
|
+
// This is also necessary for a11y on web
|
|
52
|
+
// @ts-expect-error visibility is only available on web
|
|
53
|
+
visibility: isHidden ? 'hidden' : 'visible',
|
|
54
|
+
}}
|
|
55
|
+
// Make sure this view is not removed on the new architecture, as it causes focus loss during navigation on Android.
|
|
56
|
+
// This can happen when the view flattening results in different trees - due to `overflow` style changing in a parent.
|
|
57
|
+
// `Container` has `collapsable={false}` by default so we don't need to set it here.
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</View>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
CardA11yWrapper.displayName = 'CardA11yWrapper';
|
|
@@ -18,6 +18,7 @@ import { ModalPresentationContext } from '../../utils/ModalPresentationContext';
|
|
|
18
18
|
import { useKeyboardManager } from '../../utils/useKeyboardManager';
|
|
19
19
|
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
|
20
20
|
import { Card } from './Card';
|
|
21
|
+
import { CardA11yWrapper, type CardA11yWrapperRef } from './CardA11yWrapper';
|
|
21
22
|
|
|
22
23
|
type Props = {
|
|
23
24
|
interpolationIndex: number;
|
|
@@ -94,6 +95,8 @@ function CardContainerInner({
|
|
|
94
95
|
safeAreaInsetTop,
|
|
95
96
|
scene,
|
|
96
97
|
}: Props) {
|
|
98
|
+
const wrapperRef = React.useRef<CardA11yWrapperRef>(null);
|
|
99
|
+
|
|
97
100
|
const { direction } = useLocale();
|
|
98
101
|
|
|
99
102
|
const parentHeaderHeight = React.useContext(HeaderHeightContext);
|
|
@@ -150,6 +153,8 @@ function CardContainerInner({
|
|
|
150
153
|
closing: boolean;
|
|
151
154
|
gesture: boolean;
|
|
152
155
|
}) => {
|
|
156
|
+
wrapperRef.current?.setInert(closing);
|
|
157
|
+
|
|
153
158
|
const { route } = scene.descriptor;
|
|
154
159
|
|
|
155
160
|
if (!gesture) {
|
|
@@ -172,14 +177,10 @@ function CardContainerInner({
|
|
|
172
177
|
|
|
173
178
|
const { colors } = useTheme();
|
|
174
179
|
|
|
175
|
-
const [pointerEvents, setPointerEvents] = React.useState<'box-none' | 'none'>(
|
|
176
|
-
'box-none'
|
|
177
|
-
);
|
|
178
|
-
|
|
179
180
|
React.useEffect(() => {
|
|
180
181
|
const listener = scene.progress.next?.addListener?.(
|
|
181
182
|
({ value }: { value: number }) => {
|
|
182
|
-
|
|
183
|
+
wrapperRef.current?.setInert(value > EPSILON);
|
|
183
184
|
}
|
|
184
185
|
);
|
|
185
186
|
|
|
@@ -188,7 +189,7 @@ function CardContainerInner({
|
|
|
188
189
|
scene.progress.next?.removeListener?.(listener);
|
|
189
190
|
}
|
|
190
191
|
};
|
|
191
|
-
}, [
|
|
192
|
+
}, [scene.progress.next]);
|
|
192
193
|
|
|
193
194
|
const {
|
|
194
195
|
presentation,
|
|
@@ -232,101 +233,94 @@ function CardContainerInner({
|
|
|
232
233
|
return undefined;
|
|
233
234
|
}, [canGoBack, backTitle, href]);
|
|
234
235
|
|
|
236
|
+
const animated = animation !== 'none';
|
|
237
|
+
|
|
235
238
|
return (
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
current={scene.progress.current}
|
|
244
|
-
next={scene.progress.next}
|
|
245
|
-
opening={opening}
|
|
246
|
-
closing={closing}
|
|
247
|
-
onOpen={handleOpen}
|
|
248
|
-
onClose={handleClose}
|
|
249
|
-
overlay={cardOverlay}
|
|
250
|
-
overlayEnabled={cardOverlayEnabled}
|
|
251
|
-
shadowEnabled={cardShadowEnabled}
|
|
252
|
-
onTransition={handleTransition}
|
|
253
|
-
onGestureBegin={handleGestureBegin}
|
|
254
|
-
onGestureCanceled={handleGestureCanceled}
|
|
255
|
-
onGestureEnd={handleGestureEnd}
|
|
256
|
-
gestureEnabled={index === 0 ? false : gestureEnabled}
|
|
257
|
-
gestureResponseDistance={gestureResponseDistance}
|
|
258
|
-
gestureVelocityImpact={gestureVelocityImpact}
|
|
259
|
-
transitionSpec={transitionSpec}
|
|
260
|
-
styleInterpolator={cardStyleInterpolator}
|
|
261
|
-
aria-hidden={!focused}
|
|
262
|
-
pointerEvents={active ? 'box-none' : pointerEvents}
|
|
263
|
-
pageOverflowEnabled={headerMode !== 'float' && presentation !== 'modal'}
|
|
264
|
-
preloaded={preloaded}
|
|
265
|
-
containerStyle={
|
|
266
|
-
hasAbsoluteFloatHeader && headerMode !== 'screen'
|
|
267
|
-
? { marginTop: headerHeight }
|
|
268
|
-
: null
|
|
269
|
-
}
|
|
270
|
-
contentStyle={[
|
|
271
|
-
{
|
|
272
|
-
backgroundColor:
|
|
273
|
-
presentation === 'transparentModal'
|
|
274
|
-
? 'transparent'
|
|
275
|
-
: colors.background,
|
|
276
|
-
},
|
|
277
|
-
cardStyle,
|
|
278
|
-
]}
|
|
279
|
-
style={[
|
|
280
|
-
{
|
|
281
|
-
// This is necessary to avoid unfocused larger pages increasing scroll area
|
|
282
|
-
// The issue can be seen on the web when a smaller screen is pushed over a larger one
|
|
283
|
-
overflow: active ? undefined : 'hidden',
|
|
284
|
-
display:
|
|
285
|
-
// Hide unfocused screens when animation isn't enabled
|
|
286
|
-
// This is also necessary for a11y on web
|
|
287
|
-
animation === 'none' &&
|
|
288
|
-
isNextScreenTransparent === false &&
|
|
289
|
-
detachCurrentScreen !== false &&
|
|
290
|
-
!focused
|
|
291
|
-
? 'none'
|
|
292
|
-
: 'flex',
|
|
293
|
-
},
|
|
294
|
-
StyleSheet.absoluteFill,
|
|
295
|
-
]}
|
|
239
|
+
<CardA11yWrapper
|
|
240
|
+
ref={wrapperRef}
|
|
241
|
+
focused={focused}
|
|
242
|
+
active={active}
|
|
243
|
+
animated={animated}
|
|
244
|
+
isNextScreenTransparent={isNextScreenTransparent}
|
|
245
|
+
detachCurrentScreen={detachCurrentScreen}
|
|
296
246
|
>
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
247
|
+
<Card
|
|
248
|
+
animated={animated}
|
|
249
|
+
interpolationIndex={interpolationIndex}
|
|
250
|
+
gestureDirection={gestureDirection}
|
|
251
|
+
layout={layout}
|
|
252
|
+
insets={insets}
|
|
253
|
+
direction={direction}
|
|
254
|
+
gesture={gesture}
|
|
255
|
+
current={scene.progress.current}
|
|
256
|
+
next={scene.progress.next}
|
|
257
|
+
opening={opening}
|
|
258
|
+
closing={closing}
|
|
259
|
+
onOpen={handleOpen}
|
|
260
|
+
onClose={handleClose}
|
|
261
|
+
overlay={cardOverlay}
|
|
262
|
+
overlayEnabled={cardOverlayEnabled}
|
|
263
|
+
shadowEnabled={cardShadowEnabled}
|
|
264
|
+
onTransition={handleTransition}
|
|
265
|
+
onGestureBegin={handleGestureBegin}
|
|
266
|
+
onGestureCanceled={handleGestureCanceled}
|
|
267
|
+
onGestureEnd={handleGestureEnd}
|
|
268
|
+
gestureEnabled={index === 0 ? false : gestureEnabled}
|
|
269
|
+
gestureResponseDistance={gestureResponseDistance}
|
|
270
|
+
gestureVelocityImpact={gestureVelocityImpact}
|
|
271
|
+
transitionSpec={transitionSpec}
|
|
272
|
+
styleInterpolator={cardStyleInterpolator}
|
|
273
|
+
pageOverflowEnabled={headerMode !== 'float' && presentation !== 'modal'}
|
|
274
|
+
preloaded={preloaded}
|
|
275
|
+
containerStyle={
|
|
276
|
+
hasAbsoluteFloatHeader && headerMode !== 'screen'
|
|
277
|
+
? { marginTop: headerHeight }
|
|
278
|
+
: null
|
|
279
|
+
}
|
|
280
|
+
contentStyle={[
|
|
281
|
+
{
|
|
282
|
+
backgroundColor:
|
|
283
|
+
presentation === 'transparentModal'
|
|
284
|
+
? 'transparent'
|
|
285
|
+
: colors.background,
|
|
286
|
+
},
|
|
287
|
+
cardStyle,
|
|
288
|
+
]}
|
|
289
|
+
>
|
|
290
|
+
<View style={styles.container}>
|
|
291
|
+
<ModalPresentationContext.Provider value={modal}>
|
|
292
|
+
{headerMode !== 'float'
|
|
293
|
+
? renderHeader({
|
|
294
|
+
mode: 'screen',
|
|
295
|
+
layout,
|
|
296
|
+
scenes: [previousScene, scene],
|
|
297
|
+
getPreviousScene,
|
|
298
|
+
getFocusedRoute,
|
|
299
|
+
onContentHeightChange: onHeaderHeightChange,
|
|
300
|
+
style: styles.header,
|
|
301
|
+
})
|
|
302
|
+
: null}
|
|
303
|
+
<View style={styles.scene}>
|
|
304
|
+
<HeaderBackContext.Provider value={headerBack}>
|
|
305
|
+
<HeaderShownContext.Provider
|
|
306
|
+
value={isParentHeaderShown || headerShown !== false}
|
|
321
307
|
>
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
308
|
+
<HeaderHeightContext.Provider
|
|
309
|
+
value={
|
|
310
|
+
headerShown !== false
|
|
311
|
+
? headerHeight
|
|
312
|
+
: (parentHeaderHeight ?? 0)
|
|
313
|
+
}
|
|
314
|
+
>
|
|
315
|
+
{scene.descriptor.render()}
|
|
316
|
+
</HeaderHeightContext.Provider>
|
|
317
|
+
</HeaderShownContext.Provider>
|
|
318
|
+
</HeaderBackContext.Provider>
|
|
319
|
+
</View>
|
|
320
|
+
</ModalPresentationContext.Provider>
|
|
321
|
+
</View>
|
|
322
|
+
</Card>
|
|
323
|
+
</CardA11yWrapper>
|
|
330
324
|
);
|
|
331
325
|
}
|
|
332
326
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { StyleSheet, View, type ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type Props = ViewProps & {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
layout: { width: number; height: number };
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// This component will render a page which overflows the screen
|
|
11
|
+
// if the container fills the body by comparing the size
|
|
12
|
+
// This lets the document.body handle scrolling of the content
|
|
13
|
+
// It's necessary for mobile browsers to be able to hide address bar on scroll
|
|
14
|
+
export function CardContent({ enabled, layout, style, ...rest }: Props) {
|
|
15
|
+
const [fill, setFill] = React.useState(false);
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
if (typeof document === 'undefined' || !document.body) {
|
|
19
|
+
// Only run when DOM is available
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const width = document.body.clientWidth;
|
|
24
|
+
const height = document.body.clientHeight;
|
|
25
|
+
|
|
26
|
+
// Workaround for mobile Chrome, necessary when a navigation happens
|
|
27
|
+
// when the address bar has already collapsed, which resulted in an
|
|
28
|
+
// empty space at the bottom of the page (matching the height of the
|
|
29
|
+
// address bar). To fix this, it's necessary to update the height of
|
|
30
|
+
// the DOM with the current height of the window.
|
|
31
|
+
// See https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
|
32
|
+
const isFullHeight = height === layout.height;
|
|
33
|
+
const id = '__react-navigation-stack-mobile-chrome-viewport-fix';
|
|
34
|
+
|
|
35
|
+
let unsubscribe: (() => void) | undefined;
|
|
36
|
+
|
|
37
|
+
if (isFullHeight && navigator.maxTouchPoints > 0) {
|
|
38
|
+
const style =
|
|
39
|
+
document.getElementById(id) ?? document.createElement('style');
|
|
40
|
+
|
|
41
|
+
style.id = id;
|
|
42
|
+
|
|
43
|
+
const updateStyle = () => {
|
|
44
|
+
const vh = window.innerHeight * 0.01;
|
|
45
|
+
|
|
46
|
+
style.textContent = [
|
|
47
|
+
`:root { --vh: ${vh}px; }`,
|
|
48
|
+
`body { height: calc(var(--vh, 1vh) * 100); }`,
|
|
49
|
+
].join('\n');
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
updateStyle();
|
|
53
|
+
|
|
54
|
+
if (!document.head.contains(style)) {
|
|
55
|
+
document.head.appendChild(style);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
window.addEventListener('resize', updateStyle);
|
|
59
|
+
|
|
60
|
+
unsubscribe = () => {
|
|
61
|
+
window.removeEventListener('resize', updateStyle);
|
|
62
|
+
};
|
|
63
|
+
} else {
|
|
64
|
+
// Remove the workaround if the stack does not occupy the whole
|
|
65
|
+
// height of the page
|
|
66
|
+
document.getElementById(id)?.remove();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect
|
|
70
|
+
setFill(width === layout.width && height === layout.height);
|
|
71
|
+
|
|
72
|
+
return unsubscribe;
|
|
73
|
+
}, [layout.height, layout.width]);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<View
|
|
77
|
+
{...rest}
|
|
78
|
+
pointerEvents="box-none"
|
|
79
|
+
style={[enabled && fill ? styles.page : styles.card, style]}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const styles = StyleSheet.create({
|
|
85
|
+
page: {
|
|
86
|
+
minHeight: '100%',
|
|
87
|
+
},
|
|
88
|
+
card: {
|
|
89
|
+
flex: 1,
|
|
90
|
+
overflow: 'hidden',
|
|
91
|
+
},
|
|
92
|
+
});
|