@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
@@ -16,7 +16,30 @@ import '../empty/empty.js';
16
16
  * actions-column button). Clicks/keydowns landing on one of these — or
17
17
  * bubbling up through one — must not be re-interpreted as row/column
18
18
  * activation by the table's own delegated listeners. */
19
- const INTERACTIVE_SELECTOR = 'button, a[href], input, select, textarea, [role="button"]';
19
+ const INTERACTIVE_SELECTOR = 'button, a[href], input, select, textarea, [role="button"], [role="combobox"], [role="listbox"], [role="slider"]';
20
+ /** Encodes a row/column identity key for use as a Map key or a DOM
21
+ * `data-row-key` attribute value, preserving the distinction between a
22
+ * numeric key and a string key that happen to stringify the same way
23
+ * (`1` vs `"1"`) -- a bare `String(key)` would silently collide the two. */
24
+ function encodeKey(key) {
25
+ return `${typeof key}:${key}`;
26
+ }
27
+ /** Whether `target` (or an ancestor up to the delegated listener's own
28
+ * `<table>`, exclusive) is a custom element — i.e. any tag containing a
29
+ * hyphen, the one universal rule every custom element name must follow —
30
+ * which is never itself matched by INTERACTIVE_SELECTOR's plain-HTML
31
+ * selector list but should still own its own clicks/keydowns (e.g. a
32
+ * `<lyra-select>`/`<lyra-combobox>` rendered by a `cell()` template)
33
+ * instead of being re-interpreted as row/column activation. */
34
+ function closestInteractive(target, boundary) {
35
+ let el = target;
36
+ while (el && el !== boundary) {
37
+ if (el.matches(INTERACTIVE_SELECTOR) || el.tagName.includes('-'))
38
+ return el;
39
+ el = el.parentElement;
40
+ }
41
+ return null;
42
+ }
20
43
  /**
21
44
  * `<lyra-table>` — a presentational, sort/select-aware data table.
22
45
  *
@@ -39,14 +62,23 @@ const INTERACTIVE_SELECTOR = 'button, a[href], input, select, textarea, [role="b
39
62
  *
40
63
  * `columns[].priority` ('medium' | 'low') hides that column under
41
64
  * `[part='base']`'s `@container` breakpoints; `[part='reveal-columns-button']`
42
- * (rendered whenever any column has a priority) forces them all back into
43
- * view. `columns[].sticky` pins a column's header/cells to the inline-start
44
- * edge while the table scrolls horizontally.
65
+ * forces them all back into view. Rather than a *static* check of whether any
66
+ * column merely declares a `priority`, the button (and the public
67
+ * `columnsHidden` property, see below) reflects whether a `priority` column
68
+ * is *actually* hidden right now — measured via `ResizeObserver` on
69
+ * `[part='base']` plus a post-render DOM check — or `showAllColumns`
70
+ * force-visible mode is currently active (so there's still a way to toggle
71
+ * it back off). `columns[].sticky` pins a column's header/cells to the
72
+ * inline-start edge while the table scrolls horizontally.
45
73
  *
46
74
  * @customElement lyra-table
47
75
  * @event lyra-sort - A sortable header was activated. `detail: { key }`.
48
76
  * @event lyra-row-click - A row was activated. `detail: { row }`.
49
77
  * @event lyra-load-more - The "load more" control was activated.
78
+ * @event lyra-columns-hidden-change - `columnsHidden` actually changed value
79
+ * (a `priority` column just became hidden/un-hidden by the `@container`
80
+ * rules, or `showAllColumns` force-visible mode was toggled while a
81
+ * `priority` column was hidden). `detail: { hidden: boolean }`.
50
82
  * @csspart base, table, head, header-cell, row, cell, more-button, sort-icon, reveal-columns-button
51
83
  */
