@aceshooting/lyra-ui 1.2.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.
Files changed (384) hide show
  1. package/README.md +98 -14
  2. package/custom-elements.json +30279 -12124
  3. package/dist/components/app-rail/app-rail-item.d.ts +30 -0
  4. package/dist/components/app-rail/app-rail-item.js +68 -0
  5. package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
  6. package/dist/components/app-rail/app-rail-item.styles.js +65 -0
  7. package/dist/components/app-rail/app-rail.d.ts +168 -0
  8. package/dist/components/app-rail/app-rail.js +419 -0
  9. package/dist/components/app-rail/app-rail.styles.d.ts +1 -0
  10. package/dist/components/app-rail/app-rail.styles.js +128 -0
  11. package/dist/components/attachment-chip/attachment-chip.d.ts +141 -0
  12. package/dist/components/attachment-chip/attachment-chip.js +356 -0
  13. package/dist/components/attachment-chip/attachment-chip.styles.d.ts +1 -0
  14. package/dist/components/attachment-chip/attachment-chip.styles.js +184 -0
  15. package/dist/components/attachment-trigger/attachment-trigger.d.ts +96 -0
  16. package/dist/components/attachment-trigger/attachment-trigger.js +270 -0
  17. package/dist/components/attachment-trigger/attachment-trigger.styles.d.ts +1 -0
  18. package/dist/components/attachment-trigger/attachment-trigger.styles.js +90 -0
  19. package/dist/components/chart/bar-chart.js +2 -18
  20. package/dist/components/chart/box-plot.d.ts +25 -1
  21. package/dist/components/chart/box-plot.js +147 -7
  22. package/dist/components/chart/box-plot.styles.js +13 -0
  23. package/dist/components/chart/bubble-chart.js +2 -18
  24. package/dist/components/chart/chart-loader.d.ts +44 -13
  25. package/dist/components/chart/chart-loader.js +65 -18
  26. package/dist/components/chart/chart.d.ts +55 -12
  27. package/dist/components/chart/chart.js +247 -49
  28. package/dist/components/chart/doughnut-chart.js +2 -18
  29. package/dist/components/chart/histogram-bin.d.ts +7 -1
  30. package/dist/components/chart/histogram-bin.js +32 -14
  31. package/dist/components/chart/histogram.js +8 -16
  32. package/dist/components/chart/line-chart.js +2 -18
  33. package/dist/components/chart/lite-chart.d.ts +134 -3
  34. package/dist/components/chart/lite-chart.js +382 -67
  35. package/dist/components/chart/lite-chart.styles.js +9 -0
  36. package/dist/components/chart/pie-chart.js +2 -18
  37. package/dist/components/chart/polar-area-chart.js +2 -18
  38. package/dist/components/chart/radar-chart.js +2 -18
  39. package/dist/components/chart/scatter-chart.js +2 -18
  40. package/dist/components/chat-composer/chat-composer.d.ts +133 -0
  41. package/dist/components/chat-composer/chat-composer.js +366 -0
  42. package/dist/components/chat-composer/chat-composer.styles.d.ts +1 -0
  43. package/dist/components/chat-composer/chat-composer.styles.js +145 -0
  44. package/dist/components/chat-message/chat-message.d.ts +141 -0
  45. package/dist/components/chat-message/chat-message.js +331 -0
  46. package/dist/components/chat-message/chat-message.styles.d.ts +1 -0
  47. package/dist/components/chat-message/chat-message.styles.js +203 -0
  48. package/dist/components/checkbox/checkbox.d.ts +117 -0
  49. package/dist/components/checkbox/checkbox.js +353 -0
  50. package/dist/components/checkbox/checkbox.styles.d.ts +1 -0
  51. package/dist/components/checkbox/checkbox.styles.js +77 -0
  52. package/dist/components/chip/chip-group.d.ts +56 -0
  53. package/dist/components/chip/chip-group.js +130 -0
  54. package/dist/components/chip/chip-group.styles.d.ts +1 -0
  55. package/dist/components/chip/chip-group.styles.js +57 -0
  56. package/dist/components/chip/chip.d.ts +64 -0
  57. package/dist/components/chip/chip.js +122 -0
  58. package/dist/components/chip/chip.styles.d.ts +1 -0
  59. package/dist/components/chip/chip.styles.js +115 -0
  60. package/dist/components/citation-badge/citation-badge.d.ts +113 -0
  61. package/dist/components/citation-badge/citation-badge.js +318 -0
  62. package/dist/components/citation-badge/citation-badge.styles.d.ts +1 -0
  63. package/dist/components/citation-badge/citation-badge.styles.js +96 -0
  64. package/dist/components/code-block/code-block.d.ts +127 -0
  65. package/dist/components/code-block/code-block.js +382 -0
  66. package/dist/components/code-block/code-block.styles.d.ts +1 -0
  67. package/dist/components/code-block/code-block.styles.js +187 -0
  68. package/dist/components/code-block/code-loader.d.ts +87 -0
  69. package/dist/components/code-block/code-loader.js +142 -0
  70. package/dist/components/combobox/combobox.d.ts +65 -6
  71. package/dist/components/combobox/combobox.js +258 -48
  72. package/dist/components/combobox/combobox.styles.js +3 -3
  73. package/dist/components/combobox/option.d.ts +5 -1
  74. package/dist/components/combobox/option.js +35 -0
  75. package/dist/components/context-meter/context-meter.d.ts +53 -0
  76. package/dist/components/context-meter/context-meter.js +159 -0
  77. package/dist/components/context-meter/context-meter.styles.d.ts +1 -0
  78. package/dist/components/context-meter/context-meter.styles.js +114 -0
  79. package/dist/components/conversation-item/conversation-item.d.ts +125 -0
  80. package/dist/components/conversation-item/conversation-item.js +331 -0
  81. package/dist/components/conversation-item/conversation-item.styles.d.ts +1 -0
  82. package/dist/components/conversation-item/conversation-item.styles.js +155 -0
  83. package/dist/components/date-picker/calendar-core.d.ts +24 -0
  84. package/dist/components/date-picker/calendar-core.js +51 -4
  85. package/dist/components/date-picker/date-input.d.ts +100 -9
  86. package/dist/components/date-picker/date-input.js +383 -66
  87. package/dist/components/date-picker/date-picker.d.ts +35 -4
  88. package/dist/components/date-picker/date-picker.js +171 -44
  89. package/dist/components/dialog/confirm.d.ts +40 -0
  90. package/dist/components/dialog/confirm.js +76 -0
  91. package/dist/components/dialog/dialog.d.ts +129 -0
  92. package/dist/components/dialog/dialog.js +288 -0
  93. package/dist/components/dialog/dialog.styles.d.ts +1 -0
  94. package/dist/components/dialog/dialog.styles.js +96 -0
  95. package/dist/components/dock-panel/dock-panel.d.ts +142 -0
  96. package/dist/components/dock-panel/dock-panel.js +359 -0
  97. package/dist/components/dock-panel/dock-panel.styles.d.ts +1 -0
  98. package/dist/components/dock-panel/dock-panel.styles.js +159 -0
  99. package/dist/components/document-preview/document-preview.d.ts +143 -0
  100. package/dist/components/document-preview/document-preview.js +388 -0
  101. package/dist/components/document-preview/document-preview.styles.d.ts +1 -0
  102. package/dist/components/document-preview/document-preview.styles.js +191 -0
  103. package/dist/components/empty/empty.d.ts +1 -2
  104. package/dist/components/empty/empty.js +18 -12
  105. package/dist/components/empty/empty.styles.js +0 -3
  106. package/dist/components/export-button/csv.js +1 -1
  107. package/dist/components/export-button/export-button.d.ts +12 -0
  108. package/dist/components/export-button/export-button.js +35 -5
  109. package/dist/components/export-button/export-button.styles.js +5 -1
  110. package/dist/components/file-input/accept.js +2 -0
  111. package/dist/components/file-input/file-input.d.ts +10 -1
  112. package/dist/components/file-input/file-input.js +36 -5
  113. package/dist/components/flag/flag.d.ts +29 -13
  114. package/dist/components/flag/flag.js +54 -14
  115. package/dist/components/flag/flag.styles.js +1 -1
  116. package/dist/components/flag/language-map.d.ts +4 -1
  117. package/dist/components/flag/language-map.js +6 -3
  118. package/dist/components/gauge/gauge.d.ts +10 -1
  119. package/dist/components/gauge/gauge.js +41 -13
  120. package/dist/components/generation-status/generation-status.d.ts +117 -0
  121. package/dist/components/generation-status/generation-status.js +327 -0
  122. package/dist/components/generation-status/generation-status.styles.d.ts +1 -0
  123. package/dist/components/generation-status/generation-status.styles.js +84 -0
  124. package/dist/components/graph/graph.d.ts +37 -3
  125. package/dist/components/graph/graph.js +132 -20
  126. package/dist/components/heatmap/calendar-grid.d.ts +19 -7
  127. package/dist/components/heatmap/calendar-grid.js +42 -18
  128. package/dist/components/heatmap/heatmap-scale.d.ts +11 -1
  129. package/dist/components/heatmap/heatmap-scale.js +14 -2
  130. package/dist/components/heatmap/heatmap.d.ts +227 -12
  131. package/dist/components/heatmap/heatmap.js +488 -93
  132. package/dist/components/json-viewer/json-viewer.d.ts +70 -0
  133. package/dist/components/json-viewer/json-viewer.js +361 -0
  134. package/dist/components/json-viewer/json-viewer.styles.d.ts +1 -0
  135. package/dist/components/json-viewer/json-viewer.styles.js +177 -0
  136. package/dist/components/kbd/kbd.d.ts +77 -0
  137. package/dist/components/kbd/kbd.js +231 -0
  138. package/dist/components/kbd/kbd.styles.d.ts +1 -0
  139. package/dist/components/kbd/kbd.styles.js +52 -0
  140. package/dist/components/live-region/live-region.d.ts +90 -0
  141. package/dist/components/live-region/live-region.js +189 -0
  142. package/dist/components/live-region/live-region.styles.d.ts +1 -0
  143. package/dist/components/live-region/live-region.styles.js +10 -0
  144. package/dist/components/map/map.d.ts +32 -7
  145. package/dist/components/map/map.js +133 -32
  146. package/dist/components/markdown/markdown-loader.d.ts +58 -0
  147. package/dist/components/markdown/markdown-loader.js +77 -0
  148. package/dist/components/markdown/markdown.d.ts +119 -0
  149. package/dist/components/markdown/markdown.js +389 -0
  150. package/dist/components/markdown/markdown.styles.d.ts +1 -0
  151. package/dist/components/markdown/markdown.styles.js +88 -0
  152. package/dist/components/media-card/media-card.d.ts +130 -0
  153. package/dist/components/media-card/media-card.js +273 -0
  154. package/dist/components/media-card/media-card.styles.d.ts +1 -0
  155. package/dist/components/media-card/media-card.styles.js +132 -0
  156. package/dist/components/mention-popover/mention-popover.d.ts +203 -0
  157. package/dist/components/mention-popover/mention-popover.js +493 -0
  158. package/dist/components/mention-popover/mention-popover.styles.d.ts +1 -0
  159. package/dist/components/mention-popover/mention-popover.styles.js +101 -0
  160. package/dist/components/menu/menu-item.d.ts +91 -0
  161. package/dist/components/menu/menu-item.js +206 -0
  162. package/dist/components/menu/menu-item.styles.d.ts +1 -0
  163. package/dist/components/menu/menu-item.styles.js +75 -0
  164. package/dist/components/menu/menu.d.ts +167 -0
  165. package/dist/components/menu/menu.js +461 -0
  166. package/dist/components/menu/menu.styles.d.ts +1 -0
  167. package/dist/components/menu/menu.styles.js +67 -0
  168. package/dist/components/model-select/model-select.d.ts +170 -0
  169. package/dist/components/model-select/model-select.js +588 -0
  170. package/dist/components/model-select/model-select.styles.d.ts +1 -0
  171. package/dist/components/model-select/model-select.styles.js +203 -0
  172. package/dist/components/model-settings-panel/model-settings-panel.d.ts +93 -0
  173. package/dist/components/model-settings-panel/model-settings-panel.js +209 -0
  174. package/dist/components/model-settings-panel/model-settings-panel.styles.d.ts +1 -0
  175. package/dist/components/model-settings-panel/model-settings-panel.styles.js +82 -0
  176. package/dist/components/playback/playback.d.ts +19 -3
  177. package/dist/components/playback/playback.js +109 -33
  178. package/dist/components/playback/playback.styles.js +6 -1
  179. package/dist/components/responsive-panel/responsive-panel.d.ts +163 -0
  180. package/dist/components/responsive-panel/responsive-panel.js +363 -0
  181. package/dist/components/responsive-panel/responsive-panel.styles.d.ts +1 -0
  182. package/dist/components/responsive-panel/responsive-panel.styles.js +103 -0
  183. package/dist/components/result-card/result-card.d.ts +50 -0
  184. package/dist/components/result-card/result-card.js +89 -0
  185. package/dist/components/result-card/result-card.styles.d.ts +1 -0
  186. package/dist/components/result-card/result-card.styles.js +49 -0
  187. package/dist/components/result-card/result-field.d.ts +36 -0
  188. package/dist/components/result-card/result-field.js +87 -0
  189. package/dist/components/result-card/result-field.styles.d.ts +1 -0
  190. package/dist/components/result-card/result-field.styles.js +32 -0
  191. package/dist/components/select/select.d.ts +84 -4
  192. package/dist/components/select/select.js +251 -36
  193. package/dist/components/skeleton/skeleton.d.ts +3 -0
  194. package/dist/components/skeleton/skeleton.js +7 -1
  195. package/dist/components/skeleton/skeleton.styles.js +5 -4
  196. package/dist/components/slider/slider.d.ts +141 -0
  197. package/dist/components/slider/slider.js +467 -0
  198. package/dist/components/slider/slider.styles.d.ts +1 -0
  199. package/dist/components/slider/slider.styles.js +87 -0
  200. package/dist/components/source-card/source-card.d.ts +104 -0
  201. package/dist/components/source-card/source-card.js +195 -0
  202. package/dist/components/source-card/source-card.styles.d.ts +1 -0
  203. package/dist/components/source-card/source-card.styles.js +69 -0
  204. package/dist/components/source-list/source-list.d.ts +63 -0
  205. package/dist/components/source-list/source-list.js +130 -0
  206. package/dist/components/source-list/source-list.styles.d.ts +1 -0
  207. package/dist/components/source-list/source-list.styles.js +59 -0
  208. package/dist/components/sparkline/sparkline.js +58 -20
  209. package/dist/components/split/split.d.ts +224 -5
  210. package/dist/components/split/split.js +617 -43
  211. package/dist/components/split/split.styles.js +47 -0
  212. package/dist/components/stat/stat.d.ts +15 -4
  213. package/dist/components/stat/stat.js +53 -12
  214. package/dist/components/stream-status/stream-status.d.ts +126 -0
  215. package/dist/components/stream-status/stream-status.js +292 -0
  216. package/dist/components/stream-status/stream-status.styles.d.ts +1 -0
  217. package/dist/components/stream-status/stream-status.styles.js +103 -0
  218. package/dist/components/streaming-text/streaming-text.d.ts +104 -0
  219. package/dist/components/streaming-text/streaming-text.js +224 -0
  220. package/dist/components/streaming-text/streaming-text.styles.d.ts +1 -0
  221. package/dist/components/streaming-text/streaming-text.styles.js +63 -0
  222. package/dist/components/switch/switch.d.ts +103 -0
  223. package/dist/components/switch/switch.js +250 -0
  224. package/dist/components/switch/switch.styles.d.ts +1 -0
  225. package/dist/components/switch/switch.styles.js +75 -0
  226. package/dist/components/table/table.d.ts +89 -5
  227. package/dist/components/table/table.js +243 -26
  228. package/dist/components/table/table.styles.js +6 -1
  229. package/dist/components/tabs/tabs.d.ts +74 -0
  230. package/dist/components/tabs/tabs.js +254 -0
  231. package/dist/components/tabs/tabs.styles.d.ts +1 -0
  232. package/dist/components/tabs/tabs.styles.js +74 -0
  233. package/dist/components/thinking-panel/thinking-panel.d.ts +123 -0
  234. package/dist/components/thinking-panel/thinking-panel.js +260 -0
  235. package/dist/components/thinking-panel/thinking-panel.styles.d.ts +1 -0
  236. package/dist/components/thinking-panel/thinking-panel.styles.js +123 -0
  237. package/dist/components/time-range/time-range.d.ts +59 -2
  238. package/dist/components/time-range/time-range.js +163 -67
  239. package/dist/components/toast/toast-item.d.ts +12 -2
  240. package/dist/components/toast/toast-item.js +106 -44
  241. package/dist/components/toast/toast-item.styles.js +59 -28
  242. package/dist/components/toast/toast.js +17 -5
  243. package/dist/components/toast/toaster.js +10 -1
  244. package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +131 -0
  245. package/dist/components/tool-approval-dialog/tool-approval-dialog.js +329 -0
  246. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.d.ts +1 -0
  247. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +160 -0
  248. package/dist/components/tool-call-chip/tool-call-chip.d.ts +105 -0
  249. package/dist/components/tool-call-chip/tool-call-chip.js +349 -0
  250. package/dist/components/tool-call-chip/tool-call-chip.styles.d.ts +1 -0
  251. package/dist/components/tool-call-chip/tool-call-chip.styles.js +203 -0
  252. package/dist/components/tool-param-form/tool-param-form.d.ts +206 -0
  253. package/dist/components/tool-param-form/tool-param-form.js +610 -0
  254. package/dist/components/tool-param-form/tool-param-form.styles.d.ts +1 -0
  255. package/dist/components/tool-param-form/tool-param-form.styles.js +75 -0
  256. package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +122 -0
  257. package/dist/components/tool-result-dialog/tool-result-dialog.js +358 -0
  258. package/dist/components/tool-result-dialog/tool-result-dialog.styles.d.ts +1 -0
  259. package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +176 -0
  260. package/dist/components/tool-result-view/registry.d.ts +73 -0
  261. package/dist/components/tool-result-view/registry.js +89 -0
  262. package/dist/components/tool-result-view/tool-result-view.d.ts +58 -0
  263. package/dist/components/tool-result-view/tool-result-view.js +152 -0
  264. package/dist/components/tool-result-view/tool-result-view.styles.d.ts +1 -0
  265. package/dist/components/tool-result-view/tool-result-view.styles.js +13 -0
  266. package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +151 -0
  267. package/dist/components/tool-select-dialog/tool-select-dialog.js +350 -0
  268. package/dist/components/tool-select-dialog/tool-select-dialog.styles.d.ts +1 -0
  269. package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +191 -0
  270. package/dist/components/tree/tree-node.d.ts +15 -3
  271. package/dist/components/tree/tree-node.js +20 -7
  272. package/dist/components/tree/tree.d.ts +25 -10
  273. package/dist/components/tree/tree.js +39 -31
  274. package/dist/components/typing-indicator/typing-indicator.d.ts +70 -0
  275. package/dist/components/typing-indicator/typing-indicator.js +108 -0
  276. package/dist/components/typing-indicator/typing-indicator.styles.d.ts +1 -0
  277. package/dist/components/typing-indicator/typing-indicator.styles.js +132 -0
  278. package/dist/components/virtual-list/virtual-list.d.ts +210 -0
  279. package/dist/components/virtual-list/virtual-list.js +607 -0
  280. package/dist/components/virtual-list/virtual-list.styles.d.ts +1 -0
  281. package/dist/components/virtual-list/virtual-list.styles.js +56 -0
  282. package/dist/components/widget/widget.d.ts +5 -4
  283. package/dist/components/widget/widget.js +54 -120
  284. package/dist/components/widget/widget.styles.js +8 -2
  285. package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
  286. package/dist/components/word-cloud/word-cloud-layout.js +29 -2
  287. package/dist/components/word-cloud/word-cloud.d.ts +28 -2
  288. package/dist/components/word-cloud/word-cloud.js +69 -19
  289. package/dist/components/word-cloud/word-cloud.styles.js +16 -3
  290. package/dist/internal/anchored-validity.d.ts +1 -0
  291. package/dist/internal/anchored-validity.js +86 -0
  292. package/dist/internal/announcer.d.ts +57 -0
  293. package/dist/internal/announcer.js +71 -0
  294. package/dist/internal/form-associated.d.ts +8 -0
  295. package/dist/internal/form-associated.js +98 -15
  296. package/dist/internal/group-by-recency.d.ts +50 -0
  297. package/dist/internal/group-by-recency.js +80 -0
  298. package/dist/internal/numbers.d.ts +12 -0
  299. package/dist/internal/numbers.js +26 -0
  300. package/dist/internal/overlay-manager.d.ts +51 -0
  301. package/dist/internal/overlay-manager.js +523 -0
  302. package/dist/internal/positioner.js +27 -3
  303. package/dist/internal/rtl.d.ts +23 -0
  304. package/dist/internal/rtl.js +28 -0
  305. package/dist/internal/safe-url.d.ts +12 -0
  306. package/dist/internal/safe-url.js +41 -0
  307. package/dist/internal/scroll-lock.d.ts +10 -5
  308. package/dist/internal/scroll-lock.js +30 -14
  309. package/dist/internal/tokens.styles.js +22 -10
  310. package/dist/lyra.d.ts +133 -31
  311. package/dist/lyra.js +96 -26
  312. package/llms-full.txt +4546 -183
  313. package/llms.txt +21 -5
  314. package/package.json +41 -6
  315. package/dist/components/chart/bar-chart.stories.d.ts +0 -5
  316. package/dist/components/chart/bar-chart.stories.js +0 -20
  317. package/dist/components/chart/box-plot.stories.d.ts +0 -5
  318. package/dist/components/chart/box-plot.stories.js +0 -29
  319. package/dist/components/chart/bubble-chart.stories.d.ts +0 -5
  320. package/dist/components/chart/bubble-chart.stories.js +0 -26
  321. package/dist/components/chart/chart.stories.d.ts +0 -32
  322. package/dist/components/chart/chart.stories.js +0 -141
  323. package/dist/components/chart/doughnut-chart.stories.d.ts +0 -5
  324. package/dist/components/chart/doughnut-chart.stories.js +0 -26
  325. package/dist/components/chart/histogram.stories.d.ts +0 -5
  326. package/dist/components/chart/histogram.stories.js +0 -17
  327. package/dist/components/chart/line-chart.stories.d.ts +0 -22
  328. package/dist/components/chart/line-chart.stories.js +0 -99
  329. package/dist/components/chart/lite-chart.stories.d.ts +0 -14
  330. package/dist/components/chart/lite-chart.stories.js +0 -114
  331. package/dist/components/chart/pie-chart.stories.d.ts +0 -5
  332. package/dist/components/chart/pie-chart.stories.js +0 -26
  333. package/dist/components/chart/polar-area-chart.stories.d.ts +0 -5
  334. package/dist/components/chart/polar-area-chart.stories.js +0 -20
  335. package/dist/components/chart/radar-chart.stories.d.ts +0 -5
  336. package/dist/components/chart/radar-chart.stories.js +0 -20
  337. package/dist/components/chart/scatter-chart.stories.d.ts +0 -5
  338. package/dist/components/chart/scatter-chart.stories.js +0 -31
  339. package/dist/components/combobox/combobox.stories.d.ts +0 -24
  340. package/dist/components/combobox/combobox.stories.js +0 -102
  341. package/dist/components/date-picker/date-input.stories.d.ts +0 -10
  342. package/dist/components/date-picker/date-input.stories.js +0 -48
  343. package/dist/components/date-picker/date-picker.stories.d.ts +0 -10
  344. package/dist/components/date-picker/date-picker.stories.js +0 -25
  345. package/dist/components/empty/empty.stories.d.ts +0 -7
  346. package/dist/components/empty/empty.stories.js +0 -34
  347. package/dist/components/export-button/export-button.stories.d.ts +0 -7
  348. package/dist/components/export-button/export-button.stories.js +0 -37
  349. package/dist/components/file-input/file-input.stories.d.ts +0 -7
  350. package/dist/components/file-input/file-input.stories.js +0 -19
  351. package/dist/components/flag/flag.stories.d.ts +0 -6
  352. package/dist/components/flag/flag.stories.js +0 -31
  353. package/dist/components/gauge/gauge.stories.d.ts +0 -8
  354. package/dist/components/gauge/gauge.stories.js +0 -19
  355. package/dist/components/graph/graph.stories.d.ts +0 -8
  356. package/dist/components/graph/graph.stories.js +0 -68
  357. package/dist/components/heatmap/heatmap.stories.d.ts +0 -27
  358. package/dist/components/heatmap/heatmap.stories.js +0 -179
  359. package/dist/components/map/map.stories.d.ts +0 -15
  360. package/dist/components/map/map.stories.js +0 -135
  361. package/dist/components/playback/playback.stories.d.ts +0 -7
  362. package/dist/components/playback/playback.stories.js +0 -16
  363. package/dist/components/select/select.stories.d.ts +0 -21
  364. package/dist/components/select/select.stories.js +0 -91
  365. package/dist/components/skeleton/skeleton.stories.d.ts +0 -5
  366. package/dist/components/skeleton/skeleton.stories.js +0 -16
  367. package/dist/components/sparkline/sparkline.stories.d.ts +0 -11
  368. package/dist/components/sparkline/sparkline.stories.js +0 -39
  369. package/dist/components/split/split.stories.d.ts +0 -7
  370. package/dist/components/split/split.stories.js +0 -36
  371. package/dist/components/stat/stat.stories.d.ts +0 -9
  372. package/dist/components/stat/stat.stories.js +0 -81
  373. package/dist/components/table/table.stories.d.ts +0 -12
  374. package/dist/components/table/table.stories.js +0 -67
  375. package/dist/components/time-range/time-range.stories.d.ts +0 -8
  376. package/dist/components/time-range/time-range.stories.js +0 -26
  377. package/dist/components/toast/toast.stories.d.ts +0 -8
  378. package/dist/components/toast/toast.stories.js +0 -72
  379. package/dist/components/tree/tree.stories.d.ts +0 -9
  380. package/dist/components/tree/tree.stories.js +0 -52
  381. package/dist/components/widget/widget.stories.d.ts +0 -9
  382. package/dist/components/widget/widget.stories.js +0 -74
  383. package/dist/components/word-cloud/word-cloud.stories.d.ts +0 -10
  384. package/dist/components/word-cloud/word-cloud.stories.js +0 -55
