@aceshooting/lyra-ui 0.1.0 → 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 (125) 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 +32 -0
  46. package/dist/components/empty/empty.js +90 -0
  47. package/dist/components/empty/empty.styles.d.ts +1 -0
  48. package/dist/components/empty/empty.styles.js +42 -0
  49. package/dist/components/export-button/csv.d.ts +10 -0
  50. package/dist/components/export-button/csv.js +28 -0
  51. package/dist/components/export-button/export-button.d.ts +41 -0
  52. package/dist/components/export-button/export-button.js +151 -0
  53. package/dist/components/export-button/export-button.styles.d.ts +1 -0
  54. package/dist/components/export-button/export-button.styles.js +79 -0
  55. package/dist/components/file-input/accept.d.ts +14 -0
  56. package/dist/components/file-input/accept.js +30 -0
  57. package/dist/components/file-input/file-input.d.ts +47 -0
  58. package/dist/components/file-input/file-input.js +182 -0
  59. package/dist/components/file-input/file-input.styles.d.ts +1 -0
  60. package/dist/components/file-input/file-input.styles.js +44 -0
  61. package/dist/components/flag/flag.d.ts +4 -0
  62. package/dist/components/flag/flag.js +24 -1
  63. package/dist/components/flag/flag.styles.js +1 -1
  64. package/dist/components/gauge/gauge.d.ts +29 -0
  65. package/dist/components/gauge/gauge.js +133 -0
  66. package/dist/components/gauge/gauge.styles.d.ts +1 -0
  67. package/dist/components/gauge/gauge.styles.js +60 -0
  68. package/dist/components/graph/graph.d.ts +68 -0
  69. package/dist/components/graph/graph.js +276 -0
  70. package/dist/components/graph/graph.styles.d.ts +1 -0
  71. package/dist/components/graph/graph.styles.js +34 -0
  72. package/dist/components/heatmap/heatmap-scale.d.ts +8 -0
  73. package/dist/components/heatmap/heatmap-scale.js +17 -0
  74. package/dist/components/heatmap/heatmap.d.ts +66 -0
  75. package/dist/components/heatmap/heatmap.js +252 -0
  76. package/dist/components/heatmap/heatmap.styles.d.ts +1 -0
  77. package/dist/components/heatmap/heatmap.styles.js +39 -0
  78. package/dist/components/map/map-loader.d.ts +8 -0
  79. package/dist/components/map/map-loader.js +17 -0
  80. package/dist/components/map/map.d.ts +49 -0
  81. package/dist/components/map/map.js +189 -0
  82. package/dist/components/map/map.styles.d.ts +1 -0
  83. package/dist/components/map/map.styles.js +48 -0
  84. package/dist/components/playback/playback.d.ts +51 -0
  85. package/dist/components/playback/playback.js +146 -0
  86. package/dist/components/playback/playback.styles.d.ts +1 -0
  87. package/dist/components/playback/playback.styles.js +40 -0
  88. package/dist/components/skeleton/skeleton.d.ts +26 -0
  89. package/dist/components/skeleton/skeleton.js +65 -0
  90. package/dist/components/skeleton/skeleton.styles.d.ts +1 -0
  91. package/dist/components/skeleton/skeleton.styles.js +57 -0
  92. package/dist/components/split/split.d.ts +41 -0
  93. package/dist/components/split/split.js +171 -0
  94. package/dist/components/split/split.styles.d.ts +1 -0
  95. package/dist/components/split/split.styles.js +51 -0
  96. package/dist/components/stat/stat.d.ts +36 -0
  97. package/dist/components/stat/stat.js +117 -0
  98. package/dist/components/stat/stat.styles.d.ts +1 -0
  99. package/dist/components/stat/stat.styles.js +86 -0
  100. package/dist/components/table/table.d.ts +54 -0
  101. package/dist/components/table/table.js +201 -0
  102. package/dist/components/table/table.styles.d.ts +1 -0
  103. package/dist/components/table/table.styles.js +98 -0
  104. package/dist/components/time-range/time-range.d.ts +40 -0
  105. package/dist/components/time-range/time-range.js +219 -0
  106. package/dist/components/time-range/time-range.styles.d.ts +1 -0
  107. package/dist/components/time-range/time-range.styles.js +78 -0
  108. package/dist/components/toast/toast-item.js +10 -2
  109. package/dist/components/toast/toast-item.styles.js +13 -4
  110. package/dist/components/toast/toast.styles.js +0 -1
  111. package/dist/components/tree/tree-node.d.ts +50 -0
  112. package/dist/components/tree/tree-node.js +196 -0
  113. package/dist/components/tree/tree.d.ts +65 -0
  114. package/dist/components/tree/tree.js +227 -0
  115. package/dist/components/tree/tree.styles.d.ts +1 -0
  116. package/dist/components/tree/tree.styles.js +13 -0
  117. package/dist/internal/form-associated.js +49 -1
  118. package/dist/internal/icons.d.ts +11 -0
  119. package/dist/internal/icons.js +64 -0
  120. package/dist/internal/tokens.styles.js +41 -1
  121. package/dist/lyra.d.ts +67 -0
  122. package/dist/lyra.js +55 -0
  123. package/llms-full.txt +1425 -0
  124. package/llms.txt +47 -0
  125. package/package.json +60 -11
