@samitouri / QOS-React-2 / commits / d6d83d7060

[react-interactions] Add Portal propagation configuration (#16889)

Dominic Gannaway committed Sep 25, 2019 at 16:56 UTC d6d83d7060388c6b1b3bc0a28708995de6240ce3
6 files changed +62 -3
packages/react-dom/src/events/DOMEventResponderSystem.js
+7 -3
@@ -12,7 +12,7 @@ import {
12 PASSIVE_NOT_SUPPORTED,
13 } from 'legacy-events/EventSystemFlags';
14 import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
15 -import {HostComponent, ScopeComponent} from 'shared/ReactWorkTags';
15 +import {HostComponent, ScopeComponent, HostPortal} from 'shared/ReactWorkTags';
16 import type {EventPriority} from 'shared/ReactTypes';
17 import type {
18 ReactDOMEventResponder,
@@ -451,9 +451,12 @@ function traverseAndHandleEventResponderInstances(
451 isPassiveSupported,
452 );
453 let node = targetFiber;
454 + let insidePortal = false;
455 while (node !== null) {
456 const {dependencies, tag} = node;
456 - if (
457 + if (tag === HostPortal) {
458 + insidePortal = true;
459 + } else if (
460 (tag === HostComponent || tag === ScopeComponent) &&
461 dependencies !== null
462 ) {
@@ -465,7 +468,8 @@ function traverseAndHandleEventResponderInstances(
468 const {props, responder, state} = responderInstance;
469 if (
470 !visitedResponders.has(responder) &&
468 - validateResponderTargetEventTypes(eventType, responder)
471 + validateResponderTargetEventTypes(eventType, responder) &&
472 + (!insidePortal || responder.targetPortalPropagation)
473 ) {
474 visitedResponders.add(responder);
475 const onEvent = responder.onEvent;
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+49
@@ -28,6 +28,7 @@ function createEventResponder({
28 onMount,
29 onUnmount,
30 getInitialState,
31 + targetPortalPropagation,
32 }) {
33 return React.unstable_createResponder('TestEventResponder', {
34 targetEventTypes,
@@ -37,6 +38,7 @@ function createEventResponder({
38 onMount,
39 onUnmount,
40 getInitialState,
41 + targetPortalPropagation,
42 });
43 }
44
@@ -1034,4 +1036,51 @@ describe('DOMEventResponderSystem', () => {
1036 },
1037 ]);
1038 });
1039 +
1040 + it('should not propagate target events through portals by default', () => {
1041 + const buttonRef = React.createRef();
1042 + const onEvent = jest.fn();
1043 + const TestResponder = createEventResponder({
1044 + targetEventTypes: ['click'],
1045 + onEvent,
1046 + });
1047 + const domNode = document.createElement('div');
1048 + document.body.appendChild(domNode);
1049 + const Component = () => {
1050 + const listener = React.unstable_useResponder(TestResponder, {});
1051 + return (
1052 + <div listeners={listener}>
1053 + {ReactDOM.createPortal(<button ref={buttonRef} />, domNode)}
1054 + </div>
1055 + );
1056 + };
1057 + ReactDOM.render(<Component />, container);
1058 + dispatchClickEvent(buttonRef.current);
1059 + document.body.removeChild(domNode);
1060 + expect(onEvent).not.toBeCalled();
1061 + });
1062 +
1063 + it('should propagate target events through portals when enabled', () => {
1064 + const buttonRef = React.createRef();
1065 + const onEvent = jest.fn();
1066 + const TestResponder = createEventResponder({
1067 + targetPortalPropagation: true,
1068 + targetEventTypes: ['click'],
1069 + onEvent,
1070 + });
1071 + const domNode = document.createElement('div');
1072 + document.body.appendChild(domNode);
1073 + const Component = () => {
1074 + const listener = React.unstable_useResponder(TestResponder, {});
1075 + return (
1076 + <div listeners={listener}>
1077 + {ReactDOM.createPortal(<button ref={buttonRef} />, domNode)}
1078 + </div>
1079 + );
1080 + };
1081 + ReactDOM.render(<Component />, container);
1082 + dispatchClickEvent(buttonRef.current);
1083 + document.body.removeChild(domNode);
1084 + expect(onEvent).toBeCalled();
1085 + });
1086 });
packages/react-interactions/events/src/dom/Focus.js
+2
@@ -293,6 +293,7 @@ function unmountFocusResponder(
293
294 const focusResponderImpl = {
295 targetEventTypes,
296 + targetPortalPropagation: true,
297 rootEventTypes,
298 getInitialState(): FocusState {
299 return {
@@ -430,6 +431,7 @@ function unmountFocusWithinResponder(
431
432 const focusWithinResponderImpl = {
433 targetEventTypes,
434 + targetPortalPropagation: true,
435 rootEventTypes,
436 getInitialState(): FocusState {
437 return {
packages/react-interactions/events/src/dom/Keyboard.js
+1
@@ -180,6 +180,7 @@ function dispatchKeyboardEvent(
180
181 const keyboardResponderImpl = {
182 targetEventTypes,
183 + targetPortalPropagation: true,
184 getInitialState(): KeyboardState {
185 return {
186 isActive: false,
packages/shared/ReactTypes.js
+1
@@ -96,6 +96,7 @@ export type ReactEventResponder<E, C> = {
96 $$typeof: Symbol | number,
97 displayName: string,
98 targetEventTypes: null | Array<string>,
99 + targetPortalPropagation: boolean,
100 rootEventTypes: null | Array<string>,
101 getInitialState: null | ((props: Object) => Object),
102 onEvent:
packages/shared/createEventResponder.js
+2
@@ -22,6 +22,7 @@ export default function createEventResponder<E, C>(
22 onRootEvent,
23 rootEventTypes,
24 targetEventTypes,
25 + targetPortalPropagation,
26 } = responderConfig;
27 const eventResponder = {
28 $$typeof: REACT_RESPONDER_TYPE,
@@ -33,6 +34,7 @@ export default function createEventResponder<E, C>(
34 onUnmount: onUnmount || null,
35 rootEventTypes: rootEventTypes || null,
36 targetEventTypes: targetEventTypes || null,
37 + targetPortalPropagation: targetPortalPropagation || false,
38 };
39 // We use responder as a Map key later on. When we have a bad
40 // polyfill, then we can't use it as a key as the polyfill tries