@@ -22,9 +22,22 @@ const FALLBACK_SCALE_HI = '#0969da';
22
22
  const FALLBACK_LABEL_FONT = '10px sans-serif';
23
23
  const CAL_PAD_LEFT = 28;
24
24
  const CAL_LABEL_H = 16;
25
+ /** Calendar mode's original fixed cell size — now also the effective fallback
26
+ * the `cellSize` accessor uses in calendar mode when left unset (see its
27
+ * doc comment), so a consumer who never touches `cellSize` sees no change. */
25
28
  const CAL_CELL = 11;
26
29
  const CAL_GAP = 2;
30
+ /** Matrix mode's original fixed cell size — the effective fallback the
31
+ * `cellSize` accessor uses in matrix mode when left unset. */
32
+ const DEFAULT_MATRIX_CELL_SIZE = 22;
27
33
  const DEFAULT_BUCKET_COUNT = 5;
34
+ /**
35
+ * A linear RGB ramp cannot contain more than 256 visually distinct integer
36
+ * steps on any channel. Keeping the bucket count within that bound avoids an
37
+ * untrusted attribute/property value turning the ramp into an unbounded
38
+ * allocation without discarding any useful color resolution.
39
+ */
40
+ export const MAX_BUCKET_COUNT = 256;
28
41
  /** Ring stroke width for both the annotation overlay and the keyboard focus ring. */
