Skip to content

Commit e701632

Browse files
authored
[react-interactions] Change unmount blur logic to a dedicated event (#17291)
1 parent ce4b3e9 commit e701632

File tree

3 files changed

+60
-23
lines changed

3 files changed

+60
-23
lines changed

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,11 @@ export function insertInContainerBefore(
452452
}
453453
}
454454

455-
function handleSimulateChildBlur(
455+
// This is a specific event for the React Flare
456+
// event system, so event responders can act
457+
// accordingly to a DOM node being unmounted that
458+
// previously had active document focus.
459+
function dispatchDetachedVisibleNodeEvent(
456460
child: Instance | TextInstance | SuspenseInstance,
457461
): void {
458462
if (
@@ -463,13 +467,11 @@ function handleSimulateChildBlur(
463467
const targetFiber = getClosestInstanceFromNode(child);
464468
// Simlulate a blur event to the React Flare responder system.
465469
dispatchEventForResponderEventSystem(
466-
'blur',
470+
'detachedvisiblenode',
467471
targetFiber,
468472
({
469-
relatedTarget: null,
470473
target: child,
471474
timeStamp: Date.now(),
472-
type: 'blur',
473475
}: any),
474476
((child: any): Document | Element),
475477
RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
@@ -481,7 +483,7 @@ export function removeChild(
481483
parentInstance: Instance,
482484
child: Instance | TextInstance | SuspenseInstance,
483485
): void {
484-
handleSimulateChildBlur(child);
486+
dispatchDetachedVisibleNodeEvent(child);
485487
parentInstance.removeChild(child);
486488
}
487489

@@ -492,7 +494,7 @@ export function removeChildFromContainer(
492494
if (container.nodeType === COMMENT_NODE) {
493495
(container.parentNode: any).removeChild(child);
494496
} else {
495-
handleSimulateChildBlur(child);
497+
dispatchDetachedVisibleNodeEvent(child);
496498
container.removeChild(child);
497499
}
498500
}

packages/react-interactions/events/src/dom/Focus.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,28 @@ type FocusProps = {
4545
onFocusVisibleChange: boolean => void,
4646
};
4747

48-
type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
48+
type FocusEventType =
49+
| 'focus'
50+
| 'blur'
51+
| 'focuschange'
52+
| 'focusvisiblechange'
53+
| 'detachedvisiblenode';
4954

5055
type FocusWithinProps = {
5156
disabled?: boolean,
5257
onFocusWithin?: (e: FocusEvent) => void,
5358
onBlurWithin?: (e: FocusEvent) => void,
5459
onFocusWithinChange?: boolean => void,
5560
onFocusWithinVisibleChange?: boolean => void,
61+
onDetachedVisibleNode?: (e: FocusEvent) => void,
5662
};
5763

5864
type FocusWithinEventType =
5965
| 'focuswithinvisiblechange'
6066
| 'focuswithinchange'
6167
| 'blurwithin'
62-
| 'focuswithin';
68+
| 'focuswithin'
69+
| 'detachedvisiblenode';
6370

6471
/**
6572
* Shared between Focus and FocusWithin
@@ -72,7 +79,7 @@ const isMac =
7279
? /^Mac/.test(window.navigator.platform)
7380
: false;
7481

75-
const targetEventTypes = ['focus', 'blur'];
82+
const targetEventTypes = ['focus', 'blur', 'detachedvisiblenode'];
7683

7784
const hasPointerEvents =
7885
typeof window !== 'undefined' && window.PointerEvent != null;
@@ -507,6 +514,22 @@ const focusWithinResponderImpl = {
507514
}
508515
break;
509516
}
517+
case 'detachedvisiblenode': {
518+
const onDetachedVisibleNode = (props.onDetachedVisibleNode: any);
519+
if (isFunction(onDetachedVisibleNode)) {
520+
const syntheticEvent = createFocusEvent(
521+
context,
522+
'detachedvisiblenode',
523+
event.target,
524+
state.pointerType,
525+
);
526+
context.dispatchEvent(
527+
syntheticEvent,
528+
onDetachedVisibleNode,
529+
DiscreteEvent,
530+
);
531+
}
532+
}
510533
}
511534
},
512535
onRootEvent(

packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,6 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
141141
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
142142
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
143143
});
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-
});
154144
});
155145

156146
describe('onFocusWithinVisibleChange', () => {
@@ -270,17 +260,39 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
270260
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
271261
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
272262
});
263+
});
264+
265+
describe('onDetachedVisibleNode', () => {
266+
let onDetachedVisibleNode, ref, innerRef, innerRef2;
267+
268+
const Component = ({show}) => {
269+
const listener = useFocusWithin({
270+
onDetachedVisibleNode,
271+
});
272+
return (
273+
<div ref={ref} listeners={listener}>
274+
{show && <input ref={innerRef} />}
275+
<div ref={innerRef2} />
276+
</div>
277+
);
278+
};
279+
280+
beforeEach(() => {
281+
onDetachedVisibleNode = jest.fn();
282+
ref = React.createRef();
283+
innerRef = React.createRef();
284+
innerRef2 = React.createRef();
285+
ReactDOM.render(<Component show={true} />, container);
286+
});
273287

274288
it('is called after a focused element is unmounted', () => {
275289
const inner = innerRef.current;
276290
const target = createEventTarget(inner);
277291
target.keydown({key: 'Tab'});
278292
target.focus();
279-
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
280-
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
293+
expect(onDetachedVisibleNode).toHaveBeenCalledTimes(0);
281294
ReactDOM.render(<Component show={false} />, container);
282-
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
283-
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
295+
expect(onDetachedVisibleNode).toHaveBeenCalledTimes(1);
284296
});
285297
});
286298

0 commit comments

Comments
 (0)