diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index 2e3da99145f..c7d8d84a79f 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -170,6 +170,22 @@ const eventResponderContext: ReactResponderContext = { } return false; }, + isTargetDirectlyWithinEventComponent(target: Element | Document): boolean { + validateResponderContext(); + if (target != null) { + let fiber = getClosestInstanceFromNode(target); + while (fiber !== null) { + if (fiber.stateNode === currentInstance) { + return true; + } + if (fiber.tag === EventComponent) { + return false; + } + fiber = fiber.return; + } + } + return false; + }, isTargetWithinElement( childTarget: Element | Document, parentTarget: Element | Document, 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 8d476b4568c..f1555339573 100644 --- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js @@ -917,4 +917,52 @@ describe('DOMEventResponderSystem', () => { }, ]); }); + + it('isTargetDirectlyWithinEventComponent works', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const log = []; + + const EventComponent = createReactEventComponent( + ['pointerout'], + undefined, + undefined, + (event, context) => { + const isWithin = context.isTargetDirectlyWithinEventComponent( + event.nativeEvent.relatedTarget, + ); + log.push(isWithin); + }, + ); + + const Test = () => ( + +
+ + + + + ); + ReactDOM.render(, container); + + const createEvent = (type, data) => { + const event = document.createEvent('CustomEvent'); + event.initCustomEvent(type, true, true); + if (data != null) { + Object.entries(data).forEach(([key, value]) => { + event[key] = value; + }); + } + return event; + }; + + buttonRef.current.dispatchEvent( + createEvent('pointerout', {relatedTarget: divRef.current}), + ); + divRef.current.dispatchEvent( + createEvent('pointerout', {relatedTarget: buttonRef.current}), + ); + + expect(log).toEqual([false, true, false]); + }); }); diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js index 90b5a00883c..b18dc3610b3 100644 --- a/packages/react-events/src/Hover.js +++ b/packages/react-events/src/Hover.js @@ -99,7 +99,9 @@ function dispatchHoverStartEvents( if (event !== null) { const {nativeEvent} = event; if ( - context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget) + context.isTargetDirectlyWithinEventComponent( + (nativeEvent: any).relatedTarget, + ) ) { return; } @@ -156,7 +158,9 @@ function dispatchHoverEndEvents( if (event !== null) { const {nativeEvent} = event; if ( - context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget) + context.isTargetDirectlyWithinEventComponent( + (nativeEvent: any).relatedTarget, + ) ) { return; } diff --git a/packages/react-events/src/__tests__/Hover-test.internal.js b/packages/react-events/src/__tests__/Hover-test.internal.js index dc29723b659..b4f2303008c 100644 --- a/packages/react-events/src/__tests__/Hover-test.internal.js +++ b/packages/react-events/src/__tests__/Hover-test.internal.js @@ -443,18 +443,17 @@ describe('Hover event responder', () => { createPointerEvent('pointerover', {relatedTarget: innerRef.current}), ); outerRef.current.dispatchEvent(createPointerEvent('pointerout')); - // TODO: correct result should include commented events expect(events).toEqual([ 'outer: onHoverStart', 'outer: onHoverChange', - // 'outer: onHoverEnd', - // 'outer: onHoverChange', + 'outer: onHoverEnd', + 'outer: onHoverChange', 'inner: onHoverStart', 'inner: onHoverChange', 'inner: onHoverEnd', 'inner: onHoverChange', - // 'outer: onHoverStart', - // 'outer: onHoverChange', + 'outer: onHoverStart', + 'outer: onHoverChange', 'outer: onHoverEnd', 'outer: onHoverChange', ]); diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index d625733dfd3..8f1cd7c76e0 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -167,6 +167,7 @@ export type ReactResponderContext = { parentTarget: Element | Document, ) => boolean, isTargetWithinEventComponent: (Element | Document) => boolean, + isTargetDirectlyWithinEventComponent: (Element | Document) => boolean, isPositionWithinTouchHitTarget: ( doc: Document, x: number,