{{-- An M3 Expressive menu: a list of actions that opens from a trigger.
The trigger's first button or link becomes the menu button (aria-haspopup, aria-expanded,
aria-controls). The list is a `popover="auto"` in the top layer, placed by CSS anchor
positioning at `position` (`bottom-start`, `bottom-end`, `top-start`, `top-end`) and flipping
when there is no room — to the other side, the other end, or both, so a menu on a FAB in a
corner of the window opens back across it; a click outside or Escape closes it. The keyboard
is WAI-ARIA's menu button: Enter, Space or ArrowDown open on the first item, ArrowUp on the
last; arrows, Home, End and typing a letter move between items; Tab closes; activating an item
closes the menu unless the item says `keep-open`, and Escape returns focus to the trigger.
The anchor name is rendered on the wrapper around the trigger slot, the only element the
server can name, and resources/js/menu.js moves it onto the menu button itself: a trigger
that is `position: fixed` (`` on a phone) leaves the wrapper behind as an empty
box where the page put it, and the menu opened there.
The id and the anchor name are new with every render. The popover carries a `wire:key`, which
a Livewire morph matches it by before the id, so a render of the component around an open
menu patches it in place — still open, focus and listeners kept — instead of swapping in a
closed copy; menu.js then writes the menu button's ARIA attributes again. The key goes
through an attribute bag: Livewire compiles a `wire:key` written in a template into the key
of the loop iteration around it, which would give every child component after the menu the
same key.
A menu too long for the window scrolls, as M3 asks, rather than running off the edge of the
top layer where nothing can reach it: 18rem at most, and less on a short window. The arrow
keys, Home, End and typeahead bring the item they move to into view, and a disabled item is
among them: M3 keeps one reachable so a person can find out that it exists.
It opens by growing out of the corner nearest its trigger and fades as it goes
(`popover-transition`), which is the transition M3 asks to tie a menu to what opened it.
`filter` is M3's menu as a filtering surface ("autocomplete"): a text field at the top of the
list, which stays put while the list scrolls under it, narrowing the items to those whose
label holds what has been typed — in the browser, over the items already rendered, so nothing
is fetched and a `wire:click` stays where it was. `filter="Find a person"` names the field;
bare `filter` calls it "Filter". The field, not the list, holds the focus, so a person can
type and steer at once: the arrow keys, Home and End move a highlighted row and say which one
through `aria-activedescendant`, and Enter chooses it — the APG combobox keyboard, the same
one `` uses. The list around it stays a `role="menu"` of its own inside
the popover, because a text field is not a thing a menu may contain.
The container is Expressive's standard menu (surface-container-low, 16px corner, elevation
2), or `vibrant` in tertiary-container — StandardMenuTokens and VibrantMenuTokens from
androidx Compose Material 3 (Apache-2.0). --}}
@props([
'label' => null,
'position' => 'bottom-start',
'vibrant' => false,
'filter' => false,
])
@php
$position = in_array($position, ['bottom-start', 'bottom-end', 'top-start', 'top-end'], true) ? $position : 'bottom-start';
$key = \Illuminate\Support\Str::lower(\Illuminate\Support\Str::random(10));
$anchor = "--material-menu-{$key}";
$filtering = $filter !== false && $filter !== null && $filter !== '';
$filterLabel = is_string($filter) && filled($filter) ? $filter : __('Filter');
@endphp