52
84
  export class LyraTable extends LyraElement {
@@ -65,6 +97,16 @@ export class LyraTable extends LyraElement {
65
97
  this.noColumnsDescription = '';
66
98
  this.revealColumnsLabel = 'Show all columns';
67
99
  this.hideColumnsLabel = 'Show fewer columns';
100
+ /** Whether a `priority` column is *actually* hidden right now by
101
+ * table.styles.ts's `@container` rules, or `showAllColumns` force-visible
102
+ * mode is currently active — the same computed value that gates whether
103
+ * `[part='reveal-columns-button']` renders at all (see `render()`), kept
104
+ * in sync by `recomputeColumnsHidden()`. Computed/read-only by
105
+ * convention: consumers may read it (and listen for
106
+ * `lyra-columns-hidden-change`), but setting it directly has no lasting
107
+ * effect, since it's recomputed on the very next render or
108
+ * `[part='base']` resize. */
109
+ this.columnsHidden = false;
68
110
  /** Forces `priority`-hidden columns back into view, overriding the
69
111
  * `@container` hide rules in table.styles.ts. Toggled by
70
112
  * `[part='reveal-columns-button']`. */
@@ -84,12 +126,18 @@ export class LyraTable extends LyraElement {
84
126
  };
85
127
  this.onTableClick = (e) => {
86
128
  const target = e.target;
87
- // A cell()-rendered button/link/input etc. owns its own click — don't let
88
- // the delegated row/column resolution below re-interpret it as row or
89
- // header activation.
90
- if (target.closest(INTERACTIVE_SELECTOR))
129
+ const table = e.currentTarget;
130
+ // A cell()-rendered button/link/input/custom-element etc. owns its own
131
+ // click — don't let the delegated row/column resolution below
132
+ // re-interpret it as row or header activation.
133
+ if (closestInteractive(target, table))
91
134
  return;
92
- const th = target.closest('[data-col-key]');
135
+ // Scoped to `th` body `<td>`s also carry `data-col-key` now (for the
136
+ // sticky-offset measurement pass), so an unscoped `[data-col-key]` would
137
+ // match the clicked cell itself and misroute a plain cell click to
138
+ // column-sort activation instead of falling through to the row check
139
+ // below.
140
+ const th = target.closest('th[data-col-key]');
93
141
  if (th)
94
142
  return this.activateColumn(th.dataset.colKey);
95
143
  const tr = target.closest('[data-row-key]');
@@ -98,12 +146,14 @@ export class LyraTable extends LyraElement {
98
146
  };
99
147
  this.onTableKeyDown = (e) => {
100
148
  const target = e.target;
149
+ const table = e.currentTarget;
101
150
  // Same guard as onTableClick — also skips the table's own
102
151
  // preventDefault(), so a focused nested control keeps its native/own
103
152
  // Enter or Space activation instead of having it swallowed.
104
- if (target.closest(INTERACTIVE_SELECTOR))
153
+ if (closestInteractive(target, table))
105
154
  return;
106
- const th = target.closest('[data-col-key]');
155
+ // Same th-scoping rationale as onTableClick above.
156
+ const th = target.closest('th[data-col-key]');
107
157
  if (th)
108
158
  return this.onHeaderKeyDown(e, th);
109
159
  const tr = target.closest('[data-row-key]');
@@ -112,6 +162,50 @@ export class LyraTable extends LyraElement {
112
162
  };
113
163
  }
114
164
  static { this.styles = [LyraElement.styles, styles]; }
165
+ connectedCallback() {
166
+ super.connectedCallback();
167
+ this.resizeObserver = new ResizeObserver(() => this.recomputeColumnsHidden());
168
+ // A reconnect re-creates the observer above but the shadow root content
169
+ // survives across disconnect/reconnect (Lit doesn't tear down the shadow
170
+ // root) — re-observe [part='base'] here if it already exists from before
171
+ // the disconnect. On the very first mount connectedCallback() fires
172
+ // *before* Lit's first render, so [part='base'] doesn't exist yet and
173
+ // this is a no-op; updated() below (which always runs after render, first
174
+ // paint included) covers that case instead.
175
+ const base = this.renderRoot?.querySelector('[part="base"]');
176
+ if (base)
177
+ this.observeBase(base);
178
+ }
179
+ disconnectedCallback() {
180
+ super.disconnectedCallback();
181
+ this.resizeObserver?.disconnect();
182
+ this.observedBase = undefined;
183
+ }
184
+ observeBase(base) {
185
+ if (this.observedBase === base)
186
+ return;
187
+ if (this.observedBase)
188
+ this.resizeObserver?.unobserve(this.observedBase);
189
+ this.resizeObserver?.observe(base);
190
+ this.observedBase = base;
191
+ }
192
+ /** Recomputes `columnsHidden` from the live DOM (mirrors `visibleHeaders()`'s
193
+ * own `offsetParent !== null` technique for detecting `@container`-hidden
194
+ * cells) and dispatches `lyra-columns-hidden-change` only on a real
195
+ * transition. Called from `updated()` (covers a change driven by
196
+ * `columns`/`rows`/`showAllColumns` rather than a container resize) and
197
+ * from the `ResizeObserver` callback (covers a container resize with no
198
+ * Lit-tracked property change at all). */
199
+ recomputeColumnsHidden() {
200
+ const hasPriorityColumns = this.columns.some((col) => col.priority);
201
+ const anyPriorityHidden = hasPriorityColumns &&
202
+ [...this.renderRoot.querySelectorAll('th[data-priority]')].some((el) => el.offsetParent === null);
203
+ const next = anyPriorityHidden || (hasPriorityColumns && this.showAllColumns);
204
+ if (this.columnsHidden === next)
205
+ return;
206
+ this.columnsHidden = next;
207
+ this.emit('lyra-columns-hidden-change', { hidden: next });
208
+ }
115
209
  keyOf(row, index) {
116
210
  return this.rowKey ? this.rowKey(row) : index;
117
211
  }
@@ -128,20 +222,70 @@ export class LyraTable extends LyraElement {
128
222
  return this.activeRowKey;
129
223
  }
130
224
  if (this.selectedKey !== null) {
131
- const selected = String(this.selectedKey);
225
+ const selected = encodeKey(this.selectedKey);
132
226
  if (this.rowsByKey.has(selected))
133
227
  return selected;
134
228
  }
135
- return this.rows.length > 0 ? String(this.keyOf(this.rows[0], 0)) : null;
229
+ return this.rows.length > 0 ? encodeKey(this.keyOf(this.rows[0], 0)) : null;
136
230
  }
137
231
  willUpdate(changed) {
138
232
  if (changed.has('rows') || changed.has('rowKey')) {
139
- this.rowsByKey = new Map(this.rows.map((row, i) => [String(this.keyOf(row, i)), row]));
233
+ this.rowsByKey = new Map(this.rows.map((row, i) => [encodeKey(this.keyOf(row, i)), row]));
140
234
  }
141
235
  if (changed.has('columns')) {
142
236
  this.columnsByKey = new Map(this.columns.map((c) => [c.key, c]));
143
237
  }
144
238
  }
239
+ /** Each sticky column's cumulative left offset — the sum of the *rendered
240
+ * width* of every earlier sticky column — so multiple sticky columns
241
+ * stack left-to-right instead of all pinning to inset-inline-start: 0 and
242
+ * overlapping. Table columns are intrinsically sized (not fixed-width), so
243
+ * this can't be computed in CSS alone; it requires measuring the actual
244
+ * laid-out `offsetWidth` of each earlier sticky column's header cell. */
245
+ stickyOffsets() {
246
+ const offsets = new Map();
247
+ let running = 0;
248
+ for (const col of this.columns) {
249
+ if (!col.sticky)
250
+ continue;
251
+ offsets.set(col.key, running);
252
+ const headerEl = this.renderRoot.querySelector(`th[data-col-key="${col.key}"]`);
253
+ running += headerEl?.offsetWidth ?? 0;
254
+ }
255
+ return offsets;
256
+ }
257
+ /** Applies stickyOffsets()'s measured per-column offsets as an inline
258
+ * `--lyra-table-sticky-offset` custom property on every header cell and
259
+ * body cell in that column (addressed by the shared `data-col-key`
260
+ * attribute). This is a post-render DOM measurement — column widths
261
+ * aren't known until after the browser has laid out this update's
262
+ * render() output — so it runs from `updated()`, not `willUpdate()`,
263
+ * intentionally kept as a separate pass from the rowsByKey/columnsByKey
264
+ * rebuild above: those two must stay in `willUpdate()` so `render()`'s
265
+ * own `focusedRowKey()` call sees the current update's identity maps
266
+ * (e.g. a freshly-assigned `selectedKey` resolving to the correct
267
+ * roving-tabindex row on the very first paint), whereas the sticky-offset
268
+ * measurement can only run after that same paint has happened. Only runs
269
+ * when `hasSticky` is true (opt-in) and simply recomputes on every
270
+ * update; a future optimization pass could cache column widths instead of
271
+ * re-querying every time — out of scope for this correctness fix. */
272
+ updated() {
273
+ if (this.columns.some((c) => c.sticky)) {
274
+ for (const [key, offset] of this.stickyOffsets()) {
275
+ this.renderRoot
276
+ .querySelectorAll(`[data-col-key="${key}"]`)
277
+ .forEach((el) => el.style.setProperty('--lyra-table-sticky-offset', `${offset}px`));
278
+ }
279
+ }
280
+ // Re-observe [part='base'] whenever this update's render() produced a
281
+ // fresh one (first mount, or a swap to/from the <lyra-empty> template
282
+ // shape) — observeBase() itself no-ops when it's the same element as
283
+ // already observed.
284
+ const base = this.renderRoot.querySelector('[part="base"]');
285
+ if (base)
286
+ this.observeBase(base);
287
+ this.recomputeColumnsHidden();
288
+ }
145
289
  activateColumn(key) {
146
290
  this.activeColKey = key;
147
291
  const col = this.columnsByKey.get(key);
@@ -157,9 +301,11 @@ export class LyraTable extends LyraElement {
157
301
  /** Header cells currently in the tab sequence — excludes columns hidden by
158
302
  * a `priority`-driven `@container` rule (table.styles.ts), so Left/Right/
159
303
  * Home/End never strand the roving tab stop on a `display: none` cell
160
- * that `.focus()` would silently no-op on. */
304
+ * that `.focus()` would silently no-op on. Scoped to `th` — body `<td>`s
305
+ * now carry the same `data-col-key` attribute (for the sticky-offset
306
+ * measurement pass) but must never be treated as header cells here. */
161
307
  visibleHeaders() {
162
- return [...this.renderRoot.querySelectorAll('[data-col-key]')].filter((el) => el.offsetParent !== null);
308
+ return [...this.renderRoot.querySelectorAll('th[data-col-key]')].filter((el) => el.offsetParent !== null);
163
309
  }
164
310
  focusHeader(el) {
165
311
  if (!el?.dataset.colKey)
@@ -262,7 +408,6 @@ export class LyraTable extends LyraElement {
262
408
  description=${this.emptyDescription}
263
409
  ></lyra-empty>`;
264
410
  }
265
- const hasPriorityColumns = this.columns.some((col) => col.priority);
266
411
  const focusedCol = this.focusedColKey();
267
412
  const focusedRow = this.focusedRowKey();
268
413
  return html `
@@ -302,13 +447,14 @@ export class LyraTable extends LyraElement {
302
447
  return html `<tr
303
448
  part="row"
304
449
  role="row"
305
- data-row-key=${String(key)}
450
+ data-row-key=${encodeKey(key)}
306
451
  aria-selected=${selected ? 'true' : 'false'}
307
- tabindex=${String(key) === focusedRow ? '0' : '-1'}
452
+ tabindex=${encodeKey(key) === focusedRow ? '0' : '-1'}
308
453
  >
309
454
  ${this.columns.map((col) => html `<td
310
455
  part="cell"
311
456
  role="gridcell"
457
+ data-col-key=${col.key}
312
458
  data-align=${col.align ?? 'start'}
313
459
  data-priority=${col.priority ?? nothing}
314
460
  ?data-sticky=${col.sticky}
@@ -319,7 +465,7 @@ export class LyraTable extends LyraElement {
319
465
  })}
320
466
  </tbody>
321
467
  </table>
322
- ${hasPriorityColumns
468
+ ${this.columnsHidden
323
469
  ? html `<button
324
470
  part="reveal-columns-button"
325
471
  type="button"
@@ -384,6 +530,9 @@ __decorate([
384
530
  __decorate([
385
531
  property({ attribute: 'hide-columns-label' })
386
532
  ], LyraTable.prototype, "hideColumnsLabel", void 0);
533
+ __decorate([
534
+ property({ type: Boolean, attribute: 'columns-hidden', reflect: true })
535
+ ], LyraTable.prototype, "columnsHidden", void 0);
387
536
  __decorate([
388
537
  state()
389
538
  ], LyraTable.prototype, "showAllColumns", void 0);
@@ -115,7 +115,12 @@ export const styles = css `
115
115
  [part='header-cell'][data-sticky],
116
116
  [part='cell'][data-sticky] {
117
117
  position: sticky;
118
- inset-inline-start: 0;
118
+ /* Set per-column by table.ts's stickyOffsets()/updated(), which measures
119
+ each earlier sticky column's rendered width so multiple sticky columns
120
+ stack left-to-right instead of all pinning to the same edge and
121
+ overlapping. Falls back to 0 for the first sticky column (or before
122
+ the first measurement pass has run). */
123
+ inset-inline-start: var(--lyra-table-sticky-offset, 0);
119
124
  z-index: 1;
120
125
  background: var(--lyra-color-surface);
121
126
  box-shadow: 1px 0 0 0 var(--lyra-color-border);
@@ -0,0 +1,51 @@
1
+ import { type TemplateResult, type PropertyValues } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ /**
4
+ * `<lyra-tabs>` — a tab strip whose panels are direct light-DOM children,
5
+ * each carrying `slot="<id>"` (the panel's stable id) and `label="<text>"`
6
+ * (the tab button's text). One named `<slot>` is rendered per distinct
7
+ * `slot` name found among the current children — a child with no `label`,
8
+ * or a name with no matching child, simply never produces a tab.
9
+ *
10
+ * Implements the WAI-ARIA APG tabs pattern with automatic activation:
11
+ * Left/Right (swapped under RTL) move focus *and* selection together,
12
+ * Home/End jump to the first/last enabled tab, and a roving `tabindex`
13
+ * follows whichever tab is currently selected.
14
+ *
15
+ * @customElement lyra-tabs
16
+ * @slot - Direct children with `slot="<id>" label="<text>"` (and optionally `disabled`); one becomes each tab's panel.
17
+ * @event lyra-tabs-change - `detail: { tabId }`, fired when the active tab changes via click or keyboard.
18
+ * @csspart base - The root wrapper around the tablist and panels.
19
+ * @csspart tablist - The `role="tablist"` row of tab buttons.
20
+ * @csspart tab - A single tab button.
21
+ * @csspart panel - A single `role="tabpanel"` wrapper (one per tab, hidden unless active).
22
+ */
23
+ export declare class LyraTabs extends LyraElement {
24
+ static styles: import("lit").CSSResultGroup[];
25
+ /** The active tab's `slot`/id. Falls back to the first enabled tab whenever the current value doesn't resolve to one. */
26
+ active: string;
27
+ private tabs;
28
+ private baseId;
29
+ private mutationObserver?;
30
+ connectedCallback(): void;
31
+ disconnectedCallback(): void;
32
+ /** Rebuilds `tabs` from the current direct children. First child wins when two share a `slot` name (matches native slot assignment: both would render into the one panel, but only one label can back the button). */
33
+ private syncTabs;
34
+ /** Keeps `active` resolved to a real, enabled tab -- covers the initial default, a tab disappearing/becoming disabled underneath the current selection, and a consumer assigning `.active` directly. Silent (no `lyra-tabs-change`): this corrects *invalid* state rather than responding to a user picking a different tab. */
35
+ protected willUpdate(changed: PropertyValues): void;
36
+ /** Activates `tab` (no-op for a disabled tab or one that's already active) and emits `lyra-tabs-change`. */
37
+ private selectTab;
38
+ /** Moves real DOM focus to tab `slotName`'s button. Safe to call immediately (no `updateComplete` wait): every tab button already exists in the DOM regardless of its current `tabindex`, and `tabindex="-1"` elements are still focusable via script. */
39
+ private focusTab;
40
+ private onTabListKeyDown;
41
+ private tabId;
42
+ private panelId;
43
+ private renderTab;
44
+ private renderPanel;
45
+ render(): TemplateResult;
46
+ }
47
+ declare global {
48
+ interface HTMLElementTagNameMap {
49
+ 'lyra-tabs': LyraTabs;
50
+ }
51
+ }
@@ -0,0 +1,190 @@
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, state } from 'lit/decorators.js';
9
+ import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { defineElement } from '../../internal/prefix.js';
11
+ import { isRtl } from '../../internal/rtl.js';
12
+ import { nextId } from '../../internal/a11y.js';
13
+ import { styles } from './tabs.styles.js';
14
+ /**
15
+ * `<lyra-tabs>` — a tab strip whose panels are direct light-DOM children,
16
+ * each carrying `slot="<id>"` (the panel's stable id) and `label="<text>"`
17
+ * (the tab button's text). One named `<slot>` is rendered per distinct
18
+ * `slot` name found among the current children — a child with no `label`,
19
+ * or a name with no matching child, simply never produces a tab.
20
+ *
21
+ * Implements the WAI-ARIA APG tabs pattern with automatic activation:
22
+ * Left/Right (swapped under RTL) move focus *and* selection together,
23
+ * Home/End jump to the first/last enabled tab, and a roving `tabindex`
24
+ * follows whichever tab is currently selected.
25
+ *
26
+ * @customElement lyra-tabs
27
+ * @slot - Direct children with `slot="<id>" label="<text>"` (and optionally `disabled`); one becomes each tab's panel.
28
+ * @event lyra-tabs-change - `detail: { tabId }`, fired when the active tab changes via click or keyboard.
29
+ * @csspart base - The root wrapper around the tablist and panels.
30
+ * @csspart tablist - The `role="tablist"` row of tab buttons.
31
+ * @csspart tab - A single tab button.
32
+ * @csspart panel - A single `role="tabpanel"` wrapper (one per tab, hidden unless active).
33
+ */
34
+ export class LyraTabs extends LyraElement {
35
+ constructor() {
36
+ super(...arguments);
37
+ /** The active tab's `slot`/id. Falls back to the first enabled tab whenever the current value doesn't resolve to one. */
38
+ this.active = '';
39
+ this.tabs = [];
40
+ this.baseId = nextId('tabs');
41
+ /** Rebuilds `tabs` from the current direct children. First child wins when two share a `slot` name (matches native slot assignment: both would render into the one panel, but only one label can back the button). */
42
+ this.syncTabs = () => {
43
+ const seen = new Set();
44
+ const next = [];
45
+ for (const child of Array.from(this.children)) {
46
+ const slotName = child.getAttribute('slot');
47
+ const label = child.getAttribute('label');
48
+ if (!slotName || !label || seen.has(slotName))
49
+ continue;
50
+ seen.add(slotName);
51
+ next.push({ slotName, label, disabled: child.hasAttribute('disabled') });
52
+ }
53
+ this.tabs = next;
54
+ };
55
+ this.onTabListKeyDown = (e) => {
56
+ const navigable = this.tabs.filter((t) => !t.disabled);
57
+ if (navigable.length === 0)
58
+ return;
59
+ const currentIndex = navigable.findIndex((t) => t.slotName === this.active);
60
+ // Horizontal strip -- Up/Down aren't used. Left/Right swap under RTL,
61
+ // matching lyra-split/lyra-tree's physical-direction handling.
62
+ const rtl = isRtl(this);
63
+ const forwardKey = rtl ? 'ArrowLeft' : 'ArrowRight';
64
+ const backwardKey = rtl ? 'ArrowRight' : 'ArrowLeft';
65
+ let targetIndex;
66
+ switch (e.key) {
67
+ case forwardKey:
68
+ targetIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % navigable.length;
69
+ break;
70
+ case backwardKey:
71
+ targetIndex = currentIndex < 0 ? navigable.length - 1 : (currentIndex - 1 + navigable.length) % navigable.length;
72
+ break;
73
+ case 'Home':
74
+ targetIndex = 0;
75
+ break;
76
+ case 'End':
77
+ targetIndex = navigable.length - 1;
78
+ break;
79
+ default:
80
+ return;
81
+ }
82
+ e.preventDefault();
83
+ const target = navigable[targetIndex];
84
+ this.selectTab(target);
85
+ this.focusTab(target.slotName);
86
+ };
87
+ }
88
+ static { this.styles = [LyraElement.styles, styles]; }
89
+ connectedCallback() {
90
+ super.connectedCallback();
91
+ this.syncTabs();
92
+ // Each child carries its own individual `slot` attribute (one named slot
93
+ // per tab, unlike lyra-split's single default-slot-of-many-panels) -- a
94
+ // brand-new tab's name has no matching `<slot>` to fire `slotchange` on
95
+ // until *this* component has already rendered one for it, and neither
96
+ // `slotchange` nor any Lit lifecycle hook observes a plain attribute
97
+ // edit (label/disabled) on a light-DOM child at all. A mutation observer
98
+ // on the host is the only thing that sees either case.
99
+ this.mutationObserver = new MutationObserver(this.syncTabs);
100
+ this.mutationObserver.observe(this, {
101
+ childList: true,
102
+ attributes: true,
103
+ attributeFilter: ['slot', 'label', 'disabled'],
104
+ });
105
+ }
106
+ disconnectedCallback() {
107
+ super.disconnectedCallback();
108
+ this.mutationObserver?.disconnect();
109
+ this.mutationObserver = undefined;
110
+ }
111
+ /** Keeps `active` resolved to a real, enabled tab -- covers the initial default, a tab disappearing/becoming disabled underneath the current selection, and a consumer assigning `.active` directly. Silent (no `lyra-tabs-change`): this corrects *invalid* state rather than responding to a user picking a different tab. */
112
+ willUpdate(changed) {
113
+ if (!changed.has('tabs') && !changed.has('active'))
114
+ return;
115
+ const current = this.tabs.find((t) => t.slotName === this.active);
116
+ if (current && !current.disabled)
117
+ return;
118
+ this.active = this.tabs.find((t) => !t.disabled)?.slotName ?? '';
119
+ }
120
+ /** Activates `tab` (no-op for a disabled tab or one that's already active) and emits `lyra-tabs-change`. */
121
+ selectTab(tab) {
122
+ if (tab.disabled || tab.slotName === this.active)
123
+ return;
124
+ this.active = tab.slotName;
125
+ this.emit('lyra-tabs-change', { tabId: tab.slotName });
126
+ }
127
+ /** Moves real DOM focus to tab `slotName`'s button. Safe to call immediately (no `updateComplete` wait): every tab button already exists in the DOM regardless of its current `tabindex`, and `tabindex="-1"` elements are still focusable via script. */
128
+ focusTab(slotName) {
129
+ const buttons = this.renderRoot.querySelectorAll('[part="tab"]');
130
+ for (const button of Array.from(buttons)) {
131
+ if (button.dataset.slot === slotName) {
132
+ button.focus();
133
+ return;
134
+ }
135
+ }
136
+ }
137
+ tabId(slotName) {
138
+ return `${this.baseId}-tab-${slotName}`;
139
+ }
140
+ panelId(slotName) {
141
+ return `${this.baseId}-panel-${slotName}`;
142
+ }
143
+ renderTab(tab) {
144
+ const selected = tab.slotName === this.active;
145
+ return html `<button
146
+ type="button"
147
+ part="tab"
148
+ id=${this.tabId(tab.slotName)}
149
+ data-slot=${tab.slotName}
150
+ role="tab"
151
+ aria-selected=${selected ? 'true' : 'false'}
152
+ aria-disabled=${tab.disabled ? 'true' : 'false'}
153
+ aria-controls=${this.panelId(tab.slotName)}
154
+ tabindex=${selected ? '0' : '-1'}
155
+ @click=${() => this.selectTab(tab)}
156
+ >
157
+ ${tab.label}
158
+ </button>`;
159
+ }
160
+ renderPanel(tab) {
161
+ const selected = tab.slotName === this.active;
162
+ return html `<div
163
+ part="panel"
164
+ id=${this.panelId(tab.slotName)}
165
+ role="tabpanel"
166
+ aria-labelledby=${this.tabId(tab.slotName)}
167
+ tabindex="0"
168
+ ?hidden=${!selected}
169
+ >
170
+ <slot name=${tab.slotName}></slot>
171
+ </div>`;
172
+ }
173
+ render() {
174
+ return html `
175
+ <div part="base">
176
+ <div part="tablist" role="tablist" aria-orientation="horizontal" @keydown=${this.onTabListKeyDown}>
177
+ ${this.tabs.map((tab) => this.renderTab(tab))}
178
+ </div>
179
+ ${this.tabs.map((tab) => this.renderPanel(tab))}
180
+ </div>
181
+ `;
182
+ }
183
+ }
184
+ __decorate([
185
+ property({ reflect: true })
186
+ ], LyraTabs.prototype, "active", void 0);
187
+ __decorate([
188
+ state()
189
+ ], LyraTabs.prototype, "tabs", void 0);
190
+ defineElement('tabs', LyraTabs);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,63 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ }
6
+ [part='base'] {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--lyra-space-s);
10
+ }
11
+ [part='tablist'] {
12
+ display: flex;
13
+ align-items: stretch;
14
+ gap: var(--lyra-space-m);
15
+ border-block-end: 1px solid var(--lyra-color-border);
16
+ overflow-x: auto;
17
+ }
18
+ [part='tab'] {
19
+ appearance: none;
20
+ background: none;
21
+ border: none;
22
+ /* Sits directly over the tablist's own border-block-end so the accent
23
+ underline below replaces it, pixel for pixel, when selected. */
24
+ border-block-end: 2px solid transparent;
25
+ margin-block-end: -1px;
26
+ padding: var(--lyra-space-s) var(--lyra-space-xs);
27
+ font: inherit;
28
+ font-weight: 500;
29
+ color: var(--lyra-color-text-quiet);
30
+ cursor: pointer;
31
+ white-space: nowrap;
32
+ transition:
33
+ color var(--lyra-transition-fast),
34
+ border-color var(--lyra-transition-fast);
35
+ }
36
+ [part='tab']:hover:not([aria-disabled='true']) {
37
+ color: var(--lyra-color-text);
38
+ }
39
+ [part='tab'][aria-selected='true'] {
40
+ color: var(--lyra-color-brand);
41
+ border-block-end-color: var(--lyra-color-brand);
42
+ }
43
+ [part='tab'][aria-disabled='true'] {
44
+ cursor: not-allowed;
45
+ /* No :hover color change and no pointer feedback -- the click handler
46
+ already no-ops on a disabled tab, this just matches it visually. */
47
+ pointer-events: none;
48
+ opacity: var(--lyra-opacity-disabled);
49
+ }
50
+ [part='tab']:focus-visible {
51
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
52
+ outline-offset: calc(var(--lyra-focus-ring-offset) * -1);
53
+ border-radius: var(--lyra-radius);
54
+ }
55
+ [part='panel'] {
56
+ padding-block-start: var(--lyra-space-xs);
57
+ }
58
+ [part='panel']:focus-visible {
59
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
60
+ outline-offset: var(--lyra-focus-ring-offset);
61
+ border-radius: var(--lyra-radius);
62
+ }
63
+ `;