Three related fixes touching the menu surface:
1. **Remove `modal` from DropdownMenu** (#127). The prop was declared in
soma but never wired to anything — no overlay, no pointer-events
blocker, no scrim. ark-ui / Headless-UI / Chakra all treat menu as
non-modal by convention; Dialog/Drawer/AlertDialog cover blocking
semantics. Drop it from soma (types, provider opts, component, test
fixture), the eidos doc comment, the menubar consumer, and the
dropdown-menu demo (state, control, snippet, API table, parity table).
2. **DropdownMenu.Trigger now renders `<Button>` internally** (#120
first consumer). Default variant='surface', size='md', intent='neutral'.
`variant` is narrowed to ControlVariant (`'surface' | 'outline' | 'ghost'`)
per DEMO_AUTHORING_GUIDE §12.7 so the trigger stays consistent with
the form-control surface (Field/Select/Combobox) instead of inheriting
Button's wider 6-variant set. The eidos demo exposes variant/size/intent
chips that match the canonical narrowed vocab (Intent: neutral · affirm
· fulfill · risk · threat · loss). Trigger's per-recipe CSS dropped —
chrome comes from button.css.
3. **Menu-item unchecked indicators** (follow-up to #118). CheckboxItem
and RadioItem now paint an empty outlined box / circle even when
unchecked, so the user can tell at a glance which rows are checkable.
Checked state fills with the accent color via the same `::before`. The
reserved padding slot (26px) keeps row text from shifting when state
flips.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ -182,7 +182,7 @@ No standalone functions (`createApp`, `getApp`, `useApp`). No `from()`. The `ctx
- If a handler needs a context reference, capture it in the constructor.
- If a handler needs a context reference, capture it in the constructor.
- Event handlers (onclick, onkeydown) must be included in `props`. Defining them as class methods without spreading them into props means they won't reach the DOM.
- Event handlers (onclick, onkeydown) must be included in `props`. Defining them as class methods without spreading them into props means they won't reach the DOM.
- Layers (Presence, FocusScope, Dismissal, Gesture, etc.) are instantiated in the constructor and their `.props` are spread into the component's `props`.
- Layers (Presence, FocusScope, Dismissal, Gesture, etc.) are instantiated in the constructor and their `.props` are spread into the component's `props`.
- For DropdownMenu: `interactOutsideBehavior` defaults to `'close'` (not inherited from `modal` like Dialog).
- For DropdownMenu / ContextMenu: `interactOutsideBehavior` defaults to `'close'`. Menus are intentionally non-modal — for blocking semantics use Dialog/Drawer/AlertDialog.
<tr><tdclass="name">open <spandata-uix-tagdata-kind="bindable">bindable</span></td><tdclass="type">boolean</td><tdclass="default">false</td><td>Whether the menu is open.</td></tr>
<tr><tdclass="name">open <spandata-uix-tagdata-kind="bindable">bindable</span></td><tdclass="type">boolean</td><tdclass="default">false</td><td>Whether the menu is open.</td></tr>
<tr><tdclass="name">onOpenChange / onOpenChangeComplete</td><tdclass="type">(open: boolean) => void</td><tdclass="default empty">—</td><td>Fire on every open/close and after the animation settles.</td></tr>
<tr><tdclass="name">onOpenChange / onOpenChangeComplete</td><tdclass="type">(open: boolean) => void</td><tdclass="default empty">—</td><td>Fire on every open/close and after the animation settles.</td></tr>
<tr><tdclass="name">modal</td><tdclass="type">boolean</td><tdclass="default">true</td><td>Block outside interaction while open.</td></tr>
<tr><tdclass="name">dir</td><tdclass="type">'ltr' | 'rtl'</td><tdclass="default">'ltr'</td><td>Direction used for keyboard navigation inside sub-menus.</td></tr>
<tr><tdclass="name">dir</td><tdclass="type">'ltr' | 'rtl'</td><tdclass="default">'ltr'</td><td>Direction used for keyboard navigation inside sub-menus.</td></tr>
<tr><tdclass="name">accessibleWhenDisabled</td><tdclass="type">boolean</td><tdclass="default">true</td><td>Disabled items remain focusable per WAI-ARIA APG; set false to skip them entirely.</td></tr>
<tr><tdclass="name">accessibleWhenDisabled</td><tdclass="type">boolean</td><tdclass="default">true</td><td>Disabled items remain focusable per WAI-ARIA APG; set false to skip them entirely.</td></tr>
<tr><tdclass="name">…native button attrs</td><tdclass="type">PrimitiveButtonAttributes</td><tdclass="default empty">—</td><td>Trigger renders a native <code><button></code>; all HTML button attrs flow through.</td></tr>
<tr><tdclass="name">variant <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">ControlVariant: 'surface' | 'outline' | 'ghost'</td><tdclass="default">'surface'</td><td>Forwarded to the inner <code><Button></code>. Narrowed from ButtonVariant (per DEMO_AUTHORING_GUIDE §12.7) to stay consistent with form-control surface.</td></tr>
<tr><tdclass="name">color <spandata-uix-tagdata-kind="eidos">eidos</span></td><tdclass="type">ColorRole — see eidos/lib/types.ts</td><tdclass="default empty">—</td><td>Hierarchical color override (only applies under <code>intent='neutral'</code>).</td></tr>
<tr><tdclass="name">child</td><tdclass="type">{`Snippet<[{ props }]>`}</td><tdclass="default empty">—</td><td>Opt out of <code><Button></code> entirely and render your own element.</td></tr>
<tr><tdclass="name">…native button attrs</td><tdclass="type">PrimitiveButtonAttributes</td><tdclass="default empty">—</td><td>All HTML button attrs flow through to the underlying button.</td></tr>