UNPKG

react-native

Version:

A framework for building native apps using React

288 lines (270 loc) • 8.78 kB
/** * 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 type {AccessibilityActionEvent} from '../../Components/View/ViewAccessibility'; import type {EdgeInsetsOrSizeProp} from '../../StyleSheet/EdgeInsetsPropType'; import type { BlurEvent, FocusEvent, GestureResponderEvent, LayoutChangeEvent, } from '../../Types/CoreEventTypes'; import View from '../../Components/View/View'; import {type AccessibilityProps} from '../../Components/View/ViewAccessibility'; import {PressabilityDebugView} from '../../Pressability/PressabilityDebug'; import usePressability from '../../Pressability/usePressability'; import {type ViewStyleProp} from '../../StyleSheet/StyleSheet'; import * as React from 'react'; import {useMemo} from 'react'; export type TouchableWithoutFeedbackPropsIOS = {}; export type TouchableWithoutFeedbackPropsAndroid = { /** * If true, doesn't play a system sound on touch. * * @platform android */ touchSoundDisabled?: ?boolean, }; export type TouchableWithoutFeedbackProps = $ReadOnly< { children?: ?React.Node, /** * Delay in ms, from onPressIn, before onLongPress is called. */ delayLongPress?: ?number, /** * Delay in ms, from the start of the touch, before onPressIn is called. */ delayPressIn?: ?number, /** * Delay in ms, from the release of the touch, before onPressOut is called. */ delayPressOut?: ?number, /** * If true, disable all interactions for this component. */ disabled?: ?boolean, /** * Whether this View should be focusable with a non-touch input device, * eg. receive focus with a hardware keyboard / TV remote. */ focusable?: ?boolean, /** * This defines how far your touch can start away from the button. * This is added to pressRetentionOffset when moving off of the button. * NOTE The touch area never extends past the parent view bounds and * the Z-index of sibling views always takes precedence if a touch hits * two overlapping views. */ hitSlop?: ?EdgeInsetsOrSizeProp, /** * Used to reference react managed views from native code. */ id?: string, importantForAccessibility?: ?( | 'auto' | 'yes' | 'no' | 'no-hide-descendants' ), nativeID?: ?string, onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed, /** * When `accessible` is true (which is the default) this may be called when * the OS-specific concept of "blur" occurs, meaning the element lost focus. * Some platforms may not have the concept of blur. */ onBlur?: ?(event: BlurEvent) => mixed, /** * When `accessible` is true (which is the default) this may be called when * the OS-specific concept of "focus" occurs. Some platforms may not have * the concept of focus. */ onFocus?: ?(event: FocusEvent) => mixed, /** * Invoked on mount and layout changes with * {nativeEvent: {layout: {x, y, width, height}}} */ onLayout?: ?(event: LayoutChangeEvent) => mixed, onLongPress?: ?(event: GestureResponderEvent) => mixed, /** * Called when the touch is released, * but not if cancelled (e.g. by a scroll that steals the responder lock). */ onPress?: ?(event: GestureResponderEvent) => mixed, onPressIn?: ?(event: GestureResponderEvent) => mixed, onPressOut?: ?(event: GestureResponderEvent) => mixed, /** * When the scroll view is disabled, this defines how far your * touch may move off of the button, before deactivating the button. * Once deactivated, try moving it back and you'll see that the button * is once again reactivated! Move it back and forth several times * while the scroll view is disabled. Ensure you pass in a constant * to reduce memory allocations. */ pressRetentionOffset?: ?EdgeInsetsOrSizeProp, rejectResponderTermination?: ?boolean, /** * Used to locate this view in end-to-end tests. */ testID?: ?string, /** * //FIXME: not in doc but available in examples */ style?: ?ViewStyleProp, } & TouchableWithoutFeedbackPropsAndroid & TouchableWithoutFeedbackPropsIOS & AccessibilityProps, >; const PASSTHROUGH_PROPS = [ 'accessibilityActions', 'accessibilityElementsHidden', 'accessibilityHint', 'accessibilityLanguage', 'accessibilityIgnoresInvertColors', 'accessibilityLabel', 'accessibilityLiveRegion', 'accessibilityRole', 'accessibilityValue', 'aria-valuemax', 'aria-valuemin', 'aria-valuenow', 'aria-valuetext', 'accessibilityViewIsModal', 'aria-modal', 'hitSlop', 'importantForAccessibility', 'nativeID', 'onAccessibilityAction', 'onBlur', 'onFocus', 'onLayout', 'testID', ]; /** * Do not use unless you have a very good reason. * All the elements that respond to press should have a visual feedback when touched. * This is one of the primary reason a "web" app doesn't feel "native". * * @see https://reactnative.dev/docs/touchablewithoutfeedback */ export default function TouchableWithoutFeedback( props: TouchableWithoutFeedbackProps, ): React.Node { const { disabled, rejectResponderTermination, 'aria-disabled': ariaDisabled, accessibilityState, hitSlop, delayLongPress, delayPressIn, delayPressOut, pressRetentionOffset, touchSoundDisabled, onBlur: _onBlur, onFocus: _onFocus, onLongPress, onPress, onPressIn, onPressOut, } = props; const pressabilityConfig = useMemo( () => ({ cancelable: !rejectResponderTermination, disabled: disabled !== null ? disabled : ariaDisabled ?? accessibilityState?.disabled, hitSlop: hitSlop, delayLongPress: delayLongPress, delayPressIn: delayPressIn, delayPressOut: delayPressOut, minPressDuration: 0, pressRectOffset: pressRetentionOffset, android_disableSound: touchSoundDisabled, onBlur: _onBlur, onFocus: _onFocus, onLongPress: onLongPress, onPress: onPress, onPressIn: onPressIn, onPressOut: onPressOut, }), [ rejectResponderTermination, disabled, ariaDisabled, accessibilityState?.disabled, hitSlop, delayLongPress, delayPressIn, delayPressOut, pressRetentionOffset, touchSoundDisabled, _onBlur, _onFocus, onLongPress, onPress, onPressIn, onPressOut, ], ); const eventHandlers = usePressability(pressabilityConfig); const element = React.Children.only<$FlowFixMe>(props.children); const children: Array<React.Node> = [element.props.children]; const ariaLive = props['aria-live']; if (__DEV__) { if (element.type === View) { children.push( <PressabilityDebugView color="red" hitSlop={props.hitSlop} />, ); } } let _accessibilityState = { busy: props['aria-busy'] ?? props.accessibilityState?.busy, checked: props['aria-checked'] ?? props.accessibilityState?.checked, disabled: props['aria-disabled'] ?? props.accessibilityState?.disabled, expanded: props['aria-expanded'] ?? props.accessibilityState?.expanded, selected: props['aria-selected'] ?? props.accessibilityState?.selected, }; // BACKWARD-COMPATIBILITY: Focus and blur events were never supported before // adopting `Pressability`, so preserve that behavior. const {onBlur, onFocus, ...eventHandlersWithoutBlurAndFocus} = eventHandlers; const elementProps: {[string]: mixed, ...} = { ...eventHandlersWithoutBlurAndFocus, accessible: props.accessible !== false, accessibilityState: props.disabled != null ? { ..._accessibilityState, disabled: props.disabled, } : _accessibilityState, focusable: props.focusable !== false && props.onPress !== undefined && !props.disabled, accessibilityElementsHidden: props['aria-hidden'] ?? props.accessibilityElementsHidden, importantForAccessibility: props['aria-hidden'] === true ? 'no-hide-descendants' : props.importantForAccessibility, accessibilityLiveRegion: ariaLive === 'off' ? 'none' : ariaLive ?? props.accessibilityLiveRegion, nativeID: props.id ?? props.nativeID, }; for (const prop of PASSTHROUGH_PROPS) { if (props[prop] !== undefined) { elementProps[prop] = props[prop]; } } // $FlowFixMe[incompatible-call] return React.cloneElement(element, elementProps, ...children); }