@rcarls/rc-toolbar 0.0.0-next-20260921045401

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,89 @@
1
+ # @rcarls/rc-toolbar
2
+
3
+ ## 0.0.0-next-20260921045401
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [50d5610]
8
+ - Updated dependencies [4341311]
9
+ - @rcarls/rc-common@0.0.0-next-20260921045401
10
+
11
+ ## 0.6.0
12
+
13
+ ### Patch Changes
14
+
15
+ - 57370e4: Align component metadata, aggregate framework typings, development-time native-child
16
+ validation, controlled menu state, package declarations, and public documentation before the
17
+ next release.
18
+ - Updated dependencies [6683eb9]
19
+ - Updated dependencies [ee7ba6c]
20
+ - Updated dependencies [30eb232]
21
+ - @rcarls/rc-common@0.6.0
22
+
23
+ ## 0.5.0
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [4ae2ef0]
28
+ - Updated dependencies [689340c]
29
+ - @rcarls/rc-common@0.5.0
30
+
31
+ ## 0.4.2
32
+
33
+ ### Patch Changes
34
+
35
+ - @rcarls/rc-common@0.4.2
36
+
37
+ ## 0.4.1
38
+
39
+ ### Patch Changes
40
+
41
+ - @rcarls/rc-common@0.4.1
42
+
43
+ ## 0.4.0
44
+
45
+ ### Minor Changes
46
+
47
+ - 74d9046: Add native button-based chip controls and let toolbars coordinate groups of
48
+ interactive chips with roving focus.
49
+
50
+ ### Patch Changes
51
+
52
+ - b7270df: Compose the Markdown editor toolbar from `rc-toolbar`, `rc-button`, and
53
+ `rc-select`, and include native buttons inside direct `rc-button` wrappers in
54
+ toolbar roving-focus navigation.
55
+ - 4df89fd: Document the `label` and `orientation` attributes in the class JSDoc so the
56
+ generated ApiTable's attributes section is no longer empty.
57
+ - Updated dependencies [e57277f]
58
+ - Updated dependencies [ccca8e2]
59
+ - Updated dependencies [037b1b3]
60
+ - @rcarls/rc-common@0.4.0
61
+
62
+ ## 0.3.2
63
+
64
+ ### Patch Changes
65
+
66
+ - Updated dependencies [88b4086]
67
+ - @rcarls/rc-common@0.3.2
68
+
69
+ ## 0.3.1
70
+
71
+ ### Patch Changes
72
+
73
+ - @rcarls/rc-common@0.3.1
74
+
75
+ ## 0.3.0
76
+
77
+ ### Changed
78
+
79
+ - Update package metadata, README intro, and docs links.
80
+
81
+ ### Dependencies
82
+
83
+ - Sync internal dependencies to 0.3.0.
84
+
85
+ ## 0.2.0
86
+
87
+ ### Patch Changes
88
+
89
+ - @rcarls/rc-common@0.2.0
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # `@rcarls/rc-toolbar`
2
+
3
+ Toolbar that groups consumer-supplied controls into one tab stop with arrow-key
4
+ navigation, following the
5
+ [WAI-ARIA Toolbar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/).
6
+
7
+ Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-toolbar](https://richardcarls.github.io/rc-webcomponents/components/rc-toolbar).
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @rcarls/rc-toolbar
15
+ ```
16
+
17
+ ## Import
18
+
19
+ ```js
20
+ import '@rcarls/rc-toolbar'; // side-effect: registers <rc-toolbar>
21
+ import { RCToolbar } from '@rcarls/rc-toolbar'; // named class export
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Basic usage
27
+
28
+ Place buttons, toggles, selects, or any focusable controls as direct children.
29
+ Non-focusable elements (separators, labels) are rendered but skipped during
30
+ keyboard navigation.
31
+
32
+ ```html
33
+ <rc-toolbar label="Formatting">
34
+ <button onclick="bold()">Bold</button>
35
+ <button onclick="italic()">Italic</button>
36
+ <button onclick="underline()">Underline</button>
37
+ <hr aria-hidden="true" />
38
+ <button onclick="alignLeft()">Align left</button>
39
+ <button onclick="alignCenter()">Align center</button>
40
+ <button onclick="alignRight()">Align right</button>
41
+ </rc-toolbar>
42
+ ```
43
+
44
+ Vertical orientation:
45
+
46
+ ```html
47
+ <rc-toolbar orientation="vertical" label="Actions">
48
+ <button>Cut</button>
49
+ <button>Copy</button>
50
+ <button>Paste</button>
51
+ </rc-toolbar>
52
+ ```
53
+
54
+ Directly slotted `rc-button` and `rc-chip` elements participate through their
55
+ native child buttons:
56
+
57
+ ```html
58
+ <rc-toolbar label="Filters">
59
+ <rc-button><button type="button">All</button></rc-button>
60
+ <rc-chip variant="filter"><button type="button">Quick</button></rc-chip>
61
+ <rc-chip variant="filter"><button type="button">Vegetarian</button></rc-chip>
62
+ </rc-toolbar>
63
+ ```
64
+
65
+ ---
66
+
67
+ ## API
68
+
69
+ ### Properties / attributes
70
+
71
+ | Property | Attribute | Type | Default | Description |
72
+ | ------------- | ------------- | ---------------------------- | -------------- | -------------------------------------------------------------------------------------- |
73
+ | `label` | `label` | `string` | `'Toolbar'` | `aria-label` for the toolbar. Required; no visible label is rendered by the component. |
74
+ | `orientation` | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction and keyboard navigation axis. |
75
+
76
+ ### CSS custom properties
77
+
78
+ | Property | Default | Description |
79
+ | ------------------------------ | -------------------------- | ----------------------------------------------- |
80
+ | `--rc-toolbar-gap-inline` | `0.25em` | Gap between toolbar items |
81
+ | `--rc-toolbar-padding-inline` | `0.25em` | Horizontal padding on the toolbar container |
82
+ | `--rc-toolbar-padding-block` | `0.125em` | Vertical padding on the toolbar container |
83
+ | `--rc-toolbar-flex-wrap` | `nowrap` | Whether toolbar items wrap onto multiple lines |
84
+ | `--rc-toolbar-radius` | `var(--rc-control-radius)` | Toolbar container border radius |
85
+ | `--rc-toolbar-vertical-radius` | `var(--rc-toolbar-radius)` | Border radius override for vertical orientation |
86
+
87
+ ### CSS parts
88
+
89
+ | Part | Element | Description |
90
+ | ------ | --------------------- | ----------------------------- |
91
+ | `root` | `div[role="toolbar"]` | The toolbar container element |
92
+
93
+ ### Slots
94
+
95
+ | Slot | Description |
96
+ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
97
+ | _(default)_ | Toolbar controls. Focusable elements (`<button>`, `<input>`, `<select>`, `<a href>`, elements with `tabindex`) and native buttons inside direct `rc-button` or `rc-chip` wrappers participate in roving-tabindex navigation. All others are rendered but skipped. |
98
+
99
+ ### Events
100
+
101
+ `rc-toolbar` dispatches no custom events. Listen for standard events (`click`,
102
+ `change`, etc.) on slotted children.
103
+
104
+ ### Public methods
105
+
106
+ ```ts
107
+ focusItem(item?: HTMLElement | null): void
108
+ // Focus a specific toolbar item programmatically.
109
+
110
+ focusItemAt(index: number): void
111
+ // Focus the item at zero-based index.
112
+ ```
113
+
114
+ ### Read-only getters
115
+
116
+ ```ts
117
+ items: HTMLElement[] // All currently navigable (focusable) slotted items
118
+ firstItem: HTMLElement | undefined
119
+ lastItem: HTMLElement | undefined
120
+ nextItem: HTMLElement | undefined // Relative to the currently focused item
121
+ previousItem: HTMLElement | undefined
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Keyboard behavior
127
+
128
+ The toolbar is a single tab stop. Arrow keys navigate within it.
129
+
130
+ | Key | Orientation | Action |
131
+ | ------------ | ----------- | ---------------------------------- |
132
+ | `ArrowRight` | Horizontal | Focus next item (wraps) |
133
+ | `ArrowLeft` | Horizontal | Focus previous item (wraps) |
134
+ | `ArrowDown` | Vertical | Focus next item (wraps) |
135
+ | `ArrowUp` | Vertical | Focus previous item (wraps) |
136
+ | `Home` | Both | Focus first item |
137
+ | `End` | Both | Focus last item |
138
+ | `Tab` | Both | Leave toolbar (standard tab order) |
139
+
140
+ ---
141
+
142
+ ## ARIA
143
+
144
+ | Attribute | Where | Value |
145
+ | ------------------ | --------------- | --------------------------------------------- |
146
+ | `role="toolbar"` | Root `div` | Toolbar role |
147
+ | `aria-orientation` | Root `div` | `"horizontal"` or `"vertical"` |
148
+ | `aria-label` | Root `div` | Value of `label` property |
149
+ | `tabindex="0"` | Active item | One item tabbable at a time (roving tabindex) |
150
+ | `tabindex="-1"` | All other items | Removed from tab order |
151
+
152
+ ---
153
+
154
+ ## Browser support
155
+
156
+ All modern browsers. Requires Web Components support (Chrome 67+, Firefox 63+, Safari 12.1+).
@@ -0,0 +1,250 @@
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-toolbar",
13
+ "declaration": {
14
+ "name": "RCToolbar",
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-toolbar.js"
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "kind": "javascript-module",
45
+ "path": "src/rc-toolbar.ts",
46
+ "declarations": [
47
+ {
48
+ "kind": "class",
49
+ "description": "Toolbar that groups consumer-supplied controls into one tab stop with arrow-key\nnavigation, following the WAI-ARIA Toolbar pattern.",
50
+ "name": "RCToolbar",
51
+ "cssProperties": [
52
+ {
53
+ "description": "Gap between toolbar items",
54
+ "name": "--rc-toolbar-gap-inline",
55
+ "default": "0.25em"
56
+ },
57
+ {
58
+ "description": "Horizontal padding on the toolbar container",
59
+ "name": "--rc-toolbar-padding-inline",
60
+ "default": "calc(var(--rc-control-padding-inline) / 2)"
61
+ },
62
+ {
63
+ "description": "Vertical padding on the toolbar container",
64
+ "name": "--rc-toolbar-padding-block",
65
+ "default": "calc(var(--rc-control-padding-block) / 2)"
66
+ },
67
+ {
68
+ "description": "Whether toolbar items wrap onto multiple lines",
69
+ "name": "--rc-toolbar-flex-wrap",
70
+ "default": "nowrap"
71
+ },
72
+ {
73
+ "description": "Toolbar container border radius",
74
+ "name": "--rc-toolbar-radius",
75
+ "default": "var(--rc-control-radius)"
76
+ },
77
+ {
78
+ "description": "Toolbar container border radius when orientation is vertical; falls back to `--rc-toolbar-radius`",
79
+ "name": "--rc-toolbar-vertical-radius",
80
+ "default": "var(--rc-toolbar-radius)"
81
+ }
82
+ ],
83
+ "cssParts": [
84
+ {
85
+ "description": "The toolbar container element",
86
+ "name": "root"
87
+ }
88
+ ],
89
+ "slots": [
90
+ {
91
+ "description": "Takes any number of child controls, `rc-button`, or `rc-chip` elements to display in the toolbar. Direct native controls and the native buttons inside supported wrappers are navigable.",
92
+ "name": ""
93
+ }
94
+ ],
95
+ "members": [
96
+ {
97
+ "kind": "field",
98
+ "name": "label",
99
+ "type": {
100
+ "text": "string"
101
+ },
102
+ "default": "'Toolbar'",
103
+ "description": "Accessible label for this toolbar. Default label is 'Toolbar'.",
104
+ "attribute": "label"
105
+ },
106
+ {
107
+ "kind": "field",
108
+ "name": "orientation",
109
+ "type": {
110
+ "text": "'horizontal' | 'vertical'"
111
+ },
112
+ "default": "'horizontal'",
113
+ "description": "Toolbar orientation, for keyboard navigation.",
114
+ "attribute": "orientation"
115
+ },
116
+ {
117
+ "kind": "field",
118
+ "name": "_$root",
119
+ "type": {
120
+ "text": "HTMLDivElement"
121
+ },
122
+ "privacy": "protected"
123
+ },
124
+ {
125
+ "kind": "field",
126
+ "name": "_disabledObserver",
127
+ "privacy": "protected",
128
+ "readonly": true,
129
+ "default": "new MutationObserver(() => this._syncTabStop())"
130
+ },
131
+ {
132
+ "kind": "method",
133
+ "name": "_syncTabStop",
134
+ "privacy": "protected",
135
+ "return": {
136
+ "type": {
137
+ "text": "void"
138
+ }
139
+ }
140
+ },
141
+ {
142
+ "kind": "method",
143
+ "name": "_initItems",
144
+ "privacy": "protected"
145
+ },
146
+ {
147
+ "kind": "method",
148
+ "name": "_onNavigate",
149
+ "privacy": "protected",
150
+ "parameters": [
151
+ {
152
+ "name": "action",
153
+ "type": {
154
+ "text": "KeyboardNavigationAction"
155
+ }
156
+ }
157
+ ]
158
+ },
159
+ {
160
+ "kind": "method",
161
+ "name": "_collectItems",
162
+ "privacy": "protected",
163
+ "return": {
164
+ "type": {
165
+ "text": "Element[]"
166
+ }
167
+ },
168
+ "parameters": [
169
+ {
170
+ "name": "$slot",
171
+ "type": {
172
+ "text": "HTMLSlotElement"
173
+ }
174
+ }
175
+ ]
176
+ },
177
+ {
178
+ "kind": "method",
179
+ "name": "_isToolbarItem",
180
+ "privacy": "protected",
181
+ "return": {
182
+ "type": {
183
+ "text": "boolean"
184
+ }
185
+ },
186
+ "parameters": [
187
+ {
188
+ "name": "$el",
189
+ "type": {
190
+ "text": "Element"
191
+ }
192
+ }
193
+ ]
194
+ }
195
+ ],
196
+ "attributes": [
197
+ {
198
+ "description": "Accessible label for this toolbar. Default label is 'Toolbar'.",
199
+ "name": "label",
200
+ "type": {
201
+ "text": "string"
202
+ },
203
+ "default": "'Toolbar'",
204
+ "fieldName": "label"
205
+ },
206
+ {
207
+ "description": "Toolbar orientation, for keyboard navigation.",
208
+ "name": "orientation",
209
+ "type": {
210
+ "text": "'horizontal' | 'vertical'"
211
+ },
212
+ "default": "'horizontal'",
213
+ "fieldName": "orientation"
214
+ }
215
+ ],
216
+ "mixins": [
217
+ {
218
+ "name": "RovingTabIndexMixin",
219
+ "package": "@rcarls/rc-common"
220
+ }
221
+ ],
222
+ "superclass": {
223
+ "name": "LitElement",
224
+ "package": "lit"
225
+ },
226
+ "tagName": "rc-toolbar",
227
+ "customElement": true
228
+ }
229
+ ],
230
+ "exports": [
231
+ {
232
+ "kind": "js",
233
+ "name": "RCToolbar",
234
+ "declaration": {
235
+ "name": "RCToolbar",
236
+ "module": "src/rc-toolbar.ts"
237
+ }
238
+ },
239
+ {
240
+ "kind": "js",
241
+ "name": "default",
242
+ "declaration": {
243
+ "name": "RCToolbar",
244
+ "module": "src/rc-toolbar.ts"
245
+ }
246
+ }
247
+ ]
248
+ }
249
+ ]
250
+ }
@@ -0,0 +1,136 @@
1
+ import { css as b, LitElement as h, html as f } from "lit";
2
+ import { property as u, query as p } from "lit/decorators.js";
3
+ import { RovingTabIndexMixin as m, isFocusable as d, keyNavigation as v, keyInteraction as g } from "@rcarls/rc-common";
4
+ const _ = b`
5
+ :host {
6
+ display: inline-block;
7
+ }
8
+
9
+ #root {
10
+ display: flex;
11
+ flex-direction: row;
12
+ flex-wrap: var(--rc-toolbar-flex-wrap, nowrap);
13
+ gap: var(--rc-toolbar-gap-inline, var(--rc-control-gap, 0.25em));
14
+ padding-inline: var(
15
+ --rc-toolbar-padding-inline,
16
+ calc(var(--rc-control-padding-inline, 0.5em) / 2)
17
+ );
18
+ padding-block: var(
19
+ --rc-toolbar-padding-block,
20
+ calc(var(--rc-control-padding-block, 0.25em) / 2)
21
+ );
22
+ border-radius: var(--rc-toolbar-radius, var(--rc-control-radius, 0));
23
+ font-family: var(--rc-font-family, inherit);
24
+ font-size: var(--rc-font-size, inherit);
25
+ line-height: var(--rc-line-height, normal);
26
+
27
+ &[aria-orientation='vertical'] {
28
+ flex-direction: column;
29
+ border-radius: var(
30
+ --rc-toolbar-vertical-radius,
31
+ var(--rc-toolbar-radius, var(--rc-control-radius, 0))
32
+ );
33
+ }
34
+
35
+ /* because :has(:focus-visible) doesn't work across slot boundary */
36
+ &[data-interaction-mode='keyboard']:focus-within {
37
+ outline: var(--rc-focus-ring, auto);
38
+ outline-offset: var(--rc-focus-ring-offset, 0);
39
+ }
40
+ }
41
+
42
+ #slot-wrap {
43
+ display: contents;
44
+ }
45
+ `;
46
+ var y = Object.defineProperty, a = (c, t, e, o) => {
47
+ for (var r = void 0, s = c.length - 1, l; s >= 0; s--)
48
+ (l = c[s]) && (r = l(t, e, r) || r);
49
+ return r && y(t, e, r), r;
50
+ };
51
+ const n = class n extends m(h) {
52
+ constructor() {
53
+ super(...arguments), this.label = "Toolbar", this.orientation = "horizontal", this._disabledObserver = new MutationObserver(() => this._syncTabStop());
54
+ }
55
+ connectedCallback() {
56
+ super.connectedCallback(), this._disabledObserver.observe(this, { attributeFilter: ["disabled"], subtree: !0 });
57
+ }
58
+ disconnectedCallback() {
59
+ super.disconnectedCallback(), this._disabledObserver.disconnect();
60
+ }
61
+ _syncTabStop() {
62
+ !this._lastFocused || d(this._lastFocused) || (this._lastFocused = void 0, this._initItems());
63
+ }
64
+ _initItems() {
65
+ super._initItems();
66
+ const t = this._lastFocused ?? this.firstItem;
67
+ queueMicrotask(() => {
68
+ this.matches(":focus-within") && this.focusItem(t);
69
+ });
70
+ }
71
+ _onNavigate(t) {
72
+ switch (t) {
73
+ case "next":
74
+ this.focusItem(this.nextItem);
75
+ break;
76
+ case "prev":
77
+ this.focusItem(this.previousItem);
78
+ break;
79
+ case "start":
80
+ this.focusItem(this.firstItem);
81
+ break;
82
+ case "end":
83
+ this.focusItem(this.lastItem);
84
+ break;
85
+ }
86
+ }
87
+ _collectItems(t) {
88
+ return t.assignedElements().flatMap((e) => {
89
+ if (e.matches("rc-button, rc-chip")) {
90
+ const o = e.querySelector(":scope > button");
91
+ return o ? [o] : [];
92
+ }
93
+ return this._isToolbarItem(e) ? [e] : [];
94
+ });
95
+ }
96
+ _isToolbarItem(t) {
97
+ if (t instanceof HTMLButtonElement || t instanceof HTMLInputElement || t instanceof HTMLSelectElement || t instanceof HTMLTextAreaElement)
98
+ return !0;
99
+ if (t instanceof HTMLAnchorElement || t instanceof HTMLAreaElement)
100
+ return t.hasAttribute("href");
101
+ const e = t.getAttribute("role");
102
+ return e === "button" || e === "checkbox" || e === "link" || e === "menuitem" || e === "radio" || e === "slider" || e === "spinbutton" || e === "switch" || e === "textbox" ? !0 : d(t);
103
+ }
104
+ render() {
105
+ return f`
106
+ <div
107
+ id="root"
108
+ part="root"
109
+ ${v(this._onNavigate)}
110
+ ${g()}
111
+ role="toolbar"
112
+ aria-orientation=${this.orientation}
113
+ aria-label=${this.label}
114
+ >
115
+ <div id="slot-wrap" @focusin=${this._handleItemFocus}>
116
+ <slot id="items" @slotchange=${this._onSlotChange}></slot>
117
+ </div>
118
+ </div>
119
+ `;
120
+ }
121
+ };
122
+ n.styles = [_];
123
+ let i = n;
124
+ a([
125
+ u({ type: String })
126
+ ], i.prototype, "label");
127
+ a([
128
+ u({ type: String })
129
+ ], i.prototype, "orientation");
130
+ a([
131
+ p("#root", !0)
132
+ ], i.prototype, "_$root");
133
+ export {
134
+ i as R
135
+ };
136
+ //# sourceMappingURL=rc-toolbar-Bzh9yptT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-toolbar-Bzh9yptT.js","sources":["../src/rc-toolbar.styles.ts","../src/rc-toolbar.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const toolbarStyles = css`\n :host {\n display: inline-block;\n }\n\n #root {\n display: flex;\n flex-direction: row;\n flex-wrap: var(--rc-toolbar-flex-wrap, nowrap);\n gap: var(--rc-toolbar-gap-inline, var(--rc-control-gap, 0.25em));\n padding-inline: var(\n --rc-toolbar-padding-inline,\n calc(var(--rc-control-padding-inline, 0.5em) / 2)\n );\n padding-block: var(\n --rc-toolbar-padding-block,\n calc(var(--rc-control-padding-block, 0.25em) / 2)\n );\n border-radius: var(--rc-toolbar-radius, var(--rc-control-radius, 0));\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 &[aria-orientation='vertical'] {\n flex-direction: column;\n border-radius: var(\n --rc-toolbar-vertical-radius,\n var(--rc-toolbar-radius, var(--rc-control-radius, 0))\n );\n }\n\n /* because :has(:focus-visible) doesn't work across slot boundary */\n &[data-interaction-mode='keyboard']:focus-within {\n outline: var(--rc-focus-ring, auto);\n outline-offset: var(--rc-focus-ring-offset, 0);\n }\n }\n\n #slot-wrap {\n display: contents;\n }\n`;\n\nexport default toolbarStyles;\n","import { LitElement, html } from 'lit';\nimport { property, query } from 'lit/decorators.js';\n\nimport {\n isFocusable,\n keyInteraction,\n keyNavigation,\n type KeyboardNavigationAction,\n RovingTabIndexMixin,\n} from '@rcarls/rc-common';\n\nimport toolbarStyles from './rc-toolbar.styles.js';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-toolbar': RCToolbar;\n }\n}\n\n/**\n * Toolbar that groups consumer-supplied controls into one tab stop with arrow-key\n * navigation, following the WAI-ARIA Toolbar pattern.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-toolbar rc-toolbar docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/ WAI-ARIA Toolbar pattern}\n *\n * @slot - Takes any number of child controls, `rc-button`, or `rc-chip` elements\n * to display in the toolbar. Direct native controls and the native buttons\n * inside supported wrappers are navigable.\n * @attr label - Accessible label for this toolbar. Default label is 'Toolbar'.\n * @attr orientation - Toolbar orientation, for keyboard navigation.\n * @cssprop [--rc-toolbar-gap-inline=0.25em] - Gap between toolbar items\n * @cssprop [--rc-toolbar-padding-inline=calc(var(--rc-control-padding-inline) / 2)] - Horizontal padding on the toolbar container\n * @cssprop [--rc-toolbar-padding-block=calc(var(--rc-control-padding-block) / 2)] - Vertical padding on the toolbar container\n * @cssprop [--rc-toolbar-flex-wrap=nowrap] - Whether toolbar items wrap onto multiple lines\n * @cssprop [--rc-toolbar-radius=var(--rc-control-radius)] - Toolbar container border radius\n * @cssprop [--rc-toolbar-vertical-radius=var(--rc-toolbar-radius)] - Toolbar container border radius when orientation is vertical; falls back to `--rc-toolbar-radius`\n * @csspart root - The toolbar container element\n */\nexport class RCToolbar extends RovingTabIndexMixin(LitElement) {\n static styles = [toolbarStyles];\n\n /** Accessible label for this toolbar. Default label is 'Toolbar'. */\n @property({ type: String })\n label = 'Toolbar';\n\n /** Toolbar orientation, for keyboard navigation. */\n @property({ type: String })\n orientation: 'horizontal' | 'vertical' = 'horizontal';\n\n @query('#root', true)\n protected _$root!: HTMLDivElement;\n\n protected readonly _disabledObserver = new MutationObserver(() => this._syncTabStop());\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._disabledObserver.observe(this, { attributeFilter: ['disabled'], subtree: true });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._disabledObserver.disconnect();\n }\n\n protected _syncTabStop(): void {\n if (!this._lastFocused || isFocusable(this._lastFocused)) {\n return;\n }\n\n this._lastFocused = undefined;\n this._initItems();\n }\n\n protected override _initItems() {\n // Set tabindex synchronously so items are in the tab order immediately,\n // even if the document has no focus yet (focus() would be a no-op).\n super._initItems();\n\n // Defer focus to avoid firing focusin/focusout inside a SolidJS reactive\n // update cycle, which can cause the update to hang.\n // Guard: only restore focus when the toolbar already contains it — prevents\n // focus-stealing when a toolbar mounts inside a larger component (e.g. rc-transfer-list).\n const $target = this._lastFocused ?? this.firstItem;\n\n queueMicrotask(() => {\n if (this.matches(':focus-within')) {\n this.focusItem($target);\n }\n });\n }\n\n protected _onNavigate(action: KeyboardNavigationAction) {\n switch (action) {\n case 'next':\n this.focusItem(this.nextItem);\n break;\n case 'prev':\n this.focusItem(this.previousItem);\n break;\n case 'start':\n this.focusItem(this.firstItem);\n break;\n case 'end':\n this.focusItem(this.lastItem);\n break;\n }\n }\n\n protected override _collectItems($slot: HTMLSlotElement): Element[] {\n return $slot.assignedElements().flatMap(($element) => {\n if ($element.matches('rc-button, rc-chip')) {\n const $button = $element.querySelector(':scope > button');\n\n return $button ? [$button] : [];\n }\n\n return this._isToolbarItem($element) ? [$element] : [];\n });\n }\n\n protected _isToolbarItem($el: Element): boolean {\n if (\n $el instanceof HTMLButtonElement ||\n $el instanceof HTMLInputElement ||\n $el instanceof HTMLSelectElement ||\n $el instanceof HTMLTextAreaElement\n ) {\n return true;\n }\n\n if ($el instanceof HTMLAnchorElement || $el instanceof HTMLAreaElement) {\n return $el.hasAttribute('href');\n }\n\n const role = $el.getAttribute('role');\n\n if (\n role === 'button' ||\n role === 'checkbox' ||\n role === 'link' ||\n role === 'menuitem' ||\n role === 'radio' ||\n role === 'slider' ||\n role === 'spinbutton' ||\n role === 'switch' ||\n role === 'textbox'\n ) {\n return true;\n }\n\n return isFocusable($el);\n }\n\n protected render() {\n return html`\n <div\n id=\"root\"\n part=\"root\"\n ${keyNavigation(this._onNavigate)}\n ${keyInteraction()}\n role=\"toolbar\"\n aria-orientation=${this.orientation}\n aria-label=${this.label}\n >\n <div id=\"slot-wrap\" @focusin=${this._handleItemFocus}>\n <slot id=\"items\" @slotchange=${this._onSlotChange}></slot>\n </div>\n </div>\n `;\n }\n}\n\nexport default RCToolbar;\n"],"names":["toolbarStyles","css","_RCToolbar","RovingTabIndexMixin","LitElement","isFocusable","$target","action","$slot","$element","$button","$el","role","html","keyNavigation","keyInteraction","RCToolbar","__decorateClass","property","query"],"mappings":";;;AAEO,MAAMA,IAAgBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;ACqCtB,MAAMC,IAAN,MAAMA,UAAkBC,EAAoBC,CAAU,EAAE;AAAA,EAAxD,cAAA;AAAA,UAAA,GAAA,SAAA,GAKL,KAAA,QAAQ,WAIR,KAAA,cAAyC,cAKzC,KAAmB,oBAAoB,IAAI,iBAAiB,MAAM,KAAK,cAAc;AAAA,EAAA;AAAA,EAE5E,oBAA0B;AACjC,UAAM,kBAAA,GACN,KAAK,kBAAkB,QAAQ,MAAM,EAAE,iBAAiB,CAAC,UAAU,GAAG,SAAS,IAAM;AAAA,EACvF;AAAA,EAES,uBAA6B;AACpC,UAAM,qBAAA,GACN,KAAK,kBAAkB,WAAA;AAAA,EACzB;AAAA,EAEU,eAAqB;AAC7B,IAAI,CAAC,KAAK,gBAAgBC,EAAY,KAAK,YAAY,MAIvD,KAAK,eAAe,QACpB,KAAK,WAAA;AAAA,EACP;AAAA,EAEmB,aAAa;AAG9B,UAAM,WAAA;AAMN,UAAMC,IAAU,KAAK,gBAAgB,KAAK;AAE1C,mBAAe,MAAM;AACnB,MAAI,KAAK,QAAQ,eAAe,KAC9B,KAAK,UAAUA,CAAO;AAAA,IAE1B,CAAC;AAAA,EACH;AAAA,EAEU,YAAYC,GAAkC;AACtD,YAAQA,GAAA;AAAA,MACN,KAAK;AACH,aAAK,UAAU,KAAK,QAAQ;AAC5B;AAAA,MACF,KAAK;AACH,aAAK,UAAU,KAAK,YAAY;AAChC;AAAA,MACF,KAAK;AACH,aAAK,UAAU,KAAK,SAAS;AAC7B;AAAA,MACF,KAAK;AACH,aAAK,UAAU,KAAK,QAAQ;AAC5B;AAAA,IAAA;AAAA,EAEN;AAAA,EAEmB,cAAcC,GAAmC;AAClE,WAAOA,EAAM,iBAAA,EAAmB,QAAQ,CAACC,MAAa;AACpD,UAAIA,EAAS,QAAQ,oBAAoB,GAAG;AAC1C,cAAMC,IAAUD,EAAS,cAAc,iBAAiB;AAExD,eAAOC,IAAU,CAACA,CAAO,IAAI,CAAA;AAAA,MAC/B;AAEA,aAAO,KAAK,eAAeD,CAAQ,IAAI,CAACA,CAAQ,IAAI,CAAA;AAAA,IACtD,CAAC;AAAA,EACH;AAAA,EAEU,eAAeE,GAAuB;AAC9C,QACEA,aAAe,qBACfA,aAAe,oBACfA,aAAe,qBACfA,aAAe;AAEf,aAAO;AAGT,QAAIA,aAAe,qBAAqBA,aAAe;AACrD,aAAOA,EAAI,aAAa,MAAM;AAGhC,UAAMC,IAAOD,EAAI,aAAa,MAAM;AAEpC,WACEC,MAAS,YACTA,MAAS,cACTA,MAAS,UACTA,MAAS,cACTA,MAAS,WACTA,MAAS,YACTA,MAAS,gBACTA,MAAS,YACTA,MAAS,YAEF,KAGFP,EAAYM,CAAG;AAAA,EACxB;AAAA,EAEU,SAAS;AACjB,WAAOE;AAAA;AAAA;AAAA;AAAA,UAIDC,EAAc,KAAK,WAAW,CAAC;AAAA,UAC/BC,GAAgB;AAAA;AAAA,2BAEC,KAAK,WAAW;AAAA,qBACtB,KAAK,KAAK;AAAA;AAAA,uCAEQ,KAAK,gBAAgB;AAAA,yCACnB,KAAK,aAAa;AAAA;AAAA;AAAA;AAAA,EAIzD;AACF;AAnIEb,EAAO,SAAS,CAACF,CAAa;AADzB,IAAMgB,IAANd;AAKLe,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAJfF,EAKX,WAAA,OAAA;AAIAC,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GARfF,EASX,WAAA,aAAA;AAGUC,EAAA;AAAA,EADTE,EAAM,SAAS,EAAI;AAAA,GAXTH,EAYD,WAAA,QAAA;"}
@@ -0,0 +1,6 @@
1
+ import { R as o } from "./rc-toolbar-Bzh9yptT.js";
2
+ customElements.get("rc-toolbar") || customElements.define("rc-toolbar", o);
3
+ export {
4
+ o as RCToolbar
5
+ };
6
+ //# sourceMappingURL=rc-toolbar-define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-toolbar-define.js","sources":["../src/define.ts"],"sourcesContent":["import { RCToolbar } from './index.js';\n\ncustomElements.get('rc-toolbar') || customElements.define('rc-toolbar', RCToolbar);\n\nexport * from './index.js';\n"],"names":["RCToolbar"],"mappings":";AAEA,eAAe,IAAI,YAAY,KAAK,eAAe,OAAO,cAAcA,CAAS;"}
@@ -0,0 +1,5 @@
1
+ import { R as a } from "./rc-toolbar-Bzh9yptT.js";
2
+ export {
3
+ a as RCToolbar
4
+ };
5
+ //# sourceMappingURL=rc-toolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-toolbar.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -0,0 +1 @@
1
+ export * from './rc-toolbar.js';
@@ -0,0 +1,46 @@
1
+ import { LitElement } from 'lit';
2
+ import { KeyboardNavigationAction } from '@rcarls/rc-common';
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ 'rc-toolbar': RCToolbar;
6
+ }
7
+ }
8
+ declare const RCToolbar_base: typeof LitElement & (new (...args: any[]) => import('@rcarls/rc-common').RovingTabIndexMixinBase);
9
+ /**
10
+ * Toolbar that groups consumer-supplied controls into one tab stop with arrow-key
11
+ * navigation, following the WAI-ARIA Toolbar pattern.
12
+ *
13
+ * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-toolbar rc-toolbar docs}
14
+ * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/ WAI-ARIA Toolbar pattern}
15
+ *
16
+ * @slot - Takes any number of child controls, `rc-button`, or `rc-chip` elements
17
+ * to display in the toolbar. Direct native controls and the native buttons
18
+ * inside supported wrappers are navigable.
19
+ * @attr label - Accessible label for this toolbar. Default label is 'Toolbar'.
20
+ * @attr orientation - Toolbar orientation, for keyboard navigation.
21
+ * @cssprop [--rc-toolbar-gap-inline=0.25em] - Gap between toolbar items
22
+ * @cssprop [--rc-toolbar-padding-inline=calc(var(--rc-control-padding-inline) / 2)] - Horizontal padding on the toolbar container
23
+ * @cssprop [--rc-toolbar-padding-block=calc(var(--rc-control-padding-block) / 2)] - Vertical padding on the toolbar container
24
+ * @cssprop [--rc-toolbar-flex-wrap=nowrap] - Whether toolbar items wrap onto multiple lines
25
+ * @cssprop [--rc-toolbar-radius=var(--rc-control-radius)] - Toolbar container border radius
26
+ * @cssprop [--rc-toolbar-vertical-radius=var(--rc-toolbar-radius)] - Toolbar container border radius when orientation is vertical; falls back to `--rc-toolbar-radius`
27
+ * @csspart root - The toolbar container element
28
+ */
29
+ export declare class RCToolbar extends RCToolbar_base {
30
+ static styles: import('lit').CSSResult[];
31
+ /** Accessible label for this toolbar. Default label is 'Toolbar'. */
32
+ label: string;
33
+ /** Toolbar orientation, for keyboard navigation. */
34
+ orientation: 'horizontal' | 'vertical';
35
+ protected _$root: HTMLDivElement;
36
+ protected readonly _disabledObserver: MutationObserver;
37
+ connectedCallback(): void;
38
+ disconnectedCallback(): void;
39
+ protected _syncTabStop(): void;
40
+ protected _initItems(): void;
41
+ protected _onNavigate(action: KeyboardNavigationAction): void;
42
+ protected _collectItems($slot: HTMLSlotElement): Element[];
43
+ protected _isToolbarItem($el: Element): boolean;
44
+ protected render(): import('lit').TemplateResult<1>;
45
+ }
46
+ export default RCToolbar;
@@ -0,0 +1,2 @@
1
+ export declare const toolbarStyles: import('lit').CSSResult;
2
+ export default toolbarStyles;
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@rcarls/rc-toolbar",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.0.0-next-20260921045401",
7
+ "description": "Toolbar that groups consumer-supplied controls into one tab stop with arrow-key navigation.",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-toolbar"
12
+ },
13
+ "homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-toolbar",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-toolbar.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-toolbar/src/index.d.ts",
24
+ "default": "./dist/rc-toolbar.js"
25
+ }
26
+ },
27
+ "./define": {
28
+ "import": {
29
+ "types": "./dist/types/packages/rc-toolbar/src/define.d.ts",
30
+ "default": "./dist/rc-toolbar-define.js"
31
+ }
32
+ }
33
+ },
34
+ "types": "./dist/types/packages/rc-toolbar/src/index.d.ts",
35
+ "sideEffects": [
36
+ "./dist/rc-toolbar-define.js"
37
+ ],
38
+ "customElements": "dist/custom-elements.json",
39
+ "scripts": {
40
+ "build": "tsc && vite build && cem analyze",
41
+ "cem:analyze": "cem analyze",
42
+ "preview": "vite preview",
43
+ "test:browser": "vitest --run",
44
+ "test:browser:chrome": "vitest --run --project=chromium",
45
+ "test:browser:firefox": "vitest --run --project=firefox"
46
+ },
47
+ "dependencies": {
48
+ "@rcarls/rc-common": "0.0.0-next-20260921045401"
49
+ },
50
+ "devDependencies": {
51
+ "@custom-elements-manifest/analyzer": "0.11.0",
52
+ "@vitest/browser-playwright": "4.1.5",
53
+ "lit": "^3.0.0",
54
+ "playwright": "^1.56.0",
55
+ "rollup": "^4.60.2",
56
+ "typescript": "~5.9.3",
57
+ "vite": "^7.1.7",
58
+ "vite-plugin-dts": "^4.5.4",
59
+ "vitest": "^4.0.6",
60
+ "vitest-browser-lit": "^1.0.1"
61
+ },
62
+ "peerDependencies": {
63
+ "lit": "^3.0.0"
64
+ }
65
+ }