Skip to content

Fix listbox Home/End/PageUp/PageDown across Select, MultiSelect, and AutocompleteInput (#380) - #436

Merged
czarandy merged 1 commit into
mainfrom
worktree-issue-380-listbox-keyboard-jumps
Aug 5, 2026
Merged

Fix listbox Home/End/PageUp/PageDown across Select, MultiSelect, and AutocompleteInput (#380)#436
czarandy merged 1 commit into
mainfrom
worktree-issue-380-listbox-keyboard-jumps

Conversation

@czarandy

@czarandy czarandy commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Fixes #380.

The shared piece

Select/MultiSelect (via useListboxNavigation) and AutocompleteInput/TagsInput (via BaseAutocompleteInput) each had their own combobox key handling, with different bugs. This adds src/internal/listboxKeyboard.ts, a small pure module that maps a key event onto the action a listbox should take:

type ListboxKeyAction =
  | {edge: 'first' | 'last'; type: 'jump'}
  | {edge: 'first' | 'last'; type: 'open'}
  | {step: -1 | 1; type: 'move'}
  | {type: 'commit'};

Both handlers now drive off resolveListboxKeyAction and apply the action to their own state model (value-keyed for the selects, index-keyed for autocomplete). preventDefault() stays with the caller so it can decline an action it can't fulfil — Enter with nothing highlighted still belongs to the form.

The three defects

  1. Home/End were swallowed while typing in the popover search field. They fired on isOpen regardless of target, so the caret couldn't reach the start/end of the query. They now reach the listbox only when the event target has no text caret — the same handler is bound to both the search field and the trigger button, so the target decides.
  2. Home/End were no-ops once a value was selected. They went through getInitialHighlight, which returns the selected option before honoring the direction. That helper is right for "open the listbox" and wrong for an explicit jump, which now uses a new getEdgeHighlight.
  3. PageUp/PageDown did nothing. They fell through to the browser, which page-scrolled the popover away from aria-activedescendant. They're now the jump keys that always belong to the listbox, in every combobox.

Net keyboard map: arrows own the listbox, Home/End yield to the text caret when there is one, PageUp/PageDown jump to first/last.

AutocompleteInput

Picks up the jumps as part of the alignment, plus two consistency fixes it inherits from the shared map:

  • ArrowUp on a closed menu opens it, instead of silently moving a highlight nobody can see.
  • Opening with an arrow key seeds the highlight at the matching edge (previously it stayed at -1, so the first arrow press only opened the menu).

Tests

  • Select — Home/End move the search caret and leave aria-activedescendant alone; PageDown jumps to the last filtered option, PageUp back to the first.
  • MultiSelect — with a selection present, ArrowDown opens onto the selection but Home lands on the first option; PageDown/PageUp reach the edges.
  • AutocompleteInput — Home/End keep the caret, PageDown/PageUp jump the highlight, and Enter commits the jumped-to result.

Each fails on main. Full suite: 3177 passing.

https://claude.ai/code/session_01XFkkvUQfynGRedEYDNxKoY

…put (#380)

The listbox key handling lived in two places with different bugs. Pull the
decision layer into `internal/listboxKeyboard`, which maps a key event onto
an open/move/jump/commit action and leaves `preventDefault()` to the caller.
`useListboxNavigation` and `BaseAutocompleteInput` both drive off it now, so
Select, MultiSelect, TagsInput, and AutocompleteInput answer to the same
keys.

That resolves three defects in the listbox map:

- Home/End no longer fire while the caret is in a text field, so the search
  input inside the Select/MultiSelect popover gets them back for caret
  movement. The target decides, since the same handler is bound to both the
  search field and the trigger button.
- Home/End go to the actual first/last option instead of the selection.
  `getInitialHighlight` still prefers the selection, but only for opening
  the listbox with an arrow key -- an explicit jump now uses the new
  `getEdgeHighlight`.
- PageUp/PageDown jump the highlight to the first/last option. They
  previously fell through to the browser, which page-scrolled the popover
  away from `aria-activedescendant`.

AutocompleteInput picks up the jumps as part of the alignment, plus two
smaller consistency fixes it inherits from the shared map: ArrowUp on a
closed menu opens it rather than silently moving a hidden highlight, and
opening with an arrow key seeds the highlight at the matching edge.
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
silver-ui-site Ready Ready Preview Aug 5, 2026 11:05pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
silver-ui Ignored Ignored Preview Aug 5, 2026 11:05pm

@czarandy
czarandy merged commit 7e002d2 into main Aug 5, 2026
4 checks passed
@czarandy
czarandy deleted the worktree-issue-380-listbox-keyboard-jumps branch August 5, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Select/MultiSelect search: Home/End are stolen from the text caret, land on the selection, and there's no PageUp/PageDown

1 participant