-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
refactor: No longer preventDefault in usePress and allow browser to manage focus #7542
base: main
Are you sure you want to change the base?
Conversation
export function isFocusable(element: HTMLElement) { | ||
return element.matches(FOCUSABLE_ELEMENT_SELECTOR); | ||
} | ||
|
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.
Moved to @react-aria/utils
to avoid creating a circular dependency when I import it in @react-aria/interactions
.
// If triggered from a screen reader or by using element.click(), | ||
// trigger as if it were a keyboard click. | ||
if (!state.ignoreClickAfterPress && !state.ignoreEmulatedMouseEvents && !state.isPressed && (state.pointerType === 'virtual' || isVirtualClick(e.nativeEvent))) { | ||
// Ensure the element receives focus (VoiceOver on iOS does not do this) |
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.
I can't reproduce the original issue anymore. iOS VO uses DOM focus now. Not sure if we need to worry about this elsewhere. If so, it should probably be done e.g. in dialog trigger instead of here so it doesn't affect other things like submitting a form.
@@ -544,18 +536,15 @@ export function usePress(props: PressHookProps): PressResult { | |||
cancel(e); | |||
}; | |||
} else { | |||
// NOTE: this fallback branch is almost entirely used by unit tests. | |||
// All browsers now support pointer events, but JSDOM still does not. |
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.
Next step will be to make this branch development only, and possibly get rid of the touch event handling entirely (since most people write their tests using mouse events).
checkSelection(onSelectionChange, ['Foo 10']); | ||
// screen reader automatically handles this one | ||
expect(announce).not.toHaveBeenCalled(); | ||
expect(announce).toHaveBeenCalledWith('Foo 10 selected.'); |
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.
This test was wrong before. The announcement was not fired because the table never had focus.
## API Changes
@react-aria/focus/@react-aria/focus:isFocusable isFocusable {
- element: HTMLElement
+ element: Element
returnVal: undefined
} @react-aria/test-utils/@react-aria/test-utils:triggerLongPress triggerLongPress {
opts: {
element: HTMLElement
advanceTimer: (number) => void | Promise<unknown>
- pointerOpts?: {
-
+ pointerOpts?: Record<string, any>
}
-}
returnVal: undefined
} @react-aria/utils/@react-aria/utils:isFocusable+isFocusable {
+ element: Element
+ returnVal: undefined
+} /@react-aria/utils:isTabbable+isTabbable {
+ element: Element
+ returnVal: undefined
+} @react-spectrum/test-utils/@react-spectrum/test-utils:triggerLongPress triggerLongPress {
opts: {
element: HTMLElement
advanceTimer: (number) => void | Promise<unknown>
- pointerOpts?: {
-
+ pointerOpts?: Record<string, any>
}
-}
returnVal: undefined
} |
Part of #1720
Fixes #5004, fixes #4302, fixes #6618, closes #7448, fixes #5643, fixes #7480, fixes #5522, fixes #4089, fixes #5833, fixes #6512, fixes #4355, fixes #5940, fixes #1926, fixes #1384, closes #1391
This is a major refactor to
usePress
to improve compatibility with other libraries, improve the way focus is managed, and fix many issues caused by our current approach. 🎄This is mainly possible because of a change in Safari 17, which enabled buttons and other native inputs to be focused (both via mouse/touch and keyboard tabbing) only when they have an explicit
tabIndex
set. WebKit/WebKit#12743 We previously worked around this by callingpreventDefault
and manually managing focus. However, becausepreventDefault
is not granular this had many unintended side effects.preventDefault
duringonPointerDown
prevents subsequent mouse and touch events from being fired by the browser entirely. This can break compatibility between React Aria and other libraries that rely on these events (e.g. Material UI button ripple effect).onMouseDown
event #5004preventDefault
duringonMouseDown
prevents the:active
CSS pseudo class in Firefox:active
selector withuseButton
#4302preventDefault
duringonMouseDown
prevents drag and drop. We had some special cases for this but not all of them.a
tag link draggable with usePress #7448preventDefault
to also prevent their behaviors in addition to the browser ones.Thanks to the Safari change, we can now let the browser handle focusing elements on press for us. This changes the order that things occur, so we'll need to test carefully. In particular, the element will no longer be focused before
onPressStart
becauseonFocus
is fired by the browser afteronMouseDown
. On touch devices, this will happen after the user lifts their finger. This change should fix these issues:onPointerDown
could cause the software keyboard to disappear, resulting in a layout shift that caused the user to tap on the wrong element.:focus-visible
selector to applyWhen
preventFocusOnPress
is set, we allow the browser to focus, but handle the event at a window-level capturing listener, and immediately revert it and stop propagation to make the event non-observable to child elements.We also now delay firing
onPress
until theonClick
event instead of firing it duringonPointerUp
. This fixes many issues:onPointerUp
andonClick
, the element behind it will be clicked instead.onClick
being fired for a variety of purposes:We will need to evaluate how much of a breaking change this is. Outside
usePress
itself, the main changes were for tests, which now must actually trigger the click event and not just pointerdown/pointerup. Switching to user-event fixes this for the most part. We also have to deal with some assumptions that focus will occur beforeonPressStart
, such as inuseMenuTrigger
which now usespreventFocusOnPress
and manually focuses its trigger prior to opening the popover so that it can be restored properly. This only happens for mouse interactions.