/** * 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 */ 'use strict'; type ClickEvent = any; type KeyboardEvent = any; type ResponderEvent = any; export type PressResponderConfig = $ReadOnly<{| // The gesture can be interrupted by a parent gesture, e.g., scroll. // Defaults to true. cancelable?: ?boolean, // Whether to disable initialization of the press gesture. disabled?: ?boolean, // Duration (in addition to `delayPressStart`) after which a press gesture is // considered a long press gesture. Defaults to 500 (milliseconds). delayLongPress?: ?number, // Duration to wait after press down before calling `onPressStart`. delayPressStart?: ?number, // Duration to wait after letting up before calling `onPressEnd`. delayPressEnd?: ?number, // Called when a long press gesture has been triggered. onLongPress?: ?(event: ResponderEvent) => void, // Called when a press gestute has been triggered. onPress?: ?(event: ClickEvent) => void, // Called when the press is activated to provide visual feedback. onPressChange?: ?(event: ResponderEvent) => void, // Called when the press is activated to provide visual feedback. onPressStart?: ?(event: ResponderEvent) => void, // Called when the press location moves. (This should rarely be used.) onPressMove?: ?(event: ResponderEvent) => void, // Called when the press is deactivated to undo visual feedback. onPressEnd?: ?(event: ResponderEvent) => void, |}>; export type EventHandlers = $ReadOnly<{| onClick: (event: ClickEvent) => void, onContextMenu: (event: ClickEvent) => void, onKeyDown: (event: KeyboardEvent) => void, onResponderGrant: (event: ResponderEvent) => void, onResponderMove: (event: ResponderEvent) => void, onResponderRelease: (event: ResponderEvent) => void, onResponderTerminate: (event: ResponderEvent) => void, onResponderTerminationRequest: (event: ResponderEvent) => boolean, onStartShouldSetResponder: (event: ResponderEvent) => boolean, |}>; type TouchState = 'NOT_RESPONDER' | 'RESPONDER_INACTIVE_PRESS_START' | 'RESPONDER_ACTIVE_PRESS_START' | 'RESPONDER_ACTIVE_LONG_PRESS_START' | 'ERROR'; type TouchSignal = 'DELAY' | 'RESPONDER_GRANT' | 'RESPONDER_RELEASE' | 'RESPONDER_TERMINATED' | 'LONG_PRESS_DETECTED'; const DELAY = 'DELAY'; const ERROR = 'ERROR'; const LONG_PRESS_DETECTED = 'LONG_PRESS_DETECTED'; const NOT_RESPONDER = 'NOT_RESPONDER'; const RESPONDER_ACTIVE_LONG_PRESS_START = 'RESPONDER_ACTIVE_LONG_PRESS_START'; const RESPONDER_ACTIVE_PRESS_START = 'RESPONDER_ACTIVE_PRESS_START'; const RESPONDER_INACTIVE_PRESS_START = 'RESPONDER_INACTIVE_PRESS_START'; const RESPONDER_GRANT = 'RESPONDER_GRANT'; const RESPONDER_RELEASE = 'RESPONDER_RELEASE'; const RESPONDER_TERMINATED = 'RESPONDER_TERMINATED'; const Transitions = Object.freeze({ NOT_RESPONDER: { DELAY: ERROR, RESPONDER_GRANT: RESPONDER_INACTIVE_PRESS_START, RESPONDER_RELEASE: ERROR, RESPONDER_TERMINATED: ERROR, LONG_PRESS_DETECTED: ERROR }, RESPONDER_INACTIVE_PRESS_START: { DELAY: RESPONDER_ACTIVE_PRESS_START, RESPONDER_GRANT: ERROR, RESPONDER_RELEASE: NOT_RESPONDER, RESPONDER_TERMINATED: NOT_RESPONDER, LONG_PRESS_DETECTED: ERROR }, RESPONDER_ACTIVE_PRESS_START: { DELAY: ERROR, RESPONDER_GRANT: ERROR, RESPONDER_RELEASE: NOT_RESPONDER, RESPONDER_TERMINATED: NOT_RESPONDER, LONG_PRESS_DETECTED: RESPONDER_ACTIVE_LONG_PRESS_START }, RESPONDER_ACTIVE_LONG_PRESS_START: { DELAY: ERROR, RESPONDER_GRANT: ERROR, RESPONDER_RELEASE: NOT_RESPONDER, RESPONDER_TERMINATED: NOT_RESPONDER, LONG_PRESS_DETECTED: RESPONDER_ACTIVE_LONG_PRESS_START }, ERROR: { DELAY: NOT_RESPONDER, RESPONDER_GRANT: RESPONDER_INACTIVE_PRESS_START, RESPONDER_RELEASE: NOT_RESPONDER, RESPONDER_TERMINATED: NOT_RESPONDER, LONG_PRESS_DETECTED: NOT_RESPONDER } }); declare var isActiveSignal: (signal: any) => any; declare var isButtonRole: (element: any) => any; declare var isPressStartSignal: (signal: any) => any; declare var isTerminalSignal: (signal: any) => any; declare var isValidKeyPress: (event: any) => any; const DEFAULT_LONG_PRESS_DELAY_MS = 450; // 500 - 50 const DEFAULT_PRESS_DELAY_MS = 50; /** * =========================== PressResponder Tutorial =========================== * * The `PressResponder` class helps you create press interactions by analyzing the * geometry of elements and observing when another responder (e.g. ScrollView) * has stolen the touch lock. It offers hooks for your component to provide * interaction feedback to the user: * * - When a press has activated (e.g. highlight an element) * - When a press has deactivated (e.g. un-highlight an element) * - When a press sould trigger an action, meaning it activated and deactivated * while within the geometry of the element without the lock being stolen. * * A high quality interaction isn't as simple as you might think. There should * be a slight delay before activation. Moving your finger beyond an element's * bounds should trigger deactivation, but moving the same finger back within an * element's bounds should trigger reactivation. * * In order to use `PressResponder`, do the following: * * const pressResponder = new PressResponder(config); * * 2. Choose the rendered component who should collect the press events. On that * element, spread `pressability.getEventHandlers()` into its props. * * return ( * * ); * * 3. Reset `PressResponder` when your component unmounts. * * componentWillUnmount() { * this.state.pressResponder.reset(); * } * * ==================== Implementation Details ==================== * * `PressResponder` only assumes that there exists a `HitRect` node. The `PressRect` * is an abstract box that is extended beyond the `HitRect`. * * # Geometry * * ┌────────────────────────┐ * │ ┌──────────────────┐ │ - Presses start anywhere within `HitRect`. * │ │ ┌────────────┐ │ │ * │ │ │ VisualRect │ │ │ * │ │ └────────────┘ │ │ - When pressed down for sufficient amount of time * │ │ HitRect │ │ before letting up, `VisualRect` activates. * │ └──────────────────┘ │ * │ Out Region o │ * └────────────────────│───┘ * └────── When the press is released outside the `HitRect`, * the responder is NOT eligible for a "press". * * # State Machine * * ┌───────────────┐ ◀──── RESPONDER_RELEASE * │ NOT_RESPONDER │ * └───┬───────────┘ ◀──── RESPONDER_TERMINATED * │ * │ RESPONDER_GRANT (HitRect) * │ * ▼ * ┌─────────────────────┐ ┌───────────────────┐ ┌───────────────────┐ * │ RESPONDER_INACTIVE_ │ DELAY │ RESPONDER_ACTIVE_ │ T + DELAY │ RESPONDER_ACTIVE_ │ * │ PRESS_START ├────────▶ │ PRESS_START ├────────────▶ │ LONG_PRESS_START │ * └─────────────────────┘ └───────────────────┘ └───────────────────┘ * * T + DELAY => LONG_PRESS_DELAY + DELAY * * Not drawn are the side effects of each transition. The most important side * effect is the invocation of `onLongPress`. Only when the browser produces a * `click` event is `onPress` invoked. */ declare export default class PressResponder { _config: PressResponderConfig, _eventHandlers: ?EventHandlers, _isPointerTouch: ?boolean, _longPressDelayTimeout: ?TimeoutID, _longPressDispatched: ?boolean, _pressDelayTimeout: ?TimeoutID, _pressOutDelayTimeout: ?TimeoutID, _selectionTerminated: ?boolean, _touchActivatePosition: ?$ReadOnly<{| pageX: number, pageY: number, |}>, _touchState: TouchState, constructor(config: PressResponderConfig): any, configure(config: PressResponderConfig): void, reset(): void, getEventHandlers(): EventHandlers, _createEventHandlers(): EventHandlers, _receiveSignal(signal: TouchSignal, event: ResponderEvent): void, _performTransitionSideEffects(prevState: TouchState, nextState: TouchState, signal: TouchSignal, event: ResponderEvent): void, _activate(event: ResponderEvent): void, _deactivate(event: ResponderEvent): void, _handleLongPress(event: ResponderEvent): void, _cancelLongPressDelayTimeout(): void, _cancelPressDelayTimeout(): void, _cancelPressOutDelayTimeout(): void, } declare function normalizeDelay(delay: ?number, min: any, fallback: any): number; declare function getTouchFromResponderEvent(event: ResponderEvent): any;