import type { Morfo } from '../types'; import { v } from '../types'; /** * MenuDial — a "speed dial": a FAB that fans out a LINEAR set of action buttons * (each a mini-FAB with a label). The exact feature `fabMorfo` defers to here. * Flat (no nesting) — for a few quick actions; the radial / nested case is * OnionMenu's. By APG it's a Menu Button: the trigger (`aria-haspopup="menu"`) * opens a `role="menu"` list of `menuitem` actions. * * scope: ['soma','sema','eidos'] — soma owns open + the linear keyboard nav, * focus and dismiss; eidos composes `` (trigger) + `IconButton` (actions) * and lays the fan out by `direction` (no SVG — plain flex); sema picks up * open/close from `emerge` and commit-select from `commit` (family defaults). */ export const menuDialMorfo = { name: 'MenuDial', kebab: 'menu-dial', scope: ['soma', 'sema', 'eidos'], expression: 'family-default', apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/', texts: { label: '#?components.menu-dial.label|Quick actions' }, focus: { initial: 'first-focusable', // Non-modal: arrows move along the fan, but Tab / outside-click exit and // close. The trigger gets focus back on close. trap: false, return: 'trigger', restore: true }, events: [ { name: 'open', semantic: { family: 'emerge', verb: 'open', target: v.partRef('list'), sequence: 'pre' }, commits: { part: v.partRef('list'), attr: 'data-state', value: 'open' } }, { name: 'close', semantic: { family: 'emerge', verb: 'close', target: v.partRef('list'), sequence: 'pre' }, commits: { part: v.partRef('list'), attr: 'data-state', value: 'closed' } }, { name: 'commit-select', semantic: { family: 'commit', verb: 'select', target: v.partRef('action'), intent: 'affirm', sequence: 'post' } } ], parts: [ { name: 'Provider', kebab: 'provider', archetype: 'provider', kind: 'virtual', defaultElement: 'none', optional: false, data: [], aria: [] }, { // The trigger IS a ``; the soma/eidos stamp this contract onto it. name: 'Trigger', kebab: 'trigger', archetype: 'trigger', kind: 'public', defaultElement: 'button', role: 'button', optional: false, states: ['open', 'closed'], data: [{ attr: 'data-state', values: ['open', 'closed'] }], aria: [ { attr: 'aria-haspopup', value: v.literal('menu') }, { attr: 'aria-expanded', value: v.stateRef('open') }, { attr: 'aria-controls', value: v.partRef('list'), condition: { when: 'part-present', part: 'list' }, severity: 'recommended' } ], keyboard: [ { key: 'Enter', action: 'open' }, { key: ' ', action: 'open' }, { key: 'ArrowUp', action: 'open' }, { key: 'ArrowDown', action: 'open' }, { key: 'ArrowLeft', action: 'open' }, { key: 'ArrowRight', action: 'open' } ] }, { // The fan container (role="menu"). `data-direction` drives the layout. name: 'List', kebab: 'list', archetype: 'content', kind: 'public', defaultElement: 'div', role: 'menu', optional: false, states: ['open', 'closed'], data: [{ attr: 'data-state', values: ['open', 'closed'] }], aria: [ { attr: 'aria-labelledby', value: v.partRef('trigger'), condition: { when: 'part-present', part: 'trigger' }, severity: 'recommended' } ], keyboard: [ { key: 'Escape', action: 'close' }, { key: 'ArrowDown', action: 'next-item' }, { key: 'ArrowRight', action: 'next-item' }, { key: 'ArrowUp', action: 'prev-item' }, { key: 'ArrowLeft', action: 'prev-item' }, { key: 'Home', action: 'first-item' }, { key: 'End', action: 'last-item' }, { key: 'Enter', action: 'activate' }, { key: ' ', action: 'activate' } ] }, { // One action = one mini-FAB (IconButton). `data-disabled` when disabled. name: 'Action', kebab: 'action', archetype: 'item', kind: 'public', defaultElement: 'button', role: 'menuitem', optional: false, states: [], data: [{ attr: 'data-disabled', severity: 'optional' }], aria: [ { attr: 'aria-disabled', value: v.literal('true'), severity: 'optional', condition: { when: 'prop-truthy', prop: 'disabled' } } ] } ] } as const satisfies Morfo;