@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.
Files changed (101) hide show
  1. package/custom-elements.json +1970 -229
  2. package/dist/components/accordion/index.js +36 -2
  3. package/dist/components/alert/alert.d.ts +29 -6
  4. package/dist/components/alert/alert.types.d.ts +5 -0
  5. package/dist/components/alert/index.js +127 -20
  6. package/dist/components/avatar/avatar.d.ts +2 -0
  7. package/dist/components/avatar/index.js +10 -3
  8. package/dist/components/badge/index.js +1 -1
  9. package/dist/components/breadcrumb/breadcrumb.d.ts +4 -0
  10. package/dist/components/breadcrumb/breadcrumb.types.d.ts +5 -0
  11. package/dist/components/breadcrumb/index.js +20 -3
  12. package/dist/components/button/button.d.ts +33 -0
  13. package/dist/components/button/index.js +138 -15
  14. package/dist/components/calendar/calendar.d.ts +15 -7
  15. package/dist/components/calendar/calendar.types.d.ts +17 -1
  16. package/dist/components/calendar/index.js +80 -27
  17. package/dist/components/checkbox/checkbox.d.ts +14 -2
  18. package/dist/components/checkbox/checkbox.types.d.ts +5 -0
  19. package/dist/components/checkbox/index.js +82 -5
  20. package/dist/components/chip/chip.d.ts +4 -1
  21. package/dist/components/chip/chip.types.d.ts +5 -0
  22. package/dist/components/chip/index.js +29 -1
  23. package/dist/components/color-input/color-input.d.ts +11 -2
  24. package/dist/components/color-input/color-input.types.d.ts +5 -0
  25. package/dist/components/color-input/index.js +92 -8
  26. package/dist/components/combobox/combobox.d.ts +18 -2
  27. package/dist/components/combobox/combobox.types.d.ts +7 -0
  28. package/dist/components/combobox/index.js +164 -23
  29. package/dist/components/date-input/date-input.d.ts +22 -7
  30. package/dist/components/date-input/date-input.types.d.ts +7 -0
  31. package/dist/components/date-input/index.js +218 -55
  32. package/dist/components/datepicker/datepicker.d.ts +9 -1
  33. package/dist/components/datepicker/index.js +108 -15
  34. package/dist/components/dialog/dialog.d.ts +39 -19
  35. package/dist/components/dialog/dialog.types.d.ts +5 -0
  36. package/dist/components/dialog/index.js +240 -93
  37. package/dist/components/divider/index.js +2 -2
  38. package/dist/components/dropdown/dropdown-item.d.ts +1 -1
  39. package/dist/components/dropdown/dropdown.d.ts +4 -0
  40. package/dist/components/dropdown/index.js +43 -3
  41. package/dist/components/icon-button/icon-button.d.ts +4 -0
  42. package/dist/components/icon-button/index.js +39 -4
  43. package/dist/components/image/image.d.ts +7 -7
  44. package/dist/components/image/index.js +6 -11
  45. package/dist/components/list/index.js +14 -4
  46. package/dist/components/nav/index.js +37 -17
  47. package/dist/components/nav/nav-item.d.ts +8 -8
  48. package/dist/components/pagination/index.js +57 -16
  49. package/dist/components/pagination/pagination.d.ts +8 -1
  50. package/dist/components/pagination/pagination.types.d.ts +15 -0
  51. package/dist/components/progress/index.js +44 -6
  52. package/dist/components/progress/progress.d.ts +12 -0
  53. package/dist/components/radio/index.js +148 -9
  54. package/dist/components/radio/radio.d.ts +34 -2
  55. package/dist/components/radio/radio.types.d.ts +5 -0
  56. package/dist/components/radio-group/index.js +120 -29
  57. package/dist/components/radio-group/radio-group.d.ts +27 -2
  58. package/dist/components/select/index.js +197 -44
  59. package/dist/components/select/select.d.ts +38 -4
  60. package/dist/components/select/select.types.d.ts +5 -0
  61. package/dist/components/shared/aria-forwarding.d.ts +1 -1
  62. package/dist/components/shared/form-default.d.ts +30 -0
  63. package/dist/components/shared/form-disabled.d.ts +40 -0
  64. package/dist/components/shared/form-field.d.ts +7 -0
  65. package/dist/components/shared/form-validity.d.ts +17 -0
  66. package/dist/components/shared/roving.d.ts +11 -0
  67. package/dist/components/shared/scroll-lock.d.ts +2 -0
  68. package/dist/components/sidepanel/index.js +278 -75
  69. package/dist/components/sidepanel/sidepanel.d.ts +32 -10
  70. package/dist/components/sidepanel/sidepanel.types.d.ts +10 -1
  71. package/dist/components/skeleton/index.js +3 -8
  72. package/dist/components/slider/index.js +307 -75
  73. package/dist/components/slider/slider.d.ts +16 -3
  74. package/dist/components/snackbar/index.js +82 -9
  75. package/dist/components/snackbar/snackbar.d.ts +17 -1
  76. package/dist/components/snackbar/snackbar.types.d.ts +5 -0
  77. package/dist/components/surface/index.js +8 -0
  78. package/dist/components/switch/index.js +101 -5
  79. package/dist/components/switch/switch.d.ts +13 -2
  80. package/dist/components/switch/switch.types.d.ts +5 -0
  81. package/dist/components/tab/index.js +15 -14
  82. package/dist/components/tab/tab.d.ts +0 -1
  83. package/dist/components/tab-group/index.js +102 -30
  84. package/dist/components/tab-group/tab-group.d.ts +17 -2
  85. package/dist/components/table/index.js +105 -61
  86. package/dist/components/table/table.d.ts +8 -2
  87. package/dist/components/table/table.types.d.ts +4 -3
  88. package/dist/components/text/index.js +16 -1
  89. package/dist/components/text/text.d.ts +1 -1
  90. package/dist/components/textfield/index.js +243 -35
  91. package/dist/components/textfield/textfield.d.ts +24 -2
  92. package/dist/components/textfield/textfield.types.d.ts +11 -0
  93. package/dist/components/tooltip/index.js +176 -49
  94. package/dist/components/tooltip/tooltip.d.ts +17 -0
  95. package/dist/components/tooltip/tooltip.types.d.ts +5 -1
  96. package/dist/index.cjs +2467 -692
  97. package/dist/index.js +2478 -699
  98. package/dist/manifest.json +202 -32
  99. package/dist/utils/dev-warn.d.ts +5 -0
  100. package/dist/utils/index.d.ts +1 -0
  101. package/package.json +4 -8
