@rcarls/rc-menu 0.3.2 → 0.4.1

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # @rcarls/rc-menu
2
+
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @rcarls/rc-common@0.4.1
8
+
9
+ ## 0.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 3e89095: Replace rc-menu's generated submenu glyph with an author-supplied `indicator`
14
+ slot on rc-menu-button. Keep the decorative indicator inside the trigger bounds,
15
+ reserve label space for it, and expose theme-neutral size, color, and inset
16
+ tokens for Material, Substrate, and application themes.
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [e57277f]
21
+ - Updated dependencies [ccca8e2]
22
+ - Updated dependencies [037b1b3]
23
+ - @rcarls/rc-common@0.4.0
24
+
25
+ ## 0.3.2
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [88b4086]
30
+ - @rcarls/rc-common@0.3.2
31
+
32
+ ## 0.3.1
33
+
34
+ ### Patch Changes
35
+
36
+ - @rcarls/rc-common@0.3.1
37
+
38
+ ## 0.3.0
39
+
40
+ ### Changed
41
+
42
+ - Normalize menu row styling around the light-DOM `rc-base` style contract.
43
+ - Update package metadata, README intro, and docs links.
44
+
45
+ ### Dependencies
46
+
47
+ - Sync internal dependencies to 0.3.0.
48
+
49
+ ## 0.2.0
50
+
51
+ ### Patch Changes
52
+
53
+ - @rcarls/rc-common@0.2.0
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # `@rcarls/rc-menu`
2
2
 
3
- Menu popup for command surfaces with keyboard navigation and typed activation events, following the [WAI-ARIA Menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu/).
3
+ Menu popup for command surfaces with keyboard navigation and typed activation
4
+ events, following the
5
+ [WAI-ARIA Menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu/).
4
6
 
