Skip to content

feat(naked_ui): let NakedMenu use the caller's control as its trigger - #335

Closed
leoafarias wants to merge 3 commits into
fix/naked-ui-reduced-motionfrom
feat/naked-ui-menu-open-on-tap
Closed

leoafarias wants to merge 3 commits into
fix/naked-ui-reduced-motionfrom
feat/naked-ui-menu-open-on-tap

Conversation

@leoafarias

@leoafarias leoafarias commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Adds NakedMenu.openOnTap (default true). NakedMenu always wrapped its trigger in its own NakedButton, so a real button (a design-system Button) as the trigger produced nested buttons: two focus stops, duplicate button semantics, and the inner tap winning the gesture arena.

With openOnTap: false the menu draws no trigger button:

  • The child/builder is the caller's own control: no NakedButton, no menu-owned tap or keyboard activation, no focus stop.
  • The caller opens and closes through controller and attaches triggerFocusNode to the control; focus returns to that node when the menu closes. The node goes only to RawMenuAnchor.childFocusNode, with no wrapper Focus.
  • The menu merges the control's semantics into one node that carries the expanded state (MergeSemantics(Semantics(expanded:))), which works for NakedButton-based controls, Material buttons, and controls wrapped in Semantics(container: true) or MergeSemantics; a NakedTooltip around the control stays attached to the semantics tree. Callers do not need their own MergeSemantics.
  • semanticLabel must be null in this mode (asserted); label the control.
  • The mode asserts for a direct item of a NakedMenubar; a menu inside a bar menu's dropdown panel is not affected.

It mirrors NakedPopover.openOnTap; the default is today's behavior. This is the Naked prerequisite for the stacked Remix PR adding RemixMenuTrigger.control.

Adversarial and simplification review — 2026-10-28

A menu nested in a menubar panel incorrectly inherited direct bar membership: focusing a caller-owned nested button opened it and contaminated bar traversal. Resolve actual direct membership once and reuse for activation, registration, semantics and assertion. Two regressions fail before; 58 menu/menubar tests pass corrected. No new API.

Independent review found no additional actionable issues in this delta. The strict combined gate on exact 673409ab2 passes all 8 CI legs plus bootstrap and registry build/drift, exits 0 with zero failed steps, and leaves the initial/final commit and clean tree unchanged (Flutter 3.44.0 pinned through child-tool PATH). Full-suite counts: naked_ui 1126 (3 existing skips), remix 3141, registry_source 2430; Vanilla/Fortal/HeroUI/Lucide fresh installs all pass, including the 17-test HeroUI fixture. These counts belong to the corrected combined tree; earlier counts below are historical owning-branch evidence. GitHub Checks separately validate this PR's own pushed head. Release/promotion requirements are unchanged.

Earlier validation (before the 2026-10-28 review)

  • New tests in naked_menu_test.dart (control mode, focus return on Escape, item selection, outside tap, and controller.close(), semanticLabel and menubar asserts), naked_menu_semantics_test.dart (one button node with expanded false/true for TextButton, Semantics(container:), and MergeSemantics controls), and update_tree/naked_menu_semantics_update_test.dart (tooltip-wrapped controls with the fix(naked_ui): the select trigger's merged node is the traversal parent, not an inner container #261 detached-node spy). The semantics, assert, tooltip, and menubar-panel tests fail when their fix is reverted.
  • naked_ui suite (972 passed, 3 skipped), remix menu tests (204), root dart format, melos run ci:checks; dart analyze no new issues.

Related Issues

Prerequisite for the Remix menu control trigger (stacked PR).


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.

@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 ✅ Completed 2026-10-29T21:39:57.507985Z cab015d 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d18e1b75c8

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Add NakedMenu.openOnTap (default true). When false the menu draws no
NakedButton and adds no tap, keyboard activation, or focus stop: the child
or builder is the caller's own control, and opening and closing go through
the controller. triggerFocusNode is passed only to RawMenuAnchor
.childFocusNode, and Semantics(expanded:) still merges onto the control's
node. Using it inside a NakedMenubar asserts.
…enOnTap guards

With openOnTap false, merge the caller's control into one semantics node
that carries the expanded state, so it lands on the button for Material
buttons and for controls that are semantics boundaries, and so a
NakedTooltip around the control stays attached to the tree.

Assert that semanticLabel is null when openOnTap is false, and limit the
menubar assertion to direct menubar items instead of every menu below the
bar's scope. Correct the focus docs: focus returns to triggerFocusNode when
the menu closes. Add focus-return tests for item selection, outside tap,
and controller.close().
@leoafarias
leoafarias changed the base branch from main to fix/naked-ui-reduced-motion October 29, 2026 21:38
@leoafarias
leoafarias force-pushed the feat/naked-ui-menu-open-on-tap branch from e2af2f0 to cab015d Compare October 29, 2026 21:38

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #354, which merged on Oct 30, 2026.

@leoafarias leoafarias closed this Oct 30, 2026
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