@@ -171,6 +171,24 @@ var accordionStyles = css`
171
171
  letter-spacing: 0.05rem;
172
172
  text-transform: lowercase;
173
173
  }
174
+
175
+ /* ── Reduced motion ─────────────────────────────────────────────────────── */
176
+
177
+ @media (prefers-reduced-motion: reduce) {
178
+ details,
179
+ .chevron {
180
+ transition: none;
181
+ }
182
+
183
+ /* .content's height transition can't just be removed — accordion.ts's handleTransitionEnd() clears
184
+ _animating and finalizes styles only when the CSS transition actually completes; transition:none fires no
185
+ transitionend at all, leaving _animating stuck true after the very first toggle and silently breaking
186
+ every subsequent open/close. A 1ms duration is imperceptible (satisfies the reduced-motion intent) but
187
+ still fires the event the component's own state machine depends on. */
188
+ .content {
189
+ transition: height 1ms;
190
+ }
191
+ }
174
192
  `;
175
193
 
176
194
  // src/components/accordion/accordion.ts
@@ -345,6 +363,20 @@ var textStyles = css2`
345
363
  `;
346
364
 
347
365
  // src/components/text/text.ts
366
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
367
+ "h1",
368
+ "h2",
369
+ "h3",
370
+ "h4",
371
+ "h5",
372
+ "h6",
373
+ "p",
374
+ "span",
375
+ "div",
376
+ "label",
377
+ "strong",
378
+ "em"
379
+ ]);
348
380
  var VARIANT_TAG = {
349
381
  "display-2xl": "p",
350
382
  "display-xl": "p",
@@ -369,7 +401,8 @@ var Text = class extends ThemeableElement {
369
401
  this.as = "";
370
402
  }
371
403
  render() {
372
- const tag = unsafeStatic(this.as || VARIANT_TAG[this.variant] || "p");
404
+ const requested = this.as && ALLOWED_TAGS.has(this.as) ? this.as : "";
405
+ const tag = unsafeStatic(requested || VARIANT_TAG[this.variant] || "p");
373
406
  return html`<${tag} part="base"><slot></slot></${tag}>`;
374
407
  }
375
408
  };
