@marccawood/lit-menu
v0.2.0
Published
Anchored dropdown menu as a themeable Lit web component, with a promise API.
Maintainers
Readme
@marccawood/lit-menu
An anchored dropdown menu as a Lit web component. One call, one promise, one chosen id.
A native popover, so the browser draws it in the top layer: an
overflow: hidden ancestor never clips it, no z-index has to be won, and an
outside click dismisses it. Flips above its anchor when it would run past the
bottom of the viewport. Escape closes it and marks the key press
defaultPrevented, so an app that also closes something on Escape can tell the
key was already used.
Depends only on lit. Needs the Popover API (Chrome 114, Safari 17,
Firefox 125).
Live example → — cd example && npm install && npm run dev
Sibling packages: @marccawood/lit-dialogs
for modals, @marccawood/lit-toast for
notifications.
Install
npm install @marccawood/lit-menu litUse
There is no element to place in a template. The menu mounts itself on first use:
import { AnchoredMenu } from '@marccawood/lit-menu';
button.addEventListener('click', async (e) => {
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
const picked = await AnchoredMenu.open(rect, [
{ id: 'open', label: 'Open', icon: 'open_in_new' },
{ id: 'rename', label: 'Rename', icon: 'edit' },
{ id: 'delete', label: 'Delete', icon: 'delete', danger: true },
]);
if (picked === 'delete') await remove();
});open resolves to the chosen id, or null if the user clicked outside or
pressed Escape. The anchor is a viewport-space DOMRect — usually
getBoundingClientRect() of whatever the user clicked.
| Item field | Meaning |
| ---------- | ------------------------------------------------------------- |
| id | What open resolves to |
| label | The visible text |
| icon | Optional Material Icons ligature name, shown before the label |
| danger | Optional; renders the label in the danger color |
defineAnchoredMenu(tag) is exported but optional. Call it before the first
open() only if the default anchored-menu tag clashes with something.
Theme
Set these on any ancestor. Every one falls back to the value shown.
| Token | Fallback |
| ------------------- | ----------------------- |
| --menu-surface | #fff |
| --menu-text | #111 |
| --menu-border | #e5e7eb |
| --menu-hover | #f3f4f6 |
| --menu-danger | #b91c1c |
| --menu-icon | #6b7280 |
| --menu-radius | 0.4rem |
| --menu-font | system-ui, sans-serif |
| --menu-min-width | 190px |
| --menu-max-height | min(60vh, 420px) |
| --menu-z | 150000 |
--menu-z no longer decides anything — a popover sits in the top layer, above
every stacking context. It is still applied, so setting it changes nothing
either way.
Icons
Item icons are Material Icons ligature names. Load the font once globally:
import 'material-icons/iconfont/material-icons.css';Without it an item shows its glyph name as text. Items with no icon need no
font at all.
materialIconStyles is exported for your own shadow-DOM components — class
rules in the document stylesheet do not reach inside a shadow root, so each
component needs a copy.
API
| Export | What it is |
| -------------------- | --------------------------------------------------------------------- |
| AnchoredMenu.open | (anchor: DOMRect, items) => Promise<string \| null>. Self-mounting. |
| AnchoredMenu | The element class, if you want to mount it yourself. |
| defineAnchoredMenu | Guarded customElements.define, default tag anchored-menu. |
| AnchoredMenuItem | The item type. |
| placeMenu | The pure placement rule. Exported because it is tested, and reusable. |
| materialIconStyles | Material Icons class rules for your own shadow roots. |
Develop
npm install
npm run typecheck
npm test
npm run buildPublishing runs from a version tag. Push v0.1.0 and the
publish workflow checks the tag against
package.json, runs the tests, and publishes to npm.
Origin
Extracted from easyDBAccess, where it serves the table footer menus and the per-column value pickers.
License
MIT © Marc Cawood
