Files
react-native/Libraries/Components/Pressable/Pressable.js
T
Pieter Vanderwerff 66c6a75650 Suppress missing annotations in xplat/js
Summary: Add annotations to function parameters required for Flow's Local Type Inference project. This codemod prepares the codebase to match Flow's new typechecking algorithm. The new algorithm will make Flow more reliable and predictable.

Reviewed By: bradzacher

Differential Revision: D37388949

fbshipit-source-id: cdcbc98035ce9b6994842005ea46df42de54f9b8
2022-06-23 16:54:29 -07:00

304 lines
7.7 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import * as React from 'react';
import {useMemo, useState, useRef, useImperativeHandle} from 'react';
import useAndroidRippleForView, {
type RippleConfig,
} from './useAndroidRippleForView';
import type {
AccessibilityActionEvent,
AccessibilityActionInfo,
AccessibilityRole,
AccessibilityState,
AccessibilityValue,
} from '../View/ViewAccessibility';
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
import usePressability from '../../Pressability/usePressability';
import {type RectOrSize} from '../../StyleSheet/Rect';
import type {
LayoutEvent,
MouseEvent,
PressEvent,
} from '../../Types/CoreEventTypes';
import View from '../View/View';
type ViewStyleProp = $ElementType<React.ElementConfig<typeof View>, 'style'>;
export type StateCallbackType = $ReadOnly<{|
pressed: boolean,
|}>;
type Props = $ReadOnly<{|
/**
* Accessibility.
*/
accessibilityActions?: ?$ReadOnlyArray<AccessibilityActionInfo>,
accessibilityElementsHidden?: ?boolean,
accessibilityHint?: ?Stringish,
accessibilityLanguage?: ?Stringish,
accessibilityIgnoresInvertColors?: ?boolean,
accessibilityLabel?: ?Stringish,
accessibilityLiveRegion?: ?('none' | 'polite' | 'assertive'),
accessibilityRole?: ?AccessibilityRole,
accessibilityState?: ?AccessibilityState,
accessibilityValue?: ?AccessibilityValue,
accessibilityViewIsModal?: ?boolean,
accessible?: ?boolean,
focusable?: ?boolean,
importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'),
onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed,
/**
* Whether a press gesture can be interrupted by a parent gesture such as a
* scroll event. Defaults to true.
*/
cancelable?: ?boolean,
/**
* Either children or a render prop that receives a boolean reflecting whether
* the component is currently pressed.
*/
children: React.Node | ((state: StateCallbackType) => React.Node),
/**
* Duration to wait after hover in before calling `onHoverIn`.
*/
delayHoverIn?: ?number,
/**
* Duration to wait after hover out before calling `onHoverOut`.
*/
delayHoverOut?: ?number,
/**
* Duration (in milliseconds) from `onPressIn` before `onLongPress` is called.
*/
delayLongPress?: ?number,
/**
* Whether the press behavior is disabled.
*/
disabled?: ?boolean,
/**
* Additional distance outside of this view in which a press is detected.
*/
hitSlop?: ?RectOrSize,
/**
* Additional distance outside of this view in which a touch is considered a
* press before `onPressOut` is triggered.
*/
pressRetentionOffset?: ?RectOrSize,
/**
* Called when this view's layout changes.
*/
onLayout?: ?(event: LayoutEvent) => mixed,
/**
* Called when the hover is activated to provide visual feedback.
*/
onHoverIn?: ?(event: MouseEvent) => mixed,
/**
* Called when the hover is deactivated to undo visual feedback.
*/
onHoverOut?: ?(event: MouseEvent) => mixed,
/**
* Called when a long-tap gesture is detected.
*/
onLongPress?: ?(event: PressEvent) => mixed,
/**
* Called when a single tap gesture is detected.
*/
onPress?: ?(event: PressEvent) => mixed,
/**
* Called when a touch is engaged before `onPress`.
*/
onPressIn?: ?(event: PressEvent) => mixed,
/**
* Called when a touch is released before `onPress`.
*/
onPressOut?: ?(event: PressEvent) => mixed,
/**
* Either view styles or a function that receives a boolean reflecting whether
* the component is currently pressed and returns view styles.
*/
style?: ViewStyleProp | ((state: StateCallbackType) => ViewStyleProp),
/**
* Identifier used to find this view in tests.
*/
testID?: ?string,
/**
* If true, doesn't play system sound on touch.
*/
android_disableSound?: ?boolean,
/**
* Enables the Android ripple effect and configures its color.
*/
android_ripple?: ?RippleConfig,
/**
* Used only for documentation or testing (e.g. snapshot testing).
*/
testOnly_pressed?: ?boolean,
/**
* Duration to wait after press down before calling `onPressIn`.
*/
unstable_pressDelay?: ?number,
|}>;
/**
* Component used to build display components that should respond to whether the
* component is currently pressed or not.
*/
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
function Pressable(props: Props, forwardedRef): React.Node {
const {
accessible,
android_disableSound,
android_ripple,
cancelable,
children,
delayHoverIn,
delayHoverOut,
delayLongPress,
disabled,
focusable,
hitSlop,
onHoverIn,
onHoverOut,
onLongPress,
onPress,
onPressIn,
onPressOut,
pressRetentionOffset,
style,
testOnly_pressed,
unstable_pressDelay,
...restProps
} = props;
const viewRef = useRef<React.ElementRef<typeof View> | null>(null);
useImperativeHandle(forwardedRef, () => viewRef.current);
const android_rippleConfig = useAndroidRippleForView(android_ripple, viewRef);
const [pressed, setPressed] = usePressState(testOnly_pressed === true);
const accessibilityState =
disabled != null
? {...props.accessibilityState, disabled}
: props.accessibilityState;
const restPropsWithDefaults: React.ElementConfig<typeof View> = {
...restProps,
...android_rippleConfig?.viewProps,
accessible: accessible !== false,
accessibilityState,
focusable: focusable !== false,
hitSlop,
};
const config = useMemo(
() => ({
cancelable,
disabled,
hitSlop,
pressRectOffset: pressRetentionOffset,
android_disableSound,
delayHoverIn,
delayHoverOut,
delayLongPress,
delayPressIn: unstable_pressDelay,
onHoverIn,
onHoverOut,
onLongPress,
onPress,
onPressIn(event: PressEvent): void {
if (android_rippleConfig != null) {
android_rippleConfig.onPressIn(event);
}
setPressed(true);
if (onPressIn != null) {
onPressIn(event);
}
},
onPressMove: android_rippleConfig?.onPressMove,
onPressOut(event: PressEvent): void {
if (android_rippleConfig != null) {
android_rippleConfig.onPressOut(event);
}
setPressed(false);
if (onPressOut != null) {
onPressOut(event);
}
},
}),
[
android_disableSound,
android_rippleConfig,
cancelable,
delayHoverIn,
delayHoverOut,
delayLongPress,
disabled,
hitSlop,
onHoverIn,
onHoverOut,
onLongPress,
onPress,
onPressIn,
onPressOut,
pressRetentionOffset,
setPressed,
unstable_pressDelay,
],
);
const eventHandlers = usePressability(config);
return (
<View
{...restPropsWithDefaults}
{...eventHandlers}
ref={viewRef}
style={typeof style === 'function' ? style({pressed}) : style}
collapsable={false}>
{typeof children === 'function' ? children({pressed}) : children}
{__DEV__ ? <PressabilityDebugView color="red" hitSlop={hitSlop} /> : null}
</View>
);
}
function usePressState(forcePressed: boolean): [boolean, (boolean) => void] {
const [pressed, setPressed] = useState(false);
return [pressed || forcePressed, setPressed];
}
const MemoedPressable = React.memo(React.forwardRef(Pressable));
MemoedPressable.displayName = 'Pressable';
export default (MemoedPressable: React.AbstractComponent<
Props,
React.ElementRef<typeof View>,
>);