@rcarls/rc-toolbar 0.3.1 → 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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # `@rcarls/rc-toolbar`
2
2
 
3
- Toolbar that groups consumer-supplied controls into one tab stop with arrow-key navigation, following the [WAI-ARIA Toolbar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/).
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/).
4
6
 
5
7
  Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-toolbar](https://richardcarls.github.io/rc-webcomponents/components/rc-toolbar).
6
8
 
@@ -15,15 +17,17 @@ npm install @rcarls/rc-toolbar
15
17
  ## Import
16
18
 
17
19
  ```js
18
- import '@rcarls/rc-toolbar'; // side-effect: registers <rc-toolbar>
19
- import { RCToolbar } from '@rcarls/rc-toolbar'; // named class export
20
+ import '@rcarls/rc-toolbar'; // side-effect: registers <rc-toolbar>
21
+ import { RCToolbar } from '@rcarls/rc-toolbar'; // named class export
20
22
  ```
21
23
 
22
24
  ---
23
25
 
24
26
  ## Basic usage
25
27
 
26
- Place buttons, toggles, selects, or any focusable controls as direct children. Non-focusable elements (separators, labels) are rendered but skipped during keyboard navigation.
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.
27
31
 
28
32
  ```html
29
33
  <rc-toolbar label="Formatting">
@@ -47,42 +51,55 @@ Vertical orientation:
47
51
  </rc-toolbar>
48
52
  ```
49
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
+
50
65
  ---
51
66
 
52
67
  ## API
53
68
 
54
69
  ### Properties / attributes
55
70
 
56
- | Property | Attribute | Type | Default | Description |
57
- | --- | --- | --- | --- | --- |
58
- | `label` | `label` | `string` | `'Toolbar'` | `aria-label` for the toolbar. Required no visible label is rendered by the component. |
59
- | `orientation` | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction and keyboard navigation axis. |
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. |
60
75
 
61
76
  ### CSS custom properties
62
77
 
63
- | Property | Default | Description |
64
- | --- | --- | --- |
65
- | `--rc-toolbar-gap-inline` | `0.25em` | Gap between toolbar items |
66
- | `--rc-toolbar-padding-inline` | `0.25em` | Horizontal padding on the toolbar container |
67
- | `--rc-toolbar-padding-block` | `0.125em` | Vertical padding on the toolbar container |
68
- | `--rc-toolbar-radius` | `var(--rc-control-radius)` | Toolbar container border radius |
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 |
69
85
  | `--rc-toolbar-vertical-radius` | `var(--rc-toolbar-radius)` | Border radius override for vertical orientation |
70
86
 
71
87
  ### CSS parts
72
88
 
73
- | Part | Element | Description |
74
- | --- | --- | --- |
89
+ | Part | Element | Description |
90
+ | ------ | --------------------- | ----------------------------- |
75
91
  | `root` | `div[role="toolbar"]` | The toolbar container element |
76
92
 
77
93
  ### Slots
78
94
 
79
- | Slot | Description |
80
- | --- | --- |
81
- | *(default)* | Toolbar controls. Focusable elements (`<button>`, `<input>`, `<select>`, `<a href>`, elements with `tabindex`) participate in roving-tabindex navigation. All others are rendered but skipped. |
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. |
82
98
 
83
99
  ### Events
84
100
 
85
- `rc-toolbar` dispatches no custom events. Listen for standard events (`click`, `change`, etc.) on slotted children.
101
+ `rc-toolbar` dispatches no custom events. Listen for standard events (`click`,
102
+ `change`, etc.) on slotted children.
86
103
 
87
104
  ### Public methods
88
105
 
@@ -106,31 +123,31 @@ previousItem: HTMLElement | undefined
106
123
 
107
124
  ---
108
125
 
109
- ## Keyboard behaviour
126
+ ## Keyboard behavior
110
127
 
111
128
  The toolbar is a single tab stop. Arrow keys navigate within it.
112
129
 
113
- | Key | Orientation | Action |
114
- | --- | --- | --- |
115
- | `ArrowRight` | Horizontal | Focus next item (wraps) |
116
- | `ArrowLeft` | Horizontal | Focus previous item (wraps) |
117
- | `ArrowDown` | Vertical | Focus next item (wraps) |
118
- | `ArrowUp` | Vertical | Focus previous item (wraps) |
119
- | `Home` | Both | Focus first item |
120
- | `End` | Both | Focus last item |
121
- | `Tab` | Both | Leave toolbar (standard tab order) |
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) |
122
139
 
