|
|
2 months ago | |
|---|---|---|
| .. | ||
| README.md | 2 months ago | |
| index.ts | 2 months ago | |
| menubar-content.svelte | 3 months ago | |
| menubar-context.ts | 5 months ago | |
| menubar-menu.svelte | 5 months ago | |
| menubar-panel.svelte | 2 months ago | |
| menubar-trigger.svelte | 4 months ago | |
| menubar.css | 2 months ago | |
| menubar.svelte | 4 months ago | |
| types.ts | 2 months ago | |
README.md
Menubar (eidos)
Fecha de revisión: 2026-07-10 (dossier S4, re-auditoría 2026-07-07).
Barra de menús de escritorio (File / Edit / View / …): triggers de nivel
superior con roving focus y, por menú, el árbol interno completo de
DropdownMenu reutilizado literalmente. Soma posee valor (qué menú está
abierto), teclado y ARIA; eidos añade size y la receta de la barra.
Contrato headless: soma README ·
Morfo: morfo/components/menubar.ts ·
APG: menubar.
Baseline
Portado del baseline air (la escala de tallas sm|md|lg espeja la de air —
documentado en types: un menubar es chrome de escritorio; xs amontonaría
los triggers y xl no tiene caso). Los cambios estructurales sobre air:
las partes internas por-menú ya no son propias — son re-exports de
DropdownMenu (ver Decisiones).
Superficie
<Menubar bind:value size="md">
<Menubar.Menu value="file">
<Menubar.Trigger>File</Menubar.Trigger>
<Menubar.Content>
<Menubar.Item onSelect={newFile}>New…</Menubar.Item>
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>Export</Menubar.SubTrigger>
<Menubar.SubContent>…</Menubar.SubContent>
</Menubar.Sub>
</Menubar.Content>
</Menubar.Menu>
<Menubar.Menu value="edit">…</Menubar.Menu>
</Menubar>
- Partes propias: root (
role="menubar", horizontal),Menu(scope por menú),Trigger(role="menuitem",data-state/data-highlighted/data-menubar-value),Content(panel de menú) yPanel(superficierole="dialog"para controles de formulario — alternativa a Content). - Partes re-exportadas de DropdownMenu:
Item,Group,GroupHeading,Separator,CheckboxItem,CheckboxGroup,RadioGroup,RadioItem,Sub,SubTrigger,SubContent,Arrow. - Props headless:
value/onValueChange(menú abierto),dir,loop; per-Menu:onOpenChange/onOpenChangeComplete. - Props eidos:
size(sm|md|lg, responsive; cascada a los paneles vía contexto). - Teclado del trigger: Enter/Space/ArrowDown abren; ArrowLeft/Right mueven entre triggers; Home/End extremos; dentro del menú rige el teclado de DropdownMenu.
Comparativa
| Capacidad | UIX | Radix | Bits UI | shadcn | React Aria |
|---|---|---|---|---|---|
| Menubar dedicado | ✓ | ✓ | ✓ | ✓ (Radix) | No (Menu suelto) |
| Roving + hover-open tras engage | ✓ | ✓ | ✓ | ✓ | — |
| Partes por-menú = las del dropdown | ✓ (re-export literal) | ✓ (paridad API) | ✓ | ✓ | — |
| Checkbox/Radio items + submenús | ✓ | ✓ | ✓ | ✓ | — |
loop + RTL |
✓ | ✓ | ✓ | ✓ | — |
| Evento perceptivo en el select | ✓ (commit-select) |
No | No | No | — |
| Superficie panel (role=dialog) por entrada | ✓ (Menubar.Panel) |
No | No | No | No |
Menubar.Panel es una EXTENSIÓN sobre las referencias headless (ninguna lo
modela): los precedentes son las barras reales que mezclan menús y paneles —
los menu extras de macOS (Wi-Fi / Control Center: trigger en barra, panel
no-menu, Esc cierra, las flechas operan el panel), las galerías del ribbon de
Office y los paneles de barra de Canva.
Referencias: Radix Menubar · Bits UI Menubar · APG Menubar.
Decisiones
- Pack sema propio con la razón in-place (header del morfo): "subtle commit + tap haptic for high-frequency top-level menu triggers" — un menubar se pulsa decenas de veces por sesión; el carácter baja la intensidad en vez de subirla.
- UN evento propio (
commit-select, affirm, sobre el trigger): los ítems, checkbox/radio y submenús DELEGAN en el DropdownMenu compuesto (quinta cita del patrón de delegación del catálogo) — sus eventos sema son los del dropdown. - Re-export literal de las partes internas en eidos:
Menubar.ItemESDropdownMenu.Item, etc. — el partial compartido menu-indicator aplica automáticamente y un fix visual del dropdown alcanza al menubar sin drift. data-menubar-valueen el trigger enlaza trigger↔menú para el estado controlado (value/onValueChange).Menubar.Panel— superficie de formulario por entrada (2026-07-16, encargo del editor palabraspanelMode='menubar'v2, componente genérico). Motivo: el Content de menú intercepta ↑↓/Home/End + typeahead (navegación de items) y eso rompe cualquier control de formulario compuesto dentro. El Panel es un part propio del morfo (data-menubar-panel,role="dialog",aria-labelledby→trigger; el trigger pasa aaria-haspopup="dialog") que NO añade teclado propio: las teclas dentro pertenecen a sus controles (las flechas ←→ NO cruzan menús desde dentro — mueven carets/sliders; Esc devuelve a la barra). La coordinación de barra (roving, hover-slide, keyboard-slide,commit-select, un-abierto-a-la-vez) queda intacta porque vive en el Trigger/Provider. Presencia = tipo (N-7): componer<Panel>en vez de<Content>declara la superficie. Cableado: reusa el shell flotante del DropdownMenu anidado (anchor del trigger, presence, canalcontentId/setContentRef), con FocusScope/Dismissal propios y SIN TextSelection (los inputs necesitan selección) ni ScrollLock. Foco: apertura por teclado → primer control; por puntero → la superficie (hover jamás roba el foco a un input). Elevación víadata-depth="overlay"+ tokens públicos--menubar-panel-*(z · min-width · max-height · padding · radius).- Mega-panel =
customAnchor={barra}a secas (2026-07-17). El ancho es automático: soma pre-mide el anchor (--menubar-panel-anchor-widthen el elemento) y la recipe dimensiona la superficiedata-anchoredconcontain: inline-size— el positioner flotante mide el WRAPPER (min-width: max-content) y la intrínseca de un grid (suma de tracks) lo inflaba, volteando el placement; el containment fija la contribución al ancho usado (en modo anclado el ancho viene del anchor, nunca del contenido). Regla de consumo: elstyledel Panel viste al POSITIONER (mismo plumbing que todo Content del catálogo) — el layout del contenido (grid/flex) va en un elemento INTERNO. - La apertura sigue al roving por teclado (2026-07-16): con un menú
abierto, ArrowLeft/Right/Home/End sobre los triggers deslizan la apertura al
destino — el espejo de teclado del hover-follow (la doctrina del
commit-selectya lo prometía). El slide vive en la navegación explícita del provider, no enonfocus: un listener de focus no distingue el roving del focus-return del FocusScope al cerrarse el menú que se desliza. Ese focus-return se suprime en<Menubar.Content>(patrón Radix:preventDefaultenonCloseAutoFocuscuando otro menú del menubar queda abierto); el cierre simple (Escape / outside / select) conserva el retorno de foco normal. - Toda apertura por teclado aterriza en el PRIMER ITEM activo (2026-07-16,
decisión de usuario tras confusión real: un panel abierto sin opción activa
no dice cómo navegar). Cubre ArrowDown/Enter/Space en el trigger, el slide
←→/Home/End entre triggers y el cross-menu desde dentro de un panel — la
señal viaja como
openReason: 'keyboard'del menubar al DropdownMenu interno, cuyo autofocus enfocagetItems()[0]en vez del panel (APG "focus first item"; Word hace lo mismo en el slide). Las aperturas por PUNTERO (click, hover-follow) mantienen el foco en el panel: ahí resalta el hover y un item pre-activado sería una fila fantasma bajo el cursor.
Gaps
| Gap | Disposición | Detalle |
|---|---|---|
| Tests del wrapper eidos (el soma tiene suite ✓) | diferir | Pasada SYS-2 conjunta. |
| API extendida sobre las referencias (p. ej. trigger-on-hover configurable) | diferir | Paridad Radix/Bits ya alcanzada; sin caso real. |
Intent del commit-select fijo en affirm |
descartar | Seleccionar una entrada de menú no carga evaluación variable; el intent por-evento es intrínseco (doctrina per-event intent). |
| Typeahead a nivel de barra (menubar cerrado salta al trigger por inicial) | diferir | APG lo lista; ni Radix ni Bits lo implementan (el typeahead vive dentro del menú abierto, ✓ aquí). Paridad mantenida; sin caso real. |
ArrowUp en el trigger abre enfocando el último item |
diferir | Radix lo soporta; exige que el DropdownMenu compuesto acepte "open with last item highlighted". Se aborda en el dropdown, no con un hack local. |
Referencias
- Soma Menubar:
src/uix/soma/components/menubar/README.md - DropdownMenu (partes internas):
../dropdown-menu/ - Ficha de auditoría:
docs/audit/components/menubar.md