@latty-ds/web 0.10.4 → 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 +1969 -228
- 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 +124 -17
- package/dist/components/avatar/avatar.d.ts +2 -0
- package/dist/components/avatar/index.js +10 -3
- 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 +135 -12
- 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 +78 -25
- 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 +216 -53
- 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 +237 -90
- package/dist/components/divider/index.js +2 -2
- 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 +36 -1
- 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 +275 -72
- 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 +2458 -683
- package/dist/index.js +2469 -690
- 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 +3 -7
|
@@ -36,6 +36,13 @@ __decorateClass([
|
|
|
36
36
|
property({ reflect: true })
|
|
37
37
|
], ThemeableElement.prototype, "theme", 2);
|
|
38
38
|
|
|
39
|
+
// src/utils/dev-warn.ts
|
|
40
|
+
var devWarn = (tag, message) => {
|
|
41
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
|
|
42
|
+
console.warn(`[${tag}] ${message}`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
39
46
|
// src/components/icon-button/icon-button.styles.ts
|
|
40
47
|
import { css } from "lit";
|
|
41
48
|
var iconButtonStyles = css`
|
|
@@ -282,6 +289,14 @@ var iconButtonStyles = css`
|
|
|
282
289
|
:host([round]) [part='base'] {
|
|
283
290
|
border-radius: 50%;
|
|
284
291
|
}
|
|
292
|
+
|
|
293
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
294
|
+
|
|
295
|
+
@media (prefers-reduced-motion: reduce) {
|
|
296
|
+
[part='base'] {
|
|
297
|
+
transition: none;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
285
300
|
`;
|
|
286
301
|
|
|
287
302
|
// src/components/shared/aria-forwarding.ts
|
|
@@ -290,7 +305,8 @@ var FORWARDED_ARIA_ATTRIBUTES = [
|
|
|
290
305
|
"aria-expanded",
|
|
291
306
|
"aria-haspopup",
|
|
292
307
|
"aria-controls",
|
|
293
|
-
"aria-current"
|
|
308
|
+
"aria-current",
|
|
309
|
+
"aria-description"
|
|
294
310
|
];
|
|
295
311
|
var AriaForwardingController = class {
|
|
296
312
|
constructor(host, getTarget) {
|
|
@@ -330,11 +346,30 @@ var IconButton = class extends ThemeableElement {
|
|
|
330
346
|
this.bordered = false;
|
|
331
347
|
this.size = "md";
|
|
332
348
|
this.disabled = false;
|
|
349
|
+
this._blockClickWhileDisabled = (e) => {
|
|
350
|
+
if (this.disabled) {
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
e.stopImmediatePropagation();
|
|
353
|
+
}
|
|
354
|
+
};
|
|
333
355
|
this.href = "";
|
|
334
356
|
this.target = "";
|
|
335
357
|
this.rel = "";
|
|
336
358
|
this.round = false;
|
|
337
359
|
}
|
|
360
|
+
firstUpdated() {
|
|
361
|
+
if (!this.label && !this.textContent?.trim()) {
|
|
362
|
+
devWarn("lt-icon-button", "no label or visible text \u2014 set `label` so screen readers can name the button");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
connectedCallback() {
|
|
366
|
+
super.connectedCallback();
|
|
367
|
+
this.addEventListener("click", this._blockClickWhileDisabled, { capture: true });
|
|
368
|
+
}
|
|
369
|
+
disconnectedCallback() {
|
|
370
|
+
super.disconnectedCallback();
|
|
371
|
+
this.removeEventListener("click", this._blockClickWhileDisabled, { capture: true });
|
|
372
|
+
}
|
|
338
373
|
_inner() {
|
|
339
374
|
return this.icon ? html`<lt-icon name=${this.icon}></lt-icon>` : html`<slot></slot>`;
|
|
340
375
|
}
|
|
@@ -5,18 +5,19 @@ import type { ImageLoading } from './image.types';
|
|
|
5
5
|
* A thin wrapper around `<img>` with rounded corners, responsive fill support,
|
|
6
6
|
* native lazy loading, and an error fallback mechanism.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* re-dispatched
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* The inner `<img>`'s `load`/`error` events don't leave the shadow root, so they
|
|
9
|
+
* are re-dispatched on the host under the same native names, with the `src` in
|
|
10
|
+
* `detail`. Like the native ones they don't bubble, so listen on `<lt-image>`
|
|
11
|
+
* itself. When a load fails, the `fallback` slot is shown in place of the broken
|
|
12
|
+
* image; setting a new `src` retries.
|
|
12
13
|
*
|
|
13
14
|
* @element lt-image
|
|
14
15
|
* @part image - The underlying `<img>` element
|
|
15
16
|
*
|
|
16
17
|
* @slot fallback - Content shown in place of the image when loading fails
|
|
17
18
|
*
|
|
18
|
-
* @fires {CustomEvent<{ src: string }>}
|
|
19
|
-
* @fires {CustomEvent<{ src: string }>}
|
|
19
|
+
* @fires {CustomEvent<{ src: string }>} load - Fired when the image loads successfully
|
|
20
|
+
* @fires {CustomEvent<{ src: string }>} error - Fired when the image fails to load
|
|
20
21
|
*
|
|
21
22
|
* @example
|
|
22
23
|
* ```html
|
|
@@ -50,7 +51,6 @@ export declare class Image extends ThemeableElement {
|
|
|
50
51
|
private _errored;
|
|
51
52
|
private get _borderRadius();
|
|
52
53
|
willUpdate(changed: PropertyValues<this>): void;
|
|
53
|
-
private _dispatch;
|
|
54
54
|
private _handleLoad;
|
|
55
55
|
private _handleError;
|
|
56
56
|
render(): import("lit").TemplateResult<1>;
|
|
@@ -83,21 +83,16 @@ var Image = class extends ThemeableElement {
|
|
|
83
83
|
willUpdate(changed) {
|
|
84
84
|
if (changed.has("src")) this._errored = false;
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
bubbles: true,
|
|
91
|
-
composed: true
|
|
92
|
-
})
|
|
93
|
-
);
|
|
94
|
-
}
|
|
86
|
+
// Literal event names (not a shared helper taking `name`): the manifest analyzer reads names from
|
|
87
|
+
// `new CustomEvent('…')` call sites, and a variable there produced a bogus event called "name".
|
|
88
|
+
// Non-bubbling and non-composed, exactly like the native <img> events they mirror — a bubbling
|
|
89
|
+
// `error` would also reach window-level error handlers (Sentry and friends) as a spurious error.
|
|
95
90
|
_handleLoad() {
|
|
96
|
-
this.
|
|
91
|
+
this.dispatchEvent(new CustomEvent("load", { detail: { src: this.src } }));
|
|
97
92
|
}
|
|
98
93
|
_handleError() {
|
|
99
94
|
this._errored = true;
|
|
100
|
-
this.
|
|
95
|
+
this.dispatchEvent(new CustomEvent("error", { detail: { src: this.src } }));
|
|
101
96
|
}
|
|
102
97
|
render() {
|
|
103
98
|
if (this._errored) {
|
|
@@ -49,7 +49,8 @@ var listStyles = css`
|
|
|
49
49
|
|
|
50
50
|
[part='list'] {
|
|
51
51
|
margin: 0;
|
|
52
|
-
padding: 0
|
|
52
|
+
padding-block: 0;
|
|
53
|
+
padding-inline: var(--lt-spacing-5) 0;
|
|
53
54
|
list-style-position: outside;
|
|
54
55
|
list-style-type: disc;
|
|
55
56
|
color: var(--lt-text-default);
|
|
@@ -73,14 +74,14 @@ var listStyles = css`
|
|
|
73
74
|
display: block;
|
|
74
75
|
margin-top: var(--lt-spacing-2);
|
|
75
76
|
margin-bottom: var(--lt-spacing-2);
|
|
76
|
-
padding-
|
|
77
|
+
padding-inline-start: var(--lt-spacing-5);
|
|
77
78
|
font-size: 0.9em;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
/* No marker */
|
|
81
82
|
:host([no-marker]) [part='list'] {
|
|
82
83
|
list-style: none;
|
|
83
|
-
padding-
|
|
84
|
+
padding-inline-start: 0;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
/* Divider */
|
|
@@ -168,7 +169,7 @@ var listItemStyles = css2`
|
|
|
168
169
|
|
|
169
170
|
lt-icon {
|
|
170
171
|
flex-shrink: 0;
|
|
171
|
-
color: var(--lt-
|
|
172
|
+
color: var(--lt-text-muted);
|
|
172
173
|
font-size: inherit;
|
|
173
174
|
}
|
|
174
175
|
|
|
@@ -212,6 +213,15 @@ var listItemStyles = css2`
|
|
|
212
213
|
opacity: 0.6;
|
|
213
214
|
pointer-events: none;
|
|
214
215
|
}
|
|
216
|
+
|
|
217
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
218
|
+
|
|
219
|
+
@media (prefers-reduced-motion: reduce) {
|
|
220
|
+
a.inner,
|
|
221
|
+
button.inner {
|
|
222
|
+
transition: none;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
215
225
|
`;
|
|
216
226
|
|
|
217
227
|
// src/components/list/list-item.ts
|
|
@@ -85,7 +85,7 @@ var navItemStyles = css`
|
|
|
85
85
|
font-size: var(--lt-nav-item-font-size, 0.9375rem);
|
|
86
86
|
font-family: inherit;
|
|
87
87
|
text-decoration: none;
|
|
88
|
-
text-align:
|
|
88
|
+
text-align: start;
|
|
89
89
|
transition: background 120ms;
|
|
90
90
|
white-space: nowrap;
|
|
91
91
|
}
|
|
@@ -138,7 +138,7 @@ var navItemStyles = css`
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
.children-inner {
|
|
141
|
-
padding-
|
|
141
|
+
padding-inline-start: var(--lt-spacing-4);
|
|
142
142
|
padding-top: var(--lt-spacing-1);
|
|
143
143
|
padding-bottom: var(--lt-spacing-1);
|
|
144
144
|
display: flex;
|
|
@@ -155,11 +155,11 @@ var navItemStyles = css`
|
|
|
155
155
|
:host([orientation='horizontal']) .children {
|
|
156
156
|
position: absolute;
|
|
157
157
|
top: 100%;
|
|
158
|
-
|
|
158
|
+
inset-inline-start: 0;
|
|
159
159
|
min-width: 160px;
|
|
160
160
|
background: var(--lt-bg-default);
|
|
161
|
-
color: var(--lt-
|
|
162
|
-
border: var(--lt-border-width, 1px) solid var(--lt-
|
|
161
|
+
color: var(--lt-text-neutral);
|
|
162
|
+
border: var(--lt-border-width, 1px) solid var(--lt-border-default);
|
|
163
163
|
border-radius: 8px;
|
|
164
164
|
box-shadow:
|
|
165
165
|
0 4px 6px -1px rgb(0 0 0 / 0.08),
|
|
@@ -176,6 +176,24 @@ var navItemStyles = css`
|
|
|
176
176
|
:host([orientation='horizontal']) .children-inner {
|
|
177
177
|
padding: var(--lt-spacing-2);
|
|
178
178
|
}
|
|
179
|
+
|
|
180
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
181
|
+
|
|
182
|
+
@media (prefers-reduced-motion: reduce) {
|
|
183
|
+
.item-trigger,
|
|
184
|
+
.chevron {
|
|
185
|
+
transition: none;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* .children's height transition can't just be removed — nav-item.ts's _handleTransitionEnd() only finalizes
|
|
189
|
+
the open height to 'auto' (letting it respond to later content changes) once the CSS transition actually
|
|
190
|
+
completes; transition:none fires no transitionend at all, leaving it stuck at a fixed pixel height
|
|
191
|
+
forever. A 1ms duration is imperceptible (satisfies the reduced-motion intent) but still fires the event
|
|
192
|
+
the component depends on — same reasoning as lt-accordion's .content. */
|
|
193
|
+
.children {
|
|
194
|
+
transition: height 1ms;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
179
197
|
`;
|
|
180
198
|
|
|
181
199
|
// src/components/nav/nav-item.ts
|
|
@@ -204,6 +222,10 @@ var NavItem = class extends ThemeableElement {
|
|
|
204
222
|
}
|
|
205
223
|
});
|
|
206
224
|
this.requestUpdate();
|
|
225
|
+
void this.updateComplete.then(() => this._syncInitialHeight());
|
|
226
|
+
}
|
|
227
|
+
_syncInitialHeight() {
|
|
228
|
+
if (this.open && this.orientation === "vertical" && this._childrenEl) this._childrenEl.style.height = "auto";
|
|
207
229
|
}
|
|
208
230
|
updated(changed) {
|
|
209
231
|
if (changed.has("open") && this._childrenEl && this._childrenInnerEl && this.orientation === "vertical") {
|
|
@@ -225,21 +247,15 @@ var NavItem = class extends ThemeableElement {
|
|
|
225
247
|
}
|
|
226
248
|
}
|
|
227
249
|
_handleTriggerClick(e) {
|
|
228
|
-
if (this.disabled)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
bubbles: true,
|
|
233
|
-
composed: true,
|
|
234
|
-
cancelable: true
|
|
235
|
-
})
|
|
236
|
-
);
|
|
237
|
-
if (!proceed) e.preventDefault();
|
|
250
|
+
if (this.disabled) {
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
238
254
|
if (this._hasChildren) {
|
|
239
255
|
e.preventDefault();
|
|
240
256
|
this.open = !this.open;
|
|
241
257
|
this.dispatchEvent(
|
|
242
|
-
new CustomEvent("
|
|
258
|
+
new CustomEvent("toggle", {
|
|
243
259
|
detail: { open: this.open },
|
|
244
260
|
bubbles: true,
|
|
245
261
|
composed: true
|
|
@@ -260,6 +276,8 @@ var NavItem = class extends ThemeableElement {
|
|
|
260
276
|
class="item-trigger"
|
|
261
277
|
href=${this.href}
|
|
262
278
|
aria-current=${ariaCurrent}
|
|
279
|
+
aria-disabled=${this.disabled ? "true" : nothing}
|
|
280
|
+
tabindex=${this.disabled ? "-1" : nothing}
|
|
263
281
|
@click=${this._handleTriggerClick}
|
|
264
282
|
>${inner}</a
|
|
265
283
|
>`;
|
|
@@ -267,6 +285,8 @@ var NavItem = class extends ThemeableElement {
|
|
|
267
285
|
return html`<button
|
|
268
286
|
class="item-trigger"
|
|
269
287
|
aria-current=${ariaCurrent}
|
|
288
|
+
aria-expanded=${showChevron ? this.open : nothing}
|
|
289
|
+
aria-controls=${showChevron ? "children" : nothing}
|
|
270
290
|
@click=${this._handleTriggerClick}
|
|
271
291
|
?disabled=${this.disabled}
|
|
272
292
|
>
|
|
@@ -278,7 +298,7 @@ var NavItem = class extends ThemeableElement {
|
|
|
278
298
|
<div class="item-root">
|
|
279
299
|
${this._renderTrigger()}
|
|
280
300
|
${this._hasChildren ? html`
|
|
281
|
-
<div class="children" @transitionend=${this._handleTransitionEnd}>
|
|
301
|
+
<div id="children" class="children" @transitionend=${this._handleTransitionEnd}>
|
|
282
302
|
<div class="children-inner">
|
|
283
303
|
<slot @slotchange=${this._onSlotChange}></slot>
|
|
284
304
|
</div>
|
|
@@ -9,17 +9,16 @@ import '@latty-ds/icons';
|
|
|
9
9
|
* and nested children appear as a dropdown panel. In vertical mode, nested children
|
|
10
10
|
* collapse/expand inline.
|
|
11
11
|
*
|
|
12
|
-
* SPA router integration: `
|
|
13
|
-
* to stop the anchor's native navigation,
|
|
14
|
-
*
|
|
12
|
+
* SPA router integration: items use the native `click` — listen for it on the `<lt-nav>` (or the item),
|
|
13
|
+
* call `event.preventDefault()` to stop the anchor's native navigation, and read the item from
|
|
14
|
+
* `event.target.closest('lt-nav-item')` (its `href` and `label`). Modifier keys (`event.metaKey`, …) come
|
|
15
|
+
* through as on any link. Drive the highlighted item via the `active` property (which also sets
|
|
16
|
+
* `aria-current="page"`).
|
|
15
17
|
*
|
|
16
18
|
* @element lt-nav-item
|
|
17
19
|
*
|
|
18
|
-
* @fires {CustomEvent<{
|
|
19
|
-
*
|
|
20
|
-
* anchor navigation so an SPA router can handle it.
|
|
21
|
-
* @fires {CustomEvent<{ open: boolean }>} nav-collapse - Fired when a collapsible item
|
|
22
|
-
* is opened or closed.
|
|
20
|
+
* @fires {CustomEvent<{ open: boolean }>} toggle - Fired when a collapsible item is opened or
|
|
21
|
+
* closed, like the native `toggle` of `<details>`.
|
|
23
22
|
*
|
|
24
23
|
* @slot - Nested `<lt-nav-item>` children (makes this item a collapsible group)
|
|
25
24
|
*
|
|
@@ -71,6 +70,7 @@ export declare class NavItem extends ThemeableElement {
|
|
|
71
70
|
private _childrenInnerEl?;
|
|
72
71
|
private _hasChildren;
|
|
73
72
|
private _onSlotChange;
|
|
73
|
+
private _syncInitialHeight;
|
|
74
74
|
protected updated(changed: Map<string, unknown>): void;
|
|
75
75
|
private _handleTransitionEnd;
|
|
76
76
|
private _handleTriggerClick;
|
|
@@ -99,7 +99,7 @@ var paginationStyles = css`
|
|
|
99
99
|
button[aria-current='page'] {
|
|
100
100
|
background: var(--lt-bg-primary-subtle);
|
|
101
101
|
border-color: var(--lt-border-primary);
|
|
102
|
-
color: var(--lt-
|
|
102
|
+
color: var(--lt-text-primary);
|
|
103
103
|
font-weight: 600;
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -109,15 +109,23 @@ var paginationStyles = css`
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
button:disabled {
|
|
112
|
-
color: var(--lt-
|
|
112
|
+
color: var(--lt-text-disabled);
|
|
113
113
|
cursor: not-allowed;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
:host([disabled]) button {
|
|
117
|
-
color: var(--lt-
|
|
117
|
+
color: var(--lt-text-disabled);
|
|
118
118
|
cursor: not-allowed;
|
|
119
119
|
pointer-events: none;
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
123
|
+
|
|
124
|
+
@media (prefers-reduced-motion: reduce) {
|
|
125
|
+
button {
|
|
126
|
+
transition: none;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
121
129
|
`;
|
|
122
130
|
|
|
123
131
|
// src/utils/dispatch.ts
|
|
@@ -125,6 +133,14 @@ var dispatch = (host, name, detail, init) => host.dispatchEvent(new CustomEvent(
|
|
|
125
133
|
|
|
126
134
|
// src/components/pagination/pagination.ts
|
|
127
135
|
import "@latty-ds/icons";
|
|
136
|
+
var DEFAULT_LABELS = {
|
|
137
|
+
navigation: "Pagination",
|
|
138
|
+
firstPage: "First page",
|
|
139
|
+
previousPage: "Previous page",
|
|
140
|
+
page: (n) => `Page ${n}`,
|
|
141
|
+
nextPage: "Next page",
|
|
142
|
+
lastPage: "Last page"
|
|
143
|
+
};
|
|
128
144
|
var Pagination = class extends ThemeableElement {
|
|
129
145
|
constructor() {
|
|
130
146
|
super(...arguments);
|
|
@@ -132,15 +148,29 @@ var Pagination = class extends ThemeableElement {
|
|
|
132
148
|
this.totalPages = 1;
|
|
133
149
|
this.size = "md";
|
|
134
150
|
this.disabled = false;
|
|
151
|
+
this.labels = {};
|
|
152
|
+
}
|
|
153
|
+
get _labels() {
|
|
154
|
+
return { ...DEFAULT_LABELS, ...this.labels };
|
|
155
|
+
}
|
|
156
|
+
/** `total-pages` as a whole number >= 1: NaN, zero and negatives collapse to a single page. */
|
|
157
|
+
get _total() {
|
|
158
|
+
const total = Math.floor(Number(this.totalPages));
|
|
159
|
+
return Number.isFinite(total) && total >= 1 ? total : 1;
|
|
160
|
+
}
|
|
161
|
+
/** `page` clamped into 1.._total, so `page="99"` on a 10-page set is the last page, not "no page selected". */
|
|
162
|
+
get _current() {
|
|
163
|
+
const page = Math.floor(Number(this.page));
|
|
164
|
+
return Number.isFinite(page) ? Math.min(Math.max(page, 1), this._total) : 1;
|
|
135
165
|
}
|
|
136
166
|
_go(page) {
|
|
137
|
-
if (this.disabled || page < 1 || page > this.
|
|
167
|
+
if (this.disabled || page < 1 || page > this._total || page === this._current) return;
|
|
138
168
|
this.page = page;
|
|
139
169
|
dispatch(this, "change", { page });
|
|
140
170
|
}
|
|
141
171
|
_pages() {
|
|
142
|
-
const total = this.
|
|
143
|
-
const current = this.
|
|
172
|
+
const total = this._total;
|
|
173
|
+
const current = this._current;
|
|
144
174
|
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
|
|
145
175
|
const pages = [1];
|
|
146
176
|
if (current > 3) pages.push("\u2026");
|
|
@@ -153,25 +183,25 @@ var Pagination = class extends ThemeableElement {
|
|
|
153
183
|
}
|
|
154
184
|
render() {
|
|
155
185
|
const pages = this._pages();
|
|
156
|
-
const atFirst = this.
|
|
157
|
-
const atLast = this.
|
|
186
|
+
const atFirst = this._current <= 1;
|
|
187
|
+
const atLast = this._current >= this._total;
|
|
158
188
|
return html`
|
|
159
|
-
<nav aria-label
|
|
160
|
-
<button aria-label
|
|
189
|
+
<nav aria-label=${this._labels.navigation}>
|
|
190
|
+
<button aria-label=${this._labels.firstPage} ?disabled=${atFirst || this.disabled} @click=${() => this._go(1)}>
|
|
161
191
|
<lt-icon name="double-caret-left" size="sm"></lt-icon>
|
|
162
192
|
</button>
|
|
163
193
|
<button
|
|
164
|
-
aria-label
|
|
194
|
+
aria-label=${this._labels.previousPage}
|
|
165
195
|
?disabled=${atFirst || this.disabled}
|
|
166
|
-
@click=${() => this._go(this.
|
|
196
|
+
@click=${() => this._go(this._current - 1)}
|
|
167
197
|
>
|
|
168
198
|
<lt-icon name="caret-left" size="sm"></lt-icon>
|
|
169
199
|
</button>
|
|
170
200
|
|
|
171
201
|
${pages.map(
|
|
172
202
|
(p) => p === "\u2026" ? html`<button class="ellipsis" aria-hidden="true" tabindex="-1">…</button>` : html`<button
|
|
173
|
-
aria-label
|
|
174
|
-
aria-current=${p === this.
|
|
203
|
+
aria-label=${this._labels.page(p)}
|
|
204
|
+
aria-current=${p === this._current ? "page" : nothing}
|
|
175
205
|
?disabled=${this.disabled}
|
|
176
206
|
@click=${() => this._go(p)}
|
|
177
207
|
>
|
|
@@ -179,10 +209,18 @@ var Pagination = class extends ThemeableElement {
|
|
|
179
209
|
</button>`
|
|
180
210
|
)}
|
|
181
211
|
|
|
182
|
-
<button
|
|
212
|
+
<button
|
|
213
|
+
aria-label=${this._labels.nextPage}
|
|
214
|
+
?disabled=${atLast || this.disabled}
|
|
215
|
+
@click=${() => this._go(this._current + 1)}
|
|
216
|
+
>
|
|
183
217
|
<lt-icon name="caret-right" size="sm"></lt-icon>
|
|
184
218
|
</button>
|
|
185
|
-
<button
|
|
219
|
+
<button
|
|
220
|
+
aria-label=${this._labels.lastPage}
|
|
221
|
+
?disabled=${atLast || this.disabled}
|
|
222
|
+
@click=${() => this._go(this._total)}
|
|
223
|
+
>
|
|
186
224
|
<lt-icon name="double-caret-right" size="sm"></lt-icon>
|
|
187
225
|
</button>
|
|
188
226
|
</nav>
|
|
@@ -202,6 +240,9 @@ __decorateClass([
|
|
|
202
240
|
__decorateClass([
|
|
203
241
|
property2({ type: Boolean, reflect: true })
|
|
204
242
|
], Pagination.prototype, "disabled", 2);
|
|
243
|
+
__decorateClass([
|
|
244
|
+
property2({ type: Object })
|
|
245
|
+
], Pagination.prototype, "labels", 2);
|
|
205
246
|
Pagination = __decorateClass([
|
|
206
247
|
customElement("lt-pagination")
|
|
207
248
|
], Pagination);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ThemeableElement } from '../../base';
|
|
2
|
-
import type { PaginationSize } from './pagination.types';
|
|
2
|
+
import type { PaginationSize, PaginationLabels } from './pagination.types';
|
|
3
3
|
import '@latty-ds/icons';
|
|
4
4
|
/**
|
|
5
5
|
* Page navigation control for paged data sets.
|
|
@@ -28,6 +28,13 @@ export declare class Pagination extends ThemeableElement {
|
|
|
28
28
|
size: PaginationSize;
|
|
29
29
|
/** Disable all controls. */
|
|
30
30
|
disabled: boolean;
|
|
31
|
+
/** Overridable UI text (nav landmark label, control button labels), for i18n. English defaults — override only the keys you need. */
|
|
32
|
+
labels: Partial<PaginationLabels>;
|
|
33
|
+
private get _labels();
|
|
34
|
+
/** `total-pages` as a whole number >= 1: NaN, zero and negatives collapse to a single page. */
|
|
35
|
+
private get _total();
|
|
36
|
+
/** `page` clamped into 1.._total, so `page="99"` on a 10-page set is the last page, not "no page selected". */
|
|
37
|
+
private get _current();
|
|
31
38
|
private _go;
|
|
32
39
|
private _pages;
|
|
33
40
|
render(): import("lit").TemplateResult<1>;
|
|
@@ -1 +1,16 @@
|
|
|
1
1
|
export type PaginationSize = 'sm' | 'md' | 'lg';
|
|
2
|
+
/** Overridable UI text for `lt-pagination`, for i18n. */
|
|
3
|
+
export interface PaginationLabels {
|
|
4
|
+
/** Accessible landmark label for the pagination `<nav>`. */
|
|
5
|
+
navigation: string;
|
|
6
|
+
/** Accessible label for the "jump to first page" button. */
|
|
7
|
+
firstPage: string;
|
|
8
|
+
/** Accessible label for the "go to previous page" button. */
|
|
9
|
+
previousPage: string;
|
|
10
|
+
/** Accessible label for a numbered page button, given its 1-indexed page number. */
|
|
11
|
+
page: (n: number) => string;
|
|
12
|
+
/** Accessible label for the "go to next page" button. */
|
|
13
|
+
nextPage: string;
|
|
14
|
+
/** Accessible label for the "jump to last page" button. */
|
|
15
|
+
lastPage: string;
|
|
16
|
+
}
|
|
@@ -34,6 +34,13 @@ __decorateClass([
|
|
|
34
34
|
property({ reflect: true })
|
|
35
35
|
], ThemeableElement.prototype, "theme", 2);
|
|
36
36
|
|
|
37
|
+
// src/utils/dev-warn.ts
|
|
38
|
+
var devWarn = (tag, message) => {
|
|
39
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
|
|
40
|
+
console.warn(`[${tag}] ${message}`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
37
44
|
// src/components/progress/progress.ts
|
|
38
45
|
import { customElement, property as property2 } from "lit/decorators.js";
|
|
39
46
|
|
|
@@ -61,11 +68,11 @@ var progressStyles = css`
|
|
|
61
68
|
--_fill-color: var(--lt-interactive-error-bg);
|
|
62
69
|
}
|
|
63
70
|
:host([variant='neutral']) {
|
|
64
|
-
--_fill-color: var(--lt-
|
|
71
|
+
--_fill-color: var(--lt-interactive-neutral-bg);
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
[part='track'] {
|
|
68
|
-
background: var(--lt-progress-track-color, var(--lt-
|
|
75
|
+
background: var(--lt-progress-track-color, var(--lt-bg-overlay));
|
|
69
76
|
border-radius: 999px;
|
|
70
77
|
overflow: hidden;
|
|
71
78
|
width: 100%;
|
|
@@ -115,6 +122,23 @@ var progressStyles = css`
|
|
|
115
122
|
width: 100%;
|
|
116
123
|
}
|
|
117
124
|
}
|
|
125
|
+
|
|
126
|
+
/* ── Forced colors (Windows High Contrast) ─────────────────────────────────
|
|
127
|
+
Under forced-colors, both the track's neutral background and the fill's variant-coloured background are
|
|
128
|
+
flattened to Canvas, with no border on the track to fall back on — the whole bar disappears. A border on
|
|
129
|
+
the track (its boundary) plus a Highlight-coloured fill (the OS's own "active/selected" system color,
|
|
130
|
+
exempt from the flattening) keeps the progress amount legible regardless of which variant was set —
|
|
131
|
+
forced-colors mode overrides variant branding anyway, by design. ───────────────────────────────────────── */
|
|
132
|
+
|
|
133
|
+
@media (forced-colors: active) {
|
|
134
|
+
[part='track'] {
|
|
135
|
+
border: 1px solid CanvasText;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
[part='fill'] {
|
|
139
|
+
background: Highlight;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
118
142
|
`;
|
|
119
143
|
|
|
120
144
|
// src/components/progress/progress.ts
|
|
@@ -125,20 +149,28 @@ var Progress = class extends ThemeableElement {
|
|
|
125
149
|
this.variant = "primary";
|
|
126
150
|
this.size = "md";
|
|
127
151
|
this.label = "";
|
|
152
|
+
this.max = 100;
|
|
153
|
+
this.valueText = "";
|
|
128
154
|
this.indeterminate = false;
|
|
129
155
|
}
|
|
156
|
+
firstUpdated() {
|
|
157
|
+
if (!this.label) devWarn("lt-progress", "no `label` \u2014 set one so assistive tech can name the progress bar");
|
|
158
|
+
}
|
|
130
159
|
render() {
|
|
131
|
-
const
|
|
160
|
+
const max = Number.isFinite(this.max) && this.max > 0 ? this.max : 100;
|
|
161
|
+
const value = Number.isFinite(this.value) ? Math.min(max, Math.max(0, this.value)) : 0;
|
|
162
|
+
const fillPct = value / max * 100;
|
|
132
163
|
return html`
|
|
133
164
|
<div
|
|
134
165
|
part="track"
|
|
135
166
|
role="progressbar"
|
|
136
167
|
aria-label=${this.label || nothing}
|
|
137
|
-
aria-valuenow=${this.indeterminate ? nothing :
|
|
168
|
+
aria-valuenow=${this.indeterminate ? nothing : value}
|
|
138
169
|
aria-valuemin="0"
|
|
139
|
-
aria-valuemax
|
|
170
|
+
aria-valuemax=${max}
|
|
171
|
+
aria-valuetext=${this.indeterminate || !this.valueText ? nothing : this.valueText}
|
|
140
172
|
>
|
|
141
|
-
<div part="fill" style=${this.indeterminate ? nothing : `width:${
|
|
173
|
+
<div part="fill" style=${this.indeterminate ? nothing : `width:${fillPct}%`}></div>
|
|
142
174
|
</div>
|
|
143
175
|
`;
|
|
144
176
|
}
|
|
@@ -156,6 +188,12 @@ __decorateClass([
|
|
|
156
188
|
__decorateClass([
|
|
157
189
|
property2({ reflect: true })
|
|
158
190
|
], Progress.prototype, "label", 2);
|
|
191
|
+
__decorateClass([
|
|
192
|
+
property2({ type: Number })
|
|
193
|
+
], Progress.prototype, "max", 2);
|
|
194
|
+
__decorateClass([
|
|
195
|
+
property2({ attribute: "value-text" })
|
|
196
|
+
], Progress.prototype, "valueText", 2);
|
|
159
197
|
__decorateClass([
|
|
160
198
|
property2({ type: Boolean, reflect: true })
|
|
161
199
|
], Progress.prototype, "indeterminate", 2);
|
|
@@ -35,10 +35,22 @@ export declare class Progress extends ThemeableElement {
|
|
|
35
35
|
* @default ''
|
|
36
36
|
*/
|
|
37
37
|
label: string;
|
|
38
|
+
/**
|
|
39
|
+
* The value `value` is measured against (0–100 becomes 0–max). Ignored when `indeterminate` is true.
|
|
40
|
+
* @default 100
|
|
41
|
+
*/
|
|
42
|
+
max: number;
|
|
43
|
+
/**
|
|
44
|
+
* Overrides the spoken value with a custom string (e.g. "3 of 5 steps") for scales a bare percentage
|
|
45
|
+
* doesn't describe well. Leave unset to let `aria-valuenow`/`aria-valuemax` speak for themselves.
|
|
46
|
+
* @default ''
|
|
47
|
+
*/
|
|
48
|
+
valueText: string;
|
|
38
49
|
/**
|
|
39
50
|
* When true the bar animates continuously without a set value.
|
|
40
51
|
* @default false
|
|
41
52
|
*/
|
|
42
53
|
indeterminate: boolean;
|
|
54
|
+
protected firstUpdated(): void;
|
|
43
55
|
render(): import("lit").TemplateResult<1>;
|
|
44
56
|
}
|