Support the :focus-within pseudo-class - #615
Open
maurerdietmar wants to merge 3 commits into
Open
Conversation
maurerdietmar
force-pushed
the
focus-within
branch
from
August 7, 2026 09:37
4a767ec to
37f716a
Compare
nicoburns
requested changes
Aug 7, 2026
nicoburns
left a comment
Member
There was a problem hiding this comment.
This needs to use regular ancestors (node.parent) rather than layout ancestors (node.layout_parent) as focus pertains to the DOM elements tree rather than the layout tree.
This also needs to handle updating :focus-within when an element is moved within the tree.
maurerdietmar
force-pushed
the
focus-within
branch
from
August 7, 2026 15:01
37f716a to
76c17eb
Compare
Contributor
Author
|
Thanks, both points are addressed in the force-push (76c17eb). |
maurerdietmar
force-pushed
the
focus-within
branch
from
August 10, 2026 05:57
76c17eb to
6846006
Compare
Moving the focus changes what is on screen, because the focus is styled, but neither set_focus_to nor clear_focus asked for a frame, and a keyboard focus change touches neither layout nor content, so nothing else did either. The focus ring stayed on the element that had it until an unrelated event brought a redraw along: tabbing through a group of controls appeared to do nothing at all, and the cell cursor of a data grid sat where it started while the arrow keys moved the focus behind it. Focus changes driven by a mutation, autofocus in particular, are unaffected, since the mutator asks for the frame. Hover changes already request a frame the same way. Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
compare_document_order builds root-first DOM ancestor chains with a private helper. Interaction state updates need the same walk, so give it a public name next to node_layout_ancestors, an Option-taking variant, and tolerance for freed slots via get_node, matching nearest_surviving_element_ancestor. Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Selector matching for :focus-within was stubbed to false and no element state was maintained for it, so styles keyed on it (a common pattern for form field focus rings, where the visible border lives on a wrapper around the input) never applied. Track ElementState::FOCUS_WITHIN on the focused element and all of its DOM ancestors, using the same ancestor-path diffing as hover so a focus move only restyles the non-shared parts of the two chains. The DOM chain rather than the layout chain, because focus pertains to the DOM tree: the layout chain skips display: contents ancestors, and its links do not exist before the first layout pass, which is when autofocus fires. The specification defines the containment over the flat tree; without a shadow DOM implementation to flatten, that is the DOM tree. The state follows tree changes around the focused node: it is cleared on refocus, clear_focus and removal, and transferred from the old ancestor chain to the new one when a subtree containing the focus is moved to a different parent, including out of the document. Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
maurerdietmar
force-pushed
the
focus-within
branch
from
August 10, 2026 06:57
6846006 to
1d19f18
Compare
Contributor
Author
|
The series grew a commit in front: moving the focus did not request a frame, which is also fixed now. |
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.
Selector matching for :focus-within was stubbed to false and no element state was maintained for it, so styles keyed on it never applied - a common pattern for form field focus rings, where the visible border lives on a wrapper around the input.
Track ElementState::FOCUS_WITHIN on the focused element and all of its DOM ancestors. The chain comes from a new BaseDocument::node_ancestors helper next to node_layout_ancestors. The layout chain is not usable here because focus pertains to the DOM tree: it skips display: contents ancestors, and its links do not exist before the first layout pass, which is when autofocus fires. compare_document_order already did the same walk through a private helper, which is now that public one. The specification defines the containment over the flat tree; without a shadow DOM implementation to flatten, that is the DOM tree.
The old and new chains are diffed the way hover's are, so a focus move only restyles the parts they do not share. The state follows tree changes around the focused node: it is cleared on refocus, clear_focus and removal, and transferred from the old ancestor chain to the new one in DocumentMutator::add_children_to_parent when a subtree containing the focus moves to a different parent - including a move out of the document, where the focus is cleared with it and only the old chain is unset.
The first commit is a pre-existing gap this feature runs into rather than causes: set_focus_to and clear_focus dirty selector state without asking for a frame, so a keyboard focus change - which touches neither layout nor content - leaves the old focus styling on screen until an unrelated event brings a redraw along. Focus changes driven by a mutation, autofocus in particular, ride along on the mutator's redraw. Happy to split it into its own PR if you would rather keep this one to the pseudo-class.
Tests: focus_within.rs covers ancestors and blur, a display: contents ancestor, focus set before the first layout pass, removal of the subtree holding the focus, a move within the document, and a move out of it. focus_redraw.rs counts redraw requests through a recording shell for focus, blur, a repeated focus and a Tab keypress.