Use strict equality (===) to prevent type coercion bugs
if (ResponderEventPlugin.GlobalResponderHandler !== null) {
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @noflow8 */910import {11 executeDirectDispatch,12 hasDispatches,13 executeDispatchesInOrderStopAtTrue,14 getInstanceFromNode,15 getFiberCurrentPropsFromNode,16} from './EventPluginUtils';17import ResponderSyntheticEvent from './ResponderSyntheticEvent';18import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';19import accumulate from './accumulate';20import {21 TOP_SCROLL,22 TOP_SELECTION_CHANGE,23 TOP_TOUCH_CANCEL,24 isStartish,25 isMoveish,26 isEndish,27 startDependencies,28 moveDependencies,29 endDependencies,30} from './ResponderTopLevelEventTypes';31import accumulateInto from './accumulateInto';32import forEachAccumulated from './forEachAccumulated';33import {HostComponent} from 'react-reconciler/src/ReactWorkTags';3435/**36 * Instance of element that should respond to touch/move types of interactions,37 * as indicated explicitly by relevant callbacks.38 */39let responderInst = null;4041/**42 * Count of current touches. A textInput should become responder iff the43 * selection changes while there is a touch on the screen.44 */45let trackedTouchCount = 0;4647function changeResponder(nextResponderInst, blockHostResponder) {48 const oldResponderInst = responderInst;49 responderInst = nextResponderInst;50 if (ResponderEventPlugin.GlobalResponderHandler !== null) {51 ResponderEventPlugin.GlobalResponderHandler.onChange(52 oldResponderInst,53 nextResponderInst,54 blockHostResponder,55 );56 }57}5859const eventTypes = {60 /**61 * On a `touchStart`/`mouseDown`, is it desired that this element become the62 * responder?63 */64 startShouldSetResponder: {65 phasedRegistrationNames: {66 bubbled: 'onStartShouldSetResponder',67 captured: 'onStartShouldSetResponderCapture',68 },69 dependencies: startDependencies,70 },7172 /**73 * On a `scroll`, is it desired that this element become the responder? This74 * is usually not needed, but should be used to retroactively infer that a75 * `touchStart` had occurred during momentum scroll. During a momentum scroll,76 * a touch start will be immediately followed by a scroll event if the view is77 * currently scrolling.78 *79 * TODO: This shouldn't bubble.80 */81 scrollShouldSetResponder: {82 phasedRegistrationNames: {83 bubbled: 'onScrollShouldSetResponder',84 captured: 'onScrollShouldSetResponderCapture',85 },86 dependencies: [TOP_SCROLL],87 },8889 /**90 * On text selection change, should this element become the responder? This91 * is needed for text inputs or other views with native selection, so the92 * JS view can claim the responder.93 *94 * TODO: This shouldn't bubble.95 */96 selectionChangeShouldSetResponder: {97 phasedRegistrationNames: {98 bubbled: 'onSelectionChangeShouldSetResponder',99 captured: 'onSelectionChangeShouldSetResponderCapture',100 },101 dependencies: [TOP_SELECTION_CHANGE],102 },103104 /**105 * On a `touchMove`/`mouseMove`, is it desired that this element become the106 * responder?107 */108 moveShouldSetResponder: {109 phasedRegistrationNames: {110 bubbled: 'onMoveShouldSetResponder',111 captured: 'onMoveShouldSetResponderCapture',112 },113 dependencies: moveDependencies,114 },115116 /**117 * Direct responder events dispatched directly to responder. Do not bubble.118 */119 responderStart: {120 registrationName: 'onResponderStart',121 dependencies: startDependencies,122 },123 responderMove: {124 registrationName: 'onResponderMove',125 dependencies: moveDependencies,126 },127 responderEnd: {128 registrationName: 'onResponderEnd',129 dependencies: endDependencies,130 },131 responderRelease: {132 registrationName: 'onResponderRelease',133 dependencies: endDependencies,134 },135 responderTerminationRequest: {136 registrationName: 'onResponderTerminationRequest',137 dependencies: [],138 },139 responderGrant: {140 registrationName: 'onResponderGrant',141 dependencies: [],142 },143 responderReject: {144 registrationName: 'onResponderReject',145 dependencies: [],146 },147 responderTerminate: {148 registrationName: 'onResponderTerminate',149 dependencies: [],150 },151};152153// Start of inline: the below functions were inlined from154// EventPropagator.js, as they deviated from ReactDOM's newer155// implementations.156157function getParent(inst) {158 do {159 inst = inst.return;160 // TODO: If this is a HostRoot we might want to bail out.161 // That is depending on if we want nested subtrees (layers) to bubble162 // events to their parent. We could also go through parentNode on the163 // host node but that wouldn't work for React Native and doesn't let us164 // do the portal feature.165 } while (inst && inst.tag !== HostComponent);166 if (inst) {167 return inst;168 }169 return null;170}171172/**173 * Return the lowest common ancestor of A and B, or null if they are in174 * different trees.175 */176export function getLowestCommonAncestor(instA, instB) {177 let depthA = 0;178 for (let tempA = instA; tempA; tempA = getParent(tempA)) {179 depthA++;180 }181 let depthB = 0;182 for (let tempB = instB; tempB; tempB = getParent(tempB)) {183 depthB++;184 }185186 // If A is deeper, crawl up.187 while (depthA - depthB > 0) {188 instA = getParent(instA);189 depthA--;190 }191192 // If B is deeper, crawl up.193 while (depthB - depthA > 0) {194 instB = getParent(instB);195 depthB--;196 }197198 // Walk in lockstep until we find a match.199 let depth = depthA;200 while (depth--) {201 if (instA === instB || instA === instB.alternate) {202 return instA;203 }204 instA = getParent(instA);205 instB = getParent(instB);206 }207 return null;208}209210/**211 * Return if A is an ancestor of B.212 */213function isAncestor(instA, instB) {214 while (instB) {215 if (instA === instB || instA === instB.alternate) {216 return true;217 }218 instB = getParent(instB);219 }220 return false;221}222223/**224 * Simulates the traversal of a two-phase, capture/bubble event dispatch.225 */226function traverseTwoPhase(inst, fn, arg) {227 const path = [];228 while (inst) {229 path.push(inst);230 inst = getParent(inst);231 }232 let i;233 for (i = path.length; i-- > 0; ) {234 fn(path[i], 'captured', arg);235 }236 for (i = 0; i < path.length; i++) {237 fn(path[i], 'bubbled', arg);238 }239}240241function getListener(inst, registrationName) {242 const stateNode = inst.stateNode;243 if (stateNode === null) {244 // Work in progress (ex: onload events in incremental mode).245 return null;246 }247 const props = getFiberCurrentPropsFromNode(stateNode);248 if (props === null) {249 // Work in progress.250 return null;251 }252 const listener = props[registrationName];253254 if (listener && typeof listener !== 'function') {255 throw new Error(256 `Expected \`${registrationName}\` listener to be a function, instead got a value of \`${typeof listener}\` type.`,257 );258 }259260 return listener;261}262263function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {264 const registrationName =265 event.dispatchConfig.phasedRegistrationNames[propagationPhase];266 return getListener(inst, registrationName);267}268269function accumulateDirectionalDispatches(inst, phase, event) {270 if (__DEV__) {271 if (!inst) {272 console.error('Dispatching inst must not be null');273 }274 }275 const listener = listenerAtPhase(inst, event, phase);276 if (listener) {277 event._dispatchListeners = accumulateInto(278 event._dispatchListeners,279 listener,280 );281 event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);282 }283}284285/**286 * Accumulates without regard to direction, does not look for phased287 * registration names. Same as `accumulateDirectDispatchesSingle` but without288 * requiring that the `dispatchMarker` be the same as the dispatched ID.289 */290function accumulateDispatches(291 inst: Object,292 ignoredDirection: ?boolean,293 event: Object,294): void {295 if (inst && event && event.dispatchConfig.registrationName) {296 const registrationName = event.dispatchConfig.registrationName;297 const listener = getListener(inst, registrationName);298 if (listener) {299 event._dispatchListeners = accumulateInto(300 event._dispatchListeners,301 listener,302 );303 event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);304 }305 }306}307308/**309 * Accumulates dispatches on an `SyntheticEvent`, but only for the310 * `dispatchMarker`.311 * @param {SyntheticEvent} event312 */313function accumulateDirectDispatchesSingle(event: Object) {314 if (event && event.dispatchConfig.registrationName) {315 accumulateDispatches(event._targetInst, null, event);316 }317}318319function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {320 forEachAccumulated(events, accumulateDirectDispatchesSingle);321}322323function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {324 if (event && event.dispatchConfig.phasedRegistrationNames) {325 const targetInst = event._targetInst;326 const parentInst = targetInst ? getParent(targetInst) : null;327 traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);328 }329}330331function accumulateTwoPhaseDispatchesSkipTarget(events) {332 forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);333}334335function accumulateTwoPhaseDispatchesSingle(event) {336 if (event && event.dispatchConfig.phasedRegistrationNames) {337 traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);338 }339}340341function accumulateTwoPhaseDispatches(events) {342 forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);343}344// End of inline345346/**347 *348 * Responder System:349 * ----------------350 *351 * - A global, solitary "interaction lock" on a view.352 * - If a node becomes the responder, it should convey visual feedback353 * immediately to indicate so, either by highlighting or moving accordingly.354 * - To be the responder means, that touches are exclusively important to that355 * responder view, and no other view.356 * - While touches are still occurring, the responder lock can be transferred to357 * a new view, but only to increasingly "higher" views (meaning ancestors of358 * the current responder).359 *360 * Responder being granted:361 * ------------------------362 *363 * - Touch starts, moves, and scrolls can cause an ID to become the responder.364 * - We capture/bubble `startShouldSetResponder`/`moveShouldSetResponder` to365 * the "appropriate place".366 * - If nothing is currently the responder, the "appropriate place" is the367 * initiating event's `targetID`.368 * - If something *is* already the responder, the "appropriate place" is the369 * first common ancestor of the event target and the current `responderInst`.370 * - Some negotiation happens: See the timing diagram below.371 * - Scrolled views automatically become responder. The reasoning is that a372 * platform scroll view that isn't built on top of the responder system has373 * began scrolling, and the active responder must now be notified that the374 * interaction is no longer locked to it - the system has taken over.375 *376 * - Responder being released:377 * As soon as no more touches that *started* inside of descendants of the378 * *current* responderInst, an `onResponderRelease` event is dispatched to the379 * current responder, and the responder lock is released.380 *381 * TODO:382 * - on "end", a callback hook for `onResponderEndShouldRemainResponder` that383 * determines if the responder lock should remain.384 * - If a view shouldn't "remain" the responder, any active touches should by385 * default be considered "dead" and do not influence future negotiations or386 * bubble paths. It should be as if those touches do not exist.387 * -- For multitouch: Usually a translate-z will choose to "remain" responder388 * after one out of many touches ended. For translate-y, usually the view389 * doesn't wish to "remain" responder after one of many touches end.390 * - Consider building this on top of a `stopPropagation` model similar to391 * `W3C` events.392 * - Ensure that `onResponderTerminate` is called on touch cancels, whether or393 * not `onResponderTerminationRequest` returns `true` or `false`.394 *395 */396397/* Negotiation Performed398 +-----------------------+399 / \400Process low level events to + Current Responder + wantsResponderID401determine who to perform negot-| (if any exists at all) |402iation/transition | Otherwise just pass through|403-------------------------------+----------------------------+------------------+404Bubble to find first ID | |405to return true:wantsResponderID| |406 | |407 +-------------+ | |408 | onTouchStart| | |409 +------+------+ none | |410 | return| |411+-----------v-------------+true| +------------------------+ |412|onStartShouldSetResponder|----->|onResponderStart (cur) |<-----------+413+-----------+-------------+ | +------------------------+ | |414 | | | +--------+-------+415 | returned true for| false:REJECT +-------->|onResponderReject416 | wantsResponderID | | | +----------------+417 | (now attempt | +------------------+-----+ |418 | handoff) | | onResponder | |419 +------------------->| TerminationRequest| |420 | +------------------+-----+ |421 | | | +----------------+422 | true:GRANT +-------->|onResponderGrant|423 | | +--------+-------+424 | +------------------------+ | |425 | | onResponderTerminate |<-----------+426 | +------------------+-----+ |427 | | | +----------------+428 | +-------->|onResponderStart|429 | | +----------------+430Bubble to find first ID | |431to return true:wantsResponderID| |432 | |433 +-------------+ | |434 | onTouchMove | | |435 +------+------+ none | |436 | return| |437+-----------v-------------+true| +------------------------+ |438|onMoveShouldSetResponder |----->|onResponderMove (cur) |<-----------+439+-----------+-------------+ | +------------------------+ | |440 | | | +--------+-------+441 | returned true for| false:REJECT +-------->|onResponderRejec|442 | wantsResponderID | | | +----------------+443 | (now attempt | +------------------+-----+ |444 | handoff) | | onResponder | |445 +------------------->| TerminationRequest| |446 | +------------------+-----+ |447 | | | +----------------+448 | true:GRANT +-------->|onResponderGrant|449 | | +--------+-------+450 | +------------------------+ | |451 | | onResponderTerminate |<-----------+452 | +------------------+-----+ |453 | | | +----------------+454 | +-------->|onResponderMove |455 | | +----------------+456 | |457 | |458 Some active touch started| |459 inside current responder | +------------------------+ |460 +------------------------->| onResponderEnd | |461 | | +------------------------+ |462 +---+---------+ | |463 | onTouchEnd | | |464 +---+---------+ | |465 | | +------------------------+ |466 +------------------------->| onResponderEnd | |467 No active touches started| +-----------+------------+ |468 inside current responder | | |469 | v |470 | +------------------------+ |471 | | onResponderRelease | |472 | +------------------------+ |473 | |474 + + */475476/**477 * A note about event ordering in the `EventPluginRegistry`.478 *479 * Suppose plugins are injected in the following order:480 *481 * `[R, S, C]`482 *483 * To help illustrate the example, assume `S` is `SimpleEventPlugin` (for484 * `onClick` etc) and `R` is `ResponderEventPlugin`.485 *486 * "Deferred-Dispatched Events":487 *488 * - The current event plugin system will traverse the list of injected plugins,489 * in order, and extract events by collecting the plugin's return value of490 * `extractEvents()`.491 * - These events that are returned from `extractEvents` are "deferred492 * dispatched events".493 * - When returned from `extractEvents`, deferred-dispatched events contain an494 * "accumulation" of deferred dispatches.495 * - These deferred dispatches are accumulated/collected before they are496 * returned, but processed at a later time by the `EventPluginRegistry` (hence the497 * name deferred).498 *499 * In the process of returning their deferred-dispatched events, event plugins500 * themselves can dispatch events on-demand without returning them from501 * `extractEvents`. Plugins might want to do this, so that they can use event502 * dispatching as a tool that helps them decide which events should be extracted503 * in the first place.504 *505 * "On-Demand-Dispatched Events":506 *507 * - On-demand-dispatched events are not returned from `extractEvents`.508 * - On-demand-dispatched events are dispatched during the process of returning509 * the deferred-dispatched events.510 * - They should not have side effects.511 * - They should be avoided, and/or eventually be replaced with another512 * abstraction that allows event plugins to perform multiple "rounds" of event513 * extraction.514 *515 * Therefore, the sequence of event dispatches becomes:516 *517 * - `R`s on-demand events (if any) (dispatched by `R` on-demand)518 * - `S`s on-demand events (if any) (dispatched by `S` on-demand)519 * - `C`s on-demand events (if any) (dispatched by `C` on-demand)520 * - `R`s extracted events (if any) (dispatched by `EventPluginRegistry`)521 * - `S`s extracted events (if any) (dispatched by `EventPluginRegistry`)522 * - `C`s extracted events (if any) (dispatched by `EventPluginRegistry`)523 *524 * In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`525 * on-demand dispatch returns `true` (and some other details are satisfied) the526 * `onResponderGrant` deferred dispatched event is returned from527 * `extractEvents`. The sequence of dispatch executions in this case528 * will appear as follows:529 *530 * - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)531 * - `touchStartCapture` (`EventPluginRegistry` dispatches as usual)532 * - `touchStart` (`EventPluginRegistry` dispatches as usual)533 * - `responderGrant/Reject` (`EventPluginRegistry` dispatches as usual)534 */535536function setResponderAndExtractTransfer(537 topLevelType,538 targetInst,539 nativeEvent,540 nativeEventTarget,541) {542 const shouldSetEventType = isStartish(topLevelType)543 ? eventTypes.startShouldSetResponder544 : isMoveish(topLevelType)545 ? eventTypes.moveShouldSetResponder546 : topLevelType === TOP_SELECTION_CHANGE547 ? eventTypes.selectionChangeShouldSetResponder548 : eventTypes.scrollShouldSetResponder;549550 // TODO: stop one short of the current responder.551 const bubbleShouldSetFrom = !responderInst552 ? targetInst553 : getLowestCommonAncestor(responderInst, targetInst);554555 // When capturing/bubbling the "shouldSet" event, we want to skip the target556 // (deepest ID) if it happens to be the current responder. The reasoning:557 // It's strange to get an `onMoveShouldSetResponder` when you're *already*558 // the responder.559 const skipOverBubbleShouldSetFrom = bubbleShouldSetFrom === responderInst;560 const shouldSetEvent = ResponderSyntheticEvent.getPooled(561 shouldSetEventType,562 bubbleShouldSetFrom,563 nativeEvent,564 nativeEventTarget,565 );566 shouldSetEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;567 if (skipOverBubbleShouldSetFrom) {568 accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent);569 } else {570 accumulateTwoPhaseDispatches(shouldSetEvent);571 }572 const wantsResponderInst = executeDispatchesInOrderStopAtTrue(shouldSetEvent);573 if (!shouldSetEvent.isPersistent()) {574 shouldSetEvent.constructor.release(shouldSetEvent);575 }576577 if (!wantsResponderInst || wantsResponderInst === responderInst) {578 return null;579 }580 let extracted;581 const grantEvent = ResponderSyntheticEvent.getPooled(582 eventTypes.responderGrant,583 wantsResponderInst,584 nativeEvent,585 nativeEventTarget,586 );587 grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;588589 accumulateDirectDispatches(grantEvent);590 const blockHostResponder = executeDirectDispatch(grantEvent) === true;591 if (responderInst) {592 const terminationRequestEvent = ResponderSyntheticEvent.getPooled(593 eventTypes.responderTerminationRequest,594 responderInst,595 nativeEvent,596 nativeEventTarget,597 );598 terminationRequestEvent.touchHistory =599 ResponderTouchHistoryStore.touchHistory;600 accumulateDirectDispatches(terminationRequestEvent);601 const shouldSwitch =602 !hasDispatches(terminationRequestEvent) ||603 executeDirectDispatch(terminationRequestEvent);604 if (!terminationRequestEvent.isPersistent()) {605 terminationRequestEvent.constructor.release(terminationRequestEvent);606 }607608 if (shouldSwitch) {609 const terminateEvent = ResponderSyntheticEvent.getPooled(610 eventTypes.responderTerminate,611 responderInst,612 nativeEvent,613 nativeEventTarget,614 );615 terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;616 accumulateDirectDispatches(terminateEvent);617 extracted = accumulate(extracted, [grantEvent, terminateEvent]);618 changeResponder(wantsResponderInst, blockHostResponder);619 } else {620 const rejectEvent = ResponderSyntheticEvent.getPooled(621 eventTypes.responderReject,622 wantsResponderInst,623 nativeEvent,624 nativeEventTarget,625 );626 rejectEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;627 accumulateDirectDispatches(rejectEvent);628 extracted = accumulate(extracted, rejectEvent);629 }630 } else {631 extracted = accumulate(extracted, grantEvent);632 changeResponder(wantsResponderInst, blockHostResponder);633 }634 return extracted;635}636637/**638 * A transfer is a negotiation between a currently set responder and the next639 * element to claim responder status. Any start event could trigger a transfer640 * of responderInst. Any move event could trigger a transfer.641 *642 * @param {string} topLevelType Record from `BrowserEventConstants`.643 * @return {boolean} True if a transfer of responder could possibly occur.644 */645function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {646 return (647 topLevelInst &&648 // responderIgnoreScroll: We are trying to migrate away from specifically649 // tracking native scroll events here and responderIgnoreScroll indicates we650 // will send topTouchCancel to handle canceling touch events instead651 ((topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll) ||652 (trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE) ||653 isStartish(topLevelType) ||654 isMoveish(topLevelType))655 );656}657658/**659 * Returns whether or not this touch end event makes it such that there are no660 * longer any touches that started inside of the current `responderInst`.661 *662 * @param {NativeEvent} nativeEvent Native touch end event.663 * @return {boolean} Whether or not this touch end event ends the responder.664 */665function noResponderTouches(nativeEvent) {666 const touches = nativeEvent.touches;667 if (!touches || touches.length === 0) {668 return true;669 }670 for (let i = 0; i < touches.length; i++) {671 const activeTouch = touches[i];672 const target = activeTouch.target;673 if (target !== null && target !== undefined && target !== 0) {674 // Is the original touch location inside of the current responder?675 const targetInst = getInstanceFromNode(target);676 if (isAncestor(responderInst, targetInst)) {677 return false;678 }679 }680 }681 return true;682}683684const ResponderEventPlugin = {685 /* For unit testing only */686 _getResponder: function () {687 return responderInst;688 },689690 eventTypes: eventTypes,691692 /**693 * We must be resilient to `targetInst` being `null` on `touchMove` or694 * `touchEnd`. On certain platforms, this means that a native scroll has695 * assumed control and the original touch targets are destroyed.696 */697 extractEvents: function (698 topLevelType,699 targetInst,700 nativeEvent,701 nativeEventTarget,702 eventSystemFlags,703 ) {704 if (isStartish(topLevelType)) {705 trackedTouchCount += 1;706 } else if (isEndish(topLevelType)) {707 if (trackedTouchCount >= 0) {708 trackedTouchCount -= 1;709 } else {710 if (__DEV__) {711 console.warn(712 'Ended a touch event which was not counted in `trackedTouchCount`.',713 );714 }715 return null;716 }717 }718719 ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent);720721 let extracted = canTriggerTransfer(topLevelType, targetInst, nativeEvent)722 ? setResponderAndExtractTransfer(723 topLevelType,724 targetInst,725 nativeEvent,726 nativeEventTarget,727 )728 : null;729 // Responder may or may not have transferred on a new touch start/move.730 // Regardless, whoever is the responder after any potential transfer, we731 // direct all touch start/move/ends to them in the form of732 // `onResponderMove/Start/End`. These will be called for *every* additional733 // finger that move/start/end, dispatched directly to whoever is the734 // current responder at that moment, until the responder is "released".735 //736 // These multiple individual change touch events are are always bookended737 // by `onResponderGrant`, and one of738 // (`onResponderRelease/onResponderTerminate`).739 const isResponderTouchStart = responderInst && isStartish(topLevelType);740 const isResponderTouchMove = responderInst && isMoveish(topLevelType);741 const isResponderTouchEnd = responderInst && isEndish(topLevelType);742 const incrementalTouch = isResponderTouchStart743 ? eventTypes.responderStart744 : isResponderTouchMove745 ? eventTypes.responderMove746 : isResponderTouchEnd747 ? eventTypes.responderEnd748 : null;749750 if (incrementalTouch) {751 const gesture = ResponderSyntheticEvent.getPooled(752 incrementalTouch,753 responderInst,754 nativeEvent,755 nativeEventTarget,756 );757 gesture.touchHistory = ResponderTouchHistoryStore.touchHistory;758 accumulateDirectDispatches(gesture);759 extracted = accumulate(extracted, gesture);760 }761762 const isResponderTerminate =763 responderInst && topLevelType === TOP_TOUCH_CANCEL;764 const isResponderRelease =765 responderInst &&766 !isResponderTerminate &&767 isEndish(topLevelType) &&768 noResponderTouches(nativeEvent);769 const finalTouch = isResponderTerminate770 ? eventTypes.responderTerminate771 : isResponderRelease772 ? eventTypes.responderRelease773 : null;774 if (finalTouch) {775 const finalEvent = ResponderSyntheticEvent.getPooled(776 finalTouch,777 responderInst,778 nativeEvent,779 nativeEventTarget,780 );781 finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;782 accumulateDirectDispatches(finalEvent);783 extracted = accumulate(extracted, finalEvent);784 changeResponder(null);785 }786787 return extracted;788 },789790 GlobalResponderHandler: null,791792 injection: {793 /**794 * @param {{onChange: (ReactID, ReactID) => void} GlobalResponderHandler795 * Object that handles any change in responder. Use this to inject796 * integration with an existing touch handling system etc.797 */798 injectGlobalResponderHandler(GlobalResponderHandler) {799 ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;800 },801 },802};803804export default ResponderEventPlugin;
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.