fix(date-picker): make outside-dismissal focus restore consistent, add restoreFocus prop - #3241
Open
davevanhoorn wants to merge 1 commit into
Open
fix(date-picker): make outside-dismissal focus restore consistent, add restoreFocus prop#3241davevanhoorn wants to merge 1 commit into
davevanhoorn wants to merge 1 commit into
Conversation
…d restoreFocus prop
🦋 Changeset detectedLatest commit: a1e5dce The changes in this PR will be included in the next version bump. This PR includes changesets to release 86 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 Description
Fixes two related issues with focus restoration when the date picker is dismissed by interacting outside:
Inconsistent focus restore across cycles.
onInteractOutsiderecordsrestoreFocusinto a batched bindable, but the close transition'sshouldRestoreFocusguard reads it synchronously during the same dismissal — one cycle stale, and never cleared. The first outside-click dismissal after mount never restores focus; every later dismissal applies the previous cycle's decision.No way to opt out. The restore decision lives in private context. The popover machine already exposes
restoreFocusas a public prop; the date picker exposes nothing — so consumers whose forms validate on blur can't make an outside click actually blur the field (the restored focus keeps the widget focused, and blur-driven validation doesn't fire until a second click).⛳️ Current behavior (updates)
Every dismissal uses the previous dismissal's
!event.detail.focusablevalue, and the very first uses the default. Reproduced in the new e2e test, which fails onmainat cycle 1.🚀 New behavior
restoreFocusmoves from bindable context torefs— it drives no rendering, and refs read synchronously, so the guard sees the value written during the same dismissal. The flag also resets when the picker opens, so cycles can't leak into each other.restoreFocus?: booleanprop (same name and shape as the popover machine's existing prop): unset preserves the current heuristic (restore only when the outside target is not focusable);falsekeeps focus where the user clicked;truealways restores. Keyboard dismissal is unaffected — it still always restores focus via the separatesetRestoreFocusaction.Prior art for the opt-out: the native popover's light dismiss never restores focus (the hide-popover algorithm runs with
focusPreviousElementfalse), and Radix's non-modal popover skips the trigger refocus when an outside interaction caused the close. Form fields that validate on blur need the dismissal click to actually blur the field.💣 Is this a breaking change (Yes/No):
No. The default heuristic and public API defaults are unchanged. One behavior note (it is the bug fix): the first dismissal after mount now restores focus like every later one — previously it never did. Called out in the changeset.
📝 Additional Information
mainwhere cycle 1 and cycle 2 diverge) and arestoreFocus={false}test against a newdate-picker/restore-focusexample page.date-pickere2e suite: 38 passed, 3 flaky-on-retry — the same pre-existing typed-input timing flakes noted in fix(date-input): commit deferred entry on segment leave #3210's test report.tsc --noEmitandeslint srcclean for the package.restoreFocusOnInteractOutside) if you'd rather the name carry the scoping instead of the JSDoc.