From 805e7f87334551c559ab5162c701f82c105ad0b9 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Fri, 12 Apr 2019 04:23:03 -0700 Subject: [PATCH] React events: add unmounting to Focus (#15396) --- packages/react-events/src/Focus.js | 80 ++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/packages/react-events/src/Focus.js b/packages/react-events/src/Focus.js index 355c217b56006..a710fbe3bc942 100644 --- a/packages/react-events/src/Focus.js +++ b/packages/react-events/src/Focus.js @@ -22,6 +22,7 @@ type FocusProps = { type FocusState = { isFocused: boolean, + focusTarget: null | Element | Document, }; type FocusEventType = 'focus' | 'blur' | 'focuschange'; @@ -47,54 +48,87 @@ function createFocusEvent( } function dispatchFocusInEvents( - event: ReactResponderEvent, + event: null | ReactResponderEvent, context: ReactResponderContext, props: FocusProps, + state: FocusState, ) { - const {nativeEvent, target} = event; - if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { - return; + if (event != null) { + const {nativeEvent} = event; + if ( + context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget) + ) { + return; + } } if (props.onFocus) { - const syntheticEvent = createFocusEvent('focus', target); + const syntheticEvent = createFocusEvent( + 'focus', + ((state.focusTarget: any): Element | Document), + ); context.dispatchEvent(syntheticEvent, props.onFocus, {discrete: true}); } if (props.onFocusChange) { const listener = () => { props.onFocusChange(true); }; - const syntheticEvent = createFocusEvent('focuschange', target); + const syntheticEvent = createFocusEvent( + 'focuschange', + ((state.focusTarget: any): Element | Document), + ); context.dispatchEvent(syntheticEvent, listener, {discrete: true}); } } function dispatchFocusOutEvents( - event: ReactResponderEvent, + event: null | ReactResponderEvent, context: ReactResponderContext, props: FocusProps, + state: FocusState, ) { - const {nativeEvent, target} = event; - if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) { - return; + if (event != null) { + const {nativeEvent} = event; + if ( + context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget) + ) { + return; + } } if (props.onBlur) { - const syntheticEvent = createFocusEvent('blur', target); + const syntheticEvent = createFocusEvent( + 'blur', + ((state.focusTarget: any): Element | Document), + ); context.dispatchEvent(syntheticEvent, props.onBlur, {discrete: true}); } if (props.onFocusChange) { const listener = () => { props.onFocusChange(false); }; - const syntheticEvent = createFocusEvent('focuschange', target); + const syntheticEvent = createFocusEvent( + 'focuschange', + ((state.focusTarget: any): Element | Document), + ); context.dispatchEvent(syntheticEvent, listener, {discrete: true}); } } +function unmountResponder( + context: ReactResponderContext, + props: FocusProps, + state: FocusState, +): void { + if (state.isFocused) { + dispatchFocusOutEvents(null, context, props, state); + } +} + const FocusResponder = { targetEventTypes, createInitialState(): FocusState { return { isFocused: false, + focusTarget: null, }; }, onEvent( @@ -103,25 +137,41 @@ const FocusResponder = { props: Object, state: FocusState, ): void { - const {type} = event; + const {type, target} = event; switch (type) { case 'focus': { if (!state.isFocused && !context.hasOwnership()) { - dispatchFocusInEvents(event, context, props); + state.focusTarget = target; + dispatchFocusInEvents(event, context, props, state); state.isFocused = true; } break; } case 'blur': { if (state.isFocused) { - dispatchFocusOutEvents(event, context, props); + dispatchFocusOutEvents(event, context, props, state); state.isFocused = false; + state.focusTarget = null; } break; } } }, + onUnmount( + context: ReactResponderContext, + props: FocusProps, + state: FocusState, + ) { + unmountResponder(context, props, state); + }, + onOwnershipChange( + context: ReactResponderContext, + props: FocusProps, + state: FocusState, + ) { + unmountResponder(context, props, state); + }, }; export default {