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

[react-interactions] Add handleSimulateChildBlur upon DOM node removal (#17225)

* [react-interactions] Add handleSimulateChildBlur upon DOM node removal

Dominic Gannaway committed Nov 4, 2019 at 20:02 UTC cb09dbe0ab0f42185c4892c045b0c32e04f6b9cb
3 files changed +81 -24
packages/react-dom/src/client/ReactDOMHostConfig.js
+32
@@ -55,6 +55,7 @@ import {
55 addRootEventTypesForResponderInstance,
56 mountEventResponder,
57 unmountEventResponder,
58 + dispatchEventForResponderEventSystem,
59 } from '../events/DOMEventResponderSystem';
60 import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
61
@@ -108,6 +109,10 @@ import {
109 enableFlareAPI,
110 enableFundamentalAPI,
111 } from 'shared/ReactFeatureFlags';
112 +import {
113 + RESPONDER_EVENT_SYSTEM,
114 + IS_PASSIVE,
115 +} from 'legacy-events/EventSystemFlags';
116
117 let SUPPRESS_HYDRATION_WARNING;
118 if (__DEV__) {
@@ -447,10 +452,36 @@ export function insertInContainerBefore(
452 }
453 }
454
455 +function handleSimulateChildBlur(
456 + child: Instance | TextInstance | SuspenseInstance,
457 +): void {
458 + if (
459 + enableFlareAPI &&
460 + selectionInformation &&
461 + child === selectionInformation.focusedElem
462 + ) {
463 + const targetFiber = getClosestInstanceFromNode(child);
464 + // Simlulate a blur event to the React Flare responder system.
465 + dispatchEventForResponderEventSystem(
466 + 'blur',
467 + targetFiber,
468 + ({
469 + relatedTarget: null,
470 + target: child,
471 + timeStamp: Date.now(),
472 + type: 'blur',
473 + }: any),
474 + ((child: any): Document | Element),
475 + RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
476 + );
477 + }
478 +}
479 +
480 export function removeChild(
481 parentInstance: Instance,
482 child: Instance | TextInstance | SuspenseInstance,
483 ): void {
484 + handleSimulateChildBlur(child);
485 parentInstance.removeChild(child);
486 }
487
@@ -461,6 +492,7 @@ export function removeChildFromContainer(
492 if (container.nodeType === COMMENT_NODE) {
493 (container.parentNode: any).removeChild(child);
494 } else {
495 + handleSimulateChildBlur(child);
496 container.removeChild(child);
497 }
498 }
packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js
+48 -24
@@ -77,23 +77,24 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
77 describe('onFocusWithinChange', () => {
78 let onFocusWithinChange, ref, innerRef, innerRef2;
79
80 + const Component = ({show}) => {
81 + const listener = useFocusWithin({
82 + onFocusWithinChange,
83 + });
84 + return (
85 + <div ref={ref} listeners={listener}>
86 + {show && <input ref={innerRef} />}
87 + <div ref={innerRef2} />
88 + </div>
89 + );
90 + };
91 +
92 beforeEach(() => {
93 onFocusWithinChange = jest.fn();
94 ref = React.createRef();
95 innerRef = React.createRef();
96 innerRef2 = React.createRef();
85 - const Component = () => {
86 - const listener = useFocusWithin({
87 - onFocusWithinChange,
88 - });
89 - return (
90 - <div ref={ref} listeners={listener}>
91 - <div ref={innerRef} />
92 - <div ref={innerRef2} />
93 - </div>
94 - );
95 - };
96 - ReactDOM.render(<Component />, container);
97 + ReactDOM.render(<Component show={true} />, container);
98 });
99
100 it('is called after "blur" and "focus" events on focus target', () => {
@@ -140,28 +141,39 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
141 expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
142 expect(onFocusWithinChange).toHaveBeenCalledWith(false);
143 });
144 +
145 + it('is called after a focused element is unmounted', () => {
146 + const target = createEventTarget(innerRef.current);
147 + target.focus();
148 + expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
149 + expect(onFocusWithinChange).toHaveBeenCalledWith(true);
150 + ReactDOM.render(<Component show={false} />, container);
151 + expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
152 + expect(onFocusWithinChange).toHaveBeenCalledWith(false);
153 + });
154 });
155
156 describe('onFocusWithinVisibleChange', () => {
157 let onFocusWithinVisibleChange, ref, innerRef, innerRef2;
158
159 + const Component = ({show}) => {
160 + const listener = useFocusWithin({
161 + onFocusWithinVisibleChange,
162 + });
163 + return (
164 + <div ref={ref} listeners={listener}>
165 + {show && <input ref={innerRef} />}
166 + <div ref={innerRef2} />
167 + </div>
168 + );
169 + };
170 +
171 beforeEach(() => {
172 onFocusWithinVisibleChange = jest.fn();
173 ref = React.createRef();
174 innerRef = React.createRef();
175 innerRef2 = React.createRef();
153 - const Component = () => {
154 - const listener = useFocusWithin({
155 - onFocusWithinVisibleChange,
156 - });
157 - return (
158 - <div ref={ref} listeners={listener}>
159 - <div ref={innerRef} />
160 - <div ref={innerRef2} />
161 - </div>
162 - );
163 - };
164 - ReactDOM.render(<Component />, container);
176 + ReactDOM.render(<Component show={true} />, container);
177 });
178
179 it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
@@ -258,6 +270,18 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
270 expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
271 expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
272 });
273 +
274 + it('is called after a focused element is unmounted', () => {
275 + const inner = innerRef.current;
276 + const target = createEventTarget(inner);
277 + target.keydown({key: 'Tab'});
278 + target.focus();
279 + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
280 + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
281 + ReactDOM.render(<Component show={false} />, container);
282 + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
283 + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
284 + });
285 });
286
287 it('expect displayName to show up for event component', () => {
packages/react-interactions/events/src/dom/testing-library/index.js
+1
@@ -34,6 +34,7 @@ const createEventTarget = node => ({
34 },
35 focus(payload) {
36 node.dispatchEvent(domEvents.focus(payload));
37 + node.focus();
38 },
39 scroll(payload) {
40 node.dispatchEvent(domEvents.scroll(payload));