chore: sync latest stack

This commit is contained in:
Satyajit Sahoo
2020-09-24 13:13:12 +02:00
parent 3bb21e256f
commit ce9991ffff
20 changed files with 214 additions and 96 deletions

View File

@@ -99,6 +99,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
>
<Text
accessibilityRole="header"
aria-level="1"
numberOfLines={1}
onLayout={[Function]}
style={
@@ -152,6 +153,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
onClose={[Function]}
onGestureBegin={[Function]}
onGestureCanceled={[Function]}
onGestureEnd={[Function]}
onOpen={[Function]}
onTransitionStart={[Function]}
pointerEvents="box-none"
@@ -319,6 +321,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
onClose={[Function]}
onGestureBegin={[Function]}
onGestureCanceled={[Function]}
onGestureEnd={[Function]}
onOpen={[Function]}
onTransitionStart={[Function]}
pointerEvents="box-none"

View File

@@ -100,6 +100,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
>
<Text
accessibilityRole="header"
aria-level="1"
numberOfLines={1}
onLayout={[Function]}
style={
@@ -169,6 +170,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
onClose={[Function]}
onGestureBegin={[Function]}
onGestureCanceled={[Function]}
onGestureEnd={[Function]}
onOpen={[Function]}
onTransitionStart={[Function]}
pointerEvents="box-none"
@@ -404,6 +406,7 @@ exports[`StackNavigator renders successfully 1`] = `
>
<Text
accessibilityRole="header"
aria-level="1"
numberOfLines={1}
onLayout={[Function]}
style={
@@ -457,6 +460,7 @@ exports[`StackNavigator renders successfully 1`] = `
onClose={[Function]}
onGestureBegin={[Function]}
onGestureCanceled={[Function]}
onGestureEnd={[Function]}
onOpen={[Function]}
onTransitionStart={[Function]}
pointerEvents="box-none"

View File

@@ -41,6 +41,18 @@ export type StackNavigationEventMap = {
* Event which fires when a transition animation ends.
*/
transitionEnd: { data: { closing: boolean } };
/**
* Event which fires when navigation gesture starts.
*/
gestureStart: { data: undefined };
/**
* Event which fires when navigation gesture is completed.
*/
gestureEnd: { data: undefined };
/**
* Event which fires when navigation gesture is canceled.
*/
gestureCancel: { data: undefined };
};
export type StackNavigationHelpers = NavigationProp<NavigationStackState>;
@@ -125,7 +137,7 @@ export type StackHeaderOptions = {
/**
* Style object for the title component.
*/
headerTitleStyle?: React.ComponentProps<typeof Animated.Text>['style'];
headerTitleStyle?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
/**
* Style object for the container of the `headerTitle` component, for example to add padding.
* By default, `headerTitleContainerStyle` is with an absolute position style and offsets both `left` and `right`.
@@ -426,6 +438,10 @@ export type StackHeaderLeftButtonProps = {
* Accessibility label for the button for screen readers.
*/
accessibilityLabel?: string;
/**
* Style object for the button.
*/
style?: StyleProp<ViewStyle>;
};
export type StackHeaderTitleProps = {
@@ -448,7 +464,7 @@ export type StackHeaderTitleProps = {
/**
* Style object for the title element.
*/
style?: React.ComponentProps<typeof Animated.Text>['style'];
style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
};
export type TransitionSpec =

View File

@@ -0,0 +1 @@
export * from './GestureHandlerNative';

View File

@@ -0,0 +1 @@
export * from './GestureHandlerNative';

View File

@@ -8,9 +8,9 @@ import {
StyleSheet,
LayoutChangeEvent,
} from 'react-native';
import MaskedView from '../MaskedView';
import { TouchableItem } from '../TouchableItem';
import useTheme from '../../../utils/useTheme';
import MaskedView from '../MaskedView';
import TouchableItem from '../TouchableItem';
import type { StackHeaderLeftButtonProps } from '../../types';
type Props = StackHeaderLeftButtonProps;
@@ -30,6 +30,7 @@ export default function HeaderBackButton({
titleLayout,
truncatedLabel = 'Back',
accessibilityLabel = label && label !== 'Back' ? `${label}, back` : 'Go back',
style,
}: Props) {
const { dark, colors } = useTheme();
@@ -160,7 +161,7 @@ export default function HeaderBackButton({
delayPressIn={0}
onPress={disabled ? undefined : handlePress}
pressColor={pressColorAndroid}
style={[styles.container, disabled && styles.disabled]}
style={[styles.container, disabled && styles.disabled, style]}
hitSlop={Platform.select({
ios: undefined,
default: { top: 16, right: 16, bottom: 16, left: 16 },

View File

@@ -223,7 +223,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
warnIfHeaderStylesDefined(unsafeStyles);
}
const safeStyles = {
const safeStyles: ViewStyle = {
backgroundColor,
borderBottomColor,
borderBottomEndRadius,
@@ -249,6 +249,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
borderTopStartRadius,
borderTopWidth,
borderWidth,
// @ts-expect-error: boxShadow is only for Web
boxShadow,
elevation,
shadowColor,
@@ -314,10 +315,8 @@ export default class HeaderSegment extends React.Component<Props, State> {
style={[StyleSheet.absoluteFill, { zIndex: 0 }, backgroundStyle]}
>
{headerBackground ? (
// @ts-ignore
headerBackground({ style: safeStyles })
) : headerTransparent ? null : (
// @ts-ignore
<HeaderBackground style={safeStyles} />
)}
</Animated.View>

View File

@@ -1,10 +1,18 @@
import * as React from 'react';
import { Animated, StyleSheet, Platform } from 'react-native';
import {
Animated,
StyleSheet,
Platform,
TextProps,
StyleProp,
TextStyle,
} from 'react-native';
import useTheme from '../../../utils/useTheme';
type Props = Omit<React.ComponentProps<typeof Animated.Text>, 'key'> & {
type Props = Omit<TextProps, 'style'> & {
tintColor?: string;
children?: string;
style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
};
export default function HeaderTitle({ tintColor, style, ...rest }: Props) {
@@ -13,6 +21,7 @@ export default function HeaderTitle({ tintColor, style, ...rest }: Props) {
return (
<Animated.Text
accessibilityRole="header"
aria-level="1"
numberOfLines={1}
{...rest}
style={[

View File

@@ -17,7 +17,7 @@ export default class KeyboardManager extends React.Component<Props> {
// Numeric id of the previously focused text input
// When a gesture didn't change the tab, we can restore the focused input with this
private previouslyFocusedTextInput: any = null;
private previouslyFocusedTextInput: any | null = null;
private startTimestamp: number = 0;
private keyboardTimeout: any;

View File

@@ -0,0 +1 @@
export { default } from './MaskedViewNative';

View File

@@ -0,0 +1 @@
export { default } from './MaskedViewNative';

View File

@@ -1,3 +1,6 @@
/**
* Use a stub for MaskedView on all Platforms that don't support it.
*/
import type * as React from 'react';
type Props = {

View File

@@ -1,3 +1,6 @@
/**
* The native MaskedView that we explicitly re-export for supported platforms: Android, iOS.
*/
import * as React from 'react';
import { UIManager } from 'react-native';
@@ -10,6 +13,8 @@ type Props = React.ComponentProps<MaskedViewType> & {
let RNCMaskedView: MaskedViewType | undefined;
try {
// Add try/catch to support usage even if it's not installed, since it's optional.
// Newer versions of Metro will handle it properly.
RNCMaskedView = require('@react-native-community/masked-view').default;
} catch (e) {
// Ignore

View File

@@ -49,6 +49,9 @@ type Props = TransitionPreset & {
onPageChangeStart?: () => void;
onPageChangeConfirm?: () => void;
onPageChangeCancel?: () => void;
onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void;
onGestureCancel?: (props: { route: Route<string> }) => void;
gestureEnabled?: boolean;
gestureResponseDistance?: {
vertical?: number;
@@ -98,6 +101,9 @@ function CardContainer({
onPageChangeCancel,
onPageChangeConfirm,
onPageChangeStart,
onGestureCancel,
onGestureEnd,
onGestureStart,
onTransitionEnd,
onTransitionStart,
renderHeader,
@@ -123,6 +129,20 @@ function CardContainer({
onCloseRoute({ route: scene.route });
};
const handleGestureBegin = () => {
onPageChangeStart?.();
onGestureStart?.({ route: scene.route });
};
const handleGestureCanceled = () => {
onPageChangeCancel?.();
onGestureCancel?.({ route: scene.route });
};
const handleGestureEnd = () => {
onGestureEnd?.({ route: scene.route });
};
const handleTransitionStart = ({ closing }: { closing: boolean }) => {
if (active && closing) {
onPageChangeConfirm?.();
@@ -182,8 +202,9 @@ function CardContainer({
overlayEnabled={cardOverlayEnabled}
shadowEnabled={cardShadowEnabled}
onTransitionStart={handleTransitionStart}
onGestureBegin={onPageChangeStart}
onGestureCanceled={onPageChangeCancel}
onGestureBegin={handleGestureBegin}
onGestureCanceled={handleGestureCanceled}
onGestureEnd={handleGestureEnd}
gestureEnabled={gestureEnabled}
gestureResponseDistance={gestureResponseDistance}
gestureVelocityImpact={gestureVelocityImpact}

View File

@@ -61,6 +61,9 @@ type Props = {
onPageChangeStart?: () => void;
onPageChangeConfirm?: () => void;
onPageChangeCancel?: () => void;
onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void;
onGestureCancel?: (props: { route: Route<string> }) => void;
};
type State = {
@@ -373,6 +376,9 @@ export default class CardStack extends React.Component<Props, State> {
onPageChangeStart,
onPageChangeConfirm,
onPageChangeCancel,
onGestureStart,
onGestureEnd,
onGestureCancel,
} = this.props;
const { scenes, layout, gestures, headerHeights } = this.state;
@@ -569,6 +575,9 @@ export default class CardStack extends React.Component<Props, State> {
onPageChangeStart={onPageChangeStart}
onPageChangeConfirm={onPageChangeConfirm}
onPageChangeCancel={onPageChangeCancel}
onGestureStart={onGestureStart}
onGestureCancel={onGestureCancel}
onGestureEnd={onGestureEnd}
gestureResponseDistance={gestureResponseDistance}
headerHeight={headerHeight}
onHeaderHeightChange={this.handleHeaderLayout}

View File

@@ -433,6 +433,18 @@ export default class StackView extends React.Component<Props, State> {
descriptor?.options.onTransitionEnd?.({ closing });
};
private handleGestureStart = () => {
// Do nothing
};
private handleGestureEnd = () => {
// Do nothing
};
private handleGestureCancel = () => {
// Do nothing
};
render() {
const {
state,
@@ -480,6 +492,9 @@ export default class StackView extends React.Component<Props, State> {
headerMode={headerMode}
state={state}
descriptors={descriptors}
onGestureStart={this.handleGestureStart}
onGestureEnd={this.handleGestureEnd}
onGestureCancel={this.handleGestureCancel}
{...rest}
{...props}
/>

View File

@@ -0,0 +1,50 @@
import * as React from 'react';
import { Animated, Platform } from 'react-native';
import { BaseButton } from 'react-native-gesture-handler';
const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton);
type Props = React.ComponentProps<typeof BaseButton> & {
activeOpacity: number;
};
const useNativeDriver = Platform.OS !== 'web';
export default class TouchableItem extends React.Component<Props> {
static defaultProps = {
activeOpacity: 0.3,
borderless: true,
enabled: true,
};
private opacity = new Animated.Value(1);
private handleActiveStateChange = (active: boolean) => {
Animated.spring(this.opacity, {
stiffness: 1000,
damping: 500,
mass: 3,
overshootClamping: true,
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
toValue: active ? this.props.activeOpacity : 1,
useNativeDriver,
}).start();
this.props.onActiveStateChange?.(active);
};
render() {
const { children, style, enabled, ...rest } = this.props;
return (
<AnimatedBaseButton
{...rest}
onActiveStateChange={this.handleActiveStateChange}
style={[style, enabled && { opacity: this.opacity }]}
>
{children}
</AnimatedBaseButton>
);
}
}

View File

@@ -1,81 +0,0 @@
/**
* TouchableItem renders a touchable that looks native on both iOS and Android.
*
* It provides an abstraction on top of TouchableNativeFeedback and
* TouchableOpacity.
*
* On iOS you can pass the props of TouchableOpacity, on Android pass the props
* of TouchableNativeFeedback.
*/
import * as React from 'react';
import {
Platform,
TouchableNativeFeedback,
TouchableOpacity,
View,
ViewProps,
} from 'react-native';
import BorderlessButton from './BorderlessButton';
export type Props = ViewProps & {
pressColor: string;
disabled?: boolean;
borderless?: boolean;
delayPressIn?: number;
onPress?: () => void;
children: React.ReactNode;
};
const ANDROID_VERSION_LOLLIPOP = 21;
export class TouchableItem extends React.Component<Props> {
static defaultProps = {
borderless: false,
pressColor: 'rgba(0, 0, 0, .32)',
};
render() {
/*
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
* therefore only enable it on Android Lollipop and above.
*
* All touchables on Android should have the ripple effect according to
* platform design guidelines.
* We need to pass the background prop to specify a borderless ripple effect.
*/
if (
Platform.OS === 'android' &&
Platform.Version >= ANDROID_VERSION_LOLLIPOP
) {
const { style, pressColor, borderless, children, ...rest } = this.props;
return (
<TouchableNativeFeedback
{...rest}
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
>
<View style={style}>{React.Children.only(children)}</View>
</TouchableNativeFeedback>
);
} else if (Platform.OS === 'ios') {
return (
<BorderlessButton
hitSlop={{ top: 10, bottom: 10, right: 10, left: 10 }}
disallowInterruption
enabled={!this.props.disabled}
{...this.props}
>
{this.props.children}
</BorderlessButton>
);
} else {
return (
<TouchableOpacity {...this.props}>
{this.props.children}
</TouchableOpacity>
);
}
}
}

View File

@@ -1,3 +1,63 @@
import { TouchableOpacity } from 'react-native';
/**
* TouchableItem provides an abstraction on top of TouchableNativeFeedback and
* TouchableOpacity to handle platform differences.
*
* On Android, you can pass the props of TouchableNativeFeedback.
* On other platforms, you can pass the props of TouchableOpacity.
*/
import * as React from 'react';
import {
Platform,
TouchableNativeFeedback,
TouchableOpacity,
View,
ViewProps,
} from 'react-native';
export const TouchableItem = (TouchableOpacity as any) as typeof import('./TouchableItem.native').TouchableItem;
export type Props = ViewProps & {
pressColor?: string;
disabled?: boolean;
borderless?: boolean;
delayPressIn?: number;
onPress?: () => void;
children: React.ReactNode;
};
const ANDROID_VERSION_LOLLIPOP = 21;
export default function TouchableItem({
borderless = false,
pressColor = 'rgba(0, 0, 0, .32)',
style,
children,
...rest
}: Props) {
/*
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
* therefore only enable it on Android Lollipop and above.
*
* All touchables on Android should have the ripple effect according to
* platform design guidelines.
* We need to pass the background prop to specify a borderless ripple effect.
*/
if (
Platform.OS === 'android' &&
Platform.Version >= ANDROID_VERSION_LOLLIPOP
) {
return (
<TouchableNativeFeedback
{...rest}
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
>
<View style={style}>{React.Children.only(children)}</View>
</TouchableNativeFeedback>
);
} else {
return (
<TouchableOpacity style={style} {...rest}>
{children}
</TouchableOpacity>
);
}
}