@sondor-technology/sondor-components 1.0.6 → 1.0.8
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/dist/components/calendar/calendar.d.ts +36 -0
- package/dist/components/calendar/calendar.d.ts.map +1 -0
- package/dist/components/calendar/calendar.js +439 -0
- package/dist/components/calendar/calendar.js.map +1 -0
- package/dist/components/cron-builder/cron-builder.d.ts +34 -0
- package/dist/components/cron-builder/cron-builder.d.ts.map +1 -0
- package/dist/components/cron-builder/cron-builder.js +456 -0
- package/dist/components/cron-builder/cron-builder.js.map +1 -0
- package/dist/components/form/form.js +2 -2
- package/dist/components/icon/icon-paths.d.ts +161 -86
- package/dist/components/icon/icon-paths.d.ts.map +1 -1
- package/dist/components/icon/icon-paths.js +175 -106
- package/dist/components/icon/icon-paths.js.map +1 -1
- package/dist/components/icon/icon.d.ts +1 -1
- package/dist/components/icon/icon.d.ts.map +1 -1
- package/dist/components/icon/icon.js +5 -5
- package/dist/components/icon/icon.js.map +1 -1
- package/dist/components/input/input.d.ts +1 -0
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +35 -12
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/pin-input/pin-input.d.ts +33 -0
- package/dist/components/pin-input/pin-input.d.ts.map +1 -0
- package/dist/components/pin-input/pin-input.js +253 -0
- package/dist/components/pin-input/pin-input.js.map +1 -0
- package/dist/components/popover/popover.d.ts +26 -0
- package/dist/components/popover/popover.d.ts.map +1 -0
- package/dist/components/popover/popover.js +196 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/select/select.d.ts +4 -1
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +26 -4
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/time-picker/time-picker.d.ts +38 -0
- package/dist/components/time-picker/time-picker.d.ts.map +1 -0
- package/dist/components/time-picker/time-picker.js +280 -0
- package/dist/components/time-picker/time-picker.js.map +1 -0
- package/dist/components/toggle-group/toggle-button.d.ts +23 -0
- package/dist/components/toggle-group/toggle-button.d.ts.map +1 -0
- package/dist/components/toggle-group/toggle-button.js +115 -0
- package/dist/components/toggle-group/toggle-button.js.map +1 -0
- package/dist/components/toggle-group/toggle-group.d.ts +32 -0
- package/dist/components/toggle-group/toggle-group.d.ts.map +1 -0
- package/dist/components/toggle-group/toggle-group.js +150 -0
- package/dist/components/toggle-group/toggle-group.js.map +1 -0
- package/dist/components/tooltip/tooltip.d.ts +33 -0
- package/dist/components/tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip/tooltip.js +293 -0
- package/dist/components/tooltip/tooltip.js.map +1 -0
- package/dist/styles/index.css +1 -0
- package/package.json +29 -1
- package/src/components/calendar/calendar.ts +444 -0
- package/src/components/cron-builder/cron-builder.ts +452 -0
- package/src/components/form/form.ts +2 -2
- package/src/components/icon/icon-paths.ts +175 -106
- package/src/components/icon/icon.ts +6 -6
- package/src/components/input/input.ts +38 -14
- package/src/components/pin-input/pin-input.ts +249 -0
- package/src/components/popover/popover.ts +192 -0
- package/src/components/select/select.ts +25 -63
- package/src/components/time-picker/time-picker.ts +272 -0
- package/src/components/toggle-group/toggle-button.ts +104 -0
- package/src/components/toggle-group/toggle-group.ts +148 -0
- package/src/components/tooltip/tooltip.ts +286 -0
- package/src/styles/theme.css +2 -0
|
@@ -0,0 +1,272 @@
|
|
|
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
|
+
}
|
|
220
|
+
|
|
221
|
+
private _handleChange(e: Event) {
|
|
222
|
+
this._touched = true;
|
|
223
|
+
this.value = (e.target as HTMLInputElement).value;
|
|
224
|
+
this._syncValidity();
|
|
225
|
+
this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private _handleBlur() {
|
|
229
|
+
this._touched = true;
|
|
230
|
+
this._syncValidity();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
render() {
|
|
234
|
+
return html`
|
|
235
|
+
${this.label
|
|
236
|
+
? html`<label id=${this._labelId} class=${classMap({"sr-only": this.hideLabel})}
|
|
237
|
+
>${this.label}</label
|
|
238
|
+
>`
|
|
239
|
+
: ""}
|
|
240
|
+
|
|
241
|
+
<div
|
|
242
|
+
class=${classMap({
|
|
243
|
+
"input-wrapper": true,
|
|
244
|
+
"is-disabled": this.disabled,
|
|
245
|
+
"is-invalid": this.invalid,
|
|
246
|
+
})}
|
|
247
|
+
>
|
|
248
|
+
<input
|
|
249
|
+
class="native-input"
|
|
250
|
+
type="time"
|
|
251
|
+
name=${ifDefined(this.name || undefined)}
|
|
252
|
+
.value=${this.value}
|
|
253
|
+
min=${ifDefined(this.min || undefined)}
|
|
254
|
+
max=${ifDefined(this.max || undefined)}
|
|
255
|
+
step=${ifDefined(this.step)}
|
|
256
|
+
?required=${this.required}
|
|
257
|
+
?disabled=${this.disabled}
|
|
258
|
+
aria-labelledby=${ifDefined(this.label ? this._labelId : undefined)}
|
|
259
|
+
@input=${this._handleInput}
|
|
260
|
+
@change=${this._handleChange}
|
|
261
|
+
@blur=${this._handleBlur}
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
declare global {
|
|
269
|
+
interface HTMLElementTagNameMap {
|
|
270
|
+
"st-time-picker": StTimePicker;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
// 🚀 FIX: Forwards programmatic focus natively to the inner button
|
|
8
|
+
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
|
|
9
|
+
|
|
10
|
+
@property({ type: String }) value = "";
|
|
11
|
+
@property({ type: Boolean, reflect: true }) selected = false;
|
|
12
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
13
|
+
|
|
14
|
+
static styles = [
|
|
15
|
+
resetStyles,
|
|
16
|
+
css`
|
|
17
|
+
:host {
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
flex: 1; /* Ensures all buttons in a group stretch evenly */
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
button {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
gap: var(--st-spacing-xs);
|
|
27
|
+
width: 100%;
|
|
28
|
+
padding: var(--st-spacing-sm) var(--st-spacing-md);
|
|
29
|
+
background-color: var(--st-color-surface);
|
|
30
|
+
color: var(--st-color-text);
|
|
31
|
+
border: none;
|
|
32
|
+
font-size: var(--st-font-size-sm);
|
|
33
|
+
font-weight: var(--st-font-weight-medium);
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition: all 0.2s ease;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
button:hover:not(:disabled) {
|
|
40
|
+
background-color: var(--st-color-surface-hover, #f8fafc);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
button:focus-visible {
|
|
44
|
+
outline: 2px solid var(--st-color-primary);
|
|
45
|
+
outline-offset: -2px;
|
|
46
|
+
z-index: 1; /* Brings the focus ring above siblings */
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Active/Selected State */
|
|
50
|
+
:host([selected]) button {
|
|
51
|
+
background-color: var(--st-color-primary-light, #eff6ff);
|
|
52
|
+
color: var(--st-color-primary-dark, #1d4ed8);
|
|
53
|
+
font-weight: var(--st-font-weight-semibold);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Disabled State */
|
|
57
|
+
:host([disabled]) {
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:host([disabled]) button {
|
|
62
|
+
opacity: 0.5;
|
|
63
|
+
background-color: var(--st-color-surface-muted, #f1f5f9);
|
|
64
|
+
color: var(--st-color-text-muted);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
::slotted(svg) {
|
|
68
|
+
width: 1.125rem;
|
|
69
|
+
height: 1.125rem;
|
|
70
|
+
fill: currentColor;
|
|
71
|
+
}
|
|
72
|
+
`
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
private _handleClick() {
|
|
76
|
+
if (this.disabled) return;
|
|
77
|
+
|
|
78
|
+
// Dispatch an internal event for the group orchestrator to catch
|
|
79
|
+
this.dispatchEvent(new CustomEvent("st-toggle-action", {
|
|
80
|
+
detail: { value: this.value },
|
|
81
|
+
bubbles: true,
|
|
82
|
+
composed: true
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
render() {
|
|
87
|
+
return html`
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
aria-pressed=${this.selected ? "true" : "false"}
|
|
91
|
+
?disabled=${this.disabled}
|
|
92
|
+
@click=${this._handleClick}
|
|
93
|
+
>
|
|
94
|
+
<slot></slot>
|
|
95
|
+
</button>
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
declare global {
|
|
101
|
+
interface HTMLElementTagNameMap {
|
|
102
|
+
"st-toggle-button": StToggleButton;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { LitElement, html, css, PropertyValues } from "lit";
|
|
2
|
+
import { customElement, property, queryAssignedElements } from "lit/decorators.js";
|
|
3
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
4
|
+
import { resetStyles } from "../../styles/reset.js";
|
|
5
|
+
import type { StToggleButton } from "./toggle-button.js";
|
|
6
|
+
import "./toggle-button.js";
|
|
7
|
+
|
|
8
|
+
@customElement("st-toggle-group")
|
|
9
|
+
export class StToggleGroup extends LitElement {
|
|
10
|
+
static formAssociated = true;
|
|
11
|
+
private _internals: ElementInternals;
|
|
12
|
+
|
|
13
|
+
@queryAssignedElements({ selector: "st-toggle-button" })
|
|
14
|
+
private _buttons!: StToggleButton[];
|
|
15
|
+
|
|
16
|
+
@property({ type: String, reflect: true }) name = "";
|
|
17
|
+
|
|
18
|
+
// A comma-separated list of values if multiple=true, or a single value if false
|
|
19
|
+
@property({ type: String }) value = "";
|
|
20
|
+
|
|
21
|
+
@property({ type: Boolean }) multiple = false;
|
|
22
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
23
|
+
@property({ type: Boolean, reflect: true }) required = false;
|
|
24
|
+
|
|
25
|
+
// ARIA Support
|
|
26
|
+
@property({ attribute: "aria-label" }) ariaLabel = "";
|
|
27
|
+
|
|
28
|
+
private _defaultValue = "";
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
this._internals = this.attachInternals();
|
|
33
|
+
this.addEventListener("st-toggle-action", this._handleToggle as EventListener);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static styles = [
|
|
37
|
+
resetStyles,
|
|
38
|
+
css`
|
|
39
|
+
:host {
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.group-container {
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
background-color: var(--st-color-border);
|
|
46
|
+
/* 🚀 FIX: This 1px gap perfectly acts as the inner border between children */
|
|
47
|
+
gap: 1px;
|
|
48
|
+
border: 1px solid var(--st-color-border);
|
|
49
|
+
border-radius: var(--st-radius-md);
|
|
50
|
+
overflow: hidden; /* Clips the child buttons exactly to the border radius */
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:host([disabled]) .group-container {
|
|
54
|
+
opacity: 0.5;
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
}
|
|
57
|
+
`
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
override firstUpdated(changedProperties: PropertyValues) {
|
|
61
|
+
super.firstUpdated(changedProperties);
|
|
62
|
+
this._defaultValue = this.value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override updated(changedProperties: PropertyValues) {
|
|
66
|
+
super.updated(changedProperties);
|
|
67
|
+
|
|
68
|
+
if (changedProperties.has("value")) {
|
|
69
|
+
this._internals.setFormValue(this.value || null);
|
|
70
|
+
this._syncButtons();
|
|
71
|
+
this._syncValidity();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (changedProperties.has("disabled")) {
|
|
75
|
+
this._buttons.forEach(btn => btn.disabled = this.disabled);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private _syncButtons() {
|
|
80
|
+
const values = this.multiple && this.value ? this.value.split(",") : [this.value];
|
|
81
|
+
this._buttons.forEach(btn => {
|
|
82
|
+
btn.selected = values.includes(btn.value);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private _handleSlotChange() {
|
|
87
|
+
this._syncButtons();
|
|
88
|
+
if (this.disabled) {
|
|
89
|
+
this._buttons.forEach(btn => btn.disabled = true);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private _handleToggle(e: CustomEvent<{ value: string }>) {
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
const toggledValue = e.detail.value;
|
|
96
|
+
|
|
97
|
+
if (this.multiple) {
|
|
98
|
+
let values = this.value ? this.value.split(",") : [];
|
|
99
|
+
if (values.includes(toggledValue)) {
|
|
100
|
+
values = values.filter(v => v !== toggledValue); // Deselect
|
|
101
|
+
} else {
|
|
102
|
+
values.push(toggledValue); // Select
|
|
103
|
+
}
|
|
104
|
+
this.value = values.join(",");
|
|
105
|
+
} else {
|
|
106
|
+
// Single select behavior (radio button style: clicking active doesn't deselect)
|
|
107
|
+
if (this.value !== toggledValue) {
|
|
108
|
+
this.value = toggledValue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
formResetCallback() {
|
|
116
|
+
this.value = this._defaultValue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private _syncValidity() {
|
|
120
|
+
if (this.required && !this.value) {
|
|
121
|
+
this._internals.setValidity({ valueMissing: true }, "Please select an option.");
|
|
122
|
+
} else {
|
|
123
|
+
this._internals.setValidity({});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public checkValidity() { return this._internals.checkValidity(); }
|
|
128
|
+
public reportValidity() { return this._internals.reportValidity(); }
|
|
129
|
+
|
|
130
|
+
render() {
|
|
131
|
+
return html`
|
|
132
|
+
<div
|
|
133
|
+
class="group-container"
|
|
134
|
+
role="group"
|
|
135
|
+
aria-label=${ifDefined(this.ariaLabel || undefined)}
|
|
136
|
+
aria-required=${this.required ? "true" : "false"}
|
|
137
|
+
>
|
|
138
|
+
<slot @slotchange=${this._handleSlotChange}></slot>
|
|
139
|
+
</div>
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
declare global {
|
|
145
|
+
interface HTMLElementTagNameMap {
|
|
146
|
+
"st-toggle-group": StToggleGroup;
|
|
147
|
+
}
|
|
148
|
+
}
|