@aceshooting/lyra-ui 2.0.0 → 2.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 +87 -14
- package/custom-elements.json +11555 -5539
- package/dist/components/app-rail/app-rail-item.d.ts +30 -0
- package/dist/components/app-rail/app-rail-item.js +68 -0
- package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
- package/dist/components/app-rail/app-rail-item.styles.js +65 -0
- package/dist/components/app-rail/app-rail.d.ts +21 -24
- package/dist/components/app-rail/app-rail.js +68 -132
- package/dist/components/app-rail/app-rail.styles.js +5 -2
- package/dist/components/attachment-chip/attachment-chip.d.ts +38 -11
- package/dist/components/attachment-chip/attachment-chip.js +73 -29
- package/dist/components/attachment-trigger/attachment-trigger.d.ts +13 -0
- package/dist/components/attachment-trigger/attachment-trigger.js +13 -1
- package/dist/components/attachment-trigger/attachment-trigger.styles.js +28 -6
- package/dist/components/chart/bar-chart.js +2 -18
- package/dist/components/chart/box-plot.d.ts +25 -1
- package/dist/components/chart/box-plot.js +136 -6
- package/dist/components/chart/box-plot.styles.js +13 -0
- package/dist/components/chart/bubble-chart.js +2 -18
- package/dist/components/chart/chart.d.ts +54 -10
- package/dist/components/chart/chart.js +182 -39
- package/dist/components/chart/doughnut-chart.js +2 -18
- package/dist/components/chart/histogram-bin.d.ts +5 -2
- package/dist/components/chart/histogram-bin.js +12 -4
- package/dist/components/chart/histogram.js +8 -16
- package/dist/components/chart/line-chart.js +2 -18
- package/dist/components/chart/lite-chart.d.ts +80 -3
- package/dist/components/chart/lite-chart.js +250 -53
- package/dist/components/chart/pie-chart.js +2 -18
- package/dist/components/chart/polar-area-chart.js +2 -18
- package/dist/components/chart/radar-chart.js +2 -18
- package/dist/components/chart/scatter-chart.js +2 -18
- package/dist/components/chat-composer/chat-composer.d.ts +42 -1
- package/dist/components/chat-composer/chat-composer.js +84 -3
- package/dist/components/chat-composer/chat-composer.styles.js +11 -1
- package/dist/components/chat-message/chat-message.d.ts +32 -4
- package/dist/components/chat-message/chat-message.js +57 -17
- package/dist/components/checkbox/checkbox.d.ts +66 -8
- package/dist/components/checkbox/checkbox.js +173 -50
- package/dist/components/checkbox/checkbox.styles.js +9 -2
- package/dist/components/chip/chip-group.js +8 -2
- package/dist/components/chip/chip.js +9 -2
- package/dist/components/citation-badge/citation-badge.js +21 -3
- package/dist/components/code-block/code-block.d.ts +38 -0
- package/dist/components/code-block/code-block.js +72 -7
- package/dist/components/code-block/code-loader.d.ts +44 -1
- package/dist/components/code-block/code-loader.js +63 -0
- package/dist/components/combobox/combobox.d.ts +46 -5
- package/dist/components/combobox/combobox.js +175 -35
- package/dist/components/combobox/combobox.styles.js +3 -3
- package/dist/components/context-meter/context-meter.js +17 -8
- package/dist/components/context-meter/context-meter.styles.js +0 -6
- package/dist/components/conversation-item/conversation-item.d.ts +13 -16
- package/dist/components/conversation-item/conversation-item.js +29 -20
- package/dist/components/date-picker/calendar-core.d.ts +24 -0
- package/dist/components/date-picker/calendar-core.js +51 -4
- package/dist/components/date-picker/date-input.d.ts +77 -10
- package/dist/components/date-picker/date-input.js +278 -73
- package/dist/components/date-picker/date-picker.d.ts +28 -4
- package/dist/components/date-picker/date-picker.js +95 -37
- package/dist/components/dialog/confirm.js +3 -6
- package/dist/components/dialog/dialog.d.ts +75 -31
- package/dist/components/dialog/dialog.js +147 -131
- package/dist/components/dialog/dialog.styles.js +46 -2
- package/dist/components/dock-panel/dock-panel.d.ts +8 -0
- package/dist/components/dock-panel/dock-panel.js +15 -1
- package/dist/components/dock-panel/dock-panel.styles.js +15 -1
- package/dist/components/document-preview/document-preview.d.ts +15 -7
- package/dist/components/document-preview/document-preview.js +29 -13
- package/dist/components/empty/empty.d.ts +1 -2
- package/dist/components/empty/empty.js +18 -12
- package/dist/components/empty/empty.styles.js +0 -3
- package/dist/components/export-button/export-button.d.ts +12 -0
- package/dist/components/export-button/export-button.js +33 -5
- package/dist/components/export-button/export-button.styles.js +5 -1
- package/dist/components/file-input/accept.js +2 -0
- package/dist/components/file-input/file-input.d.ts +10 -1
- package/dist/components/file-input/file-input.js +36 -5
- package/dist/components/flag/flag.d.ts +1 -1
- package/dist/components/flag/flag.js +15 -1
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/flag/language-map.d.ts +4 -1
- package/dist/components/flag/language-map.js +6 -3
- package/dist/components/gauge/gauge.d.ts +10 -1
- package/dist/components/gauge/gauge.js +28 -9
- package/dist/components/generation-status/generation-status.d.ts +13 -5
- package/dist/components/generation-status/generation-status.js +59 -5
- package/dist/components/graph/graph.d.ts +37 -3
- package/dist/components/graph/graph.js +132 -20
- package/dist/components/heatmap/calendar-grid.d.ts +15 -6
- package/dist/components/heatmap/calendar-grid.js +17 -6
- package/dist/components/heatmap/heatmap-scale.d.ts +5 -1
- package/dist/components/heatmap/heatmap-scale.js +8 -2
- package/dist/components/heatmap/heatmap.d.ts +166 -25
- package/dist/components/heatmap/heatmap.js +372 -89
- package/dist/components/json-viewer/json-viewer.d.ts +22 -5
- package/dist/components/json-viewer/json-viewer.js +115 -37
- package/dist/components/json-viewer/json-viewer.styles.js +2 -1
- package/dist/components/kbd/kbd.js +24 -7
- package/dist/components/live-region/live-region.d.ts +10 -1
- package/dist/components/live-region/live-region.js +42 -6
- package/dist/components/map/map.d.ts +24 -1
- package/dist/components/map/map.js +89 -24
- package/dist/components/markdown/markdown-loader.d.ts +24 -0
- package/dist/components/markdown/markdown-loader.js +35 -1
- package/dist/components/markdown/markdown.d.ts +46 -8
- package/dist/components/markdown/markdown.js +136 -31
- package/dist/components/markdown/markdown.styles.js +7 -1
- package/dist/components/media-card/media-card.d.ts +25 -17
- package/dist/components/media-card/media-card.js +43 -65
- package/dist/components/mention-popover/mention-popover.d.ts +4 -1
- package/dist/components/mention-popover/mention-popover.js +23 -5
- package/dist/components/menu/menu-item.d.ts +32 -3
- package/dist/components/menu/menu-item.js +94 -7
- package/dist/components/menu/menu-item.styles.js +8 -0
- package/dist/components/menu/menu.d.ts +25 -1
- package/dist/components/menu/menu.js +99 -3
- package/dist/components/menu/menu.styles.js +3 -2
- package/dist/components/model-select/model-select.d.ts +48 -3
- package/dist/components/model-select/model-select.js +120 -22
- package/dist/components/model-select/model-select.styles.js +17 -3
- package/dist/components/model-settings-panel/model-settings-panel.d.ts +25 -1
- package/dist/components/model-settings-panel/model-settings-panel.js +65 -1
- package/dist/components/playback/playback.d.ts +6 -2
- package/dist/components/playback/playback.js +72 -29
- package/dist/components/playback/playback.styles.js +6 -1
- package/dist/components/responsive-panel/responsive-panel.d.ts +29 -16
- package/dist/components/responsive-panel/responsive-panel.js +130 -132
- package/dist/components/responsive-panel/responsive-panel.styles.js +1 -1
- package/dist/components/result-card/result-card.d.ts +8 -2
- package/dist/components/result-card/result-card.js +9 -3
- package/dist/components/result-card/result-card.styles.js +0 -1
- package/dist/components/select/select.d.ts +59 -20
- package/dist/components/select/select.js +125 -43
- package/dist/components/skeleton/skeleton.d.ts +3 -0
- package/dist/components/skeleton/skeleton.js +7 -1
- package/dist/components/skeleton/skeleton.styles.js +5 -4
- package/dist/components/slider/slider.d.ts +57 -5
- package/dist/components/slider/slider.js +195 -80
- package/dist/components/source-card/source-card.d.ts +17 -5
- package/dist/components/source-card/source-card.js +55 -8
- package/dist/components/source-card/source-card.styles.js +1 -1
- package/dist/components/source-list/source-list.js +9 -1
- package/dist/components/sparkline/sparkline.js +34 -10
- package/dist/components/split/split.d.ts +141 -14
- package/dist/components/split/split.js +357 -52
- package/dist/components/split/split.styles.js +18 -1
- package/dist/components/stat/stat.d.ts +10 -4
- package/dist/components/stat/stat.js +46 -11
- package/dist/components/stream-status/stream-status.d.ts +27 -11
- package/dist/components/stream-status/stream-status.js +95 -13
- package/dist/components/streaming-text/streaming-text.d.ts +9 -3
- package/dist/components/streaming-text/streaming-text.js +25 -10
- package/dist/components/streaming-text/streaming-text.styles.js +3 -2
- package/dist/components/switch/switch.d.ts +52 -7
- package/dist/components/switch/switch.js +111 -40
- package/dist/components/switch/switch.styles.js +1 -1
- package/dist/components/table/table.d.ts +18 -3
- package/dist/components/table/table.js +88 -20
- package/dist/components/tabs/tabs.d.ts +23 -0
- package/dist/components/tabs/tabs.js +76 -12
- package/dist/components/tabs/tabs.styles.js +11 -0
- package/dist/components/thinking-panel/thinking-panel.d.ts +3 -1
- package/dist/components/thinking-panel/thinking-panel.js +34 -9
- package/dist/components/thinking-panel/thinking-panel.styles.js +6 -0
- package/dist/components/time-range/time-range.d.ts +59 -2
- package/dist/components/time-range/time-range.js +163 -67
- package/dist/components/toast/toast-item.d.ts +12 -3
- package/dist/components/toast/toast-item.js +90 -53
- package/dist/components/toast/toast-item.styles.js +59 -28
- package/dist/components/toast/toast.js +17 -5
- package/dist/components/toast/toaster.js +10 -1
- package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +11 -10
- package/dist/components/tool-approval-dialog/tool-approval-dialog.js +50 -124
- package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +1 -1
- package/dist/components/tool-call-chip/tool-call-chip.d.ts +18 -2
- package/dist/components/tool-call-chip/tool-call-chip.js +86 -25
- package/dist/components/tool-param-form/tool-param-form.d.ts +89 -28
- package/dist/components/tool-param-form/tool-param-form.js +350 -106
- package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +27 -7
- package/dist/components/tool-result-dialog/tool-result-dialog.js +80 -115
- package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +1 -1
- package/dist/components/tool-result-view/registry.d.ts +0 -8
- package/dist/components/tool-result-view/registry.js +7 -2
- package/dist/components/tool-result-view/tool-result-view.d.ts +6 -3
- package/dist/components/tool-result-view/tool-result-view.js +18 -4
- package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +11 -11
- package/dist/components/tool-select-dialog/tool-select-dialog.js +44 -133
- package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +13 -4
- package/dist/components/tree/tree-node.d.ts +4 -1
- package/dist/components/tree/tree-node.js +4 -1
- package/dist/components/tree/tree.d.ts +17 -8
- package/dist/components/tree/tree.js +21 -20
- package/dist/components/typing-indicator/typing-indicator.d.ts +12 -2
- package/dist/components/typing-indicator/typing-indicator.js +16 -4
- package/dist/components/typing-indicator/typing-indicator.styles.js +2 -2
- package/dist/components/virtual-list/virtual-list.d.ts +56 -19
- package/dist/components/virtual-list/virtual-list.js +184 -38
- package/dist/components/virtual-list/virtual-list.styles.js +12 -0
- package/dist/components/widget/widget.d.ts +5 -4
- package/dist/components/widget/widget.js +54 -120
- package/dist/components/widget/widget.styles.js +8 -2
- package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
- package/dist/components/word-cloud/word-cloud-layout.js +20 -6
- package/dist/components/word-cloud/word-cloud.d.ts +22 -2
- package/dist/components/word-cloud/word-cloud.js +45 -6
- package/dist/components/word-cloud/word-cloud.styles.js +13 -1
- package/dist/internal/anchored-validity.d.ts +1 -0
- package/dist/internal/anchored-validity.js +86 -0
- package/dist/internal/form-associated.d.ts +7 -0
- package/dist/internal/form-associated.js +43 -4
- package/dist/internal/numbers.d.ts +12 -0
- package/dist/internal/numbers.js +26 -0
- package/dist/internal/overlay-manager.d.ts +51 -0
- package/dist/internal/overlay-manager.js +523 -0
- package/dist/internal/positioner.js +27 -3
- package/dist/internal/safe-url.d.ts +12 -0
- package/dist/internal/safe-url.js +41 -0
- package/dist/internal/tokens.styles.js +17 -11
- package/dist/lyra.d.ts +5 -2
- package/dist/lyra.js +3 -1
- package/llms-full.txt +970 -426
- package/llms.txt +5 -4
- package/package.json +19 -2
|
@@ -71,10 +71,11 @@ export declare class LyraGenerationStatus extends LyraElement {
|
|
|
71
71
|
/** Whether generation is currently in progress. The elapsed-time ticker
|
|
72
72
|
* runs only while this is `true` (see the class doc). */
|
|
73
73
|
active: boolean;
|
|
74
|
-
/** Epoch-ms timestamp of when generation began. Optional -- when unset
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
74
|
+
/** Epoch-ms timestamp of when generation began. Optional -- when unset (or
|
|
75
|
+
* when set to a value that fails to parse as a finite number, e.g. an
|
|
76
|
+
* ISO-8601 date string) while `active` is `true`, this component captures
|
|
77
|
+
* the current time itself the moment `active` becomes `true` and counts
|
|
78
|
+
* from there instead (see the class doc). */
|
|
78
79
|
startedAt?: number;
|
|
79
80
|
/** Running token count so far. Omitted from the readout entirely (no
|
|
80
81
|
* `tokens` segment) while unset. */
|
|
@@ -84,16 +85,23 @@ export declare class LyraGenerationStatus extends LyraElement {
|
|
|
84
85
|
* itself once one is available -- see the class doc for the exact rule
|
|
85
86
|
* and why. */
|
|
86
87
|
tokensPerSecond?: number;
|
|
87
|
-
/** Whether the built-in Stop button renders at all.
|
|
88
|
+
/** Whether the built-in Stop button renders at all. Defaults to `true`.
|
|
89
|
+
* Uses {@link showStopConverter} rather than Lit's default presence-based
|
|
90
|
+
* `type: Boolean` handling, so a plain-HTML consumer with no way to write
|
|
91
|
+
* a `.showStop` property binding can still turn this off with
|
|
92
|
+
* `show-stop="false"`; a Lit template can do the same with either that
|
|
93
|
+
* attribute string or a `.showStop=${false}` property binding. */
|
|
88
94
|
showStop: boolean;
|
|
89
95
|
private elapsedMs;
|
|
90
96
|
private tickTimer?;
|
|
91
97
|
private fallbackStartMs?;
|
|
98
|
+
connectedCallback(): void;
|
|
92
99
|
disconnectedCallback(): void;
|
|
93
100
|
protected willUpdate(changed: PropertyValues): void;
|
|
94
101
|
protected updated(changed: PropertyValues): void;
|
|
95
102
|
private startTicker;
|
|
96
103
|
private stopTicker;
|
|
104
|
+
private get validStartedAt();
|
|
97
105
|
private computeElapsedMs;
|
|
98
106
|
/** `tokens-per-second` when set; otherwise a derived figure once enough
|
|
99
107
|
* elapsed time has accumulated for it to be meaningful; otherwise
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
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
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { html, nothing, svg } from 'lit';
|
|
7
|
+
import { html, nothing, svg, } from 'lit';
|
|
8
8
|
import { property, state } from 'lit/decorators.js';
|
|
9
9
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
@@ -71,6 +71,29 @@ function formatThroughput(value) {
|
|
|
71
71
|
const rounded = clamped < 10 ? Math.round(clamped * 10) / 10 : Math.round(clamped);
|
|
72
72
|
return `${rounded} tok/s`;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* String-aware boolean attribute converter for `show-stop`. Lit's built-in
|
|
76
|
+
* `type: Boolean` converter is presence-based -- the attribute's mere
|
|
77
|
+
* presence (regardless of its string value) maps to `true`, so a plain-
|
|
78
|
+
* markup consumer writing the literal `show-stop="false"` would actually get
|
|
79
|
+
* the button *shown*, the opposite of what that string reads as (the same
|
|
80
|
+
* bug class `<lyra-streaming-text>`'s `optionalBooleanConverter` and
|
|
81
|
+
* `<lyra-line-chart>`'s `WithoutBeginAtZero` story both document). Unlike
|
|
82
|
+
* `<lyra-streaming-text>`'s tri-state converter, this property's default is
|
|
83
|
+
* `true`, not "unset" -- so this one only needs two states: attribute absent
|
|
84
|
+
* (or removed) -> `true` (the default); `show-stop="false"` -> `false`;
|
|
85
|
+
* anything else present (no value, `="true"`, ...) -> `true`.
|
|
86
|
+
*/
|
|
87
|
+
const showStopConverter = {
|
|
88
|
+
fromAttribute(value) {
|
|
89
|
+
return value !== 'false';
|
|
90
|
+
},
|
|
91
|
+
toAttribute(value) {
|
|
92
|
+
// `true` is this property's default, so there's nothing worth reflecting
|
|
93
|
+
// for it; only the non-default `false` needs an attribute at all.
|
|
94
|
+
return value ? null : 'false';
|
|
95
|
+
},
|
|
96
|
+
};
|
|
74
97
|
/**
|
|
75
98
|
* `<lyra-generation-status>` — a compact, ticking status readout shown
|
|
76
99
|
* alongside an in-progress AI response: elapsed time, token count, and
|
|
@@ -143,7 +166,12 @@ export class LyraGenerationStatus extends LyraElement {
|
|
|
143
166
|
/** Whether generation is currently in progress. The elapsed-time ticker
|
|
144
167
|
* runs only while this is `true` (see the class doc). */
|
|
145
168
|
this.active = false;
|
|
146
|
-
/** Whether the built-in Stop button renders at all.
|
|
169
|
+
/** Whether the built-in Stop button renders at all. Defaults to `true`.
|
|
170
|
+
* Uses {@link showStopConverter} rather than Lit's default presence-based
|
|
171
|
+
* `type: Boolean` handling, so a plain-HTML consumer with no way to write
|
|
172
|
+
* a `.showStop` property binding can still turn this off with
|
|
173
|
+
* `show-stop="false"`; a Lit template can do the same with either that
|
|
174
|
+
* attribute string or a `.showStop=${false}` property binding. */
|
|
147
175
|
this.showStop = true;
|
|
148
176
|
// Recomputed on activation and on every ~1s tick; frozen (not reset) once
|
|
149
177
|
// `active` goes false -- see the class doc's "ticker" paragraph.
|
|
@@ -153,6 +181,21 @@ export class LyraGenerationStatus extends LyraElement {
|
|
|
153
181
|
};
|
|
154
182
|
}
|
|
155
183
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
184
|
+
// A re-parenting host (e.g. a virtualized/reordering list) disconnects and
|
|
185
|
+
// reconnects this element without ever toggling `active` -- `willUpdate`/
|
|
186
|
+
// `updated` only react to *changes* of `active`, so with no override here
|
|
187
|
+
// a still-active element would come back with `disconnectedCallback`'s
|
|
188
|
+
// cleared ticker never restarted, freezing the readout even though
|
|
189
|
+
// `active` (still `true`) says generation is ongoing. Restarting from
|
|
190
|
+
// `computeElapsedMs()` (rather than resuming a stale in-flight interval)
|
|
191
|
+
// matches how `startTicker()` already re-baselines on every fresh start.
|
|
192
|
+
connectedCallback() {
|
|
193
|
+
super.connectedCallback();
|
|
194
|
+
if (this.active) {
|
|
195
|
+
this.elapsedMs = this.computeElapsedMs();
|
|
196
|
+
this.startTicker();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
156
199
|
disconnectedCallback() {
|
|
157
200
|
super.disconnectedCallback();
|
|
158
201
|
this.stopTicker();
|
|
@@ -175,7 +218,7 @@ export class LyraGenerationStatus extends LyraElement {
|
|
|
175
218
|
// first-update special case needed.
|
|
176
219
|
willUpdate(changed) {
|
|
177
220
|
if (changed.has('active') && this.active) {
|
|
178
|
-
if (this.
|
|
221
|
+
if (this.validStartedAt == null)
|
|
179
222
|
this.fallbackStartMs = Date.now();
|
|
180
223
|
this.elapsedMs = this.computeElapsedMs();
|
|
181
224
|
}
|
|
@@ -214,8 +257,19 @@ export class LyraGenerationStatus extends LyraElement {
|
|
|
214
257
|
this.tickTimer = undefined;
|
|
215
258
|
}
|
|
216
259
|
}
|
|
260
|
+
// `startedAt` is host-supplied and, unlike `tokenCount`/`tokensPerSecond`
|
|
261
|
+
// (both already guarded at their read sites), had no `Number.isFinite`
|
|
262
|
+
// check of its own -- a non-numeric `started-at` attribute (e.g. an
|
|
263
|
+
// ISO-8601 date string that failed the `type: Number` conversion, landing
|
|
264
|
+
// as `NaN`) would flow straight into `Date.now() - start`, permanently
|
|
265
|
+
// rendering the literal text "NaNm NaNs" with no fallback or recovery.
|
|
266
|
+
// Treating an invalid value the same as "unset" here restores this
|
|
267
|
+
// property's own documented fallback-clock behavior instead.
|
|
268
|
+
get validStartedAt() {
|
|
269
|
+
return this.startedAt != null && Number.isFinite(this.startedAt) ? this.startedAt : undefined;
|
|
270
|
+
}
|
|
217
271
|
computeElapsedMs() {
|
|
218
|
-
const start = this.
|
|
272
|
+
const start = this.validStartedAt ?? this.fallbackStartMs;
|
|
219
273
|
return start == null ? 0 : Math.max(0, Date.now() - start);
|
|
220
274
|
}
|
|
221
275
|
/** `tokens-per-second` when set; otherwise a derived figure once enough
|
|
@@ -265,7 +319,7 @@ __decorate([
|
|
|
265
319
|
property({ type: Number, attribute: 'tokens-per-second' })
|
|
266
320
|
], LyraGenerationStatus.prototype, "tokensPerSecond", void 0);
|
|
267
321
|
__decorate([
|
|
268
|
-
property({
|
|
322
|
+
property({ attribute: 'show-stop', converter: showStopConverter })
|
|
269
323
|
], LyraGenerationStatus.prototype, "showStop", void 0);
|
|
270
324
|
__decorate([
|
|
271
325
|
state()
|
|
@@ -4,6 +4,7 @@ import '../skeleton/skeleton.js';
|
|
|
4
4
|
export interface GraphNode {
|
|
5
5
|
id: string;
|
|
6
6
|
label?: string;
|
|
7
|
+
/** Clamped to [6, 24] (a non-finite/missing value uses the midpoint, 15) — never rendered smaller/larger. */
|
|
7
8
|
radius?: number;
|
|
8
9
|
color?: string;
|
|
9
10
|
}
|
|
@@ -19,12 +20,24 @@ export interface GraphLink {
|
|
|
19
20
|
*
|
|
20
21
|
* Set `seed` for a deterministic layout: node initial positions become
|
|
21
22
|
* reproducible (keyed by node id) and the settle happens synchronously
|
|
22
|
-
* instead of animating, like `prefers-reduced-motion`.
|
|
23
|
+
* instead of animating, like `prefers-reduced-motion`. `seed` only takes
|
|
24
|
+
* effect on the update that first populates `nodes`/`links` (or a later
|
|
25
|
+
* update that adds genuinely new node ids) — willUpdate() only reads it from
|
|
26
|
+
* inside rebuildSimulation(), which itself only ever assigns x/y to nodes
|
|
27
|
+
* that don't already have a settled position, so changing `seed` on an
|
|
28
|
+
* already-rendered graph is a no-op; nothing re-derives already-positioned
|
|
29
|
+
* nodes' x/y from the new value.
|
|
23
30
|
*
|
|
24
31
|
* @customElement lyra-graph
|
|
25
32
|
* @event lyra-node-click - `detail: { id }`.
|
|
26
33
|
* @event lyra-link-click - `detail: { source, target }`.
|
|
27
|
-
* @csspart base
|
|
34
|
+
* @csspart base - The graph wrapper.
|
|
35
|
+
* @csspart svg - The graph SVG.
|
|
36
|
+
* @csspart node - A graph node.
|
|
37
|
+
* @csspart link - A graph link.
|
|
38
|
+
* @csspart label - A node label.
|
|
39
|
+
* @csspart live-region - The current graph item announcement.
|
|
40
|
+
* @csspart data-list - A visually hidden list alternative for graph data.
|
|
28
41
|
*/
|
|
29
42
|
export declare class LyraGraph extends LyraElement {
|
|
30
43
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -38,12 +51,20 @@ export declare class LyraGraph extends LyraElement {
|
|
|
38
51
|
maxZoom: number;
|
|
39
52
|
/** When set, seeds each node's initial x/y deterministically (keyed by
|
|
40
53
|
* node id, not array index) instead of forceSimulation()'s own random
|
|
41
|
-
* start, and settles the simulation synchronously — see rebuildSimulation().
|
|
54
|
+
* start, and settles the simulation synchronously — see rebuildSimulation().
|
|
55
|
+
* Only takes effect on the update that first assigns a given node id an
|
|
56
|
+
* x/y (i.e. supplied at/before `nodes`/`links` first populate, or when a
|
|
57
|
+
* later update introduces new node ids) — changing `seed` afterwards does
|
|
58
|
+
* not retroactively reposition already-settled nodes; there is currently
|
|
59
|
+
* no way to make an already-rendered graph reproducible after the fact. */
|
|
42
60
|
seed?: number;
|
|
43
61
|
/** True until the lazy-loaded d3 peer dependencies have settled (success or failure). */
|
|
44
62
|
private loading;
|
|
45
63
|
private simNodes;
|
|
46
64
|
private simLinks;
|
|
65
|
+
/** One roving tab stop across all nodes and links; nodes are the initial entry order. */
|
|
66
|
+
private activeGraphItem;
|
|
67
|
+
private graphLiveText;
|
|
47
68
|
private simulation?;
|
|
48
69
|
/** The live charge/link force objects, kept so chargeStrength/linkDistance
|
|
49
70
|
* changes can retune them in place (see updated()) instead of requiring a
|
|
@@ -73,6 +94,13 @@ export declare class LyraGraph extends LyraElement {
|
|
|
73
94
|
private linkEls;
|
|
74
95
|
connectedCallback(): void;
|
|
75
96
|
disconnectedCallback(): void;
|
|
97
|
+
/**
|
|
98
|
+
* A caller-supplied `radius` is clamped to [MIN_RADIUS, MAX_RADIUS] (and a
|
|
99
|
+
* non-finite/NaN value falls back to the same default average as an unset
|
|
100
|
+
* one) — an unclamped 0/negative radius would render an invisibly small
|
|
101
|
+
* `<circle>` that's still `role="button" tabindex="0"`, an invisible,
|
|
102
|
+
* focusable/clickable control with no visible focus indicator.
|
|
103
|
+
*/
|
|
76
104
|
private nodeRadius;
|
|
77
105
|
protected willUpdate(changed: PropertyValues): void;
|
|
78
106
|
protected updated(changed: PropertyValues): void;
|
|
@@ -114,6 +142,12 @@ export declare class LyraGraph extends LyraElement {
|
|
|
114
142
|
private rebuildSimulation;
|
|
115
143
|
private onNodeClick;
|
|
116
144
|
private onLinkClick;
|
|
145
|
+
private graphItemCount;
|
|
146
|
+
private normalizedGraphItem;
|
|
147
|
+
private graphItemText;
|
|
148
|
+
private onGraphItemFocus;
|
|
149
|
+
private focusGraphItem;
|
|
150
|
+
private onGraphKeyDown;
|
|
117
151
|
render(): TemplateResult;
|
|
118
152
|
}
|
|
119
153
|
declare global {
|
|
@@ -9,6 +9,7 @@ import { property, state } from 'lit/decorators.js';
|
|
|
9
9
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
10
10
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
11
11
|
import { defineElement } from '../../internal/prefix.js';
|
|
12
|
+
import { srOnly } from '../../internal/a11y.js';
|
|
12
13
|
import { styles } from './graph.styles.js';
|
|
13
14
|
import { loadD3 } from './graph-loader.js';
|
|
14
15
|
import '../skeleton/skeleton.js';
|
|
@@ -67,12 +68,24 @@ function sanitizeNodeColor(color) {
|
|
|
67
68
|
*
|
|
68
69
|
* Set `seed` for a deterministic layout: node initial positions become
|
|
69
70
|
* reproducible (keyed by node id) and the settle happens synchronously
|
|
70
|
-
* instead of animating, like `prefers-reduced-motion`.
|
|
71
|
+
* instead of animating, like `prefers-reduced-motion`. `seed` only takes
|
|
72
|
+
* effect on the update that first populates `nodes`/`links` (or a later
|
|
73
|
+
* update that adds genuinely new node ids) — willUpdate() only reads it from
|
|
74
|
+
* inside rebuildSimulation(), which itself only ever assigns x/y to nodes
|
|
75
|
+
* that don't already have a settled position, so changing `seed` on an
|
|
76
|
+
* already-rendered graph is a no-op; nothing re-derives already-positioned
|
|
77
|
+
* nodes' x/y from the new value.
|
|
71
78
|
*
|
|
72
79
|
* @customElement lyra-graph
|
|
73
80
|
* @event lyra-node-click - `detail: { id }`.
|
|
74
81
|
* @event lyra-link-click - `detail: { source, target }`.
|
|
75
|
-
* @csspart base
|
|
82
|
+
* @csspart base - The graph wrapper.
|
|
83
|
+
* @csspart svg - The graph SVG.
|
|
84
|
+
* @csspart node - A graph node.
|
|
85
|
+
* @csspart link - A graph link.
|
|
86
|
+
* @csspart label - A node label.
|
|
87
|
+
* @csspart live-region - The current graph item announcement.
|
|
88
|
+
* @csspart data-list - A visually hidden list alternative for graph data.
|
|
76
89
|
*/
|
|
77
90
|
export class LyraGraph extends LyraElement {
|
|
78
91
|
constructor() {
|
|
@@ -89,6 +102,9 @@ export class LyraGraph extends LyraElement {
|
|
|
89
102
|
this.loading = true;
|
|
90
103
|
this.simNodes = [];
|
|
91
104
|
this.simLinks = [];
|
|
105
|
+
/** One roving tab stop across all nodes and links; nodes are the initial entry order. */
|
|
106
|
+
this.activeGraphItem = 0;
|
|
107
|
+
this.graphLiveText = '';
|
|
92
108
|
/** Node `<circle>`s already wired up with d3-drag; cleared on every simulation rebuild
|
|
93
109
|
* so DOM elements Lit reuses across a rebuild get rebound to their fresh datum. */
|
|
94
110
|
this.boundNodeEls = new WeakSet();
|
|
@@ -100,9 +116,22 @@ export class LyraGraph extends LyraElement {
|
|
|
100
116
|
this.nodeLabelEls = [];
|
|
101
117
|
this.linkEls = [];
|
|
102
118
|
}
|
|
103
|
-
static { this.styles = [LyraElement.styles, styles]; }
|
|
119
|
+
static { this.styles = [LyraElement.styles, styles, srOnly]; }
|
|
104
120
|
connectedCallback() {
|
|
105
121
|
super.connectedCallback();
|
|
122
|
+
// A reconnect (e.g. a drag-and-drop reparent that keeps this same
|
|
123
|
+
// element instance) fires disconnectedCallback then connectedCallback
|
|
124
|
+
// synchronously with no update in between — this.d3 is already set from
|
|
125
|
+
// the initial mount, and the live simulation/DOM are still intact
|
|
126
|
+
// (disconnectedCallback only stops the simulation's timer, it doesn't
|
|
127
|
+
// tear anything down). Redoing the lazy-load + rebuildSimulation() here
|
|
128
|
+
// would discard every already-settled node's position and restart the
|
|
129
|
+
// whole ~300-tick alpha=1 settle animation for no reason — just resume
|
|
130
|
+
// the existing simulation in place instead.
|
|
131
|
+
if (this.d3) {
|
|
132
|
+
this.simulation?.restart();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
106
135
|
void loadD3().then((mods) => {
|
|
107
136
|
this.loading = false;
|
|
108
137
|
// The element may have been removed from the DOM while the dynamic
|
|
@@ -118,8 +147,16 @@ export class LyraGraph extends LyraElement {
|
|
|
118
147
|
super.disconnectedCallback();
|
|
119
148
|
this.simulation?.stop();
|
|
120
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* A caller-supplied `radius` is clamped to [MIN_RADIUS, MAX_RADIUS] (and a
|
|
152
|
+
* non-finite/NaN value falls back to the same default average as an unset
|
|
153
|
+
* one) — an unclamped 0/negative radius would render an invisibly small
|
|
154
|
+
* `<circle>` that's still `role="button" tabindex="0"`, an invisible,
|
|
155
|
+
* focusable/clickable control with no visible focus indicator.
|
|
156
|
+
*/
|
|
121
157
|
nodeRadius(n) {
|
|
122
|
-
|
|
158
|
+
const r = n.radius ?? (MIN_RADIUS + MAX_RADIUS) / 2;
|
|
159
|
+
return Number.isFinite(r) ? Math.min(MAX_RADIUS, Math.max(MIN_RADIUS, r)) : (MIN_RADIUS + MAX_RADIUS) / 2;
|
|
123
160
|
}
|
|
124
161
|
willUpdate(changed) {
|
|
125
162
|
// rebuildSimulation() (re)assigns the simNodes/simLinks reactive
|
|
@@ -355,6 +392,69 @@ export class LyraGraph extends LyraElement {
|
|
|
355
392
|
const target = typeof link.target === 'object' ? link.target.id : String(link.target);
|
|
356
393
|
this.emit('lyra-link-click', { source, target });
|
|
357
394
|
}
|
|
395
|
+
graphItemCount() {
|
|
396
|
+
return this.simNodes.length + this.simLinks.length;
|
|
397
|
+
}
|
|
398
|
+
normalizedGraphItem(index = this.activeGraphItem) {
|
|
399
|
+
const count = this.graphItemCount();
|
|
400
|
+
return count ? Math.min(Math.max(index, 0), count - 1) : -1;
|
|
401
|
+
}
|
|
402
|
+
graphItemText(index) {
|
|
403
|
+
if (index < this.simNodes.length) {
|
|
404
|
+
const node = this.simNodes[index];
|
|
405
|
+
return node ? `Node ${node.label ?? node.id}` : '';
|
|
406
|
+
}
|
|
407
|
+
const link = this.simLinks[index - this.simNodes.length];
|
|
408
|
+
if (!link)
|
|
409
|
+
return '';
|
|
410
|
+
const source = typeof link.source === 'object' ? link.source.label ?? link.source.id : String(link.source);
|
|
411
|
+
const target = typeof link.target === 'object' ? link.target.label ?? link.target.id : String(link.target);
|
|
412
|
+
return `Link from ${source} to ${target}`;
|
|
413
|
+
}
|
|
414
|
+
onGraphItemFocus(index) {
|
|
415
|
+
if (this.normalizedGraphItem(index) < 0)
|
|
416
|
+
return;
|
|
417
|
+
this.activeGraphItem = index;
|
|
418
|
+
this.graphLiveText = `${this.graphItemText(index)} (${index + 1} of ${this.graphItemCount()})`;
|
|
419
|
+
}
|
|
420
|
+
focusGraphItem(index) {
|
|
421
|
+
const normalized = this.normalizedGraphItem(index);
|
|
422
|
+
if (normalized < 0)
|
|
423
|
+
return;
|
|
424
|
+
this.activeGraphItem = normalized;
|
|
425
|
+
this.graphLiveText = `${this.graphItemText(normalized)} (${normalized + 1} of ${this.graphItemCount()})`;
|
|
426
|
+
void this.updateComplete.then(() => {
|
|
427
|
+
const items = [
|
|
428
|
+
...Array.from(this.renderRoot.querySelectorAll('[part="node"]')),
|
|
429
|
+
...Array.from(this.renderRoot.querySelectorAll('[part="link"]')),
|
|
430
|
+
];
|
|
431
|
+
items[normalized]?.focus();
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
onGraphKeyDown(e, index, activate) {
|
|
435
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
436
|
+
e.preventDefault();
|
|
437
|
+
this.onGraphItemFocus(index);
|
|
438
|
+
activate();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const count = this.graphItemCount();
|
|
442
|
+
if (!count)
|
|
443
|
+
return;
|
|
444
|
+
let next = index;
|
|
445
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown')
|
|
446
|
+
next = Math.min(count - 1, index + 1);
|
|
447
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp')
|
|
448
|
+
next = Math.max(0, index - 1);
|
|
449
|
+
else if (e.key === 'Home')
|
|
450
|
+
next = 0;
|
|
451
|
+
else if (e.key === 'End')
|
|
452
|
+
next = count - 1;
|
|
453
|
+
else
|
|
454
|
+
return;
|
|
455
|
+
e.preventDefault();
|
|
456
|
+
this.focusGraphItem(next);
|
|
457
|
+
}
|
|
358
458
|
render() {
|
|
359
459
|
if (this.loading) {
|
|
360
460
|
return html `
|
|
@@ -373,15 +473,17 @@ export class LyraGraph extends LyraElement {
|
|
|
373
473
|
role="group"
|
|
374
474
|
aria-label="Node-link diagram with ${this.simNodes.length} nodes and ${this.simLinks.length} links"
|
|
375
475
|
viewBox="0 0 ${this.width} ${this.height}"
|
|
476
|
+
tabindex=${this.graphItemCount() ? '-1' : '0'}
|
|
376
477
|
>
|
|
377
478
|
<g transform="">
|
|
378
|
-
${this.simLinks.map((l) => {
|
|
479
|
+
${this.simLinks.map((l, linkIndex) => {
|
|
379
480
|
const source = l.source;
|
|
380
481
|
const target = l.target;
|
|
482
|
+
const itemIndex = this.simNodes.length + linkIndex;
|
|
381
483
|
return svg `<line
|
|
382
484
|
part="link"
|
|
383
485
|
role="button"
|
|
384
|
-
tabindex
|
|
486
|
+
tabindex=${this.normalizedGraphItem() === itemIndex ? '0' : '-1'}
|
|
385
487
|
aria-label="Link from ${source.label ?? source.id} to ${target.label ?? target.id}"
|
|
386
488
|
stroke-width=${l.width ?? 1.5}
|
|
387
489
|
x1=${source.x ?? 0}
|
|
@@ -389,33 +491,26 @@ export class LyraGraph extends LyraElement {
|
|
|
389
491
|
x2=${target.x ?? 0}
|
|
390
492
|
y2=${target.y ?? 0}
|
|
391
493
|
@click=${() => this.onLinkClick(l)}
|
|
392
|
-
@
|
|
393
|
-
|
|
394
|
-
e.preventDefault();
|
|
395
|
-
this.onLinkClick(l);
|
|
396
|
-
}
|
|
397
|
-
}}
|
|
494
|
+
@focus=${() => this.onGraphItemFocus(itemIndex)}
|
|
495
|
+
@keydown=${(e) => this.onGraphKeyDown(e, itemIndex, () => this.onLinkClick(l))}
|
|
398
496
|
></line>`;
|
|
399
497
|
})}
|
|
400
|
-
${this.simNodes.map((n) => {
|
|
498
|
+
${this.simNodes.map((n, nodeIndex) => {
|
|
401
499
|
const fill = sanitizeNodeColor(n.color);
|
|
500
|
+
const itemIndex = nodeIndex;
|
|
402
501
|
return svg `<g>
|
|
403
502
|
<circle
|
|
404
503
|
part="node"
|
|
405
504
|
role="button"
|
|
406
|
-
tabindex
|
|
505
|
+
tabindex=${this.normalizedGraphItem() === itemIndex ? '0' : '-1'}
|
|
407
506
|
aria-label=${n.label ?? n.id}
|
|
408
507
|
r=${this.nodeRadius(n)}
|
|
409
508
|
cx=${n.x ?? 0}
|
|
410
509
|
cy=${n.y ?? 0}
|
|
411
510
|
style=${styleMap(fill ? { '--lyra-node-fill': fill } : {})}
|
|
412
511
|
@click=${() => this.onNodeClick(n)}
|
|
413
|
-
@
|
|
414
|
-
|
|
415
|
-
e.preventDefault();
|
|
416
|
-
this.onNodeClick(n);
|
|
417
|
-
}
|
|
418
|
-
}}
|
|
512
|
+
@focus=${() => this.onGraphItemFocus(itemIndex)}
|
|
513
|
+
@keydown=${(e) => this.onGraphKeyDown(e, itemIndex, () => this.onNodeClick(n))}
|
|
419
514
|
></circle>
|
|
420
515
|
${n.label
|
|
421
516
|
? svg `<text part="label" aria-hidden="true" x=${(n.x ?? 0) + this.nodeRadius(n) + 2} y=${n.y ?? 0}>${n.label}</text>`
|
|
@@ -424,6 +519,17 @@ export class LyraGraph extends LyraElement {
|
|
|
424
519
|
})}
|
|
425
520
|
</g>
|
|
426
521
|
</svg>
|
|
522
|
+
<div part="live-region" class="sr-only" role="status" aria-live="polite" aria-atomic="true">
|
|
523
|
+
${this.graphLiveText || (this.normalizedGraphItem() >= 0 ? `${this.graphItemText(this.normalizedGraphItem())} (1 of ${this.graphItemCount()})` : '')}
|
|
524
|
+
</div>
|
|
525
|
+
<ul part="data-list" class="sr-only" aria-label="Graph data">
|
|
526
|
+
${this.simNodes.map((node) => html `<li>Node ${node.label ?? node.id}</li>`)}
|
|
527
|
+
${this.simLinks.map((link) => {
|
|
528
|
+
const source = link.source;
|
|
529
|
+
const target = link.target;
|
|
530
|
+
return html `<li>Link from ${source.label ?? source.id} to ${target.label ?? target.id}</li>`;
|
|
531
|
+
})}
|
|
532
|
+
</ul>
|
|
427
533
|
</div>
|
|
428
534
|
`;
|
|
429
535
|
}
|
|
@@ -464,4 +570,10 @@ __decorate([
|
|
|
464
570
|
__decorate([
|
|
465
571
|
state()
|
|
466
572
|
], LyraGraph.prototype, "simLinks", void 0);
|
|
573
|
+
__decorate([
|
|
574
|
+
state()
|
|
575
|
+
], LyraGraph.prototype, "activeGraphItem", void 0);
|
|
576
|
+
__decorate([
|
|
577
|
+
state()
|
|
578
|
+
], LyraGraph.prototype, "graphLiveText", void 0);
|
|
467
579
|
defineElement('graph', LyraGraph);
|
|
@@ -6,17 +6,26 @@ export interface CalendarDay {
|
|
|
6
6
|
export interface CalendarCell {
|
|
7
7
|
date: string;
|
|
8
8
|
value: number;
|
|
9
|
-
/** 0-based week column, counting from the grid's first (earliest-containing)
|
|
9
|
+
/** 0-based week column, counting from the grid's first (earliest-containing) anchor week — see `buildCalendarGrid()`'s `firstDayOfWeek` parameter. */
|
|
10
10
|
week: number;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Day-of-week position within the row, relative to `firstDayOfWeek`: 0 for
|
|
13
|
+
* the anchor weekday itself, up to 6 for the day immediately before it.
|
|
14
|
+
* With the default `firstDayOfWeek` of 0, this is the familiar 0 (Sunday)
|
|
15
|
+
* .. 6 (Saturday) numbering, unchanged from before `firstDayOfWeek` existed.
|
|
16
|
+
*/
|
|
12
17
|
weekday: number;
|
|
13
18
|
}
|
|
14
19
|
/** Parses a `yyyy-mm-dd` string as UTC midnight, avoiding local-timezone day-boundary drift. */
|
|
15
20
|
export declare function parseIsoDate(iso: string): Date;
|
|
16
21
|
/**
|
|
17
|
-
* Lays `days` out onto a GitHub-style
|
|
18
|
-
*
|
|
19
|
-
*
|
|
22
|
+
* Lays `days` out onto a GitHub-style week grid, 7 rows deep. `days` need not
|
|
23
|
+
* be sorted or contiguous; the grid spans from the earliest to the latest
|
|
24
|
+
* date present, anchored at the `firstDayOfWeek`-weekday on/before the
|
|
25
|
+
* earliest date (`firstDayOfWeek` is 0–6, Sunday-first by default — same
|
|
26
|
+
* numbering as `CalendarCell.weekday`). With the default `firstDayOfWeek` of
|
|
27
|
+
* 0, this is the Sunday on/before the earliest date, exactly as before this
|
|
28
|
+
* parameter existed.
|
|
20
29
|
*
|
|
21
30
|
* An entry whose `date` doesn't parse to a valid calendar date (e.g. `''` or
|
|
22
31
|
* `'2026-03'`) is dropped rather than included: `parseIsoDate` on a malformed
|
|
@@ -28,7 +37,7 @@ export declare function parseIsoDate(iso: string): Date;
|
|
|
28
37
|
* over into March) is dropped rather than silently renormalized — see
|
|
29
38
|
* `isCalendarValid()`.
|
|
30
39
|
*/
|
|
31
|
-
export declare function buildCalendarGrid(days: CalendarDay[]): {
|
|
40
|
+
export declare function buildCalendarGrid(days: CalendarDay[], firstDayOfWeek?: number): {
|
|
32
41
|
cells: CalendarCell[];
|
|
33
42
|
weekCount: number;
|
|
34
43
|
firstWeekStart: Date;
|
|
@@ -12,9 +12,13 @@ function isCalendarValid(iso, parsed) {
|
|
|
12
12
|
return parsed.getUTCFullYear() === y && parsed.getUTCMonth() === m - 1 && parsed.getUTCDate() === d;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Lays `days` out onto a GitHub-style
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Lays `days` out onto a GitHub-style week grid, 7 rows deep. `days` need not
|
|
16
|
+
* be sorted or contiguous; the grid spans from the earliest to the latest
|
|
17
|
+
* date present, anchored at the `firstDayOfWeek`-weekday on/before the
|
|
18
|
+
* earliest date (`firstDayOfWeek` is 0–6, Sunday-first by default — same
|
|
19
|
+
* numbering as `CalendarCell.weekday`). With the default `firstDayOfWeek` of
|
|
20
|
+
* 0, this is the Sunday on/before the earliest date, exactly as before this
|
|
21
|
+
* parameter existed.
|
|
18
22
|
*
|
|
19
23
|
* An entry whose `date` doesn't parse to a valid calendar date (e.g. `''` or
|
|
20
24
|
* `'2026-03'`) is dropped rather than included: `parseIsoDate` on a malformed
|
|
@@ -26,7 +30,7 @@ function isCalendarValid(iso, parsed) {
|
|
|
26
30
|
* over into March) is dropped rather than silently renormalized — see
|
|
27
31
|
* `isCalendarValid()`.
|
|
28
32
|
*/
|
|
29
|
-
export function buildCalendarGrid(days) {
|
|
33
|
+
export function buildCalendarGrid(days, firstDayOfWeek = 0) {
|
|
30
34
|
const parsed = days
|
|
31
35
|
.map((d) => ({ ...d, dateObj: parseIsoDate(d.date) }))
|
|
32
36
|
.filter((d) => !Number.isNaN(d.dateObj.getTime()) && isCalendarValid(d.date, d.dateObj));
|
|
@@ -35,9 +39,16 @@ export function buildCalendarGrid(days) {
|
|
|
35
39
|
}
|
|
36
40
|
const timeBounds = minMax(parsed.map((d) => d.dateObj.getTime()));
|
|
37
41
|
const min = new Date(timeBounds[0]);
|
|
38
|
-
|
|
42
|
+
// Days back from `min` to the nearest `firstDayOfWeek`-weekday on/before
|
|
43
|
+
// it — with the default firstDayOfWeek of 0, this reduces to
|
|
44
|
+
// `min.getUTCDay()` exactly, i.e. today's original Sunday anchor.
|
|
45
|
+
const daysBackToAnchor = (min.getUTCDay() - firstDayOfWeek + 7) % 7;
|
|
46
|
+
const firstWeekStart = new Date(Date.UTC(min.getUTCFullYear(), min.getUTCMonth(), min.getUTCDate() - daysBackToAnchor));
|
|
39
47
|
const cells = parsed.map((d) => {
|
|
40
|
-
|
|
48
|
+
// Relative to the anchor weekday (see `CalendarCell.weekday`'s doc
|
|
49
|
+
// comment) — with the default firstDayOfWeek of 0 this is just
|
|
50
|
+
// `d.dateObj.getUTCDay()`, unchanged from before.
|
|
51
|
+
const weekday = (d.dateObj.getUTCDay() - firstDayOfWeek + 7) % 7;
|
|
41
52
|
const daysSinceStart = Math.round((d.dateObj.getTime() - firstWeekStart.getTime()) / MS_PER_DAY);
|
|
42
53
|
const week = Math.floor(daysSinceStart / 7);
|
|
43
54
|
return { date: d.date, value: d.value, week, weekday };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Maps `value` in `[lo, hi]` to a 0.1
|
|
1
|
+
/** Maps `value` in `[lo, hi]` to a 0.1-1.0 alpha so the lowest real value still reads as faintly present rather than invisible. */
|
|
2
2
|
export declare function linearAlpha(value: number, lo: number, hi: number): number;
|
|
3
3
|
/**
|
|
4
4
|
* Computes `[min, max]` of `values` via a linear scan, or `null` for an
|
|
@@ -18,5 +18,9 @@ export declare function minMax(values: number[]): [number, number] | null;
|
|
|
18
18
|
* (`drawMatrix()`) before `sqrtStep` is ever invoked, so a real `count === 0`
|
|
19
19
|
* (e.g. "zero events that day") reaches this function and must bucket to the
|
|
20
20
|
* lowest ramp step like any other legitimate value, not render as no-data.
|
|
21
|
+
* Likewise, `max <= 0` means every real value in the dataset is zero (a
|
|
22
|
+
* legitimate "zero events everywhere" dataset, not an absence of data) — the
|
|
23
|
+
* whole range collapses to a single point, so `count` (itself necessarily 0)
|
|
24
|
+
* buckets to the lowest step rather than being misread as no-data.
|
|
21
25
|
*/
|
|
22
26
|
export declare function sqrtStep(count: number, max: number, steps: number): number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Maps `value` in `[lo, hi]` to a 0.1
|
|
1
|
+
/** Maps `value` in `[lo, hi]` to a 0.1-1.0 alpha so the lowest real value still reads as faintly present rather than invisible. */
|
|
2
2
|
export function linearAlpha(value, lo, hi) {
|
|
3
3
|
const span = hi - lo || 1;
|
|
4
4
|
const t = (value - lo) / span;
|
|
@@ -35,10 +35,16 @@ export function minMax(values) {
|
|
|
35
35
|
* (`drawMatrix()`) before `sqrtStep` is ever invoked, so a real `count === 0`
|
|
36
36
|
* (e.g. "zero events that day") reaches this function and must bucket to the
|
|
37
37
|
* lowest ramp step like any other legitimate value, not render as no-data.
|
|
38
|
+
* Likewise, `max <= 0` means every real value in the dataset is zero (a
|
|
39
|
+
* legitimate "zero events everywhere" dataset, not an absence of data) — the
|
|
40
|
+
* whole range collapses to a single point, so `count` (itself necessarily 0)
|
|
41
|
+
* buckets to the lowest step rather than being misread as no-data.
|
|
38
42
|
*/
|
|
39
43
|
export function sqrtStep(count, max, steps) {
|
|
40
|
-
if (count < 0
|
|
44
|
+
if (count < 0)
|
|
41
45
|
return -1;
|
|
46
|
+
if (max <= 0)
|
|
47
|
+
return 0;
|
|
42
48
|
const ratio = Math.sqrt(count) / Math.sqrt(max);
|
|
43
49
|
return Math.min(steps - 1, Math.floor(ratio * steps));
|
|
44
50
|
}
|