-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[react-interactions] Refine custom active element blur logic #17354
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -104,6 +104,12 @@ export type ChildSet = void; // Unused | |||||||||||||||||||||||||||||||||||||||
export type TimeoutHandle = TimeoutID; | ||||||||||||||||||||||||||||||||||||||||
export type NoTimeout = -1; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
type SelectionInformation = {| | ||||||||||||||||||||||||||||||||||||||||
blurredActiveElement: null | HTMLElement, | ||||||||||||||||||||||||||||||||||||||||
focusedElem: null | HTMLElement, | ||||||||||||||||||||||||||||||||||||||||
selectionRange: mixed, | ||||||||||||||||||||||||||||||||||||||||
|}; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||||||||
enableSuspenseServerRenderer, | ||||||||||||||||||||||||||||||||||||||||
enableFlareAPI, | ||||||||||||||||||||||||||||||||||||||||
|
@@ -127,7 +133,7 @@ const SUSPENSE_FALLBACK_START_DATA = '$!'; | |||||||||||||||||||||||||||||||||||||||
const STYLE = 'style'; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
let eventsEnabled: ?boolean = null; | ||||||||||||||||||||||||||||||||||||||||
let selectionInformation: ?mixed = null; | ||||||||||||||||||||||||||||||||||||||||
let selectionInformation: null | SelectionInformation = null; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
function shouldAutoFocusHostComponent(type: string, props: Props): boolean { | ||||||||||||||||||||||||||||||||||||||||
switch (type) { | ||||||||||||||||||||||||||||||||||||||||
|
@@ -205,6 +211,13 @@ 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); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+215
to
+219
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
selectionInformation = null; | ||||||||||||||||||||||||||||||||||||||||
ReactBrowserEventEmitterSetEnabled(eventsEnabled); | ||||||||||||||||||||||||||||||||||||||||
eventsEnabled = null; | ||||||||||||||||||||||||||||||||||||||||
|
@@ -452,38 +465,57 @@ export function insertInContainerBefore( | |||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
function dispatchFlareDetachedBlurEvent( | ||||||||||||||||||||||||||||||||||||||||
elementDetached: boolean, | ||||||||||||||||||||||||||||||||||||||||
targetInstance: null | Object, | ||||||||||||||||||||||||||||||||||||||||
target: Element | Document, | ||||||||||||||||||||||||||||||||||||||||
): void { | ||||||||||||||||||||||||||||||||||||||||
// Simlulate the custom event to the React Flare responder system. | ||||||||||||||||||||||||||||||||||||||||
dispatchEventForResponderEventSystem( | ||||||||||||||||||||||||||||||||||||||||
'blur', | ||||||||||||||||||||||||||||||||||||||||
targetInstance, | ||||||||||||||||||||||||||||||||||||||||
({ | ||||||||||||||||||||||||||||||||||||||||
elementDetached, | ||||||||||||||||||||||||||||||||||||||||
target, | ||||||||||||||||||||||||||||||||||||||||
timeStamp: Date.now(), | ||||||||||||||||||||||||||||||||||||||||
}: any), | ||||||||||||||||||||||||||||||||||||||||
target, | ||||||||||||||||||||||||||||||||||||||||
RESPONDER_EVENT_SYSTEM | IS_PASSIVE, | ||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
function dispatchBeforeActiveElementBlur(element: HTMLElement): void { | ||||||||||||||||||||||||||||||||||||||||
const targtInstance = getClosestInstanceFromNode(element); | ||||||||||||||||||||||||||||||||||||||||
((selectionInformation: any): SelectionInformation).blurredActiveElement = element; | ||||||||||||||||||||||||||||||||||||||||
dispatchFlareDetachedBlurEvent(false, targtInstance, element); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+487
to
+491
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
function dispatchActiveElementBlur( | ||||||||||||||||||||||||||||||||||||||||
node: Instance | TextInstance | SuspenseInstance, | ||||||||||||||||||||||||||||||||||||||||
): void { | ||||||||||||||||||||||||||||||||||||||||
dispatchFlareDetachedBlurEvent(true, null, ((node: any): HTMLElement)); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+493
to
+497
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
// 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, | ||||||||||||||||||||||||||||||||||||||||
export function beforeRemoveInstance( | ||||||||||||||||||||||||||||||||||||||||
instance: Instance | TextInstance | SuspenseInstance, | ||||||||||||||||||||||||||||||||||||||||
): void { | ||||||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||||||
enableFlareAPI && | ||||||||||||||||||||||||||||||||||||||||
selectionInformation && | ||||||||||||||||||||||||||||||||||||||||
child === selectionInformation.focusedElem | ||||||||||||||||||||||||||||||||||||||||
instance === selectionInformation.focusedElem | ||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||
const targetFiber = getClosestInstanceFromNode(child); | ||||||||||||||||||||||||||||||||||||||||
// Simlulate a blur event to the React Flare responder system. | ||||||||||||||||||||||||||||||||||||||||
dispatchEventForResponderEventSystem( | ||||||||||||||||||||||||||||||||||||||||
'detachedvisiblenode', | ||||||||||||||||||||||||||||||||||||||||
targetFiber, | ||||||||||||||||||||||||||||||||||||||||
({ | ||||||||||||||||||||||||||||||||||||||||
target: child, | ||||||||||||||||||||||||||||||||||||||||
timeStamp: Date.now(), | ||||||||||||||||||||||||||||||||||||||||
}: any), | ||||||||||||||||||||||||||||||||||||||||
((child: any): Document | Element), | ||||||||||||||||||||||||||||||||||||||||
RESPONDER_EVENT_SYSTEM | IS_PASSIVE, | ||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||
dispatchBeforeActiveElementBlur(((instance: any): HTMLElement)); | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
export function removeChild( | ||||||||||||||||||||||||||||||||||||||||
parentInstance: Instance, | ||||||||||||||||||||||||||||||||||||||||
child: Instance | TextInstance | SuspenseInstance, | ||||||||||||||||||||||||||||||||||||||||
): void { | ||||||||||||||||||||||||||||||||||||||||
dispatchDetachedVisibleNodeEvent(child); | ||||||||||||||||||||||||||||||||||||||||
parentInstance.removeChild(child); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
@@ -494,7 +526,6 @@ export function removeChildFromContainer( | |||||||||||||||||||||||||||||||||||||||
if (container.nodeType === COMMENT_NODE) { | ||||||||||||||||||||||||||||||||||||||||
(container.parentNode: any).removeChild(child); | ||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||
dispatchDetachedVisibleNodeEvent(child); | ||||||||||||||||||||||||||||||||||||||||
container.removeChild(child); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -100,6 +100,8 @@ export function hasSelectionCapabilities(elem) { | |||||
export function getSelectionInformation() { | ||||||
const focusedElem = getActiveElementDeep(); | ||||||
return { | ||||||
// Used by Flare | ||||||
blurredActiveElement: null, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
focusedElem: focusedElem, | ||||||
selectionRange: hasSelectionCapabilities(focusedElem) | ||||||
? getSelection(focusedElem) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,28 +45,25 @@ type FocusProps = { | |
onFocusVisibleChange: boolean => void, | ||
}; | ||
|
||
type FocusEventType = | ||
| 'focus' | ||
| 'blur' | ||
| 'focuschange' | ||
| 'focusvisiblechange' | ||
| 'detachedvisiblenode'; | ||
type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange'; | ||
|
||
type FocusWithinProps = { | ||
disabled?: boolean, | ||
onFocusWithin?: (e: FocusEvent) => void, | ||
onBlurWithin?: (e: FocusEvent) => void, | ||
onFocusWithinChange?: boolean => void, | ||
onFocusWithinVisibleChange?: boolean => void, | ||
onDetachedVisibleNode?: (e: FocusEvent) => void, | ||
onBeforeFocusedElementDetached?: (e: FocusEvent) => void, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
onFocusedElementDetached?: (e: FocusEvent) => void, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wouldn't be needed, |
||
}; | ||
|
||
type FocusWithinEventType = | ||
| 'focuswithinvisiblechange' | ||
| 'focuswithinchange' | ||
| 'blurwithin' | ||
| 'focuswithin' | ||
| 'detachedvisiblenode'; | ||
| 'focusedelementdetached' | ||
| 'beforefocusedelementdetached'; | ||
Comment on lines
+65
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced with |
||
|
||
/** | ||
* Shared between Focus and FocusWithin | ||
|
@@ -79,14 +76,22 @@ const isMac = | |
? /^Mac/.test(window.navigator.platform) | ||
: false; | ||
|
||
const targetEventTypes = ['focus', 'blur', 'detachedvisiblenode']; | ||
const targetEventTypes = ['focus', 'blur']; | ||
|
||
const hasPointerEvents = | ||
typeof window !== 'undefined' && window.PointerEvent != null; | ||
|
||
const rootEventTypes = hasPointerEvents | ||
? ['keydown', 'keyup', 'pointermove', 'pointerdown', 'pointerup'] | ||
: ['keydown', 'keyup', 'mousedown', 'touchmove', 'touchstart', 'touchend']; | ||
? ['keydown', 'keyup', 'pointermove', 'pointerdown', 'pointerup', 'blur'] | ||
: [ | ||
'keydown', | ||
'keyup', | ||
'mousedown', | ||
'touchmove', | ||
'touchstart', | ||
'touchend', | ||
'blur', | ||
]; | ||
|
||
function isFunction(obj): boolean { | ||
return typeof obj === 'function'; | ||
|
@@ -504,6 +509,23 @@ 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) | ||
|
@@ -514,22 +536,6 @@ 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( | ||
|
@@ -538,6 +544,23 @@ 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, | ||
); | ||
} | ||
return; | ||
} | ||
handleRootEvent(event, context, state, isFocusVisible => { | ||
if (state.isFocused && state.isFocusVisible !== isFocusVisible) { | ||
state.isFocusVisible = isFocusVisible; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.