Canvas Design System
Main Site Tokens

Action Menu: one panel per list

A list whose rows share one menu renders its entries once. ws_menu_panel prints the panel as a template, and each row's Action Menu names it with panel.

When to Use

Use when: Every row of a list has the same menu, give or take a few entries a row may lack: a list of workshops, of team members, of files. Render the entries once with ws_menu_panel, give each row ws_menu($label, [], ['panel' => $id]), and drop the entries a row lacks in a ws-menu-open listener. The page carries one set of entries instead of one per row.
Don't use when: There is one menu, or the rows' menus differ in more than a few removable entries: give each ws_menu its own items. The panel's mode must match the menus that use it.

Example

Two rows, one panel. Open either menu: the panel is cloned in after that row's trigger, and removed when it closes.

document.addEventListener('ws-menu-open', function (e) {
    const id = e.target.dataset.planId;           // the row's own facts, on its menu
    e.detail.panel.querySelectorAll('a[href]').forEach(a => a.href += '&plan=' + id);
});

Accessibility

ConcernBehavior
At restThe template is inert, so the page holds no entries and the trigger promises no aria-controls it cannot keep.
OpenThe clone is the panel ws_menu renders: its list is role="menu", named by the row's trigger, and the trigger's aria-controls names it while it is in the page. Keys, focus and dismissal are ws_menu's.
ClosedThe clone is removed and aria-controls with it; the next open clones afresh against the row as it is then.

Files

FilePurpose
includes/components/helpers.phpws_menu_panel(), and ws_menu()'s panel option
includes/components/menu.phpThe template both render through
includes/components/components.jswsMenuClonePanel(): the clone on open, removed by wsMenuClose()