From 539640d89f327743f2872098ec8aad08ab3a4dfd Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 4 Sep 2019 18:05:56 +0100 Subject: [PATCH] [react-events] Various core tweaks for event responder system (#16654) --- .../src/events/DOMEventResponderSystem.js | 65 ++++++++++++++----- .../DOMEventResponderSystem-test.internal.js | 2 +- packages/react-events/README.md | 1 - packages/react-events/src/dom/Focus.js | 4 +- packages/react-events/src/dom/Hover.js | 4 +- packages/react-events/src/dom/Input.js | 11 ++-- packages/react-events/src/dom/Keyboard.js | 8 +-- packages/react-events/src/dom/Press.js | 4 +- packages/react-events/src/rn/Press.js | 2 +- .../src/ReactFabricEventResponderSystem.js | 42 +++++++++--- .../src/ReactNativeTypes.js | 2 +- .../src/ReactFiberCommitWork.js | 11 +++- .../src/ReactFiberCompleteWork.js | 32 +++++++-- .../react-reconciler/src/ReactFiberEvents.js | 51 ++++++--------- .../src/__tests__/ReactScope-test.internal.js | 28 ++++++++ packages/shared/ReactDOMTypes.js | 2 +- packages/shared/ReactTypes.js | 1 - 17 files changed, 181 insertions(+), 89 deletions(-) diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 0544b24cec71b..ec2d977d97efd 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -12,7 +12,7 @@ import { PASSIVE_NOT_SUPPORTED, } from 'legacy-events/EventSystemFlags'; import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; -import {HostComponent} from 'shared/ReactWorkTags'; +import {HostComponent, ScopeComponent} from 'shared/ReactWorkTags'; import type {EventPriority} from 'shared/ReactTypes'; import type { ReactDOMEventResponder, @@ -66,6 +66,7 @@ type ResponderTimer = {| instance: ReactDOMEventResponderInstance, func: () => void, id: number, + targetFiber: Fiber | null, timeStamp: number, |}; @@ -80,6 +81,7 @@ let currentTimers = new Map(); let currentInstance: null | ReactDOMEventResponderInstance = null; let currentTimerIDCounter = 0; let currentDocument: null | Document = null; +let currentTargetFiber: null | Fiber = null; const eventResponderContext: ReactDOMResponderContext = { dispatchEvent( @@ -158,16 +160,20 @@ const eventResponderContext: ReactDOMResponderContext = { validateResponderContext(); const childFiber = getClosestInstanceFromNode(childTarget); const parentFiber = getClosestInstanceFromNode(parentTarget); - const parentAlternateFiber = parentFiber.alternate; - let node = childFiber; - while (node !== null) { - if (node === parentFiber || node === parentAlternateFiber) { - return true; + if (childFiber != null && parentFiber != null) { + const parentAlternateFiber = parentFiber.alternate; + let node = childFiber; + while (node !== null) { + if (node === parentFiber || node === parentAlternateFiber) { + return true; + } + node = node.return; } - node = node.return; + return false; } - return false; + // Fallback to DOM APIs + return parentTarget.contains(childTarget); }, addRootEventTypes(rootEventTypes: Array): void { validateResponderContext(); @@ -221,6 +227,7 @@ const eventResponderContext: ReactDOMResponderContext = { instance: ((currentInstance: any): ReactDOMEventResponderInstance), func, id: timerId, + targetFiber: currentTargetFiber, timeStamp: currentTimeStamp, }); activeTimeouts.set(timerId, timeout); @@ -260,6 +267,24 @@ const eventResponderContext: ReactDOMResponderContext = { return false; }, enqueueStateRestore, + getCurrentTarget(): Element | null { + validateResponderContext(); + const responderFiber = ((currentInstance: any): ReactDOMEventResponderInstance) + .fiber; + let fiber = currentTargetFiber; + let currentTarget = null; + + while (fiber !== null) { + if (fiber.tag === HostComponent) { + currentTarget = fiber.stateNode; + } + if (fiber === responderFiber || fiber.alternate === responderFiber) { + break; + } + fiber = fiber.return; + } + return currentTarget; + }, }; function validateEventValue(eventValue: any): void { @@ -317,7 +342,8 @@ function doesFiberHaveResponder( fiber: Fiber, responder: ReactDOMEventResponder, ): boolean { - if (fiber.tag === HostComponent) { + const tag = fiber.tag; + if (tag === HostComponent || tag === ScopeComponent) { const dependencies = fiber.dependencies; if (dependencies !== null) { const respondersMap = dependencies.responders; @@ -341,8 +367,9 @@ function processTimers( try { batchedEventUpdates(() => { for (let i = 0; i < timersArr.length; i++) { - const {instance, func, id, timeStamp} = timersArr[i]; + const {instance, func, id, timeStamp, targetFiber} = timersArr[i]; currentInstance = instance; + currentTargetFiber = targetFiber; currentTimeStamp = timeStamp + delay; try { func(); @@ -355,6 +382,7 @@ function processTimers( currentTimers = null; currentInstance = null; currentTimeStamp = 0; + currentTargetFiber = null; } } @@ -386,7 +414,6 @@ function createDOMResponderEvent( passiveSupported, pointerId, pointerType: eventPointerType, - responderTarget: null, target: nativeEventTarget, type: topLevelType, }; @@ -443,13 +470,16 @@ function traverseAndHandleEventResponderInstances( let node = targetFiber; while (node !== null) { const {dependencies, tag} = node; - if (tag === HostComponent && dependencies !== null) { + if ( + (tag === HostComponent || tag === ScopeComponent) && + dependencies !== null + ) { const respondersMap = dependencies.responders; if (respondersMap !== null) { const responderInstances = Array.from(respondersMap.values()); for (let i = 0, length = responderInstances.length; i < length; i++) { const responderInstance = responderInstances[i]; - const {props, responder, state, target} = responderInstance; + const {props, responder, state} = responderInstance; if ( !visitedResponders.has(responder) && validateResponderTargetEventTypes(eventType, responder) @@ -458,9 +488,6 @@ function traverseAndHandleEventResponderInstances( const onEvent = responder.onEvent; if (onEvent !== null) { currentInstance = responderInstance; - responderEvent.responderTarget = ((target: any): - | Element - | Document); onEvent(responderEvent, eventResponderContext, props, state); } } @@ -478,11 +505,10 @@ function traverseAndHandleEventResponderInstances( for (let i = 0; i < responderInstances.length; i++) { const responderInstance = responderInstances[i]; - const {props, responder, state, target} = responderInstance; + const {props, responder, state} = responderInstance; const onRootEvent = responder.onRootEvent; if (onRootEvent !== null) { currentInstance = responderInstance; - responderEvent.responderTarget = ((target: any): Element | Document); onRootEvent(responderEvent, eventResponderContext, props, state); } } @@ -562,7 +588,9 @@ export function dispatchEventForResponderEventSystem( const previousTimers = currentTimers; const previousTimeStamp = currentTimeStamp; const previousDocument = currentDocument; + const previousTargetFiber = currentTargetFiber; currentTimers = null; + currentTargetFiber = targetFiber; // nodeType 9 is DOCUMENT_NODE currentDocument = (nativeEventTarget: any).nodeType === 9 @@ -585,6 +613,7 @@ export function dispatchEventForResponderEventSystem( currentInstance = previousInstance; currentTimeStamp = previousTimeStamp; currentDocument = previousDocument; + currentTargetFiber = previousTargetFiber; } } } diff --git a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js index 1af5ed5dc6dec..d939ea5553f9c 100644 --- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js @@ -1017,7 +1017,7 @@ describe('DOMEventResponderSystem', () => { const obj = { counter, timeStamp: context.getTimeStamp(), - target: event.responderTarget, + target: context.getCurrentTarget(), type: 'click-test', }; context.dispatchEvent(obj, props.onClick, DiscreteEvent); diff --git a/packages/react-events/README.md b/packages/react-events/README.md index 25d68d870aaae..3d4e2b008fa37 100644 --- a/packages/react-events/README.md +++ b/packages/react-events/README.md @@ -22,7 +22,6 @@ type ResponderEventType = string; type ResponderEvent = {| nativeEvent: any, - responderTarget: Element | Document, target: Element | Document, pointerType: string, type: string, diff --git a/packages/react-events/src/dom/Focus.js b/packages/react-events/src/dom/Focus.js index e86af18fda2b4..a319b9f8436b7 100644 --- a/packages/react-events/src/dom/Focus.js +++ b/packages/react-events/src/dom/Focus.js @@ -289,7 +289,7 @@ const focusResponderImpl = { switch (type) { case 'focus': { - state.focusTarget = event.responderTarget; + state.focusTarget = context.getCurrentTarget(); // Limit focus events to the direct child of the event component. // Browser focus is not expected to bubble. if (!state.isFocused && state.focusTarget === target) { @@ -427,7 +427,7 @@ const focusWithinResponderImpl = { switch (type) { case 'focus': { - state.focusTarget = event.responderTarget; + state.focusTarget = context.getCurrentTarget(); // Limit focus events to the direct child of the event component. // Browser focus is not expected to bubble. if (!state.isFocused) { diff --git a/packages/react-events/src/dom/Hover.js b/packages/react-events/src/dom/Hover.js index 1d1386df162d9..8e372d7ec3054 100644 --- a/packages/react-events/src/dom/Hover.js +++ b/packages/react-events/src/dom/Hover.js @@ -235,7 +235,7 @@ const hoverResponderImpl = { // START case 'pointerover': { if (!state.isHovered && pointerType !== 'touch') { - state.hoverTarget = event.responderTarget; + state.hoverTarget = context.getCurrentTarget(); dispatchHoverStartEvents(event, context, props, state); } break; @@ -295,7 +295,7 @@ const hoverResponderFallbackImpl = { // START case 'mouseover': { if (!state.isHovered && !state.ignoreEmulatedMouseEvents) { - state.hoverTarget = event.responderTarget; + state.hoverTarget = context.getCurrentTarget(); dispatchHoverStartEvents(event, context, props, state); } break; diff --git a/packages/react-events/src/dom/Input.js b/packages/react-events/src/dom/Input.js index 81e746da1a4bb..a7e9ae5c8438c 100644 --- a/packages/react-events/src/dom/Input.js +++ b/packages/react-events/src/dom/Input.js @@ -179,30 +179,31 @@ const inputResponderImpl = { context: ReactDOMResponderContext, props: InputResponderProps, ): void { - const {responderTarget, type, target} = event; + const {type, target} = event; if (props.disabled) { return; } - if (target !== responderTarget || responderTarget === null) { + const currentTarget = context.getCurrentTarget(); + if (target !== currentTarget || currentTarget === null) { return; } switch (type) { default: { if (shouldUseChangeEvent(target) && type === 'change') { - dispatchBothChangeEvents(event, context, props, responderTarget); + dispatchBothChangeEvents(event, context, props, currentTarget); } else if ( isTextInputElement(target) && (type === 'input' || type === 'change') && updateValueIfChanged(target) ) { - dispatchBothChangeEvents(event, context, props, responderTarget); + dispatchBothChangeEvents(event, context, props, currentTarget); } else if ( isCheckable(target) && type === 'click' && updateValueIfChanged(target) ) { - dispatchBothChangeEvents(event, context, props, responderTarget); + dispatchBothChangeEvents(event, context, props, currentTarget); } break; } diff --git a/packages/react-events/src/dom/Keyboard.js b/packages/react-events/src/dom/Keyboard.js index 00f4ffb16a880..c636f97fb1f26 100644 --- a/packages/react-events/src/dom/Keyboard.js +++ b/packages/react-events/src/dom/Keyboard.js @@ -131,7 +131,6 @@ function createKeyboardEvent( event: ReactDOMResponderEvent, context: ReactDOMResponderContext, type: KeyboardEventType, - target: Document | Element, defaultPrevented: boolean, ): KeyboardEvent { const nativeEvent = (event: any).nativeEvent; @@ -144,6 +143,7 @@ function createKeyboardEvent( repeat, shiftKey, } = nativeEvent; + const target = ((context.getCurrentTarget(): any): Element); return { altKey, @@ -166,14 +166,12 @@ function dispatchKeyboardEvent( listener: KeyboardEvent => void, context: ReactDOMResponderContext, type: KeyboardEventType, - target: Element | Document, defaultPrevented: boolean, ): void { const syntheticEvent = createKeyboardEvent( event, context, type, - target, defaultPrevented, ); context.dispatchEvent(syntheticEvent, listener, DiscreteEvent); @@ -186,7 +184,7 @@ const keyboardResponderImpl = { context: ReactDOMResponderContext, props: KeyboardProps, ): void { - const {responderTarget, type} = event; + const {type} = event; const nativeEvent: any = event.nativeEvent; if (props.disabled) { @@ -227,7 +225,6 @@ const keyboardResponderImpl = { onKeyDown, context, 'keydown', - ((responderTarget: any): Element | Document), defaultPrevented, ); } @@ -239,7 +236,6 @@ const keyboardResponderImpl = { onKeyUp, context, 'keyup', - ((responderTarget: any): Element | Document), defaultPrevented, ); } diff --git a/packages/react-events/src/dom/Press.js b/packages/react-events/src/dom/Press.js index 267240776af6e..31fed91638793 100644 --- a/packages/react-events/src/dom/Press.js +++ b/packages/react-events/src/dom/Press.js @@ -582,7 +582,7 @@ const pressResponderImpl = { // We set these here, before the button check so we have this // data around for handling of the context menu state.pointerType = pointerType; - const pressTarget = (state.pressTarget = event.responderTarget); + const pressTarget = (state.pressTarget = context.getCurrentTarget()); if (isPointerEvent) { state.activePointerId = pointerId; } else if (isTouchEvent) { @@ -634,7 +634,7 @@ const pressResponderImpl = { if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) { state.pointerType = 'keyboard'; - state.pressTarget = event.responderTarget; + state.pressTarget = context.getCurrentTarget(); const preventDefault = props.preventDefault; if (preventDefault !== false) { diff --git a/packages/react-events/src/rn/Press.js b/packages/react-events/src/rn/Press.js index fac15a59d2ffb..ed25e0655e474 100644 --- a/packages/react-events/src/rn/Press.js +++ b/packages/react-events/src/rn/Press.js @@ -412,7 +412,7 @@ const pressResponderImpl = { if (type === 'topTouchStart') { if (!state.isPressed) { state.pointerType = 'touch'; - const pressTarget = (state.pressTarget = event.responderTarget); + const pressTarget = (state.pressTarget = context.getCurrentTarget()); const touchEvent = getTouchFromPressEvent(nativeEvent); if (touchEvent === null) { return; diff --git a/packages/react-native-renderer/src/ReactFabricEventResponderSystem.js b/packages/react-native-renderer/src/ReactFabricEventResponderSystem.js index 4ffaf534cf2c4..1ad5dfcb6be34 100644 --- a/packages/react-native-renderer/src/ReactFabricEventResponderSystem.js +++ b/packages/react-native-renderer/src/ReactFabricEventResponderSystem.js @@ -7,7 +7,7 @@ * @flow */ -import {HostComponent} from 'shared/ReactWorkTags'; +import {HostComponent, ScopeComponent} from 'shared/ReactWorkTags'; import type {Fiber} from 'react-reconciler/src/ReactFiber'; import { batchedEventUpdates, @@ -52,6 +52,7 @@ type ResponderTimer = {| instance: ReactNativeEventResponderInstance, func: () => void, id: number, + targetFiber: Fiber | null, timeStamp: number, |}; @@ -77,6 +78,7 @@ let currentTimeStamp = 0; let currentTimers = new Map(); let currentInstance: null | ReactNativeEventResponderInstance = null; let currentTimerIDCounter = 0; +let currentTargetFiber: Fiber | null = null; const eventResponderContext: ReactNativeResponderContext = { dispatchEvent( @@ -196,6 +198,7 @@ const eventResponderContext: ReactNativeResponderContext = { instance: ((currentInstance: any): ReactNativeEventResponderInstance), func, id: timerId, + targetFiber: currentTargetFiber, timeStamp: currentTimeStamp, }); activeTimeouts.set(timerId, timeout); @@ -217,6 +220,24 @@ const eventResponderContext: ReactNativeResponderContext = { validateResponderContext(); return currentTimeStamp; }, + getCurrentTarget(): ReactNativeEventTarget | null { + validateResponderContext(); + const responderFiber = ((currentInstance: any): ReactNativeEventResponderInstance) + .fiber; + let fiber = currentTargetFiber; + let currentTarget = null; + + while (fiber !== null) { + if (fiber.tag === HostComponent) { + currentTarget = fiber.stateNode; + } + if (fiber === responderFiber || fiber.alternate === responderFiber) { + break; + } + fiber = fiber.return; + } + return currentTarget; + }, }; function validateEventValue(eventValue: any): void { @@ -287,8 +308,9 @@ function processTimers( try { batchedEventUpdates(() => { for (let i = 0; i < timersArr.length; i++) { - const {instance, func, id, timeStamp} = timersArr[i]; + const {instance, func, id, targetFiber, timeStamp} = timersArr[i]; currentInstance = instance; + currentTargetFiber = targetFiber; currentTimeStamp = timeStamp + delay; try { func(); @@ -301,6 +323,7 @@ function processTimers( currentTimers = null; currentInstance = null; currentTimeStamp = 0; + currentTargetFiber = null; } } @@ -311,7 +334,6 @@ function createFabricResponderEvent( ): ReactNativeResponderEvent { return { nativeEvent, - responderTarget: target, target, type: topLevelType, }; @@ -373,13 +395,16 @@ function traverseAndHandleEventResponderInstances( let node = targetFiber; while (node !== null) { const {dependencies, tag} = node; - if (tag === HostComponent && dependencies !== null) { + if ( + (tag === HostComponent || tag === ScopeComponent) && + dependencies !== null + ) { const respondersMap = dependencies.responders; if (respondersMap !== null) { const responderInstances = Array.from(respondersMap.values()); for (let i = 0, length = responderInstances.length; i < length; i++) { const responderInstance = responderInstances[i]; - const {props, responder, state, target} = responderInstance; + const {props, responder, state} = responderInstance; if ( !visitedResponders.has(responder) && validateResponderTargetEventTypes(eventType, responder) @@ -388,7 +413,6 @@ function traverseAndHandleEventResponderInstances( visitedResponders.add(responder); if (onEvent !== null) { currentInstance = responderInstance; - responderEvent.responderTarget = ((target: any): ReactNativeEventTarget); onEvent(responderEvent, eventResponderContext, props, state); } } @@ -406,11 +430,10 @@ function traverseAndHandleEventResponderInstances( for (let i = 0; i < responderInstances.length; i++) { const responderInstance = responderInstances[i]; - const {props, responder, state, target} = responderInstance; + const {props, responder, state} = responderInstance; const onRootEvent = responder.onRootEvent; if (onRootEvent !== null) { currentInstance = responderInstance; - responderEvent.responderTarget = ((target: any): ReactNativeEventTarget); onRootEvent(responderEvent, eventResponderContext, props, state); } } @@ -427,7 +450,9 @@ export function dispatchEventForResponderEventSystem( const previousInstance = currentInstance; const previousTimers = currentTimers; const previousTimeStamp = currentTimeStamp; + const previousTargetFiber = currentTargetFiber; currentTimers = null; + currentTargetFiber = targetFiber; // We might want to control timeStamp another way here currentTimeStamp = Date.now(); try { @@ -442,6 +467,7 @@ export function dispatchEventForResponderEventSystem( currentTimers = previousTimers; currentInstance = previousInstance; currentTimeStamp = previousTimeStamp; + currentTargetFiber = previousTargetFiber; } } diff --git a/packages/react-native-renderer/src/ReactNativeTypes.js b/packages/react-native-renderer/src/ReactNativeTypes.js index 04200f2bb9fa8..cb7e867d4dc20 100644 --- a/packages/react-native-renderer/src/ReactNativeTypes.js +++ b/packages/react-native-renderer/src/ReactNativeTypes.js @@ -191,7 +191,6 @@ export type ReactFaricEvent = { export type ReactNativeResponderEvent = { nativeEvent: ReactFaricEvent, - responderTarget: null | ReactNativeEventTarget, target: null | ReactNativeEventTarget, type: string, }; @@ -220,6 +219,7 @@ export type ReactNativeResponderContext = { setTimeout: (func: () => void, timeout: number) => number, clearTimeout: (timerId: number) => void, getTimeStamp: () => number, + getCurrentTarget(): ReactNativeEventTarget | null, }; export type PointerType = diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index b289a40614066..44cb2e59d6a92 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -1343,7 +1343,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void { const prevListeners = oldProps.listeners; const nextListeners = newProps.listeners; if (prevListeners !== nextListeners) { - updateEventListeners(nextListeners, instance, finishedWork); + updateEventListeners(nextListeners, finishedWork); } } } @@ -1394,6 +1394,15 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void { if (enableScopeAPI) { const scopeInstance = finishedWork.stateNode; scopeInstance.fiber = finishedWork; + if (enableFlareAPI) { + const newProps = finishedWork.memoizedProps; + const oldProps = current !== null ? current.memoizedProps : newProps; + const prevListeners = oldProps.listeners; + const nextListeners = newProps.listeners; + if (prevListeners !== nextListeners) { + updateEventListeners(nextListeners, finishedWork); + } + } } return; } diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 8f9addc35a186..dc8558ef54176 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -722,10 +722,9 @@ function completeWork( markUpdate(workInProgress); } if (enableFlareAPI) { - const instance = workInProgress.stateNode; const listeners = newProps.listeners; if (listeners != null) { - updateEventListeners(listeners, instance, workInProgress); + updateEventListeners(listeners, workInProgress); } } } else { @@ -739,10 +738,13 @@ function completeWork( appendAllChildren(instance, workInProgress, false, false); + // This needs to be set before we mount Flare event listeners + workInProgress.stateNode = instance; + if (enableFlareAPI) { const listeners = newProps.listeners; if (listeners != null) { - updateEventListeners(listeners, instance, workInProgress); + updateEventListeners(listeners, workInProgress); } } @@ -760,7 +762,6 @@ function completeWork( ) { markUpdate(workInProgress); } - workInProgress.stateNode = instance; } if (workInProgress.ref !== null) { @@ -1229,17 +1230,34 @@ function completeWork( }; workInProgress.stateNode = scopeInstance; scopeInstance.methods = createScopeMethods(type, scopeInstance); + if (enableFlareAPI) { + const listeners = newProps.listeners; + if (listeners != null) { + updateEventListeners(listeners, workInProgress); + } + } if (workInProgress.ref !== null) { markRef(workInProgress); markUpdate(workInProgress); } } else { + if (enableFlareAPI) { + const prevListeners = current.memoizedProps.listeners; + const nextListeners = newProps.listeners; + if ( + prevListeners !== nextListeners || + workInProgress.ref !== null + ) { + markUpdate(workInProgress); + } + } else { + if (workInProgress.ref !== null) { + markUpdate(workInProgress); + } + } if (current.ref !== workInProgress.ref) { markRef(workInProgress); } - if (workInProgress.ref !== null) { - markUpdate(workInProgress); - } } } break; diff --git a/packages/react-reconciler/src/ReactFiberEvents.js b/packages/react-reconciler/src/ReactFiberEvents.js index b87a11eb2ed00..90fe39a8d3674 100644 --- a/packages/react-reconciler/src/ReactFiberEvents.js +++ b/packages/react-reconciler/src/ReactFiberEvents.js @@ -25,6 +25,7 @@ import warning from 'shared/warning'; import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols'; import invariant from 'shared/invariant'; +import {HostComponent, HostRoot} from 'shared/ReactWorkTags'; const emptyObject = {}; const isArray = Array.isArray; @@ -33,7 +34,6 @@ export function createResponderInstance( responder: ReactEventResponder, responderProps: Object, responderState: Object, - target: Instance, fiber: Fiber, ): ReactEventResponderInstance { return { @@ -42,14 +42,12 @@ export function createResponderInstance( responder, rootEventTypes: null, state: responderState, - target, }; } function mountEventResponder( responder: ReactEventResponder, responderProps: Object, - instance: Instance, fiber: Fiber, respondersMap: Map< ReactEventResponder, @@ -65,15 +63,27 @@ function mountEventResponder( responder, responderProps, responderState, - instance, fiber, ); + let instance = null; + let node = fiber; + while (node !== null) { + const tag = node.tag; + if (tag === HostComponent) { + instance = node.stateNode; + break; + } else if (tag === HostRoot) { + instance = node.stateNode.containerInfo; + break; + } + node = node.return; + } mountResponderInstance( responder, responderInstance, responderProps, responderState, - instance, + ((instance: any): Instance), ); respondersMap.set(responder, responderInstance); } @@ -86,7 +96,6 @@ function updateEventListener( ReactEventResponder, ReactEventResponderInstance, >, - instance: Instance, ): void { let responder; let props; @@ -118,13 +127,7 @@ function updateEventListener( if (responderInstance === undefined) { // Mount (happens in either complete or commit phase) - mountEventResponder( - responder, - listenerProps, - instance, - fiber, - respondersMap, - ); + mountEventResponder(responder, listenerProps, fiber, respondersMap); } else { // Update (happens during commit phase only) responderInstance.props = listenerProps; @@ -132,11 +135,7 @@ function updateEventListener( } } -export function updateEventListeners( - listeners: any, - instance: Instance, - fiber: Fiber, -): void { +export function updateEventListeners(listeners: any, fiber: Fiber): void { const visistedResponders = new Set(); let dependencies = fiber.dependencies; if (listeners != null) { @@ -154,22 +153,10 @@ export function updateEventListeners( if (isArray(listeners)) { for (let i = 0, length = listeners.length; i < length; i++) { const listener = listeners[i]; - updateEventListener( - listener, - fiber, - visistedResponders, - respondersMap, - instance, - ); + updateEventListener(listener, fiber, visistedResponders, respondersMap); } } else { - updateEventListener( - listeners, - fiber, - visistedResponders, - respondersMap, - instance, - ); + updateEventListener(listeners, fiber, visistedResponders, respondersMap); } } if (dependencies !== null) { diff --git a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js index 6665081f5fad7..77bde7763e95a 100644 --- a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js @@ -9,6 +9,8 @@ 'use strict'; +import {createEventTarget} from 'react-events/src/dom/testing-library'; + let React; let ReactFeatureFlags; @@ -17,6 +19,7 @@ describe('ReactScope', () => { jest.resetModules(); ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.enableScopeAPI = true; + ReactFeatureFlags.enableFlareAPI = true; React = require('react'); }); @@ -194,6 +197,31 @@ describe('ReactScope', () => { const nodes = scopeRef.current.getScopedNodes(); expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]); }); + + it('event responders can be attached to scopes', () => { + const TestScope = React.unstable_createScope((type, props) => true); + const onKeyDown = jest.fn(); + const ref = React.createRef(); + const useKeyboard = require('react-events/keyboard').useKeyboard; + const Component = () => { + const listener = useKeyboard({ + onKeyDown, + }); + return ( + +
+ + ); + }; + ReactDOM.render(, container); + + const target = createEventTarget(ref.current); + target.keydown({key: 'Q'}); + expect(onKeyDown).toHaveBeenCalledTimes(1); + expect(onKeyDown).toHaveBeenCalledWith( + expect.objectContaining({key: 'Q', type: 'keydown'}), + ); + }); }); describe('ReactTestRenderer', () => { diff --git a/packages/shared/ReactDOMTypes.js b/packages/shared/ReactDOMTypes.js index 3519b830cfd9b..bbaabb9e60326 100644 --- a/packages/shared/ReactDOMTypes.js +++ b/packages/shared/ReactDOMTypes.js @@ -30,7 +30,6 @@ export type ReactDOMResponderEvent = { passiveSupported: boolean, pointerId: null | number, pointerType: PointerType, - responderTarget: null | Element | Document, target: Element | Document, type: string, }; @@ -75,4 +74,5 @@ export type ReactDOMResponderContext = { ) => boolean, // Used for controller components enqueueStateRestore(Element | Document): void, + getCurrentTarget(): Element | null, }; diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 7d6d5a75cbf84..13866ba14aedf 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -85,7 +85,6 @@ export type ReactEventResponderInstance = {| responder: ReactEventResponder, rootEventTypes: null | Set, state: Object, - target: mixed, |}; export type ReactEventResponderListener = {|