@latty-ds/web 0.10.3 → 1.0.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/custom-elements.json +1970 -229
- package/dist/components/accordion/index.js +36 -2
- package/dist/components/alert/alert.d.ts +29 -6
- package/dist/components/alert/alert.types.d.ts +5 -0
- package/dist/components/alert/index.js +127 -20
- package/dist/components/avatar/avatar.d.ts +2 -0
- package/dist/components/avatar/index.js +10 -3
- package/dist/components/badge/index.js +1 -1
- package/dist/components/breadcrumb/breadcrumb.d.ts +4 -0
- package/dist/components/breadcrumb/breadcrumb.types.d.ts +5 -0
- package/dist/components/breadcrumb/index.js +20 -3
- package/dist/components/button/button.d.ts +33 -0
- package/dist/components/button/index.js +138 -15
- package/dist/components/calendar/calendar.d.ts +15 -7
- package/dist/components/calendar/calendar.types.d.ts +17 -1
- package/dist/components/calendar/index.js +80 -27
- package/dist/components/checkbox/checkbox.d.ts +14 -2
- package/dist/components/checkbox/checkbox.types.d.ts +5 -0
- package/dist/components/checkbox/index.js +82 -5
- package/dist/components/chip/chip.d.ts +4 -1
- package/dist/components/chip/chip.types.d.ts +5 -0
- package/dist/components/chip/index.js +29 -1
- package/dist/components/color-input/color-input.d.ts +11 -2
- package/dist/components/color-input/color-input.types.d.ts +5 -0
- package/dist/components/color-input/index.js +92 -8
- package/dist/components/combobox/combobox.d.ts +18 -2
- package/dist/components/combobox/combobox.types.d.ts +7 -0
- package/dist/components/combobox/index.js +164 -23
- package/dist/components/date-input/date-input.d.ts +22 -7
- package/dist/components/date-input/date-input.types.d.ts +7 -0
- package/dist/components/date-input/index.js +218 -55
- package/dist/components/datepicker/datepicker.d.ts +9 -1
- package/dist/components/datepicker/index.js +108 -15
- package/dist/components/dialog/dialog.d.ts +39 -19
- package/dist/components/dialog/dialog.types.d.ts +5 -0
- package/dist/components/dialog/index.js +240 -93
- package/dist/components/divider/index.js +2 -2
- package/dist/components/dropdown/dropdown-item.d.ts +1 -1
- package/dist/components/dropdown/dropdown.d.ts +4 -0
- package/dist/components/dropdown/index.js +43 -3
- package/dist/components/icon-button/icon-button.d.ts +4 -0
- package/dist/components/icon-button/index.js +39 -4
- package/dist/components/image/image.d.ts +7 -7
- package/dist/components/image/index.js +6 -11
- package/dist/components/list/index.js +14 -4
- package/dist/components/nav/index.js +37 -17
- package/dist/components/nav/nav-item.d.ts +8 -8
- package/dist/components/pagination/index.js +57 -16
- package/dist/components/pagination/pagination.d.ts +8 -1
- package/dist/components/pagination/pagination.types.d.ts +15 -0
- package/dist/components/progress/index.js +44 -6
- package/dist/components/progress/progress.d.ts +12 -0
- package/dist/components/radio/index.js +148 -9
- package/dist/components/radio/radio.d.ts +34 -2
- package/dist/components/radio/radio.types.d.ts +5 -0
- package/dist/components/radio-group/index.js +120 -29
- package/dist/components/radio-group/radio-group.d.ts +27 -2
- package/dist/components/select/index.js +197 -44
- package/dist/components/select/select.d.ts +38 -4
- package/dist/components/select/select.types.d.ts +5 -0
- package/dist/components/shared/aria-forwarding.d.ts +1 -1
- package/dist/components/shared/form-default.d.ts +30 -0
- package/dist/components/shared/form-disabled.d.ts +40 -0
- package/dist/components/shared/form-field.d.ts +7 -0
- package/dist/components/shared/form-validity.d.ts +17 -0
- package/dist/components/shared/roving.d.ts +11 -0
- package/dist/components/shared/scroll-lock.d.ts +2 -0
- package/dist/components/sidepanel/index.js +278 -75
- package/dist/components/sidepanel/sidepanel.d.ts +32 -10
- package/dist/components/sidepanel/sidepanel.types.d.ts +10 -1
- package/dist/components/skeleton/index.js +3 -8
- package/dist/components/slider/index.js +307 -75
- package/dist/components/slider/slider.d.ts +16 -3
- package/dist/components/snackbar/index.js +82 -9
- package/dist/components/snackbar/snackbar.d.ts +17 -1
- package/dist/components/snackbar/snackbar.types.d.ts +5 -0
- package/dist/components/surface/index.js +8 -0
- package/dist/components/switch/index.js +101 -5
- package/dist/components/switch/switch.d.ts +13 -2
- package/dist/components/switch/switch.types.d.ts +5 -0
- package/dist/components/tab/index.js +15 -14
- package/dist/components/tab/tab.d.ts +0 -1
- package/dist/components/tab-group/index.js +102 -30
- package/dist/components/tab-group/tab-group.d.ts +17 -2
- package/dist/components/table/index.js +105 -61
- package/dist/components/table/table.d.ts +8 -2
- package/dist/components/table/table.types.d.ts +4 -3
- package/dist/components/text/index.js +16 -1
- package/dist/components/text/text.d.ts +1 -1
- package/dist/components/textfield/index.js +243 -35
- package/dist/components/textfield/textfield.d.ts +24 -2
- package/dist/components/textfield/textfield.types.d.ts +11 -0
- package/dist/components/tooltip/index.js +176 -49
- package/dist/components/tooltip/tooltip.d.ts +17 -0
- package/dist/components/tooltip/tooltip.types.d.ts +5 -1
- package/dist/index.cjs +2467 -692
- package/dist/index.js +2478 -699
- package/dist/manifest.json +202 -32
- package/dist/utils/dev-warn.d.ts +5 -0
- package/dist/utils/index.d.ts +1 -0
- package/package.json +4 -8
|
@@ -10,7 +10,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/components/radio/radio.ts
|
|
13
|
-
import { html } from "lit";
|
|
13
|
+
import { html, LitElement as LitElement2 } from "lit";
|
|
14
14
|
|
|
15
15
|
// src/base/themeable-element.ts
|
|
16
16
|
import { LitElement } from "lit";
|
|
@@ -61,7 +61,10 @@ var radioStyles = css`
|
|
|
61
61
|
flex-direction: row-reverse;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
:host(
|
|
64
|
+
/* :host(:disabled) — the browser matches this automatically for a form-associated element inside a
|
|
65
|
+
disabled <fieldset>, no JS required; :host([disabled]) covers the component's own explicit attribute. */
|
|
66
|
+
:host([disabled]) .radio-wrapper,
|
|
67
|
+
:host(:disabled) .radio-wrapper {
|
|
65
68
|
cursor: not-allowed;
|
|
66
69
|
opacity: 0.6;
|
|
67
70
|
}
|
|
@@ -118,7 +121,7 @@ var radioStyles = css`
|
|
|
118
121
|
:host([size='sm']) .dot {
|
|
119
122
|
height: 8px;
|
|
120
123
|
width: 8px;
|
|
121
|
-
|
|
124
|
+
inset-inline-start: 4px;
|
|
122
125
|
top: 4px;
|
|
123
126
|
}
|
|
124
127
|
|
|
@@ -134,7 +137,7 @@ var radioStyles = css`
|
|
|
134
137
|
:host([size='md']) .dot {
|
|
135
138
|
height: 10px;
|
|
136
139
|
width: 10px;
|
|
137
|
-
|
|
140
|
+
inset-inline-start: 5px;
|
|
138
141
|
top: 5px;
|
|
139
142
|
}
|
|
140
143
|
|
|
@@ -150,7 +153,7 @@ var radioStyles = css`
|
|
|
150
153
|
:host([size='lg']) .dot {
|
|
151
154
|
height: 12px;
|
|
152
155
|
width: 12px;
|
|
153
|
-
|
|
156
|
+
inset-inline-start: 6px;
|
|
154
157
|
top: 6px;
|
|
155
158
|
}
|
|
156
159
|
|
|
@@ -203,12 +206,102 @@ var radioStyles = css`
|
|
|
203
206
|
background: var(--lt-interactive-info-bg-hover);
|
|
204
207
|
border-color: var(--lt-interactive-info-bg-hover);
|
|
205
208
|
}
|
|
209
|
+
|
|
210
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
211
|
+
|
|
212
|
+
@media (prefers-reduced-motion: reduce) {
|
|
213
|
+
input[type='radio'] {
|
|
214
|
+
transition: none;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ── Forced colors (Windows High Contrast) ─────────────────────────────────
|
|
219
|
+
Under forced-colors, any author background-color (including every variant's token-resolved fill above) is
|
|
220
|
+
flattened to Canvas — the checked indicator is a plain background:white .dot with no border of its own, so
|
|
221
|
+
it disappears into the input's own (also-flattened) background, leaving checked and unchecked visually
|
|
222
|
+
identical. System color keywords are exempt from that flattening and always resolve to the OS's own
|
|
223
|
+
guaranteed-contrasting palette, regardless of which variant was selected — Highlight/HighlightText is the
|
|
224
|
+
canonical "selected state" pairing (the same pair the OS uses for native text selection), unaffected by
|
|
225
|
+
this component's own --lt-* tokens, which forced-colors mode is specifically overriding. !important is
|
|
226
|
+
needed since a variant rule like :host([variant='primary']) input:checked outranks a bare
|
|
227
|
+
input[type='radio']:checked in specificity — this is the standard, accepted use of !important for a
|
|
228
|
+
forced-colors override (overriding the page's own author styling is the entire point). ─────────────────── */
|
|
229
|
+
|
|
230
|
+
@media (forced-colors: active) {
|
|
231
|
+
input[type='radio']:checked {
|
|
232
|
+
background: Highlight !important;
|
|
233
|
+
border-color: Highlight !important;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.dot {
|
|
237
|
+
background: HighlightText !important;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
206
240
|
`;
|
|
207
241
|
|
|
242
|
+
// src/components/shared/form-default.ts
|
|
243
|
+
var FormDefaultController = class {
|
|
244
|
+
constructor(host, get) {
|
|
245
|
+
this._captured = false;
|
|
246
|
+
this._get = get;
|
|
247
|
+
host.addController(this);
|
|
248
|
+
}
|
|
249
|
+
hostConnected() {
|
|
250
|
+
if (this._captured) return;
|
|
251
|
+
this._captured = true;
|
|
252
|
+
this.value = this._get();
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/components/shared/form-disabled.ts
|
|
257
|
+
var FormDisabledController = class {
|
|
258
|
+
constructor(host) {
|
|
259
|
+
this._fieldsetDisabled = false;
|
|
260
|
+
this._host = host;
|
|
261
|
+
host.addController(this);
|
|
262
|
+
}
|
|
263
|
+
// No-op — this controller has no connect-time work of its own (formDisabledCallback drives it instead), but
|
|
264
|
+
// ReactiveController's members are all optional, and a class satisfying an all-optional interface with no
|
|
265
|
+
// members in common is a TS error (TS2559); this gives it one.
|
|
266
|
+
hostConnected() {
|
|
267
|
+
}
|
|
268
|
+
/** Whether an ancestor `<fieldset>` is currently disabled. */
|
|
269
|
+
get fieldsetDisabled() {
|
|
270
|
+
return this._fieldsetDisabled;
|
|
271
|
+
}
|
|
272
|
+
setFieldsetDisabled(disabled) {
|
|
273
|
+
if (this._fieldsetDisabled === disabled) return;
|
|
274
|
+
this._fieldsetDisabled = disabled;
|
|
275
|
+
this._host.requestUpdate();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
208
279
|
// src/components/radio/radio.ts
|
|
280
|
+
var DEFAULT_LABELS = {
|
|
281
|
+
validationMessage: "Please select this option"
|
|
282
|
+
};
|
|
283
|
+
var radioRegistries = /* @__PURE__ */ new WeakMap();
|
|
284
|
+
var radioGroupFor = (radio) => {
|
|
285
|
+
if (!radio.name) return null;
|
|
286
|
+
const scope = radio.closest("form") ?? radio.getRootNode();
|
|
287
|
+
let byName = radioRegistries.get(scope);
|
|
288
|
+
if (!byName) {
|
|
289
|
+
byName = /* @__PURE__ */ new Map();
|
|
290
|
+
radioRegistries.set(scope, byName);
|
|
291
|
+
}
|
|
292
|
+
let group = byName.get(radio.name);
|
|
293
|
+
if (!group) {
|
|
294
|
+
group = /* @__PURE__ */ new Set();
|
|
295
|
+
byName.set(radio.name, group);
|
|
296
|
+
}
|
|
297
|
+
return group;
|
|
298
|
+
};
|
|
209
299
|
var Radio = class extends ThemeableElement {
|
|
210
300
|
constructor() {
|
|
211
301
|
super();
|
|
302
|
+
this._group = null;
|
|
303
|
+
this._default = new FormDefaultController(this, () => this.checked);
|
|
304
|
+
this._fieldsetDisabled = new FormDisabledController(this);
|
|
212
305
|
this.variant = "primary";
|
|
213
306
|
this.size = "md";
|
|
214
307
|
this.checked = false;
|
|
@@ -218,19 +311,49 @@ var Radio = class extends ThemeableElement {
|
|
|
218
311
|
this.labelPosition = "right";
|
|
219
312
|
this.name = "";
|
|
220
313
|
this.value = "";
|
|
314
|
+
this.tabbable = true;
|
|
315
|
+
this.labels = {};
|
|
221
316
|
this._internals = this.attachInternals();
|
|
222
317
|
}
|
|
318
|
+
connectedCallback() {
|
|
319
|
+
super.connectedCallback();
|
|
320
|
+
this._joinGroup();
|
|
321
|
+
}
|
|
322
|
+
disconnectedCallback() {
|
|
323
|
+
super.disconnectedCallback();
|
|
324
|
+
this._group?.delete(this);
|
|
325
|
+
this._group = null;
|
|
326
|
+
}
|
|
327
|
+
willUpdate(changed) {
|
|
328
|
+
super.willUpdate(changed);
|
|
329
|
+
if (changed.has("name")) this._joinGroup();
|
|
330
|
+
}
|
|
331
|
+
_joinGroup() {
|
|
332
|
+
this._group?.delete(this);
|
|
333
|
+
this._group = radioGroupFor(this);
|
|
334
|
+
this._group?.add(this);
|
|
335
|
+
}
|
|
336
|
+
get _labels() {
|
|
337
|
+
return { ...DEFAULT_LABELS, ...this.labels };
|
|
338
|
+
}
|
|
223
339
|
updated() {
|
|
224
340
|
this._internals.setFormValue(this.checked ? this.value : null);
|
|
225
341
|
const input = this.shadowRoot?.querySelector("input");
|
|
226
342
|
if (this.required && !this.checked && input) {
|
|
227
|
-
this._internals.setValidity({ valueMissing: true },
|
|
343
|
+
this._internals.setValidity({ valueMissing: true }, this._labels.validationMessage, input);
|
|
228
344
|
} else {
|
|
229
345
|
this._internals.setValidity({});
|
|
230
346
|
}
|
|
231
347
|
}
|
|
232
348
|
formResetCallback() {
|
|
233
|
-
this.checked =
|
|
349
|
+
this.checked = this._default.value;
|
|
350
|
+
}
|
|
351
|
+
formDisabledCallback(disabled) {
|
|
352
|
+
this._fieldsetDisabled.setFieldsetDisabled(disabled);
|
|
353
|
+
}
|
|
354
|
+
/** `this.disabled` combined with an ancestor `<fieldset>`'s own disabled state (see `FormDisabledController`). */
|
|
355
|
+
get _isDisabled() {
|
|
356
|
+
return this.disabled || this._fieldsetDisabled.fieldsetDisabled;
|
|
234
357
|
}
|
|
235
358
|
checkValidity() {
|
|
236
359
|
return this._internals.checkValidity();
|
|
@@ -249,6 +372,11 @@ var Radio = class extends ThemeableElement {
|
|
|
249
372
|
e.stopPropagation();
|
|
250
373
|
const target = e.target;
|
|
251
374
|
this.checked = target.checked;
|
|
375
|
+
if (this.checked) {
|
|
376
|
+
this._group?.forEach((other) => {
|
|
377
|
+
if (other !== this) other.checked = false;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
252
380
|
this.dispatchEvent(
|
|
253
381
|
new CustomEvent("change", {
|
|
254
382
|
detail: { value: this.value, checked: this.checked },
|
|
@@ -264,10 +392,11 @@ var Radio = class extends ThemeableElement {
|
|
|
264
392
|
<input
|
|
265
393
|
type="radio"
|
|
266
394
|
.checked=${this.checked}
|
|
267
|
-
?disabled=${this.
|
|
395
|
+
?disabled=${this._isDisabled}
|
|
268
396
|
?required=${this.required}
|
|
269
397
|
name=${this.name}
|
|
270
398
|
value=${this.value}
|
|
399
|
+
tabindex=${this.tabbable ? "0" : "-1"}
|
|
271
400
|
@change=${this.handleChange}
|
|
272
401
|
aria-label=${this.label || "radio"}
|
|
273
402
|
/>
|
|
@@ -280,6 +409,10 @@ var Radio = class extends ThemeableElement {
|
|
|
280
409
|
};
|
|
281
410
|
Radio.styles = radioStyles;
|
|
282
411
|
Radio.formAssociated = true;
|
|
412
|
+
// Makes host.focus() move focus into the shadow root (the inner <input>) — lt-radio-group needs this to move
|
|
413
|
+
// focus programmatically for arrow-key navigation (RAD-2), the same way lt-button's delegatesFocus (BTN-4)
|
|
414
|
+
// lets lt-dropdown do the same for its trigger.
|
|
415
|
+
Radio.shadowRootOptions = { ...LitElement2.shadowRootOptions, delegatesFocus: true };
|
|
283
416
|
__decorateClass([
|
|
284
417
|
property2({ reflect: true })
|
|
285
418
|
], Radio.prototype, "variant", 2);
|
|
@@ -287,7 +420,7 @@ __decorateClass([
|
|
|
287
420
|
property2({ reflect: true })
|
|
288
421
|
], Radio.prototype, "size", 2);
|
|
289
422
|
__decorateClass([
|
|
290
|
-
property2({ type: Boolean
|
|
423
|
+
property2({ type: Boolean })
|
|
291
424
|
], Radio.prototype, "checked", 2);
|
|
292
425
|
__decorateClass([
|
|
293
426
|
property2({ type: Boolean, reflect: true })
|
|
@@ -307,6 +440,12 @@ __decorateClass([
|
|
|
307
440
|
__decorateClass([
|
|
308
441
|
property2({ reflect: true })
|
|
309
442
|
], Radio.prototype, "value", 2);
|
|
443
|
+
__decorateClass([
|
|
444
|
+
property2({ type: Boolean })
|
|
445
|
+
], Radio.prototype, "tabbable", 2);
|
|
446
|
+
__decorateClass([
|
|
447
|
+
property2({ type: Object })
|
|
448
|
+
], Radio.prototype, "labels", 2);
|
|
310
449
|
Radio = __decorateClass([
|
|
311
450
|
customElement("lt-radio")
|
|
312
451
|
], Radio);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type PropertyValues } from 'lit';
|
|
1
2
|
import { ThemeableElement } from '../../base';
|
|
2
|
-
import { RadioSize, RadioVariant, RadioLabelPosition } from './radio.types';
|
|
3
|
+
import { RadioSize, RadioVariant, RadioLabelPosition, type RadioLabels } from './radio.types';
|
|
3
4
|
/**
|
|
4
5
|
* A customizable radio button component with support for multiple variants and sizes.
|
|
5
6
|
*
|
|
@@ -39,8 +40,23 @@ import { RadioSize, RadioVariant, RadioLabelPosition } from './radio.types';
|
|
|
39
40
|
export declare class Radio extends ThemeableElement {
|
|
40
41
|
static styles: import("lit").CSSResult;
|
|
41
42
|
static formAssociated: boolean;
|
|
43
|
+
static shadowRootOptions: {
|
|
44
|
+
delegatesFocus: boolean;
|
|
45
|
+
clonable?: boolean;
|
|
46
|
+
customElementRegistry?: CustomElementRegistry | null;
|
|
47
|
+
mode: ShadowRootMode;
|
|
48
|
+
serializable?: boolean;
|
|
49
|
+
slotAssignment?: SlotAssignmentMode;
|
|
50
|
+
};
|
|
42
51
|
private _internals;
|
|
52
|
+
private _group;
|
|
53
|
+
private _default;
|
|
54
|
+
private _fieldsetDisabled;
|
|
43
55
|
constructor();
|
|
56
|
+
connectedCallback(): void;
|
|
57
|
+
disconnectedCallback(): void;
|
|
58
|
+
willUpdate(changed: PropertyValues<this>): void;
|
|
59
|
+
private _joinGroup;
|
|
44
60
|
/**
|
|
45
61
|
* Visual variant that determines the color when checked.
|
|
46
62
|
* @default 'primary'
|
|
@@ -52,7 +68,10 @@ export declare class Radio extends ThemeableElement {
|
|
|
52
68
|
*/
|
|
53
69
|
size: RadioSize;
|
|
54
70
|
/**
|
|
55
|
-
* Whether the radio is checked.
|
|
71
|
+
* Whether the radio is checked. Not reflected as an attribute — a native radio's `checked` _content_
|
|
72
|
+
* attribute stays fixed at the markup default and never tracks interaction; reflecting it live meant
|
|
73
|
+
* `formResetCallback` could no longer tell what the original default was. Read the property, or the `change`
|
|
74
|
+
* event's `detail.checked`, instead of `[checked]`/`hasAttribute('checked')`.
|
|
56
75
|
* @default false
|
|
57
76
|
*/
|
|
58
77
|
checked: boolean;
|
|
@@ -87,8 +106,21 @@ export declare class Radio extends ThemeableElement {
|
|
|
87
106
|
* @default ''
|
|
88
107
|
*/
|
|
89
108
|
value: string;
|
|
109
|
+
/**
|
|
110
|
+
* Whether this radio is reachable with Tab. Defaults to true for a standalone radio; `lt-radio-group` drives
|
|
111
|
+
* this itself for its managed radios — a roving tabindex (only the checked radio, or the first enabled one,
|
|
112
|
+
* is a Tab stop), matching the native radiogroup pattern.
|
|
113
|
+
* @default true
|
|
114
|
+
*/
|
|
115
|
+
tabbable: boolean;
|
|
116
|
+
/** Overridable UI text (the required-field validation message), for i18n. English defaults — override only the keys you need. */
|
|
117
|
+
labels: Partial<RadioLabels>;
|
|
118
|
+
private get _labels();
|
|
90
119
|
updated(): void;
|
|
91
120
|
formResetCallback(): void;
|
|
121
|
+
formDisabledCallback(disabled: boolean): void;
|
|
122
|
+
/** `this.disabled` combined with an ancestor `<fieldset>`'s own disabled state (see `FormDisabledController`). */
|
|
123
|
+
private get _isDisabled();
|
|
92
124
|
checkValidity(): boolean;
|
|
93
125
|
reportValidity(): boolean;
|
|
94
126
|
/**
|
|
@@ -19,3 +19,8 @@ export type RadioSize = 'sm' | 'md' | 'lg';
|
|
|
19
19
|
*/
|
|
20
20
|
export type RadioVariant = 'primary' | 'secondary' | 'success' | 'error' | 'info';
|
|
21
21
|
export type RadioLabelPosition = 'left' | 'right';
|
|
22
|
+
/** Overridable UI text for `lt-radio`, for i18n. */
|
|
23
|
+
export interface RadioLabels {
|
|
24
|
+
/** Validation message shown when a required radio group has no option selected. */
|
|
25
|
+
validationMessage: string;
|
|
26
|
+
}
|
|
@@ -9,6 +9,15 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
9
9
|
return result;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/components/shared/roving.ts
|
|
13
|
+
var enabledItems = (items) => items.filter((item) => !item.disabled);
|
|
14
|
+
var rovingTargetOf = (enabled, value) => enabled.find((item) => item.value === value) ?? enabled[0];
|
|
15
|
+
var stepIndex = (index, length, delta) => (Math.max(index, 0) + delta + length) % length;
|
|
16
|
+
|
|
17
|
+
// src/components/shared/form-field.ts
|
|
18
|
+
var fieldCount = 0;
|
|
19
|
+
var createFieldIds = () => ({ helper: `lt-field-${fieldCount++}-helper` });
|
|
20
|
+
|
|
12
21
|
// src/components/radio-group/radio-group.ts
|
|
13
22
|
import { html as html2, nothing } from "lit";
|
|
14
23
|
|
|
@@ -57,8 +66,8 @@ var radioGroupStyles = css`
|
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
.required-indicator {
|
|
60
|
-
color: var(--lt-
|
|
61
|
-
margin-
|
|
69
|
+
color: var(--lt-text-error);
|
|
70
|
+
margin-inline-start: var(--lt-spacing-1);
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
.radios-container {
|
|
@@ -258,6 +267,20 @@ var textStyles = css2`
|
|
|
258
267
|
`;
|
|
259
268
|
|
|
260
269
|
// src/components/text/text.ts
|
|
270
|
+
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
271
|
+
"h1",
|
|
272
|
+
"h2",
|
|
273
|
+
"h3",
|
|
274
|
+
"h4",
|
|
275
|
+
"h5",
|
|
276
|
+
"h6",
|
|
277
|
+
"p",
|
|
278
|
+
"span",
|
|
279
|
+
"div",
|
|
280
|
+
"label",
|
|
281
|
+
"strong",
|
|
282
|
+
"em"
|
|
283
|
+
]);
|
|
261
284
|
var VARIANT_TAG = {
|
|
262
285
|
"display-2xl": "p",
|
|
263
286
|
"display-xl": "p",
|
|
@@ -282,7 +305,8 @@ var Text = class extends ThemeableElement {
|
|
|
282
305
|
this.as = "";
|
|
283
306
|
}
|
|
284
307
|
render() {
|
|
285
|
-
const
|
|
308
|
+
const requested = this.as && ALLOWED_TAGS.has(this.as) ? this.as : "";
|
|
309
|
+
const tag = unsafeStatic(requested || VARIANT_TAG[this.variant] || "p");
|
|
286
310
|
return html`<${tag} part="base"><slot></slot></${tag}>`;
|
|
287
311
|
}
|
|
288
312
|
};
|
|
@@ -305,53 +329,103 @@ var RadioGroup = class extends ThemeableElement {
|
|
|
305
329
|
this.name = "";
|
|
306
330
|
this.value = "";
|
|
307
331
|
this.orientation = "vertical";
|
|
332
|
+
/**
|
|
333
|
+
* Helper text displayed below the radio group.
|
|
334
|
+
* @default ''
|
|
335
|
+
*/
|
|
336
|
+
this._fieldIds = createFieldIds();
|
|
308
337
|
this.helperText = "";
|
|
309
338
|
this.error = false;
|
|
310
339
|
this.required = false;
|
|
311
|
-
|
|
340
|
+
// Reacts to a slotted radio's `disabled` attribute changing after the fact (e.g. a consumer disabling one
|
|
341
|
+
// dynamically, with no accompanying change to this group's own value/name), so the roving tabindex and
|
|
342
|
+
// keyboard navigation immediately stop landing on it.
|
|
343
|
+
this._radioObserver = new MutationObserver(() => this._syncRadios());
|
|
312
344
|
/**
|
|
313
345
|
* Handles change events from individual radio buttons.
|
|
314
|
-
* Updates the group value and ensures only one radio is checked.
|
|
315
346
|
*
|
|
316
347
|
* @param e - The change event from a radio button
|
|
317
348
|
* @private
|
|
318
349
|
*/
|
|
319
350
|
this.handleRadioChange = (e) => {
|
|
320
351
|
const radio = e.target;
|
|
321
|
-
if (radio.checked)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
352
|
+
if (!radio.checked) return;
|
|
353
|
+
e.stopPropagation();
|
|
354
|
+
this._select(radio);
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Arrow keys move the roving tab stop and, per the native radiogroup's automatic-activation model, select the
|
|
358
|
+
* radio moved to — matching a real `<input type="radio">` group, where arrowing both moves focus and checks
|
|
359
|
+
* the newly-focused button. All four directions move (Right/Down forward, Left/Up back), disabled radios are
|
|
360
|
+
* skipped entirely, and it wraps at both ends.
|
|
361
|
+
* @private
|
|
362
|
+
*/
|
|
363
|
+
this.handleRadioKeydown = (e) => {
|
|
364
|
+
const enabled = enabledItems(this.radios ?? []);
|
|
365
|
+
if (enabled.length === 0) return;
|
|
366
|
+
const index = enabled.indexOf(e.currentTarget);
|
|
367
|
+
let next;
|
|
368
|
+
switch (e.key) {
|
|
369
|
+
case "ArrowRight":
|
|
370
|
+
case "ArrowDown":
|
|
371
|
+
next = enabled[stepIndex(index, enabled.length, 1)];
|
|
372
|
+
break;
|
|
373
|
+
case "ArrowLeft":
|
|
374
|
+
case "ArrowUp":
|
|
375
|
+
next = enabled[stepIndex(index, enabled.length, -1)];
|
|
376
|
+
break;
|
|
335
377
|
}
|
|
378
|
+
if (!next) return;
|
|
379
|
+
e.preventDefault();
|
|
380
|
+
this._select(next);
|
|
381
|
+
next.focus();
|
|
336
382
|
};
|
|
337
383
|
}
|
|
338
|
-
|
|
384
|
+
connectedCallback() {
|
|
385
|
+
super.connectedCallback();
|
|
386
|
+
this._radioObserver.observe(this, { attributes: true, attributeFilter: ["disabled"], subtree: true });
|
|
339
387
|
this._syncRadios();
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
388
|
+
}
|
|
389
|
+
firstUpdated() {
|
|
390
|
+
if (!this.value) {
|
|
391
|
+
const checkedRadio = this.radios?.find((r) => r.checked);
|
|
392
|
+
if (checkedRadio) this.value = checkedRadio.value;
|
|
343
393
|
}
|
|
394
|
+
this._syncRadios();
|
|
344
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Wires each radio's `name`, roving tabindex, and listeners, and drives `checked` from this group's `value` —
|
|
398
|
+
* the direction a consumer setting `group.value = '…'` needs, since that's a plain property assignment on the
|
|
399
|
+
* group, not a native input event any individual radio would otherwise react to.
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
345
402
|
_syncRadios() {
|
|
346
403
|
if (!this.radios) return;
|
|
404
|
+
const enabled = enabledItems(this.radios);
|
|
405
|
+
const rovingTarget = rovingTargetOf(enabled, this.value);
|
|
347
406
|
this.radios.forEach((radio) => {
|
|
348
407
|
if (this.name) radio.name = this.name;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
408
|
+
radio.checked = radio.value === this.value;
|
|
409
|
+
radio.tabbable = radio === rovingTarget;
|
|
410
|
+
radio.addEventListener("change", this.handleRadioChange);
|
|
411
|
+
radio.addEventListener("keydown", this.handleRadioKeydown);
|
|
353
412
|
});
|
|
354
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Selects a radio (used by both the native-change and keyboard-navigation handlers below).
|
|
416
|
+
* @private
|
|
417
|
+
*/
|
|
418
|
+
_select(radio) {
|
|
419
|
+
if (radio.disabled || radio.value === this.value) return;
|
|
420
|
+
this.value = radio.value;
|
|
421
|
+
this.dispatchEvent(
|
|
422
|
+
new CustomEvent("change", {
|
|
423
|
+
detail: { value: this.value },
|
|
424
|
+
bubbles: true,
|
|
425
|
+
composed: true
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
}
|
|
355
429
|
/**
|
|
356
430
|
* Handles slot changes to update radios when content changes.
|
|
357
431
|
* @private
|
|
@@ -359,20 +433,35 @@ var RadioGroup = class extends ThemeableElement {
|
|
|
359
433
|
handleSlotChange() {
|
|
360
434
|
this._syncRadios();
|
|
361
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* A group `value` set by the consumer (`group.value = '…'`) is a plain property assignment — it doesn't pass
|
|
438
|
+
* through any radio's own change handling, so this is what drives `checked` on the radios to match it.
|
|
439
|
+
*/
|
|
440
|
+
updated(changed) {
|
|
441
|
+
super.updated(changed);
|
|
442
|
+
if (changed.has("value") || changed.has("name")) this._syncRadios();
|
|
443
|
+
}
|
|
362
444
|
/**
|
|
363
445
|
* Cleanup event listeners when component is removed.
|
|
364
446
|
*/
|
|
365
447
|
disconnectedCallback() {
|
|
366
448
|
super.disconnectedCallback();
|
|
449
|
+
this._radioObserver.disconnect();
|
|
367
450
|
if (this.radios) {
|
|
368
451
|
this.radios.forEach((radio) => {
|
|
369
452
|
radio.removeEventListener("change", this.handleRadioChange);
|
|
453
|
+
radio.removeEventListener("keydown", this.handleRadioKeydown);
|
|
370
454
|
});
|
|
371
455
|
}
|
|
372
456
|
}
|
|
373
457
|
render() {
|
|
374
458
|
return html2`
|
|
375
|
-
<div
|
|
459
|
+
<div
|
|
460
|
+
class="radio-group"
|
|
461
|
+
role="radiogroup"
|
|
462
|
+
aria-label=${this.label || nothing}
|
|
463
|
+
aria-describedby=${this.helperText ? this._fieldIds.helper : nothing}
|
|
464
|
+
>
|
|
376
465
|
${this.label ? html2`
|
|
377
466
|
<div class="label">
|
|
378
467
|
<lt-text variant="label" as="span">${this.label}</lt-text>
|
|
@@ -382,7 +471,9 @@ var RadioGroup = class extends ThemeableElement {
|
|
|
382
471
|
<div class="radios-container">
|
|
383
472
|
<slot @slotchange=${this.handleSlotChange}></slot>
|
|
384
473
|
</div>
|
|
385
|
-
${this.helperText ? html2`<lt-text variant="caption" as="span" class="helper-text"
|
|
474
|
+
${this.helperText ? html2`<lt-text id=${this._fieldIds.helper} variant="caption" as="span" class="helper-text"
|
|
475
|
+
>${this.helperText}</lt-text
|
|
476
|
+
>` : ""}
|
|
386
477
|
</div>
|
|
387
478
|
`;
|
|
388
479
|
}
|
|
@@ -60,6 +60,7 @@ export declare class RadioGroup extends ThemeableElement {
|
|
|
60
60
|
* Helper text displayed below the radio group.
|
|
61
61
|
* @default ''
|
|
62
62
|
*/
|
|
63
|
+
private readonly _fieldIds;
|
|
63
64
|
helperText: string;
|
|
64
65
|
/**
|
|
65
66
|
* Whether the radio group is in an error state.
|
|
@@ -76,22 +77,46 @@ export declare class RadioGroup extends ThemeableElement {
|
|
|
76
77
|
* @private
|
|
77
78
|
*/
|
|
78
79
|
private radios;
|
|
79
|
-
private
|
|
80
|
+
private readonly _radioObserver;
|
|
81
|
+
connectedCallback(): void;
|
|
80
82
|
firstUpdated(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Wires each radio's `name`, roving tabindex, and listeners, and drives `checked` from this group's `value` —
|
|
85
|
+
* the direction a consumer setting `group.value = '…'` needs, since that's a plain property assignment on the
|
|
86
|
+
* group, not a native input event any individual radio would otherwise react to.
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
81
89
|
private _syncRadios;
|
|
90
|
+
/**
|
|
91
|
+
* Selects a radio (used by both the native-change and keyboard-navigation handlers below).
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
private _select;
|
|
82
95
|
/**
|
|
83
96
|
* Handles change events from individual radio buttons.
|
|
84
|
-
* Updates the group value and ensures only one radio is checked.
|
|
85
97
|
*
|
|
86
98
|
* @param e - The change event from a radio button
|
|
87
99
|
* @private
|
|
88
100
|
*/
|
|
89
101
|
private handleRadioChange;
|
|
102
|
+
/**
|
|
103
|
+
* Arrow keys move the roving tab stop and, per the native radiogroup's automatic-activation model, select the
|
|
104
|
+
* radio moved to — matching a real `<input type="radio">` group, where arrowing both moves focus and checks
|
|
105
|
+
* the newly-focused button. All four directions move (Right/Down forward, Left/Up back), disabled radios are
|
|
106
|
+
* skipped entirely, and it wraps at both ends.
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
private handleRadioKeydown;
|
|
90
110
|
/**
|
|
91
111
|
* Handles slot changes to update radios when content changes.
|
|
92
112
|
* @private
|
|
93
113
|
*/
|
|
94
114
|
private handleSlotChange;
|
|
115
|
+
/**
|
|
116
|
+
* A group `value` set by the consumer (`group.value = '…'`) is a plain property assignment — it doesn't pass
|
|
117
|
+
* through any radio's own change handling, so this is what drives `checked` on the radios to match it.
|
|
118
|
+
*/
|
|
119
|
+
protected updated(changed: Map<string, unknown>): void;
|
|
95
120
|
/**
|
|
96
121
|
* Cleanup event listeners when component is removed.
|
|
97
122
|
*/
|