@@ -439,7 +472,8 @@ var Accordion = class extends ThemeableElement {
439
472
  * Handles the end of the transition to clear inline styles.
440
473
  * @private
441
474
  */
442
- handleTransitionEnd() {
475
+ handleTransitionEnd(e) {
476
+ if (e.target !== this._content || e.propertyName !== "height") return;
443
477
  this._animating = false;
444
478
  if (this.open) {
445
479
  this._content.style.height = "auto";
@@ -1,5 +1,6 @@
1
+ import { type PropertyValues } from 'lit';
1
2
  import { ThemeableElement } from '../../base';
2
- import type { AlertVariant, AlertAppearance } from './alert.types';
3
+ import type { AlertVariant, AlertAppearance, AlertLabels } from './alert.types';
3
4
  import '../icon-button/icon-button';
4
5
  import '../text/text';
5
6
  /**
@@ -9,11 +10,11 @@ import '../text/text';
9
10
  *
10
11
  * @slot - Alert body. Accepts any HTML — bold, links, lists, etc.
11
12
  *
12
- * @fires close - Fired when the dismiss button is clicked, just before the element is removed.
13
+ * @fires close - Fired when the dismiss button is clicked, just before the element is hidden. Cancelable.
13
14
  *
14
15
  * @example
15
16
  * ```html
16
- * <lt-alert variant="success" title="Upload complete">
17
+ * <lt-alert variant="success" heading="Upload complete">
17
18
  * Your file has been processed and is ready to download.
18
19
  * </lt-alert>
19
20
  * ```
@@ -32,20 +33,42 @@ export declare class Alert extends ThemeableElement {
32
33
  /** Visual treatment. `filled` uses a tinted background with border, `outlined` uses a white background with border, `solid` uses a solid color background with no border. */
33
34
  appearance: AlertAppearance;
34
35
  /** Optional bold heading above the body content. */
35
- title: string;
36
+ heading: string;
37
+ /**
38
+ * @deprecated Use `heading` instead. `title` collides with the browser's own native tooltip attribute — every
39
+ * HTML element has one regardless of custom-element status, so `<lt-alert title="…">` made hovering anywhere
40
+ * over the alert show a browser tooltip, and assistive tech could read it as a duplicate description (ALR-3).
41
+ * This alias keeps existing markup/JS working; the native `title` attribute is always stripped as soon as
42
+ * it's read, so the tooltip never appears.
43
+ */
44
+ get title(): string;
45
+ set title(value: string);
36
46
  /**
37
47
  * Icon to show before the body. Leave empty for auto (shows variant icon for status variants),
38
48
  * set to `"none"` to suppress, or pass any icon name to override.
39
49
  */
40
50
  icon: string;
41
- /** Show a dismiss button. Clicking it fires lt-close and removes the element. */
51
+ /** Show a dismiss button. Clicking it fires `close` and hides the element (sets `open` to false). */
42
52
  closable: boolean;
53
+ /**
54
+ * Whether the alert is visible. Defaults to true, so existing markup with no attribute keeps showing.
55
+ * Dismissing (or setting this to false directly) hides the alert but does not remove it from the DOM —
56
+ * that choice is left to the consumer, since a framework that owns this node would otherwise fight the
57
+ * alert over removing it out from under its own reconciliation.
58
+ */
59
+ open: boolean;
43
60
  /** Background color override. Accepts a hex value (#1a1a2e) or a CSS token name (--lt-color-primary-600). */
44
61
  background: string;
45
- /** Renders the title in small caps with wider letter spacing. */
62
+ /** Renders the heading in small caps with wider letter spacing. */
46
63
  uppercase: boolean;
64
+ /** Overridable UI text (the dismiss button's accessible name), for i18n. English defaults — override only the keys you need. */
65
+ labels: Partial<AlertLabels>;
66
+ private get _labels();
67
+ connectedCallback(): void;
47
68
  private static readonly _iconMap;
48
69
  private _handleClose;
70
+ updated(changed: PropertyValues<this>): void;
71
+ private get _role();
49
72
  private get _resolvedIcon();
50
73
  render(): import("lit").TemplateResult<1>;
51
74
  }
@@ -1,2 +1,7 @@
1
1
  export type AlertVariant = 'default' | 'success' | 'warning' | 'error' | 'info';
2
2
  export type AlertAppearance = 'filled' | 'outlined' | 'solid';
3
+ /** Overridable UI text, for i18n — English defaults; override only the keys you need. */
4
+ export interface AlertLabels {
5
+ /** Accessible label for the dismiss button. */
6
+ dismiss: string;
7
+ }
@@ -63,11 +63,16 @@ var alertStyles = css`
63
63
  transform: translateY(-4px);
64
64
  }
65
65
 
66
+ /* Hidden once the fade above finishes. Not removed from the DOM — see the open property's doc comment. */
67
+ :host(:not([open])) {
68
+ display: none;
69
+ }
70
+
66
71
  /* ── Per-variant tokens ──────────────────────────────────── */
67
72
 
68
73
  :host([variant='success']) {
69
74
  --_bg-subtle: var(--lt-alert-bg, var(--lt-bg-success-subtle));
70
- --_bg-solid: var(--lt-alert-bg, var(--lt-bg-success));
75
+ --_bg-solid: var(--lt-alert-bg, var(--lt-interactive-success-bg));
71
76
  --_border: var(--lt-alert-border-color, var(--lt-border-success-strong));
72
77
  --_color: var(--lt-alert-color, var(--lt-text-success));
73
78
  --_color-solid: var(--lt-alert-color, var(--lt-text-on-success));
@@ -75,7 +80,7 @@ var alertStyles = css`
75
80
 
76
81
  :host([variant='warning']) {
77
82
  --_bg-subtle: var(--lt-alert-bg, var(--lt-bg-warning-subtle));
78
- --_bg-solid: var(--lt-alert-bg, var(--lt-bg-warning));
83
+ --_bg-solid: var(--lt-alert-bg, var(--lt-interactive-warning-bg));
79
84
  --_border: var(--lt-alert-border-color, var(--lt-border-warning-strong));
80
85
  --_color: var(--lt-alert-color, var(--lt-text-warning));
81
86
  --_color-solid: var(--lt-alert-color, var(--lt-text-on-warning));
@@ -83,7 +88,7 @@ var alertStyles = css`
83
88
 
84
89
  :host([variant='error']) {
85
90
  --_bg-subtle: var(--lt-alert-bg, var(--lt-bg-error-subtle));
86
- --_bg-solid: var(--lt-alert-bg, var(--lt-bg-error));
91
+ --_bg-solid: var(--lt-alert-bg, var(--lt-interactive-error-bg));
87
92
  --_border: var(--lt-alert-border-color, var(--lt-border-error-strong));
88
93
  --_color: var(--lt-alert-color, var(--lt-text-error));
89
94
  --_color-solid: var(--lt-alert-color, var(--lt-text-on-error));
@@ -91,7 +96,7 @@ var alertStyles = css`
91
96
 
92
97
  :host([variant='info']) {
93
98
  --_bg-subtle: var(--lt-alert-bg, var(--lt-bg-info-subtle));
94
- --_bg-solid: var(--lt-alert-bg, var(--lt-bg-info));
99
+ --_bg-solid: var(--lt-alert-bg, var(--lt-interactive-info-bg));
95
100
  --_border: var(--lt-alert-border-color, var(--lt-border-info-strong));
96
101
  --_color: var(--lt-alert-color, var(--lt-text-info));
97
102
  --_color-solid: var(--lt-alert-color, var(--lt-text-on-info));
@@ -124,7 +129,8 @@ var alertStyles = css`
124
129
  color: var(--_color);
125
130
  }
126
131
 
127
- /* solid — solid color bg, no border */
132
+ /* solid — solid color bg, no border. Fills come from the interactive tokens, the same ones the on-colour is
133
+ picked against, so the text holds AA; body text is never dimmed with opacity, which would only lower it. */
128
134
  :host([appearance='solid']) .inner {
129
135
  background: var(--_bg-solid);
130
136
  border: none;
@@ -155,10 +161,6 @@ var alertStyles = css`
155
161
  line-height: 1.5;
156
162
  }
157
163
 
158
- .title + .content {
159
- opacity: 0.8;
160
- }
161
-
162
164
  /* ── Close button ────────────────────────────────────────── */
163
165
 
164
166
  .close {
@@ -176,6 +178,14 @@ var alertStyles = css`
176
178
  letter-spacing: 0.05rem;
177
179
  text-transform: lowercase;
178
180
  }
181
+
182
+ /* ── Reduced motion ─────────────────────────────────────────────────────── */
183
+
184
+ @media (prefers-reduced-motion: reduce) {
185
+ :host {
186
+ transition: none;
187
+ }
188
+ }
179
189
  `;
180
190
 
181
191
  // src/utils/dispatch.ts
@@ -184,6 +194,13 @@ var dispatch = (host, name, detail, init) => host.dispatchEvent(new CustomEvent(
184
194
  // src/utils/color.ts
185
195
  var resolveColorValue = (value) => value.startsWith("--") ? `var(${value})` : value;
186
196
 
197
+ // src/utils/dev-warn.ts
198
+ var devWarn = (tag, message) => {
199
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
200
+ console.warn(`[${tag}] ${message}`);
201
+ }
202
+ };
203
+
187
204
  // src/components/icon-button/icon-button.ts
188
205
  import { html, nothing } from "lit";
189
206
  import { customElement, property as property2 } from "lit/decorators.js";
@@ -321,11 +338,11 @@ var iconButtonStyles = css2`
321
338
  --_active-bg: var(--lt-interactive-secondary-bg-active);
322
339
  }
323
340
  :host([appearance='filled'][variant='neutral']) {
324
- --_bg: var(--lt-color-neutral-500);
341
+ --_bg: var(--lt-interactive-neutral-bg);
325
342
  --_color: var(--lt-color-neutral-50);
326
- --_hover-bg: var(--lt-color-neutral-700);
343
+ --_hover-bg: var(--lt-interactive-neutral-bg-hover);
327
344
  --_hover-color: var(--lt-color-neutral-50);
328
- --_active-bg: var(--lt-color-neutral-800);
345
+ --_active-bg: var(--lt-interactive-neutral-bg-active);
329
346
  }
330
347
  :host([appearance='filled'][variant='success']) {
331
348
  --_bg: var(--lt-interactive-success-bg);
@@ -435,6 +452,14 @@ var iconButtonStyles = css2`
435
452
  :host([round]) [part='base'] {
436
453
  border-radius: 50%;
437
454
  }
455
+
456
+ /* ── Reduced motion ─────────────────────────────────────────────────────── */
457
+
458
+ @media (prefers-reduced-motion: reduce) {
459
+ [part='base'] {
460
+ transition: none;
461
+ }
462
+ }
438
463
  `;
439
464
 
440
465
  // src/components/shared/aria-forwarding.ts
@@ -443,7 +468,8 @@ var FORWARDED_ARIA_ATTRIBUTES = [
443
468
  "aria-expanded",
444
469
  "aria-haspopup",
445
470
  "aria-controls",
446
- "aria-current"
471
+ "aria-current",
472
+ "aria-description"
447
473
  ];
448
474
  var AriaForwardingController = class {
449
475
  constructor(host, getTarget) {
@@ -483,11 +509,30 @@ var IconButton = class extends ThemeableElement {
483
509
  this.bordered = false;
484
510
  this.size = "md";
485
511
  this.disabled = false;
512
+ this._blockClickWhileDisabled = (e) => {
513
+ if (this.disabled) {
514
+ e.preventDefault();
515
+ e.stopImmediatePropagation();
516
+ }
517
+ };
486
518
  this.href = "";
487
519
  this.target = "";
488
520
  this.rel = "";
489
521
  this.round = false;
490
522
  }
523
+ firstUpdated() {
524
+ if (!this.label && !this.textContent?.trim()) {
525
+ devWarn("lt-icon-button", "no label or visible text \u2014 set `label` so screen readers can name the button");
526
+ }
527
+ }
528
+ connectedCallback() {
529
+ super.connectedCallback();
530
+ this.addEventListener("click", this._blockClickWhileDisabled, { capture: true });
531
+ }
532
+ disconnectedCallback() {
533
+ super.disconnectedCallback();
534
+ this.removeEventListener("click", this._blockClickWhileDisabled, { capture: true });
535
+ }
491
536
  _inner() {
492
537
  return this.icon ? html`<lt-icon name=${this.icon}></lt-icon>` : html`<slot></slot>`;
493
538
  }
@@ -721,6 +766,20 @@ var textStyles = css3`
721
766
  `;
722
767
 
723
768
  // src/components/text/text.ts
769
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
770
+ "h1",
771
+ "h2",
772
+ "h3",
773
+ "h4",
774
+ "h5",
775
+ "h6",
776
+ "p",
777
+ "span",
778
+ "div",
779
+ "label",
780
+ "strong",
781
+ "em"
782
+ ]);
724
783
  var VARIANT_TAG = {
725
784
  "display-2xl": "p",
726
785
  "display-xl": "p",
@@ -745,7 +804,8 @@ var Text = class extends ThemeableElement {
745
804
  this.as = "";
746
805
  }
747
806
  render() {
748
- const tag = unsafeStatic(this.as || VARIANT_TAG[this.variant] || "p");
807
+ const requested = this.as && ALLOWED_TAGS.has(this.as) ? this.as : "";
808
+ const tag = unsafeStatic(requested || VARIANT_TAG[this.variant] || "p");
749
809
  return html2`<${tag} part="base"><slot></slot></${tag}>`;
750
810
  }
751
811
  };
@@ -761,22 +821,63 @@ Text = __decorateClass([
761
821
  ], Text);
762
822
 
763
823
  // src/components/alert/alert.ts
824
+ var DEFAULT_LABELS = {
825
+ dismiss: "Dismiss"
826
+ };
764
827
  var Alert = class extends ThemeableElement {
765
828
  constructor() {
766
829
  super(...arguments);
767
830
  this.variant = "default";
768
831
  this.appearance = "filled";
769
- this.title = "";
832
+ this.heading = "";
770
833
  this.icon = "";
771
834
  this.closable = false;
835
+ this.open = true;
772
836
  this.background = "";
773
837
  this.uppercase = false;
838
+ this.labels = {};
839
+ }
840
+ /**
841
+ * @deprecated Use `heading` instead. `title` collides with the browser's own native tooltip attribute — every
842
+ * HTML element has one regardless of custom-element status, so `<lt-alert title="…">` made hovering anywhere
843
+ * over the alert show a browser tooltip, and assistive tech could read it as a duplicate description (ALR-3).
844
+ * This alias keeps existing markup/JS working; the native `title` attribute is always stripped as soon as
845
+ * it's read, so the tooltip never appears.
846
+ */
847
+ get title() {
848
+ return this.heading;
849
+ }
850
+ set title(value) {
851
+ this.heading = value;
852
+ }
853
+ get _labels() {
854
+ return { ...DEFAULT_LABELS, ...this.labels };
855
+ }
856
+ connectedCallback() {
857
+ super.connectedCallback();
858
+ if (this.hasAttribute("title")) {
859
+ if (!this.heading) this.heading = this.getAttribute("title");
860
+ this.removeAttribute("title");
861
+ }
774
862
  }
775
863
  _handleClose() {
776
864
  const cancelled = !dispatch(this, "close", void 0, { cancelable: true });
777
865
  if (cancelled) return;
866
+ this.tabIndex = -1;
867
+ this.focus();
778
868
  this.setAttribute("dismissed", "");
779
- setTimeout(() => this.remove(), 200);
869
+ setTimeout(() => {
870
+ this.open = false;
871
+ }, 200);
872
+ }
873
+ updated(changed) {
874
+ super.updated(changed);
875
+ if (changed.has("open") && this.open) this.removeAttribute("dismissed");
876
+ }
877
+ // Error/warning alerts are urgent enough to interrupt the screen reader (`alert`, assertive); the rest are
878
+ // informational and should announce without cutting off whatever the user was already hearing (`status`, polite).
879
+ get _role() {
880
+ return this.variant === "error" || this.variant === "warning" ? "alert" : "status";
780
881
  }
781
882
  get _resolvedIcon() {
782
883
  if (this.icon === "none") return "";
@@ -788,17 +889,17 @@ var Alert = class extends ThemeableElement {
788
889
  const iconName = this._resolvedIcon;
789
890
  const bgStyle = styleMap(this.background ? { background: resolveColorValue(this.background) } : {});
790
891
  return html3`
791
- <div class="inner" part="base" role="alert" style=${bgStyle}>
892
+ <div class="inner" part="base" role=${this._role} style=${bgStyle}>
792
893
  ${iconName ? html3`<lt-icon class="icon" name=${iconName} part="icon"></lt-icon>` : ""}
793
894
  <div class="body">
794
- ${this.title ? html3`<lt-text variant="h6" as="p" class="title" part="title">${this.title}</lt-text>` : ""}
895
+ ${this.heading ? html3`<lt-text variant="h6" as="p" class="title" part="title">${this.heading}</lt-text>` : ""}
795
896
  <div class="content" part="content"><slot></slot></div>
796
897
  </div>
797
898
  ${this.closable ? html3`<lt-icon-button
798
899
  class="close"
799
900
  part="close"
800
901
  icon="xmark"
801
- label="Dismiss"
902
+ label=${this._labels.dismiss}
802
903
  size="sm"
803
904
  @click=${this._handleClose}
804
905
  ></lt-icon-button>` : ""}
@@ -822,19 +923,25 @@ __decorateClass([
822
923
  ], Alert.prototype, "appearance", 2);
823
924
  __decorateClass([
824
925
  property4({ reflect: true })
825
- ], Alert.prototype, "title", 2);
926
+ ], Alert.prototype, "heading", 2);
826
927
  __decorateClass([
827
928
  property4({ reflect: true })
828
929
  ], Alert.prototype, "icon", 2);
829
930
  __decorateClass([
830
931
  property4({ type: Boolean, reflect: true })
831
932
  ], Alert.prototype, "closable", 2);
933
+ __decorateClass([
934
+ property4({ type: Boolean, reflect: true })
935
+ ], Alert.prototype, "open", 2);
832
936
  __decorateClass([
833
937
  property4({ reflect: true })
834
938
  ], Alert.prototype, "background", 2);
835
939
  __decorateClass([
836
940
  property4({ type: Boolean, reflect: true })
837
941
  ], Alert.prototype, "uppercase", 2);
942
+ __decorateClass([
943
+ property4({ type: Object })
944
+ ], Alert.prototype, "labels", 2);
838
945
  Alert = __decorateClass([
839
946
  customElement3("lt-alert")
840
947
  ], Alert);
@@ -1,4 +1,5 @@
1
1
  import { ThemeableElement } from '../../base';
2
+ import type { PropertyValues } from 'lit';
2
3
  import type { AvatarSize, AvatarShape, AvatarColor } from './avatar.types';
3
4
  import '@latty-ds/icons';
4
5
  /**
@@ -22,6 +23,7 @@ export declare class Avatar extends ThemeableElement {
22
23
  color: AvatarColor;
23
24
  private _imgError;
24
25
  private get _initials();
26
+ willUpdate(changed: PropertyValues<this>): void;
25
27
  private _handleImgError;
26
28
  render(): import("lit").TemplateResult<1>;
27
29
  }
@@ -11,6 +11,7 @@ var __decorateClass = (decorators, target, key, kind) => {
11
11
 
12
12
  // src/components/avatar/avatar.ts
13
13
  import { html } from "lit";
14
+ import { ifDefined } from "lit/directives/if-defined.js";
14
15
 
15
16
  // src/base/themeable-element.ts
16
17
  import { LitElement } from "lit";
@@ -176,17 +177,23 @@ var Avatar = class extends ThemeableElement {
176
177
  this.color = "neutral";
177
178
  this._imgError = false;
178
179
  }
180
+ // Array.from walks code points, so an emoji or other astral character isn't cut in half; empty words are dropped,
181
+ // so a whitespace-only name yields no initials instead of throwing.
179
182
  get _initials() {
180
- return this.name.trim().split(/\s+/).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
183
+ return this.name.split(/\s+/).filter(Boolean).slice(0, 2).map((word) => Array.from(word)[0].toUpperCase()).join("");
184
+ }
185
+ willUpdate(changed) {
186
+ if (changed.has("src")) this._imgError = false;
181
187
  }
182
188
  _handleImgError() {
183
189
  this._imgError = true;
184
190
  }
185
191
  render() {
186
192
  const showImage = this.src && !this._imgError;
187
- const showInitials = !showImage && this.name;
193
+ const showInitials = !showImage && this._initials;
194
+ const label = this.alt || this.name.trim();
188
195
  return html`
189
- <span part="base" aria-label=${this.alt || this.name || "avatar"} role="img">
196
+ <span part="base" role=${label ? "img" : "presentation"} aria-label=${ifDefined(label || void 0)}>
190
197
  ${showImage ? html`<img part="image" src=${this.src} alt=${this.alt || this.name} @error=${this._handleImgError} />` : showInitials ? html`<span part="initials" class="initials">${this._initials}</span>` : html`<lt-icon part="icon" name="user" size="sm"></lt-icon>`}
191
198
  </span>
192
199
  `;
@@ -230,7 +230,7 @@ var badgeStyles = css`
230
230
  }
231
231
 
232
232
  :host([appearance='solid'][variant='neutral']) span[part='base'] {
233
- background: var(--lt-color-neutral-500);
233
+ background: var(--lt-interactive-neutral-bg);
234
234
  border-color: transparent;
235
235
  color: var(--lt-color-white);
236
236
  }
@@ -1,5 +1,6 @@
1
1
  import { type PropertyValues } from 'lit';
2
2
  import { ThemeableElement } from '../../base';
3
+ import type { BreadcrumbLabels } from './breadcrumb.types';
3
4
  /**
4
5
  * Navigation breadcrumb trail container.
5
6
  * Place `lt-breadcrumb-item` elements inside as direct children.
@@ -23,6 +24,9 @@ export declare class Breadcrumb extends ThemeableElement {
23
24
  * @default '/'
24
25
  */
25
26
  separator: string;
27
+ /** Overridable UI text (the breadcrumb nav's landmark label), for i18n. English defaults — override only the keys you need. */
28
+ labels: Partial<BreadcrumbLabels>;
29
+ private get _labels();
26
30
  updated(changed: PropertyValues<this>): void;
27
31
  render(): import("lit").TemplateResult<1>;
28
32
  }
@@ -1 +1,6 @@
1
1
  export type BreadcrumbSeparator = string;
2
+ /** Overridable UI text, for i18n — English defaults; override only the keys you need. */
3
+ export interface BreadcrumbLabels {
4
+ /** Accessible landmark label for the breadcrumb `<nav>`. */
5
+ navigation: string;
6
+ }
@@ -62,13 +62,12 @@ var breadcrumbItemStyles = css`
62
62
  }
63
63
 
64
64
  [part='link'] {
65
- color: var(--lt-interactive-primary-bg);
65
+ color: var(--lt-text-primary);
66
66
  text-decoration: none;
67
67
  transition: color 120ms ease;
68
68
  }
69
69
 
70
70
  [part='link']:hover {
71
- color: var(--lt-interactive-primary-bg-hover);
72
71
  text-decoration: underline;
73
72
  }
74
73
 
@@ -90,13 +89,28 @@ var breadcrumbItemStyles = css`
90
89
  :host(:last-child) [part='separator'] {
91
90
  display: none;
92
91
  }
92
+
93
+ /* ── Reduced motion ─────────────────────────────────────────────────────── */
94
+
95
+ @media (prefers-reduced-motion: reduce) {
96
+ [part='link'] {
97
+ transition: none;
98
+ }
99
+ }
93
100
  `;
94
101
 
95
102
  // src/components/breadcrumb/breadcrumb.ts
103
+ var DEFAULT_LABELS = {
104
+ navigation: "breadcrumb"
105
+ };
96
106
  var Breadcrumb = class extends ThemeableElement {
97
107
  constructor() {
98
108
  super(...arguments);
99
109
  this.separator = "/";
110
+ this.labels = {};
111
+ }
112
+ get _labels() {
113
+ return { ...DEFAULT_LABELS, ...this.labels };
100
114
  }
101
115
  updated(changed) {
102
116
  super.updated(changed);
@@ -110,7 +124,7 @@ var Breadcrumb = class extends ThemeableElement {
110
124
  }
111
125
  render() {
112
126
  return html`
113
- <nav aria-label="breadcrumb">
127
+ <nav aria-label=${this._labels.navigation}>
114
128
  <div part="list" role="list">
115
129
  <slot></slot>
116
130
  </div>
@@ -122,6 +136,9 @@ Breadcrumb.styles = breadcrumbStyles;
122
136
  __decorateClass([
123
137
  property2()
124
138
  ], Breadcrumb.prototype, "separator", 2);
139
+ __decorateClass([
140
+ property2({ type: Object })
141
+ ], Breadcrumb.prototype, "labels", 2);
125
142
  Breadcrumb = __decorateClass([
126
143
  customElement("lt-breadcrumb")
127
144
  ], Breadcrumb);
@@ -42,9 +42,22 @@ import '@latty-ds/icons';
42
42
  export declare class Button extends ThemeableElement {
43
43
  static styles: import("lit").CSSResult;
44
44
  static formAssociated: boolean;
45
+ static shadowRootOptions: {
46
+ delegatesFocus: boolean;
47
+ clonable?: boolean;
48
+ customElementRegistry?: CustomElementRegistry | null;
49
+ mode: ShadowRootMode;
50
+ serializable?: boolean;
51
+ slotAssignment?: SlotAssignmentMode;
52
+ };
45
53
  private _internals;
46
54
  private _ariaForwarding;
55
+ private _fieldsetDisabled;
47
56
  constructor();
57
+ connectedCallback(): void;
58
+ disconnectedCallback(): void;
59
+ private _blockClickWhileBusy;
60
+ formDisabledCallback(disabled: boolean): void;
48
61
  /**
49
62
  * Visual variant that determines the button's color scheme.
50
63
  * @default 'primary'
@@ -117,6 +130,26 @@ export declare class Button extends ThemeableElement {
117
130
  * @default 'button'
118
131
  */
119
132
  type: ButtonType;
133
+ /**
134
+ * Name submitted alongside `value` when this button triggers a form submission (`type="submit"`), matching a
135
+ * native `<button name value>` — only included in the submitted data for the button that actually triggered
136
+ * the submission, not every submit button in the form.
137
+ * @default ''
138
+ */
139
+ name: string;
140
+ /**
141
+ * Value submitted alongside `name`. See `name`.
142
+ * @default ''
143
+ */
144
+ value: string;
145
+ /**
146
+ * Overrides the form's own `action` for a submission triggered by this button, matching a native
147
+ * `<button formaction>`. Only applies when `type="submit"`.
148
+ * @default ''
149
+ */
150
+ formAction: string;
151
+ private _proxySubmitBtn;
152
+ private _getProxySubmitBtn;
120
153
  private _handleClick;
121
154
  render(): import("lit").TemplateResult<1>;
122
155
  }