feat(dockview-core): L4 focus management#1321
Merged
Merged
Conversation
First Layer-4 focus-management piece. When removing a panel or group pulls focus out of the dock, return it to the neighbour the layout just activated instead of stranding it on <body> (which would silently break keyboard operation until the user clicks back in). The service snapshots whether focus was inside the dock on the 'remove' mutation (focus still on the closing element), and after the teardown — if it was inside and has now been lost — focuses the active group's content. Only acts when focus was actually pulled out, so closing a background tab while focused elsewhere never yanks focus into the dock. Gated on keyboardNavigation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (L4) Maximize hides sibling groups via visibility toggling and leaves the maximized group's DOM in place, so the active panel keeps focus across maximize/restore — the L4 requirement is already met by construction. Add a regression test so a future change that re-renders on maximize can't silently drop focus to <body>. (The niche case of maximizing a *different* group than the focused one can lose focus in a real browser; deferred — jsdom can't reproduce hidden-element blur, and a fix risks stealing focus from mouse users.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When focus is inside a floating group, Esc returns it to the invoking control — the last element focused in the main dock before entering the float (tracked via a focusin observer), falling back to a grid group's content if that element is gone. Never strands focus in a dismissed float. Deliberately polite: the handler runs in the BUBBLE phase and bails on defaultPrevented, so panel content that uses Esc (closing a menu, clearing a field) keeps priority. Scoped to floats inside this dock and gated on keyboardNavigation. Float Tab-containment is a separate, later piece. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tab inside a floating group now wraps within it — at the last tabbable Tab moves to the first, at the first Shift+Tab moves to the last — so keyboard focus doesn't leak into the grid behind the (non-modal) float. Tabbables are the float's naturally-focusable controls plus the active tab's roving anchor; tabindex="-1" plumbing (content containers, inactive tabs) is excluded. Scoped to floats in this dock and gated on keyboardNavigation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…just the boundary The boundary-only trap leaked in a real browser: it fired only when focus was on the exact last/first tabbable, but focus usually sits on the content container (tabindex="-1"), which matched no tabbable — so the default Tab escaped to the grid behind the float. (Unit tests missed it by focusing a known button.) Now Tab inside a float is always handled: drive the cursor through the float's tabbables ourselves (wrapping, and snapping to first/last when focus is on non-tabbable plumbing) and swallow the default so it can never leak out. Adds a regression test that Tabs from the content container. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
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.



Layer-4 focus management for the keyboard accessibility module, building on #1319 / #1320. All gated on
keyboardNavigation.1. Focus restore on close
When a panel/group holding focus is closed, focus would drop to
<body>— outside the dock — silently breaking keyboard operation. The service snapshots focus on the'remove'mutation and, after teardown, returns it to the neighbour the layout just auto-activated. Only acts when focus was actually pulled out.2. Maximize/restore (already met — guarded)
Maximize hides siblings via visibility toggling and leaves the maximized group's DOM in place, so the active panel keeps focus by construction. Regression test guards against a future re-render-on-maximize.
3. Floating
Escreturns focusEscinside a floating group returns focus to the invoking control (last element focused in the main dock, tracked viafocusin), falling back to a grid group. Polite: bubble phase + bails ondefaultPrevented, so panel content keepsEsc.4. Floating Tab-containment
Tab wraps within a floating group (last → first, first → last on Shift+Tab) so focus doesn't leak to the grid behind the non-modal float.
Tests
1096 passing (+13 across the four pieces).
Deferred
Popout cross-window focus is intentionally out of scope here — it needs new infrastructure (a service-accessible popout-window/open event, per-popout-document listener attachment, cross-document gating) and real multi-window verification. Tracked as a dedicated follow-up.
🤖 Generated with Claude Code