@rcarls/rc-menu-button 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -7
- package/dist/custom-elements.json +55 -13
- package/dist/{rc-menu-button-BEFLL7vi.js → rc-menu-button-CBUIq3iu.js} +85 -36
- package/dist/rc-menu-button-CBUIq3iu.js.map +1 -0
- package/dist/rc-menu-button-define.js +1 -1
- package/dist/rc-menu-button.js +1 -1
- package/dist/types/packages/rc-menu-button/src/rc-menu-button.d.ts +13 -1
- package/package.json +1 -1
- package/dist/rc-menu-button-BEFLL7vi.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ Trigger button that opens an `rc-menu` popup, following the [WAI-ARIA Menu Butto
|
|
|
4
4
|
|
|
5
5
|
Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-menu-button](https://richardcarls.github.io/rc-webcomponents/components/rc-menu-button).
|
|
6
6
|
|
|
7
|
-
Wraps a trigger element and an `rc-menu` popup, wiring ARIA attributes,
|
|
7
|
+
Wraps a trigger element and an `rc-menu` popup, wiring ARIA attributes,
|
|
8
|
+
light-dismiss, and keyboard open/close automatically.
|
|
8
9
|
|
|
9
10
|
---
|
|
10
11
|
|
|
@@ -32,6 +33,7 @@ Place a trigger element in the `trigger` slot and an `rc-menu` in the default sl
|
|
|
32
33
|
```html
|
|
33
34
|
<rc-menu-button>
|
|
34
35
|
<button slot="trigger">File</button>
|
|
36
|
+
<span slot="indicator" aria-hidden="true">›</span>
|
|
35
37
|
|
|
36
38
|
<rc-menu label="File">
|
|
37
39
|
<button>New</button>
|
|
@@ -41,7 +43,9 @@ Place a trigger element in the `trigger` slot and an `rc-menu` in the default sl
|
|
|
41
43
|
</rc-menu-button>
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
The component automatically sets `aria-haspopup="menu"` and `aria-expanded` on
|
|
46
|
+
The component automatically sets `aria-haspopup="menu"` and `aria-expanded` on
|
|
47
|
+
the trigger, and closes the menu when the user clicks outside or selects an
|
|
48
|
+
item.
|
|
45
49
|
|
|
46
50
|
---
|
|
47
51
|
|
|
@@ -51,8 +55,10 @@ The component automatically sets `aria-haspopup="menu"` and `aria-expanded` on t
|
|
|
51
55
|
|
|
52
56
|
| Property | Attribute | Type | Default | Description |
|
|
53
57
|
|---|---|---|---|---|
|
|
54
|
-
| `open` | `open` | `boolean` | `false` | Whether the menu popup is visible. Reflects to attribute. |
|
|
58
|
+
| `open` | `open` | `boolean` | `false` | Whether the menu popup is visible. Reflects to attribute. Controlled mode: host writes silently. |
|
|
59
|
+
| `defaultOpen` | `default-open` | `boolean` | `false` | Initial open state for uncontrolled mode. Ignored after the first controlled write to `open`. |
|
|
55
60
|
| `orientation` | `orientation` | `'horizontal' \| 'vertical' \| undefined` | `undefined` | Arrow-key axis for opening the menu. When unset, inherited from a parent `rc-menubar` or `[role="menubar"]` element. Falls back to `'horizontal'`. |
|
|
61
|
+
| `placement` | `placement` | `AnchorPlacement` | `'bottom-start'` | Preferred placement of the popup relative to the trigger. Switches to `'right-start'` under vertical orientation unless explicitly overridden. |
|
|
56
62
|
|
|
57
63
|
### CSS custom properties
|
|
58
64
|
|
|
@@ -66,13 +72,16 @@ The component automatically sets `aria-haspopup="menu"` and `aria-expanded` on t
|
|
|
66
72
|
| `--rc-menu-button-trigger-radius` | `var(--rc-control-radius)` | Trigger border radius |
|
|
67
73
|
| `--rc-menu-button-trigger-background` | `var(--rc-button-bg)` | Trigger background |
|
|
68
74
|
| `--rc-menu-button-trigger-color` | `var(--rc-button-text)` | Trigger text color |
|
|
69
|
-
| `--rc-menu-button-trigger-transition` |
|
|
75
|
+
| `--rc-menu-button-trigger-transition` | `none` | Trigger state transition |
|
|
70
76
|
| `--rc-menu-button-trigger-hover-background` | `color-mix(in srgb, Highlight 8%, transparent)` | Trigger hover background |
|
|
71
77
|
| `--rc-menu-button-trigger-hover-color` | `inherit` | Trigger hover text color |
|
|
72
78
|
| `--rc-menu-button-trigger-hover-border-color` | `currentColor` | Trigger hover border color |
|
|
73
79
|
| `--rc-menu-button-trigger-open-background` | `color-mix(in srgb, Highlight 12%, transparent)` | Trigger background while the menu is open |
|
|
74
80
|
| `--rc-menu-button-trigger-open-color` | `inherit` | Trigger text color while the menu is open |
|
|
75
81
|
| `--rc-menu-button-trigger-open-border-color` | `currentColor` | Trigger border color while the menu is open |
|
|
82
|
+
| `--rc-menu-button-indicator-size` | `1em` | Inline and block size reserved for the optional indicator |
|
|
83
|
+
| `--rc-menu-button-indicator-color` | `currentColor` | Color of the optional indicator |
|
|
84
|
+
| `--rc-menu-button-indicator-inset` | Trigger inline padding | Indicator distance from the trigger's inline end |
|
|
76
85
|
| `--rc-menu-button-popup-z-index` | `1000` | `z-index` of the popup container |
|
|
77
86
|
|
|
78
87
|
### CSS parts
|
|
@@ -87,6 +96,7 @@ The component automatically sets `aria-haspopup="menu"` and `aria-expanded` on t
|
|
|
87
96
|
| Slot | Description |
|
|
88
97
|
|---|---|
|
|
89
98
|
| `trigger` | The button (or other interactive element) that toggles the menu. Receives `aria-haspopup` and `aria-expanded` automatically. |
|
|
99
|
+
| `indicator` | Optional author-supplied decorative submenu indicator. Rendered at the trigger's inline end with pointer events passed through to the trigger. |
|
|
90
100
|
| *(default)* | The `rc-menu` to show as the popup. |
|
|
91
101
|
|
|
92
102
|
### Events
|
|
@@ -110,9 +120,10 @@ toggleMenu(): void
|
|
|
110
120
|
|
|
111
121
|
---
|
|
112
122
|
|
|
113
|
-
## Keyboard
|
|
123
|
+
## Keyboard behavior
|
|
114
124
|
|
|
115
|
-
Keyboard handling depends on the resolved `orientation` (horizontal by
|
|
125
|
+
Keyboard handling depends on the resolved `orientation` (horizontal by
|
|
126
|
+
default, or inherited from a parent menubar).
|
|
116
127
|
|
|
117
128
|
| Key | Condition | Action |
|
|
118
129
|
|---|---|---|
|
|
@@ -142,7 +153,9 @@ Keyboard handling depends on the resolved `orientation` (horizontal by default,
|
|
|
142
153
|
Positioning when available and a viewport-aware fallback otherwise. Set the
|
|
143
154
|
`placement` property or attribute to choose the preferred direction.
|
|
144
155
|
|
|
145
|
-
**Light dismiss** is active at all times. A capture-phase `click` listener on
|
|
156
|
+
**Light dismiss** is active at all times. A capture-phase `click` listener on
|
|
157
|
+
`document` closes the menu whenever the click falls outside the component
|
|
158
|
+
boundary (checked via `composedPath()`).
|
|
146
159
|
|
|
147
160
|
---
|
|
148
161
|
|
|
@@ -123,6 +123,21 @@
|
|
|
123
123
|
"name": "--rc-menu-button-trigger-open-border-color",
|
|
124
124
|
"default": "currentColor"
|
|
125
125
|
},
|
|
126
|
+
{
|
|
127
|
+
"description": "Inline and block size of the slotted indicator",
|
|
128
|
+
"name": "--rc-menu-button-indicator-size",
|
|
129
|
+
"default": "1em"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"description": "Color of the slotted indicator",
|
|
133
|
+
"name": "--rc-menu-button-indicator-color",
|
|
134
|
+
"default": "currentColor"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"description": "Indicator distance from the trigger's inline end",
|
|
138
|
+
"name": "--rc-menu-button-indicator-inset",
|
|
139
|
+
"default": "var(--rc-menu-button-trigger-padding-inline)"
|
|
140
|
+
},
|
|
126
141
|
{
|
|
127
142
|
"description": "Z-index of the popup overlay",
|
|
128
143
|
"name": "--rc-menu-button-popup-z-index",
|
|
@@ -144,6 +159,10 @@
|
|
|
144
159
|
"description": "The button element that triggers the menu",
|
|
145
160
|
"name": "trigger"
|
|
146
161
|
},
|
|
162
|
+
{
|
|
163
|
+
"description": "Optional decorative indicator rendered at the trigger's inline end",
|
|
164
|
+
"name": "indicator"
|
|
165
|
+
},
|
|
147
166
|
{
|
|
148
167
|
"description": "The rc-menu element to display as popup",
|
|
149
168
|
"name": "default"
|
|
@@ -178,6 +197,15 @@
|
|
|
178
197
|
"privacy": "private",
|
|
179
198
|
"default": "false"
|
|
180
199
|
},
|
|
200
|
+
{
|
|
201
|
+
"kind": "field",
|
|
202
|
+
"name": "_openInitialized",
|
|
203
|
+
"type": {
|
|
204
|
+
"text": "boolean"
|
|
205
|
+
},
|
|
206
|
+
"privacy": "private",
|
|
207
|
+
"default": "false"
|
|
208
|
+
},
|
|
181
209
|
{
|
|
182
210
|
"kind": "field",
|
|
183
211
|
"name": "open",
|
|
@@ -348,6 +376,20 @@
|
|
|
348
376
|
],
|
|
349
377
|
"description": "Caches a weak reference to the first assigned trigger element and syncs its ARIA and tabindex state."
|
|
350
378
|
},
|
|
379
|
+
{
|
|
380
|
+
"kind": "method",
|
|
381
|
+
"name": "_handleIndicatorSlotChange",
|
|
382
|
+
"privacy": "protected",
|
|
383
|
+
"parameters": [
|
|
384
|
+
{
|
|
385
|
+
"name": "e",
|
|
386
|
+
"type": {
|
|
387
|
+
"text": "Event"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
],
|
|
391
|
+
"description": "Tracks optional indicator content so the trigger reserves its inline-end space."
|
|
392
|
+
},
|
|
351
393
|
{
|
|
352
394
|
"kind": "method",
|
|
353
395
|
"name": "_syncTriggerTabindex",
|
|
@@ -431,7 +473,7 @@
|
|
|
431
473
|
{
|
|
432
474
|
"kind": "method",
|
|
433
475
|
"name": "_setOpen",
|
|
434
|
-
"privacy": "
|
|
476
|
+
"privacy": "protected",
|
|
435
477
|
"return": {
|
|
436
478
|
"type": {
|
|
437
479
|
"text": "void"
|
|
@@ -475,37 +517,37 @@
|
|
|
475
517
|
],
|
|
476
518
|
"attributes": [
|
|
477
519
|
{
|
|
478
|
-
"description": "
|
|
479
|
-
"name": "placement",
|
|
480
|
-
"type": {
|
|
481
|
-
"text": "AnchorPlacement"
|
|
482
|
-
},
|
|
483
|
-
"default": "'bottom-start'",
|
|
484
|
-
"fieldName": "placement"
|
|
485
|
-
},
|
|
486
|
-
{
|
|
520
|
+
"description": "Applies open state in controlled mode without dispatching an event.",
|
|
487
521
|
"name": "open",
|
|
488
522
|
"type": {
|
|
489
523
|
"text": "boolean"
|
|
490
524
|
},
|
|
491
|
-
"description": "Applies open state in controlled mode without dispatching an event.",
|
|
492
525
|
"fieldName": "open"
|
|
493
526
|
},
|
|
494
527
|
{
|
|
528
|
+
"description": "Applies the uncontrolled default before any controlled write occurs.",
|
|
495
529
|
"name": "default-open",
|
|
496
530
|
"type": {
|
|
497
531
|
"text": "boolean"
|
|
498
532
|
},
|
|
499
|
-
"description": "Applies the uncontrolled default before any controlled write occurs.",
|
|
500
533
|
"fieldName": "defaultOpen"
|
|
501
534
|
},
|
|
502
535
|
{
|
|
536
|
+
"description": "Orientation of this menu button, affects which arrow keys open/close the menu.\n\nIf not set, inherits from a parent rc-menubar or element with role=\"menubar\".",
|
|
503
537
|
"name": "orientation",
|
|
504
538
|
"type": {
|
|
505
539
|
"text": "'horizontal' | 'vertical' | undefined"
|
|
506
540
|
},
|
|
507
|
-
"description": "Orientation of this menu button, affects which arrow keys open/close the menu.\n\nIf not set, inherits from a parent rc-menubar or element with role=\"menubar\".",
|
|
508
541
|
"fieldName": "orientation"
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
"description": "Preferred placement of the popup relative to the trigger button.",
|
|
545
|
+
"name": "placement",
|
|
546
|
+
"type": {
|
|
547
|
+
"text": "AnchorPlacement"
|
|
548
|
+
},
|
|
549
|
+
"default": "'bottom-start'",
|
|
550
|
+
"fieldName": "placement"
|
|
509
551
|
}
|
|
510
552
|
],
|
|
511
553
|
"superclass": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { css as
|
|
2
|
-
import { property as l, query as g, state as
|
|
3
|
-
import { AnchorController as m, keyNavigation as
|
|
4
|
-
const _ =
|
|
1
|
+
import { css as h, LitElement as d, html as b } from "lit";
|
|
2
|
+
import { property as l, query as g, state as p } from "lit/decorators.js";
|
|
3
|
+
import { AnchorController as m, keyNavigation as v, keyInteraction as f } from "@rcarls/rc-common";
|
|
4
|
+
const _ = h`
|
|
5
5
|
:host {
|
|
6
6
|
display: inline-block;
|
|
7
7
|
}
|
|
@@ -19,7 +19,12 @@ const _ = d`
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
#trigger-wrap {
|
|
22
|
-
|
|
22
|
+
position: relative;
|
|
23
|
+
display: inline-block;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:host([orientation='vertical']) #trigger-wrap {
|
|
27
|
+
display: block;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
slot[name='trigger']::slotted(button),
|
|
@@ -70,6 +75,40 @@ const _ = d`
|
|
|
70
75
|
text-align: start;
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
:host([has-indicator]) slot[name='trigger']::slotted(button),
|
|
79
|
+
:host([has-indicator]) slot[name='trigger']::slotted([role='button']) {
|
|
80
|
+
padding-inline-end: calc(
|
|
81
|
+
var(
|
|
82
|
+
--rc-menu-button-indicator-inset,
|
|
83
|
+
var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em))
|
|
84
|
+
) +
|
|
85
|
+
var(--rc-menu-button-indicator-size, 1em) +
|
|
86
|
+
var(--rc-menu-button-trigger-gap, var(--rc-item-gap, 0.5em))
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
slot[name='indicator'] {
|
|
91
|
+
position: absolute;
|
|
92
|
+
inset-block-start: 50%;
|
|
93
|
+
inset-inline-end: var(
|
|
94
|
+
--rc-menu-button-indicator-inset,
|
|
95
|
+
var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em))
|
|
96
|
+
);
|
|
97
|
+
display: inline-flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
inline-size: var(--rc-menu-button-indicator-size, 1em);
|
|
101
|
+
block-size: var(--rc-menu-button-indicator-size, 1em);
|
|
102
|
+
color: var(--rc-menu-button-indicator-color, currentColor);
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
transform: translateY(-50%);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
slot[name='indicator']::slotted(*) {
|
|
108
|
+
inline-size: 100%;
|
|
109
|
+
block-size: 100%;
|
|
110
|
+
}
|
|
111
|
+
|
|
73
112
|
#popup {
|
|
74
113
|
z-index: var(--rc-menu-button-popup-z-index, 1000);
|
|
75
114
|
}
|
|
@@ -78,14 +117,14 @@ const _ = d`
|
|
|
78
117
|
display: none;
|
|
79
118
|
}
|
|
80
119
|
`;
|
|
81
|
-
var
|
|
82
|
-
for (var n = r > 1 ? void 0 : r ?
|
|
120
|
+
var $ = Object.defineProperty, k = Object.getOwnPropertyDescriptor, o = (u, t, e, r) => {
|
|
121
|
+
for (var n = r > 1 ? void 0 : r ? k(t, e) : t, s = u.length - 1, c; s >= 0; s--)
|
|
83
122
|
(c = u[s]) && (n = (r ? c(t, e, n) : c(n)) || n);
|
|
84
|
-
return r && n &&
|
|
123
|
+
return r && n && $(t, e, n), n;
|
|
85
124
|
};
|
|
86
|
-
const a = class a extends
|
|
125
|
+
const a = class a extends d {
|
|
87
126
|
constructor() {
|
|
88
|
-
super(...arguments), this._defaultOpen = !1, this._open = !1, this.placement = "bottom-start", this._anchorCtrl = new m(this, {
|
|
127
|
+
super(...arguments), this._defaultOpen = !1, this._open = !1, this._openInitialized = !1, this.placement = "bottom-start", this._anchorCtrl = new m(this, {
|
|
89
128
|
anchor: () => this._$root ?? null,
|
|
90
129
|
floating: () => this._$popup ?? null,
|
|
91
130
|
shadowHost: () => this,
|
|
@@ -112,7 +151,7 @@ const a = class a extends p {
|
|
|
112
151
|
/** Applies open state in controlled mode without dispatching an event. */
|
|
113
152
|
set open(t) {
|
|
114
153
|
const e = this._open;
|
|
115
|
-
t !== void 0 && (this._setOpen(t, !1), this.requestUpdate("open", e));
|
|
154
|
+
t !== void 0 && (this._openInitialized = !0, this._setOpen(t, !1), this.requestUpdate("open", e));
|
|
116
155
|
}
|
|
117
156
|
get defaultOpen() {
|
|
118
157
|
return this._defaultOpen;
|
|
@@ -120,7 +159,7 @@ const a = class a extends p {
|
|
|
120
159
|
/** Applies the uncontrolled default before any controlled write occurs. */
|
|
121
160
|
set defaultOpen(t) {
|
|
122
161
|
const e = this._defaultOpen;
|
|
123
|
-
this._defaultOpen = t, t && !this.
|
|
162
|
+
this._defaultOpen = t, t && !this._openInitialized && this._setOpen(!0, !1), this.requestUpdate("defaultOpen", e);
|
|
124
163
|
}
|
|
125
164
|
/**
|
|
126
165
|
* Popup placement adjusted for orientation.
|
|
@@ -194,6 +233,11 @@ const a = class a extends p {
|
|
|
194
233
|
const r = t.currentTarget.assignedElements()[0];
|
|
195
234
|
r && (this._$trigger = new WeakRef(r), this._syncTriggerAria(), this._syncTriggerTabindex());
|
|
196
235
|
}
|
|
236
|
+
/** Tracks optional indicator content so the trigger reserves its inline-end space. */
|
|
237
|
+
_handleIndicatorSlotChange(t) {
|
|
238
|
+
const e = t.currentTarget;
|
|
239
|
+
this.toggleAttribute("has-indicator", e.assignedElements().length > 0);
|
|
240
|
+
}
|
|
197
241
|
/**
|
|
198
242
|
* Mirrors the host's `tabindex` attribute to the trigger button.
|
|
199
243
|
*
|
|
@@ -267,17 +311,22 @@ const a = class a extends p {
|
|
|
267
311
|
<div id="root" part="root">
|
|
268
312
|
<div
|
|
269
313
|
id="trigger-wrap"
|
|
270
|
-
${
|
|
314
|
+
${v(this._onNavigate, {
|
|
271
315
|
navigationAxis: this._resolvedOrientation,
|
|
272
316
|
handleNavAxis: !1,
|
|
273
317
|
handleOpenAxis: !0,
|
|
274
318
|
handleActivate: !0,
|
|
275
319
|
handleEscape: this.open
|
|
276
320
|
})}
|
|
277
|
-
${
|
|
321
|
+
${f()}
|
|
278
322
|
@click=${this._handleTriggerClick}
|
|
279
323
|
>
|
|
280
324
|
<slot name="trigger" @slotchange=${this._handleTriggerSlotChange}></slot>
|
|
325
|
+
<slot
|
|
326
|
+
name="indicator"
|
|
327
|
+
aria-hidden="true"
|
|
328
|
+
@slotchange=${this._handleIndicatorSlotChange}
|
|
329
|
+
></slot>
|
|
281
330
|
</div>
|
|
282
331
|
|
|
283
332
|
<div
|
|
@@ -294,35 +343,35 @@ const a = class a extends p {
|
|
|
294
343
|
}
|
|
295
344
|
};
|
|
296
345
|
a.styles = [_], a.shadowRootOptions = {
|
|
297
|
-
...
|
|
346
|
+
...d.shadowRootOptions,
|
|
298
347
|
delegatesFocus: !0
|
|
299
348
|
};
|
|
300
|
-
let
|
|
301
|
-
|
|
349
|
+
let i = a;
|
|
350
|
+
o([
|
|
302
351
|
l({ type: Boolean, reflect: !0 })
|
|
303
|
-
],
|
|
304
|
-
|
|
352
|
+
], i.prototype, "open", 1);
|
|
353
|
+
o([
|
|
305
354
|
l({ type: Boolean, attribute: "default-open" })
|
|
306
|
-
],
|
|
307
|
-
|
|
355
|
+
], i.prototype, "defaultOpen", 1);
|
|
356
|
+
o([
|
|
308
357
|
l({ type: String, reflect: !0 })
|
|
309
|
-
],
|
|
310
|
-
|
|
358
|
+
], i.prototype, "orientation", 2);
|
|
359
|
+
o([
|
|
311
360
|
l({ reflect: !0 })
|
|
312
|
-
],
|
|
313
|
-
|
|
361
|
+
], i.prototype, "placement", 2);
|
|
362
|
+
o([
|
|
314
363
|
g("#root")
|
|
315
|
-
],
|
|
316
|
-
|
|
364
|
+
], i.prototype, "_$root", 2);
|
|
365
|
+
o([
|
|
317
366
|
g("#popup")
|
|
318
|
-
],
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
],
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
],
|
|
367
|
+
], i.prototype, "_$popup", 2);
|
|
368
|
+
o([
|
|
369
|
+
p()
|
|
370
|
+
], i.prototype, "_$trigger", 2);
|
|
371
|
+
o([
|
|
372
|
+
p()
|
|
373
|
+
], i.prototype, "_$menu", 2);
|
|
325
374
|
export {
|
|
326
|
-
|
|
375
|
+
i as R
|
|
327
376
|
};
|
|
328
|
-
//# sourceMappingURL=rc-menu-button-
|
|
377
|
+
//# sourceMappingURL=rc-menu-button-CBUIq3iu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-menu-button-CBUIq3iu.js","sources":["../src/rc-menu-button.styles.ts","../src/rc-menu-button.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const menuButtonStyles = css`\n :host {\n display: inline-block;\n }\n\n :host([orientation='vertical']) {\n display: block;\n }\n\n #root {\n display: inline-block;\n }\n\n :host([orientation='vertical']) #root {\n display: block;\n }\n\n #trigger-wrap {\n position: relative;\n display: inline-block;\n }\n\n :host([orientation='vertical']) #trigger-wrap {\n display: block;\n }\n\n slot[name='trigger']::slotted(button),\n slot[name='trigger']::slotted([role='button']) {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--rc-menu-button-trigger-gap, var(--rc-item-gap, 0.5em));\n min-block-size: var(--rc-menu-button-trigger-block-size, var(--rc-control-block-size, 2.25em));\n padding: var(--rc-menu-button-trigger-padding-block, var(--rc-control-padding-block, 0.25em))\n var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em));\n appearance: none;\n box-sizing: border-box;\n border: var(--rc-menu-button-trigger-border, var(--rc-border, 1px solid ButtonBorder));\n border-radius: var(--rc-menu-button-trigger-radius, var(--rc-control-radius, 0.125em));\n background: var(--rc-menu-button-trigger-background, var(--rc-button-bg, ButtonFace));\n color: var(--rc-menu-button-trigger-color, var(--rc-button-text, ButtonText));\n font: inherit;\n text-decoration: none;\n cursor: default;\n user-select: none;\n transition: var(--rc-menu-button-trigger-transition);\n }\n\n slot[name='trigger']::slotted(button:hover),\n slot[name='trigger']::slotted([role='button']:hover) {\n border-color: var(--rc-menu-button-trigger-hover-border-color, currentColor);\n background: var(\n --rc-menu-button-trigger-hover-background,\n color-mix(in srgb, Highlight 8%, transparent)\n );\n color: var(--rc-menu-button-trigger-hover-color, inherit);\n }\n\n slot[name='trigger']::slotted(button[aria-expanded='true']),\n slot[name='trigger']::slotted([role='button'][aria-expanded='true']) {\n border-color: var(--rc-menu-button-trigger-open-border-color, currentColor);\n background: var(\n --rc-menu-button-trigger-open-background,\n color-mix(in srgb, Highlight 12%, transparent)\n );\n color: var(--rc-menu-button-trigger-open-color, inherit);\n }\n\n :host([orientation='vertical']) slot[name='trigger']::slotted(button),\n :host([orientation='vertical']) slot[name='trigger']::slotted([role='button']) {\n inline-size: 100%;\n text-align: start;\n }\n\n :host([has-indicator]) slot[name='trigger']::slotted(button),\n :host([has-indicator]) slot[name='trigger']::slotted([role='button']) {\n padding-inline-end: calc(\n var(\n --rc-menu-button-indicator-inset,\n var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em))\n ) +\n var(--rc-menu-button-indicator-size, 1em) +\n var(--rc-menu-button-trigger-gap, var(--rc-item-gap, 0.5em))\n );\n }\n\n slot[name='indicator'] {\n position: absolute;\n inset-block-start: 50%;\n inset-inline-end: var(\n --rc-menu-button-indicator-inset,\n var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em))\n );\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: var(--rc-menu-button-indicator-size, 1em);\n block-size: var(--rc-menu-button-indicator-size, 1em);\n color: var(--rc-menu-button-indicator-color, currentColor);\n pointer-events: none;\n transform: translateY(-50%);\n }\n\n slot[name='indicator']::slotted(*) {\n inline-size: 100%;\n block-size: 100%;\n }\n\n #popup {\n z-index: var(--rc-menu-button-popup-z-index, 1000);\n }\n\n #popup[hidden] {\n display: none;\n }\n`;\n\nexport default menuButtonStyles;\n","import { LitElement, html } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\n\nimport {\n AnchorController,\n keyInteraction,\n keyNavigation,\n type AnchorPlacement,\n type KeyboardNavigationAction,\n} from '@rcarls/rc-common';\nimport type { RCMenu } from '@rcarls/rc-menu';\n\nimport menuButtonStyles from './rc-menu-button.styles';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-menu-button': RCMenuButton;\n }\n}\n\n/** Detail payload for the `rc-menu-button-toggle` event. */\nexport interface RCMenuButtonToggleEvent {\n /** Whether the menu transitioned to open (`true`) or closed (`false`). */\n open: boolean;\n}\n\n/**\n * Trigger button that opens an rc-menu popup, following the WAI-ARIA Menu Button pattern.\n *\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-menu-button rc-menu-button docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ WAI-ARIA Menu Button pattern}\n *\n * @slot trigger - The button element that triggers the menu\n * @slot indicator - Optional decorative indicator rendered at the trigger's inline end\n * @slot default - The rc-menu element to display as popup\n *\n * @fires rc-menu-button-toggle - Fired when the menu opens or closes\n *\n * @cssprop [--rc-menu-button-trigger-block-size=var(--rc-control-block-size)] - Minimum block size of the trigger\n * @cssprop [--rc-menu-button-trigger-padding-block=var(--rc-control-padding-block)] - Trigger block-axis padding\n * @cssprop [--rc-menu-button-trigger-padding-inline=var(--rc-control-padding-inline)] - Trigger inline-axis padding\n * @cssprop [--rc-menu-button-trigger-gap=var(--rc-item-gap)] - Gap between flex children in the trigger\n * @cssprop [--rc-menu-button-trigger-border=var(--rc-border)] - Trigger border\n * @cssprop [--rc-menu-button-trigger-radius=var(--rc-control-radius)] - Trigger border radius\n * @cssprop [--rc-menu-button-trigger-background=var(--rc-button-bg)] - Trigger background\n * @cssprop [--rc-menu-button-trigger-color=var(--rc-button-text)] - Trigger text color\n * @cssprop [--rc-menu-button-trigger-transition] - CSS transition applied to the trigger\n * @cssprop [--rc-menu-button-trigger-hover-background=color-mix(in srgb, Highlight 8%, transparent)] - Trigger hover background\n * @cssprop [--rc-menu-button-trigger-hover-color=inherit] - Trigger hover text color\n * @cssprop [--rc-menu-button-trigger-hover-border-color=currentColor] - Trigger hover border color\n * @cssprop [--rc-menu-button-trigger-open-background=color-mix(in srgb, Highlight 12%, transparent)] - Trigger background when the menu is open\n * @cssprop [--rc-menu-button-trigger-open-color=inherit] - Trigger text color when the menu is open\n * @cssprop [--rc-menu-button-trigger-open-border-color=currentColor] - Trigger border color when the menu is open\n * @cssprop [--rc-menu-button-indicator-size=1em] - Inline and block size of the slotted indicator\n * @cssprop [--rc-menu-button-indicator-color=currentColor] - Color of the slotted indicator\n * @cssprop [--rc-menu-button-indicator-inset=var(--rc-menu-button-trigger-padding-inline)] - Indicator distance from the trigger's inline end\n * @cssprop [--rc-menu-button-popup-z-index=1000] - Z-index of the popup overlay\n *\n * @csspart root - The root container element\n * @csspart popup - The popup container element\n *\n * @attr open - Whether the menu popup is visible. Controlled mode: host writes silently.\n * @attr default-open - Initial open state for uncontrolled mode. Ignored after the first\n * controlled write to `open`.\n * @attr orientation - Arrow-key axis for opening the menu. Inherits from a parent\n * `rc-menubar` or `[role=\"menubar\"]` when unset.\n * @attr placement - Preferred placement of the popup relative to the trigger.\n */\nexport class RCMenuButton extends LitElement {\n static styles = [menuButtonStyles];\n\n /**\n * Enables delegatesFocus so the browser routes focus() on the host element\n * to the slotted trigger button.\n */\n static override shadowRootOptions: ShadowRootInit = {\n ...LitElement.shadowRootOptions,\n\n delegatesFocus: true,\n };\n\n private _defaultOpen = false;\n private _open = false;\n private _openInitialized = false;\n\n /**\n * Whether the menu is currently open.\n *\n * In controlled mode the host owns this value; writing the property applies\n * the state silently without dispatching `rc-menu-button-toggle`.\n */\n @property({ type: Boolean, reflect: true })\n get open(): boolean {\n return this._open;\n }\n\n /** Applies open state in controlled mode without dispatching an event. */\n set open(value: boolean | undefined) {\n const oldValue = this._open;\n\n if (value === undefined) {\n return;\n }\n\n this._openInitialized = true;\n this._setOpen(value, false);\n this.requestUpdate('open', oldValue);\n }\n\n /**\n * Initial open state for uncontrolled mode.\n *\n * Ignored after the first controlled write; use `open` to update state\n * programmatically after mount.\n */\n @property({ type: Boolean, attribute: 'default-open' })\n get defaultOpen(): boolean {\n return this._defaultOpen;\n }\n\n /** Applies the uncontrolled default before any controlled write occurs. */\n set defaultOpen(value: boolean) {\n const oldValue = this._defaultOpen;\n\n this._defaultOpen = value;\n\n if (value && !this._openInitialized) {\n this._setOpen(true, false);\n }\n\n this.requestUpdate('defaultOpen', oldValue);\n }\n\n /**\n * Orientation of this menu button, affects which arrow keys open/close the menu.\n *\n * If not set, inherits from a parent rc-menubar or element with role=\"menubar\".\n */\n @property({ type: String, reflect: true })\n orientation: 'horizontal' | 'vertical' | undefined;\n\n /** Preferred placement of the popup relative to the trigger button. */\n @property({ reflect: true })\n placement: AnchorPlacement = 'bottom-start';\n\n /**\n * Popup placement adjusted for orientation.\n *\n * Switches from `bottom-start` to `right-start` when the resolved orientation\n * is `vertical`, so vertical menubars open submenus to the side.\n */\n protected get _effectivePlacement(): AnchorPlacement {\n if (this.placement !== 'bottom-start') {\n return this.placement;\n }\n\n return this._resolvedOrientation === 'vertical' ? 'right-start' : this.placement;\n }\n\n /**\n * Resolved orientation, considering inheritance from parent menubar.\n * Falls back to 'horizontal' if no orientation is set or inherited.\n */\n protected get _resolvedOrientation(): 'horizontal' | 'vertical' {\n if (this.orientation) {\n return this.orientation;\n }\n\n const $menubar = this.closest('rc-menubar, [role=\"menubar\"]');\n\n if ($menubar) {\n const orientation =\n $menubar.getAttribute('orientation') ?? $menubar.getAttribute('aria-orientation');\n\n if (orientation === 'vertical') {\n return 'vertical';\n }\n\n if (orientation === 'horizontal') {\n return 'horizontal';\n }\n }\n\n return 'horizontal';\n }\n\n /** Shadow DOM root container. Serves as the anchor element for `_anchorCtrl`. */\n @query('#root') protected _$root!: HTMLElement;\n\n /** Shadow DOM popup container. Hidden when `open` is `false`. */\n @query('#popup') protected _$popup!: HTMLElement;\n\n /** Weak reference to the slotted trigger button. */\n @state()\n protected _$trigger: WeakRef<HTMLElement> | undefined;\n\n /** Weak reference to the slotted `rc-menu` element. */\n @state()\n protected _$menu: WeakRef<RCMenu> | undefined;\n\n /** Positions the popup relative to the trigger using CSS anchor positioning. */\n protected _anchorCtrl = new AnchorController(this, {\n anchor: () => this._$root ?? null,\n floating: () => this._$popup ?? null,\n shadowHost: () => this,\n placement: 'bottom-start',\n offset: 2,\n });\n\n /** Bound handler for document click (light dismiss). Stored for `removeEventListener` pairing. */\n protected _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n\n /**\n * Watches the host's own `tabindex` attribute so the trigger button's\n * tabindex can be kept in sync.\n *\n * When a parent (e.g. rc-toolbar) places the\n * host under roving-tabindex management it sets/removes tabindex on the\n * host; we mirror that to the trigger so it doesn't become an independent\n * tab stop. When the host has no tabindex the trigger keeps its natural\n * tabindex=0 for standalone usage.\n */\n protected _tabObserver?: MutationObserver;\n\n /** Installs the tabindex mutation observer and the document-level click listener. */\n override connectedCallback() {\n super.connectedCallback();\n\n this._tabObserver = new MutationObserver(() => this._syncTriggerTabindex());\n this._tabObserver.observe(this, { attributes: true, attributeFilter: ['tabindex'] });\n\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n\n if (this.defaultOpen) {\n this._setOpen(true, false);\n }\n }\n\n /** Disconnects the tabindex observer and removes the document-level click listener. */\n override disconnectedCallback() {\n this._tabObserver?.disconnect();\n this._tabObserver = undefined;\n\n super.disconnectedCallback();\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n }\n\n /**\n * Opens the menu and moves focus into it.\n *\n * @param focusTarget - which item receives initial focus when the menu opens\n */\n openMenu(focusTarget: 'first' | 'last' = 'first') {\n if (this.open) {\n return;\n }\n\n this._anchorCtrl.setOptions({ placement: this._effectivePlacement });\n this._setOpen(true, true);\n this._anchorCtrl.update();\n\n this.updateComplete.then(() => {\n const $menu = this._$menu?.deref();\n\n focusTarget === 'last' ? $menu?.focusLast() : $menu?.focusFirst();\n });\n }\n\n /**\n * Closes the menu.\n *\n * @param returnFocus - when `true`, returns focus to the trigger button after closing\n */\n closeMenu(returnFocus = true) {\n if (!this.open) {\n return;\n }\n\n this._setOpen(false, true);\n\n if (returnFocus) {\n const $trigger = this._$trigger?.deref();\n\n $trigger?.focus();\n }\n }\n\n /** Toggles the menu between open and closed. */\n toggleMenu() {\n if (this.open) {\n this.closeMenu();\n } else {\n this.openMenu();\n }\n }\n\n /** Dispatches the `rc-menu-button-toggle` bubbling composed event with the current open state. */\n protected _dispatchToggle() {\n this.dispatchEvent(\n new CustomEvent<RCMenuButtonToggleEvent>('rc-menu-button-toggle', {\n bubbles: true,\n composed: true,\n detail: { open: this.open },\n }),\n );\n }\n\n /** Caches a weak reference to the first assigned trigger element and syncs its ARIA and tabindex state. */\n protected _handleTriggerSlotChange(e: Event) {\n const $slot = e.currentTarget as HTMLSlotElement;\n const $trigger = $slot.assignedElements()[0] as HTMLElement | undefined;\n\n if ($trigger) {\n this._$trigger = new WeakRef($trigger);\n this._syncTriggerAria();\n this._syncTriggerTabindex();\n }\n }\n\n /** Tracks optional indicator content so the trigger reserves its inline-end space. */\n protected _handleIndicatorSlotChange(e: Event) {\n const $slot = e.currentTarget as HTMLSlotElement;\n\n this.toggleAttribute('has-indicator', $slot.assignedElements().length > 0);\n }\n\n /**\n * Mirrors the host's `tabindex` attribute to the trigger button.\n *\n * When a roving-tabindex parent (e.g. rc-toolbar) marks this host inactive\n * with `tabindex=\"-1\"`, the trigger must also be suppressed so it does not\n * become an independent tab stop. When the host is the active item\n * (`tabindex=\"0\"`) or standalone (no tabindex), the trigger retains its\n * natural focusability so that Chrome's delegatesFocus can route sequential\n * Tab navigation through the host to the trigger.\n */\n protected _syncTriggerTabindex() {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger) {\n return;\n }\n\n if (this.getAttribute('tabindex') === '-1') {\n $trigger.setAttribute('tabindex', '-1');\n } else {\n $trigger.removeAttribute('tabindex');\n }\n }\n\n /**\n * Overrides the default focus() so that programmatic focus calls (e.g. from\n * a roving-tabindex parent navigating back to this element via arrow keys)\n * always reach the trigger.\n *\n * Chrome's delegatesFocus will not delegate to a tabindex=\"-1\" element, so\n * when the toolbar marks this host inactive it suppresses the trigger — and\n * the next focusItem() call silently fails. Lifting the suppression here and\n * directly calling trigger.focus() bypasses that restriction.\n */\n override focus(options?: FocusOptions) {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger) {\n super.focus(options);\n\n return;\n }\n\n $trigger.removeAttribute('tabindex');\n $trigger.focus(options);\n }\n\n /** Caches a weak reference to the first assigned `rc-menu` element. */\n protected _handleMenuSlotChange(e: Event) {\n const $slot = e.currentTarget as HTMLSlotElement;\n const $elements = $slot.assignedElements();\n const $menu = $elements.find(($el) => $el.tagName === 'RC-MENU') as RCMenu | undefined;\n\n if ($menu) {\n this._$menu = new WeakRef($menu);\n }\n }\n\n /**\n * Keyboard navigation handler.\n *\n * Opens or closes the menu in response to arrow/Enter/Escape keys.\n **/\n protected _onNavigate = (action: KeyboardNavigationAction) => {\n switch (action) {\n case 'open-to-first':\n case 'activate':\n this.openMenu();\n break;\n\n case 'open-to-last':\n this.openMenu('last');\n break;\n\n case 'escape':\n this.closeMenu();\n break;\n }\n };\n\n /** Prevents the default button action and delegates to `toggleMenu`. */\n protected _handleTriggerClick(e: MouseEvent) {\n e.preventDefault();\n\n this.toggleMenu();\n }\n\n /** Closes the menu when an `rc-menu-close` event bubbles up from the popup. */\n protected _handleMenuClose() {\n this.closeMenu();\n }\n\n /** Closes the menu when an `rc-menu-activate` event bubbles up from the popup. */\n protected _handleMenuActivate() {\n this.closeMenu();\n }\n\n /** Closes the menu on clicks outside this component (light dismiss). */\n protected _handleDocumentClick(e: MouseEvent) {\n if (!this.open) {\n return;\n }\n\n const path = e.composedPath();\n\n if (path.includes(this)) {\n return;\n }\n\n this.closeMenu(false);\n }\n\n /** Syncs trigger ARIA when `open` changes; updates anchor placement when `placement` or `orientation` change. */\n protected override updated(changedProperties: Map<PropertyKey, unknown>) {\n super.updated(changedProperties);\n\n if (changedProperties.has('open')) {\n this._syncTriggerAria();\n }\n\n if (changedProperties.has('placement') || changedProperties.has('orientation')) {\n this._anchorCtrl.setOptions({ placement: this._effectivePlacement });\n }\n }\n\n protected _setOpen(open: boolean, dispatch: boolean): void {\n if (this._open === open) {\n return;\n }\n\n const oldValue = this._open;\n\n this._open = open;\n\n this.requestUpdate('open', oldValue);\n this._syncTriggerAria();\n\n if (dispatch) {\n this._dispatchToggle();\n }\n }\n\n /** Writes `aria-haspopup` and `aria-expanded` to the trigger button to match the current open state. */\n protected _syncTriggerAria(): void {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger?.isConnected) {\n return;\n }\n\n $trigger.setAttribute('aria-haspopup', 'menu');\n $trigger.setAttribute('aria-expanded', String(this.open));\n }\n\n protected override render() {\n return html`\n <div id=\"root\" part=\"root\">\n <div\n id=\"trigger-wrap\"\n ${keyNavigation(this._onNavigate, {\n navigationAxis: this._resolvedOrientation,\n handleNavAxis: false,\n handleOpenAxis: true,\n handleActivate: true,\n handleEscape: this.open,\n })}\n ${keyInteraction()}\n @click=${this._handleTriggerClick}\n >\n <slot name=\"trigger\" @slotchange=${this._handleTriggerSlotChange}></slot>\n <slot\n name=\"indicator\"\n aria-hidden=\"true\"\n @slotchange=${this._handleIndicatorSlotChange}\n ></slot>\n </div>\n\n <div\n id=\"popup\"\n part=\"popup\"\n ?hidden=${!this.open}\n @rc-menu-close=${this._handleMenuClose}\n @rc-menu-activate=${this._handleMenuActivate}\n >\n <slot @slotchange=${this._handleMenuSlotChange}></slot>\n </div>\n </div>\n `;\n }\n}\n\nexport default RCMenuButton;\n"],"names":["menuButtonStyles","css","_RCMenuButton","LitElement","AnchorController","action","value","oldValue","$menubar","orientation","focusTarget","$menu","returnFocus","e","$trigger","$slot","options","$el","changedProperties","open","dispatch","html","keyNavigation","keyInteraction","RCMenuButton","__decorateClass","property","query","state"],"mappings":"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"}
|
package/dist/rc-menu-button.js
CHANGED
|
@@ -19,6 +19,7 @@ export interface RCMenuButtonToggleEvent {
|
|
|
19
19
|
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ WAI-ARIA Menu Button pattern}
|
|
20
20
|
*
|
|
21
21
|
* @slot trigger - The button element that triggers the menu
|
|
22
|
+
* @slot indicator - Optional decorative indicator rendered at the trigger's inline end
|
|
22
23
|
* @slot default - The rc-menu element to display as popup
|
|
23
24
|
*
|
|
24
25
|
* @fires rc-menu-button-toggle - Fired when the menu opens or closes
|
|
@@ -38,11 +39,19 @@ export interface RCMenuButtonToggleEvent {
|
|
|
38
39
|
* @cssprop [--rc-menu-button-trigger-open-background=color-mix(in srgb, Highlight 12%, transparent)] - Trigger background when the menu is open
|
|
39
40
|
* @cssprop [--rc-menu-button-trigger-open-color=inherit] - Trigger text color when the menu is open
|
|
40
41
|
* @cssprop [--rc-menu-button-trigger-open-border-color=currentColor] - Trigger border color when the menu is open
|
|
42
|
+
* @cssprop [--rc-menu-button-indicator-size=1em] - Inline and block size of the slotted indicator
|
|
43
|
+
* @cssprop [--rc-menu-button-indicator-color=currentColor] - Color of the slotted indicator
|
|
44
|
+
* @cssprop [--rc-menu-button-indicator-inset=var(--rc-menu-button-trigger-padding-inline)] - Indicator distance from the trigger's inline end
|
|
41
45
|
* @cssprop [--rc-menu-button-popup-z-index=1000] - Z-index of the popup overlay
|
|
42
46
|
*
|
|
43
47
|
* @csspart root - The root container element
|
|
44
48
|
* @csspart popup - The popup container element
|
|
45
49
|
*
|
|
50
|
+
* @attr open - Whether the menu popup is visible. Controlled mode: host writes silently.
|
|
51
|
+
* @attr default-open - Initial open state for uncontrolled mode. Ignored after the first
|
|
52
|
+
* controlled write to `open`.
|
|
53
|
+
* @attr orientation - Arrow-key axis for opening the menu. Inherits from a parent
|
|
54
|
+
* `rc-menubar` or `[role="menubar"]` when unset.
|
|
46
55
|
* @attr placement - Preferred placement of the popup relative to the trigger.
|
|
47
56
|
*/
|
|
48
57
|
export declare class RCMenuButton extends LitElement {
|
|
@@ -54,6 +63,7 @@ export declare class RCMenuButton extends LitElement {
|
|
|
54
63
|
static shadowRootOptions: ShadowRootInit;
|
|
55
64
|
private _defaultOpen;
|
|
56
65
|
private _open;
|
|
66
|
+
private _openInitialized;
|
|
57
67
|
/**
|
|
58
68
|
* Whether the menu is currently open.
|
|
59
69
|
*
|
|
@@ -137,6 +147,8 @@ export declare class RCMenuButton extends LitElement {
|
|
|
137
147
|
protected _dispatchToggle(): void;
|
|
138
148
|
/** Caches a weak reference to the first assigned trigger element and syncs its ARIA and tabindex state. */
|
|
139
149
|
protected _handleTriggerSlotChange(e: Event): void;
|
|
150
|
+
/** Tracks optional indicator content so the trigger reserves its inline-end space. */
|
|
151
|
+
protected _handleIndicatorSlotChange(e: Event): void;
|
|
140
152
|
/**
|
|
141
153
|
* Mirrors the host's `tabindex` attribute to the trigger button.
|
|
142
154
|
*
|
|
@@ -177,7 +189,7 @@ export declare class RCMenuButton extends LitElement {
|
|
|
177
189
|
protected _handleDocumentClick(e: MouseEvent): void;
|
|
178
190
|
/** Syncs trigger ARIA when `open` changes; updates anchor placement when `placement` or `orientation` change. */
|
|
179
191
|
protected updated(changedProperties: Map<PropertyKey, unknown>): void;
|
|
180
|
-
|
|
192
|
+
protected _setOpen(open: boolean, dispatch: boolean): void;
|
|
181
193
|
/** Writes `aria-haspopup` and `aria-expanded` to the trigger button to match the current open state. */
|
|
182
194
|
protected _syncTriggerAria(): void;
|
|
183
195
|
protected render(): import('lit').TemplateResult<1>;
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rc-menu-button-BEFLL7vi.js","sources":["../src/rc-menu-button.styles.ts","../src/rc-menu-button.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const menuButtonStyles = css`\n :host {\n display: inline-block;\n }\n\n :host([orientation='vertical']) {\n display: block;\n }\n\n #root {\n display: inline-block;\n }\n\n :host([orientation='vertical']) #root {\n display: block;\n }\n\n #trigger-wrap {\n display: contents;\n }\n\n slot[name='trigger']::slotted(button),\n slot[name='trigger']::slotted([role='button']) {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--rc-menu-button-trigger-gap, var(--rc-item-gap, 0.5em));\n min-block-size: var(--rc-menu-button-trigger-block-size, var(--rc-control-block-size, 2.25em));\n padding: var(--rc-menu-button-trigger-padding-block, var(--rc-control-padding-block, 0.25em))\n var(--rc-menu-button-trigger-padding-inline, var(--rc-control-padding-inline, 0.5em));\n appearance: none;\n box-sizing: border-box;\n border: var(--rc-menu-button-trigger-border, var(--rc-border, 1px solid ButtonBorder));\n border-radius: var(--rc-menu-button-trigger-radius, var(--rc-control-radius, 0.125em));\n background: var(--rc-menu-button-trigger-background, var(--rc-button-bg, ButtonFace));\n color: var(--rc-menu-button-trigger-color, var(--rc-button-text, ButtonText));\n font: inherit;\n text-decoration: none;\n cursor: default;\n user-select: none;\n transition: var(--rc-menu-button-trigger-transition);\n }\n\n slot[name='trigger']::slotted(button:hover),\n slot[name='trigger']::slotted([role='button']:hover) {\n border-color: var(--rc-menu-button-trigger-hover-border-color, currentColor);\n background: var(\n --rc-menu-button-trigger-hover-background,\n color-mix(in srgb, Highlight 8%, transparent)\n );\n color: var(--rc-menu-button-trigger-hover-color, inherit);\n }\n\n slot[name='trigger']::slotted(button[aria-expanded='true']),\n slot[name='trigger']::slotted([role='button'][aria-expanded='true']) {\n border-color: var(--rc-menu-button-trigger-open-border-color, currentColor);\n background: var(\n --rc-menu-button-trigger-open-background,\n color-mix(in srgb, Highlight 12%, transparent)\n );\n color: var(--rc-menu-button-trigger-open-color, inherit);\n }\n\n :host([orientation='vertical']) slot[name='trigger']::slotted(button),\n :host([orientation='vertical']) slot[name='trigger']::slotted([role='button']) {\n inline-size: 100%;\n text-align: start;\n }\n\n #popup {\n z-index: var(--rc-menu-button-popup-z-index, 1000);\n }\n\n #popup[hidden] {\n display: none;\n }\n`;\n\nexport default menuButtonStyles;\n","import { LitElement, html } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\n\nimport {\n AnchorController,\n keyInteraction,\n keyNavigation,\n type AnchorPlacement,\n type KeyboardNavigationAction,\n} from '@rcarls/rc-common';\nimport type { RCMenu } from '@rcarls/rc-menu';\n\nimport menuButtonStyles from './rc-menu-button.styles';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-menu-button': RCMenuButton;\n }\n}\n\n/** Detail payload for the `rc-menu-button-toggle` event. */\nexport interface RCMenuButtonToggleEvent {\n /** Whether the menu transitioned to open (`true`) or closed (`false`). */\n open: boolean;\n}\n\n/**\n * Trigger button that opens an rc-menu popup, following the WAI-ARIA Menu Button pattern.\n *\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-menu-button rc-menu-button docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ WAI-ARIA Menu Button pattern}\n *\n * @slot trigger - The button element that triggers the menu\n * @slot default - The rc-menu element to display as popup\n *\n * @fires rc-menu-button-toggle - Fired when the menu opens or closes\n *\n * @cssprop [--rc-menu-button-trigger-block-size=var(--rc-control-block-size)] - Minimum block size of the trigger\n * @cssprop [--rc-menu-button-trigger-padding-block=var(--rc-control-padding-block)] - Trigger block-axis padding\n * @cssprop [--rc-menu-button-trigger-padding-inline=var(--rc-control-padding-inline)] - Trigger inline-axis padding\n * @cssprop [--rc-menu-button-trigger-gap=var(--rc-item-gap)] - Gap between flex children in the trigger\n * @cssprop [--rc-menu-button-trigger-border=var(--rc-border)] - Trigger border\n * @cssprop [--rc-menu-button-trigger-radius=var(--rc-control-radius)] - Trigger border radius\n * @cssprop [--rc-menu-button-trigger-background=var(--rc-button-bg)] - Trigger background\n * @cssprop [--rc-menu-button-trigger-color=var(--rc-button-text)] - Trigger text color\n * @cssprop [--rc-menu-button-trigger-transition] - CSS transition applied to the trigger\n * @cssprop [--rc-menu-button-trigger-hover-background=color-mix(in srgb, Highlight 8%, transparent)] - Trigger hover background\n * @cssprop [--rc-menu-button-trigger-hover-color=inherit] - Trigger hover text color\n * @cssprop [--rc-menu-button-trigger-hover-border-color=currentColor] - Trigger hover border color\n * @cssprop [--rc-menu-button-trigger-open-background=color-mix(in srgb, Highlight 12%, transparent)] - Trigger background when the menu is open\n * @cssprop [--rc-menu-button-trigger-open-color=inherit] - Trigger text color when the menu is open\n * @cssprop [--rc-menu-button-trigger-open-border-color=currentColor] - Trigger border color when the menu is open\n * @cssprop [--rc-menu-button-popup-z-index=1000] - Z-index of the popup overlay\n *\n * @csspart root - The root container element\n * @csspart popup - The popup container element\n *\n * @attr placement - Preferred placement of the popup relative to the trigger.\n */\nexport class RCMenuButton extends LitElement {\n static styles = [menuButtonStyles];\n\n /**\n * Enables delegatesFocus so the browser routes focus() on the host element\n * to the slotted trigger button.\n */\n static override shadowRootOptions: ShadowRootInit = {\n ...LitElement.shadowRootOptions,\n\n delegatesFocus: true,\n };\n\n private _defaultOpen = false;\n private _open = false;\n\n /**\n * Whether the menu is currently open.\n *\n * In controlled mode the host owns this value; writing the property applies\n * the state silently without dispatching `rc-menu-button-toggle`.\n */\n @property({ type: Boolean, reflect: true })\n get open(): boolean {\n return this._open;\n }\n\n /** Applies open state in controlled mode without dispatching an event. */\n set open(value: boolean | undefined) {\n const oldValue = this._open;\n\n if (value === undefined) return;\n\n this._setOpen(value, false);\n this.requestUpdate('open', oldValue);\n }\n\n /**\n * Initial open state for uncontrolled mode.\n *\n * Ignored after the first controlled write; use `open` to update state\n * programmatically after mount.\n */\n @property({ type: Boolean, attribute: 'default-open' })\n get defaultOpen(): boolean {\n return this._defaultOpen;\n }\n\n /** Applies the uncontrolled default before any controlled write occurs. */\n set defaultOpen(value: boolean) {\n const oldValue = this._defaultOpen;\n\n this._defaultOpen = value;\n\n if (value && !this._open) {\n this._setOpen(true, false);\n }\n\n this.requestUpdate('defaultOpen', oldValue);\n }\n\n /**\n * Orientation of this menu button, affects which arrow keys open/close the menu.\n *\n * If not set, inherits from a parent rc-menubar or element with role=\"menubar\".\n */\n @property({ type: String, reflect: true })\n orientation: 'horizontal' | 'vertical' | undefined;\n\n /** Preferred placement of the popup relative to the trigger button. */\n @property({ reflect: true })\n placement: AnchorPlacement = 'bottom-start';\n\n /**\n * Popup placement adjusted for orientation.\n *\n * Switches from `bottom-start` to `right-start` when the resolved orientation\n * is `vertical`, so vertical menubars open submenus to the side.\n */\n protected get _effectivePlacement(): AnchorPlacement {\n if (this.placement !== 'bottom-start') {\n return this.placement;\n }\n\n return this._resolvedOrientation === 'vertical' ? 'right-start' : this.placement;\n }\n\n /**\n * Resolved orientation, considering inheritance from parent menubar.\n * Falls back to 'horizontal' if no orientation is set or inherited.\n */\n protected get _resolvedOrientation(): 'horizontal' | 'vertical' {\n if (this.orientation) {\n return this.orientation;\n }\n\n const $menubar = this.closest('rc-menubar, [role=\"menubar\"]');\n\n if ($menubar) {\n const orientation =\n $menubar.getAttribute('orientation') ?? $menubar.getAttribute('aria-orientation');\n\n if (orientation === 'vertical') {\n return 'vertical';\n }\n\n if (orientation === 'horizontal') {\n return 'horizontal';\n }\n }\n\n return 'horizontal';\n }\n\n /** Shadow DOM root container. Serves as the anchor element for `_anchorCtrl`. */\n @query('#root') protected _$root!: HTMLElement;\n\n /** Shadow DOM popup container. Hidden when `open` is `false`. */\n @query('#popup') protected _$popup!: HTMLElement;\n\n /** Weak reference to the slotted trigger button. */\n @state()\n protected _$trigger: WeakRef<HTMLElement> | undefined;\n\n /** Weak reference to the slotted `rc-menu` element. */\n @state()\n protected _$menu: WeakRef<RCMenu> | undefined;\n\n /** Positions the popup relative to the trigger using CSS anchor positioning. */\n protected _anchorCtrl = new AnchorController(this, {\n anchor: () => this._$root ?? null,\n floating: () => this._$popup ?? null,\n shadowHost: () => this,\n placement: 'bottom-start',\n offset: 2,\n });\n\n /** Bound handler for document click (light dismiss). Stored for `removeEventListener` pairing. */\n protected _boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n\n /**\n * Watches the host's own `tabindex` attribute so the trigger button's\n * tabindex can be kept in sync.\n *\n * When a parent (e.g. rc-toolbar) places the\n * host under roving-tabindex management it sets/removes tabindex on the\n * host; we mirror that to the trigger so it doesn't become an independent\n * tab stop. When the host has no tabindex the trigger keeps its natural\n * tabindex=0 for standalone usage.\n */\n protected _tabObserver?: MutationObserver;\n\n /** Installs the tabindex mutation observer and the document-level click listener. */\n override connectedCallback() {\n super.connectedCallback();\n\n this._tabObserver = new MutationObserver(() => this._syncTriggerTabindex());\n this._tabObserver.observe(this, { attributes: true, attributeFilter: ['tabindex'] });\n\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n\n if (this.defaultOpen) {\n this._setOpen(true, false);\n }\n }\n\n /** Disconnects the tabindex observer and removes the document-level click listener. */\n override disconnectedCallback() {\n this._tabObserver?.disconnect();\n this._tabObserver = undefined;\n\n super.disconnectedCallback();\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n }\n\n /**\n * Opens the menu and moves focus into it.\n *\n * @param focusTarget - which item receives initial focus when the menu opens\n */\n openMenu(focusTarget: 'first' | 'last' = 'first') {\n if (this.open) return;\n\n this._anchorCtrl.setOptions({ placement: this._effectivePlacement });\n this._setOpen(true, true);\n this._anchorCtrl.update();\n\n this.updateComplete.then(() => {\n const $menu = this._$menu?.deref();\n\n focusTarget === 'last' ? $menu?.focusLast() : $menu?.focusFirst();\n });\n }\n\n /**\n * Closes the menu.\n *\n * @param returnFocus - when `true`, returns focus to the trigger button after closing\n */\n closeMenu(returnFocus = true) {\n if (!this.open) return;\n\n this._setOpen(false, true);\n\n if (returnFocus) {\n const $trigger = this._$trigger?.deref();\n\n $trigger?.focus();\n }\n }\n\n /** Toggles the menu between open and closed. */\n toggleMenu() {\n if (this.open) {\n this.closeMenu();\n } else {\n this.openMenu();\n }\n }\n\n /** Dispatches the `rc-menu-button-toggle` bubbling composed event with the current open state. */\n protected _dispatchToggle() {\n this.dispatchEvent(\n new CustomEvent<RCMenuButtonToggleEvent>('rc-menu-button-toggle', {\n bubbles: true,\n composed: true,\n detail: { open: this.open },\n }),\n );\n }\n\n /** Caches a weak reference to the first assigned trigger element and syncs its ARIA and tabindex state. */\n protected _handleTriggerSlotChange(e: Event) {\n const slot = e.currentTarget as HTMLSlotElement;\n const $trigger = slot.assignedElements()[0] as HTMLElement | undefined;\n\n if ($trigger) {\n this._$trigger = new WeakRef($trigger);\n this._syncTriggerAria();\n this._syncTriggerTabindex();\n }\n }\n\n /**\n * Mirrors the host's `tabindex` attribute to the trigger button.\n *\n * When a roving-tabindex parent (e.g. rc-toolbar) marks this host inactive\n * with `tabindex=\"-1\"`, the trigger must also be suppressed so it does not\n * become an independent tab stop. When the host is the active item\n * (`tabindex=\"0\"`) or standalone (no tabindex), the trigger retains its\n * natural focusability so that Chrome's delegatesFocus can route sequential\n * Tab navigation through the host to the trigger.\n */\n protected _syncTriggerTabindex() {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger) return;\n\n if (this.getAttribute('tabindex') === '-1') {\n $trigger.setAttribute('tabindex', '-1');\n } else {\n $trigger.removeAttribute('tabindex');\n }\n }\n\n /**\n * Overrides the default focus() so that programmatic focus calls (e.g. from\n * a roving-tabindex parent navigating back to this element via arrow keys)\n * always reach the trigger.\n *\n * Chrome's delegatesFocus will not delegate to a tabindex=\"-1\" element, so\n * when the toolbar marks this host inactive it suppresses the trigger — and\n * the next focusItem() call silently fails. Lifting the suppression here and\n * directly calling trigger.focus() bypasses that restriction.\n */\n override focus(options?: FocusOptions) {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger) {\n super.focus(options);\n return;\n }\n\n $trigger.removeAttribute('tabindex');\n $trigger.focus(options);\n }\n\n /** Caches a weak reference to the first assigned `rc-menu` element. */\n protected _handleMenuSlotChange(e: Event) {\n const slot = e.currentTarget as HTMLSlotElement;\n const elements = slot.assignedElements();\n const $menu = elements.find((el) => el.tagName === 'RC-MENU') as RCMenu | undefined;\n\n if ($menu) {\n this._$menu = new WeakRef($menu);\n }\n }\n\n /**\n * Keyboard navigation handler.\n *\n * Opens or closes the menu in response to arrow/Enter/Escape keys.\n **/\n protected _onNavigate = (action: KeyboardNavigationAction) => {\n switch (action) {\n case 'open-to-first':\n case 'activate':\n this.openMenu();\n break;\n\n case 'open-to-last':\n this.openMenu('last');\n break;\n\n case 'escape':\n this.closeMenu();\n break;\n }\n };\n\n /** Prevents the default button action and delegates to `toggleMenu`. */\n protected _handleTriggerClick(e: MouseEvent) {\n e.preventDefault();\n\n this.toggleMenu();\n }\n\n /** Closes the menu when an `rc-menu-close` event bubbles up from the popup. */\n protected _handleMenuClose() {\n this.closeMenu();\n }\n\n /** Closes the menu when an `rc-menu-activate` event bubbles up from the popup. */\n protected _handleMenuActivate() {\n this.closeMenu();\n }\n\n /** Closes the menu on clicks outside this component (light dismiss). */\n protected _handleDocumentClick(e: MouseEvent) {\n if (!this.open) {\n return;\n }\n\n const path = e.composedPath();\n\n if (path.includes(this)) {\n return;\n }\n\n this.closeMenu(false);\n }\n\n /** Syncs trigger ARIA when `open` changes; updates anchor placement when `placement` or `orientation` change. */\n protected override updated(changedProperties: Map<PropertyKey, unknown>) {\n super.updated(changedProperties);\n\n if (changedProperties.has('open')) {\n this._syncTriggerAria();\n }\n\n if (changedProperties.has('placement') || changedProperties.has('orientation')) {\n this._anchorCtrl.setOptions({ placement: this._effectivePlacement });\n }\n }\n\n private _setOpen(open: boolean, dispatch: boolean): void {\n if (this._open === open) {\n return;\n }\n\n const oldValue = this._open;\n\n this._open = open;\n\n this.requestUpdate('open', oldValue);\n this._syncTriggerAria();\n\n if (dispatch) {\n this._dispatchToggle();\n }\n }\n\n /** Writes `aria-haspopup` and `aria-expanded` to the trigger button to match the current open state. */\n protected _syncTriggerAria(): void {\n const $trigger = this._$trigger?.deref();\n\n if (!$trigger?.isConnected) {\n return;\n }\n\n $trigger.setAttribute('aria-haspopup', 'menu');\n $trigger.setAttribute('aria-expanded', String(this.open));\n }\n\n protected override render() {\n return html`\n <div id=\"root\" part=\"root\">\n <div\n id=\"trigger-wrap\"\n ${keyNavigation(this._onNavigate, {\n navigationAxis: this._resolvedOrientation,\n handleNavAxis: false,\n handleOpenAxis: true,\n handleActivate: true,\n handleEscape: this.open,\n })}\n ${keyInteraction()}\n @click=${this._handleTriggerClick}\n >\n <slot name=\"trigger\" @slotchange=${this._handleTriggerSlotChange}></slot>\n </div>\n\n <div\n id=\"popup\"\n part=\"popup\"\n ?hidden=${!this.open}\n @rc-menu-close=${this._handleMenuClose}\n @rc-menu-activate=${this._handleMenuActivate}\n >\n <slot @slotchange=${this._handleMenuSlotChange}></slot>\n </div>\n </div>\n `;\n }\n}\n\nexport default RCMenuButton;\n"],"names":["menuButtonStyles","css","_RCMenuButton","LitElement","AnchorController","action","value","oldValue","$menubar","orientation","focusTarget","$menu","returnFocus","e","$trigger","options","el","changedProperties","open","dispatch","html","keyNavigation","keyInteraction","RCMenuButton","__decorateClass","property","query","state"],"mappings":"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"}
|