@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 +80 -0
- package/README.md +43 -0
- package/dist/custom-elements.json +439 -0
- package/dist/rc-segmented-button-BHU4o-Pr.js +309 -0
- package/dist/rc-segmented-button-BHU4o-Pr.js.map +1 -0
- package/dist/rc-segmented-button-define.js +3 -0
- package/dist/rc-segmented-button-define.js.map +1 -0
- package/dist/rc-segmented-button.js +5 -0
- package/dist/rc-segmented-button.js.map +1 -0
- package/dist/types/packages/rc-segmented-button/src/define.d.ts +1 -0
- package/dist/types/packages/rc-segmented-button/src/index.d.ts +2 -0
- package/dist/types/packages/rc-segmented-button/src/rc-segmented-button.d.ts +111 -0
- package/dist/types/packages/rc-segmented-button/src/rc-segmented-button.styles.d.ts +2 -0
- package/package.json +62 -0
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"rc-segmented-button.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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;
|
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
|
+
}
|