@sondor-technology/sondor-components 1.0.7 → 1.0.9

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.
Files changed (53) hide show
  1. package/dist/components/calendar/calendar.d.ts +36 -0
  2. package/dist/components/calendar/calendar.d.ts.map +1 -0
  3. package/dist/components/calendar/calendar.js +449 -0
  4. package/dist/components/calendar/calendar.js.map +1 -0
  5. package/dist/components/cron-builder/cron-builder.d.ts +37 -0
  6. package/dist/components/cron-builder/cron-builder.d.ts.map +1 -0
  7. package/dist/components/cron-builder/cron-builder.js +522 -0
  8. package/dist/components/cron-builder/cron-builder.js.map +1 -0
  9. package/dist/components/input/input.d.ts +1 -0
  10. package/dist/components/input/input.d.ts.map +1 -1
  11. package/dist/components/input/input.js +35 -12
  12. package/dist/components/input/input.js.map +1 -1
  13. package/dist/components/pin-input/pin-input.d.ts +33 -0
  14. package/dist/components/pin-input/pin-input.d.ts.map +1 -0
  15. package/dist/components/pin-input/pin-input.js +253 -0
  16. package/dist/components/pin-input/pin-input.js.map +1 -0
  17. package/dist/components/popover/popover.d.ts +26 -0
  18. package/dist/components/popover/popover.d.ts.map +1 -0
  19. package/dist/components/popover/popover.js +196 -0
  20. package/dist/components/popover/popover.js.map +1 -0
  21. package/dist/components/select/select.d.ts +4 -1
  22. package/dist/components/select/select.d.ts.map +1 -1
  23. package/dist/components/select/select.js +26 -4
  24. package/dist/components/select/select.js.map +1 -1
  25. package/dist/components/time-picker/time-picker.d.ts +38 -0
  26. package/dist/components/time-picker/time-picker.d.ts.map +1 -0
  27. package/dist/components/time-picker/time-picker.js +290 -0
  28. package/dist/components/time-picker/time-picker.js.map +1 -0
  29. package/dist/components/toggle-group/toggle-button.d.ts +23 -0
  30. package/dist/components/toggle-group/toggle-button.d.ts.map +1 -0
  31. package/dist/components/toggle-group/toggle-button.js +114 -0
  32. package/dist/components/toggle-group/toggle-button.js.map +1 -0
  33. package/dist/components/toggle-group/toggle-group.d.ts +38 -0
  34. package/dist/components/toggle-group/toggle-group.d.ts.map +1 -0
  35. package/dist/components/toggle-group/toggle-group.js +270 -0
  36. package/dist/components/toggle-group/toggle-group.js.map +1 -0
  37. package/dist/components/tooltip/tooltip.d.ts +33 -0
  38. package/dist/components/tooltip/tooltip.d.ts.map +1 -0
  39. package/dist/components/tooltip/tooltip.js +293 -0
  40. package/dist/components/tooltip/tooltip.js.map +1 -0
  41. package/dist/styles/index.css +2 -0
  42. package/package.json +33 -1
  43. package/src/components/calendar/calendar.ts +458 -0
  44. package/src/components/cron-builder/cron-builder.ts +529 -0
  45. package/src/components/input/input.ts +38 -14
  46. package/src/components/pin-input/pin-input.ts +249 -0
  47. package/src/components/popover/popover.ts +192 -0
  48. package/src/components/select/select.ts +25 -63
  49. package/src/components/time-picker/time-picker.ts +286 -0
  50. package/src/components/toggle-group/toggle-button.ts +103 -0
  51. package/src/components/toggle-group/toggle-group.ts +264 -0
  52. package/src/components/tooltip/tooltip.ts +286 -0
  53. package/src/styles/theme.css +3 -0
