@rcarls/rc-menu 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,103 @@
1
+ # @rcarls/rc-menu
2
+
3
+ ## 0.0.0-next-20260921045401
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [50d5610]
8
+ - Updated dependencies [4341311]
9
+ - @rcarls/rc-common@0.0.0-next-20260921045401
10
+
11
+ ## 0.6.0
12
+
13
+ ### Patch Changes
14
+
15
+ - 7382c71: Add a priority-aware action toolbar that moves original controls into an
16
+ overflow menu. Which actions are promoted is a purely declarative computation
17
+ from `max-shown` and each action's `data-priority`/authored `slot`, with no
18
+ runtime measurement of the toolbar's or its children's rendered size: a
19
+ consumer whose promoted count needs to react to available space owns that
20
+ decision and writes the resulting `max-shown` value down. Include controlled
21
+ and uncontrolled open state, keyboard navigation, light dismissal, and a
22
+ non-Popover fallback.
23
+
24
+ Fix `--rc-adaptive-menu-touch-target-overlap-inline-start`/`-end`: the host's
25
+ own `max-inline-size: 100%` self-referenced a shrink-to-fit ancestor's
26
+ already-margin-reduced auto size (an app bar's own trailing group, for
27
+ example), quietly clamping the host back down and canceling the overlap out
28
+ from under it. The cap now grows by the overlap amount so it only ever bounds
29
+ the host's real content.
30
+
31
+ - 30eb232: Add icon-only activation-area sizing and Material icon-button variants. Align menu
32
+ selection and submenu affordances, and keep anchored popups within the viewport
33
+ across native and fallback positioning.
34
+ - Updated dependencies [6683eb9]
35
+ - Updated dependencies [ee7ba6c]
36
+ - Updated dependencies [30eb232]
37
+ - @rcarls/rc-common@0.6.0
38
+
39
+ ## 0.5.0
40
+
41
+ ### Patch Changes
42
+
43
+ - Updated dependencies [4ae2ef0]
44
+ - Updated dependencies [689340c]
45
+ - @rcarls/rc-common@0.5.0
46
+
47
+ ## 0.4.2
48
+
49
+ ### Patch Changes
50
+
51
+ - @rcarls/rc-common@0.4.2
52
+
53
+ ## 0.4.1
54
+
55
+ ### Patch Changes
56
+
57
+ - @rcarls/rc-common@0.4.1
58
+
59
+ ## 0.4.0
60
+
61
+ ### Minor Changes
62
+
63
+ - 3e89095: Replace rc-menu's generated submenu glyph with an author-supplied `indicator`
64
+ slot on rc-menu-button. Keep the decorative indicator inside the trigger bounds,
65
+ reserve label space for it, and expose theme-neutral size, color, and inset
66
+ tokens for Material, Substrate, and application themes.
67
+
68
+ ### Patch Changes
69
+
70
+ - Updated dependencies [e57277f]
71
+ - Updated dependencies [ccca8e2]
72
+ - Updated dependencies [037b1b3]
73
+ - @rcarls/rc-common@0.4.0
74
+
75
+ ## 0.3.2
76
+
77
+ ### Patch Changes
78
+
79
+ - Updated dependencies [88b4086]
80
+ - @rcarls/rc-common@0.3.2
81
+
82
+ ## 0.3.1
83
+
84
+ ### Patch Changes
85
+
86
+ - @rcarls/rc-common@0.3.1
87
+
88
+ ## 0.3.0
89
+
90
+ ### Changed
91
+
92
+ - Normalize menu row styling around the light-DOM `rc-base` style contract.
93
+ - Update package metadata, README intro, and docs links.
94
+
95
+ ### Dependencies
96
+
97
+ - Sync internal dependencies to 0.3.0.
98
+
99
+ ## 0.2.0
100
+
101
+ ### Patch Changes
102
+
103
+ - @rcarls/rc-common@0.2.0
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # `@rcarls/rc-menu`
2
+
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/).
6
+
7
+ Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-menu](https://richardcarls.github.io/rc-webcomponents/components/rc-menu).
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @rcarls/rc-menu
15
+ ```
16
+
17
+ ## Import
18
+
19
+ ```js
20
+ import '@rcarls/rc-menu/define';
21
+ import { RCMenu } from '@rcarls/rc-menu';
22
+ ```
23
+
24
+ The `define` entry point registers `<rc-menu>`. The package root exports the
25
+ class without registering the custom element.
26
+
27
+ ---
28
+
29
+ ## Basic usage
30
+
31
+ Slot any focusable elements as menu items. Non-focusable elements (separators,
32
+ headings) are rendered but excluded from keyboard navigation.
33
+
34
+ ```html
35
+ <rc-menu label="File">
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>
41
+ </rc-menu>
42
+ ```
43
+
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.
48
+
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.
53
+
54
+ ## Interaction model
55
+
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
+
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.
66
+
67
+ ## Events
68
+
69
+ Both events bubble across shadow boundaries and are not cancelable.
70
+
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 |
75
+
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
+
79
+ ## Styling
80
+
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.
86
+
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.
@@ -0,0 +1,465 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "readme": "",
4
+ "modules": [
5
+ {
6
+ "kind": "javascript-module",
7
+ "path": "src/define.ts",
8
+ "declarations": [],
9
+ "exports": [
10
+ {
11
+ "kind": "custom-element-definition",
12
+ "name": "rc-menu",
13
+ "declaration": {
14
+ "name": "RCMenu",
15
+ "module": "/src/index.js"
16
+ }
17
+ },
18
+ {
19
+ "kind": "js",
20
+ "name": "*",
21
+ "declaration": {
22
+ "name": "*",
23
+ "module": "src/index.js"
24
+ }
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "kind": "javascript-module",
30
+ "path": "src/index.ts",
31
+ "declarations": [],
32
+ "exports": [
33
+ {
34
+ "kind": "js",
35
+ "name": "*",
36
+ "declaration": {
37
+ "name": "*",
38
+ "module": "src/rc-menu.js"
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "kind": "javascript-module",
45
+ "path": "src/rc-menu.ts",
46
+ "declarations": [
47
+ {
48
+ "kind": "class",
49
+ "description": "Menu popup for command surfaces with keyboard navigation and typed activation events,\nfollowing the WAI-ARIA Menu pattern.",
50
+ "name": "RCMenu",
51
+ "cssProperties": [
52
+ {
53
+ "description": "Minimum width of the menu panel.",
54
+ "name": "--rc-menu-min-width",
55
+ "default": "10em"
56
+ },
57
+ {
58
+ "description": "Block (top/bottom) padding inside the panel.",
59
+ "name": "--rc-menu-padding-block",
60
+ "default": "0.25em"
61
+ },
62
+ {
63
+ "description": "Background color; falls back through --rc-surface.",
64
+ "name": "--rc-menu-background",
65
+ "default": "Canvas"
66
+ },
67
+ {
68
+ "description": "Border; falls back through --rc-border.",
69
+ "name": "--rc-menu-border",
70
+ "default": "1px solid ButtonBorder"
71
+ },
72
+ {
73
+ "description": "Menu panel border radius.",
74
+ "name": "--rc-menu-radius",
75
+ "default": "var(--rc-control-radius)"
76
+ },
77
+ {
78
+ "description": "Box shadow; falls back through --rc-shadow.",
79
+ "name": "--rc-menu-shadow",
80
+ "default": "0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)"
81
+ },
82
+ {
83
+ "description": "Text color; falls back through --rc-field-text.",
84
+ "name": "--rc-menu-color",
85
+ "default": "CanvasText"
86
+ },
87
+ {
88
+ "description": "Viewport-aware maximum inline size.",
89
+ "name": "--rc-menu-max-inline-size",
90
+ "default": "calc(100dvi - 0.5rem)"
91
+ },
92
+ {
93
+ "description": "Viewport-aware maximum block size; taller menus scroll.",
94
+ "name": "--rc-menu-max-block-size",
95
+ "default": "calc(100dvb - 0.5rem)"
96
+ },
97
+ {
98
+ "description": "Minimum block size for menu item rows.",
99
+ "name": "--rc-menu-item-min-block-size",
100
+ "default": "0"
101
+ },
102
+ {
103
+ "description": "Menu item block-axis padding.",
104
+ "name": "--rc-menu-item-padding-block",
105
+ "default": "var(--rc-item-padding-block)"
106
+ },
107
+ {
108
+ "description": "Menu item inline-axis padding.",
109
+ "name": "--rc-menu-item-padding-inline",
110
+ "default": "var(--rc-item-padding-inline)"
111
+ },
112
+ {
113
+ "description": "Gap between flex children inside each item.",
114
+ "name": "--rc-menu-item-gap",
115
+ "default": "var(--rc-item-gap)"
116
+ },
117
+ {
118
+ "description": "CSS transition applied to each item row.",
119
+ "name": "--rc-menu-item-transition"
120
+ },
121
+ {
122
+ "description": "Pointer-hover background.",
123
+ "name": "--rc-menu-hover-bg",
124
+ "default": "color-mix(in srgb, Highlight 8%, transparent)"
125
+ },
126
+ {
127
+ "description": "Pointer-hover text color.",
128
+ "name": "--rc-menu-hover-color",
129
+ "default": "inherit"
130
+ },
131
+ {
132
+ "description": "Virtual-cursor active background.",
133
+ "name": "--rc-menu-active-bg",
134
+ "default": "color-mix(in srgb, Highlight 8%, transparent)"
135
+ },
136
+ {
137
+ "description": "Virtual-cursor active text color.",
138
+ "name": "--rc-menu-active-color",
139
+ "default": "inherit"
140
+ },
141
+ {
142
+ "description": "Width of the trailing indicator column for checkbox/radio items.",
143
+ "name": "--rc-menu-check-size",
144
+ "default": "1.25em"
145
+ },
146
+ {
147
+ "description": "Color of the check/radio symbol.",
148
+ "name": "--rc-menu-check-color",
149
+ "default": "inherit"
150
+ },
151
+ {
152
+ "description": "Font size of `[data-rc-menu-shortcut]` labels.",
153
+ "name": "--rc-menu-shortcut-size",
154
+ "default": "0.8em"
155
+ },
156
+ {
157
+ "description": "Text color of `[data-rc-menu-shortcut]` labels.",
158
+ "name": "--rc-menu-shortcut-color",
159
+ "default": "GrayText"
160
+ },
161
+ {
162
+ "description": "Disabled menu item text color.",
163
+ "name": "--rc-menu-disabled-color",
164
+ "default": "GrayText"
165
+ },
166
+ {
167
+ "description": "Disabled menu item opacity.",
168
+ "name": "--rc-menu-disabled-opacity",
169
+ "default": "var(--rc-disabled-opacity)"
170
+ },
171
+ {
172
+ "description": "Separator border.",
173
+ "name": "--rc-menu-separator-border",
174
+ "default": "1px solid ButtonBorder"
175
+ },
176
+ {
177
+ "description": "Separator block-axis margin.",
178
+ "name": "--rc-menu-separator-margin-block",
179
+ "default": "var(--rc-control-gap)"
180
+ },
181
+ {
182
+ "description": "Separator inline margin; set to match item padding for inset separators.",
183
+ "name": "--rc-menu-separator-margin-inline",
184
+ "default": "0px"
185
+ },
186
+ {
187
+ "description": "Block padding on `[data-group-label]` section headings.",
188
+ "name": "--rc-menu-group-label-padding-block",
189
+ "default": "0.1em"
190
+ },
191
+ {
192
+ "description": "Text color of `[data-group-label]` section headings.",
193
+ "name": "--rc-menu-group-label-color",
194
+ "default": "GrayText"
195
+ }
196
+ ],
197
+ "slots": [
198
+ {
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 trailing indicator column via `::after` (coexists with a leading icon the item supplies itself); `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, use `[data-rc-menu-leading]`, `[data-rc-menu-label]`, and `[data-rc-menu-trailing]` to identify content columns. `[data-rc-menu-shortcut]` (and the legacy `[data-menu-shortcut]`) is pushed to the inline-end edge. - Nested menu triggers supply their own trailing indicator through `rc-menu-button`'s `indicator` slot.",
200
+ "name": "default"
201
+ }
202
+ ],
203
+ "members": [
204
+ {
205
+ "kind": "field",
206
+ "name": "_$styledRoots",
207
+ "privacy": "private",
208
+ "static": true,
209
+ "readonly": true,
210
+ "default": "new Set<Document | ShadowRoot>()"
211
+ },
212
+ {
213
+ "kind": "method",
214
+ "name": "_ensureBaseStyles",
215
+ "privacy": "private",
216
+ "static": true,
217
+ "return": {
218
+ "type": {
219
+ "text": "void"
220
+ }
221
+ },
222
+ "parameters": [
223
+ {
224
+ "name": "$root",
225
+ "type": {
226
+ "text": "Document | ShadowRoot"
227
+ }
228
+ }
229
+ ]
230
+ },
231
+ {
232
+ "kind": "field",
233
+ "name": "label",
234
+ "type": {
235
+ "text": "string"
236
+ },
237
+ "default": "''",
238
+ "description": "Accessible label for this menu. Synced to `aria-label` on the host.\n\nPrefer `aria-labelledby` for a visible label associated via ID.",
239
+ "attribute": "label"
240
+ },
241
+ {
242
+ "kind": "field",
243
+ "name": "_$menuItems",
244
+ "type": {
245
+ "text": "Element[]"
246
+ },
247
+ "privacy": "protected",
248
+ "default": "[]"
249
+ },
250
+ {
251
+ "kind": "field",
252
+ "name": "_activeDescendantCtrl",
253
+ "privacy": "protected",
254
+ "readonly": true,
255
+ "default": "new ActiveDescendantController(this, { host: () => this, items: () => this._$menuItems, })"
256
+ },
257
+ {
258
+ "kind": "field",
259
+ "name": "items",
260
+ "type": {
261
+ "text": "readonly Element[]"
262
+ },
263
+ "description": "All navigable menu item elements in document order.",
264
+ "readonly": true
265
+ },
266
+ {
267
+ "kind": "method",
268
+ "name": "focusFirst",
269
+ "return": {
270
+ "type": {
271
+ "text": "void"
272
+ }
273
+ },
274
+ "description": "Move the virtual cursor and DOM focus to the first navigable item."
275
+ },
276
+ {
277
+ "kind": "method",
278
+ "name": "focusLast",
279
+ "return": {
280
+ "type": {
281
+ "text": "void"
282
+ }
283
+ },
284
+ "description": "Move the virtual cursor and DOM focus to the last navigable item."
285
+ },
286
+ {
287
+ "kind": "method",
288
+ "name": "focusItem",
289
+ "return": {
290
+ "type": {
291
+ "text": "void"
292
+ }
293
+ },
294
+ "parameters": [
295
+ {
296
+ "name": "$item",
297
+ "optional": true,
298
+ "type": {
299
+ "text": "Element | null"
300
+ },
301
+ "description": "The element to make active. No-op when null or undefined."
302
+ }
303
+ ],
304
+ "description": "Move the virtual cursor to a specific item."
305
+ },
306
+ {
307
+ "kind": "method",
308
+ "name": "focusItemAt",
309
+ "return": {
310
+ "type": {
311
+ "text": "void"
312
+ }
313
+ },
314
+ "parameters": [
315
+ {
316
+ "name": "index",
317
+ "type": {
318
+ "text": "number"
319
+ },
320
+ "description": "Zero-based index into the navigable items array."
321
+ }
322
+ ],
323
+ "description": "Move the virtual cursor to the item at `index`."
324
+ },
325
+ {
326
+ "kind": "method",
327
+ "name": "_syncAriaLabel",
328
+ "privacy": "protected",
329
+ "return": {
330
+ "type": {
331
+ "text": "void"
332
+ }
333
+ }
334
+ },
335
+ {
336
+ "kind": "method",
337
+ "name": "_onSlotChange",
338
+ "privacy": "protected",
339
+ "return": {
340
+ "type": {
341
+ "text": "void"
342
+ }
343
+ },
344
+ "parameters": [
345
+ {
346
+ "name": "e",
347
+ "type": {
348
+ "text": "Event"
349
+ }
350
+ }
351
+ ]
352
+ },
353
+ {
354
+ "kind": "method",
355
+ "name": "_collectMenuItems",
356
+ "privacy": "protected",
357
+ "return": {
358
+ "type": {
359
+ "text": "Element[]"
360
+ }
361
+ },
362
+ "parameters": [
363
+ {
364
+ "name": "$slot",
365
+ "type": {
366
+ "text": "HTMLSlotElement"
367
+ }
368
+ }
369
+ ],
370
+ "description": "Collects navigable menu items from the slot, supporting `role=\"group\"`\ncontainers and skipping `role=\"separator\"` elements.\n\nDirect focusable children are included as-is. Elements with\n`role=\"group\"` are traversed one level deep so their focusable children\nparticipate in arrow-key navigation. Separators and non-focusable\ncontainer elements are excluded."
371
+ },
372
+ {
373
+ "kind": "method",
374
+ "name": "_initMenuItems",
375
+ "privacy": "protected",
376
+ "return": {
377
+ "type": {
378
+ "text": "void"
379
+ }
380
+ }
381
+ },
382
+ {
383
+ "kind": "method",
384
+ "name": "_applyCheckedState",
385
+ "privacy": "protected",
386
+ "return": {
387
+ "type": {
388
+ "text": "'true' | 'false' | 'mixed' | undefined"
389
+ }
390
+ },
391
+ "parameters": [
392
+ {
393
+ "name": "$item",
394
+ "type": {
395
+ "text": "HTMLElement"
396
+ }
397
+ }
398
+ ]
399
+ },
400
+ {
401
+ "kind": "field",
402
+ "name": "_onKeydown",
403
+ "privacy": "protected"
404
+ },
405
+ {
406
+ "kind": "field",
407
+ "name": "_onBlur",
408
+ "privacy": "protected"
409
+ },
410
+ {
411
+ "kind": "field",
412
+ "name": "_onClick",
413
+ "privacy": "protected"
414
+ }
415
+ ],
416
+ "events": [
417
+ {
418
+ "description": "Fired when a menu item is activated via keyboard (Enter/Space) or pointer click.",
419
+ "name": "rc-menu-activate"
420
+ },
421
+ {
422
+ "description": "Fired when Escape is pressed.",
423
+ "name": "rc-menu-close"
424
+ }
425
+ ],
426
+ "attributes": [
427
+ {
428
+ "description": "Accessible label for this menu. Synced to `aria-label` on the host.\n\nPrefer `aria-labelledby` for a visible label associated via ID.",
429
+ "name": "label",
430
+ "type": {
431
+ "text": "string"
432
+ },
433
+ "default": "''",
434
+ "fieldName": "label"
435
+ }
436
+ ],
437
+ "superclass": {
438
+ "name": "LitElement",
439
+ "package": "lit"
440
+ },
441
+ "tagName": "rc-menu",
442
+ "customElement": true
443
+ }
444
+ ],
445
+ "exports": [
446
+ {
447
+ "kind": "js",
448
+ "name": "RCMenu",
449
+ "declaration": {
450
+ "name": "RCMenu",
451
+ "module": "src/rc-menu.ts"
452
+ }
453
+ },
454
+ {
455
+ "kind": "js",
456
+ "name": "default",
457
+ "declaration": {
458
+ "name": "RCMenu",
459
+ "module": "src/rc-menu.ts"
460
+ }
461
+ }
462
+ ]
463
+ }
464
+ ]
465
+ }
@@ -0,0 +1,353 @@
1
+ import { css as u, LitElement as m, html as d } from "lit";
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`
5
+ :host {
6
+ box-sizing: border-box;
7
+ display: flex;
8
+ flex-direction: column;
9
+ min-width: var(--rc-menu-min-width, 10em);
10
+ padding-block: var(--rc-menu-padding-block, var(--rc-control-padding-block, 0.25em));
11
+ background: var(--rc-menu-background, var(--rc-surface, Canvas));
12
+ border: var(--rc-menu-border, var(--rc-border, 1px solid ButtonBorder));
13
+ border-radius: var(--rc-menu-radius, var(--rc-control-radius, 0));
14
+ box-shadow: var(
15
+ --rc-menu-shadow,
16
+ var(--rc-shadow, 0 2px 8px color-mix(in srgb, CanvasText 15%, transparent))
17
+ );
18
+ color: var(--rc-menu-color, var(--rc-field-text, CanvasText));
19
+ font-family: var(--rc-font-family, inherit);
20
+ font-size: var(--rc-font-size, inherit);
21
+ line-height: var(--rc-line-height, normal);
22
+ max-inline-size: var(--rc-menu-max-inline-size, calc(100dvi - 0.5rem));
23
+ max-block-size: var(--rc-menu-max-block-size, calc(100dvb - 0.5rem));
24
+ overflow: auto;
25
+ overscroll-behavior: contain;
26
+ scrollbar-gutter: stable;
27
+ }
28
+
29
+ :host(:focus-visible) {
30
+ outline: var(--rc-focus-ring, auto);
31
+ outline-offset: var(--rc-focus-ring-offset, 0);
32
+ }
33
+ `;
34
+ var p = Object.defineProperty, g = (o, e, t, r) => {
35
+ for (var n = void 0, i = o.length - 1, c; i >= 0; i--)
36
+ (c = o[i]) && (n = c(e, t, n) || n);
37
+ return n && p(e, t, n), n;
38
+ };
39
+ const f = ["menuitem", "menuitemcheckbox", "menuitemradio"], k = `
40
+ @layer rc-base {
41
+ /* Item base layout */
42
+ rc-menu [role='menuitem'],
43
+ rc-menu [role='menuitemcheckbox'],
44
+ rc-menu [role='menuitemradio'],
45
+ rc-menu button:disabled {
46
+ display: flex;
47
+ flex-shrink: 0;
48
+ align-items: center;
49
+ gap: var(--rc-menu-item-gap, var(--rc-item-gap, 0.5em));
50
+ min-block-size: var(--rc-menu-item-min-block-size, 0px);
51
+ width: 100%;
52
+ text-align: start;
53
+ padding: var(--rc-menu-item-padding-block, var(--rc-item-padding-block, 0.3em))
54
+ var(--rc-menu-item-padding-inline, var(--rc-item-padding-inline, 0.75em));
55
+ appearance: none;
56
+ box-sizing: border-box;
57
+ border: none;
58
+ background: transparent;
59
+ text-decoration: none;
60
+ color: inherit;
61
+ font: inherit;
62
+ cursor: default;
63
+ user-select: none;
64
+ outline: none;
65
+ transition: var(--rc-menu-item-transition);
66
+ }
67
+
68
+ /* Pointer hover */
69
+ rc-menu [role='menuitem']:hover,
70
+ rc-menu [role='menuitemcheckbox']:hover,
71
+ rc-menu [role='menuitemradio']:hover {
72
+ background: var(--rc-menu-hover-bg, color-mix(in srgb, Highlight 8%, transparent));
73
+ color: var(--rc-menu-hover-color, inherit);
74
+ }
75
+
76
+ /* Virtual cursor (set by ActiveDescendantController) */
77
+ rc-menu [role='menuitem'][data-active],
78
+ rc-menu [role='menuitemcheckbox'][data-active],
79
+ rc-menu [role='menuitemradio'][data-active] {
80
+ background: var(--rc-menu-active-bg, color-mix(in srgb, Highlight 8%, transparent));
81
+ color: var(--rc-menu-active-color, inherit);
82
+ }
83
+
84
+ /* Check/radio indicator — ::after acts as a fixed-width trailing column,
85
+ pushed to the item's inline-end regardless of label length (same
86
+ positioning as the [data-rc-menu-shortcut] slot below). Trailing rather
87
+ than leading so it coexists with a leading icon a consumer supplies for
88
+ the item itself (a radio/checkbox item's own icon and its checked state
89
+ are different things; leading is already spoken for). Content is empty
90
+ (unchecked) or a Unicode symbol (checked/mixed). Semantic state is
91
+ conveyed by aria-checked; the visual symbol is decorative. */
92
+ rc-menu [role='menuitemcheckbox']::after,
93
+ rc-menu [role='menuitemradio']::after {
94
+ content: '';
95
+ flex: 0 0 var(--rc-menu-check-size, 1.25em);
96
+ margin-inline-start: auto;
97
+ text-align: center;
98
+ line-height: 1;
99
+ color: var(--rc-menu-check-color, inherit);
100
+ }
101
+
102
+ /* A shortcut owns the auto margin when both trailing affordances exist,
103
+ keeping the shortcut and selection indicator in one end-aligned group. */
104
+ rc-menu [role='menuitemcheckbox']:has(:is([data-rc-menu-shortcut], [data-menu-shortcut]))::after,
105
+ rc-menu [role='menuitemradio']:has(:is([data-rc-menu-shortcut], [data-menu-shortcut]))::after {
106
+ margin-inline-start: 0;
107
+ }
108
+
109
+ rc-menu [role='menuitemcheckbox'][aria-checked='true']::after {
110
+ content: '✓';
111
+ }
112
+
113
+ rc-menu [role='menuitemcheckbox'][aria-checked='mixed']::after {
114
+ content: '−';
115
+ }
116
+
117
+ rc-menu [role='menuitemradio'][aria-checked='true']::after {
118
+ content: '•';
119
+ }
120
+
121
+ rc-menu [data-rc-menu-leading],
122
+ rc-menu [data-rc-menu-trailing] {
123
+ flex: none;
124
+ display: inline-flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ }
128
+
129
+ rc-menu [data-rc-menu-label] {
130
+ flex: 1 1 auto;
131
+ min-inline-size: 0;
132
+ }
133
+
134
+ /* Trailing label — keyboard shortcut or hint, pushed to the inline-end of the item */
135
+ rc-menu [data-rc-menu-trailing],
136
+ rc-menu [data-rc-menu-shortcut],
137
+ rc-menu [data-menu-shortcut] {
138
+ margin-inline-start: auto;
139
+ }
140
+
141
+ rc-menu [data-rc-menu-shortcut],
142
+ rc-menu [data-menu-shortcut] {
143
+ font-size: var(--rc-menu-shortcut-size, 0.8em);
144
+ color: var(--rc-menu-shortcut-color, GrayText);
145
+ white-space: nowrap;
146
+ }
147
+
148
+ /* Separators */
149
+ rc-menu [role='separator'],
150
+ rc-menu hr {
151
+ border: none;
152
+ border-block-start: var(--rc-menu-separator-border, var(--rc-border, 1px solid ButtonBorder));
153
+ margin-block: var(--rc-menu-separator-margin-block, var(--rc-control-gap, 0.25em));
154
+ margin-inline: var(--rc-menu-separator-margin-inline, 0px);
155
+ }
156
+
157
+ /* Group containers */
158
+ rc-menu [role='group'] {
159
+ display: flex;
160
+ flex-direction: column;
161
+ align-items: stretch;
162
+ }
163
+
164
+ /* Optional section label (non-interactive heading above a group of items) */
165
+ rc-menu [data-group-label] {
166
+ padding-block: var(--rc-menu-group-label-padding-block, 0.1em);
167
+ padding-inline: var(--rc-menu-item-padding-inline, 0.75em);
168
+ color: var(--rc-menu-group-label-color, GrayText);
169
+ user-select: none;
170
+ pointer-events: none;
171
+ }
172
+
173
+ /* Disabled items */
174
+ rc-menu button:disabled,
175
+ rc-menu [aria-disabled='true'] {
176
+ color: var(--rc-menu-disabled-color, var(--rc-text-disabled, GrayText));
177
+ opacity: var(--rc-menu-disabled-opacity, var(--rc-disabled-opacity, 0.55));
178
+ pointer-events: none;
179
+ }
180
+ }
181
+ `;
182
+ var a;
183
+ const l = (a = class extends m {
184
+ constructor() {
185
+ super(...arguments), this.label = "", this._$menuItems = [], this._activeDescendantCtrl = new b(this, {
186
+ host: () => this,
187
+ items: () => this._$menuItems
188
+ }), this._onKeydown = (e) => {
189
+ switch (e.key) {
190
+ case "ArrowDown": {
191
+ e.preventDefault(), e.stopPropagation(), this._activeDescendantCtrl.activeItem ? this._activeDescendantCtrl.navigate(1) : this._activeDescendantCtrl.navigateToFirst();
192
+ break;
193
+ }
194
+ case "ArrowUp": {
195
+ e.preventDefault(), e.stopPropagation(), this._activeDescendantCtrl.activeItem ? this._activeDescendantCtrl.navigate(-1) : this._activeDescendantCtrl.navigateToLast();
196
+ break;
197
+ }
198
+ case "Home": {
199
+ e.preventDefault(), e.stopPropagation(), this._activeDescendantCtrl.navigateToFirst();
200
+ break;
201
+ }
202
+ case "End": {
203
+ e.preventDefault(), e.stopPropagation(), this._activeDescendantCtrl.navigateToLast();
204
+ break;
205
+ }
206
+ case "Enter":
207
+ // Falls through so both activation keys share the same handler.
208
+ case " ": {
209
+ e.preventDefault(), e.stopPropagation();
210
+ const t = this._activeDescendantCtrl.activeItem;
211
+ t && t.click();
212
+ break;
213
+ }
214
+ case "Escape": {
215
+ e.preventDefault(), e.stopPropagation(), this.dispatchEvent(
216
+ new CustomEvent("rc-menu-close", {
217
+ bubbles: !0,
218
+ composed: !0,
219
+ detail: { reason: "escape" }
220
+ })
221
+ );
222
+ break;
223
+ }
224
+ }
225
+ }, this._onBlur = () => {
226
+ this._activeDescendantCtrl.clear();
227
+ }, this._onClick = (e) => {
228
+ const r = e.target.closest(
229
+ '[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"]'
230
+ );
231
+ if (!r || !this.contains(r) || r.matches(':disabled, [aria-disabled="true"]'))
232
+ return;
233
+ const n = this._applyCheckedState(r);
234
+ this.dispatchEvent(
235
+ new CustomEvent("rc-menu-activate", {
236
+ bubbles: !0,
237
+ composed: !0,
238
+ detail: {
239
+ item: r,
240
+ value: r.dataset.value ?? r.getAttribute("value") ?? "",
241
+ text: r.textContent?.trim() ?? "",
242
+ checked: n
243
+ }
244
+ })
245
+ );
246
+ };
247
+ }
248
+ static _ensureBaseStyles(e) {
249
+ if (a._$styledRoots.has(e))
250
+ return;
251
+ a._$styledRoots.add(e);
252
+ const t = document.createElement("style");
253
+ t.setAttribute("data-rc-light-dom-base", "rc-menu"), t.textContent = k, e instanceof Document ? e.head.appendChild(t) : e.appendChild(t);
254
+ }
255
+ connectedCallback() {
256
+ super.connectedCallback(), this.hasAttribute("role") || this.setAttribute("role", "menu"), this.setAttribute("tabindex", "0"), this._syncAriaLabel(), this.addEventListener("keydown", this._onKeydown), this.addEventListener("blur", this._onBlur), this.addEventListener("click", this._onClick), a._ensureBaseStyles(this.getRootNode());
257
+ }
258
+ disconnectedCallback() {
259
+ super.disconnectedCallback(), this.removeEventListener("keydown", this._onKeydown), this.removeEventListener("blur", this._onBlur), this.removeEventListener("click", this._onClick);
260
+ }
261
+ updated(e) {
262
+ e.has("label") && this._syncAriaLabel();
263
+ }
264
+ render() {
265
+ return d`<slot @slotchange=${this._onSlotChange}></slot>`;
266
+ }
267
+ /** All navigable menu item elements in document order. */
268
+ get items() {
269
+ return this._$menuItems;
270
+ }
271
+ /** Move the virtual cursor and DOM focus to the first navigable item. */
272
+ focusFirst() {
273
+ this.focus(), this._activeDescendantCtrl.navigateToFirst();
274
+ }
275
+ /** Move the virtual cursor and DOM focus to the last navigable item. */
276
+ focusLast() {
277
+ this.focus(), this._activeDescendantCtrl.navigateToLast();
278
+ }
279
+ /**
280
+ * Move the virtual cursor to a specific item.
281
+ *
282
+ * @param $item - The element to make active. No-op when null or undefined.
283
+ */
284
+ focusItem(e) {
285
+ e && (this.focus(), this._activeDescendantCtrl.navigateToItem(e));
286
+ }
287
+ /**
288
+ * Move the virtual cursor to the item at `index`.
289
+ *
290
+ * @param index - Zero-based index into the navigable items array.
291
+ */
292
+ focusItemAt(e) {
293
+ this.focus(), this._activeDescendantCtrl.navigateTo(e);
294
+ }
295
+ _syncAriaLabel() {
296
+ this.label ? this.setAttribute("aria-label", this.label) : this.removeAttribute("aria-label");
297
+ }
298
+ _onSlotChange(e) {
299
+ const t = e.currentTarget;
300
+ this._$menuItems = this._collectMenuItems(t), this._initMenuItems();
301
+ }
302
+ /**
303
+ * Collects navigable menu items from the slot, supporting `role="group"`
304
+ * containers and skipping `role="separator"` elements.
305
+ *
306
+ * Direct focusable children are included as-is. Elements with
307
+ * `role="group"` are traversed one level deep so their focusable children
308
+ * participate in arrow-key navigation. Separators and non-focusable
309
+ * container elements are excluded.
310
+ */
311
+ _collectMenuItems(e) {
312
+ const t = [];
313
+ for (const r of e.assignedElements()) {
314
+ const n = r.getAttribute("role");
315
+ if (n !== "separator") {
316
+ if (n === "group") {
317
+ for (const i of r.children)
318
+ s(i) && t.push(i);
319
+ continue;
320
+ }
321
+ s(r) && t.push(r);
322
+ }
323
+ }
324
+ return t;
325
+ }
326
+ _initMenuItems() {
327
+ this._$menuItems.forEach((e) => {
328
+ const t = e.getAttribute("role");
329
+ (!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");
330
+ });
331
+ }
332
+ _applyCheckedState(e) {
333
+ const t = e.getAttribute("role");
334
+ if (t === "menuitemcheckbox") {
335
+ const r = e.getAttribute("aria-checked") === "true" ? "false" : "true";
336
+ return e.setAttribute("aria-checked", r), r;
337
+ }
338
+ if (t === "menuitemradio") {
339
+ const r = e.closest('[role="group"]'), n = r && this.contains(r) ? r : this;
340
+ for (const i of n.querySelectorAll('[role="menuitemradio"]'))
341
+ i.closest("rc-menu") === this && i.setAttribute("aria-checked", i === e ? "true" : "false");
342
+ return "true";
343
+ }
344
+ }
345
+ }, a.styles = [v], a._$styledRoots = /* @__PURE__ */ new Set(), a);
346
+ g([
347
+ h({ type: String })
348
+ ], l.prototype, "label");
349
+ let C = l;
350
+ export {
351
+ C as R
352
+ };
353
+ //# sourceMappingURL=rc-menu-Df7GAw1H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-menu-Df7GAw1H.js","sources":["../src/rc-menu.styles.ts","../src/rc-menu.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const menuStyles = css`\n :host {\n box-sizing: border-box;\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 max-inline-size: var(--rc-menu-max-inline-size, calc(100dvi - 0.5rem));\n max-block-size: var(--rc-menu-max-block-size, calc(100dvb - 0.5rem));\n overflow: auto;\n overscroll-behavior: contain;\n scrollbar-gutter: stable;\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.js';\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 flex-shrink: 0;\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 — ::after acts as a fixed-width trailing column,\n pushed to the item's inline-end regardless of label length (same\n positioning as the [data-rc-menu-shortcut] slot below). Trailing rather\n than leading so it coexists with a leading icon a consumer supplies for\n the item itself (a radio/checkbox item's own icon and its checked state\n are different things; leading is already spoken for). Content is empty\n (unchecked) or a Unicode symbol (checked/mixed). Semantic state is\n conveyed by aria-checked; the visual symbol is decorative. */\n rc-menu [role='menuitemcheckbox']::after,\n rc-menu [role='menuitemradio']::after {\n content: '';\n flex: 0 0 var(--rc-menu-check-size, 1.25em);\n margin-inline-start: auto;\n text-align: center;\n line-height: 1;\n color: var(--rc-menu-check-color, inherit);\n }\n\n /* A shortcut owns the auto margin when both trailing affordances exist,\n keeping the shortcut and selection indicator in one end-aligned group. */\n rc-menu [role='menuitemcheckbox']:has(:is([data-rc-menu-shortcut], [data-menu-shortcut]))::after,\n rc-menu [role='menuitemradio']:has(:is([data-rc-menu-shortcut], [data-menu-shortcut]))::after {\n margin-inline-start: 0;\n }\n\n rc-menu [role='menuitemcheckbox'][aria-checked='true']::after {\n content: '✓';\n }\n\n rc-menu [role='menuitemcheckbox'][aria-checked='mixed']::after {\n content: '−';\n }\n\n rc-menu [role='menuitemradio'][aria-checked='true']::after {\n content: '•';\n }\n\n rc-menu [data-rc-menu-leading],\n rc-menu [data-rc-menu-trailing] {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n rc-menu [data-rc-menu-label] {\n flex: 1 1 auto;\n min-inline-size: 0;\n }\n\n /* Trailing label — keyboard shortcut or hint, pushed to the inline-end of the item */\n rc-menu [data-rc-menu-trailing],\n rc-menu [data-rc-menu-shortcut],\n rc-menu [data-menu-shortcut] {\n margin-inline-start: auto;\n }\n\n rc-menu [data-rc-menu-shortcut],\n rc-menu [data-menu-shortcut] {\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 trailing indicator column via `::after`\n * (coexists with a leading icon the item supplies itself); `aria-checked` is initialized when missing and\n * 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, use `[data-rc-menu-leading]`, `[data-rc-menu-label]`, and\n * `[data-rc-menu-trailing]` to identify content columns. `[data-rc-menu-shortcut]`\n * (and the legacy `[data-menu-shortcut]`) is pushed to the inline-end edge.\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-max-inline-size=calc(100dvi - 0.5rem)] - Viewport-aware maximum inline size.\n * @cssprop [--rc-menu-max-block-size=calc(100dvb - 0.5rem)] - Viewport-aware maximum block size; taller menus scroll.\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 trailing 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-rc-menu-shortcut]` labels.\n * @cssprop [--rc-menu-shortcut-color=GrayText] - Text color of `[data-rc-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":";;;AAEO,MAAMA,IAAaC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;AC2B1B,MAAMC,IAAkB,CAAC,YAAY,oBAAoB,eAAe,GAGlEC,IAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAwMf,MAAMC,KAANC,IAAA,cAAqBC,EAAW;AAAA,EAAhC,cAAA;AAAA,UAAA,GAAA,SAAA,GA8BL,KAAA,QAAQ,IAER,KAAU,cAAyB,CAAA,GAEnC,KAAmB,wBAAwB,IAAIC,EAA2B,MAAM;AAAA,MAC9E,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM,KAAK;AAAA,IAAA,CACnB,GAqLD,KAAU,aAAa,CAAC,MAA2B;AACjD,cAAQ,EAAE,KAAA;AAAA,QACR,KAAK,aAAa;AAChB,YAAE,eAAA,GACF,EAAE,gBAAA,GAEE,KAAK,sBAAsB,aAC7B,KAAK,sBAAsB,SAAS,CAAC,IAErC,KAAK,sBAAsB,gBAAA;AAG7B;AAAA,QACF;AAAA,QAEA,KAAK,WAAW;AACd,YAAE,eAAA,GACF,EAAE,gBAAA,GAEE,KAAK,sBAAsB,aAC7B,KAAK,sBAAsB,SAAS,EAAE,IAEtC,KAAK,sBAAsB,eAAA;AAG7B;AAAA,QACF;AAAA,QAEA,KAAK,QAAQ;AACX,YAAE,eAAA,GACF,EAAE,gBAAA,GAEF,KAAK,sBAAsB,gBAAA;AAE3B;AAAA,QACF;AAAA,QAEA,KAAK,OAAO;AACV,YAAE,eAAA,GACF,EAAE,gBAAA,GAEF,KAAK,sBAAsB,eAAA;AAE3B;AAAA,QACF;AAAA,QAEA,KAAK;AAAA;AAAA,QAGL,KAAK,KAAK;AACR,YAAE,eAAA,GACF,EAAE,gBAAA;AAEF,gBAAMC,IAAU,KAAK,sBAAsB;AAE3C,UAAIA,KAEDA,EAAwB,MAAA;AAG3B;AAAA,QACF;AAAA,QAEA,KAAK,UAAU;AACb,YAAE,eAAA,GACF,EAAE,gBAAA,GAEF,KAAK;AAAA,YACH,IAAI,YAAY,iBAAiB;AAAA,cAC/B,SAAS;AAAA,cACT,UAAU;AAAA,cACV,QAAQ,EAAE,QAAQ,SAAA;AAAA,YAAS,CAC5B;AAAA,UAAA;AAGH;AAAA,QACF;AAAA,MAAA;AAAA,IAEJ,GAEA,KAAU,UAAU,MAAY;AAC9B,WAAK,sBAAsB,MAAA;AAAA,IAC7B,GAEA,KAAU,WAAW,CAAC,MAAwB;AAE5C,YAAMC,IADU,EAAE,OACI;AAAA,QACpB;AAAA,MAAA;AAOF,UAJI,CAACA,KAAS,CAAC,KAAK,SAASA,CAAK,KAI9BA,EAAM,QAAQ,mCAAmC;AACnD;AAGF,YAAMC,IAAU,KAAK,mBAAmBD,CAAK;AAE7C,WAAK;AAAA,QACH,IAAI,YAAiC,oBAAoB;AAAA,UACvD,SAAS;AAAA,UACT,UAAU;AAAA,UACV,QAAQ;AAAA,YACN,MAAMA;AAAA,YACN,OAAOA,EAAM,QAAQ,SAASA,EAAM,aAAa,OAAO,KAAK;AAAA,YAC7D,MAAMA,EAAM,aAAa,KAAA,KAAU;AAAA,YACnC,SAAAC;AAAA,UAAA;AAAA,QACF,CACD;AAAA,MAAA;AAAA,IAEL;AAAA,EAAA;AAAA,EArUA,OAAe,kBAAkBC,GAAoC;AACnE,QAAIN,EAAO,cAAc,IAAIM,CAAK;AAChC;AAGF,IAAAN,EAAO,cAAc,IAAIM,CAAK;AAE9B,UAAMC,IAAS,SAAS,cAAc,OAAO;AAE7C,IAAAA,EAAO,aAAa,0BAA0B,SAAS,GACvDA,EAAO,cAAcT,GAEjBQ,aAAiB,WACnBA,EAAM,KAAK,YAAYC,CAAM,IAE7BD,EAAM,YAAYC,CAAM;AAAA,EAE5B;AAAA,EAiBS,oBAA0B;AACjC,UAAM,kBAAA,GAED,KAAK,aAAa,MAAM,KAC3B,KAAK,aAAa,QAAQ,MAAM,GAGlC,KAAK,aAAa,YAAY,GAAG,GACjC,KAAK,eAAA,GAEL,KAAK,iBAAiB,WAAW,KAAK,UAAU,GAChD,KAAK,iBAAiB,QAAQ,KAAK,OAAO,GAC1C,KAAK,iBAAiB,SAAS,KAAK,QAAQ,GAE5CP,EAAO,kBAAkB,KAAK,aAAsC;AAAA,EACtE;AAAA,EAES,uBAA6B;AACpC,UAAM,qBAAA,GAEN,KAAK,oBAAoB,WAAW,KAAK,UAAU,GACnD,KAAK,oBAAoB,QAAQ,KAAK,OAAO,GAC7C,KAAK,oBAAoB,SAAS,KAAK,QAAQ;AAAA,EACjD;AAAA,EAEmB,QAAQQ,GAA0C;AACnE,IAAIA,EAAQ,IAAI,OAAO,KACrB,KAAK,eAAA;AAAA,EAET;AAAA,EAEmB,SAAS;AAC1B,WAAOC,sBAAyB,KAAK,aAAa;AAAA,EACpD;AAAA;AAAA,EAGA,IAAI,QAA4B;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,aAAmB;AACjB,SAAK,MAAA,GACL,KAAK,sBAAsB,gBAAA;AAAA,EAC7B;AAAA;AAAA,EAGA,YAAkB;AAChB,SAAK,MAAA,GACL,KAAK,sBAAsB,eAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAUL,GAA8B;AACtC,IAAKA,MAIL,KAAK,MAAA,GACL,KAAK,sBAAsB,eAAeA,CAAK;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAYM,GAAqB;AAC/B,SAAK,MAAA,GACL,KAAK,sBAAsB,WAAWA,CAAK;AAAA,EAC7C;AAAA,EAEU,iBAAuB;AAC/B,IAAI,KAAK,QACP,KAAK,aAAa,cAAc,KAAK,KAAK,IAE1C,KAAK,gBAAgB,YAAY;AAAA,EAErC;AAAA,EAEU,cAAc,GAAgB;AACtC,UAAMC,IAAQ,EAAE;AAEhB,SAAK,cAAc,KAAK,kBAAkBA,CAAK,GAC/C,KAAK,eAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWU,kBAAkBA,GAAmC;AAC7D,UAAMC,IAAoB,CAAA;AAE1B,eAAWC,KAAOF,EAAM,oBAAoB;AAC1C,YAAMG,IAAOD,EAAI,aAAa,MAAM;AAEpC,UAAIC,MAAS,aAIb;AAAA,YAAIA,MAAS,SAAS;AACpB,qBAAWC,KAAUF,EAAI;AACvB,YAAIG,EAAYD,CAAM,KACpBH,EAAO,KAAKG,CAAM;AAItB;AAAA,QACF;AAEA,QAAIC,EAAYH,CAAG,KACjBD,EAAO,KAAKC,CAAG;AAAA;AAAA,IAEnB;AAEA,WAAOD;AAAA,EACT;AAAA,EAEU,iBAAuB;AAC/B,SAAK,YAAY,QAAQ,CAACC,MAAQ;AAChC,YAAMI,IAAeJ,EAAI,aAAa,MAAM;AAG5C,OACE,CAACI,KACD,CAACpB,EAAgB,SAASoB,CAAgD,MAE1EJ,EAAI,aAAa,QAAQ,UAAU,GAGrCA,EAAI,aAAa,YAAY,IAAI,IAG9BA,EAAI,aAAa,MAAM,MAAM,sBAC5BA,EAAI,aAAa,MAAM,MAAM,oBAC/B,CAACA,EAAI,aAAa,cAAc,KAEhCA,EAAI,aAAa,gBAAgB,OAAO;AAAA,IAE5C,CAAC;AAAA,EACH;AAAA,EAEU,mBAAmBT,GAA4D;AACvF,UAAMU,IAAOV,EAAM,aAAa,MAAM;AAEtC,QAAIU,MAAS,oBAAoB;AAC/B,YAAMT,IAAUD,EAAM,aAAa,cAAc,MAAM,SAAS,UAAU;AAE1E,aAAAA,EAAM,aAAa,gBAAgBC,CAAO,GAEnCA;AAAA,IACT;AAEA,QAAIS,MAAS,iBAAiB;AAC5B,YAAMI,IAASd,EAAM,QAAQ,gBAAgB,GACvCe,IAASD,KAAU,KAAK,SAASA,CAAM,IAAIA,IAAS;AAE1D,iBAAWE,KAAUD,EAAO,iBAA8B,wBAAwB;AAChF,QAAIC,EAAO,QAAQ,SAAS,MAAM,QAChCA,EAAO,aAAa,gBAAgBA,MAAWhB,IAAQ,SAAS,OAAO;AAI3E,aAAO;AAAA,IACT;AAAA,EAGF;AAmHF,GA1UEJ,EAAO,SAAS,CAACL,CAAU,GAE3BK,EAAwB,oCAAoB,IAAA,GAHvCA;AA8BLqB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GA7BfvB,EA8BX,WAAA,OAAA;AA9BK,IAAMwB,IAANxB;"}
@@ -0,0 +1,6 @@
1
+ import { R as e } from "./rc-menu-Df7GAw1H.js";
2
+ customElements.get("rc-menu") || customElements.define("rc-menu", e);
3
+ export {
4
+ e as RCMenu
5
+ };
6
+ //# sourceMappingURL=rc-menu-define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-menu-define.js","sources":["../src/define.ts"],"sourcesContent":["import { RCMenu } from './index.js';\n\ncustomElements.get('rc-menu') || customElements.define('rc-menu', RCMenu);\n\nexport * from './index.js';\n"],"names":["RCMenu"],"mappings":";AAEA,eAAe,IAAI,SAAS,KAAK,eAAe,OAAO,WAAWA,CAAM;"}
@@ -0,0 +1,5 @@
1
+ import { R as r } from "./rc-menu-Df7GAw1H.js";
2
+ export {
3
+ r as RCMenu
4
+ };
5
+ //# sourceMappingURL=rc-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-menu.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -0,0 +1 @@
1
+ export * from './rc-menu.js';
@@ -0,0 +1,127 @@
1
+ import { LitElement } from 'lit';
2
+ import { ActiveDescendantController } from '@rcarls/rc-common';
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ 'rc-menu': RCMenu;
6
+ }
7
+ }
8
+ /** Detail payload for the `rc-menu-activate` event. */
9
+ export interface RCMenuActivateEvent {
10
+ /** The activated menu item element. */
11
+ item: HTMLElement;
12
+ /** The item's `data-value` or `value` attribute, or an empty string. */
13
+ value: string;
14
+ /** Trimmed text content of the activated item. */
15
+ text: string;
16
+ /** Current `aria-checked` state for checkbox/radio menu items. */
17
+ checked?: 'true' | 'false' | 'mixed';
18
+ }
19
+ /**
20
+ * Menu popup for command surfaces with keyboard navigation and typed activation events,
21
+ * following the WAI-ARIA Menu pattern.
22
+ *
23
+ *
24
+ * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-menu rc-menu docs}
25
+ * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/menu/ WAI-ARIA Menu pattern}
26
+ *
27
+ * @slot default - Menu items, groups, and separators. Supported content model:
28
+ * - Focusable elements (`<button>`, `<a>`, `[tabindex]`) become navigable `role="menuitem"` entries.
29
+ * - `role="menuitemcheckbox"` / `role="menuitemradio"` elements get a trailing indicator column via `::after`
30
+ * (coexists with a leading icon the item supplies itself); `aria-checked` is initialized when missing and
31
+ * updated on activation.
32
+ * - `role="group"` divs group related items; label them with `aria-label` or `aria-labelledby`. Optionally
33
+ * add a visible `[data-group-label]` element as first child of the group for section headings.
34
+ * - `role="separator"` / `<hr>` produce horizontal dividers between sections.
35
+ * - Within any item, use `[data-rc-menu-leading]`, `[data-rc-menu-label]`, and
36
+ * `[data-rc-menu-trailing]` to identify content columns. `[data-rc-menu-shortcut]`
37
+ * (and the legacy `[data-menu-shortcut]`) is pushed to the inline-end edge.
38
+ * - Nested menu triggers supply their own trailing indicator through `rc-menu-button`'s `indicator` slot.
39
+ *
40
+ * @fires rc-menu-activate - Fired when a menu item is activated via keyboard (Enter/Space) or pointer click.
41
+ * @fires rc-menu-close - Fired when Escape is pressed.
42
+ *
43
+ * @attr label - Accessible label for the menu; synced to `aria-label` on the host.
44
+ *
45
+ * @cssprop [--rc-menu-min-width=10em] - Minimum width of the menu panel.
46
+ * @cssprop [--rc-menu-padding-block=0.25em] - Block (top/bottom) padding inside the panel.
47
+ * @cssprop [--rc-menu-background=Canvas] - Background color; falls back through --rc-surface.
48
+ * @cssprop [--rc-menu-border=1px solid ButtonBorder] - Border; falls back through --rc-border.
49
+ * @cssprop [--rc-menu-radius=var(--rc-control-radius)] - Menu panel border radius.
50
+ * @cssprop [--rc-menu-shadow=0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)] - Box shadow; falls back through --rc-shadow.
51
+ * @cssprop [--rc-menu-color=CanvasText] - Text color; falls back through --rc-field-text.
52
+ * @cssprop [--rc-menu-max-inline-size=calc(100dvi - 0.5rem)] - Viewport-aware maximum inline size.
53
+ * @cssprop [--rc-menu-max-block-size=calc(100dvb - 0.5rem)] - Viewport-aware maximum block size; taller menus scroll.
54
+ * @cssprop [--rc-menu-item-min-block-size=0] - Minimum block size for menu item rows.
55
+ * @cssprop [--rc-menu-item-padding-block=var(--rc-item-padding-block)] - Menu item block-axis padding.
56
+ * @cssprop [--rc-menu-item-padding-inline=var(--rc-item-padding-inline)] - Menu item inline-axis padding.
57
+ * @cssprop [--rc-menu-item-gap=var(--rc-item-gap)] - Gap between flex children inside each item.
58
+ * @cssprop [--rc-menu-item-transition] - CSS transition applied to each item row.
59
+ * @cssprop [--rc-menu-hover-bg=color-mix(in srgb, Highlight 8%, transparent)] - Pointer-hover background.
60
+ * @cssprop [--rc-menu-hover-color=inherit] - Pointer-hover text color.
61
+ * @cssprop [--rc-menu-active-bg=color-mix(in srgb, Highlight 8%, transparent)] - Virtual-cursor active background.
62
+ * @cssprop [--rc-menu-active-color=inherit] - Virtual-cursor active text color.
63
+ * @cssprop [--rc-menu-check-size=1.25em] - Width of the trailing indicator column for checkbox/radio items.
64
+ * @cssprop [--rc-menu-check-color=inherit] - Color of the check/radio symbol.
65
+ * @cssprop [--rc-menu-shortcut-size=0.8em] - Font size of `[data-rc-menu-shortcut]` labels.
66
+ * @cssprop [--rc-menu-shortcut-color=GrayText] - Text color of `[data-rc-menu-shortcut]` labels.
67
+ * @cssprop [--rc-menu-disabled-color=GrayText] - Disabled menu item text color.
68
+ * @cssprop [--rc-menu-disabled-opacity=var(--rc-disabled-opacity)] - Disabled menu item opacity.
69
+ * @cssprop [--rc-menu-separator-border=1px solid ButtonBorder] - Separator border.
70
+ * @cssprop [--rc-menu-separator-margin-block=var(--rc-control-gap)] - Separator block-axis margin.
71
+ * @cssprop [--rc-menu-separator-margin-inline=0px] - Separator inline margin; set to match item padding for inset separators.
72
+ * @cssprop [--rc-menu-group-label-padding-block=0.1em] - Block padding on `[data-group-label]` section headings.
73
+ * @cssprop [--rc-menu-group-label-color=GrayText] - Text color of `[data-group-label]` section headings.
74
+ */
75
+ export declare class RCMenu extends LitElement {
76
+ static styles: import('lit').CSSResult[];
77
+ private static readonly _$styledRoots;
78
+ private static _ensureBaseStyles;
79
+ /**
80
+ * Accessible label for this menu. Synced to `aria-label` on the host.
81
+ *
82
+ * Prefer `aria-labelledby` for a visible label associated via ID.
83
+ */
84
+ label: string;
85
+ protected _$menuItems: Element[];
86
+ protected readonly _activeDescendantCtrl: ActiveDescendantController;
87
+ connectedCallback(): void;
88
+ disconnectedCallback(): void;
89
+ protected updated(changed: Map<PropertyKey, unknown>): void;
90
+ protected render(): import('lit').TemplateResult<1>;
91
+ /** All navigable menu item elements in document order. */
92
+ get items(): readonly Element[];
93
+ /** Move the virtual cursor and DOM focus to the first navigable item. */
94
+ focusFirst(): void;
95
+ /** Move the virtual cursor and DOM focus to the last navigable item. */
96
+ focusLast(): void;
97
+ /**
98
+ * Move the virtual cursor to a specific item.
99
+ *
100
+ * @param $item - The element to make active. No-op when null or undefined.
101
+ */
102
+ focusItem($item?: Element | null): void;
103
+ /**
104
+ * Move the virtual cursor to the item at `index`.
105
+ *
106
+ * @param index - Zero-based index into the navigable items array.
107
+ */
108
+ focusItemAt(index: number): void;
109
+ protected _syncAriaLabel(): void;
110
+ protected _onSlotChange(e: Event): void;
111
+ /**
112
+ * Collects navigable menu items from the slot, supporting `role="group"`
113
+ * containers and skipping `role="separator"` elements.
114
+ *
115
+ * Direct focusable children are included as-is. Elements with
116
+ * `role="group"` are traversed one level deep so their focusable children
117
+ * participate in arrow-key navigation. Separators and non-focusable
118
+ * container elements are excluded.
119
+ */
120
+ protected _collectMenuItems($slot: HTMLSlotElement): Element[];
121
+ protected _initMenuItems(): void;
122
+ protected _applyCheckedState($item: HTMLElement): 'true' | 'false' | 'mixed' | undefined;
123
+ protected _onKeydown: (e: KeyboardEvent) => void;
124
+ protected _onBlur: () => void;
125
+ protected _onClick: (e: MouseEvent) => void;
126
+ }
127
+ export default RCMenu;
@@ -0,0 +1,2 @@
1
+ export declare const menuStyles: import('lit').CSSResult;
2
+ export default menuStyles;
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@rcarls/rc-menu",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.0.0-next-20260921045401",
7
+ "description": "Menu popup for command surfaces with keyboard navigation and typed activation events.",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-menu"
12
+ },
13
+ "homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-menu",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-menu.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-menu/src/index.d.ts",
24
+ "default": "./dist/rc-menu.js"
25
+ }
26
+ },
27
+ "./define": {
28
+ "import": {
29
+ "types": "./dist/types/packages/rc-menu/src/define.d.ts",
30
+ "default": "./dist/rc-menu-define.js"
31
+ }
32
+ }
33
+ },
34
+ "types": "./dist/types/packages/rc-menu/src/index.d.ts",
35
+ "sideEffects": [
36
+ "./dist/rc-menu-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
+ },
50
+ "devDependencies": {
51
+ "@custom-elements-manifest/analyzer": "0.11.0",
52
+ "@vitest/browser-playwright": "4.1.5",
53
+ "lit": "^3.0.0",
54
+ "playwright": "^1.56.0",
55
+ "rollup": "^4.60.2",
56
+ "typescript": "~5.9.3",
57
+ "vite": "^7.1.7",
58
+ "vite-plugin-dts": "^4.5.4",
59
+ "vitest": "^4.0.6",
60
+ "vitest-browser-lit": "^1.0.1"
61
+ },
62
+ "peerDependencies": {
63
+ "lit": "^3.0.0"
64
+ }
65
+ }