@aceshooting/lyra-ui 0.1.1 → 0.1.2

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 (117) hide show
  1. package/README.md +77 -6
  2. package/custom-elements.json +12015 -0
  3. package/dist/components/chart/bar-chart.d.ts +14 -0
  4. package/dist/components/chart/bar-chart.js +14 -0
  5. package/dist/components/chart/box-plot.d.ts +46 -0
  6. package/dist/components/chart/box-plot.js +145 -0
  7. package/dist/components/chart/box-plot.styles.d.ts +1 -0
  8. package/dist/components/chart/box-plot.styles.js +22 -0
  9. package/dist/components/chart/bubble-chart.d.ts +19 -0
  10. package/dist/components/chart/bubble-chart.js +18 -0
  11. package/dist/components/chart/chart-loader.d.ts +8 -0
  12. package/dist/components/chart/chart-loader.js +26 -0
  13. package/dist/components/chart/chart.d.ts +91 -0
  14. package/dist/components/chart/chart.js +275 -0
  15. package/dist/components/chart/chart.styles.d.ts +1 -0
  16. package/dist/components/chart/chart.styles.js +33 -0
  17. package/dist/components/chart/doughnut-chart.d.ts +15 -0
  18. package/dist/components/chart/doughnut-chart.js +14 -0
  19. package/dist/components/chart/histogram-bin.d.ts +6 -0
  20. package/dist/components/chart/histogram-bin.js +19 -0
  21. package/dist/components/chart/histogram.d.ts +20 -0
  22. package/dist/components/chart/histogram.js +70 -0
  23. package/dist/components/chart/histogram.styles.d.ts +1 -0
  24. package/dist/components/chart/histogram.styles.js +1 -0
  25. package/dist/components/chart/line-chart.d.ts +14 -0
  26. package/dist/components/chart/line-chart.js +14 -0
  27. package/dist/components/chart/pie-chart.d.ts +16 -0
  28. package/dist/components/chart/pie-chart.js +15 -0
  29. package/dist/components/chart/polar-area-chart.d.ts +15 -0
  30. package/dist/components/chart/polar-area-chart.js +14 -0
  31. package/dist/components/chart/radar-chart.d.ts +15 -0
  32. package/dist/components/chart/radar-chart.js +14 -0
  33. package/dist/components/chart/scatter-chart.d.ts +15 -0
  34. package/dist/components/chart/scatter-chart.js +15 -0
  35. package/dist/components/combobox/combobox.d.ts +16 -0
  36. package/dist/components/combobox/combobox.js +113 -6
  37. package/dist/components/combobox/combobox.styles.js +68 -7
  38. package/dist/components/combobox/option.d.ts +2 -2
  39. package/dist/components/combobox/option.js +11 -7
  40. package/dist/components/date-picker/date-input.d.ts +12 -1
  41. package/dist/components/date-picker/date-input.js +68 -6
  42. package/dist/components/date-picker/date-input.styles.js +58 -6
  43. package/dist/components/date-picker/date-picker.js +5 -2
  44. package/dist/components/date-picker/date-picker.styles.js +9 -2
  45. package/dist/components/empty/empty.d.ts +1 -0
  46. package/dist/components/empty/empty.js +14 -2
  47. package/dist/components/export-button/csv.d.ts +10 -0
  48. package/dist/components/export-button/csv.js +28 -0
  49. package/dist/components/export-button/export-button.d.ts +41 -0
  50. package/dist/components/export-button/export-button.js +151 -0
  51. package/dist/components/export-button/export-button.styles.d.ts +1 -0
  52. package/dist/components/export-button/export-button.styles.js +79 -0
  53. package/dist/components/file-input/accept.d.ts +14 -0
  54. package/dist/components/file-input/accept.js +30 -0
  55. package/dist/components/file-input/file-input.d.ts +47 -0
  56. package/dist/components/file-input/file-input.js +182 -0
  57. package/dist/components/file-input/file-input.styles.d.ts +1 -0
  58. package/dist/components/file-input/file-input.styles.js +44 -0
  59. package/dist/components/flag/flag.d.ts +4 -0
  60. package/dist/components/flag/flag.js +24 -1
  61. package/dist/components/flag/flag.styles.js +1 -1
  62. package/dist/components/gauge/gauge.d.ts +29 -0
  63. package/dist/components/gauge/gauge.js +133 -0
  64. package/dist/components/gauge/gauge.styles.d.ts +1 -0
  65. package/dist/components/gauge/gauge.styles.js +60 -0
  66. package/dist/components/graph/graph.d.ts +68 -0
  67. package/dist/components/graph/graph.js +276 -0
  68. package/dist/components/graph/graph.styles.d.ts +1 -0
  69. package/dist/components/graph/graph.styles.js +34 -0
  70. package/dist/components/heatmap/heatmap-scale.d.ts +8 -0
  71. package/dist/components/heatmap/heatmap-scale.js +17 -0
  72. package/dist/components/heatmap/heatmap.d.ts +66 -0
  73. package/dist/components/heatmap/heatmap.js +252 -0
  74. package/dist/components/heatmap/heatmap.styles.d.ts +1 -0
  75. package/dist/components/heatmap/heatmap.styles.js +39 -0
  76. package/dist/components/map/map-loader.d.ts +8 -0
  77. package/dist/components/map/map-loader.js +17 -0
  78. package/dist/components/map/map.d.ts +49 -0
  79. package/dist/components/map/map.js +189 -0
  80. package/dist/components/map/map.styles.d.ts +1 -0
  81. package/dist/components/map/map.styles.js +48 -0
  82. package/dist/components/playback/playback.d.ts +51 -0
  83. package/dist/components/playback/playback.js +146 -0
  84. package/dist/components/playback/playback.styles.d.ts +1 -0
  85. package/dist/components/playback/playback.styles.js +40 -0
  86. package/dist/components/split/split.d.ts +41 -0
  87. package/dist/components/split/split.js +171 -0
  88. package/dist/components/split/split.styles.d.ts +1 -0
  89. package/dist/components/split/split.styles.js +51 -0
  90. package/dist/components/stat/stat.d.ts +9 -0
  91. package/dist/components/stat/stat.js +52 -7
  92. package/dist/components/stat/stat.styles.js +15 -5
  93. package/dist/components/table/table.d.ts +18 -6
  94. package/dist/components/table/table.js +76 -24
  95. package/dist/components/table/table.styles.js +32 -0
  96. package/dist/components/time-range/time-range.d.ts +40 -0
  97. package/dist/components/time-range/time-range.js +219 -0
  98. package/dist/components/time-range/time-range.styles.d.ts +1 -0
  99. package/dist/components/time-range/time-range.styles.js +78 -0
  100. package/dist/components/toast/toast-item.js +10 -2
  101. package/dist/components/toast/toast-item.styles.js +13 -4
  102. package/dist/components/toast/toast.styles.js +0 -1
  103. package/dist/components/tree/tree-node.d.ts +50 -0
  104. package/dist/components/tree/tree-node.js +196 -0
  105. package/dist/components/tree/tree.d.ts +65 -0
  106. package/dist/components/tree/tree.js +227 -0
  107. package/dist/components/tree/tree.styles.d.ts +1 -0
  108. package/dist/components/tree/tree.styles.js +13 -0
  109. package/dist/internal/form-associated.js +49 -1
  110. package/dist/internal/icons.d.ts +11 -0
  111. package/dist/internal/icons.js +64 -0
  112. package/dist/internal/tokens.styles.js +41 -1
  113. package/dist/lyra.d.ts +67 -0
  114. package/dist/lyra.js +55 -0
  115. package/llms-full.txt +1425 -0
  116. package/llms.txt +47 -0
  117. package/package.json +53 -4