29
42
  const RING_LINE_WIDTH = 2;
30
43
  const FALLBACK_FOCUS_RING_COLOR = '#0969da';
@@ -66,30 +79,26 @@ function warnInvalidColor(color) {
66
79
  console.warn(`<lyra-heatmap> could not parse "${color}" (set via --lyra-heatmap-scale-lo/-hi) as a CSS ` +
67
80
  'color; falling back to the default ramp endpoint.');
68
81
  }
69
- const warnedInvalidBucketCounts = new Set();
70
- function warnInvalidBucketCount(value) {
71
- if (warnedInvalidBucketCounts.has(value))
72
- return;
73
- warnedInvalidBucketCounts.add(value);
74
- console.warn(`<lyra-heatmap> received a non-finite bucket-count (${value}); falling back to ${DEFAULT_BUCKET_COUNT}.`);
75
- }
76
82
  /**
77
- * Normalizes `bucketCount` to a finite integer >= 2. `Math.max(2, bucketCount)`
78
- * alone stays `NaN` for a non-numeric `bucket-count` attribute (`Math.max`
79
- * propagates `NaN`), which silently zeroes out the ramp array and makes every
80
- * cell keep whatever `fillStyle` the previous draw left. A fractional count
81
- * is floored rather than passed through as-is, so the ramp array's length
82
- * (built via `Array.from({ length: buckets }, ...)`, which truncates a
83
- * fractional `length`) always agrees with the index `quartileBucket` computes
84
- * from the same `buckets` value.
83
+ * Normalizes a bucket count to the safe, renderable range. Non-finite values
84
+ * restore the public default; finite values are floored and clamped to
85
+ * `[2, MAX_BUCKET_COUNT]`. Flooring keeps the ramp array's length in exact
86
+ * agreement with the count used by `quartileBucket()`.
85
87
  */