@@ -0,0 +1,286 @@
1
+ import {LitElement, html, css, PropertyValues} from "lit";
2
+ import {customElement, property, query, state} from "lit/decorators.js";
3
+ import {classMap} from "lit/directives/class-map.js";
4
+ import {ifDefined} from "lit/directives/if-defined.js";
5
+ import {resetStyles} from "../../styles/reset.js";
6
+
7
+ @customElement("st-time-picker")
8
+ export class StTimePicker extends LitElement {
9
+ static formAssociated = true;
10
+ private _internals: ElementInternals;
11
+
12
+ @property({type: String, reflect: true}) name = "";
13
+ @property({type: String}) label = "";
14
+
15
+ // Visually hide the label for screen-reader only accessibility
16
+ @property({type: Boolean, attribute: "hide-label"}) hideLabel = false;
17
+
18
+ // Time string in HH:mm format (e.g., "14:30")
19
+ @property({type: String}) value = "";
20
+
21
+ @property({type: String}) min = "";
22
+ @property({type: String}) max = "";
23
+ @property({type: Number}) step?: number;
24
+
25
+ @property({type: Boolean, reflect: true}) disabled = false;
26
+ @property({type: Boolean, reflect: true}) required = false;
27
+ @property({type: Boolean, reflect: true}) invalid = false;
28
+
29
+ @state() private _touched = false;
30
+
31
+ @query(".native-input") private _inputEl!: HTMLInputElement;
32
+
33
+ private _labelId = `time-label-${Math.random().toString(36).substring(2, 9)}`;
34
+ private _defaultValue = "";
35
+
36
+ constructor() {
37
+ super();
38
+ this._internals = this.attachInternals();
39
+ }
40
+
41
+ static styles = [
42
+ resetStyles,
43
+ css`
44
+ :host {
45
+ display: block;
46
+ font-family: inherit;
47
+ }
48
+
49
+ label {
50
+ display: block;
51
+ font-size: var(--st-font-size-sm);
52
+ font-weight: 500;
53
+ color: var(--st-color-text-muted);
54
+ margin-bottom: var(--st-spacing-xs);
55
+ }
56
+
57
+ .sr-only {
58
+ position: absolute;
59
+ width: 1px;
60
+ height: 1px;
61
+ padding: 0;
62
+ margin: -1px;
63
+ overflow: hidden;
64
+ clip: rect(0, 0, 0, 0);
65
+ border: 0;
66
+ }
67
+
68
+ .input-wrapper {
69
+ position: relative;
70
+ display: flex;
71
+ align-items: center;
72
+ width: 100%;
73
+ background: var(--st-color-surface);
74
+ border: 1px solid var(--st-color-border);
75
+ border-radius: var(--st-radius-md);
76
+ box-sizing: border-box;
77
+ transition:
78
+ border-color 0.2s,
79
+ box-shadow 0.2s,
80
+ background-color 0.2s;
81
+ }
82
+
83
+ /* Focus within ring */
84
+ .input-wrapper:focus-within {
85
+ border-color: var(--st-color-primary);
86
+ box-shadow: 0 0 0 2px var(--st-color-primary-muted, rgba(59, 130, 246, 0.25));
87
+ }
88
+
89
+ .input-wrapper.is-invalid {
90
+ border-color: var(--st-color-danger, #dc2626);
91
+ }
92
+
93
+ .input-wrapper.is-disabled {
94
+ background: var(--st-color-surface-muted, #f1f5f9);
95
+ cursor: not-allowed;
96
+ opacity: 0.7;
97
+ }
98
+
99
+ .native-input {
100
+ width: 100%;
101
+ min-height: 42px;
102
+ padding: var(--st-spacing-xs) var(--st-spacing-md);
103
+ background: transparent;
104
+ border: none;
105
+ color: var(--st-color-text);
106
+ font-family: inherit;
107
+ font-size: var(--st-font-size-base);
108
+ outline: none;
109
+ cursor: inherit;
110
+ appearance: none;
111
+ -webkit-appearance: none;
112
+ }
113
+
114
+ /* Style the native clock icon generated by WebKit/Blink */
115
+ .native-input::-webkit-calendar-picker-indicator {
116
+ cursor: pointer;
117
+ opacity: 0.6;
118
+ transition: opacity 0.2s;
119
+ filter: var(--st-icon-filter, none); /* Allows inverting for dark mode */
120
+ }
121
+
122
+ .native-input::-webkit-calendar-picker-indicator:hover {
123
+ opacity: 1;
124
+ }
125
+
126
+ .native-input:disabled {
127
+ color: var(--st-color-text-muted);
128
+ }
129
+ `,
130
+ ];
131
+
132
+ override firstUpdated(changedProperties: PropertyValues) {
133
+ super.firstUpdated(changedProperties);
134
+ this._defaultValue = this.value;
135
+
136
+ // Sync initial validity state based on native input
137
+ this._syncValidity();
138
+ }
139
+
140
+ override updated(changedProperties: PropertyValues) {
141
+ super.updated(changedProperties);
142
+ if (
143
+ changedProperties.has("value") ||
144
+ changedProperties.has("required") ||
145
+ changedProperties.has("min") ||
146
+ changedProperties.has("max")
147
+ ) {
148
+ this._internals.setFormValue(this.value || null);
149
+ this._syncValidity();
150
+ }
151
+ }
152
+
153
+ private _syncValidity() {
154
+ if (!this._inputEl) return;
155
+
156
+ this.invalid = false;
157
+
158
+ // 🚀 FIX: Safari/WebKit poorly supports min/max on time inputs.
159
+ // We enforce these constraints manually using strict lexicographical comparison.
160
+ if (this.required && !this.value) {
161
+ this._internals.setValidity({valueMissing: true}, "Please select a time.");
162
+ if (this._touched) this.invalid = true;
163
+ return;
164
+ }
165
+
166
+ if (this.value && this.min && this.value < this.min) {
167
+ this._internals.setValidity({rangeUnderflow: true}, `Time must be ${this.min} or later.`);
168
+ if (this._touched) this.invalid = true;
169
+ return;
170
+ }
171
+
172
+ if (this.value && this.max && this.value > this.max) {
173
+ this._internals.setValidity({rangeOverflow: true}, `Time must be ${this.max} or earlier.`);
174
+ if (this._touched) this.invalid = true;
175
+ return;
176
+ }
177
+
178
+ // Fallback to native validation for things like 'step' bounds
179
+ const isValid = this._inputEl.checkValidity();
180
+
181
+ if (isValid) {
182
+ this._internals.setValidity({});
183
+ this.invalid = false;
184
+ } else {
185
+ this._internals.setValidity(
186
+ this._inputEl.validity,
187
+ this._inputEl.validationMessage,
188
+ this._inputEl,
189
+ );
190
+ if (this._touched) {
191
+ this.invalid = true;
192
+ }
193
+ }
194
+ }
195
+
196
+ public checkValidity() {
197
+ return this._internals.checkValidity();
198
+ }
199
+ public reportValidity() {
200
+ this._touched = true;
201
+ this._syncValidity();
202
+ return this._internals.reportValidity();
203
+ }
204
+
205
+ formResetCallback() {
206
+ this.value = this._defaultValue;
207
+ this._touched = false;
208
+ this.invalid = false;
209
+ this._syncValidity();
210
+ }
211
+
212
+ public override focus(options?: FocusOptions) {
213
+ this._inputEl?.focus(options);
214
+ }
215
+
216
+ private _handleInput(e: Event) {
217
+ this.value = (e.target as HTMLInputElement).value;
218
+ this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
219
+ this.dispatchEvent(
220
+ new CustomEvent("st-input", {
221
+ bubbles: true,
222
+ composed: true,
223
+ detail: {name: this.name, value: this.value},
224
+ }),
225
+ );
226
+ }
227
+
228
+ private _handleChange(e: Event) {
229
+ this._touched = true;
230
+ this.value = (e.target as HTMLInputElement).value;
231
+ this._syncValidity();
232
+ this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
233
+ this.dispatchEvent(
234
+ new CustomEvent("st-change", {
235
+ bubbles: true,
236
+ composed: true,
237
+ detail: {name: this.name, value: this.value},
238
+ }),
239
+ );
240
+ }
241
+
242
+ private _handleBlur() {
243
+ this._touched = true;
244
+ this._syncValidity();
245
+ }
246
+
247
+ render() {
248
+ return html`
249
+ ${this.label
250
+ ? html`<label id=${this._labelId} class=${classMap({"sr-only": this.hideLabel})}
251
+ >${this.label}</label
252
+ >`
253
+ : ""}
254
+
255
+ <div
256
+ class=${classMap({
257
+ "input-wrapper": true,
258
+ "is-disabled": this.disabled,
259
+ "is-invalid": this.invalid,
260
+ })}
261
+ >
262
+ <input
263
+ class="native-input"
264
+ type="time"
265
+ name=${ifDefined(this.name || undefined)}
266
+ .value=${this.value}
267
+ min=${ifDefined(this.min || undefined)}
268
+ max=${ifDefined(this.max || undefined)}
269
+ step=${ifDefined(this.step)}
270
+ ?required=${this.required}
271
+ ?disabled=${this.disabled}
272
+ aria-labelledby=${ifDefined(this.label ? this._labelId : undefined)}
273
+ @input=${this._handleInput}
274
+ @change=${this._handleChange}
275
+ @blur=${this._handleBlur}
276
+ />
277
+ </div>
278
+ `;
279
+ }
280
+ }
281
+
282
+ declare global {
283
+ interface HTMLElementTagNameMap {
284
+ "st-time-picker": StTimePicker;
285
+ }
286
+ }
@@ -0,0 +1,103 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import { resetStyles } from "../../styles/reset.js";
4
+
5
+ @customElement("st-toggle-button")
6
+ export class StToggleButton extends LitElement {
7
+ static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
8
+
9
+ @property({ type: String }) value = "";
10
+ @property({ type: Boolean, reflect: true }) selected = false;
11
+ @property({ type: Boolean, reflect: true }) disabled = false;
12
+
13
+ static styles = [
14
+ resetStyles,
15
+ css`
16
+ :host {
17
+ display: inline-flex;
18
+ flex: 1; /* Ensures all buttons in a group stretch evenly */
19
+ }
20
+
21
+ button {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ gap: var(--st-spacing-xs);
26
+ width: 100%;
27
+ padding: var(--st-spacing-sm) var(--st-spacing-md);
28
+ background-color: var(--st-color-surface);
29
+ color: var(--st-color-text);
30
+ border: none;
31
+ font-size: var(--st-font-size-sm);
32
+ font-weight: var(--st-font-weight-medium);
33
+ cursor: pointer;
34
+ transition: all 0.2s ease;
35
+ white-space: nowrap;
36
+ }
37
+
38
+ button:hover:not(:disabled) {
39
+ background-color: var(--st-color-surface-hover, #f8fafc);
40
+ }
41
+
42
+ button:focus-visible {
43
+ outline: 2px solid var(--st-color-primary);
44
+ outline-offset: -2px;
45
+ z-index: 1; /* Brings the focus ring above siblings */
46
+ }
47
+
48
+ /* Active/Selected State */
49
+ :host([selected]) button {
50
+ background-color: var(--st-color-primary-light, #eff6ff);
51
+ color: var(--st-color-primary-dark, #1d4ed8);
52
+ font-weight: var(--st-font-weight-semibold);
53
+ }
54
+
55
+ /* Disabled State */
56
+ :host([disabled]) {
57
+ pointer-events: none;
58
+ }
59
+
60
+ :host([disabled]) button {
61
+ opacity: 0.5;
62
+ background-color: var(--st-color-surface-muted, #f1f5f9);
63
+ color: var(--st-color-text-muted);
64
+ }
65
+
66
+ ::slotted(svg) {
67
+ width: 1.125rem;
68
+ height: 1.125rem;
69
+ fill: currentColor;
70
+ }
71
+ `
72
+ ];
73
+
74
+ private _handleClick() {
75
+ if (this.disabled) return;
76
+
77
+ // Dispatch an internal event for the group orchestrator to catch
78
+ this.dispatchEvent(new CustomEvent("st-toggle-action", {
79
+ detail: { value: this.value },
80
+ bubbles: true,
81
+ composed: true
82
+ }));
83
+ }
84
+
85
+ render() {
86
+ return html`
87
+ <button
88
+ type="button"
89
+ aria-pressed=${this.selected ? "true" : "false"}
90
+ ?disabled=${this.disabled}
91
+ @click=${this._handleClick}
92
+ >
93
+ <slot></slot>
94
+ </button>
95
+ `;
96
+ }
97
+ }
98
+
99
+ declare global {
100
+ interface HTMLElementTagNameMap {
101
+ "st-toggle-button": StToggleButton;
102
+ }
103
+ }
@@ -0,0 +1,264 @@
1
+ import {LitElement, html, css, PropertyValues} from "lit";
2
+ import {customElement, property, queryAssignedElements, state, query} from "lit/decorators.js";
3
+ import {ifDefined} from "lit/directives/if-defined.js";
4
+ import {classMap} from "lit/directives/class-map.js";
5
+ import {resetStyles} from "../../styles/reset.js";
6
+ import type {StToggleButton} from "./toggle-button.js";
7
+ import "./toggle-button.js";
8
+
9
+ @customElement("st-toggle-group")
10
+ export class StToggleGroup extends LitElement {
11
+ static formAssociated = true;
12
+ private _internals: ElementInternals;
13
+ private _resizeObserver!: ResizeObserver;
14
+
15
+ @query(".group-container") private _container!: HTMLElement;
16
+
17
+ @queryAssignedElements({selector: "st-toggle-button"})
18
+ private _buttons!: StToggleButton[];
19
+
20
+ @property({type: String, reflect: true}) name = "";
21
+ @property({type: String}) value = "";
22
+ @property({type: Boolean}) multiple = false;
23
+ @property({type: Boolean, reflect: true}) disabled = false;
24
+ @property({type: Boolean, reflect: true}) required = false;
25
+ @property({attribute: "aria-label"}) ariaLabel = "";
26
+
27
+ // 🚀 NEW: State properties for dynamic shadows
28
+ @state() private _canScrollLeft = false;
29
+ @state() private _canScrollRight = false;
30
+
31
+ private _defaultValue = "";
32
+
33
+ constructor() {
34
+ super();
35
+ this._internals = this.attachInternals();
36
+ this.addEventListener("st-toggle-action", this._handleToggle as EventListener);
37
+ }
38
+
39
+ static styles = [
40
+ resetStyles,
41
+ css`
42
+ :host {
43
+ display: inline-flex;
44
+ max-width: 100%;
45
+ min-width: 0;
46
+ }
47
+
48
+ .scroll-wrapper {
49
+ position: relative;
50
+ display: inline-flex;
51
+ max-width: 100%;
52
+ min-width: 0;
53
+ }
54
+
55
+ /* 🚀 FIX: Widened to 40px, boosted z-index to 10, and darkened the opacity */
56
+ .scroll-wrapper::before,
57
+ .scroll-wrapper::after {
58
+ content: "";
59
+ position: absolute;
60
+ top: 0;
61
+ bottom: 0;
62
+ width: 40px;
63
+ pointer-events: none;
64
+ z-index: 10;
65
+ opacity: 0;
66
+ transition: opacity 0.3s ease;
67
+ }
68
+
69
+ .scroll-wrapper::before {
70
+ left: 0;
71
+ background: linear-gradient(
72
+ to right,
73
+ var(--st-color-scroll-shadow, rgba(0, 0, 0, 0.15)) 0%,
74
+ transparent 100%
75
+ );
76
+ border-top-left-radius: var(--st-radius-md);
77
+ border-bottom-left-radius: var(--st-radius-md);
78
+ }
79
+
80
+ .scroll-wrapper::after {
81
+ right: 0;
82
+ background: linear-gradient(
83
+ to left,
84
+ var(--st-color-scroll-shadow, rgba(0, 0, 0, 0.15)) 0%,
85
+ transparent 100%
86
+ );
87
+ border-top-right-radius: var(--st-radius-md);
88
+ border-bottom-right-radius: var(--st-radius-md);
89
+ }
90
+
91
+ .scroll-wrapper.can-scroll-left::before {
92
+ opacity: 1;
93
+ }
94
+
95
+ .scroll-wrapper.can-scroll-right::after {
96
+ opacity: 1;
97
+ }
98
+
99
+ /* 🚀 FIX: Dialed up to 0.5 for dark mode */
100
+ :host([data-theme="dark"]) .scroll-wrapper::before {
101
+ background: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, transparent 100%);
102
+ }
103
+ :host([data-theme="dark"]) .scroll-wrapper::after {
104
+ background: linear-gradient(to left, rgba(0, 0, 0, 0.5) 0%, transparent 100%);
105
+ }
106
+
107
+ .group-container {
108
+ display: inline-flex;
109
+ max-width: 100%;
110
+ min-width: 0;
111
+ background-color: var(--st-color-border);
112
+ gap: 1px;
113
+ border: 1px solid var(--st-color-border);
114
+ border-radius: var(--st-radius-md);
115
+
116
+ overflow-x: auto;
117
+ overflow-y: hidden;
118
+ -webkit-overflow-scrolling: touch;
119
+ scrollbar-width: none;
120
+ -ms-overflow-style: none;
121
+ }
122
+
123
+ .group-container::-webkit-scrollbar {
124
+ display: none;
125
+ }
126
+
127
+ ::slotted(*) {
128
+ flex-shrink: 0;
129
+ }
130
+
131
+ :host([disabled]) .group-container {
132
+ opacity: 0.5;
133
+ pointer-events: none;
134
+ }
135
+ `,
136
+ ];
137
+
138
+ override firstUpdated(changedProperties: PropertyValues) {
139
+ super.firstUpdated(changedProperties);
140
+ this._defaultValue = this.value;
141
+
142
+ // 🚀 NEW: Observe the container. If the user rotates their phone,
143
+ // we need to recalculate if shadows are still needed.
144
+ this._resizeObserver = new ResizeObserver(() => this._checkScroll());
145
+ if (this._container) {
146
+ this._resizeObserver.observe(this._container);
147
+ }
148
+ }
149
+
150
+ override disconnectedCallback() {
151
+ super.disconnectedCallback();
152
+ if (this._resizeObserver) {
153
+ this._resizeObserver.disconnect();
154
+ }
155
+ }
156
+
157
+ override updated(changedProperties: PropertyValues) {
158
+ super.updated(changedProperties);
159
+
160
+ if (changedProperties.has("value")) {
161
+ this._internals.setFormValue(this.value || null);
162
+ this._syncButtons();
163
+ this._syncValidity();
164
+ }
165
+
166
+ if (changedProperties.has("disabled")) {
167
+ this._buttons.forEach((btn) => (btn.disabled = this.disabled));
168
+ }
169
+ }
170
+
171
+ // 🚀 NEW: Mathematically determine if we are at the edges of the scroll box
172
+ private _checkScroll = () => {
173
+ if (!this._container) return;
174
+ const {scrollLeft, scrollWidth, clientWidth} = this._container;
175
+
176
+ // Use a 1px buffer to prevent fractional pixel rounding errors on high-DPI screens
177
+ this._canScrollLeft = scrollLeft > 1;
178
+ this._canScrollRight = Math.ceil(scrollLeft + clientWidth) < scrollWidth - 1;
179
+ };
180
+
181
+ private _syncButtons() {
182
+ const values = this.multiple && this.value ? this.value.split(",") : [this.value];
183
+ this._buttons.forEach((btn) => {
184
+ btn.selected = values.includes(btn.value);
185
+ });
186
+ }
187
+
188
+ private _handleSlotChange() {
189
+ this._syncButtons();
190
+ if (this.disabled) {
191
+ this._buttons.forEach((btn) => (btn.disabled = true));
192
+ }
193
+ // Calculate shadows after slots render
194
+ setTimeout(() => this._checkScroll(), 50);
195
+ }
196
+
197
+ private _handleToggle(e: CustomEvent<{value: string}>) {
198
+ e.stopPropagation();
199
+ const toggledValue = e.detail.value;
200
+
201
+ if (this.multiple) {
202
+ let values = this.value ? this.value.split(",") : [];
203
+ if (values.includes(toggledValue)) {
204
+ values = values.filter((v) => v !== toggledValue);
205
+ } else {
206
+ values.push(toggledValue);
207
+ }
208
+ this.value = values.join(",");
209
+ } else {
210
+ if (this.value !== toggledValue) {
211
+ this.value = toggledValue;
212
+ }
213
+ }
214
+
215
+ this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
216
+ }
217
+
218
+ formResetCallback() {
219
+ this.value = this._defaultValue;
220
+ }
221
+
222
+ private _syncValidity() {
223
+ if (this.required && !this.value) {
224
+ this._internals.setValidity({valueMissing: true}, "Please select an option.");
225
+ } else {
226
+ this._internals.setValidity({});
227
+ }
228
+ }
229
+
230
+ public checkValidity() {
231
+ return this._internals.checkValidity();
232
+ }
233
+ public reportValidity() {
234
+ return this._internals.reportValidity();
235
+ }
236
+
237
+ render() {
238
+ const wrapperClasses = classMap({
239
+ "scroll-wrapper": true,
240
+ "can-scroll-left": this._canScrollLeft,
241
+ "can-scroll-right": this._canScrollRight,
242
+ });
243
+
244
+ return html`
245
+ <div class=${wrapperClasses}>
246
+ <div
247
+ class="group-container"
248
+ role="group"
249
+ aria-label=${ifDefined(this.ariaLabel || undefined)}
250
+ aria-required=${this.required ? "true" : "false"}
251
+ @scroll=${this._checkScroll}
252
+ >
253
+ <slot @slotchange=${this._handleSlotChange}></slot>
254
+ </div>
255
+ </div>
256
+ `;
257
+ }
258
+ }
259
+
260
+ declare global {
261
+ interface HTMLElementTagNameMap {
262
+ "st-toggle-group": StToggleGroup;
263
+ }
264
+ }