@@ -0,0 +1,40 @@
1
+ import { type TemplateResult, type PropertyValues } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ /**
4
+ * `<lyra-time-range>` — a two-handle brush/scrubber over a numeric domain.
5
+ * Callers map their own time axis to `[min, max]`; no date logic lives here
6
+ * (matches the no-date-library constraint used elsewhere in this library).
7
+ *
8
+ * @customElement lyra-time-range
9
+ * @event lyra-input - Fired continuously while dragging. `detail: { start, end }`.
10
+ * @event lyra-change - Fired on release / keyup-commit. `detail: { start, end }`.
11
+ * @csspart base, track, range, handle-start, handle-end
12
+ */
13
+ export declare class LyraTimeRange extends LyraElement {
14
+ static styles: import("lit").CSSResultGroup[];
15
+ min: number;
16
+ max: number;
17
+ start: number;
18
+ end: number;
19
+ step: number;
20
+ disabled: boolean;
21
+ private dragging;
22
+ disconnectedCallback(): void;
23
+ private percentOf;
24
+ private clamp;
25
+ private setValue;
26
+ private onKeyDown;
27
+ private onKeyUp;
28
+ private onPointerDown;
29
+ private onPointerMove;
30
+ private onPointerUp;
31
+ /** Stop an in-progress drag, optionally committing a final lyra-change. */
32
+ private endDrag;
33
+ protected willUpdate(changed: PropertyValues): void;
34
+ render(): TemplateResult;
35
+ }
36
+ declare global {
37
+ interface HTMLElementTagNameMap {
38
+ 'lyra-time-range': LyraTimeRange;
39
+ }
40
+ }
@@ -0,0 +1,219 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html } from 'lit';
8
+ import { property } from 'lit/decorators.js';
9
+ import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { defineElement } from '../../internal/prefix.js';
11
+ import { styles } from './time-range.styles.js';
12
+ function isArrowKey(key) {
13
+ return key === 'ArrowRight' || key === 'ArrowUp' || key === 'ArrowLeft' || key === 'ArrowDown';
14
+ }
15
+ /**
16
+ * `<lyra-time-range>` — a two-handle brush/scrubber over a numeric domain.
17
+ * Callers map their own time axis to `[min, max]`; no date logic lives here
18
+ * (matches the no-date-library constraint used elsewhere in this library).
19
+ *
20
+ * @customElement lyra-time-range
21
+ * @event lyra-input - Fired continuously while dragging. `detail: { start, end }`.
22
+ * @event lyra-change - Fired on release / keyup-commit. `detail: { start, end }`.
23
+ * @csspart base, track, range, handle-start, handle-end
24
+ */
25
+ export class LyraTimeRange extends LyraElement {
26
+ constructor() {
27
+ super(...arguments);
28
+ this.min = 0;
29
+ this.max = 100;
30
+ this.start = 0;
31
+ this.end = 100;
32
+ this.step = 1;
33
+ this.disabled = false;
34
+ this.dragging = null;
35
+ this.onKeyDown = (handle, e) => {
36
+ // A handle that already has focus when the component becomes disabled
37
+ // must not still respond to arrow keys (new pointerdowns are already
38
+ // blocked by the `if (this.disabled) return;` guard in onPointerDown,
39
+ // and disabled handles carry `tabindex="-1"`, but a pre-existing focus
40
+ // can bypass both of those).
41
+ if (this.disabled)
42
+ return;
43
+ const current = handle === 'start' ? this.start : this.end;
44
+ if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
45
+ e.preventDefault();
46
+ this.setValue(handle, current + this.step, false);
47
+ }
48
+ else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
49
+ e.preventDefault();
50
+ this.setValue(handle, current - this.step, false);
51
+ }
52
+ };
53
+ this.onKeyUp = (e) => {
54
+ // Only commit on release of the arrow keys that onKeyDown acts on —
55
+ // releasing an unrelated key (Tab, Shift, ...) while a handle happens
56
+ // to be focused must not emit a spurious lyra-change.
57
+ if (this.disabled || !isArrowKey(e.key))
58
+ return;
59
+ this.emit('lyra-change', { start: this.start, end: this.end });
60
+ };
61
+ this.onPointerDown = (handle, e) => {
62
+ if (this.disabled)
63
+ return;
64
+ this.dragging = handle;
65
+ e.target.setPointerCapture(e.pointerId);
66
+ window.addEventListener('pointermove', this.onPointerMove);
67
+ window.addEventListener('pointerup', this.onPointerUp);
68
+ };
69
+ this.onPointerMove = (e) => {
70
+ if (!this.dragging)
71
+ return;
72
+ if (this.disabled) {
73
+ // These are window-level listeners driven by setPointerCapture, so
74
+ // they keep firing for this pointerId regardless of any CSS on the
75
+ // host (this was already true even back when the host used
76
+ // `pointer-events: none`, since that never governed an
77
+ // already-captured window listener) — a drag already in progress
78
+ // would otherwise keep mutating start/end (and emitting lyra-input)
79
+ // after `disabled` flips true mid-drag. Abort the drag instead of
80
+ // continuing to process it.
81
+ this.endDrag(false);
82
+ return;
83
+ }
84
+ const base = this.renderRoot.querySelector('[part="base"]');
85
+ const rect = base.getBoundingClientRect();
86
+ const ratio = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
87
+ const value = this.min + ratio * (this.max - this.min);
88
+ this.setValue(this.dragging, value, false);
89
+ };
90
+ this.onPointerUp = () => {
91
+ this.endDrag(true);
92
+ };
93
+ }
94
+ static { this.styles = [LyraElement.styles, styles]; }
95
+ disconnectedCallback() {
96
+ super.disconnectedCallback();
97
+ // Mirror lyra-split's cleanup: if the element is removed mid-drag (or a
98
+ // pointercancel/alt-tab means `pointerup` never reaches `window`), these
99
+ // window-level listeners — and the closure keeping this instance alive —
100
+ // would otherwise leak indefinitely.
101
+ window.removeEventListener('pointermove', this.onPointerMove);
102
+ window.removeEventListener('pointerup', this.onPointerUp);
103
+ }
104
+ percentOf(value) {
105
+ const span = this.max - this.min || 1;
106
+ return ((value - this.min) / span) * 100;
107
+ }
108
+ clamp(handle, value) {
109
+ const stepped = Math.round(value / this.step) * this.step;
110
+ const bounded = Math.min(this.max, Math.max(this.min, stepped));
111
+ if (handle === 'start')
112
+ return Math.min(bounded, this.end);
113
+ return Math.max(bounded, this.start);
114
+ }
115
+ setValue(handle, value, commit) {
116
+ const clamped = this.clamp(handle, value);
117
+ if (handle === 'start')
118
+ this.start = clamped;
119
+ else
120
+ this.end = clamped;
121
+ this.emit('lyra-input', { start: this.start, end: this.end });
122
+ if (commit)
123
+ this.emit('lyra-change', { start: this.start, end: this.end });
124
+ }
125
+ /** Stop an in-progress drag, optionally committing a final lyra-change. */
126
+ endDrag(commit) {
127
+ if (this.dragging && commit)
128
+ this.emit('lyra-change', { start: this.start, end: this.end });
129
+ this.dragging = null;
130
+ window.removeEventListener('pointermove', this.onPointerMove);
131
+ window.removeEventListener('pointerup', this.onPointerUp);
132
+ }
133
+ willUpdate(changed) {
134
+ if (changed.has('start') ||
135
+ changed.has('end') ||
136
+ changed.has('min') ||
137
+ changed.has('max')) {
138
+ // Clamp both handles into the current [min, max] domain first — a
139
+ // caller narrowing the domain after mount (e.g. zooming the time
140
+ // axis via `el.max = 50`) must not leave a handle rendered outside
141
+ // the track, since percentOf() would otherwise produce <0%/>100%.
142
+ // Guard against a caller passing min > max so the bounds themselves
143
+ // stay well-formed.
144
+ const lo = Math.min(this.min, this.max);
145
+ const hi = Math.max(this.min, this.max);
146
+ this.start = Math.min(hi, Math.max(lo, this.start));
147
+ this.end = Math.min(hi, Math.max(lo, this.end));
148
+ }
149
+ // Keep start <= end regardless of which side changed (a controlled
150
+ // caller may set only `end`, e.g. two-way-binding an external store —
151
+ // see finding in 2026-07-09 tier2 review). The handle that just moved
152
+ // wins; the other side is pulled to meet it, mirroring the direction
153
+ // `clamp()` already uses during interactive dragging.
154
+ if (changed.has('start') || changed.has('min') || changed.has('max')) {
155
+ this.start = Math.min(this.start, this.end);
156
+ }
157
+ if (changed.has('end') || changed.has('min') || changed.has('max')) {
158
+ this.end = Math.max(this.end, this.start);
159
+ }
160
+ }
161
+ render() {
162
+ const startPct = this.percentOf(this.start);
163
+ const endPct = this.percentOf(this.end);
164
+ return html `
165
+ <div part="base">
166
+ <div part="track"></div>
167
+ <div
168
+ part="range"
169
+ style=${`inset-inline-start:${startPct}%;inline-size:${endPct - startPct}%`}
170
+ ></div>
171
+ <div
172
+ part="handle-start"
173
+ role="slider"
174
+ tabindex=${this.disabled ? '-1' : '0'}
175
+ aria-label="Range start"
176
+ aria-valuemin=${this.min}
177
+ aria-valuemax=${this.max}
178
+ aria-valuenow=${this.start}
179
+ style=${`inset-inline-start:${startPct}%`}
180
+ @pointerdown=${(e) => this.onPointerDown('start', e)}
181
+ @keydown=${(e) => this.onKeyDown('start', e)}
182
+ @keyup=${(e) => this.onKeyUp(e)}
183
+ ></div>
184
+ <div
185
+ part="handle-end"
186
+ role="slider"
187
+ tabindex=${this.disabled ? '-1' : '0'}
188
+ aria-label="Range end"
189
+ aria-valuemin=${this.min}
190
+ aria-valuemax=${this.max}
191
+ aria-valuenow=${this.end}
192
+ style=${`inset-inline-start:${endPct}%`}
193
+ @pointerdown=${(e) => this.onPointerDown('end', e)}
194
+ @keydown=${(e) => this.onKeyDown('end', e)}
195
+ @keyup=${(e) => this.onKeyUp(e)}
196
+ ></div>
197
+ </div>
198
+ `;
199
+ }
200
+ }
201
+ __decorate([
202
+ property({ type: Number })
203
+ ], LyraTimeRange.prototype, "min", void 0);
204
+ __decorate([
205
+ property({ type: Number })
206
+ ], LyraTimeRange.prototype, "max", void 0);
207
+ __decorate([
208
+ property({ type: Number })
209
+ ], LyraTimeRange.prototype, "start", void 0);
210
+ __decorate([
211
+ property({ type: Number })
212
+ ], LyraTimeRange.prototype, "end", void 0);
213
+ __decorate([
214
+ property({ type: Number })
215
+ ], LyraTimeRange.prototype, "step", void 0);
216
+ __decorate([
217
+ property({ type: Boolean, reflect: true })
218
+ ], LyraTimeRange.prototype, "disabled", void 0);
219
+ defineElement('time-range', LyraTimeRange);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,78 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ inline-size: 100%;
6
+ block-size: 1.5rem;
7
+ }
8
+ [part='base'] {
9
+ position: relative;
10
+ inline-size: 100%;
11
+ block-size: 100%;
12
+ display: flex;
13
+ align-items: center;
14
+ }
15
+ [part='track'] {
16
+ position: absolute;
17
+ inset-inline: 0;
18
+ block-size: 4px;
19
+ border-radius: 2px;
20
+ background: var(--lyra-color-border);
21
+ }
22
+ [part='range'] {
23
+ position: absolute;
24
+ block-size: 4px;
25
+ border-radius: 2px;
26
+ background: var(--lyra-color-brand);
27
+ }
28
+ [part^='handle'] {
29
+ position: absolute;
30
+ inline-size: 14px;
31
+ block-size: 14px;
32
+ border-radius: 50%;
33
+ background: var(--lyra-color-brand);
34
+ border: 2px solid var(--lyra-color-surface);
35
+ box-shadow: var(--lyra-shadow);
36
+ transform: translateX(-50%);
37
+ cursor: grab;
38
+ touch-action: none;
39
+ }
40
+ /*
41
+ * The visible dot stays 14px by design, but that's well under the ~24px
42
+ * minimum touch target size despite \`touch-action: none\` signalling this
43
+ * is meant to be touch-dragged. Widen the actual hit/drag area with a
44
+ * transparent ::before instead of growing the handle box itself:
45
+ * onPointerMove (time-range.ts) never reads the handle's own
46
+ * getBoundingClientRect() — it only measures \`[part="base"]\`'s rect and
47
+ * e.clientX/e.clientY — and a pointerdown inside the ::before still
48
+ * reports \`e.target\` as the real handle element (pseudo-elements have no
49
+ * separate DOM node/event target), so this is purely additive and cannot
50
+ * change the drag math.
51
+ */
52
+ [part^='handle']::before {
53
+ content: '';
54
+ position: absolute;
55
+ inset-block-start: 50%;
56
+ inset-inline-start: 50%;
57
+ inline-size: 28px;
58
+ block-size: 28px;
59
+ transform: translate(-50%, -50%);
60
+ border-radius: 50%;
61
+ }
62
+ [part^='handle']:focus-visible {
63
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
64
+ outline-offset: var(--lyra-focus-ring-offset);
65
+ }
66
+ :host([disabled]) {
67
+ opacity: var(--lyra-opacity-disabled);
68
+ cursor: not-allowed;
69
+ }
70
+ :host([disabled]) [part^='handle'] {
71
+ /* [part^='handle'] above sets \`cursor: grab\` unconditionally, which
72
+ would otherwise keep winning over the inherited :host cursor (it
73
+ isn't conditioned on [disabled]) — restate not-allowed here so the
74
+ cursor actually changes over the handles themselves, not just the
75
+ track/base. */
76
+ cursor: not-allowed;
77
+ }
78
+ `;
@@ -8,8 +8,16 @@ import { html } from 'lit';
8
8
  import { property } from 'lit/decorators.js';
9
9
  import { LyraElement } from '../../internal/lyra-element.js';
10
10
  import { defineElement } from '../../internal/prefix.js';
11
+ import { closeIcon } from '../../internal/icons.js';
11
12
  import { styles } from './toast-item.styles.js';
12
- const ANIM_MS = 250;
13
+ // Must stay in sync with --lyra-transition-base's 180ms fallback
14
+ // (internal/tokens.styles.ts) — toast-item.styles.ts derives
15
+ // --show-duration/--hide-duration from that same token. Kept as a literal
16
+ // here rather than read via getComputedStyle() because the token's value is
17
+ // a full transition shorthand ("180ms ease-out"), not a bare duration, and
18
+ // parsing that back out in JS for a single setTimeout would be more fragile
19
+ // than this documented literal.
20
+ const ANIM_MS = 180;
13
21
  /**
14
22
  * `<lyra-toast-item>` — a single toast notification.
15
23
  * Mirrors the Web Awesome `<wa-toast-item>` API under the `lyra-` prefix.
@@ -102,7 +110,7 @@ export class LyraToastItem extends LyraElement {
102
110
  ${this.withIcon ? html `<span part="icon"><slot name="icon"></slot></span>` : ''}
103
111
  <div part="content"><slot></slot></div>
104
112
  <button part="close-button" type="button" aria-label="Close" @click=${() => this.hide()}>
105
- &times;
113
+ ${closeIcon()}
106
114
  </button>
107
115
  </div>
108
116
  `;
