@rcarls/rc-segmented-button 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,113 @@
1
1
  # @rcarls/rc-segmented-button
2
2
 
3
+ ## 0.7.0
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
+ - 5c006de: Ship component CSS minified. Package builds now run the CSS in Lit `css` styles
35
+ and light-DOM structural styles through Lightning CSS, removing comments and
36
+ whitespace while keeping source maps that resolve to each template. Rendered
37
+ styles are unchanged.
38
+
39
+ `@rcarls/rc-common` adds `cssText`, an identity tag that marks a plain CSS
40
+ string for minification. `rc-chip`, `rc-disclosure`, and `rc-accordion` now
41
+ depend on `@rcarls/rc-common` for it.
42
+
43
+ - 51f6403: Arrow keys follow the reading direction everywhere. `keyNavigation` now also
44
+ flips the cross axis in RTL, so a vertical menu opens its submenu with
45
+ ArrowLeft, as the APG describes. A horizontal menubar and segmented button
46
+ advance with ArrowLeft in RTL, Alt+ArrowLeft moves items toward the selected
47
+ list of a transfer list in RTL, and the arrow toward the inline start reaches
48
+ the chips of a multiple select or combobox (ArrowRight in RTL).
49
+
50
+ Add `inlineArrowKeys(flow)` for content that runs along the inline axis, such
51
+ as chips or text.
52
+
53
+ - 11680b6: Expose and navigate the orientation a widget actually renders in. An
54
+ `orientation` attribute names a layout: `horizontal` runs along the inline
55
+ axis and `vertical` along the block axis, so both turn over in vertical
56
+ writing modes, the way a native `<input type="range">` does. Toolbar,
57
+ menubar, and adaptive menu now report the rendered orientation as
58
+ `aria-orientation`, and their arrow keys follow it, so a horizontal toolbar
59
+ in vertical text navigates with ArrowDown.
60
+
61
+ `keyNavigation` treats an explicit `navigationAxis` and role defaults as
62
+ layout orientations and converts them the same way; an explicit
63
+ `aria-orientation` attribute is still used as is. Add `renderedOrientation`
64
+ and a `FlowController` that keeps a host's writing mode and direction current
65
+ for rendering.
66
+
67
+ `rc-toolbar` now reflects `orientation` to its host attribute, and its
68
+ vertical layout is keyed off that attribute rather than `aria-orientation`.
69
+
70
+ - fb54f77: Keep the selected segment visible in forced-colors mode.
71
+
72
+ Forced colors replaces the background a theme selects a segment with, which left
73
+ selection legible only through the optional selected-icon slot. The base styles
74
+ now restate the selected segment in `Highlight` and `HighlightText`, matching the
75
+ `rc-chip` treatment, so selection survives in every theme whether or not the icon
76
+ slot is used.
77
+
78
+ - Updated dependencies [e3dfa59]
79
+ - Updated dependencies [50d5610]
80
+ - Updated dependencies [9ee769c]
81
+ - Updated dependencies [1179f24]
82
+ - Updated dependencies [595fe37]
83
+ - Updated dependencies [38cd00c]
84
+ - Updated dependencies [5c006de]
85
+ - Updated dependencies [a6553ff]
86
+ - Updated dependencies [2205478]
87
+ - Updated dependencies [4341311]
88
+ - Updated dependencies [899bb5e]
89
+ - Updated dependencies [25fcfe0]
90
+ - Updated dependencies [51f6403]
91
+ - Updated dependencies [9caca6b]
92
+ - Updated dependencies [b764a3c]
93
+ - Updated dependencies [54d9ead]
94
+ - Updated dependencies [b9175e9]
95
+ - Updated dependencies [c8b762b]
96
+ - Updated dependencies [55e1e7f]
97
+ - Updated dependencies [f1ca82d]
98
+ - Updated dependencies [b87f27b]
99
+ - Updated dependencies [32ebead]
100
+ - Updated dependencies [bfefedd]
101
+ - Updated dependencies [c130173]
102
+ - Updated dependencies [11680b6]
103
+ - Updated dependencies [223d36c]
104
+ - Updated dependencies [7b91a44]
105
+ - Updated dependencies [aa04ead]
106
+ - Updated dependencies [ac65b5b]
107
+ - Updated dependencies [afcfef8]
108
+ - Updated dependencies [11ef1ad]
109
+ - @rcarls/rc-common@0.7.0
110
+
3
111
  ## 0.6.0
4
112
 
5
113
  ## 0.5.0