123
140
  ---
124
141
 
125
142
  ## ARIA
126
143
 
127
- | Attribute | Where | Value |
128
- | --- | --- | --- |
129
- | `role="toolbar"` | Root `div` | |
130
- | `aria-orientation` | Root `div` | `"horizontal"` or `"vertical"` |
131
- | `aria-label` | Root `div` | Value of `label` property |
132
- | `tabindex="0"` | Active item | One item tabbable at a time (roving tabindex) |
133
- | `tabindex="-1"` | All other items | Removed from tab order |
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 |
134
151
 
135
152
  ---
136
153
 
@@ -64,6 +64,11 @@
64
64
  "name": "--rc-toolbar-padding-block",
65
65
  "default": "calc(var(--rc-control-padding-block) / 2)"
66
66
  },
67
+ {
68
+ "description": "Whether toolbar items wrap onto multiple lines",
69
+ "name": "--rc-toolbar-flex-wrap",
70
+ "default": "nowrap"
71
+ },
67
72
  {
68
73
  "description": "Toolbar container border radius",
69
74
  "name": "--rc-toolbar-radius",
@@ -83,7 +88,7 @@
83
88
  ],
84
89
  "slots": [
85
90
  {
86
- "description": "any number of child elements to display in the toolbar. Only focusable elements are navigable.",
91
+ "description": "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.",
87
92
  "name": "Takes"
88
93
  }
89
94
  ],
@@ -119,14 +124,14 @@
119
124
  {
120
125
  "kind": "field",
121
126
  "name": "_disabledObserver",
122
- "privacy": "private",
127
+ "privacy": "protected",
123
128
  "readonly": true,
124
129
  "default": "new MutationObserver(() => this._syncTabStop())"
125
130
  },
