mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[react-interactions] Follow up active element blur logic (#17364)
This commit is contained in:
+24
-24
@@ -105,7 +105,7 @@ export type TimeoutHandle = TimeoutID;
|
||||
export type NoTimeout = -1;
|
||||
|
||||
type SelectionInformation = {|
|
||||
blurredActiveElement: null | HTMLElement,
|
||||
activeElementDetached: null | HTMLElement,
|
||||
focusedElem: null | HTMLElement,
|
||||
selectionRange: mixed,
|
||||
|};
|
||||
@@ -212,10 +212,10 @@ export function prepareForCommit(containerInfo: Container): void {
|
||||
export function resetAfterCommit(containerInfo: Container): void {
|
||||
restoreSelection(selectionInformation);
|
||||
if (enableFlareAPI) {
|
||||
const blurredActiveElement = (selectionInformation: any)
|
||||
.blurredActiveElement;
|
||||
if (blurredActiveElement !== null) {
|
||||
dispatchActiveElementBlur(blurredActiveElement);
|
||||
const activeElementDetached = (selectionInformation: any)
|
||||
.activeElementDetached;
|
||||
if (activeElementDetached !== null) {
|
||||
dispatchDetachedBlur(activeElementDetached);
|
||||
}
|
||||
}
|
||||
selectionInformation = null;
|
||||
@@ -465,17 +465,14 @@ export function insertInContainerBefore(
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchFlareDetachedBlurEvent(
|
||||
elementDetached: boolean,
|
||||
targetInstance: null | Object,
|
||||
target: Element | Document,
|
||||
): void {
|
||||
// Simlulate the custom event to the React Flare responder system.
|
||||
function dispatchBeforeDetachedBlur(target: HTMLElement): void {
|
||||
const targetInstance = getClosestInstanceFromNode(target);
|
||||
((selectionInformation: any): SelectionInformation).activeElementDetached = target;
|
||||
|
||||
dispatchEventForResponderEventSystem(
|
||||
'blur',
|
||||
'beforeblur',
|
||||
targetInstance,
|
||||
({
|
||||
elementDetached,
|
||||
target,
|
||||
timeStamp: Date.now(),
|
||||
}: any),
|
||||
@@ -484,16 +481,19 @@ function dispatchFlareDetachedBlurEvent(
|
||||
);
|
||||
}
|
||||
|
||||
function dispatchBeforeActiveElementBlur(element: HTMLElement): void {
|
||||
const targtInstance = getClosestInstanceFromNode(element);
|
||||
((selectionInformation: any): SelectionInformation).blurredActiveElement = element;
|
||||
dispatchFlareDetachedBlurEvent(false, targtInstance, element);
|
||||
}
|
||||
|
||||
function dispatchActiveElementBlur(
|
||||
node: Instance | TextInstance | SuspenseInstance,
|
||||
): void {
|
||||
dispatchFlareDetachedBlurEvent(true, null, ((node: any): HTMLElement));
|
||||
function dispatchDetachedBlur(target: HTMLElement): void {
|
||||
const targetInstance = getClosestInstanceFromNode(target);
|
||||
dispatchEventForResponderEventSystem(
|
||||
'blur',
|
||||
targetInstance,
|
||||
({
|
||||
isTargetAttached: false,
|
||||
target,
|
||||
timeStamp: Date.now(),
|
||||
}: any),
|
||||
target,
|
||||
RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
|
||||
);
|
||||
}
|
||||
|
||||
// This is a specific event for the React Flare
|
||||
@@ -508,7 +508,7 @@ export function beforeRemoveInstance(
|
||||
selectionInformation &&
|
||||
instance === selectionInformation.focusedElem
|
||||
) {
|
||||
dispatchBeforeActiveElementBlur(((instance: any): HTMLElement));
|
||||
dispatchBeforeDetachedBlur(((instance: any): HTMLElement));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ export function getSelectionInformation() {
|
||||
const focusedElem = getActiveElementDeep();
|
||||
return {
|
||||
// Used by Flare
|
||||
blurredActiveElement: null,
|
||||
activeElementDetached: null,
|
||||
focusedElem: focusedElem,
|
||||
selectionRange: hasSelectionCapabilities(focusedElem)
|
||||
? getSelection(focusedElem)
|
||||
|
||||
+37
-36
@@ -22,6 +22,7 @@ import {DiscreteEvent} from 'shared/ReactTypes';
|
||||
*/
|
||||
|
||||
type FocusEvent = {|
|
||||
isTargetAttached: boolean,
|
||||
target: Element | Document,
|
||||
type: FocusEventType | FocusWithinEventType,
|
||||
pointerType: PointerType,
|
||||
@@ -30,6 +31,7 @@ type FocusEvent = {|
|
||||
|};
|
||||
|
||||
type FocusState = {
|
||||
detachedTarget: null | Element | Document,
|
||||
focusTarget: null | Element | Document,
|
||||
isFocused: boolean,
|
||||
isFocusVisible: boolean,
|
||||
@@ -50,11 +52,10 @@ type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
|
||||
type FocusWithinProps = {
|
||||
disabled?: boolean,
|
||||
onFocusWithin?: (e: FocusEvent) => void,
|
||||
onBeforeBlurWithin?: (e: FocusEvent) => void,
|
||||
onBlurWithin?: (e: FocusEvent) => void,
|
||||
onFocusWithinChange?: boolean => void,
|
||||
onFocusWithinVisibleChange?: boolean => void,
|
||||
onBeforeFocusedElementDetached?: (e: FocusEvent) => void,
|
||||
onFocusedElementDetached?: (e: FocusEvent) => void,
|
||||
};
|
||||
|
||||
type FocusWithinEventType =
|
||||
@@ -62,8 +63,7 @@ type FocusWithinEventType =
|
||||
| 'focuswithinchange'
|
||||
| 'blurwithin'
|
||||
| 'focuswithin'
|
||||
| 'focusedelementdetached'
|
||||
| 'beforefocusedelementdetached';
|
||||
| 'beforeblurwithin';
|
||||
|
||||
/**
|
||||
* Shared between Focus and FocusWithin
|
||||
@@ -76,7 +76,7 @@ const isMac =
|
||||
? /^Mac/.test(window.navigator.platform)
|
||||
: false;
|
||||
|
||||
const targetEventTypes = ['focus', 'blur'];
|
||||
const targetEventTypes = ['focus', 'blur', 'beforeblur'];
|
||||
|
||||
const hasPointerEvents =
|
||||
typeof window !== 'undefined' && window.PointerEvent != null;
|
||||
@@ -102,8 +102,10 @@ function createFocusEvent(
|
||||
type: FocusEventType | FocusWithinEventType,
|
||||
target: Element | Document,
|
||||
pointerType: PointerType,
|
||||
isTargetAttached: boolean,
|
||||
): FocusEvent {
|
||||
return {
|
||||
isTargetAttached,
|
||||
target,
|
||||
type,
|
||||
pointerType,
|
||||
@@ -218,6 +220,7 @@ function dispatchFocusEvents(
|
||||
'focus',
|
||||
target,
|
||||
pointerType,
|
||||
true,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, onFocus, DiscreteEvent);
|
||||
}
|
||||
@@ -241,6 +244,7 @@ function dispatchBlurEvents(
|
||||
'blur',
|
||||
target,
|
||||
pointerType,
|
||||
true,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, onBlur, DiscreteEvent);
|
||||
}
|
||||
@@ -265,6 +269,7 @@ function dispatchFocusWithinEvents(
|
||||
'focuswithin',
|
||||
target,
|
||||
pointerType,
|
||||
true,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, onFocusWithin, DiscreteEvent);
|
||||
}
|
||||
@@ -279,12 +284,14 @@ function dispatchBlurWithinEvents(
|
||||
const pointerType = state.pointerType;
|
||||
const target = ((state.focusTarget: any): Element | Document) || event.target;
|
||||
const onBlurWithin = (props.onBlurWithin: any);
|
||||
const isTargetAttached = state.detachedTarget === null;
|
||||
if (isFunction(onBlurWithin)) {
|
||||
const syntheticEvent = createFocusEvent(
|
||||
context,
|
||||
'blurwithin',
|
||||
target,
|
||||
pointerType,
|
||||
isTargetAttached,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, onBlurWithin, DiscreteEvent);
|
||||
}
|
||||
@@ -328,6 +335,7 @@ const focusResponderImpl = {
|
||||
rootEventTypes,
|
||||
getInitialState(): FocusState {
|
||||
return {
|
||||
detachedTarget: null,
|
||||
focusTarget: null,
|
||||
isEmulatingMouseEvents: false,
|
||||
isFocused: false,
|
||||
@@ -466,6 +474,7 @@ const focusWithinResponderImpl = {
|
||||
rootEventTypes,
|
||||
getInitialState(): FocusState {
|
||||
return {
|
||||
detachedTarget: null,
|
||||
focusTarget: null,
|
||||
isEmulatingMouseEvents: false,
|
||||
isFocused: false,
|
||||
@@ -509,23 +518,6 @@ const focusWithinResponderImpl = {
|
||||
break;
|
||||
}
|
||||
case 'blur': {
|
||||
if ((nativeEvent: any).elementDetached === false) {
|
||||
const onBeforeFocusedElementDetached = (props.onBeforeFocusedElementDetached: any);
|
||||
if (isFunction(onBeforeFocusedElementDetached)) {
|
||||
const syntheticEvent = createFocusEvent(
|
||||
context,
|
||||
'beforefocusedelementdetached',
|
||||
event.target,
|
||||
state.pointerType,
|
||||
);
|
||||
context.dispatchEvent(
|
||||
syntheticEvent,
|
||||
onBeforeFocusedElementDetached,
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
state.isFocused &&
|
||||
!context.isTargetWithinResponder(relatedTarget)
|
||||
@@ -536,6 +528,24 @@ const focusWithinResponderImpl = {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'beforeblur': {
|
||||
const onBeforeBlurWithin = (props.onBeforeBlurWithin: any);
|
||||
if (isFunction(onBeforeBlurWithin)) {
|
||||
const syntheticEvent = createFocusEvent(
|
||||
context,
|
||||
'beforeblurwithin',
|
||||
event.target,
|
||||
state.pointerType,
|
||||
true,
|
||||
);
|
||||
state.detachedTarget = event.target;
|
||||
context.dispatchEvent(
|
||||
syntheticEvent,
|
||||
onBeforeBlurWithin,
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onRootEvent(
|
||||
@@ -544,20 +554,11 @@ const focusWithinResponderImpl = {
|
||||
props: FocusWithinProps,
|
||||
state: FocusState,
|
||||
): void {
|
||||
if ((event.nativeEvent: any).elementDetached === true) {
|
||||
const onFocusedElementDetached = (props.onFocusedElementDetached: any);
|
||||
if (isFunction(onFocusedElementDetached)) {
|
||||
const syntheticEvent = createFocusEvent(
|
||||
context,
|
||||
'focusedelementdetached',
|
||||
event.target,
|
||||
state.pointerType,
|
||||
);
|
||||
context.dispatchEvent(
|
||||
syntheticEvent,
|
||||
onFocusedElementDetached,
|
||||
DiscreteEvent,
|
||||
);
|
||||
if (event.type === 'blur') {
|
||||
const detachedTarget = state.detachedTarget;
|
||||
if (detachedTarget !== null && detachedTarget === event.target) {
|
||||
dispatchBlurWithinEvents(context, event, props, state);
|
||||
state.detachedTarget = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -262,16 +262,12 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('onBeforeFocusedElementDetached/onFocusedElementDetached', () => {
|
||||
let onBeforeFocusedElementDetached,
|
||||
onFocusedElementDetached,
|
||||
ref,
|
||||
innerRef,
|
||||
innerRef2;
|
||||
describe('onBeforeBlurWithin', () => {
|
||||
let onBeforeBlurWithin, onBlurWithin, ref, innerRef, innerRef2;
|
||||
|
||||
beforeEach(() => {
|
||||
onBeforeFocusedElementDetached = jest.fn();
|
||||
onFocusedElementDetached = jest.fn();
|
||||
onBeforeBlurWithin = jest.fn();
|
||||
onBlurWithin = jest.fn();
|
||||
ref = React.createRef();
|
||||
innerRef = React.createRef();
|
||||
innerRef2 = React.createRef();
|
||||
@@ -280,8 +276,8 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
it('is called after a focused element is unmounted', () => {
|
||||
const Component = ({show}) => {
|
||||
const listener = useFocusWithin({
|
||||
onBeforeFocusedElementDetached,
|
||||
onFocusedElementDetached,
|
||||
onBeforeBlurWithin,
|
||||
onBlurWithin,
|
||||
});
|
||||
return (
|
||||
<div ref={ref} listeners={listener}>
|
||||
@@ -297,18 +293,21 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
const target = createEventTarget(inner);
|
||||
target.keydown({key: 'Tab'});
|
||||
target.focus();
|
||||
expect(onBeforeFocusedElementDetached).toHaveBeenCalledTimes(0);
|
||||
expect(onFocusedElementDetached).toHaveBeenCalledTimes(0);
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onBlurWithin).toHaveBeenCalledTimes(0);
|
||||
ReactDOM.render(<Component show={false} />, container);
|
||||
expect(onBeforeFocusedElementDetached).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusedElementDetached).toHaveBeenCalledTimes(1);
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onBlurWithin).toHaveBeenCalledWith(
|
||||
expect.objectContaining({isTargetAttached: false}),
|
||||
);
|
||||
});
|
||||
|
||||
it('is called after a nested focused element is unmounted', () => {
|
||||
const Component = ({show}) => {
|
||||
const listener = useFocusWithin({
|
||||
onBeforeFocusedElementDetached,
|
||||
onFocusedElementDetached,
|
||||
onBeforeBlurWithin,
|
||||
onBlurWithin,
|
||||
});
|
||||
return (
|
||||
<div ref={ref} listeners={listener}>
|
||||
@@ -328,11 +327,14 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
const target = createEventTarget(inner);
|
||||
target.keydown({key: 'Tab'});
|
||||
target.focus();
|
||||
expect(onBeforeFocusedElementDetached).toHaveBeenCalledTimes(0);
|
||||
expect(onFocusedElementDetached).toHaveBeenCalledTimes(0);
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onBlurWithin).toHaveBeenCalledTimes(0);
|
||||
ReactDOM.render(<Component show={false} />, container);
|
||||
expect(onBeforeFocusedElementDetached).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusedElementDetached).toHaveBeenCalledTimes(1);
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onBlurWithin).toHaveBeenCalledWith(
|
||||
expect.objectContaining({isTargetAttached: false}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user