@rcarls/rc-segmented-button 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,80 @@
1
+ # @rcarls/rc-segmented-button
2
+
3
+ ## 0.0.0-next-20260921045401
4
+
5
+ ### Minor Changes
6
+
7
+ - fef89dc: Move the segmented appearance into the component behind one switch.
8
+
9
+ Turning the native fieldset into a segmented control took 27 `--_rc-*`
10
+ declarations that every theme had to restate, 23 of them byte-identical. Two of
11
+ those blocks were accessibility recipes rather than taste: the visually hidden
12
+ legend that keeps the group's accessible name, and the hidden but focusable
13
+ radio. A theme that got either wrong removed the accessible name or made the
14
+ control unreachable by keyboard.
15
+
16
+ The component now owns the recipe and a theme sets
17
+ `--rc-segmented-button-appearance: segmented`. Four genuine theme choices become
18
+ public properties: `--rc-segmented-button-cursor`,
19
+ `--rc-segmented-button-disabled-cursor`,
20
+ `--rc-segmented-button-vertical-divider`, and
21
+ `--rc-segmented-button-selected-icon-display`.
22
+
23
+ Unset, the fieldset, legend, and radios keep their browser appearance, as
24
+ before. The switch is read with a style container query (Chrome 111, Safari 18,
25
+ Firefox 128); where that is unsupported the recipe does not apply and the
26
+ control falls back to the same native appearance.
27
+
28
+ To migrate a theme, replace its `--_rc-segmented-button-*` block with the single
29
+ `--rc-segmented-button-appearance: segmented` declaration, plus any of the four
30
+ public properties whose value differs from the default.
31
+
32
+ ### Patch Changes
33
+
34
+ - fb54f77: Keep the selected segment visible in forced-colors mode.
35
+
36
+ Forced colors replaces the background a theme selects a segment with, which left
37
+ selection legible only through the optional selected-icon slot. The base styles
38
+ now restate the selected segment in `Highlight` and `HighlightText`, matching the
39
+ `rc-chip` treatment, so selection survives in every theme whether or not the icon
40
+ slot is used.
41
+
42
+ ## 0.6.0
43
+
44
+ ## 0.5.0
45
+
46
+ ### Minor Changes
47
+
48
+ - 7c68235: Fix two spacing bugs in the optional selected-icon (checkmark) pattern:
49
+ `--rc-segmented-button-segment-gap` had no fallback value, so there was no
50
+ gap at all between the checkmark and the label text; and the checkmark
51
+ slot was `display: none` when unchecked (zero width, zero gap consumed),
52
+ so a segment's width visibly grew when it became selected.
53
+
54
+ The selected-icon slot now always reserves its width (new
55
+ `--rc-segmented-button-selected-icon-size`, default `1.25em`), toggling
56
+ only `visibility` between checked/unchecked states, with a matching
57
+ pure-CSS mirror spacer on the opposite side (RTL-safe via logical
58
+ `inline-size`) so the label text stays centered instead of leaning toward
59
+ the now-always-reserved leading icon slot. Segment width is now constant
60
+ regardless of selection state. `--rc-segmented-button-segment-gap`
61
+ defaults to `0.5em`. Both changes are backward compatible for consumers
62
+ not using the selected-icon slot at all.
63
+
64
+ ## 0.4.2
65
+
66
+ ## 0.4.1
67
+
68
+ ## 0.4.0
69
+
70
+ ### Minor Changes
71
+
72
+ - 193ef66: Add the rc-segmented-button package for native radio group enhancement.
73
+
74
+ ### Patch Changes
75
+
76
+ - 55e8ab5: Preserve UA-like control and surface styling until an optional theme supplies decorative tokens.
77
+ Restore themed button hover and pressed state layers, including a pointer-origin Material ripple.
78
+ - d918392: Document all 14 `rc-segmented-button` CSS custom properties in the generated
79
+ API reference, including which ones defer to native fieldset/label/radio
80
+ styling when unset.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @rcarls/rc-segmented-button
2
+
3
+ Segmented button group backed by native radio inputs.
4
+
5
+ ```html
6
+ <rc-segmented-button>
7
+ <fieldset>
8
+ <legend>Text size</legend>
9
+ <label><input type="radio" name="size" value="small" /> Small</label>
10
+ <label><input type="radio" name="size" value="medium" checked /> Medium</label>
11
+ <label><input type="radio" name="size" value="large" /> Large</label>
12
+ </fieldset>
13
+ </rc-segmented-button>
14
+ ```
15
+
16
+ The fieldset, legend, labels, and radios remain in light DOM for forms and no-JavaScript fallback.
17
+
18
+ ## Segmented appearance
19
+
20
+ Unstyled, the fieldset, legend, and radios keep their browser appearance. Set
21
+ `--rc-segmented-button-appearance: segmented` to turn on the component's own
22
+ recipe: a flat fieldset, a visually hidden legend, and radios that stay
23
+ focusable and in the accessibility tree while being invisible. The packaged
24
+ themes set it for you.
25
+
26
+ ```css
27
+ .my-theme rc-segmented-button {
28
+ --rc-segmented-button-appearance: segmented;
29
+ }
30
+ ```
31
+
32
+ ### Browser support
33
+
34
+ That switch is read with a CSS style container query, which has narrower support
35
+ than the rest of this package: Chrome 111, Safari 18, and Firefox 128. Everything
36
+ else here, including the component's behavior, keyboard handling, and form
37
+ participation, works wherever custom elements do.
38
+
39
+ Where style queries are unsupported the recipe simply does not apply and the
40
+ control falls back to the native fieldset and radio appearance, which stays
41
+ fully usable and accessible. Nothing breaks; it looks like plain radio buttons.
42
+ If that fallback is not acceptable for your audience, write the segment layout
43
+ in your own CSS instead of using the switch.
@@ -0,0 +1,439 @@
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-segmented-button",
13
+ "declaration": {
14
+ "name": "RCSegmentedButton",
15
+ "module": "/src/rc-segmented-button.js"
16
+ }
17
+ }
18
+ ]
19
+ },
20
+ {
21
+ "kind": "javascript-module",
22
+ "path": "src/index.ts",
23
+ "declarations": [],
24
+ "exports": [
25
+ {
26
+ "kind": "js",
27
+ "name": "RCSegmentedButton",
28
+ "declaration": {
29
+ "name": "default",
30
+ "module": "./rc-segmented-button.js"
31
+ }
32
+ },
33
+ {
34
+ "kind": "js",
35
+ "name": "RCSegmentedButtonChangeDetail",
36
+ "declaration": {
37
+ "name": "RCSegmentedButtonChangeDetail",
38
+ "module": "./rc-segmented-button.js"
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "kind": "javascript-module",
45
+ "path": "src/rc-segmented-button.ts",
46
+ "declarations": [
47
+ {
48
+ "kind": "class",
49
+ "description": "Segmented button group that enhances a direct native `<fieldset>` of radio inputs.",
50
+ "name": "RCSegmentedButton",
51
+ "cssProperties": [
52
+ {
53
+ "description": "Set to `segmented` to turn on the component's segmented recipe: a flat fieldset, a visually hidden legend, and hidden but focusable radios. Unset, the fieldset, legend, and radios keep their browser appearance.",
54
+ "name": "--rc-segmented-button-appearance"
55
+ },
56
+ {
57
+ "description": "Segment cursor once the segmented recipe is on.",
58
+ "name": "--rc-segmented-button-cursor",
59
+ "default": "pointer"
60
+ },
61
+ {
62
+ "description": "Segment cursor when its radio is disabled, or when the host is `disabled`, once the segmented recipe is on.",
63
+ "name": "--rc-segmented-button-disabled-cursor",
64
+ "default": "not-allowed"
65
+ },
66
+ {
67
+ "description": "Border on the inline-start edge of adjacent segments in vertical orientation (defaults to the segment border).",
68
+ "name": "--rc-segmented-button-vertical-divider"
69
+ },
70
+ {
71
+ "description": "Display of the selected-icon slot. Set to `none` for a theme that shows selection without a tick.",
72
+ "name": "--rc-segmented-button-selected-icon-display",
73
+ "default": "inline-grid"
74
+ },
75
+ {
76
+ "description": "Gap between segment icon and label content.",
77
+ "name": "--rc-segmented-button-segment-gap",
78
+ "default": "0.5em"
79
+ },
80
+ {
81
+ "description": "Reserved inline-size of the selected-icon slot (and its opposite-side mirror spacer) — constant regardless of checked state, so segment width doesn't change on selection.",
82
+ "name": "--rc-segmented-button-selected-icon-size",
83
+ "default": "1.25em"
84
+ },
85
+ {
86
+ "description": "Minimum segment block size (defers to native label layout when unset).",
87
+ "name": "--rc-segmented-button-segment-min-block-size"
88
+ },
89
+ {
90
+ "description": "Segment block-axis padding (defers to native label padding when unset).",
91
+ "name": "--rc-segmented-button-segment-padding-block",
92
+ "default": "revert"
93
+ },
94
+ {
95
+ "description": "Segment inline-axis padding (defers to native label padding when unset).",
96
+ "name": "--rc-segmented-button-segment-padding-inline",
97
+ "default": "revert"
98
+ },
99
+ {
100
+ "description": "Segment border (defers to native label border when unset).",
101
+ "name": "--rc-segmented-button-border",
102
+ "default": "revert"
103
+ },
104
+ {
105
+ "description": "Segment text color (defers to native label color when unset).",
106
+ "name": "--rc-segmented-button-color",
107
+ "default": "revert"
108
+ },
109
+ {
110
+ "description": "Segment background (defers to native label background when unset).",
111
+ "name": "--rc-segmented-button-bg",
112
+ "default": "revert"
113
+ },
114
+ {
115
+ "description": "Rounded corner radius on the first and last segments (defers to native label styling when unset).",
116
+ "name": "--rc-segmented-button-radius",
117
+ "default": "revert"
118
+ },
119
+ {
120
+ "description": "Border between adjacent segments (defers to native label styling when unset).",
121
+ "name": "--rc-segmented-button-divider",
122
+ "default": "revert"
123
+ },
124
+ {
125
+ "description": "Segment text color when its radio is checked (defers to native label color when unset).",
126
+ "name": "--rc-segmented-button-selected-color",
127
+ "default": "revert"
128
+ },
129
+ {
130
+ "description": "Segment background when its radio is checked (defers to native label background when unset).",
131
+ "name": "--rc-segmented-button-selected-bg",
132
+ "default": "revert"
133
+ },
134
+ {
135
+ "description": "Segment outline when its radio is focus-visible (defers to native focus styling when unset).",
136
+ "name": "--rc-segmented-button-focus-ring",
137
+ "default": "revert"
138
+ },
139
+ {
140
+ "description": "Segment outline offset when its radio is focus-visible (defers to native focus styling when unset).",
141
+ "name": "--rc-segmented-button-focus-ring-offset",
142
+ "default": "revert"
143
+ },
144
+ {
145
+ "description": "Segment opacity when its radio is disabled, or when the host is `disabled` (defers to native disabled styling when unset). Under `forced-colors: active` the selected segment is restated in system colors (`Highlight` / `HighlightText`), so a selected state stays visible even when a theme paints selection with a background the forced palette replaces, and even when the optional selected-icon slot is not used.",
146
+ "name": "--rc-segmented-button-disabled-opacity",
147
+ "default": "revert"
148
+ }
149
+ ],
150
+ "slots": [
151
+ {
152
+ "description": "A direct `<fieldset>` containing native radio inputs and labels.",
153
+ "name": ""
154
+ }
155
+ ],
156
+ "members": [
157
+ {
158
+ "kind": "field",
159
+ "name": "_styledRoots",
160
+ "privacy": "private",
161
+ "static": true,
162
+ "readonly": true,
163
+ "default": "new Set<Document | ShadowRoot>()"
164
+ },
165
+ {
166
+ "kind": "method",
167
+ "name": "_ensureBaseStyles",
168
+ "privacy": "private",
169
+ "static": true,
170
+ "return": {
171
+ "type": {
172
+ "text": "void"
173
+ }
174
+ },
175
+ "parameters": [
176
+ {
177
+ "name": "root",
178
+ "type": {
179
+ "text": "Document | ShadowRoot"
180
+ }
181
+ }
182
+ ]
183
+ },
184
+ {
185
+ "kind": "field",
186
+ "name": "_value",
187
+ "type": {
188
+ "text": "string"
189
+ },
190
+ "privacy": "private",
191
+ "default": "''"
192
+ },
193
+ {
194
+ "kind": "field",
195
+ "name": "_defaultValue",
196
+ "type": {
197
+ "text": "string"
198
+ },
199
+ "privacy": "private",
200
+ "default": "''"
201
+ },
202
+ {
203
+ "kind": "field",
204
+ "name": "_valueInitialized",
205
+ "type": {
206
+ "text": "boolean"
207
+ },
208
+ "privacy": "private",
209
+ "default": "false"
210
+ },
211
+ {
212
+ "kind": "field",
213
+ "name": "_$fieldset",
214
+ "type": {
215
+ "text": "HTMLFieldSetElement | null"
216
+ },
217
+ "privacy": "private",
218
+ "default": "null"
219
+ },
220
+ {
221
+ "kind": "field",
222
+ "name": "_disabledOwned",
223
+ "type": {
224
+ "text": "boolean"
225
+ },
226
+ "privacy": "private",
227
+ "default": "false"
228
+ },
229
+ {
230
+ "kind": "field",
231
+ "name": "_slotMicrotaskQueued",
232
+ "type": {
233
+ "text": "boolean"
234
+ },
235
+ "privacy": "private",
236
+ "default": "false"
237
+ },
238
+ {
239
+ "kind": "field",
240
+ "name": "value",
241
+ "type": {
242
+ "text": "string"
243
+ },
244
+ "description": "Current selected radio value. Host writes are silent.",
245
+ "attribute": "value",
246
+ "reflects": true
247
+ },
248
+ {
249
+ "kind": "field",
250
+ "name": "defaultValue",
251
+ "type": {
252
+ "text": "string"
253
+ },
254
+ "description": "Initial selected value for uncontrolled usage.",
255
+ "attribute": "default-value"
256
+ },
257
+ {
258
+ "kind": "field",
259
+ "name": "disabled",
260
+ "type": {
261
+ "text": "boolean"
262
+ },
263
+ "default": "false",
264
+ "description": "Mirror disabled state to the native fieldset.",
265
+ "attribute": "disabled",
266
+ "reflects": true
267
+ },
268
+ {
269
+ "kind": "field",
270
+ "name": "orientation",
271
+ "type": {
272
+ "text": "'horizontal' | 'vertical'"
273
+ },
274
+ "default": "'horizontal'",
275
+ "description": "Keyboard orientation.",
276
+ "attribute": "orientation",
277
+ "reflects": true
278
+ },
279
+ {
280
+ "kind": "method",
281
+ "name": "_handleSlotChange",
282
+ "privacy": "private",
283
+ "return": {
284
+ "type": {
285
+ "text": "void"
286
+ }
287
+ }
288
+ },
289
+ {
290
+ "kind": "method",
291
+ "name": "_syncFieldset",
292
+ "privacy": "private",
293
+ "return": {
294
+ "type": {
295
+ "text": "void"
296
+ }
297
+ }
298
+ },
299
+ {
300
+ "kind": "method",
301
+ "name": "_syncInitialValue",
302
+ "privacy": "private",
303
+ "return": {
304
+ "type": {
305
+ "text": "void"
306
+ }
307
+ }
308
+ },
309
+ {
310
+ "kind": "field",
311
+ "name": "_$radios",
312
+ "type": {
313
+ "text": "HTMLInputElement[]"
314
+ },
315
+ "privacy": "private",
316
+ "readonly": true
317
+ },
318
+ {
319
+ "kind": "method",
320
+ "name": "_$enabledRadios",
321
+ "privacy": "private",
322
+ "return": {
323
+ "type": {
324
+ "text": "HTMLInputElement[]"
325
+ }
326
+ }
327
+ },
328
+ {
329
+ "kind": "field",
330
+ "name": "_handleChange",
331
+ "privacy": "private",
332
+ "readonly": true
333
+ },
334
+ {
335
+ "kind": "field",
336
+ "name": "_handleKeyDown",
337
+ "privacy": "private",
338
+ "readonly": true
339
+ },
340
+ {
341
+ "kind": "method",
342
+ "name": "_applyValue",
343
+ "privacy": "private",
344
+ "return": {
345
+ "type": {
346
+ "text": "void"
347
+ }
348
+ },
349
+ "parameters": [
350
+ {
351
+ "name": "_writeDefault",
352
+ "type": {
353
+ "text": "boolean"
354
+ }
355
+ }
356
+ ]
357
+ },
358
+ {
359
+ "kind": "method",
360
+ "name": "_applyDisabled",
361
+ "privacy": "private",
362
+ "return": {
363
+ "type": {
364
+ "text": "void"
365
+ }
366
+ }
367
+ }
368
+ ],
369
+ "events": [
370
+ {
371
+ "description": "Fired after user interaction changes the selected value.",
372
+ "name": "rc-segmented-button-change"
373
+ }
374
+ ],
375
+ "attributes": [
376
+ {
377
+ "description": "Current selected radio value. Host writes are silent.",
378
+ "name": "value",
379
+ "type": {
380
+ "text": "string"
381
+ },
382
+ "fieldName": "value"
383
+ },
384
+ {
385
+ "description": "Initial selected value for uncontrolled usage.",
386
+ "name": "default-value",
387
+ "type": {
388
+ "text": "string"
389
+ },
390
+ "fieldName": "defaultValue"
391
+ },
392
+ {
393
+ "description": "Mirror disabled state to the native fieldset.",
394
+ "name": "disabled",
395
+ "type": {
396
+ "text": "boolean"
397
+ },
398
+ "default": "false",
399
+ "fieldName": "disabled"
400
+ },
401
+ {
402
+ "description": "Keyboard orientation.",
403
+ "name": "orientation",
404
+ "type": {
405
+ "text": "'horizontal' | 'vertical'"
406
+ },
407
+ "default": "'horizontal'",
408
+ "fieldName": "orientation"
409
+ }
410
+ ],
411
+ "superclass": {
412
+ "name": "LitElement",
413
+ "package": "lit"
414
+ },
415
+ "tagName": "rc-segmented-button",
416
+ "customElement": true
417
+ }
418
+ ],
419
+ "exports": [
420
+ {
421
+ "kind": "js",
422
+ "name": "RCSegmentedButton",
423
+ "declaration": {
424
+ "name": "RCSegmentedButton",
425
+ "module": "src/rc-segmented-button.ts"
426
+ }
427
+ },
428
+ {
429
+ "kind": "js",
430
+ "name": "default",
431
+ "declaration": {
432
+ "name": "RCSegmentedButton",
433
+ "module": "src/rc-segmented-button.ts"
434
+ }
435
+ }
436
+ ]
437
+ }
438
+ ]
439
+ }
@@ -0,0 +1,309 @@
1
+ import { css as h, LitElement as b, html as p } from "lit";
2
+ import { property as o } from "lit/decorators.js";
3
+ const g = h`
4
+ :host {
5
+ display: inline-block;
6
+ color-scheme: inherit;
7
+ }
8
+
9
+ :host([hidden]) {
10
+ display: none;
11
+ }
12
+ `;
13
+ var f = Object.defineProperty, v = Object.getOwnPropertyDescriptor, c = (u, e, t, r) => {
14
+ for (var i = r > 1 ? void 0 : r ? v(e, t) : e, s = u.length - 1, l; s >= 0; s--)
15
+ (l = u[s]) && (i = (r ? l(e, t, i) : l(i)) || i);
16
+ return r && i && f(e, t, i), i;
17
+ };
18
+ const m = `
19
+ @layer rc-base {
20
+ /*
21
+ * The segmented appearance, behind one switch a theme sets.
22
+ *
23
+ * Everything here is structure rather than paint, and two of the blocks are
24
+ * accessibility recipes rather than taste: the legend stays in the
25
+ * accessibility tree while being visually hidden, and each radio stays
26
+ * focusable and hit-testable through its label while being invisible. A
27
+ * theme that reached for display:none on either would remove the group's
28
+ * accessible name or make the control unreachable by keyboard, so the
29
+ * component owns both rather than restating them per theme.
30
+ *
31
+ * Gating on a style query keeps the documented unthemed contract: with the
32
+ * property unset the fieldset, legend, and radios keep their browser
33
+ * appearance. Where style queries are unsupported the recipe simply does not
34
+ * apply, which lands on that same native appearance.
35
+ */
36
+ @container style(--rc-segmented-button-appearance: segmented) {
37
+ rc-segmented-button > fieldset {
38
+ display: inline-flex;
39
+ margin: 0;
40
+ padding: 0;
41
+ border: 0;
42
+ border-radius: 0;
43
+ overflow: visible;
44
+ }
45
+
46
+ rc-segmented-button > fieldset > legend {
47
+ position: absolute;
48
+ inline-size: 1px;
49
+ block-size: 1px;
50
+ margin: -1px;
51
+ padding: 0;
52
+ border: 0;
53
+ overflow: hidden;
54
+ clip: rect(0 0 0 0);
55
+ white-space: nowrap;
56
+ }
57
+
58
+ rc-segmented-button > fieldset > label {
59
+ display: inline-flex;
60
+ cursor: var(--rc-segmented-button-cursor, pointer);
61
+ user-select: none;
62
+ }
63
+
64
+ rc-segmented-button > fieldset > label:has(input[type='radio']:disabled),
65
+ rc-segmented-button[disabled] > fieldset > label {
66
+ cursor: var(--rc-segmented-button-disabled-cursor, not-allowed);
67
+ }
68
+
69
+ rc-segmented-button[orientation='vertical'] > fieldset > label + label {
70
+ border-inline-start: var(
71
+ --rc-segmented-button-vertical-divider,
72
+ var(--rc-segmented-button-border, revert)
73
+ );
74
+ }
75
+
76
+ rc-segmented-button > fieldset > label > input[type='radio'] {
77
+ position: absolute;
78
+ inline-size: 1px;
79
+ block-size: 1px;
80
+ margin: 0;
81
+ opacity: 0;
82
+ pointer-events: none;
83
+ }
84
+ }
85
+
86
+ rc-segmented-button[orientation='vertical'] > fieldset {
87
+ flex-direction: column;
88
+ }
89
+
90
+ rc-segmented-button > fieldset > label {
91
+ align-items: center;
92
+ justify-content: center;
93
+ gap: var(--rc-segmented-button-segment-gap, 0.5em);
94
+ min-block-size: var(--rc-segmented-button-segment-min-block-size);
95
+ padding-block: var(--rc-segmented-button-segment-padding-block, revert);
96
+ padding-inline: var(--rc-segmented-button-segment-padding-inline, revert);
97
+ border: var(--rc-segmented-button-border, revert);
98
+ color: var(--rc-segmented-button-color, revert);
99
+ background: var(--rc-segmented-button-bg, revert);
100
+ }
101
+
102
+ rc-segmented-button > fieldset > label:first-of-type {
103
+ border-start-start-radius: var(--rc-segmented-button-radius, revert);
104
+ border-end-start-radius: var(--rc-segmented-button-radius, revert);
105
+ }
106
+
107
+ rc-segmented-button > fieldset > label:last-of-type {
108
+ border-start-end-radius: var(--rc-segmented-button-radius, revert);
109
+ border-end-end-radius: var(--rc-segmented-button-radius, revert);
110
+ }
111
+
112
+ rc-segmented-button > fieldset > label + label {
113
+ border-inline-start: var(--rc-segmented-button-divider, revert);
114
+ }
115
+
116
+ rc-segmented-button[orientation='vertical'] > fieldset > label + label {
117
+ border-block-start: var(--rc-segmented-button-divider, revert);
118
+ }
119
+
120
+ rc-segmented-button[orientation='vertical'] > fieldset > label:first-of-type {
121
+ border-start-start-radius: var(--rc-segmented-button-radius, revert);
122
+ border-start-end-radius: var(--rc-segmented-button-radius, revert);
123
+ border-end-start-radius: 0;
124
+ }
125
+
126
+ rc-segmented-button[orientation='vertical'] > fieldset > label:last-of-type {
127
+ border-start-end-radius: 0;
128
+ border-end-start-radius: var(--rc-segmented-button-radius, revert);
129
+ border-end-end-radius: var(--rc-segmented-button-radius, revert);
130
+ }
131
+
132
+ rc-segmented-button > fieldset > label:has(input[type='radio']:checked) {
133
+ color: var(--rc-segmented-button-selected-color, revert);
134
+ background: var(--rc-segmented-button-selected-bg, revert);
135
+ }
136
+
137
+ rc-segmented-button > fieldset > label:has(input[type='radio']:focus-visible) {
138
+ outline: var(--rc-segmented-button-focus-ring, revert);
139
+ outline-offset: var(--rc-segmented-button-focus-ring-offset, revert);
140
+ }
141
+
142
+ rc-segmented-button > fieldset > label:has(input[type='radio']:disabled),
143
+ rc-segmented-button[disabled] > fieldset > label {
144
+ opacity: var(--rc-segmented-button-disabled-opacity, revert);
145
+ }
146
+
147
+ /* Always reserved (never display:none) so a segment's width doesn't
148
+ change between checked/unchecked — only visibility toggles. A fixed
149
+ inline-size keeps the reserved width deterministic regardless of the
150
+ icon's own intrinsic size. */
151
+ rc-segmented-button > fieldset > label > [data-rc-segmented-button-selected-icon] {
152
+ display: var(--rc-segmented-button-selected-icon-display, inline-grid);
153
+ inline-size: var(--rc-segmented-button-selected-icon-size, 1.25em);
154
+ place-items: center;
155
+ visibility: hidden;
156
+ }
157
+
158
+ rc-segmented-button > fieldset > label:has(input[type='radio']:checked) > [data-rc-segmented-button-selected-icon] {
159
+ visibility: visible;
160
+ }
161
+
162
+ /* Mirrors the selected-icon slot's reserved width on the opposite side,
163
+ so the label text stays centered instead of leaning toward the
164
+ always-reserved leading icon slot. Logical inline-size + scoping via
165
+ :has() (only labels that opt into the checkmark pattern get a mirror)
166
+ keeps this RTL-correct and backward compatible for consumers not
167
+ using the selected-icon slot at all. */
168
+ rc-segmented-button > fieldset > label:has([data-rc-segmented-button-selected-icon])::after {
169
+ content: '';
170
+ inline-size: var(--rc-segmented-button-selected-icon-size, 1.25em);
171
+ visibility: hidden;
172
+ }
173
+
174
+ /* Forced colors replaces the palette a theme selects a segment with, so the
175
+ selected state has to be restated in system colors here. Without it the
176
+ only surviving indicator is the optional selected-icon slot, which a theme
177
+ is free to switch off, and a theme that does leaves the control with no
178
+ visible selection at all. Mirrors the rc-chip treatment. */
179
+ @media (forced-colors: active) {
180
+ rc-segmented-button > fieldset > label {
181
+ border-color: ButtonBorder;
182
+ background: ButtonFace;
183
+ color: ButtonText;
184
+ }
185
+
186
+ rc-segmented-button > fieldset > label:has(input[type='radio']:checked) {
187
+ border-color: Highlight;
188
+ background: Highlight;
189
+ color: HighlightText;
190
+ }
191
+ }
192
+ }
193
+ `;
194
+ var n;
195
+ const d = (n = class extends b {
196
+ constructor() {
197
+ super(...arguments), this._value = "", this._defaultValue = "", this._valueInitialized = !1, this._$fieldset = null, this._disabledOwned = !1, this._slotMicrotaskQueued = !1, this.disabled = !1, this.orientation = "horizontal", this._handleChange = (e) => {
198
+ const t = e.target;
199
+ if (!(t instanceof HTMLInputElement) || t.type !== "radio" || !t.checked)
200
+ return;
201
+ const r = this.value;
202
+ this._value = t.value, this._valueInitialized = !0, this.requestUpdate("value", r), this.dispatchEvent(
203
+ new CustomEvent("rc-segmented-button-change", {
204
+ bubbles: !0,
205
+ composed: !0,
206
+ detail: { value: this.value }
207
+ })
208
+ );
209
+ }, this._handleKeyDown = (e) => {
210
+ const t = e.target;
211
+ if (!(t instanceof HTMLInputElement) || t.type !== "radio" || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
212
+ return;
213
+ const r = this.orientation === "vertical" ? ["ArrowDown"] : ["ArrowRight", "ArrowDown"], i = this.orientation === "vertical" ? ["ArrowUp"] : ["ArrowLeft", "ArrowUp"], s = this._$enabledRadios(), l = s.indexOf(t);
214
+ if (l === -1)
215
+ return;
216
+ let a;
217
+ r.includes(e.key) ? a = s[(l + 1) % s.length] : i.includes(e.key) ? a = s[(l - 1 + s.length) % s.length] : e.key === "Home" ? a = s[0] : e.key === "End" && (a = s[s.length - 1]), a && (e.preventDefault(), a.focus(), a.checked = !0, a.dispatchEvent(new Event("change", { bubbles: !0 })));
218
+ };
219
+ }
220
+ static _ensureBaseStyles(e) {
221
+ if (n._styledRoots.has(e))
222
+ return;
223
+ n._styledRoots.add(e);
224
+ const t = document.createElement("style");
225
+ t.setAttribute("data-rc-light-dom-base", "rc-segmented-button"), t.textContent = m, e instanceof Document ? e.head.append(t) : e.append(t);
226
+ }
227
+ get value() {
228
+ return this._value || this._defaultValue;
229
+ }
230
+ set value(e) {
231
+ const t = this.value;
232
+ this._value = e ?? "", this._valueInitialized = !0, this._applyValue(!1), this.requestUpdate("value", t);
233
+ }
234
+ get defaultValue() {
235
+ return this._defaultValue;
236
+ }
237
+ set defaultValue(e) {
238
+ const t = this._defaultValue;
239
+ this._defaultValue = e, !this._valueInitialized && !this._value && (this._applyValue(!1), this.requestUpdate("value", t)), this.requestUpdate("defaultValue", t);
240
+ }
241
+ connectedCallback() {
242
+ super.connectedCallback(), n._ensureBaseStyles(this.getRootNode());
243
+ }
244
+ disconnectedCallback() {
245
+ this._$fieldset?.removeEventListener("change", this._handleChange), this._$fieldset?.removeEventListener("keydown", this._handleKeyDown), super.disconnectedCallback();
246
+ }
247
+ firstUpdated() {
248
+ this._syncFieldset();
249
+ }
250
+ updated(e) {
251
+ e.has("disabled") && this._applyDisabled();
252
+ }
253
+ render() {
254
+ return p`<slot @slotchange=${this._handleSlotChange}></slot>`;
255
+ }
256
+ _handleSlotChange() {
257
+ this._slotMicrotaskQueued || (this._slotMicrotaskQueued = !0, queueMicrotask(() => {
258
+ this._slotMicrotaskQueued = !1, this.isConnected && this._syncFieldset();
259
+ }));
260
+ }
261
+ _syncFieldset() {
262
+ const e = this.querySelector(":scope > fieldset");
263
+ if (e === this._$fieldset) {
264
+ this._syncInitialValue(), this._applyValue(!1), this._applyDisabled();
265
+ return;
266
+ }
267
+ this._$fieldset?.removeEventListener("change", this._handleChange), this._$fieldset?.removeEventListener("keydown", this._handleKeyDown), this._$fieldset = e, this._disabledOwned = !1, e && (e.addEventListener("change", this._handleChange), e.addEventListener("keydown", this._handleKeyDown)), this._syncInitialValue(), this._applyValue(!1), this._applyDisabled();
268
+ }
269
+ _syncInitialValue() {
270
+ if (this._valueInitialized || this._value)
271
+ return;
272
+ const e = this._$radios.find((t) => t.checked);
273
+ e && (this._value = e.value, this._defaultValue = e.defaultChecked ? e.value : this._defaultValue);
274
+ }
275
+ get _$radios() {
276
+ return this._$fieldset ? Array.from(
277
+ this._$fieldset.querySelectorAll(':scope input[type="radio"]')
278
+ ) : [];
279
+ }
280
+ _$enabledRadios() {
281
+ return this._$radios.filter((e) => !e.disabled && !this.disabled);
282
+ }
283
+ _applyValue(e) {
284
+ const t = this._$radios, r = this.value;
285
+ for (const i of t)
286
+ i.checked = i.value === r;
287
+ }
288
+ _applyDisabled() {
289
+ const e = this._$fieldset;
290
+ e && (this.disabled ? (e.disabled || (this._disabledOwned = !0), e.disabled = !0) : this._disabledOwned && (e.disabled = !1, this._disabledOwned = !1));
291
+ }
292
+ }, n.styles = g, n._styledRoots = /* @__PURE__ */ new Set(), n);
293
+ c([
294
+ o({ type: String, reflect: !0 })
295
+ ], d.prototype, "value", 1);
296
+ c([
297
+ o({ type: String, attribute: "default-value" })
298
+ ], d.prototype, "defaultValue", 1);
299
+ c([
300
+ o({ type: Boolean, reflect: !0 })
301
+ ], d.prototype, "disabled", 2);
302
+ c([
303
+ o({ type: String, reflect: !0 })
304
+ ], d.prototype, "orientation", 2);
305
+ let w = d;
306
+ export {
307
+ w as R
308
+ };
309
+ //# sourceMappingURL=rc-segmented-button-BHU4o-Pr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-segmented-button-BHU4o-Pr.js","sources":["../src/rc-segmented-button.styles.ts","../src/rc-segmented-button.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const segmentedButtonStyles = css`\n :host {\n display: inline-block;\n color-scheme: inherit;\n }\n\n :host([hidden]) {\n display: none;\n }\n`;\n\nexport default segmentedButtonStyles;\n","import { LitElement, html } from 'lit';\nimport type { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport segmentedButtonStyles from './rc-segmented-button.styles.js';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-segmented-button': RCSegmentedButton;\n }\n\n interface HTMLElementEventMap {\n 'rc-segmented-button-change': CustomEvent<RCSegmentedButtonChangeDetail>;\n }\n}\n\nconst LIGHT_DOM_CSS = `\n@layer rc-base {\n /*\n * The segmented appearance, behind one switch a theme sets.\n *\n * Everything here is structure rather than paint, and two of the blocks are\n * accessibility recipes rather than taste: the legend stays in the\n * accessibility tree while being visually hidden, and each radio stays\n * focusable and hit-testable through its label while being invisible. A\n * theme that reached for display:none on either would remove the group's\n * accessible name or make the control unreachable by keyboard, so the\n * component owns both rather than restating them per theme.\n *\n * Gating on a style query keeps the documented unthemed contract: with the\n * property unset the fieldset, legend, and radios keep their browser\n * appearance. Where style queries are unsupported the recipe simply does not\n * apply, which lands on that same native appearance.\n */\n @container style(--rc-segmented-button-appearance: segmented) {\n rc-segmented-button > fieldset {\n display: inline-flex;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: 0;\n overflow: visible;\n }\n\n rc-segmented-button > fieldset > legend {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n }\n\n rc-segmented-button > fieldset > label {\n display: inline-flex;\n cursor: var(--rc-segmented-button-cursor, pointer);\n user-select: none;\n }\n\n rc-segmented-button > fieldset > label:has(input[type='radio']:disabled),\n rc-segmented-button[disabled] > fieldset > label {\n cursor: var(--rc-segmented-button-disabled-cursor, not-allowed);\n }\n\n rc-segmented-button[orientation='vertical'] > fieldset > label + label {\n border-inline-start: var(\n --rc-segmented-button-vertical-divider,\n var(--rc-segmented-button-border, revert)\n );\n }\n\n rc-segmented-button > fieldset > label > input[type='radio'] {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n margin: 0;\n opacity: 0;\n pointer-events: none;\n }\n }\n\n rc-segmented-button[orientation='vertical'] > fieldset {\n flex-direction: column;\n }\n\n rc-segmented-button > fieldset > label {\n align-items: center;\n justify-content: center;\n gap: var(--rc-segmented-button-segment-gap, 0.5em);\n min-block-size: var(--rc-segmented-button-segment-min-block-size);\n padding-block: var(--rc-segmented-button-segment-padding-block, revert);\n padding-inline: var(--rc-segmented-button-segment-padding-inline, revert);\n border: var(--rc-segmented-button-border, revert);\n color: var(--rc-segmented-button-color, revert);\n background: var(--rc-segmented-button-bg, revert);\n }\n\n rc-segmented-button > fieldset > label:first-of-type {\n border-start-start-radius: var(--rc-segmented-button-radius, revert);\n border-end-start-radius: var(--rc-segmented-button-radius, revert);\n }\n\n rc-segmented-button > fieldset > label:last-of-type {\n border-start-end-radius: var(--rc-segmented-button-radius, revert);\n border-end-end-radius: var(--rc-segmented-button-radius, revert);\n }\n\n rc-segmented-button > fieldset > label + label {\n border-inline-start: var(--rc-segmented-button-divider, revert);\n }\n\n rc-segmented-button[orientation='vertical'] > fieldset > label + label {\n border-block-start: var(--rc-segmented-button-divider, revert);\n }\n\n rc-segmented-button[orientation='vertical'] > fieldset > label:first-of-type {\n border-start-start-radius: var(--rc-segmented-button-radius, revert);\n border-start-end-radius: var(--rc-segmented-button-radius, revert);\n border-end-start-radius: 0;\n }\n\n rc-segmented-button[orientation='vertical'] > fieldset > label:last-of-type {\n border-start-end-radius: 0;\n border-end-start-radius: var(--rc-segmented-button-radius, revert);\n border-end-end-radius: var(--rc-segmented-button-radius, revert);\n }\n\n rc-segmented-button > fieldset > label:has(input[type='radio']:checked) {\n color: var(--rc-segmented-button-selected-color, revert);\n background: var(--rc-segmented-button-selected-bg, revert);\n }\n\n rc-segmented-button > fieldset > label:has(input[type='radio']:focus-visible) {\n outline: var(--rc-segmented-button-focus-ring, revert);\n outline-offset: var(--rc-segmented-button-focus-ring-offset, revert);\n }\n\n rc-segmented-button > fieldset > label:has(input[type='radio']:disabled),\n rc-segmented-button[disabled] > fieldset > label {\n opacity: var(--rc-segmented-button-disabled-opacity, revert);\n }\n\n /* Always reserved (never display:none) so a segment's width doesn't\n change between checked/unchecked — only visibility toggles. A fixed\n inline-size keeps the reserved width deterministic regardless of the\n icon's own intrinsic size. */\n rc-segmented-button > fieldset > label > [data-rc-segmented-button-selected-icon] {\n display: var(--rc-segmented-button-selected-icon-display, inline-grid);\n inline-size: var(--rc-segmented-button-selected-icon-size, 1.25em);\n place-items: center;\n visibility: hidden;\n }\n\n rc-segmented-button > fieldset > label:has(input[type='radio']:checked) > [data-rc-segmented-button-selected-icon] {\n visibility: visible;\n }\n\n /* Mirrors the selected-icon slot's reserved width on the opposite side,\n so the label text stays centered instead of leaning toward the\n always-reserved leading icon slot. Logical inline-size + scoping via\n :has() (only labels that opt into the checkmark pattern get a mirror)\n keeps this RTL-correct and backward compatible for consumers not\n using the selected-icon slot at all. */\n rc-segmented-button > fieldset > label:has([data-rc-segmented-button-selected-icon])::after {\n content: '';\n inline-size: var(--rc-segmented-button-selected-icon-size, 1.25em);\n visibility: hidden;\n }\n\n /* Forced colors replaces the palette a theme selects a segment with, so the\n selected state has to be restated in system colors here. Without it the\n only surviving indicator is the optional selected-icon slot, which a theme\n is free to switch off, and a theme that does leaves the control with no\n visible selection at all. Mirrors the rc-chip treatment. */\n @media (forced-colors: active) {\n rc-segmented-button > fieldset > label {\n border-color: ButtonBorder;\n background: ButtonFace;\n color: ButtonText;\n }\n\n rc-segmented-button > fieldset > label:has(input[type='radio']:checked) {\n border-color: Highlight;\n background: Highlight;\n color: HighlightText;\n }\n }\n}\n`;\n\n/** Detail payload for `rc-segmented-button-change`. */\nexport interface RCSegmentedButtonChangeDetail {\n /** Current selected radio value after user interaction. */\n value: string;\n}\n\n/**\n * Segmented button group that enhances a direct native `<fieldset>` of radio inputs.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-segmented-button rc-segmented-button docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/radio/ WAI-ARIA radio group pattern}\n *\n * @slot - A direct `<fieldset>` containing native radio inputs and labels.\n *\n * @fires rc-segmented-button-change - Fired after user interaction changes the selected value.\n *\n * @attr value - Current selected radio value. Host writes are silent.\n * @attr default-value - Initial selected value for uncontrolled usage.\n * @attr disabled - Mirrors disabled state to the native fieldset.\n * @attr orientation - Keyboard orientation: `horizontal` or `vertical`.\n *\n * @cssprop [--rc-segmented-button-appearance] - Set to `segmented` to turn on the component's\n * segmented recipe: a flat fieldset, a visually hidden legend, and hidden but focusable radios.\n * Unset, the fieldset, legend, and radios keep their browser appearance.\n * @cssprop [--rc-segmented-button-cursor=pointer] - Segment cursor once the segmented recipe is on.\n * @cssprop [--rc-segmented-button-disabled-cursor=not-allowed] - Segment cursor when its radio is\n * disabled, or when the host is `disabled`, once the segmented recipe is on.\n * @cssprop [--rc-segmented-button-vertical-divider] - Border on the inline-start edge of adjacent\n * segments in vertical orientation (defaults to the segment border).\n * @cssprop [--rc-segmented-button-selected-icon-display=inline-grid] - Display of the selected-icon\n * slot. Set to `none` for a theme that shows selection without a tick.\n * @cssprop [--rc-segmented-button-segment-gap=0.5em] - Gap between segment icon and label content.\n * @cssprop [--rc-segmented-button-selected-icon-size=1.25em] - Reserved inline-size of the\n * selected-icon slot (and its opposite-side mirror spacer) — constant regardless of checked\n * state, so segment width doesn't change on selection.\n * @cssprop [--rc-segmented-button-segment-min-block-size] - Minimum segment block size (defers\n * to native label layout when unset).\n * @cssprop [--rc-segmented-button-segment-padding-block=revert] - Segment block-axis padding\n * (defers to native label padding when unset).\n * @cssprop [--rc-segmented-button-segment-padding-inline=revert] - Segment inline-axis padding\n * (defers to native label padding when unset).\n * @cssprop [--rc-segmented-button-border=revert] - Segment border (defers to native label border\n * when unset).\n * @cssprop [--rc-segmented-button-color=revert] - Segment text color (defers to native label\n * color when unset).\n * @cssprop [--rc-segmented-button-bg=revert] - Segment background (defers to native label\n * background when unset).\n * @cssprop [--rc-segmented-button-radius=revert] - Rounded corner radius on the first and last\n * segments (defers to native label styling when unset).\n * @cssprop [--rc-segmented-button-divider=revert] - Border between adjacent segments (defers to\n * native label styling when unset).\n * @cssprop [--rc-segmented-button-selected-color=revert] - Segment text color when its radio is\n * checked (defers to native label color when unset).\n * @cssprop [--rc-segmented-button-selected-bg=revert] - Segment background when its radio is\n * checked (defers to native label background when unset).\n * @cssprop [--rc-segmented-button-focus-ring=revert] - Segment outline when its radio is\n * focus-visible (defers to native focus styling when unset).\n * @cssprop [--rc-segmented-button-focus-ring-offset=revert] - Segment outline offset when its\n * radio is focus-visible (defers to native focus styling when unset).\n * @cssprop [--rc-segmented-button-disabled-opacity=revert] - Segment opacity when its radio is\n * disabled, or when the host is `disabled` (defers to native disabled styling when unset).\n *\n * Under `forced-colors: active` the selected segment is restated in system colors\n * (`Highlight` / `HighlightText`), so a selected state stays visible even when a theme\n * paints selection with a background the forced palette replaces, and even when the\n * optional selected-icon slot is not used.\n */\nexport class RCSegmentedButton extends LitElement {\n static override styles = segmentedButtonStyles;\n\n private static readonly _styledRoots = new Set<Document | ShadowRoot>();\n\n private static _ensureBaseStyles(root: Document | ShadowRoot): void {\n if (RCSegmentedButton._styledRoots.has(root)) {\n return;\n }\n\n RCSegmentedButton._styledRoots.add(root);\n\n const style = document.createElement('style');\n\n style.setAttribute('data-rc-light-dom-base', 'rc-segmented-button');\n style.textContent = LIGHT_DOM_CSS;\n\n if (root instanceof Document) {\n root.head.append(style);\n } else {\n root.append(style);\n }\n }\n\n private _value = '';\n private _defaultValue = '';\n private _valueInitialized = false;\n private _$fieldset: HTMLFieldSetElement | null = null;\n private _disabledOwned = false;\n private _slotMicrotaskQueued = false;\n\n /** Current selected radio value. Host writes are silent. */\n @property({ type: String, reflect: true })\n get value(): string {\n return this._value || this._defaultValue;\n }\n\n set value(value: string | undefined) {\n const oldValue = this.value;\n\n this._value = value ?? '';\n this._valueInitialized = true;\n this._applyValue(false);\n this.requestUpdate('value', oldValue);\n }\n\n /** Initial selected value for uncontrolled usage. */\n @property({ type: String, attribute: 'default-value' })\n get defaultValue(): string {\n return this._defaultValue;\n }\n\n set defaultValue(value: string) {\n const oldValue = this._defaultValue;\n\n this._defaultValue = value;\n\n if (!this._valueInitialized && !this._value) {\n this._applyValue(false);\n this.requestUpdate('value', oldValue);\n }\n\n this.requestUpdate('defaultValue', oldValue);\n }\n\n /** Mirror disabled state to the native fieldset. */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /** Keyboard orientation. */\n @property({ type: String, reflect: true })\n orientation: 'horizontal' | 'vertical' = 'horizontal';\n\n override connectedCallback(): void {\n super.connectedCallback();\n RCSegmentedButton._ensureBaseStyles(this.getRootNode() as Document | ShadowRoot);\n }\n\n override disconnectedCallback(): void {\n this._$fieldset?.removeEventListener('change', this._handleChange);\n this._$fieldset?.removeEventListener('keydown', this._handleKeyDown);\n super.disconnectedCallback();\n }\n\n protected override firstUpdated(): void {\n this._syncFieldset();\n }\n\n protected override updated(changed: PropertyValues<this>): void {\n if (changed.has('disabled')) {\n this._applyDisabled();\n }\n }\n\n protected override render() {\n return html`<slot @slotchange=${this._handleSlotChange}></slot>`;\n }\n\n private _handleSlotChange(): void {\n if (this._slotMicrotaskQueued) {\n return;\n }\n\n this._slotMicrotaskQueued = true;\n\n queueMicrotask(() => {\n this._slotMicrotaskQueued = false;\n\n if (this.isConnected) {\n this._syncFieldset();\n }\n });\n }\n\n private _syncFieldset(): void {\n const $nextFieldset = this.querySelector<HTMLFieldSetElement>(':scope > fieldset');\n\n if (!$nextFieldset && import.meta.env.DEV) {\n console.warn(\n '[rc-segmented-button] No direct child <fieldset> found. Place native radio inputs inside a fieldset.',\n this,\n );\n }\n\n if ($nextFieldset === this._$fieldset) {\n this._syncInitialValue();\n this._applyValue(false);\n this._applyDisabled();\n\n return;\n }\n\n this._$fieldset?.removeEventListener('change', this._handleChange);\n this._$fieldset?.removeEventListener('keydown', this._handleKeyDown);\n this._$fieldset = $nextFieldset;\n this._disabledOwned = false;\n\n if ($nextFieldset) {\n $nextFieldset.addEventListener('change', this._handleChange);\n $nextFieldset.addEventListener('keydown', this._handleKeyDown);\n }\n\n this._syncInitialValue();\n this._applyValue(false);\n this._applyDisabled();\n }\n\n private _syncInitialValue(): void {\n if (this._valueInitialized || this._value) {\n return;\n }\n\n const $checked = this._$radios.find(($radio) => $radio.checked);\n\n if ($checked) {\n this._value = $checked.value;\n this._defaultValue = $checked.defaultChecked ? $checked.value : this._defaultValue;\n }\n }\n\n private get _$radios(): HTMLInputElement[] {\n if (!this._$fieldset) {\n return [];\n }\n\n return Array.from(\n this._$fieldset.querySelectorAll<HTMLInputElement>(':scope input[type=\"radio\"]'),\n );\n }\n\n private _$enabledRadios(): HTMLInputElement[] {\n return this._$radios.filter(($radio) => !$radio.disabled && !this.disabled);\n }\n\n private readonly _handleChange = (event: Event): void => {\n const $target = event.target;\n\n if (!($target instanceof HTMLInputElement) || $target.type !== 'radio' || !$target.checked) {\n return;\n }\n\n const oldValue = this.value;\n\n this._value = $target.value;\n this._valueInitialized = true;\n this.requestUpdate('value', oldValue);\n\n this.dispatchEvent(\n new CustomEvent<RCSegmentedButtonChangeDetail>('rc-segmented-button-change', {\n bubbles: true,\n composed: true,\n detail: { value: this.value },\n }),\n );\n };\n\n private readonly _handleKeyDown = (event: KeyboardEvent): void => {\n const $target = event.target;\n\n if (!($target instanceof HTMLInputElement) || $target.type !== 'radio') {\n return;\n }\n\n if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {\n return;\n }\n\n const forwardKeys =\n this.orientation === 'vertical' ? ['ArrowDown'] : ['ArrowRight', 'ArrowDown'];\n const backwardKeys = this.orientation === 'vertical' ? ['ArrowUp'] : ['ArrowLeft', 'ArrowUp'];\n const $radios = this._$enabledRadios();\n const currentIndex = $radios.indexOf($target);\n\n if (currentIndex === -1) {\n return;\n }\n\n let $next: HTMLInputElement | undefined;\n\n if (forwardKeys.includes(event.key)) {\n $next = $radios[(currentIndex + 1) % $radios.length];\n } else if (backwardKeys.includes(event.key)) {\n $next = $radios[(currentIndex - 1 + $radios.length) % $radios.length];\n } else if (event.key === 'Home') {\n $next = $radios[0];\n } else if (event.key === 'End') {\n $next = $radios[$radios.length - 1];\n }\n\n if (!$next) {\n return;\n }\n\n event.preventDefault();\n $next.focus();\n $next.checked = true;\n $next.dispatchEvent(new Event('change', { bubbles: true }));\n };\n\n private _applyValue(_writeDefault: boolean): void {\n const $radios = this._$radios;\n const value = this.value;\n\n for (const $radio of $radios) {\n $radio.checked = $radio.value === value;\n }\n }\n\n private _applyDisabled(): void {\n const $fieldset = this._$fieldset;\n\n if (!$fieldset) {\n return;\n }\n\n if (this.disabled) {\n if (!$fieldset.disabled) {\n this._disabledOwned = true;\n }\n\n $fieldset.disabled = true;\n } else if (this._disabledOwned) {\n $fieldset.disabled = false;\n this._disabledOwned = false;\n }\n }\n}\n\nexport default RCSegmentedButton;\n"],"names":["segmentedButtonStyles","css","LIGHT_DOM_CSS","_RCSegmentedButton","_a","LitElement","event","$target","oldValue","forwardKeys","backwardKeys","$radios","currentIndex","$next","root","style","value","changed","html","$nextFieldset","$checked","$radio","_writeDefault","$fieldset","__decorateClass","property","RCSegmentedButton"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { R as e } from "./rc-segmented-button-BHU4o-Pr.js";
2
+ customElements.get("rc-segmented-button") || customElements.define("rc-segmented-button", e);
3
+ //# sourceMappingURL=rc-segmented-button-define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-segmented-button-define.js","sources":["../src/define.ts"],"sourcesContent":["import RCSegmentedButton from './rc-segmented-button.js';\n\ncustomElements.get('rc-segmented-button') ||\n customElements.define('rc-segmented-button', RCSegmentedButton);\n"],"names":["RCSegmentedButton"],"mappings":";AAEA,eAAe,IAAI,qBAAqB,KACtC,eAAe,OAAO,uBAAuBA,CAAiB;"}
@@ -0,0 +1,5 @@
1
+ import { R as o } from "./rc-segmented-button-BHU4o-Pr.js";
2
+ export {
3
+ o as RCSegmentedButton
4
+ };
5
+ //# sourceMappingURL=rc-segmented-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-segmented-button.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,2 @@
1
+ export { default as RCSegmentedButton } from './rc-segmented-button.js';
2
+ export type { RCSegmentedButtonChangeDetail } from './rc-segmented-button.js';
@@ -0,0 +1,111 @@
1
+ import { LitElement, PropertyValues } from 'lit';
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ 'rc-segmented-button': RCSegmentedButton;
5
+ }
6
+ interface HTMLElementEventMap {
7
+ 'rc-segmented-button-change': CustomEvent<RCSegmentedButtonChangeDetail>;
8
+ }
9
+ }
10
+ /** Detail payload for `rc-segmented-button-change`. */
11
+ export interface RCSegmentedButtonChangeDetail {
12
+ /** Current selected radio value after user interaction. */
13
+ value: string;
14
+ }
15
+ /**
16
+ * Segmented button group that enhances a direct native `<fieldset>` of radio inputs.
17
+ *
18
+ * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-segmented-button rc-segmented-button docs}
19
+ * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/radio/ WAI-ARIA radio group pattern}
20
+ *
21
+ * @slot - A direct `<fieldset>` containing native radio inputs and labels.
22
+ *
23
+ * @fires rc-segmented-button-change - Fired after user interaction changes the selected value.
24
+ *
25
+ * @attr value - Current selected radio value. Host writes are silent.
26
+ * @attr default-value - Initial selected value for uncontrolled usage.
27
+ * @attr disabled - Mirrors disabled state to the native fieldset.
28
+ * @attr orientation - Keyboard orientation: `horizontal` or `vertical`.
29
+ *
30
+ * @cssprop [--rc-segmented-button-appearance] - Set to `segmented` to turn on the component's
31
+ * segmented recipe: a flat fieldset, a visually hidden legend, and hidden but focusable radios.
32
+ * Unset, the fieldset, legend, and radios keep their browser appearance.
33
+ * @cssprop [--rc-segmented-button-cursor=pointer] - Segment cursor once the segmented recipe is on.
34
+ * @cssprop [--rc-segmented-button-disabled-cursor=not-allowed] - Segment cursor when its radio is
35
+ * disabled, or when the host is `disabled`, once the segmented recipe is on.
36
+ * @cssprop [--rc-segmented-button-vertical-divider] - Border on the inline-start edge of adjacent
37
+ * segments in vertical orientation (defaults to the segment border).
38
+ * @cssprop [--rc-segmented-button-selected-icon-display=inline-grid] - Display of the selected-icon
39
+ * slot. Set to `none` for a theme that shows selection without a tick.
40
+ * @cssprop [--rc-segmented-button-segment-gap=0.5em] - Gap between segment icon and label content.
41
+ * @cssprop [--rc-segmented-button-selected-icon-size=1.25em] - Reserved inline-size of the
42
+ * selected-icon slot (and its opposite-side mirror spacer) — constant regardless of checked
43
+ * state, so segment width doesn't change on selection.
44
+ * @cssprop [--rc-segmented-button-segment-min-block-size] - Minimum segment block size (defers
45
+ * to native label layout when unset).
46
+ * @cssprop [--rc-segmented-button-segment-padding-block=revert] - Segment block-axis padding
47
+ * (defers to native label padding when unset).
48
+ * @cssprop [--rc-segmented-button-segment-padding-inline=revert] - Segment inline-axis padding
49
+ * (defers to native label padding when unset).
50
+ * @cssprop [--rc-segmented-button-border=revert] - Segment border (defers to native label border
51
+ * when unset).
52
+ * @cssprop [--rc-segmented-button-color=revert] - Segment text color (defers to native label
53
+ * color when unset).
54
+ * @cssprop [--rc-segmented-button-bg=revert] - Segment background (defers to native label
55
+ * background when unset).
56
+ * @cssprop [--rc-segmented-button-radius=revert] - Rounded corner radius on the first and last
57
+ * segments (defers to native label styling when unset).
58
+ * @cssprop [--rc-segmented-button-divider=revert] - Border between adjacent segments (defers to
59
+ * native label styling when unset).
60
+ * @cssprop [--rc-segmented-button-selected-color=revert] - Segment text color when its radio is
61
+ * checked (defers to native label color when unset).
62
+ * @cssprop [--rc-segmented-button-selected-bg=revert] - Segment background when its radio is
63
+ * checked (defers to native label background when unset).
64
+ * @cssprop [--rc-segmented-button-focus-ring=revert] - Segment outline when its radio is
65
+ * focus-visible (defers to native focus styling when unset).
66
+ * @cssprop [--rc-segmented-button-focus-ring-offset=revert] - Segment outline offset when its
67
+ * radio is focus-visible (defers to native focus styling when unset).
68
+ * @cssprop [--rc-segmented-button-disabled-opacity=revert] - Segment opacity when its radio is
69
+ * disabled, or when the host is `disabled` (defers to native disabled styling when unset).
70
+ *
71
+ * Under `forced-colors: active` the selected segment is restated in system colors
72
+ * (`Highlight` / `HighlightText`), so a selected state stays visible even when a theme
73
+ * paints selection with a background the forced palette replaces, and even when the
74
+ * optional selected-icon slot is not used.
75
+ */
76
+ export declare class RCSegmentedButton extends LitElement {
77
+ static styles: import('lit').CSSResult;
78
+ private static readonly _styledRoots;
79
+ private static _ensureBaseStyles;
80
+ private _value;
81
+ private _defaultValue;
82
+ private _valueInitialized;
83
+ private _$fieldset;
84
+ private _disabledOwned;
85
+ private _slotMicrotaskQueued;
86
+ /** Current selected radio value. Host writes are silent. */
87
+ get value(): string;
88
+ set value(value: string | undefined);
89
+ /** Initial selected value for uncontrolled usage. */
90
+ get defaultValue(): string;
91
+ set defaultValue(value: string);
92
+ /** Mirror disabled state to the native fieldset. */
93
+ disabled: boolean;
94
+ /** Keyboard orientation. */
95
+ orientation: 'horizontal' | 'vertical';
96
+ connectedCallback(): void;
97
+ disconnectedCallback(): void;
98
+ protected firstUpdated(): void;
99
+ protected updated(changed: PropertyValues<this>): void;
100
+ protected render(): import('lit').TemplateResult<1>;
101
+ private _handleSlotChange;
102
+ private _syncFieldset;
103
+ private _syncInitialValue;
104
+ private get _$radios();
105
+ private _$enabledRadios;
106
+ private readonly _handleChange;
107
+ private readonly _handleKeyDown;
108
+ private _applyValue;
109
+ private _applyDisabled;
110
+ }
111
+ export default RCSegmentedButton;
@@ -0,0 +1,2 @@
1
+ export declare const segmentedButtonStyles: import('lit').CSSResult;
2
+ export default segmentedButtonStyles;
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@rcarls/rc-segmented-button",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.0.0-next-20260921045401",
7
+ "description": "Segmented button group backed by native radio inputs.",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-segmented-button"
12
+ },
13
+ "homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-segmented-button",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-segmented-button.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-segmented-button/src/index.d.ts",
24
+ "default": "./dist/rc-segmented-button.js"
25
+ }
26
+ },
27
+ "./define": {
28
+ "import": {
29
+ "types": "./dist/types/packages/rc-segmented-button/src/define.d.ts",
30
+ "default": "./dist/rc-segmented-button-define.js"
31
+ }
32
+ }
33
+ },
34
+ "types": "./dist/types/packages/rc-segmented-button/src/index.d.ts",
35
+ "sideEffects": [
36
+ "./dist/rc-segmented-button-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
+ "devDependencies": {
48
+ "@custom-elements-manifest/analyzer": "0.11.0",
49
+ "@vitest/browser-playwright": "4.1.5",
50
+ "lit": "^3.0.0",
51
+ "playwright": "^1.56.0",
52
+ "rollup": "^4.60.2",
53
+ "typescript": "~5.9.3",
54
+ "vite": "^7.1.7",
55
+ "vite-plugin-dts": "^4.5.4",
56
+ "vitest": "^4.0.6",
57
+ "vitest-browser-lit": "^1.0.1"
58
+ },
59
+ "peerDependencies": {
60
+ "lit": "^3.0.0"
61
+ }
62
+ }