@@ -0,0 +1,252 @@
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, query } from 'lit/decorators.js';
9
+ import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { defineElement } from '../../internal/prefix.js';
11
+ import { linearAlpha, sqrtStep } from './heatmap-scale.js';
12
+ import { styles } from './heatmap.styles.js';
13
+ const PAD_LEFT = 60;
14
+ const PAD_TOP = 20;
15
+ const NO_DATA_FILL = 'rgba(128,128,128,0.25)';
16
+ const RAMP_STEPS = 7;
17
+ const FALLBACK_SCALE_LO = '#cde2fb';
18
+ const FALLBACK_SCALE_HI = '#0969da';
19
+ const HEX_RE = /^([0-9a-f]{3}|[0-9a-f]{6})$/i;
20
+ const RGB_RE = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i;
21
+ /**
22
+ * Parses a strict `#rgb` or `#rrggbb` hex string into an `[r, g, b]` triple,
23
+ * or `null` if `hex` isn't one (rather than silently coercing an unparsable
24
+ * string to `0` via `Number.parseInt(..., 16)` returning `NaN`).
25
+ */
26
+ export function hexToRgb(hex) {
27
+ const clean = hex.trim().replace('#', '');
28
+ if (!HEX_RE.test(clean))
29
+ return null;
30
+ const full = clean.length === 3 ? clean.split('').map((c) => c + c).join('') : clean;
31
+ const num = Number.parseInt(full, 16);
32
+ return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
33
+ }
34
+ function parseRgbString(value) {
35
+ const match = RGB_RE.exec(value);
36
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
37
+ }
38
+ let scratchCtx;
39
+ /** A detached 1x1 canvas used solely to normalize CSS color strings. */
40
+ function getScratchCtx() {
41
+ if (scratchCtx === undefined) {
42
+ scratchCtx = document.createElement('canvas').getContext('2d');
43
+ }
44
+ return scratchCtx;
45
+ }
46
+ const warnedInvalidColors = new Set();
47
+ function warnInvalidColor(color) {
48
+ if (warnedInvalidColors.has(color))
49
+ return;
50
+ warnedInvalidColors.add(color);
51
+ console.warn(`<lyra-heatmap> could not parse "${color}" (set via --lyra-heatmap-scale-lo/-hi) as a CSS ` +
52
+ 'color; falling back to the default ramp endpoint.');
53
+ }
54
+ /**
55
+ * Resolves any syntactically valid CSS `<color>` — hex, `rgb()`, `hsl()`,
56
+ * `oklch()`, a named color, etc. — to an `[r, g, b]` triple.
57
+ *
58
+ * Hand-rolling a parser for every CSS color syntax is unnecessary and
59
+ * error-prone (a naive hex-only parser silently turns an unrecognized format
60
+ * into `NaN` -> `0`, i.e. solid black). The canvas 2D context already
61
+ * implements the full CSS color grammar via its `fillStyle` setter, so this
62
+ * normalizes through that instead. Assigning an unparsable string to
63
+ * `fillStyle` is a spec'd no-op (the previous value is kept, it never
64
+ * throws), so a sentinel round-trip is used to detect that case and fall
65
+ * back to `fallbackHex` (with a one-time `console.warn`) instead of
66
+ * silently drawing the wrong color.
67
+ */
68
+ export function resolveRgb(color, fallbackHex) {
69
+ const fallback = hexToRgb(fallbackHex) ?? [0, 0, 0];
70
+ const direct = hexToRgb(color);
71
+ if (direct)
72
+ return direct;
73
+ const ctx = getScratchCtx();
74
+ if (!ctx)
75
+ return fallback;
76
+ const sentinel = 'rgb(1, 2, 3)';
77
+ ctx.fillStyle = sentinel;
78
+ const sentinelNormalized = ctx.fillStyle;
79
+ ctx.fillStyle = color;
80
+ if (ctx.fillStyle === sentinelNormalized && color.trim() !== sentinel) {
81
+ warnInvalidColor(color);
82
+ return fallback;
83
+ }
84
+ const normalized = ctx.fillStyle;
85
+ return hexToRgb(normalized) ?? parseRgbString(normalized) ?? fallback;
86
+ }
87
+ /** Linearly interpolates between two already-resolved `[r, g, b]` triples at `t` in `[0, 1]`. */
88
+ function mixRgb(from, to, t) {
89
+ const clamped = Math.min(1, Math.max(0, t));
90
+ const r = Math.round(from[0] + (to[0] - from[0]) * clamped);
91
+ const g = Math.round(from[1] + (to[1] - from[1]) * clamped);
92
+ const b = Math.round(from[2] + (to[2] - from[2]) * clamped);
93
+ return `rgb(${r}, ${g}, ${b})`;
94
+ }
95
+ /** Linearly interpolates between two CSS colors (any valid syntax) at `t` in `[0, 1]`. */
96
+ export function mixColor(fromColor, toColor, t) {
97
+ return mixRgb(resolveRgb(fromColor, FALLBACK_SCALE_LO), resolveRgb(toColor, FALLBACK_SCALE_HI), t);
98
+ }
99
+ /**
100
+ * `<lyra-heatmap>` — a Canvas matrix heatmap with a DPR-aware, resize-aware
101
+ * redraw loop. `-1` cells are treated as "no data". The sequential color
102
+ * ramp's endpoints are read from the `--lyra-heatmap-scale-lo`/`-hi` custom
103
+ * properties (declared in `heatmap.styles.ts`) so hosts can retheme it —
104
+ * canvas can't consume `var()` directly, so they're resolved once per draw
105
+ * via `getComputedStyle`, then normalized to RGB by `resolveRgb()` (any
106
+ * valid CSS color syntax, not just hex — see its doc comment).
107
+ *
108
+ * @customElement lyra-heatmap
109
+ * @csspart base, canvas, legend
110
+ */
111
+ export class LyraHeatmap extends LyraElement {
112
+ constructor() {
113
+ super(...arguments);
114
+ this.rowLabels = [];
115
+ this.colLabels = [];
116
+ this.values = [];
117
+ this.cellSize = 22;
118
+ this.valueLabel = 'value';
119
+ this.scale = 'linear';
120
+ this.onDprChange = () => {
121
+ this.watchDpr();
122
+ this.draw();
123
+ };
124
+ }
125
+ static { this.styles = [LyraElement.styles, styles]; }
126
+ connectedCallback() {
127
+ super.connectedCallback();
128
+ this.resizeObserver = new ResizeObserver(() => this.draw());
129
+ this.resizeObserver.observe(this);
130
+ this.watchDpr();
131
+ }
132
+ disconnectedCallback() {
133
+ super.disconnectedCallback();
134
+ this.resizeObserver?.disconnect();
135
+ this.dprQuery?.removeEventListener('change', this.onDprChange);
136
+ }
137
+ watchDpr() {
138
+ this.dprQuery = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
139
+ this.dprQuery.addEventListener('change', this.onDprChange);
140
+ }
141
+ willUpdate() {
142
+ const rows = this.rowLabels.length;
143
+ const cols = this.colLabels.length;
144
+ const flat = this.values.flat().filter((v) => v >= 0);
145
+ const range = flat.length ? `${Math.min(...flat)}–${Math.max(...flat)}` : 'no data';
146
+ this.setAttribute('role', 'img');
147
+ this.setAttribute('aria-label', `Heatmap of ${rows} × ${cols} cells, ${this.valueLabel} range ${range}`);
148
+ }
149
+ updated() {
150
+ this.draw();
151
+ }
152
+ /** Reads the customizable ramp endpoints off the host's computed style. */
153
+ scaleEndpoints() {
154
+ const cs = getComputedStyle(this);
155
+ const lo = cs.getPropertyValue('--lyra-heatmap-scale-lo').trim() || FALLBACK_SCALE_LO;
156
+ const hi = cs.getPropertyValue('--lyra-heatmap-scale-hi').trim() || FALLBACK_SCALE_HI;
157
+ return [lo, hi];
158
+ }
159
+ /** Resolves the `--lyra-color-text-quiet` chrome token for axis labels. */
160
+ labelColor() {
161
+ return getComputedStyle(this).getPropertyValue('--lyra-color-text-quiet').trim() || '#6b7280';
162
+ }
163
+ draw() {
164
+ if (!this.canvas)
165
+ return;
166
+ const rows = this.rowLabels.length;
167
+ const cols = this.colLabels.length;
168
+ const w = PAD_LEFT + cols * this.cellSize;
169
+ const h = PAD_TOP + rows * this.cellSize;
170
+ const dpr = window.devicePixelRatio || 1;
171
+ this.canvas.width = w * dpr;
172
+ this.canvas.height = h * dpr;
173
+ this.canvas.style.width = `${w}px`;
174
+ this.canvas.style.height = `${h}px`;
175
+ const ctx = this.canvas.getContext('2d');
176
+ if (!ctx)
177
+ return;
178
+ ctx.scale(dpr, dpr);
179
+ ctx.clearRect(0, 0, w, h);
180
+ const flat = this.values.flat().filter((v) => v >= 0);
181
+ const lo = flat.length ? Math.min(...flat) : 0;
182
+ const hi = flat.length ? Math.max(...flat) : 1;
183
+ const [scaleLo, scaleHi] = this.scaleEndpoints();
184
+ const loRgb = resolveRgb(scaleLo, FALLBACK_SCALE_LO);
185
+ const hiRgb = resolveRgb(scaleHi, FALLBACK_SCALE_HI);
186
+ const ramp = Array.from({ length: RAMP_STEPS }, (_, i) => mixRgb(loRgb, hiRgb, i / (RAMP_STEPS - 1)));
187
+ for (let r = 0; r < rows; r++) {
188
+ for (let c = 0; c < cols; c++) {
189
+ const v = this.values[r]?.[c] ?? -1;
190
+ const x = PAD_LEFT + c * this.cellSize;
191
+ const y = PAD_TOP + r * this.cellSize;
192
+ if (v < 0) {
193
+ ctx.fillStyle = NO_DATA_FILL;
194
+ }
195
+ else if (this.scale === 'sqrt') {
196
+ const step = sqrtStep(v, hi, RAMP_STEPS);
197
+ ctx.fillStyle = step < 0 ? NO_DATA_FILL : ramp[step];
198
+ }
199
+ else {
200
+ // linearAlpha() returns a 0.1-1.0 ramp position; reused here as a
201
+ // mix ratio between the two ramp-endpoint colors (rather than as a
202
+ // literal canvas alpha channel) so the 'linear' scale also respects
203
+ // --lyra-heatmap-scale-lo/-hi.
204
+ const t = linearAlpha(v, lo, hi);
205
+ ctx.fillStyle = mixRgb(loRgb, hiRgb, t);
206
+ }
207
+ ctx.fillRect(x, y, this.cellSize - 1, this.cellSize - 1);
208
+ }
209
+ }
210
+ ctx.fillStyle = this.labelColor();
211
+ ctx.font = '10px sans-serif';
212
+ this.rowLabels.forEach((label, r) => {
213
+ ctx.fillText(label, 4, PAD_TOP + r * this.cellSize + this.cellSize / 2 + 3);
214
+ });
215
+ this.colLabels.forEach((label, c) => {
216
+ ctx.fillText(label, PAD_LEFT + c * this.cellSize + 2, PAD_TOP - 6);
217
+ });
218
+ }
219
+ render() {
220
+ return html `
221
+ <div part="base">
222
+ <canvas part="canvas"></canvas>
223
+ <div part="legend">
224
+ <span class="bar"></span>
225
+ <span>${this.valueLabel}</span>
226
+ </div>
227
+ </div>
228
+ `;
229
+ }
230
+ }
231
+ __decorate([
232
+ property({ attribute: false })
233
+ ], LyraHeatmap.prototype, "rowLabels", void 0);
234
+ __decorate([
235
+ property({ attribute: false })
236
+ ], LyraHeatmap.prototype, "colLabels", void 0);
237
+ __decorate([
238
+ property({ attribute: false })
239
+ ], LyraHeatmap.prototype, "values", void 0);
240
+ __decorate([
241
+ property({ type: Number, attribute: 'cell-size' })
242
+ ], LyraHeatmap.prototype, "cellSize", void 0);
243
+ __decorate([
244
+ property({ attribute: 'value-label' })
245
+ ], LyraHeatmap.prototype, "valueLabel", void 0);
246
+ __decorate([
247
+ property()
248
+ ], LyraHeatmap.prototype, "scale", void 0);
249
+ __decorate([
250
+ query('canvas')
251
+ ], LyraHeatmap.prototype, "canvas", void 0);
252
+ defineElement('heatmap', LyraHeatmap);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,39 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ /* Sequential ramp endpoints for the data-driven cell colors (canvas can't
6
+ consume var() directly, so heatmap.ts resolves these via
7
+ getComputedStyle and interpolates between them). Componentspecific
8
+ business color per Global Constraints — exposed so hosts can retheme
9
+ the ramp without raw hex leaking into the public API. */
10
+ --lyra-heatmap-scale-lo: #cde2fb;
11
+ --lyra-heatmap-scale-hi: #0969da;
12
+ }
13
+ [part='base'] {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: var(--lyra-space-xs);
17
+ }
18
+ canvas {
19
+ display: block;
20
+ inline-size: 100%;
21
+ }
22
+ [part='legend'] {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: var(--lyra-space-xs);
26
+ font-size: 0.75rem;
27
+ color: var(--lyra-color-text-quiet);
28
+ }
29
+ [part='legend'] .bar {
30
+ inline-size: 6rem;
31
+ block-size: 0.5rem;
32
+ border-radius: 2px;
33
+ background: linear-gradient(
34
+ to right,
35
+ var(--lyra-heatmap-scale-lo),
36
+ var(--lyra-heatmap-scale-hi)
37
+ );
38
+ }
39
+ `;
@@ -0,0 +1,8 @@
1
+ import type * as MaplibreModule from 'maplibre-gl';
2
+ /**
3
+ * Lazily loads the optional peer dependency `maplibre-gl` once per page.
4
+ * Resolves to `null` (with a one-time warning) if it isn't installed —
5
+ * mirrors `<lyra-flag>`'s peer-dependency pattern. Consumers must separately
6
+ * import `maplibre-gl/dist/maplibre-gl.css` once they install the peer.
7
+ */
8
+ export declare function loadMaplibre(): Promise<typeof MaplibreModule | null>;
@@ -0,0 +1,17 @@
1
+ let maplibre;
2
+ /**
3
+ * Lazily loads the optional peer dependency `maplibre-gl` once per page.
4
+ * Resolves to `null` (with a one-time warning) if it isn't installed —
5
+ * mirrors `<lyra-flag>`'s peer-dependency pattern. Consumers must separately
6
+ * import `maplibre-gl/dist/maplibre-gl.css` once they install the peer.
7
+ */
8
+ export function loadMaplibre() {
9
+ if (!maplibre) {
10
+ maplibre = import('maplibre-gl').catch(() => {
11
+ console.warn('<lyra-map> needs the optional peer dependency `maplibre-gl` — install it with ' +
12
+ '`pnpm add maplibre-gl` and import `maplibre-gl/dist/maplibre-gl.css` once in your app.');
13
+ return null;
14
+ });
15
+ }
16
+ return maplibre;
17
+ }
@@ -0,0 +1,49 @@
1
+ import { type TemplateResult, type PropertyValues } from 'lit';
2
+ import type { Map as MaplibreMap, StyleSpecification } from 'maplibre-gl';
3
+ import { LyraElement } from '../../internal/lyra-element.js';
4
+ import '../skeleton/skeleton.js';
5
+ export interface LegendEntry {
6
+ color: string;
7
+ label: string;
8
+ }
9
+ export interface ChoroplethLayer {
10
+ sourceId: string;
11
+ geojson: GeoJSON.FeatureCollection;
12
+ field: string;
13
+ stops: [number, string][];
14
+ }
15
+ /**
16
+ * `<lyra-map>` — a maplibre-gl wrapper with a declarative legend and
17
+ * choropleth GeoJSON layer, plus a raw `map` escape hatch. Requires the
18
+ * optional peer dep `maplibre-gl` (consumers also import its CSS once).
19
+ *
20
+ * @customElement lyra-map
21
+ * @event lyra-map-load - Fired once the underlying maplibregl.Map loads.
22
+ * @event lyra-map-click - `detail: { lngLat, feature? }`.
23
+ * @csspart base, container, legend, legend-swatch
24
+ */
25
+ export declare class LyraMap extends LyraElement {
26
+ static styles: import("lit").CSSResultGroup[];
27
+ center: [number, number];
28
+ zoom: number;
29
+ mapStyle: StyleSpecification | string;
30
+ legend: LegendEntry[];
31
+ choropleth?: ChoroplethLayer;
32
+ /** True until the lazy-loaded `maplibre-gl` peer dependency has settled (success or failure). */
33
+ private loading;
34
+ private containerEl?;
35
+ private _map?;
36
+ private _styleLoaded;
37
+ /** The raw `maplibregl.Map` instance — escape hatch for anything this wrapper doesn't expose. */
38
+ get map(): MaplibreMap | undefined;
39
+ connectedCallback(): void;
40
+ disconnectedCallback(): void;
41
+ protected updated(changed: PropertyValues): void;
42
+ private applyChoropleth;
43
+ render(): TemplateResult;
44
+ }
45
+ declare global {
46
+ interface HTMLElementTagNameMap {
47
+ 'lyra-map': LyraMap;
48
+ }
49
+ }
@@ -0,0 +1,189 @@
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
+ /// <reference types="geojson" />
8
+ import { html, nothing } from 'lit';
9
+ import { property, query, state } from 'lit/decorators.js';
10
+ import { LyraElement } from '../../internal/lyra-element.js';
11
+ import { defineElement } from '../../internal/prefix.js';
12
+ import { loadMaplibre } from './map-loader.js';
13
+ import { styles } from './map.styles.js';
14
+ import '../skeleton/skeleton.js';
15
+ const DEFAULT_STYLE = {
16
+ version: 8,
17
+ sources: {
18
+ 'lyra-osm': {
19
+ type: 'raster',
20
+ tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
21
+ tileSize: 256,
22
+ attribution: '© OpenStreetMap contributors',
23
+ },
24
+ },
25
+ layers: [{ id: 'lyra-osm', type: 'raster', source: 'lyra-osm' }],
26
+ };
27
+ /**
28
+ * `<lyra-map>` — a maplibre-gl wrapper with a declarative legend and
29
+ * choropleth GeoJSON layer, plus a raw `map` escape hatch. Requires the
30
+ * optional peer dep `maplibre-gl` (consumers also import its CSS once).
31
+ *
32
+ * @customElement lyra-map
33
+ * @event lyra-map-load - Fired once the underlying maplibregl.Map loads.
34
+ * @event lyra-map-click - `detail: { lngLat, feature? }`.
35
+ * @csspart base, container, legend, legend-swatch
36
+ */
37
+ export class LyraMap extends LyraElement {
38
+ constructor() {
39
+ super(...arguments);
40
+ this.center = [0, 0];
41
+ this.zoom = 2;
42
+ this.mapStyle = DEFAULT_STYLE;
43
+ this.legend = [];
44
+ /** True until the lazy-loaded `maplibre-gl` peer dependency has settled (success or failure). */
45
+ this.loading = true;
46
+ // Tracks whether the style has fired its initial 'load' (i.e. addSource/
47
+ // addLayer/setPaintProperty are now safe to call), rather than re-querying
48
+ // `this._map.isStyleLoaded()`: that also reflects in-flight *tile* loading
49
+ // for every source, so it flips back to `false` as soon as the choropleth's
50
+ // own GeoJSON source is added — which would wrongly block subsequent
51
+ // choropleth updates from ever re-running.
52
+ this._styleLoaded = false;
53
+ }
54
+ static { this.styles = [LyraElement.styles, styles]; }
55
+ /** The raw `maplibregl.Map` instance — escape hatch for anything this wrapper doesn't expose. */
56
+ get map() {
57
+ return this._map;
58
+ }
59
+ connectedCallback() {
60
+ super.connectedCallback();
61
+ void loadMaplibre().then(async (mod) => {
62
+ this.loading = false;
63
+ if (!mod)
64
+ return;
65
+ // `[part="container"]` only exists once `loading` flips to `false` and
66
+ // Lit re-renders — wait for that render to land before querying it.
67
+ await this.updateComplete;
68
+ // The element may have been removed from the DOM while the async
69
+ // loadMaplibre()/updateComplete window was in flight — don't spin up
70
+ // a maplibre Map (WebGL context + event listeners) for a detached
71
+ // instance (disconnectedCallback's cleanup already ran).
72
+ if (!this.containerEl || !this.isConnected)
73
+ return;
74
+ this._map = new mod.Map({
75
+ container: this.containerEl,
76
+ style: this.mapStyle,
77
+ center: this.center,
78
+ zoom: this.zoom,
79
+ });
80
+ this._map.on('load', () => {
81
+ this._styleLoaded = true;
82
+ this.applyChoropleth();
83
+ this.emit('lyra-map-load');
84
+ });
85
+ this._map.on('click', (e) => {
86
+ // Guard on the fill layer actually existing, not just `this.choropleth` being
87
+ // set: applyChoropleth() only adds it once the style has finished loading, so
88
+ // a click landing in that window (choropleth set before the map's own 'load')
89
+ // would otherwise query a layer id maplibre-gl doesn't know about yet.
90
+ const fillLayerId = this.choropleth ? `${this.choropleth.sourceId}-fill` : undefined;
91
+ const features = fillLayerId && this._map.getLayer(fillLayerId)
92
+ ? this._map.queryRenderedFeatures(e.point, { layers: [fillLayerId] })
93
+ : [];
94
+ this.emit('lyra-map-click', {
95
+ lngLat: [e.lngLat.lng, e.lngLat.lat],
96
+ feature: features[0],
97
+ });
98
+ });
99
+ });
100
+ }
101
+ disconnectedCallback() {
102
+ super.disconnectedCallback();
103
+ this._map?.remove();
104
+ this._map = undefined;
105
+ this._styleLoaded = false;
106
+ }
107
+ updated(changed) {
108
+ if (this.loading)
109
+ this.setAttribute('aria-busy', 'true');
110
+ else
111
+ this.removeAttribute('aria-busy');
112
+ if (changed.has('choropleth') && this._styleLoaded)
113
+ this.applyChoropleth();
114
+ if (changed.has('center') && this._map)
115
+ this._map.setCenter(this.center);
116
+ if (changed.has('zoom') && this._map)
117
+ this._map.setZoom(this.zoom);
118
+ }
119
+ applyChoropleth() {
120
+ if (!this.choropleth || !this._map)
121
+ return;
122
+ const { sourceId, geojson, field, stops } = this.choropleth;
123
+ const fillLayerId = `${sourceId}-fill`;
124
+ const colorExpr = ['interpolate', ['linear'], ['get', field]];
125
+ for (const [value, color] of stops)
126
+ colorExpr.push(value, color);
127
+ const existingSource = this._map.getSource(sourceId);
128
+ if (existingSource) {
129
+ // Re-apply both the data and the color expression: `field`/`stops` may have
130
+ // changed even though `sourceId` didn't (e.g. switching which metric colors
131
+ // the same GeoJSON), so the paint property must be refreshed too.
132
+ existingSource.setData(geojson);
133
+ this._map.setPaintProperty(fillLayerId, 'fill-color', colorExpr);
134
+ }
135
+ else {
136
+ this._map.addSource(sourceId, { type: 'geojson', data: geojson, promoteId: 'id' });
137
+ this._map.addLayer({
138
+ id: fillLayerId,
139
+ type: 'fill',
140
+ source: sourceId,
141
+ paint: { 'fill-color': colorExpr, 'fill-opacity': 0.75 },
142
+ });
143
+ }
144
+ }
145
+ render() {
146
+ // Lit's own whitespace/comment marker nodes around the `${...}` binding mean
147
+ // `[part="legend"]` is never truly `:empty` in CSS even with zero entries, so
148
+ // the panel is omitted from the template entirely when `legend` is empty
149
+ // (same `nothing` pattern `stat.ts` uses to omit its optional trend section)
150
+ // rather than relying on a CSS `:empty` selector that can never match.
151
+ return html `
152
+ <div part="base">
153
+ ${this.loading
154
+ ? html `<lyra-skeleton variant="rect"></lyra-skeleton>`
155
+ : html `<div part="container"></div>`}
156
+ ${this.legend.length
157
+ ? html `<div part="legend">
158
+ ${this.legend.map((entry) => html `<div class="legend-row">
159
+ <span part="legend-swatch" style=${`background:${entry.color}`}></span>
160
+ <span>${entry.label}</span>
161
+ </div>`)}
162
+ </div>`
163
+ : nothing}
164
+ </div>
165
+ `;
166
+ }
167
+ }
168
+ __decorate([
169
+ property({ type: Array })
170
+ ], LyraMap.prototype, "center", void 0);
171
+ __decorate([
172
+ property({ type: Number })
173
+ ], LyraMap.prototype, "zoom", void 0);
174
+ __decorate([
175
+ property({ attribute: false })
176
+ ], LyraMap.prototype, "mapStyle", void 0);
177
+ __decorate([
178
+ property({ attribute: false })
179
+ ], LyraMap.prototype, "legend", void 0);
180
+ __decorate([
181
+ property({ attribute: false })
182
+ ], LyraMap.prototype, "choropleth", void 0);
183
+ __decorate([
184
+ state()
185
+ ], LyraMap.prototype, "loading", void 0);
186
+ __decorate([
187
+ query('[part="container"]')
188
+ ], LyraMap.prototype, "containerEl", void 0);
189
+ defineElement('map', LyraMap);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,48 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ position: relative;
6
+ inline-size: 100%;
7
+ block-size: 24rem;
8
+ }
9
+ [part='base'] {
10
+ position: relative;
11
+ inline-size: 100%;
12
+ block-size: 100%;
13
+ }
14
+ lyra-skeleton {
15
+ --lyra-skeleton-w: 100%;
16
+ --lyra-skeleton-h: 100%;
17
+ }
18
+ [part='container'] {
19
+ position: absolute;
20
+ inset: 0;
21
+ }
22
+ [part='legend'] {
23
+ position: absolute;
24
+ inset-block-end: var(--lyra-space-s);
25
+ inset-inline-start: var(--lyra-space-s);
26
+ z-index: 1;
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: 0.2rem;
30
+ padding: var(--lyra-space-xs) var(--lyra-space-s);
31
+ background: var(--lyra-color-surface);
32
+ border: 1px solid var(--lyra-color-border);
33
+ border-radius: var(--lyra-radius);
34
+ box-shadow: var(--lyra-shadow);
35
+ font-size: 0.75rem;
36
+ }
37
+ .legend-row {
38
+ display: flex;
39
+ align-items: center;
40
+ gap: 0.35rem;
41
+ }
42
+ [part='legend-swatch'] {
43
+ inline-size: 0.75rem;
44
+ block-size: 0.75rem;
45
+ border-radius: 2px;
46
+ flex: 0 0 auto;
47
+ }
48
+ `;
@@ -0,0 +1,51 @@
1
+ import { type TemplateResult, type PropertyValues } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ /**
4
+ * `<lyra-playback>` — steps an index through `[0, length)` on a fixed
5
+ * interval (play/pause), like the manual play-timers duplicated across
6
+ * per-repo time-series dashboards.
7
+ *
8
+ * @customElement lyra-playback
9
+ * @event lyra-play
10
+ * @event lyra-pause
11
+ * @event lyra-step - `detail: { index }`, fired on every tick and manual step.
12
+ * @csspart base, play-button, slider
13
+ */
14
+ export declare class LyraPlayback extends LyraElement {
15
+ static styles: import("lit").CSSResultGroup[];
16
+ length: number;
17
+ index: number;
18
+ intervalMs: number;
19
+ playing: boolean;
20
+ loop: boolean;
21
+ /**
22
+ * Re-declared as a Lit reactive property (shadowing the inherited plain
23
+ * `HTMLElement.hidden` IDL property) so that setting `el.hidden = true` —
24
+ * or the `hidden` attribute — actually enters Lit's change-tracking system
25
+ * and the `willUpdate` auto-pause guard below can see it via `changed`.
26
+ * `reflect: true` preserves the native attribute-reflection behavior.
27
+ */
28
+ hidden: boolean;
29
+ private timer?;
30
+ disconnectedCallback(): void;
31
+ protected willUpdate(changed: PropertyValues): void;
32
+ /** Start playback; no-op if there's nothing to advance through. */
33
+ play(): void;
34
+ /** Stop playback. */
35
+ pause(): void;
36
+ toggle(): void;
37
+ private tick;
38
+ private setIndex;
39
+ /** Advance one step without starting playback. */
40
+ next(): void;
41
+ /** Go back one step without starting playback. */
42
+ previous(): void;
43
+ /** Jump to an explicit index, pausing playback. */
44
+ goTo(index: number): void;
45
+ render(): TemplateResult;
46
+ }
47
+ declare global {
48
+ interface HTMLElementTagNameMap {
49
+ 'lyra-playback': LyraPlayback;
50
+ }
51
+ }