Filter a menu from a field at the top of it
Plan step 22, actions.md § Missing (Menus as a filtering surface): M3's menus page describes a menu that embeds a text field and filters its options as you type, and nothing in the library did that. `<x-menu filter>` renders the field, sticky above the list, and hides the rows the query leaves out — client-side over the items already rendered, so nothing is fetched and a `wire:click` stays where it was. The field keeps the focus and the arrow keys move a highlight it names through `aria-activedescendant`, the APG combobox keyboard `<x-choices searchable>` already uses; Enter chooses the highlighted row, and a query that leaves nothing says so. The list becomes a `role="menu"` inside the popover, because a text field is not something a menu may contain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
3f651c5c08
commit
88c46001fb
@@ -43,6 +43,53 @@
|
||||
color: var(--material-menu-ink, var(--md-sys-color-on-surface));
|
||||
}
|
||||
|
||||
/*
|
||||
* `<x-menu filter>`: M3's menu as a filtering surface. The field stays put while the list scrolls
|
||||
* under it and takes the menu's own container, so a vibrant menu's field is vibrant too. It is a
|
||||
* plain <input> with no field chrome — M3's menus embed a text field, not a text field component.
|
||||
*/
|
||||
[data-menu-filter] {
|
||||
position: sticky;
|
||||
inset-block-start: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
min-block-size: 3rem;
|
||||
padding-inline: 1rem;
|
||||
border-block-end: 1px solid var(--md-sys-color-outline-variant);
|
||||
background: var(--material-menu-surface, var(--md-sys-color-surface-container-low));
|
||||
}
|
||||
|
||||
[data-menu-filter] input {
|
||||
flex: 1;
|
||||
min-inline-size: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font: var(--md-sys-typescale-body-lg);
|
||||
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
[data-menu-filter] input::placeholder {
|
||||
color: color-mix(in srgb, var(--material-menu-ink, var(--md-sys-color-on-surface)) 70%, transparent);
|
||||
}
|
||||
|
||||
/* The filter hides what the query leaves out with the `hidden` attribute; a row's own
|
||||
`display: flex` utility would otherwise beat the user agent's `[hidden] { display: none }`. */
|
||||
[data-menu] [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* `data-active` is the row the arrow keys are on while the focus stays in the field — the state
|
||||
layer the roving focus would have drawn, without taking the focus off the field. A chosen or
|
||||
current row keeps its own colour. */
|
||||
[data-menu] [role^="menuitem"][data-active]:not([aria-checked="true"], [aria-current="page"]) {
|
||||
background: color-mix(in srgb, var(--material-menu-ink, var(--md-sys-color-on-surface)) 8%, transparent);
|
||||
}
|
||||
|
||||
.field-menu {
|
||||
max-block-size: 18rem;
|
||||
overflow-y: auto;
|
||||
|
||||
Reference in New Issue
Block a user