126
131
  {
127
132
  "kind": "method",
128
133
  "name": "_syncTabStop",
129
- "privacy": "private",
134
+ "privacy": "protected",
130
135
  "return": {
131
136
  "type": {
132
137
  "text": "void"
@@ -162,7 +167,7 @@
162
167
  },
163
168
  "parameters": [
164
169
  {
165
- "name": "slot",
170
+ "name": "$slot",
166
171
  "type": {
167
172
  "text": "HTMLSlotElement"
168
173
  }
@@ -172,7 +177,7 @@
172
177
  {
173
178
  "kind": "method",
174
179
  "name": "_isToolbarItem",
175
- "privacy": "private",
180
+ "privacy": "protected",
176
181
  "return": {
177
182
  "type": {
178
183
  "text": "boolean"
@@ -180,7 +185,7 @@
180
185
  },
181
186
  "parameters": [
182
187
  {
183
- "name": "el",
188
+ "name": "$el",
184
189
  "type": {
185
190
  "text": "Element"
186
191
  }
@@ -190,21 +195,21 @@
190
195
  ],
191
196
  "attributes": [
192
197
  {
198
+ "description": "Accessible label for this toolbar. Default label is 'Toolbar'.",
193
199
  "name": "label",
194
200
  "type": {
195
201
  "text": "string"
196
202
  },
197
203
  "default": "'Toolbar'",
198
- "description": "Accessible label for this toolbar. Default label is 'Toolbar'.",
199
204
  "fieldName": "label"
200
205
  },
201
206
  {
207
+ "description": "Toolbar orientation, for keyboard navigation.",
202
208
  "name": "orientation",
203
209
  "type": {
204
210
  "text": "'horizontal' | 'vertical'"
205
211
  },
206
212
  "default": "'horizontal'",
207
- "description": "Toolbar orientation, for keyboard navigation.",
208
213
  "fieldName": "orientation"
209
214
  }
210
215
  ],
@@ -1,7 +1,7 @@
1
- import { css as u, LitElement as b, html as h } from "lit";
2
- import { property as d, query as f } from "lit/decorators.js";
3
- import { RovingTabIndexMixin as m, isFocusable as l, keyNavigation as p, keyInteraction as v } from "@rcarls/rc-common";
4
- const g = u`
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
5
  :host {
6
6
  display: inline-block;
7
7
  }
@@ -9,9 +9,16 @@ const g = u`
9
9
  #root {
10
10
  display: flex;
11
11
  flex-direction: row;
12
+ flex-wrap: var(--rc-toolbar-flex-wrap, nowrap);
12
13
  gap: var(--rc-toolbar-gap-inline, var(--rc-control-gap, 0.25em));
13
- padding-inline: var(--rc-toolbar-padding-inline, calc(var(--rc-control-padding-inline, 0.5em) / 2));
14
- padding-block: var(--rc-toolbar-padding-block, calc(var(--rc-control-padding-block, 0.25em) / 2));
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
+ );
15
22
  border-radius: var(--rc-toolbar-radius, var(--rc-control-radius, 0));
16
23
  font-family: var(--rc-font-family, inherit);
17
24
  font-size: var(--rc-font-size, inherit);
@@ -19,10 +26,13 @@ const g = u`
19
26
 
20
27
  &[aria-orientation='vertical'] {
21
28
  flex-direction: column;
22
- border-radius: var(--rc-toolbar-vertical-radius, var(--rc-toolbar-radius, var(--rc-control-radius, 0)));
29
+ border-radius: var(
30
+ --rc-toolbar-vertical-radius,
31
+ var(--rc-toolbar-radius, var(--rc-control-radius, 0))
32
+ );
23
33
  }
24
34
 
25
- /* because :has(:focus-visible) doens't work across slot boundary */
35
+ /* because :has(:focus-visible) doesn't work across slot boundary */
26
36
  &[data-interaction-mode='keyboard']:focus-within {
27
37
  outline: var(--rc-focus-ring, auto);
28
38
  outline-offset: var(--rc-focus-ring-offset, 0);
@@ -33,12 +43,12 @@ const g = u`
33
43
  display: contents;
34
44
  }
35
45
  `;
36
- var _ = Object.defineProperty, s = (n, t, e, y) => {
37
- for (var r = void 0, o = n.length - 1, c; o >= 0; o--)
38
- (c = n[o]) && (r = c(t, e, r) || r);
39
- return r && _(t, e, r), r;
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;
40
50
  };
41
- const a = class a extends m(b) {
51
+ const n = class n extends m(h) {
42
52
  constructor() {
43
53
  super(...arguments), this.label = "Toolbar", this.orientation = "horizontal", this._disabledObserver = new MutationObserver(() => this._syncTabStop());
44
54
  }
@@ -49,7 +59,7 @@ const a = class a extends m(b) {
49
59
  super.disconnectedCallback(), this._disabledObserver.disconnect();
50
60
  }
51
61
  _syncTabStop() {
52
- !this._lastFocused || l(this._lastFocused) || (this._lastFocused = void 0, this._initItems());
62
+ !this._lastFocused || d(this._lastFocused) || (this._lastFocused = void 0, this._initItems());
53
63
  }
54
64
  _initItems() {
55
65
  super._initItems();
@@ -75,7 +85,13 @@ const a = class a extends m(b) {
75
85
  }
76
86
  }
77
87
  _collectItems(t) {
78
- return t.assignedElements().filter((e) => this._isToolbarItem(e));
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
+ });
79
95
  }
80
96
  _isToolbarItem(t) {
81
97
  if (t instanceof HTMLButtonElement || t instanceof HTMLInputElement || t instanceof HTMLSelectElement || t instanceof HTMLTextAreaElement)
@@ -83,15 +99,15 @@ const a = class a extends m(b) {
83
99
  if (t instanceof HTMLAnchorElement || t instanceof HTMLAreaElement)
84
100
  return t.hasAttribute("href");
85
101
  const e = t.getAttribute("role");
86
- return e === "button" || e === "checkbox" || e === "link" || e === "menuitem" || e === "radio" || e === "slider" || e === "spinbutton" || e === "switch" || e === "textbox" ? !0 : l(t);
102
+ return e === "button" || e === "checkbox" || e === "link" || e === "menuitem" || e === "radio" || e === "slider" || e === "spinbutton" || e === "switch" || e === "textbox" ? !0 : d(t);
87
103
  }
88
104
  render() {
89
- return h`
105
+ return f`
90
106
  <div
91
107
  id="root"
92
108
  part="root"
93
- ${p(this._onNavigate)}
94
- ${v()}
109
+ ${v(this._onNavigate)}
110
+ ${g()}
95
111
  role="toolbar"
96
112
  aria-orientation=${this.orientation}
97
113
  aria-label=${this.label}
@@ -103,18 +119,18 @@ const a = class a extends m(b) {
103
119
  `;
104
120
  }
105
121
  };
106
- a.styles = [g];
107
- let i = a;
108
- s([
109
- d({ type: String })
122
+ n.styles = [_];
123
+ let i = n;
124
+ a([
125
+ u({ type: String })
110
126
  ], i.prototype, "label");
111
- s([
112
- d({ type: String })
127
+ a([
128
+ u({ type: String })
113
129
  ], i.prototype, "orientation");
114
- s([
115
- f("#root", !0)
130
+ a([
131
+ p("#root", !0)
116
132
  ], i.prototype, "_$root");
117
133
  export {
118
134
  i as R
119
135
  };
120
- //# sourceMappingURL=rc-toolbar-BPcEdxN8.js.map
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';\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":"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"}
@@ -1,4 +1,4 @@
1
- import { R as o } from "./rc-toolbar-BPcEdxN8.js";
1
+ import { R as o } from "./rc-toolbar-Bzh9yptT.js";
2
2
  customElements.get("rc-toolbar") || customElements.define("rc-toolbar", o);
3
3
  export {
4
4
  o as RCToolbar
@@ -1,4 +1,4 @@
1
- import { R as a } from "./rc-toolbar-BPcEdxN8.js";
1
+ import { R as a } from "./rc-toolbar-Bzh9yptT.js";
2
2
  export {
3
3
  a as RCToolbar
4
4
  };
@@ -13,10 +13,15 @@ declare const RCToolbar_base: typeof LitElement & (new (...args: any[]) => impor
13
13
  * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-toolbar rc-toolbar docs}
14
14
  * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/ WAI-ARIA Toolbar pattern}
15
15
  *
16
- * @slot Takes any number of child elements to display in the toolbar. Only focusable elements are navigable.
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.
17
21
  * @cssprop [--rc-toolbar-gap-inline=0.25em] - Gap between toolbar items
18
22
  * @cssprop [--rc-toolbar-padding-inline=calc(var(--rc-control-padding-inline) / 2)] - Horizontal padding on the toolbar container
19
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
20
25
  * @cssprop [--rc-toolbar-radius=var(--rc-control-radius)] - Toolbar container border radius
21
26
  * @cssprop [--rc-toolbar-vertical-radius=var(--rc-toolbar-radius)] - Toolbar container border radius when orientation is vertical; falls back to `--rc-toolbar-radius`
22
27
  * @csspart root - The toolbar container element
@@ -28,14 +33,14 @@ export declare class RCToolbar extends RCToolbar_base {
28
33
  /** Toolbar orientation, for keyboard navigation. */
29
34
  orientation: 'horizontal' | 'vertical';
30
35
  protected _$root: HTMLDivElement;
31
- private readonly _disabledObserver;
36
+ protected readonly _disabledObserver: MutationObserver;
32
37
  connectedCallback(): void;
33
38
  disconnectedCallback(): void;
34
- private _syncTabStop;
39
+ protected _syncTabStop(): void;
35
40
  protected _initItems(): void;
36
41
  protected _onNavigate(action: KeyboardNavigationAction): void;
37
- protected _collectItems(slot: HTMLSlotElement): Element[];
38
- private _isToolbarItem;
42
+ protected _collectItems($slot: HTMLSlotElement): Element[];
43
+ protected _isToolbarItem($el: Element): boolean;
39
44
  protected render(): import('lit').TemplateResult<1>;
40
45
  }
41
46
  export default RCToolbar;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.1",
6
+ "version": "0.4.0",
7
7
  "description": "Toolbar that groups consumer-supplied controls into one tab stop with arrow-key navigation.",
8
8
  "repository": {
9
9
  "type": "git",
@@ -1 +0,0 @@
1
- {"version":3,"file":"rc-toolbar-BPcEdxN8.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 gap: var(--rc-toolbar-gap-inline, var(--rc-control-gap, 0.25em));\n padding-inline: var(--rc-toolbar-padding-inline, calc(var(--rc-control-padding-inline, 0.5em) / 2));\n padding-block: var(--rc-toolbar-padding-block, calc(var(--rc-control-padding-block, 0.25em) / 2));\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(--rc-toolbar-vertical-radius, var(--rc-toolbar-radius, var(--rc-control-radius, 0)));\n }\n\n /* because :has(:focus-visible) doens'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';\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 elements to display in the toolbar. Only focusable elements are navigable.\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-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 private 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 private _syncTabStop(): void {\n if (!this._lastFocused || isFocusable(this._lastFocused)) return;\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 queueMicrotask(() => {\n if (this.matches(':focus-within')) this.focusItem(target);\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().filter((el) => this._isToolbarItem(el));\n }\n\n private _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 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","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;;;;;;ACgCtB,MAAMC,IAAN,MAAMA,UAAkBC,EAAoBC,CAAU,EAAE;AAAA,EAAxD,cAAA;AAAA,UAAA,GAAA,SAAA,GAKL,KAAA,QAAQ,WAIR,KAAA,cAAyC,cAKzC,KAAiB,oBAAoB,IAAI,iBAAiB,MAAM,KAAK,cAAc;AAAA,EAAA;AAAA,EAE1E,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,EAEQ,eAAqB;AAC3B,IAAI,CAAC,KAAK,gBAAgBC,EAAY,KAAK,YAAY,MACvD,KAAK,eAAe,QACpB,KAAK,WAAA;AAAA,EACP;AAAA,EAEmB,aAAa;AAG9B,UAAM,WAAA;AAMN,UAAMC,IAAS,KAAK,gBAAgB,KAAK;AACzC,mBAAe,MAAM;AACnB,MAAI,KAAK,QAAQ,eAAe,KAAG,KAAK,UAAUA,CAAM;AAAA,IAC1D,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,GAAkC;AACjE,WAAOA,EAAK,mBAAmB,OAAO,CAACC,MAAO,KAAK,eAAeA,CAAE,CAAC;AAAA,EACvE;AAAA,EAEQ,eAAeA,GAAsB;AAC3C,QACEA,aAAc,qBACdA,aAAc,oBACdA,aAAc,qBACdA,aAAc;AAEd,aAAO;AAGT,QAAIA,aAAc,qBAAqBA,aAAc;AACnD,aAAOA,EAAG,aAAa,MAAM;AAG/B,UAAMC,IAAOD,EAAG,aAAa,MAAM;AACnC,WACEC,MAAS,YACTA,MAAS,cACTA,MAAS,UACTA,MAAS,cACTA,MAAS,WACTA,MAAS,YACTA,MAAS,gBACTA,MAAS,YACTA,MAAS,YAEF,KAGFL,EAAYI,CAAE;AAAA,EACvB;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;AApHEX,EAAO,SAAS,CAACF,CAAa;AADzB,IAAMc,IAANZ;AAKLa,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;"}