Skip to content

VSelect.tsx still checks raw document.activeElement, missed by the #23024/#23027 Shadow DOM focus fixes #23101

Description

@MILLERMARRU

I ran into this while comparing #23024 and #23027 (the two PRs that replaced document.activeElement with the new getActiveElement() helper across VDialog, VField, VFileInput, VTextField, VTextarea, useActivator, hotkey and VCommandPalette) against the rest of the codebase.

VSelect.tsx's onAfterEnter still has the old check:

// VSelect.tsx
function onAfterEnter () {
  if (props.eager) {
    vVirtualScrollRef.value?.calculateVisibleItems()
  }
  if (!listRef.value || !isFocused.value) return

  // VMenu re-dispatches ArrowUp/Down after open and already moved focus to next/prev
  if (listRef.value.$el?.contains(document.activeElement)) return
  ...

Inside a shadow root, document.activeElement only ever resolves to the shadow host, not the actual focused element inside the shadow tree, same mechanism #23027 fixed for VDialog/VOverlay/VTextField etc. So this guard reports false even when focus is legitimately inside the list, and the fallback focus-move logic right below it fires when it shouldn't, causing the same kind of focus fight the other components were just fixed for, specifically for VSelect (and by extension VAutocomplete/VCombobox, which build on it).

I grepped the repo for remaining document.activeElement usage after those two PRs merged (document.activeElement repo:vuetifyjs/vuetify). This is the only production source hit besides useFocusRepair.ts, which #23027's own description already calls out as not needing a change, and I checked that one too: it compares against document.body, not a shadow-scoped container, so it's genuinely fine as-is. VSelect.tsx is the one that was missed.

Repro: render VSelect inside a defineCustomElement-wrapped shadow root (the harness from #23027's own repro page works for this), open the menu, use arrow keys to move focus into the list, and watch the fallback focus-move branch fire even though focus is already correctly inside the list.

Fix should be the same one-line swap as the other components: getActiveElement() (from @/util) instead of document.activeElement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions