@aceshooting/lyra-ui 1.2.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.
Files changed (330) hide show
  1. package/README.md +11 -0
  2. package/custom-elements.json +23341 -11202
  3. package/dist/components/app-rail/app-rail.d.ts +171 -0
  4. package/dist/components/app-rail/app-rail.js +483 -0
  5. package/dist/components/app-rail/app-rail.styles.d.ts +1 -0
  6. package/dist/components/app-rail/app-rail.styles.js +125 -0
  7. package/dist/components/attachment-chip/attachment-chip.d.ts +114 -0
  8. package/dist/components/attachment-chip/attachment-chip.js +312 -0
  9. package/dist/components/attachment-chip/attachment-chip.styles.d.ts +1 -0
  10. package/dist/components/attachment-chip/attachment-chip.styles.js +184 -0
  11. package/dist/components/attachment-trigger/attachment-trigger.d.ts +83 -0
  12. package/dist/components/attachment-trigger/attachment-trigger.js +258 -0
  13. package/dist/components/attachment-trigger/attachment-trigger.styles.d.ts +1 -0
  14. package/dist/components/attachment-trigger/attachment-trigger.styles.js +68 -0
  15. package/dist/components/chart/box-plot.js +11 -1
  16. package/dist/components/chart/chart-loader.d.ts +44 -13
  17. package/dist/components/chart/chart-loader.js +65 -18
  18. package/dist/components/chart/chart.d.ts +1 -2
  19. package/dist/components/chart/chart.js +67 -12
  20. package/dist/components/chart/histogram-bin.d.ts +4 -1
  21. package/dist/components/chart/histogram-bin.js +24 -14
  22. package/dist/components/chart/lite-chart.d.ts +54 -0
  23. package/dist/components/chart/lite-chart.js +140 -22
  24. package/dist/components/chart/lite-chart.styles.js +9 -0
  25. package/dist/components/chat-composer/chat-composer.d.ts +92 -0
  26. package/dist/components/chat-composer/chat-composer.js +285 -0
  27. package/dist/components/chat-composer/chat-composer.styles.d.ts +1 -0
  28. package/dist/components/chat-composer/chat-composer.styles.js +135 -0
  29. package/dist/components/chat-message/chat-message.d.ts +113 -0
  30. package/dist/components/chat-message/chat-message.js +291 -0
  31. package/dist/components/chat-message/chat-message.styles.d.ts +1 -0
  32. package/dist/components/chat-message/chat-message.styles.js +203 -0
  33. package/dist/components/checkbox/checkbox.d.ts +59 -0
  34. package/dist/components/checkbox/checkbox.js +230 -0
  35. package/dist/components/checkbox/checkbox.styles.d.ts +1 -0
  36. package/dist/components/checkbox/checkbox.styles.js +70 -0
  37. package/dist/components/chip/chip-group.d.ts +56 -0
  38. package/dist/components/chip/chip-group.js +124 -0
  39. package/dist/components/chip/chip-group.styles.d.ts +1 -0
  40. package/dist/components/chip/chip-group.styles.js +57 -0
  41. package/dist/components/chip/chip.d.ts +64 -0
  42. package/dist/components/chip/chip.js +115 -0
  43. package/dist/components/chip/chip.styles.d.ts +1 -0
  44. package/dist/components/chip/chip.styles.js +115 -0
  45. package/dist/components/citation-badge/citation-badge.d.ts +113 -0
  46. package/dist/components/citation-badge/citation-badge.js +300 -0
  47. package/dist/components/citation-badge/citation-badge.styles.d.ts +1 -0
  48. package/dist/components/citation-badge/citation-badge.styles.js +96 -0
  49. package/dist/components/code-block/code-block.d.ts +89 -0
  50. package/dist/components/code-block/code-block.js +317 -0
  51. package/dist/components/code-block/code-block.styles.d.ts +1 -0
  52. package/dist/components/code-block/code-block.styles.js +187 -0
  53. package/dist/components/code-block/code-loader.d.ts +44 -0
  54. package/dist/components/code-block/code-loader.js +79 -0
  55. package/dist/components/combobox/combobox.d.ts +19 -1
  56. package/dist/components/combobox/combobox.js +84 -14
  57. package/dist/components/combobox/option.d.ts +5 -1
  58. package/dist/components/combobox/option.js +35 -0
  59. package/dist/components/context-meter/context-meter.d.ts +53 -0
  60. package/dist/components/context-meter/context-meter.js +150 -0
  61. package/dist/components/context-meter/context-meter.styles.d.ts +1 -0
  62. package/dist/components/context-meter/context-meter.styles.js +120 -0
  63. package/dist/components/conversation-item/conversation-item.d.ts +128 -0
  64. package/dist/components/conversation-item/conversation-item.js +322 -0
  65. package/dist/components/conversation-item/conversation-item.styles.d.ts +1 -0
  66. package/dist/components/conversation-item/conversation-item.styles.js +155 -0
  67. package/dist/components/date-picker/date-input.d.ts +24 -0
  68. package/dist/components/date-picker/date-input.js +134 -22
  69. package/dist/components/date-picker/date-picker.d.ts +7 -0
  70. package/dist/components/date-picker/date-picker.js +85 -16
  71. package/dist/components/dialog/confirm.d.ts +40 -0
  72. package/dist/components/dialog/confirm.js +79 -0
  73. package/dist/components/dialog/dialog.d.ts +85 -0
  74. package/dist/components/dialog/dialog.js +272 -0
  75. package/dist/components/dialog/dialog.styles.d.ts +1 -0
  76. package/dist/components/dialog/dialog.styles.js +52 -0
  77. package/dist/components/dock-panel/dock-panel.d.ts +134 -0
  78. package/dist/components/dock-panel/dock-panel.js +345 -0
  79. package/dist/components/dock-panel/dock-panel.styles.d.ts +1 -0
  80. package/dist/components/dock-panel/dock-panel.styles.js +145 -0
  81. package/dist/components/document-preview/document-preview.d.ts +135 -0
  82. package/dist/components/document-preview/document-preview.js +372 -0
  83. package/dist/components/document-preview/document-preview.styles.d.ts +1 -0
  84. package/dist/components/document-preview/document-preview.styles.js +191 -0
  85. package/dist/components/export-button/csv.js +1 -1
  86. package/dist/components/export-button/export-button.js +2 -0
  87. package/dist/components/flag/flag.d.ts +29 -13
  88. package/dist/components/flag/flag.js +39 -13
  89. package/dist/components/gauge/gauge.js +19 -10
  90. package/dist/components/generation-status/generation-status.d.ts +109 -0
  91. package/dist/components/generation-status/generation-status.js +273 -0
  92. package/dist/components/generation-status/generation-status.styles.d.ts +1 -0
  93. package/dist/components/generation-status/generation-status.styles.js +84 -0
  94. package/dist/components/heatmap/calendar-grid.d.ts +4 -1
  95. package/dist/components/heatmap/calendar-grid.js +25 -12
  96. package/dist/components/heatmap/heatmap-scale.d.ts +6 -0
  97. package/dist/components/heatmap/heatmap-scale.js +7 -1
  98. package/dist/components/heatmap/heatmap.d.ts +74 -0
  99. package/dist/components/heatmap/heatmap.js +135 -23
  100. package/dist/components/json-viewer/json-viewer.d.ts +53 -0
  101. package/dist/components/json-viewer/json-viewer.js +283 -0
  102. package/dist/components/json-viewer/json-viewer.styles.d.ts +1 -0
  103. package/dist/components/json-viewer/json-viewer.styles.js +176 -0
  104. package/dist/components/kbd/kbd.d.ts +77 -0
  105. package/dist/components/kbd/kbd.js +214 -0
  106. package/dist/components/kbd/kbd.styles.d.ts +1 -0
  107. package/dist/components/kbd/kbd.styles.js +52 -0
  108. package/dist/components/live-region/live-region.d.ts +81 -0
  109. package/dist/components/live-region/live-region.js +153 -0
  110. package/dist/components/live-region/live-region.styles.d.ts +1 -0
  111. package/dist/components/live-region/live-region.styles.js +10 -0
  112. package/dist/components/map/map.d.ts +8 -6
  113. package/dist/components/map/map.js +44 -8
  114. package/dist/components/markdown/markdown-loader.d.ts +34 -0
  115. package/dist/components/markdown/markdown-loader.js +43 -0
  116. package/dist/components/markdown/markdown.d.ts +81 -0
  117. package/dist/components/markdown/markdown.js +284 -0
  118. package/dist/components/markdown/markdown.styles.d.ts +1 -0
  119. package/dist/components/markdown/markdown.styles.js +82 -0
  120. package/dist/components/media-card/media-card.d.ts +122 -0
  121. package/dist/components/media-card/media-card.js +295 -0
  122. package/dist/components/media-card/media-card.styles.d.ts +1 -0
  123. package/dist/components/media-card/media-card.styles.js +132 -0
  124. package/dist/components/mention-popover/mention-popover.d.ts +200 -0
  125. package/dist/components/mention-popover/mention-popover.js +475 -0
  126. package/dist/components/mention-popover/mention-popover.styles.d.ts +1 -0
  127. package/dist/components/mention-popover/mention-popover.styles.js +101 -0
  128. package/dist/components/menu/menu-item.d.ts +62 -0
  129. package/dist/components/menu/menu-item.js +119 -0
  130. package/dist/components/menu/menu-item.styles.d.ts +1 -0
  131. package/dist/components/menu/menu-item.styles.js +67 -0
  132. package/dist/components/menu/menu.d.ts +143 -0
  133. package/dist/components/menu/menu.js +365 -0
  134. package/dist/components/menu/menu.styles.d.ts +1 -0
  135. package/dist/components/menu/menu.styles.js +66 -0
  136. package/dist/components/model-select/model-select.d.ts +125 -0
  137. package/dist/components/model-select/model-select.js +490 -0
  138. package/dist/components/model-select/model-select.styles.d.ts +1 -0
  139. package/dist/components/model-select/model-select.styles.js +189 -0
  140. package/dist/components/model-settings-panel/model-settings-panel.d.ts +69 -0
  141. package/dist/components/model-settings-panel/model-settings-panel.js +145 -0
  142. package/dist/components/model-settings-panel/model-settings-panel.styles.d.ts +1 -0
  143. package/dist/components/model-settings-panel/model-settings-panel.styles.js +82 -0
  144. package/dist/components/playback/playback.d.ts +13 -1
  145. package/dist/components/playback/playback.js +47 -14
  146. package/dist/components/responsive-panel/responsive-panel.d.ts +150 -0
  147. package/dist/components/responsive-panel/responsive-panel.js +365 -0
  148. package/dist/components/responsive-panel/responsive-panel.styles.d.ts +1 -0
  149. package/dist/components/responsive-panel/responsive-panel.styles.js +103 -0
  150. package/dist/components/result-card/result-card.d.ts +44 -0
  151. package/dist/components/result-card/result-card.js +83 -0
  152. package/dist/components/result-card/result-card.styles.d.ts +1 -0
  153. package/dist/components/result-card/result-card.styles.js +50 -0
  154. package/dist/components/result-card/result-field.d.ts +36 -0
  155. package/dist/components/result-card/result-field.js +87 -0
  156. package/dist/components/result-card/result-field.styles.d.ts +1 -0
  157. package/dist/components/result-card/result-field.styles.js +32 -0
  158. package/dist/components/select/select.d.ts +43 -2
  159. package/dist/components/select/select.js +150 -17
  160. package/dist/components/slider/slider.d.ts +89 -0
  161. package/dist/components/slider/slider.js +352 -0
  162. package/dist/components/slider/slider.styles.d.ts +1 -0
  163. package/dist/components/slider/slider.styles.js +87 -0
  164. package/dist/components/source-card/source-card.d.ts +92 -0
  165. package/dist/components/source-card/source-card.js +148 -0
  166. package/dist/components/source-card/source-card.styles.d.ts +1 -0
  167. package/dist/components/source-card/source-card.styles.js +69 -0
  168. package/dist/components/source-list/source-list.d.ts +63 -0
  169. package/dist/components/source-list/source-list.js +122 -0
  170. package/dist/components/source-list/source-list.styles.d.ts +1 -0
  171. package/dist/components/source-list/source-list.styles.js +59 -0
  172. package/dist/components/sparkline/sparkline.js +27 -13
  173. package/dist/components/split/split.d.ts +93 -1
  174. package/dist/components/split/split.js +284 -15
  175. package/dist/components/split/split.styles.js +30 -0
  176. package/dist/components/stat/stat.d.ts +6 -1
  177. package/dist/components/stat/stat.js +8 -2
  178. package/dist/components/stream-status/stream-status.d.ts +110 -0
  179. package/dist/components/stream-status/stream-status.js +210 -0
  180. package/dist/components/stream-status/stream-status.styles.d.ts +1 -0
  181. package/dist/components/stream-status/stream-status.styles.js +103 -0
  182. package/dist/components/streaming-text/streaming-text.d.ts +98 -0
  183. package/dist/components/streaming-text/streaming-text.js +209 -0
  184. package/dist/components/streaming-text/streaming-text.styles.d.ts +1 -0
  185. package/dist/components/streaming-text/streaming-text.styles.js +62 -0
  186. package/dist/components/switch/switch.d.ts +58 -0
  187. package/dist/components/switch/switch.js +179 -0
  188. package/dist/components/switch/switch.styles.d.ts +1 -0
  189. package/dist/components/switch/switch.styles.js +75 -0
  190. package/dist/components/table/table.d.ts +73 -4
  191. package/dist/components/table/table.js +169 -20
  192. package/dist/components/table/table.styles.js +6 -1
  193. package/dist/components/tabs/tabs.d.ts +51 -0
  194. package/dist/components/tabs/tabs.js +190 -0
  195. package/dist/components/tabs/tabs.styles.d.ts +1 -0
  196. package/dist/components/tabs/tabs.styles.js +63 -0
  197. package/dist/components/thinking-panel/thinking-panel.d.ts +121 -0
  198. package/dist/components/thinking-panel/thinking-panel.js +235 -0
  199. package/dist/components/thinking-panel/thinking-panel.styles.d.ts +1 -0
  200. package/dist/components/thinking-panel/thinking-panel.styles.js +117 -0
  201. package/dist/components/toast/toast-item.d.ts +1 -0
  202. package/dist/components/toast/toast-item.js +31 -6
  203. package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +130 -0
  204. package/dist/components/tool-approval-dialog/tool-approval-dialog.js +403 -0
  205. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.d.ts +1 -0
  206. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +160 -0
  207. package/dist/components/tool-call-chip/tool-call-chip.d.ts +89 -0
  208. package/dist/components/tool-call-chip/tool-call-chip.js +288 -0
  209. package/dist/components/tool-call-chip/tool-call-chip.styles.d.ts +1 -0
  210. package/dist/components/tool-call-chip/tool-call-chip.styles.js +203 -0
  211. package/dist/components/tool-param-form/tool-param-form.d.ts +145 -0
  212. package/dist/components/tool-param-form/tool-param-form.js +366 -0
  213. package/dist/components/tool-param-form/tool-param-form.styles.d.ts +1 -0
  214. package/dist/components/tool-param-form/tool-param-form.styles.js +75 -0
  215. package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +102 -0
  216. package/dist/components/tool-result-dialog/tool-result-dialog.js +393 -0
  217. package/dist/components/tool-result-dialog/tool-result-dialog.styles.d.ts +1 -0
  218. package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +176 -0
  219. package/dist/components/tool-result-view/registry.d.ts +81 -0
  220. package/dist/components/tool-result-view/registry.js +84 -0
  221. package/dist/components/tool-result-view/tool-result-view.d.ts +55 -0
  222. package/dist/components/tool-result-view/tool-result-view.js +138 -0
  223. package/dist/components/tool-result-view/tool-result-view.styles.d.ts +1 -0
  224. package/dist/components/tool-result-view/tool-result-view.styles.js +13 -0
  225. package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +151 -0
  226. package/dist/components/tool-select-dialog/tool-select-dialog.js +439 -0
  227. package/dist/components/tool-select-dialog/tool-select-dialog.styles.d.ts +1 -0
  228. package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +182 -0
  229. package/dist/components/tree/tree-node.d.ts +11 -2
  230. package/dist/components/tree/tree-node.js +16 -6
  231. package/dist/components/tree/tree.d.ts +8 -2
  232. package/dist/components/tree/tree.js +18 -11
  233. package/dist/components/typing-indicator/typing-indicator.d.ts +60 -0
  234. package/dist/components/typing-indicator/typing-indicator.js +96 -0
  235. package/dist/components/typing-indicator/typing-indicator.styles.d.ts +1 -0
  236. package/dist/components/typing-indicator/typing-indicator.styles.js +132 -0
  237. package/dist/components/virtual-list/virtual-list.d.ts +173 -0
  238. package/dist/components/virtual-list/virtual-list.js +461 -0
  239. package/dist/components/virtual-list/virtual-list.styles.d.ts +1 -0
  240. package/dist/components/virtual-list/virtual-list.styles.js +44 -0
  241. package/dist/components/word-cloud/word-cloud-layout.js +14 -1
  242. package/dist/components/word-cloud/word-cloud.d.ts +6 -0
  243. package/dist/components/word-cloud/word-cloud.js +25 -14
  244. package/dist/components/word-cloud/word-cloud.styles.js +3 -2
  245. package/dist/internal/announcer.d.ts +57 -0
  246. package/dist/internal/announcer.js +71 -0
  247. package/dist/internal/form-associated.d.ts +1 -0
  248. package/dist/internal/form-associated.js +55 -11
  249. package/dist/internal/group-by-recency.d.ts +50 -0
  250. package/dist/internal/group-by-recency.js +80 -0
  251. package/dist/internal/rtl.d.ts +23 -0
  252. package/dist/internal/rtl.js +28 -0
  253. package/dist/internal/scroll-lock.d.ts +10 -5
  254. package/dist/internal/scroll-lock.js +30 -14
  255. package/dist/internal/tokens.styles.js +6 -0
  256. package/dist/lyra.d.ts +130 -31
  257. package/dist/lyra.js +94 -26
  258. package/llms-full.txt +3836 -17
  259. package/llms.txt +16 -1
  260. package/package.json +23 -5
  261. package/dist/components/chart/bar-chart.stories.d.ts +0 -5
  262. package/dist/components/chart/bar-chart.stories.js +0 -20
  263. package/dist/components/chart/box-plot.stories.d.ts +0 -5
  264. package/dist/components/chart/box-plot.stories.js +0 -29
  265. package/dist/components/chart/bubble-chart.stories.d.ts +0 -5
  266. package/dist/components/chart/bubble-chart.stories.js +0 -26
  267. package/dist/components/chart/chart.stories.d.ts +0 -32
  268. package/dist/components/chart/chart.stories.js +0 -141
  269. package/dist/components/chart/doughnut-chart.stories.d.ts +0 -5
  270. package/dist/components/chart/doughnut-chart.stories.js +0 -26
  271. package/dist/components/chart/histogram.stories.d.ts +0 -5
  272. package/dist/components/chart/histogram.stories.js +0 -17
  273. package/dist/components/chart/line-chart.stories.d.ts +0 -22
  274. package/dist/components/chart/line-chart.stories.js +0 -99
  275. package/dist/components/chart/lite-chart.stories.d.ts +0 -14
  276. package/dist/components/chart/lite-chart.stories.js +0 -114
  277. package/dist/components/chart/pie-chart.stories.d.ts +0 -5
  278. package/dist/components/chart/pie-chart.stories.js +0 -26
  279. package/dist/components/chart/polar-area-chart.stories.d.ts +0 -5
  280. package/dist/components/chart/polar-area-chart.stories.js +0 -20
  281. package/dist/components/chart/radar-chart.stories.d.ts +0 -5
  282. package/dist/components/chart/radar-chart.stories.js +0 -20
  283. package/dist/components/chart/scatter-chart.stories.d.ts +0 -5
  284. package/dist/components/chart/scatter-chart.stories.js +0 -31
  285. package/dist/components/combobox/combobox.stories.d.ts +0 -24
  286. package/dist/components/combobox/combobox.stories.js +0 -102
  287. package/dist/components/date-picker/date-input.stories.d.ts +0 -10
  288. package/dist/components/date-picker/date-input.stories.js +0 -48
  289. package/dist/components/date-picker/date-picker.stories.d.ts +0 -10
  290. package/dist/components/date-picker/date-picker.stories.js +0 -25
  291. package/dist/components/empty/empty.stories.d.ts +0 -7
  292. package/dist/components/empty/empty.stories.js +0 -34
  293. package/dist/components/export-button/export-button.stories.d.ts +0 -7
  294. package/dist/components/export-button/export-button.stories.js +0 -37
  295. package/dist/components/file-input/file-input.stories.d.ts +0 -7
  296. package/dist/components/file-input/file-input.stories.js +0 -19
  297. package/dist/components/flag/flag.stories.d.ts +0 -6
  298. package/dist/components/flag/flag.stories.js +0 -31
  299. package/dist/components/gauge/gauge.stories.d.ts +0 -8
  300. package/dist/components/gauge/gauge.stories.js +0 -19
  301. package/dist/components/graph/graph.stories.d.ts +0 -8
  302. package/dist/components/graph/graph.stories.js +0 -68
  303. package/dist/components/heatmap/heatmap.stories.d.ts +0 -27
  304. package/dist/components/heatmap/heatmap.stories.js +0 -179
  305. package/dist/components/map/map.stories.d.ts +0 -15
  306. package/dist/components/map/map.stories.js +0 -135
  307. package/dist/components/playback/playback.stories.d.ts +0 -7
  308. package/dist/components/playback/playback.stories.js +0 -16
  309. package/dist/components/select/select.stories.d.ts +0 -21
  310. package/dist/components/select/select.stories.js +0 -91
  311. package/dist/components/skeleton/skeleton.stories.d.ts +0 -5
  312. package/dist/components/skeleton/skeleton.stories.js +0 -16
  313. package/dist/components/sparkline/sparkline.stories.d.ts +0 -11
  314. package/dist/components/sparkline/sparkline.stories.js +0 -39
  315. package/dist/components/split/split.stories.d.ts +0 -7
  316. package/dist/components/split/split.stories.js +0 -36
  317. package/dist/components/stat/stat.stories.d.ts +0 -9
  318. package/dist/components/stat/stat.stories.js +0 -81
  319. package/dist/components/table/table.stories.d.ts +0 -12
  320. package/dist/components/table/table.stories.js +0 -67
  321. package/dist/components/time-range/time-range.stories.d.ts +0 -8
  322. package/dist/components/time-range/time-range.stories.js +0 -26
  323. package/dist/components/toast/toast.stories.d.ts +0 -8
  324. package/dist/components/toast/toast.stories.js +0 -72
  325. package/dist/components/tree/tree.stories.d.ts +0 -9
  326. package/dist/components/tree/tree.stories.js +0 -52
  327. package/dist/components/widget/widget.stories.d.ts +0 -9
  328. package/dist/components/widget/widget.stories.js +0 -74
  329. package/dist/components/word-cloud/word-cloud.stories.d.ts +0 -10
  330. package/dist/components/word-cloud/word-cloud.stories.js +0 -55
@@ -0,0 +1,214 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html, nothing } from 'lit';
8
+ import { property, state } from 'lit/decorators.js';
9
+ import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { defineElement } from '../../internal/prefix.js';
11
+ import { styles } from './kbd.styles.js';
12
+ // A deliberately small map — just the modifier-adjacent/navigation keys
13
+ // common enough in real shortcuts to be worth a friendly glyph. Anything not
14
+ // listed here (e.g. 'f1', 'k', a punctuation key) falls through to the
15
+ // generic "render as typed, upper-case single letters" rule in
16
+ // `shortcutTokenLabel` below, per this component's spec.
17
+ //
18
+ // 'plus'/'minus' exist specifically so a shortcut that includes a literal
19
+ // "+" or "-" key (e.g. the classic zoom-in shortcut) has a way to say so:
20
+ // the '+'-separated `keys` grammar can't itself carry a literal "+" token
21
+ // (it's the delimiter), so callers spell it as the word instead.
22
+ const NAMED_KEY_LABELS = {
23
+ enter: { visual: '↵', word: 'Enter' },
24
+ esc: { visual: 'Esc', word: 'Escape' },
25
+ escape: { visual: 'Esc', word: 'Escape' },
26
+ tab: { visual: 'Tab', word: 'Tab' },
27
+ space: { visual: 'Space', word: 'Space' },
28
+ backspace: { visual: '⌫', word: 'Backspace' },
29
+ delete: { visual: 'Del', word: 'Delete' },
30
+ home: { visual: 'Home', word: 'Home' },
31
+ end: { visual: 'End', word: 'End' },
32
+ pageup: { visual: 'PgUp', word: 'Page Up' },
33
+ pagedown: { visual: 'PgDn', word: 'Page Down' },
34
+ arrowup: { visual: '↑', word: 'Arrow Up' },
35
+ arrowdown: { visual: '↓', word: 'Arrow Down' },
36
+ arrowleft: { visual: '←', word: 'Arrow Left' },
37
+ arrowright: { visual: '→', word: 'Arrow Right' },
38
+ plus: { visual: '+', word: 'Plus' },
39
+ minus: { visual: '−', word: 'Minus' },
40
+ };
41
+ /**
42
+ * Resolves one `+`-separated token of a `keys` string to its rendered glyph
43
+ * and spelled-out word, given whether the shortcut is being shown on macOS.
44
+ *
45
+ * Exported as a pure function (parameterized on `isMac` rather than reading
46
+ * the module-scope platform constant directly) so both the macOS and
47
+ * non-macOS branches are directly unit-testable without having to spoof
48
+ * `navigator` in a browser test environment that only ever runs as one
49
+ * platform.
50
+ */
51
+ export function shortcutTokenLabel(rawToken, isMac) {
52
+ const token = rawToken.trim();
53
+ const lower = token.toLowerCase();
54
+ // 'mod' is the platform-neutral primary modifier: Command on macOS,
55
+ // Control everywhere else. 'ctrl' is deliberately distinct — it always
56
+ // means the literal Control key, even on macOS, for shortcuts that are
57
+ // specifically Ctrl-based (e.g. terminal Ctrl+C) rather than
58
+ // platform-adapted.
59
+ if (lower === 'mod')
60
+ return isMac ? { visual: '⌘', word: 'Command' } : { visual: 'Ctrl', word: 'Control' };
61
+ if (lower === 'ctrl' || lower === 'control')
62
+ return { visual: 'Ctrl', word: 'Control' };
63
+ if (lower === 'alt')
64
+ return isMac ? { visual: '⌥', word: 'Option' } : { visual: 'Alt', word: 'Alt' };
65
+ if (lower === 'shift')
66
+ return { visual: '⇧', word: 'Shift' };
67
+ const named = NAMED_KEY_LABELS[lower];
68
+ if (named)
69
+ return named;
70
+ // Anything else renders as typed (preserving the caller's own casing),
71
+ // except a bare single letter/digit, which is upper-cased for a
72
+ // consistent key-cap look ('k' and 'K' both render 'K').
73
+ if (token.length === 1) {
74
+ const upper = token.toUpperCase();
75
+ return { visual: upper, word: upper };
76
+ }
77
+ return { visual: token, word: token };
78
+ }
79
+ /** Splits a `keys` string (e.g. `'mod+shift+p'`) into its resolved token
80
+ * labels, dropping empty segments from stray/leading/trailing `+`s. */
81
+ export function parseShortcut(keys, isMac) {
82
+ return keys
83
+ .split('+')
84
+ .map((t) => t.trim())
85
+ .filter((t) => t.length > 0)
86
+ .map((t) => shortcutTokenLabel(t, isMac));
87
+ }
88
+ /**
89
+ * Detects macOS to decide whether `mod`/`alt` render their Mac glyphs (⌘/⌥)
90
+ * or their generic-platform text (Ctrl/Alt). Prefers the modern
91
+ * `navigator.userAgentData` (Client Hints) API when the browser exposes it,
92
+ * falling back through `navigator.platform` and finally a `navigator.userAgent`
93
+ * substring check — all three are deprecated/non-standard to varying
94
+ * degrees (`userAgentData` is Chromium-only so far; `platform` is
95
+ * long-deprecated; `userAgent` sniffing is a last resort) but remain, in
96
+ * combination, the practical cross-browser way to answer "is this macOS"
97
+ * today with no dependency.
98
+ */
99
+ function detectIsMac() {
100
+ if (typeof navigator === 'undefined')
101
+ return false;
102
+ const uaData = navigator.userAgentData;
103
+ if (uaData?.platform)
104
+ return uaData.platform.toLowerCase().includes('mac');
105
+ if (navigator.platform)
106
+ return /mac/i.test(navigator.platform);
107
+ return /mac/i.test(navigator.userAgent ?? '');
108
+ }
109
+ // Computed once at module scope, not per-instance/per-render — a page's
110
+ // platform never changes mid-session, so there is nothing to gain (and a
111
+ // little cost, however small) from re-detecting it on every <lyra-kbd>
112
+ // instance or every re-render.
113
+ const IS_MAC = detectIsMac();
114
+ /**
115
+ * `<lyra-kbd>` — a small chip representing a keyboard shortcut, rendering
116
+ * the platform-appropriate glyph for cross-platform modifier keys (⌘ on
117
+ * macOS, "Ctrl" elsewhere) from a single platform-neutral `keys` string.
118
+ *
119
+ * `keys` is a `+`-separated sequence of tokens, e.g. `"mod+k"` or
120
+ * `"mod+shift+p"`. Recognized modifier tokens: `mod` (the platform-neutral
121
+ * primary modifier — ⌘ on macOS, "Ctrl" elsewhere), `alt` (⌥ / "Alt"),
122
+ * `shift` (⇧ on every platform), and `ctrl` (always the literal Control
123
+ * key, distinct from `mod`, for a shortcut that's specifically Ctrl even on
124
+ * macOS). Any other token renders as typed, except a bare single
125
+ * letter/digit which is upper-cased, with a small built-in map of friendly
126
+ * labels for common named keys (`enter` → `↵`, `esc` → "Esc", the four
127
+ * arrow keys → arrow glyphs, plus `tab`/`space`/`backspace`/`delete`/`home`/
128
+ * `end`/`pageup`/`pagedown`/`plus`/`minus`). `enter` renders as its `↵`
129
+ * glyph (not the word "Enter") to match the other single-glyph modifier/
130
+ * arrow keys visually — its spelled-out word form still appears in the
131
+ * computed `aria-label`.
132
+ *
133
+ * Each token renders as its own key cap (`part="key"`); consecutive caps
134
+ * are joined by a small "+" separator between them, matching how most
135
+ * cross-platform shortcut documentation (including on macOS, despite the
136
+ * OS's own native shortcut hints usually running the glyphs together with
137
+ * no separator) reads unambiguously regardless of how many/which glyphs are
138
+ * involved.
139
+ *
140
+ * The default slot is not used for the normal glyph rendering above — it's
141
+ * an escape hatch for fully custom content (e.g. an icon instead of a text
142
+ * glyph) that, when non-empty, replaces the `keys`-driven rendering
143
+ * entirely and stops this component from asserting its own `aria-label`,
144
+ * leaving the slotted content to carry its own accessible name.
145
+ *
146
+ * @customElement lyra-kbd
147
+ * @slot - Optional override for fully custom key-cap content, replacing the
148
+ * `keys`-driven rendering. Leave empty to use `keys`.
149
+ * @csspart base - The chip's root element.
150
+ * @csspart key - Each rendered key cap (one per token in `keys`).
151
+ */
152
+ export class LyraKbd extends LyraElement {
153
+ constructor() {
154
+ super(...arguments);
155
+ /** A `+`-separated shortcut, e.g. `'mod+k'`. See the class doc for the
156
+ * full token grammar. */
157
+ this.keys = '';
158
+ // Real (non-whitespace) light-DOM content overrides the keys-driven
159
+ // rendering below — same "seed synchronously, refine on slotchange"
160
+ // pattern as lyra-checkbox's hasLabelSlot/lyra-citation-badge's
161
+ // hasPreviewSlot, so a declaratively-slotted override doesn't flash the
162
+ // keys rendering for one frame before the first slotchange event.
163
+ this.hasCustomContent = false;
164
+ this.onSlotChange = (e) => {
165
+ this.hasCustomContent = e.target.assignedNodes({ flatten: true }).length > 0;
166
+ };
167
+ }
168
+ static { this.styles = [LyraElement.styles, styles]; }
169
+ willUpdate() {
170
+ if (!this.hasUpdated) {
171
+ this.hasCustomContent = Array.from(this.childNodes).some((n) => (n.textContent ?? '').trim().length > 0);
172
+ }
173
+ }
174
+ render() {
175
+ const explicitLabel = this.getAttribute('aria-label');
176
+ if (this.hasCustomContent) {
177
+ return html `
178
+ <span part="base" aria-label=${explicitLabel || nothing}>
179
+ <slot @slotchange=${this.onSlotChange}></slot>
180
+ </span>
181
+ `;
182
+ }
183
+ const tokens = parseShortcut(this.keys, IS_MAC);
184
+ // role="img" treats the chip as one opaque unit (matching
185
+ // lyra-context-meter's/lyra-chart's canvas usage of the same pattern):
186
+ // the individual glyphs and "+" separators aren't real words, so
187
+ // exposing them as separate accessible-tree text would read worse than
188
+ // the single spelled-out aria-label below. An empty `keys` renders
189
+ // nothing visible, so it's marked aria-hidden instead of exposed as a
190
+ // nameless image.
191
+ const ariaLabel = explicitLabel || tokens.map((t) => t.word).join('+');
192
+ return html `
193
+ <span
194
+ part="base"
195
+ role=${tokens.length > 0 ? 'img' : nothing}
196
+ aria-hidden=${tokens.length === 0 && !explicitLabel ? 'true' : nothing}
197
+ aria-label=${ariaLabel || nothing}
198
+ >
199
+ ${tokens.map((t, i) => html `
200
+ ${i > 0 ? html `<span class="sep" aria-hidden="true">+</span>` : nothing}
201
+ <span part="key">${t.visual}</span>
202
+ `)}
203
+ <slot @slotchange=${this.onSlotChange} hidden></slot>
204
+ </span>
205
+ `;
206
+ }
207
+ }
208
+ __decorate([
209
+ property()
210
+ ], LyraKbd.prototype, "keys", void 0);
211
+ __decorate([
212
+ state()
213
+ ], LyraKbd.prototype, "hasCustomContent", void 0);
214
+ defineElement('kbd', LyraKbd);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,52 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: inline-flex;
5
+ max-inline-size: 100%;
6
+ vertical-align: middle;
7
+ }
8
+
9
+ [part='base'] {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ flex-wrap: wrap;
13
+ /* The body font, not a monospace stack — unlike lyra-code-block's own
14
+ local --lyra-code-block-font token, a key cap's content is mostly
15
+ single characters and modifier glyphs (⌘⇧⌥), which read more legibly
16
+ — and have more complete glyph coverage — in the UI font than in a
17
+ monospace stack that may not even ship those symbols. */
18
+ font-family: var(--lyra-font);
19
+ font-size: 0.8125rem;
20
+ line-height: 1.3;
21
+ color: var(--lyra-color-text-quiet);
22
+ }
23
+
24
+ [part='key'] {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ box-sizing: border-box;
29
+ min-inline-size: 1.5em;
30
+ padding: 0.0625rem var(--lyra-space-xs);
31
+ border: 1px solid var(--lyra-color-border);
32
+ border-radius: calc(var(--lyra-radius) * 0.6);
33
+ background: var(--lyra-color-surface);
34
+ color: var(--lyra-color-text);
35
+ font-weight: 500;
36
+ /* A subtle bottom-weighted inset shadow reads as a slightly-raised
37
+ physical key cap rather than a flat label chip, echoing the
38
+ lyra-chip/lyra-checkbox box's own bordered-box treatment. */
39
+ box-shadow: inset 0 -1px 0 var(--lyra-color-border);
40
+ }
41
+
42
+ .sep {
43
+ margin-inline: 0.1875rem;
44
+ color: var(--lyra-color-text-quiet);
45
+ font-size: 0.75em;
46
+ user-select: none;
47
+ }
48
+
49
+ ::slotted(*) {
50
+ vertical-align: middle;
51
+ }
52
+ `;
@@ -0,0 +1,81 @@
1
+ import { type TemplateResult, type PropertyValues } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ import { type AnnounceOptions } from '../../internal/announcer.js';
4
+ export type LiveRegionMode = 'polite' | 'assertive';
5
+ /**
6
+ * `<lyra-live-region>` — a visually-hidden ARIA live region that throttles
7
+ * and coalesces announcements instead of relaying every call verbatim.
8
+ *
9
+ * Naive live regions plus token-by-token streaming text (a chat response, a
10
+ * progress readout, ...) equals screen-reader spam: every incremental chunk
11
+ * gets announced. This component wraps `Announcer`
12
+ * (`../../internal/announcer.js`) so callers can fire `announce()` as often
13
+ * as they like — only the latest text within each `throttle-ms` window
14
+ * actually reaches assistive tech, and a `{ force: true }` call (e.g. once a
15
+ * stream ends) always lands immediately regardless of any window in
16
+ * progress.
17
+ *
18
+ * A consumer typically mounts one `<lyra-live-region>` per page/surface
19
+ * (much like `<lyra-toast>` is one region per placement — see
20
+ * `../toast/toaster.ts`) and keeps a reference to call `announce()` from
21
+ * application code or a parent component:
22
+ *
23
+ * @example
24
+ * ```html
25
+ * <!-- once, near the root of a chat surface's shell -->
26
+ * <lyra-live-region id="chat-live" mode="polite"></lyra-live-region>
27
+ * ```
28
+ * ```ts
29
+ * const live = document.getElementById('chat-live') as LyraLiveRegion;
30
+ *
31
+ * // streaming tokens: fine to call on every chunk, only the trailing state lands
32
+ * live.announce(`${partialText} …`);
33
+ *
34
+ * // stream finished: always announced, even mid-throttle-window
35
+ * live.announce('Response complete', { force: true });
36
+ * ```
37
+ *
38
+ * A parent Lit component would instead hold the reference via `@query`:
39
+ * ```ts
40
+ * @query('lyra-live-region') private liveRegion!: LyraLiveRegion;
41
+ * ```
42
+ * and is exactly what later components in this family (a stream-status
43
+ * indicator, a tool-call chip's status transitions, a chat message's
44
+ * streaming state) are expected to do rather than hand-rolling their own
45
+ * `aria-live` element.
46
+ *
47
+ * @customElement lyra-live-region
48
+ * @csspart region - The visually-hidden element carrying `role`/`aria-live`.
49
+ */
50
+ export declare class LyraLiveRegion extends LyraElement {
51
+ static styles: import("lit").CSSResultGroup[];
52
+ /** `polite` (role="status") waits for the user to be idle; `assertive`
53
+ * (role="alert") interrupts. Mirrors native `aria-live` semantics. */
54
+ mode: LiveRegionMode;
55
+ /** Throttle window in ms — see `Announcer` in `internal/announcer.ts`. */
56
+ throttleMs: number;
57
+ private readonly announcer;
58
+ private lastWritten;
59
+ private regionEl?;
60
+ private reannounceHandle?;
61
+ private pendingWrite?;
62
+ constructor();
63
+ protected updated(changed: PropertyValues): void;
64
+ firstUpdated(): void;
65
+ disconnectedCallback(): void;
66
+ /**
67
+ * Queue `text` for announcement. Calls arriving within `throttle-ms` of
68
+ * the first call in a burst collapse to a single announcement of the
69
+ * latest text; pass `{ force: true }` to bypass the window and flush
70
+ * immediately (e.g. for a final/terminal message that must not be
71
+ * dropped).
72
+ */
73
+ announce(text: string, options?: AnnounceOptions): void;
74
+ private write;
75
+ render(): TemplateResult;
76
+ }
77
+ declare global {
78
+ interface HTMLElementTagNameMap {
79
+ 'lyra-live-region': LyraLiveRegion;
80
+ }
81
+ }
@@ -0,0 +1,153 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html } from 'lit';
8
+ import { property } from 'lit/decorators.js';
9
+ import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { defineElement } from '../../internal/prefix.js';
11
+ import { srOnly } from '../../internal/a11y.js';
12
+ import { Announcer } from '../../internal/announcer.js';
13
+ import { styles } from './live-region.styles.js';
14
+ /**
15
+ * `<lyra-live-region>` — a visually-hidden ARIA live region that throttles
16
+ * and coalesces announcements instead of relaying every call verbatim.
17
+ *
18
+ * Naive live regions plus token-by-token streaming text (a chat response, a
19
+ * progress readout, ...) equals screen-reader spam: every incremental chunk
20
+ * gets announced. This component wraps `Announcer`
21
+ * (`../../internal/announcer.js`) so callers can fire `announce()` as often
22
+ * as they like — only the latest text within each `throttle-ms` window
23
+ * actually reaches assistive tech, and a `{ force: true }` call (e.g. once a
24
+ * stream ends) always lands immediately regardless of any window in
25
+ * progress.
26
+ *
27
+ * A consumer typically mounts one `<lyra-live-region>` per page/surface
28
+ * (much like `<lyra-toast>` is one region per placement — see
29
+ * `../toast/toaster.ts`) and keeps a reference to call `announce()` from
30
+ * application code or a parent component:
31
+ *
32
+ * @example
33
+ * ```html
34
+ * <!-- once, near the root of a chat surface's shell -->
35
+ * <lyra-live-region id="chat-live" mode="polite"></lyra-live-region>
36
+ * ```
37
+ * ```ts
38
+ * const live = document.getElementById('chat-live') as LyraLiveRegion;
39
+ *
40
+ * // streaming tokens: fine to call on every chunk, only the trailing state lands
41
+ * live.announce(`${partialText} …`);
42
+ *
43
+ * // stream finished: always announced, even mid-throttle-window
44
+ * live.announce('Response complete', { force: true });
45
+ * ```
46
+ *
47
+ * A parent Lit component would instead hold the reference via `@query`:
48
+ * ```ts
49
+ * @query('lyra-live-region') private liveRegion!: LyraLiveRegion;
50
+ * ```
51
+ * and is exactly what later components in this family (a stream-status
52
+ * indicator, a tool-call chip's status transitions, a chat message's
53
+ * streaming state) are expected to do rather than hand-rolling their own
54
+ * `aria-live` element.
55
+ *
56
+ * @customElement lyra-live-region
57
+ * @csspart region - The visually-hidden element carrying `role`/`aria-live`.
58
+ */
59
+ export class LyraLiveRegion extends LyraElement {
60
+ static { this.styles = [LyraElement.styles, styles, srOnly]; }
61
+ constructor() {
62
+ super();
63
+ /** `polite` (role="status") waits for the user to be idle; `assertive`
64
+ * (role="alert") interrupts. Mirrors native `aria-live` semantics. */
65
+ this.mode = 'polite';
66
+ /** Throttle window in ms — see `Announcer` in `internal/announcer.ts`. */
67
+ this.throttleMs = 500;
68
+ // The live region's own last-written text. Tracked separately from
69
+ // whatever Lit thinks the DOM looks like because `write()` mutates
70
+ // `regionEl.textContent` directly (outside Lit's template bindings) --
71
+ // see `write()` for why.
72
+ this.lastWritten = '';
73
+ // Built in the constructor (not a class-field initializer) so it reads
74
+ // `this.throttleMs` only after that property's own field initializer
75
+ // has already run and set the declared default.
76
+ this.announcer = new Announcer({
77
+ throttleMs: this.throttleMs,
78
+ onFlush: (text) => this.write(text),
79
+ });
80
+ }
81
+ updated(changed) {
82
+ if (changed.has('throttleMs')) {
83
+ this.announcer.throttleMs = this.throttleMs;
84
+ }
85
+ }
86
+ firstUpdated() {
87
+ this.regionEl = this.renderRoot.querySelector('[part="region"]') ?? undefined;
88
+ if (this.pendingWrite !== undefined) {
89
+ const text = this.pendingWrite;
90
+ this.pendingWrite = undefined;
91
+ this.write(text);
92
+ }
93
+ }
94
+ disconnectedCallback() {
95
+ super.disconnectedCallback();
96
+ this.announcer.cancel();
97
+ if (this.reannounceHandle !== undefined) {
98
+ cancelAnimationFrame(this.reannounceHandle);
99
+ this.reannounceHandle = undefined;
100
+ }
101
+ }
102
+ /**
103
+ * Queue `text` for announcement. Calls arriving within `throttle-ms` of
104
+ * the first call in a burst collapse to a single announcement of the
105
+ * latest text; pass `{ force: true }` to bypass the window and flush
106
+ * immediately (e.g. for a final/terminal message that must not be
107
+ * dropped).
108
+ */
109
+ announce(text, options) {
110
+ this.announcer.announce(text, options);
111
+ }
112
+ write(text) {
113
+ const region = this.regionEl;
114
+ if (!region) {
115
+ this.pendingWrite = text;
116
+ return;
117
+ }
118
+ if (text === this.lastWritten) {
119
+ // Screen readers announce a live region on text-content *change* --
120
+ // re-writing the identical string is otherwise a silent no-op to
121
+ // assistive tech. Clearing first and re-setting on the next frame
122
+ // (rather than in the same task) gives the DOM an actual empty ->
123
+ // populated transition to observe instead of a same-tick clear+set
124
+ // that can coalesce into nothing ever appearing to change.
125
+ region.textContent = '';
126
+ this.reannounceHandle = requestAnimationFrame(() => {
127
+ this.reannounceHandle = undefined;
128
+ region.textContent = text;
129
+ });
130
+ }
131
+ else {
132
+ region.textContent = text;
133
+ }
134
+ this.lastWritten = text;
135
+ }
136
+ render() {
137
+ const assertive = this.mode === 'assertive';
138
+ return html `<div
139
+ part="region"
140
+ class="sr-only"
141
+ role=${assertive ? 'alert' : 'status'}
142
+ aria-live=${assertive ? 'assertive' : 'polite'}
143
+ aria-atomic="true"
144
+ ></div>`;
145
+ }
146
+ }
147
+ __decorate([
148
+ property({ reflect: true })
149
+ ], LyraLiveRegion.prototype, "mode", void 0);
150
+ __decorate([
151
+ property({ type: Number, attribute: 'throttle-ms' })
152
+ ], LyraLiveRegion.prototype, "throttleMs", void 0);
153
+ defineElement('live-region', LyraLiveRegion);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,10 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ /* The host contributes no box of its own -- \`.sr-only\` (composed in via
4
+ internal/a11y.ts's shared \`srOnly\`) already takes the single child out
5
+ of flow via absolute positioning, so a wrapping host box would just be
6
+ an empty, purposeless inline element sitting in the document. */
7
+ :host {
8
+ display: contents;
9
+ }
10
+ `;
@@ -1,5 +1,4 @@
1
1
  import { type TemplateResult, type PropertyValues } from 'lit';
