@rcarls/rc-menu-button 0.0.0-next-20260921045401

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,222 @@
1
+ import { LitElement } from 'lit';
2
+ import { AnchorController, AnchorPlacement, KeyboardNavigationAction } from '@rcarls/rc-common';
3
+ import { RCMenu } from '@rcarls/rc-menu';
4
+ declare global {
5
+ interface HTMLElementTagNameMap {
6
+ 'rc-menu-button': RCMenuButton;
7
+ }
8
+ }
9
+ /** Detail payload for the `rc-menu-button-toggle` event. */
10
+ export interface RCMenuButtonToggleEvent {
11
+ /** Whether the menu transitioned to open (`true`) or closed (`false`). */
12
+ open: boolean;
13
+ }
14
+ /**
15
+ * Trigger button that opens an rc-menu popup, following the WAI-ARIA Menu Button pattern.
16
+ *
17
+ *
18
+ * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-menu-button rc-menu-button docs}
19
+ * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ WAI-ARIA Menu Button pattern}
20
+ *
21
+ * @slot trigger - The button element that triggers the menu
22
+ * @slot indicator - Optional decorative indicator rendered at the trigger's inline end
23
+ * @slot default - The rc-menu element to display as popup
24
+ *
25
+ * @fires rc-menu-button-toggle - Fired when the menu opens or closes
26
+ *
27
+ * @cssprop [--rc-menu-button-trigger-block-size=var(--rc-control-block-size)] - Minimum block size of the trigger
28
+ * @cssprop [--rc-menu-button-trigger-padding-block=var(--rc-control-padding-block)] - Trigger block-axis padding
29
+ * @cssprop [--rc-menu-button-trigger-padding-inline=var(--rc-control-padding-inline)] - Trigger inline-axis padding
30
+ * @cssprop [--rc-menu-button-trigger-gap=var(--rc-item-gap)] - Gap between flex children in the trigger
31
+ * @cssprop [--rc-menu-button-trigger-border=var(--rc-border)] - Trigger border
32
+ * @cssprop [--rc-menu-button-trigger-radius=var(--rc-control-radius)] - Trigger border radius
33
+ * @cssprop [--rc-menu-button-trigger-background=var(--rc-button-bg)] - Trigger background
34
+ * @cssprop [--rc-menu-button-trigger-color=var(--rc-button-text)] - Trigger text color
35
+ * @cssprop [--rc-menu-button-trigger-transition] - CSS transition applied to the trigger
36
+ * @cssprop [--rc-menu-button-trigger-hover-background=color-mix(in srgb, Highlight 8%, transparent)] - Trigger hover background
37
+ * @cssprop [--rc-menu-button-trigger-hover-color=inherit] - Trigger hover text color
38
+ * @cssprop [--rc-menu-button-trigger-hover-border-color=currentColor] - Trigger hover border color
39
+ * @cssprop [--rc-menu-button-trigger-open-background=color-mix(in srgb, Highlight 12%, transparent)] - Trigger background when the menu is open
40
+ * @cssprop [--rc-menu-button-trigger-open-color=inherit] - Trigger text color when the menu is open
41
+ * @cssprop [--rc-menu-button-trigger-open-border-color=currentColor] - Trigger border color when the menu is open
42
+ * @cssprop [--rc-menu-button-indicator-size=1em] - Inline and block size of the slotted indicator
43
+ * @cssprop [--rc-menu-button-indicator-color=currentColor] - Color of the slotted indicator
44
+ * @cssprop [--rc-menu-button-indicator-inset=var(--rc-menu-button-trigger-padding-inline)] - Indicator distance from the trigger's inline end
45
+ * @cssprop [--rc-menu-button-icon-size] - Inline size of an `icon-only` trigger's visible box.
46
+ * Square by default (equal to `--rc-menu-button-trigger-block-size`); set narrower or wider
47
+ * to change only the trigger's width, independent of its height.
48
+ * @cssprop [--rc-menu-button-touch-target-block-size=3rem] - Minimum accessible touch target
49
+ * block size for an `icon-only` trigger. A floor, not a fixed size: has no effect once
50
+ * `--rc-menu-button-trigger-block-size` already meets or exceeds it. Grows the shadow-DOM
51
+ * trigger wrapper (reserving layout space) and the light-DOM hit-slop (the actual larger
52
+ * clickable region) together; the visible trigger itself stays at its own size, centered.
53
+ * @cssprop [--rc-menu-button-touch-target-inline-size] - Minimum accessible touch target inline
54
+ * size for an `icon-only` trigger. Defers to `--rc-menu-button-touch-target-block-size` when
55
+ * unset.
56
+ * @cssprop [--rc-menu-button-touch-target-overlap-inline-start=0px] - Zero by default. A theme
57
+ * or consumer sets this on an `icon-only` trigger that sits at a real leading edge (no
58
+ * neighbor on that side) to let its touch-target inflation overlap into whatever sits just
59
+ * outside the host, such as a container's own edge padding, instead of also reserving layout
60
+ * space there.
61
+ * @cssprop [--rc-menu-button-touch-target-overlap-inline-end=0px] - The trailing-edge
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.
64
+ *
65
+ * @csspart root - The root container element
66
+ * @csspart popup - The popup container element
67
+ *
68
+ * @attr open - Whether the menu popup is visible. Controlled mode: host writes silently.
69
+ * @attr default-open - Initial open state for uncontrolled mode. Ignored after the first
70
+ * controlled write to `open`.
71
+ * @attr orientation - Arrow-key axis for opening the menu. Inherits from a parent
72
+ * `rc-menubar` or `[role="menubar"]` when unset.
73
+ * @attr placement - Preferred placement of the popup relative to the trigger.
74
+ * @attr icon-only - Hints that the slotted trigger has no visible label, so themes can size
75
+ * it and its touch target as an icon button (see `--rc-menu-button-icon-size` and
76
+ * `--rc-menu-button-touch-target-*` above). Purely a styling hook; RCMenuButton does not read
77
+ * or derive it — set it whenever the trigger is rendered icon-only.
78
+ */
79
+ export declare class RCMenuButton extends LitElement {
80
+ static styles: import('lit').CSSResult[];
81
+ /**
82
+ * Enables delegatesFocus so the browser routes focus() on the host element
83
+ * to the slotted trigger button.
84
+ */
85
+ static shadowRootOptions: ShadowRootInit;
86
+ protected static readonly _styledRoots: Set<Document | ShadowRoot>;
87
+ protected static _ensureBaseStyles(root: Document | ShadowRoot): void;
88
+ private _defaultOpen;
89
+ private _open;
90
+ private _uncontrolledOpen;
91
+ private _openInitialized;
92
+ /**
93
+ * Whether the menu is currently open.
94
+ *
95
+ * In controlled mode the host owns this value; writing the property applies
96
+ * the state silently without dispatching `rc-menu-button-toggle`.
97
+ */
98
+ get open(): boolean;
99
+ /** Applies open state in controlled mode without dispatching an event. */
100
+ set open(value: boolean | undefined);
101
+ /**
102
+ * Initial open state for uncontrolled mode.
103
+ *
104
+ * Ignored after the first controlled write; use `open` to update state
105
+ * programmatically after mount.
106
+ */
107
+ get defaultOpen(): boolean;
108
+ /** Applies the uncontrolled default before any controlled write occurs. */
109
+ set defaultOpen(value: boolean);
110
+ /**
111
+ * Orientation of this menu button, affects which arrow keys open/close the menu.
112
+ *
113
+ * If not set, inherits from a parent rc-menubar or element with role="menubar".
114
+ */
115
+ orientation: 'horizontal' | 'vertical' | undefined;
116
+ /** Preferred placement of the popup relative to the trigger button. */
117
+ placement: AnchorPlacement;
118
+ /**
119
+ * Popup placement adjusted for orientation.
120
+ *
121
+ * Switches from `bottom-start` to `right-start` when the resolved orientation
122
+ * is `vertical`, so vertical menubars open submenus to the side.
123
+ */
124
+ protected get _effectivePlacement(): AnchorPlacement;
125
+ /**
126
+ * Resolved orientation, considering inheritance from parent menubar.
127
+ * Falls back to 'horizontal' if no orientation is set or inherited.
128
+ */
129
+ protected get _resolvedOrientation(): 'horizontal' | 'vertical';
130
+ /** Shadow DOM root container. Serves as the anchor element for `_anchorCtrl`. */
131
+ protected _$root: HTMLElement;
132
+ /** Shadow DOM popup container. Hidden when `open` is `false`. */
133
+ protected _$popup: HTMLElement;
134
+ /** Weak reference to the slotted trigger button. */
135
+ protected _$trigger: WeakRef<HTMLElement> | undefined;
136
+ /** Weak reference to the slotted `rc-menu` element. */
137
+ protected _$menu: WeakRef<RCMenu> | undefined;
138
+ /** Positions the popup relative to the trigger using CSS anchor positioning. */
139
+ protected _anchorCtrl: AnchorController;
140
+ /** Bound handler for document click (light dismiss). Stored for `removeEventListener` pairing. */
141
+ protected _boundHandleDocumentClick: (e: MouseEvent) => void;
142
+ /**
143
+ * Watches the host's own `tabindex` attribute so the trigger button's
144
+ * tabindex can be kept in sync.
145
+ *
146
+ * When a parent (e.g. rc-toolbar) places the
147
+ * host under roving-tabindex management it sets/removes tabindex on the
148
+ * host; we mirror that to the trigger so it doesn't become an independent
149
+ * tab stop. When the host has no tabindex the trigger keeps its natural
150
+ * tabindex=0 for standalone usage.
151
+ */
152
+ protected _tabObserver?: MutationObserver;
153
+ /** Installs the tabindex mutation observer and the document-level click listener. */
154
+ connectedCallback(): void;
155
+ /** Disconnects the tabindex observer and removes the document-level click listener. */
156
+ disconnectedCallback(): void;
157
+ /**
158
+ * Opens the menu and moves focus into it.
159
+ *
160
+ * @param focusTarget - which item receives initial focus when the menu opens
161
+ */
162
+ openMenu(focusTarget?: 'first' | 'last'): void;
163
+ /**
164
+ * Closes the menu.
165
+ *
166
+ * @param returnFocus - when `true`, returns focus to the trigger button after closing
167
+ */
168
+ closeMenu(returnFocus?: boolean): void;
169
+ /** Toggles the menu between open and closed. */
170
+ toggleMenu(): void;
171
+ /** Dispatches the `rc-menu-button-toggle` bubbling composed event with the requested open state. */
172
+ protected _dispatchToggle(open?: boolean): void;
173
+ /** Caches a weak reference to the first assigned trigger element and syncs its ARIA and tabindex state. */
174
+ protected _handleTriggerSlotChange(e: Event): void;
175
+ /** Tracks optional indicator content so the trigger reserves its inline-end space. */
176
+ protected _handleIndicatorSlotChange(e: Event): void;
177
+ /**
178
+ * Mirrors the host's `tabindex` attribute to the trigger button.
179
+ *
180
+ * When a roving-tabindex parent (e.g. rc-toolbar) marks this host inactive
181
+ * with `tabindex="-1"`, the trigger must also be suppressed so it does not
182
+ * become an independent tab stop. When the host is the active item
183
+ * (`tabindex="0"`) or standalone (no tabindex), the trigger retains its
184
+ * natural focusability so that Chrome's delegatesFocus can route sequential
185
+ * Tab navigation through the host to the trigger.
186
+ */
187
+ protected _syncTriggerTabindex(): void;
188
+ /**
189
+ * Overrides the default focus() so that programmatic focus calls (e.g. from
190
+ * a roving-tabindex parent navigating back to this element via arrow keys)
191
+ * always reach the trigger.
192
+ *
193
+ * Chrome's delegatesFocus will not delegate to a tabindex="-1" element, so
194
+ * when the toolbar marks this host inactive it suppresses the trigger — and
195
+ * the next focusItem() call silently fails. Lifting the suppression here and
196
+ * directly calling trigger.focus() bypasses that restriction.
197
+ */
198
+ focus(options?: FocusOptions): void;
199
+ /** Caches a weak reference to the first assigned `rc-menu` element. */
200
+ protected _handleMenuSlotChange(e: Event): void;
201
+ /**
202
+ * Keyboard navigation handler.
203
+ *
204
+ * Opens or closes the menu in response to arrow/Enter/Escape keys.
205
+ **/
206
+ protected _onNavigate: (action: KeyboardNavigationAction) => void;
207
+ /** Prevents the default button action and delegates to `toggleMenu`. */
208
+ protected _handleTriggerClick(e: MouseEvent): void;
209
+ /** Closes the menu when an `rc-menu-close` event bubbles up from the popup. */
210
+ protected _handleMenuClose(): void;
211
+ /** Closes the menu when an `rc-menu-activate` event bubbles up from the popup. */
212
+ protected _handleMenuActivate(): void;
213
+ /** Closes the menu on clicks outside this component (light dismiss). */
214
+ protected _handleDocumentClick(e: MouseEvent): void;
215
+ /** Syncs trigger ARIA when `open` changes; updates anchor placement when `placement` or `orientation` change. */
216
+ protected updated(changedProperties: Map<PropertyKey, unknown>): void;
217
+ protected _setOpen(open: boolean, dispatch: boolean): void;
218
+ /** Writes `aria-haspopup` and `aria-expanded` to the trigger button to match the current open state. */
219
+ protected _syncTriggerAria(): void;
220
+ protected render(): import('lit').TemplateResult<1>;
221
+ }
222
+ export default RCMenuButton;
@@ -0,0 +1,2 @@
1
+ export declare const menuButtonStyles: import('lit').CSSResult;
2
+ export default menuButtonStyles;
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@rcarls/rc-menu-button",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.0.0-next-20260921045401",
7
+ "description": "Trigger button that opens an rc-menu popup.",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-menu-button"
12
+ },
13
+ "homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-menu-button",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-menu-button.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-menu-button/src/index.d.ts",
24
+ "default": "./dist/rc-menu-button.js"
25
+ }
26
+ },
27
+ "./define": {
28
+ "import": {
29
+ "types": "./dist/types/packages/rc-menu-button/src/define.d.ts",
30
+ "default": "./dist/rc-menu-button-define.js"
31
+ }
32
+ }
33
+ },
34
+ "types": "./dist/types/packages/rc-menu-button/src/index.d.ts",
35
+ "sideEffects": [
36
+ "./dist/rc-menu-button-define.js"
37
+ ],
38
+ "customElements": "dist/custom-elements.json",
39
+ "scripts": {
40
+ "build": "tsc && vite build && cem analyze",
41
+ "cem:analyze": "cem analyze",
42
+ "preview": "vite preview",
43
+ "test:browser": "vitest --run",
44
+ "test:browser:chrome": "vitest --run --project=chromium",
45
+ "test:browser:firefox": "vitest --run --project=firefox"
46
+ },
47
+ "dependencies": {
48
+ "@rcarls/rc-common": "0.0.0-next-20260921045401",
49
+ "@rcarls/rc-menu": "0.0.0-next-20260921045401"
50
+ },
51
+ "devDependencies": {
52
+ "@custom-elements-manifest/analyzer": "0.11.0",
53
+ "@types/node": "^25.6.0",
54
+ "@vitest/browser-playwright": "4.1.5",
55
+ "lit": "^3.0.0",
56
+ "playwright": "^1.56.0",
57
+ "rollup": "^4.60.2",
58
+ "typescript": "~5.9.3",
59
+ "vite": "^7.1.7",
60
+ "vite-plugin-dts": "^4.5.4",
61
+ "vitest": "^4.0.6",
62
+ "vitest-browser-lit": "^1.0.1"
63
+ },
64
+ "peerDependencies": {
65
+ "lit": "^3.0.0"
66
+ }
67
+ }