Skip to content

Commit 77b0db9

Browse files
committed
fix(emcn): stop the combobox cursor diverging from what Enter commits
The option rows painted --surface-active from CSS :hover as well as from isHighlighted. CSS :hover tracks the pointer continuously while highlightedIndex only advances on mouseenter, so once the list scrolled under a stationary pointer the row that looked selected was not the one Enter would commit — Enter reads filteredOptions[highlightedIndex]. isHighlighted is now the single source of truth for the cursor, so paint and commit cannot disagree. The row under a stationary pointer may lag a scroll until the mouse moves, but it lags in agreement with what Enter will do, which is the invariant worth keeping. Disabled options also stop painting on hover, matching the mouseenter guard that already refused to highlight them. The 'All' row keeps its own hover: it clears the highlight rather than taking it, so it has no isHighlighted paint to fall back on.
1 parent 252045b commit 77b0db9

1 file changed

Lines changed: 11 additions & 10 deletions

File tree

packages/emcn/src/components/combobox/combobox.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,6 @@ export interface ComboboxProps
151151
emptyMessage?: string
152152
}
153153

154-
/**
155-
* Option cursor, not a hover affordance: it matches the keyboard highlight, so it
156-
* keeps `--surface-active` rather than the dimmer `chipHoverSurfaceClass` rows
157-
* use. Kept alongside `isHighlighted` because `onMouseEnter` does not re-fire
158-
* when the list scrolls under a stationary pointer.
159-
*/
160-
const OPTION_CURSOR_CLASS = 'hover-hover:bg-[var(--surface-active)]'
161-
162154
/**
163155
* Minimal combobox component matching the input and textarea styling.
164156
* Provides a dropdown selection interface with keyboard navigation support.
@@ -850,7 +842,16 @@ const Combobox = memo(
850842
className={cn(
851843
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-1.5 font-sans',
852844
size === 'sm' ? 'py-[5px] text-caption' : 'py-1.5 text-sm',
853-
OPTION_CURSOR_CLASS,
845+
/*
846+
No CSS `:hover` here — `isHighlighted` is the
847+
single source of truth for the cursor, because
848+
it is also what Enter commits. A `:hover` class
849+
tracks the pointer continuously while
850+
`highlightedIndex` only moves on `mouseenter`,
851+
so after the list scrolls under a stationary
852+
pointer the two disagree and the row that looks
853+
selected is not the one Enter would choose.
854+
*/
854855
(isHighlighted || isSelected) && chipActiveSurfaceClass,
855856
option.disabled && 'cursor-not-allowed opacity-50'
856857
)}
@@ -926,7 +927,7 @@ const Combobox = memo(
926927
className={cn(
927928
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-1.5 font-sans',
928929
size === 'sm' ? 'py-[5px] text-caption' : 'py-1.5 text-sm',
929-
OPTION_CURSOR_CLASS,
930+
// See above: `isHighlighted` alone, so paint matches what Enter commits.
930931
(isHighlighted || isSelected) && chipActiveSurfaceClass,
931932
option.disabled && 'cursor-not-allowed opacity-50'
932933
)}

0 commit comments

Comments
 (0)