UNPKG

@tamagui/react-native-web-lite

Version:
256 lines (255 loc) 15.8 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: !0 }); }, __copyProps = (to, from, except, desc) => { if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target, mod )), __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod); var PanResponder_exports = {}; __export(PanResponder_exports, { default: () => PanResponder_default }); module.exports = __toCommonJS(PanResponder_exports); var import_react_native_web_internals = require("@tamagui/react-native-web-internals"), import_TouchHistoryMath = __toESM(require("../TouchHistoryMath")), currentCentroidXOfTouchesChangedAfter = import_TouchHistoryMath.default.currentCentroidXOfTouchesChangedAfter, currentCentroidYOfTouchesChangedAfter = import_TouchHistoryMath.default.currentCentroidYOfTouchesChangedAfter, previousCentroidXOfTouchesChangedAfter = import_TouchHistoryMath.default.previousCentroidXOfTouchesChangedAfter, previousCentroidYOfTouchesChangedAfter = import_TouchHistoryMath.default.previousCentroidYOfTouchesChangedAfter, currentCentroidX = import_TouchHistoryMath.default.currentCentroidX, currentCentroidY = import_TouchHistoryMath.default.currentCentroidY, PanResponder = { /** * * A graphical explanation of the touch data flow: * * +----------------------------+ +--------------------------------+ * | ResponderTouchHistoryStore | |TouchHistoryMath | * +----------------------------+ +----------+---------------------+ * |Global store of touchHistory| |Allocation-less math util | * |including activeness, start | |on touch history (centroids | * |position, prev/cur position.| |and multitouch movement etc) | * | | | | * +----^-----------------------+ +----^---------------------------+ * | | * | (records relevant history | * | of touches relevant for | * | implementing higher level | * | gestures) | * | | * +----+-----------------------+ +----|---------------------------+ * | ResponderEventPlugin | | | Your App/Component | * +----------------------------+ +----|---------------------------+ * |Negotiates which view gets | Low level | | High level | * |onResponderMove events. | events w/ | +-+-------+ events w/ | * |Also records history into | touchHistory| | Pan | multitouch + | * |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative| * +----------------------------+ attached to | | | distance and | * each event | +---------+ velocity. | * | | * | | * +--------------------------------+ * * * * Gesture that calculates cumulative movement over time in a way that just * "does the right thing" for multiple touches. The "right thing" is very * nuanced. When moving two touches in opposite directions, the cumulative * distance is zero in each dimension. When two touches move in parallel five * pixels in the same direction, the cumulative distance is five, not ten. If * two touches start, one moves five in a direction, then stops and the other * touch moves fives in the same direction, the cumulative distance is ten. * * This logic requires a kind of processing of time "clusters" of touch events * so that two touch moves that essentially occur in parallel but move every * other frame respectively, are considered part of the same movement. * * Explanation of some of the non-obvious fields: * * - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is * invalid. If a move event has been observed, `(moveX, moveY)` is the * centroid of the most recently moved "cluster" of active touches. * (Currently all move have the same timeStamp, but later we should add some * threshold for what is considered to be "moving"). If a palm is * accidentally counted as a touch, but a finger is moving greatly, the palm * will move slightly, but we only want to count the single moving touch. * - x0/y0: Centroid location (non-cumulative) at the time of becoming * responder. * - dx/dy: Cumulative touch distance - not the same thing as sum of each touch * distance. Accounts for touch moves that are clustered together in time, * moving the same direction. Only valid when currently responder (otherwise, * it only represents the drag distance below the threshold). * - vx/vy: Velocity. */ _initializeGestureState(gestureState) { gestureState.moveX = 0, gestureState.moveY = 0, gestureState.x0 = 0, gestureState.y0 = 0, gestureState.dx = 0, gestureState.dy = 0, gestureState.vx = 0, gestureState.vy = 0, gestureState.numberActiveTouches = 0, gestureState._accountsForMovesUpTo = 0; }, /** * This is nuanced and is necessary. It is incorrect to continuously take all * active *and* recently moved touches, find the centroid, and track how that * result changes over time. Instead, we must take all recently moved * touches, and calculate how the centroid has changed just for those * recently moved touches, and append that change to an accumulator. This is * to (at least) handle the case where the user is moving three fingers, and * then one of the fingers stops but the other two continue. * * This is very different than taking all of the recently moved touches and * storing their centroid as `dx/dy`. For correctness, we must *accumulate * changes* in the centroid of recently moved touches. * * There is also some nuance with how we handle multiple moved touches in a * single event. With the way `ReactNativeEventEmitter` dispatches touches as * individual events, multiple touches generate two 'move' events, each of * them triggering `onResponderMove`. But with the way `PanResponder` works, * all of the gesture inference is performed on the first dispatch, since it * looks at all of the touches (even the ones for which there hasn't been a * native dispatch yet). Therefore, `PanResponder` does not call * `onResponderMove` passed the first dispatch. This diverges from the * typical responder callback pattern (without using `PanResponder`), but * avoids more dispatches than necessary. */ _updateGestureStateOnMove(gestureState, touchHistory) { gestureState.numberActiveTouches = touchHistory.numberActiveTouches, gestureState.moveX = currentCentroidXOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo), gestureState.moveY = currentCentroidYOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo); var movedAfter = gestureState._accountsForMovesUpTo, prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter), x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter), prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter), y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter), nextDX = gestureState.dx + (x - prevX), nextDY = gestureState.dy + (y - prevY), dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo; gestureState.vx = (nextDX - gestureState.dx) / dt, gestureState.vy = (nextDY - gestureState.dy) / dt, gestureState.dx = nextDX, gestureState.dy = nextDY, gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp; }, /** * @param {object} config Enhanced versions of all of the responder callbacks * that provide not only the typical `ResponderSyntheticEvent`, but also the * `PanResponder` gesture state. Simply replace the word `Responder` with * `PanResponder` in each of the typical `onResponder*` callbacks. For * example, the `config` object would look like: * * - `onMoveShouldSetPanResponder: (e, gestureState) => {...}` * - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}` * - `onStartShouldSetPanResponder: (e, gestureState) => {...}` * - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}` * - `onPanResponderReject: (e, gestureState) => {...}` * - `onPanResponderGrant: (e, gestureState) => {...}` * - `onPanResponderStart: (e, gestureState) => {...}` * - `onPanResponderEnd: (e, gestureState) => {...}` * - `onPanResponderRelease: (e, gestureState) => {...}` * - `onPanResponderMove: (e, gestureState) => {...}` * - `onPanResponderTerminate: (e, gestureState) => {...}` * - `onPanResponderTerminationRequest: (e, gestureState) => {...}` * - `onShouldBlockNativeResponder: (e, gestureState) => {...}` * * In general, for events that have capture equivalents, we update the * gestureState once in the capture phase and can use it in the bubble phase * as well. * * Be careful with onStartShould* callbacks. They only reflect updated * `gestureState` for start/end events that bubble/capture to the Node. * Once the node is the responder, you can rely on every start/end event * being processed by the gesture and `gestureState` being updated * accordingly. (numberActiveTouches) may not be totally accurate unless you * are the responder. */ create(config) { var interactionState = { handle: null, shouldCancelClick: !1, timeout: null }, gestureState = { // Useful for debugging stateID: Math.random(), moveX: 0, moveY: 0, x0: 0, y0: 0, dx: 0, dy: 0, vx: 0, vy: 0, numberActiveTouches: 0, _accountsForMovesUpTo: 0 }, panHandlers = { onStartShouldSetResponder(event) { return config.onStartShouldSetPanResponder == null ? !1 : config.onStartShouldSetPanResponder(event, gestureState); }, onMoveShouldSetResponder(event) { return config.onMoveShouldSetPanResponder == null ? !1 : config.onMoveShouldSetPanResponder(event, gestureState); }, onStartShouldSetResponderCapture(event) { return event.nativeEvent.touches.length === 1 && PanResponder._initializeGestureState(gestureState), gestureState.numberActiveTouches = event.touchHistory.numberActiveTouches, config.onStartShouldSetPanResponderCapture != null ? config.onStartShouldSetPanResponderCapture(event, gestureState) : !1; }, onMoveShouldSetResponderCapture(event) { var touchHistory = event.touchHistory; return gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp ? !1 : (PanResponder._updateGestureStateOnMove(gestureState, touchHistory), config.onMoveShouldSetPanResponderCapture ? config.onMoveShouldSetPanResponderCapture(event, gestureState) : !1); }, onResponderGrant(event) { return interactionState.handle || (interactionState.handle = import_react_native_web_internals.InteractionManager.createInteractionHandle()), interactionState.timeout && clearInteractionTimeout(interactionState), interactionState.shouldCancelClick = !0, gestureState.x0 = currentCentroidX(event.touchHistory), gestureState.y0 = currentCentroidY(event.touchHistory), gestureState.dx = 0, gestureState.dy = 0, config.onPanResponderGrant && config.onPanResponderGrant(event, gestureState), config.onShouldBlockNativeResponder == null ? !0 : config.onShouldBlockNativeResponder(event, gestureState); }, onResponderReject(event) { clearInteractionHandle( interactionState, // @ts-ignore config.onPanResponderReject, event, gestureState ); }, onResponderRelease(event) { clearInteractionHandle(interactionState, config.onPanResponderRelease, event, gestureState), setInteractionTimeout(interactionState), PanResponder._initializeGestureState(gestureState); }, onResponderStart(event) { var touchHistory = event.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches, config.onPanResponderStart && config.onPanResponderStart(event, gestureState); }, onResponderMove(event) { var touchHistory = event.touchHistory; gestureState._accountsForMovesUpTo !== touchHistory.mostRecentTimeStamp && (PanResponder._updateGestureStateOnMove(gestureState, touchHistory), config.onPanResponderMove && config.onPanResponderMove(event, gestureState)); }, onResponderEnd(event) { var touchHistory = event.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches, clearInteractionHandle(interactionState, config.onPanResponderEnd, event, gestureState); }, onResponderTerminate(event) { clearInteractionHandle( interactionState, // @ts-ignore config.onPanResponderTerminate, event, gestureState ), setInteractionTimeout(interactionState), PanResponder._initializeGestureState(gestureState); }, onResponderTerminationRequest(event) { return config.onPanResponderTerminationRequest == null ? !0 : config.onPanResponderTerminationRequest(event, gestureState); }, // We do not want to trigger 'click' activated gestures or native behaviors // on any pan target that is under a mouse cursor when it is released. // Browsers will natively cancel 'click' events on a target if a non-mouse // active pointer moves. onClickCapture: function(event) { interactionState.shouldCancelClick === !0 && (event.stopPropagation(), event.preventDefault()); } }; return { panHandlers, getInteractionHandle() { return interactionState.handle; } }; } }; function clearInteractionHandle(interactionState, callback, event, gestureState) { interactionState.handle && (import_react_native_web_internals.InteractionManager.clearInteractionHandle(interactionState.handle), interactionState.handle = null), callback && callback(event, gestureState); } function clearInteractionTimeout(interactionState) { clearTimeout(interactionState.timeout); } function setInteractionTimeout(interactionState) { interactionState.timeout = setTimeout(function() { interactionState.shouldCancelClick = !1; }, 250); } var PanResponder_default = PanResponder; //# sourceMappingURL=index.js.map