-
Notifications
You must be signed in to change notification settings - Fork 332
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
fix(autocomplete-js): leave the modal open on reset on pointer devices #987
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 22ff478:
|
if (onDetachedEscape && event.key === 'Escape') { | ||
event.preventDefault(); | ||
onDetachedEscape(); | ||
return; | ||
} |
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.
onDetachedEscape: isDetached | ||
? () => { | ||
autocomplete.setIsOpen(false); | ||
setIsModalOpen(false); | ||
} | ||
: undefined, |
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 was no longer needed because the logic in onDetachedEscape
has been covered by this since #556.
This is likely stale code from the first Autocomplete Touch (now Detached Mode) PR.
await waitForElementToBeRemoved( | ||
document.querySelector('.aa-DetachedOverlay') | ||
).catch(() => {}); |
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.
We want to check that the modal stays open, but the following assertions in waitFor
will always pass since Testing Library just waits for this scenario to hold true. If the modal was later closing, the test would still pass, so we need to also make sure the element is never removed.
I couldn't find a cleaner way to do it, I'm open to suggestions.
@@ -1893,81 +1918,6 @@ describe('getInputProps', () => { | |||
}); | |||
}); | |||
|
|||
describe('onBlur', () => { |
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.
The test cases from this sub-suite are now split across multiple tests in the onKeyDown
sub-suite and the getEnvironmentProps
suite.
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.
👏
// If requests are still pending when the panel closes, they could reopen | ||
// the panel once they resolve. | ||
// We want to prevent any subsequent query from reopening the panel | ||
// because it would result in an unsolicited UI behavior. |
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.
maybe here it should be qualified that the requests are still happening, but the handlers ignored, what do you think?
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.
Since we call cancelAll
in multiple places, do you think it would make more sense to comment the method directly?
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'll do this in a separate PR to keep this one focused.
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.
Great refactoring! I suspect some more dead code because this behavior was the original nature of Autocomplete.
There's one behavioral change that I think we shouldn't do: hitting Tab on the input doesn't close the panel anymore. Can you think of a way to support this?
Also, a future possible improvement is to make the results keyboard navigable in Detached Mode. This is related to focus trap.
@francoischalifour Do you think hitting Tab should close the panel? This seems odd to me, I expect it to circle through focusable elements like it does right now, this seems more accessible. |
@sarahdayan This is the behavior Google, YouTube, Amazon, etc. are doing. So I think this is desirable. |
@francoischalifour It's the case on YouTube and Google indeed, but not on Amazon. Still, the combobox spec examples seem to abide by this: https://w3c.github.io/aria-practices/examples/combobox/combobox-select-only.html Should we do this only in dropdown mode so we give ourselves the possibility to implement focus trap later on in detached mode? |
Yes, only in dropdown mode. |
This PR fixes a bug in detached mode where clicking the reset button would also close the modal, even with
openOnFocus=true
. This bug appeared only on pointer devices, not on touch devices.Preview
Here are a gew scenarios that this PR fixes. You can test it out in this sandbox. To compare, here's the same sandbox without the fix.
detached-desktop.mov
The modal closes on reset.
detached-desktop.mov
The modal stays open on reset.
desktop-flicker.mov
The panel "flickers": it closes then reopens on focus.
desktop-no-flicker.mov
The panel stays open, there's no flicker.
detached-touch.mov
The modal stays open in detached mode on a touch device
detached-touch.mov
The modal still stays open in detached mode on a touch device
Root cause
Clicking the reset button triggers the DOM
blur
event on the<input>
. Before this fix, we had anonBlur
handler that triggered logic that closed the search modal. This didn't happen on touch devices because of some guard logic that only applied to touch devices.This is a problem, because detached mode isn't touch-specific: it's the default mode on screens below 500px, and users can enable it with manually.
More context in the issue →
Solution
This PR fixes the problem by no longer relying on the
blur
input event, and instead using themousedown
event at the environment level. This is a similar approach to what we're doing with thetouchstart
event, but for pointer devices. Both handlers reuse the same logic, and are colocated.The rationale behind choosing this path is the following:
formElement
andpanelElement
. Keeping theonBlur
handler would mean making these elements available ingetInputProps
, which would be complicated, and a breaking change.reset
,keydown
, etc.) than consequential events, particularly because we know exactly what caused them. Tracing the origin on ablur
isn't always possible.Output
This PR fixes the issue, and solves other problems in the process:
Escape
in detached mode on touch devices was still vulnerable to stale promises due to some outdated logic that was removed in this PR. This was an unlikely scenario anyway, since you don't useEscape
on most mobile and tablet devices. Still, it could technically happen on some computers with touch screens.Next steps
blur()
on an input, they click on things or press keys. They don't callreset()
on a form, they click a reset button, etc. We do this irregularly in our tests, which causes false negatives and positives. We should do a pass to straighten this out.fixes #971