package/README.md CHANGED
@@ -14,3 +14,30 @@ Segmented button group backed by native radio inputs.
14
14
  ```
15
15
 
16
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.
@@ -49,6 +49,29 @@
49
49
  "description": "Segmented button group that enhances a direct native `<fieldset>` of radio inputs.",
50
50
  "name": "RCSegmentedButton",
51
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
+ },
52
75
  {
53
76
  "description": "Gap between segment icon and label content.",
54
77
  "name": "--rc-segmented-button-segment-gap",
@@ -119,7 +142,7 @@
119
142
  "default": "revert"
120
143
  },
121
144
  {
122
- "description": "Segment opacity when its radio is disabled, or when the host is `disabled` (defers to native disabled styling when unset).",
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.",
123
146
  "name": "--rc-segmented-button-disabled-opacity",
124
147
  "default": "revert"
125
148
  }
@@ -0,0 +1,126 @@
1
+ import { css as f, LitElement as g, html as p } from "lit";
2
+ import { property as u } from "lit/decorators.js";
3
+ import { resolveFlow as v, arrowKeys as m, renderedOrientation as y, cssText as _ } from "@rcarls/rc-common";
4
+ const k = f`:host{color-scheme:inherit;display:inline-block}:host([hidden]){display:none}`;
5
+ var w = Object.defineProperty, $ = Object.getOwnPropertyDescriptor, c = (h, e, t, i) => {
6
+ for (var s = i > 1 ? void 0 : i ? $(e, t) : e, l = h.length - 1, d; l >= 0; l--)
7
+ (d = h[l]) && (s = (i ? d(e, t, s) : d(s)) || s);
8
+ return i && s && w(e, t, s), s;
9
+ };
10
+ const V = _`@layer rc-base{@container style(--rc-segmented-button-appearance:segmented){rc-segmented-button>fieldset{border:0;border-radius:0;margin:0;padding:0;display:inline-flex;overflow:visible}rc-segmented-button>fieldset>legend{clip:rect(0 0 0 0);white-space:nowrap;border:0;block-size:1px;inline-size:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}rc-segmented-button>fieldset>label{cursor:var(--rc-segmented-button-cursor,pointer);user-select:none;display:inline-flex}rc-segmented-button>fieldset>label:has(input[type=radio]:disabled),rc-segmented-button[disabled]>fieldset>label{cursor:var(--rc-segmented-button-disabled-cursor,not-allowed)}rc-segmented-button[orientation=vertical]>fieldset>label+label{border-inline-start:var(--rc-segmented-button-vertical-divider,var(--rc-segmented-button-border,revert))}rc-segmented-button>fieldset>label>input[type=radio]{opacity:0;pointer-events:none;block-size:1px;inline-size:1px;margin:0;position:absolute}}rc-segmented-button[orientation=vertical]>fieldset{flex-direction:column}rc-segmented-button>fieldset>label{justify-content:center;align-items:center;gap:var(--rc-segmented-button-segment-gap,.5em);min-block-size:var(--rc-segmented-button-segment-min-block-size);padding-block:var(--rc-segmented-button-segment-padding-block,revert);padding-inline:var(--rc-segmented-button-segment-padding-inline,revert);border:var(--rc-segmented-button-border,revert);color:var(--rc-segmented-button-color,revert);background:var(--rc-segmented-button-bg,revert)}rc-segmented-button>fieldset>label:first-of-type{border-start-start-radius:var(--rc-segmented-button-radius,revert);border-end-start-radius:var(--rc-segmented-button-radius,revert)}rc-segmented-button>fieldset>label:last-of-type{border-start-end-radius:var(--rc-segmented-button-radius,revert);border-end-end-radius:var(--rc-segmented-button-radius,revert)}rc-segmented-button>fieldset>label+label{border-inline-start:var(--rc-segmented-button-divider,revert)}rc-segmented-button[orientation=vertical]>fieldset>label+label{border-block-start:var(--rc-segmented-button-divider,revert)}rc-segmented-button[orientation=vertical]>fieldset>label:first-of-type{border-start-start-radius:var(--rc-segmented-button-radius,revert);border-start-end-radius:var(--rc-segmented-button-radius,revert);border-end-start-radius:0}rc-segmented-button[orientation=vertical]>fieldset>label:last-of-type{border-start-end-radius:0;border-end-end-radius:var(--rc-segmented-button-radius,revert);border-end-start-radius:var(--rc-segmented-button-radius,revert)}rc-segmented-button>fieldset>label:has(input[type=radio]:checked){color:var(--rc-segmented-button-selected-color,revert);background:var(--rc-segmented-button-selected-bg,revert)}rc-segmented-button>fieldset>label:has(input[type=radio]:focus-visible){outline:var(--rc-segmented-button-focus-ring,revert);outline-offset:var(--rc-segmented-button-focus-ring-offset,revert)}rc-segmented-button>fieldset>label:has(input[type=radio]:disabled),rc-segmented-button[disabled]>fieldset>label{opacity:var(--rc-segmented-button-disabled-opacity,revert)}rc-segmented-button>fieldset>label>[data-rc-segmented-button-selected-icon]{display:var(--rc-segmented-button-selected-icon-display,inline-grid);inline-size:var(--rc-segmented-button-selected-icon-size,1.25em);visibility:hidden;place-items:center}rc-segmented-button>fieldset>label:has(input[type=radio]:checked)>[data-rc-segmented-button-selected-icon]{visibility:visible}rc-segmented-button>fieldset>label:has([data-rc-segmented-button-selected-icon]):after{content:"";inline-size:var(--rc-segmented-button-selected-icon-size,1.25em);visibility:hidden}@media (forced-colors:active){rc-segmented-button>fieldset>label{color:buttontext;background:buttonface;border-color:buttonborder}rc-segmented-button>fieldset>label:has(input[type=radio]:checked){color:highlighttext;background:highlight;border-color:highlight}}}`;
11
+ var r;
12
+ const o = (r = class extends g {
13
+ constructor() {
14
+ 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) => {
15
+ const t = e.target;
16
+ if (!(t instanceof HTMLInputElement) || t.type !== "radio" || !t.checked)
17
+ return;
18
+ const i = this.value;
19
+ this._value = t.value, this._valueInitialized = !0, this.requestUpdate("value", i), this.dispatchEvent(
20
+ new CustomEvent("rc-segmented-button-change", {
21
+ bubbles: !0,
22
+ composed: !0,
23
+ detail: { value: this.value }
24
+ })
25
+ );
26
+ }, this._handleKeyDown = (e) => {
27
+ const t = e.target;
28
+ if (!(t instanceof HTMLInputElement) || t.type !== "radio" || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
29
+ return;
30
+ const i = v(this), s = m(y(this.orientation, i), i), l = this.orientation === "vertical" ? [s.next] : [s.next, s.openFirst], d = this.orientation === "vertical" ? [s.prev] : [s.prev, s.openLast], n = this._$enabledRadios(), b = n.indexOf(t);
31
+ if (b === -1)
32
+ return;
33
+ let a;
34
+ l.includes(e.key) ? a = n[(b + 1) % n.length] : d.includes(e.key) ? a = n[(b - 1 + n.length) % n.length] : e.key === "Home" ? a = n[0] : e.key === "End" && (a = n[n.length - 1]), a && (e.preventDefault(), a.focus(), a.checked = !0, a.dispatchEvent(new Event("change", { bubbles: !0 })));
35
+ };
36
+ }
37
+ static _ensureBaseStyles(e) {
38
+ if (r._styledRoots.has(e))
39
+ return;
40
+ r._styledRoots.add(e);
41
+ const t = document.createElement("style");
42
+ t.setAttribute("data-rc-light-dom-base", "rc-segmented-button"), t.textContent = V, e instanceof Document ? e.head.append(t) : e.append(t);
43
+ }
44
+ get value() {
45
+ return this._value || this._defaultValue;
46
+ }
47
+ set value(e) {
48
+ const t = this.value;
49
+ this._value = e ?? "", this._valueInitialized = !0, this._applyValue(!1), this.requestUpdate("value", t);
50
+ }
51
+ get defaultValue() {
52
+ return this._defaultValue;
53
+ }
54
+ set defaultValue(e) {
55
+ const t = this._defaultValue;
56
+ this._defaultValue = e, !this._valueInitialized && !this._value && (this._applyValue(!1), this.requestUpdate("value", t)), this.requestUpdate("defaultValue", t);
57
+ }
58
+ connectedCallback() {
59
+ super.connectedCallback(), r._ensureBaseStyles(this.getRootNode());
60
+ }
61
+ disconnectedCallback() {
62
+ this._$fieldset?.removeEventListener("change", this._handleChange), this._$fieldset?.removeEventListener("keydown", this._handleKeyDown), super.disconnectedCallback();
63
+ }
64
+ firstUpdated() {
65
+ this._syncFieldset();
66
+ }
67
+ updated(e) {
68
+ e.has("disabled") && this._applyDisabled();
69
+ }
70
+ render() {
71
+ return p`<slot @slotchange=${this._handleSlotChange}></slot>`;
72
+ }
73
+ _handleSlotChange() {
74
+ this._slotMicrotaskQueued || (this._slotMicrotaskQueued = !0, queueMicrotask(() => {
75
+ this._slotMicrotaskQueued = !1, this.isConnected && this._syncFieldset();
76
+ }));
77
+ }
78
+ _syncFieldset() {
79
+ const e = this.querySelector(":scope > fieldset");
80
+ if (e === this._$fieldset) {
81
+ this._syncInitialValue(), this._applyValue(!1), this._applyDisabled();
82
+ return;
83
+ }
84
+ 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();
85
+ }
86
+ _syncInitialValue() {
87
+ if (this._valueInitialized || this._value)
88
+ return;
89
+ const e = this._$radios.find((t) => t.checked);
90
+ e && (this._value = e.value, this._defaultValue = e.defaultChecked ? e.value : this._defaultValue);
91
+ }
92
+ get _$radios() {
93
+ return this._$fieldset ? Array.from(
94
+ this._$fieldset.querySelectorAll(':scope input[type="radio"]')
95
+ ) : [];
96
+ }
97
+ _$enabledRadios() {
98
+ return this._$radios.filter((e) => !e.disabled && !this.disabled);
99
+ }
100
+ _applyValue(e) {
101
+ const t = this._$radios, i = this.value;
102
+ for (const s of t)
103
+ s.checked = s.value === i;
104
+ }
105
+ _applyDisabled() {
106
+ const e = this._$fieldset;
107
+ e && (this.disabled ? (e.disabled || (this._disabledOwned = !0), e.disabled = !0) : this._disabledOwned && (e.disabled = !1, this._disabledOwned = !1));
108
+ }
109
+ }, r.styles = k, r._styledRoots = /* @__PURE__ */ new Set(), r);
110
+ c([
111
+ u({ type: String, reflect: !0 })
112
+ ], o.prototype, "value", 1);
113
+ c([
114
+ u({ type: String, attribute: "default-value" })
115
+ ], o.prototype, "defaultValue", 1);
116
+ c([
117
+ u({ type: Boolean, reflect: !0 })
118
+ ], o.prototype, "disabled", 2);
119
+ c([
120
+ u({ type: String, reflect: !0 })
121
+ ], o.prototype, "orientation", 2);
122
+ let E = o;
123
+ export {
124
+ E as R
125
+ };
126
+ //# sourceMappingURL=rc-segmented-button-BitI555k.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-segmented-button-BitI555k.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 { arrowKeys, cssText, renderedOrientation, resolveFlow } from '@rcarls/rc-common';\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 = cssText`\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 - Layout along the inline (`horizontal`) or block (`vertical`) axis. Arrow\n * keys follow the orientation actually rendered, which turns over in vertical writing modes.\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 // Keys follow the orientation actually rendered, which turns over in\n // vertical text; horizontal keys flip in RTL. A horizontal layout also\n // accepts the cross-axis keys, as a native radio group does.\n const flow = resolveFlow(this);\n const keys = arrowKeys(renderedOrientation(this.orientation, flow), flow);\n const forwardKeys: string[] =\n this.orientation === 'vertical' ? [keys.next] : [keys.next, keys.openFirst];\n const backwardKeys: string[] =\n this.orientation === 'vertical' ? [keys.prev] : [keys.prev, keys.openLast];\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","cssText","_RCSegmentedButton","_a","LitElement","event","$target","oldValue","flow","resolveFlow","keys","arrowKeys","renderedOrientation","forwardKeys","backwardKeys","$radios","currentIndex","$next","root","style","value","changed","html","$nextFieldset","$checked","$radio","_writeDefault","$fieldset","__decorateClass","property","RCSegmentedButton"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { R as e } from "./rc-segmented-button-COV7INIU.js";
1
+ import { R as e } from "./rc-segmented-button-BitI555k.js";
2
2
  customElements.get("rc-segmented-button") || customElements.define("rc-segmented-button", e);
