[react-interactions] Change unmount blur logic to a dedicated event (#17291)
This commit is contained in:
parent
ce4b3e9981
commit
e701632ad4
|
@ -452,7 +452,11 @@ export function insertInContainerBefore(
|
|||
}
|
||||
}
|
||||
|
||||
function handleSimulateChildBlur(
|
||||
// This is a specific event for the React Flare
|
||||
// event system, so event responders can act
|
||||
// accordingly to a DOM node being unmounted that
|
||||
// previously had active document focus.
|
||||
function dispatchDetachedVisibleNodeEvent(
|
||||
child: Instance | TextInstance | SuspenseInstance,
|
||||
): void {
|
||||
if (
|
||||
|
@ -463,13 +467,11 @@ function handleSimulateChildBlur(
|
|||
const targetFiber = getClosestInstanceFromNode(child);
|
||||
// Simlulate a blur event to the React Flare responder system.
|
||||
dispatchEventForResponderEventSystem(
|
||||
'blur',
|
||||
'detachedvisiblenode',
|
||||
targetFiber,
|
||||
({
|
||||
relatedTarget: null,
|
||||
target: child,
|
||||
timeStamp: Date.now(),
|
||||
type: 'blur',
|
||||
}: any),
|
||||
((child: any): Document | Element),
|
||||
RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
|
||||
|
@ -481,7 +483,7 @@ export function removeChild(
|
|||
parentInstance: Instance,
|
||||
child: Instance | TextInstance | SuspenseInstance,
|
||||
): void {
|
||||
handleSimulateChildBlur(child);
|
||||
dispatchDetachedVisibleNodeEvent(child);
|
||||
parentInstance.removeChild(child);
|
||||
}
|
||||
|
||||
|
@ -492,7 +494,7 @@ export function removeChildFromContainer(
|
|||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).removeChild(child);
|
||||
} else {
|
||||
handleSimulateChildBlur(child);
|
||||
dispatchDetachedVisibleNodeEvent(child);
|
||||
container.removeChild(child);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,12 @@ type FocusProps = {
|
|||
onFocusVisibleChange: boolean => void,
|
||||
};
|
||||
|
||||
type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
|
||||
type FocusEventType =
|
||||
| 'focus'
|
||||
| 'blur'
|
||||
| 'focuschange'
|
||||
| 'focusvisiblechange'
|
||||
| 'detachedvisiblenode';
|
||||
|
||||
type FocusWithinProps = {
|
||||
disabled?: boolean,
|
||||
|
@ -53,13 +58,15 @@ type FocusWithinProps = {
|
|||
onBlurWithin?: (e: FocusEvent) => void,
|
||||
onFocusWithinChange?: boolean => void,
|
||||
onFocusWithinVisibleChange?: boolean => void,
|
||||
onDetachedVisibleNode?: (e: FocusEvent) => void,
|
||||
};
|
||||
|
||||
type FocusWithinEventType =
|
||||
| 'focuswithinvisiblechange'
|
||||
| 'focuswithinchange'
|
||||
| 'blurwithin'
|
||||
| 'focuswithin';
|
||||
| 'focuswithin'
|
||||
| 'detachedvisiblenode';
|
||||
|
||||
/**
|
||||
* Shared between Focus and FocusWithin
|
||||
|
@ -72,7 +79,7 @@ const isMac =
|
|||
? /^Mac/.test(window.navigator.platform)
|
||||
: false;
|
||||
|
||||
const targetEventTypes = ['focus', 'blur'];
|
||||
const targetEventTypes = ['focus', 'blur', 'detachedvisiblenode'];
|
||||
|
||||
const hasPointerEvents =
|
||||
typeof window !== 'undefined' && window.PointerEvent != null;
|
||||
|
@ -507,6 +514,22 @@ const focusWithinResponderImpl = {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'detachedvisiblenode': {
|
||||
const onDetachedVisibleNode = (props.onDetachedVisibleNode: any);
|
||||
if (isFunction(onDetachedVisibleNode)) {
|
||||
const syntheticEvent = createFocusEvent(
|
||||
context,
|
||||
'detachedvisiblenode',
|
||||
event.target,
|
||||
state.pointerType,
|
||||
);
|
||||
context.dispatchEvent(
|
||||
syntheticEvent,
|
||||
onDetachedVisibleNode,
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onRootEvent(
|
||||
|
|
|
@ -141,16 +141,6 @@ 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', () => {
|
||||
|
@ -270,17 +260,39 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
|||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onDetachedVisibleNode', () => {
|
||||
let onDetachedVisibleNode, ref, innerRef, innerRef2;
|
||||
|
||||
const Component = ({show}) => {
|
||||
const listener = useFocusWithin({
|
||||
onDetachedVisibleNode,
|
||||
});
|
||||
return (
|
||||
<div ref={ref} listeners={listener}>
|
||||
{show && <input ref={innerRef} />}
|
||||
<div ref={innerRef2} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
onDetachedVisibleNode = jest.fn();
|
||||
ref = React.createRef();
|
||||
innerRef = React.createRef();
|
||||
innerRef2 = React.createRef();
|
||||
ReactDOM.render(<Component show={true} />, container);
|
||||
});
|
||||
|
||||
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);
|
||||
expect(onDetachedVisibleNode).toHaveBeenCalledTimes(0);
|
||||
ReactDOM.render(<Component show={false} />, container);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
|
||||
expect(onDetachedVisibleNode).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue