@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.
- package/README.md +77 -6
- package/custom-elements.json +12015 -0
- package/dist/components/chart/bar-chart.d.ts +14 -0
- package/dist/components/chart/bar-chart.js +14 -0
- package/dist/components/chart/box-plot.d.ts +46 -0
- package/dist/components/chart/box-plot.js +145 -0
- package/dist/components/chart/box-plot.styles.d.ts +1 -0
- package/dist/components/chart/box-plot.styles.js +22 -0
- package/dist/components/chart/bubble-chart.d.ts +19 -0
- package/dist/components/chart/bubble-chart.js +18 -0
- package/dist/components/chart/chart-loader.d.ts +8 -0
- package/dist/components/chart/chart-loader.js +26 -0
- package/dist/components/chart/chart.d.ts +91 -0
- package/dist/components/chart/chart.js +275 -0
- package/dist/components/chart/chart.styles.d.ts +1 -0
- package/dist/components/chart/chart.styles.js +33 -0
- package/dist/components/chart/doughnut-chart.d.ts +15 -0
- package/dist/components/chart/doughnut-chart.js +14 -0
- package/dist/components/chart/histogram-bin.d.ts +6 -0
- package/dist/components/chart/histogram-bin.js +19 -0
- package/dist/components/chart/histogram.d.ts +20 -0
- package/dist/components/chart/histogram.js +70 -0
- package/dist/components/chart/histogram.styles.d.ts +1 -0
- package/dist/components/chart/histogram.styles.js +1 -0
- package/dist/components/chart/line-chart.d.ts +14 -0
- package/dist/components/chart/line-chart.js +14 -0
- package/dist/components/chart/pie-chart.d.ts +16 -0
- package/dist/components/chart/pie-chart.js +15 -0
- package/dist/components/chart/polar-area-chart.d.ts +15 -0
- package/dist/components/chart/polar-area-chart.js +14 -0
- package/dist/components/chart/radar-chart.d.ts +15 -0
- package/dist/components/chart/radar-chart.js +14 -0
- package/dist/components/chart/scatter-chart.d.ts +15 -0
- package/dist/components/chart/scatter-chart.js +15 -0
- package/dist/components/combobox/combobox.d.ts +16 -0
- package/dist/components/combobox/combobox.js +113 -6
- package/dist/components/combobox/combobox.styles.js +68 -7
- package/dist/components/combobox/option.d.ts +2 -2
- package/dist/components/combobox/option.js +11 -7
- package/dist/components/date-picker/date-input.d.ts +12 -1
- package/dist/components/date-picker/date-input.js +68 -6
- package/dist/components/date-picker/date-input.styles.js +58 -6
- package/dist/components/date-picker/date-picker.js +5 -2
- package/dist/components/date-picker/date-picker.styles.js +9 -2
- package/dist/components/empty/empty.d.ts +1 -0
- package/dist/components/empty/empty.js +14 -2
- package/dist/components/export-button/csv.d.ts +10 -0
- package/dist/components/export-button/csv.js +28 -0
- package/dist/components/export-button/export-button.d.ts +41 -0
- package/dist/components/export-button/export-button.js +151 -0
- package/dist/components/export-button/export-button.styles.d.ts +1 -0
- package/dist/components/export-button/export-button.styles.js +79 -0
- package/dist/components/file-input/accept.d.ts +14 -0
- package/dist/components/file-input/accept.js +30 -0
- package/dist/components/file-input/file-input.d.ts +47 -0
- package/dist/components/file-input/file-input.js +182 -0
- package/dist/components/file-input/file-input.styles.d.ts +1 -0
- package/dist/components/file-input/file-input.styles.js +44 -0
- package/dist/components/flag/flag.d.ts +4 -0
- package/dist/components/flag/flag.js +24 -1
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/gauge/gauge.d.ts +29 -0
- package/dist/components/gauge/gauge.js +133 -0
- package/dist/components/gauge/gauge.styles.d.ts +1 -0
- package/dist/components/gauge/gauge.styles.js +60 -0
- package/dist/components/graph/graph.d.ts +68 -0
- package/dist/components/graph/graph.js +276 -0
- package/dist/components/graph/graph.styles.d.ts +1 -0
- package/dist/components/graph/graph.styles.js +34 -0
- package/dist/components/heatmap/heatmap-scale.d.ts +8 -0
- package/dist/components/heatmap/heatmap-scale.js +17 -0
- package/dist/components/heatmap/heatmap.d.ts +66 -0
- package/dist/components/heatmap/heatmap.js +252 -0
- package/dist/components/heatmap/heatmap.styles.d.ts +1 -0
- package/dist/components/heatmap/heatmap.styles.js +39 -0
- package/dist/components/map/map-loader.d.ts +8 -0
- package/dist/components/map/map-loader.js +17 -0
- package/dist/components/map/map.d.ts +49 -0
- package/dist/components/map/map.js +189 -0
- package/dist/components/map/map.styles.d.ts +1 -0
- package/dist/components/map/map.styles.js +48 -0
- package/dist/components/playback/playback.d.ts +51 -0
- package/dist/components/playback/playback.js +146 -0
- package/dist/components/playback/playback.styles.d.ts +1 -0
- package/dist/components/playback/playback.styles.js +40 -0
- package/dist/components/split/split.d.ts +41 -0
- package/dist/components/split/split.js +171 -0
- package/dist/components/split/split.styles.d.ts +1 -0
- package/dist/components/split/split.styles.js +51 -0
- package/dist/components/stat/stat.d.ts +9 -0
- package/dist/components/stat/stat.js +52 -7
- package/dist/components/stat/stat.styles.js +15 -5
- package/dist/components/table/table.d.ts +18 -6
- package/dist/components/table/table.js +76 -24
- package/dist/components/table/table.styles.js +32 -0
- package/dist/components/time-range/time-range.d.ts +40 -0
- package/dist/components/time-range/time-range.js +219 -0
- package/dist/components/time-range/time-range.styles.d.ts +1 -0
- package/dist/components/time-range/time-range.styles.js +78 -0
- package/dist/components/toast/toast-item.js +10 -2
- package/dist/components/toast/toast-item.styles.js +13 -4
- package/dist/components/toast/toast.styles.js +0 -1
- package/dist/components/tree/tree-node.d.ts +50 -0
- package/dist/components/tree/tree-node.js +196 -0
- package/dist/components/tree/tree.d.ts +65 -0
- package/dist/components/tree/tree.js +227 -0
- package/dist/components/tree/tree.styles.d.ts +1 -0
- package/dist/components/tree/tree.styles.js +13 -0
- package/dist/internal/form-associated.js +49 -1
- package/dist/internal/icons.d.ts +11 -0
- package/dist/internal/icons.js +64 -0
- package/dist/internal/tokens.styles.js +41 -1
- package/dist/lyra.d.ts +67 -0
- package/dist/lyra.js +55 -0
- package/llms-full.txt +1425 -0
- package/llms.txt +47 -0
- 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
|
-
|
|
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
|
-
|
|
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:
|
|
7
|
-
--hide-duration:
|
|
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)
|
|
41
|
-
transform var(--show-duration)
|
|
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
|
`;
|
|
@@ -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);
|