Skip to content

fix(naked_ui): open a NakedSelect on its selected option - #349

Open
leoafarias wants to merge 6 commits into
fix/naked-ui-overlay-boundary-scrollfrom
fix/naked-ui-select-focus-selected
Open

leoafarias wants to merge 6 commits into
fix/naked-ui-overlay-boundary-scrollfrom
fix/naked-ui-select-focus-selected

Conversation

@leoafarias

@leoafarias leoafarias commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Description

NakedSelect opened with focus on the overlay container. A selected option below the fold stayed hidden, and the first Arrow Down went to the first option instead of the one after the selection.

  • Selected option: the select now opens with focus on the selected option, scrolled just far enough into view with requestFocusInView from fix(naked_ui): scroll keyboard focus targets into view in overlays, tabs, toggle groups, and menubars #344. Escape still returns focus to the trigger.
  • Fallback: with no selection, a disabled selected option, or a selected option that a lazily built list has not built, it opens on the first item that can take focus, normally the first enabled option. The WAI-ARIA listbox pattern, Radix Select, and Material's DropdownButton start there too.
  • Existing focus: a field in the panel that requests focus while the panel builds keeps it. A TextField(autofocus: true) applies its autofocus later and loses to the initial focus.
  • Pointer: items and submenu rows clear keyboard focus, and a submenu opens after its delay, only when the pointer moves over them, not when a list scrolls under a resting pointer. Before, the first Arrow Down after the open reveal could drop focus back to the container. This applies to NakedSelect and NakedMenu.
  • NakedSelectOption values must be unique within a select; this is now documented.

The internal AnchoredOverlay gets an initialFocusNode. NakedMenu and the other overlays do not pass one and keep today's behavior.

Validation

  • New tests in naked_select_test.dart cover:

    • the selected option, including one below the fold;
    • the first-option fallbacks (no selection, disabled selection, lazy list);
    • a self-focusing search field;
    • Escape, reopening, and a value change while open;
    • a resting pointer, for select options, menu items, and submenu rows;
    • a page that holds the overlay, which does not scroll.

    RemixSelect gets tests for the selected item and for the first item without a selection.

  • All new and updated tests fail without the change, except two guards that pin the pointer behavior that must not change. Mutations of each guard in the code fail the intended test.

  • Full naked_ui suite: 1001 passed. Full remix suite: 2833 passed. dart analyze, format, and melos run ci:checks: clean.

Merge note

Merge after #344. Tests elsewhere in the stack assume a select opens on its container:

  • feat(registry): add the HeroUI preset #324 (HeroUI), which lands on main first: after rebasing this PR, delete the Arrow Down and pump after opening in registry_source/test/heroui/select_test.dart "keyboard focus rings an item without moving it". The select now opens on the first item, which matches HeroUI's own keyboard-open state.
  • feat(remix): add containerEffects to focusable parts #331: in packages/remix/test/rendering/remix_box_effects_slots_test.dart, measure Beta rather than Alpha in the SelectMenuItem effects case. Alpha is already focused on open; ArrowDown focuses Beta. This preserves the idle/focused pixel and full-layout assertions (27 effects tests pass on integration).
  • fix(remix): scroll a long RemixSelect list #341: it carries the matching note for its Long lists press counts.

The integration branch (#339) shows these changes.

Related Issues

Stacked on #344; merge after it. Belongs in naked_ui 1.3.0.


Checklist

  • My PR includes unit or integration tests for all changed/updated/fixed behaviors.
  • I have updated or added relevant documentation (doc comments with ///).
  • I am prepared to follow up on review comments in a timely manner.

Breaking Change

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

No API change. A select without a value now opens on its first option, so the first Arrow Down reaches the second option and Enter selects the first. Before, focus started on the overlay container.

NakedSelect opened with focus on the overlay container, so a selected option
below the fold stayed hidden and the first Arrow Down went to the first option
instead of the one after the selection. AnchoredOverlay takes an optional
initialFocus node; NakedSelect hands its selected option that node, and once
the overlay is built focus moves to the option and it is revealed through
requestFocusInView. A select with no selected option, a disabled one, or one a
lazily built list has not built still opens on the container.
… keep panel focus

A select without a selected option, or whose selected option is disabled or not
built yet in a lazy list, opened on the overlay container, so its first Arrow
Down reached the first option and Enter did nothing. WAI-ARIA's listbox pattern,
Radix Select, and Material's DropdownButton start on the first enabled option.
AnchoredOverlay now falls back to the first focusable item through the traversal
policy, as Home does, and reveals it.

The container's autofocus is back, and the initial focus request leaves focus
alone when something else in the panel already holds it, so a search field that
focuses itself in initState is not overridden. AnchoredOverlay.initialFocus is
now initialFocusNode, as in the dialog. NakedSelectOption values must be unique:
equal values share the selected option's focus node.

The RemixSelect test compares the focused option's value; checking for the
option's text also matched while the container held focus.
…inter

Items cleared keyboard focus on hover enter, which also fires when a list
scrolls under a pointer that has not moved. After the select revealed its
selected option, the first Arrow Down scrolled the list and dropped focus to the
container. Items now clear keyboard focus on pointer hover, which only arrives
when the pointer moves, and a disabled item ignores it as before. NakedMenu
shares the base and keeps clearing focus when the pointer moves onto an item.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-27T22:29:05.836297Z b63228b New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

A submenu row cleared keyboard focus and scheduled its hover open from hover
enter, which also fires when a list scrolls under a pointer that has not moved.
Arrowing past a submenu row under a resting mouse sent focus to the container
and opened the submenu after its hover delay. Hover enter still sets the hovered
flag and cancels ancestor timers; clearing item focus and scheduling the open
now wait for a hover that moves the pointer, and a disabled row ignores it.
The select Home and End test now presses Arrow Down once, so it starts away from
both edges instead of on the last option. The search-field case without a
selection puts the field after the options, so a request that takes focus from
it fails the test. Stale first-option comments are fixed.

The docs say a field that requests focus while the panel builds keeps it, while
a TextField with autofocus requests it after the build and loses to the initial
focus, and describe the fallback as the first item that can take focus, as Home
does. The reveal is documented as scrolling the scrollables that contain the
item, and the hover comment says the delta check guards synthetic hovers.
leoafarias added a commit that referenced this pull request Oct 27, 2026
…stack

# Conflicts:
#	packages/naked_ui/CHANGELOG.md

This branch has not been deployed

No deployments
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.

1 participant