Open a submenu from a menu item

Plan step 22, actions.md § Missing (Submenus): M3's Expressive vertical
menu specifies submenus and the Left/Right keys that open and close them,
and the library had neither.

`<x-menu-item submenu>` puts its slot in a second popover on the item's
end, flipping to the start where there is no room. `materialSubmenu`
reuses the menu button pattern one level in: the item is its own trigger,
so the anchor move and the button lookup are overridden away; Right,
Enter and Space open it, Left and Escape close it and return the focus,
and a fine pointer resting on the item opens it after a moment. `items()`
now stops at the popover it belongs to, so the arrows never walk between
a menu and an open submenu. The menu publishes its container as custom
properties so a vibrant menu's submenus are vibrant too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 06:31:56 +02:00
co-authored by Claude Opus 5
parent d74fa250ee
commit 25091ebf21
7 changed files with 462 additions and 205 deletions
+25 -2
View File
@@ -1,7 +1,7 @@
/*
* The exposed dropdown menu — the list a field drops open.
* The menus: the exposed dropdown a field drops open, and what `<x-menu>` cannot say in utilities.
*
* Two lists wear it, so a form reads as one family:
* Two lists wear the dropdown, so a form reads as one family:
*
* - `<x-select>`'s: the native <select>, opted into the browser's customizable select
* (`appearance: base-select`). `::picker(select)` is the menu and each <option> a row, while the
@@ -20,6 +20,29 @@
* paints option colours into its native list would otherwise show a half-painted one.
*/
/*
* `<x-menu>` and the submenus inside it.
*
* A submenu is a popover of its own nested in the menu's, so it cannot take the container colour
* from a class the item knows nothing about: the menu declares the pair as custom properties,
* which a nested popover inherits down the DOM whatever the top layer does with its painting. The
* fallbacks are the standard menu's, for a submenu in some other list (a FAB menu's).
*/
[data-menu] {
--material-menu-surface: var(--md-sys-color-surface-container-low);
--material-menu-ink: var(--md-sys-color-on-surface);
}
[data-menu][data-vibrant] {
--material-menu-surface: var(--md-sys-color-tertiary-container);
--material-menu-ink: var(--md-sys-color-on-tertiary-container);
}
[data-submenu] {
background: var(--material-menu-surface, var(--md-sys-color-surface-container-low));
color: var(--material-menu-ink, var(--md-sys-color-on-surface));
}
.field-menu {
max-block-size: 18rem;
overflow-y: auto;