@aceshooting/lyra-ui 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -9
- package/custom-elements.json +10463 -4519
- package/dist/components/chart/chart.d.ts +46 -1
- package/dist/components/chart/chart.js +148 -10
- package/dist/components/chart/chart.stories.d.ts +32 -0
- package/dist/components/chart/chart.stories.js +141 -0
- package/dist/components/chart/chart.styles.js +16 -0
- package/dist/components/chart/histogram.d.ts +2 -0
- package/dist/components/chart/histogram.js +18 -2
- package/dist/components/chart/line-chart.stories.d.ts +17 -0
- package/dist/components/chart/line-chart.stories.js +75 -0
- package/dist/components/chart/lite-chart.d.ts +74 -0
- package/dist/components/chart/lite-chart.js +384 -0
- package/dist/components/chart/lite-chart.stories.d.ts +12 -0
- package/dist/components/chart/lite-chart.stories.js +98 -0
- package/dist/components/chart/lite-chart.styles.d.ts +1 -0
- package/dist/components/chart/lite-chart.styles.js +83 -0
- package/dist/components/combobox/combobox.d.ts +9 -0
- package/dist/components/combobox/combobox.js +75 -12
- package/dist/components/combobox/combobox.stories.d.ts +18 -0
- package/dist/components/combobox/combobox.stories.js +75 -0
- package/dist/components/date-picker/calendar-core.js +13 -2
- package/dist/components/date-picker/date-input.d.ts +9 -1
- package/dist/components/date-picker/date-input.js +55 -7
- package/dist/components/date-picker/date-input.stories.d.ts +5 -0
- package/dist/components/date-picker/date-input.stories.js +36 -0
- package/dist/components/date-picker/date-picker.d.ts +11 -3
- package/dist/components/date-picker/date-picker.js +87 -37
- package/dist/components/date-picker/date-picker.stories.d.ts +4 -0
- package/dist/components/date-picker/date-picker.stories.js +12 -0
- package/dist/components/date-picker/date-picker.styles.js +1 -1
- package/dist/components/empty/empty.d.ts +12 -1
- package/dist/components/empty/empty.js +20 -3
- package/dist/components/empty/empty.stories.d.ts +2 -0
- package/dist/components/empty/empty.stories.js +20 -0
- package/dist/components/empty/empty.styles.js +11 -0
- package/dist/components/export-button/csv.js +6 -2
- package/dist/components/export-button/export-button.d.ts +15 -1
- package/dist/components/export-button/export-button.js +89 -12
- package/dist/components/export-button/export-button.stories.d.ts +1 -0
- package/dist/components/export-button/export-button.stories.js +5 -0
- package/dist/components/export-button/export-button.styles.js +11 -3
- package/dist/components/file-input/accept.d.ts +8 -2
- package/dist/components/file-input/accept.js +9 -3
- package/dist/components/file-input/file-input.d.ts +5 -2
- package/dist/components/file-input/file-input.js +23 -8
- package/dist/components/file-input/file-input.stories.d.ts +2 -0
- package/dist/components/file-input/file-input.stories.js +9 -0
- package/dist/components/flag/flag.d.ts +48 -3
- package/dist/components/flag/flag.js +65 -29
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/flag/language-map.d.ts +11 -0
- package/dist/components/flag/language-map.js +12 -1
- package/dist/components/gauge/gauge.d.ts +2 -0
- package/dist/components/gauge/gauge.js +19 -5
- package/dist/components/gauge/gauge.stories.d.ts +2 -0
- package/dist/components/gauge/gauge.stories.js +6 -0
- package/dist/components/graph/graph-loader.d.ts +28 -0
- package/dist/components/graph/graph-loader.js +44 -0
- package/dist/components/graph/graph.d.ts +41 -15
- package/dist/components/graph/graph.js +146 -71
- package/dist/components/graph/graph.stories.d.ts +3 -0
- package/dist/components/graph/graph.stories.js +38 -0
- package/dist/components/graph/graph.styles.js +6 -1
- package/dist/components/heatmap/calendar-grid.d.ts +7 -0
- package/dist/components/heatmap/calendar-grid.js +27 -10
- package/dist/components/heatmap/heatmap.d.ts +137 -12
- package/dist/components/heatmap/heatmap.js +427 -27
- package/dist/components/heatmap/heatmap.stories.d.ts +17 -0
- package/dist/components/heatmap/heatmap.stories.js +130 -0
- package/dist/components/heatmap/heatmap.styles.js +60 -2
- package/dist/components/map/map.d.ts +17 -1
- package/dist/components/map/map.js +68 -21
- package/dist/components/map/map.stories.d.ts +10 -0
- package/dist/components/map/map.stories.js +107 -12
- package/dist/components/map/map.styles.js +2 -2
- package/dist/components/playback/playback.d.ts +6 -2
- package/dist/components/playback/playback.js +36 -12
- package/dist/components/playback/playback.stories.d.ts +2 -0
- package/dist/components/playback/playback.stories.js +6 -0
- package/dist/components/playback/playback.styles.js +10 -2
- package/dist/components/select/select.d.ts +122 -0
- package/dist/components/select/select.js +516 -0
- package/dist/components/select/select.stories.d.ts +19 -0
- package/dist/components/select/select.stories.js +75 -0
- package/dist/components/select/select.styles.d.ts +1 -0
- package/dist/components/select/select.styles.js +188 -0
- package/dist/components/skeleton/skeleton.js +4 -7
- package/dist/components/sparkline/sparkline.d.ts +1 -1
- package/dist/components/sparkline/sparkline.js +40 -13
- package/dist/components/sparkline/sparkline.stories.d.ts +4 -0
- package/dist/components/sparkline/sparkline.stories.js +22 -0
- package/dist/components/split/split.d.ts +24 -3
- package/dist/components/split/split.js +155 -31
- package/dist/components/split/split.stories.d.ts +2 -0
- package/dist/components/split/split.stories.js +20 -0
- package/dist/components/stat/stat.d.ts +27 -1
- package/dist/components/stat/stat.js +56 -3
- package/dist/components/stat/stat.stories.d.ts +3 -0
- package/dist/components/stat/stat.stories.js +49 -1
- package/dist/components/stat/stat.styles.js +44 -0
- package/dist/components/table/table.d.ts +64 -2
- package/dist/components/table/table.js +213 -17
- package/dist/components/table/table.stories.d.ts +6 -0
- package/dist/components/table/table.stories.js +45 -0
- package/dist/components/table/table.styles.js +51 -4
- package/dist/components/time-range/time-range.d.ts +34 -5
- package/dist/components/time-range/time-range.js +217 -36
- package/dist/components/time-range/time-range.stories.d.ts +2 -0
- package/dist/components/time-range/time-range.stories.js +13 -0
- package/dist/components/time-range/time-range.styles.js +44 -2
- package/dist/components/toast/toast-item.d.ts +14 -2
- package/dist/components/toast/toast-item.js +134 -17
- package/dist/components/toast/toast-item.styles.js +28 -2
- package/dist/components/toast/toast.stories.d.ts +3 -0
- package/dist/components/toast/toast.stories.js +41 -1
- package/dist/components/toast/toast.styles.js +11 -11
- package/dist/components/toast/toaster.d.ts +1 -1
- package/dist/components/toast/toaster.js +13 -6
- package/dist/components/tree/tree-node.d.ts +13 -7
- package/dist/components/tree/tree-node.js +24 -10
- package/dist/components/tree/tree.d.ts +64 -15
- package/dist/components/tree/tree.js +113 -22
- package/dist/components/tree/tree.stories.d.ts +4 -0
- package/dist/components/tree/tree.stories.js +31 -1
- package/dist/components/tree/update-cascade.d.ts +13 -0
- package/dist/components/tree/update-cascade.js +13 -0
- package/dist/components/widget/widget.d.ts +15 -1
- package/dist/components/widget/widget.js +140 -5
- package/dist/components/widget/widget.stories.d.ts +1 -0
- package/dist/components/widget/widget.stories.js +10 -0
- package/dist/components/widget/widget.styles.js +11 -1
- package/dist/components/word-cloud/word-cloud-layout.d.ts +63 -0
- package/dist/components/word-cloud/word-cloud-layout.js +145 -0
- package/dist/components/word-cloud/word-cloud.d.ts +65 -0
- package/dist/components/word-cloud/word-cloud.js +255 -0
- package/dist/components/word-cloud/word-cloud.stories.d.ts +10 -0
- package/dist/components/word-cloud/word-cloud.stories.js +55 -0
- package/dist/components/word-cloud/word-cloud.styles.d.ts +1 -0
- package/dist/components/word-cloud/word-cloud.styles.js +54 -0
- package/dist/internal/a11y.js +2 -1
- package/dist/internal/rtl.d.ts +7 -0
- package/dist/internal/rtl.js +9 -0
- package/dist/lyra.d.ts +9 -1
- package/dist/lyra.js +6 -0
- package/llms-full.txt +892 -276
- package/llms.txt +12 -9
- package/package.json +6 -4
|
@@ -5,22 +5,32 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { html } from 'lit';
|
|
8
|
-
import { property, query } from 'lit/decorators.js';
|
|
8
|
+
import { property, query, state } from 'lit/decorators.js';
|
|
9
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
9
10
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
11
|
import { defineElement } from '../../internal/prefix.js';
|
|
12
|
+
import { srOnly } from '../../internal/a11y.js';
|
|
11
13
|
import { linearAlpha, minMax, sqrtStep } from './heatmap-scale.js';
|
|
12
14
|
import { styles } from './heatmap.styles.js';
|
|
13
|
-
import { buildCalendarGrid, quartileBucket } from './calendar-grid.js';
|
|
15
|
+
import { buildCalendarGrid, parseIsoDate, quartileBucket } from './calendar-grid.js';
|
|
14
16
|
const PAD_LEFT = 60;
|
|
15
17
|
const PAD_TOP = 20;
|
|
16
18
|
const FALLBACK_NO_DATA_FILL = 'rgba(128,128,128,0.25)';
|
|
17
19
|
const RAMP_STEPS = 7;
|
|
18
20
|
const FALLBACK_SCALE_LO = '#cde2fb';
|
|
19
21
|
const FALLBACK_SCALE_HI = '#0969da';
|
|
22
|
+
const FALLBACK_LABEL_FONT = '10px sans-serif';
|
|
20
23
|
const CAL_PAD_LEFT = 28;
|
|
21
24
|
const CAL_LABEL_H = 16;
|
|
22
25
|
const CAL_CELL = 11;
|
|
23
26
|
const CAL_GAP = 2;
|
|
27
|
+
const DEFAULT_BUCKET_COUNT = 5;
|
|
28
|
+
/** Ring stroke width for both the annotation overlay and the keyboard focus ring. */
|
|
29
|
+
const RING_LINE_WIDTH = 2;
|
|
30
|
+
const FALLBACK_FOCUS_RING_COLOR = '#0969da';
|
|
31
|
+
const FALLBACK_ANNOTATION_COLOR = '#cf222e';
|
|
32
|
+
const ARROW_KEYS = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']);
|
|
33
|
+
const MS_PER_DAY = 86_400_000;
|
|
24
34
|
const HEX_RE = /^([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
25
35
|
const RGB_RE = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i;
|
|
26
36
|
/**
|
|
@@ -56,6 +66,30 @@ function warnInvalidColor(color) {
|
|
|
56
66
|
console.warn(`<lyra-heatmap> could not parse "${color}" (set via --lyra-heatmap-scale-lo/-hi) as a CSS ` +
|
|
57
67
|
'color; falling back to the default ramp endpoint.');
|
|
58
68
|
}
|
|
69
|
+
const warnedInvalidBucketCounts = new Set();
|
|
70
|
+
function warnInvalidBucketCount(value) {
|
|
71
|
+
if (warnedInvalidBucketCounts.has(value))
|
|
72
|
+
return;
|
|
73
|
+
warnedInvalidBucketCounts.add(value);
|
|
74
|
+
console.warn(`<lyra-heatmap> received a non-finite bucket-count (${value}); falling back to ${DEFAULT_BUCKET_COUNT}.`);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Normalizes `bucketCount` to a finite integer >= 2. `Math.max(2, bucketCount)`
|
|
78
|
+
* alone stays `NaN` for a non-numeric `bucket-count` attribute (`Math.max`
|
|
79
|
+
* propagates `NaN`), which silently zeroes out the ramp array and makes every
|
|
80
|
+
* cell keep whatever `fillStyle` the previous draw left. A fractional count
|
|
81
|
+
* is floored rather than passed through as-is, so the ramp array's length
|
|
82
|
+
* (built via `Array.from({ length: buckets }, ...)`, which truncates a
|
|
83
|
+
* fractional `length`) always agrees with the index `quartileBucket` computes
|
|
84
|
+
* from the same `buckets` value.
|
|
85
|
+
*/
|
|
86
|
+
function resolveBucketCount(bucketCount) {
|
|
87
|
+
if (!Number.isFinite(bucketCount)) {
|
|
88
|
+
warnInvalidBucketCount(bucketCount);
|
|
89
|
+
return DEFAULT_BUCKET_COUNT;
|
|
90
|
+
}
|
|
91
|
+
return Math.max(2, Math.floor(bucketCount));
|
|
92
|
+
}
|
|
59
93
|
/**
|
|
60
94
|
* Resolves any syntactically valid CSS `<color>` — hex, `rgb()`, `hsl()`,
|
|
61
95
|
* `oklch()`, a named color, etc. — to an `[r, g, b]` triple.
|
|
@@ -102,16 +136,42 @@ export function mixColor(fromColor, toColor, t) {
|
|
|
102
136
|
return mixRgb(resolveRgb(fromColor, FALLBACK_SCALE_LO), resolveRgb(toColor, FALLBACK_SCALE_HI), t);
|
|
103
137
|
}
|
|
104
138
|
/**
|
|
105
|
-
* `<lyra-heatmap>` — a Canvas
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* via `
|
|
111
|
-
*
|
|
139
|
+
* `<lyra-heatmap>` — a Canvas heatmap with a DPR-aware, resize-aware redraw
|
|
140
|
+
* loop, in one of two `mode`s:
|
|
141
|
+
*
|
|
142
|
+
* - `"matrix"` (default): a `rowLabels` x `colLabels` grid of `values`. `-1`
|
|
143
|
+
* (or any non-finite value) is treated as "no data". `scale="sqrt"`
|
|
144
|
+
* compresses the ramp via `sqrtStep()` so one heavy cell doesn't wash out
|
|
145
|
+
* the rest; the default `"linear"` scale maps linearly instead. `scale`
|
|
146
|
+
* only affects matrix mode — calendar mode always buckets by quartile.
|
|
147
|
+
* - `"calendar"`: a GitHub-style Sunday-Saturday x week grid built from
|
|
148
|
+
* `days`, colored by `quartileBucket()` into `bucketCount` buckets. As in
|
|
149
|
+
* matrix mode, a cell whose `value` is negative or non-finite is treated
|
|
150
|
+
* as "no data" rather than being bucketed.
|
|
151
|
+
*
|
|
152
|
+
* The sequential color ramp's endpoints are read from the
|
|
153
|
+
* `--lyra-heatmap-scale-lo`/`-hi` custom properties (declared in
|
|
154
|
+
* `heatmap.styles.ts`) so hosts can retheme it — canvas can't consume
|
|
155
|
+
* `var()` directly, so they're resolved once per draw via
|
|
156
|
+
* `getComputedStyle`, then normalized to RGB by `resolveRgb()` (any valid
|
|
157
|
+
* CSS color syntax, not just hex — see its doc comment).
|
|
158
|
+
*
|
|
159
|
+
* Every cell is independently addressable: a `pointermove` hit test over the
|
|
160
|
+
* canvas shows `[part="tooltip"]` with that cell's label + value (hidden on
|
|
161
|
+
* `pointerleave`); the canvas is `tabindex="0"` with arrow-key roving focus
|
|
162
|
+
* (a stroked ring redrawn over the focused cell on every draw, plus a
|
|
163
|
+
* visually-hidden `aria-live="polite"` status announcement — avoids a
|
|
164
|
+
* DOM-node-per-cell overlay, which would be hundreds of nodes for a year
|
|
165
|
+
* calendar); and a click, or Enter/Space on the focused cell, fires
|
|
166
|
+
* `lyra-cell-click`. `annotations` additionally strokes a ring around
|
|
167
|
+
* specific cells (e.g. to call out an anomaly), each one optionally
|
|
168
|
+
* surfaced in the legend too via `[part="legend-annotation"]`.
|
|
112
169
|
*
|
|
113
170
|
* @customElement lyra-heatmap
|
|
114
|
-
* @
|
|
171
|
+
* @event lyra-cell-click - Fired on click, or Enter/Space on the
|
|
172
|
+
* focused/hovered cell. `detail: { row, col, value }` in matrix mode,
|
|
173
|
+
* `detail: { date, value }` in calendar mode.
|
|
174
|
+
* @csspart base, canvas, tooltip, live-region, legend, legend-lo, legend-hi, legend-annotation
|
|
115
175
|
*/
|
|
116
176
|
export class LyraHeatmap extends LyraElement {
|
|
117
177
|
constructor() {
|
|
@@ -133,13 +193,47 @@ export class LyraHeatmap extends LyraElement {
|
|
|
133
193
|
this.fitToWidth = false;
|
|
134
194
|
this.mode = 'matrix';
|
|
135
195
|
this.days = [];
|
|
136
|
-
this.bucketCount =
|
|
196
|
+
this.bucketCount = DEFAULT_BUCKET_COUNT;
|
|
197
|
+
/** Cells to ring-highlight — `row`/`col` in matrix mode, `date` in calendar mode. See `HeatmapAnnotation`. */
|
|
198
|
+
this.annotations = [];
|
|
199
|
+
/** The current value range, refreshed once per update cycle by `willUpdate()`. See `computeValueRange()`. */
|
|
200
|
+
this.cachedValueRange = null;
|
|
201
|
+
/** The cell currently under the pointer (`null` when not hovering one) — drives `[part="tooltip"]`. */
|
|
202
|
+
this.hoverCell = null;
|
|
203
|
+
/** The roving keyboard-focus cell cursor, moved by arrow keys — drives the
|
|
204
|
+
* canvas-drawn focus ring and the `[part="live-region"]` announcement. */
|
|
205
|
+
this.focusedCell = null;
|
|
206
|
+
/** Text of the visually-hidden `aria-live="polite"` status announcement, refreshed on every focus move. */
|
|
207
|
+
this.liveText = '';
|
|
137
208
|
this.onDprChange = () => {
|
|
138
209
|
this.watchDpr();
|
|
139
210
|
this.draw();
|
|
140
211
|
};
|
|
212
|
+
this.onPointerMove = (e) => {
|
|
213
|
+
this.hoverCell = this.hitTest(e.offsetX, e.offsetY);
|
|
214
|
+
};
|
|
215
|
+
this.onPointerLeave = () => {
|
|
216
|
+
this.hoverCell = null;
|
|
217
|
+
};
|
|
218
|
+
this.onCanvasClick = (e) => {
|
|
219
|
+
// Prefer the click's own position; fall back to whatever's already
|
|
220
|
+
// keyboard-focused (e.g. a synthetic click dispatched via
|
|
221
|
+
// HTMLElement.click(), which carries no real coordinates).
|
|
222
|
+
const pos = this.hitTest(e.offsetX, e.offsetY) ?? this.focusedCell;
|
|
223
|
+
if (!pos)
|
|
224
|
+
return;
|
|
225
|
+
this.focusedCell = pos;
|
|
226
|
+
this.announce(pos);
|
|
227
|
+
this.emitCellClick(pos);
|
|
228
|
+
};
|
|
229
|
+
this.onKeyDown = (e) => {
|
|
230
|
+
if (this.mode === 'calendar')
|
|
231
|
+
this.onCalendarKeyDown(e);
|
|
232
|
+
else
|
|
233
|
+
this.onMatrixKeyDown(e);
|
|
234
|
+
};
|
|
141
235
|
}
|
|
142
|
-
static { this.styles = [LyraElement.styles, styles]; }
|
|
236
|
+
static { this.styles = [LyraElement.styles, styles, srOnly]; }
|
|
143
237
|
connectedCallback() {
|
|
144
238
|
super.connectedCallback();
|
|
145
239
|
this.resizeObserver = new ResizeObserver(() => this.draw());
|
|
@@ -160,8 +254,21 @@ export class LyraHeatmap extends LyraElement {
|
|
|
160
254
|
this.dprQuery = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
|
|
161
255
|
this.dprQuery.addEventListener('change', this.onDprChange);
|
|
162
256
|
}
|
|
163
|
-
willUpdate() {
|
|
164
|
-
|
|
257
|
+
willUpdate(changed) {
|
|
258
|
+
if (changed.has('mode') || changed.has('rowLabels') || changed.has('colLabels') || changed.has('days')) {
|
|
259
|
+
// The previous focus/hover cursor may no longer address a real cell
|
|
260
|
+
// once the grid's shape (or mode) changes out from under it —
|
|
261
|
+
// stroking a focus ring at a stale (row, col)/(week, weekday), or
|
|
262
|
+
// leaving the live region announcing a stale value, would be actively
|
|
263
|
+
// misleading, so drop both instead. Reactive-property writes belong in
|
|
264
|
+
// willUpdate() (not updated()), which folds them into this same
|
|
265
|
+
// render rather than scheduling a whole extra update pass.
|
|
266
|
+
this.focusedCell = null;
|
|
267
|
+
this.hoverCell = null;
|
|
268
|
+
this.liveText = '';
|
|
269
|
+
}
|
|
270
|
+
this.cachedValueRange = this.computeValueRange();
|
|
271
|
+
const bounds = this.cachedValueRange;
|
|
165
272
|
const range = bounds ? `${bounds[0]}–${bounds[1]}` : 'no data';
|
|
166
273
|
this.setAttribute('role', 'img');
|
|
167
274
|
if (this.mode === 'calendar') {
|
|
@@ -173,8 +280,16 @@ export class LyraHeatmap extends LyraElement {
|
|
|
173
280
|
this.setAttribute('aria-label', `Heatmap of ${rows} × ${cols} cells, ${this.valueLabel} range ${range}`);
|
|
174
281
|
}
|
|
175
282
|
}
|
|
176
|
-
/**
|
|
177
|
-
|
|
283
|
+
/**
|
|
284
|
+
* The real (non-no-data) value range across `values` (or `days` in
|
|
285
|
+
* calendar mode), or `null` if there is none. Only called from
|
|
286
|
+
* `willUpdate()`, which caches the result in `cachedValueRange` for
|
|
287
|
+
* `render()` and `drawMatrix()` to reuse — `willUpdate()`, `render()`, and
|
|
288
|
+
* `updated()` (which triggers `draw()`) all run within the same Lit update
|
|
289
|
+
* cycle against the same `values`/`days`, so a single scan per cycle
|
|
290
|
+
* suffices instead of one per consumer.
|
|
291
|
+
*/
|
|
292
|
+
computeValueRange() {
|
|
178
293
|
const source = this.mode === 'calendar' ? this.days.map((d) => d.value) : this.values.flat();
|
|
179
294
|
return minMax(source.filter((v) => Number.isFinite(v) && v >= 0));
|
|
180
295
|
}
|
|
@@ -192,10 +307,22 @@ export class LyraHeatmap extends LyraElement {
|
|
|
192
307
|
labelColor() {
|
|
193
308
|
return getComputedStyle(this).getPropertyValue('--lyra-color-text-quiet').trim() || '#6b7280';
|
|
194
309
|
}
|
|
310
|
+
/** Reads the customizable canvas axis/label font off the host's computed style. */
|
|
311
|
+
labelFont() {
|
|
312
|
+
return getComputedStyle(this).getPropertyValue('--lyra-heatmap-label-font').trim() || FALLBACK_LABEL_FONT;
|
|
313
|
+
}
|
|
195
314
|
/** Reads the customizable no-data cell fill off the host's computed style. */
|
|
196
315
|
noDataFill() {
|
|
197
316
|
return getComputedStyle(this).getPropertyValue('--lyra-heatmap-no-data-fill').trim() || FALLBACK_NO_DATA_FILL;
|
|
198
317
|
}
|
|
318
|
+
/** Reads the customizable canvas-drawn keyboard-focus-ring stroke color off the host's computed style. */
|
|
319
|
+
focusRingColor() {
|
|
320
|
+
return (getComputedStyle(this).getPropertyValue('--lyra-heatmap-focus-ring-color').trim() || FALLBACK_FOCUS_RING_COLOR);
|
|
321
|
+
}
|
|
322
|
+
/** Reads the customizable canvas-drawn annotation-ring stroke color off the host's computed style. */
|
|
323
|
+
annotationColor() {
|
|
324
|
+
return (getComputedStyle(this).getPropertyValue('--lyra-heatmap-annotation-color').trim() || FALLBACK_ANNOTATION_COLOR);
|
|
325
|
+
}
|
|
199
326
|
draw() {
|
|
200
327
|
if (this.mode === 'calendar')
|
|
201
328
|
this.drawCalendar();
|
|
@@ -225,7 +352,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
225
352
|
const [scaleLo, scaleHi] = this.scaleEndpoints();
|
|
226
353
|
const loRgb = resolveRgb(scaleLo, FALLBACK_SCALE_LO);
|
|
227
354
|
const hiRgb = resolveRgb(scaleHi, FALLBACK_SCALE_HI);
|
|
228
|
-
const buckets =
|
|
355
|
+
const buckets = resolveBucketCount(this.bucketCount);
|
|
229
356
|
const ramp = Array.from({ length: buckets }, (_, i) => mixRgb(loRgb, hiRgb, i / (buckets - 1)));
|
|
230
357
|
const noDataFill = this.noDataFill();
|
|
231
358
|
for (const cell of cells) {
|
|
@@ -237,8 +364,37 @@ export class LyraHeatmap extends LyraElement {
|
|
|
237
364
|
: ramp[quartileBucket(cell.value, sortedValues, buckets)];
|
|
238
365
|
ctx.fillRect(x, y, CAL_CELL, CAL_CELL);
|
|
239
366
|
}
|
|
367
|
+
// Annotation ring overlay, stroked after the fill pass so it reads
|
|
368
|
+
// clearly over the data-driven cell color — see drawMatrix()'s twin of
|
|
369
|
+
// this block for the matrix-mode equivalent.
|
|
370
|
+
if (this.annotations.length) {
|
|
371
|
+
ctx.lineWidth = RING_LINE_WIDTH;
|
|
372
|
+
ctx.strokeStyle = this.annotationColor();
|
|
373
|
+
for (const ann of this.annotations) {
|
|
374
|
+
if (ann.date == null)
|
|
375
|
+
continue;
|
|
376
|
+
const match = cells.find((c) => c.date === ann.date);
|
|
377
|
+
if (!match)
|
|
378
|
+
continue;
|
|
379
|
+
const x = CAL_PAD_LEFT + match.week * (CAL_CELL + CAL_GAP);
|
|
380
|
+
const y = CAL_LABEL_H + match.weekday * (CAL_CELL + CAL_GAP);
|
|
381
|
+
ctx.strokeRect(x + 0.5, y + 0.5, CAL_CELL - 1, CAL_CELL - 1);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Keyboard focus ring, redrawn on top of the fill pass (and any
|
|
385
|
+
// annotation rings) on every draw.
|
|
386
|
+
if (this.focusedCell && 'week' in this.focusedCell) {
|
|
387
|
+
const { week, weekday } = this.focusedCell;
|
|
388
|
+
if (week < weekCount && weekday < 7) {
|
|
389
|
+
ctx.lineWidth = RING_LINE_WIDTH;
|
|
390
|
+
ctx.strokeStyle = this.focusRingColor();
|
|
391
|
+
const x = CAL_PAD_LEFT + week * (CAL_CELL + CAL_GAP);
|
|
392
|
+
const y = CAL_LABEL_H + weekday * (CAL_CELL + CAL_GAP);
|
|
393
|
+
ctx.strokeRect(x + 0.5, y + 0.5, CAL_CELL - 1, CAL_CELL - 1);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
240
396
|
ctx.fillStyle = this.labelColor();
|
|
241
|
-
ctx.font =
|
|
397
|
+
ctx.font = this.labelFont();
|
|
242
398
|
for (const m of monthLabels) {
|
|
243
399
|
ctx.fillText(m.label, CAL_PAD_LEFT + m.week * (CAL_CELL + CAL_GAP), CAL_LABEL_H - 4);
|
|
244
400
|
}
|
|
@@ -248,16 +404,26 @@ export class LyraHeatmap extends LyraElement {
|
|
|
248
404
|
ctx.fillText(label, 2, CAL_LABEL_H + weekday * (CAL_CELL + CAL_GAP) + CAL_CELL - 1);
|
|
249
405
|
});
|
|
250
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Effective per-cell size in matrix mode — `fitToWidth` derives it from
|
|
409
|
+
* the host's measured width, otherwise it's the fixed `cellSize`
|
|
410
|
+
* property. Shared by `drawMatrix()` and the pointer/keyboard hit-testing
|
|
411
|
+
* below (`hitTestMatrix()`, `cellRect()`) so they always agree on exactly
|
|
412
|
+
* the same geometry as what's actually painted.
|
|
413
|
+
*/
|
|
414
|
+
matrixCellSize(cols) {
|
|
415
|
+
if (this.fitToWidth && cols > 0) {
|
|
416
|
+
const hostWidth = this.clientWidth || PAD_LEFT + cols * this.cellSize;
|
|
417
|
+
return Math.max(4, (hostWidth - PAD_LEFT) / cols);
|
|
418
|
+
}
|
|
419
|
+
return this.cellSize;
|
|
420
|
+
}
|
|
251
421
|
drawMatrix() {
|
|
252
422
|
if (!this.canvas)
|
|
253
423
|
return;
|
|
254
424
|
const rows = this.rowLabels.length;
|
|
255
425
|
const cols = this.colLabels.length;
|
|
256
|
-
|
|
257
|
-
if (this.fitToWidth && cols > 0) {
|
|
258
|
-
const hostWidth = this.clientWidth || PAD_LEFT + cols * this.cellSize;
|
|
259
|
-
cellSize = Math.max(4, (hostWidth - PAD_LEFT) / cols);
|
|
260
|
-
}
|
|
426
|
+
const cellSize = this.matrixCellSize(cols);
|
|
261
427
|
const w = PAD_LEFT + cols * cellSize;
|
|
262
428
|
const h = PAD_TOP + rows * cellSize;
|
|
263
429
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -270,7 +436,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
270
436
|
return;
|
|
271
437
|
ctx.scale(dpr, dpr);
|
|
272
438
|
ctx.clearRect(0, 0, w, h);
|
|
273
|
-
const bounds = this.
|
|
439
|
+
const bounds = this.cachedValueRange;
|
|
274
440
|
const lo = bounds ? bounds[0] : 0;
|
|
275
441
|
const hi = bounds ? bounds[1] : 1;
|
|
276
442
|
const [scaleLo, scaleHi] = this.scaleEndpoints();
|
|
@@ -305,8 +471,40 @@ export class LyraHeatmap extends LyraElement {
|
|
|
305
471
|
ctx.fillRect(x, y, cellSize - 1, cellSize - 1);
|
|
306
472
|
}
|
|
307
473
|
}
|
|
474
|
+
// Annotation ring overlay, stroked after the fill pass so it reads
|
|
475
|
+
// clearly over the data-driven cell color. Uses a dedicated
|
|
476
|
+
// --lyra-heatmap-annotation-color token (defaults to --lyra-color-danger)
|
|
477
|
+
// rather than any of the sequential ramp colors, since it needs to stay
|
|
478
|
+
// visible regardless of which point on that ramp it's drawn over.
|
|
479
|
+
if (this.annotations.length) {
|
|
480
|
+
ctx.lineWidth = RING_LINE_WIDTH;
|
|
481
|
+
ctx.strokeStyle = this.annotationColor();
|
|
482
|
+
for (const ann of this.annotations) {
|
|
483
|
+
if (ann.row == null || ann.col == null)
|
|
484
|
+
continue;
|
|
485
|
+
if (ann.row < 0 || ann.row >= rows || ann.col < 0 || ann.col >= cols)
|
|
486
|
+
continue;
|
|
487
|
+
const x = PAD_LEFT + ann.col * cellSize;
|
|
488
|
+
const y = PAD_TOP + ann.row * cellSize;
|
|
489
|
+
ctx.strokeRect(x + 1, y + 1, cellSize - 3, cellSize - 3);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Keyboard focus ring, redrawn on top of the fill pass (and any
|
|
493
|
+
// annotation rings) on every draw — canvas has no persistent DOM focus
|
|
494
|
+
// affordance of its own for an individual cell, only a `:focus-visible`
|
|
495
|
+
// outline around the whole element (see heatmap.styles.ts).
|
|
496
|
+
if (this.focusedCell && 'row' in this.focusedCell) {
|
|
497
|
+
const { row, col } = this.focusedCell;
|
|
498
|
+
if (row < rows && col < cols) {
|
|
499
|
+
ctx.lineWidth = RING_LINE_WIDTH;
|
|
500
|
+
ctx.strokeStyle = this.focusRingColor();
|
|
501
|
+
const x = PAD_LEFT + col * cellSize;
|
|
502
|
+
const y = PAD_TOP + row * cellSize;
|
|
503
|
+
ctx.strokeRect(x + 1, y + 1, cellSize - 3, cellSize - 3);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
308
506
|
ctx.fillStyle = this.labelColor();
|
|
309
|
-
ctx.font =
|
|
507
|
+
ctx.font = this.labelFont();
|
|
310
508
|
this.rowLabels.forEach((label, r) => {
|
|
311
509
|
ctx.fillText(label, 4, PAD_TOP + r * cellSize + cellSize / 2 + 3);
|
|
312
510
|
});
|
|
@@ -314,16 +512,206 @@ export class LyraHeatmap extends LyraElement {
|
|
|
314
512
|
ctx.fillText(label, PAD_LEFT + c * cellSize + 2, PAD_TOP - 6);
|
|
315
513
|
});
|
|
316
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Maps a pointer position, in canvas-local CSS px (e.g.
|
|
517
|
+
* `PointerEvent.offsetX/offsetY`), to the cell underneath it — or `null`
|
|
518
|
+
* if the pointer is outside the grid. Dispatches on `mode` so callers
|
|
519
|
+
* don't have to.
|
|
520
|
+
*/
|
|
521
|
+
hitTest(x, y) {
|
|
522
|
+
return this.mode === 'calendar' ? this.hitTestCalendar(x, y) : this.hitTestMatrix(x, y);
|
|
523
|
+
}
|
|
524
|
+
hitTestMatrix(x, y) {
|
|
525
|
+
const rows = this.rowLabels.length;
|
|
526
|
+
const cols = this.colLabels.length;
|
|
527
|
+
if (rows === 0 || cols === 0)
|
|
528
|
+
return null;
|
|
529
|
+
const cellSize = this.matrixCellSize(cols);
|
|
530
|
+
const col = Math.floor((x - PAD_LEFT) / cellSize);
|
|
531
|
+
const row = Math.floor((y - PAD_TOP) / cellSize);
|
|
532
|
+
if (row < 0 || row >= rows || col < 0 || col >= cols)
|
|
533
|
+
return null;
|
|
534
|
+
return { row, col };
|
|
535
|
+
}
|
|
536
|
+
hitTestCalendar(x, y) {
|
|
537
|
+
const { weekCount } = buildCalendarGrid(this.days);
|
|
538
|
+
if (weekCount === 0)
|
|
539
|
+
return null;
|
|
540
|
+
const week = Math.floor((x - CAL_PAD_LEFT) / (CAL_CELL + CAL_GAP));
|
|
541
|
+
const weekday = Math.floor((y - CAL_LABEL_H) / (CAL_CELL + CAL_GAP));
|
|
542
|
+
if (week < 0 || week >= weekCount || weekday < 0 || weekday > 6)
|
|
543
|
+
return null;
|
|
544
|
+
return { week, weekday };
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Resolves a (week, weekday) grid position to its real calendar date and
|
|
548
|
+
* value. Computed from grid geometry (`firstWeekStart` + offset), not
|
|
549
|
+
* just looked up in `cells` — so a position with no matching entry in
|
|
550
|
+
* `days` (a gap in a sparse calendar) still resolves to a real ISO date,
|
|
551
|
+
* with a `-1` "no data" sentinel value (the same convention `values`' `-1`
|
|
552
|
+
* sentinel uses in matrix mode), instead of being unresolvable.
|
|
553
|
+
*/
|
|
554
|
+
calendarCellAt(pos) {
|
|
555
|
+
const { cells, firstWeekStart } = buildCalendarGrid(this.days);
|
|
556
|
+
const match = cells.find((c) => c.week === pos.week && c.weekday === pos.weekday);
|
|
557
|
+
if (match)
|
|
558
|
+
return { date: match.date, value: match.value };
|
|
559
|
+
const date = new Date(firstWeekStart.getTime() + (pos.week * 7 + pos.weekday) * MS_PER_DAY);
|
|
560
|
+
return { date: date.toISOString().slice(0, 10), value: -1 };
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Pixel rect (canvas-local CSS px) of a cell — shared by `tooltipStyle()`
|
|
564
|
+
* so the tooltip's position always agrees with the same geometry
|
|
565
|
+
* `drawMatrix()`/`drawCalendar()`/`hitTest*()` use.
|
|
566
|
+
*/
|
|
567
|
+
cellRect(pos) {
|
|
568
|
+
if ('week' in pos) {
|
|
569
|
+
return {
|
|
570
|
+
x: CAL_PAD_LEFT + pos.week * (CAL_CELL + CAL_GAP),
|
|
571
|
+
y: CAL_LABEL_H + pos.weekday * (CAL_CELL + CAL_GAP),
|
|
572
|
+
w: CAL_CELL,
|
|
573
|
+
h: CAL_CELL,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const cellSize = this.matrixCellSize(this.colLabels.length);
|
|
577
|
+
return { x: PAD_LEFT + pos.col * cellSize, y: PAD_TOP + pos.row * cellSize, w: cellSize - 1, h: cellSize - 1 };
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Human-readable "<label>: <value>" text for a cell — shared by the hover
|
|
581
|
+
* tooltip and the keyboard-focus live-region announcement, so both always
|
|
582
|
+
* describe a cell the same way.
|
|
583
|
+
*/
|
|
584
|
+
cellText(pos) {
|
|
585
|
+
return 'week' in pos ? this.calendarCellText(pos) : this.matrixCellText(pos);
|
|
586
|
+
}
|
|
587
|
+
matrixCellText(pos) {
|
|
588
|
+
const rowLabel = this.rowLabels[pos.row] ?? `row ${pos.row + 1}`;
|
|
589
|
+
const colLabel = this.colLabels[pos.col] ?? `col ${pos.col + 1}`;
|
|
590
|
+
const v = this.values[pos.row]?.[pos.col];
|
|
591
|
+
const valueText = v == null || v < 0 || !Number.isFinite(v) ? 'no data' : String(v);
|
|
592
|
+
return `Row ${rowLabel}, Col ${colLabel}: ${valueText}`;
|
|
593
|
+
}
|
|
594
|
+
calendarCellText(pos) {
|
|
595
|
+
const { date, value } = this.calendarCellAt(pos);
|
|
596
|
+
const label = parseIsoDate(date).toLocaleString('en', { month: 'short', day: 'numeric', timeZone: 'UTC' });
|
|
597
|
+
const valueText = value < 0 || !Number.isFinite(value) ? 'no data' : String(value);
|
|
598
|
+
return `${label}: ${valueText}`;
|
|
599
|
+
}
|
|
600
|
+
/** Refreshes the visually-hidden live-region text for a newly-focused cell. */
|
|
601
|
+
announce(pos) {
|
|
602
|
+
this.liveText = this.cellText(pos);
|
|
603
|
+
}
|
|
604
|
+
emitCellClick(pos) {
|
|
605
|
+
if ('week' in pos) {
|
|
606
|
+
const { date, value } = this.calendarCellAt(pos);
|
|
607
|
+
this.emit('lyra-cell-click', { date, value });
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
const value = this.values[pos.row]?.[pos.col] ?? -1;
|
|
611
|
+
this.emit('lyra-cell-click', { row: pos.row, col: pos.col, value });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/** Inline `style` for `[part="tooltip"]`, centered above the given cell. */
|
|
615
|
+
tooltipStyle(pos) {
|
|
616
|
+
const rect = this.cellRect(pos);
|
|
617
|
+
return { left: `${rect.x + rect.w / 2}px`, top: `${rect.y}px` };
|
|
618
|
+
}
|
|
619
|
+
onMatrixKeyDown(e) {
|
|
620
|
+
const rows = this.rowLabels.length;
|
|
621
|
+
const cols = this.colLabels.length;
|
|
622
|
+
if (rows === 0 || cols === 0)
|
|
623
|
+
return;
|
|
624
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
625
|
+
e.preventDefault();
|
|
626
|
+
if (this.focusedCell)
|
|
627
|
+
this.emitCellClick(this.focusedCell);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (!ARROW_KEYS.has(e.key))
|
|
631
|
+
return;
|
|
632
|
+
e.preventDefault();
|
|
633
|
+
// The first arrow keypress just moves focus onto the grid (at the
|
|
634
|
+
// top-left cell) rather than also applying that keypress's delta —
|
|
635
|
+
// otherwise ArrowRight from "unfocused" would land on column 1,
|
|
636
|
+
// silently skipping column 0.
|
|
637
|
+
if (!this.focusedCell || !('row' in this.focusedCell)) {
|
|
638
|
+
const next = { row: 0, col: 0 };
|
|
639
|
+
this.focusedCell = next;
|
|
640
|
+
this.announce(next);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const { row, col } = this.focusedCell;
|
|
644
|
+
let next = { row, col };
|
|
645
|
+
if (e.key === 'ArrowUp')
|
|
646
|
+
next = { row: Math.max(0, row - 1), col };
|
|
647
|
+
else if (e.key === 'ArrowDown')
|
|
648
|
+
next = { row: Math.min(rows - 1, row + 1), col };
|
|
649
|
+
else if (e.key === 'ArrowLeft')
|
|
650
|
+
next = { row, col: Math.max(0, col - 1) };
|
|
651
|
+
else if (e.key === 'ArrowRight')
|
|
652
|
+
next = { row, col: Math.min(cols - 1, col + 1) };
|
|
653
|
+
this.focusedCell = next;
|
|
654
|
+
this.announce(next);
|
|
655
|
+
}
|
|
656
|
+
onCalendarKeyDown(e) {
|
|
657
|
+
const { weekCount } = buildCalendarGrid(this.days);
|
|
658
|
+
if (weekCount === 0)
|
|
659
|
+
return;
|
|
660
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
661
|
+
e.preventDefault();
|
|
662
|
+
if (this.focusedCell)
|
|
663
|
+
this.emitCellClick(this.focusedCell);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (!ARROW_KEYS.has(e.key))
|
|
667
|
+
return;
|
|
668
|
+
e.preventDefault();
|
|
669
|
+
if (!this.focusedCell || !('week' in this.focusedCell)) {
|
|
670
|
+
const next = { week: 0, weekday: 0 };
|
|
671
|
+
this.focusedCell = next;
|
|
672
|
+
this.announce(next);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const { week, weekday } = this.focusedCell;
|
|
676
|
+
let next = { week, weekday };
|
|
677
|
+
if (e.key === 'ArrowUp')
|
|
678
|
+
next = { week, weekday: Math.max(0, weekday - 1) };
|
|
679
|
+
else if (e.key === 'ArrowDown')
|
|
680
|
+
next = { week, weekday: Math.min(6, weekday + 1) };
|
|
681
|
+
else if (e.key === 'ArrowLeft')
|
|
682
|
+
next = { week: Math.max(0, week - 1), weekday };
|
|
683
|
+
else if (e.key === 'ArrowRight')
|
|
684
|
+
next = { week: Math.min(weekCount - 1, week + 1), weekday };
|
|
685
|
+
this.focusedCell = next;
|
|
686
|
+
this.announce(next);
|
|
687
|
+
}
|
|
317
688
|
render() {
|
|
318
|
-
const range = this.
|
|
689
|
+
const range = this.cachedValueRange;
|
|
690
|
+
const labeledAnnotations = this.annotations.filter((a) => a.label);
|
|
319
691
|
return html `
|
|
320
692
|
<div part="base">
|
|
321
|
-
<canvas
|
|
693
|
+
<canvas
|
|
694
|
+
part="canvas"
|
|
695
|
+
tabindex="0"
|
|
696
|
+
@pointermove=${this.onPointerMove}
|
|
697
|
+
@pointerleave=${this.onPointerLeave}
|
|
698
|
+
@click=${this.onCanvasClick}
|
|
699
|
+
@keydown=${this.onKeyDown}
|
|
700
|
+
></canvas>
|
|
701
|
+
<div
|
|
702
|
+
part="tooltip"
|
|
703
|
+
?hidden=${!this.hoverCell}
|
|
704
|
+
style=${styleMap(this.hoverCell ? this.tooltipStyle(this.hoverCell) : {})}
|
|
705
|
+
>
|
|
706
|
+
${this.hoverCell ? this.cellText(this.hoverCell) : ''}
|
|
707
|
+
</div>
|
|
708
|
+
<div part="live-region" class="sr-only" role="status" aria-live="polite">${this.liveText}</div>
|
|
322
709
|
<div part="legend">
|
|
323
710
|
<span part="legend-lo">${range ? range[0] : ''}</span>
|
|
324
711
|
<span class="bar"></span>
|
|
325
712
|
<span part="legend-hi">${range ? range[1] : ''}</span>
|
|
326
713
|
<span>${this.valueLabel}</span>
|
|
714
|
+
${labeledAnnotations.map((a) => html `<span part="legend-annotation"><span class="ring-swatch"></span>${a.label}</span>`)}
|
|
327
715
|
</div>
|
|
328
716
|
</div>
|
|
329
717
|
`;
|
|
@@ -359,7 +747,19 @@ __decorate([
|
|
|
359
747
|
__decorate([
|
|
360
748
|
property({ attribute: 'bucket-count', type: Number })
|
|
361
749
|
], LyraHeatmap.prototype, "bucketCount", void 0);
|
|
750
|
+
__decorate([
|
|
751
|
+
property({ attribute: false })
|
|
752
|
+
], LyraHeatmap.prototype, "annotations", void 0);
|
|
362
753
|
__decorate([
|
|
363
754
|
query('canvas')
|
|
364
755
|
], LyraHeatmap.prototype, "canvas", void 0);
|
|
756
|
+
__decorate([
|
|
757
|
+
state()
|
|
758
|
+
], LyraHeatmap.prototype, "hoverCell", void 0);
|
|
759
|
+
__decorate([
|
|
760
|
+
state()
|
|
761
|
+
], LyraHeatmap.prototype, "focusedCell", void 0);
|
|
762
|
+
__decorate([
|
|
763
|
+
state()
|
|
764
|
+
], LyraHeatmap.prototype, "liveText", void 0);
|
|
365
765
|
defineElement('heatmap', LyraHeatmap);
|
|
@@ -3,3 +3,20 @@ declare const meta: Meta;
|
|
|
3
3
|
export default meta;
|
|
4
4
|
type Story = StoryObj;
|
|
5
5
|
export declare const Default: Story;
|
|
6
|
+
export declare const SqrtScaleFitToWidth: Story;
|
|
7
|
+
export declare const CalendarMode: Story;
|
|
8
|
+
export declare const CustomTheme: Story;
|
|
9
|
+
/**
|
|
10
|
+
* Hover a cell for its tooltip, tab to the grid and use the arrow keys to
|
|
11
|
+
* move the focus ring (a visually-hidden live region announces each move),
|
|
12
|
+
* and click or press Enter/Space on a cell to see its `lyra-cell-click`
|
|
13
|
+
* detail logged below.
|
|
14
|
+
*/
|
|
15
|
+
export declare const HoverFocusClick: Story;
|
|
16
|
+
/**
|
|
17
|
+
* Cells flagged via `annotations` get a stroked ring; ones with a `label`
|
|
18
|
+
* also surface a swatch + text entry in the legend.
|
|
19
|
+
*/
|
|
20
|
+
export declare const Annotations: Story;
|
|
21
|
+
/** `annotations` in calendar mode match by ISO `date` instead of `row`/`col`. */
|
|
22
|
+
export declare const CalendarAnnotations: Story;
|