86
- function resolveBucketCount(bucketCount) {
88
+ export function normalizeBucketCount(bucketCount) {
87
89
  if (!Number.isFinite(bucketCount)) {
88
- warnInvalidBucketCount(bucketCount);
89
90
  return DEFAULT_BUCKET_COUNT;
90
91
  }
91
- return Math.max(2, Math.floor(bucketCount));
92
+ return Math.min(MAX_BUCKET_COUNT, Math.max(2, Math.floor(bucketCount)));
92
93
  }
94
+ const bucketCountConverter = {
95
+ fromAttribute(value) {
96
+ return value === null ? DEFAULT_BUCKET_COUNT : normalizeBucketCount(Number(value));
97
+ },
98
+ toAttribute(value) {
99
+ return String(normalizeBucketCount(value));
100
+ },
101
+ };
93
102
  /**
94
103
  * Resolves any syntactically valid CSS `<color>` — hex, `rgb()`, `hsl()`,
95
104
  * `oklch()`, a named color, etc. — to an `[r, g, b]` triple.
@@ -142,12 +151,16 @@ export function mixColor(fromColor, toColor, t) {
142
151
  * - `"matrix"` (default): a `rowLabels` x `colLabels` grid of `values`. `-1`
143
152
  * (or any non-finite value) is treated as "no data". `scale="sqrt"`
144
153
  * compresses the ramp via `sqrtStep()` so one heavy cell doesn't wash out
145
- * the rest; the default `"linear"` scale maps linearly instead. `scale`
146
- * only affects matrix mode — calendar mode always buckets by quartile.
147
- * - `"calendar"`: a GitHub-style Sunday-Saturday x week grid built from
148
- * `days`, colored by `quartileBucket()` into `bucketCount` buckets. As in
149
- * matrix mode, a cell whose `value` is negative or non-finite is treated
150
- * as "no data" rather than being bucketed.
154
+ * the rest; the default `"linear"` scale maps linearly instead.
155
+ * - `"calendar"`: a GitHub-style Sunday-Saturday (or `firstDayOfWeek`-anchored
156
+ * see below) x week grid built from `days`. `scale` governs its
157
+ * bucketing too: the default `"linear"` buckets by `quartileBucket()`
158
+ * (today's original behavior, unchanged); `"sqrt"` instead compresses via
159
+ * the same `sqrtStep()` magnitude compression matrix mode uses, so one
160
+ * heavy day doesn't wash out the rest. As in matrix mode, a cell whose
161
+ * `value` is negative or non-finite is treated as "no data" rather than
162
+ * being bucketed — as is a grid position with no matching entry in `days`
163
+ * at all (a gap in a sparse calendar).
151
164
  *
152
165
  * The sequential color ramp's endpoints are read from the
153
166
  * `--lyra-heatmap-scale-lo`/`-hi` custom properties (declared in
@@ -167,13 +180,40 @@ export function mixColor(fromColor, toColor, t) {
167
180
  * specific cells (e.g. to call out an anomaly), each one optionally
168
181
  * surfaced in the legend too via `[part="legend-annotation"]`.
169
182
  *
183
+ * `columnX` (calendar mode only) overrides the x-origin computed for each
184
+ * week column — drawing, hit-testing, the focus ring, and month-label
185
+ * positioning all consult it consistently, so a consumer can pixel-align a
186
+ * calendar's week columns with a sibling chart's coordinate system. Unset
187
+ * (the default) keeps the original evenly-spaced formula. `rowY` is its
188
+ * calendar-mode vertical analogue — overrides the y-origin computed for each
189
+ * weekday row, consulted consistently by drawing, hit-testing, and the focus
190
+ * ring via the private `rowYFor()` helper (mirroring `columnXFor()` exactly).
191
+ *
192
+ * `firstDayOfWeek` (calendar mode only, default `0`/Sunday, no-op in matrix
193
+ * mode) anchors the calendar grid at a different weekday — `0`-`6`, same
194
+ * numbering as `CalendarCellPos.weekday` (`0` Sunday .. `6` Saturday) —
195
+ * threaded into `buildCalendarGrid()`.
196
+ *
197
+ * `cellSize`/`fitToWidth` (previously matrix-mode only) also drive calendar
198
+ * mode's per-cell size: unset, calendar mode keeps today's original 11px
199
+ * cell size unchanged; explicitly set, the same fixed size (or, with
200
+ * `fitToWidth`, the same host-width-derived size matrix mode already
201
+ * supports) governs calendar mode's grid too.
202
+ *
170
203
  * @customElement lyra-heatmap
171
204
  * @event lyra-cell-click - Fired on click, or Enter/Space on the
172
205
  * focused/hovered cell. `detail: { row, col, value }` in matrix mode,
173
206
  * `detail: { date, value }` in calendar mode. `cellText` overrides the
174
207
  * built-in English "Row X, Col Y: value" / "Mon DD: value" template used for
175
208
  * both the hover tooltip and the keyboard live-region announcement.
176
- * @csspart base, canvas, tooltip, live-region, legend, legend-lo, legend-hi, legend-annotation
209
+ * @csspart base - The heatmap wrapper.
210
+ * @csspart canvas - The heatmap canvas.
211
+ * @csspart tooltip - The hover tooltip.
212
+ * @csspart live-region - The visually hidden keyboard announcement region.
213
+ * @csspart legend - The color legend.
214
+ * @csspart legend-lo - The low legend endpoint.
215
+ * @csspart legend-hi - The high legend endpoint.
216
+ * @csspart legend-annotation - An annotation label.
177
217
  */
178
218
  export class LyraHeatmap extends LyraElement {
179
219
  constructor() {
@@ -181,25 +221,57 @@ export class LyraHeatmap extends LyraElement {
181
221
  this.rowLabels = [];
182
222
  this.colLabels = [];
183
223
  this.values = [];
184
- this.cellSize = 22;
185
224
  this.valueLabel = 'value';
225
+ /**
226
+ * `"linear"` (default) maps values linearly to the color ramp in matrix
227
+ * mode, and buckets calendar-mode values via `quartileBucket()` — both
228
+ * unchanged from before this property governed calendar mode too.
229
+ * `"sqrt"` compresses via `sqrtStep()`'s square-root magnitude compression
230
+ * instead, in *both* modes, so one heavy cell/day doesn't wash out the
231
+ * rest of a skewed dataset.
232
+ */
186
233
  this.scale = 'linear';
187
234
  /**
188
235
  * When set, `cellSize` is derived from the host's measured `clientWidth`
189
236
  * on every draw (including ResizeObserver-triggered redraws) instead of
190
237
  * the fixed `cell-size` attribute, so the grid actually fills the
191
238
  * available width. Without this, canvas dimensions are computed purely
192
- * from `PAD_LEFT + cols * cellSize`, so a resize-triggered redraw is a
193
- * geometric no-op.
239
+ * from `PAD_LEFT + cols * cellSize` (matrix mode) or
240
+ * `CAL_PAD_LEFT + weekCount * cellSize` (calendar mode), so a
241
+ * resize-triggered redraw is a geometric no-op. Originally matrix-mode
242
+ * only; now applies to calendar mode too.
194
243
  */
195
244
  this.fitToWidth = false;
196
245
  this.mode = 'matrix';
197
246
  this.days = [];
198
- this.bucketCount = DEFAULT_BUCKET_COUNT;
247
+ /**
248
+ * Calendar mode only (no-op in matrix mode): anchors the calendar grid at
249
+ * a different weekday instead of always Sunday — `0`-`6`, same numbering
250
+ * as `CalendarCellPos.weekday` (`0` Sunday .. `6` Saturday, matching
251
+ * `CalendarCell.weekday`'s existing convention). Threaded into
252
+ * `buildCalendarGrid()`. Defaults to `0` (Sunday), unchanged from before
253
+ * this property existed.
254
+ */
255
+ this.firstDayOfWeek = 0;
256
+ this._bucketCount = DEFAULT_BUCKET_COUNT;
199
257
  /** Cells to ring-highlight — `row`/`col` in matrix mode, `date` in calendar mode. See `HeatmapAnnotation`. */
200
258
  this.annotations = [];
259
+ this.themeRefreshQueued = false;
201
260
  /** The current value range, refreshed once per update cycle by `willUpdate()`. See `computeValueRange()`. */
202
261
  this.cachedValueRange = null;
262
+ /**
263
+ * The calendar-mode grid layout, refreshed by `willUpdate()` only when
264
+ * `days` actually changes (see `computeValueRange()`'s twin comment above)
265
+ * — `buildCalendarGrid()` parses every date, filters invalid ones, and does
266
+ * a full `.slice().sort()` for month labels, so recomputing it from
267
+ * `drawCalendar()`, `hitTestCalendar()`, `calendarCellAt()`, and
268
+ * `onCalendarKeyDown()` independently would redo that work 2-4x for a
269
+ * single hover/click/keydown.
270
+ */
271
+ this.cachedCalendarGrid = buildCalendarGrid(this.days);
272
+ this.cachedCalendarSortedValues = [];
273
+ this.cachedCalendarCellsByPos = new Map();
274
+ this.cachedRamp = null;
203
275
  /** The cell currently under the pointer (`null` when not hovering one) — drives `[part="tooltip"]`. */
204
276
  this.hoverCell = null;
205
277
  /** The roving keyboard-focus cell cursor, moved by arrow keys — drives the
@@ -207,21 +279,59 @@ export class LyraHeatmap extends LyraElement {
207
279
  this.focusedCell = null;
208
280
  /** Text of the visually-hidden `aria-live="polite"` status announcement, refreshed on every focus move. */
209
281
  this.liveText = '';
282
+ /**
283
+ * Whether `role`/`aria-label` were present on the host *before* this
284
+ * element's own code ever wrote to them — snapshotted once, in the very
285
+ * first `willUpdate()` (guarded by `hasUpdated`, which Lit only flips to
286
+ * `true` after that first update commits). Re-checking `hasAttribute()` on
287
+ * every update instead would self-poison: once this element writes its own
288
+ * default `aria-label` on the first render, `hasAttribute('aria-label')`
289
+ * is permanently `true` afterwards, which would wrongly look like an
290
+ * author-supplied value and freeze the label instead of refreshing it as
291
+ * `values`/`days` change on later updates.
292
+ */
293
+ this.authorSuppliedRole = false;
294
+ this.authorSuppliedAriaLabel = false;
210
295
  this.onDprChange = () => {
211
296
  this.watchDpr();
212
297
  this.draw();
213
298
  };
299
+ this.onColorSchemeChange = () => {
300
+ this.refreshTheme();
301
+ };
302
+ this.queueThemeRefresh = () => {
303
+ if (this.themeRefreshQueued)
304
+ return;
305
+ this.themeRefreshQueued = true;
306
+ queueMicrotask(() => {
307
+ this.themeRefreshQueued = false;
308
+ if (this.isConnected)
309
+ this.refreshTheme();
310
+ });
311
+ };
312
+ this.scheduleDraw = () => {
313
+ if (this.drawRafId !== undefined)
314
+ return;
315
+ this.drawRafId = requestAnimationFrame(() => {
316
+ this.drawRafId = undefined;
317
+ if (this.isConnected)
318
+ this.draw();
319
+ });
320
+ };
214
321
  this.onPointerMove = (e) => {
215
- this.hoverCell = this.hitTest(e.offsetX, e.offsetY);
322
+ const next = this.hitTest(e.offsetX, e.offsetY);
323
+ if (!this.samePos(this.hoverCell, next))
324
+ this.hoverCell = next;
216
325
  };
217
326
  this.onPointerLeave = () => {
218
327
  this.hoverCell = null;
219
328
  };
220
329
  this.onCanvasClick = (e) => {
221
- // Prefer the click's own position; fall back to whatever's already
222
- // keyboard-focused (e.g. a synthetic click dispatched via
223
- // HTMLElement.click(), which carries no real coordinates).
224
- const pos = this.hitTest(e.offsetX, e.offsetY) ?? this.focusedCell;
330
+ // Only the click's own hit-tested position counts a click that lands
331
+ // outside the grid (e.g. on the canvas's own padding) is a genuine
332
+ // out-of-grid click and should do nothing, not fall back to whatever
333
+ // cell happens to still be keyboard-focused from an earlier interaction.
334
+ const pos = this.hitTest(e.offsetX, e.offsetY);
225
335
  if (!pos)
226
336
  return;
227
337
  this.focusedCell = pos;
@@ -236,16 +346,49 @@ export class LyraHeatmap extends LyraElement {
236
346
  };
237
347
  }
238
348
  static { this.styles = [LyraElement.styles, styles, srOnly]; }
349
+ /**
350
+ * Effective per-cell size (CSS px). Originally matrix-mode only; now also
351
+ * governs calendar mode's cell size (replacing the previously hardcoded
352
+ * 11px constant there) once explicitly set. Left unset, each mode keeps
353
+ * its own original default — `DEFAULT_MATRIX_CELL_SIZE` (22) in matrix
354
+ * mode, `CAL_CELL` (11) in calendar mode — so an existing consumer who
355
+ * never touches `cellSize` sees no change in either mode. Set explicitly
356
+ * (attribute or property), the same value governs both modes alike.
357
+ */
358
+ get cellSize() {
359
+ return this._cellSize ?? (this.mode === 'calendar' ? CAL_CELL : DEFAULT_MATRIX_CELL_SIZE);
360
+ }
361
+ set cellSize(value) {
362
+ const oldValue = this._cellSize;
363
+ this._cellSize = value;
364
+ this.requestUpdate('cellSize', oldValue);
365
+ }
366
+ get bucketCount() {
367
+ return this._bucketCount;
368
+ }
369
+ set bucketCount(value) {
370
+ const oldValue = this._bucketCount;
371
+ this._bucketCount = normalizeBucketCount(value);
372
+ this.requestUpdate('bucketCount', oldValue);
373
+ }
239
374
  connectedCallback() {
240
375
  super.connectedCallback();
241
- this.resizeObserver = new ResizeObserver(() => this.draw());
376
+ this.resizeObserver = new ResizeObserver(this.scheduleDraw);
242
377
  this.resizeObserver.observe(this);
243
378
  this.watchDpr();
379
+ this.watchTheme();
244
380
  }
245
381
  disconnectedCallback() {
246
382
  super.disconnectedCallback();
247
383
  this.resizeObserver?.disconnect();
248
384
  this.dprQuery?.removeEventListener('change', this.onDprChange);
385
+ this.colorSchemeQuery?.removeEventListener('change', this.onColorSchemeChange);
386
+ this.themeObserver?.disconnect();
387
+ this.themeObserver = undefined;
388
+ if (this.drawRafId !== undefined) {
389
+ cancelAnimationFrame(this.drawRafId);
390
+ this.drawRafId = undefined;
391
+ }
249
392
  }
250
393
  watchDpr() {
251
394
  // A MediaQueryList's `matches` is fixed at creation time, so crossing the
@@ -256,30 +399,89 @@ export class LyraHeatmap extends LyraElement {
256
399
  this.dprQuery = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
257
400
  this.dprQuery.addEventListener('change', this.onDprChange);
258
401
  }
402
+ watchTheme() {
403
+ const view = this.ownerDocument.defaultView;
404
+ if (!view)
405
+ return;
406
+ this.colorSchemeQuery = view.matchMedia?.('(prefers-color-scheme: dark)');
407
+ this.colorSchemeQuery?.addEventListener('change', this.onColorSchemeChange);
408
+ if (typeof MutationObserver === 'undefined')
409
+ return;
410
+ const targets = [this];
411
+ let parent = this.parentElement;
412
+ while (parent) {
413
+ targets.push(parent);
414
+ parent = parent.parentElement;
415
+ }
416
+ this.themeObserver = new MutationObserver(this.queueThemeRefresh);
417
+ for (const target of targets) {
418
+ this.themeObserver.observe(target, {
419
+ attributes: true,
420
+ attributeFilter: ['class', 'style', 'data-theme', 'data-color-scheme'],
421
+ });
422
+ }
423
+ }
259
424
  willUpdate(changed) {
260
- if (changed.has('mode') || changed.has('rowLabels') || changed.has('colLabels') || changed.has('days')) {
425
+ if (changed.has('mode') ||
426
+ changed.has('rowLabels') ||
427
+ changed.has('colLabels') ||
428
+ changed.has('days') ||
429
+ changed.has('firstDayOfWeek')) {
261
430
  // The previous focus/hover cursor may no longer address a real cell
262
431
  // once the grid's shape (or mode) changes out from under it —
263
432
  // stroking a focus ring at a stale (row, col)/(week, weekday), or
264
433
  // leaving the live region announcing a stale value, would be actively
265
434
  // misleading, so drop both instead. Reactive-property writes belong in
266
435
  // willUpdate() (not updated()), which folds them into this same
267
- // render rather than scheduling a whole extra update pass.
436
+ // render rather than scheduling a whole extra update pass. A
437
+ // `firstDayOfWeek` change reshuffles every cell's week/weekday the same
438
+ // way a `days` change does, so it resets the cursor too.
268
439
  this.focusedCell = null;
269
440
  this.hoverCell = null;
270
441
  this.liveText = '';
271
442
  }
272
- this.cachedValueRange = this.computeValueRange();
443
+ // Calendar-mode grid layout depends only on `days`/`firstDayOfWeek` (not
444
+ // `mode`) — rebuilt here, once per cycle, instead of independently by
445
+ // every call site (see `cachedCalendarGrid`'s doc comment).
446
+ if (changed.has('days') || changed.has('firstDayOfWeek') || !this.hasUpdated) {
447
+ this.cachedCalendarGrid = buildCalendarGrid(this.days, this.firstDayOfWeek);
448
+ this.cachedCalendarSortedValues = this.cachedCalendarGrid.cells
449
+ .map((cell) => cell.value)
450
+ .filter((value) => Number.isFinite(value) && value >= 0)
451
+ .sort((a, b) => a - b);
452
+ this.cachedCalendarCellsByPos = new Map(this.cachedCalendarGrid.cells.map((cell) => [`${cell.week}:${cell.weekday}`, cell]));
453
+ }
454
+ if (!this.hasUpdated) {
455
+ this.authorSuppliedRole = this.hasAttribute('role');
456
+ this.authorSuppliedAriaLabel = this.hasAttribute('aria-label');
457
+ }
458
+ // A hover/keyboard-focus @state() change (hoverCell/focusedCell/liveText)
459
+ // triggers this same willUpdate() but never touches values/days/mode, so
460
+ // gating the O(rows*cols) rescan the same way the focus/hover reset above
461
+ // is gated avoids redoing it on every pointer move.
462
+ if (changed.has('values') || changed.has('days') || changed.has('mode') || !this.hasUpdated) {
463
+ this.cachedValueRange = this.computeValueRange();
464
+ }
273
465
  const bounds = this.cachedValueRange;
274
466
  const range = bounds ? `${bounds[0]}–${bounds[1]}` : 'no data';
275
- this.setAttribute('role', 'group');
276
- if (this.mode === 'calendar') {
277
- this.setAttribute('aria-label', `Calendar heatmap of ${this.days.length} days, ${this.valueLabel} range ${range}`);
278
- }
279
- else {
280
- const rows = this.rowLabels.length;
281
- const cols = this.colLabels.length;
282
- this.setAttribute('aria-label', `Heatmap of ${rows} × ${cols} cells, ${this.valueLabel} range ${range}`);
467
+ // Only default role/aria-label when the author hasn't already supplied
468
+ // one the canvas is a genuinely focusable/interactive control
469
+ // (tabindex="0", click/keydown handlers) plus a live role="status"
470
+ // region, so unconditionally forcing a role here would tell assistive
471
+ // tech to flatten the whole subtree, hiding both from the accessibility
472
+ // tree; it would also silently overwrite any author-supplied
473
+ // role/aria-label on every update.
474
+ if (!this.authorSuppliedRole)
475
+ this.setAttribute('role', 'group');
476
+ if (!this.authorSuppliedAriaLabel) {
477
+ if (this.mode === 'calendar') {
478
+ this.setAttribute('aria-label', `Calendar heatmap of ${this.days.length} days, ${this.valueLabel} range ${range}`);
479
+ }
480
+ else {
481
+ const rows = this.rowLabels.length;
482
+ const cols = this.colLabels.length;
483
+ this.setAttribute('aria-label', `Heatmap of ${rows} × ${cols} cells, ${this.valueLabel} range ${range}`);
484
+ }
283
485
  }
284
486
  }
285
487
  /**
@@ -295,7 +497,29 @@ export class LyraHeatmap extends LyraElement {
295
497
  const source = this.mode === 'calendar' ? this.days.map((d) => d.value) : this.values.flat();
296
498
  return minMax(source.filter((v) => Number.isFinite(v) && v >= 0));
297
499
  }
298
- updated() {
500
+ updated(changed) {
501
+ if ([
502
+ 'values',
503
+ 'rowLabels',
504
+ 'colLabels',
505
+ 'cellSize',
506
+ 'valueLabel',
507
+ 'scale',
508
+ 'fitToWidth',
509
+ 'mode',
510
+ 'days',
511
+ 'bucketCount',
512
+ 'annotations',
513
+ 'columnX',
514
+ 'rowY',
515
+ 'firstDayOfWeek',
516
+ 'focusedCell',
517
+ ].some((name) => changed.has(name))) {
518
+ this.draw();
519
+ }
520
+ }
521
+ /** Redraws canvas content after an upstream token or theme change. */
522
+ refreshTheme() {
299
523
  this.draw();
300
524
  }
301
525
  /** Reads the customizable ramp endpoints off the host's computed style. */
@@ -331,12 +555,141 @@ export class LyraHeatmap extends LyraElement {
331
555
  else
332
556
  this.drawMatrix();
333
557
  }
558
+ colorRamp(bucketCount) {
559
+ const [scaleLo, scaleHi] = this.scaleEndpoints();
560
+ const normalizedBucketCount = normalizeBucketCount(bucketCount);
561
+ const key = `${scaleLo}\u0000${scaleHi}\u0000${normalizedBucketCount}`;
562
+ if (this.cachedRamp?.key === key)
563
+ return this.cachedRamp;
564
+ const loRgb = resolveRgb(scaleLo, FALLBACK_SCALE_LO);
565
+ const hiRgb = resolveRgb(scaleHi, FALLBACK_SCALE_HI);
566
+ const colors = Array.from({ length: normalizedBucketCount }, (_, i) => mixRgb(loRgb, hiRgb, i / (normalizedBucketCount - 1)));
567
+ this.cachedRamp = { key, colors, loRgb, hiRgb };
568
+ return this.cachedRamp;
569
+ }
570
+ /**
571
+ * Effective per-cell size in calendar mode — mirrors `matrixCellSize()`
572
+ * exactly: `fitToWidth` derives it from the host's measured width,
573
+ * otherwise it's the (possibly explicitly-set) `cellSize` property, which
574
+ * itself falls back to today's original 11px calendar default when left
575
+ * unset (see `cellSize`'s accessor doc comment). Shared by `columnXFor()`,
576
+ * `rowYFor()`, `drawCalendar()`, and the hit-testing below (`weekAtX()`,
577
+ * `weekdayAtY()`, `cellRect()`) so they always agree on exactly the same
578
+ * geometry as what's actually painted.
579
+ */
580
+ calendarCellSize() {
581
+ const { weekCount } = this.cachedCalendarGrid;
582
+ if (this.fitToWidth && weekCount > 0) {
583
+ // Unlike matrix mode's contiguous cells, calendar columns are spaced
584
+ // `cellSize + CAL_GAP` apart (see `columnXFor()`), so the gap has to be
585
+ // subtracted back out here — otherwise the painted width would overshoot
586
+ // `hostWidth` by `weekCount * CAL_GAP` (every column's gap, once each).
587
+ const hostWidth = this.clientWidth || CAL_PAD_LEFT + weekCount * (this.cellSize + CAL_GAP);
588
+ return Math.max(4, (hostWidth - CAL_PAD_LEFT) / weekCount - CAL_GAP);
589
+ }
590
+ return this.cellSize;
591
+ }
592
+ /**
593
+ * Calendar-mode week-column x-origin (canvas-local CSS px) — `columnX(week)`
594
+ * when set, otherwise the original evenly-spaced formula (now derived from
595
+ * `calendarCellSize()` rather than the fixed `CAL_CELL` constant). Shared
596
+ * by every calendar-mode drawing and hit-testing call site (`drawCalendar()`,
597
+ * `hitTestCalendar()`/`weekAtX()`, `cellRect()`) so painted geometry and
598
+ * interactive hit-testing never disagree — mirrors the existing
599
+ * `matrixCellSize()` invariant for matrix mode's
600
+ * `drawMatrix()`/`hitTestMatrix()`/`cellRect()`.
601
+ */
602
+ columnXFor(week) {
603
+ return this.columnX ? this.columnX(week) : CAL_PAD_LEFT + week * (this.calendarCellSize() + CAL_GAP);
604
+ }
605
+ /**
606
+ * Calendar-mode weekday-row y-origin (canvas-local CSS px) — `rowY(weekday)`
607
+ * when set, otherwise the original evenly-spaced formula (now derived from
608
+ * `calendarCellSize()` rather than the fixed `CAL_CELL` constant). The
609
+ * vertical analogue of `columnXFor()`, mirroring its exact
610
+ * dispatch-with-computed-fallback shape and shared by the same
611
+ * drawing/hit-testing/focus-ring call sites.
612
+ */
613
+ rowYFor(weekday) {
614
+ return this.rowY ? this.rowY(weekday) : CAL_LABEL_H + weekday * (this.calendarCellSize() + CAL_GAP);
615
+ }
616
+ /**
617
+ * Inverse of `columnXFor()`: resolves an x position (canvas-local CSS px)
618
+ * to the week column it falls in, or `null` if it's outside every column
619
+ * (`weekCount` is 0, or `x` doesn't land inside `[0, weekCount)`'s span).
620
+ * The default spacing has a closed-form inverse (division); an arbitrary
621
+ * `columnX` override doesn't, so that case instead scans each column's
622
+ * `[columnXFor(week), columnXFor(week + 1))` span — algebraically the same
623
+ * span the default formula's division derives — so hit-testing always
624
+ * agrees with wherever `columnXFor()` actually painted that column.
625
+ */
626
+ weekAtX(x, weekCount) {
627
+ if (!this.columnX) {
628
+ const cellSize = this.calendarCellSize();
629
+ const week = Math.floor((x - CAL_PAD_LEFT) / (cellSize + CAL_GAP));
630
+ return week >= 0 && week < weekCount ? week : null;
631
+ }
632
+ for (let week = 0; week < weekCount; week++) {
633
+ const start = this.columnXFor(week);
634
+ const end = week + 1 < weekCount ? this.columnXFor(week + 1) : start + (this.calendarCellSize() + CAL_GAP);
635
+ if (x >= start && x < end)
636
+ return week;
637
+ }
638
+ return null;
639
+ }
640
+ /**
641
+ * Inverse of `rowYFor()`: resolves a y position (canvas-local CSS px) to
642
+ * the weekday row it falls in (0-6), or `null` if it's outside every row.
643
+ * Mirrors `weekAtX()`'s closed-form-vs-scan split for the same reason: the
644
+ * default spacing has a closed-form inverse; an arbitrary `rowY` override
645
+ * doesn't.
646
+ */
647
+ weekdayAtY(y) {
648
+ if (!this.rowY) {
649
+ const cellSize = this.calendarCellSize();
650
+ const weekday = Math.floor((y - CAL_LABEL_H) / (cellSize + CAL_GAP));
651
+ return weekday >= 0 && weekday < 7 ? weekday : null;
652
+ }
653
+ for (let weekday = 0; weekday < 7; weekday++) {
654
+ const start = this.rowYFor(weekday);
655
+ const end = weekday + 1 < 7 ? this.rowYFor(weekday + 1) : start + (this.calendarCellSize() + CAL_GAP);
656
+ if (y >= start && y < end)
657
+ return weekday;
658
+ }
659
+ return null;
660
+ }
661
+ /**
662
+ * Locale-derived weekday-axis labels for the 7 rows drawn down the left of
663
+ * the calendar grid. Only indices 1, 3, and 5 (relative to `firstWeekStart`)
664
+ * are filled in — matching today's sparse every-other-day label density —
665
+ * the rest stay blank. `firstWeekStart` is always the UTC
666
+ * `firstDayOfWeek`-weekday by construction (see `buildCalendarGrid()`), so
667
+ * weekday index 0..6 is a fixed positional mapping relative to it (with the
668
+ * default `firstDayOfWeek` of 0, that's the original Sun..Sat mapping,
669
+ * unchanged); only the label *text* for indices 1/3/5 is derived here, via
670
+ * `Intl.DateTimeFormat` on a real UTC date that actually falls on that
671
+ * weekday, so it follows the runtime locale instead of a hardcoded English
672
+ * array (see `calendarCellText()`'s tooltip text for the same pattern).
673
+ */
674
+ weekdayLabels(firstWeekStart) {
675
+ const formatter = new Intl.DateTimeFormat(undefined, { weekday: 'short', timeZone: 'UTC' });
676
+ const labels = ['', '', '', '', '', '', ''];
677
+ for (const weekday of [1, 3, 5]) {
678
+ labels[weekday] = formatter.format(new Date(firstWeekStart.getTime() + weekday * MS_PER_DAY));
679
+ }
680
+ return labels;
681
+ }
334
682
  drawCalendar() {
335
683
  if (!this.canvas)
336
684
  return;
337
- const { cells, weekCount, monthLabels } = buildCalendarGrid(this.days);
338
- const w = CAL_PAD_LEFT + Math.max(1, weekCount) * (CAL_CELL + CAL_GAP);
339
- const h = CAL_LABEL_H + 7 * (CAL_CELL + CAL_GAP);
685
+ const { cells, weekCount, firstWeekStart, monthLabels } = this.cachedCalendarGrid;
686
+ const cellSize = this.calendarCellSize();
687
+ const w = this.columnXFor(Math.max(1, weekCount));
688
+ // Derived from `rowYFor(7)` (one past the last of the 7 weekday rows), the
689
+ // vertical mirror of `w`'s `columnXFor(weekCount)` — so a custom `rowY`
690
+ // that spaces rows out further than the default formula still gets a
691
+ // canvas tall enough to paint every row, instead of clipping them.
692
+ const h = this.rowYFor(7);
340
693
  const dpr = window.devicePixelRatio || 1;
341
694
  this.canvas.width = w * dpr;
342
695
  this.canvas.height = h * dpr;
@@ -347,24 +700,39 @@ export class LyraHeatmap extends LyraElement {
347
700
  return;
348
701
  ctx.scale(dpr, dpr);
349
702
  ctx.clearRect(0, 0, w, h);
350
- const sortedValues = cells
351
- .map((c) => c.value)
352
- .filter((v) => Number.isFinite(v) && v >= 0)
353
- .sort((a, b) => a - b);
354
- const [scaleLo, scaleHi] = this.scaleEndpoints();
355
- const loRgb = resolveRgb(scaleLo, FALLBACK_SCALE_LO);
356
- const hiRgb = resolveRgb(scaleHi, FALLBACK_SCALE_HI);
357
- const buckets = resolveBucketCount(this.bucketCount);
358
- const ramp = Array.from({ length: buckets }, (_, i) => mixRgb(loRgb, hiRgb, i / (buckets - 1)));
703
+ // Normalize at the allocation boundary as a final guard even though the
704
+ // public accessor and attribute converter already normalize their inputs.
705
+ const buckets = normalizeBucketCount(this.bucketCount);
706
+ const ramp = this.colorRamp(buckets).colors;
359
707
  const noDataFill = this.noDataFill();
360
- for (const cell of cells) {
361
- const x = CAL_PAD_LEFT + cell.week * (CAL_CELL + CAL_GAP);
362
- const y = CAL_LABEL_H + cell.weekday * (CAL_CELL + CAL_GAP);
363
- ctx.fillStyle =
364
- cell.value < 0 || !Number.isFinite(cell.value)
365
- ? noDataFill
366
- : ramp[quartileBucket(cell.value, sortedValues, buckets)];
367
- ctx.fillRect(x, y, CAL_CELL, CAL_CELL);
708
+ // Only consulted when `scale === 'sqrt'` — mirrors `drawMatrix()`'s own
709
+ // `hi` bound, reusing the already-cached value range instead of
710
+ // rescanning `sortedValues` for its max.
711
+ const bounds = this.cachedValueRange;
712
+ const hi = bounds ? bounds[1] : 1;
713
+ // Indexed once per draw so every (week, weekday) grid position can be
714
+ // painted below, not just positions with a matching entry in `cells` — a
715
+ // day missing from `days` entirely (a gap in a sparse calendar) still
716
+ // gets a real grid cell (see `calendarCellAt()`'s doc comment for the
717
+ // same `-1` no-data sentinel used here) instead of being left
718
+ // transparent.
719
+ for (let week = 0; week < weekCount; week++) {
720
+ for (let weekday = 0; weekday < 7; weekday++) {
721
+ const value = this.cachedCalendarCellsByPos.get(`${week}:${weekday}`)?.value ?? -1;
722
+ const x = this.columnXFor(week);
723
+ const y = this.rowYFor(weekday);
724
+ if (value < 0 || !Number.isFinite(value)) {
725
+ ctx.fillStyle = noDataFill;
726
+ }
727
+ else if (this.scale === 'sqrt') {
728
+ const step = sqrtStep(value, hi, buckets);
729
+ ctx.fillStyle = step < 0 ? noDataFill : ramp[step];
730
+ }
731
+ else {
732
+ ctx.fillStyle = ramp[quartileBucket(value, this.cachedCalendarSortedValues, buckets)];
733
+ }
734
+ ctx.fillRect(x, y, cellSize, cellSize);
735
+ }
368
736
  }
369
737
  // Annotation ring overlay, stroked after the fill pass so it reads
370
738
  // clearly over the data-driven cell color — see drawMatrix()'s twin of
@@ -378,9 +746,9 @@ export class LyraHeatmap extends LyraElement {
378
746
  const match = cells.find((c) => c.date === ann.date);
379
747
  if (!match)
380
748
  continue;
381
- const x = CAL_PAD_LEFT + match.week * (CAL_CELL + CAL_GAP);
382
- const y = CAL_LABEL_H + match.weekday * (CAL_CELL + CAL_GAP);
383
- ctx.strokeRect(x + 0.5, y + 0.5, CAL_CELL - 1, CAL_CELL - 1);
749
+ const x = this.columnXFor(match.week);
750
+ const y = this.rowYFor(match.weekday);
751
+ ctx.strokeRect(x + 0.5, y + 0.5, cellSize - 1, cellSize - 1);
384
752
  }
385
753
  }
386
754
  // Keyboard focus ring, redrawn on top of the fill pass (and any
@@ -390,20 +758,20 @@ export class LyraHeatmap extends LyraElement {
390
758
  if (week < weekCount && weekday < 7) {
391
759
  ctx.lineWidth = RING_LINE_WIDTH;
392
760
  ctx.strokeStyle = this.focusRingColor();
393
- const x = CAL_PAD_LEFT + week * (CAL_CELL + CAL_GAP);
394
- const y = CAL_LABEL_H + weekday * (CAL_CELL + CAL_GAP);
395
- ctx.strokeRect(x + 0.5, y + 0.5, CAL_CELL - 1, CAL_CELL - 1);
761
+ const x = this.columnXFor(week);
762
+ const y = this.rowYFor(weekday);
763
+ ctx.strokeRect(x + 0.5, y + 0.5, cellSize - 1, cellSize - 1);
396
764
  }
397
765
  }
398
766
  ctx.fillStyle = this.labelColor();
399
767
  ctx.font = this.labelFont();
400
768
  for (const m of monthLabels) {
401
- ctx.fillText(m.label, CAL_PAD_LEFT + m.week * (CAL_CELL + CAL_GAP), CAL_LABEL_H - 4);
769
+ ctx.fillText(m.label, this.columnXFor(m.week), CAL_LABEL_H - 4);
402
770
  }
403
- const WEEKDAY_LABELS = ['', 'Mon', '', 'Wed', '', 'Fri', ''];
771
+ const WEEKDAY_LABELS = this.weekdayLabels(firstWeekStart);
404
772
  WEEKDAY_LABELS.forEach((label, weekday) => {
405
773
  if (label)
406
- ctx.fillText(label, 2, CAL_LABEL_H + weekday * (CAL_CELL + CAL_GAP) + CAL_CELL - 1);
774
+ ctx.fillText(label, 2, this.rowYFor(weekday) + cellSize - 1);
407
775
  });
408
776
  }
409
777
  /**
@@ -441,10 +809,9 @@ export class LyraHeatmap extends LyraElement {
441
809
  const bounds = this.cachedValueRange;
442
810
  const lo = bounds ? bounds[0] : 0;
443
811
  const hi = bounds ? bounds[1] : 1;
444
- const [scaleLo, scaleHi] = this.scaleEndpoints();
445
- const loRgb = resolveRgb(scaleLo, FALLBACK_SCALE_LO);
446
- const hiRgb = resolveRgb(scaleHi, FALLBACK_SCALE_HI);
447
- const ramp = Array.from({ length: RAMP_STEPS }, (_, i) => mixRgb(loRgb, hiRgb, i / (RAMP_STEPS - 1)));
812
+ const rampData = this.colorRamp(RAMP_STEPS);
813
+ const ramp = rampData.colors;
814
+ const { loRgb, hiRgb } = rampData;
448
815
  const noDataFill = this.noDataFill();
449
816
  for (let r = 0; r < rows; r++) {
450
817
  for (let c = 0; c < cols; c++) {
@@ -536,12 +903,12 @@ export class LyraHeatmap extends LyraElement {
536
903
  return { row, col };
537
904
  }
538
905
  hitTestCalendar(x, y) {
539
- const { weekCount } = buildCalendarGrid(this.days);
906
+ const { weekCount } = this.cachedCalendarGrid;
540
907
  if (weekCount === 0)
541
908
  return null;
542
- const week = Math.floor((x - CAL_PAD_LEFT) / (CAL_CELL + CAL_GAP));
543
- const weekday = Math.floor((y - CAL_LABEL_H) / (CAL_CELL + CAL_GAP));
544
- if (week < 0 || week >= weekCount || weekday < 0 || weekday > 6)
909
+ const week = this.weekAtX(x, weekCount);
910
+ const weekday = this.weekdayAtY(y);
911
+ if (week === null || weekday === null)
545
912
  return null;
546
913
  return { week, weekday };
547
914
  }
@@ -554,7 +921,7 @@ export class LyraHeatmap extends LyraElement {
554
921
  * sentinel uses in matrix mode), instead of being unresolvable.
555
922
  */
556
923
  calendarCellAt(pos) {
557
- const { cells, firstWeekStart } = buildCalendarGrid(this.days);
924
+ const { cells, firstWeekStart } = this.cachedCalendarGrid;
558
925
  const match = cells.find((c) => c.week === pos.week && c.weekday === pos.weekday);
559
926
  if (match)
560
927
  return { date: match.date, value: match.value };
@@ -568,11 +935,12 @@ export class LyraHeatmap extends LyraElement {
568
935
  */
569
936
  cellRect(pos) {
570
937
  if ('week' in pos) {
938
+ const cellSize = this.calendarCellSize();
571
939
  return {
572
- x: CAL_PAD_LEFT + pos.week * (CAL_CELL + CAL_GAP),
573
- y: CAL_LABEL_H + pos.weekday * (CAL_CELL + CAL_GAP),
574
- w: CAL_CELL,
575
- h: CAL_CELL,
940
+ x: this.columnXFor(pos.week),
941
+ y: this.rowYFor(pos.weekday),
942
+ w: cellSize,
943
+ h: cellSize,
576
944
  };
577
945
  }
578
946
  const cellSize = this.matrixCellSize(this.colLabels.length);
@@ -630,6 +998,24 @@ export class LyraHeatmap extends LyraElement {
630
998
  const rect = this.cellRect(pos);
631
999
  return { left: `${rect.x + rect.w / 2}px`, top: `${rect.y}px` };
632
1000
  }
1001
+ /**
1002
+ * Value-equality check for a `CellPos`, since `hoverCell` is assigned a
1003
+ * fresh object literal on every `pointermove` — Lit's `@state()`
1004
+ * change-detection only no-ops an identical *reference*, so without this,
1005
+ * `{row,col} !== {row,col}` by reference triggers a full canvas clear +
1006
+ * redraw on every pixel of pointer movement, even within the same cell.
1007
+ */
1008
+ samePos(a, b) {
1009
+ if (a === b)
1010
+ return true;
1011
+ if (!a || !b)
1012
+ return false;
1013
+ if ('week' in a && 'week' in b)
1014
+ return a.week === b.week && a.weekday === b.weekday;
1015
+ if ('row' in a && 'row' in b)
1016
+ return a.row === b.row && a.col === b.col;
1017
+ return false;
1018
+ }
633
1019
  onMatrixKeyDown(e) {
634
1020
  const rows = this.rowLabels.length;
635
1021
  const cols = this.colLabels.length;
@@ -668,7 +1054,7 @@ export class LyraHeatmap extends LyraElement {
668
1054
  this.announce(next);
669
1055
  }
670
1056
  onCalendarKeyDown(e) {
671
- const { weekCount } = buildCalendarGrid(this.days);
1057
+ const { weekCount } = this.cachedCalendarGrid;
672
1058
  if (weekCount === 0)
673
1059
  return;
674
1060
  if (e.key === 'Enter' || e.key === ' ') {
@@ -742,7 +1128,7 @@ __decorate([
742
1128
  ], LyraHeatmap.prototype, "values", void 0);
743
1129
  __decorate([
744
1130
  property({ type: Number, attribute: 'cell-size' })
745
- ], LyraHeatmap.prototype, "cellSize", void 0);
1131
+ ], LyraHeatmap.prototype, "cellSize", null);
746
1132
  __decorate([
747
1133
  property({ attribute: 'value-label' })
748
1134
  ], LyraHeatmap.prototype, "valueLabel", void 0);
@@ -759,14 +1145,23 @@ __decorate([
759
1145
  property({ attribute: false })
760
1146
  ], LyraHeatmap.prototype, "days", void 0);
761
1147
  __decorate([
762
- property({ attribute: 'bucket-count', type: Number })
763
- ], LyraHeatmap.prototype, "bucketCount", void 0);
1148
+ property({ type: Number, attribute: 'first-day-of-week' })
1149
+ ], LyraHeatmap.prototype, "firstDayOfWeek", void 0);
1150
+ __decorate([
1151
+ property({ attribute: 'bucket-count', converter: bucketCountConverter })
1152
+ ], LyraHeatmap.prototype, "bucketCount", null);
764
1153
  __decorate([
765
1154
  property({ attribute: false })
766
1155
  ], LyraHeatmap.prototype, "annotations", void 0);
767
1156
  __decorate([
768
1157
  property({ attribute: false })
769
1158
  ], LyraHeatmap.prototype, "cellText", void 0);
1159
+ __decorate([
1160
+ property({ attribute: false })
1161
+ ], LyraHeatmap.prototype, "columnX", void 0);
1162
+ __decorate([
1163
+ property({ attribute: false })
1164
+ ], LyraHeatmap.prototype, "rowY", void 0);
770
1165
  __decorate([
771
1166
  query('canvas')
772
1167
  ], LyraHeatmap.prototype, "canvas", void 0);