@@ -3,8 +3,8 @@ export const styles = css `
3
3
  :host {
4
4
  display: block;
5
5
  --accent-width: 4px;
6
- --show-duration: 250ms;
7
- --hide-duration: 250ms;
6
+ --show-duration: var(--lyra-transition-base, 180ms ease-out);
7
+ --hide-duration: var(--lyra-transition-base, 180ms ease-out);
8
8
  --padding: var(--lyra-space-m);
9
9
  --accent-color: var(--lyra-color-border);
10
10
  }
@@ -37,13 +37,18 @@ export const styles = css `
37
37
  opacity: 0;
38
38
  transform: translateY(-8px);
39
39
  transition:
40
- opacity var(--show-duration) ease,
41
- transform var(--show-duration) ease;
40
+ opacity var(--show-duration),
41
+ transform var(--show-duration);
42
42
  }
43
43
  :host([data-visible]) [part='toast-item'] {
44
44
  opacity: 1;
45
45
  transform: none;
46
46
  }
47
+ @media (prefers-reduced-motion: reduce) {
48
+ [part='toast-item'] {
49
+ transition-duration: 0.01ms !important;
50
+ }
51
+ }
47
52
 
48
53
  [part='accent'] {
49
54
  position: absolute;
@@ -78,4 +83,8 @@ export const styles = css `
78
83
  [part='close-button']:hover {
79
84
  color: var(--lyra-color-text);
80
85
  }
86
+ [part='close-button']:focus-visible {
87
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
88
+ outline-offset: var(--lyra-focus-ring-offset);
89
+ }
81
90
  `;
@@ -15,7 +15,6 @@ export const styles = css `
15
15
  inline-size: var(--width);
16
16
  max-inline-size: calc(100vw - 2rem);
17
17
  }
18
- [part='stack']::slotted(*),
19
18
  ::slotted(*) {
20
19
  pointer-events: auto;
21
20
  }
@@ -0,0 +1,50 @@
1
+ import { type TemplateResult } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ import type { TreeItem } from './tree.js';
4
+ /**
5
+ * `<lyra-tree-node>` — internal recursive renderer for `<lyra-tree>`.
6
+ *
7
+ * `role="treeitem"` (plus `aria-expanded`/`aria-level`/`aria-setsize`/
8
+ * `aria-posinset` and the roving `tabindex`, driven by `<lyra-tree>`) live on
9
+ * the *host* element, not the internal `[part="row"]` div — that makes this
10
+ * node's own nested children (rendered in *its own* shadow root as further
11
+ * `role="group"` content) genuine DOM descendants of the treeitem, which is
12
+ * what the WAI-ARIA treeitem pattern requires (2026-07-10 audit,
13
+ * "temporal-graph" §lyra-tree, Medium — previously a shadow-root sibling).
14
+ *
15
+ * @customElement lyra-tree-node
16
+ * @csspart row, toggle, label, badge
17
+ */
18
+ export declare class LyraTreeNode extends LyraElement {
19
+ static styles: import("lit").CSSResultGroup[];
20
+ item: TreeItem;
21
+ depth: number;
22
+ expanded: boolean;
23
+ /** The id of the tree's roving-tabindex-focused item, pushed down from `<lyra-tree>`. */
24
+ activeId: string | null;
25
+ setSize: number;
26
+ posInSet: number;
27
+ get hasChildren(): boolean;
28
+ protected willUpdate(): void;
29
+ /** Expand this node (no-op if already expanded or a leaf). */
30
+ expand(): void;
31
+ /** Collapse this node (no-op if already collapsed or a leaf). */
32
+ collapse(): void;
33
+ /** Fire this item's primary "select" action (Enter/Space, or clicking the label). */
34
+ select(): void;
35
+ /**
36
+ * Cascade `updateComplete`: a nested `<lyra-tree-node>` rendered by this
37
+ * one (in its own shadow root) only receives a pushed-down `activeId` (and
38
+ * the `tabIndex` that comes from it) once *this* node's own render has
39
+ * committed. Without this override, awaiting a parent's `updateComplete`
40
+ * doesn't guarantee an arbitrarily-nested descendant's own pending update
41
+ * has also settled — see the matching override on `<lyra-tree>`.
42
+ */
43
+ protected getUpdateComplete(): Promise<boolean>;
44
+ render(): TemplateResult;
45
+ }
46
+ declare global {
47
+ interface HTMLElementTagNameMap {
48
+ 'lyra-tree-node': LyraTreeNode;
49
+ }
50
+ }
@@ -0,0 +1,196 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html, nothing } from 'lit';
8
+ import { property } from 'lit/decorators.js';
9
+ import { repeat } from 'lit/directives/repeat.js';
10
+ import { LyraElement } from '../../internal/lyra-element.js';
11
+ import { defineElement } from '../../internal/prefix.js';
12
+ import { chevronIcon } from '../../internal/icons.js';
13
+ import { css } from 'lit';
14
+ const styles = css `
15
+ :host {
16
+ display: block;
17
+ outline: none; /* the host is the focusable treeitem; the visible ring lives on [part=row] */
18
+ }
19
+ :host(:focus-visible) [part='row'] {
20
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
21
+ outline-offset: var(--lyra-focus-ring-offset);
22
+ }
23
+ [part='row'] {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: var(--lyra-space-xs);
27
+ padding: var(--lyra-space-xs) var(--lyra-space-s);
28
+ /* Depth-based indent is capped at 8rem so a deeply-nested item can't
29
+ push its content off-screen with no way back; [part=label] below
30
+ truncates the remaining overflow and tree.styles.ts's [part=base]
31
+ adds an overflow-x:auto fallback for whatever's left. */
32
+ padding-inline-start: calc(var(--lyra-space-s) + min(var(--lyra-tree-depth, 0) * 1rem, 8rem));
33
+ cursor: pointer;
34
+ border-radius: var(--lyra-radius);
35
+ }
36
+ [part='row']:hover {
37
+ background: var(--lyra-color-brand-quiet);
38
+ }
39
+ [part='toggle'] {
40
+ /* Deliberately smaller than the shared --lyra-icon-button-size (2.5rem,
41
+ for standalone icon-only buttons) — this toggle sits inline in a
42
+ compact row, but still needs a real touch target, not a 1rem hitbox. */
43
+ min-inline-size: 1.75rem;
44
+ min-block-size: 1.75rem;
45
+ padding: var(--lyra-space-xs);
46
+ display: inline-flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ border: none;
50
+ background: none;
51
+ color: var(--lyra-color-text-quiet);
52
+ cursor: pointer;
53
+ flex: 0 0 auto;
54
+ }
55
+ [part='toggle']:empty {
56
+ visibility: hidden;
57
+ }
58
+ :host([expanded]) [part='toggle'] {
59
+ transform: rotate(90deg);
60
+ }
61
+ [part='label'] {
62
+ flex: 1 1 auto;
63
+ min-inline-size: 0;
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ white-space: nowrap;
67
+ }
68
+ [part='badge'] {
69
+ font-size: 0.75rem;
70
+ color: var(--lyra-color-text);
71
+ background: var(--lyra-color-border);
72
+ border-radius: var(--lyra-radius);
73
+ padding: 0 0.35rem;
74
+ }
75
+ `;
76
+ /**
77
+ * `<lyra-tree-node>` — internal recursive renderer for `<lyra-tree>`.
78
+ *
79
+ * `role="treeitem"` (plus `aria-expanded`/`aria-level`/`aria-setsize`/
80
+ * `aria-posinset` and the roving `tabindex`, driven by `<lyra-tree>`) live on
81
+ * the *host* element, not the internal `[part="row"]` div — that makes this
82
+ * node's own nested children (rendered in *its own* shadow root as further
83
+ * `role="group"` content) genuine DOM descendants of the treeitem, which is
84
+ * what the WAI-ARIA treeitem pattern requires (2026-07-10 audit,
85
+ * "temporal-graph" §lyra-tree, Medium — previously a shadow-root sibling).
86
+ *
87
+ * @customElement lyra-tree-node
88
+ * @csspart row, toggle, label, badge
89
+ */
90
+ export class LyraTreeNode extends LyraElement {
91
+ constructor() {
92
+ super(...arguments);
93
+ this.depth = 0;
94
+ this.expanded = false;
95
+ /** The id of the tree's roving-tabindex-focused item, pushed down from `<lyra-tree>`. */
96
+ this.activeId = null;
97
+ this.setSize = 1;
98
+ this.posInSet = 1;
99
+ }
100
+ static { this.styles = [LyraElement.styles, styles]; }
101
+ get hasChildren() {
102
+ return Boolean(this.item.children?.length);
103
+ }
104
+ willUpdate() {
105
+ this.setAttribute('role', 'treeitem');
106
+ this.setAttribute('aria-level', String(this.depth + 1));
107
+ this.setAttribute('aria-setsize', String(this.setSize));
108
+ this.setAttribute('aria-posinset', String(this.posInSet));
109
+ if (this.hasChildren)
110
+ this.setAttribute('aria-expanded', String(this.expanded));
111
+ else
112
+ this.removeAttribute('aria-expanded');
113
+ this.tabIndex = this.item?.id === this.activeId ? 0 : -1;
114
+ }
115
+ /** Expand this node (no-op if already expanded or a leaf). */
116
+ expand() {
117
+ if (!this.hasChildren || this.expanded)
118
+ return;
119
+ this.expanded = true;
120
+ this.emit('lyra-node-toggle', { id: this.item.id, expanded: true });
121
+ }
122
+ /** Collapse this node (no-op if already collapsed or a leaf). */
123
+ collapse() {
124
+ if (!this.hasChildren || !this.expanded)
125
+ return;
126
+ this.expanded = false;
127
+ this.emit('lyra-node-toggle', { id: this.item.id, expanded: false });
128
+ }
129
+ /** Fire this item's primary "select" action (Enter/Space, or clicking the label). */
130
+ select() {
131
+ this.emit('lyra-node-select', { id: this.item.id });
132
+ }
133
+ /**
134
+ * Cascade `updateComplete`: a nested `<lyra-tree-node>` rendered by this
135
+ * one (in its own shadow root) only receives a pushed-down `activeId` (and
136
+ * the `tabIndex` that comes from it) once *this* node's own render has
137
+ * committed. Without this override, awaiting a parent's `updateComplete`
138
+ * doesn't guarantee an arbitrarily-nested descendant's own pending update
139
+ * has also settled — see the matching override on `<lyra-tree>`.
140
+ */
141
+ async getUpdateComplete() {
142
+ const result = await super.getUpdateComplete();
143
+ await Promise.all([...(this.shadowRoot?.querySelectorAll('lyra-tree-node') ?? [])].map((n) => n.updateComplete));
144
+ return result;
145
+ }
146
+ render() {
147
+ return html `
148
+ <div part="row" style=${`--lyra-tree-depth:${this.depth}`}>
149
+ <button
150
+ part="toggle"
151
+ type="button"
152
+ tabindex="-1"
153
+ aria-hidden="true"
154
+ @click=${(e) => {
155
+ e.stopPropagation();
156
+ this.expanded ? this.collapse() : this.expand();
157
+ }}
158
+ >
159
+ ${this.hasChildren ? chevronIcon() : nothing}
160
+ </button>
161
+ <span part="label" @click=${() => this.select()}>${this.item.label}</span>
162
+ ${this.item.badge != null ? html `<span part="badge">${this.item.badge}</span>` : nothing}
163
+ </div>
164
+ ${this.expanded && this.hasChildren
165
+ ? html `<div role="group">
166
+ ${repeat(this.item.children, (child) => child.id, (child, i) => html `<lyra-tree-node
167
+ .item=${child}
168
+ .depth=${this.depth + 1}
169
+ .activeId=${this.activeId}
170
+ .setSize=${this.item.children.length}
171
+ .posInSet=${i + 1}
172
+ ></lyra-tree-node>`)}
173
+ </div>`
174
+ : nothing}
175
+ `;
176
+ }
177
+ }
178
+ __decorate([
179
+ property({ attribute: false })
180
+ ], LyraTreeNode.prototype, "item", void 0);
181
+ __decorate([
182
+ property({ type: Number })
183
+ ], LyraTreeNode.prototype, "depth", void 0);
184
+ __decorate([
185
+ property({ type: Boolean, reflect: true })
186
+ ], LyraTreeNode.prototype, "expanded", void 0);
187
+ __decorate([
188
+ property({ attribute: false })
189
+ ], LyraTreeNode.prototype, "activeId", void 0);
190
+ __decorate([
191
+ property({ type: Number, attribute: false })
192
+ ], LyraTreeNode.prototype, "setSize", void 0);
193
+ __decorate([
194
+ property({ type: Number, attribute: false })
195
+ ], LyraTreeNode.prototype, "posInSet", void 0);
196
+ defineElement('tree-node', LyraTreeNode);