@rcarls/rc-menu-button 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-menu-button-C2gavu7b.js","sources":["../src/rc-menu-button.styles.ts","../src/rc-menu-button.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const menuButtonStyles = css`\n :host {\n display: inline-block;\n }\n\n :host([orientation='vertical']) {\n display: block;\n }\n\n #root {\n display: inline-block;\n }\n\n :host([orientation='vertical']) #root {\n display: block;\n inline-size: 100%;\n }\n\n #trigger-wrap {\n position: relative;\n display: inline-block;\n }\n\n :host([orientation='vertical']) #trigger-wrap {\n display: block;\n inline-size: 100%;\n }\n\n /*\n * Icon-only triggers (e.g. a toolbar's overflow button) can render\n * visually smaller than the accessible 48dp touch-target minimum,\n * especially at a narrow width. This is a floor, not a fixed size: a\n * trigger already at or above it (the default width, or a labeled\n * trigger) is unaffected. #trigger-wrap reserves the layout space and\n * centers the (possibly smaller) slotted trigger within it; the actual\n * larger hit region comes from the light-DOM hit-slop pseudo-element in\n * rc-menu-button.ts. Mirrors rc-button's own touch-target affordance.\n *\n * Scoped to [icon-only] rather than applied unconditionally: switching\n * #trigger-wrap to a flex/grid container would blockify the slotted\n * trigger's own inline-flex display (its getComputedStyle().display\n * becomes \"flex\", per the CSS Display spec's flex-item blockification —\n * harmless for layout, since a flex item is sized by its container's\n * flex algorithm regardless of its own inline/block outer type, but an\n * observable change other callers shouldn't have to account for).\n */\n :host([icon-only]) #trigger-wrap {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-block-size: var(--rc-menu-button-touch-target-block-size, 3rem);\n min-inline-size: var(\n --rc-menu-button-touch-target-inline-size,\n var(--rc-menu-button-touch-target-block-size, 3rem)\n );\n /*\n * Zero by default. A theme or consumer sets one of these on a trigger\n * that sits at a real edge (no neighbor on that side) to let the\n * touch-target inflation above overlap into whatever sits just outside\n * the host instead of also reserving layout space there. Mirrors\n * rc-button's own --rc-button-touch-target-overlap-inline-* tokens.\n */\n margin-inline-start: calc(-1 * var(--rc-menu-button-touch-target-overlap-inline-start, 0px));\n margin-inline-end: calc(-1 * var(--rc-menu-button-touch-target-overlap-inline-end, 0px));\n }\n\n :host([icon-only][orientation='vertical']) #trigger-wrap {\n display: flex;\n }\n\n /*\n * Icon-only trigger width: square by default (matches its own block\n * size), or a distinct value for a narrower/wider variant. Padding is\n * cleared since an icon-only trigger centers its (typically single) icon\n * child via the flex alignment above rather than via inline padding.\n */\n :host([icon-only]) slot[name='trigger']::slotted(button),\n :host([icon-only]) slot[name='trigger']::slotted([role='button']) {\n inline-size: var(\n --rc-menu-button-icon-size,\n var(--rc-menu-button-trigger-block-size, var(--rc-control-block-size, 2.25em))\n );\n min-inline-size: var(\n --rc-menu-button-icon-size,\n var(--rc-menu-button-trigger-block-size, var(--rc-control-block-size, 2.25em))\n );\n padding-inline: 0;\n }\n\n slot[name='trigger']::slotted(button),\n slot[name='trigger']::slotted([role='button']) {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--rc-menu-button-trigger-gap, var(--rc-item-gap, 0.5em));\n min-block-size: var(--rc-menu-button-trigger-block-size, var(--rc-control-block-size, 2.25em));\n padding: var(--rc-menu-button-trigger-padding-block, var(--rc-control-padding-block, 0.25em))\n var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em));\n appearance: none;\n box-sizing: border-box;\n border: var(--rc-menu-button-trigger-border, var(--rc-border, 1px solid ButtonBorder));\n border-radius: var(--rc-menu-button-trigger-radius, var(--rc-control-radius, 0.125em));\n background: var(--rc-menu-button-trigger-background, var(--rc-button-bg, ButtonFace));\n color: var(--rc-menu-button-trigger-color, var(--rc-button-text, ButtonText));\n font: inherit;\n text-decoration: none;\n cursor: default;\n user-select: none;\n transition: var(--rc-menu-button-trigger-transition);\n }\n\n slot[name='trigger']::slotted(button:hover),\n slot[name='trigger']::slotted([role='button']:hover) {\n border-color: var(--rc-menu-button-trigger-hover-border-color, currentColor);\n background: var(\n --rc-menu-button-trigger-hover-background,\n color-mix(in srgb, Highlight 8%, transparent)\n );\n color: var(--rc-menu-button-trigger-hover-color, inherit);\n }\n\n slot[name='trigger']::slotted(button[aria-expanded='true']),\n slot[name='trigger']::slotted([role='button'][aria-expanded='true']) {\n border-color: var(--rc-menu-button-trigger-open-border-color, currentColor);\n background: var(\n --rc-menu-button-trigger-open-background,\n color-mix(in srgb, Highlight 12%, transparent)\n );\n color: var(--rc-menu-button-trigger-open-color, inherit);\n }\n\n :host([orientation='vertical']) slot[name='trigger']::slotted(button),\n :host([orientation='vertical']) slot[name='trigger']::slotted([role='button']) {\n inline-size: 100%;\n text-align: start;\n }\n\n :host([has-indicator]) slot[name='trigger']::slotted(button),\n :host([has-indicator]) slot[name='trigger']::slotted([role='button']) {\n padding-inline-end: calc(\n var(\n --rc-menu-button-indicator-inset,\n var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em))\n ) +\n var(--rc-menu-button-indicator-size, 1em) +\n var(--rc-menu-button-trigger-gap, var(--rc-item-gap, 0.5em))\n );\n }\n\n slot[name='indicator'] {\n position: absolute;\n inset-block-start: 50%;\n inset-inline-end: var(\n --rc-menu-button-indicator-inset,\n var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em))\n );\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: var(--rc-menu-button-indicator-size, 1em);\n block-size: var(--rc-menu-button-indicator-size, 1em);\n color: var(--rc-menu-button-indicator-color, currentColor);\n pointer-events: none;\n transform: translateY(-50%);\n }\n\n slot[name='indicator']::slotted(*) {\n inline-size: 100%;\n block-size: 100%;\n }\n\n #popup {\n margin: 0;\n padding: 0;\n border: none;\n background: none;\n overflow: visible;\n color: inherit;\n }\n\n #popup[data-rc-popover-fallback-open] {\n display: block;\n }\n\n /*\n * Opacity only, deliberately not a scale or slide transform: AnchorController\n * (see rc-common) positions this popup and applies its own viewport-clamping\n * translate to it, tracked against getBoundingClientRect() to compute the\n * next correction. A second, CSS-driven transform on the same element and\n * property would fight that math on every open, not just during a resize\n * window the way rc-dialog's conflict was.\n *\n * display and overlay join the transition list, allow-discrete: without\n * them, the popover's own native display: none for :not(:popover-open)\n * applies instantly, and a transition on a property of an element with no\n * box doesn't render at all. Confirmed directly against rc-select's\n * identical conflict: an opacity-only version of this same shape measured\n * display: none within 300ms of a 2-second exit fade that should still\n * have been running.\n */\n @media (prefers-reduced-motion: no-preference) {\n :host([open]) #popup {\n opacity: 1;\n transition:\n opacity var(--rc-menu-button-popup-duration, 150ms)\n var(--rc-motion-effects-easing-enter, ease-out),\n overlay var(--rc-menu-button-popup-duration, 150ms) allow-discrete,\n display var(--rc-menu-button-popup-duration, 150ms) allow-discrete;\n }\n\n :host(:not([open])) #popup {\n opacity: 0;\n transition:\n opacity var(--rc-menu-button-popup-duration, 150ms)\n var(--rc-motion-effects-easing-exit, ease-in),\n overlay var(--rc-menu-button-popup-duration, 150ms) allow-discrete,\n display var(--rc-menu-button-popup-duration, 150ms) allow-discrete;\n }\n\n @starting-style {\n :host([open]) #popup {\n opacity: 0;\n }\n }\n }\n`;\n\nexport default menuButtonStyles;\n","import { LitElement, html } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\n\nimport {\n AnchorController,\n type AnchorPlacement,\n cssText,\n type KeyboardNavigationAction,\n keyInteraction,\n keyNavigation,\n layoutOrientation,\n PopoverController,\n resolveFlow,\n} from '@rcarls/rc-common';\nimport type { RCMenu } from '@rcarls/rc-menu';\n\nimport menuButtonStyles from './rc-menu-button.styles.js';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-menu-button': RCMenuButton;\n }\n}\n\nconst LIGHT_DOM_CSS = cssText`\n@layer rc-base {\n /*\n * Expands an icon-only trigger's clickable region beyond its visible box\n * up to the accessible touch-target minimum, the same way rc-button and\n * rc-chip do: an absolutely-positioned, invisible pseudo-element on the\n * actual slotted trigger element (not the host) extends the hit area\n * symmetrically. The trigger already has position: relative from the\n * shadow-scoped ::slotted(button) rule. max(100%, size) keeps this a\n * no-op once the visible trigger already meets or exceeds the touch\n * target, rather than shrinking it.\n */\n rc-menu-button[icon-only] > [slot='trigger']::before {\n content: '';\n position: absolute;\n inset-block: calc(\n (\n var(--rc-menu-button-touch-target-block-size, 3rem) -\n max(100%, var(--rc-menu-button-trigger-block-size, 0px))\n ) / -2\n );\n inset-inline: calc(\n (\n var(\n --rc-menu-button-touch-target-inline-size,\n var(--rc-menu-button-touch-target-block-size, 3rem)\n ) -\n max(100%, var(--rc-menu-button-icon-size, var(--rc-menu-button-trigger-block-size, 0px)))\n ) / -2\n );\n }\n}\n`;\n\n/** Detail payload for the `rc-menu-button-toggle` event. */\nexport interface RCMenuButtonToggleEvent {\n /** Whether the menu transitioned to open (`true`) or closed (`false`). */\n open: boolean;\n}\n\n/**\n * Trigger button that opens an rc-menu popup, following the WAI-ARIA Menu Button pattern.\n *\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-menu-button rc-menu-button docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ WAI-ARIA Menu Button pattern}\n *\n * @slot trigger - The button element that triggers the menu\n * @slot indicator - Optional decorative indicator rendered at the trigger's inline end\n * @slot default - The rc-menu element to display as popup\n *\n * @fires rc-menu-button-toggle - Fired when the menu opens or closes\n *\n * @cssprop [--rc-menu-button-trigger-block-size=var(--rc-control-block-size)] - Minimum block size of the trigger\n * @cssprop [--rc-menu-button-trigger-padding-block=var(--rc-control-padding-block)] - Trigger block-axis padding\n * @cssprop [--rc-menu-button-trigger-padding-inline=var(--rc-control-padding-inline)] - Trigger inline-axis padding\n * @cssprop [--rc-menu-button-trigger-gap=var(--rc-item-gap)] - Gap between flex children in the trigger\n * @cssprop [--rc-menu-button-trigger-border=var(--rc-border)] - Trigger border\n * @cssprop [--rc-menu-button-trigger-radius=var(--rc-control-radius)] - Trigger border radius\n * @cssprop [--rc-menu-button-trigger-background=var(--rc-button-bg)] - Trigger background\n * @cssprop [--rc-menu-button-trigger-color=var(--rc-button-text)] - Trigger text color\n * @cssprop [--rc-menu-button-trigger-transition] - CSS transition applied to the trigger\n * @cssprop [--rc-menu-button-trigger-hover-background=color-mix(in srgb, Highlight 8%, transparent)] - Trigger hover background\n * @cssprop [--rc-menu-button-trigger-hover-color=inherit] - Trigger hover text color\n * @cssprop [--rc-menu-button-trigger-hover-border-color=currentColor] - Trigger hover border color\n * @cssprop [--rc-menu-button-trigger-open-background=color-mix(in srgb, Highlight 12%, transparent)] - Trigger background when the menu is open\n * @cssprop [--rc-menu-button-trigger-open-color=inherit] - Trigger text color when the menu is open\n * @cssprop [--rc-menu-button-trigger-open-border-color=currentColor] - Trigger border color when the menu is open\n * @cssprop [--rc-menu-button-indicator-size=1em] - Inline and block size of the slotted indicator\n * @cssprop [--rc-menu-button-indicator-color=currentColor] - Color of the slotted indicator\n * @cssprop [--rc-menu-button-indicator-inset=var(--rc-menu-button-trigger-padding-inline)] - Indicator distance from the trigger's inline end\n * @cssprop [--rc-menu-button-icon-size] - Inline size of an `icon-only` trigger's visible box.\n * Square by default (equal to `--rc-menu-button-trigger-block-size`); set narrower or wider\n * to change only the trigger's width, independent of its height.\n * @cssprop [--rc-menu-button-touch-target-block-size=3rem] - Minimum accessible touch target\n * block size for an `icon-only` trigger. A floor, not a fixed size: has no effect once\n * `--rc-menu-button-trigger-block-size` already meets or exceeds it. Grows the shadow-DOM\n * trigger wrapper (reserving layout space) and the light-DOM hit-slop (the actual larger\n * clickable region) together; the visible trigger itself stays at its own size, centered.\n * @cssprop [--rc-menu-button-touch-target-inline-size] - Minimum accessible touch target inline\n * size for an `icon-only` trigger. Defers to `--rc-menu-button-touch-target-block-size` when\n * unset.\n * @cssprop [--rc-menu-button-touch-target-overlap-inline-start=0px] - Zero by default. A theme\n * or consumer sets this on an `icon-only` trigger that sits at a real leading edge (no\n * neighbor on that side) to let its touch-target inflation overlap into whatever sits just\n * outside the host, such as a container's own edge padding, instead of also reserving layout\n * space there.\n * @cssprop [--rc-menu-button-touch-target-overlap-inline-end=0px] - The trailing-edge\n * counterpart to `--rc-menu-button-touch-target-overlap-inline-start`.\n * @cssprop [--rc-menu-button-popup-duration=150ms] - Popup open/close fade transition duration.\n *\n * @csspart root - The root container element\n * @csspart popup - The popup container element\n *\n * @attr open - Whether the menu popup is visible. Controlled mode: host writes silently.\n * @attr default-open - Initial open state for uncontrolled mode. Ignored after the first\n * controlled write to `open`.\n * @attr orientation - The layout the trigger sits in: `horizontal` along the inline axis,\n * like a menubar row, or `vertical` along the block axis. It picks the arrow key that opens\n * the menu, following how that layout renders, so the keys turn with RTL and vertical text.\n * Inherits from a parent `rc-menubar` or `[role=\"menubar\"]` when unset.\n * @attr placement - Preferred placement of the popup relative to the trigger, in logical terms:\n * `block-start`/`block-end` (above or below in horizontal text, beside it in vertical text) or\n * `inline-start`/`inline-end` (beside it along the reading direction), each with an optional\n * `-start`/`-end` edge alignment. Defaults to `block-end-start`, or `inline-end-start` under\n * vertical orientation when unset.\n * @attr icon-only - Hints that the slotted trigger has no visible label, so themes can size\n * it and its touch target as an icon button (see `--rc-menu-button-icon-size` and\n * `--rc-menu-button-touch-target-*` above). Purely a styling hook; RCMenuButton does not read\n * or derive it — set it whenever the trigger is rendered icon-only.\n */\nexport class RCMenuButton extends LitElement {\n static styles = [menuButtonStyles];\n\n /**\n * Enables delegatesFocus so the browser routes focus() on the host element\n * to the slotted trigger button.\n */\n static override shadowRootOptions: ShadowRootInit = {\n ...LitElement.shadowRootOptions,\n\n delegatesFocus: true,\n };\n\n protected static readonly _styledRoots = new Set<Document | ShadowRoot>();\n\n protected static _ensureBaseStyles(root: Document | ShadowRoot): void {\n if (RCMenuButton._styledRoots.has(root)) {\n return;\n }\n\n RCMenuButton._styledRoots.add(root);\n\n const style = document.createElement('style');\n\n style.setAttribute('data-rc-light-dom-base', 'rc-menu-button');\n style.textContent = LIGHT_DOM_CSS;\n\n if (root instanceof Document) {\n root.head.append(style);\n } else {\n root.append(style);\n }\n }\n\n private _defaultOpen = false;\n private _open: boolean | undefined;\n private _uncontrolledOpen: boolean | undefined;\n private _openInitialized = false;\n\n /**\n * Whether the menu is currently open.\n *\n * In controlled mode the host owns this value; writing the property applies\n * the state silently without dispatching `rc-menu-button-toggle`.\n */\n @property({ type: Boolean, reflect: true })\n get open(): boolean {\n return this._open ?? this._uncontrolledOpen ?? this._defaultOpen;\n }\n\n /** Applies open state in controlled mode without dispatching an event. */\n set open(value: boolean | undefined) {\n const oldValue = this.open;\n\n this._open = value;\n this._openInitialized = true;\n this.requestUpdate('open', oldValue);\n this._syncTriggerAria();\n }\n\n /**\n * Initial open state for uncontrolled mode.\n *\n * Ignored after the first controlled write; use `open` to update state\n * programmatically after mount.\n */\n @property({ type: Boolean, attribute: 'default-open' })\n get defaultOpen(): boolean {\n return this._defaultOpen;\n }\n\n /** Applies the uncontrolled default before any controlled write occurs. */\n set defaultOpen(value: boolean) {\n const oldValue = this._defaultOpen;\n\n this._defaultOpen = value;\n\n if (\n !this._openInitialized &&\n this._open === undefined &&\n this._uncontrolledOpen === undefined\n ) {\n this.requestUpdate('open', oldValue);\n }\n\n this.requestUpdate('defaultOpen', oldValue);\n }\n\n /**\n * The layout this menu button sits in, which picks the arrow keys that open\n * and close the menu. `horizontal` runs along the inline axis and `vertical`\n * along the block axis; the keys follow how that layout renders.\n *\n * If not set, inherits from a parent rc-menubar or element with role=\"menubar\".\n */\n @property({ type: String, reflect: true })\n orientation: 'horizontal' | 'vertical' | undefined;\n\n private _placement: AnchorPlacement | undefined;\n\n /**\n * Preferred placement of the popup relative to the trigger button. Reads the\n * default until the author sets one.\n *\n * @default 'block-end-start'\n */\n @property({ reflect: true })\n get placement(): AnchorPlacement {\n return this._placement ?? this._defaultPlacement;\n }\n\n set placement(value: AnchorPlacement | null | undefined) {\n const oldValue = this.placement;\n\n this._placement = value ?? undefined;\n this.requestUpdate('placement', oldValue);\n }\n\n /** The placement used when the author sets none. Subclasses override it. */\n protected get _defaultPlacement(): AnchorPlacement {\n return 'block-end-start';\n }\n\n /**\n * Popup placement adjusted for orientation.\n *\n * Without an author placement, a menu button whose resolved orientation is\n * `vertical` opens at `inline-end-start`, so vertical menubars open\n * submenus to the side, toward the inline end.\n */\n protected get _effectivePlacement(): AnchorPlacement {\n if (this._placement) {\n return this._placement;\n }\n\n // Opens toward the inline end, which is the left in RTL.\n return this._resolvedOrientation === 'vertical' ? 'inline-end-start' : this._defaultPlacement;\n }\n\n /**\n * Resolved orientation, considering inheritance from parent menubar.\n * Falls back to 'horizontal' if no orientation is set or inherited.\n */\n protected get _resolvedOrientation(): 'horizontal' | 'vertical' {\n if (this.orientation) {\n return this.orientation;\n }\n\n const $menubar = this.closest('rc-menubar, [role=\"menubar\"]');\n\n if ($menubar) {\n const orientation = $menubar.getAttribute('orientation');\n\n if (orientation === 'vertical' || orientation === 'horizontal') {\n return orientation;\n }\n\n // Without a layout attribute, only aria-orientation is left, and it\n // reports how the menubar renders; in vertical text that is the other\n // orientation from its layout.\n const rendered = $menubar.getAttribute('aria-orientation');\n\n if (rendered === 'vertical' || rendered === 'horizontal') {\n return layoutOrientation(rendered, resolveFlow($menubar));\n }\n }\n\n return 'horizontal';\n }\n\n /** Shadow DOM root container. Serves as the anchor element for `_anchorCtrl`. */\n @query('#root') protected _$root!: HTMLElement;\n\n /** Shadow DOM popup container. Hidden when `open` is `false`. */\n @query('#popup') protected _$popup!: HTMLElement;\n\n /** Weak reference to the slotted trigger button. */\n @state()\n protected _$trigger: WeakRef<HTMLElement> | undefined;\n\n /** Weak reference to the slotted `rc-menu` element. */\n @state()\n protected _$menu: WeakRef<RCMenu> | undefined;\n\n /** Positions the popup relative to the trigger using CSS anchor positioning. */\n protected _anchorCtrl = new AnchorController(this, {\n anchor: () => this._$root ?? null,\n floating: () => this._$popup ?? null,\n shadowHost: () => this,\n placement: 'block-end-start',\n offset: 2,\n disabled: () => !this.open,\n });\n\n protected _popoverCtrl = new PopoverController(this, { element: () => this._$popup });\n\n /** Bound handler for document click (light dismiss). Stored for `removeEventListener` pairing. */\n protected _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n\n /**\n * Watches the host's own `tabindex` attribute so the trigger button's\n * tabindex can be kept in sync.\n *\n * When a parent (e.g. rc-toolbar) places the\n * host under roving-tabindex management it sets/removes tabindex on the\n * host; we mirror that to the trigger so it doesn't become an independent\n * tab stop. When the host has no tabindex the trigger keeps its natural\n * tabindex=0 for standalone usage.\n */\n protected _tabObserver?: MutationObserver;\n\n /** Installs the tabindex mutation observer and the document-level click listener. */\n override connectedCallback() {\n super.connectedCallback();\n RCMenuButton._ensureBaseStyles(this.getRootNode() as Document | ShadowRoot);\n\n this._tabObserver = new MutationObserver(() => this._syncTriggerTabindex());\n this._tabObserver.observe(this, { attributes: true, attributeFilter: ['tabindex'] });\n\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n\n this.requestUpdate('open');\n }\n\n /** Disconnects the tabindex observer and removes the document-level click listener. */\n override disconnectedCallback() {\n this._tabObserver?.disconnect();\n this._tabObserver = undefined;\n\n super.disconnectedCallback();\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n }\n\n /**\n * Opens the menu and moves focus into it.\n *\n * @param focusTarget - which item receives initial focus when the menu opens\n */\n openMenu(focusTarget: 'first' | 'last' = 'first') {\n if (this.open) {\n return;\n }\n\n this._anchorCtrl.setOptions({ placement: this._effectivePlacement });\n this._setOpen(true, true);\n\n this.updateComplete.then(() => {\n if (!this.open) {\n return;\n }\n\n const $menu = this._$menu?.deref();\n\n focusTarget === 'last' ? $menu?.focusLast() : $menu?.focusFirst();\n });\n }\n\n /**\n * Closes the menu.\n *\n * @param returnFocus - when `true`, returns focus to the trigger button after closing\n */\n closeMenu(returnFocus = true) {\n if (!this.open) {\n return;\n }\n\n this._setOpen(false, true);\n\n if (returnFocus && !this.open) {\n const $trigger = this._$trigger?.deref();\n\n $trigger?.focus();\n }\n }\n\n /** Toggles the menu between open and closed. */\n toggleMenu() {\n if (this.open) {\n this.closeMenu();\n } else {\n this.openMenu();\n }\n }\n\n /** Dispatches the `rc-menu-button-toggle` bubbling composed event with the requested open state. */\n protected _dispatchToggle(open = this.open) {\n this.dispatchEvent(\n new CustomEvent<RCMenuButtonToggleEvent>('rc-menu-button-toggle', {\n bubbles: true,\n composed: true,\n detail: { open },\n }),\n );\n }\n\n /** Caches a weak reference to the first assigned trigger element and syncs its ARIA and tabindex state. */\n protected _handleTriggerSlotChange(e: Event) {\n const $slot = e.currentTarget as HTMLSlotElement;\n const $trigger = $slot.assignedElements()[0] as HTMLElement | undefined;\n\n if ($trigger) {\n this._$trigger = new WeakRef($trigger);\n this._syncTriggerAria();\n this._syncTriggerTabindex();\n }\n }\n\n /** Tracks optional indicator content so the trigger reserves its inline-end space. */\n protected _handleIndicatorSlotChange(e: Event) {\n const $slot = e.currentTarget as HTMLSlotElement;\n\n this.toggleAttribute('has-indicator', $slot.assignedElements().length > 0);\n }\n\n /**\n * Mirrors the host's `tabindex` attribute to the trigger button.\n *\n * When a roving-tabindex parent (e.g. rc-toolbar) marks this host inactive\n * with `tabindex=\"-1\"`, the trigger must also be suppressed so it does not\n * become an independent tab stop. When the host is the active item\n * (`tabindex=\"0\"`) or standalone (no tabindex), the trigger retains its\n * natural focusability so that Chrome's delegatesFocus can route sequential\n * Tab navigation through the host to the trigger.\n */\n protected _syncTriggerTabindex() {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger) {\n return;\n }\n\n if (this.getAttribute('tabindex') === '-1') {\n $trigger.setAttribute('tabindex', '-1');\n } else {\n $trigger.removeAttribute('tabindex');\n }\n }\n\n /**\n * Overrides the default focus() so that programmatic focus calls (e.g. from\n * a roving-tabindex parent navigating back to this element via arrow keys)\n * always reach the trigger.\n *\n * Chrome's delegatesFocus will not delegate to a tabindex=\"-1\" element, so\n * when the toolbar marks this host inactive it suppresses the trigger — and\n * the next focusItem() call silently fails. Lifting the suppression here and\n * directly calling trigger.focus() bypasses that restriction.\n */\n override focus(options?: FocusOptions) {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger) {\n super.focus(options);\n\n return;\n }\n\n $trigger.removeAttribute('tabindex');\n $trigger.focus(options);\n }\n\n /** Caches a weak reference to the first assigned `rc-menu` element. */\n protected _handleMenuSlotChange(e: Event) {\n const $slot = e.currentTarget as HTMLSlotElement;\n const $elements = $slot.assignedElements();\n const $menu = $elements.find(($el) => $el.tagName === 'RC-MENU') as RCMenu | undefined;\n\n if ($menu) {\n this._$menu = new WeakRef($menu);\n }\n }\n\n /**\n * Keyboard navigation handler.\n *\n * Opens or closes the menu in response to arrow/Enter/Escape keys.\n **/\n protected _onNavigate = (action: KeyboardNavigationAction) => {\n switch (action) {\n case 'open-to-first':\n case 'activate':\n this.openMenu();\n break;\n\n case 'open-to-last':\n this.openMenu('last');\n break;\n\n case 'escape':\n this.closeMenu();\n break;\n }\n };\n\n /** Prevents the default button action and delegates to `toggleMenu`. */\n protected _handleTriggerClick(e: MouseEvent) {\n e.preventDefault();\n\n this.toggleMenu();\n }\n\n /** Closes the menu when an `rc-menu-close` event bubbles up from the popup. */\n protected _handleMenuClose() {\n this.closeMenu();\n }\n\n /** Closes the menu when an `rc-menu-activate` event bubbles up from the popup. */\n protected _handleMenuActivate() {\n this.closeMenu();\n }\n\n /** Closes the menu on clicks outside this component (light dismiss). */\n protected _handleDocumentClick(e: MouseEvent) {\n if (!this.open) {\n return;\n }\n\n const path = e.composedPath();\n\n if (path.includes(this)) {\n return;\n }\n\n this.closeMenu(false);\n }\n\n /** Syncs trigger ARIA when `open` changes; updates anchor placement when `placement` or `orientation` change. */\n protected override updated(changedProperties: Map<PropertyKey, unknown>) {\n super.updated(changedProperties);\n\n if (changedProperties.has('open')) {\n this._syncTriggerAria();\n\n this._popoverCtrl.sync(this.open);\n\n if (this.open && this._$popup?.isConnected) {\n this._anchorCtrl.update();\n }\n }\n\n if (changedProperties.has('placement') || changedProperties.has('orientation')) {\n this._anchorCtrl.setOptions({ placement: this._effectivePlacement });\n }\n }\n\n protected _setOpen(open: boolean, dispatch: boolean): void {\n const oldValue = this.open;\n\n if (oldValue === open) {\n return;\n }\n\n if (this._open === undefined) {\n this._uncontrolledOpen = open;\n }\n\n this.requestUpdate('open', oldValue);\n this._syncTriggerAria();\n\n if (dispatch) {\n this._dispatchToggle(open);\n }\n }\n\n /** Writes `aria-haspopup` and `aria-expanded` to the trigger button to match the current open state. */\n protected _syncTriggerAria(): void {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger?.isConnected) {\n return;\n }\n\n $trigger.setAttribute('aria-haspopup', 'menu');\n $trigger.setAttribute('aria-expanded', String(this.open));\n }\n\n protected override render() {\n return html`\n <div id=\"root\" part=\"root\">\n <div\n id=\"trigger-wrap\"\n ${keyNavigation(this._onNavigate, {\n navigationAxis: this._resolvedOrientation,\n handleNavAxis: false,\n handleOpenAxis: true,\n handleActivate: true,\n handleEscape: this.open,\n })}\n ${keyInteraction()}\n @click=${this._handleTriggerClick}\n >\n <slot name=\"trigger\" @slotchange=${this._handleTriggerSlotChange}></slot>\n <slot\n name=\"indicator\"\n aria-hidden=\"true\"\n @slotchange=${this._handleIndicatorSlotChange}\n ></slot>\n </div>\n\n <div\n id=\"popup\"\n part=\"popup\"\n popover=\"manual\"\n @rc-menu-close=${this._handleMenuClose}\n @rc-menu-activate=${this._handleMenuActivate}\n >\n <slot @slotchange=${this._handleMenuSlotChange}></slot>\n </div>\n </div>\n `;\n }\n}\n\nexport default RCMenuButton;\n"],"names":["menuButtonStyles","css","LIGHT_DOM_CSS","cssText","_RCMenuButton","_a","LitElement","AnchorController","PopoverController","action","root","style","value","oldValue","$menubar","orientation","rendered","layoutOrientation","resolveFlow","focusTarget","$menu","returnFocus","open","e","$trigger","$slot","options","$el","changedProperties","dispatch","html","keyNavigation","keyInteraction","__decorateClass","property","query","state","RCMenuButton"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import "@rcarls/rc-menu/define";
2
- import { R as t } from "./rc-menu-button-DPs2AT2d.js";
2
+ import { R as t } from "./rc-menu-button-C2gavu7b.js";
3
3
  customElements.get("rc-menu-button") || customElements.define("rc-menu-button", t);
4
4
  export {
5
5
  t as RCMenuButton
@@ -1,4 +1,4 @@
1
- import { R as e } from "./rc-menu-button-DPs2AT2d.js";
1
+ import { R as e } from "./rc-menu-button-C2gavu7b.js";
2
2
  export {
3
3
  e as RCMenuButton
4
4
  };
@@ -1,5 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
- import { AnchorController, AnchorPlacement, KeyboardNavigationAction } from '@rcarls/rc-common';
2
+ import { AnchorController, AnchorPlacement, KeyboardNavigationAction, PopoverController } from '@rcarls/rc-common';
3
3
  import { RCMenu } from '@rcarls/rc-menu';
4
4
  declare global {
5
5
  interface HTMLElementTagNameMap {
@@ -60,6 +60,7 @@ export interface RCMenuButtonToggleEvent {
60
60
  * space there.
61
61
  * @cssprop [--rc-menu-button-touch-target-overlap-inline-end=0px] - The trailing-edge
62
62
  * counterpart to `--rc-menu-button-touch-target-overlap-inline-start`.
63
+ * @cssprop [--rc-menu-button-popup-duration=150ms] - Popup open/close fade transition duration.
63
64
  *
64
65
  * @csspart root - The root container element
65
66
  * @csspart popup - The popup container element
@@ -67,9 +68,15 @@ export interface RCMenuButtonToggleEvent {
67
68
  * @attr open - Whether the menu popup is visible. Controlled mode: host writes silently.
68
69
  * @attr default-open - Initial open state for uncontrolled mode. Ignored after the first
69
70
  * controlled write to `open`.
70
- * @attr orientation - Arrow-key axis for opening the menu. Inherits from a parent
71
- * `rc-menubar` or `[role="menubar"]` when unset.
72
- * @attr placement - Preferred placement of the popup relative to the trigger.
71
+ * @attr orientation - The layout the trigger sits in: `horizontal` along the inline axis,
72
+ * like a menubar row, or `vertical` along the block axis. It picks the arrow key that opens
73
+ * the menu, following how that layout renders, so the keys turn with RTL and vertical text.
74
+ * Inherits from a parent `rc-menubar` or `[role="menubar"]` when unset.
75
+ * @attr placement - Preferred placement of the popup relative to the trigger, in logical terms:
76
+ * `block-start`/`block-end` (above or below in horizontal text, beside it in vertical text) or
77
+ * `inline-start`/`inline-end` (beside it along the reading direction), each with an optional
78
+ * `-start`/`-end` edge alignment. Defaults to `block-end-start`, or `inline-end-start` under
79
+ * vertical orientation when unset.
73
80
  * @attr icon-only - Hints that the slotted trigger has no visible label, so themes can size
74
81
  * it and its touch target as an icon button (see `--rc-menu-button-icon-size` and
75
82
  * `--rc-menu-button-touch-target-*` above). Purely a styling hook; RCMenuButton does not read
@@ -107,18 +114,30 @@ export declare class RCMenuButton extends LitElement {
107
114
  /** Applies the uncontrolled default before any controlled write occurs. */
108
115
  set defaultOpen(value: boolean);
109
116
  /**
110
- * Orientation of this menu button, affects which arrow keys open/close the menu.
117
+ * The layout this menu button sits in, which picks the arrow keys that open
118
+ * and close the menu. `horizontal` runs along the inline axis and `vertical`
119
+ * along the block axis; the keys follow how that layout renders.
111
120
  *
112
121
  * If not set, inherits from a parent rc-menubar or element with role="menubar".
113
122
  */
114
123
  orientation: 'horizontal' | 'vertical' | undefined;
115
- /** Preferred placement of the popup relative to the trigger button. */
116
- placement: AnchorPlacement;
124
+ private _placement;
125
+ /**
126
+ * Preferred placement of the popup relative to the trigger button. Reads the
127
+ * default until the author sets one.
128
+ *
129
+ * @default 'block-end-start'
130
+ */
131
+ get placement(): AnchorPlacement;
132
+ set placement(value: AnchorPlacement | null | undefined);
133
+ /** The placement used when the author sets none. Subclasses override it. */
134
+ protected get _defaultPlacement(): AnchorPlacement;
117
135
  /**
118
136
  * Popup placement adjusted for orientation.
119
137
  *
120
- * Switches from `bottom-start` to `right-start` when the resolved orientation
121
- * is `vertical`, so vertical menubars open submenus to the side.
138
+ * Without an author placement, a menu button whose resolved orientation is
139
+ * `vertical` opens at `inline-end-start`, so vertical menubars open
140
+ * submenus to the side, toward the inline end.
122
141
  */
123
142
  protected get _effectivePlacement(): AnchorPlacement;
124
143
  /**
@@ -136,6 +155,7 @@ export declare class RCMenuButton extends LitElement {
136
155
  protected _$menu: WeakRef<RCMenu> | undefined;
137
156
  /** Positions the popup relative to the trigger using CSS anchor positioning. */
138
157
  protected _anchorCtrl: AnchorController;
158
+ protected _popoverCtrl: PopoverController;
139
159
  /** Bound handler for document click (light dismiss). Stored for `removeEventListener` pairing. */
140
160
  protected _boundHandleDocumentClick: (e: MouseEvent) => void;
141
161
  /**
@@ -216,6 +236,6 @@ export declare class RCMenuButton extends LitElement {
216
236
  protected _setOpen(open: boolean, dispatch: boolean): void;
217
237
  /** Writes `aria-haspopup` and `aria-expanded` to the trigger button to match the current open state. */
218
238
  protected _syncTriggerAria(): void;
219
- protected render(): import('lit').TemplateResult<1>;
239
+ protected render(): import('lit-html').TemplateResult<1>;
220
240
  }
221
241
  export default RCMenuButton;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.6.0",
6
+ "version": "0.7.0",
7
7
  "description": "Trigger button that opens an rc-menu popup.",
8
8
  "repository": {
9
9
  "type": "git",
@@ -45,8 +45,8 @@
45
45
  "test:browser:firefox": "vitest --run --project=firefox"
46
46
  },
47
47
  "dependencies": {
48
- "@rcarls/rc-common": "0.6.0",
49
- "@rcarls/rc-menu": "0.6.0"
48
+ "@rcarls/rc-common": "0.7.0",
49
+ "@rcarls/rc-menu": "0.7.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@custom-elements-manifest/analyzer": "0.11.0",