@aceshooting/lyra-ui 1.1.0 → 2.0.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 +12 -1
- package/custom-elements.json +21460 -8489
- package/dist/components/app-rail/app-rail.d.ts +171 -0
- package/dist/components/app-rail/app-rail.js +483 -0
- package/dist/components/app-rail/app-rail.styles.d.ts +1 -0
- package/dist/components/app-rail/app-rail.styles.js +125 -0
- package/dist/components/attachment-chip/attachment-chip.d.ts +114 -0
- package/dist/components/attachment-chip/attachment-chip.js +312 -0
- package/dist/components/attachment-chip/attachment-chip.styles.d.ts +1 -0
- package/dist/components/attachment-chip/attachment-chip.styles.js +184 -0
- package/dist/components/attachment-trigger/attachment-trigger.d.ts +83 -0
- package/dist/components/attachment-trigger/attachment-trigger.js +258 -0
- package/dist/components/attachment-trigger/attachment-trigger.styles.d.ts +1 -0
- package/dist/components/attachment-trigger/attachment-trigger.styles.js +68 -0
- package/dist/components/chart/box-plot.js +11 -1
- package/dist/components/chart/chart-loader.d.ts +44 -13
- package/dist/components/chart/chart-loader.js +65 -18
- package/dist/components/chart/chart.d.ts +12 -2
- package/dist/components/chart/chart.js +121 -14
- package/dist/components/chart/histogram-bin.d.ts +4 -1
- package/dist/components/chart/histogram-bin.js +24 -14
- package/dist/components/chart/lite-chart.d.ts +63 -0
- package/dist/components/chart/lite-chart.js +181 -23
- package/dist/components/chart/lite-chart.styles.js +9 -0
- package/dist/components/chat-composer/chat-composer.d.ts +92 -0
- package/dist/components/chat-composer/chat-composer.js +285 -0
- package/dist/components/chat-composer/chat-composer.styles.d.ts +1 -0
- package/dist/components/chat-composer/chat-composer.styles.js +135 -0
- package/dist/components/chat-message/chat-message.d.ts +113 -0
- package/dist/components/chat-message/chat-message.js +291 -0
- package/dist/components/chat-message/chat-message.styles.d.ts +1 -0
- package/dist/components/chat-message/chat-message.styles.js +203 -0
- package/dist/components/checkbox/checkbox.d.ts +59 -0
- package/dist/components/checkbox/checkbox.js +230 -0
- package/dist/components/checkbox/checkbox.styles.d.ts +1 -0
- package/dist/components/checkbox/checkbox.styles.js +70 -0
- package/dist/components/chip/chip-group.d.ts +56 -0
- package/dist/components/chip/chip-group.js +124 -0
- package/dist/components/chip/chip-group.styles.d.ts +1 -0
- package/dist/components/chip/chip-group.styles.js +57 -0
- package/dist/components/chip/chip.d.ts +64 -0
- package/dist/components/chip/chip.js +115 -0
- package/dist/components/chip/chip.styles.d.ts +1 -0
- package/dist/components/chip/chip.styles.js +115 -0
- package/dist/components/citation-badge/citation-badge.d.ts +113 -0
- package/dist/components/citation-badge/citation-badge.js +300 -0
- package/dist/components/citation-badge/citation-badge.styles.d.ts +1 -0
- package/dist/components/citation-badge/citation-badge.styles.js +96 -0
- package/dist/components/code-block/code-block.d.ts +89 -0
- package/dist/components/code-block/code-block.js +317 -0
- package/dist/components/code-block/code-block.styles.d.ts +1 -0
- package/dist/components/code-block/code-block.styles.js +187 -0
- package/dist/components/code-block/code-loader.d.ts +44 -0
- package/dist/components/code-block/code-loader.js +79 -0
- package/dist/components/combobox/combobox.d.ts +19 -1
- package/dist/components/combobox/combobox.js +84 -14
- package/dist/components/combobox/option.d.ts +5 -1
- package/dist/components/combobox/option.js +35 -0
- package/dist/components/context-meter/context-meter.d.ts +53 -0
- package/dist/components/context-meter/context-meter.js +150 -0
- package/dist/components/context-meter/context-meter.styles.d.ts +1 -0
- package/dist/components/context-meter/context-meter.styles.js +120 -0
- package/dist/components/conversation-item/conversation-item.d.ts +128 -0
- package/dist/components/conversation-item/conversation-item.js +322 -0
- package/dist/components/conversation-item/conversation-item.styles.d.ts +1 -0
- package/dist/components/conversation-item/conversation-item.styles.js +155 -0
- package/dist/components/date-picker/date-input.d.ts +24 -0
- package/dist/components/date-picker/date-input.js +134 -22
- package/dist/components/date-picker/date-picker.d.ts +7 -0
- package/dist/components/date-picker/date-picker.js +85 -16
- package/dist/components/dialog/confirm.d.ts +40 -0
- package/dist/components/dialog/confirm.js +79 -0
- package/dist/components/dialog/dialog.d.ts +85 -0
- package/dist/components/dialog/dialog.js +272 -0
- package/dist/components/dialog/dialog.styles.d.ts +1 -0
- package/dist/components/dialog/dialog.styles.js +52 -0
- package/dist/components/dock-panel/dock-panel.d.ts +134 -0
- package/dist/components/dock-panel/dock-panel.js +345 -0
- package/dist/components/dock-panel/dock-panel.styles.d.ts +1 -0
- package/dist/components/dock-panel/dock-panel.styles.js +145 -0
- package/dist/components/document-preview/document-preview.d.ts +135 -0
- package/dist/components/document-preview/document-preview.js +372 -0
- package/dist/components/document-preview/document-preview.styles.d.ts +1 -0
- package/dist/components/document-preview/document-preview.styles.js +191 -0
- package/dist/components/export-button/csv.js +1 -1
- package/dist/components/export-button/export-button.js +2 -0
- package/dist/components/flag/flag.d.ts +42 -5
- package/dist/components/flag/flag.js +70 -3
- package/dist/components/gauge/gauge.js +19 -10
- package/dist/components/generation-status/generation-status.d.ts +109 -0
- package/dist/components/generation-status/generation-status.js +273 -0
- package/dist/components/generation-status/generation-status.styles.d.ts +1 -0
- package/dist/components/generation-status/generation-status.styles.js +84 -0
- package/dist/components/heatmap/calendar-grid.d.ts +4 -1
- package/dist/components/heatmap/calendar-grid.js +25 -12
- package/dist/components/heatmap/heatmap-scale.d.ts +6 -0
- package/dist/components/heatmap/heatmap-scale.js +7 -1
- package/dist/components/heatmap/heatmap.d.ts +89 -3
- package/dist/components/heatmap/heatmap.js +158 -29
- package/dist/components/json-viewer/json-viewer.d.ts +53 -0
- package/dist/components/json-viewer/json-viewer.js +283 -0
- package/dist/components/json-viewer/json-viewer.styles.d.ts +1 -0
- package/dist/components/json-viewer/json-viewer.styles.js +176 -0
- package/dist/components/kbd/kbd.d.ts +77 -0
- package/dist/components/kbd/kbd.js +214 -0
- package/dist/components/kbd/kbd.styles.d.ts +1 -0
- package/dist/components/kbd/kbd.styles.js +52 -0
- package/dist/components/live-region/live-region.d.ts +81 -0
- package/dist/components/live-region/live-region.js +153 -0
- package/dist/components/live-region/live-region.styles.d.ts +1 -0
- package/dist/components/live-region/live-region.styles.js +10 -0
- package/dist/components/map/map.d.ts +8 -6
- package/dist/components/map/map.js +44 -8
- package/dist/components/markdown/markdown-loader.d.ts +34 -0
- package/dist/components/markdown/markdown-loader.js +43 -0
- package/dist/components/markdown/markdown.d.ts +81 -0
- package/dist/components/markdown/markdown.js +284 -0
- package/dist/components/markdown/markdown.styles.d.ts +1 -0
- package/dist/components/markdown/markdown.styles.js +82 -0
- package/dist/components/media-card/media-card.d.ts +122 -0
- package/dist/components/media-card/media-card.js +295 -0
- package/dist/components/media-card/media-card.styles.d.ts +1 -0
- package/dist/components/media-card/media-card.styles.js +132 -0
- package/dist/components/mention-popover/mention-popover.d.ts +200 -0
- package/dist/components/mention-popover/mention-popover.js +475 -0
- package/dist/components/mention-popover/mention-popover.styles.d.ts +1 -0
- package/dist/components/mention-popover/mention-popover.styles.js +101 -0
- package/dist/components/menu/menu-item.d.ts +62 -0
- package/dist/components/menu/menu-item.js +119 -0
- package/dist/components/menu/menu-item.styles.d.ts +1 -0
- package/dist/components/menu/menu-item.styles.js +67 -0
- package/dist/components/menu/menu.d.ts +143 -0
- package/dist/components/menu/menu.js +365 -0
- package/dist/components/menu/menu.styles.d.ts +1 -0
- package/dist/components/menu/menu.styles.js +66 -0
- package/dist/components/model-select/model-select.d.ts +125 -0
- package/dist/components/model-select/model-select.js +490 -0
- package/dist/components/model-select/model-select.styles.d.ts +1 -0
- package/dist/components/model-select/model-select.styles.js +189 -0
- package/dist/components/model-settings-panel/model-settings-panel.d.ts +69 -0
- package/dist/components/model-settings-panel/model-settings-panel.js +145 -0
- package/dist/components/model-settings-panel/model-settings-panel.styles.d.ts +1 -0
- package/dist/components/model-settings-panel/model-settings-panel.styles.js +82 -0
- package/dist/components/playback/playback.d.ts +13 -1
- package/dist/components/playback/playback.js +47 -14
- package/dist/components/responsive-panel/responsive-panel.d.ts +150 -0
- package/dist/components/responsive-panel/responsive-panel.js +365 -0
- package/dist/components/responsive-panel/responsive-panel.styles.d.ts +1 -0
- package/dist/components/responsive-panel/responsive-panel.styles.js +103 -0
- package/dist/components/result-card/result-card.d.ts +44 -0
- package/dist/components/result-card/result-card.js +83 -0
- package/dist/components/result-card/result-card.styles.d.ts +1 -0
- package/dist/components/result-card/result-card.styles.js +50 -0
- package/dist/components/result-card/result-field.d.ts +36 -0
- package/dist/components/result-card/result-field.js +87 -0
- package/dist/components/result-card/result-field.styles.d.ts +1 -0
- package/dist/components/result-card/result-field.styles.js +32 -0
- package/dist/components/select/select.d.ts +45 -1
- package/dist/components/select/select.js +114 -17
- package/dist/components/select/select.styles.js +26 -2
- package/dist/components/slider/slider.d.ts +89 -0
- package/dist/components/slider/slider.js +352 -0
- package/dist/components/slider/slider.styles.d.ts +1 -0
- package/dist/components/slider/slider.styles.js +87 -0
- package/dist/components/source-card/source-card.d.ts +92 -0
- package/dist/components/source-card/source-card.js +148 -0
- package/dist/components/source-card/source-card.styles.d.ts +1 -0
- package/dist/components/source-card/source-card.styles.js +69 -0
- package/dist/components/source-list/source-list.d.ts +63 -0
- package/dist/components/source-list/source-list.js +122 -0
- package/dist/components/source-list/source-list.styles.d.ts +1 -0
- package/dist/components/source-list/source-list.styles.js +59 -0
- package/dist/components/sparkline/sparkline.js +27 -13
- package/dist/components/split/split.d.ts +93 -1
- package/dist/components/split/split.js +284 -15
- package/dist/components/split/split.styles.js +30 -0
- package/dist/components/stat/stat.d.ts +22 -1
- package/dist/components/stat/stat.js +49 -3
- package/dist/components/stat/stat.styles.js +20 -0
- package/dist/components/stream-status/stream-status.d.ts +110 -0
- package/dist/components/stream-status/stream-status.js +210 -0
- package/dist/components/stream-status/stream-status.styles.d.ts +1 -0
- package/dist/components/stream-status/stream-status.styles.js +103 -0
- package/dist/components/streaming-text/streaming-text.d.ts +98 -0
- package/dist/components/streaming-text/streaming-text.js +209 -0
- package/dist/components/streaming-text/streaming-text.styles.d.ts +1 -0
- package/dist/components/streaming-text/streaming-text.styles.js +62 -0
- package/dist/components/switch/switch.d.ts +58 -0
- package/dist/components/switch/switch.js +179 -0
- package/dist/components/switch/switch.styles.d.ts +1 -0
- package/dist/components/switch/switch.styles.js +75 -0
- package/dist/components/table/table.d.ts +73 -4
- package/dist/components/table/table.js +169 -20
- package/dist/components/table/table.styles.js +6 -1
- package/dist/components/tabs/tabs.d.ts +51 -0
- package/dist/components/tabs/tabs.js +190 -0
- package/dist/components/tabs/tabs.styles.d.ts +1 -0
- package/dist/components/tabs/tabs.styles.js +63 -0
- package/dist/components/thinking-panel/thinking-panel.d.ts +121 -0
- package/dist/components/thinking-panel/thinking-panel.js +235 -0
- package/dist/components/thinking-panel/thinking-panel.styles.d.ts +1 -0
- package/dist/components/thinking-panel/thinking-panel.styles.js +117 -0
- package/dist/components/toast/toast-item.d.ts +1 -0
- package/dist/components/toast/toast-item.js +31 -6
- package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +130 -0
- package/dist/components/tool-approval-dialog/tool-approval-dialog.js +403 -0
- package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.d.ts +1 -0
- package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +160 -0
- package/dist/components/tool-call-chip/tool-call-chip.d.ts +89 -0
- package/dist/components/tool-call-chip/tool-call-chip.js +288 -0
- package/dist/components/tool-call-chip/tool-call-chip.styles.d.ts +1 -0
- package/dist/components/tool-call-chip/tool-call-chip.styles.js +203 -0
- package/dist/components/tool-param-form/tool-param-form.d.ts +145 -0
- package/dist/components/tool-param-form/tool-param-form.js +366 -0
- package/dist/components/tool-param-form/tool-param-form.styles.d.ts +1 -0
- package/dist/components/tool-param-form/tool-param-form.styles.js +75 -0
- package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +102 -0
- package/dist/components/tool-result-dialog/tool-result-dialog.js +393 -0
- package/dist/components/tool-result-dialog/tool-result-dialog.styles.d.ts +1 -0
- package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +176 -0
- package/dist/components/tool-result-view/registry.d.ts +81 -0
- package/dist/components/tool-result-view/registry.js +84 -0
- package/dist/components/tool-result-view/tool-result-view.d.ts +55 -0
- package/dist/components/tool-result-view/tool-result-view.js +138 -0
- package/dist/components/tool-result-view/tool-result-view.styles.d.ts +1 -0
- package/dist/components/tool-result-view/tool-result-view.styles.js +13 -0
- package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +151 -0
- package/dist/components/tool-select-dialog/tool-select-dialog.js +439 -0
- package/dist/components/tool-select-dialog/tool-select-dialog.styles.d.ts +1 -0
- package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +182 -0
- package/dist/components/tree/tree-node.d.ts +11 -2
- package/dist/components/tree/tree-node.js +16 -6
- package/dist/components/tree/tree.d.ts +8 -2
- package/dist/components/tree/tree.js +18 -11
- package/dist/components/typing-indicator/typing-indicator.d.ts +60 -0
- package/dist/components/typing-indicator/typing-indicator.js +96 -0
- package/dist/components/typing-indicator/typing-indicator.styles.d.ts +1 -0
- package/dist/components/typing-indicator/typing-indicator.styles.js +132 -0
- package/dist/components/virtual-list/virtual-list.d.ts +173 -0
- package/dist/components/virtual-list/virtual-list.js +461 -0
- package/dist/components/virtual-list/virtual-list.styles.d.ts +1 -0
- package/dist/components/virtual-list/virtual-list.styles.js +44 -0
- package/dist/components/widget/widget.d.ts +10 -0
- package/dist/components/widget/widget.js +24 -1
- package/dist/components/widget/widget.styles.js +9 -2
- package/dist/components/word-cloud/word-cloud-layout.js +14 -1
- package/dist/components/word-cloud/word-cloud.d.ts +6 -0
- package/dist/components/word-cloud/word-cloud.js +25 -14
- package/dist/components/word-cloud/word-cloud.styles.js +4 -2
- package/dist/internal/announcer.d.ts +57 -0
- package/dist/internal/announcer.js +71 -0
- package/dist/internal/form-associated.d.ts +1 -0
- package/dist/internal/form-associated.js +55 -11
- package/dist/internal/group-by-recency.d.ts +50 -0
- package/dist/internal/group-by-recency.js +80 -0
- package/dist/internal/rtl.d.ts +23 -0
- package/dist/internal/rtl.js +28 -0
- package/dist/internal/scroll-lock.d.ts +10 -5
- package/dist/internal/scroll-lock.js +30 -14
- package/dist/internal/tokens.styles.js +6 -0
- package/dist/lyra.d.ts +130 -31
- package/dist/lyra.js +94 -26
- package/llms-full.txt +3921 -32
- package/llms.txt +16 -1
- package/package.json +23 -5
- package/dist/components/chart/bar-chart.stories.d.ts +0 -5
- package/dist/components/chart/bar-chart.stories.js +0 -20
- package/dist/components/chart/box-plot.stories.d.ts +0 -5
- package/dist/components/chart/box-plot.stories.js +0 -29
- package/dist/components/chart/bubble-chart.stories.d.ts +0 -5
- package/dist/components/chart/bubble-chart.stories.js +0 -26
- package/dist/components/chart/chart.stories.d.ts +0 -32
- package/dist/components/chart/chart.stories.js +0 -141
- package/dist/components/chart/doughnut-chart.stories.d.ts +0 -5
- package/dist/components/chart/doughnut-chart.stories.js +0 -26
- package/dist/components/chart/histogram.stories.d.ts +0 -5
- package/dist/components/chart/histogram.stories.js +0 -17
- package/dist/components/chart/line-chart.stories.d.ts +0 -22
- package/dist/components/chart/line-chart.stories.js +0 -99
- package/dist/components/chart/lite-chart.stories.d.ts +0 -12
- package/dist/components/chart/lite-chart.stories.js +0 -98
- package/dist/components/chart/pie-chart.stories.d.ts +0 -5
- package/dist/components/chart/pie-chart.stories.js +0 -26
- package/dist/components/chart/polar-area-chart.stories.d.ts +0 -5
- package/dist/components/chart/polar-area-chart.stories.js +0 -20
- package/dist/components/chart/radar-chart.stories.d.ts +0 -5
- package/dist/components/chart/radar-chart.stories.js +0 -20
- package/dist/components/chart/scatter-chart.stories.d.ts +0 -5
- package/dist/components/chart/scatter-chart.stories.js +0 -31
- package/dist/components/combobox/combobox.stories.d.ts +0 -24
- package/dist/components/combobox/combobox.stories.js +0 -102
- package/dist/components/date-picker/date-input.stories.d.ts +0 -10
- package/dist/components/date-picker/date-input.stories.js +0 -48
- package/dist/components/date-picker/date-picker.stories.d.ts +0 -10
- package/dist/components/date-picker/date-picker.stories.js +0 -25
- package/dist/components/empty/empty.stories.d.ts +0 -7
- package/dist/components/empty/empty.stories.js +0 -34
- package/dist/components/export-button/export-button.stories.d.ts +0 -7
- package/dist/components/export-button/export-button.stories.js +0 -37
- package/dist/components/file-input/file-input.stories.d.ts +0 -7
- package/dist/components/file-input/file-input.stories.js +0 -19
- package/dist/components/flag/flag.stories.d.ts +0 -5
- package/dist/components/flag/flag.stories.js +0 -17
- package/dist/components/gauge/gauge.stories.d.ts +0 -8
- package/dist/components/gauge/gauge.stories.js +0 -19
- package/dist/components/graph/graph.stories.d.ts +0 -8
- package/dist/components/graph/graph.stories.js +0 -68
- package/dist/components/heatmap/heatmap.stories.d.ts +0 -22
- package/dist/components/heatmap/heatmap.stories.js +0 -154
- package/dist/components/map/map.stories.d.ts +0 -15
- package/dist/components/map/map.stories.js +0 -135
- package/dist/components/playback/playback.stories.d.ts +0 -7
- package/dist/components/playback/playback.stories.js +0 -16
- package/dist/components/select/select.stories.d.ts +0 -19
- package/dist/components/select/select.stories.js +0 -75
- package/dist/components/skeleton/skeleton.stories.d.ts +0 -5
- package/dist/components/skeleton/skeleton.stories.js +0 -16
- package/dist/components/sparkline/sparkline.stories.d.ts +0 -11
- package/dist/components/sparkline/sparkline.stories.js +0 -39
- package/dist/components/split/split.stories.d.ts +0 -7
- package/dist/components/split/split.stories.js +0 -36
- package/dist/components/stat/stat.stories.d.ts +0 -8
- package/dist/components/stat/stat.stories.js +0 -64
- package/dist/components/table/table.stories.d.ts +0 -12
- package/dist/components/table/table.stories.js +0 -67
- package/dist/components/time-range/time-range.stories.d.ts +0 -8
- package/dist/components/time-range/time-range.stories.js +0 -26
- package/dist/components/toast/toast.stories.d.ts +0 -8
- package/dist/components/toast/toast.stories.js +0 -72
- package/dist/components/tree/tree.stories.d.ts +0 -9
- package/dist/components/tree/tree.stories.js +0 -52
- package/dist/components/widget/widget.stories.d.ts +0 -7
- package/dist/components/widget/widget.stories.js +0 -41
- package/dist/components/word-cloud/word-cloud.stories.d.ts +0 -10
- package/dist/components/word-cloud/word-cloud.stories.js +0 -55
|
@@ -23,7 +23,10 @@ export declare function parseIsoDate(iso: string): Date;
|
|
|
23
23
|
* string yields an `Invalid Date` (`getTime()` is `NaN`), and letting that
|
|
24
24
|
* single `NaN` reach the min/max below would poison `firstWeekStart` (and
|
|
25
25
|
* therefore every cell's `week`) for the whole grid instead of just the bad
|
|
26
|
-
* entry.
|
|
26
|
+
* entry. Likewise, an entry whose `date` is numerically well-formed but
|
|
27
|
+
* calendar-invalid (e.g. `'2026-02-30'`, which `Date.UTC` silently rolls
|
|
28
|
+
* over into March) is dropped rather than silently renormalized — see
|
|
29
|
+
* `isCalendarValid()`.
|
|
27
30
|
*/
|
|
28
31
|
export declare function buildCalendarGrid(days: CalendarDay[]): {
|
|
29
32
|
cells: CalendarCell[];
|
|
@@ -5,6 +5,12 @@ export function parseIsoDate(iso) {
|
|
|
5
5
|
const [y, m, d] = iso.split('-').map(Number);
|
|
6
6
|
return new Date(Date.UTC(y, m - 1, d));
|
|
7
7
|
}
|
|
8
|
+
/** Whether `iso` (already run through parseIsoDate) actually round-trips —
|
|
9
|
+
* Date.UTC silently rolls e.g. day 30 of a 28-day February into March. */
|
|
10
|
+
function isCalendarValid(iso, parsed) {
|
|
11
|
+
const [y, m, d] = iso.split('-').map(Number);
|
|
12
|
+
return parsed.getUTCFullYear() === y && parsed.getUTCMonth() === m - 1 && parsed.getUTCDate() === d;
|
|
13
|
+
}
|
|
8
14
|
/**
|
|
9
15
|
* Lays `days` out onto a GitHub-style Sunday-Saturday x week grid. `days`
|
|
10
16
|
* need not be sorted or contiguous; the grid spans from the earliest to the
|
|
@@ -15,12 +21,15 @@ export function parseIsoDate(iso) {
|
|
|
15
21
|
* string yields an `Invalid Date` (`getTime()` is `NaN`), and letting that
|
|
16
22
|
* single `NaN` reach the min/max below would poison `firstWeekStart` (and
|
|
17
23
|
* therefore every cell's `week`) for the whole grid instead of just the bad
|
|
18
|
-
* entry.
|
|
24
|
+
* entry. Likewise, an entry whose `date` is numerically well-formed but
|
|
25
|
+
* calendar-invalid (e.g. `'2026-02-30'`, which `Date.UTC` silently rolls
|
|
26
|
+
* over into March) is dropped rather than silently renormalized — see
|
|
27
|
+
* `isCalendarValid()`.
|
|
19
28
|
*/
|
|
20
29
|
export function buildCalendarGrid(days) {
|
|
21
30
|
const parsed = days
|
|
22
31
|
.map((d) => ({ ...d, dateObj: parseIsoDate(d.date) }))
|
|
23
|
-
.filter((d) => !Number.isNaN(d.dateObj.getTime()));
|
|
32
|
+
.filter((d) => !Number.isNaN(d.dateObj.getTime()) && isCalendarValid(d.date, d.dateObj));
|
|
24
33
|
if (parsed.length === 0) {
|
|
25
34
|
return { cells: [], weekCount: 0, firstWeekStart: new Date(0), monthLabels: [] };
|
|
26
35
|
}
|
|
@@ -34,18 +43,22 @@ export function buildCalendarGrid(days) {
|
|
|
34
43
|
return { date: d.date, value: d.value, week, weekday };
|
|
35
44
|
});
|
|
36
45
|
const weekCount = minMax(cells.map((c) => c.week))[1] + 1;
|
|
46
|
+
// Label every distinct (year, month) present in the data, not just months
|
|
47
|
+
// that happen to contain a Sunday-anchored cell — a sparse calendar (few
|
|
48
|
+
// days per month, none landing on a Sunday) would otherwise render zero
|
|
49
|
+
// month labels for months that do have data.
|
|
37
50
|
const monthLabels = [];
|
|
38
|
-
|
|
39
|
-
for (const cell of cells.slice().sort((a, b) => a.week - b.week)) {
|
|
51
|
+
const seenMonths = new Set();
|
|
52
|
+
for (const cell of cells.slice().sort((a, b) => a.week - b.week || a.weekday - b.weekday)) {
|
|
40
53
|
const cellDate = parseIsoDate(cell.date);
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
48
|
-
}
|
|
54
|
+
const key = `${cellDate.getUTCFullYear()}-${cellDate.getUTCMonth()}`;
|
|
55
|
+
if (seenMonths.has(key))
|
|
56
|
+
continue;
|
|
57
|
+
seenMonths.add(key);
|
|
58
|
+
monthLabels.push({
|
|
59
|
+
week: cell.week,
|
|
60
|
+
label: cellDate.toLocaleString(undefined, { month: 'short', timeZone: 'UTC' }),
|
|
61
|
+
});
|
|
49
62
|
}
|
|
50
63
|
return { cells, weekCount, firstWeekStart, monthLabels };
|
|
51
64
|
}
|
|
@@ -12,5 +12,11 @@ export declare function minMax(values: number[]): [number, number] | null;
|
|
|
12
12
|
* Square-root-scaled bucket index in `[0, steps-1]`, or `-1` for no-data.
|
|
13
13
|
* Compresses large counts so a single heavy cell doesn't wash out the rest
|
|
14
14
|
* of a sequential color ramp.
|
|
15
|
+
*
|
|
16
|
+
* Only a negative `count` means "no data" here — the true no-data sentinel
|
|
17
|
+
* (`v < 0 || !Number.isFinite(v)`) is already filtered out by the caller
|
|
18
|
+
* (`drawMatrix()`) before `sqrtStep` is ever invoked, so a real `count === 0`
|
|
19
|
+
* (e.g. "zero events that day") reaches this function and must bucket to the
|
|
20
|
+
* lowest ramp step like any other legitimate value, not render as no-data.
|
|
15
21
|
*/
|
|
16
22
|
export declare function sqrtStep(count: number, max: number, steps: number): number;
|
|
@@ -29,9 +29,15 @@ export function minMax(values) {
|
|
|
29
29
|
* Square-root-scaled bucket index in `[0, steps-1]`, or `-1` for no-data.
|
|
30
30
|
* Compresses large counts so a single heavy cell doesn't wash out the rest
|
|
31
31
|
* of a sequential color ramp.
|
|
32
|
+
*
|
|
33
|
+
* Only a negative `count` means "no data" here — the true no-data sentinel
|
|
34
|
+
* (`v < 0 || !Number.isFinite(v)`) is already filtered out by the caller
|
|
35
|
+
* (`drawMatrix()`) before `sqrtStep` is ever invoked, so a real `count === 0`
|
|
36
|
+
* (e.g. "zero events that day") reaches this function and must bucket to the
|
|
37
|
+
* lowest ramp step like any other legitimate value, not render as no-data.
|
|
32
38
|
*/
|
|
33
39
|
export function sqrtStep(count, max, steps) {
|
|
34
|
-
if (count
|
|
40
|
+
if (count < 0 || max <= 0)
|
|
35
41
|
return -1;
|
|
36
42
|
const ratio = Math.sqrt(count) / Math.sqrt(max);
|
|
37
43
|
return Math.min(steps - 1, Math.floor(ratio * steps));
|
|
@@ -79,10 +79,18 @@ export declare function mixColor(fromColor: string, toColor: string, t: number):
|
|
|
79
79
|
* specific cells (e.g. to call out an anomaly), each one optionally
|
|
80
80
|
* surfaced in the legend too via `[part="legend-annotation"]`.
|
|
81
81
|
*
|
|
82
|
+
* `columnX` (calendar mode only) overrides the x-origin computed for each
|
|
83
|
+
* week column — drawing, hit-testing, the focus ring, and month-label
|
|
84
|
+
* positioning all consult it consistently, so a consumer can pixel-align a
|
|
85
|
+
* calendar's week columns with a sibling chart's coordinate system. Unset
|
|
86
|
+
* (the default) keeps the original evenly-spaced formula.
|
|
87
|
+
*
|
|
82
88
|
* @customElement lyra-heatmap
|
|
83
89
|
* @event lyra-cell-click - Fired on click, or Enter/Space on the
|
|
84
90
|
* focused/hovered cell. `detail: { row, col, value }` in matrix mode,
|
|
85
|
-
* `detail: { date, value }` in calendar mode.
|
|
91
|
+
* `detail: { date, value }` in calendar mode. `cellText` overrides the
|
|
92
|
+
* built-in English "Row X, Col Y: value" / "Mon DD: value" template used for
|
|
93
|
+
* both the hover tooltip and the keyboard live-region announcement.
|
|
86
94
|
* @csspart base, canvas, tooltip, live-region, legend, legend-lo, legend-hi, legend-annotation
|
|
87
95
|
*/
|
|
88
96
|
export declare class LyraHeatmap extends LyraElement {
|
|
@@ -107,6 +115,23 @@ export declare class LyraHeatmap extends LyraElement {
|
|
|
107
115
|
bucketCount: number;
|
|
108
116
|
/** Cells to ring-highlight — `row`/`col` in matrix mode, `date` in calendar mode. See `HeatmapAnnotation`. */
|
|
109
117
|
annotations: HeatmapAnnotation[];
|
|
118
|
+
/** Formats the per-cell tooltip and keyboard live-region text — receives the cell position
|
|
119
|
+
* (`MatrixCellPos` in matrix mode, `CalendarCellPos` in calendar mode) and its value. Falls back to
|
|
120
|
+
* the built-in English "Row X, Col Y: value" / "Mon DD: value" template when unset. */
|
|
121
|
+
cellText?: (pos: MatrixCellPos | CalendarCellPos, value: number) => string;
|
|
122
|
+
/**
|
|
123
|
+
* Calendar mode only: overrides the x-origin (canvas-local CSS px) of week
|
|
124
|
+
* column `index` (0-based, same indexing as `CalendarCellPos.week`).
|
|
125
|
+
* Consulted consistently by every calendar-mode geometry call site —
|
|
126
|
+
* drawing, hit-testing, the keyboard focus ring, and month-label
|
|
127
|
+
* positioning — via the private `columnXFor()` helper, so painted
|
|
128
|
+
* geometry and pointer hit-testing never disagree. Lets a consumer
|
|
129
|
+
* pixel-align a calendar's week columns with a sibling chart's bars by
|
|
130
|
+
* supplying that chart the same coordinate function. Unset (the default)
|
|
131
|
+
* keeps today's evenly-spaced `CAL_PAD_LEFT + week * (CAL_CELL + CAL_GAP)`
|
|
132
|
+
* formula unchanged. Ignored in matrix mode.
|
|
133
|
+
*/
|
|
134
|
+
columnX?: (index: number) => number;
|
|
110
135
|
private canvas?;
|
|
111
136
|
private resizeObserver?;
|
|
112
137
|
private dprQuery?;
|
|
@@ -119,6 +144,19 @@ export declare class LyraHeatmap extends LyraElement {
|
|
|
119
144
|
private focusedCell;
|
|
120
145
|
/** Text of the visually-hidden `aria-live="polite"` status announcement, refreshed on every focus move. */
|
|
121
146
|
private liveText;
|
|
147
|
+
/**
|
|
148
|
+
* Whether `role`/`aria-label` were present on the host *before* this
|
|
149
|
+
* element's own code ever wrote to them — snapshotted once, in the very
|
|
150
|
+
* first `willUpdate()` (guarded by `hasUpdated`, which Lit only flips to
|
|
151
|
+
* `true` after that first update commits). Re-checking `hasAttribute()` on
|
|
152
|
+
* every update instead would self-poison: once this element writes its own
|
|
153
|
+
* default `aria-label` on the first render, `hasAttribute('aria-label')`
|
|
154
|
+
* is permanently `true` afterwards, which would wrongly look like an
|
|
155
|
+
* author-supplied value and freeze the label instead of refreshing it as
|
|
156
|
+
* `values`/`days` change on later updates.
|
|
157
|
+
*/
|
|
158
|
+
private authorSuppliedRole;
|
|
159
|
+
private authorSuppliedAriaLabel;
|
|
122
160
|
connectedCallback(): void;
|
|
123
161
|
disconnectedCallback(): void;
|
|
124
162
|
private watchDpr;
|
|
@@ -148,6 +186,40 @@ export declare class LyraHeatmap extends LyraElement {
|
|
|
148
186
|
/** Reads the customizable canvas-drawn annotation-ring stroke color off the host's computed style. */
|
|
149
187
|
private annotationColor;
|
|
150
188
|
private draw;
|
|
189
|
+
/**
|
|
190
|
+
* Calendar-mode week-column x-origin (canvas-local CSS px) — `columnX(week)`
|
|
191
|
+
* when set, otherwise the original evenly-spaced formula. Shared by every
|
|
192
|
+
* calendar-mode drawing and hit-testing call site (`drawCalendar()`,
|
|
193
|
+
* `hitTestCalendar()`/`weekAtX()`, `cellRect()`) so painted geometry and
|
|
194
|
+
* interactive hit-testing never disagree — mirrors the existing
|
|
195
|
+
* `matrixCellSize()` invariant for matrix mode's
|
|
196
|
+
* `drawMatrix()`/`hitTestMatrix()`/`cellRect()`.
|
|
197
|
+
*/
|
|
198
|
+
private columnXFor;
|
|
199
|
+
/**
|
|
200
|
+
* Inverse of `columnXFor()`: resolves an x position (canvas-local CSS px)
|
|
201
|
+
* to the week column it falls in, or `null` if it's outside every column
|
|
202
|
+
* (`weekCount` is 0, or `x` doesn't land inside `[0, weekCount)`'s span).
|
|
203
|
+
* The default spacing has a closed-form inverse (division); an arbitrary
|
|
204
|
+
* `columnX` override doesn't, so that case instead scans each column's
|
|
205
|
+
* `[columnXFor(week), columnXFor(week + 1))` span — algebraically the same
|
|
206
|
+
* span the default formula's division derives — so hit-testing always
|
|
207
|
+
* agrees with wherever `columnXFor()` actually painted that column.
|
|
208
|
+
*/
|
|
209
|
+
private weekAtX;
|
|
210
|
+
/**
|
|
211
|
+
* Locale-derived weekday-axis labels for the Sunday..Saturday rows drawn
|
|
212
|
+
* down the left of the calendar grid. Only indices 1 (Mon), 3 (Wed), and 5
|
|
213
|
+
* (Fri) are filled in — matching today's sparse every-other-day label
|
|
214
|
+
* density — the rest stay blank. `firstWeekStart` is always a UTC Sunday
|
|
215
|
+
* by construction (see `buildCalendarGrid()`), so weekday index 0..6 is a
|
|
216
|
+
* fixed Sun..Sat positional mapping; only the label *text* for indices
|
|
217
|
+
* 1/3/5 is derived here, via `Intl.DateTimeFormat` on a real UTC date that
|
|
218
|
+
* actually falls on that weekday, so it follows the runtime locale instead
|
|
219
|
+
* of a hardcoded English array (see `calendarCellText()`'s tooltip text
|
|
220
|
+
* for the same pattern).
|
|
221
|
+
*/
|
|
222
|
+
private weekdayLabels;
|
|
151
223
|
private drawCalendar;
|
|
152
224
|
/**
|
|
153
225
|
* Effective per-cell size in matrix mode — `fitToWidth` derives it from
|
|
@@ -185,16 +257,30 @@ export declare class LyraHeatmap extends LyraElement {
|
|
|
185
257
|
/**
|
|
186
258
|
* Human-readable "<label>: <value>" text for a cell — shared by the hover
|
|
187
259
|
* tooltip and the keyboard-focus live-region announcement, so both always
|
|
188
|
-
* describe a cell the same way.
|
|
260
|
+
* describe a cell the same way. This is the built-in English fallback used
|
|
261
|
+
* when `cellText` isn't set — see `resolveCellText()`.
|
|
189
262
|
*/
|
|
190
|
-
private
|
|
263
|
+
private defaultCellText;
|
|
191
264
|
private matrixCellText;
|
|
192
265
|
private calendarCellText;
|
|
266
|
+
/** The raw numeric value at a cell position, in either mode — shared by `resolveCellText()`
|
|
267
|
+
* so a custom `cellText` formatter receives the same value the built-in template would use. */
|
|
268
|
+
private valueAt;
|
|
269
|
+
/** Dispatches to the host-provided `cellText` formatter when set, otherwise the built-in template. */
|
|
270
|
+
private resolveCellText;
|
|
193
271
|
/** Refreshes the visually-hidden live-region text for a newly-focused cell. */
|
|
194
272
|
private announce;
|
|
195
273
|
private emitCellClick;
|
|
196
274
|
/** Inline `style` for `[part="tooltip"]`, centered above the given cell. */
|
|
197
275
|
private tooltipStyle;
|
|
276
|
+
/**
|
|
277
|
+
* Value-equality check for a `CellPos`, since `hoverCell` is assigned a
|
|
278
|
+
* fresh object literal on every `pointermove` — Lit's `@state()`
|
|
279
|
+
* change-detection only no-ops an identical *reference*, so without this,
|
|
280
|
+
* `{row,col} !== {row,col}` by reference triggers a full canvas clear +
|
|
281
|
+
* redraw on every pixel of pointer movement, even within the same cell.
|
|
282
|
+
*/
|
|
283
|
+
private samePos;
|
|
198
284
|
private onPointerMove;
|
|
199
285
|
private onPointerLeave;
|
|
200
286
|
private onCanvasClick;
|
|
@@ -167,10 +167,18 @@ export function mixColor(fromColor, toColor, t) {
|
|
|
167
167
|
* specific cells (e.g. to call out an anomaly), each one optionally
|
|
168
168
|
* surfaced in the legend too via `[part="legend-annotation"]`.
|
|
169
169
|
*
|
|
170
|
+
* `columnX` (calendar mode only) overrides the x-origin computed for each
|
|
171
|
+
* week column — drawing, hit-testing, the focus ring, and month-label
|
|
172
|
+
* positioning all consult it consistently, so a consumer can pixel-align a
|
|
173
|
+
* calendar's week columns with a sibling chart's coordinate system. Unset
|
|
174
|
+
* (the default) keeps the original evenly-spaced formula.
|
|
175
|
+
*
|
|
170
176
|
* @customElement lyra-heatmap
|
|
171
177
|
* @event lyra-cell-click - Fired on click, or Enter/Space on the
|
|
172
178
|
* focused/hovered cell. `detail: { row, col, value }` in matrix mode,
|
|
173
|
-
* `detail: { date, value }` in calendar mode.
|
|
179
|
+
* `detail: { date, value }` in calendar mode. `cellText` overrides the
|
|
180
|
+
* built-in English "Row X, Col Y: value" / "Mon DD: value" template used for
|
|
181
|
+
* both the hover tooltip and the keyboard live-region announcement.
|
|
174
182
|
* @csspart base, canvas, tooltip, live-region, legend, legend-lo, legend-hi, legend-annotation
|
|
175
183
|
*/
|
|
176
184
|
export class LyraHeatmap extends LyraElement {
|
|
@@ -205,21 +213,37 @@ export class LyraHeatmap extends LyraElement {
|
|
|
205
213
|
this.focusedCell = null;
|
|
206
214
|
/** Text of the visually-hidden `aria-live="polite"` status announcement, refreshed on every focus move. */
|
|
207
215
|
this.liveText = '';
|
|
216
|
+
/**
|
|
217
|
+
* Whether `role`/`aria-label` were present on the host *before* this
|
|
218
|
+
* element's own code ever wrote to them — snapshotted once, in the very
|
|
219
|
+
* first `willUpdate()` (guarded by `hasUpdated`, which Lit only flips to
|
|
220
|
+
* `true` after that first update commits). Re-checking `hasAttribute()` on
|
|
221
|
+
* every update instead would self-poison: once this element writes its own
|
|
222
|
+
* default `aria-label` on the first render, `hasAttribute('aria-label')`
|
|
223
|
+
* is permanently `true` afterwards, which would wrongly look like an
|
|
224
|
+
* author-supplied value and freeze the label instead of refreshing it as
|
|
225
|
+
* `values`/`days` change on later updates.
|
|
226
|
+
*/
|
|
227
|
+
this.authorSuppliedRole = false;
|
|
228
|
+
this.authorSuppliedAriaLabel = false;
|
|
208
229
|
this.onDprChange = () => {
|
|
209
230
|
this.watchDpr();
|
|
210
231
|
this.draw();
|
|
211
232
|
};
|
|
212
233
|
this.onPointerMove = (e) => {
|
|
213
|
-
|
|
234
|
+
const next = this.hitTest(e.offsetX, e.offsetY);
|
|
235
|
+
if (!this.samePos(this.hoverCell, next))
|
|
236
|
+
this.hoverCell = next;
|
|
214
237
|
};
|
|
215
238
|
this.onPointerLeave = () => {
|
|
216
239
|
this.hoverCell = null;
|
|
217
240
|
};
|
|
218
241
|
this.onCanvasClick = (e) => {
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
242
|
+
// Only the click's own hit-tested position counts — a click that lands
|
|
243
|
+
// outside the grid (e.g. on the canvas's own padding) is a genuine
|
|
244
|
+
// out-of-grid click and should do nothing, not fall back to whatever
|
|
245
|
+
// cell happens to still be keyboard-focused from an earlier interaction.
|
|
246
|
+
const pos = this.hitTest(e.offsetX, e.offsetY);
|
|
223
247
|
if (!pos)
|
|
224
248
|
return;
|
|
225
249
|
this.focusedCell = pos;
|
|
@@ -267,17 +291,31 @@ export class LyraHeatmap extends LyraElement {
|
|
|
267
291
|
this.hoverCell = null;
|
|
268
292
|
this.liveText = '';
|
|
269
293
|
}
|
|
294
|
+
if (!this.hasUpdated) {
|
|
295
|
+
this.authorSuppliedRole = this.hasAttribute('role');
|
|
296
|
+
this.authorSuppliedAriaLabel = this.hasAttribute('aria-label');
|
|
297
|
+
}
|
|
270
298
|
this.cachedValueRange = this.computeValueRange();
|
|
271
299
|
const bounds = this.cachedValueRange;
|
|
272
300
|
const range = bounds ? `${bounds[0]}–${bounds[1]}` : 'no data';
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
301
|
+
// Only default role/aria-label when the author hasn't already supplied
|
|
302
|
+
// one — the canvas is a genuinely focusable/interactive control
|
|
303
|
+
// (tabindex="0", click/keydown handlers) plus a live role="status"
|
|
304
|
+
// region, so unconditionally forcing a role here would tell assistive
|
|
305
|
+
// tech to flatten the whole subtree, hiding both from the accessibility
|
|
306
|
+
// tree; it would also silently overwrite any author-supplied
|
|
307
|
+
// role/aria-label on every update.
|
|
308
|
+
if (!this.authorSuppliedRole)
|
|
309
|
+
this.setAttribute('role', 'group');
|
|
310
|
+
if (!this.authorSuppliedAriaLabel) {
|
|
311
|
+
if (this.mode === 'calendar') {
|
|
312
|
+
this.setAttribute('aria-label', `Calendar heatmap of ${this.days.length} days, ${this.valueLabel} range ${range}`);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const rows = this.rowLabels.length;
|
|
316
|
+
const cols = this.colLabels.length;
|
|
317
|
+
this.setAttribute('aria-label', `Heatmap of ${rows} × ${cols} cells, ${this.valueLabel} range ${range}`);
|
|
318
|
+
}
|
|
281
319
|
}
|
|
282
320
|
}
|
|
283
321
|
/**
|
|
@@ -329,11 +367,66 @@ export class LyraHeatmap extends LyraElement {
|
|
|
329
367
|
else
|
|
330
368
|
this.drawMatrix();
|
|
331
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Calendar-mode week-column x-origin (canvas-local CSS px) — `columnX(week)`
|
|
372
|
+
* when set, otherwise the original evenly-spaced formula. Shared by every
|
|
373
|
+
* calendar-mode drawing and hit-testing call site (`drawCalendar()`,
|
|
374
|
+
* `hitTestCalendar()`/`weekAtX()`, `cellRect()`) so painted geometry and
|
|
375
|
+
* interactive hit-testing never disagree — mirrors the existing
|
|
376
|
+
* `matrixCellSize()` invariant for matrix mode's
|
|
377
|
+
* `drawMatrix()`/`hitTestMatrix()`/`cellRect()`.
|
|
378
|
+
*/
|
|
379
|
+
columnXFor(week) {
|
|
380
|
+
return this.columnX ? this.columnX(week) : CAL_PAD_LEFT + week * (CAL_CELL + CAL_GAP);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Inverse of `columnXFor()`: resolves an x position (canvas-local CSS px)
|
|
384
|
+
* to the week column it falls in, or `null` if it's outside every column
|
|
385
|
+
* (`weekCount` is 0, or `x` doesn't land inside `[0, weekCount)`'s span).
|
|
386
|
+
* The default spacing has a closed-form inverse (division); an arbitrary
|
|
387
|
+
* `columnX` override doesn't, so that case instead scans each column's
|
|
388
|
+
* `[columnXFor(week), columnXFor(week + 1))` span — algebraically the same
|
|
389
|
+
* span the default formula's division derives — so hit-testing always
|
|
390
|
+
* agrees with wherever `columnXFor()` actually painted that column.
|
|
391
|
+
*/
|
|
392
|
+
weekAtX(x, weekCount) {
|
|
393
|
+
if (!this.columnX) {
|
|
394
|
+
const week = Math.floor((x - CAL_PAD_LEFT) / (CAL_CELL + CAL_GAP));
|
|
395
|
+
return week >= 0 && week < weekCount ? week : null;
|
|
396
|
+
}
|
|
397
|
+
for (let week = 0; week < weekCount; week++) {
|
|
398
|
+
const start = this.columnXFor(week);
|
|
399
|
+
const end = week + 1 < weekCount ? this.columnXFor(week + 1) : start + (CAL_CELL + CAL_GAP);
|
|
400
|
+
if (x >= start && x < end)
|
|
401
|
+
return week;
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Locale-derived weekday-axis labels for the Sunday..Saturday rows drawn
|
|
407
|
+
* down the left of the calendar grid. Only indices 1 (Mon), 3 (Wed), and 5
|
|
408
|
+
* (Fri) are filled in — matching today's sparse every-other-day label
|
|
409
|
+
* density — the rest stay blank. `firstWeekStart` is always a UTC Sunday
|
|
410
|
+
* by construction (see `buildCalendarGrid()`), so weekday index 0..6 is a
|
|
411
|
+
* fixed Sun..Sat positional mapping; only the label *text* for indices
|
|
412
|
+
* 1/3/5 is derived here, via `Intl.DateTimeFormat` on a real UTC date that
|
|
413
|
+
* actually falls on that weekday, so it follows the runtime locale instead
|
|
414
|
+
* of a hardcoded English array (see `calendarCellText()`'s tooltip text
|
|
415
|
+
* for the same pattern).
|
|
416
|
+
*/
|
|
417
|
+
weekdayLabels(firstWeekStart) {
|
|
418
|
+
const formatter = new Intl.DateTimeFormat(undefined, { weekday: 'short', timeZone: 'UTC' });
|
|
419
|
+
const labels = ['', '', '', '', '', '', ''];
|
|
420
|
+
for (const weekday of [1, 3, 5]) {
|
|
421
|
+
labels[weekday] = formatter.format(new Date(firstWeekStart.getTime() + weekday * MS_PER_DAY));
|
|
422
|
+
}
|
|
423
|
+
return labels;
|
|
424
|
+
}
|
|
332
425
|
drawCalendar() {
|
|
333
426
|
if (!this.canvas)
|
|
334
427
|
return;
|
|
335
|
-
const { cells, weekCount, monthLabels } = buildCalendarGrid(this.days);
|
|
336
|
-
const w =
|
|
428
|
+
const { cells, weekCount, firstWeekStart, monthLabels } = buildCalendarGrid(this.days);
|
|
429
|
+
const w = this.columnXFor(Math.max(1, weekCount));
|
|
337
430
|
const h = CAL_LABEL_H + 7 * (CAL_CELL + CAL_GAP);
|
|
338
431
|
const dpr = window.devicePixelRatio || 1;
|
|
339
432
|
this.canvas.width = w * dpr;
|
|
@@ -356,7 +449,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
356
449
|
const ramp = Array.from({ length: buckets }, (_, i) => mixRgb(loRgb, hiRgb, i / (buckets - 1)));
|
|
357
450
|
const noDataFill = this.noDataFill();
|
|
358
451
|
for (const cell of cells) {
|
|
359
|
-
const x =
|
|
452
|
+
const x = this.columnXFor(cell.week);
|
|
360
453
|
const y = CAL_LABEL_H + cell.weekday * (CAL_CELL + CAL_GAP);
|
|
361
454
|
ctx.fillStyle =
|
|
362
455
|
cell.value < 0 || !Number.isFinite(cell.value)
|
|
@@ -376,7 +469,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
376
469
|
const match = cells.find((c) => c.date === ann.date);
|
|
377
470
|
if (!match)
|
|
378
471
|
continue;
|
|
379
|
-
const x =
|
|
472
|
+
const x = this.columnXFor(match.week);
|
|
380
473
|
const y = CAL_LABEL_H + match.weekday * (CAL_CELL + CAL_GAP);
|
|
381
474
|
ctx.strokeRect(x + 0.5, y + 0.5, CAL_CELL - 1, CAL_CELL - 1);
|
|
382
475
|
}
|
|
@@ -388,7 +481,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
388
481
|
if (week < weekCount && weekday < 7) {
|
|
389
482
|
ctx.lineWidth = RING_LINE_WIDTH;
|
|
390
483
|
ctx.strokeStyle = this.focusRingColor();
|
|
391
|
-
const x =
|
|
484
|
+
const x = this.columnXFor(week);
|
|
392
485
|
const y = CAL_LABEL_H + weekday * (CAL_CELL + CAL_GAP);
|
|
393
486
|
ctx.strokeRect(x + 0.5, y + 0.5, CAL_CELL - 1, CAL_CELL - 1);
|
|
394
487
|
}
|
|
@@ -396,9 +489,9 @@ export class LyraHeatmap extends LyraElement {
|
|
|
396
489
|
ctx.fillStyle = this.labelColor();
|
|
397
490
|
ctx.font = this.labelFont();
|
|
398
491
|
for (const m of monthLabels) {
|
|
399
|
-
ctx.fillText(m.label,
|
|
492
|
+
ctx.fillText(m.label, this.columnXFor(m.week), CAL_LABEL_H - 4);
|
|
400
493
|
}
|
|
401
|
-
const WEEKDAY_LABELS =
|
|
494
|
+
const WEEKDAY_LABELS = this.weekdayLabels(firstWeekStart);
|
|
402
495
|
WEEKDAY_LABELS.forEach((label, weekday) => {
|
|
403
496
|
if (label)
|
|
404
497
|
ctx.fillText(label, 2, CAL_LABEL_H + weekday * (CAL_CELL + CAL_GAP) + CAL_CELL - 1);
|
|
@@ -537,9 +630,9 @@ export class LyraHeatmap extends LyraElement {
|
|
|
537
630
|
const { weekCount } = buildCalendarGrid(this.days);
|
|
538
631
|
if (weekCount === 0)
|
|
539
632
|
return null;
|
|
540
|
-
const week =
|
|
633
|
+
const week = this.weekAtX(x, weekCount);
|
|
541
634
|
const weekday = Math.floor((y - CAL_LABEL_H) / (CAL_CELL + CAL_GAP));
|
|
542
|
-
if (week
|
|
635
|
+
if (week === null || weekday < 0 || weekday > 6)
|
|
543
636
|
return null;
|
|
544
637
|
return { week, weekday };
|
|
545
638
|
}
|
|
@@ -567,7 +660,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
567
660
|
cellRect(pos) {
|
|
568
661
|
if ('week' in pos) {
|
|
569
662
|
return {
|
|
570
|
-
x:
|
|
663
|
+
x: this.columnXFor(pos.week),
|
|
571
664
|
y: CAL_LABEL_H + pos.weekday * (CAL_CELL + CAL_GAP),
|
|
572
665
|
w: CAL_CELL,
|
|
573
666
|
h: CAL_CELL,
|
|
@@ -579,9 +672,10 @@ export class LyraHeatmap extends LyraElement {
|
|
|
579
672
|
/**
|
|
580
673
|
* Human-readable "<label>: <value>" text for a cell — shared by the hover
|
|
581
674
|
* tooltip and the keyboard-focus live-region announcement, so both always
|
|
582
|
-
* describe a cell the same way.
|
|
675
|
+
* describe a cell the same way. This is the built-in English fallback used
|
|
676
|
+
* when `cellText` isn't set — see `resolveCellText()`.
|
|
583
677
|
*/
|
|
584
|
-
|
|
678
|
+
defaultCellText(pos) {
|
|
585
679
|
return 'week' in pos ? this.calendarCellText(pos) : this.matrixCellText(pos);
|
|
586
680
|
}
|
|
587
681
|
matrixCellText(pos) {
|
|
@@ -593,13 +687,24 @@ export class LyraHeatmap extends LyraElement {
|
|
|
593
687
|
}
|
|
594
688
|
calendarCellText(pos) {
|
|
595
689
|
const { date, value } = this.calendarCellAt(pos);
|
|
596
|
-
const label = parseIsoDate(date).toLocaleString(
|
|
690
|
+
const label = parseIsoDate(date).toLocaleString(undefined, { month: 'short', day: 'numeric', timeZone: 'UTC' });
|
|
597
691
|
const valueText = value < 0 || !Number.isFinite(value) ? 'no data' : String(value);
|
|
598
692
|
return `${label}: ${valueText}`;
|
|
599
693
|
}
|
|
694
|
+
/** The raw numeric value at a cell position, in either mode — shared by `resolveCellText()`
|
|
695
|
+
* so a custom `cellText` formatter receives the same value the built-in template would use. */
|
|
696
|
+
valueAt(pos) {
|
|
697
|
+
if ('week' in pos)
|
|
698
|
+
return this.calendarCellAt(pos).value;
|
|
699
|
+
return this.values[pos.row]?.[pos.col] ?? -1;
|
|
700
|
+
}
|
|
701
|
+
/** Dispatches to the host-provided `cellText` formatter when set, otherwise the built-in template. */
|
|
702
|
+
resolveCellText(pos) {
|
|
703
|
+
return this.cellText ? this.cellText(pos, this.valueAt(pos)) : this.defaultCellText(pos);
|
|
704
|
+
}
|
|
600
705
|
/** Refreshes the visually-hidden live-region text for a newly-focused cell. */
|
|
601
706
|
announce(pos) {
|
|
602
|
-
this.liveText = this.
|
|
707
|
+
this.liveText = this.resolveCellText(pos);
|
|
603
708
|
}
|
|
604
709
|
emitCellClick(pos) {
|
|
605
710
|
if ('week' in pos) {
|
|
@@ -616,6 +721,24 @@ export class LyraHeatmap extends LyraElement {
|
|
|
616
721
|
const rect = this.cellRect(pos);
|
|
617
722
|
return { left: `${rect.x + rect.w / 2}px`, top: `${rect.y}px` };
|
|
618
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* Value-equality check for a `CellPos`, since `hoverCell` is assigned a
|
|
726
|
+
* fresh object literal on every `pointermove` — Lit's `@state()`
|
|
727
|
+
* change-detection only no-ops an identical *reference*, so without this,
|
|
728
|
+
* `{row,col} !== {row,col}` by reference triggers a full canvas clear +
|
|
729
|
+
* redraw on every pixel of pointer movement, even within the same cell.
|
|
730
|
+
*/
|
|
731
|
+
samePos(a, b) {
|
|
732
|
+
if (a === b)
|
|
733
|
+
return true;
|
|
734
|
+
if (!a || !b)
|
|
735
|
+
return false;
|
|
736
|
+
if ('week' in a && 'week' in b)
|
|
737
|
+
return a.week === b.week && a.weekday === b.weekday;
|
|
738
|
+
if ('row' in a && 'row' in b)
|
|
739
|
+
return a.row === b.row && a.col === b.col;
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
619
742
|
onMatrixKeyDown(e) {
|
|
620
743
|
const rows = this.rowLabels.length;
|
|
621
744
|
const cols = this.colLabels.length;
|
|
@@ -703,7 +826,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
703
826
|
?hidden=${!this.hoverCell}
|
|
704
827
|
style=${styleMap(this.hoverCell ? this.tooltipStyle(this.hoverCell) : {})}
|
|
705
828
|
>
|
|
706
|
-
${this.hoverCell ? this.
|
|
829
|
+
${this.hoverCell ? this.resolveCellText(this.hoverCell) : ''}
|
|
707
830
|
</div>
|
|
708
831
|
<div part="live-region" class="sr-only" role="status" aria-live="polite">${this.liveText}</div>
|
|
709
832
|
<div part="legend">
|
|
@@ -750,6 +873,12 @@ __decorate([
|
|
|
750
873
|
__decorate([
|
|
751
874
|
property({ attribute: false })
|
|
752
875
|
], LyraHeatmap.prototype, "annotations", void 0);
|
|
876
|
+
__decorate([
|
|
877
|
+
property({ attribute: false })
|
|
878
|
+
], LyraHeatmap.prototype, "cellText", void 0);
|
|
879
|
+
__decorate([
|
|
880
|
+
property({ attribute: false })
|
|
881
|
+
], LyraHeatmap.prototype, "columnX", void 0);
|
|
753
882
|
__decorate([
|
|
754
883
|
query('canvas')
|
|
755
884
|
], LyraHeatmap.prototype, "canvas", void 0);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
/**
|
|
4
|
+
* `<lyra-json-viewer>` — a collapsible, copyable tree view for an arbitrary
|
|
5
|
+
* JSON-serializable value (object/array/string/number/boolean/null/
|
|
6
|
+
* undefined). Serves as the fallback renderer wherever a raw payload (tool
|
|
7
|
+
* call arguments, a tool result, an API response) needs inspecting without a
|
|
8
|
+
* bespoke view.
|
|
9
|
+
*
|
|
10
|
+
* Expand/collapse state is keyed by structural path (not by object identity),
|
|
11
|
+
* so it survives a `data` reassignment that keeps the same shape -- e.g. a
|
|
12
|
+
* streaming tool result being patched in place.
|
|
13
|
+
*
|
|
14
|
+
* @customElement lyra-json-viewer
|
|
15
|
+
* @event lyra-copy - `detail: { text }` -- fired by the top-level copy button or a per-node one. Fires even when `navigator.clipboard` silently failed or is unavailable (insecure context, older browser, stubbed-out test environment), so a consumer can still observe copy *intent*.
|
|
16
|
+
* @csspart base - The root scroll container; respects `max-height`.
|
|
17
|
+
* @csspart toolbar - The wrapper around the top-level copy button (only rendered when `copyable`).
|
|
18
|
+
* @csspart tree - The wrapper around the rendered node tree.
|
|
19
|
+
* @csspart key - An object property key or array index label.
|
|
20
|
+
* @csspart value - A primitive value's text -- carries `data-type` (`string`/`number`/`boolean`/`null`/`undefined`) for per-type coloring, and `data-match` while it matches `search`.
|
|
21
|
+
* @csspart bracket - A `{`, `}`, `[`, or `]` delimiter.
|
|
22
|
+
* @csspart toggle - A container node's expand/collapse button (hidden, but present for row alignment, on leaf/empty nodes).
|
|
23
|
+
* @csspart copy-button - A copy-to-clipboard button -- the top-level one (in `toolbar`) or a per-node one (only rendered when `copyable`).
|
|
24
|
+
*/
|
|
25
|
+
export declare class LyraJsonViewer extends LyraElement {
|
|
26
|
+
static styles: import("lit").CSSResultGroup[];
|
|
27
|
+
/** The value to render. Any JSON-serializable value, plus `undefined`. */
|
|
28
|
+
data: unknown;
|
|
29
|
+
/** Nodes at or beyond this nesting depth (root = 0) start collapsed. Omit/undefined: nothing auto-collapses. */
|
|
30
|
+
collapsedDepth?: number;
|
|
31
|
+
/** A CSS length (e.g. `"20rem"`); once set, the viewer scrolls internally past this height instead of growing the page. */
|
|
32
|
+
maxHeight: string;
|
|
33
|
+
/** Shows copy-to-clipboard affordances: one for the whole value, plus one per node. */
|
|
34
|
+
copyable: boolean;
|
|
35
|
+
/** Case-insensitive substring match against keys/values; matches are highlighted and their ancestors auto-expanded. */
|
|
36
|
+
search: string;
|
|
37
|
+
/** Per-path (`JSON.stringify(path)`) explicit expand/collapse, overriding the `collapsedDepth`/search defaults once a node's toggle has been used. */
|
|
38
|
+
private expandedOverrides;
|
|
39
|
+
private isExpanded;
|
|
40
|
+
private toggleNode;
|
|
41
|
+
private writeClipboard;
|
|
42
|
+
private copy;
|
|
43
|
+
/** Builds the key/value-match sets and the ancestor-paths-of-a-match set that `search` drives. */
|
|
44
|
+
private computeSearch;
|
|
45
|
+
private renderCopyButton;
|
|
46
|
+
private renderNode;
|
|
47
|
+
render(): TemplateResult;
|
|
48
|
+
}
|
|
49
|
+
declare global {
|
|
50
|
+
interface HTMLElementTagNameMap {
|
|
51
|
+
'lyra-json-viewer': LyraJsonViewer;
|
|
52
|
+
}
|
|
53
|
+
}
|