123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- /**
- * Copyright (c) Facebook, Inc. and its 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
- */
- 'use strict';
- 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 {normalizeRect, type RectOrSize} from '../../StyleSheet/Rect';
- import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
- import type {LayoutEvent, 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,
- 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,
- /**
- * 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 (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) => void,
- /**
- * Called when a long-tap gesture is detected.
- */
- onLongPress?: ?(event: PressEvent) => void,
- /**
- * Called when a single tap gesture is detected.
- */
- onPress?: ?(event: PressEvent) => void,
- /**
- * Called when a touch is engaged before `onPress`.
- */
- onPressIn?: ?(event: PressEvent) => void,
- /**
- * Called when a touch is released before `onPress`.
- */
- onPressOut?: ?(event: PressEvent) => void,
- /**
- * 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,
- |}>;
- /**
- * Component used to build display components that should respond to whether the
- * component is currently pressed or not.
- */
- function Pressable(props: Props, forwardedRef): React.Node {
- const {
- accessible,
- android_disableSound,
- android_ripple,
- children,
- delayLongPress,
- disabled,
- focusable,
- onLongPress,
- onPress,
- onPressIn,
- onPressOut,
- pressRetentionOffset,
- style,
- testOnly_pressed,
- ...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 hitSlop = normalizeRect(props.hitSlop);
- const config = useMemo(
- () => ({
- disabled,
- hitSlop,
- pressRectOffset: pressRetentionOffset,
- android_disableSound,
- delayLongPress,
- 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,
- delayLongPress,
- disabled,
- hitSlop,
- onLongPress,
- onPress,
- onPressIn,
- onPressOut,
- pressRetentionOffset,
- setPressed,
- ],
- );
- const eventHandlers = usePressability(config);
- return (
- <View
- {...restProps}
- {...eventHandlers}
- {...android_rippleConfig?.viewProps}
- accessible={accessible !== false}
- focusable={focusable !== false}
- hitSlop={hitSlop}
- ref={viewRef}
- style={typeof style === 'function' ? style({pressed}) : style}>
- {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>,
- >);
|