@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
|
@@ -40,27 +40,50 @@ import { customElement as customElement2, property as property3 } from "lit/deco
|
|
|
40
40
|
// src/components/snackbar/snackbar.styles.ts
|
|
41
41
|
import { css } from "lit";
|
|
42
42
|
var snackbarStyles = css`
|
|
43
|
+
/* ── Popover ─────────────────────────────────────────────── The host itself is the popover element (no
|
|
44
|
+
wrapped child, unlike lt-dialog/lt-sidepanel's inner <dialog>) — the browser's own [popover] UA stylesheet
|
|
45
|
+
sets inset:0, margin:auto, width/height:fit-content, padding, border, and Canvas colors, all of which are
|
|
46
|
+
reset below in favor of this component's own bottom-center placement. display is deliberately NOT set on
|
|
47
|
+
the base rule — a closed (not :popover-open) popover must stay display:none (the native default) so it's
|
|
48
|
+
genuinely non-interactive and out of the accessibility tree; it's set to block only for the open state,
|
|
49
|
+
below. display/overlay need transition-behavior: allow-discrete so hidePopover() doesn't remove it from
|
|
50
|
+
rendering instantly, cutting the exit transition off partway through — same reasoning as lt-dialog. ─────── */
|
|
51
|
+
|
|
43
52
|
:host {
|
|
44
|
-
|
|
53
|
+
background: transparent;
|
|
54
|
+
border: none;
|
|
45
55
|
bottom: 24px;
|
|
46
56
|
left: 50%;
|
|
57
|
+
margin: 0;
|
|
47
58
|
max-width: calc(100vw - 48px);
|
|
48
59
|
opacity: 0;
|
|
60
|
+
overflow: visible;
|
|
61
|
+
padding: 0;
|
|
49
62
|
position: fixed;
|
|
63
|
+
right: auto;
|
|
64
|
+
top: auto;
|
|
50
65
|
transform: translateX(-50%) translateY(8px);
|
|
51
66
|
transition:
|
|
52
67
|
opacity 200ms ease,
|
|
53
68
|
transform 200ms ease,
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
display 200ms allow-discrete,
|
|
70
|
+
overlay 200ms allow-discrete;
|
|
56
71
|
width: max-content;
|
|
57
|
-
z-index: 9000;
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
:host([open]) {
|
|
75
|
+
display: block;
|
|
61
76
|
opacity: 1;
|
|
62
77
|
transform: translateX(-50%) translateY(0);
|
|
63
|
-
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* A newly-shown popover has no earlier rendered frame to transition from — @starting-style supplies one so
|
|
81
|
+
the fade/slide-in actually animates instead of snapping straight to the open position. */
|
|
82
|
+
@starting-style {
|
|
83
|
+
:host([open]) {
|
|
84
|
+
opacity: 0;
|
|
85
|
+
transform: translateX(-50%) translateY(8px);
|
|
86
|
+
}
|
|
64
87
|
}
|
|
65
88
|
|
|
66
89
|
/* ── Layout ──────────────────────────────────────────────── */
|
|
@@ -115,13 +138,14 @@ var snackbarStyles = css`
|
|
|
115
138
|
font-size: 0.8125rem;
|
|
116
139
|
font-weight: 600;
|
|
117
140
|
letter-spacing: 0.05em;
|
|
118
|
-
opacity: 0.85;
|
|
119
141
|
padding: 0 var(--lt-spacing-1);
|
|
120
142
|
text-transform: uppercase;
|
|
121
143
|
}
|
|
122
144
|
|
|
145
|
+
/* Text on a coloured fill is never dimmed with opacity (that blends it into the fill and lowers its contrast
|
|
146
|
+
below whatever the on-colour was picked for), so hover is shown with an underline instead. */
|
|
123
147
|
.action:hover {
|
|
124
|
-
|
|
148
|
+
text-decoration: underline;
|
|
125
149
|
}
|
|
126
150
|
|
|
127
151
|
/* ── Close button ────────────────────────────────────────── */
|
|
@@ -135,6 +159,8 @@ var snackbarStyles = css`
|
|
|
135
159
|
display: flex;
|
|
136
160
|
flex-shrink: 0;
|
|
137
161
|
justify-content: center;
|
|
162
|
+
min-height: 24px; /* WCAG 2.5.8 */
|
|
163
|
+
min-width: 24px;
|
|
138
164
|
opacity: 0.7;
|
|
139
165
|
padding: 0;
|
|
140
166
|
}
|
|
@@ -142,6 +168,14 @@ var snackbarStyles = css`
|
|
|
142
168
|
.close:hover {
|
|
143
169
|
opacity: 1;
|
|
144
170
|
}
|
|
171
|
+
|
|
172
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
173
|
+
|
|
174
|
+
@media (prefers-reduced-motion: reduce) {
|
|
175
|
+
:host {
|
|
176
|
+
transition: none;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
145
179
|
`;
|
|
146
180
|
|
|
147
181
|
// src/utils/dispatch.ts
|
|
@@ -208,6 +242,14 @@ var surfaceStyles = css2`
|
|
|
208
242
|
:host([appearance='outlined']:hover) .surface {
|
|
209
243
|
border-color: var(--lt-border-strong);
|
|
210
244
|
}
|
|
245
|
+
|
|
246
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
247
|
+
|
|
248
|
+
@media (prefers-reduced-motion: reduce) {
|
|
249
|
+
.surface {
|
|
250
|
+
transition: none;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
211
253
|
`;
|
|
212
254
|
|
|
213
255
|
// src/components/surface/surface.ts
|
|
@@ -242,6 +284,9 @@ Surface = __decorateClass([
|
|
|
242
284
|
], Surface);
|
|
243
285
|
|
|
244
286
|
// src/components/snackbar/snackbar.ts
|
|
287
|
+
var DEFAULT_LABELS = {
|
|
288
|
+
dismiss: "Dismiss"
|
|
289
|
+
};
|
|
245
290
|
var Snackbar = class extends ThemeableElement {
|
|
246
291
|
constructor() {
|
|
247
292
|
super(...arguments);
|
|
@@ -253,6 +298,17 @@ var Snackbar = class extends ThemeableElement {
|
|
|
253
298
|
this.icon = "";
|
|
254
299
|
this.elevation = "5";
|
|
255
300
|
this.appearance = "filled";
|
|
301
|
+
this.labels = {};
|
|
302
|
+
// `updated()` also runs after the very first render, where `changed.has('open')` is true even though nothing
|
|
303
|
+
// was ever shown — `open` starts false, so the else branch ran, dispatching a `hide` for a snackbar that was
|
|
304
|
+
// never visible (SNK-1). Same fix and reasoning as lt-dropdown's `_skipNextOpenChange` (DDN-1). This guards
|
|
305
|
+
// only the timer/event side effects, not the popover sync below — a snackbar mounted already open (markup
|
|
306
|
+
// `open=""`) still needs `showPopover()` called once, or it stays in the native `display: none` state despite
|
|
307
|
+
// `open` being true.
|
|
308
|
+
this._skipNextOpenChange = true;
|
|
309
|
+
}
|
|
310
|
+
get _labels() {
|
|
311
|
+
return { ...DEFAULT_LABELS, ...this.labels };
|
|
256
312
|
}
|
|
257
313
|
/** Shows the snackbar and starts the auto-dismiss timer. */
|
|
258
314
|
show() {
|
|
@@ -262,9 +318,16 @@ var Snackbar = class extends ThemeableElement {
|
|
|
262
318
|
hide() {
|
|
263
319
|
this.open = false;
|
|
264
320
|
}
|
|
321
|
+
connectedCallback() {
|
|
322
|
+
super.connectedCallback();
|
|
323
|
+
this.setAttribute("popover", "manual");
|
|
324
|
+
}
|
|
265
325
|
updated(changedProperties) {
|
|
266
326
|
if (changedProperties.has("open")) {
|
|
267
|
-
|
|
327
|
+
this._syncPopoverState();
|
|
328
|
+
if (this._skipNextOpenChange) {
|
|
329
|
+
this._skipNextOpenChange = false;
|
|
330
|
+
} else if (this.open) {
|
|
268
331
|
this._startTimer();
|
|
269
332
|
dispatch(this, "show");
|
|
270
333
|
} else {
|
|
@@ -273,6 +336,13 @@ var Snackbar = class extends ThemeableElement {
|
|
|
273
336
|
}
|
|
274
337
|
}
|
|
275
338
|
}
|
|
339
|
+
_syncPopoverState() {
|
|
340
|
+
if (this.open) {
|
|
341
|
+
if (!this.matches(":popover-open")) this.showPopover();
|
|
342
|
+
} else if (this.matches(":popover-open")) {
|
|
343
|
+
this.hidePopover();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
276
346
|
disconnectedCallback() {
|
|
277
347
|
super.disconnectedCallback();
|
|
278
348
|
this._clearTimer();
|
|
@@ -312,7 +382,7 @@ var Snackbar = class extends ThemeableElement {
|
|
|
312
382
|
<slot></slot>
|
|
313
383
|
</span>
|
|
314
384
|
${this.actionLabel ? html2`<button class="action" @click=${this._handleAction} part="action">${this.actionLabel}</button>` : ""}
|
|
315
|
-
${this.closable ? html2`<button class="close" @click=${this._handleClose} aria-label
|
|
385
|
+
${this.closable ? html2`<button class="close" @click=${this._handleClose} aria-label=${this._labels.dismiss} part="close">
|
|
316
386
|
<lt-icon name="xmark"></lt-icon>
|
|
317
387
|
</button>` : ""}
|
|
318
388
|
</div>
|
|
@@ -358,6 +428,9 @@ __decorateClass([
|
|
|
358
428
|
__decorateClass([
|
|
359
429
|
property3({ reflect: true })
|
|
360
430
|
], Snackbar.prototype, "appearance", 2);
|
|
431
|
+
__decorateClass([
|
|
432
|
+
property3({ type: Object })
|
|
433
|
+
], Snackbar.prototype, "labels", 2);
|
|
361
434
|
Snackbar = __decorateClass([
|
|
362
435
|
customElement2("lt-snackbar")
|
|
363
436
|
], Snackbar);
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { ThemeableElement } from '../../base';
|
|
2
|
-
import type { SnackbarVariant } from './snackbar.types';
|
|
2
|
+
import type { SnackbarVariant, SnackbarLabels } from './snackbar.types';
|
|
3
3
|
import type { SurfaceElevation, SurfaceAppearance } from '../surface/surface.types';
|
|
4
4
|
import '@latty-ds/icons';
|
|
5
5
|
import '../surface/surface';
|
|
6
6
|
/**
|
|
7
7
|
* Brief notification that appears temporarily at the bottom of the viewport.
|
|
8
8
|
*
|
|
9
|
+
* Renders as a native "manual" popover on its own host, so it always appears in the top layer — above an open
|
|
10
|
+
* `lt-dialog`/`lt-sidepanel`, regardless of z-index, since top-layer content always wins over non-top-layer
|
|
11
|
+
* content no matter how high (SNK-companion to §6.1's `<dialog>` migration). Confirmed live: fully visible and
|
|
12
|
+
* readable above an open modal. One narrower, unavoidable limitation (confirmed live too, not fixable from this
|
|
13
|
+
* component's side): a native `showModal()` makes everything **outside the dialog's own subtree** inert,
|
|
14
|
+
* including a sibling popover — explicitly setting `inert = false` on the snackbar does not override it. A
|
|
15
|
+
* snackbar shown while a dialog is open is visible but not clickable until the dialog closes; its own
|
|
16
|
+
* `duration` timer still fires normally regardless (a plain `setTimeout`, unaffected by inertness), so only a
|
|
17
|
+
* `duration="0"` snackbar with a close/action button would be briefly stuck non-interactive in that scenario.
|
|
18
|
+
*
|
|
9
19
|
* @element lt-snackbar
|
|
10
20
|
*
|
|
11
21
|
* @slot - Notification content. Accepts any HTML — bold, links, etc.
|
|
@@ -56,12 +66,18 @@ export declare class Snackbar extends ThemeableElement {
|
|
|
56
66
|
* @default 'filled'
|
|
57
67
|
*/
|
|
58
68
|
appearance: SurfaceAppearance;
|
|
69
|
+
/** Overridable UI text (the close button's accessible name), for i18n. English defaults — override only the keys you need. */
|
|
70
|
+
labels: Partial<SnackbarLabels>;
|
|
71
|
+
private get _labels();
|
|
59
72
|
private _timer?;
|
|
73
|
+
private _skipNextOpenChange;
|
|
60
74
|
/** Shows the snackbar and starts the auto-dismiss timer. */
|
|
61
75
|
show(): void;
|
|
62
76
|
/** Dismisses the snackbar. */
|
|
63
77
|
hide(): void;
|
|
78
|
+
connectedCallback(): void;
|
|
64
79
|
updated(changedProperties: Map<string, unknown>): void;
|
|
80
|
+
private _syncPopoverState;
|
|
65
81
|
disconnectedCallback(): void;
|
|
66
82
|
private _startTimer;
|
|
67
83
|
private _clearTimer;
|
|
@@ -1 +1,6 @@
|
|
|
1
1
|
export type SnackbarVariant = 'default' | 'success' | 'warning' | 'error' | 'info';
|
|
2
|
+
/** Overridable UI text, for i18n — English defaults; override only the keys you need. */
|
|
3
|
+
export interface SnackbarLabels {
|
|
4
|
+
/** Accessible label for the dismiss button. */
|
|
5
|
+
dismiss: string;
|
|
6
|
+
}
|
|
@@ -88,6 +88,14 @@ var surfaceStyles = css`
|
|
|
88
88
|
:host([appearance='outlined']:hover) .surface {
|
|
89
89
|
border-color: var(--lt-border-strong);
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
93
|
+
|
|
94
|
+
@media (prefers-reduced-motion: reduce) {
|
|
95
|
+
.surface {
|
|
96
|
+
transition: none;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
91
99
|
`;
|
|
92
100
|
|
|
93
101
|
// src/utils/color.ts
|
|
@@ -56,7 +56,10 @@ var switchStyles = css`
|
|
|
56
56
|
flex-direction: row-reverse;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
:host(
|
|
59
|
+
/* :host(:disabled) — the browser matches this automatically for a form-associated element inside a
|
|
60
|
+
disabled <fieldset>, no JS required; :host([disabled]) covers the component's own explicit attribute. */
|
|
61
|
+
:host([disabled]) .switch-wrapper,
|
|
62
|
+
:host(:disabled) .switch-wrapper {
|
|
60
63
|
cursor: not-allowed;
|
|
61
64
|
opacity: 0.6;
|
|
62
65
|
}
|
|
@@ -200,12 +203,91 @@ var switchStyles = css`
|
|
|
200
203
|
:host([variant='info']) input[type='checkbox']:checked:hover:not(:disabled) {
|
|
201
204
|
background: var(--lt-interactive-info-bg-hover);
|
|
202
205
|
}
|
|
206
|
+
|
|
207
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
208
|
+
|
|
209
|
+
@media (prefers-reduced-motion: reduce) {
|
|
210
|
+
input[type='checkbox'],
|
|
211
|
+
.thumb {
|
|
212
|
+
transition: none;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* ── Forced colors (Windows High Contrast) ─────────────────────────────────
|
|
217
|
+
Under forced-colors, every author background-color here (the unchecked track's neutral fill, every
|
|
218
|
+
variant's checked fill, and the thumb's plain white) is flattened to Canvas — the track has no border of
|
|
219
|
+
its own to fall back on, and the thumb disappears into it, leaving neither track nor thumb visible at all.
|
|
220
|
+
System color keywords are exempt from that flattening; Highlight/HighlightText is the canonical OS
|
|
221
|
+
"selected state" pairing, unaffected by which variant was selected (forced-colors overrides that anyway).
|
|
222
|
+
!important matches every variant's own :checked rule in specificity — standard for a forced-colors
|
|
223
|
+
override, whose entire point is overriding the page's own author styling. ─────────────────────────────── */
|
|
224
|
+
|
|
225
|
+
@media (forced-colors: active) {
|
|
226
|
+
input[type='checkbox'] {
|
|
227
|
+
border: 1px solid ButtonText;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
input[type='checkbox']:checked {
|
|
231
|
+
background: Highlight !important;
|
|
232
|
+
border-color: Highlight !important;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.thumb {
|
|
236
|
+
background: ButtonText;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
input[type='checkbox']:checked ~ .thumb {
|
|
240
|
+
background: HighlightText;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
203
243
|
`;
|
|
204
244
|
|
|
245
|
+
// src/components/shared/form-default.ts
|
|
246
|
+
var FormDefaultController = class {
|
|
247
|
+
constructor(host, get) {
|
|
248
|
+
this._captured = false;
|
|
249
|
+
this._get = get;
|
|
250
|
+
host.addController(this);
|
|
251
|
+
}
|
|
252
|
+
hostConnected() {
|
|
253
|
+
if (this._captured) return;
|
|
254
|
+
this._captured = true;
|
|
255
|
+
this.value = this._get();
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// src/components/shared/form-disabled.ts
|
|
260
|
+
var FormDisabledController = class {
|
|
261
|
+
constructor(host) {
|
|
262
|
+
this._fieldsetDisabled = false;
|
|
263
|
+
this._host = host;
|
|
264
|
+
host.addController(this);
|
|
265
|
+
}
|
|
266
|
+
// No-op — this controller has no connect-time work of its own (formDisabledCallback drives it instead), but
|
|
267
|
+
// ReactiveController's members are all optional, and a class satisfying an all-optional interface with no
|
|
268
|
+
// members in common is a TS error (TS2559); this gives it one.
|
|
269
|
+
hostConnected() {
|
|
270
|
+
}
|
|
271
|
+
/** Whether an ancestor `<fieldset>` is currently disabled. */
|
|
272
|
+
get fieldsetDisabled() {
|
|
273
|
+
return this._fieldsetDisabled;
|
|
274
|
+
}
|
|
275
|
+
setFieldsetDisabled(disabled) {
|
|
276
|
+
if (this._fieldsetDisabled === disabled) return;
|
|
277
|
+
this._fieldsetDisabled = disabled;
|
|
278
|
+
this._host.requestUpdate();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
205
282
|
// src/components/switch/switch.ts
|
|
283
|
+
var DEFAULT_LABELS = {
|
|
284
|
+
validationMessage: "Please check this switch"
|
|
285
|
+
};
|
|
206
286
|
var Switch = class extends ThemeableElement {
|
|
207
287
|
constructor() {
|
|
208
288
|
super();
|
|
289
|
+
this._default = new FormDefaultController(this, () => this.checked);
|
|
290
|
+
this._fieldsetDisabled = new FormDisabledController(this);
|
|
209
291
|
this.variant = "primary";
|
|
210
292
|
this.size = "md";
|
|
211
293
|
this.checked = false;
|
|
@@ -215,8 +297,12 @@ var Switch = class extends ThemeableElement {
|
|
|
215
297
|
this.labelPosition = "right";
|
|
216
298
|
this.name = "";
|
|
217
299
|
this.value = "on";
|
|
300
|
+
this.labels = {};
|
|
218
301
|
this._internals = this.attachInternals();
|
|
219
302
|
}
|
|
303
|
+
get _labels() {
|
|
304
|
+
return { ...DEFAULT_LABELS, ...this.labels };
|
|
305
|
+
}
|
|
220
306
|
/**
|
|
221
307
|
* Handles switch change events.
|
|
222
308
|
* Updates the checked state and dispatches a custom change event.
|
|
@@ -228,13 +314,20 @@ var Switch = class extends ThemeableElement {
|
|
|
228
314
|
this._internals.setFormValue(this.checked ? this.value : null);
|
|
229
315
|
const input = this.shadowRoot?.querySelector("input");
|
|
230
316
|
if (this.required && !this.checked && input) {
|
|
231
|
-
this._internals.setValidity({ valueMissing: true },
|
|
317
|
+
this._internals.setValidity({ valueMissing: true }, this._labels.validationMessage, input);
|
|
232
318
|
} else {
|
|
233
319
|
this._internals.setValidity({});
|
|
234
320
|
}
|
|
235
321
|
}
|
|
236
322
|
formResetCallback() {
|
|
237
|
-
this.checked =
|
|
323
|
+
this.checked = this._default.value;
|
|
324
|
+
}
|
|
325
|
+
formDisabledCallback(disabled) {
|
|
326
|
+
this._fieldsetDisabled.setFieldsetDisabled(disabled);
|
|
327
|
+
}
|
|
328
|
+
/** `this.disabled` combined with an ancestor `<fieldset>`'s own disabled state (see `FormDisabledController`). */
|
|
329
|
+
get _isDisabled() {
|
|
330
|
+
return this.disabled || this._fieldsetDisabled.fieldsetDisabled;
|
|
238
331
|
}
|
|
239
332
|
checkValidity() {
|
|
240
333
|
return this._internals.checkValidity();
|
|
@@ -261,7 +354,7 @@ var Switch = class extends ThemeableElement {
|
|
|
261
354
|
<input
|
|
262
355
|
type="checkbox"
|
|
263
356
|
.checked=${this.checked}
|
|
264
|
-
?disabled=${this.
|
|
357
|
+
?disabled=${this._isDisabled}
|
|
265
358
|
?required=${this.required}
|
|
266
359
|
name=${this.name}
|
|
267
360
|
value=${this.value}
|
|
@@ -286,7 +379,7 @@ __decorateClass([
|
|
|
286
379
|
property2({ reflect: true })
|
|
287
380
|
], Switch.prototype, "size", 2);
|
|
288
381
|
__decorateClass([
|
|
289
|
-
property2({ type: Boolean
|
|
382
|
+
property2({ type: Boolean })
|
|
290
383
|
], Switch.prototype, "checked", 2);
|
|
291
384
|
__decorateClass([
|
|
292
385
|
property2({ type: Boolean, reflect: true })
|
|
@@ -306,6 +399,9 @@ __decorateClass([
|
|
|
306
399
|
__decorateClass([
|
|
307
400
|
property2({ reflect: true })
|
|
308
401
|
], Switch.prototype, "value", 2);
|
|
402
|
+
__decorateClass([
|
|
403
|
+
property2({ type: Object })
|
|
404
|
+
], Switch.prototype, "labels", 2);
|
|
309
405
|
Switch = __decorateClass([
|
|
310
406
|
customElement("lt-switch")
|
|
311
407
|
], Switch);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ThemeableElement } from '../../base';
|
|
2
|
-
import { SwitchSize, SwitchVariant, SwitchLabelPosition } from './switch.types';
|
|
2
|
+
import { SwitchSize, SwitchVariant, SwitchLabelPosition, type SwitchLabels } from './switch.types';
|
|
3
3
|
/**
|
|
4
4
|
* A toggle switch component with sliding animation.
|
|
5
5
|
* Internally uses a checkbox input with custom toggle styling.
|
|
@@ -35,6 +35,8 @@ export declare class Switch extends ThemeableElement {
|
|
|
35
35
|
static styles: import("lit").CSSResult;
|
|
36
36
|
static formAssociated: boolean;
|
|
37
37
|
private _internals;
|
|
38
|
+
private _default;
|
|
39
|
+
private _fieldsetDisabled;
|
|
38
40
|
constructor();
|
|
39
41
|
/**
|
|
40
42
|
* Visual variant that determines the color when checked.
|
|
@@ -47,7 +49,10 @@ export declare class Switch extends ThemeableElement {
|
|
|
47
49
|
*/
|
|
48
50
|
size: SwitchSize;
|
|
49
51
|
/**
|
|
50
|
-
* Whether the switch is checked (on).
|
|
52
|
+
* Whether the switch is checked (on). Not reflected as an attribute — a native checkbox's `checked` _content_
|
|
53
|
+
* attribute stays fixed at the markup default and never tracks interaction; reflecting it live meant
|
|
54
|
+
* `formResetCallback` could no longer tell what the original default was. Read the property, or the `change`
|
|
55
|
+
* event's `detail.checked`, instead of `[checked]`/`hasAttribute('checked')`.
|
|
51
56
|
* @default false
|
|
52
57
|
*/
|
|
53
58
|
checked: boolean;
|
|
@@ -81,6 +86,9 @@ export declare class Switch extends ThemeableElement {
|
|
|
81
86
|
* @default 'on'
|
|
82
87
|
*/
|
|
83
88
|
value: string;
|
|
89
|
+
/** Overridable UI text (the required-field validation message), for i18n. English defaults — override only the keys you need. */
|
|
90
|
+
labels: Partial<SwitchLabels>;
|
|
91
|
+
private get _labels();
|
|
84
92
|
/**
|
|
85
93
|
* Handles switch change events.
|
|
86
94
|
* Updates the checked state and dispatches a custom change event.
|
|
@@ -90,6 +98,9 @@ export declare class Switch extends ThemeableElement {
|
|
|
90
98
|
*/
|
|
91
99
|
updated(): void;
|
|
92
100
|
formResetCallback(): void;
|
|
101
|
+
formDisabledCallback(disabled: boolean): void;
|
|
102
|
+
/** `this.disabled` combined with an ancestor `<fieldset>`'s own disabled state (see `FormDisabledController`). */
|
|
103
|
+
private get _isDisabled();
|
|
93
104
|
checkValidity(): boolean;
|
|
94
105
|
reportValidity(): boolean;
|
|
95
106
|
private handleChange;
|
|
@@ -19,3 +19,8 @@ export type SwitchSize = 'sm' | 'md' | 'lg';
|
|
|
19
19
|
*/
|
|
20
20
|
export type SwitchVariant = 'primary' | 'secondary' | 'success' | 'error' | 'info';
|
|
21
21
|
export type SwitchLabelPosition = 'left' | 'right';
|
|
22
|
+
/** Overridable UI text for `lt-switch`, for i18n. */
|
|
23
|
+
export interface SwitchLabels {
|
|
24
|
+
/** Validation message shown when a required switch is left unchecked. */
|
|
25
|
+
validationMessage: string;
|
|
26
|
+
}
|
|
@@ -82,7 +82,7 @@ var tabStyles = css`
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
:host([disabled]) .tab {
|
|
85
|
-
color: var(--lt-
|
|
85
|
+
color: var(--lt-text-disabled);
|
|
86
86
|
cursor: not-allowed;
|
|
87
87
|
opacity: 0.5;
|
|
88
88
|
}
|
|
@@ -125,6 +125,17 @@ var tabStyles = css`
|
|
|
125
125
|
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
|
|
126
126
|
color: var(--lt-text-primary);
|
|
127
127
|
}
|
|
128
|
+
|
|
129
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
130
|
+
|
|
131
|
+
@media (prefers-reduced-motion: reduce) {
|
|
132
|
+
/* :host([data-variant='pills']) .tab must be listed explicitly, not just .tab — it outranks a bare .tab in
|
|
133
|
+
specificity (an extra :host() attribute selector), so it would otherwise keep its own transition. */
|
|
134
|
+
.tab,
|
|
135
|
+
:host([data-variant='pills']) .tab {
|
|
136
|
+
transition: none;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
128
139
|
`;
|
|
129
140
|
|
|
130
141
|
// src/components/tab/tab.ts
|
|
@@ -138,21 +149,13 @@ var Tab = class extends ThemeableElement {
|
|
|
138
149
|
this.active = false;
|
|
139
150
|
this.disabled = false;
|
|
140
151
|
this.size = "md";
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
this.dispatchEvent(
|
|
144
|
-
new CustomEvent("tab-click", {
|
|
145
|
-
detail: { value: this.value },
|
|
146
|
-
bubbles: true,
|
|
147
|
-
composed: true
|
|
148
|
-
})
|
|
149
|
-
);
|
|
150
|
-
};
|
|
152
|
+
// Enter/Space activate the tab exactly like a mouse click: a native `click` on the host, which
|
|
153
|
+
// `lt-tab-group` (or any consumer) listens for. There is no separate tab event.
|
|
151
154
|
this._handleHostKeydown = (e) => {
|
|
152
155
|
if (this.disabled) return;
|
|
153
156
|
if (e.key === "Enter" || e.key === " ") {
|
|
154
157
|
e.preventDefault();
|
|
155
|
-
this.
|
|
158
|
+
this.click();
|
|
156
159
|
}
|
|
157
160
|
};
|
|
158
161
|
}
|
|
@@ -160,12 +163,10 @@ var Tab = class extends ThemeableElement {
|
|
|
160
163
|
super.connectedCallback();
|
|
161
164
|
this.setAttribute("role", "tab");
|
|
162
165
|
this.tabIndex = 0;
|
|
163
|
-
this.addEventListener("click", this._handleHostClick);
|
|
164
166
|
this.addEventListener("keydown", this._handleHostKeydown);
|
|
165
167
|
}
|
|
166
168
|
disconnectedCallback() {
|
|
167
169
|
super.disconnectedCallback();
|
|
168
|
-
this.removeEventListener("click", this._handleHostClick);
|
|
169
170
|
this.removeEventListener("keydown", this._handleHostKeydown);
|
|
170
171
|
}
|
|
171
172
|
updated(changedProperties) {
|
|
@@ -53,7 +53,6 @@ export declare class Tab extends ThemeableElement {
|
|
|
53
53
|
connectedCallback(): void;
|
|
54
54
|
disconnectedCallback(): void;
|
|
55
55
|
protected updated(changedProperties: Map<string, unknown>): void;
|
|
56
|
-
private _handleHostClick;
|
|
57
56
|
private _handleHostKeydown;
|
|
58
57
|
render(): import("lit").TemplateResult<1>;
|
|
59
58
|
}
|