3
3
  //# sourceMappingURL=rc-segmented-button-define.js.map
@@ -1,4 +1,4 @@
1
- import { R as o } from "./rc-segmented-button-COV7INIU.js";
1
+ import { R as o } from "./rc-segmented-button-BitI555k.js";
2
2
  export {
3
3
  o as RCSegmentedButton
4
4
  };
@@ -25,8 +25,19 @@ export interface RCSegmentedButtonChangeDetail {
25
25
  * @attr value - Current selected radio value. Host writes are silent.
26
26
  * @attr default-value - Initial selected value for uncontrolled usage.
27
27
  * @attr disabled - Mirrors disabled state to the native fieldset.
28
- * @attr orientation - Keyboard orientation: `horizontal` or `vertical`.
28
+ * @attr orientation - Layout along the inline (`horizontal`) or block (`vertical`) axis. Arrow
29
+ * keys follow the orientation actually rendered, which turns over in vertical writing modes.
29
30
  *
31
+ * @cssprop [--rc-segmented-button-appearance] - Set to `segmented` to turn on the component's
32
+ * segmented recipe: a flat fieldset, a visually hidden legend, and hidden but focusable radios.
33
+ * Unset, the fieldset, legend, and radios keep their browser appearance.
34
+ * @cssprop [--rc-segmented-button-cursor=pointer] - Segment cursor once the segmented recipe is on.
35
+ * @cssprop [--rc-segmented-button-disabled-cursor=not-allowed] - Segment cursor when its radio is
36
+ * disabled, or when the host is `disabled`, once the segmented recipe is on.
37
+ * @cssprop [--rc-segmented-button-vertical-divider] - Border on the inline-start edge of adjacent
38
+ * segments in vertical orientation (defaults to the segment border).
39
+ * @cssprop [--rc-segmented-button-selected-icon-display=inline-grid] - Display of the selected-icon
40
+ * slot. Set to `none` for a theme that shows selection without a tick.
30
41
  * @cssprop [--rc-segmented-button-segment-gap=0.5em] - Gap between segment icon and label content.
31
42
  * @cssprop [--rc-segmented-button-selected-icon-size=1.25em] - Reserved inline-size of the
32
43
  * selected-icon slot (and its opposite-side mirror spacer) — constant regardless of checked
@@ -57,6 +68,11 @@ export interface RCSegmentedButtonChangeDetail {
57
68
  * radio is focus-visible (defers to native focus styling when unset).
58
69
  * @cssprop [--rc-segmented-button-disabled-opacity=revert] - Segment opacity when its radio is
59
70
  * disabled, or when the host is `disabled` (defers to native disabled styling when unset).
71
+ *
72
+ * Under `forced-colors: active` the selected segment is restated in system colors
73
+ * (`Highlight` / `HighlightText`), so a selected state stays visible even when a theme
74
+ * paints selection with a background the forced palette replaces, and even when the
75
+ * optional selected-icon slot is not used.
60
76
  */
61
77
  export declare class RCSegmentedButton extends LitElement {
62
78
  static styles: import('lit').CSSResult;
@@ -82,7 +98,7 @@ export declare class RCSegmentedButton extends LitElement {
82
98
  disconnectedCallback(): void;
83
99
  protected firstUpdated(): void;
84
100
  protected updated(changed: PropertyValues<this>): void;
85
- protected render(): import('lit').TemplateResult<1>;
101
+ protected render(): import('lit-html').TemplateResult<1>;
86
102
  private _handleSlotChange;
87
103
  private _syncFieldset;
88
104
  private _syncInitialValue;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.6.0",
6
+ "version": "0.7.0",
7
7
  "description": "Segmented button group backed by native radio inputs.",
8
8
  "repository": {
9
9
  "type": "git",
@@ -44,6 +44,9 @@
44
44
  "test:browser:chrome": "vitest --run --project=chromium",
45
45
  "test:browser:firefox": "vitest --run --project=firefox"
46
46
  },
47
+ "dependencies": {
48
+ "@rcarls/rc-common": "0.7.0"
49
+ },
47
50
  "devDependencies": {
48
51
  "@custom-elements-manifest/analyzer": "0.11.0",
49
52
  "@vitest/browser-playwright": "4.1.5",
@@ -1,259 +0,0 @@
1
- import { css as b, LitElement as v, html as h } from "lit";
2
- import { property as o } from "lit/decorators.js";
3
- const g = b`
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, m = Object.getOwnPropertyDescriptor, c = (u, e, t, i) => {
14
- for (var s = i > 1 ? void 0 : i ? m(e, t) : e, r = u.length - 1, d; r >= 0; r--)
15
- (d = u[r]) && (s = (i ? d(e, t, s) : d(s)) || s);
16
- return i && s && f(e, t, s), s;
17
- };
18
- const p = `
19
- @layer rc-base {
20
- rc-segmented-button > fieldset {
21
- display: var(--_rc-segmented-button-fieldset-display, revert);
22
- margin: var(--_rc-segmented-button-fieldset-margin, revert);
23
- padding: var(--_rc-segmented-button-fieldset-padding, revert);
24
- border: var(--_rc-segmented-button-fieldset-border, revert);
25
- border-radius: var(--_rc-segmented-button-fieldset-radius, revert);
26
- overflow: var(--_rc-segmented-button-fieldset-overflow, revert);
27
- }
28
-
29
- rc-segmented-button[orientation='vertical'] > fieldset {
30
- flex-direction: column;
31
- }
32
-
33
- rc-segmented-button > fieldset > legend {
34
- position: var(--_rc-segmented-button-legend-position, revert);
35
- inline-size: var(--_rc-segmented-button-legend-inline-size, revert);
36
- block-size: var(--_rc-segmented-button-legend-block-size, revert);
37
- margin: var(--_rc-segmented-button-legend-margin, revert);
38
- padding: var(--_rc-segmented-button-legend-padding, revert);
39
- border: var(--_rc-segmented-button-legend-border, revert);
40
- overflow: var(--_rc-segmented-button-legend-overflow, revert);
41
- clip: var(--_rc-segmented-button-legend-clip, revert);
42
- white-space: var(--_rc-segmented-button-legend-white-space, revert);
43
- }
44
-
45
- rc-segmented-button > fieldset > label {
46
- display: var(--_rc-segmented-button-segment-display, revert);
47
- align-items: center;
48
- justify-content: center;
49
- gap: var(--rc-segmented-button-segment-gap, 0.5em);
50
- min-block-size: var(--rc-segmented-button-segment-min-block-size);
51
- padding-block: var(--rc-segmented-button-segment-padding-block, revert);
52
- padding-inline: var(--rc-segmented-button-segment-padding-inline, revert);
53
- border: var(--rc-segmented-button-border, revert);
54
- color: var(--rc-segmented-button-color, revert);
55
- background: var(--rc-segmented-button-bg, revert);
56
- cursor: var(--_rc-segmented-button-segment-cursor, revert);
57
- user-select: var(--_rc-segmented-button-segment-user-select, revert);
58
- }
59
-
60
- rc-segmented-button > fieldset > label:first-of-type {
61
- border-start-start-radius: var(--rc-segmented-button-radius, revert);
62
- border-end-start-radius: var(--rc-segmented-button-radius, revert);
63
- }
64
-
65
- rc-segmented-button > fieldset > label:last-of-type {
66
- border-start-end-radius: var(--rc-segmented-button-radius, revert);
67
- border-end-end-radius: var(--rc-segmented-button-radius, revert);
68
- }
69
-
70
- rc-segmented-button > fieldset > label + label {
71
- border-inline-start: var(--rc-segmented-button-divider, revert);
72
- }
73
-
74
- rc-segmented-button[orientation='vertical'] > fieldset > label + label {
75
- border-inline-start: var(--_rc-segmented-button-vertical-border-inline-start, revert);
76
- border-block-start: var(--rc-segmented-button-divider, revert);
77
- }
78
-
79
- rc-segmented-button[orientation='vertical'] > fieldset > label:first-of-type {
80
- border-start-start-radius: var(--rc-segmented-button-radius, revert);
81
- border-start-end-radius: var(--rc-segmented-button-radius, revert);
82
- border-end-start-radius: 0;
83
- }
84
-
85
- rc-segmented-button[orientation='vertical'] > fieldset > label:last-of-type {
86
- border-start-end-radius: 0;
87
- border-end-start-radius: var(--rc-segmented-button-radius, revert);
88
- border-end-end-radius: var(--rc-segmented-button-radius, revert);
89
- }
90
-
91
- rc-segmented-button > fieldset > label:has(input[type='radio']:checked) {
92
- color: var(--rc-segmented-button-selected-color, revert);
93
- background: var(--rc-segmented-button-selected-bg, revert);
94
- }
95
-
96
- rc-segmented-button > fieldset > label:has(input[type='radio']:focus-visible) {
97
- outline: var(--rc-segmented-button-focus-ring, revert);
98
- outline-offset: var(--rc-segmented-button-focus-ring-offset, revert);
99
- }
100
-
101
- rc-segmented-button > fieldset > label:has(input[type='radio']:disabled),
102
- rc-segmented-button[disabled] > fieldset > label {
103
- opacity: var(--rc-segmented-button-disabled-opacity, revert);
104
- cursor: var(--_rc-segmented-button-disabled-cursor, revert);
105
- }
106
-
107
- rc-segmented-button > fieldset > label > input[type='radio'] {
108
- position: var(--_rc-segmented-button-radio-position, revert);
109
- inline-size: var(--_rc-segmented-button-radio-inline-size, revert);
110
- block-size: var(--_rc-segmented-button-radio-block-size, revert);
111
- margin: var(--_rc-segmented-button-radio-margin, revert);
112
- opacity: var(--_rc-segmented-button-radio-opacity, revert);
113
- pointer-events: var(--_rc-segmented-button-radio-pointer-events, revert);
114
- }
115
-
116
- /* Always reserved (never display:none) so a segment's width doesn't
117
- change between checked/unchecked — only visibility toggles. A fixed
118
- inline-size keeps the reserved width deterministic regardless of the
119
- icon's own intrinsic size. */
120
- rc-segmented-button > fieldset > label > [data-rc-segmented-button-selected-icon] {
121
- display: var(--_rc-segmented-button-selected-icon-display, inline-grid);
122
- inline-size: var(--rc-segmented-button-selected-icon-size, 1.25em);
123
- place-items: center;
124
- visibility: hidden;
125
- }
126
-
127
- rc-segmented-button > fieldset > label:has(input[type='radio']:checked) > [data-rc-segmented-button-selected-icon] {
128
- visibility: visible;
129
- }
130
-
131
- /* Mirrors the selected-icon slot's reserved width on the opposite side,
132
- so the label text stays centered instead of leaning toward the
133
- always-reserved leading icon slot. Logical inline-size + scoping via
134
- :has() (only labels that opt into the checkmark pattern get a mirror)
135
- keeps this RTL-correct and backward compatible for consumers not
136
- using the selected-icon slot at all. */
137
- rc-segmented-button > fieldset > label:has([data-rc-segmented-button-selected-icon])::after {
138
- content: '';
139
- inline-size: var(--rc-segmented-button-selected-icon-size, 1.25em);
140
- visibility: hidden;
141
- }
142
- }
143
- `;
144
- var n;
145
- const l = (n = class extends v {
146
- constructor() {
147
- 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) => {
148
- const t = e.target;
149
- if (!(t instanceof HTMLInputElement) || t.type !== "radio" || !t.checked)
150
- return;
151
- const i = this.value;
152
- this._value = t.value, this._valueInitialized = !0, this.requestUpdate("value", i), this.dispatchEvent(
153
- new CustomEvent("rc-segmented-button-change", {
154
- bubbles: !0,
155
- composed: !0,
156
- detail: { value: this.value }
157
- })
158
- );
159
- }, this._handleKeyDown = (e) => {
160
- const t = e.target;
161
- if (!(t instanceof HTMLInputElement) || t.type !== "radio" || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
162
- return;
163
- const i = this.orientation === "vertical" ? ["ArrowDown"] : ["ArrowRight", "ArrowDown"], s = this.orientation === "vertical" ? ["ArrowUp"] : ["ArrowLeft", "ArrowUp"], r = this._$enabledRadios(), d = r.indexOf(t);
164
- if (d === -1)
165
- return;
166
- let a;
167
- i.includes(e.key) ? a = r[(d + 1) % r.length] : s.includes(e.key) ? a = r[(d - 1 + r.length) % r.length] : e.key === "Home" ? a = r[0] : e.key === "End" && (a = r[r.length - 1]), a && (e.preventDefault(), a.focus(), a.checked = !0, a.dispatchEvent(new Event("change", { bubbles: !0 })));
168
- };
169
- }
170
- static _ensureBaseStyles(e) {
171
- if (n._styledRoots.has(e))
172
- return;
173
- n._styledRoots.add(e);
174
- const t = document.createElement("style");
175
- t.setAttribute("data-rc-light-dom-base", "rc-segmented-button"), t.textContent = p, e instanceof Document ? e.head.append(t) : e.append(t);
176
- }
177
- get value() {
178
- return this._value || this._defaultValue;
179
- }
180
- set value(e) {
181
- const t = this.value;
182
- this._value = e ?? "", this._valueInitialized = !0, this._applyValue(!1), this.requestUpdate("value", t);
183
- }
184
- get defaultValue() {
185
- return this._defaultValue;
186
- }
187
- set defaultValue(e) {
188
- const t = this._defaultValue;
189
- this._defaultValue = e, !this._valueInitialized && !this._value && (this._applyValue(!1), this.requestUpdate("value", t)), this.requestUpdate("defaultValue", t);
190
- }
191
- connectedCallback() {
192
- super.connectedCallback(), n._ensureBaseStyles(this.getRootNode());
193
- }
194
- disconnectedCallback() {
195
- this._$fieldset?.removeEventListener("change", this._handleChange), this._$fieldset?.removeEventListener("keydown", this._handleKeyDown), super.disconnectedCallback();
196
- }
197
- firstUpdated() {
198
- this._syncFieldset();
199
- }
200
- updated(e) {
201
- e.has("disabled") && this._applyDisabled();
202
- }
203
- render() {
204
- return h`<slot @slotchange=${this._handleSlotChange}></slot>`;
205
- }
206
- _handleSlotChange() {
207
- this._slotMicrotaskQueued || (this._slotMicrotaskQueued = !0, queueMicrotask(() => {
208
- this._slotMicrotaskQueued = !1, this.isConnected && this._syncFieldset();
209
- }));
210
- }
211
- _syncFieldset() {
212
- const e = this.querySelector(":scope > fieldset");
213
- if (e === this._$fieldset) {
214
- this._syncInitialValue(), this._applyValue(!1), this._applyDisabled();
215
- return;
216
- }
217
- 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();
218
- }
219
- _syncInitialValue() {
220
- if (this._valueInitialized || this._value)
221
- return;
222
- const e = this._$radios.find((t) => t.checked);
223
- e && (this._value = e.value, this._defaultValue = e.defaultChecked ? e.value : this._defaultValue);
224
- }
225
- get _$radios() {
226
- return this._$fieldset ? Array.from(
227
- this._$fieldset.querySelectorAll(':scope input[type="radio"]')
228
- ) : [];
229
- }
230
- _$enabledRadios() {
231
- return this._$radios.filter((e) => !e.disabled && !this.disabled);
232
- }
233
- _applyValue(e) {
234
- const t = this._$radios, i = this.value;
235
- for (const s of t)
236
- s.checked = s.value === i;
237
- }
238
- _applyDisabled() {
239
- const e = this._$fieldset;
240
- e && (this.disabled ? (e.disabled || (this._disabledOwned = !0), e.disabled = !0) : this._disabledOwned && (e.disabled = !1, this._disabledOwned = !1));
241
- }
242
- }, n.styles = g, n._styledRoots = /* @__PURE__ */ new Set(), n);
243
- c([
244
- o({ type: String, reflect: !0 })
245
- ], l.prototype, "value", 1);
246
- c([
247
- o({ type: String, attribute: "default-value" })
248
- ], l.prototype, "defaultValue", 1);
249
- c([
250
- o({ type: Boolean, reflect: !0 })
251
- ], l.prototype, "disabled", 2);
252
- c([
253
- o({ type: String, reflect: !0 })
254
- ], l.prototype, "orientation", 2);
255
- let w = l;
256
- export {
257
- w as R
258
- };
259
- //# sourceMappingURL=rc-segmented-button-COV7INIU.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rc-segmented-button-COV7INIU.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 rc-segmented-button > fieldset {\n display: var(--_rc-segmented-button-fieldset-display, revert);\n margin: var(--_rc-segmented-button-fieldset-margin, revert);\n padding: var(--_rc-segmented-button-fieldset-padding, revert);\n border: var(--_rc-segmented-button-fieldset-border, revert);\n border-radius: var(--_rc-segmented-button-fieldset-radius, revert);\n overflow: var(--_rc-segmented-button-fieldset-overflow, revert);\n }\n\n rc-segmented-button[orientation='vertical'] > fieldset {\n flex-direction: column;\n }\n\n rc-segmented-button > fieldset > legend {\n position: var(--_rc-segmented-button-legend-position, revert);\n inline-size: var(--_rc-segmented-button-legend-inline-size, revert);\n block-size: var(--_rc-segmented-button-legend-block-size, revert);\n margin: var(--_rc-segmented-button-legend-margin, revert);\n padding: var(--_rc-segmented-button-legend-padding, revert);\n border: var(--_rc-segmented-button-legend-border, revert);\n overflow: var(--_rc-segmented-button-legend-overflow, revert);\n clip: var(--_rc-segmented-button-legend-clip, revert);\n white-space: var(--_rc-segmented-button-legend-white-space, revert);\n }\n\n rc-segmented-button > fieldset > label {\n display: var(--_rc-segmented-button-segment-display, revert);\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 cursor: var(--_rc-segmented-button-segment-cursor, revert);\n user-select: var(--_rc-segmented-button-segment-user-select, 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-inline-start: var(--_rc-segmented-button-vertical-border-inline-start, revert);\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 cursor: var(--_rc-segmented-button-disabled-cursor, revert);\n }\n\n rc-segmented-button > fieldset > label > input[type='radio'] {\n position: var(--_rc-segmented-button-radio-position, revert);\n inline-size: var(--_rc-segmented-button-radio-inline-size, revert);\n block-size: var(--_rc-segmented-button-radio-block-size, revert);\n margin: var(--_rc-segmented-button-radio-margin, revert);\n opacity: var(--_rc-segmented-button-radio-opacity, revert);\n pointer-events: var(--_rc-segmented-button-radio-pointer-events, 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`;\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-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 */\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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"}