Skip to content

Commit

Permalink
[react-interactions] Add handleSimulateChildBlur upon DOM node removal (
Browse files Browse the repository at this point in the history
#17225)

* [react-interactions] Add handleSimulateChildBlur upon DOM node removal
  • Loading branch information
trueadm authored Nov 4, 2019
1 parent 6095993 commit cb09dbe
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 24 deletions.
32 changes: 32 additions & 0 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
addRootEventTypesForResponderInstance,
mountEventResponder,
unmountEventResponder,
dispatchEventForResponderEventSystem,
} from '../events/DOMEventResponderSystem';
import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';

Expand Down Expand Up @@ -108,6 +109,10 @@ import {
enableFlareAPI,
enableFundamentalAPI,
} from 'shared/ReactFeatureFlags';
import {
RESPONDER_EVENT_SYSTEM,
IS_PASSIVE,
} from 'legacy-events/EventSystemFlags';

let SUPPRESS_HYDRATION_WARNING;
if (__DEV__) {
Expand Down Expand Up @@ -447,10 +452,36 @@ export function insertInContainerBefore(
}
}

function handleSimulateChildBlur(
child: Instance | TextInstance | SuspenseInstance,
): void {
if (
enableFlareAPI &&
selectionInformation &&
child === selectionInformation.focusedElem
) {
const targetFiber = getClosestInstanceFromNode(child);
// Simlulate a blur event to the React Flare responder system.
dispatchEventForResponderEventSystem(
'blur',
targetFiber,
({
relatedTarget: null,
target: child,
timeStamp: Date.now(),
type: 'blur',
}: any),
((child: any): Document | Element),
RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
);
}
}

export function removeChild(
parentInstance: Instance,
child: Instance | TextInstance | SuspenseInstance,
): void {
handleSimulateChildBlur(child);
parentInstance.removeChild(child);
}

Expand All @@ -461,6 +492,7 @@ export function removeChildFromContainer(
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
handleSimulateChildBlur(child);
container.removeChild(child);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,24 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
describe('onFocusWithinChange', () => {
let onFocusWithinChange, ref, innerRef, innerRef2;

const Component = ({show}) => {
const listener = useFocusWithin({
onFocusWithinChange,
});
return (
<div ref={ref} listeners={listener}>
{show && <input ref={innerRef} />}
<div ref={innerRef2} />
</div>
);
};

beforeEach(() => {
onFocusWithinChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
const Component = () => {
const listener = useFocusWithin({
onFocusWithinChange,
});
return (
<div ref={ref} listeners={listener}>
<div ref={innerRef} />
<div ref={innerRef2} />
</div>
);
};
ReactDOM.render(<Component />, container);
ReactDOM.render(<Component show={true} />, container);
});

it('is called after "blur" and "focus" events on focus target', () => {
Expand Down Expand Up @@ -140,28 +141,39 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
});

it('is called after a focused element is unmounted', () => {
const target = createEventTarget(innerRef.current);
target.focus();
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
ReactDOM.render(<Component show={false} />, container);
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
});
});

describe('onFocusWithinVisibleChange', () => {
let onFocusWithinVisibleChange, ref, innerRef, innerRef2;

const Component = ({show}) => {
const listener = useFocusWithin({
onFocusWithinVisibleChange,
});
return (
<div ref={ref} listeners={listener}>
{show && <input ref={innerRef} />}
<div ref={innerRef2} />
</div>
);
};

beforeEach(() => {
onFocusWithinVisibleChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
const Component = () => {
const listener = useFocusWithin({
onFocusWithinVisibleChange,
});
return (
<div ref={ref} listeners={listener}>
<div ref={innerRef} />
<div ref={innerRef2} />
</div>
);
};
ReactDOM.render(<Component />, container);
ReactDOM.render(<Component show={true} />, container);
});

it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
Expand Down Expand Up @@ -258,6 +270,18 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
});

it('is called after a focused element is unmounted', () => {
const inner = innerRef.current;
const target = createEventTarget(inner);
target.keydown({key: 'Tab'});
target.focus();
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
ReactDOM.render(<Component show={false} />, container);
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
});
});

it('expect displayName to show up for event component', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const createEventTarget = node => ({
},
focus(payload) {
node.dispatchEvent(domEvents.focus(payload));
node.focus();
},
scroll(payload) {
node.dispatchEvent(domEvents.scroll(payload));
Expand Down

0 comments on commit cb09dbe

Please sign in to comment.