fix(popover): single hover-follows highlight without stealing input focus (UAT)

Second UAT finding on the tag popover: the keyboard-active row stayed put while
a fainter CSS :hover chased the pointer — two competing highlights. Root cause:
floating-ui's focusItemOnHover is (correctly) OFF for input-hosting menus because
setting its activeIndex on hover drags DOM focus onto the item and blurs the
create-tag input mid-typing; the only pointer feedback left was the item's own
`hover:bg-*`.

Fix, applied in the shared PopoverItem so it's general:
- Drive the visible highlight off a NEW `hoverIndex` state (highlightIndex =
  hoverIndex ?? activeIndex), decoupled from floating-ui's activeIndex — hovering
  sets only hoverIndex, never DOM focus, so one highlight follows the pointer AND
  the input keeps its caret. Keyboard nav (onNavigate/onMatch) clears hoverIndex
  so it takes the highlight back; leaving the menu clears it too.
- Split PopoverItem's `active` into focusActive (roving tabindex, keyboard) vs
  visualActive (data-active highlight).
- Remove the now-redundant `hover:bg-*` from the 5 consumers so data-active is the
  sole highlight (it was also a same-specificity tie with data-active).

Verified in-browser: hovering moves the single highlight; typing "testtag" in the
create field then hovering items keeps focus+caret on the input (caretPos intact);
mouse-leave returns the highlight to the keyboard row. Gate: tsc/eslint/prettier/
vitest 79 green.
This commit is contained in:
2026-08-08 01:38:06 +02:00
parent 22e0d286c0
commit f8e0ab1520
6 changed files with 55 additions and 14 deletions
+1 -1
View File
@@ -124,7 +124,7 @@ export default function AddToPlaylist({
// the floating element only). `toggle()` already no-ops while busy, so re-entrancy is
// guarded without touching focus; `aria-busy` on the list conveys the pending state.
label={playlistName(pl, t)}
className="w-full flex items-center gap-2 text-sm px-1.5 py-1.5 rounded-lg text-fg outline-hidden transition hover:bg-card/60"
className="w-full flex items-center gap-2 text-sm px-1.5 py-1.5 rounded-lg text-fg outline-hidden transition"
>
<span
className={`grid place-items-center w-4 h-4 rounded border ${