/** * Copyright (c) Nicolas Gallagher * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ /** * RESPONDER EVENT SYSTEM * * A single, global "interaction lock" on views. For a view to be the "responder" means * that pointer interactions are exclusive to that view and none other. The "interaction * lock" can be transferred (only) to ancestors of the current "responder" as long as * pointers continue to be active. * * Responder being granted: * * A view can become the "responder" after the following events: * * "pointerdown" (implemented using "touchstart", "mousedown") * * "pointermove" (implemented using "touchmove", "mousemove") * * "scroll" (while a pointer is down) * * "selectionchange" (while a pointer is down) * * If nothing is already the "responder", the event propagates to (capture) and from * (bubble) the event target until a view returns `true` for * `on*ShouldSetResponder(Capture)`. * * If something is already the responder, the event propagates to (capture) and from * (bubble) the lowest common ancestor of the event target and the current "responder". * Then negotiation happens between the current "responder" and a view that wants to * become the "responder": see the timing diagram below. * * (NOTE: Scrolled views either automatically become the "responder" or release the * "interaction lock". A native scroll view that isn't built on top of the responder * system must result in the current "responder" being notified that it no longer has * the "interaction lock" - the native system has taken over. * * Responder being released: * * As soon as there are no more active pointers that *started* inside descendants * of the *current* "responder", an `onResponderRelease` event is dispatched to the * current "responder", and the responder lock is released. * * Typical sequence of events: * * startShouldSetResponder * * responderGrant/Reject * * responderStart * * responderMove * * responderEnd * * responderRelease */ /* Negotiation Performed +-----------------------+ / \ Process low level events to + Current Responder + wantsResponderID determine who to perform negot-| (if any exists at all) | iation/transition | Otherwise just pass through| -------------------------------+----------------------------+------------------+ Bubble to find first ID | | to return true:wantsResponderID| | | | +--------------+ | | | onTouchStart | | | +------+-------+ none | | | return| | +-----------v-------------+true| +------------------------+ | |onStartShouldSetResponder|----->| onResponderStart (cur) |<-----------+ +-----------+-------------+ | +------------------------+ | | | | | +--------+-------+ | returned true for| false:REJECT +-------->|onResponderReject | wantsResponderID | | | +----------------+ | (now attempt | +------------------+-----+ | | handoff) | | onResponder | | +------------------->| TerminationRequest | | | +------------------+-----+ | | | | +----------------+ | true:GRANT +-------->|onResponderGrant| | | +--------+-------+ | +------------------------+ | | | | onResponderTerminate |<-----------+ | +------------------+-----+ | | | | +----------------+ | +-------->|onResponderStart| | | +----------------+ Bubble to find first ID | | to return true:wantsResponderID| | | | +-------------+ | | | onTouchMove | | | +------+------+ none | | | return| | +-----------v-------------+true| +------------------------+ | |onMoveShouldSetResponder |----->| onResponderMove (cur) |<-----------+ +-----------+-------------+ | +------------------------+ | | | | | +--------+-------+ | returned true for| false:REJECT +-------->|onResponderReject | wantsResponderID | | | +----------------+ | (now attempt | +------------------+-----+ | | handoff) | | onResponder | | +------------------->| TerminationRequest| | | +------------------+-----+ | | | | +----------------+ | true:GRANT +-------->|onResponderGrant| | | +--------+-------+ | +------------------------+ | | | | onResponderTerminate |<-----------+ | +------------------+-----+ | | | | +----------------+ | +-------->|onResponderMove | | | +----------------+ | | | | Some active touch started| | inside current responder | +------------------------+ | +------------------------->| onResponderEnd | | | | +------------------------+ | +---+---------+ | | | onTouchEnd | | | +---+---------+ | | | | +------------------------+ | +------------------------->| onResponderEnd | | No active touches started| +-----------+------------+ | inside current responder | | | | v | | +------------------------+ | | | onResponderRelease | | | +------------------------+ | | | + + */ import type { ResponderEvent } from './createResponderEvent'; import createResponderEvent from './createResponderEvent'; import { isCancelish, isEndish, isMoveish, isScroll, isSelectionChange, isStartish } from './ResponderEventTypes'; import { getLowestCommonAncestor, getResponderPaths, hasTargetTouches, hasValidSelection, isPrimaryPointerDown, setResponderId } from './utils'; import { ResponderTouchHistoryStore } from './ResponderTouchHistoryStore'; import canUseDOM from '../canUseDom'; /* ------------ TYPES ------------ */ type ResponderId = number; type ActiveResponderInstance = { id: ResponderId, idPath: Array, node: any, }; type EmptyResponderInstance = { id: null, idPath: null, node: null, }; type ResponderInstance = ActiveResponderInstance | EmptyResponderInstance; export type ResponderConfig = { // Direct responder events dispatched directly to responder. Do not bubble. onResponderEnd?: ?(e: ResponderEvent) => void, onResponderGrant?: ?(e: ResponderEvent) => void | boolean, onResponderMove?: ?(e: ResponderEvent) => void, onResponderRelease?: ?(e: ResponderEvent) => void, onResponderReject?: ?(e: ResponderEvent) => void, onResponderStart?: ?(e: ResponderEvent) => void, onResponderTerminate?: ?(e: ResponderEvent) => void, onResponderTerminationRequest?: ?(e: ResponderEvent) => boolean, // On pointer down, should this element become the responder? onStartShouldSetResponder?: ?(e: ResponderEvent) => boolean, onStartShouldSetResponderCapture?: ?(e: ResponderEvent) => boolean, // On pointer move, should this element become the responder? onMoveShouldSetResponder?: ?(e: ResponderEvent) => boolean, onMoveShouldSetResponderCapture?: ?(e: ResponderEvent) => boolean, // On scroll, should this element become the responder? Do no bubble onScrollShouldSetResponder?: ?(e: ResponderEvent) => boolean, onScrollShouldSetResponderCapture?: ?(e: ResponderEvent) => boolean, // On text selection change, should this element become the responder? onSelectionChangeShouldSetResponder?: ?(e: ResponderEvent) => boolean, onSelectionChangeShouldSetResponderCapture?: ?(e: ResponderEvent) => boolean, }; const emptyObject = {}; /* ------------ IMPLEMENTATION ------------ */ const startRegistration = ['onStartShouldSetResponderCapture', 'onStartShouldSetResponder', { bubbles: true }]; const moveRegistration = ['onMoveShouldSetResponderCapture', 'onMoveShouldSetResponder', { bubbles: true }]; const scrollRegistration = ['onScrollShouldSetResponderCapture', 'onScrollShouldSetResponder', { bubbles: false }]; const shouldSetResponderEvents = { touchstart: startRegistration, mousedown: startRegistration, touchmove: moveRegistration, mousemove: moveRegistration, scroll: scrollRegistration }; const emptyResponder = { id: null, idPath: null, node: null }; const responderListenersMap = new Map(); let isEmulatingMouseEvents = false; let trackedTouchCount = 0; let currentResponder: ResponderInstance = { id: null, node: null, idPath: null }; const responderTouchHistoryStore = new ResponderTouchHistoryStore(); declare function changeCurrentResponder(responder: ResponderInstance): any; declare function getResponderConfig(id: ResponderId): ResponderConfig | Object; /** * Process native events * * A single event listener is used to manage the responder system. * All pointers are tracked in the ResponderTouchHistoryStore. Native events * are interpreted in terms of the Responder System and checked to see if * the responder should be transferred. Each host node that is attached to * the Responder System has an ID, which is used to look up its associated * callbacks. */ declare function eventListener(domEvent: any): any; /** * Walk the event path to/from the target node. At each node, stop and call the * relevant "shouldSet" functions for the given event type. If any of those functions * call "stopPropagation" on the event, stop searching for a responder. */ declare function findWantsResponder(eventPaths: any, domEvent: any, responderEvent: any): any; /** * Attempt to transfer the responder. */ declare function attemptTransfer(responderEvent: ResponderEvent, wantsResponder: ActiveResponderInstance): any; /* ------------ PUBLIC API ------------ */ /** * Attach Listeners * * Use native events as ReactDOM doesn't have a non-plugin API to implement * this system. */ const documentEventsCapturePhase = ['blur', 'scroll']; const documentEventsBubblePhase = [// mouse 'mousedown', 'mousemove', 'mouseup', 'dragstart', // touch 'touchstart', 'touchmove', 'touchend', 'touchcancel', // other 'contextmenu', 'select', 'selectionchange']; declare export function attachListeners(): any; /** * Register a node with the ResponderSystem. */ declare export function addNode(id: ResponderId, node: any, config: ResponderConfig): any; /** * Unregister a node with the ResponderSystem. */ declare export function removeNode(id: ResponderId): any; /** * Allow the current responder to be terminated from within components to support * more complex requirements, such as use with other React libraries for working * with scroll views, input views, etc. */ declare export function terminateResponder(): any; /** * Allow unit tests to inspect the current responder in the system. * FOR TESTING ONLY. */ declare export function getResponderNode(): any;