You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
9.2 KiB

/**
* 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 (
* <View {...this.state.pressResponder.getEventHandlers()} />
* );
*
* 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;