@rcarls/rc-menubar 0.1.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 +149 -0
- package/dist/custom-elements.json +444 -0
- package/dist/demo.css +85 -0
- package/dist/demo.js +40 -0
- package/dist/rc-menubar-BWcf6GHO.js +227 -0
- package/dist/rc-menubar-BWcf6GHO.js.map +1 -0
- package/dist/rc-menubar-define.js +7 -0
- package/dist/rc-menubar-define.js.map +1 -0
- package/dist/rc-menubar.js +5 -0
- package/dist/rc-menubar.js.map +1 -0
- package/dist/types/packages/rc-menubar/src/define.d.ts +1 -0
- package/dist/types/packages/rc-menubar/src/index.d.ts +1 -0
- package/dist/types/packages/rc-menubar/src/rc-menubar.d.ts +72 -0
- package/dist/types/packages/rc-menubar/src/rc-menubar.styles.d.ts +2 -0
- package/dist/types/packages/rc-menubar/src/rc-menubar.test.d.ts +0 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# `@rcarls/rc-menubar`
|
|
2
|
+
|
|
3
|
+
A WAI-ARIA compliant menubar component implementing the [Menubar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/). Manages roving tabindex across `rc-menu-button` triggers and cascades menu open/close as the user navigates. Built with [Lit 3](https://lit.dev).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @rcarls/rc-menubar
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Import
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import '@rcarls/rc-menubar'; // side-effect: registers <rc-menubar>
|
|
17
|
+
import { RCMenubar } from '@rcarls/rc-menubar'; // named class export
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`rc-menubar` depends on `rc-menu-button` and `rc-menu`. Ensure all three are installed.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Basic usage
|
|
25
|
+
|
|
26
|
+
Place `rc-menu-button` elements as direct children. The menubar manages focus and opens the appropriate menu as the user navigates.
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<rc-menubar label="Application menu">
|
|
30
|
+
<rc-menu-button>
|
|
31
|
+
<button slot="trigger">File</button>
|
|
32
|
+
<rc-menu label="File">
|
|
33
|
+
<button>New</button>
|
|
34
|
+
<button>Open…</button>
|
|
35
|
+
<button>Save</button>
|
|
36
|
+
</rc-menu>
|
|
37
|
+
</rc-menu-button>
|
|
38
|
+
|
|
39
|
+
<rc-menu-button>
|
|
40
|
+
<button slot="trigger">Edit</button>
|
|
41
|
+
<rc-menu label="Edit">
|
|
42
|
+
<button>Undo</button>
|
|
43
|
+
<button>Redo</button>
|
|
44
|
+
<button>Cut</button>
|
|
45
|
+
<button>Copy</button>
|
|
46
|
+
<button>Paste</button>
|
|
47
|
+
</rc-menu>
|
|
48
|
+
</rc-menu-button>
|
|
49
|
+
|
|
50
|
+
<rc-menu-button>
|
|
51
|
+
<button slot="trigger">View</button>
|
|
52
|
+
<rc-menu label="View">
|
|
53
|
+
<button>Zoom In</button>
|
|
54
|
+
<button>Zoom Out</button>
|
|
55
|
+
</rc-menu>
|
|
56
|
+
</rc-menu-button>
|
|
57
|
+
</rc-menubar>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### Properties / attributes
|
|
65
|
+
|
|
66
|
+
| Property | Attribute | Type | Default | Description |
|
|
67
|
+
|---|---|---|---|---|
|
|
68
|
+
| `label` | `label` | `string` | `'Menu'` | `aria-label` for the menubar. Required — no visible label is rendered by the component. |
|
|
69
|
+
| `orientation` | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction and keyboard navigation axis. Propagated to child `rc-menu-button` elements for arrow-key open direction. |
|
|
70
|
+
|
|
71
|
+
### CSS custom properties
|
|
72
|
+
|
|
73
|
+
| Property | Default | Description |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `--rc-menubar-gap` | `0` | Gap between menu buttons |
|
|
76
|
+
| `--rc-menubar-padding-inline` | `0` | Horizontal padding on the menubar container |
|
|
77
|
+
| `--rc-menubar-padding-block` | `0` | Vertical padding on the menubar container |
|
|
78
|
+
|
|
79
|
+
### CSS parts
|
|
80
|
+
|
|
81
|
+
| Part | Element | Description |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `root` | `div[role="menubar"]` | The inner menubar container |
|
|
84
|
+
|
|
85
|
+
### Slots
|
|
86
|
+
|
|
87
|
+
| Slot | Description |
|
|
88
|
+
|---|---|
|
|
89
|
+
| *(default)* | `rc-menu-button` elements. Non-menu-button elements are rendered but excluded from keyboard navigation. |
|
|
90
|
+
|
|
91
|
+
### Events
|
|
92
|
+
|
|
93
|
+
`rc-menubar` dispatches no events of its own. Listen for `rc-menu-button-toggle` and `rc-menu-activate` bubbling up from child components.
|
|
94
|
+
|
|
95
|
+
### Public methods
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
focusItem(item?: HTMLElement | null): void
|
|
99
|
+
// Focus a specific trigger element.
|
|
100
|
+
|
|
101
|
+
closeActiveMenu(): void
|
|
102
|
+
// Close the currently open menu without returning focus to the trigger.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Read-only getters
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
items: HTMLElement[] // All trigger elements across all child rc-menu-buttons
|
|
109
|
+
menuButtons: RCMenuButton[] // All child rc-menu-button elements
|
|
110
|
+
firstItem: HTMLElement | undefined
|
|
111
|
+
lastItem: HTMLElement | undefined
|
|
112
|
+
nextItem: HTMLElement | undefined // Relative to last-focused trigger
|
|
113
|
+
previousItem: HTMLElement | undefined
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Keyboard behaviour
|
|
119
|
+
|
|
120
|
+
Horizontal orientation (default):
|
|
121
|
+
|
|
122
|
+
| Key | Action |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `ArrowRight` | Focus next trigger (wraps); if a menu is open, closes current and opens next |
|
|
125
|
+
| `ArrowLeft` | Focus previous trigger (wraps); if a menu is open, closes current and opens previous |
|
|
126
|
+
| `Home` | Focus first trigger |
|
|
127
|
+
| `End` | Focus last trigger |
|
|
128
|
+
| `Escape` | Close the active menu; focus remains on trigger |
|
|
129
|
+
| `Tab` | Leaves the menubar (standard tab order) |
|
|
130
|
+
|
|
131
|
+
For vertical orientation, `ArrowDown`/`ArrowUp` replace `ArrowRight`/`ArrowLeft`. The `rc-menu-button` children automatically inherit the menubar's orientation to determine their own open-axis arrow keys.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## ARIA
|
|
136
|
+
|
|
137
|
+
| Attribute | Where | Value |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `role="menubar"` | Root `div` | — |
|
|
140
|
+
| `aria-orientation` | Root `div` | `"horizontal"` or `"vertical"` |
|
|
141
|
+
| `aria-label` | Root `div` | Value of `label` property |
|
|
142
|
+
| `tabindex="0"` | Active trigger | One trigger tabbable at a time (roving tabindex) |
|
|
143
|
+
| `tabindex="-1"` | All other triggers | Removed from tab order |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Browser support
|
|
148
|
+
|
|
149
|
+
All modern browsers. Requires Web Components support (Chrome 67+, Firefox 63+, Safari 12.1+).
|
|
@@ -0,0 +1,444 @@
|
|
|
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-menubar",
|
|
13
|
+
"declaration": {
|
|
14
|
+
"name": "RCMenubar",
|
|
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-menubar"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"kind": "javascript-module",
|
|
45
|
+
"path": "src/rc-menubar.ts",
|
|
46
|
+
"declarations": [
|
|
47
|
+
{
|
|
48
|
+
"kind": "class",
|
|
49
|
+
"description": "A menubar component containing menu buttons, as defined in WAI-ARIA",
|
|
50
|
+
"name": "RCMenubar",
|
|
51
|
+
"cssProperties": [
|
|
52
|
+
{
|
|
53
|
+
"description": "Gap between menu buttons",
|
|
54
|
+
"name": "--rc-menubar-gap",
|
|
55
|
+
"default": "var(--rc-control-gap)"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"description": "Inline-axis padding",
|
|
59
|
+
"name": "--rc-menubar-padding-inline",
|
|
60
|
+
"default": "var(--rc-control-padding-inline)"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"description": "Block-axis padding",
|
|
64
|
+
"name": "--rc-menubar-padding-block",
|
|
65
|
+
"default": "var(--rc-control-padding-block)"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"description": "Root container border",
|
|
69
|
+
"name": "--rc-menubar-border",
|
|
70
|
+
"default": "var(--rc-border)"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"description": "Root container border radius",
|
|
74
|
+
"name": "--rc-menubar-radius",
|
|
75
|
+
"default": "var(--rc-control-radius)"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"description": "Root container background",
|
|
79
|
+
"name": "--rc-menubar-background",
|
|
80
|
+
"default": "Canvas"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"description": "Root container text color",
|
|
84
|
+
"name": "--rc-menubar-color",
|
|
85
|
+
"default": "CanvasText"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"cssParts": [
|
|
89
|
+
{
|
|
90
|
+
"description": "The root container element",
|
|
91
|
+
"name": "root"
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"slots": [
|
|
95
|
+
{
|
|
96
|
+
"description": "Takes rc-menu-button elements to display in the menubar",
|
|
97
|
+
"name": "default"
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"members": [
|
|
101
|
+
{
|
|
102
|
+
"kind": "field",
|
|
103
|
+
"name": "label",
|
|
104
|
+
"type": {
|
|
105
|
+
"text": "string"
|
|
106
|
+
},
|
|
107
|
+
"default": "'Menu'",
|
|
108
|
+
"description": "Accessible label for this menubar",
|
|
109
|
+
"attribute": "label"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"kind": "field",
|
|
113
|
+
"name": "orientation",
|
|
114
|
+
"type": {
|
|
115
|
+
"text": "'horizontal' | 'vertical'"
|
|
116
|
+
},
|
|
117
|
+
"default": "'horizontal'",
|
|
118
|
+
"description": "Menubar orientation, for keyboard navigation",
|
|
119
|
+
"attribute": "orientation",
|
|
120
|
+
"reflects": true
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"kind": "field",
|
|
124
|
+
"name": "_activeMenuButton",
|
|
125
|
+
"type": {
|
|
126
|
+
"text": "WeakRef<RCMenuButton> | undefined"
|
|
127
|
+
},
|
|
128
|
+
"privacy": "private",
|
|
129
|
+
"description": "Reference to the currently open menu button"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"kind": "field",
|
|
133
|
+
"name": "_lastFocused",
|
|
134
|
+
"type": {
|
|
135
|
+
"text": "HTMLElement | undefined"
|
|
136
|
+
},
|
|
137
|
+
"privacy": "private",
|
|
138
|
+
"description": "The last menu button trigger to have focus"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"kind": "field",
|
|
142
|
+
"name": "_menuButtons",
|
|
143
|
+
"type": {
|
|
144
|
+
"text": "WeakRef<RCMenuButton>[]"
|
|
145
|
+
},
|
|
146
|
+
"privacy": "private",
|
|
147
|
+
"default": "[]",
|
|
148
|
+
"description": "Array of menu button elements"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"kind": "field",
|
|
152
|
+
"name": "_boundHandleKeydown",
|
|
153
|
+
"privacy": "private"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"kind": "field",
|
|
157
|
+
"name": "_boundHandleClick",
|
|
158
|
+
"privacy": "private"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"kind": "method",
|
|
162
|
+
"name": "_syncHostAria",
|
|
163
|
+
"privacy": "private",
|
|
164
|
+
"return": {
|
|
165
|
+
"type": {
|
|
166
|
+
"text": "void"
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"description": "Keep ARIA semantics on the custom element host for slotted children."
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"kind": "method",
|
|
173
|
+
"name": "_syncMenuButtonOrientation",
|
|
174
|
+
"privacy": "private",
|
|
175
|
+
"return": {
|
|
176
|
+
"type": {
|
|
177
|
+
"text": "void"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"kind": "method",
|
|
183
|
+
"name": "_getTrigger",
|
|
184
|
+
"privacy": "private",
|
|
185
|
+
"return": {
|
|
186
|
+
"type": {
|
|
187
|
+
"text": "HTMLElement | null"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"parameters": [
|
|
191
|
+
{
|
|
192
|
+
"name": "menuButton",
|
|
193
|
+
"type": {
|
|
194
|
+
"text": "RCMenuButton"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
"description": "Get the trigger element from a menu button"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"kind": "field",
|
|
202
|
+
"name": "items",
|
|
203
|
+
"type": {
|
|
204
|
+
"text": "HTMLElement[]"
|
|
205
|
+
},
|
|
206
|
+
"description": "Array of focusable trigger elements",
|
|
207
|
+
"readonly": true
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"kind": "field",
|
|
211
|
+
"name": "menuButtons",
|
|
212
|
+
"type": {
|
|
213
|
+
"text": "RCMenuButton[]"
|
|
214
|
+
},
|
|
215
|
+
"description": "Array of menu button elements",
|
|
216
|
+
"readonly": true
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"kind": "field",
|
|
220
|
+
"name": "firstItem",
|
|
221
|
+
"type": {
|
|
222
|
+
"text": "HTMLElement | undefined"
|
|
223
|
+
},
|
|
224
|
+
"description": "First trigger element",
|
|
225
|
+
"readonly": true
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"kind": "field",
|
|
229
|
+
"name": "lastItem",
|
|
230
|
+
"type": {
|
|
231
|
+
"text": "HTMLElement | undefined"
|
|
232
|
+
},
|
|
233
|
+
"description": "Last trigger element",
|
|
234
|
+
"readonly": true
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"kind": "field",
|
|
238
|
+
"name": "nextItem",
|
|
239
|
+
"type": {
|
|
240
|
+
"text": "HTMLElement | undefined"
|
|
241
|
+
},
|
|
242
|
+
"description": "Next trigger element, in tab-order",
|
|
243
|
+
"readonly": true
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"kind": "field",
|
|
247
|
+
"name": "previousItem",
|
|
248
|
+
"type": {
|
|
249
|
+
"text": "HTMLElement | undefined"
|
|
250
|
+
},
|
|
251
|
+
"description": "Previous trigger element, in tab-order",
|
|
252
|
+
"readonly": true
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"kind": "method",
|
|
256
|
+
"name": "_getMenuButtonForTrigger",
|
|
257
|
+
"privacy": "private",
|
|
258
|
+
"return": {
|
|
259
|
+
"type": {
|
|
260
|
+
"text": "RCMenuButton | undefined"
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"parameters": [
|
|
264
|
+
{
|
|
265
|
+
"name": "trigger",
|
|
266
|
+
"type": {
|
|
267
|
+
"text": "HTMLElement"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
],
|
|
271
|
+
"description": "Get the menu button that contains a trigger"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"kind": "method",
|
|
275
|
+
"name": "focusItem",
|
|
276
|
+
"parameters": [
|
|
277
|
+
{
|
|
278
|
+
"name": "item",
|
|
279
|
+
"optional": true,
|
|
280
|
+
"type": {
|
|
281
|
+
"text": "HTMLElement | null"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
],
|
|
285
|
+
"description": "Set focus to a specific trigger element"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"kind": "method",
|
|
289
|
+
"name": "closeActiveMenu",
|
|
290
|
+
"description": "Close any open menu"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"kind": "method",
|
|
294
|
+
"name": "_handleItemFocus",
|
|
295
|
+
"privacy": "private",
|
|
296
|
+
"parameters": [
|
|
297
|
+
{
|
|
298
|
+
"name": "e",
|
|
299
|
+
"type": {
|
|
300
|
+
"text": "FocusEvent"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"kind": "method",
|
|
307
|
+
"name": "_handleSlotChange",
|
|
308
|
+
"privacy": "private",
|
|
309
|
+
"parameters": [
|
|
310
|
+
{
|
|
311
|
+
"name": "e",
|
|
312
|
+
"type": {
|
|
313
|
+
"text": "Event"
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"kind": "method",
|
|
320
|
+
"name": "_handleMenuButtonToggle",
|
|
321
|
+
"privacy": "private",
|
|
322
|
+
"parameters": [
|
|
323
|
+
{
|
|
324
|
+
"name": "e",
|
|
325
|
+
"type": {
|
|
326
|
+
"text": "CustomEvent<RCMenuButtonToggleEvent>"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
"kind": "method",
|
|
333
|
+
"name": "_handleNavigate",
|
|
334
|
+
"privacy": "private",
|
|
335
|
+
"parameters": [
|
|
336
|
+
{
|
|
337
|
+
"name": "action",
|
|
338
|
+
"type": {
|
|
339
|
+
"text": "KeyboardNavigationAction"
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
]
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"kind": "method",
|
|
346
|
+
"name": "_openCurrentMenu",
|
|
347
|
+
"privacy": "private"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"kind": "method",
|
|
351
|
+
"name": "_handleKeydown",
|
|
352
|
+
"privacy": "private",
|
|
353
|
+
"return": {
|
|
354
|
+
"type": {
|
|
355
|
+
"text": "void"
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
"parameters": [
|
|
359
|
+
{
|
|
360
|
+
"name": "e",
|
|
361
|
+
"type": {
|
|
362
|
+
"text": "KeyboardEvent"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"kind": "method",
|
|
369
|
+
"name": "_handleClick",
|
|
370
|
+
"privacy": "private",
|
|
371
|
+
"return": {
|
|
372
|
+
"type": {
|
|
373
|
+
"text": "void"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"kind": "method",
|
|
379
|
+
"name": "_normalizeKey",
|
|
380
|
+
"privacy": "private",
|
|
381
|
+
"return": {
|
|
382
|
+
"type": {
|
|
383
|
+
"text": "string"
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
"parameters": [
|
|
387
|
+
{
|
|
388
|
+
"name": "key",
|
|
389
|
+
"type": {
|
|
390
|
+
"text": "string"
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
]
|
|
394
|
+
}
|
|
395
|
+
],
|
|
396
|
+
"attributes": [
|
|
397
|
+
{
|
|
398
|
+
"name": "label",
|
|
399
|
+
"type": {
|
|
400
|
+
"text": "string"
|
|
401
|
+
},
|
|
402
|
+
"default": "'Menu'",
|
|
403
|
+
"description": "Accessible label for this menubar",
|
|
404
|
+
"fieldName": "label"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
"name": "orientation",
|
|
408
|
+
"type": {
|
|
409
|
+
"text": "'horizontal' | 'vertical'"
|
|
410
|
+
},
|
|
411
|
+
"default": "'horizontal'",
|
|
412
|
+
"description": "Menubar orientation, for keyboard navigation",
|
|
413
|
+
"fieldName": "orientation"
|
|
414
|
+
}
|
|
415
|
+
],
|
|
416
|
+
"superclass": {
|
|
417
|
+
"name": "LitElement",
|
|
418
|
+
"package": "lit"
|
|
419
|
+
},
|
|
420
|
+
"tagName": "rc-menubar",
|
|
421
|
+
"customElement": true
|
|
422
|
+
}
|
|
423
|
+
],
|
|
424
|
+
"exports": [
|
|
425
|
+
{
|
|
426
|
+
"kind": "js",
|
|
427
|
+
"name": "RCMenubar",
|
|
428
|
+
"declaration": {
|
|
429
|
+
"name": "RCMenubar",
|
|
430
|
+
"module": "src/rc-menubar.ts"
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"kind": "js",
|
|
435
|
+
"name": "default",
|
|
436
|
+
"declaration": {
|
|
437
|
+
"name": "RCMenubar",
|
|
438
|
+
"module": "src/rc-menubar.ts"
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
]
|
|
444
|
+
}
|
package/dist/demo.css
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
*, *::before, *::after {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
color-scheme: light dark;
|
|
7
|
+
--rc-accent: Highlight; /* Chrome doesn't support AccentColor on the open web */
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@supports (color: AccentColor) {
|
|
11
|
+
:root {
|
|
12
|
+
--rc-accent: AccentColor;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
[data-theme="light"] { color-scheme: light; }
|
|
17
|
+
[data-theme="dark"] { color-scheme: dark; }
|
|
18
|
+
|
|
19
|
+
[data-theme="solarized-light"] {
|
|
20
|
+
color-scheme: light;
|
|
21
|
+
--rc-surface: #fdf6e3;
|
|
22
|
+
--rc-text: #657b83;
|
|
23
|
+
--rc-border: 1px solid #93a1a1;
|
|
24
|
+
--rc-shadow: 0 2px 8px rgba(0, 0, 0, .12);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
[data-theme="solarized-dark"] {
|
|
28
|
+
color-scheme: dark;
|
|
29
|
+
--rc-surface: #002b36;
|
|
30
|
+
--rc-text: #839496;
|
|
31
|
+
--rc-border: 1px solid #586e75;
|
|
32
|
+
--rc-shadow: 0 2px 8px rgba(0, 0, 0, .3);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
body {
|
|
36
|
+
font-family: system-ui, sans-serif;
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.demo-page {
|
|
41
|
+
max-width: 60rem;
|
|
42
|
+
margin: 0 auto;
|
|
43
|
+
padding: 2rem 1.5rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.demo-controls {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 1rem;
|
|
50
|
+
margin-bottom: 2rem;
|
|
51
|
+
padding-bottom: 1rem;
|
|
52
|
+
border-bottom: 1px solid ButtonBorder;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.demo-controls .demo-title {
|
|
56
|
+
flex: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.demo-controls h1 {
|
|
60
|
+
margin: 0 0 0.15rem;
|
|
61
|
+
font-size: 1.5rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.demo-controls a {
|
|
65
|
+
font-size: 0.8rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.demo-section {
|
|
69
|
+
margin-bottom: 2rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.demo-section h2 {
|
|
73
|
+
margin: 0 0 0.5rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.theme-picker {
|
|
77
|
+
padding: 0.35em 0.75em;
|
|
78
|
+
font-family: inherit;
|
|
79
|
+
font-size: 0.8rem;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
border: 1px solid ButtonBorder;
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
background: Canvas;
|
|
84
|
+
color: CanvasText;
|
|
85
|
+
}
|
package/dist/demo.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const THEMES = ['', 'light', 'dark', 'solarized-light', 'solarized-dark'];
|
|
2
|
+
const LABELS = ['Auto', 'Light', 'Dark', 'Solarized ☀', 'Solarized ☾'];
|
|
3
|
+
|
|
4
|
+
const stored = localStorage.getItem('rc-demo-theme') ?? '';
|
|
5
|
+
applyTheme(stored);
|
|
6
|
+
|
|
7
|
+
function applyTheme(theme) {
|
|
8
|
+
if (theme) {
|
|
9
|
+
document.documentElement.dataset.theme = theme;
|
|
10
|
+
} else {
|
|
11
|
+
delete document.documentElement.dataset.theme;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function currentIndex() {
|
|
16
|
+
const current = document.documentElement.dataset.theme ?? '';
|
|
17
|
+
const idx = THEMES.indexOf(current);
|
|
18
|
+
return idx === -1 ? 0 : idx;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function updateButtons() {
|
|
22
|
+
const label = LABELS[currentIndex()];
|
|
23
|
+
document.querySelectorAll('.theme-picker').forEach((btn) => {
|
|
24
|
+
btn.textContent = label;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
window.cycleTheme = function () {
|
|
29
|
+
const next = THEMES[(currentIndex() + 1) % THEMES.length];
|
|
30
|
+
applyTheme(next);
|
|
31
|
+
localStorage.setItem('rc-demo-theme', next);
|
|
32
|
+
updateButtons();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
36
|
+
updateButtons();
|
|
37
|
+
document.querySelectorAll('.theme-picker').forEach((btn) => {
|
|
38
|
+
btn.addEventListener('click', window.cycleTheme);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { css as f, LitElement as g, html as v } from "lit";
|
|
2
|
+
import { property as d, state as m } from "lit/decorators.js";
|
|
3
|
+
import "@rcarls/rc-common";
|
|
4
|
+
const p = f`
|
|
5
|
+
:host {
|
|
6
|
+
display: inline-block;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
#root {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: row;
|
|
12
|
+
align-items: stretch;
|
|
13
|
+
gap: var(--rc-menubar-gap, var(--rc-control-gap, 0.25em));
|
|
14
|
+
padding-inline: var(--rc-menubar-padding-inline, var(--rc-control-padding-inline, 0.25em));
|
|
15
|
+
padding-block: var(--rc-menubar-padding-block, var(--rc-control-padding-block, 0.25em));
|
|
16
|
+
border: var(--rc-menubar-border, var(--rc-border, 1px solid ButtonBorder));
|
|
17
|
+
border-radius: var(--rc-menubar-radius, var(--rc-control-radius, 0));
|
|
18
|
+
background: var(--rc-menubar-background, var(--rc-surface, Canvas));
|
|
19
|
+
color: var(--rc-menubar-color, var(--rc-field-text, CanvasText));
|
|
20
|
+
font-family: var(--rc-font-family, inherit);
|
|
21
|
+
font-size: var(--rc-font-size, inherit);
|
|
22
|
+
line-height: var(--rc-line-height, normal);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
:host([orientation='vertical']) #root {
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
align-items: stretch;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#slot-wrap {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: inherit;
|
|
33
|
+
align-items: stretch;
|
|
34
|
+
gap: inherit;
|
|
35
|
+
min-inline-size: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
::slotted(rc-menu-button) {
|
|
39
|
+
flex: 0 0 auto;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:host([orientation='vertical']) ::slotted(rc-menu-button) {
|
|
43
|
+
inline-size: 100%;
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
var b = Object.defineProperty, u = (c, t, e, n) => {
|
|
47
|
+
for (var r = void 0, i = c.length - 1, s; i >= 0; i--)
|
|
48
|
+
(s = c[i]) && (r = s(t, e, r) || r);
|
|
49
|
+
return r && b(t, e, r), r;
|
|
50
|
+
};
|
|
51
|
+
const l = class l extends g {
|
|
52
|
+
constructor() {
|
|
53
|
+
super(...arguments), this.label = "Menu", this.orientation = "horizontal", this._menuButtons = [], this._boundHandleKeydown = this._handleKeydown.bind(this), this._boundHandleClick = this._handleClick.bind(this);
|
|
54
|
+
}
|
|
55
|
+
connectedCallback() {
|
|
56
|
+
super.connectedCallback(), this._syncHostAria(), this.addEventListener("keydown", this._boundHandleKeydown), this.addEventListener("click", this._boundHandleClick);
|
|
57
|
+
}
|
|
58
|
+
disconnectedCallback() {
|
|
59
|
+
super.disconnectedCallback(), this.removeEventListener("keydown", this._boundHandleKeydown), this.removeEventListener("click", this._boundHandleClick);
|
|
60
|
+
}
|
|
61
|
+
updated(t) {
|
|
62
|
+
super.updated(t), (t.has("label") || t.has("orientation")) && (this._syncHostAria(), this._syncMenuButtonOrientation());
|
|
63
|
+
}
|
|
64
|
+
/** Keep ARIA semantics on the custom element host for slotted children. */
|
|
65
|
+
_syncHostAria() {
|
|
66
|
+
this.setAttribute("role", "menubar"), this.setAttribute("aria-label", this.label), this.setAttribute("aria-orientation", this.orientation);
|
|
67
|
+
}
|
|
68
|
+
_syncMenuButtonOrientation() {
|
|
69
|
+
this.menuButtons.forEach((t) => {
|
|
70
|
+
t.orientation = this.orientation;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Get the trigger element from a menu button */
|
|
74
|
+
_getTrigger(t) {
|
|
75
|
+
return t.querySelector('[slot="trigger"]');
|
|
76
|
+
}
|
|
77
|
+
/** Array of focusable trigger elements */
|
|
78
|
+
get items() {
|
|
79
|
+
return this._menuButtons.map((t) => t.deref()).filter((t) => t != null).map((t) => this._getTrigger(t)).filter((t) => t != null);
|
|
80
|
+
}
|
|
81
|
+
/** Array of menu button elements */
|
|
82
|
+
get menuButtons() {
|
|
83
|
+
return this._menuButtons.map((t) => t.deref()).filter((t) => t != null);
|
|
84
|
+
}
|
|
85
|
+
/** First trigger element */
|
|
86
|
+
get firstItem() {
|
|
87
|
+
return this.items.at(0);
|
|
88
|
+
}
|
|
89
|
+
/** Last trigger element */
|
|
90
|
+
get lastItem() {
|
|
91
|
+
return this.items.at(-1);
|
|
92
|
+
}
|
|
93
|
+
/** Next trigger element, in tab-order */
|
|
94
|
+
get nextItem() {
|
|
95
|
+
const t = this._lastFocused ? this.items.indexOf(this._lastFocused) : 0;
|
|
96
|
+
return this.items.at((t + 1) % this.items.length);
|
|
97
|
+
}
|
|
98
|
+
/** Previous trigger element, in tab-order */
|
|
99
|
+
get previousItem() {
|
|
100
|
+
const t = this._lastFocused ? this.items.indexOf(this._lastFocused) : 0;
|
|
101
|
+
return this.items.at((t - 1) % this.items.length);
|
|
102
|
+
}
|
|
103
|
+
/** Get the menu button that contains a trigger */
|
|
104
|
+
_getMenuButtonForTrigger(t) {
|
|
105
|
+
return this.menuButtons.find((e) => this._getTrigger(e) === t);
|
|
106
|
+
}
|
|
107
|
+
/** Set focus to a specific trigger element */
|
|
108
|
+
focusItem(t) {
|
|
109
|
+
t?.focus();
|
|
110
|
+
}
|
|
111
|
+
/** Close any open menu */
|
|
112
|
+
closeActiveMenu() {
|
|
113
|
+
const t = this._activeMenuButton?.deref();
|
|
114
|
+
t && (t.closeMenu(!1), this._activeMenuButton = void 0);
|
|
115
|
+
}
|
|
116
|
+
_handleItemFocus(t) {
|
|
117
|
+
const e = t.target;
|
|
118
|
+
e.slot === "trigger" && (this._lastFocused = e, this.items.forEach((n) => n.setAttribute("tabindex", "-1")), e.setAttribute("tabindex", "0"));
|
|
119
|
+
}
|
|
120
|
+
_handleSlotChange(t) {
|
|
121
|
+
const n = t.currentTarget.assignedElements(), r = this._menuButtons;
|
|
122
|
+
this._menuButtons = n.filter((i) => i.tagName === "RC-MENU-BUTTON").map((i) => new WeakRef(i)), queueMicrotask(() => {
|
|
123
|
+
if (!this.isConnected) return;
|
|
124
|
+
r.forEach((s) => {
|
|
125
|
+
const o = s.deref();
|
|
126
|
+
if (!o) return;
|
|
127
|
+
o.removeAttribute("role");
|
|
128
|
+
const h = this._getTrigger(o);
|
|
129
|
+
h?.removeAttribute("tabindex"), h?.removeAttribute("role");
|
|
130
|
+
}), this.menuButtons.forEach((s) => {
|
|
131
|
+
s.removeAttribute("role"), s.orientation = this.orientation, this._getTrigger(s)?.setAttribute("role", "menuitem");
|
|
132
|
+
});
|
|
133
|
+
const i = this.items;
|
|
134
|
+
i.forEach((s, o) => {
|
|
135
|
+
s.setAttribute("tabindex", o === 0 ? "0" : "-1");
|
|
136
|
+
}), i.length > 0 && !this._lastFocused && (this._lastFocused = i[0]);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
_handleMenuButtonToggle(t) {
|
|
140
|
+
const e = t.target;
|
|
141
|
+
t.detail.open ? this._activeMenuButton = new WeakRef(e) : this._activeMenuButton?.deref() === e && (this._activeMenuButton = void 0);
|
|
142
|
+
}
|
|
143
|
+
_handleNavigate(t) {
|
|
144
|
+
const n = this._activeMenuButton?.deref()?.open ?? !1;
|
|
145
|
+
switch (t) {
|
|
146
|
+
case "next": {
|
|
147
|
+
n && this.closeActiveMenu(), this.focusItem(this.nextItem), n && this._openCurrentMenu();
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "prev": {
|
|
151
|
+
n && this.closeActiveMenu(), this.focusItem(this.previousItem), n && this._openCurrentMenu();
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case "start":
|
|
155
|
+
n && this.closeActiveMenu(), this.focusItem(this.firstItem), n && this._openCurrentMenu();
|
|
156
|
+
break;
|
|
157
|
+
case "end":
|
|
158
|
+
n && this.closeActiveMenu(), this.focusItem(this.lastItem), n && this._openCurrentMenu();
|
|
159
|
+
break;
|
|
160
|
+
case "escape":
|
|
161
|
+
this.closeActiveMenu();
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
_openCurrentMenu() {
|
|
166
|
+
if (this._lastFocused) {
|
|
167
|
+
const t = this._getMenuButtonForTrigger(this._lastFocused);
|
|
168
|
+
t && t.openMenu();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
_handleKeydown(t) {
|
|
172
|
+
const e = this._normalizeKey(t.key), n = this.orientation === "horizontal" ? "ArrowRight" : "ArrowDown", r = this.orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
|
|
173
|
+
let i;
|
|
174
|
+
e === n ? i = "next" : e === r ? i = "prev" : e === "Home" ? i = "start" : e === "End" ? i = "end" : e === "Escape" ? i = "escape" : e === "Tab" && this.setAttribute("data-interaction-mode", "keyboard"), i && (this._handleNavigate(i), this.setAttribute("data-interaction-mode", "keyboard"), t.stopPropagation(), t.preventDefault());
|
|
175
|
+
}
|
|
176
|
+
_handleClick() {
|
|
177
|
+
this.removeAttribute("data-interaction-mode");
|
|
178
|
+
}
|
|
179
|
+
_normalizeKey(t) {
|
|
180
|
+
switch (t) {
|
|
181
|
+
case "Up":
|
|
182
|
+
return "ArrowUp";
|
|
183
|
+
case "Down":
|
|
184
|
+
return "ArrowDown";
|
|
185
|
+
case "Left":
|
|
186
|
+
return "ArrowLeft";
|
|
187
|
+
case "Right":
|
|
188
|
+
return "ArrowRight";
|
|
189
|
+
default:
|
|
190
|
+
return t;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
render() {
|
|
194
|
+
return v`
|
|
195
|
+
<div
|
|
196
|
+
id="root"
|
|
197
|
+
part="root"
|
|
198
|
+
>
|
|
199
|
+
<div
|
|
200
|
+
id="slot-wrap"
|
|
201
|
+
@focusin=${this._handleItemFocus}
|
|
202
|
+
@rc-menu-button-toggle=${this._handleMenuButtonToggle}
|
|
203
|
+
>
|
|
204
|
+
<slot @slotchange=${this._handleSlotChange}></slot>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
l.styles = [p];
|
|
211
|
+
let a = l;
|
|
212
|
+
u([
|
|
213
|
+
d({ type: String })
|
|
214
|
+
], a.prototype, "label");
|
|
215
|
+
u([
|
|
216
|
+
d({ type: String, reflect: !0 })
|
|
217
|
+
], a.prototype, "orientation");
|
|
218
|
+
u([
|
|
219
|
+
m()
|
|
220
|
+
], a.prototype, "_activeMenuButton");
|
|
221
|
+
u([
|
|
222
|
+
m()
|
|
223
|
+
], a.prototype, "_lastFocused");
|
|
224
|
+
export {
|
|
225
|
+
a as R
|
|
226
|
+
};
|
|
227
|
+
//# sourceMappingURL=rc-menubar-BWcf6GHO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-menubar-BWcf6GHO.js","sources":["../src/rc-menubar.styles.ts","../src/rc-menubar.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const menubarStyles = css`\n :host {\n display: inline-block;\n }\n\n #root {\n display: flex;\n flex-direction: row;\n align-items: stretch;\n gap: var(--rc-menubar-gap, var(--rc-control-gap, 0.25em));\n padding-inline: var(--rc-menubar-padding-inline, var(--rc-control-padding-inline, 0.25em));\n padding-block: var(--rc-menubar-padding-block, var(--rc-control-padding-block, 0.25em));\n border: var(--rc-menubar-border, var(--rc-border, 1px solid ButtonBorder));\n border-radius: var(--rc-menubar-radius, var(--rc-control-radius, 0));\n background: var(--rc-menubar-background, var(--rc-surface, Canvas));\n color: var(--rc-menubar-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 }\n\n :host([orientation='vertical']) #root {\n flex-direction: column;\n align-items: stretch;\n }\n\n #slot-wrap {\n display: flex;\n flex-direction: inherit;\n align-items: stretch;\n gap: inherit;\n min-inline-size: 0;\n }\n\n ::slotted(rc-menu-button) {\n flex: 0 0 auto;\n }\n\n :host([orientation='vertical']) ::slotted(rc-menu-button) {\n inline-size: 100%;\n }\n`;\n\nexport default menubarStyles;\n","import { LitElement, html } from 'lit';\nimport { property, state } from 'lit/decorators.js';\n\nimport { type KeyboardNavigationAction } from '@rcarls/rc-common';\n\nimport type {\n RCMenuButton,\n RCMenuButtonToggleEvent,\n} from '@rcarls/rc-menu-button';\n\nimport menubarStyles from './rc-menubar.styles';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-menubar': RCMenubar;\n }\n}\n\n// Roving tabindex (not aria-activedescendant): trigger buttons need real focus\n// for submenu handoff and screen reader announcements.\n\n/**\n * A menubar component containing menu buttons, as defined in WAI-ARIA\n *\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n * @slot default - Takes rc-menu-button elements to display in the menubar\n * @csspart root - The root container element\n * @cssprop [--rc-menubar-gap=var(--rc-control-gap)] - Gap between menu buttons\n * @cssprop [--rc-menubar-padding-inline=var(--rc-control-padding-inline)] - Inline-axis padding\n * @cssprop [--rc-menubar-padding-block=var(--rc-control-padding-block)] - Block-axis padding\n * @cssprop [--rc-menubar-border=var(--rc-border)] - Root container border\n * @cssprop [--rc-menubar-radius=var(--rc-control-radius)] - Root container border radius\n * @cssprop [--rc-menubar-background=Canvas] - Root container background\n * @cssprop [--rc-menubar-color=CanvasText] - Root container text color\n */\nexport class RCMenubar extends LitElement {\n static styles = [menubarStyles];\n\n /** Accessible label for this menubar */\n @property({ type: String })\n label = 'Menu';\n\n /** Menubar orientation, for keyboard navigation */\n @property({ type: String, reflect: true })\n orientation: 'horizontal' | 'vertical' = 'horizontal';\n\n /** Reference to the currently open menu button */\n @state()\n private _activeMenuButton: WeakRef<RCMenuButton> | undefined;\n\n /** The last menu button trigger to have focus */\n @state()\n private _lastFocused: HTMLElement | undefined;\n\n /** Array of menu button elements */\n private _menuButtons: WeakRef<RCMenuButton>[] = [];\n\n private _boundHandleKeydown = this._handleKeydown.bind(this);\n\n private _boundHandleClick = this._handleClick.bind(this);\n\n override connectedCallback() {\n super.connectedCallback();\n\n this._syncHostAria();\n this.addEventListener('keydown', this._boundHandleKeydown);\n this.addEventListener('click', this._boundHandleClick);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n\n this.removeEventListener('keydown', this._boundHandleKeydown);\n this.removeEventListener('click', this._boundHandleClick);\n }\n\n protected override updated(changedProperties: Map<PropertyKey, unknown>) {\n super.updated(changedProperties);\n\n if (\n changedProperties.has('label') ||\n changedProperties.has('orientation')\n ) {\n this._syncHostAria();\n this._syncMenuButtonOrientation();\n }\n }\n\n /** Keep ARIA semantics on the custom element host for slotted children. */\n private _syncHostAria(): void {\n this.setAttribute('role', 'menubar');\n this.setAttribute('aria-label', this.label);\n this.setAttribute('aria-orientation', this.orientation);\n }\n\n private _syncMenuButtonOrientation(): void {\n this.menuButtons.forEach((menuButton) => {\n menuButton.orientation = this.orientation;\n });\n }\n\n /** Get the trigger element from a menu button */\n private _getTrigger(menuButton: RCMenuButton): HTMLElement | null {\n return menuButton.querySelector('[slot=\"trigger\"]');\n }\n\n /** Array of focusable trigger elements */\n get items(): HTMLElement[] {\n return this._menuButtons\n .map((ref) => ref.deref())\n .filter((el): el is RCMenuButton => el != null)\n .map((mb) => this._getTrigger(mb))\n .filter((el): el is HTMLElement => el != null);\n }\n\n /** Array of menu button elements */\n get menuButtons(): RCMenuButton[] {\n return this._menuButtons\n .map((ref) => ref.deref())\n .filter((el): el is RCMenuButton => el != null);\n }\n\n /** First trigger element */\n get firstItem(): HTMLElement | undefined {\n return this.items.at(0);\n }\n\n /** Last trigger element */\n get lastItem(): HTMLElement | undefined {\n return this.items.at(-1);\n }\n\n /** Next trigger element, in tab-order */\n get nextItem(): HTMLElement | undefined {\n const index = this._lastFocused ? this.items.indexOf(this._lastFocused) : 0;\n return this.items.at((index + 1) % this.items.length);\n }\n\n /** Previous trigger element, in tab-order */\n get previousItem(): HTMLElement | undefined {\n const index = this._lastFocused ? this.items.indexOf(this._lastFocused) : 0;\n return this.items.at((index - 1) % this.items.length);\n }\n\n /** Get the menu button that contains a trigger */\n private _getMenuButtonForTrigger(\n trigger: HTMLElement,\n ): RCMenuButton | undefined {\n return this.menuButtons.find((mb) => this._getTrigger(mb) === trigger);\n }\n\n /** Set focus to a specific trigger element */\n focusItem(item?: HTMLElement | null) {\n if (item != null) {\n item.focus();\n }\n }\n\n /** Close any open menu */\n closeActiveMenu() {\n const activeMenuButton = this._activeMenuButton?.deref();\n if (activeMenuButton) {\n activeMenuButton.closeMenu(false);\n this._activeMenuButton = undefined;\n }\n }\n\n private _handleItemFocus(e: FocusEvent) {\n const target = e.target as HTMLElement;\n\n // Check if focus is on a trigger (slot=\"trigger\")\n if (target.slot !== 'trigger') return;\n\n this._lastFocused = target;\n\n // Set roving tab index\n this.items.forEach((el) => el.setAttribute('tabindex', '-1'));\n target.setAttribute('tabindex', '0');\n }\n\n private _handleSlotChange(e: Event) {\n const slot = e.currentTarget as HTMLSlotElement;\n const elements = slot.assignedElements();\n\n const prevButtons = this._menuButtons;\n\n // Cache element references synchronously — defer all DOM mutations.\n // slotchange fires synchronously inside a framework reactive update pass\n // on second+ mount (shadow DOM already exists), so the handler must be\n // instantaneous to avoid interacting with the reactive system.\n this._menuButtons = elements\n .filter((el): el is RCMenuButton => el.tagName === 'RC-MENU-BUTTON')\n .map((el) => new WeakRef(el));\n\n queueMicrotask(() => {\n if (!this.isConnected) return;\n\n prevButtons.forEach((ref) => {\n const menuButton = ref.deref();\n if (!menuButton) return;\n\n menuButton.removeAttribute('role');\n\n const trigger = this._getTrigger(menuButton);\n trigger?.removeAttribute('tabindex');\n trigger?.removeAttribute('role');\n });\n\n this.menuButtons.forEach((menuButton) => {\n menuButton.removeAttribute('role');\n menuButton.orientation = this.orientation;\n\n const trigger = this._getTrigger(menuButton);\n trigger?.setAttribute('role', 'menuitem');\n });\n\n const items = this.items;\n\n items.forEach((trigger, index) => {\n trigger.setAttribute('tabindex', index === 0 ? '0' : '-1');\n });\n\n if (items.length > 0 && !this._lastFocused) {\n this._lastFocused = items[0];\n }\n });\n }\n\n private _handleMenuButtonToggle(e: CustomEvent<RCMenuButtonToggleEvent>) {\n const menuButton = e.target as RCMenuButton;\n\n if (e.detail.open) {\n // Track the newly opened menu\n this._activeMenuButton = new WeakRef(menuButton);\n } else {\n // Clear active menu if this was the active one\n if (this._activeMenuButton?.deref() === menuButton) {\n this._activeMenuButton = undefined;\n }\n }\n }\n\n private _handleNavigate(action: KeyboardNavigationAction) {\n const activeMenu = this._activeMenuButton?.deref();\n const wasMenuOpen = activeMenu?.open ?? false;\n\n switch (action) {\n case 'next': {\n // Close current menu and move to next\n if (wasMenuOpen) {\n this.closeActiveMenu();\n }\n this.focusItem(this.nextItem);\n // If a menu was open, open the new one\n if (wasMenuOpen) {\n this._openCurrentMenu();\n }\n break;\n }\n case 'prev': {\n // Close current menu and move to previous\n if (wasMenuOpen) {\n this.closeActiveMenu();\n }\n this.focusItem(this.previousItem);\n // If a menu was open, open the new one\n if (wasMenuOpen) {\n this._openCurrentMenu();\n }\n break;\n }\n case 'start':\n if (wasMenuOpen) {\n this.closeActiveMenu();\n }\n this.focusItem(this.firstItem);\n if (wasMenuOpen) {\n this._openCurrentMenu();\n }\n break;\n case 'end':\n if (wasMenuOpen) {\n this.closeActiveMenu();\n }\n this.focusItem(this.lastItem);\n if (wasMenuOpen) {\n this._openCurrentMenu();\n }\n break;\n case 'escape':\n this.closeActiveMenu();\n break;\n }\n }\n\n private _openCurrentMenu() {\n if (this._lastFocused) {\n const menuButton = this._getMenuButtonForTrigger(this._lastFocused);\n if (menuButton) {\n menuButton.openMenu();\n }\n }\n }\n\n private _handleKeydown(e: KeyboardEvent): void {\n const key = this._normalizeKey(e.key);\n const navNext = this.orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';\n const navPrev = this.orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';\n\n let action: KeyboardNavigationAction | undefined;\n\n if (key === navNext) {\n action = 'next';\n } else if (key === navPrev) {\n action = 'prev';\n } else if (key === 'Home') {\n action = 'start';\n } else if (key === 'End') {\n action = 'end';\n } else if (key === 'Escape') {\n action = 'escape';\n } else if (key === 'Tab') {\n this.setAttribute('data-interaction-mode', 'keyboard');\n }\n\n if (!action) return;\n\n this._handleNavigate(action);\n this.setAttribute('data-interaction-mode', 'keyboard');\n e.stopPropagation();\n e.preventDefault();\n }\n\n private _handleClick(): void {\n this.removeAttribute('data-interaction-mode');\n }\n\n private _normalizeKey(key: string): string {\n switch (key) {\n case 'Up':\n return 'ArrowUp';\n case 'Down':\n return 'ArrowDown';\n case 'Left':\n return 'ArrowLeft';\n case 'Right':\n return 'ArrowRight';\n default:\n return key;\n }\n }\n\n protected render() {\n return html`\n <div\n id=\"root\"\n part=\"root\"\n >\n <div\n id=\"slot-wrap\"\n @focusin=${this._handleItemFocus}\n @rc-menu-button-toggle=${this._handleMenuButtonToggle}\n >\n <slot @slotchange=${this._handleSlotChange}></slot>\n </div>\n </div>\n `;\n }\n}\n\nexport default RCMenubar;\n"],"names":["menubarStyles","css","_RCMenubar","LitElement","changedProperties","menuButton","ref","el","mb","index","trigger","item","activeMenuButton","e","target","elements","prevButtons","items","action","wasMenuOpen","key","navNext","navPrev","html","RCMenubar","__decorateClass","property","state"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-menubar-define.js","sources":["../src/define.ts"],"sourcesContent":["import '@rcarls/rc-menu-button/define';\nimport { RCMenubar } from './index.js';\n\ncustomElements.get('rc-menubar') || customElements.define('rc-menubar', RCMenubar);\n\nexport * from './index.js';\n"],"names":["RCMenubar"],"mappings":";;AAGA,eAAe,IAAI,YAAY,KAAK,eAAe,OAAO,cAAcA,CAAS;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-menubar.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './rc-menubar';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { RCMenuButton } from '@rcarls/rc-menu-button';
|
|
3
|
+
declare global {
|
|
4
|
+
interface HTMLElementTagNameMap {
|
|
5
|
+
'rc-menubar': RCMenubar;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A menubar component containing menu buttons, as defined in WAI-ARIA
|
|
10
|
+
*
|
|
11
|
+
* @see https://www.w3.org/WAI/ARIA/apg/patterns/menubar/
|
|
12
|
+
* @slot default - Takes rc-menu-button elements to display in the menubar
|
|
13
|
+
* @csspart root - The root container element
|
|
14
|
+
* @cssprop [--rc-menubar-gap=var(--rc-control-gap)] - Gap between menu buttons
|
|
15
|
+
* @cssprop [--rc-menubar-padding-inline=var(--rc-control-padding-inline)] - Inline-axis padding
|
|
16
|
+
* @cssprop [--rc-menubar-padding-block=var(--rc-control-padding-block)] - Block-axis padding
|
|
17
|
+
* @cssprop [--rc-menubar-border=var(--rc-border)] - Root container border
|
|
18
|
+
* @cssprop [--rc-menubar-radius=var(--rc-control-radius)] - Root container border radius
|
|
19
|
+
* @cssprop [--rc-menubar-background=Canvas] - Root container background
|
|
20
|
+
* @cssprop [--rc-menubar-color=CanvasText] - Root container text color
|
|
21
|
+
*/
|
|
22
|
+
export declare class RCMenubar extends LitElement {
|
|
23
|
+
static styles: import('lit').CSSResult[];
|
|
24
|
+
/** Accessible label for this menubar */
|
|
25
|
+
label: string;
|
|
26
|
+
/** Menubar orientation, for keyboard navigation */
|
|
27
|
+
orientation: 'horizontal' | 'vertical';
|
|
28
|
+
/** Reference to the currently open menu button */
|
|
29
|
+
private _activeMenuButton;
|
|
30
|
+
/** The last menu button trigger to have focus */
|
|
31
|
+
private _lastFocused;
|
|
32
|
+
/** Array of menu button elements */
|
|
33
|
+
private _menuButtons;
|
|
34
|
+
private _boundHandleKeydown;
|
|
35
|
+
private _boundHandleClick;
|
|
36
|
+
connectedCallback(): void;
|
|
37
|
+
disconnectedCallback(): void;
|
|
38
|
+
protected updated(changedProperties: Map<PropertyKey, unknown>): void;
|
|
39
|
+
/** Keep ARIA semantics on the custom element host for slotted children. */
|
|
40
|
+
private _syncHostAria;
|
|
41
|
+
private _syncMenuButtonOrientation;
|
|
42
|
+
/** Get the trigger element from a menu button */
|
|
43
|
+
private _getTrigger;
|
|
44
|
+
/** Array of focusable trigger elements */
|
|
45
|
+
get items(): HTMLElement[];
|
|
46
|
+
/** Array of menu button elements */
|
|
47
|
+
get menuButtons(): RCMenuButton[];
|
|
48
|
+
/** First trigger element */
|
|
49
|
+
get firstItem(): HTMLElement | undefined;
|
|
50
|
+
/** Last trigger element */
|
|
51
|
+
get lastItem(): HTMLElement | undefined;
|
|
52
|
+
/** Next trigger element, in tab-order */
|
|
53
|
+
get nextItem(): HTMLElement | undefined;
|
|
54
|
+
/** Previous trigger element, in tab-order */
|
|
55
|
+
get previousItem(): HTMLElement | undefined;
|
|
56
|
+
/** Get the menu button that contains a trigger */
|
|
57
|
+
private _getMenuButtonForTrigger;
|
|
58
|
+
/** Set focus to a specific trigger element */
|
|
59
|
+
focusItem(item?: HTMLElement | null): void;
|
|
60
|
+
/** Close any open menu */
|
|
61
|
+
closeActiveMenu(): void;
|
|
62
|
+
private _handleItemFocus;
|
|
63
|
+
private _handleSlotChange;
|
|
64
|
+
private _handleMenuButtonToggle;
|
|
65
|
+
private _handleNavigate;
|
|
66
|
+
private _openCurrentMenu;
|
|
67
|
+
private _handleKeydown;
|
|
68
|
+
private _handleClick;
|
|
69
|
+
private _normalizeKey;
|
|
70
|
+
protected render(): import('lit').TemplateResult<1>;
|
|
71
|
+
}
|
|
72
|
+
export default RCMenubar;
|
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rcarls/rc-menubar",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"description": "Headless WAI-ARIA menubar with roving tabindex, built with Lit",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/richardcarls/rc-webcomponents.git",
|
|
11
|
+
"directory": "packages/rc-menubar"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/richardcarls/rc-webcomponents#readme",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"module": "./dist/rc-menubar.js",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/types/packages/rc-menubar/src/index.d.ts",
|
|
24
|
+
"default": "./dist/rc-menubar.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"./define": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/types/packages/rc-menubar/src/define.d.ts",
|
|
30
|
+
"default": "./dist/rc-menubar-define.js"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"types": "./dist/types/packages/rc-menubar/src/index.d.ts",
|
|
35
|
+
"sideEffects": [
|
|
36
|
+
"./dist/rc-menubar-define.js"
|
|
37
|
+
],
|
|
38
|
+
"customElements": "dist/custom-elements.json",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"dev": "vite",
|
|
41
|
+
"build": "tsc && vite build && cem analyze",
|
|
42
|
+
"cem:analyze": "cem analyze",
|
|
43
|
+
"preview": "vite preview",
|
|
44
|
+
"test:browser": "vitest",
|
|
45
|
+
"test:browser:chrome": "vitest --project=chromium",
|
|
46
|
+
"test:browser:firefox": "vitest --project=firefox",
|
|
47
|
+
"yalc:publish": "yalc publish --push"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@rcarls/rc-common": "workspace:^",
|
|
51
|
+
"@rcarls/rc-menu": "workspace:^",
|
|
52
|
+
"@rcarls/rc-menu-button": "workspace:^",
|
|
53
|
+
"@types/node": "^25.6.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@custom-elements-manifest/analyzer": "0.11.0",
|
|
57
|
+
"@guanghechen/rollup-plugin-copy": "^6.0.9",
|
|
58
|
+
"@vitest/browser-playwright": "4.1.5",
|
|
59
|
+
"lit": "^3.0.0",
|
|
60
|
+
"playwright": "^1.56.0",
|
|
61
|
+
"rollup": "^4.60.2",
|
|
62
|
+
"typescript": "~5.9.3",
|
|
63
|
+
"vite": "^7.1.7",
|
|
64
|
+
"vite-plugin-dts": "^4.5.4",
|
|
65
|
+
"vitest": "^4.0.6",
|
|
66
|
+
"vitest-browser-lit": "^1.0.1"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"lit": "^3.0.0"
|
|
70
|
+
}
|
|
71
|
+
}
|