5
7
  Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-menu](https://richardcarls.github.io/rc-webcomponents/components/rc-menu).
6
8
 
@@ -15,113 +17,73 @@ npm install @rcarls/rc-menu
15
17
  ## Import
16
18
 
17
19
  ```js
18
- import '@rcarls/rc-menu'; // side-effect: registers <rc-menu>
19
- import { RCMenu } from '@rcarls/rc-menu'; // named class export
20
+ import '@rcarls/rc-menu/define';
21
+ import { RCMenu } from '@rcarls/rc-menu';
20
22
  ```
21
23
 
24
+ The `define` entry point registers `<rc-menu>`. The package root exports the
25
+ class without registering the custom element.
26
+
22
27
  ---
23
28
 
24
29
  ## Basic usage
25
30
 
26
- Slot any focusable elements as menu items. Non-focusable elements (separators, headings) are rendered but excluded from keyboard navigation.
31
+ Slot any focusable elements as menu items. Non-focusable elements (separators,
32
+ headings) are rendered but excluded from keyboard navigation.
27
33
 
28
34
  ```html
29
35
  <rc-menu label="File">
30
- <button>New</button>
31
- <button>Open…</button>
32
- <hr aria-hidden="true" />
33
- <button disabled>Save</button>
34
- <button>Save As…</button>
36
+ <button type="button" value="new">New</button>
37
+ <button type="button" value="open">Open…</button>
38
+ <hr />
39
+ <button type="button" disabled>Save</button>
40
+ <button type="button" value="save-as">Save As…</button>
35
41
  </rc-menu>
36
42
  ```
37
43
 
38
- `rc-menu` is typically used inside an [`rc-menu-button`](../rc-menu-button/README.md) or [`rc-menubar`](../rc-menubar/README.md), which handle positioning and trigger wiring. It can also be used standalone as a context menu.
39
-
40
- ---
41
-
42
- ## API
43
-
44
- ### Properties / attributes
45
-
46
- | Property | Attribute | Type | Default | Description |
47
- |---|---|---|---|---|
48
- | `label` | `label` | `string` | `''` | Value of the `aria-label` on the menu container. Required for accessibility when no visible label is present. |
44
+ For a cascading item, use an [`rc-menu-button`](../rc-menu-button/README.md)
45
+ and provide its visual affordance through the `indicator` slot. `rc-menu` does
46
+ not generate an indicator glyph, so applications and themes retain control of
47
+ iconography and writing-direction behavior.
49
48
 
50
- ### CSS custom properties
49
+ `rc-menu` is typically used inside an
50
+ [`rc-menu-button`](../rc-menu-button/README.md) or
51
+ [`rc-menubar`](../rc-menubar/README.md), which handle positioning and trigger
52
+ wiring. It can also be used standalone as a context menu.
51
53
 
52
- | Property | Default | Description |
53
- |---|---|---|
54
- | `--rc-menu-min-width` | `10em` | Minimum width of the menu container |
55
- | `--rc-menu-padding-block` | `0.25em` | Block-axis (top/bottom) padding inside the root |
56
- | `--rc-menu-background` | `Canvas` | Menu background color (system color keyword) |
57
- | `--rc-menu-border` | `1px solid ButtonBorder` | Menu border |
58
- | `--rc-menu-shadow` | `0 2px 8px rgba(0,0,0,0.15)` | Box shadow |
54
+ ## Interaction model
59
55
 
60
- ### CSS parts
56
+ DOM focus remains on the `rc-menu` host. Arrow keys, Home, and End move a
57
+ virtual cursor exposed through `aria-activedescendant`; slotted menu items keep
58
+ `tabindex="-1"`. The `focusFirst()`, `focusLast()`, `focusItem()`, and
59
+ `focusItemAt()` methods use the same model: they focus the host and select an
60
+ active item without moving DOM focus into that item.
61
61
 
62
- | Part | Element | Description |
63
- |---|---|---|
64
- | `root` | `div[role="menu"]` | The inner menu container |
62
+ Direct focusable children become `role="menuitem"` entries unless they already
63
+ use `menuitemcheckbox` or `menuitemradio`. A labeled `role="group"` may contain
64
+ one level of related items. Checkbox and radio items maintain `aria-checked`,
65
+ while `<hr>` and `role="separator"` elements remain outside navigation.
65
66
 
66
- ### Slots
67
+ ## Events
67
68
 
68
- | Slot | Description |
69
- |---|---|
70
- | *(default)* | Menu items. Only focusable elements participate in keyboard navigation. `<button>`, `<a href>`, elements with `tabindex`, and `<input>`/`<select>`/`<textarea>` are recognised as focusable; all others are skipped. |
69
+ Both events bubble across shadow boundaries and are not cancelable.
71
70
 
72
- ### Events
71
+ | Event | Detail | When |
72
+ | ------------------ | --------------------------------- | ---------------------------------------------------------- |
73
+ | `rc-menu-activate` | `{ item, value, text, checked? }` | Enter, Space, or a pointer click activates an enabled item |
74
+ | `rc-menu-close` | `{ reason: 'escape' }` | Escape requests that the containing popup close |
73
75
 
74
- | Event | Bubbles | Cancelable | Detail | When |
75
- |---|---|---|---|---|
76
- | `rc-menu-activate` | Yes (composed) | No | `{ item: HTMLElement }` | User presses Enter or Space on a focused item |
77
- | `rc-menu-close` | Yes (composed) | No | — | User presses Escape |
76
+ `value` comes from `data-value` or `value`, `text` is the item's trimmed text
77
+ content, and `checked` reports the resulting checkbox or radio state.
78
78
 
79
- ### Public methods
80
-
81
- ```ts
82
- focusFirst(): void // Focus the first navigable item
83
- focusLast(): void // Focus the last navigable item
84
- focusItem(item: HTMLElement): void // Focus a specific item
85
- focusItemAt(index: number): void // Focus item at zero-based index
86
- ```
87
-
88
- ### Read-only getters
89
-
90
- ```ts
91
- items: HTMLElement[] // All currently navigable (focusable) slotted items
92
- firstItem: HTMLElement | undefined
93
- lastItem: HTMLElement | undefined
94
- nextItem: HTMLElement | undefined // Relative to the currently focused item
95
- previousItem: HTMLElement | undefined
96
- ```
97
-
98
- ---
99
-
100
- ## Keyboard behaviour
101
-
102
- | Key | Action |
103
- |---|---|
104
- | `ArrowDown` | Focus next item (wraps) |
105
- | `ArrowUp` | Focus previous item (wraps) |
106
- | `Home` | Focus first item |
107
- | `End` | Focus last item |
108
- | `Enter` / `Space` | Activate focused item (fires `rc-menu-activate`) |
109
- | `Escape` | Fire `rc-menu-close` |
110
-
111
- ---
112
-
113
- ## ARIA
114
-
115
- | Attribute | Where | Value |
116
- |---|---|---|
117
- | `role="menu"` | Root `div` | — |
118
- | `aria-label` | Root `div` | Value of `label` property |
119
- | `role="menuitem"` | Each slotted focusable item | Set dynamically on slot change |
120
- | `tabindex="0"` | Active item | One item is tabbable at a time (roving tabindex) |
121
- | `tabindex="-1"` | All other items | Removed from tab order |
122
-
123
- ---
79
+ ## Styling
124
80
 
125
- ## Browser support
81
+ Menu items remain in light DOM. `rc-menu` injects their structural base styles
82
+ once per containing document or shadow root under `@layer rc-base`; inherited
83
+ `--rc-menu-*` custom properties customize the host and rows. The component does
84
+ not expose CSS parts. Add keyboard hints with `[data-menu-shortcut]`, and provide
85
+ nested-menu indicators through `rc-menu-button`'s `indicator` slot.
126
86
 
127
- All modern browsers. Requires Web Components support (Chrome 67+, Firefox 63+, Safari 12.1+).
87
+ See the [component docs](https://richardcarls.github.io/rc-webcomponents/components/rc-menu)
88
+ for the generated API reference, including attributes, methods, slots, events,
89
+ and CSS custom properties.
@@ -79,6 +79,11 @@
79
79
  "name": "--rc-menu-shadow",
80
80
  "default": "0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)"
81
81
  },
82
+ {
83
+ "description": "Text color; falls back through --rc-field-text.",
84
+ "name": "--rc-menu-color",
85
+ "default": "CanvasText"
86
+ },
82
87
  {
83
88
  "description": "Minimum block size for menu item rows.",
84
89
  "name": "--rc-menu-item-min-block-size",
@@ -143,21 +148,6 @@
143
148
  "name": "--rc-menu-shortcut-color",
144
149
  "default": "GrayText"
145
150
  },
146
- {
147
- "description": "Text content for submenu indicators.",
148
- "name": "--rc-menu-submenu-indicator-content",
149
- "default": "'›'"
150
- },
151
- {
152
- "description": "Font size of submenu indicators.",
153
- "name": "--rc-menu-submenu-indicator-size",
154
- "default": "1em"
155
- },
156
- {
157
- "description": "Text color of submenu indicators.",
158
- "name": "--rc-menu-submenu-indicator-color",
159
- "default": "currentColor"
160
- },
161
151
  {
162
152
  "description": "Disabled menu item text color.",
163
153
  "name": "--rc-menu-disabled-color",
@@ -196,14 +186,14 @@
196
186
  ],
197
187
  "slots": [
198
188
  {
199
- "description": "Menu items, groups, and separators. Supported content model: - Focusable elements (`<button>`, `<a>`, `[tabindex]`) become navigable `role=\"menuitem\"` entries. - `role=\"menuitemcheckbox\"` / `role=\"menuitemradio\"` elements get a leading indicator column via `::before`; `aria-checked` is initialized when missing and updated on activation. - `role=\"group\"` divs group related items; label them with `aria-label` or `aria-labelledby`. Optionally add a visible `[data-group-label]` element as first child of the group for section headings. - `role=\"separator\"` / `<hr>` produce horizontal dividers between sections. - Within any item, `[data-menu-shortcut]` elements are pushed to the inline-end edge for keyboard hints. - Items with `aria-haspopup=\"menu\"` or `aria-haspopup=\"true\"` get a trailing submenu indicator.",
189
+ "description": "Menu items, groups, and separators. Supported content model: - Focusable elements (`<button>`, `<a>`, `[tabindex]`) become navigable `role=\"menuitem\"` entries. - `role=\"menuitemcheckbox\"` / `role=\"menuitemradio\"` elements get a leading indicator column via `::before`; `aria-checked` is initialized when missing and updated on activation. - `role=\"group\"` divs group related items; label them with `aria-label` or `aria-labelledby`. Optionally add a visible `[data-group-label]` element as first child of the group for section headings. - `role=\"separator\"` / `<hr>` produce horizontal dividers between sections. - Within any item, `[data-menu-shortcut]` elements are pushed to the inline-end edge for keyboard hints. - Nested menu triggers supply their own trailing indicator through `rc-menu-button`'s `indicator` slot.",
200
190
  "name": "default"
201
191
  }
202
192
  ],
203
193
  "members": [
204
194
  {
205
195
  "kind": "field",
206
- "name": "_styledRoots",
196
+ "name": "_$styledRoots",
207
197
  "privacy": "private",
208
198
  "static": true,
209
199
  "readonly": true,
@@ -221,7 +211,7 @@
221
211
  },
222
212
  "parameters": [
223
213
  {
224
- "name": "root",
214
+ "name": "$root",
225
215
  "type": {
226
216
  "text": "Document | ShadowRoot"
227
217
  }
@@ -240,7 +230,7 @@
240
230
  },
241
231
  {
242
232
  "kind": "field",
243
- "name": "_menuItems",
233
+ "name": "_$menuItems",
244
234
  "type": {
245
235
  "text": "Element[]"
246
236
  },
@@ -252,7 +242,7 @@
252
242
  "name": "_activeDescendantCtrl",
253
243
  "privacy": "protected",
254
244
  "readonly": true,
255
- "default": "new ActiveDescendantController(this, { host: () => this, items: () => this._menuItems, })"
245
+ "default": "new ActiveDescendantController(this, { host: () => this, items: () => this._$menuItems, })"
256
246
  },
257
247
  {
258
248
  "kind": "field",
@@ -293,7 +283,7 @@
293
283
  },
294
284
  "parameters": [
295
285
  {
296
- "name": "item",
286
+ "name": "$item",
297
287
  "optional": true,
298
288
  "type": {
299
289
  "text": "Element | null"
@@ -361,7 +351,7 @@
361
351
  },
362
352
  "parameters": [
363
353
  {
364
- "name": "slot",
354
+ "name": "$slot",
365
355
  "type": {
366
356
  "text": "HTMLSlotElement"
367
357
  }
@@ -390,7 +380,7 @@
390
380
  },
391
381
  "parameters": [
392
382
  {
393
- "name": "item",
383
+ "name": "$item",
394
384
  "type": {
395
385
  "text": "HTMLElement"
396
386
  }
@@ -425,12 +415,12 @@
425
415
  ],
426
416
  "attributes": [
427
417
  {
418
+ "description": "Accessible label for this menu. Synced to `aria-label` on the host.\n\nPrefer `aria-labelledby` for a visible label associated via ID.",
428
419
  "name": "label",
429
420
  "type": {
430
421
  "text": "string"
431
422
  },
432
423
  "default": "''",
433
- "description": "Accessible label for this menu. Synced to `aria-label` on the host.\n\nPrefer `aria-labelledby` for a visible label associated via ID.",
434
424
  "fieldName": "label"
435
425
  }
436
426
  ],
@@ -1,7 +1,7 @@
1
1
  import { css as u, LitElement as m, html as d } from "lit";
2
2
  import { property as h } from "lit/decorators.js";
3
- import { ActiveDescendantController as b, isFocusable as s } from "@rcarls/rc-common";
4
- const v = u`
3
+ import { ActiveDescendantController as v, isFocusable as s } from "@rcarls/rc-common";
4
+ const b = u`
5
5
  :host {
6
6
  display: flex;
7
7
  flex-direction: column;
@@ -107,15 +107,6 @@ const f = ["menuitem", "menuitemcheckbox", "menuitemradio"], k = `
107
107
  white-space: nowrap;
108
108
  }
109
109
 
110
- /* Submenu indicator for items that open a child menu. */
111
- rc-menu [aria-haspopup='menu']::after,
112
- rc-menu [aria-haspopup='true']::after {
113
- content: var(--rc-menu-submenu-indicator-content, '›');
114
- margin-inline-start: auto;
115
- font-size: var(--rc-menu-submenu-indicator-size, 1em);
116
- color: var(--rc-menu-submenu-indicator-color, currentColor);
117
- }
118
-
119
110
  /* Separators */
120
111
  rc-menu [role='separator'],
121
112
  rc-menu hr {
@@ -153,9 +144,9 @@ const f = ["menuitem", "menuitemcheckbox", "menuitemradio"], k = `
153
144
  var a;
154
145
  const l = (a = class extends m {
155
146
  constructor() {
156
- super(...arguments), this.label = "", this._menuItems = [], this._activeDescendantCtrl = new b(this, {
147
+ super(...arguments), this.label = "", this._$menuItems = [], this._activeDescendantCtrl = new v(this, {
157
148
  host: () => this,
158
- items: () => this._menuItems
149
+ items: () => this._$menuItems
159
150
  }), this._onKeydown = (e) => {
160
151
  switch (e.key) {
161
152
  case "ArrowDown": {
@@ -175,6 +166,7 @@ const l = (a = class extends m {
175
166
  break;
176
167
  }
177
168
  case "Enter":
169
+ // Falls through so both activation keys share the same handler.
178
170
  case " ": {
179
171
  e.preventDefault(), e.stopPropagation();
180
172
  const t = this._activeDescendantCtrl.activeItem;
@@ -216,9 +208,9 @@ const l = (a = class extends m {
216
208
  };
217
209
  }
218
210
  static _ensureBaseStyles(e) {
219
- if (a._styledRoots.has(e))
211
+ if (a._$styledRoots.has(e))
220
212
  return;
221
- a._styledRoots.add(e);
213
+ a._$styledRoots.add(e);
222
214
  const t = document.createElement("style");
223
215
  t.setAttribute("data-rc-light-dom-base", "rc-menu"), t.textContent = k, e instanceof Document ? e.head.appendChild(t) : e.appendChild(t);
224
216
  }
@@ -236,7 +228,7 @@ const l = (a = class extends m {
236
228
  }
237
229
  /** All navigable menu item elements in document order. */
238
230
  get items() {
239
- return this._menuItems;
231
+ return this._$menuItems;
240
232
  }
241
233
  /** Move the virtual cursor and DOM focus to the first navigable item. */
242
234
  focusFirst() {
@@ -249,7 +241,7 @@ const l = (a = class extends m {
249
241
  /**
250
242
  * Move the virtual cursor to a specific item.
251
243
  *
252
- * @param item - The element to make active. No-op when null or undefined.
244
+ * @param $item - The element to make active. No-op when null or undefined.
253
245
  */
254
246
  focusItem(e) {
255
247
  e && (this.focus(), this._activeDescendantCtrl.navigateToItem(e));
@@ -267,7 +259,7 @@ const l = (a = class extends m {
267
259
  }
268
260
  _onSlotChange(e) {
269
261
  const t = e.currentTarget;
270
- this._menuItems = this._collectMenuItems(t), this._initMenuItems();
262
+ this._$menuItems = this._collectMenuItems(t), this._initMenuItems();
271
263
  }
272
264
  /**
273
265
  * Collects navigable menu items from the slot, supporting `role="group"`
@@ -294,7 +286,7 @@ const l = (a = class extends m {
294
286
  return t;
295
287
  }
296
288
  _initMenuItems() {
297
- this._menuItems.forEach((e) => {
289
+ this._$menuItems.forEach((e) => {
298
290
  const t = e.getAttribute("role");
299
291
  (!t || !f.includes(t)) && e.setAttribute("role", "menuitem"), e.setAttribute("tabindex", "-1"), (e.getAttribute("role") === "menuitemcheckbox" || e.getAttribute("role") === "menuitemradio") && !e.hasAttribute("aria-checked") && e.setAttribute("aria-checked", "false");
300
292
  });
@@ -312,7 +304,7 @@ const l = (a = class extends m {
312
304
  return "true";
313
305
  }
314
306
  }
315
- }, a.styles = [v], a._styledRoots = /* @__PURE__ */ new Set(), a);
307
+ }, a.styles = [b], a._$styledRoots = /* @__PURE__ */ new Set(), a);
316
308
  g([
317
309
  h({ type: String })
318
310
  ], l.prototype, "label");
@@ -320,4 +312,4 @@ let C = l;
320
312
  export {
321
313
  C as R
322
314
  };
323
- //# sourceMappingURL=rc-menu-Drg26iOK.js.map
315
+ //# sourceMappingURL=rc-menu-DJ8upIAd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-menu-DJ8upIAd.js","sources":["../src/rc-menu.styles.ts","../src/rc-menu.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const menuStyles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-width: var(--rc-menu-min-width, 10em);\n padding-block: var(--rc-menu-padding-block, var(--rc-control-padding-block, 0.25em));\n background: var(--rc-menu-background, var(--rc-surface, Canvas));\n border: var(--rc-menu-border, var(--rc-border, 1px solid ButtonBorder));\n border-radius: var(--rc-menu-radius, var(--rc-control-radius, 0));\n box-shadow: var(\n --rc-menu-shadow,\n var(--rc-shadow, 0 2px 8px color-mix(in srgb, CanvasText 15%, transparent))\n );\n color: var(--rc-menu-color, var(--rc-field-text, CanvasText));\n font-family: var(--rc-font-family, inherit);\n font-size: var(--rc-font-size, inherit);\n line-height: var(--rc-line-height, normal);\n overflow: hidden;\n }\n\n :host(:focus-visible) {\n outline: var(--rc-focus-ring, auto);\n outline-offset: var(--rc-focus-ring-offset, 0);\n }\n`;\n\nexport default menuStyles;\n","import { LitElement, html } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { ActiveDescendantController, isFocusable } from '@rcarls/rc-common';\n\nimport menuStyles from './rc-menu.styles';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-menu': RCMenu;\n }\n}\n\n/** Detail payload for the `rc-menu-activate` event. */\nexport interface RCMenuActivateEvent {\n /** The activated menu item element. */\n item: HTMLElement;\n\n /** The item's `data-value` or `value` attribute, or an empty string. */\n value: string;\n\n /** Trimmed text content of the activated item. */\n text: string;\n\n /** Current `aria-checked` state for checkbox/radio menu items. */\n checked?: 'true' | 'false' | 'mixed';\n}\n\n/** WAI-ARIA roles that are valid direct children of `role=\"menu\"`. */\nconst MENU_ITEM_ROLES = ['menuitem', 'menuitemcheckbox', 'menuitemradio'] as const;\n\n/** Structural CSS for light-DOM menu items, injected into the containing document or shadow root. */\nconst LIGHT_DOM_CSS = `\n@layer rc-base {\n /* Item base layout */\n rc-menu [role='menuitem'],\n rc-menu [role='menuitemcheckbox'],\n rc-menu [role='menuitemradio'],\n rc-menu button:disabled {\n display: flex;\n align-items: center;\n gap: var(--rc-menu-item-gap, var(--rc-item-gap, 0.5em));\n min-block-size: var(--rc-menu-item-min-block-size, 0px);\n width: 100%;\n text-align: start;\n padding: var(--rc-menu-item-padding-block, var(--rc-item-padding-block, 0.3em))\n var(--rc-menu-item-padding-inline, var(--rc-item-padding-inline, 0.75em));\n appearance: none;\n box-sizing: border-box;\n border: none;\n background: transparent;\n text-decoration: none;\n color: inherit;\n font: inherit;\n cursor: default;\n user-select: none;\n outline: none;\n transition: var(--rc-menu-item-transition);\n }\n\n /* Pointer hover */\n rc-menu [role='menuitem']:hover,\n rc-menu [role='menuitemcheckbox']:hover,\n rc-menu [role='menuitemradio']:hover {\n background: var(--rc-menu-hover-bg, color-mix(in srgb, Highlight 8%, transparent));\n color: var(--rc-menu-hover-color, inherit);\n }\n\n /* Virtual cursor (set by ActiveDescendantController) */\n rc-menu [role='menuitem'][data-active],\n rc-menu [role='menuitemcheckbox'][data-active],\n rc-menu [role='menuitemradio'][data-active] {\n background: var(--rc-menu-active-bg, color-mix(in srgb, Highlight 8%, transparent));\n color: var(--rc-menu-active-color, inherit);\n }\n\n /* Check/radio indicator — ::before acts as a fixed-width leading column.\n Content is empty (unchecked) or a Unicode symbol (checked/mixed).\n Semantic state is conveyed by aria-checked; the visual symbol is decorative. */\n rc-menu [role='menuitemcheckbox']::before,\n rc-menu [role='menuitemradio']::before {\n content: '';\n flex: 0 0 var(--rc-menu-check-size, 1.25em);\n text-align: center;\n line-height: 1;\n color: var(--rc-menu-check-color, inherit);\n }\n\n rc-menu [role='menuitemcheckbox'][aria-checked='true']::before {\n content: '✓';\n }\n\n rc-menu [role='menuitemcheckbox'][aria-checked='mixed']::before {\n content: '−';\n }\n\n rc-menu [role='menuitemradio'][aria-checked='true']::before {\n content: '•';\n }\n\n /* Trailing label — keyboard shortcut or hint, pushed to the inline-end of the item */\n rc-menu [data-menu-shortcut] {\n margin-inline-start: auto;\n font-size: var(--rc-menu-shortcut-size, 0.8em);\n color: var(--rc-menu-shortcut-color, GrayText);\n white-space: nowrap;\n }\n\n /* Separators */\n rc-menu [role='separator'],\n rc-menu hr {\n border: none;\n border-block-start: var(--rc-menu-separator-border, var(--rc-border, 1px solid ButtonBorder));\n margin-block: var(--rc-menu-separator-margin-block, var(--rc-control-gap, 0.25em));\n margin-inline: var(--rc-menu-separator-margin-inline, 0px);\n }\n\n /* Group containers */\n rc-menu [role='group'] {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n }\n\n /* Optional section label (non-interactive heading above a group of items) */\n rc-menu [data-group-label] {\n padding-block: var(--rc-menu-group-label-padding-block, 0.1em);\n padding-inline: var(--rc-menu-item-padding-inline, 0.75em);\n color: var(--rc-menu-group-label-color, GrayText);\n user-select: none;\n pointer-events: none;\n }\n\n /* Disabled items */\n rc-menu button:disabled,\n rc-menu [aria-disabled='true'] {\n color: var(--rc-menu-disabled-color, var(--rc-text-disabled, GrayText));\n opacity: var(--rc-menu-disabled-opacity, var(--rc-disabled-opacity, 0.55));\n pointer-events: none;\n }\n}\n`;\n\n/**\n * Menu popup for command surfaces with keyboard navigation and typed activation events,\n * following the WAI-ARIA Menu pattern.\n *\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-menu rc-menu docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/menu/ WAI-ARIA Menu pattern}\n *\n * @slot default - Menu items, groups, and separators. Supported content model:\n * - Focusable elements (`<button>`, `<a>`, `[tabindex]`) become navigable `role=\"menuitem\"` entries.\n * - `role=\"menuitemcheckbox\"` / `role=\"menuitemradio\"` elements get a leading indicator column via `::before`;\n * `aria-checked` is initialized when missing and updated on activation.\n * - `role=\"group\"` divs group related items; label them with `aria-label` or `aria-labelledby`. Optionally\n * add a visible `[data-group-label]` element as first child of the group for section headings.\n * - `role=\"separator\"` / `<hr>` produce horizontal dividers between sections.\n * - Within any item, `[data-menu-shortcut]` elements are pushed to the inline-end edge for keyboard hints.\n * - Nested menu triggers supply their own trailing indicator through `rc-menu-button`'s `indicator` slot.\n *\n * @fires rc-menu-activate - Fired when a menu item is activated via keyboard (Enter/Space) or pointer click.\n * @fires rc-menu-close - Fired when Escape is pressed.\n *\n * @attr label - Accessible label for the menu; synced to `aria-label` on the host.\n *\n * @cssprop [--rc-menu-min-width=10em] - Minimum width of the menu panel.\n * @cssprop [--rc-menu-padding-block=0.25em] - Block (top/bottom) padding inside the panel.\n * @cssprop [--rc-menu-background=Canvas] - Background color; falls back through --rc-surface.\n * @cssprop [--rc-menu-border=1px solid ButtonBorder] - Border; falls back through --rc-border.\n * @cssprop [--rc-menu-radius=var(--rc-control-radius)] - Menu panel border radius.\n * @cssprop [--rc-menu-shadow=0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)] - Box shadow; falls back through --rc-shadow.\n * @cssprop [--rc-menu-color=CanvasText] - Text color; falls back through --rc-field-text.\n * @cssprop [--rc-menu-item-min-block-size=0] - Minimum block size for menu item rows.\n * @cssprop [--rc-menu-item-padding-block=var(--rc-item-padding-block)] - Menu item block-axis padding.\n * @cssprop [--rc-menu-item-padding-inline=var(--rc-item-padding-inline)] - Menu item inline-axis padding.\n * @cssprop [--rc-menu-item-gap=var(--rc-item-gap)] - Gap between flex children inside each item.\n * @cssprop [--rc-menu-item-transition] - CSS transition applied to each item row.\n * @cssprop [--rc-menu-hover-bg=color-mix(in srgb, Highlight 8%, transparent)] - Pointer-hover background.\n * @cssprop [--rc-menu-hover-color=inherit] - Pointer-hover text color.\n * @cssprop [--rc-menu-active-bg=color-mix(in srgb, Highlight 8%, transparent)] - Virtual-cursor active background.\n * @cssprop [--rc-menu-active-color=inherit] - Virtual-cursor active text color.\n * @cssprop [--rc-menu-check-size=1.25em] - Width of the leading indicator column for checkbox/radio items.\n * @cssprop [--rc-menu-check-color=inherit] - Color of the check/radio symbol.\n * @cssprop [--rc-menu-shortcut-size=0.8em] - Font size of `[data-menu-shortcut]` labels.\n * @cssprop [--rc-menu-shortcut-color=GrayText] - Text color of `[data-menu-shortcut]` labels.\n * @cssprop [--rc-menu-disabled-color=GrayText] - Disabled menu item text color.\n * @cssprop [--rc-menu-disabled-opacity=var(--rc-disabled-opacity)] - Disabled menu item opacity.\n * @cssprop [--rc-menu-separator-border=1px solid ButtonBorder] - Separator border.\n * @cssprop [--rc-menu-separator-margin-block=var(--rc-control-gap)] - Separator block-axis margin.\n * @cssprop [--rc-menu-separator-margin-inline=0px] - Separator inline margin; set to match item padding for inset separators.\n * @cssprop [--rc-menu-group-label-padding-block=0.1em] - Block padding on `[data-group-label]` section headings.\n * @cssprop [--rc-menu-group-label-color=GrayText] - Text color of `[data-group-label]` section headings.\n */\nexport class RCMenu extends LitElement {\n static styles = [menuStyles];\n\n private static readonly _$styledRoots = new Set<Document | ShadowRoot>();\n\n private static _ensureBaseStyles($root: Document | ShadowRoot): void {\n if (RCMenu._$styledRoots.has($root)) {\n return;\n }\n\n RCMenu._$styledRoots.add($root);\n\n const $style = document.createElement('style');\n\n $style.setAttribute('data-rc-light-dom-base', 'rc-menu');\n $style.textContent = LIGHT_DOM_CSS;\n\n if ($root instanceof Document) {\n $root.head.appendChild($style);\n } else {\n $root.appendChild($style);\n }\n }\n\n /**\n * Accessible label for this menu. Synced to `aria-label` on the host.\n *\n * Prefer `aria-labelledby` for a visible label associated via ID.\n */\n @property({ type: String })\n label = '';\n\n protected _$menuItems: Element[] = [];\n\n protected readonly _activeDescendantCtrl = new ActiveDescendantController(this, {\n host: () => this,\n items: () => this._$menuItems,\n });\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (!this.hasAttribute('role')) {\n this.setAttribute('role', 'menu');\n }\n\n this.setAttribute('tabindex', '0');\n this._syncAriaLabel();\n\n this.addEventListener('keydown', this._onKeydown);\n this.addEventListener('blur', this._onBlur);\n this.addEventListener('click', this._onClick);\n\n RCMenu._ensureBaseStyles(this.getRootNode() as Document | ShadowRoot);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n\n this.removeEventListener('keydown', this._onKeydown);\n this.removeEventListener('blur', this._onBlur);\n this.removeEventListener('click', this._onClick);\n }\n\n protected override updated(changed: Map<PropertyKey, unknown>): void {\n if (changed.has('label')) {\n this._syncAriaLabel();\n }\n }\n\n protected override render() {\n return html`<slot @slotchange=${this._onSlotChange}></slot>`;\n }\n\n /** All navigable menu item elements in document order. */\n get items(): readonly Element[] {\n return this._$menuItems;\n }\n\n /** Move the virtual cursor and DOM focus to the first navigable item. */\n focusFirst(): void {\n this.focus();\n this._activeDescendantCtrl.navigateToFirst();\n }\n\n /** Move the virtual cursor and DOM focus to the last navigable item. */\n focusLast(): void {\n this.focus();\n this._activeDescendantCtrl.navigateToLast();\n }\n\n /**\n * Move the virtual cursor to a specific item.\n *\n * @param $item - The element to make active. No-op when null or undefined.\n */\n focusItem($item?: Element | null): void {\n if (!$item) {\n return;\n }\n\n this.focus();\n this._activeDescendantCtrl.navigateToItem($item);\n }\n\n /**\n * Move the virtual cursor to the item at `index`.\n *\n * @param index - Zero-based index into the navigable items array.\n */\n focusItemAt(index: number): void {\n this.focus();\n this._activeDescendantCtrl.navigateTo(index);\n }\n\n protected _syncAriaLabel(): void {\n if (this.label) {\n this.setAttribute('aria-label', this.label);\n } else {\n this.removeAttribute('aria-label');\n }\n }\n\n protected _onSlotChange(e: Event): void {\n const $slot = e.currentTarget as HTMLSlotElement;\n\n this._$menuItems = this._collectMenuItems($slot);\n this._initMenuItems();\n }\n\n /**\n * Collects navigable menu items from the slot, supporting `role=\"group\"`\n * containers and skipping `role=\"separator\"` elements.\n *\n * Direct focusable children are included as-is. Elements with\n * `role=\"group\"` are traversed one level deep so their focusable children\n * participate in arrow-key navigation. Separators and non-focusable\n * container elements are excluded.\n */\n protected _collectMenuItems($slot: HTMLSlotElement): Element[] {\n const $items: Element[] = [];\n\n for (const $el of $slot.assignedElements()) {\n const role = $el.getAttribute('role');\n\n if (role === 'separator') {\n continue;\n }\n\n if (role === 'group') {\n for (const $child of $el.children) {\n if (isFocusable($child)) {\n $items.push($child);\n }\n }\n\n continue;\n }\n\n if (isFocusable($el)) {\n $items.push($el);\n }\n }\n\n return $items;\n }\n\n protected _initMenuItems(): void {\n this._$menuItems.forEach(($el) => {\n const existingRole = $el.getAttribute('role');\n\n // Preserve explicit roles; only assign the default when none is present.\n if (\n !existingRole ||\n !MENU_ITEM_ROLES.includes(existingRole as (typeof MENU_ITEM_ROLES)[number])\n ) {\n $el.setAttribute('role', 'menuitem');\n }\n\n $el.setAttribute('tabindex', '-1');\n\n if (\n ($el.getAttribute('role') === 'menuitemcheckbox' ||\n $el.getAttribute('role') === 'menuitemradio') &&\n !$el.hasAttribute('aria-checked')\n ) {\n $el.setAttribute('aria-checked', 'false');\n }\n });\n }\n\n protected _applyCheckedState($item: HTMLElement): 'true' | 'false' | 'mixed' | undefined {\n const role = $item.getAttribute('role');\n\n if (role === 'menuitemcheckbox') {\n const checked = $item.getAttribute('aria-checked') === 'true' ? 'false' : 'true';\n\n $item.setAttribute('aria-checked', checked);\n\n return checked;\n }\n\n if (role === 'menuitemradio') {\n const $group = $item.closest('[role=\"group\"]');\n const $scope = $group && this.contains($group) ? $group : this;\n\n for (const $radio of $scope.querySelectorAll<HTMLElement>('[role=\"menuitemradio\"]')) {\n if ($radio.closest('rc-menu') === this) {\n $radio.setAttribute('aria-checked', $radio === $item ? 'true' : 'false');\n }\n }\n\n return 'true';\n }\n\n return undefined;\n }\n\n protected _onKeydown = (e: KeyboardEvent): void => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n e.stopPropagation();\n\n if (this._activeDescendantCtrl.activeItem) {\n this._activeDescendantCtrl.navigate(1);\n } else {\n this._activeDescendantCtrl.navigateToFirst();\n }\n\n break;\n }\n\n case 'ArrowUp': {\n e.preventDefault();\n e.stopPropagation();\n\n if (this._activeDescendantCtrl.activeItem) {\n this._activeDescendantCtrl.navigate(-1);\n } else {\n this._activeDescendantCtrl.navigateToLast();\n }\n\n break;\n }\n\n case 'Home': {\n e.preventDefault();\n e.stopPropagation();\n\n this._activeDescendantCtrl.navigateToFirst();\n\n break;\n }\n\n case 'End': {\n e.preventDefault();\n e.stopPropagation();\n\n this._activeDescendantCtrl.navigateToLast();\n\n break;\n }\n\n case 'Enter':\n // Falls through so both activation keys share the same handler.\n\n case ' ': {\n e.preventDefault();\n e.stopPropagation();\n\n const $active = this._activeDescendantCtrl.activeItem;\n\n if ($active) {\n // _onClick dispatches rc-menu-activate when the synthetic click bubbles up.\n ($active as HTMLElement).click();\n }\n\n break;\n }\n\n case 'Escape': {\n e.preventDefault();\n e.stopPropagation();\n\n this.dispatchEvent(\n new CustomEvent('rc-menu-close', {\n bubbles: true,\n composed: true,\n detail: { reason: 'escape' },\n }),\n );\n\n break;\n }\n }\n };\n\n protected _onBlur = (): void => {\n this._activeDescendantCtrl.clear();\n };\n\n protected _onClick = (e: MouseEvent): void => {\n const $target = e.target as Element;\n const $item = $target.closest<HTMLElement>(\n '[role=\"menuitem\"],[role=\"menuitemcheckbox\"],[role=\"menuitemradio\"]',\n );\n\n if (!$item || !this.contains($item)) {\n return;\n }\n\n if ($item.matches(':disabled, [aria-disabled=\"true\"]')) {\n return;\n }\n\n const checked = this._applyCheckedState($item);\n\n this.dispatchEvent(\n new CustomEvent<RCMenuActivateEvent>('rc-menu-activate', {\n bubbles: true,\n composed: true,\n detail: {\n item: $item,\n value: $item.dataset.value ?? $item.getAttribute('value') ?? '',\n text: $item.textContent?.trim() ?? '',\n checked,\n },\n }),\n );\n };\n}\n\nexport default RCMenu;\n"],"names":["menuStyles","css","MENU_ITEM_ROLES","LIGHT_DOM_CSS","_RCMenu","_a","LitElement","ActiveDescendantController","$active","$item","checked","$root","$style","changed","html","index","$slot","$items","$el","role","$child","isFocusable","existingRole","$group","$scope","$radio","__decorateClass","property","RCMenu"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { R as e } from "./rc-menu-Drg26iOK.js";
1
+ import { R as e } from "./rc-menu-DJ8upIAd.js";
2
2
  customElements.get("rc-menu") || customElements.define("rc-menu", e);
3
3
  export {
4
4
  e as RCMenu
package/dist/rc-menu.js CHANGED
@@ -1,4 +1,4 @@
1
- import { R as r } from "./rc-menu-Drg26iOK.js";
1
+ import { R as r } from "./rc-menu-DJ8upIAd.js";
2
2
  export {
3
3
  r as RCMenu
4
4
  };
@@ -32,17 +32,20 @@ export interface RCMenuActivateEvent {
32
32
  * add a visible `[data-group-label]` element as first child of the group for section headings.
33
33
  * - `role="separator"` / `<hr>` produce horizontal dividers between sections.
34
34
  * - Within any item, `[data-menu-shortcut]` elements are pushed to the inline-end edge for keyboard hints.
35
- * - Items with `aria-haspopup="menu"` or `aria-haspopup="true"` get a trailing submenu indicator.
35
+ * - Nested menu triggers supply their own trailing indicator through `rc-menu-button`'s `indicator` slot.
36
36
  *
37
37
  * @fires rc-menu-activate - Fired when a menu item is activated via keyboard (Enter/Space) or pointer click.
38
38
  * @fires rc-menu-close - Fired when Escape is pressed.
39
39
  *
40
+ * @attr label - Accessible label for the menu; synced to `aria-label` on the host.
41
+ *
40
42
  * @cssprop [--rc-menu-min-width=10em] - Minimum width of the menu panel.
41
43
  * @cssprop [--rc-menu-padding-block=0.25em] - Block (top/bottom) padding inside the panel.
42
44
  * @cssprop [--rc-menu-background=Canvas] - Background color; falls back through --rc-surface.
43
45
  * @cssprop [--rc-menu-border=1px solid ButtonBorder] - Border; falls back through --rc-border.
44
46
  * @cssprop [--rc-menu-radius=var(--rc-control-radius)] - Menu panel border radius.
45
47
  * @cssprop [--rc-menu-shadow=0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)] - Box shadow; falls back through --rc-shadow.
48
+ * @cssprop [--rc-menu-color=CanvasText] - Text color; falls back through --rc-field-text.
46
49
  * @cssprop [--rc-menu-item-min-block-size=0] - Minimum block size for menu item rows.
47
50
  * @cssprop [--rc-menu-item-padding-block=var(--rc-item-padding-block)] - Menu item block-axis padding.
48
51
  * @cssprop [--rc-menu-item-padding-inline=var(--rc-item-padding-inline)] - Menu item inline-axis padding.
@@ -56,9 +59,6 @@ export interface RCMenuActivateEvent {
56
59
  * @cssprop [--rc-menu-check-color=inherit] - Color of the check/radio symbol.
57
60
  * @cssprop [--rc-menu-shortcut-size=0.8em] - Font size of `[data-menu-shortcut]` labels.
58
61
  * @cssprop [--rc-menu-shortcut-color=GrayText] - Text color of `[data-menu-shortcut]` labels.
59
- * @cssprop [--rc-menu-submenu-indicator-content='›'] - Text content for submenu indicators.
60
- * @cssprop [--rc-menu-submenu-indicator-size=1em] - Font size of submenu indicators.
61
- * @cssprop [--rc-menu-submenu-indicator-color=currentColor] - Text color of submenu indicators.
62
62
  * @cssprop [--rc-menu-disabled-color=GrayText] - Disabled menu item text color.
63
63
  * @cssprop [--rc-menu-disabled-opacity=var(--rc-disabled-opacity)] - Disabled menu item opacity.
64
64
  * @cssprop [--rc-menu-separator-border=1px solid ButtonBorder] - Separator border.
@@ -69,7 +69,7 @@ export interface RCMenuActivateEvent {
69
69
  */
70
70
  export declare class RCMenu extends LitElement {
71
71
  static styles: import('lit').CSSResult[];
72
- private static readonly _styledRoots;
72
+ private static readonly _$styledRoots;
73
73
  private static _ensureBaseStyles;
74
74
  /**
75
75
  * Accessible label for this menu. Synced to `aria-label` on the host.
@@ -77,7 +77,7 @@ export declare class RCMenu extends LitElement {
77
77
  * Prefer `aria-labelledby` for a visible label associated via ID.
78
78
  */
79
79
  label: string;
80
- protected _menuItems: Element[];
80
+ protected _$menuItems: Element[];
81
81
  protected readonly _activeDescendantCtrl: ActiveDescendantController;
82
82
  connectedCallback(): void;
83
83
  disconnectedCallback(): void;
@@ -92,9 +92,9 @@ export declare class RCMenu extends LitElement {
92
92
  /**
93
93
  * Move the virtual cursor to a specific item.
94
94
  *
95
- * @param item - The element to make active. No-op when null or undefined.
95
+ * @param $item - The element to make active. No-op when null or undefined.
96
96
  */
97
- focusItem(item?: Element | null): void;
97
+ focusItem($item?: Element | null): void;
98
98
  /**
99
99
  * Move the virtual cursor to the item at `index`.
100
100
  *
@@ -112,9 +112,9 @@ export declare class RCMenu extends LitElement {
112
112
  * participate in arrow-key navigation. Separators and non-focusable
113
113
  * container elements are excluded.
114
114
  */
115
- protected _collectMenuItems(slot: HTMLSlotElement): Element[];
115
+ protected _collectMenuItems($slot: HTMLSlotElement): Element[];
116
116
  protected _initMenuItems(): void;
117
- protected _applyCheckedState(item: HTMLElement): 'true' | 'false' | 'mixed' | undefined;
117
+ protected _applyCheckedState($item: HTMLElement): 'true' | 'false' | 'mixed' | undefined;
118
118
  protected _onKeydown: (e: KeyboardEvent) => void;
119
119
  protected _onBlur: () => void;
120
120
  protected _onClick: (e: MouseEvent) => void;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.2",
6
+ "version": "0.4.1",
7
7
  "description": "Menu popup for command surfaces with keyboard navigation and typed activation events.",
8
8
  "repository": {
9
9
  "type": "git",
@@ -45,7 +45,7 @@
45
45
  "test:browser:firefox": "vitest --run --project=firefox"
46
46
  },
47
47
  "dependencies": {
48
- "@rcarls/rc-common": "workspace:*"
48
+ "@rcarls/rc-common": "0.4.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@custom-elements-manifest/analyzer": "0.11.0",
@@ -62,4 +62,4 @@
62
62
  "peerDependencies": {
63
63
  "lit": "^3.0.0"
64
64
  }
65
- }
65
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"rc-menu-Drg26iOK.js","sources":["../src/rc-menu.styles.ts","../src/rc-menu.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const menuStyles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-width: var(--rc-menu-min-width, 10em);\n padding-block: var(--rc-menu-padding-block, var(--rc-control-padding-block, 0.25em));\n background: var(--rc-menu-background, var(--rc-surface, Canvas));\n border: var(--rc-menu-border, var(--rc-border, 1px solid ButtonBorder));\n border-radius: var(--rc-menu-radius, var(--rc-control-radius, 0));\n box-shadow: var(\n --rc-menu-shadow,\n var(--rc-shadow, 0 2px 8px color-mix(in srgb, CanvasText 15%, transparent))\n );\n color: var(--rc-menu-color, var(--rc-field-text, CanvasText));\n font-family: var(--rc-font-family, inherit);\n font-size: var(--rc-font-size, inherit);\n line-height: var(--rc-line-height, normal);\n overflow: hidden;\n }\n\n :host(:focus-visible) {\n outline: var(--rc-focus-ring, auto);\n outline-offset: var(--rc-focus-ring-offset, 0);\n }\n`;\n\nexport default menuStyles;\n","import { LitElement, html } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { ActiveDescendantController, isFocusable } from '@rcarls/rc-common';\n\nimport menuStyles from './rc-menu.styles';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-menu': RCMenu;\n }\n}\n\n/** Detail payload for the `rc-menu-activate` event. */\nexport interface RCMenuActivateEvent {\n /** The activated menu item element. */\n item: HTMLElement;\n\n /** The item's `data-value` or `value` attribute, or an empty string. */\n value: string;\n\n /** Trimmed text content of the activated item. */\n text: string;\n\n /** Current `aria-checked` state for checkbox/radio menu items. */\n checked?: 'true' | 'false' | 'mixed';\n}\n\n/** WAI-ARIA roles that are valid direct children of `role=\"menu\"`. */\nconst MENU_ITEM_ROLES = ['menuitem', 'menuitemcheckbox', 'menuitemradio'] as const;\n\n/** Structural CSS for light-DOM menu items, injected into the containing document or shadow root. */\nconst LIGHT_DOM_CSS = `\n@layer rc-base {\n /* Item base layout */\n rc-menu [role='menuitem'],\n rc-menu [role='menuitemcheckbox'],\n rc-menu [role='menuitemradio'],\n rc-menu button:disabled {\n display: flex;\n align-items: center;\n gap: var(--rc-menu-item-gap, var(--rc-item-gap, 0.5em));\n min-block-size: var(--rc-menu-item-min-block-size, 0px);\n width: 100%;\n text-align: start;\n padding: var(--rc-menu-item-padding-block, var(--rc-item-padding-block, 0.3em))\n var(--rc-menu-item-padding-inline, var(--rc-item-padding-inline, 0.75em));\n appearance: none;\n box-sizing: border-box;\n border: none;\n background: transparent;\n text-decoration: none;\n color: inherit;\n font: inherit;\n cursor: default;\n user-select: none;\n outline: none;\n transition: var(--rc-menu-item-transition);\n }\n\n /* Pointer hover */\n rc-menu [role='menuitem']:hover,\n rc-menu [role='menuitemcheckbox']:hover,\n rc-menu [role='menuitemradio']:hover {\n background: var(--rc-menu-hover-bg, color-mix(in srgb, Highlight 8%, transparent));\n color: var(--rc-menu-hover-color, inherit);\n }\n\n /* Virtual cursor (set by ActiveDescendantController) */\n rc-menu [role='menuitem'][data-active],\n rc-menu [role='menuitemcheckbox'][data-active],\n rc-menu [role='menuitemradio'][data-active] {\n background: var(--rc-menu-active-bg, color-mix(in srgb, Highlight 8%, transparent));\n color: var(--rc-menu-active-color, inherit);\n }\n\n /* Check/radio indicator — ::before acts as a fixed-width leading column.\n Content is empty (unchecked) or a Unicode symbol (checked/mixed).\n Semantic state is conveyed by aria-checked; the visual symbol is decorative. */\n rc-menu [role='menuitemcheckbox']::before,\n rc-menu [role='menuitemradio']::before {\n content: '';\n flex: 0 0 var(--rc-menu-check-size, 1.25em);\n text-align: center;\n line-height: 1;\n color: var(--rc-menu-check-color, inherit);\n }\n\n rc-menu [role='menuitemcheckbox'][aria-checked='true']::before {\n content: '✓';\n }\n\n rc-menu [role='menuitemcheckbox'][aria-checked='mixed']::before {\n content: '−';\n }\n\n rc-menu [role='menuitemradio'][aria-checked='true']::before {\n content: '•';\n }\n\n /* Trailing label — keyboard shortcut or hint, pushed to the inline-end of the item */\n rc-menu [data-menu-shortcut] {\n margin-inline-start: auto;\n font-size: var(--rc-menu-shortcut-size, 0.8em);\n color: var(--rc-menu-shortcut-color, GrayText);\n white-space: nowrap;\n }\n\n /* Submenu indicator for items that open a child menu. */\n rc-menu [aria-haspopup='menu']::after,\n rc-menu [aria-haspopup='true']::after {\n content: var(--rc-menu-submenu-indicator-content, '›');\n margin-inline-start: auto;\n font-size: var(--rc-menu-submenu-indicator-size, 1em);\n color: var(--rc-menu-submenu-indicator-color, currentColor);\n }\n\n /* Separators */\n rc-menu [role='separator'],\n rc-menu hr {\n border: none;\n border-block-start: var(--rc-menu-separator-border, var(--rc-border, 1px solid ButtonBorder));\n margin-block: var(--rc-menu-separator-margin-block, var(--rc-control-gap, 0.25em));\n margin-inline: var(--rc-menu-separator-margin-inline, 0px);\n }\n\n /* Group containers */\n rc-menu [role='group'] {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n }\n\n /* Optional section label (non-interactive heading above a group of items) */\n rc-menu [data-group-label] {\n padding-block: var(--rc-menu-group-label-padding-block, 0.1em);\n padding-inline: var(--rc-menu-item-padding-inline, 0.75em);\n color: var(--rc-menu-group-label-color, GrayText);\n user-select: none;\n pointer-events: none;\n }\n\n /* Disabled items */\n rc-menu button:disabled,\n rc-menu [aria-disabled='true'] {\n color: var(--rc-menu-disabled-color, var(--rc-text-disabled, GrayText));\n opacity: var(--rc-menu-disabled-opacity, var(--rc-disabled-opacity, 0.55));\n pointer-events: none;\n }\n}\n`;\n\n/**\n * Menu popup for command surfaces with keyboard navigation and typed activation events,\n * following the WAI-ARIA Menu pattern.\n *\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-menu rc-menu docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/menu/ WAI-ARIA Menu pattern}\n *\n * @slot default - Menu items, groups, and separators. Supported content model:\n * - Focusable elements (`<button>`, `<a>`, `[tabindex]`) become navigable `role=\"menuitem\"` entries.\n * - `role=\"menuitemcheckbox\"` / `role=\"menuitemradio\"` elements get a leading indicator column via `::before`;\n * `aria-checked` is initialized when missing and updated on activation.\n * - `role=\"group\"` divs group related items; label them with `aria-label` or `aria-labelledby`. Optionally\n * add a visible `[data-group-label]` element as first child of the group for section headings.\n * - `role=\"separator\"` / `<hr>` produce horizontal dividers between sections.\n * - Within any item, `[data-menu-shortcut]` elements are pushed to the inline-end edge for keyboard hints.\n * - Items with `aria-haspopup=\"menu\"` or `aria-haspopup=\"true\"` get a trailing submenu indicator.\n *\n * @fires rc-menu-activate - Fired when a menu item is activated via keyboard (Enter/Space) or pointer click.\n * @fires rc-menu-close - Fired when Escape is pressed.\n *\n * @cssprop [--rc-menu-min-width=10em] - Minimum width of the menu panel.\n * @cssprop [--rc-menu-padding-block=0.25em] - Block (top/bottom) padding inside the panel.\n * @cssprop [--rc-menu-background=Canvas] - Background color; falls back through --rc-surface.\n * @cssprop [--rc-menu-border=1px solid ButtonBorder] - Border; falls back through --rc-border.\n * @cssprop [--rc-menu-radius=var(--rc-control-radius)] - Menu panel border radius.\n * @cssprop [--rc-menu-shadow=0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)] - Box shadow; falls back through --rc-shadow.\n * @cssprop [--rc-menu-item-min-block-size=0] - Minimum block size for menu item rows.\n * @cssprop [--rc-menu-item-padding-block=var(--rc-item-padding-block)] - Menu item block-axis padding.\n * @cssprop [--rc-menu-item-padding-inline=var(--rc-item-padding-inline)] - Menu item inline-axis padding.\n * @cssprop [--rc-menu-item-gap=var(--rc-item-gap)] - Gap between flex children inside each item.\n * @cssprop [--rc-menu-item-transition] - CSS transition applied to each item row.\n * @cssprop [--rc-menu-hover-bg=color-mix(in srgb, Highlight 8%, transparent)] - Pointer-hover background.\n * @cssprop [--rc-menu-hover-color=inherit] - Pointer-hover text color.\n * @cssprop [--rc-menu-active-bg=color-mix(in srgb, Highlight 8%, transparent)] - Virtual-cursor active background.\n * @cssprop [--rc-menu-active-color=inherit] - Virtual-cursor active text color.\n * @cssprop [--rc-menu-check-size=1.25em] - Width of the leading indicator column for checkbox/radio items.\n * @cssprop [--rc-menu-check-color=inherit] - Color of the check/radio symbol.\n * @cssprop [--rc-menu-shortcut-size=0.8em] - Font size of `[data-menu-shortcut]` labels.\n * @cssprop [--rc-menu-shortcut-color=GrayText] - Text color of `[data-menu-shortcut]` labels.\n * @cssprop [--rc-menu-submenu-indicator-content='›'] - Text content for submenu indicators.\n * @cssprop [--rc-menu-submenu-indicator-size=1em] - Font size of submenu indicators.\n * @cssprop [--rc-menu-submenu-indicator-color=currentColor] - Text color of submenu indicators.\n * @cssprop [--rc-menu-disabled-color=GrayText] - Disabled menu item text color.\n * @cssprop [--rc-menu-disabled-opacity=var(--rc-disabled-opacity)] - Disabled menu item opacity.\n * @cssprop [--rc-menu-separator-border=1px solid ButtonBorder] - Separator border.\n * @cssprop [--rc-menu-separator-margin-block=var(--rc-control-gap)] - Separator block-axis margin.\n * @cssprop [--rc-menu-separator-margin-inline=0px] - Separator inline margin; set to match item padding for inset separators.\n * @cssprop [--rc-menu-group-label-padding-block=0.1em] - Block padding on `[data-group-label]` section headings.\n * @cssprop [--rc-menu-group-label-color=GrayText] - Text color of `[data-group-label]` section headings.\n */\nexport class RCMenu extends LitElement {\n static styles = [menuStyles];\n\n private static readonly _styledRoots = new Set<Document | ShadowRoot>();\n\n private static _ensureBaseStyles(root: Document | ShadowRoot): void {\n if (RCMenu._styledRoots.has(root)) {\n return;\n }\n\n RCMenu._styledRoots.add(root);\n\n const style = document.createElement('style');\n\n style.setAttribute('data-rc-light-dom-base', 'rc-menu');\n style.textContent = LIGHT_DOM_CSS;\n\n if (root instanceof Document) {\n root.head.appendChild(style);\n } else {\n root.appendChild(style);\n }\n }\n\n /**\n * Accessible label for this menu. Synced to `aria-label` on the host.\n *\n * Prefer `aria-labelledby` for a visible label associated via ID.\n */\n @property({ type: String })\n label = '';\n\n protected _menuItems: Element[] = [];\n\n protected readonly _activeDescendantCtrl = new ActiveDescendantController(this, {\n host: () => this,\n items: () => this._menuItems,\n });\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (!this.hasAttribute('role')) {\n this.setAttribute('role', 'menu');\n }\n\n this.setAttribute('tabindex', '0');\n this._syncAriaLabel();\n\n this.addEventListener('keydown', this._onKeydown);\n this.addEventListener('blur', this._onBlur);\n this.addEventListener('click', this._onClick);\n\n RCMenu._ensureBaseStyles(this.getRootNode() as Document | ShadowRoot);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n\n this.removeEventListener('keydown', this._onKeydown);\n this.removeEventListener('blur', this._onBlur);\n this.removeEventListener('click', this._onClick);\n }\n\n protected override updated(changed: Map<PropertyKey, unknown>): void {\n if (changed.has('label')) {\n this._syncAriaLabel();\n }\n }\n\n protected override render() {\n return html`<slot @slotchange=${this._onSlotChange}></slot>`;\n }\n\n /** All navigable menu item elements in document order. */\n get items(): readonly Element[] {\n return this._menuItems;\n }\n\n /** Move the virtual cursor and DOM focus to the first navigable item. */\n focusFirst(): void {\n this.focus();\n this._activeDescendantCtrl.navigateToFirst();\n }\n\n /** Move the virtual cursor and DOM focus to the last navigable item. */\n focusLast(): void {\n this.focus();\n this._activeDescendantCtrl.navigateToLast();\n }\n\n /**\n * Move the virtual cursor to a specific item.\n *\n * @param item - The element to make active. No-op when null or undefined.\n */\n focusItem(item?: Element | null): void {\n if (!item) return;\n this.focus();\n this._activeDescendantCtrl.navigateToItem(item);\n }\n\n /**\n * Move the virtual cursor to the item at `index`.\n *\n * @param index - Zero-based index into the navigable items array.\n */\n focusItemAt(index: number): void {\n this.focus();\n this._activeDescendantCtrl.navigateTo(index);\n }\n\n protected _syncAriaLabel(): void {\n if (this.label) {\n this.setAttribute('aria-label', this.label);\n } else {\n this.removeAttribute('aria-label');\n }\n }\n\n protected _onSlotChange(e: Event): void {\n const slot = e.currentTarget as HTMLSlotElement;\n\n this._menuItems = this._collectMenuItems(slot);\n this._initMenuItems();\n }\n\n /**\n * Collects navigable menu items from the slot, supporting `role=\"group\"`\n * containers and skipping `role=\"separator\"` elements.\n *\n * Direct focusable children are included as-is. Elements with\n * `role=\"group\"` are traversed one level deep so their focusable children\n * participate in arrow-key navigation. Separators and non-focusable\n * container elements are excluded.\n */\n protected _collectMenuItems(slot: HTMLSlotElement): Element[] {\n const items: Element[] = [];\n\n for (const el of slot.assignedElements()) {\n const role = el.getAttribute('role');\n\n if (role === 'separator') continue;\n\n if (role === 'group') {\n for (const child of el.children) {\n if (isFocusable(child)) items.push(child);\n }\n continue;\n }\n\n if (isFocusable(el)) items.push(el);\n }\n\n return items;\n }\n\n protected _initMenuItems(): void {\n this._menuItems.forEach((el) => {\n const existingRole = el.getAttribute('role');\n\n // Preserve explicit roles; only assign the default when none is present.\n if (\n !existingRole ||\n !MENU_ITEM_ROLES.includes(existingRole as (typeof MENU_ITEM_ROLES)[number])\n ) {\n el.setAttribute('role', 'menuitem');\n }\n\n el.setAttribute('tabindex', '-1');\n\n if (\n (el.getAttribute('role') === 'menuitemcheckbox' ||\n el.getAttribute('role') === 'menuitemradio') &&\n !el.hasAttribute('aria-checked')\n ) {\n el.setAttribute('aria-checked', 'false');\n }\n });\n }\n\n protected _applyCheckedState(item: HTMLElement): 'true' | 'false' | 'mixed' | undefined {\n const role = item.getAttribute('role');\n\n if (role === 'menuitemcheckbox') {\n const checked = item.getAttribute('aria-checked') === 'true' ? 'false' : 'true';\n\n item.setAttribute('aria-checked', checked);\n\n return checked;\n }\n\n if (role === 'menuitemradio') {\n const group = item.closest('[role=\"group\"]');\n const scope = group && this.contains(group) ? group : this;\n\n for (const radio of scope.querySelectorAll<HTMLElement>('[role=\"menuitemradio\"]')) {\n if (radio.closest('rc-menu') === this) {\n radio.setAttribute('aria-checked', radio === item ? 'true' : 'false');\n }\n }\n\n return 'true';\n }\n\n return undefined;\n }\n\n protected _onKeydown = (e: KeyboardEvent): void => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n e.stopPropagation();\n\n if (this._activeDescendantCtrl.activeItem) {\n this._activeDescendantCtrl.navigate(1);\n } else {\n this._activeDescendantCtrl.navigateToFirst();\n }\n\n break;\n }\n\n case 'ArrowUp': {\n e.preventDefault();\n e.stopPropagation();\n\n if (this._activeDescendantCtrl.activeItem) {\n this._activeDescendantCtrl.navigate(-1);\n } else {\n this._activeDescendantCtrl.navigateToLast();\n }\n\n break;\n }\n\n case 'Home': {\n e.preventDefault();\n e.stopPropagation();\n\n this._activeDescendantCtrl.navigateToFirst();\n\n break;\n }\n\n case 'End': {\n e.preventDefault();\n e.stopPropagation();\n\n this._activeDescendantCtrl.navigateToLast();\n\n break;\n }\n\n case 'Enter':\n case ' ': {\n e.preventDefault();\n e.stopPropagation();\n\n const active = this._activeDescendantCtrl.activeItem;\n\n if (active) {\n // _onClick dispatches rc-menu-activate when the synthetic click bubbles up.\n (active as HTMLElement).click();\n }\n\n break;\n }\n\n case 'Escape': {\n e.preventDefault();\n e.stopPropagation();\n\n this.dispatchEvent(\n new CustomEvent('rc-menu-close', {\n bubbles: true,\n composed: true,\n detail: { reason: 'escape' },\n }),\n );\n\n break;\n }\n }\n };\n\n protected _onBlur = (): void => {\n this._activeDescendantCtrl.clear();\n };\n\n protected _onClick = (e: MouseEvent): void => {\n const target = e.target as Element;\n const item = target.closest<HTMLElement>(\n '[role=\"menuitem\"],[role=\"menuitemcheckbox\"],[role=\"menuitemradio\"]',\n );\n\n if (!item || !this.contains(item)) {\n return;\n }\n\n if (item.matches(':disabled, [aria-disabled=\"true\"]')) {\n return;\n }\n\n const checked = this._applyCheckedState(item);\n\n this.dispatchEvent(\n new CustomEvent<RCMenuActivateEvent>('rc-menu-activate', {\n bubbles: true,\n composed: true,\n detail: {\n item,\n value: item.dataset.value ?? item.getAttribute('value') ?? '',\n text: item.textContent?.trim() ?? '',\n checked,\n },\n }),\n );\n };\n}\n\nexport default RCMenu;\n"],"names":["menuStyles","css","MENU_ITEM_ROLES","LIGHT_DOM_CSS","_RCMenu","_a","LitElement","ActiveDescendantController","active","item","checked","root","style","changed","html","index","slot","items","el","role","child","isFocusable","existingRole","group","scope","radio","__decorateClass","property","RCMenu"],"mappings":"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"}