Skip to content

feat(utilities): shadow-DOM-aware getActiveElement for focus checks - #693

Open
johnleider wants to merge 1 commit into
devfrom
fix/shadow-aware-active-element
Open

feat(utilities): shadow-DOM-aware getActiveElement for focus checks#693
johnleider wants to merge 1 commit into
devfrom
fix/shadow-aware-active-element

Conversation

@johnleider

Copy link
Copy Markdown
Member

Hardens v0 for the open-Shadow-DOM / web-component case, prompted by the v3/v4 VMenu keyboard-nav bug (vuetifyjs/vuetify#23024).

Context

document.activeElement returns the shadow host, not the focused descendant, inside an open shadow root — which silently breaks focus logic. In v4 this broke VList/VMenu arrow-nav. v0's core nav is already immune (useRovingFocus/useVirtualFocus are registry/state-driven, not document.activeElement-driven; useClickOutside already uses composedPath(); usePopover uses native Popover + CSS anchor-name with no getElementById). This PR closes the remaining peripheral reads.

Change

  • Add getActiveElement() to #v0/utilities (helpers.ts) — walks open shadow roots; same value as document.activeElement in light DOM; SSR-safe (null); open roots only.
  • Use it in the three remaining raw document.activeElement sites: useHotkey (typing guard), useClickOutside (iframe-outside check), useDragDrop keyboard adapter.
  • Surface freeze + maturity.json (utilities, preview) updated for the new export; changeset added (minor).

Not covered

Portal still teleports to: 'body' by default, so overlays escape the shadow root (styles/focus cross the boundary) — mitigable via to = a shadow-internal target; documenting for embedders is a separate follow-up, not a focus bug.

document.activeElement returns the shadow HOST when focus is inside an open
shadow root (custom element), silently breaking focus checks. Add a
shadow-piercing getActiveElement() to #v0/utilities and use it in useHotkey
(typing guard), useClickOutside (iframe-outside check), and useDragDrop's
keyboard adapter. Same value as document.activeElement in light DOM; open
shadow roots only.

v0's core focus nav (useRovingFocus/useVirtualFocus) is registry/state-driven
and was already immune; this closes the remaining peripheral document.activeElement
reads. Prompted by the v3/v4 VMenu shadow-DOM bug (vuetifyjs/vuetify#23024).
@johnleider johnleider added this to the v1.0.x milestone Jul 22, 2026
@johnleider johnleider added the enhancement New feature or request label Jul 22, 2026
@johnleider johnleider self-assigned this Jul 22, 2026
@johnleider johnleider modified the milestones: v1.0.x, v1.1.0 Jul 22, 2026
@johnleider
johnleider changed the base branch from master to dev July 23, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant