diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.js index a75cb9799587..539d53b781c9 100644 --- a/packages/react-dom/src/client/ReactDOMComponent.js +++ b/packages/react-dom/src/client/ReactDOMComponent.js @@ -1355,6 +1355,10 @@ export function listenToEventResponderEventTypes( const targetEventType = isPassive ? eventType : eventType.substring(0, eventType.length - 7); + // We don't listen to this as we actually emulate it in the host config + if (targetEventType === 'beforeblur') { + continue; + } if (!listenerMap.has(eventKey)) { if (isPassive) { const activeKey = targetEventType + '_active'; diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index e2e921123441..33cd58584603 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -62,6 +62,7 @@ import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols'; import { mountEventResponder, unmountEventResponder, + DEPRECATED_dispatchEventForResponderEventSystem, } from '../events/DeprecatedDOMEventResponderSystem'; import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying'; @@ -73,6 +74,8 @@ import { enableScopeAPI, } from 'shared/ReactFeatureFlags'; import { + RESPONDER_EVENT_SYSTEM, + IS_PASSIVE, PLUGIN_EVENT_SYSTEM, USE_EVENT_SYSTEM, } from '../events/EventSystemFlags'; @@ -525,9 +528,22 @@ function createEvent(type: TopLevelType): Event { } function dispatchBeforeDetachedBlur(target: HTMLElement): void { + const targetInstance = getClosestInstanceFromNode(target); ((selectionInformation: any): SelectionInformation).activeElementDetached = target; - if (enableDeprecatedFlareAPI || enableUseEventAPI) { + if (enableDeprecatedFlareAPI) { + DEPRECATED_dispatchEventForResponderEventSystem( + 'beforeblur', + targetInstance, + ({ + target, + timeStamp: Date.now(), + }: any), + target, + RESPONDER_EVENT_SYSTEM | IS_PASSIVE, + ); + } + if (enableUseEventAPI) { const event = createEvent(TOP_BEFORE_BLUR); // Dispatch "beforeblur" directly on the target, // so it gets picked up by the event system and @@ -537,7 +553,20 @@ function dispatchBeforeDetachedBlur(target: HTMLElement): void { } function dispatchAfterDetachedBlur(target: HTMLElement): void { - if (enableDeprecatedFlareAPI || enableUseEventAPI) { + if (enableDeprecatedFlareAPI) { + DEPRECATED_dispatchEventForResponderEventSystem( + 'blur', + null, + ({ + isTargetAttached: false, + target, + timeStamp: Date.now(), + }: any), + target, + RESPONDER_EVENT_SYSTEM | IS_PASSIVE, + ); + } + if (enableUseEventAPI) { const event = createEvent(TOP_AFTER_BLUR); // So we know what was detached, make the relatedTarget the // detached target on the "afterblur" event. diff --git a/packages/react-interactions/events/src/dom/DeprecatedFocus.js b/packages/react-interactions/events/src/dom/DeprecatedFocus.js index bbc35dd1348d..5cba09a4ca23 100644 --- a/packages/react-interactions/events/src/dom/DeprecatedFocus.js +++ b/packages/react-interactions/events/src/dom/DeprecatedFocus.js @@ -22,7 +22,7 @@ import {DiscreteEvent} from 'shared/ReactTypes'; */ type FocusEvent = {| - relatedTarget: null | Element | Document, + isTargetAttached: boolean, target: Element | Document, type: FocusEventType | FocusWithinEventType, pointerType: PointerType, @@ -53,7 +53,6 @@ type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange'; type FocusWithinProps = { disabled?: boolean, onFocusWithin?: (e: FocusEvent) => void, - onAfterBlurWithin?: (e: FocusEvent) => void, onBeforeBlurWithin?: (e: FocusEvent) => void, onBlurWithin?: (e: FocusEvent) => void, onFocusWithinChange?: boolean => void, @@ -66,8 +65,7 @@ type FocusWithinEventType = | 'focuswithinchange' | 'blurwithin' | 'focuswithin' - | 'beforeblurwithin' - | 'afterblurwithin'; + | 'beforeblurwithin'; /** * Shared between Focus and FocusWithin @@ -118,7 +116,8 @@ const focusVisibleEvents = hasPointerEvents const targetEventTypes = ['focus', 'blur', 'beforeblur', ...focusVisibleEvents]; -const rootEventTypes = ['afterblur']; +// Used only for the blur "detachedTarget" logic +const rootEventTypes = ['blur']; function addWindowEventListener(types, callback, options) { types.forEach(type => { @@ -193,10 +192,10 @@ function createFocusEvent( type: FocusEventType | FocusWithinEventType, target: Element | Document, pointerType: PointerType, - relatedTarget: null | Element | Document, + isTargetAttached: boolean, ): FocusEvent { return { - relatedTarget, + isTargetAttached, target, type, pointerType, @@ -298,7 +297,7 @@ function dispatchFocusEvents( 'focus', target, pointerType, - null, + true, ); context.dispatchEvent(syntheticEvent, onFocus, DiscreteEvent); } @@ -322,7 +321,7 @@ function dispatchBlurEvents( 'blur', target, pointerType, - null, + true, ); context.dispatchEvent(syntheticEvent, onBlur, DiscreteEvent); } @@ -347,7 +346,7 @@ function dispatchFocusWithinEvents( 'focuswithin', target, pointerType, - null, + true, ); context.dispatchEvent(syntheticEvent, onFocusWithin, DiscreteEvent); } @@ -362,39 +361,19 @@ 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, - null, + isTargetAttached, ); context.dispatchEvent(syntheticEvent, onBlurWithin, DiscreteEvent); } } -function dispatchAfterBlurWithinEvents( - context: ReactDOMResponderContext, - event: ReactDOMResponderEvent, - props: FocusWithinProps, - state: FocusState, -) { - const pointerType = state.pointerType; - const onAfterBlurWithin = (props.onAfterBlurWithin: any); - const relatedTarget = state.detachedTarget; - if (isFunction(onAfterBlurWithin) && relatedTarget !== null) { - const syntheticEvent = createFocusEvent( - context, - 'afterblurwithin', - relatedTarget, - pointerType, - relatedTarget, - ); - context.dispatchEvent(syntheticEvent, onAfterBlurWithin, DiscreteEvent); - } -} - function dispatchFocusChange( context: ReactDOMResponderContext, props: FocusProps, @@ -637,7 +616,7 @@ const focusWithinResponderImpl = { 'beforeblurwithin', event.target, state.pointerType, - null, + true, ); state.detachedTarget = event.target; context.dispatchEvent( @@ -681,13 +660,10 @@ const focusWithinResponderImpl = { props: FocusWithinProps, state: FocusState, ): void { - if (event.type === 'afterblur') { + if (event.type === 'blur') { const detachedTarget = state.detachedTarget; - if ( - detachedTarget !== null && - detachedTarget === event.nativeEvent.relatedTarget - ) { - dispatchAfterBlurWithinEvents(context, event, props, state); + if (detachedTarget !== null && detachedTarget === event.target) { + dispatchBlurWithinEvents(context, event, props, state); state.detachedTarget = null; if (state.addedRootEvents) { state.addedRootEvents = false; diff --git a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js index be5052fdbd5f..9997c5401dfc 100644 --- a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js @@ -290,11 +290,11 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { }); describe('onBeforeBlurWithin', () => { - let onBeforeBlurWithin, onAfterBlurWithin, ref, innerRef, innerRef2; + let onBeforeBlurWithin, onBlurWithin, ref, innerRef, innerRef2; beforeEach(() => { onBeforeBlurWithin = jest.fn(); - onAfterBlurWithin = jest.fn(); + onBlurWithin = jest.fn(); ref = React.createRef(); innerRef = React.createRef(); innerRef2 = React.createRef(); @@ -305,7 +305,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { const Component = ({show}) => { const listener = useFocusWithin({ onBeforeBlurWithin, - onAfterBlurWithin, + onBlurWithin, }); return (
@@ -322,12 +322,12 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { target.keydown({key: 'Tab'}); target.focus(); expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + expect(onBlurWithin).toHaveBeenCalledTimes(0); ReactDOM.render(, container); expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledWith( - expect.objectContaining({relatedTarget: inner}), + expect(onBlurWithin).toHaveBeenCalledTimes(1); + expect(onBlurWithin).toHaveBeenCalledWith( + expect.objectContaining({isTargetAttached: false}), ); }); @@ -336,7 +336,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { const Component = ({show}) => { const listener = useFocusWithin({ onBeforeBlurWithin, - onAfterBlurWithin, + onBlurWithin, }); return (
@@ -357,12 +357,12 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { target.keydown({key: 'Tab'}); target.focus(); expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + expect(onBlurWithin).toHaveBeenCalledTimes(0); ReactDOM.render(, container); expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledWith( - expect.objectContaining({relatedTarget: inner}), + expect(onBlurWithin).toHaveBeenCalledTimes(1); + expect(onBlurWithin).toHaveBeenCalledWith( + expect.objectContaining({isTargetAttached: false}), ); }); @@ -418,7 +418,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { const Component = ({show}) => { const listener = useFocusWithin({ onBeforeBlurWithin, - onAfterBlurWithin, + onBlurWithin, }); return ( @@ -444,7 +444,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { target.keydown({key: 'Tab'}); target.focus(); expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); + expect(onBlurWithin).toHaveBeenCalledTimes(0); suspend = true; root.render(); @@ -454,7 +454,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => { '
Loading...
', ); expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); + expect(onBlurWithin).toHaveBeenCalledTimes(1); resolve(); document.body.removeChild(container2);