2
- import type { Map as MaplibreMap, StyleSpecification } from 'maplibre-gl';
3
2
  import { LyraElement } from '../../internal/lyra-element.js';
4
3
  import '../skeleton/skeleton.js';
5
4
  export interface LegendEntry {
@@ -30,10 +29,12 @@ export interface MapMarker {
30
29
  /**
31
30
  * Rendered as the marker's popup content via maplibre-gl's
32
31
  * `Popup.setHTML()` -- parsed as raw markup, inline event handlers
33
- * included. Only ever pass trusted content; sanitize anything derived
34
- * from user input before assigning it here.
32
+ * included. This is the library's one explicit unsafe-HTML escape hatch:
33
+ * only ever pass trusted content, and sanitize anything derived from user
34
+ * input before assigning it here. Prefer `label` (rendered via the safe
35
+ * `Popup.setText()`) whenever the content is plain text.
35
36
  */
36
- html?: string;
37
+ unsafeHtml?: string;
37
38
  }
38
39
  /**
39
40
  * `<lyra-map>` — a maplibre-gl wrapper with a declarative legend and
@@ -54,7 +55,7 @@ export declare class LyraMap extends LyraElement {
54
55
  static styles: import("lit").CSSResultGroup[];
55
56
  center: [number, number];
56
57
  zoom: number;
57
- mapStyle: StyleSpecification | string;
58
+ mapStyle: import('maplibre-gl').StyleSpecification | string;
58
59
  legend: LegendEntry[];
59
60
  choropleth?: ChoroplethLayer;
60
61
  markers: MapMarker[];
@@ -67,9 +68,10 @@ export declare class LyraMap extends LyraElement {
67
68
  private _appliedFillLayerId?;
68
69
  private _maplibreModule?;
69
70
  private _markerInstances;
71
+ private _markerColors;
70
72
  private _connectGeneration;
71
73
  /** The raw `maplibregl.Map` instance — escape hatch for anything this wrapper doesn't expose. */
72
- get map(): MaplibreMap | undefined;
74
+ get map(): import('maplibre-gl').Map | undefined;
73
75
  connectedCallback(): void;
74
76
  disconnectedCallback(): void;
75
77
  protected updated(changed: PropertyValues): void;
@@ -93,6 +93,14 @@ export class LyraMap extends LyraElement {
93
93
  // choropleth updates from ever re-running.
94
94
  this._styleLoaded = false;
95
95
  this._markerInstances = new Map();
96
+ // The installed maplibre-gl's `Marker` class has no `setColor()` (verified
97
+ // against its shipped `.d.ts` -- `color` is only ever consumed by the
98
+ // constructor), so an id-matched marker whose `color` changes can't be
99
+ // mutated in place; it has to be torn down and reconstructed instead. This
100
+ // tracks the color each currently-live marker instance was last
101
+ // constructed with, keyed the same as `_markerInstances`, so `applyMarkers`
102
+ // can detect that mismatch without re-deriving it from the DOM.
103
+ this._markerColors = new Map();
96
104
  // Bumped on every connectedCallback and captured by value in its
97
105
  // loadMaplibre().then() closure below. A disconnect immediately followed
98
106
  // by a reconnect (fast remounts, route/tab switches, etc.) before that
@@ -170,6 +178,7 @@ export class LyraMap extends LyraElement {
170
178
  for (const marker of this._markerInstances.values())
171
179
  marker.remove();
172
180
  this._markerInstances.clear();
181
+ this._markerColors.clear();
173
182
  }
174
183
  updated(changed) {
175
184
  if (this.loading)
@@ -281,31 +290,57 @@ export class LyraMap extends LyraElement {
281
290
  if (!map || !mod)
282
291
  return;
283
292
  const visible = new Set();
293
+ // Markers with no `id` used to key solely by `lngLat`, so two different
294
+ // id-less markers placed at the exact same coordinates collided onto one
295
+ // `_markerInstances` entry. An occurrence index (reset per render, per
296
+ // coordinate) makes same-coordinate id-less markers distinct while
297
+ // staying stable/consistent across re-renders as long as their relative
298
+ // order in `this.markers` doesn't change.
299
+ const coordCounts = new Map();
284
300
  for (const m of this.markers) {
285
- const key = m.id ?? `${m.lngLat[0]},${m.lngLat[1]}`;
301
+ let key = m.id;
302
+ if (key == null) {
303
+ const coordKey = `${m.lngLat[0]},${m.lngLat[1]}`;
304
+ const occurrence = coordCounts.get(coordKey) ?? 0;
305
+ coordCounts.set(coordKey, occurrence + 1);
306
+ key = `${coordKey}#${occurrence}`;
307
+ }
286
308
  visible.add(key);
287
- const existing = this._markerInstances.get(key);
309
+ let existing = this._markerInstances.get(key);
310
+ if (existing && this._markerColors.get(key) !== m.color) {
311
+ // `color` is baked into the marker's SVG at construction time with
312
+ // no way to mutate it afterwards -- fall through to the "no existing
313
+ // marker" branch below to reconstruct it instead. Note: this closes
314
+ // any popup the user currently has open on this marker (a fresh,
315
+ // closed Popup is built for the new instance) -- an accepted, narrow
316
+ // side effect of the reconstruction fallback, not a bug.
317
+ existing.remove();
318
+ this._markerInstances.delete(key);
319
+ this._markerColors.delete(key);
320
+ existing = undefined;
321
+ }
288
322
  if (!existing) {
289
323
  const marker = new mod.Marker(m.color ? { color: m.color } : undefined).setLngLat(m.lngLat);
290
- if (m.html || m.label) {
324
+ if (m.unsafeHtml || m.label) {
291
325
  const popup = new mod.Popup({ offset: 12 });
292
- if (m.html)
293
- popup.setHTML(m.html);
326
+ if (m.unsafeHtml)
327
+ popup.setHTML(m.unsafeHtml);
294
328
  else if (m.label)
295
329
  popup.setText(m.label);
296
330
  marker.setPopup(popup);
297
331
  }
298
332
  marker.addTo(map);
299
333
  this._markerInstances.set(key, marker);
334
+ this._markerColors.set(key, m.color);
300
335
  }
301
336
  else {
302
337
  existing.setLngLat(m.lngLat);
303
338
  const popup = existing.getPopup();
304
- if (m.html) {
339
+ if (m.unsafeHtml) {
305
340
  if (popup)
306
- popup.setHTML(m.html);
341
+ popup.setHTML(m.unsafeHtml);
307
342
  else
308
- existing.setPopup(new mod.Popup({ offset: 12 }).setHTML(m.html));
343
+ existing.setPopup(new mod.Popup({ offset: 12 }).setHTML(m.unsafeHtml));
309
344
  }
310
345
  else if (m.label) {
311
346
  if (popup)
@@ -322,6 +357,7 @@ export class LyraMap extends LyraElement {
322
357
  if (!visible.has(key)) {
323
358
  marker.remove();
324
359
  this._markerInstances.delete(key);
360
+ this._markerColors.delete(key);
325
361
  }
326
362
  }
327
363
  }