@aceshooting/lyra-ui 16.0.0 → 18.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 (358) hide show
  1. package/CHANGELOG.md +758 -0
  2. package/README.md +2 -2
  3. package/custom-elements.json +1 -1
  4. package/dist/cli/migration-contract.json +1 -1
  5. package/dist/components/agent-tools/browser-frame/browser-frame.class.d.ts +3 -3
  6. package/dist/components/agent-tools/browser-frame/browser-frame.class.js +2 -2
  7. package/dist/components/agent-tools/compare-panel/compare-panel.class.d.ts +2 -2
  8. package/dist/components/agent-tools/compare-panel/compare-panel.class.js +3 -3
  9. package/dist/components/agent-tools/confirm-bar/confirm-bar.class.d.ts +37 -13
  10. package/dist/components/agent-tools/confirm-bar/confirm-bar.class.js +2 -2
  11. package/dist/components/agent-tools/task-list/task-list.class.js +2 -2
  12. package/dist/components/agent-tools/tool-call-chip/tool-call-chip.class.d.ts +4 -0
  13. package/dist/components/agent-tools/tool-call-chip/tool-call-chip.class.js +2 -2
  14. package/dist/components/agent-tools/tool-param-form/tool-param-form.class.d.ts +3 -3
  15. package/dist/components/agent-tools/tool-param-form/tool-param-form.class.js +1 -1
  16. package/dist/components/charts/chart/chart.class.d.ts +117 -17
  17. package/dist/components/charts/chart/chart.class.js +3 -3
  18. package/dist/components/charts/chart/histogram-bin.d.ts +5 -1
  19. package/dist/components/charts/chart/histogram-bin.js +1 -1
  20. package/dist/components/charts/chart/lite-chart.class.d.ts +9 -0
  21. package/dist/components/conversation/code-block/code-block.styles.js +1 -1
  22. package/dist/components/conversation/message-actions/message-actions.styles.js +1 -1
  23. package/dist/components/conversation/model-select/model-select.class.d.ts +13 -4
  24. package/dist/components/conversation/model-select/model-select.class.js +2 -1
  25. package/dist/components/conversation/model-select/model-select.styles.js +1 -1
  26. package/dist/components/conversation/streaming-text/streaming-text-base.class.d.ts +40 -2
  27. package/dist/components/conversation/streaming-text/streaming-text-base.class.js +2 -2
  28. package/dist/components/conversation/streaming-text/streaming-text-core.class.d.ts +27 -3
  29. package/dist/components/conversation/streaming-text/streaming-text-core.class.js +11 -0
  30. package/dist/components/conversation/streaming-text/streaming-text.class.d.ts +31 -3
  31. package/dist/components/conversation/streaming-text/streaming-text.class.js +11 -0
  32. package/dist/components/conversation/suggestion-chips/suggestion-chips.class.d.ts +30 -2
  33. package/dist/components/conversation/suggestion-chips/suggestion-chips.class.js +4 -3
  34. package/dist/components/conversation/suggestion-chips/suggestion-chips.styles.js +1 -1
  35. package/dist/components/conversation/thread-list/thread-list.class.js +1 -1
  36. package/dist/components/conversation/transcript-feed/transcript-feed.class.js +1 -1
  37. package/dist/components/conversation/usage-badge/usage-badge-overlay-runtime.js +1 -1
  38. package/dist/components/conversation/usage-badge/usage-badge.class.d.ts +4 -0
  39. package/dist/components/conversation/voice-picker/voice-picker.class.d.ts +16 -4
  40. package/dist/components/conversation/voice-picker/voice-picker.class.js +2 -1
  41. package/dist/components/conversation/voice-picker/voice-picker.styles.js +1 -1
  42. package/dist/components/data/context-meter/context-meter.class.d.ts +23 -4
  43. package/dist/components/data/context-meter/context-meter.class.js +10 -7
  44. package/dist/components/data/context-meter/context-meter.styles.js +1 -1
  45. package/dist/components/data/data-grid/data-grid.class.d.ts +5 -2
  46. package/dist/components/data/data-grid/data-grid.class.js +1 -1
  47. package/dist/components/data/flow-canvas/flow-canvas.class.d.ts +32 -5
  48. package/dist/components/data/flow-canvas/flow-canvas.class.js +3 -1
  49. package/dist/components/data/flow-canvas/flow-canvas.styles.js +1 -1
  50. package/dist/components/data/flow-canvas/flow-model.js +1 -1
  51. package/dist/components/data/flow-canvas/flow-types.d.ts +10 -1
  52. package/dist/components/data/gauge/gauge.class.d.ts +15 -2
  53. package/dist/components/data/gauge/gauge.class.js +20 -20
  54. package/dist/components/data/gauge/gauge.styles.js +1 -1
  55. package/dist/components/data/graph-query-builder/graph-query-builder.class.d.ts +14 -5
  56. package/dist/components/data/graph-query-builder/graph-query-builder.class.js +2 -2
  57. package/dist/components/data/sequence-strip/sequence-strip.class.d.ts +28 -2
  58. package/dist/components/data/sequence-strip/sequence-strip.class.js +3 -2
  59. package/dist/components/data/sequence-strip/sequence-strip.styles.js +1 -1
  60. package/dist/components/data/table/table.class.d.ts +155 -30
  61. package/dist/components/data/table/table.class.js +6 -4
  62. package/dist/components/data/table/table.styles.js +1 -1
  63. package/dist/components/data/timeline/timeline-item.class.d.ts +9 -1
  64. package/dist/components/data/timeline/timeline-item.class.js +2 -2
  65. package/dist/components/data/word-cloud/word-cloud.class.js +1 -1
  66. package/dist/components/data/word-cloud/word-cloud.styles.js +1 -1
  67. package/dist/components/forms/button/button.class.d.ts +30 -1
  68. package/dist/components/forms/button/button.class.js +2 -2
  69. package/dist/components/forms/checkbox/checkbox.class.d.ts +8 -6
  70. package/dist/components/forms/checkbox/checkbox.class.js +2 -2
  71. package/dist/components/forms/checkbox-group/checkbox-group.class.d.ts +7 -5
  72. package/dist/components/forms/checkbox-group/checkbox-group.class.js +1 -1
  73. package/dist/components/forms/color-picker/color-picker.class.d.ts +16 -2
  74. package/dist/components/forms/color-picker/color-picker.class.js +2 -2
  75. package/dist/components/forms/color-picker/color-picker.styles.js +1 -1
  76. package/dist/components/forms/combobox/combobox.class.d.ts +52 -7
  77. package/dist/components/forms/combobox/combobox.class.js +2 -2
  78. package/dist/components/forms/combobox/combobox.styles.js +1 -1
  79. package/dist/components/forms/date-picker/date-input.class.d.ts +4 -0
  80. package/dist/components/forms/date-picker/date-input.class.js +1 -1
  81. package/dist/components/forms/icon-button/icon-button.class.d.ts +20 -3
  82. package/dist/components/forms/icon-button/icon-button.class.js +1 -1
  83. package/dist/components/forms/icon-button/icon-button.styles.js +1 -1
  84. package/dist/components/forms/input/input.class.d.ts +5 -2
  85. package/dist/components/forms/input/input.class.js +1 -1
  86. package/dist/components/forms/input/time-input.class.d.ts +4 -0
  87. package/dist/components/forms/input/time-input.class.js +1 -1
  88. package/dist/components/forms/locale-picker/locale-picker.class.d.ts +11 -5
  89. package/dist/components/forms/locale-picker/locale-picker.class.js +1 -1
  90. package/dist/components/forms/otp-input/otp-input.class.d.ts +7 -2
  91. package/dist/components/forms/otp-input/otp-input.class.js +2 -2
  92. package/dist/components/forms/radio/radio-button.class.d.ts +2 -1
  93. package/dist/components/forms/radio/radio-group.class.d.ts +9 -4
  94. package/dist/components/forms/radio/radio-group.class.js +1 -1
  95. package/dist/components/forms/radio/radio.class.d.ts +13 -7
  96. package/dist/components/forms/radio/radio.class.js +1 -1
  97. package/dist/components/forms/rubric-form/rubric-form.class.d.ts +16 -6
  98. package/dist/components/forms/rubric-form/rubric-form.class.js +2 -2
  99. package/dist/components/forms/select/select.class.d.ts +50 -13
  100. package/dist/components/forms/select/select.class.js +2 -2
  101. package/dist/components/forms/slider/slider.class.d.ts +4 -2
  102. package/dist/components/forms/slider/slider.class.js +1 -1
  103. package/dist/components/forms/switch/switch.class.d.ts +8 -6
  104. package/dist/components/forms/switch/switch.class.js +1 -1
  105. package/dist/components/forms/textarea/textarea.class.d.ts +5 -3
  106. package/dist/components/forms/textarea/textarea.class.js +1 -1
  107. package/dist/components/forms/time-range/time-range.class.d.ts +12 -7
  108. package/dist/components/forms/time-range/time-range.class.js +1 -1
  109. package/dist/components/forms/time-range/time-range.styles.js +1 -1
  110. package/dist/components/forms/token-input/token-input.class.d.ts +6 -4
  111. package/dist/components/forms/token-input/token-input.class.js +1 -1
  112. package/dist/components/layout/app-rail/app-rail-item.class.d.ts +109 -4
  113. package/dist/components/layout/app-rail/app-rail-item.class.js +34 -29
  114. package/dist/components/layout/app-rail/app-rail-item.styles.js +1 -1
  115. package/dist/components/layout/app-rail/app-rail.class.d.ts +25 -3
  116. package/dist/components/layout/app-rail/app-rail.styles.js +1 -1
  117. package/dist/components/layout/app-rail-group/app-rail-group.class.d.ts +2 -2
  118. package/dist/components/layout/app-rail-group/app-rail-group.class.js +2 -2
  119. package/dist/components/layout/carousel/carousel.class.d.ts +1 -1
  120. package/dist/components/layout/carousel/carousel.class.js +1 -1
  121. package/dist/components/layout/details/accordion.class.d.ts +2 -2
  122. package/dist/components/layout/details/accordion.class.js +2 -2
  123. package/dist/components/layout/filter-bar/filter-bar-register.d.ts +1 -0
  124. package/dist/components/layout/filter-bar/filter-bar-register.js +1 -0
  125. package/dist/components/layout/filter-bar/filter-bar.class.d.ts +153 -16
  126. package/dist/components/layout/filter-bar/filter-bar.class.js +8 -5
  127. package/dist/components/layout/menu/menu-item.class.d.ts +3 -3
  128. package/dist/components/layout/menu/menu-item.class.js +3 -3
  129. package/dist/components/layout/menu/menu.class.d.ts +5 -1
  130. package/dist/components/layout/menu/menu.class.js +2 -2
  131. package/dist/components/layout/reorder-list/reorder-item.styles.js +1 -1
  132. package/dist/components/layout/reorder-list/reorder-list.class.js +1 -1
  133. package/dist/components/layout/virtual-list/virtual-list.class.d.ts +107 -2
  134. package/dist/components/layout/virtual-list/virtual-list.class.js +12 -7
  135. package/dist/components/media/attachment-trigger/attachment-trigger.styles.js +1 -1
  136. package/dist/components/media/avatar-group/avatar-group.class.d.ts +12 -1
  137. package/dist/components/media/avatar-group/avatar-group.class.js +2 -2
  138. package/dist/components/media/drop-zone/drop-zone.class.d.ts +32 -11
  139. package/dist/components/media/drop-zone/drop-zone.class.js +2 -2
  140. package/dist/components/media/file-input/file-input.class.d.ts +40 -19
  141. package/dist/components/media/file-input/file-input.class.js +2 -2
  142. package/dist/components/media/map/map.class.d.ts +138 -12
  143. package/dist/components/media/map/map.class.js +25 -20
  144. package/dist/components/media/map/map.styles.js +1 -1
  145. package/dist/components/overlays/callout/callout.styles.js +1 -1
  146. package/dist/components/overlays/dialog/dialog.styles.js +1 -1
  147. package/dist/components/overlays/overlay/dropdown.class.d.ts +13 -3
  148. package/dist/components/overlays/overlay/dropdown.class.js +2 -2
  149. package/dist/components/overlays/overlay/popover.class.d.ts +12 -3
  150. package/dist/components/overlays/overlay/popover.class.js +2 -2
  151. package/dist/components/overlays/rating/rating.class.d.ts +9 -7
  152. package/dist/components/overlays/rating/rating.class.js +1 -1
  153. package/dist/components/retrieval/citation-badge/citation-badge.class.d.ts +5 -1
  154. package/dist/components/retrieval/citation-badge/citation-badge.class.js +1 -1
  155. package/dist/components/retrieval/embedding-explorer/embedding-explorer.styles.js +1 -1
  156. package/dist/components/retrieval/entity-chip/entity-chip.class.d.ts +5 -1
  157. package/dist/components/retrieval/entity-chip/entity-chip.class.js +1 -1
  158. package/dist/components/retrieval/graph-legend/graph-legend.styles.js +1 -1
  159. package/dist/components/retrieval/retrieval-value-format.js +1 -1
  160. package/dist/components/retrieval/source-list/source-list.styles.js +1 -1
  161. package/dist/components/utility/copy-button/copy-button.class.d.ts +23 -11
  162. package/dist/components/utility/copy-button/copy-button.class.js +2 -2
  163. package/dist/components/utility/copy-button/copy-button.styles.js +1 -1
  164. package/dist/components/utility/diff-view/diff-view.styles.js +1 -1
  165. package/dist/components/utility/export-button/export-button.class.d.ts +4 -0
  166. package/dist/components/utility/export-button/export-button.class.js +1 -1
  167. package/dist/components/utility/json-viewer/json-viewer.styles.js +1 -1
  168. package/dist/components/utility/known-date/known-date.class.js +1 -1
  169. package/dist/components/utility/mention-popover/mention-popover.class.d.ts +38 -3
  170. package/dist/components/utility/mention-popover/mention-popover.class.js +2 -1
  171. package/dist/components/utility/mention-popover/mention-popover.styles.js +1 -1
  172. package/dist/components/utility/poll-status/poll-status.styles.js +1 -1
  173. package/dist/components/utility/tour/tour.class.d.ts +4 -0
  174. package/dist/components/utility/tour/tour.class.js +1 -1
  175. package/dist/components/utility/tour/tour.styles.js +1 -1
  176. package/dist/components/viewers/document-preview/document-preview.class.js +2 -2
  177. package/dist/components/viewers/docx-viewer/docx-viewer.class.js +2 -2
  178. package/dist/components/viewers/ebook-viewer/ebook-viewer.class.js +2 -2
  179. package/dist/components/viewers/highlight-layer/highlight-layer.class.js +2 -2
  180. package/dist/components/viewers/notebook-viewer/notebook-viewer.class.js +2 -2
  181. package/dist/components/viewers/pdf-viewer/pdf-viewer.class.js +2 -2
  182. package/dist/components/viewers/pptx-viewer/pptx-viewer.class.js +2 -2
  183. package/dist/components/viewers/xml-viewer/xml-viewer.class.js +2 -2
  184. package/dist/custom-elements-jsx.d.ts +2 -2
  185. package/dist/events.d.ts +20 -13
  186. package/dist/internal/aggregate-file-limits.d.ts +33 -0
  187. package/dist/internal/aggregate-file-limits.js +1 -0
  188. package/dist/internal/catalog-picker.d.ts +15 -2
  189. package/dist/internal/catalog-picker.js +1 -1
  190. package/dist/internal/debounce-controller.d.ts +14 -0
  191. package/dist/internal/debounce-controller.js +1 -1
  192. package/dist/internal/default-strings.generated.d.ts +1 -1
  193. package/dist/internal/default-strings.generated.js +2 -2
  194. package/dist/internal/focus-navigation.d.ts +45 -0
  195. package/dist/internal/focus-navigation.js +1 -1
  196. package/dist/internal/form-associated.js +1 -1
  197. package/dist/internal/initial-slot-collection.d.ts +30 -0
  198. package/dist/internal/initial-slot-collection.js +1 -0
  199. package/dist/internal/intl-cache.js +1 -1
  200. package/dist/internal/invalid-event-alias.d.ts +46 -0
  201. package/dist/internal/invalid-event-alias.js +1 -1
  202. package/dist/internal/localization-runtime.d.ts +24 -0
  203. package/dist/internal/localization-runtime.js +1 -1
  204. package/dist/internal/localization-types.d.ts +1 -1
  205. package/dist/internal/localization.js +1 -1
  206. package/dist/internal/package-metadata.d.ts +1 -1
  207. package/dist/internal/package-metadata.js +1 -1
  208. package/dist/internal/positioning-strategy.d.ts +3 -1
  209. package/dist/internal/state-part.d.ts +49 -0
  210. package/dist/internal/state-part.js +1 -0
  211. package/dist/internal/variants.styles.js +1 -1
  212. package/dist/lyra.d.ts +1 -1
  213. package/dist/lyra.js +1 -1
  214. package/dist/svelte.d.ts +2 -2
  215. package/dist/testing/index.d.ts +1 -1
  216. package/dist/testing/index.js +1 -1
  217. package/dist/testing/lyra-tag-event-map.d.ts +2 -2
  218. package/dist/testing/lyra-tag-event-map.js +1 -1
  219. package/dist/testing/wait-for-mount.d.ts +46 -0
  220. package/dist/testing/wait-for-mount.js +1 -0
  221. package/dist/theme/theme-bootstrap.js +1 -1
  222. package/dist/theme/theme.d.ts +11 -2
  223. package/dist/theme/theme.js +1 -1
  224. package/dist/translations/ar/layout.js +1 -1
  225. package/dist/translations/ar/retrieval.js +1 -1
  226. package/dist/translations/ar/shared.js +1 -1
  227. package/dist/translations/de/layout.js +1 -1
  228. package/dist/translations/de/retrieval.js +1 -1
  229. package/dist/translations/de/shared.js +1 -1
  230. package/dist/translations/es/layout.js +1 -1
  231. package/dist/translations/es/retrieval.js +1 -1
  232. package/dist/translations/es/shared.js +1 -1
  233. package/dist/translations/fa/layout.js +1 -1
  234. package/dist/translations/fa/retrieval.js +1 -1
  235. package/dist/translations/fa/shared.js +1 -1
  236. package/dist/translations/fr/layout.js +1 -1
  237. package/dist/translations/fr/retrieval.js +1 -1
  238. package/dist/translations/fr/shared.js +1 -1
  239. package/dist/translations/he/layout.js +1 -1
  240. package/dist/translations/he/retrieval.js +1 -1
  241. package/dist/translations/he/shared.js +1 -1
  242. package/dist/translations/it/layout.js +1 -1
  243. package/dist/translations/it/retrieval.js +1 -1
  244. package/dist/translations/it/shared.js +1 -1
  245. package/dist/translations/ja/layout.js +1 -1
  246. package/dist/translations/ja/retrieval.js +1 -1
  247. package/dist/translations/ja/shared.js +1 -1
  248. package/dist/translations/pt-BR/layout.js +1 -1
  249. package/dist/translations/pt-BR/retrieval.js +1 -1
  250. package/dist/translations/pt-BR/shared.js +1 -1
  251. package/dist/translations/ro/agent-tools.d.ts +1 -0
  252. package/dist/translations/ro/agent-tools.js +1 -0
  253. package/dist/translations/ro/charts.d.ts +1 -0
  254. package/dist/translations/ro/charts.js +1 -0
  255. package/dist/translations/ro/conversation.d.ts +1 -0
  256. package/dist/translations/ro/conversation.js +1 -0
  257. package/dist/translations/ro/data.d.ts +1 -0
  258. package/dist/translations/ro/data.js +1 -0
  259. package/dist/translations/ro/forms.d.ts +1 -0
  260. package/dist/translations/ro/forms.js +1 -0
  261. package/dist/translations/ro/layout.d.ts +1 -0
  262. package/dist/translations/ro/layout.js +1 -0
  263. package/dist/translations/ro/media.d.ts +1 -0
  264. package/dist/translations/ro/media.js +1 -0
  265. package/dist/translations/ro/overlays.d.ts +1 -0
  266. package/dist/translations/ro/overlays.js +1 -0
  267. package/dist/translations/ro/retrieval.d.ts +1 -0
  268. package/dist/translations/ro/retrieval.js +1 -0
  269. package/dist/translations/ro/shared.d.ts +1 -0
  270. package/dist/translations/ro/shared.js +1 -0
  271. package/dist/translations/ro/utility.d.ts +1 -0
  272. package/dist/translations/ro/utility.js +1 -0
  273. package/dist/translations/ro/viewers.d.ts +1 -0
  274. package/dist/translations/ro/viewers.js +5 -0
  275. package/dist/translations/ro.d.ts +1 -0
  276. package/dist/translations/ro.js +1 -0
  277. package/dist/translations/ru/layout.js +1 -1
  278. package/dist/translations/ru/retrieval.js +1 -1
  279. package/dist/translations/ru/shared.js +1 -1
  280. package/dist/translations/zh-CN/layout.js +1 -1
  281. package/dist/translations/zh-CN/retrieval.js +1 -1
  282. package/dist/translations/zh-CN/shared.js +1 -1
  283. package/dist/utilities/format.d.ts +17 -0
  284. package/dist/utilities/format.js +1 -1
  285. package/dist/vue.d.ts +2 -2
  286. package/llms/components/lr-app-rail-group.md +87 -10
  287. package/llms/components/lr-app-rail-item.md +88 -11
  288. package/llms/components/lr-app-rail.md +88 -11
  289. package/llms/components/lr-attachment-trigger.md +21 -0
  290. package/llms/components/lr-bar-chart.md +3 -2
  291. package/llms/components/lr-bubble-chart.md +3 -2
  292. package/llms/components/lr-button.md +5 -4
  293. package/llms/components/lr-callout.md +10 -0
  294. package/llms/components/lr-card.md +2 -1
  295. package/llms/components/lr-chart.md +43 -12
  296. package/llms/components/lr-citation-badge.md +7 -1
  297. package/llms/components/lr-code-block.md +21 -0
  298. package/llms/components/lr-color-picker.md +12 -7
  299. package/llms/components/lr-combobox.md +20 -1
  300. package/llms/components/lr-confirm-bar.md +36 -7
  301. package/llms/components/lr-context-meter.md +17 -4
  302. package/llms/components/lr-date-input.md +7 -1
  303. package/llms/components/lr-date-picker.md +6 -0
  304. package/llms/components/lr-dialog.md +21 -0
  305. package/llms/components/lr-doughnut-chart.md +3 -2
  306. package/llms/components/lr-drawer.md +10 -0
  307. package/llms/components/lr-drop-zone.md +16 -7
  308. package/llms/components/lr-dropdown-item.md +7 -0
  309. package/llms/components/lr-entity-chip.md +7 -1
  310. package/llms/components/lr-export-button.md +7 -1
  311. package/llms/components/lr-file-input.md +21 -10
  312. package/llms/components/lr-filter-bar.md +177 -18
  313. package/llms/components/lr-flow-canvas.md +16 -9
  314. package/llms/components/lr-gauge.md +22 -2
  315. package/llms/components/lr-histogram.md +7 -2
  316. package/llms/components/lr-icon-button.md +17 -8
  317. package/llms/components/lr-input.md +3 -1
  318. package/llms/components/lr-line-chart.md +3 -2
  319. package/llms/components/lr-lite-chart.md +13 -0
  320. package/llms/components/lr-locale-picker.md +7 -1
  321. package/llms/components/lr-map.md +63 -6
  322. package/llms/components/lr-mention-popover.md +16 -3
  323. package/llms/components/lr-menu-item.md +7 -0
  324. package/llms/components/lr-menu.md +8 -1
  325. package/llms/components/lr-message-actions.md +10 -0
  326. package/llms/components/lr-model-select.md +8 -3
  327. package/llms/components/lr-native-time-input.md +8 -1
  328. package/llms/components/lr-number-input.md +8 -1
  329. package/llms/components/lr-option.md +19 -0
  330. package/llms/components/lr-otp-input.md +6 -0
  331. package/llms/components/lr-pie-chart.md +3 -2
  332. package/llms/components/lr-polar-area-chart.md +3 -2
  333. package/llms/components/lr-prompt-input.md +2 -2
  334. package/llms/components/lr-radar-chart.md +3 -2
  335. package/llms/components/lr-reorder-item.md +12 -0
  336. package/llms/components/lr-reorder-list.md +12 -0
  337. package/llms/components/lr-scatter-chart.md +3 -2
  338. package/llms/components/lr-select.md +19 -2
  339. package/llms/components/lr-sequence-strip.md +10 -4
  340. package/llms/components/lr-streaming-text-core.md +28 -11
  341. package/llms/components/lr-streaming-text.md +41 -6
  342. package/llms/components/lr-suggestion-chips.md +8 -4
  343. package/llms/components/lr-table.md +47 -10
  344. package/llms/components/lr-textarea.md +1 -1
  345. package/llms/components/lr-time-input.md +7 -1
  346. package/llms/components/lr-tool-call-chip.md +7 -1
  347. package/llms/components/lr-tour.md +7 -1
  348. package/llms/components/lr-usage-badge.md +7 -1
  349. package/llms/components/lr-virtual-list.md +53 -0
  350. package/llms/components/lr-voice-picker.md +7 -3
  351. package/llms/shared.md +175 -19
  352. package/llms/tokens.md +17 -1
  353. package/llms-full.txt +1214 -169
  354. package/package.json +43 -3
  355. package/registrations.json +1 -1
  356. package/vscode-css-data.json +1 -1
  357. package/vscode-html-data.json +1 -1
  358. package/web-types.json +1 -1
package/llms-full.txt CHANGED
@@ -229,8 +229,9 @@ The entry points, then:
229
229
  `@aceshooting/lyra-ui/translations/<locale>.js` (the eleven shipped message catalogs),
230
230
  `@aceshooting/lyra-ui/events` (the global typed-event map — types only, no runtime),
231
231
  `@aceshooting/lyra-ui/ai` (provider-neutral data types), `@aceshooting/lyra-ui/testing`
232
- (happy-dom shims, `createLyraEvent()` for building a validated test event, plus a small set of
233
- interaction drivers that go through a component's own real activation path),
232
+ (happy-dom shims, `createLyraEvent()` for building a validated test event, a small set of
233
+ interaction drivers that go through a component's own real activation path, and
234
+ `waitForLyraElement()`/`waitForToast()` for awaiting a lazily registered mount),
234
235
  `@aceshooting/lyra-ui/utilities/*` (the curated shared helpers, all documented below).
235
236
 
236
237
  ### Registration-free component helpers
@@ -1418,9 +1419,50 @@ per-response nonce — a static HTML entry, for example — where the documented
1418
1419
  nonce/hash guidance above does not apply. Serving it same-origin (copy it into your build output,
1419
1420
  or configure your bundler/static host to do so) needs no hash at all; hashing it for an even
1420
1421
  stricter policy uses the same CSP `script-src` hash mechanism browsers already apply to any
1421
- external script resource. It only ever carries the default storage key (`'lyra-theme'`) — an
1422
- application-owned key from `createLyraThemeBootstrap({ storageKey })` still has to be inlined,
1423
- since a static file cannot take a call-time argument.
1422
+ external script resource.
1423
+
1424
+ **Configuring the static asset from its own `<script>` tag.** `theme-bootstrap.js` must be loaded
1425
+ as a plain classic script — never `type="module"` and never `async` — because it reads its own
1426
+ configuration synchronously through `document.currentScript` while it runs, and that property is
1427
+ `null` for both of those loading modes (as well as for anything scheduled after the script has
1428
+ already finished executing). Two optional attributes on that same `<script>` tag override the
1429
+ defaults without regenerating the file:
1430
+
1431
+ ```html
1432
+ <head>
1433
+ <script
1434
+ src="/vendor/theme-bootstrap.js"
1435
+ data-lr-theme-storage-key="my-app-theme"
1436
+ data-lr-theme-attributes="data-lr-theme data-theme"
1437
+ ></script>
1438
+ <link rel="stylesheet" href="/theme.css" />
1439
+ </head>
1440
+ ```
1441
+
1442
+ - `data-lr-theme-storage-key` — the `localStorage` key to read, in place of the default
1443
+ `'lyra-theme'`. Equivalent to `createLyraThemeBootstrap({ storageKey })`'s argument, but
1444
+ resolved by the static file itself at parse time rather than baked in ahead of time. This is
1445
+ what lets an application with its own pre-existing storage key use the static asset instead of
1446
+ inlining a per-app copy.
1447
+ - `data-lr-theme-attributes` — a space-separated list of attribute names to set on
1448
+ `<html>` in place of the default `data-lr-theme data-theme` pair, replacing that list entirely
1449
+ rather than adding to it.
1450
+
1451
+ Both attributes are optional and independently validated; an absent, empty, oversized, or
1452
+ malformed value falls back to the built-in default rather than throwing, so a `<script>` tag with
1453
+ neither attribute — every existing deployment — behaves exactly as before. `data-lr-theme-storage-key`
1454
+ must be a non-empty string of at most 200 characters (its content is otherwise unrestricted — it is
1455
+ only ever used as an opaque `localStorage` key, never written to the DOM). `data-lr-theme-attributes`
1456
+ must parse to one to eight tokens, each unique and each matching `data-[a-z0-9]+(-[a-z0-9]+)*` —
1457
+ which rejects an event-handler name (`onload`), a native attribute (`style`, `class`, `id`), any
1458
+ token containing whitespace, a quote, `=`, or a control character, an empty list, and a duplicated
1459
+ token — because these attribute names reach `setAttribute()`/`removeAttribute()` on the document
1460
+ root. A `document.currentScript` of `null` (module/async misuse, or a script tag re-read after it
1461
+ finished running) is treated the same as no configuration at all.
1462
+
1463
+ An application-owned key from `createLyraThemeBootstrap({ storageKey })` can still be inlined as
1464
+ documented above; the static file's own script-tag attributes are the alternative for a strict-CSP
1465
+ deployment that cannot inline that call.
1424
1466
 
1425
1467
  **Migrating from 15.x.** `accent` used to be exactly an absolute CSS color or `null`; that shape
1426
1468
  still works unchanged (`setLyraTheme({ accent: '#7c3aed' })` keeps deriving only the brand ramp).
@@ -1779,8 +1821,8 @@ silently defeats a registered catalog — omit it, or pass `undefined`.
1779
1821
 
1780
1822
  ### Ready-made catalogs: `@aceshooting/lyra-ui/translations/<locale>.js`
1781
1823
 
1782
- Eleven full catalogs ship with the package — **`ar`, `de`, `es`, `fa`, `fr`, `he`, `it`, `ja`,
1783
- `pt-BR`, `ru`, `zh-CN`** — each covering every key in `LYRA_DEFAULT_STRINGS`. They are
1824
+ Twelve full catalogs ship with the package — **`ar`, `de`, `es`, `fa`, `fr`, `he`, `it`, `ja`,
1825
+ `pt-BR`, `ro`, `ru`, `zh-CN`** — each covering every key in `LYRA_DEFAULT_STRINGS`. They are
1784
1826
  **side-effect-only modules**: import one bare, read nothing from it, and it calls
1785
1827
  `registerLyraLocale()` for you.
1786
1828
 
@@ -1791,11 +1833,14 @@ import "@aceshooting/lyra-ui/translations/fa.js"; // fa-IR falls back to this ba
1791
1833
  import "@aceshooting/lyra-ui/translations/he.js"; // he-IL falls back to this base catalog
1792
1834
  import "@aceshooting/lyra-ui/translations/it.js"; // Italian
1793
1835
  import "@aceshooting/lyra-ui/translations/pt-BR.js"; // also serves pt and pt-PT
1836
+ import "@aceshooting/lyra-ui/translations/ro.js"; // Romanian
1794
1837
  import "@aceshooting/lyra-ui/translations/zh-CN.js"; // also serves zh, zh-Hans and zh-Hans-CN
1795
1838
  ```
1796
1839
 
1797
1840
  Persian and Hebrew use CLDR plural categories (`fa`: `one`/`other`; `he`:
1798
- `one`/`two`/`other`); Italian uses a non-default set too (`it`: `one`/`many`/`other`). `ar`, `fa`
1841
+ `one`/`two`/`other`); Italian uses a non-default set too (`it`: `one`/`many`/`other`), and so does
1842
+ Romanian (`ro`: `one`/`few`/`other`, where `few` covers `0` and `2`-`19` and `other` is the
1843
+ `de`-requiring form from `20` upward). `ar`, `fa`
1799
1844
  and `he` declare `dir: 'rtl'`, so `getLyraLocaleDirection()`
1800
1845
  answers for them (and for `ar-EG`, `fa-IR`, `he-IL`) — but locale selection still does not _force_
1801
1846
  writing direction: set `dir="rtl"` on the page or an ancestor yourself.
@@ -2162,6 +2207,83 @@ happy-dom/jsdom environment, not only a real browser.
2162
2207
  Scope: one driver per interaction named above. Not a general "drive any component" toolkit —
2163
2208
  render the real component and interact with it directly for anything else.
2164
2209
 
2210
+ ## Awaiting a lazily registered mount: `waitForLyraElement()` and `waitForToast()`
2211
+
2212
+ An imperative API can register its elements lazily -- `toast()` dynamically `import()`s
2213
+ `<lr-toast>`/`<lr-toast-item>` on first call (a deliberate bundle-size trade: importing the package
2214
+ root, or even `toast()` itself, never pulls the element classes into an eagerly loaded bundle). A
2215
+ fire-and-forget `toast(...)` call -- the normal application pattern, since a component should not
2216
+ block its own flow on a toast -- therefore leaves the document empty for at least one microtask
2217
+ after the call returns. `@aceshooting/lyra-ui/testing` exports `waitForLyraElement()` for this shape
2218
+ in general, plus `waitForToast()` as the named convenience for `toast()` specifically:
2219
+
2220
+ ```ts
2221
+ import { waitForToast } from '@aceshooting/lyra-ui/testing';
2222
+
2223
+ toast('Saved'); // fire-and-forget; toast.class.js/toast-item.class.js may still be importing
2224
+ const item = await waitForToast('Saved'); // resolves once a matching <lr-toast-item> mounts
2225
+ expect(item.textContent?.trim()).to.equal('Saved');
2226
+ ```
2227
+
2228
+ `waitForToast(match?, options?)` resolves once a `<lr-toast-item>` is connected and upgraded. A
2229
+ string `match` compares against the item's trimmed `textContent` (what `toast('Saved')` sets
2230
+ verbatim); pass a predicate — `(item: LyraToastItem) => boolean` — for anything else (a substring,
2231
+ an icon/action check, a specific variant); omitting `match` resolves the first toast item to mount.
2232
+
2233
+ The underlying `waitForLyraElement<T>(selector, options?)` is generic over any element reachable
2234
+ from `options.root` (`document` by default): it resolves once an element matching `selector` is
2235
+ both connected and upgraded — registered with a constructor the element is actually an instance of
2236
+ — filtered further by an optional `options.match: (element: T) => boolean`. An element already
2237
+ present in markup before its class registers (the SSR/hydration case) is not a match until it
2238
+ upgrades. Both resolve via `MutationObserver` (new elements arriving) and
2239
+ `customElements.whenDefined()` (an already-connected-but-undefined element finishing registration)
2240
+ rather than polling on a timer, and both reject with an Error describing the selector, the timeout,
2241
+ and how many non-matching candidates were found — after a bounded `options.timeoutMs` (2000ms
2242
+ default). Every underlying API is standard DOM/HTML with no `@web/test-runner`/CDP dependency, so
2243
+ both also run under a downstream suite's own happy-dom environment, not only a real browser.
2244
+
2245
+ Scope: awaiting a lazy mount reachable from a root you already have a handle to. Not a replacement
2246
+ for `updateComplete` (a mounted element may still have a pending render) or for the interaction
2247
+ drivers above (already-mounted components' own activation paths). `toast()` is currently the only
2248
+ imperative `lyra-ui` API that registers its elements through a dynamic `import()`; `confirm()`
2249
+ registers `<lr-dialog>` synchronously (a static import plus an idempotent `defineElement()` call) and
2250
+ mounts its transient dialog before returning, so it has no equivalent gap.
2251
+
2252
+ ## happy-dom's custom-property resolver and host-to-part token forwarding
2253
+
2254
+ In 16.0.0, seven built-in controls that each compose a real
2255
+ `<lr-icon-button>` for their icon-only action — `<lr-copy-button>`, `<lr-dialog>` (whose close
2256
+ button is inherited by `<lr-drawer>`), `<lr-reorder-item>`, `<lr-message-actions>`,
2257
+ `<lr-attachment-trigger>`, `<lr-code-block>` (shared by `<lr-code-block-core>`), and `<lr-callout>`
2258
+ — captured the composed control's public `--lr-icon-button-*` tokens on their own `:host` and
2259
+ forwarded that private token back onto the SAME public token name on the `[part]` rendering the
2260
+ composed control, so that an ancestor theme override still reached the composed child instead of
2261
+ being shadowed by the component's own default. That was legal under the CSS Custom Properties spec
2262
+ — `:host` and `[part]` resolve on different elements, so a real browser resolves the host
2263
+ declaration to a concrete value first and the part substitutes that, and per-element cycle detection
2264
+ never fired — but **happy-dom does not model that element boundary**. Its `CSSComputedStyle` merges
2265
+ ancestor and own-element custom properties into a single flat map with no notion of which element
2266
+ declared what, and (at least through 20.14.5, the newest release at time of writing)
2267
+ `CSSVariableFormatter.resolveVariables` substitutes into that map recursively with no visited set
2268
+ and no depth cap — so the capture-and-forward pair resolved into each other forever, throwing an
2269
+ unhandled `RangeError: Maximum call stack size exceeded` from `CSSVariableFormatter.resolveVariables`
2270
+ on every render of any of the seven components. Every test still reported as passing — there was no
2271
+ failing assertion to point at — but the runner counted the unhandled errors and exited non-zero
2272
+ anyway, which read as unrelated flakiness rather than a CSS issue.
2273
+
2274
+ **Current versions are unaffected.** `<lr-icon-button>` now carries a private
2275
+ `--_lr-icon-button-<token>-default` fallback tier for every paint token (background, color, border,
2276
+ and their hover/active variants — the same shape its corner radius already used via
2277
+ `--_lr-icon-button-radius-default`), and each composing component sets its own default directly on
2278
+ that private tier rather than re-declaring the public token name. `<lr-icon-button>`'s own
2279
+ stylesheet still checks the public token first, so an ancestor override reaches a composed control
2280
+ exactly as before, but no descendant declares a public `--lr-icon-button-*` token from a private
2281
+ token that was itself derived from that same public token — so no resolver, scoped or flattened,
2282
+ ever sees a cycle. A project still hitting the `RangeError` above should upgrade
2283
+ `@aceshooting/lyra-ui`; the workarounds that version range needed (patching or upgrading the DOM
2284
+ implementation to cycle-aware/depth-limited custom-property resolution, or running the affected
2285
+ suites against a real browser engine) are no longer necessary once it does.
2286
+
2165
2287
  ## Accessibility contract
2166
2288
 
2167
2289
  Semantic roles live on the shadow-DOM element that owns them, with explicit false states for
@@ -2213,18 +2335,36 @@ those four kinds must walk the declaration's own `superclass.name`/`superclass.m
2213
2335
  `modules[].declarations[]` itself, or read `web-types.json`/`vscode-html-data.json` instead, which
2214
2336
  are already fully resolved.
2215
2337
 
2216
- **Which tags a per-tag entry registers (`registrations.json`).** A stable per-tag entry
2217
- (`@aceshooting/lyra-ui/components/lr-<name>.js`) can, at import time, define more than one custom
2218
- element: importing `lr-table.js` also registers `<lr-empty>`, `<lr-pagination>`, `<lr-skeleton>` and
2219
- `<lr-spinner>`, because `lr-table`'s registration entry imports those composed children's own
2220
- registration entries before defining `<lr-table>` itself. `custom-elements.json` declares one
2221
- custom element per family source module, with no field for a stable per-tag entry specifier and
2222
- none for the extra tags importing it registers as a side effect. For that, read the generated
2223
- `@aceshooting/lyra-ui/registrations.json` instead: `{ schemaVersion: 1, entries: [{ tag, entry,
2224
- registrationModule, registers }] }`, where `registers` is every `lr-*` tag importing `entry`
2225
- defines, derived from the same transitive-import analysis
2338
+ **Which tags an entry registers, and which message keys it can reach (`registrations.json`).** A
2339
+ stable per-tag entry (`@aceshooting/lyra-ui/components/lr-<name>.js`) can, at import time, define
2340
+ more than one custom element: importing `lr-table.js` also registers `<lr-empty>`,
2341
+ `<lr-pagination>`, `<lr-skeleton>` and `<lr-spinner>`, because `lr-table`'s registration entry
2342
+ imports those composed children's own registration entries before defining `<lr-table>` itself.
2343
+ `custom-elements.json` declares one custom element per family source module, with no field for a
2344
+ stable per-tag entry specifier and none for the extra tags importing it registers as a side effect.
2345
+ For that, read the generated `@aceshooting/lyra-ui/registrations.json` instead (`schemaVersion: 1`):
2346
+ `{ entries: [{ tag, entry, registrationModule, distModule, registers, localeKeys }],
2347
+ integrations: [{ entry, registrationModule, distModule, registers, localeKeys }] }`. Every
2348
+ `entries` row describes the stable per-tag alias above and always carries `tag`. `integrations`
2349
+ lists the published integration-bridge specifiers, which install an integration — an optional-peer
2350
+ resolver, a lazy document-format registrar — without being any single component's own alias, so
2351
+ they carry no `tag`: `components/media/flag/flag-peer.js`,
2352
+ `components/viewers/archive-viewer/archive-viewer-register.js` and
2353
+ `components/viewers/ebook-viewer/ebook-viewer-register.js`, the three imports a per-tag alias cannot
2354
+ stand in for. They are a separate array rather than tag-less rows mixed into `entries`, so a reader
2355
+ that keys `entries` by `tag` keeps working; `integrations`, `distModule` and `localeKeys` are all
2356
+ additive, which is why the schema version is unchanged. `registrationModule` is the `src/` path used
2357
+ internally; `distModule` is that same module's own published deep specifier (e.g.
2358
+ `./components/data/table/table.js`) so a caller holding either a per-tag alias or a deep import can
2359
+ resolve the other without reading `src/` or walking `dist/` — for an integration bridge,
2360
+ `distModule` equals `entry`, since there is no separate alias. `registers` is every `lr-*` tag
2361
+ importing `entry` defines, direct or transitive, derived from the same transitive-import analysis
2226
2362
  `scripts/check-component-dependencies.mjs` already performs against the real registration graph
2227
- (not a second hand-maintained list) and regenerated by `pnpm run registration-graph`.
2363
+ (not a second hand-maintained list). `localeKeys` is every `LyraMessageKey` the registered tags can
2364
+ reach — including a key reached only through an indirect lookup table (e.g. `lr-attachment-trigger`'s
2365
+ `{ triggerKey: 'attachmentTriggerFiles' }`-shaped map), because it reuses
2366
+ `generate-default-string-slices.mjs`'s own reachability walk rather than a literal-`localize()`-only
2367
+ scan. All of it is regenerated by `pnpm run registration-graph`.
2228
2368
 
2229
2369
  ## Independence and migration
2230
2370
 
@@ -2479,6 +2619,11 @@ inlineSize: number; blockSize: number }> }`.
2479
2619
  element: interpolating into a message template, populating a text-only property on another
2480
2620
  component (a stat tile's value, a chart tick label, a badge's cost text), building a search
2481
2621
  predicate, or composing an accessibility announcement.
2622
+ An omitted `locale` (or the explicit `'auto'` sentinel) on any of the four resolves to the page's
2623
+ active `setLyraLocale()` locale, exactly like a rendered `<lr-*>` component with no closer
2624
+ `locale`/`lang` override — not a hardcoded `'en'`. It falls back to `'en'` only once no active
2625
+ locale has ever been set, so an app that never calls `setLyraLocale()` sees no change. An
2626
+ explicit BCP-47 tag always stays authoritative over the active locale.
2482
2627
  `formatNumber()` and `formatBytes()` accept a `bigint` or a decimal/integer string, not just a
2483
2628
  `number`, for exact-precision input (large ids, monetary amounts, exact byte counts) — a plain
2484
2629
  `number` is a float64 and cannot exactly represent an integer beyond `Number.MAX_SAFE_INTEGER` or
@@ -4167,6 +4312,17 @@ These named interfaces and helper signatures are available to typed integrations
4167
4312
  `activateStep(/* public names: stepper, target */): unknown`
4168
4313
  See "Driving a component's real activation path: interaction drivers" above for the full contract.
4169
4314
 
4315
+ - **`testing-wait-for-mount-contracts`** — Shared utility contracts.
4316
+ `waitForLyraElement(/* public names: selector, options */): unknown`
4317
+ `WaitForLyraElementOptions {
4318
+ root: unknown;
4319
+ match: (element: unknown) => unknown;
4320
+ timeoutMs: unknown;
4321
+ }`
4322
+ `waitForToast(/* public names: match, options */): unknown`
4323
+ See "Awaiting a lazily registered mount: `waitForLyraElement()` and `waitForToast()`" above for the
4324
+ full contract.
4325
+
4170
4326
  - **`theme-gemstones-data-contracts`** — Shared utility contracts.
4171
4327
  `GemstoneAccent {
4172
4328
  key: unknown;
@@ -4763,6 +4919,25 @@ An async `source` row can carry the same two fields (`start`, `end`) alongside i
4763
4919
  that window instead, since the value is not yet known to be unmatched at all
4764
4920
  - `appearance: 'filled' | 'outlined' | 'filled-outlined' = 'outlined'` (reflected)
4765
4921
  - `placement: 'top' | 'bottom' = 'bottom'` (reflected; flip/shift can still keep the listbox in view)
4922
+ - `positioningStrategy: PlaceStrategy = 'fixed'` (attribute `positioning-strategy`, reflected) —
4923
+ the CSS positioning scheme the listbox is laid out with, spelled the same as on `lr-select`,
4924
+ `lr-popover`, `lr-dropdown`, `lr-tooltip` and `lr-color-picker`. `fixed` is this control's
4925
+ default and what it has always rendered: it positions against the viewport and escapes most
4926
+ clipping ancestors, which suits a typeahead list that usually sits inside a scrollable region.
4927
+ `absolute` positions against the nearest containing block and scrolls with it. An unsupported
4928
+ value resolves to the default. Like `placement`, a change takes effect the next time the listbox
4929
+ opens. When the instance sets nothing, the cascading `--lr-positioning-strategy` custom property
4930
+ (`absolute`/`fixed`, set on `:root`, a theme, or one clipping ancestor) is honoured ahead of the
4931
+ default; an explicit instance value always wins. There is deliberately no `hoist` alias here:
4932
+ unlike on `lr-select`, where it is Shoelace's established spelling, it would be a boolean
4933
+ defaulting to `true`, so its attribute could only ever express the value the control already has
4934
+ - `sync?: PlaceSync` (reflected) — copies the trigger's width, height, or both onto the listbox,
4935
+ spelled the same as on `lr-dropdown`/`lr-popup`/`lr-popover`. Unset (the default), the listbox
4936
+ sizes to its own content, clamped between `--lr-size-12rem` and `--lr-size-28rem`, exactly as
4937
+ before. `sync="width"` drops that content-based clamp (keeping only the outer viewport/
4938
+ available-space ceiling) so a full-width trigger with short option labels gets a listbox that
4939
+ aligns to its own edges instead of floating narrower in the middle. Like `placement`, a change
4940
+ takes effect the next time the listbox opens
4766
4941
  - `clearable: boolean = false` (reflected) — displays the clear button while there is something to
4767
4942
  clear on **either** axis this control owns: a committed selection, or _visible_ filter text. See
4768
4943
  "the clear button covers two axes" below
@@ -5392,7 +5567,17 @@ exactly like the multi-option case, until the trigger is actually activated.
5392
5567
  - `getUnknownLabel?: (value: string) => string` (attribute: false) — renders the label for a
5393
5568
  committed value that matches no option, everywhere it appears (trigger, `multiple` tag, synthetic
5394
5569
  row). `getTag` cannot serve this case: it is handed a matched option and there is none. A blank
5395
- return falls back to the raw value
5570
+ return falls back to the raw value. Not consulted while `loading` suppresses that same value —
5571
+ see below
5572
+ - `loading: boolean = false` (reflected) — whether a committed value's real label may still be
5573
+ pending because its `<lr-option>` catalog hasn't arrived yet (e.g. an async fetch still in
5574
+ flight). While `true`, a committed value that currently matches no option renders the localized
5575
+ `loading` placeholder in the trigger label or the relevant `multiple` tag instead of the raw
5576
+ value, with no `notInCatalog`/`[part='unknown-value']` badge and no synthetic
5577
+ `showUnknownOption` listbox row — "not yet resolved" is a different state from "known to be
5578
+ missing". A value already matching a live option is unaffected. Never mutates
5579
+ `value`/`selectedOptions` itself, and does not itself disable the trigger — pair it with
5580
+ `disabled` when the control should also be non-interactive while pending
5396
5581
  - `filled: boolean = false` (reflected) — Shoelace alias for the filled trigger treatment
5397
5582
  - `autofocus: boolean = false` / `title: string = ''` — forwarded to the internal trigger
5398
5583
  - `multiple: boolean = false` (reflected) — several options selectable at once; see "Multi-select"
@@ -5444,7 +5629,14 @@ from a removed option, or a programmatic assignment with a typo) still commits
5444
5629
  stays fully reachable through `value`/`selectedOptions` — but renders a dashed/italic
5445
5630
  `[part='unknown-value']` badge next to the trigger label, or on the relevant `multiple`-mode tag,
5446
5631
  instead of an unexplained bare label, mirroring `<lr-model-select>`'s synthetic "not in catalog"
5447
- stale-value row — see `--lr-select-unknown-value-border-style`/`-color` below.
5632
+ stale-value row — see `--lr-select-unknown-value-border-style`/`-color` below. Set `loading` while
5633
+ that same value's own `<lr-option>` catalog simply hasn't arrived yet (unlike `<lr-combobox>`,
5634
+ `<lr-select>` has no async `source` of its own, so this is consumer-driven rather than automatic):
5635
+ a still-unmatched value then renders the localized `loading` placeholder instead of the raw value,
5636
+ with no `unknown-value` badge and no synthetic `showUnknownOption` row, since it is not yet known
5637
+ to be missing. Once the matching option mounts, the real label renders on the next render with no
5638
+ `value`/`selectedOptions` re-assignment needed, whether or not `loading` is also flipped back to
5639
+ `false`.
5448
5640
 
5449
5641
  **Methods:** `focus(options?)`, `blur()`, and `click()` forward to the internal trigger button.
5450
5642
  `show()` and `hide()` return `Promise<void>` and resolve after `lr-after-show`/`lr-after-hide` once
@@ -6028,6 +6220,12 @@ state hooks are `--lr-date-input-action-hover-color`, `--lr-date-input-action-ho
6028
6220
  `--lr-date-input-action-active-radius` for the pressed state. They inherit from theme ancestors;
6029
6221
  direct values on `lr-date-input` win without retuning library-wide tokens.
6030
6222
 
6223
+ `--lr-positioning-strategy` (16.0.0) — the calendar popup reads this same cascading
6224
+ `absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
6225
+ its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
6226
+ property on `<lr-date-input>`; set the custom property on `:root`, a theme, or one clipping
6227
+ ancestor to change every unset date input beneath it.
6228
+
6031
6229
  **Optional peer deps:** none.
6032
6230
 
6033
6231
  ```html
@@ -6213,7 +6411,7 @@ disabled state.
6213
6411
  | `enterKeyHint` | `enterkeyhint` | `string` | `''` | Virtual-keyboard Enter-key hint forwarded to the native `<textarea>`. |
6214
6412
  | `minlength` | `minlength` | `number \| undefined` | `undefined` | Minimum text length; forwarded to the native `<textarea>` and reported as `validity.tooShort`. |
6215
6413
  | `maxlength` | `maxlength` | `number \| undefined` | `undefined` | Maximum text length; forwarded to the native `<textarea>` (which also stops typing past it) and reported as `validity.tooLong`. |
6216
- | `debounce` | `debounce` | `number \| undefined` | `undefined` | How long (ms) to wait after the last keystroke before emitting one `lr-input-settled`, while `input`/`lr-input` keep firing per keystroke as before. Omitted, `0`, or non-finite means no debounce: `lr-input-settled` never fires. A pending debounce is flushed immediately by `change`/Enter/blur and cancelled with no stray settle by disconnection and a programmatic `value` write. Shares its `DebounceController` primitive with `lr-filter-bar`'s own per-filter `debounce` and with `lr-input`'s identical property. |
6414
+ | `debounce` | `debounce` | `number \| undefined` | `undefined` | How long (ms) to wait after the last keystroke before emitting one `lr-input-settled`, while `input`/`lr-input` keep firing per keystroke as before. Omitted, `0`, or non-finite means no debounce: `lr-input-settled` never fires. A pending debounce is flushed immediately by `change`/Enter/blur and cancelled with no stray settle by disconnection and by a programmatic `value` write that changes the value. A write of the value already held leaves it pending, so a framework binding that writes the just-typed value back on each render (the controlled-input pattern) still settles. Shares its `DebounceController` primitive with `lr-filter-bar`'s own per-filter `debounce` and with `lr-input`'s identical property. |
6217
6415
  | `name` | `name` | `string` | `''` | Form field name. |
6218
6416
  | `disabled` | `disabled` | `boolean` | `false` | Disables the control. |
6219
6417
  | `required` | `required` | `boolean` | `false` | Participates in native constraint validation. |
@@ -6738,10 +6936,11 @@ box no matter what tier or override is in play.
6738
6936
  For toggle buttons, host `aria-pressed="true|false|mixed"` reaches the focused native button —
6739
6937
  BUTTONS only. A link button (`href` set) never receives it, because `role="link"` does not support
6740
6938
  `aria-pressed`; remove `href` and the same host attribute starts reaching the `<button>` that
6741
- replaces the anchor. Navigation can use the global
6742
- `aria-current="page|step|location|date|time|true|false"`, which does reach the anchor. Both states
6743
- update reactively, including native `ariaPressed`/`ariaCurrent` property assignments, attribute
6744
- removal and `href` changes. Empty or unsupported tokens are omitted. The native button/link role
6939
+ replaces the anchor. That link carve-out arrived in **16.0.0** — before it, `aria-pressed` was
6940
+ forwarded onto the anchor as well, asserting a state `role="link"` does not have. Navigation can
6941
+ use the global `aria-current="page|step|location|date|time|true|false"`, which does reach the
6942
+ anchor. Both states update reactively, including native `ariaPressed`/`ariaCurrent` property
6943
+ assignments, attribute removal and `href` changes. Empty or unsupported tokens are omitted. The native button/link role
6745
6944
  remains unchanged for an enabled control: use pressed state for button toggles and current state
6746
6945
  for navigation links. A DISABLED link button drops `href` (so it genuinely cannot navigate) and
6747
6946
  therefore also drops the anchor's implicit role, so it renders an explicit `role="link"` — without
@@ -6821,7 +7020,8 @@ button whose purpose isn't generic.
6821
7020
  Host `aria-haspopup` and `aria-expanded` values are forwarded reactively to the shadow-internal
6822
7021
  native button. `aria-pressed` (`true`, `false`, `mixed`) supports icon-only toggle actions such as
6823
7022
  mute, favorite, or pin, and reaches the `<button>` rendering only — a link icon-button (`href` set)
6824
- never receives it, since `role="link"` has no pressed state; `aria-current` (`page`, `step`,
7023
+ never receives it, since `role="link"` has no pressed state (that carve-out arrived in **16.0.0**;
7024
+ earlier versions forwarded it onto the anchor too); `aria-current` (`page`, `step`,
6825
7025
  `location`, `date`, `time`, `true`, `false`) is global and supports current-item icon buttons such
6826
7026
  as an active nav/pagination target in both renderings. Both follow attribute
6827
7027
  changes, removal, and button/link replacement without changing the native role of an enabled
@@ -6875,16 +7075,24 @@ DOM while at least one top-level slotted element needs the bare-geometry fallbac
6875
7075
  **Themeable custom properties:** `--lr-icon-button-size` (default `2.5rem`) is the **minimum**
6876
7076
  tappable inline and block size of the native button — a floor, not a fixed size. Content larger
6877
7077
  than it grows the button and keeps its own aspect ratio; a small glyph pads out to it. It is a
6878
- library-wide token (declared on `:root` by the token layer, and the shared minimum tappable size
7078
+ library-wide token (declared on every `lr-*` host by the shared token layer, and the shared minimum tappable size
6879
7079
  that several other components size their icon-only controls against), so overriding
6880
7080
  `--lr-theme-icon-button-size` globally resizes all of them together. Keep the resolved value at or
6881
- above 24px — see `llms/shared.md`. Lowering the floor for a dense action row (directly via
6882
- `--lr-icon-button-size`, or via `--lr-theme-icon-button-size` on an ancestor to reach a
7081
+ above 24px — see `llms/shared.md`. **`--lr-icon-button-size` is element-scoped, unlike every other
7082
+ token in this section:** the shared token layer re-declares it on every `lr-*` host's own `:host`,
7083
+ so a rule that sets `--lr-icon-button-size` on an ancestor wrapper is reset the moment it crosses
7084
+ into any intervening `lr-*` component and never reaches a `<lr-icon-button>` composed inside it
7085
+ (e.g. one slotted through `<lr-popover>`). The only two levers that actually reach it are: setting
7086
+ `--lr-icon-button-size` directly on the icon button element itself, where no intervening component
7087
+ sits between the rule and the property; or setting `--lr-theme-icon-button-size` on an ancestor,
7088
+ which the shared layer reads through `var()` at every level and which therefore reaches a
6883
7089
  `<lr-icon-button>` composed inside another component, e.g. `<lr-copy-button>`/
6884
- `<lr-message-actions>`) is safe even below 24px: a coarse-pointer/no-hover media rule floors the
6885
- RENDERED hit area back at 2.75rem/44px regardless of how far the override lowered it, so the
6886
- control stays comfortably tappable the moment the pointer reaching it is a finger rather than a
6887
- mouse. `--lr-icon-button-radius` (default `--lr-radius`) is the
7090
+ `<lr-message-actions>`. Lowering the floor for a dense action row through either lever is safe even
7091
+ below 24px: a coarse-pointer/no-hover media rule floors the RENDERED hit area back at 2.75rem/44px
7092
+ regardless of how far the override lowered it, so the control stays comfortably tappable the moment
7093
+ the pointer reaching it is a finger rather than a mouse. `--lr-icon-button-radius` (default
7094
+ `--lr-radius`) is not re-declared anywhere in the shared layer, so — like every other token below —
7095
+ it inherits normally from an ancestor even through an intervening component; it is the
6888
7096
  `[part='button']` corner radius, retunable without a `::part(button)` rule — the same
6889
7097
  `--lr-button-radius` pattern; `lr-icon-button` has no `size` tiers, so there is no per-tier gap
6890
7098
  counterpart to it. The internal control sets `font: inherit`, so an `em`-sized slotted glyph takes
@@ -7063,7 +7271,9 @@ writes remain valid and read back as booleans. Markup uses `autocorrect="on"` /
7063
7271
  before. Omitted, `0`, or a non-finite value means no debounce at all: `lr-input-settled` never
7064
7272
  fires. A pending debounce is flushed immediately by `change`/Enter/blur (so a blur never drops
7065
7273
  the last keystroke) and cancelled with no stray settle by disconnection, the built-in clear
7066
- button, and a programmatic `value` write. Shares its `DebounceController` primitive with
7274
+ button, and a programmatic `value` write that changes the value. A write of the value already
7275
+ held leaves it pending, so a framework binding that writes the just-typed value back on each
7276
+ render (the controlled-input pattern) still settles. Shares its `DebounceController` primitive with
7067
7277
  `lr-filter-bar`'s own per-filter `debounce` and with `lr-textarea`'s identical property
7068
7278
  - `name`/`disabled`/`required` (from `FormAssociated`)
7069
7279
 
@@ -7421,7 +7631,14 @@ action-height ladder as `lr-input` instead of remaining at the default tier for
7421
7631
  identical meaning. This component changes the mapped defaults to `appearance='outlined'`,
7422
7632
  `inputMode='numeric'`, and `step=1`. `clearable` (and its `with-clear` spelling),
7423
7633
  `passwordVisible` (`password-visible`), and `minlength`/`maxlength`/`pattern` are inherited but
7424
- inert — see gotchas.
7634
+ inert — see gotchas. Also inherited from `lr-input`, with identical meaning:
7635
+ `defaultValue: string = ''` (attribute `value`, reflected) — the reset value; and
7636
+ `customError: string | null = null` (attribute `custom-error`, reflected) — a consumer-supplied
7637
+ validation message. `getForm()` returns the browser-resolved form owner, including an external
7638
+ owner selected by `form`; `setCustomValidity(message)` sets or clears `customError` without
7639
+ discarding intrinsic validity; `resetValidity()` clears only that consumer layer and recomputes
7640
+ the current intrinsic constraints, leaving `value`/`defaultValue` and prior interaction state
7641
+ unchanged.
7425
7642
 
7426
7643
  Stepper switches:
7427
7644
 
@@ -7687,6 +7904,12 @@ mode the raised tone is already distinct from the page, so this panel never had
7687
7904
  reads-as-a-hole problem the family exists to fix. Setting `--lr-overlay-surface` still repaints it
7688
7905
  along with every other popup.
7689
7906
 
7907
+ `--lr-positioning-strategy` (16.0.0) — the `popup` panel reads this same cascading
7908
+ `absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
7909
+ its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
7910
+ property on `<lr-time-input>`; set the custom property on `:root`, a theme, or one clipping
7911
+ ancestor to change every unset time input beneath it.
7912
+
7690
7913
  ```html
7691
7914
  <lr-time-input
7692
7915
  label="Start time"
@@ -7725,7 +7948,14 @@ properties apply. `step` is native seconds; `showPicker()`, `stepUp()`, and `ste
7725
7948
  native-wrapper behavior. The control row carries `base input-wrapper time-input` part tokens on one
7726
7949
  node. Its native picker UI and AM/PM presentation are browser-owned and intentionally unstyled.
7727
7950
  The inherited `--lr-input-*` theme inputs therefore remain configurable from an ancestor theme
7728
- wrapper without being shadowed by the subclass.
7951
+ wrapper without being shadowed by the subclass. Among those inherited properties and methods:
7952
+ `defaultValue: string = ''` (attribute `value`, reflected) is the reset value, and
7953
+ `customError: string | null = null` (attribute `custom-error`, reflected) is a consumer-supplied
7954
+ validation message. `getForm()` returns the browser-resolved form owner, including an external
7955
+ owner selected by `form`; `setCustomValidity(message)` sets or clears `customError` without
7956
+ discarding intrinsic validity; `resetValidity()` clears only that consumer layer and recomputes
7957
+ the current intrinsic constraints, leaving `value`/`defaultValue` and prior interaction state
7958
+ unchanged.
7729
7959
 
7730
7960
  **Events:** native-style `input` and `change`; bubbling, composed `focus` and `blur` bridges; the
7731
7961
  `lr-input` / `lr-change` aliases with `{ value }`;
@@ -9479,6 +9709,12 @@ The exact-320px RTL story covers an eight-cell row and an unbroken localized lab
9479
9709
  horizontal reachability for every cell.
9480
9710
  The internal role token `--lr-otp-input-segment-size` supplies that `2.5em` default and can be
9481
9711
  retuned through `--lr-theme-otp-input-segment-size` when the `--segment-size` override is absent.
9712
+ Like `--lr-icon-button-size`, `--lr-otp-input-segment-size` is element-scoped: the shared token
9713
+ layer re-declares it on every `lr-*` host, so a rule that sets it on an ancestor wrapper is reset at
9714
+ the first intervening `lr-*` component and never reaches a nested `<lr-otp-input>`. Set it directly
9715
+ on the element, or set `--lr-theme-otp-input-segment-size` on an ancestor to resize every OTP input
9716
+ in the subtree at once — the retained per-cell hooks below are not re-declared anywhere in the
9717
+ shared layer and inherit normally.
9482
9718
 
9483
9719
  The retained per-cell hooks are `--lr-otp-input-segment-fill` (default `transparent`),
9484
9720
  `--lr-otp-input-segment-border-color` (default `var(--lr-color-border)`), and
@@ -10086,12 +10322,15 @@ and:
10086
10322
  applies to the whole string, function names included (`RGB(255, 0, 0)`)
10087
10323
  - `swatches: string | string[] | LyraColorPickerSwatch[] = ''` — a predefined palette, given as a
10088
10324
  `;`-separated string, an array of colour strings, or an array of
10089
- `{ color: string; label?: string }` objects. Any colour the picker can parse is accepted; blank
10090
- entries are dropped. An entry that is _not_ parseable is kept in the list and still renders a
10091
- swatch — it just paints no colour (the bare checkerboard) and clicking it does nothing, so filter
10092
- the palette yourself if that matters. `label` becomes the swatch's accessible name; a missing,
10093
- empty, or whitespace-only label falls back to announcing the raw colour string. The palette
10094
- container renders only while the normalized list is non-empty
10325
+ `{ color: string; label?: string; disabled?: boolean }` objects. Any colour the picker can parse
10326
+ is accepted; blank entries are dropped. An entry that is _not_ parseable is kept in the list and
10327
+ still renders a swatch — it just paints no colour (the bare checkerboard) and clicking it does
10328
+ nothing, so filter the palette yourself if that matters. `label` becomes the swatch's accessible
10329
+ name; a missing, empty, or whitespace-only label falls back to announcing the raw colour string.
10330
+ `disabled` marks that one swatch non-actionable, independent of the whole control's own
10331
+ `disabled`: it renders a genuinely disabled `<button>` (no tab stop, no hover/press affordance)
10332
+ and clicking it commits nothing; omitted or `false` renders the swatch exactly as before this
10333
+ field existed. The palette container renders only while the normalized list is non-empty
10095
10334
  - `withoutFormatToggle: boolean = false` (attribute `without-format-toggle`) — removes the button
10096
10335
  that cycles between formats. `noFormatToggle` (`no-format-toggle`) is the Shoelace spelling and
10097
10336
  reaches the same behavior; either one wins
@@ -10275,6 +10514,8 @@ of authoring `positioning-strategy`/`hoist` on each instance.
10275
10514
  `var(--lr-color-brand)`.
10276
10515
  - `--lr-color-picker-selected-check-color` — Checkmark on the selected palette swatch. Default:
10277
10516
  `var(--lr-color-surface)`.
10517
+ - `--lr-color-picker-swatch-disabled-opacity` — Opacity of a palette swatch whose own entry sets
10518
+ `disabled`. Default: `0.5`.
10278
10519
 
10279
10520
  ```html
10280
10521
  <lr-color-picker
@@ -10745,6 +10986,12 @@ this listbox together with every other floating surface. `--lr-overlay-radius` r
10745
10986
  only as the middle arm of this component's own `--lr-locale-picker-radius`, which still wins when
10746
10987
  set: a component-scoped override outranks the shared family, never the other way round.
10747
10988
 
10989
+ `--lr-positioning-strategy` (16.0.0) — the listbox reads this same cascading `absolute`/`fixed`
10990
+ override documented on `<lr-popover>` when it is (re)positioned, falling back to its own `fixed`
10991
+ default when nothing is set. There is no per-instance `positioning-strategy` property on
10992
+ `<lr-locale-picker>`; set the custom property on `:root`, a theme, or one clipping ancestor to
10993
+ change every unset locale picker beneath it.
10994
+
10748
10995
  **Optional peer deps:** none directly — each row's `<lr-flag>` degrades to an empty render (no
10749
10996
  peer warning duplication; `lr-flag` itself already logs one) when the optional
10750
10997
  `@aceshooting/lyra-flags` package isn't installed and `showFlags` is left on.
@@ -10829,6 +11076,7 @@ These named interfaces and helper signatures are available to typed integrations
10829
11076
  `LyraColorPickerSwatch {
10830
11077
  color: string;
10831
11078
  label?: string;
11079
+ disabled?: boolean;
10832
11080
  }`
10833
11081
 
10834
11082
  - **`components-forms-combobox-combobox-contracts`** — Supporting data types and helpers for this component family.
@@ -11678,9 +11926,11 @@ unchanged.
11678
11926
  headerCell?, width?, minWidth?, maxWidth?,
11679
11927
  resizable?, sortable?, sortValue?, defaultSortDir?: 'asc'|'desc', align?: 'start'|'end',
11680
11928
  priority?: 'medium'|'low',
11681
- sticky?: 'start'|'end', editTrigger?: 'double-click'|'always', editValue?, editType?: 'text'|'number'|'select',
11929
+ sticky?: 'start'|'end', editTrigger?: 'double-click'|'always', editValue?, editLabel?: (row) => string,
11930
+ editType?: 'text'|'number'|'select',
11682
11931
  editOptions?: { value: string; label: string }[], footer?, cellStyle?, heatValue?,
11683
- cell: (row) => unknown }` —
11932
+ cell: (row) => unknown }` — `cell` is required for every `editTrigger` except `'always'`, whose
11933
+ persistent editor renders unconditionally so the table's render path never falls back to it —
11684
11934
  `sortValue(row) => string | number | null | undefined` supplies the comparable value backing
11685
11935
  client-mode sorting for that column: a finite number sorts numerically, a string sorts through an
11686
11936
  `Intl.Collator` built from the component's effective locale with `numeric: true` (so `item2`
@@ -11720,13 +11970,15 @@ cell: (row) => unknown }` —
11720
11970
  own ramp-token convention; `cellStyle` is applied directly to the generated `<td>` via `styleMap` — e.g. a computed heat-tint
11721
11971
  background a `cell()`-returned inner element can't paint into the cell's own padding — omit it for
11722
11972
  no per-cell style override (the default, unchanged output);
11723
- `editTrigger: 'double-click'` opens a native editor on that cell's double-click (one cell at a
11724
- time), while `'always'` renders a persistent editor in every body cell from first paint, for a
11973
+ `editTrigger: 'double-click'` opens a native editor on that cell's double-click, `F2`, or `Enter`
11974
+ (one cell at a time), while `'always'` renders a persistent editor in every body cell from first paint, for a
11725
11975
  settings/rate-style column meant to be typed straight into — while `editValue` supplies the editor
11726
11976
  value and `editType` selects `'text'`, `'number'`, or `'select'` (a native `<select>` populated
11727
11977
  from `editOptions: { value: string; label: string }[]`, one `<option>` per entry in order; a
11728
11978
  `'select'` column with no `editOptions` renders an empty, valueless `<select>` instead of
11729
- throwing)
11979
+ throwing); `editLabel(row) => string`, read once per row exactly like `editValue`/`cellTitle`,
11980
+ overrides that editor's accessible name — omit it and every editor in the column shares the same
11981
+ interpolated `tableEditCell` name instead (see the accessibility note under `editTrigger` below)
11730
11982
  `cellTitle(row) => string | undefined` is the `title` analogue of `cellStyle`, applied directly to
11731
11983
  the generated `<td>` — e.g. the untruncated text behind an ellipsized cell, or a formatted
11732
11984
  timestamp behind a relative one;
@@ -11748,8 +12000,21 @@ cell: (row) => unknown }` —
11748
12000
  Enter commits and closes, Escape cancels and closes, and blur-after-change commits. `'always'`
11749
12001
  renders an editor in
11750
12002
  every body cell of that column, permanently:
11751
- - **Focus model.** Each editor is a plain tab stop — no `tabindex` of its own exactly like the
11752
- existing row-expand toggle, and stays _outside_ the header/row roving-tabindex model. Tab walks
12003
+ - **Keyboard entry (`'double-click'` columns).** The resting (not-currently-editing) cell of a
12004
+ `'double-click'` column is its own roving-focus stop — `tabindex="-1"`, carrying `[data-editable]`
12005
+ — reachable once the row itself has focus: ArrowRight (ArrowLeft under RTL) enters at the first
12006
+ editable cell in the row and steps forward through any further ones; ArrowLeft (ArrowRight under
12007
+ RTL) steps back and, from the first editable cell, returns focus to the row. None of this is a Tab
12008
+ stop — a table with a `'double-click'` column gains no new entry in the page's Tab order, only a
12009
+ new arrow-reachable one — and a table with no `editTrigger` column at all renders no `tabindex`/
12010
+ `[data-editable]` anywhere. `F2` or `Enter` on the focused cell opens its editor; `Enter` on the
12011
+ row itself (no cell focused) still only activates the row. `editCell(rowKey, columnKey)` (see
12012
+ Methods above) is the same open action as a public method. Closing the editor, by commit or by
12013
+ cancel, returns focus to the cell that opened it.
12014
+ - **Focus model (`'always'` columns).** Each editor is a plain tab stop — no `tabindex` of its own
12015
+ — exactly like the
12016
+ existing row-expand toggle, and stays _outside_ the header/row/cell roving-tabindex model. Tab
12017
+ walks
11753
12018
  down the column; arrow keys still navigate the grid from a row's own roving stop, and act as
11754
12019
  ordinary caret movement once focus is inside a field. Non-editable columns are unaffected.
11755
12020
  - **Value binding.** A persistent `'text'`/`'number'` editor binds its `value` as a **content
@@ -11768,8 +12033,14 @@ cell: (row) => unknown }` —
11768
12033
  `<input>` (the typed value rides along) and the table restores focus to the same logical cell
11769
12034
  afterwards. If the focused row leaves the rendered page entirely (pagination, filtering), focus
11770
12035
  is simply lost rather than yanked to whichever unrelated row now sits in that position.
11771
- - Each editor keeps its own interpolated `tableEditCell` accessible name (`Edit {column}`), so a
11772
- column of otherwise-identical inputs is still individually named to a screen reader.
12036
+ - **Accessible name.** Each editor's name is `columns[].editLabel(row)` when the column defines
12037
+ it, or otherwise the interpolated `tableEditCell` string (`Edit {column}`) identical for every
12038
+ row in that column, since the default has no row context. That default is adequate for
12039
+ `'double-click'`, where at most one editor is ever open, but not for `'always'`: every row's
12040
+ editor there is a permanent, individually focusable Tab stop, so leaving `editLabel` unset on an
12041
+ `'always'` column exposes as many identically named controls as there are rows (e.g. fifty "Edit
12042
+ Status" comboboxes), failing WCAG 2.4.6 and 1.3.1. Define `editLabel` for any `'always'` column
12043
+ to give each row's editor its own name.
11773
12044
  - `hasHiddenPriorityColumns: boolean = false` (attribute `has-hidden-priority-columns`, reflected) —
11774
12045
  computed/read-only and true only while a priority column is actually hidden. It becomes false
11775
12046
  when `priorityColumnsVisible` reveals the columns. Measured via a `ResizeObserver` on
@@ -11907,6 +12178,14 @@ cell: (row) => unknown }` —
11907
12178
  rendered panel anyway, `expandedContentElement(rowKey)` (below) resolves that `<td>`;
11908
12179
  `rowElement(rowKey)` does not, because the panel is a sibling `<tr>` rather than part of the row
11909
12180
  - `canExpand?: (row: T) => boolean` (attribute: false) — optional per-row gate for expansion
12181
+ - `rowExpandLabel?: (row: T, expanded: boolean) => string | undefined` (attribute: false) —
12182
+ accessible name for one row's expand/collapse chevron, read once per render for that row,
12183
+ exactly like a column's `editLabel`/`cellTitle`; consumer-owned text used verbatim, never
12184
+ passed through localization. Omit it and every chevron shares the same localized
12185
+ `expand`/`collapse` name, which carries no row context — fine for a handful of rows, but each
12186
+ chevron is its own Tab stop, so a long table otherwise announces the same two names over and
12187
+ over with no way to tell the rows apart. There is no default row context to derive one from
12188
+ here: this component has no row-header notion (`rowKey` is an opaque identity, not a label)
11910
12189
  - `expansionMode: 'none'|'single'|'multiple' = 'none'` (attribute `expansion-mode`, reflected) —
11911
12190
  mirrors `selectionMode` member for member, for expansion. The default `'none'` keeps
11912
12191
  `expandedRowKeys` fully consumer-controlled: an activation only reports `lr-row-expand-toggle`.
@@ -12013,8 +12292,14 @@ cell: (row) => unknown }` —
12013
12292
  - `expandedContentElement(rowKey)` — the rendered `[part='expanded-cell']` holding that row's
12014
12293
  `expandedContent(row)` output, or `null` when the row is not currently rendered, is not expanded,
12015
12294
  or the table sets no `expandedContent`
12016
-
12017
- All three exist for code that has to reach content a `cell(row)`/`expandedContent(row)` callback
12295
+ - `editCell(rowKey, columnKey)` — opens the inline editor at that row/column pair, exactly as a
12296
+ double-click, `F2`, or `Enter` on the cell's own focus stop would (see `columns[].editTrigger`
12297
+ below) — the entry point for a consumer's own key binding or menu action. A no-op for an unknown
12298
+ row key, an unknown column key, or a column with no `editTrigger`. For an `editTrigger: 'always'`
12299
+ column (already open from first paint) it moves focus into that editor instead of opening
12300
+ anything.
12301
+
12302
+ The first three exist for code that has to reach content a `cell(row)`/`expandedContent(row)` callback
12018
12303
  rendered into this component's shadow root — measuring it, scrolling it into view, or applying a
12019
12304
  style `::part()` cannot express, since only pseudo-classes may follow a part selector. One method per
12020
12305
  callback, and the split is not cosmetic: the expansion panel is a **sibling** `<tr part='expanded-row'>`
@@ -12640,6 +12925,12 @@ explicitly empty labels remain empty and later labels render normally.
12640
12925
  - `valueText?: string` (attribute `value-text` — overrides both the visible text and the host's
12641
12926
  `aria-valuetext`; an empty string is treated the same as unset and falls back to the numeric
12642
12927
  `value` while removing `aria-valuetext`)
12928
+ - `showValue: boolean = true` (attribute `show-value`, not reflected) — whether the decorative
12929
+ `part="value"` caption renders at all; `show-value="false"` omits it the same way an empty
12930
+ `label` already omits `part="label"`. `aria-valuenow`/`aria-valuetext` and the host's accessible
12931
+ name are unaffected either way, since the caption itself is always `aria-hidden`. Mirrors
12932
+ `<lr-progress-bar>`'s/`<lr-progress-ring>`'s own `showValue` name and meaning; the default here
12933
+ is `true` (a gauge's whole purpose is showing the reading it announces) where theirs is `false`.
12643
12934
  - `variant: LyraProgressVariant = 'brand'` (reflected) — the same shared semantic-tone vocabulary
12644
12935
  `<lr-progress-bar>` uses (`'neutral'|'brand'|'success'|'warning'|'danger'`). The fallback color
12645
12936
  whenever `thresholds` is empty or matches nothing.
@@ -12656,7 +12947,8 @@ explicitly empty labels remain empty and later labels render normally.
12656
12947
 
12657
12948
  **Slots:** none.
12658
12949
 
12659
- **CSS parts:** `base` (the `<svg>`), `track`, `fill`, `value`, `label`
12950
+ **CSS parts:** `base` (the `<svg>`), `track`, `fill`, `value` (rendered only while `showValue` is
12951
+ true), `label` (rendered only while `label` is non-empty)
12660
12952
 
12661
12953
  **Themeable custom properties:** `--lr-gauge-fill` (fill stroke; overrides `variant`/`thresholds`
12662
12954
  entirely and falls back to the effective variant's shared semantic token —
@@ -12673,6 +12965,15 @@ entirely and falls back to the effective variant's shared semantic token —
12673
12965
  style="--lr-gauge-fill: var(--lr-color-success)"
12674
12966
  ></lr-gauge>
12675
12967
  <lr-gauge shape="linear" value="0.4" max="1" value-text="72°F"></lr-gauge>
12968
+ <!-- compact dashboard meter: a slim, captionless, thresholded row -->
12969
+ <lr-gauge
12970
+ shape="linear"
12971
+ size="xs"
12972
+ show-value="false"
12973
+ aria-label="Spend"
12974
+ value="84"
12975
+ max="100"
12976
+ ></lr-gauge>
12676
12977
  <!-- automatic threshold coloring: same rule, opposite direction -->
12677
12978
  <lr-gauge id="cpu" value="82" label="CPU"></lr-gauge>
12678
12979
  <script>
@@ -12716,7 +13017,11 @@ entirely and falls back to the effective variant's shared semantic token —
12716
13017
  - no documented component-specific sizing custom property. The host box is fixed em values
12717
13018
  (`8em` radial/ring, `12em`/`1.5em` linear) against the host font size, so `size` is the supported
12718
13019
  way to step it; for a dimension off the ladder, set plain CSS `width`/`height` (or `font-size`)
12719
- on the element instead.
13020
+ on the element instead. The value/label captions are `em`-sized against that same host font-size,
13021
+ so they shrink right along with the frame at a smaller tier or a smaller host `font-size` —
13022
+ combine a small tier (or a small host `font-size`) with `show-value="false"` for a slim,
13023
+ thresholded dashboard meter, matching `<lr-progress-bar size="xs">`'s footprint but with
13024
+ `role="meter"` and one `thresholds` array instead of a hand-written ratio-to-variant mapping.
12720
13025
  - Divide-by-zero guarded, and radial/linear share one component via the `shape`
12721
13026
  attribute.
12722
13027
  - non-finite `value` text remains blank unless `valueText` supplies a truthful fallback; that
@@ -13550,13 +13855,18 @@ numeric disclosure.
13550
13855
  **Properties:**
13551
13856
 
13552
13857
  - `items: readonly SequenceStripItem[] = []` (attribute: false) — `{ readonly id, readonly
13553
- categoryId, readonly marker?, readonly label? }`;
13858
+ categoryId, readonly marker?, readonly label?, readonly disabled? }`;
13554
13859
  `marker` renders a small bottom marker on that cell independent of the category color (e.g. a
13555
13860
  subagent-dispatched turn); `label` is per-item hover/focus tooltip text _and_ that cell's own
13556
13861
  `role="listitem"` accessible name, falling back to the matching category's own nonblank `label`,
13557
13862
  then localized `sequenceStripUnnamedCategory` (`"Unnamed category"` in the built-in English
13558
13863
  catalog) when unset — it is not read by `[part="base"]`'s auto-generated `aria-label`, which
13559
- summarizes by category/count only
13864
+ summarizes by category/count only. `disabled` marks the item non-actionable:
13865
+ `aria-disabled="true"` replaces the selected/active affordances of the cell that represents it,
13866
+ activating it (click or Enter/Space) emits nothing, and roving Left/Right/Home/End navigation —
13867
+ including the default resting tab stop — steps past it. Above the 200-cell cap, a range cell's
13868
+ disabled state follows its own activated item, the range's first. Omitted or `false` renders the
13869
+ item exactly as before this field existed
13560
13870
  - `categories: readonly SequenceStripCategory[] = []` (attribute: false) — `{ readonly id,
13561
13871
  readonly color, readonly label? }`; `color`
13562
13872
  is the cell background for every item whose `categoryId` matches `id`; invalid CSS colors,
@@ -13632,7 +13942,8 @@ block-size), `--lr-sequence-strip-marker-color` (default `var(--lr-color-text)`
13632
13942
  (default `0.625rem` — a legend swatch's inline- and block-size, category and marker rows alike), and
13633
13943
  `--lr-sequence-strip-legend-marker-bg` (default `var(--lr-color-surface-raised)` — the neutral chip
13634
13944
  background behind the marker legend row's bar; it stands in for "any cell", so it deliberately
13635
- matches no category color); the tooltip also consumes shared tokens
13945
+ matches no category color), and `--lr-sequence-strip-disabled-opacity` (default `var(--lr-opacity-disabled)` opacity of
13946
+ a cell whose activated item sets `disabled`); the tooltip also consumes shared tokens
13636
13947
  `--lr-color-surface`, `--lr-color-text`, `--lr-font-size-xs`, `--lr-radius`, and `--lr-shadow`, and
13637
13948
  the legend consumes `--lr-space-2xs`, `--lr-space-xs`, `--lr-space-s`, `--lr-font-size-xs`,
13638
13949
  `--lr-color-text-quiet`, and `--lr-radius-xs`.
@@ -14058,14 +14369,19 @@ import type {
14058
14369
  **Properties:**
14059
14370
 
14060
14371
  - `nodes: readonly FlowNode[] = []` (attribute: false) — each record has readonly `id`, optional
14061
- `type`, `position`, `data`, `accessibleLabel`, `inputs`, and `outputs`. A missing `position` opts
14062
- into layered layout. String `data.label` and `data.description` feed the declarative fallback
14063
- card. Assignment takes a detached, deeply frozen snapshot of plain arrays/records, omitting blank
14064
- ids and later duplicates first-wins before layout, focus, selection, gestures, companion
14065
- snapshots, and events. At most the first 10,000 source nodes are retained, with finite nested
14066
- depth/entry budgets; reassign `nodes` after changes. Replacing the model cancels node-drag and
14067
- connect gestures whose ids belonged to the old model and silently prunes selected ids that no
14068
- longer exist.
14372
+ `type`, `position`, `data`, `accessibleLabel`, `inputs`, `outputs`, and `disabled`. A missing
14373
+ `position` opts into layered layout. String `data.label` and `data.description` feed the
14374
+ declarative fallback card. Assignment takes a detached, deeply frozen snapshot of plain
14375
+ arrays/records, omitting blank ids and later duplicates first-wins before layout, focus,
14376
+ selection, gestures, companion snapshots, and events. At most the first 10,000 source nodes are
14377
+ retained, with finite nested depth/entry budgets; reassign `nodes` after changes. Replacing the
14378
+ model cancels node-drag and connect gestures whose ids belonged to the old model and silently
14379
+ prunes selected ids that no longer exist. `disabled` marks a node non-actionable: it keeps its
14380
+ position and card content but cannot be selected/activated by click or keyboard, roving-tabindex
14381
+ navigation (arrow keys, Home/End) steps past it, it cannot be dragged even while
14382
+ `nodes-draggable`, and it is excluded from starting or receiving a new connection while
14383
+ `connectable` (an edge already touching it is left alone). Omitted or `false` renders the node
14384
+ exactly as before this field existed.
14069
14385
  - `edges: readonly FlowEdge[] = []` (attribute: false) — readonly `id`, `source`, `target`, optional
14070
14386
  handle ids, optional drawn `label`, and optional `tone: LyraVariant`. The canonical brand value is
14071
14387
  `brand`; the former `accent` value and `FlowEdgeTone` alias are not part of this contract. Blank
@@ -14231,6 +14547,8 @@ four above. Set it to `transparent` to opt out of the hover treatment.
14231
14547
  - `part="edge-hit-area"` — The transparent wide pointer target behind an edge.
14232
14548
  - `part="node-control"` — The visually hidden, roving selection button for a node.
14233
14549
  - `--lr-flow-canvas-node-selected-outline-color` — Outline color of a selected node. Default: `var(--lr-color-brand)`.
14550
+ - `--lr-flow-canvas-node-disabled-opacity` — Opacity of a node whose `FlowNode` entry sets
14551
+ `disabled`. Default: `var(--lr-opacity-disabled)`.
14234
14552
 
14235
14553
  ---
14236
14554
 
@@ -14549,9 +14867,13 @@ used" summary.
14549
14867
  **Properties:**
14550
14868
 
14551
14869
  - `segments: ContextMeterSegment[] = []` (attribute: false, JS-only) — `{ label: string; value:
14552
- number; tone?: 'brand' | 'success' | 'warning' | 'danger' | 'neutral'; color?: string }[]`. `value` is an _absolute_
14870
+ number; tone?: 'brand' | 'success' | 'warning' | 'danger' | 'neutral'; color?: string; disabled?:
14871
+ boolean }[]`. `value` is an _absolute_
14553
14872
  quantity measured against `total`, never a pre-computed percentage.
14554
14873
  `color`, when supplied, is a sanitized arbitrary CSS color that takes precedence over `tone`.
14874
+ `disabled`, when set, marks that band non-actionable while `interactive` is set: its control
14875
+ renders genuinely disabled (no tab stop, no hover/press affordance) and activating it emits no
14876
+ `lr-segment-activate`.
14555
14877
  - `total: number = 0` — the full capacity segments are measured against (e.g. a model's context
14556
14878
  window size).
14557
14879
  - `shape: ContextMeterShape = 'bar'` (`'bar' | 'ring'`, reflected) — the v9 geometry name;
@@ -14618,7 +14940,14 @@ color hook as `segment`) and `legend-label`, plus `legend-value` and `legend-per
14618
14940
  matching `legendDisplay` settings. While `interactive` is set, `segment` and `legend-item` are
14619
14941
  `<button>`s (a `role="button"` arc under `shape="ring"`) and a selected one carries a second part
14620
14942
  token — `segment-selected` / `legend-item-selected` — because nothing but a pseudo-class may follow
14621
- `::part()`, so the state has to live in the part name
14943
+ `::part()`, so the state has to live in the part name. Two further state tokens join them, and they
14944
+ compose: `segment-empty` / `legend-item-empty` on a band whose `value` is 0, and
14945
+ `segment-disabled` / `legend-item-disabled` on a band whose `segments` entry sets `disabled`. The
14946
+ empty pair is DERIVED and carries no built-in treatment — it is the hook for your own "nothing in
14947
+ this bucket" styling, and a zero band stays actionable. The disabled pair is DECLARED: that control
14948
+ renders genuinely disabled (no tab stop, no hover or press affordance) and activating it emits no
14949
+ `lr-segment-activate`. Inertness is never inferred from a zero value, because a zero band is
14950
+ legitimately clickable in a budget meter
14622
14951
 
14623
14952
  **Themeable custom properties:** `--lr-context-meter-segment-color` is set per segment when its
14624
14953
  `color` field is supplied, and is read by both `segment` and its matching `legend-swatch` so the
@@ -14639,7 +14968,9 @@ selected band stays visibly selected exactly while it is being pointed at or foc
14639
14968
  ring stays intact. `--lr-context-meter-selected-arc-stroke` (default `16`, in this component's
14640
14969
  `0 0 100 100` viewBox units) is the stroke width of a selected `ring`-shape arc: every arc shares
14641
14970
  one bounding box, so a selected arc reports itself by thickening in place rather than by an outline
14642
- that would trace the whole ring identically for every selection. Otherwise the component consumes shared tokens
14971
+ that would trace the whole ring identically for every selection.
14972
+ `--lr-context-meter-disabled-opacity` (default `var(--lr-opacity-disabled)`) dims a band or legend row whose entry sets
14973
+ `disabled`; the band keeps its own colour, since that colour is the datum. Otherwise the component consumes shared tokens
14643
14974
  `--lr-space-xs`, `--lr-color-text-quiet`, `--lr-font`, `--lr-radius`, `--lr-color-border`,
14644
14975
  `--lr-color-brand`, `--lr-color-success`, `--lr-color-warning`, `--lr-color-danger`,
14645
14976
  `--lr-transition-base`.
@@ -15259,6 +15590,7 @@ These named interfaces and helper signatures are available to typed integrations
15259
15590
  value: number;
15260
15591
  tone?: ContextMeterTone;
15261
15592
  color?: string;
15593
+ disabled?: boolean;
15262
15594
  }`
15263
15595
  Import: `@aceshooting/lyra-ui/components/data/context-meter/context-meter.class.js`.
15264
15596
  `LyraContextMeterSegmentActivateDetail {
@@ -15557,6 +15889,7 @@ These named interfaces and helper signatures are available to typed integrations
15557
15889
  readonly accessibleLabel?: string;
15558
15890
  readonly inputs?: readonly FlowHandle[];
15559
15891
  readonly outputs?: readonly FlowHandle[];
15892
+ readonly disabled?: boolean;
15560
15893
  }`
15561
15894
  Import: `@aceshooting/lyra-ui/components/data/flow-canvas/flow-types.js`.
15562
15895
  `FlowRunDecoration {
@@ -15752,6 +16085,7 @@ These named interfaces and helper signatures are available to typed integrations
15752
16085
  readonly categoryId: string;
15753
16086
  readonly marker?: boolean;
15754
16087
  readonly label?: string;
16088
+ readonly disabled?: boolean;
15755
16089
  }`
15756
16090
 
15757
16091
  - **`components-data-stat-stat-contracts`** — Supporting data types and helpers for this component family.
@@ -15784,6 +16118,7 @@ These named interfaces and helper signatures are available to typed integrations
15784
16118
  heatValue?(row: T): number | null | undefined;
15785
16119
  editTrigger?: TableColumnEditTrigger;
15786
16120
  editValue?: (row: T) => string | number;
16121
+ editLabel?: (row: T) => string;
15787
16122
  editType?: 'text' | 'number' | 'select';
15788
16123
  editOptions?: TableColumnEditOption[];
15789
16124
  cell: (row: T) => unknown;
@@ -17540,6 +17875,18 @@ rotation and activation, while background, radius, hover/press mixes, focus ring
17540
17875
  come from `--lr-icon-button-*`, and the component's own `--lr-reorder-item-move-button-*` hooks still
17541
17876
  win over those defaults), `content` (default-slot wrapper).
17542
17877
 
17878
+ **Border reaches the composed move controls the same way background/color/radius do.** This
17879
+ component paints no resting border on either move control, so it relays no
17880
+ `--_lr-icon-button-border-default` into their private fallback tier — but that absence is not a
17881
+ gap. The public `--lr-icon-button-border` (and its `-hover`/`-active` variants) is the FIRST arm
17882
+ of the token chain, resolved by ordinary custom-property inheritance regardless of whether this
17883
+ component relays a default for that same property, so setting it on either move control or an
17884
+ ancestor reaches it exactly as the background/color/radius tokens do. A component with no resting
17885
+ border simply has no default to relay, which is different from border theming being broken. Size
17886
+ remains the one exception that does not cross this way: use `--lr-theme-icon-button-size`, never
17887
+ `--lr-icon-button-size` — every `LyraElement` re-declares the latter on its own `:host`, so it
17888
+ never reaches a composed child (see `llms/tokens.md`).
17889
+
17543
17890
  **Themeable custom properties:** `--lr-reorder-item-gap` (default `var(--lr-space-xs)`) — gap
17544
17891
  between the move buttons and the row content. The move-button interaction paints are independent,
17545
17892
  inherited inline fallbacks: `--lr-reorder-item-move-button-hover-bg` (default
@@ -17791,6 +18138,17 @@ list's `base` scroll container exposes horizontal scrolling for that explicit op
17791
18138
  number `56` and fixes every row to that many pixels. Property callers assign a number, not a
17792
18139
  numeric string. Anything else (non-numeric, zero, negative, non-finite) safely canonicalizes to
17793
18140
  `'auto'` rather than throwing.
18141
+ - `rowProjection: 'shadow' | 'light' = 'shadow'` (attribute `row-projection`) — where
18142
+ `renderItem`'s output is instantiated. `'shadow'` (default) stamps it inside this component's own
18143
+ shadow root, so only inherited custom properties and the public row parts reach it. `'light'`
18144
+ renders the windowed rows into the host's own light DOM instead, assigned into the shadow viewport
18145
+ through internal named slots, so ordinary document CSS styles a virtualized row exactly as it
18146
+ styles the same row unvirtualized. The component keeps owning windowing, measurement, spacer
18147
+ sizing, `scrollToIndex()`, the external-scroller mode and the ARIA contract either way, and
18148
+ positioning stays on the shadow-side `[part="row"]` wrapper that document CSS cannot select — so
18149
+ consumer styles can never break windowing. Any other value canonicalizes to `'shadow'`. Left
18150
+ unset, the rendered output is byte-identical to before and the host's light DOM stays empty.
18151
+ See **Light-DOM row projection** below for the trade-offs it carries.
17794
18152
  - `itemRole: 'listitem' | 'row' = 'listitem'` (attribute `item-role`) — `'listitem'` (default)
17795
18153
  preserves the plain `role="list"`/`role="listitem"` mapping with `aria-setsize`/`aria-posinset`.
17796
18154
  `'row'` additionally maps `[part="base"]` to `role="rowgroup"`, `[part="spacer"]` to
@@ -18045,6 +18403,48 @@ default estimate with sparse `ResizeObserver` measurements for rows that have ac
18045
18403
  decides: groups render before the rows, so an active row wins while (and only while) it needs to,
18046
18404
  which is right — a group header is a non-interactive `pointer-events: none` label.
18047
18405
 
18406
+ ### Light-DOM row projection
18407
+
18408
+ `rowProjection="light"` exists for one shape: an application whose list rows are already styled by
18409
+ its own global stylesheet, and which therefore could not adopt virtualization without rehoming a
18410
+ dozen descendant rules per row into a new custom element or a growing set of custom properties. In
18411
+ projection mode the windowed rows render into the host's own light DOM, so ordinary document CSS
18412
+ reaches row content directly.
18413
+
18414
+ Positioning, measurement and semantics stay where they were. The `[part="row"]` wrapper remains in
18415
+ the shadow root and keeps `position: absolute`, the per-frame `transform`, `role`, `aria-setsize`/
18416
+ `aria-posinset` (or `aria-rowindex`) and the `ResizeObserver` box — document CSS cannot select it,
18417
+ so consumer styles can never break windowing. The whole part vocabulary (`base`, `spacer`, `row`,
18418
+ `group`, `sticky-group`) keeps matching in both modes, and `row-height="auto"` still measures
18419
+ projected content because the light row is an ordinary in-flow child of that wrapper.
18420
+
18421
+ `projectedRows: HTMLElement[]` returns the projected light-DOM row wrappers in item order, and is
18422
+ empty outside projection mode. The exported type is `LyraVirtualListRowProjection`; the reserved
18423
+ attributes marking library-owned light-DOM nodes are exported as `VIRTUAL_LIST_ROW_ATTRIBUTE`
18424
+ (`data-lr-virtual-list-row`) and `VIRTUAL_LIST_STICKY_ATTRIBUTE` (`data-lr-virtual-list-sticky`).
18425
+
18426
+ **Known gotchas, all inherent to handing the cascade back to the consumer:**
18427
+
18428
+ - **One component-owned wrapper sits between the host and your markup.** A slot cannot assign a text
18429
+ node or a multi-root fragment by attribute, so each row's content lives inside a wrapper carrying
18430
+ `data-lr-virtual-list-row`. Descendant selectors (`lr-virtual-list .row-title`) port unchanged;
18431
+ child combinators (`lr-virtual-list > .row`), `:nth-child`, `:first-child` and sibling combinators
18432
+ written against the unvirtualized markup do not. `:nth-child` on the wrappers reflects the current
18433
+ *window*, not the item index.
18434
+ - **`closest('[part="row"]')` stops resolving.** A delegated listener on the host now sees an
18435
+ un-retargeted `event.target` inside the light DOM. Use `closest('[data-lr-virtual-list-row]')`.
18436
+ - **The document cascade now reaches row content**, including resets and element-level rules that
18437
+ previously could not, so a projected row can look different from the same row in shadow mode.
18438
+ - **Per-row light-DOM state does not survive a disconnect/reconnect.** Disconnect removes the
18439
+ projected rows completely (no rows, no markers, no anchor left behind), so a reparenting move
18440
+ rebuilds them. Scroll position, measurements and the window are unaffected — they live in
18441
+ component state, not in the rows.
18442
+ - **Projection activates one task after hydration.** A server render has no DOM to project into, so
18443
+ the first window is shadow-rendered, hydration matches the server markup, and the rows then swap
18444
+ into the light DOM on the next task.
18445
+ - **A row taken out of flow collapses its wrapper.** `position: fixed`/`absolute` or
18446
+ `display: none` on your own row leaves nothing for the wrapper to measure.
18447
+
18048
18448
  ---
18049
18449
 
18050
18450
  ## `lr-app-rail`
@@ -18240,9 +18640,18 @@ width), `--lr-app-rail-overlay-color` (default `var(--lr-color-overlay)` — the
18240
18640
  color; component-specific since no shared token exists), `--lr-app-rail-panel-inset-block-start`
18241
18641
  (default `0`, applied to both `[part="panel"]` and `[part="backdrop"]` — raise it to leave room for
18242
18642
  a fixed app bar/status area above the drawer instead of the panel/scrim starting flush with the
18243
- viewport top), `--lr-app-rail-panel-radius` (default `0` — corner radius of `[part="panel"]`; pairs
18244
- naturally with a nonzero `--lr-app-rail-panel-inset-block-start`, which exposes the panel's top
18245
- corners), `--lr-app-rail-panel-overflow-block` (default `auto`) and
18643
+ viewport top), `--lr-app-rail-panel-radius` (default `0` — uniform corner radius of `[part="panel"]`;
18644
+ pairs naturally with a nonzero `--lr-app-rail-panel-inset-block-start`, which exposes the panel's top
18645
+ corners). Four direction-aware per-corner tokens each default to `--lr-app-rail-panel-radius`, so
18646
+ setting only the uniform token still rounds all four corners exactly as before:
18647
+ `--lr-app-rail-panel-radius-start-start` and `--lr-app-rail-panel-radius-end-start` (logical
18648
+ `border-start-start-radius`/`border-end-start-radius` — the two corners at the panel's own flush
18649
+ inline-start edge, since the drawer always sits flush against `inset-inline-start: 0`) and
18650
+ `--lr-app-rail-panel-radius-start-end`/`--lr-app-rail-panel-radius-end-end` (logical
18651
+ `border-start-end-radius`/`border-end-end-radius` — the two corners away from that flush edge, the
18652
+ pair a flush-against-one-edge drawer typically rounds). All four are logical, so which physical
18653
+ corner each one paints swaps under `dir="rtl"` with no second consumer rule.
18654
+ `--lr-app-rail-panel-overflow-block` (default `auto`) and
18246
18655
  `--lr-app-rail-panel-overflow-inline` (default `clip`) — `[part="panel"]`'s logical overflow axes;
18247
18656
  either non-`visible` value clips a `position: fixed` popup opened by a slotted/nav-item control
18248
18657
  (e.g. a slotted `<lr-select>`/`<lr-menu>`) whenever its rendered box extends past the panel,
@@ -18258,7 +18667,10 @@ docked rail chrome). `--lr-app-rail-header-padding` and `--lr-app-rail-footer-pa
18258
18667
  `var(--lr-space-m)`) retune `[part="header"]`/`[part="footer"]`'s padding independently.
18259
18668
  `--lr-app-rail-header-min-block-size` (default `auto`, the property's own initial value, so unset
18260
18669
  reproduces today's exact height) reserves a minimum height for `[part="header"]`, for content that
18261
- mounts or resizes asynchronously. Plus shared
18670
+ mounts or resizes asynchronously. `--lr-app-rail-nav-padding` and `--lr-app-rail-nav-gap` (default
18671
+ `var(--lr-space-s)`/`var(--lr-space-xs)`, the values this rule hard-coded before either token
18672
+ existed) retune `[part="nav"]`'s own padding and inter-item gap — the rail's vertical rhythm,
18673
+ previously reachable only through `::part(nav)`. Plus shared
18262
18674
  tokens (`--lr-color-border`,
18263
18675
  `--lr-color-surface`, `--lr-color-text`, `--lr-color-brand`, `--lr-color-brand-quiet`,
18264
18676
  `--lr-space-*`, `--lr-radius`, `--lr-shadow`, `--lr-icon-button-size`,
@@ -18391,10 +18803,22 @@ removing the label from the accessibility tree.
18391
18803
  `<lr-app-rail>` as the viewport narrows) hides it from view. No effect outside icon-only mode,
18392
18804
  since the label is already visible there. `false` (the default) reproduces the exact existing
18393
18805
  output.
18806
+ - `expanded: boolean = false` (reflected) — whether this item's own `children` are shown. `false`
18807
+ reproduces exactly what an item without this property rendered before this feature existed.
18808
+ Driven through the same request/commit pair as `<lr-app-rail-group>`'s `open`, see Events below.
18394
18809
 
18395
18810
  A host `aria-label` is copied to the rendered native link or button by attribute presence,
18396
18811
  including an explicitly empty value; without it, the default slot supplies the native name. The
18397
- same precedence supplies the tooltip text when that opt-in flyout is visible.
18812
+ same precedence supplies the tooltip text when that opt-in flyout is visible, and the disclosure's
18813
+ interpolated `{label}` (see Events below).
18814
+
18815
+ **Events:** `lr-toggle-request` — cancelable, emitted before `expanded` changes from the built-in
18816
+ disclosure (`detail: { open }` — the field is named `open`, matching `<lr-app-rail-group>`'s
18817
+ identical event name and detail shape exactly). Call `preventDefault()` to keep the current state,
18818
+ or assign `expanded` from the listener to resolve it yourself; a write during the dispatch
18819
+ suppresses the default commit even when it assigns the value the property already held. Not
18820
+ emitted for a direct `expanded` write. `lr-toggle` — non-cancelable, emitted after `expanded` is
18821
+ written, never for a vetoed or listener-resolved request (`detail: { open }`).
18398
18822
 
18399
18823
  **Methods:** `click(): void` activates the internal native link or button; it is a no-op while
18400
18824
  `disabled`.
@@ -18417,6 +18841,28 @@ names the native control, which remains the sole action).
18417
18841
  control keeps its own click, keyboard activation and focus order instead of being swallowed.
18418
18842
  Unlike `meta` it stays visible in `icon-only` mode, where it shares the narrow rail's width with
18419
18843
  the icon.
18844
+ - `children` slot — nested `<lr-app-rail-item>`s disclosed beneath this item (the
18845
+ treeitem-with-link pattern: the row itself navigates, a separate disclosure expands its own
18846
+ child rows). Slotting anything into it grows a built-in `[part="toggle"]` disclosure button as a
18847
+ SIBLING of the item's own link/button, never nested inside it, so the link keeps navigating on
18848
+ its own and the disclosure keeps toggling on its own — clicking one never triggers the other.
18849
+ Leaving `children` empty renders neither the disclosure nor `[part="children"]` at all: an item
18850
+ authored without any `children` content renders byte-identically to one authored before this
18851
+ slot existed. The disclosure carries `aria-expanded` (both states) and `aria-controls` pointing
18852
+ at `[part="children"]`'s id, and a localized accessible name interpolating this item's own label
18853
+ (`Expand {label}`/`Collapse {label}` in the default locale — no literal fallback, so a
18854
+ `registerLyraLocale()` translation or a `.strings` override always reaches it). `<lr-app-rail-group>`
18855
+ cannot express this pattern: its collapsible heading *is* the toggle, so a navigable link cannot
18856
+ live inside it without nesting an interactive element inside a button.
18857
+
18858
+ `icon-only` forwards from this item onto every `<lr-app-rail-item>` it directly owns through
18859
+ `children` — including ones appended later — exactly how `<lr-app-rail-group>` forwards onto the
18860
+ items and nested groups it owns. The disclosure itself never changes shape between
18861
+ presentations: it is always a fixed icon-button-sized square beside `[part="base"]`, reusing the
18862
+ same hover/active/focus tokens as the link/button (`--lr-app-rail-item-hover-bg` etc.) rather than
18863
+ a second disclosure-only set. There is no ancestor-current treatment — `<lr-app-rail-group>` has
18864
+ no equivalent concept for a group containing the current item, so none is invented here either; a
18865
+ current descendant stays perceivable only through its own `current` property.
18420
18866
 
18421
18867
  Both wrappers (`[part="meta"]`, `[part="end"]`) are hidden while empty, so an item using neither
18422
18868
  renders exactly as before. Note that while the mobile overlay is open, a click anywhere in the
@@ -18425,10 +18871,16 @@ nav-slot behaviour, not new to these slots.
18425
18871
 
18426
18872
  **CSS parts:** `base`, `icon`, `label`, `current-indicator` (a decorative inline indicator rendered
18427
18873
  only while the item is `current`/`aria-current="page"`, mirroring `<lr-conversation-item>`'s
18428
- shipped `active-indicator` part), `tooltip` (the hover/focus label flyout, only rendered while
18429
- `tooltip` is set, the item is `icon-only`, and it is hovered or focused), `meta` (the wrapper around
18430
- the `meta` slot, hidden while empty) and `end` (the wrapper around the `end` slot, hidden while
18431
- empty).
18874
+ shipped `active-indicator` part suppressed by default while `icon-only`, see the current-ring
18875
+ tokens below), `tooltip` (the hover/focus label flyout, only rendered while `tooltip` is set, the
18876
+ item is `icon-only`, and it is hovered or focused), `meta` (the wrapper around the `meta` slot,
18877
+ hidden while empty), `end` (the wrapper around the `end` slot, hidden while empty), `toggle` (the
18878
+ `children` disclosure, rendered only while something is slotted into `children`; a sibling of
18879
+ `base`, never nested inside it), `toggle-icon` (the wrapper around the disclosure chevron,
18880
+ direction-aware through this wrapper's own `transform` — mirrors `<lr-app-rail-group>`'s own
18881
+ `[part="toggle-icon"]`) and `children` (the wrapper around the `children` slot, rendered only
18882
+ alongside `toggle`; hidden — but present, so `aria-controls` keeps resolving — while `expanded` is
18883
+ `false`).
18432
18884
 
18433
18885
  **Themeable custom properties:** `--lr-app-rail-item-current-bg` (default
18434
18886
  `var(--lr-color-brand-quiet)`), `--lr-app-rail-item-current-color` (default
@@ -18447,6 +18899,16 @@ before. `--lr-app-rail-item-current-font-weight` mirrors `<lr-stepper>`'s
18447
18899
  `--lr-app-rail-item-current-indicator-width` (default `var(--lr-size-2px)`), and
18448
18900
  `--lr-app-rail-item-current-indicator-inset-inline` (default `0 auto`; set `auto 0` to place the
18449
18901
  indicator at the inline-end edge instead) theme `[part="current-indicator"]`.
18902
+ `--lr-app-rail-item-current-indicator-display` (no default; unset resolves to `none` while
18903
+ `icon-only`) restores the indicator bar in icon-only presentation — a full-height edge bar reads
18904
+ as a rendering glitch on the square icon-only tile, so it is suppressed there by default; full
18905
+ presentation is unaffected either way, since its own `[part="current-indicator"]` rule declares no
18906
+ `display` at all. `--lr-app-rail-item-current-ring` (no default; unset resolves to `none` in full
18907
+ presentation and an inset ring in icon-only presentation) sets `box-shadow` on `[part="base"]`
18908
+ while current: unset, icon-only gets an inset ring automatically — the non-color-only signal
18909
+ (WCAG 1.4.1) that replaces the bar suppressed there, since full presentation already conveys
18910
+ current state through the indicator bar and `--lr-app-rail-item-current-font-weight`. Setting this
18911
+ token explicitly applies the same value in both presentations.
18450
18912
  Ordinary interaction states are independently inheritable through
18451
18913
  `--lr-app-rail-item-hover-bg`, `--lr-app-rail-item-hover-color`,
18452
18914
  `--lr-app-rail-item-active-bg`, and `--lr-app-rail-item-active-color`, again retaining the former
@@ -18464,7 +18926,22 @@ floor-clamped since the icon is decorative, not itself a pointer target), and
18464
18926
  is retuned while family/weight/line-height stay inherited) retune the row's geometry.
18465
18927
  While `icon-only`, `[part="base"]` resolves to a square hit target matching the icon-button
18466
18928
  footprint used elsewhere in this library (`aspect-ratio: 1` against its already floor-clamped
18467
- block size) instead of stretching across the rail's icon column.
18929
+ block size) instead of stretching across the rail's icon column. `--lr-app-rail-item-icon-only-size`
18930
+ (no default) sizes that square directly — both `inline-size` and `block-size`, and the row's own
18931
+ `min-block-size` floor — independent of `--lr-app-rail-item-min-block-size`, so a taller expanded
18932
+ row and an icon-only square pinned to `--lr-icon-button-size` can coexist. Unset, the square is
18933
+ still derived via `aspect-ratio: 1` against the row's block size exactly as before.
18934
+
18935
+ **`--lr-positioning-strategy`** (16.0.0) — the icon-only flyout tooltip reads this same cascading
18936
+ `absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
18937
+ its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
18938
+ property on `<lr-app-rail-item>`; set the custom property on `:root`, a theme, or one clipping
18939
+ ancestor to change every unset rail item's flyout beneath it.
18940
+
18941
+ `--lr-app-rail-item-indent` (default `var(--lr-space-l)`) sets `[part="children"]`'s
18942
+ `padding-inline-start`. Applied once per nesting level — a doubly-nested `children` list compounds
18943
+ two insets automatically, since each level's own `[part="children"]` applies the token again.
18944
+ Logical, so it mirrors under `dir="rtl"` with no separate rule.
18468
18945
 
18469
18946
  **Optional peer deps:** none.
18470
18947
 
@@ -18791,6 +19268,13 @@ an out-of-syntax value falls back cleanly instead of invalidating the whole decl
18791
19268
  without `CSS.registerProperty` degrades to "use `100%`, not `none`".) A menu contained by
18792
19269
  `<lr-dropdown>` sizes from its dropdown and is unaffected by both names.
18793
19270
 
19271
+ **`--lr-positioning-strategy`** (16.0.0) — the private submenu surface reads this same cascading
19272
+ `absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
19273
+ its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
19274
+ property on `<lr-menu>`; set the custom property on `:root`, a theme, or one clipping ancestor to
19275
+ change every unset submenu beneath it. A menu contained by `<lr-dropdown>` is positioned by the
19276
+ dropdown instead and is unaffected.
19277
+
18794
19278
  **Methods:** no menu-specific public overlay methods. Use `<lr-dropdown>`'s `show()`/`hide()` and
18795
19279
  `open` state for an overlay. Menu-item submenu methods remain public because they drive a row's
18796
19280
  nested disclosure.
@@ -19198,7 +19682,8 @@ to `<wa-card>`'s contract, staying slot-compatible with `lr-result-card` where t
19198
19682
  - `aria-pressed` and `aria-current` (attributes only) — forwarded reactively onto the native
19199
19683
  control the card actually renders, the same mechanism `<lr-button>` and `<lr-icon-button>` use.
19200
19684
  `aria-pressed` accepts `'true' | 'false' | 'mixed'` and reaches the `activation-button` only —
19201
- `link` has no pressed state, so a linked card never receives it. The global `aria-current`
19685
+ `link` has no pressed state, so a linked card never receives it the same **16.0.0** carve-out
19686
+ `<lr-button>` and `<lr-icon-button>` took. The global `aria-current`
19202
19687
  accepts `'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'` and reaches both the
19203
19688
  activation button and the stretched link. Anything outside those sets is dropped rather than
19204
19689
  passed through, so a typo never reaches the accessibility tree. This is what lets a single-select
@@ -19972,7 +20457,43 @@ Dashboard filter row that composes Lyra inputs and removable chips, with reset a
19972
20457
  Choice option entries must expose string `value` and `label` data fields; malformed entries are
19973
20458
  omitted independently, while supplied empty strings remain valid. A custom definition requires its
19974
20459
  adapter and a callable `render`; a rejected definition does not reserve its filter ID. Valid
19975
- siblings remain available. Exceptions thrown by an admitted trusted renderer still propagate.
20460
+ siblings remain available. Exceptions thrown by an admitted trusted renderer still propagate. A
20461
+ chip-only definition requires neither options nor an adapter — a stable filter ID and a label are
20462
+ the whole schema, since a malformed `formatValue`/`isEmpty` still has a correct fallback and so is
20463
+ guarded where it is used rather than rejected outright.
20464
+
20465
+ **Lean registration entry.** `components/layout/filter-bar/filter-bar.js` (the default entry)
20466
+ eagerly imports every composed control this bar could possibly render — `<lr-select>`,
20467
+ `<lr-combobox>`, `<lr-dropdown>` + `<lr-dropdown-item>` (the `'checkbox-menu'` branch),
20468
+ `<lr-date-input>`, `<lr-input>`, `<lr-chip>`/`<lr-chip-group>` (the active-filter row), and
20469
+ `<lr-button>`/`<lr-spinner>` (the reset action and the loading status) — because `filters` is a
20470
+ runtime value it cannot inspect ahead of time. A bar that only ever declares `'select'`/`'text'`
20471
+ filters still pays for `<lr-combobox>` and `<lr-date-input>` through that entry: a measured ~69.5 kB
20472
+ gzip more than importing only what it uses. A consumer who knows their own filter `type`s ahead of
20473
+ time can import `components/layout/filter-bar/filter-bar-register.js` instead, which registers
20474
+ `<lr-filter-bar>` and nothing else, then import each composed control's own registration entry for
20475
+ the filter `type`s actually declared:
20476
+
20477
+ | Filter `type` | Registration entry |
20478
+ | --- | --- |
20479
+ | `'select'` | `components/forms/select/select.js` |
20480
+ | `'combobox'` | `components/forms/combobox/combobox.js` |
20481
+ | `'checkbox-menu'` | `components/overlays/overlay/dropdown.js` **and** `components/layout/menu/dropdown-item.js` |
20482
+ | `'date'` / `'date-range'` | `components/forms/date-picker/date-input.js` |
20483
+ | `'text'` | `components/forms/input/input.js` |
20484
+ | `'chip'` | none — renders no control |
20485
+
20486
+ Two more are unconditional regardless of which filter `type`s are declared: `<lr-button>` renders
20487
+ the reset action on every bar, and `<lr-chip>`/`<lr-chip-group>` render the active-filter row
20488
+ whenever any filter has a value (further gated by `activeFiltersDisplay`, but never provably absent
20489
+ for a generic bar) — `components/forms/button/button.js` and `components/overlays/chip/chip.js` +
20490
+ `components/overlays/chip/chip-group.js`. `<lr-spinner>`
20491
+ (`components/overlays/spinner/spinner.js`) is the one built-in dependency the lean entry omits even
20492
+ though every bar could use it: `loading` is a plain boolean any consumer can leave unset entirely,
20493
+ unlike a filter `type`, which `filters` always names outright — import it too if the bar ever sets
20494
+ `loading`. A filter definition whose `type` has no matching import above renders no usable control
20495
+ until something else registers it, the same trade `icon-button-register.js` documents for
20496
+ `<lr-icon-button>`'s own `icon`/`src` attribute.
19976
20497
 
19977
20498
  **Properties:**
19978
20499
 
@@ -19998,8 +20519,20 @@ siblings remain available. Exceptions thrown by an admitted trusted renderer sti
19998
20519
  - `loading: boolean = false` (reflected) — shows the status spinner and disables reset while leaving
19999
20520
  filters editable.
20000
20521
  - `hasActiveFilters: boolean` (read-only) — whether any configured filter currently has a value.
20522
+ Drives the reset button's own disabled state; unaffected by `activeFiltersDisplay`.
20001
20523
  - `invalidFilterIds: readonly string[]` (read-only) — immutable ids of required filters whose
20002
20524
  values are unset.
20525
+ - `activeFiltersDisplay: 'all' | 'changed' | 'hidden' = 'all'` (reflected, attribute
20526
+ `active-filters-display`) — which currently-active filters render as removable chips in the row
20527
+ below the fields. `'all'` (the default, and this component's only behavior before this property
20528
+ existed) shows one chip per non-empty filter, including one sitting at its own `defaultValue`.
20529
+ `'changed'` shows a chip only for a filter whose value differs from its own `defaultValue` — so a
20530
+ bar whose defaults narrow the view on load does not claim the user narrowed it — and a filter with
20531
+ no declared `defaultValue` counts as changed as soon as it has any value at all. `'hidden'` never
20532
+ renders the row. Array values compare against `defaultValue` positionally (same length, same entry
20533
+ at each index), matching this component's only other array-equality precedent (a custom adapter's
20534
+ own `clearValue` comparison); a `'date-range'` value is a single composed string, so it compares
20535
+ like any other string. Removing a chip always clears that filter, unaffected by this property.
20003
20536
 
20004
20537
  The composed reset action uses `lr-button`'s default `m` size tier, matching the default rendered
20005
20538
  height of adjacent select, combobox, input, and date fields instead of introducing a shorter action
@@ -20028,12 +20561,13 @@ action) rendered inside `controls`, next to the reset button. Hidden and claimin
20028
20561
  space while nothing is slotted.
20029
20562
 
20030
20563
  **CSS parts:** `base`, `controls`, `field`, `field-<filterId>`, `end`, `filter-control`,
20031
- `filter-control-label`, `filter-control-field`, `filter-control-input`, `filter-control-start`,
20032
- `filter-control-end`, `filter-control-listbox`, `filter-control-option`, `filter-control-tags`,
20033
- `filter-control-tag`, `filter-control-tag-label`, `filter-control-clear-button`,
20034
- `filter-control-expand-button`, `filter-control-expand-icon`, `filter-control-popup`,
20035
- `filter-control-error`, `filter-control-hint`, `active-filters`, `chips`, `chip`, `reset-button`,
20036
- `status`.
20564
+ `filter-control-label`, `filter-control-label-group`, `filter-control-field`,
20565
+ `filter-control-input`, `filter-control-start`, `filter-control-end`, `filter-control-listbox`,
20566
+ `filter-control-option`, `filter-control-tags`, `filter-control-tag`, `filter-control-tag-label`,
20567
+ `filter-control-tag-remove-button`, `filter-control-tag-remove-button-base`,
20568
+ `filter-control-clear-button`, `filter-control-expand-button`, `filter-control-expand-icon`,
20569
+ `filter-control-popup`, `filter-control-error`, `filter-control-hint`, `active-filters`, `chips`,
20570
+ `chip`, `reset-button`, `status`.
20037
20571
 
20038
20572
  The `filter-control-*` parts are semantic aliases forwarded from each built-in control's shadow
20039
20573
  surface. `filter-control-field` consistently reaches the select trigger, combobox container, or
@@ -20041,9 +20575,23 @@ text/date input wrapper; `filter-control-input` reaches the corresponding displa
20041
20575
  Listbox/option aliases apply to select and combobox filters, `filter-control-tags`/
20042
20576
  `filter-control-tag`/`filter-control-tag-label` apply to a `multiple` combobox filter's selected-tag
20043
20577
  chips (`filter-control-tag-label` is capped by that control's own `--tag-max-size`), and
20044
- expand-button/popup apply to date filters. This lets a consumer theme the composed tier from
20578
+ expand-button/popup apply to date filters. `filter-control-tag-remove-button`/
20579
+ `filter-control-tag-remove-button-base` reach a selected tag's own remove button and its inner icon
20580
+ wrapper — the same reach a standalone `lr-combobox`/`lr-select` consumer already has, now available
20581
+ from `lr-filter-bar` too, for a consumer re-skinning filter tags as pills who needs the remove
20582
+ target inside one to be stylable. This lets a consumer theme the composed tier from
20045
20583
  `lr-filter-bar::part(...)` without depending on the built-in control type selected by a filter
20046
20584
  definition. Custom renderers retain ownership of their own part forwarding.
20585
+
20586
+ A `multiple` `'combobox'` filter collapses past its own `max-options-visible` (3 by default, an
20587
+ `<lr-combobox>` property this component does not forward) into a localized "+N" overflow indicator,
20588
+ the same substance as `lr-select`'s own `multiple`-mode overflow chip. The one remaining difference:
20589
+ `lr-select`'s overflow chip carries a second, distinguishing `tag-overflow` part
20590
+ (`part="tag tag-overflow tag__base"`) so a consumer can style just that chip; `lr-combobox`'s
20591
+ overflow chip carries only the plain `tag` part, with no equivalent token to forward as
20592
+ `filter-control-tag-overflow`. Adding one is `<lr-combobox>`'s own surface to grow, not something
20593
+ `lr-filter-bar`'s `exportparts` can manufacture for a part its composed child never renders — noted
20594
+ here as a known, deliberate gap rather than silently undocumented.
20047
20595
  On a `'checkbox-menu'` filter, `filter-control-field` is the trigger button's own frame — the
20048
20596
  element inside `<lr-button>` that draws the border, background and radius, not the chrome-less
20049
20597
  button host, so a `::part(filter-control-field) { border-color: … }` rule works there exactly as it
@@ -20052,7 +20600,19 @@ a definition `icon` lands), `filter-control-input` is its selection summary, `fi
20052
20600
  is the trigger's own label text (not a stacked label above the control), `filter-control-listbox` is
20053
20601
  the dropdown's popup surface, `filter-control-option` is one `role="menuitemcheckbox"` row, and
20054
20602
  `filter-control-error` is the revealed required message — rendered by the bar itself, because the
20055
- composed dropdown has no error chrome of its own.
20603
+ composed dropdown has no error chrome of its own. The trigger also renders a `with-caret` disclosure
20604
+ chevron, matching `lr-select`'s own — forwarded as `filter-control-expand-icon`, the same name a
20605
+ select/combobox/date-input filter's own chevron already uses, so one consumer rule styles every
20606
+ filter type's expand icon. `filter-control-label-group` reaches the trigger's own label wrapper —
20607
+ the flex row this component lays `filter-control-label` and `filter-control-input` out in, which
20608
+ also grows to fill the stretched trigger (via `with-caret`) so its content starts at the leading
20609
+ edge instead of centring; no other filter type renders this part, since every other type's label and
20610
+ input are two independent elements with no shared wrapper of their own. This component does not
20611
+ render a stacked label above a `'checkbox-menu'` field the way every other built-in type does:
20612
+ every other type's stacked label is rendered by the composed control itself, and there is no
20613
+ equivalent shared "stacked label" template inside `<lr-filter-bar>` for this branch to reuse without
20614
+ inventing a new one, so `labelVisibility` keeps its narrower meaning here (whether the trigger's own
20615
+ baked-in label text is visible or screen-reader-only).
20056
20616
 
20057
20617
  `field` wraps one filter's composed control and its validation spacer inside `controls`; its
20058
20618
  flex-basis is themeable via `--lr-filter-bar-field-basis` (default `var(--lr-size-12rem)`).
@@ -20070,12 +20630,17 @@ alone, exactly as before this part existed, rather than risking a `part` attribu
20070
20630
  space-separated token list fabricates an unrelated second token.
20071
20631
 
20072
20632
  A `'select'`, `'combobox'` or `'checkbox-menu'` filter's required `options` entries are
20073
- `LyraFilterBarOption { value, label, icon?, searchText? }`. `searchText` is extra text the option
20074
- also matches on, forwarded verbatim to `<lr-option>`'s own `search-text`, so a row can keep a short
20075
- visible `label` ("Urgent") while still matching a long canonical key ("SEV-1 production outage").
20076
- It affects a `'combobox'` filter only: the attribute is written on every choice type's `<lr-option>`,
20077
- but `<lr-select>`'s listbox type-ahead matches the option's `label` alone and never reads it, and a
20078
- `'checkbox-menu'` has no text entry to match against.
20633
+ `LyraFilterBarOption { value, label, icon?, searchText?, disabled? }`. `searchText` is extra text
20634
+ the option also matches on, forwarded verbatim to `<lr-option>`'s own `search-text`, so a row can
20635
+ keep a short visible `label` ("Urgent") while still matching a long canonical key ("SEV-1
20636
+ production outage"). It affects a `'combobox'` filter only: the attribute is written on every
20637
+ choice type's `<lr-option>`, but `<lr-select>`'s listbox type-ahead matches the option's `label`
20638
+ alone and never reads it, and a `'checkbox-menu'` has no text entry to match against. `disabled`
20639
+ marks the option non-actionable: forwarded to `<lr-option disabled>` for `'select'`/`'combobox'`
20640
+ and to the composed `<lr-dropdown-item disabled>` for `'checkbox-menu'`, so the row renders
20641
+ genuinely disabled (no tab/roving stop, no hover/press affordance) and arrow-key navigation already
20642
+ steps past it, since that is the composed control's own existing `disabled` behavior. Omitted or
20643
+ `false` renders the option exactly as before this field existed.
20079
20644
  `icon` is optional Lit content — a status dot, a type glyph, a flag — rendered into the composed
20080
20645
  `<lr-option>`'s own `start` slot as inert, `aria-hidden` chrome, so it never joins the option's
20081
20646
  accessible name:
@@ -20097,7 +20662,9 @@ same-named counterparts (with `combobox`'s `multiple` opting into a multi-value
20097
20662
  `'date'`/`'date-range'` both map to `<lr-date-input>` (single vs. `mode="range"`), and `'text'` maps
20098
20663
  to `<lr-input>` for an open-ended free-text query rather than a closed choice set. A `'text'`
20099
20664
  filter's value is the raw query string, verbatim, and its chip shows exactly that string — the same
20100
- text the user typed, not a truncated or normalized form.
20665
+ text the user typed, not a truncated or normalized form. `'chip'` is the one type that renders no
20666
+ control at all (see **Chip-only filters** below): its value belongs to a widget elsewhere on the
20667
+ page, so the bar renders only its active-filter chip and gives it no toolbar cell.
20101
20668
 
20102
20669
  Every built-in (non-`'custom'`) filter definition additionally accepts optional `size: LyraSize`,
20103
20670
  `icon: unknown` and `labelVisibility: 'visible' | 'hidden'` fields, and every one whose composed
@@ -20313,12 +20880,89 @@ const filters: LyraFilterBarFilterDefinition[] = [
20313
20880
  The custom renderer returns a Lit `TemplateResult`; the filter bar places it in its
20314
20881
  `filter-control` part and re-renders it whenever the controlled value or validation state changes.
20315
20882
 
20883
+ ### Chip-only filters
20884
+
20885
+ Use `type: 'chip'` when the value is already owned by a widget elsewhere on the page — a calendar
20886
+ heatmap cell, a map selection, a chart brush — and the bar's job is only to *show* that the filter
20887
+ is applied and to let the user take it off. Unlike `type: 'custom'`, which still renders a control
20888
+ inside the toolbar, a chip-only filter renders **no control and no toolbar cell at all**: no `field`
20889
+ wrapper is emitted for it, so `lr-filter-bar::part(field)` and `::part(field-<filterId>)` never
20890
+ match one and a bar whose filters are *all* chip-only shows no empty column — its `controls` row
20891
+ still holds the reset button (the "clear all" action such a bar needs), the `end` slot, and the
20892
+ loading spinner, exactly like a bar with no filters at all.
20893
+
20894
+ Everything else is unchanged from any other filter type. The value lives in `value` under its own
20895
+ filter ID, rides every `lr-input`/`lr-reset` detail, counts toward `hasActiveFilters` (so it enables
20896
+ the reset button) and toward `invalidFilterIds` when the definition is `required`, renders a
20897
+ removable active-filter chip subject to `activeFiltersDisplay`, and is cleared both by removing that
20898
+ chip and by `reset()`. A `required` chip-only filter is honoured in **bookkeeping only**: it joins
20899
+ `invalidFilterIds`, fails `checkValidity()` and moves `lr-validity-change`, but renders no inline
20900
+ error, because the bar renders no element of its own for it — the owning widget is responsible for
20901
+ its own error affordance. The inherited `placeholder` is inert here for the same reason it is for
20902
+ `type: 'custom'`: there is no field to place it in.
20903
+
20904
+ A chip-only definition adds three optional fields of its own:
20905
+
20906
+ ```ts
20907
+ type: 'chip';
20908
+ formatValue?: (value: LyraFilterBarFieldValue, locale: string) => string;
20909
+ clearValue?: LyraFilterBarFieldValue;
20910
+ isEmpty?: (value: LyraFilterBarFieldValue) => boolean;
20911
+ ```
20912
+
20913
+ `formatValue` produces the chip's text, and its `locale` argument is the bar's `effectiveLocale` —
20914
+ the same locale every built-in type's own chip formatting and a custom adapter's `formatValue`
20915
+ already receive, and the reason a chip-only value (normally an already-formatted string such as a
20916
+ localized date) can be localized by the caller. That output is caller data, so — like a filter's
20917
+ own label — the bar never routes it through its own localization. Omitted, the fallback ladder is
20918
+ the one a custom adapter's omitted `formatValue` uses: a string array renders as a localized
20919
+ conjunction list, anything else renders verbatim through `String(value)`, and an unset value renders
20920
+ as the empty string. Verbatim is exact: a chip-only value is never run through the date branch that
20921
+ localizes a `'date'`/`'date-range'` chip, so an ISO day is not silently reformatted and a value
20922
+ containing a slash is not mangled.
20923
+
20924
+ `clearValue` is what a chip removal (and `clearFilter()`) writes, defaulting to the empty string —
20925
+ what every non-multi built-in type writes. Declare an empty array for an array-valued chip-only
20926
+ filter. `isEmpty` overrides the built-in emptiness rule (absent, `false`, the empty string and the
20927
+ empty array are empty; everything else is set). **A domain sentinel must pair the two**: declaring
20928
+ a sentinel clear value without a matching `isEmpty` leaves the bar reading the "cleared" value as
20929
+ still set, so it keeps rendering a chip for it — the identical pairing a custom adapter's own
20930
+ `clearValue`/`isEmpty` documents. With the pair declared, the sentinel is never stored in `value`
20931
+ (cleared keys are omitted) and an absent key reads back as the sentinel for the owning widget.
20932
+
20933
+ ```ts
20934
+ const filters: LyraFilterBarFilterDefinition[] = [
20935
+ { filterId: "query", label: "Query", type: "text" },
20936
+ {
20937
+ filterId: "day",
20938
+ label: "Day",
20939
+ type: "chip",
20940
+ formatValue: (value, locale) =>
20941
+ new Intl.DateTimeFormat(locale, {
20942
+ dateStyle: "medium",
20943
+ timeZone: "UTC",
20944
+ }).format(new Date(`${String(value)}T00:00:00Z`)),
20945
+ },
20946
+ ];
20947
+
20948
+ // The calendar heatmap beside the bar owns the value; the bar only shows and removes it.
20949
+ heatmap.addEventListener("app-select-day", (event) => {
20950
+ const { isoDate } = (event as CustomEvent<{ isoDate: string }>).detail;
20951
+ bar.value = { ...bar.value, day: isoDate };
20952
+ });
20953
+ bar.addEventListener("lr-input", (event) => {
20954
+ const { value } = (event as LyraFilterBarInputEvent).detail;
20955
+ heatmap.selectedDay = (value["day"] as string | undefined) ?? "";
20956
+ });
20957
+ ```
20958
+
20316
20959
  **TypeScript:** `LyraFilterBar<Defs extends readonly LyraFilterBarFilterDefinition[] =
20317
20960
  readonly LyraFilterBarFilterDefinition[]>` — `value` and the `lr-input`/`lr-reset` detail `value`
20318
20961
  narrow to a record keyed per `filterId`, whose value type follows that filter's own definition (a
20319
20962
  `'select'`, a non-`multiple` `'combobox'`, `'text'`, `'date'`, and `'date-range'` narrow to
20320
20963
  `string`; a `'checkbox-menu'` and a `multiple: true` `'combobox'` narrow to `readonly string[]`; a
20321
- `'custom'` filter keeps the full unconstrained field value). Declare the schema with `as const
20964
+ `'custom'` filter keeps the full unconstrained field value, and so does a `'chip'` filter, whose
20965
+ value is owned by a widget this component never renders). Declare the schema with `as const
20322
20966
  satisfies readonly LyraFilterBarFilterDefinition[]` and type the element as
20323
20967
  `LyraFilterBar<typeof FILTERS>` to pick it up. Types only; the runtime is unchanged, and an untyped
20324
20968
  `<lr-filter-bar>` keeps today's `LyraFilterBarValue` (`Readonly<Record<string, string | readonly
@@ -20507,6 +21151,12 @@ These named interfaces and helper signatures are available to typed integrations
20507
21151
  open: boolean;
20508
21152
  }`
20509
21153
 
21154
+ - **`components-layout-app-rail-item-app-rail-item-contracts`** — Supporting data types and helpers for this component family.
21155
+ Import: `@aceshooting/lyra-ui/components/layout/app-rail/app-rail-item.class.js`.
21156
+ `LyraAppRailItemToggleDetail {
21157
+ open: boolean;
21158
+ }`
21159
+
20510
21160
  - **`components-layout-command-palette-command-palette-contracts`** — Supporting data types and helpers for this component family.
20511
21161
  Import: `@aceshooting/lyra-ui/components/layout/command-palette/command-palette.class.js`.
20512
21162
  `LyraCommand {
@@ -20734,6 +21384,19 @@ These named interfaces and helper signatures are available to typed integrations
20734
21384
  readonly defaultValue?: string | readonly string[] | boolean;
20735
21385
  }`
20736
21386
  Import: `@aceshooting/lyra-ui/components/layout/filter-bar/filter-bar.class.js`.
21387
+ `LyraFilterBarChipDefinition extends LyraFilterBarDefinitionBase {
21388
+ readonly type: 'chip';
21389
+ readonly formatValue?: (value: LyraFilterBarFieldValue, locale: string) => string;
21390
+ readonly clearValue?: LyraFilterBarFieldValue;
21391
+ readonly isEmpty?: (value: LyraFilterBarFieldValue) => boolean;
21392
+ // Inherited from LyraFilterBarDefinitionBase.
21393
+ readonly filterId: string;
21394
+ readonly label: string;
21395
+ readonly placeholder?: string;
21396
+ readonly required?: boolean;
21397
+ readonly defaultValue?: string | readonly string[] | boolean;
21398
+ }`
21399
+ Import: `@aceshooting/lyra-ui/components/layout/filter-bar/filter-bar.class.js`.
20737
21400
  `LyraFilterBarCustomControlAdapter {
20738
21401
  readonly valueFromEvent: (event: Event) => LyraFilterBarFieldValue;
20739
21402
  readonly clearValue: LyraFilterBarFieldValue;
@@ -20819,6 +21482,7 @@ These named interfaces and helper signatures are available to typed integrations
20819
21482
  readonly label: string;
20820
21483
  readonly icon?: unknown;
20821
21484
  readonly searchText?: string;
21485
+ readonly disabled?: boolean;
20822
21486
  }`
20823
21487
  Import: `@aceshooting/lyra-ui/components/layout/filter-bar/filter-bar.class.js`.
20824
21488
  `LyraFilterBarResetDetail {
@@ -21618,6 +22282,16 @@ controls, rendered before the built-in close button), `footer` — all inherited
21618
22282
  Names grouped together are aliases on the same functional node; `close-button__control` is the
21619
22283
  composed `<lr-icon-button>`'s own native `<button>`, inherited from `lr-dialog` as of 16.0.0.
21620
22284
 
22285
+ **Migrating a pre-16.0.0 `::part()` rule.** This component's icon-only action is a composed
22286
+ `<lr-icon-button>`, so the part naming that action now names the composed child's HOST, which
22287
+ paints nothing. A `border`, `background` or `border-radius` set on it is silently dead — only
22288
+ `color` still appears to work, because it inherits, which makes such a rule look half-alive rather
22289
+ than broken. Set `--lr-icon-button-background`/`-color`/`-border`/`-radius` (and their
22290
+ `-hover`/`-active` variants) on this element or an ancestor instead: the composed control reads
22291
+ those public tokens ahead of any default this component supplies. For SIZE use
22292
+ `--lr-theme-icon-button-size`, not `--lr-icon-button-size` — every `LyraElement` re-declares the
22293
+ latter on its own `:host`, so it never reaches a composed child (see `llms/tokens.md`).
22294
+
21621
22295
  **Themeable custom properties:** mapped `--size` controls the active axis. For start/end drawers,
21622
22296
  the inherited `--width` and `--lr-dialog-width` remain compatibility fallbacks when neither
21623
22297
  `--size` nor `--lr-drawer-width` is set, and `--lr-dialog-max-width` remains an effective cap,
@@ -21842,6 +22516,27 @@ focusable control _inside_ the body, and the body itself is used only when there
21842
22516
  focus. So a dialog full of form controls behaves exactly as before, and a dialog full of text is now
21843
22517
  scrollable with the arrow keys, Page Up/Down and Home/End once Tab reaches it.
21844
22518
 
22519
+ **Migrating a pre-16.0.0 `::part()` rule.** This component's icon-only action is a composed
22520
+ `<lr-icon-button>`, so the part naming that action now names the composed child's HOST, which
22521
+ paints nothing. A `border`, `background` or `border-radius` set on it is silently dead — only
22522
+ `color` still appears to work, because it inherits, which makes such a rule look half-alive rather
22523
+ than broken. Set `--lr-icon-button-background`/`-color`/`-border`/`-radius` (and their
22524
+ `-hover`/`-active` variants) on this element or an ancestor instead: the composed control reads
22525
+ those public tokens ahead of any default this component supplies. For SIZE use
22526
+ `--lr-theme-icon-button-size`, not `--lr-icon-button-size` — every `LyraElement` re-declares the
22527
+ latter on its own `:host`, so it never reaches a composed child (see `llms/tokens.md`).
22528
+
22529
+ **Border reaches the composed close control the same way background/color/radius do.** This
22530
+ component paints no resting border of its own, so it relays no `--_lr-icon-button-border-default`
22531
+ into the close control's private fallback tier — but that absence is not a gap. The public
22532
+ `--lr-icon-button-border` (and its `-hover`/`-active` variants) is the FIRST arm of the token
22533
+ chain, resolved by ordinary custom-property inheritance regardless of whether this component
22534
+ relays a default for that same property, so setting it on this element or an ancestor reaches the
22535
+ close control exactly as the background/color/radius tokens do. A component with no resting
22536
+ border simply has no default to relay, which is different from border theming being broken. Size
22537
+ remains the one exception that does not cross this way: use `--lr-theme-icon-button-size`, never
22538
+ `--lr-icon-button-size`, as noted above.
22539
+
21845
22540
  **Themeable custom properties:** mapped aliases are `--backdrop-filter`, `--width`, `--spacing`,
21846
22541
  `--header-spacing`, `--body-spacing`, `--footer-spacing`, `--show-duration`, and
21847
22542
  `--hide-duration`. The individual region properties override `--spacing`; mapped properties in
@@ -23613,6 +24308,16 @@ The surface chrome lives on the custom-element host, not inside `base`. Ordinary
23613
24308
  work directly and take normal author precedence. `inline` removes the host's border, background,
23614
24309
  and padding.
23615
24310
 
24311
+ **Migrating a pre-16.0.0 `::part()` rule.** This component's icon-only action is a composed
24312
+ `<lr-icon-button>`, so the part naming that action now names the composed child's HOST, which
24313
+ paints nothing. A `border`, `background` or `border-radius` set on it is silently dead — only
24314
+ `color` still appears to work, because it inherits, which makes such a rule look half-alive rather
24315
+ than broken. Set `--lr-icon-button-background`/`-color`/`-border`/`-radius` (and their
24316
+ `-hover`/`-active` variants) on this element or an ancestor instead: the composed control reads
24317
+ those public tokens ahead of any default this component supplies. For SIZE use
24318
+ `--lr-theme-icon-button-size`, not `--lr-icon-button-size` — every `LyraElement` re-declares the
24319
+ latter on its own `:host`, so it never reaches a composed child (see `llms/tokens.md`).
24320
+
23616
24321
  **Themeable custom properties:** `--lr-callout-background`, `--lr-callout-color`, and
23617
24322
  `--lr-callout-border` read the inherited generic semantic quiet/loud slots, with brand quiet/loud
23618
24323
  as their standalone fallback. An explicit `variant` maps all generic slots locally; leaving it
@@ -24055,6 +24760,12 @@ The menu popup is a floating surface and paints from the **shared overlay-surfac
24055
24760
  declared on `:host`, so one declaration on `:root` — or on any ancestor, to scope it — retints this
24056
24761
  surface together with every other floating surface in the library. `--lr-overlay-radius` (default `var(--lr-radius)`) is the matching corner radius.
24057
24762
 
24763
+ `--lr-positioning-strategy` (16.0.0) — the format menu reads this same cascading `absolute`/`fixed`
24764
+ override documented on `<lr-popover>` when it is (re)positioned, falling back to its own `fixed`
24765
+ default when nothing is set. There is no per-instance `positioning-strategy` property on
24766
+ `<lr-export-button>`; set the custom property on `:root`, a theme, or one clipping ancestor to
24767
+ change every unset export button beneath it.
24768
+
24058
24769
  **Optional peer deps:** none.
24059
24770
 
24060
24771
  ```html
@@ -24832,6 +25543,11 @@ preserving null readback; an explicitly empty query remains empty.
24832
25543
  pre-`query`-filtering. Assignment takes a shallow frozen snapshot. Runtime rows without a string
24833
25544
  `label` remain in that diagnostic snapshot but are omitted from filtering/rendering before the
24834
25545
  built-in or custom predicate runs, so one malformed provider row cannot take down valid siblings.
25546
+ An entry's `disabled` marks that row non-actionable: `aria-disabled="true"` replaces its
25547
+ selected/active affordances, activating it (click, or Enter/Tab while highlighted) commits
25548
+ nothing and emits no `lr-mention-select`, and ArrowDown/ArrowUp highlighting -- including the
25549
+ default pre-highlighted first row -- steps past it instead of landing on it. Omitted or `false`
25550
+ renders the row exactly as before this field existed.
24835
25551
  - `query: string = ''` — the text typed since the trigger character; drives the built-in filtering
24836
25552
  (see `filter`).
24837
25553
  - `open: boolean = false` (reflected)
@@ -24876,7 +25592,7 @@ preserving null readback; an explicitly empty query remains empty.
24876
25592
  disconnect/adoption, a newer transfer, or failed ownership resolves `false` without moving focus.
24877
25593
 
24878
25594
  **Exported types:** `LyraMentionItem { suggestionId: string; label: string; description?: string;
24879
- icon?: string }`; `LyraMentionFilter = (item: LyraMentionItem, query: string) => boolean`;
25595
+ icon?: string; disabled?: boolean }`; `LyraMentionFilter = (item: LyraMentionItem, query: string) => boolean`;
24880
25596
  `LyraMentionFocusOptions { ownsFocus?: () => boolean }`;
24881
25597
  `LyraMentionSelectDetail { suggestionId: string; index: number; label: string }`.
24882
25598
 
@@ -24897,7 +25613,9 @@ renders `open="false"` on first paint)
24897
25613
  `var(--lr-color-brand-quiet)`) — background of the hovered or `[data-active]`
24898
25614
  (keyboard-highlighted) suggestion row. Component-scoped indirection over the shared
24899
25615
  `--lr-color-brand-quiet` token, so a consumer can retheme just this highlighted/active row without
24900
- repainting every other component that reuses the same shared token. Plus shared tokens —
25616
+ repainting every other component that reuses the same shared token.
25617
+ `--lr-mention-popover-option-disabled-opacity` (default `0.5`) — opacity of a row whose `items`
25618
+ entry sets `disabled`. Plus shared tokens —
24901
25619
  `--lr-space-xs`/`-s`/`-m` (popup padding,
24902
25620
  row padding/gap), `--lr-radius`
24903
25621
  (row corners — the popup's own corner is the overlay family's, below),
@@ -24914,6 +25632,12 @@ The popup is a floating surface and paints from the **shared overlay-surface fam
24914
25632
  declared on `:host`, so one declaration on `:root` — or on any ancestor, to scope it — retints this
24915
25633
  surface together with every other floating surface in the library. `--lr-overlay-radius` (default `var(--lr-radius)`) is the matching corner radius.
24916
25634
 
25635
+ `--lr-positioning-strategy` (16.0.0) — the popup reads this same cascading `absolute`/`fixed`
25636
+ override documented on `<lr-popover>` when it is (re)positioned, falling back to its own `fixed`
25637
+ default when nothing is set. There is no per-instance `positioning-strategy` property on
25638
+ `<lr-mention-popover>`; set the custom property on `:root`, a theme, or one clipping ancestor to
25639
+ change every unset mention popover beneath it.
25640
+
24917
25641
  **Optional peer deps:** none.
24918
25642
 
24919
25643
  ```html
@@ -25894,6 +26618,12 @@ shared ceiling that keeps any floating surface inside a narrow viewport. `lr-tou
25894
26618
  retuning `--lr-theme-popover-viewport-clamp` once at `:root` narrows or widens all three together
25895
26619
  rather than per component.
25896
26620
 
26621
+ `--lr-positioning-strategy` (16.0.0) — the step popover reads this same cascading `absolute`/`fixed`
26622
+ override documented on `<lr-popover>` when a step is (re)positioned, falling back to its own `fixed`
26623
+ default when nothing is set. There is no per-instance `positioning-strategy` property on `<lr-tour>`;
26624
+ set the custom property on `:root`, a theme, or one clipping ancestor to change every unset tour
26625
+ beneath it.
26626
+
25897
26627
  **Known gotchas:**
25898
26628
 
25899
26629
  - By default the spotlighted target is **non-interactive**: it stays visible and announceable (not
@@ -26007,6 +26737,7 @@ These named interfaces and helper signatures are available to typed integrations
26007
26737
  readonly label: string;
26008
26738
  readonly description?: string;
26009
26739
  readonly icon?: string;
26740
+ readonly disabled?: boolean;
26010
26741
  }`
26011
26742
  Import: `@aceshooting/lyra-ui/components/utility/mention-popover/mention-popover.class.js`.
26012
26743
  `LyraMentionSelectDetail {
@@ -26475,10 +27206,18 @@ LyraMapLegendPattern }`, where `LyraMapLegendPattern` is `'solid' | 'diagonal' |
26475
27206
  'crosshatch'`. Pattern is required so color is never the sole category cue. At most 100 valid
26476
27207
  rows, 256 characters per label, and 8,192 aggregate label characters are retained; colors are
26477
27208
  bounded before validation. The overlay scrolls within the map allocation.
27209
+ A row may also carry its own `value`: the category key that row stands for — the same string a
27210
+ `point.colors`/`point.icons` entry matches against `point.field`/`point.iconField`. It is
27211
+ trimmed, bounded to 256 characters (sliced, never ellipsized, so a bounded key still matches) and
27212
+ **retained** in the canonical readback; a non-string, empty or whitespace-only key leaves no
27213
+ `value` property on the frozen row at all. It is what makes a row operable under
27214
+ `legendInteractive`, and it does not count toward the label budget.
26478
27215
  A row may also carry `icon`, deliberately the same record a `point.icons` entry uses — hand the
26479
27216
  legend the very icon object its point layer renders and the key shows the symbol drawn on the
26480
- map instead of describing it in colour alone. The point layer's category key (`value`) is
26481
- accepted so a pass-through needs no reshaping, and is left out of the canonical readback;
27217
+ map instead of describing it in colour alone. The **icon record's own** `value` is a separate
27218
+ field from the row-level one above: it is accepted so a pass-through needs no reshaping, and it
27219
+ is still left out of the canonical readback. A row's key is never derived from its glyph's, which
27220
+ would silently make a row interactive that the author never marked;
26482
27221
  `path`, `viewBox`, `mode`, `strokeWidth`, `lineCap` and `lineJoin` keep their point-icon meaning
26483
27222
  and their point-icon defaults, and the same validation applies (path data only, at most 8,192
26484
27223
  characters, positive `viewBox` dimensions). An unusable record is dropped and that row keeps
@@ -26487,6 +27226,22 @@ LyraMapLegendPattern }`, where `LyraMapLegendPattern` is `'solid' | 'diagonal' |
26487
27226
  omittedCount, truncatedLabelCount, truncated }` result for the latest assignment. A truncated
26488
27227
  projection renders a localized visible `1–N of M items` summary rather than silently claiming
26489
27228
  the bounded rows are complete.
27229
+ - `legendInteractive: boolean = false` (attribute `legend-interactive`, reflected) — opt-in: turns
27230
+ every legend row that carries a `value` into a keyboard-operable visibility toggle, and leaves a
27231
+ row without one inert. Unset, the legend renders exactly the read-only key it rendered before —
27232
+ no `button`, no extra attribute, and no extra MapLibre paint key. Each toggle is an independently
27233
+ tabbable native `button`, so Enter and Space are the platform's own activation and there is no
27234
+ roving tabindex: a 100-row interactive legend contributes 100 tab stops, exactly as a 100-series
27235
+ `lr-chart` legend does. Each interactive row also grows to the shared `--lr-icon-button-size`
27236
+ hit-area floor (WCAG 2.5.8), which the panel's existing `max-block-size` and scrolling contain.
27237
+ - `hiddenCategories: readonly string[] = []` (attribute: false) — the complete controlled set of
27238
+ muted category keys, mirroring `lr-chart`'s `hiddenDatasets`. Clone-owned and frozen; non-string,
27239
+ empty, whitespace-only and duplicate entries are dropped (first occurrence wins, matching
27240
+ `point.colors`), and at most 100 keys are retained. Honoured on the **first** render and the
27241
+ first MapLibre paint, not only after a user toggle, so a host can restore a saved selection. An
27242
+ empty array deliberately means every category is visible. It is controlled public state and
27243
+ survives a disconnect/reconnect. A hidden key that matches no category is harmless. Assigning it
27244
+ programmatically emits nothing.
26490
27245
  - `choropleth?: LyraMapChoroplethLayer` (attribute: false) — `LyraMapChoroplethLayer { sourceId:
26491
27246
  string; geojson: GeoJSON.FeatureCollection; field: string; stops: [number, string][]; interpolation?:
26492
27247
  'linear' | 'logarithmic' | 'step'; stepBaseColor?: string }` (interpolated
@@ -26767,7 +27522,8 @@ payload beside the map.
26767
27522
  `LyraMapHeatmapZoomValue`, `LyraMapLineOptions`, `LyraMapPointOptions`, `LyraMapPointRadiusOptions`,
26768
27523
  `LyraMapPointRadiusInterpolation`, `LyraMapPointIcon`, `LyraMapPointIconMode`,
26769
27524
  `LyraMapPointIconLineCap`, `LyraMapPointIconLineJoin`, `LyraMapMarker`, `LyraMapMarkerActivationDetail`,
26770
- `LyraMapMarkerActivationSource`, `LyraMapStyleSpecification`, and `LyraMapInstance`.
27525
+ `LyraMapMarkerActivationSource`, `LyraMapLegendToggleDetail`, `LyraMapStyleSpecification`, and
27526
+ `LyraMapInstance`.
26771
27527
  The former `LegendEntry`, `ChoroplethLayer`, `GeoJsonDataLayer`, and `MapMarker` names are removed
26772
27528
  in v9 rather than retained as aliases.
26773
27529
 
@@ -26783,7 +27539,17 @@ shared `maplibre-gl` import without constructing a map or allocating a WebGL con
26783
27539
  an element.
26784
27540
 
26785
27541
  **Events:** `lr-map-load` (fired once, after the underlying map's own `'load'`),
26786
- `lr-map-marker-activate` (non-cancelable; frozen `LyraMapMarkerActivationDetail { id, lngLat,
27542
+ `lr-map-legend-toggle` (**cancelable**; frozen `LyraMapLegendToggleDetail { value, visible,
27543
+ hiddenCategories }` — the activated row's category key, its proposed visibility, and the complete
27544
+ proposed hidden set in the order it would be committed, with the array detached and frozen so a
27545
+ listener cannot mutate the component's state through it). `preventDefault()` is a genuine veto, not
27546
+ a notification: `hiddenCategories` is not written, the row's `aria-pressed` does not change, the
27547
+ MapLibre paint is untouched, and nothing is announced — which is exactly what a host needs to own
27548
+ the set and assign its own value instead. There is deliberately no second, confirmation event: the
27549
+ committed state is `hiddenCategories`, which the host already observes, so a paired before/after
27550
+ vocabulary would be permanent public surface nobody asked for. The event is a DOM-interaction
27551
+ proposal only, so a programmatic `hiddenCategories` assignment reconciles without emitting it.
27552
+ Also `lr-map-marker-activate` (non-cancelable; frozen `LyraMapMarkerActivationDetail { id, lngLat,
26787
27553
  marker, source }`; `id` is the trimmed explicit identity or `undefined`, `marker` is the accepted
26788
27554
  declarative snapshot, and `source` is `'pointer' | 'keyboard'`), and `lr-map-click`
26789
27555
  (frozen `detail: { readonly lngLat: readonly [lng, lat], readonly feature?, readonly origin?,
@@ -26807,7 +27573,8 @@ markup as described above.
26807
27573
  stays positioned with the map instead of floating beside it. Supplying it opens the panel even
26808
27574
  when `legend` and `legendGradient` are both empty.
26809
27575
 
26810
- **CSS parts:** `base`, `container`, `legend`, `legend-swatch`, `legend-gradient`, `legend-lo`,
27576
+ **CSS parts:** `base`, `container`, `legend`, `legend-swatch`, `legend-toggle`,
27577
+ `legend-toggle-hidden`, `legend-gradient`, `legend-lo`,
26811
27578
  `legend-hi`, `legend-limit`, `marker`, `popup`,
26812
27579
  `popup-content`, `popup-close-button`, `attribution`, `attribution-toggle`, `navigation`,
26813
27580
  `zoom-in`, `zoom-out`, `compass`, `scale`, `error`.
@@ -26832,6 +27599,18 @@ visible label carries its meaning. That association is advertised only while it
26832
27599
  the optional `maplibre-gl` peer is still loading, and after any failure, there is no map
26833
27600
  container in the tree, so `legend` withholds `aria-controls` rather than leaving a dangling
26834
27601
  idref.
27602
+ Under `legendInteractive`, a row carrying a `value` wraps its swatch and label in a
27603
+ `legend-toggle` `button` with `aria-pressed` rendered as the literal `"true"`/`"false"` — never
27604
+ omitted, because a missing attribute reports "not a toggle button" rather than "unpressed". The
27605
+ button's accessible name is its own visible label (caller-supplied data, so deliberately not
27606
+ localized), scoped by the legend's own localized group name; no `aria-label` restates the state,
27607
+ which would make assistive tech announce it twice. A hidden row's button additionally carries the
27608
+ `legend-toggle-hidden` token — state lives in the part name, so `::part(legend-toggle-hidden)` is a
27609
+ reachable hook — and dims only its `aria-hidden` swatch while re-colouring the label through the
27610
+ quiet text token, so the label keeps AA contrast rather than fading with the whole button. In
27611
+ forced colors the hidden row falls back to `line-through`, which survives a system-color collapse.
27612
+ Each activation is announced through the shared light-DOM polite live region, using the same
27613
+ `legendTypeShown`/`legendTypeHidden` strings `lr-graph-legend` announces with.
26835
27614
  `legend-limit` is the localized bounded-projection summary. The five peer-chrome parts project
26836
27615
  stable Lyra names onto MapLibre-generated DOM without erasing peer-supplied part tokens;
26837
27616
  `marker` retains a 24px minimum target in both axes even when a peer/custom marker has no intrinsic
@@ -26863,6 +27642,15 @@ integration; there is no declarative controls property.
26863
27642
  - `--lr-map-choropleth-fill-opacity` (default `0.75`) — fill opacity for the declarative
26864
27643
  `choropleth` layer and polygon fills in every `dataLayers` entry. It intentionally inherits from
26865
27644
  an ancestor, so one scoped declaration rethemes every nested map without setting each host.
27645
+ - `--lr-map-hidden-category-opacity` (default `0.15`) — opacity a category listed in
27646
+ `hiddenCategories` is muted to in the rendered MapLibre paint: `circle-opacity` and
27647
+ `circle-stroke-opacity` on the points layer and `icon-opacity` on the point-icon symbol layer.
27648
+ Like the fill-opacity token it is read from the resolved cascade on every paint, because MapLibre
27649
+ draws to a WebGL canvas the CSS cascade never reaches, and it inherits from an ancestor. A
27650
+ `kind: 'heatmap'` entry is out of scope: a density surface has no per-category field to mute.
27651
+ - `--lr-map-legend-hidden-swatch-opacity` (default `0.5`) — opacity of a hidden interactive legend
27652
+ row's decorative swatch. Only the `aria-hidden` swatch dims; the label re-colours through
27653
+ `--lr-color-text-quiet` instead, so it never drops below 4.5:1.
26866
27654
  - `--lr-map-popup-close-button-hover-bg` (default `var(--lr-color-brand-quiet)`) and
26867
27655
  `--lr-map-popup-close-button-hover-color` (default `var(--lr-color-brand)`) — hover background
26868
27656
  and foreground of `popup-close-button`.
@@ -27002,16 +27790,25 @@ reimplemented.
27002
27790
  - `maxFileSize: number = 0` (attribute `max-file-size` — bytes; `0` disables the check), `maxFiles:
27003
27791
  number = 0` (attribute `max-files`), `maxTotalSize: number = 0` (attribute `max-total-size`) —
27004
27792
  identical contract and invalid-override fail-safe fallback to `lr-file-input`'s own three limits.
27005
- Since this component retains nothing between drops, `maxFiles`/`maxTotalSize` always cover only
27006
- the current drop (there is no persisted count/total to add).
27793
+ Since this component retains nothing of its own between drops, `maxFiles`/`maxTotalSize` would
27794
+ otherwise always cover only the current drop `heldFileCount`/`heldTotalSize` below are what let
27795
+ a cumulative cap span separate drops.
27796
+ - `heldFileCount: number = 0` (attribute `held-file-count`) and `heldTotalSize: number = 0`
27797
+ (attribute `held-total-size`) — externally held baseline added to the running count/byte-total
27798
+ `maxFiles`/`maxTotalSize` evaluate against, identical contract to `lr-file-input`'s own
27799
+ `heldFileCount`/`heldTotalSize`: `0` (the default) means "nothing held" and reproduces prior
27800
+ behavior exactly, and a negative, `NaN`, or `Infinity` override is normalized to `0` via
27801
+ `finiteCount` rather than corrupting every later comparison.
27007
27802
  - `readonly dragging: boolean` — `true` during an active drag session
27008
27803
 
27009
27804
  **Events:** `lr-files` (`detail: LyraDropZoneFilesDetail`, with fresh frozen readonly `files` and
27010
- `rejected` arrays and frozen rejected-file records, fired on drop; immutable `File` objects retain
27011
- identity) typed as `LyraDropZoneFilesEvent`, so `event.target`/`event.currentTarget` are
27012
- `LyraDropZone` without a cast. `LyraDropZoneRejectedFile = { readonly file: File; readonly reason:
27013
- 'type' | 'count' | 'size' | 'directory' | 'read' | 'limit' | 'maxFiles' | 'maxTotalSize' }`, the
27014
- same reason vocabulary as `lr-file-input`'s `LyraFileInputRejectedFile`.
27805
+ `rejected` arrays and frozen rejected-file records, plus `remainingFiles`/`remainingTotalSize`
27806
+ reporting the allowance still left under `maxFiles`/`maxTotalSize` after this drop (`null` while
27807
+ that limit is unset), fired on drop; immutable `File` objects retain identity) typed as
27808
+ `LyraDropZoneFilesEvent`, so `event.target`/`event.currentTarget` are `LyraDropZone` without a
27809
+ cast. `LyraDropZoneRejectedFile = { readonly file: File; readonly reason: 'type' | 'count' | 'size'
27810
+ | 'directory' | 'read' | 'limit' | 'maxFiles' | 'maxTotalSize' }`, the same reason vocabulary as
27811
+ `lr-file-input`'s `LyraFileInputRejectedFile`.
27015
27812
 
27016
27813
  **Slots:** the default slot is the wrapped region, rendered as ordinary light DOM; `overlay`
27017
27814
  overrides the localized accept/reject overlay text.
@@ -27112,14 +27909,23 @@ enabled buttons retain pointer feedback.
27112
27909
  update them by assigning a new collection.
27113
27910
  - `maxFileSize: number = 0` (attribute `max-file-size` — bytes; `0` disables the check)
27114
27911
  - `maxFiles: number = 0` (attribute `max-files`) — largest total file count accepted, counting
27115
- retained files plus the current batch; `0` disables the check. An excess file in the batch is
27116
- rejected with reason `'maxFiles'`, in the same `[part="rejection"]` shape as `maxFileSize`. While
27117
- `nonRetaining` is set, the count covers only the current batch (there is no externally-held count
27118
- to add).
27912
+ retained files (unless `nonRetaining`) plus `heldFileCount` plus the current batch; `0` disables
27913
+ the check. An excess file in the batch is rejected with reason `'maxFiles'`, in the same
27914
+ `[part="rejection"]` shape as `maxFileSize`.
27119
27915
  - `maxTotalSize: number = 0` (attribute `max-total-size`) — largest combined byte size accepted,
27120
- summing retained files plus the current batch; `0` disables the check. Same rejection-UI shape and
27121
- fail-safe invalid-override behavior as `maxFileSize` (see gotchas). Same `nonRetaining` batch-only
27122
- scoping as `maxFiles`.
27916
+ summing retained files (unless `nonRetaining`) plus `heldTotalSize` plus the current batch; `0`
27917
+ disables the check. Same rejection-UI shape and fail-safe invalid-override behavior as
27918
+ `maxFileSize` (see gotchas).
27919
+ - `heldFileCount: number = 0` (attribute `held-file-count`) — externally held file count added to
27920
+ the running count `maxFiles` evaluates against, in both retaining and `nonRetaining` modes — the
27921
+ numeric counterpart of `valuePresent`, for a cumulative cap (e.g. a server-backed upload limit)
27922
+ that spans separate picker sessions rather than resetting to what this control alone can see. `0`
27923
+ (the default) means "nothing held" and reproduces prior behavior exactly. A negative, `NaN`, or
27924
+ `Infinity` override is normalized to `0` via `finiteCount` rather than corrupting every later
27925
+ comparison or permanently blocking every future file.
27926
+ - `heldTotalSize: number = 0` (attribute `held-total-size`) — externally held byte total added to
27927
+ the running size `maxTotalSize` evaluates against, in both retaining and `nonRetaining` modes.
27928
+ Same contract, default, and invalid-input normalization as `heldFileCount`.
27123
27929
  - `nonRetaining: boolean = false` (reflected, attribute `non-retaining`) — opt-in mode where an
27124
27930
  accepted selection still fires `lr-files`/`input`/`change` but is never written to `files` or
27125
27931
  rendered as a built-in `[part="file"]` row. For a host that persists files elsewhere and renders
@@ -27198,9 +28004,11 @@ enabled buttons retain pointer feedback.
27198
28004
  host `change`; programmatic `files` writes are silent (both still fire while `nonRetaining` is set,
27199
28005
  even though `files` itself is not written in that mode). `lr-files` (`detail:
27200
28006
  LyraFileInputFilesDetail`, with fresh frozen readonly `files` and `rejected` arrays and frozen
27201
- rejected-file records, fired on both drop and manual file-picker selection; immutable `File` objects
27202
- retain identity) typed as `LyraFileInputFilesEvent`, so `event.target`/`event.currentTarget` are
27203
- `LyraFileInput` without a cast. `LyraFileInputRejectedFile = { readonly file: File; readonly reason:
28007
+ rejected-file records, plus `remainingFiles`/`remainingTotalSize` reporting the allowance still left
28008
+ under `maxFiles`/`maxTotalSize` after this batch (`null` while that limit is unset), fired on both
28009
+ drop and manual file-picker selection; immutable `File` objects retain identity) typed as
28010
+ `LyraFileInputFilesEvent`, so `event.target`/`event.currentTarget` are `LyraFileInput` without a
28011
+ cast. `LyraFileInputRejectedFile = { readonly file: File; readonly reason:
27204
28012
  'type' | 'count' | 'size' | 'directory' | 'read' | 'limit' | 'maxFiles' | 'maxTotalSize' }`: `'type'`
27205
28013
  from `accept`/`allowedMimeTypes`/`forbiddenMimeTypes`, `'count'` when a single-file input
27206
28014
  (`multiple` unset) receives more than one file (in which case _all_ files are rejected, none
@@ -28074,6 +28882,27 @@ painted surface sits one boundary deeper),
28074
28882
  hidden via CSS by default, exposed as a part only so a consumer can override that with
28075
28883
  `::part(hidden-input)` in the unlikely case their integration needs to).
28076
28884
 
28885
+ **Migrating a pre-16.0.0 `::part()` rule.** This component's icon-only action is a composed
28886
+ `<lr-icon-button>`, so the part naming that action now names the composed child's HOST, which
28887
+ paints nothing. A `border`, `background` or `border-radius` set on it is silently dead — only
28888
+ `color` still appears to work, because it inherits, which makes such a rule look half-alive rather
28889
+ than broken. Set `--lr-icon-button-background`/`-color`/`-border`/`-radius` (and their
28890
+ `-hover`/`-active` variants) on this element or an ancestor instead: the composed control reads
28891
+ those public tokens ahead of any default this component supplies. For SIZE use
28892
+ `--lr-theme-icon-button-size`, not `--lr-icon-button-size` — every `LyraElement` re-declares the
28893
+ latter on its own `:host`, so it never reaches a composed child (see `llms/tokens.md`).
28894
+
28895
+ **An ancestor's public border wins over this component's own relayed default, not just its
28896
+ absence.** Unlike most composing components, the `outlined`/`filled-outlined` appearances here DO
28897
+ relay a non-zero `--_lr-icon-button-border-default` (a themed edge) into the trigger's private
28898
+ fallback tier. That does not change the resolution order: `--lr-icon-button-border` (and its
28899
+ `-hover`/`-active` variants) is still the FIRST arm of the token chain, read by the composed
28900
+ trigger ahead of whatever this component relays, so setting it on this element or an ancestor
28901
+ overrides the outlined edge exactly as it overrides the `plain`/`filled`/`accent` appearances'
28902
+ zero default. The private relay is only this component's own default opinion, never a gate the
28903
+ public token must pass through. Size remains the one exception that does not cross this way: use
28904
+ `--lr-theme-icon-button-size`, never `--lr-icon-button-size`, as noted above.
28905
+
28077
28906
  **Themeable custom properties:** shared tokens only — `--lr-space-xs`, `--lr-color-text`/
28078
28907
  `-text-quiet`, `--lr-icon-button-size`, `--lr-focus-ring-*`, `--lr-opacity-disabled`,
28079
28908
  `--lr-radius`, `--lr-transition-fast`.
@@ -29205,6 +30034,8 @@ These named interfaces and helper signatures are available to typed integrations
29205
30034
  `LyraDropZoneFilesDetail {
29206
30035
  readonly files: readonly File[];
29207
30036
  readonly rejected: readonly LyraDropZoneRejectedFile[];
30037
+ readonly remainingFiles: number | null;
30038
+ readonly remainingTotalSize: number | null;
29208
30039
  }`
29209
30040
  Import: `@aceshooting/lyra-ui/components/media/drop-zone/drop-zone.class.js`.
29210
30041
  `LyraDropZoneFilesEvent extends CustomEvent<LyraDropZoneFilesDetail> {
@@ -29253,6 +30084,8 @@ These named interfaces and helper signatures are available to typed integrations
29253
30084
  `LyraFileInputFilesDetail {
29254
30085
  readonly files: readonly File[];
29255
30086
  readonly rejected: readonly LyraFileInputRejectedFile[];
30087
+ readonly remainingFiles: number | null;
30088
+ readonly remainingTotalSize: number | null;
29256
30089
  }`
29257
30090
  Import: `@aceshooting/lyra-ui/components/media/file-input/file-input.class.js`.
29258
30091
  `LyraFileInputObjectValidator {
@@ -29544,6 +30377,12 @@ These named interfaces and helper signatures are available to typed integrations
29544
30377
  readonly source: LyraMapMarkerActivationSource;
29545
30378
  }`
29546
30379
  Import: `@aceshooting/lyra-ui/components/media/map/map.class.js`.
30380
+ `LyraMapLegendToggleDetail {
30381
+ readonly value: string;
30382
+ readonly visible: boolean;
30383
+ readonly hiddenCategories: readonly string[];
30384
+ }`
30385
+ Import: `@aceshooting/lyra-ui/components/media/map/map.class.js`.
29547
30386
  `LyraMapStyleSpecification {
29548
30387
  readonly version: 8;
29549
30388
  readonly sources: Readonly<Record<string, unknown>>;
@@ -29725,8 +30564,14 @@ structured points retain their y-value formatting.
29725
30564
  - `labels: readonly string[] = []` (attribute: false)
29726
30565
  - `datasets: readonly LyraChartSeries[] = []` (attribute: false) — `LyraChartSeries { readonly
29727
30566
  label: string; readonly data?: readonly (number|null)[]; readonly points?: readonly
29728
- LyraChartPoint[]; readonly color?: string|readonly string[]; ... }`. The deprecated `Series` and
29729
- `ChartPoint` names were removed in 9.0.0 — import `LyraChartSeries`/`LyraChartPoint` instead.
30567
+ LyraChartPoint[]; readonly color?: string|readonly string[]; readonly stack?: string; ... }`. The
30568
+ deprecated `Series` and `ChartPoint` names were removed in 9.0.0 — import
30569
+ `LyraChartSeries`/`LyraChartPoint` instead.
30570
+ - `stack` is a Chart.js dataset `stack` group id: series sharing one `stack` value on the same
30571
+ (stacked) axis accumulate into one stack; a different id starts an independent stack Chart.js
30572
+ draws side by side with the first on that axis. Omitted series share one implicit group, so
30573
+ every chart written before `stack` existed sums exactly as it always did. Only meaningful on an
30574
+ axis that is actually stacked — see `stacked`/`stackedAxes` below.
29730
30575
  `LyraChartPoint { readonly x: number; readonly
29731
30576
  y: number; readonly r?: number; readonly label?: string }`: `r` is the bubble
29732
30577
  radius, and the optional per-point `label` is retained by events, CSV export, keyboard
@@ -29821,6 +30666,16 @@ structured points retain their y-value formatting.
29821
30666
  and the stack's own `axis`, but no `datasetIndex` and no `seriesLabel` — naming the topmost
29822
30667
  series would make a unit-switching formatter render that one series' unit for a cross-series
29823
30668
  number. `lr-lite-chart`'s total cells drop the same two fields.
30669
+ - `tooltipTitleFormatter?: LyraChartTooltipGroupFormatter` (attribute: false) — tooltip title
30670
+ formatter (e.g. a scatter point's own name). `LyraChartTooltipGroupFormatter = (items:
30671
+ readonly LyraChartFormatterContext[]) => string`: unlike `formatter`, which runs once per item,
30672
+ this runs once per tooltip render and receives every hovered item's context at once (one entry
30673
+ per dataset the tooltip covers, each in the same shape `formatter`'s `'tooltip'` surface already
30674
+ produces). Unset (the default) leaves Chart.js's own default title — the shared category label.
30675
+ - `tooltipFooterFormatter?: LyraChartTooltipGroupFormatter` (attribute: false) — tooltip footer
30676
+ formatter (e.g. a category's stack total under the items), in the same shape and calling
30677
+ convention as `tooltipTitleFormatter`. Unset (the default) leaves Chart.js's own default: no
30678
+ footer.
29824
30679
  - `area: boolean = false` — chart-wide default for whether line-type series fill the region under
29825
30680
  their line; a series's own `fill` overrides it, rendered with a translucent version of its color
29826
30681
  - `zoom: boolean = false` — wheel/drag/pinch zoom on the `x` axis only (pan disabled, and the zoom
@@ -29835,6 +30690,14 @@ structured points retain their y-value formatting.
29835
30690
  - `stacked: boolean = false` — stacks the `x`/`y`(/`y2`) scale entries `buildScales()` returns; only
29836
30691
  meaningful for `bar`/`line` types (scatter/bubble's linear `x` scale and the radial `r` scale used
29837
30692
  by radar/polar-area are out of scope)
30693
+ - `stackedAxes?: Partial<Record<'y' | 'y2', boolean>>` (attribute: false) — per-value-axis override
30694
+ of `stacked`, keyed by `'y'`/`'y2'`. An axis absent from this record — including every axis when
30695
+ the whole property is unset — falls back to `stacked`, so a chart that never sets this renders
30696
+ byte-identically to before. Lets a `stacked` bar series on the primary axis sit next to an
30697
+ unstacked overlay series on `y2` (via `LyraChartSeries.axis: 'y2'`): e.g. `stacked` plus
30698
+ `stackedAxes: { y2: false }`. The shared categorical axis (`x` for a vertical bar/line, or `y`
30699
+ under a horizontal `indexAxis`) has no entry of its own — it always mirrors the resolved `'y'`
30700
+ value, matching Chart.js's own paired index/value-scale stacking contract.
29838
30701
  - `withoutAnimation: boolean = false` (attribute `without-animation`, reflected) — disables Chart.js
29839
30702
  construction animation; reduced-motion preference also disables it regardless of this value
29840
30703
  - `withoutTooltip: boolean = false` (attribute `without-tooltip`, reflected) — disables the
@@ -29847,14 +30710,20 @@ structured points retain their y-value formatting.
29847
30710
  `feature-warning` plus assertive announcement explains the nonfatal limitation. The screen-reader
29848
30711
  equivalent is the always-present accessible data table (`show-data-table` makes it visible) —
29849
30712
  labels are a purely visual, canvas-only addition and add no new a11y surface.
29850
- - `stackTotals: boolean = false` (attribute `stack-totals`) — with `stacked` (bar/line only), draws
29851
- the per-category stack total above each stack, via the same `chartjs-plugin-datalabels` peer.
29852
- Null/undefined points are skipped; a category whose every value is null shows no total (not
29853
- `0`). The generated accessible table receives the same formatted total column; a dual-axis stack
29854
- receives separately labelled primary- and secondary-axis total columns. The table totals do not
29855
- depend on the optional visual-label peer being installed. If that peer is unavailable, the chart
29856
- retains its core rendering and generated table totals while a localized nonfatal warning explains
29857
- that the canvas labels cannot be drawn.
30713
+ - `stackTotals: boolean = false` (attribute `stack-totals`) — on an actually-stacked axis (`stacked`
30714
+ or `stackedAxes`, bar/line only), draws the per-category stack total above each stack, via the
30715
+ same `chartjs-plugin-datalabels` peer. Computed per `LyraChartSeries.stack` group as well as per
30716
+ axis: two stack groups sharing one axis each get their own total, drawn above their own topmost
30717
+ dataset, and a dataset on an axis that isn't stacked (e.g. an unstacked `stackedAxes` overlay)
30718
+ never gets one. Null/undefined points are skipped; a category whose every value is null shows no
30719
+ total (not `0`). The generated accessible table receives one formatted total column per stacked
30720
+ axis (a dual-axis stack gets separately labelled primary- and secondary-axis columns); with
30721
+ multiple stack groups on one axis, that column still totals only the implicit group of series
30722
+ that never set their own `stack` id — a per-group breakdown is available programmatically but not
30723
+ yet surfaced as extra table columns. The table totals do not depend on the optional visual-label
30724
+ peer being installed. If that peer is unavailable, the chart retains its core rendering and
30725
+ generated table totals while a localized nonfatal warning explains that the canvas labels cannot
30726
+ be drawn.
29858
30727
  - `config?: LyraChartConfiguration` (attribute: false) — peer-neutral configuration structurally
29859
30728
  compatible with Chart.js's `ChartConfiguration`, deep-merged over the generated
29860
30729
  config; any nested key wins without clobbering sibling generated keys. This is the raw Chart.js
@@ -30175,8 +31044,9 @@ announced. In particular, unavailable data labels do not remove generated table
30175
31044
  pending. Independently, `updated()` only reaches
30176
31045
  Chart.js when at least one of `type`, `labels`, `datasets`, `description`, `grid`, `axes`, `compact`, `indexAxis`,
30177
31046
  `label`, `hiddenDatasets`, `legendPosition`, `min`, `max`, `plugins`, the internal resolved auto legend
30178
- position, `valueFormatter`, `formatter`, `area`, `height`, `xLabel`, `yLabel`, `y2Label`, `beginAtZero`,
30179
- `stacked`, any `without*` control, `dataLabels`, `stackTotals`, `config`, the parsed
31047
+ position, `valueFormatter`, `formatter`, `tooltipTitleFormatter`, `tooltipFooterFormatter`, `area`,
31048
+ `height`, `xLabel`, `yLabel`, `y2Label`, `beginAtZero`,
31049
+ `stacked`, `stackedAxes`, any `without*` control, `dataLabels`, `stackTotals`, `config`, the parsed
30180
31050
  slotted config, `zoom`, `locale`, `strings`, or the internal loading state actually changed in
30181
31051
  that update (so an
30182
31052
  unrelated property/state update, or a bare `requestUpdate()`, draws nothing). Resize callbacks
@@ -30200,6 +31070,19 @@ tooltips (native SVG `<title>`, no positioning JS) — not a full `lr-chart` rep
30200
31070
  zoom/pan, no pie/doughnut/radar/scatter/bubble types, no horizontal/dual-y-axis, no raw-config
30201
31071
  passthrough). Not a subclass of `LyraChart`.
30202
31072
 
31073
+ Deliberate omissions, assessed and not implemented: `lr-chart`'s per-series `LyraChartSeries.stack`
31074
+ group and per-axis `stackedAxes` have no `lr-lite-chart` counterpart. `stacked` here is already
31075
+ chart-wide only (see below) and this component has exactly one value scale — no `y2` — so "an
31076
+ unstacked overlay on a second axis," the motivating case for `stackedAxes`, has no equivalent
31077
+ shape to express. A per-series stack-group id would also need the hand-rolled SVG bar-geometry
31078
+ pass (linear/sqrt/log stack compression, `minBarHeight`) to track independent running offsets per
31079
+ group instead of one per category, which is a materially larger, higher-risk change than this
31080
+ component's existing single-stack model. `tooltipTitleFormatter`/`tooltipFooterFormatter` are
31081
+ similarly absent: this component's hover tooltip is a native SVG `<title>` on each mark — one
31082
+ self-contained string per mark, generated by `pointText` — not a Chart.js-style multi-item tooltip
31083
+ with separate title/body/footer regions for several datasets sharing a hovered category, so there
31084
+ is no "every item in the tooltip" surface to hook a title or footer formatter onto.
31085
+
30203
31086
  **Properties:**
30204
31087
  - `type: LyraLiteChartType = 'bar'` — `'bar' | 'line'`
30205
31088
  - `labels: readonly string[] = []` (attribute: false)
@@ -30518,9 +31401,10 @@ of every entry in these lists.**
30518
31401
  **Properties:** `description`, `grid`, `axes`, `compact`, `indexAxis` (`index-axis`), `label`, `hiddenDatasets`, `legendPosition`
30519
31402
  (`legend-position`), `hiddenDatums`, `legendMode` (`legend-mode`), `legendDisplay` (`legend-display`),
30520
31403
  `max`, `min`, `plugins`, `scaleType` (`scale-type`), `annotations`,
30521
- `stacked`, `withoutAnimation` (`without-animation`),
31404
+ `stacked`, `stackedAxes`, `withoutAnimation` (`without-animation`),
30522
31405
  `withoutLegend` (`without-legend`), `withoutTooltip` (`without-tooltip`), `xLabel` (`x-label`),
30523
- `yLabel` (`y-label`), plus additive `labels`, `datasets`, `valueFormatter`, `formatter`, `area`, `zoom`,
31406
+ `yLabel` (`y-label`), plus additive `labels`, `datasets`, `valueFormatter`, `formatter`,
31407
+ `tooltipTitleFormatter`, `tooltipFooterFormatter`, `area`, `zoom`,
30524
31408
  `height`, `y2Label` (`y2-label`), `beginAtZero` (`begin-at-zero`), `dataLabels`
30525
31409
  (`data-labels`), `stackTotals` (`stack-totals`), `config`, `showDataTable`
30526
31410
  (`show-data-table`), `dataTableToggle` (`data-table-toggle`), `chartArea` (readonly), and `chart`.
@@ -30607,9 +31491,10 @@ Bins `values` into `bins` equal-width buckets and renders as a bar chart (extend
30607
31491
  (`index-axis`), `hiddenDatasets`, `hiddenDatums`, `legendPosition` (`legend-position`),
30608
31492
  `legendMode` (`legend-mode`), `legendDisplay` (`legend-display`), `max`, `min`, `plugins`,
30609
31493
  `withoutAnimation` (`without-animation`), `withoutLegend` (`without-legend`), `withoutTooltip`
30610
- (`without-tooltip`), `valueFormatter`, `formatter`, `area`, `zoom`, `config`, `height`, `xLabel` (`x-label`),
31494
+ (`without-tooltip`), `valueFormatter`, `formatter`, `tooltipTitleFormatter`,
31495
+ `tooltipFooterFormatter`, `area`, `zoom`, `config`, `height`, `xLabel` (`x-label`),
30611
31496
  `yLabel` (`y-label`), `y2Label` (`y2-label`), `beginAtZero` (`begin-at-zero`),
30612
- `stacked`, `dataLabels` (`data-labels`), `stackTotals` (`stack-totals`), `showDataTable`
31497
+ `stacked`, `stackedAxes`, `dataLabels` (`data-labels`), `stackTotals` (`stack-totals`), `showDataTable`
30613
31498
  (`show-data-table`), `dataTableToggle` (`data-table-toggle`), `chartArea` (readonly).
30614
31499
 
30615
31500
  **Methods:** `resetZoom()`, `refreshTheme()`, and `renderChart()` are inherited; `appendSamples(values,
@@ -30673,6 +31558,10 @@ their semantics, defaults, and gotchas.
30673
31558
  - `values`/`bins`/`seriesLabel` changes join the inherited connected-and-visible redraw path. There is no
30674
31559
  second post-update refresh, so a same-tick disconnect cannot recreate Chart.js on a detached
30675
31560
  canvas and off-screen sample updates do not repaint it.
31561
+ - calling `binValues()` directly (not through `<lr-histogram>`, which always passes its own
31562
+ resolved `effectiveLocale`) with an omitted `locale` (or `'auto'`) now resolves to the page's
31563
+ active `setLyraLocale()` locale instead of a hardcoded `'en'`, matching `utilities/format.ts`'s
31564
+ helpers; an app that never calls `setLyraLocale()` sees no change, and an explicit tag still wins.
30676
31565
 
30677
31566
  ---
30678
31567
 
@@ -31060,6 +31949,7 @@ These named interfaces and helper signatures are available to typed integrations
31060
31949
  hidden?: boolean;
31061
31950
  axis?: string;
31062
31951
  yAxisID?: string;
31952
+ stack?: string;
31063
31953
  noTooltip?: boolean;
31064
31954
  fill?: unknown;
31065
31955
  backgroundColor?: unknown;
@@ -31142,6 +32032,7 @@ These named interfaces and helper signatures are available to typed integrations
31142
32032
  readonly dash?: boolean;
31143
32033
  readonly noTooltip?: boolean;
31144
32034
  readonly axis?: 'y' | 'y2';
32035
+ readonly stack?: string;
31145
32036
  readonly pointColors?: readonly string[];
31146
32037
  readonly pointRadius?: number | readonly number[];
31147
32038
  readonly segmentColors?: readonly string[];
@@ -35130,7 +36021,11 @@ focus move.
35130
36021
 
35131
36022
  **Exported types:**
35132
36023
 
35133
- - `LyraCatalogEntry { id: string; label: string }` — the shared minimum row vocabulary.
36024
+ - `LyraCatalogEntry { id: string; label: string; disabled?: boolean }` — the shared minimum row
36025
+ vocabulary. `disabled` marks a row non-actionable: `aria-disabled="true"` replaces its
36026
+ selected/active affordances, activating it (click or keyboard) commits nothing and changes no
36027
+ state, and arrow-key/Home/End active-descendant navigation steps past it instead of landing on
36028
+ it. Omitted or `false` renders the row exactly as before this field existed.
35134
36029
  - `LyraCatalog<T extends LyraCatalogEntry = LyraCatalogEntry> = readonly string[] | readonly T[]`
35135
36030
  — a homogeneous catalog shared by model-select, voice-picker, and composed controls. String
35136
36031
  shorthand uses the same string for both id and label; readonly tuples/arrays are accepted. Ids
@@ -35324,7 +36219,8 @@ use, not on `:host`, so it isn't tied to `size`. The selected row
35324
36219
  `--lr-model-select-option-selected-border` and `--lr-model-select-option-selected-color` (both
35325
36220
  `var(--lr-color-brand)`), and `--lr-model-select-option-selected-font-weight`
35326
36221
  (`var(--lr-font-weight-semibold)`), all inline `var()` fallbacks so the selected row is rethemeable
35327
- without hijacking `--lr-color-brand`. `--lr-model-select-max-inline-size` (default
36222
+ without hijacking `--lr-color-brand`. `--lr-model-select-option-disabled-opacity` (default `0.5`)
36223
+ — opacity of an option row whose catalog entry sets `disabled`. `--lr-model-select-max-inline-size` (default
35328
36224
  `var(--lr-size-24rem)`) publishes the host's own width ceiling, which was previously a hard-wired
35329
36225
  literal: set a length to retune it, or `none` to let the control fill its container the way
35330
36226
  `lr-select` does. Like every other name here it is read as a `var()` fallback and never declared on
@@ -35399,6 +36295,32 @@ not a delta — this component does no accumulation or ordering of its own.
35399
36295
  `<lr-code-block>`/`<lr-markdown>` support. Unset leaves the composed element's own default
35400
36296
  untouched.
35401
36297
 
36298
+ The rest of `<lr-markdown>`'s configuration surface forwards verbatim too, each defaulting to
36299
+ exactly `<lr-markdown>`'s own default so leaving it unset renders identically to before these
36300
+ properties existed:
36301
+
36302
+ - `tabSize: number = 4` (attribute `tab-size`) — forwarded to the composed `<lr-markdown>`'s own
36303
+ `tabSize`.
36304
+ - `htmlMode: 'sanitize' | 'escape' | 'trusted' = 'sanitize'` (attribute `html-mode`) — forwarded to
36305
+ the composed `<lr-markdown>`'s own `htmlMode`.
36306
+ - `gfm: boolean = true` — forwarded to the composed `<lr-markdown>`'s own `gfm`.
36307
+ - `linkTarget: string | null = '_blank'` (attribute `link-target`) — forwarded to the composed
36308
+ `<lr-markdown>`'s own `linkTarget`; the composed element always applies its own
36309
+ `rel="noopener noreferrer"` guard whenever a `target` is emitted, including a forwarded
36310
+ non-default value, and never a bare `opener`.
36311
+ - `internalLinkPrefix: string = ''` (attribute `internal-link-prefix`) — forwarded to the composed
36312
+ `<lr-markdown>`'s own `internalLinkPrefix`.
36313
+ - `headingOffset: number = 0` (attribute `heading-offset`) — forwarded to the composed
36314
+ `<lr-markdown>`'s own `headingOffset`.
36315
+ - `highlightCode: boolean = true` (attribute `highlight-code`) — forwarded to the composed
36316
+ `<lr-markdown>`'s own `highlightCode`.
36317
+ - `headingAnchors: boolean = false` (attribute `heading-anchors`) — forwarded to the composed
36318
+ `<lr-markdown>`'s own `headingAnchors`.
36319
+ - `math: boolean = false` — forwarded to the composed `<lr-markdown>`'s own `math`; the transitive
36320
+ `katex` peer is requested only once this is set.
36321
+ - `maxHeight: string = ''` (attribute `max-height`) — forwarded to the composed `<lr-markdown>`'s
36322
+ own `maxHeight`.
36323
+
35402
36324
  **Exported helper:** `looksLikeMarkdown(text: string): boolean` — runs a fixed, ordered list of
35403
36325
  lightweight regexes (ATX heading, fenced code block, `**bold**`, `_italic_`, inline code, bullet
35404
36326
  list item, numbered list item, `[text](url)` link, blockquote) against the whole string and returns
@@ -35420,7 +36342,12 @@ never see that update happen. See `<lr-thinking-panel>`'s own reference at `llms
35420
36342
 
35421
36343
  **Slots:** none — content renders from `content`, not a slot.
35422
36344
 
35423
- **CSS parts:** `base`, `cursor` (only rendered while `streaming` is `true`)
36345
+ **CSS parts:** `base`, `cursor` (only rendered while `streaming` is `true`), plus every part
36346
+ `<lr-markdown>` documents forwarded verbatim (no aliasing — none collides with `base`/`cursor`)
36347
+ from the composed `<lr-markdown>` in Markdown mode via `exportparts`: `content`, `heading`,
36348
+ `paragraph`, `list`, `code-block`, `inline-code`, `link`, `table`, `blockquote`, `img`, `math`. A
36349
+ host-level `lr-streaming-text::part(link)`/`::part(img)` rule reaches the rendered `<a>`/`<img>`
36350
+ exactly as the same rule does applied directly to `<lr-markdown>`.
35424
36351
 
35425
36352
  **Themeable custom properties:** `--lr-inline-cursor-width` (default
35426
36353
  `var(--lr-size-0-125rem)`, the shared inline cursor width), `--lr-inline-cursor-height` (default
@@ -35435,8 +36362,9 @@ does not register it separately), so its optional-peer module graph includes `ma
35435
36362
  stay on the peer-free plain-text path; Markdown rendering lazy-loads `marked` plus the default
35436
36363
  `dompurify` sanitizer and falls back to readable plain text if either is unavailable. Fenced code
35437
36364
  can additionally use `shiki`, whose absence only leaves code unhighlighted. The composed Markdown
35438
- implementation contains the opt-in `katex` loader, but this wrapper does not enable its `math`
35439
- property and therefore never requests `katex` itself.
36365
+ implementation contains the opt-in `katex` loader; this wrapper forwards its own `math` property
36366
+ (default `false`) to the composed element verbatim, so `katex` is requested only once a consumer
36367
+ sets `math` here.
35440
36368
 
35441
36369
  ```html
35442
36370
  <lr-streaming-text id="out" coalesce-ms="80" streaming></lr-streaming-text>
@@ -35461,8 +36389,11 @@ never be left stranded mid-window, and a stream restarting on a reused element c
35461
36389
  showing the previous stream's stale final content for the length of the window.
35462
36390
 
35463
36391
  Rendering itself is never reimplemented here: Markdown mode composes `<lr-markdown>` directly,
35464
- forwarding this component's own `streaming` through as that component's `streaming` hint prop and
35465
- `languages` verbatim; plain-text mode renders into a `white-space: pre-wrap` span instead. The
36392
+ forwarding this component's own `streaming` through as that component's `streaming` hint prop,
36393
+ `languages` verbatim, and the rest of `<lr-markdown>`'s configuration surface verbatim too
36394
+ (`tabSize`, `htmlMode`, `gfm`, `linkTarget`, `internalLinkPrefix`, `headingOffset`,
36395
+ `highlightCode`, `headingAnchors`, `math`, `maxHeight` — see **Properties** above); plain-text mode
36396
+ renders into a `white-space: pre-wrap` span instead. The
35466
36397
  blinking cursor degrades
35467
36398
  to a static, always-visible bar under `prefers-reduced-motion: reduce`. In plain-text mode it sits
35468
36399
  inline at the tail of the final character; in Markdown mode it renders as its own trailing block
@@ -35490,13 +36421,15 @@ happens to end with.
35490
36421
  A build-lean `<lr-streaming-text>` variant for a consumer whose fenced-code `languages` map already
35491
36422
  covers every language it will ever stream, or who never renders fenced code at all. Every
35492
36423
  capability — token coalescing, `contentMode` auto-detection, the blinking cursor, the
35493
- `lr-content-settled` event, the `languages` property is identical to `<lr-streaming-text>`; only
35494
- which Markdown element Markdown mode composes differs: this variant renders `<lr-markdown-core>`
35495
- (`../markdown/markdown-core.js`) instead of `<lr-markdown>`, so importing this entry point instead
35496
- of `streaming-text.js` never references `<lr-markdown>`'s ~200-language dynamic-import table at
35497
- all. A fenced code block whose language isn't a key in `languages` always renders the plain-text
35498
- fallback there is no default/full-table highlighter here to fall back to, mirroring
35499
- `<lr-markdown-core>`'s own contract.
36424
+ `lr-content-settled` event, the `languages` property, and the full forwarded Markdown configuration
36425
+ surface (`tabSize`, `htmlMode`, `gfm`, `linkTarget`, `internalLinkPrefix`, `headingOffset`,
36426
+ `highlightCode`, `headingAnchors`, `math`, `maxHeight`) is identical to `<lr-streaming-text>`;
36427
+ only which Markdown element Markdown mode composes differs: this variant renders
36428
+ `<lr-markdown-core>` (`../markdown/markdown-core.js`) instead of `<lr-markdown>`, so importing this
36429
+ entry point instead of `streaming-text.js` never references `<lr-markdown>`'s ~200-language
36430
+ dynamic-import table at all. A fenced code block whose language isn't a key in `languages` always
36431
+ renders the plain-text fallback — there is no default/full-table highlighter here to fall back to,
36432
+ mirroring `<lr-markdown-core>`'s own contract.
35500
36433
 
35501
36434
  **Properties:** `content: string = ''` — the full current text so far, identical contract to
35502
36435
  `<lr-streaming-text>`'s own; `streaming: boolean = false` (reflected); `coalesceMs: number = 50`
@@ -35505,7 +36438,18 @@ fallback — there is no default/full-table highlighter here to fall back to, mi
35505
36438
  `content-mode`, reflected) — `auto`
35506
36439
  uses `looksLikeMarkdown`, `plain`/`markdown` force their named paths; `languages?:
35507
36440
  Readonly<Record<string, ShikiLanguageInput>>` (property only) — forwarded verbatim to the composed
35508
- `<lr-markdown-core>`'s own `languages` instead of `<lr-markdown>`'s.
36441
+ `<lr-markdown-core>`'s own `languages` instead of `<lr-markdown>`'s (defaulting the composed
36442
+ element's own `languages` to `{}` when unset, unlike the full variant's `undefined`). The rest of
36443
+ `<lr-markdown-core>`'s configuration surface forwards verbatim too, sharing the same properties,
36444
+ attribute names, and defaults described under `<lr-streaming-text>`'s own **Properties** above:
36445
+ `tabSize: number = 4` (attribute `tab-size`); `htmlMode: 'sanitize' | 'escape' | 'trusted' =
36446
+ 'sanitize'` (attribute `html-mode`); `gfm: boolean = true`; `linkTarget: string | null = '_blank'`
36447
+ (attribute `link-target`, still guarded by the composed element's own
36448
+ `rel="noopener noreferrer"` whenever a `target` is emitted); `internalLinkPrefix: string = ''`
36449
+ (attribute `internal-link-prefix`); `headingOffset: number = 0` (attribute `heading-offset`);
36450
+ `highlightCode: boolean = true` (attribute `highlight-code`); `headingAnchors: boolean = false`
36451
+ (attribute `heading-anchors`); `math: boolean = false`; `maxHeight: string = ''` (attribute
36452
+ `max-height`).
35509
36453
 
35510
36454
  **Exported helper:** `looksLikeMarkdown(text: string): boolean` — the same standalone heuristic
35511
36455
  `<lr-streaming-text>` exports and documents, in `llms/components/lr-streaming-text.md`; both tags
@@ -35517,8 +36461,12 @@ share one implementation.
35517
36461
 
35518
36462
  **Slots:** none — content renders from `content`, not a slot.
35519
36463
 
35520
- **CSS parts:** `base`, `cursor` (only rendered while `streaming` is `true`) identical to
35521
- `<lr-streaming-text>`'s own.
36464
+ **CSS parts:** `base`, `cursor` (only rendered while `streaming` is `true`), plus every part
36465
+ `<lr-markdown-core>` documents forwarded verbatim from the composed `<lr-markdown-core>` in
36466
+ Markdown mode via `exportparts`: `content`, `heading`, `paragraph`, `list`, `code-block`,
36467
+ `inline-code`, `link`, `table`, `blockquote`, `img`, `math` — the identical forwarded list
36468
+ `<lr-streaming-text>` documents, since `<lr-markdown>` and `<lr-markdown-core>` share the same
36469
+ documented part vocabulary.
35522
36470
 
35523
36471
  **Themeable custom properties:** `--lr-inline-cursor-width` (default `var(--lr-size-0-125rem)`) and
35524
36472
  `--lr-inline-cursor-height` (default `var(--lr-size-1em)`) — the same shared inline-cursor tokens
@@ -35765,6 +36713,27 @@ header gap — a rule that sets `display` on it must qualify itself with `:not([
35765
36713
  `line-button` (a gutter line-number button, only rendered while `activatableLines` and `lineNumbers`
35766
36714
  are both set)
35767
36715
 
36716
+ **Migrating a pre-16.0.0 `::part()` rule.** This component's icon-only action is a composed
36717
+ `<lr-icon-button>`, so the part naming that action now names the composed child's HOST, which
36718
+ paints nothing. A `border`, `background` or `border-radius` set on it is silently dead — only
36719
+ `color` still appears to work, because it inherits, which makes such a rule look half-alive rather
36720
+ than broken. Set `--lr-icon-button-background`/`-color`/`-border`/`-radius` (and their
36721
+ `-hover`/`-active` variants) on this element or an ancestor instead: the composed control reads
36722
+ those public tokens ahead of any default this component supplies. For SIZE use
36723
+ `--lr-theme-icon-button-size`, not `--lr-icon-button-size` — every `LyraElement` re-declares the
36724
+ latter on its own `:host`, so it never reaches a composed child (see `llms/tokens.md`).
36725
+
36726
+ **Border reaches the composed copy control the same way background/color/radius do.** This
36727
+ component paints no resting border of its own, so it relays no `--_lr-icon-button-border-default`
36728
+ into the copy control's private fallback tier — but that absence is not a gap. The public
36729
+ `--lr-icon-button-border` (and its `-hover`/`-active` variants) is the FIRST arm of the token
36730
+ chain, resolved by ordinary custom-property inheritance regardless of whether this component
36731
+ relays a default for that same property, so setting it on this element or an ancestor reaches the
36732
+ copy control exactly as the background/color/radius tokens do. A component with no resting
36733
+ border simply has no default to relay, which is different from border theming being broken. Size
36734
+ remains the one exception that does not cross this way: use `--lr-theme-icon-button-size`, never
36735
+ `--lr-icon-button-size`, as noted above.
36736
+
35768
36737
  **Themeable custom properties:** `--lr-code-block-max-height` (default `none` — an independently
35769
36738
  settable scroll cap; a `max-height` attribute writes the same property inline on `body` and wins),
35770
36739
  `--lr-code-block-font` (default
@@ -36260,6 +37229,16 @@ or an ancestor, or reach a built-in's composed native control directly through
36260
37229
  media rule then floors the rendered hit area at 2.75rem/44px regardless of how far a dense-row
36261
37230
  override lowered it, so the shrink is safe on a touch device.
36262
37231
 
37232
+ **Migrating a pre-16.0.0 `::part()` rule.** This component's icon-only actions are composed
37233
+ `<lr-icon-button>`s, so a part naming one of them now names the composed child's HOST, which paints
37234
+ nothing. A `border`, `background` or `border-radius` set on it is silently dead — only `color` still
37235
+ appears to work, because it inherits, which makes such a rule look half-alive rather than broken.
37236
+ Set `--lr-icon-button-background`/`-color`/`-border`/`-radius` (and their `-hover`/`-active`
37237
+ variants) on this element or an ancestor instead: the composed control reads those public tokens
37238
+ ahead of any default this component supplies. For SIZE use `--lr-theme-icon-button-size`, not
37239
+ `--lr-icon-button-size` — every `LyraElement` re-declares the latter on its own `:host`, so it never
37240
+ reaches a composed child (see `llms/tokens.md`).
37241
+
36263
37242
  ## `lr-message-feedback`
36264
37243
 
36265
37244
  Thumbs up/down for one assistant message, with an optional inline detail step (categorical reason
@@ -36597,12 +37576,15 @@ identifier disappears, focus repairs to the nearest surviving occurrence without
36597
37576
  external focus move.
36598
37577
 
36599
37578
  **Properties:** `suggestions: readonly LyraChatSuggestion[] = []` (attribute: false) —
36600
- `LyraChatSuggestion { suggestionId: string; label: string; icon?: string; detail?: string }`
37579
+ `LyraChatSuggestion { suggestionId: string; label: string; icon?: string; detail?: string; disabled?: boolean }`
36601
37580
  (exported here). Identifiers must be nonempty and unique; invalid/later duplicates are omitted with
36602
37581
  the first valid occurrence winning. The input is clone-owned, bounded, and frozen; reassign a new
36603
37582
  array after changing the sequence or a row. `icon` is an optional
36604
37583
  peer-neutral literal hint (for example, an emoji), rendered decoratively before the text, and
36605
- `detail` is an optional secondary line. Empty renders nothing at all. `wrap: boolean = false`
37584
+ `detail` is an optional secondary line. `disabled` marks a suggestion non-actionable: its chip
37585
+ renders a genuinely disabled `<button>` (no roving tab stop, no hover/press affordance), activating
37586
+ it emits nothing, and arrow-key/Home/End roving navigation steps past it. Empty renders nothing at
37587
+ all. `wrap: boolean = false`
36606
37588
  (reflected) — wraps into multiple rows instead of a single horizontally scrollable line. `label?:
36607
37589
  string` — accessible name for the group. Omitting it uses the localized `suggestionsLabel`; an
36608
37590
  explicit empty string intentionally leaves the group unnamed.
@@ -36621,7 +37603,8 @@ cannot do (once the chips wrap, the row fills the available inline size and each
36621
37603
  start). `--lr-suggestion-chips-hover-bg` (default `var(--lr-color-brand-quiet)`) — a `chip`'s
36622
37604
  background on hover. `--lr-suggestion-chips-hover-border` (default `var(--lr-color-brand)`) — a
36623
37605
  `chip`'s border color on hover. All three are declared as `var()` fallbacks at the point of use, not
36624
- on `:host`. Plus shared tokens `--lr-space-xs/-m/-2xs`,
37606
+ on `:host`. `--lr-suggestion-chips-disabled-opacity` (default `0.5`) — opacity of a chip whose
37607
+ suggestion sets `disabled`. Plus shared tokens `--lr-space-xs/-m/-2xs`,
36625
37608
  `--lr-color-border/-surface/-text/-text-quiet`, `--lr-radius-pill`, `--lr-font-size-xs`,
36626
37609
  `--lr-focus-ring-width/-color/-offset`.
36627
37610
 
@@ -37019,6 +38002,12 @@ family** (16.0.0): `--lr-overlay-surface` (default `var(--lr-color-surface-overl
37019
38002
  ancestor, to scope it — retints this surface together with every other floating surface in the
37020
38003
  library. `--lr-overlay-radius` (default `var(--lr-radius)`) is the matching corner radius.
37021
38004
 
38005
+ `--lr-positioning-strategy` (16.0.0) — the breakdown tooltip reads this same cascading
38006
+ `absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
38007
+ its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
38008
+ property on `<lr-usage-badge>`; set the custom property on `:root`, a theme, or one clipping
38009
+ ancestor to change every unset usage badge beneath it.
38010
+
37022
38011
  ```html
37023
38012
  <lr-chat-message message-role="assistant" status="sent">
37024
38013
  <lr-usage-badge
@@ -37262,8 +38251,11 @@ popup. Ordinary keyboard behavior resumes after composition.
37262
38251
  **Exported types:** `LyraVoiceCatalogEntry extends LyraCatalogEntry { language?: string;
37263
38252
  description?: string; previewUrl?: string }` — `language`/`description` render as a quiet
37264
38253
  `[part="option-meta"]` second line. Voice catalogs use the shared
37265
- `LyraCatalog<LyraVoiceCatalogEntry>` homogeneous readonly union documented under `lr-model-select`.
37266
- The public `size` property uses `LyraSize`, including the long-form aliases.
38254
+ `LyraCatalog<LyraVoiceCatalogEntry>` homogeneous readonly union documented under `lr-model-select`,
38255
+ including the shared `disabled` field: a disabled voice row cannot be selected by click or keyboard
38256
+ and is stepped over by arrow-key/Home/End navigation, but its own `[part="option-preview"]` stays
38257
+ independently clickable, since previewing a voice is a separate affordance from selecting it. The
38258
+ public `size` property uses `LyraSize`, including the long-form aliases.
37267
38259
  `LyraVoicePickerSelectionDirection = 'forward' | 'backward' | 'none'` is the native
37268
38260
  selection direction exposed in free-text mode.
37269
38261
 
@@ -37423,6 +38415,7 @@ trigger), `expand-icon`, `empty`, `hint`, `error`.
37423
38415
  - `--lr-voice-picker-option-selected-font-weight` — Selected option label weight. Default: `var(--lr-font-weight-semibold)`.
37424
38416
  - `--lr-voice-picker-option-synthetic-border-style` — Synthetic stale-value row border style. Default: `dashed`.
37425
38417
  - `--lr-voice-picker-option-synthetic-border-color` — Synthetic stale-value row border color. Default: `var(--lr-color-border)`.
38418
+ - `--lr-voice-picker-option-disabled-opacity` — Opacity of an option row whose catalog entry sets `disabled`. Default: `0.5`.
37426
38419
  - `--lr-voice-picker-option-synthetic-font-style` — Synthetic stale-value option-label font style. Default: `italic`.
37427
38420
  - `--lr-voice-picker-preview-hover-bg` — Preview hover fill. Default: `var(--lr-color-brand-quiet)`.
37428
38421
  - `--lr-voice-picker-preview-hover-color` — Preview hover icon. Default: `var(--lr-color-brand)`.
@@ -37685,8 +38678,8 @@ Every array-valued property above is a clone-owned, bounded, frozen readonly sna
37685
38678
  nested source children and queued attachments. Mutating a previously assigned collection has no
37686
38679
  effect; create and reassign a new array after changes.
37687
38680
 
37688
- `LyraPromptSuggestion` extends `LyraMentionItem { suggestionId, label, description?, icon? }` with
37689
- optional `insertText` (defaults to `label`). The selected occurrence's original, pre-filter `index`
38681
+ `LyraPromptSuggestion` extends `LyraMentionItem { suggestionId, label, description?, icon?, disabled? }`
38682
+ with optional `insertText` (defaults to `label`). The selected occurrence's original, pre-filter `index`
37690
38683
  is preserved in the event detail. `LyraPromptInputAttachment` replaces `DocumentRef.id` with
37691
38684
  `attachmentId` and adds `file?`, `bytes?`, `status?: 'pending' | 'uploading' | 'error' | 'success'`,
37692
38685
  and numeric `progress?`.
@@ -38203,6 +39196,7 @@ These named interfaces and helper signatures are available to typed integrations
38203
39196
  // Inherited from LyraCatalogEntry.
38204
39197
  id: string;
38205
39198
  label: string;
39199
+ disabled?: boolean;
38206
39200
  }`
38207
39201
 
38208
39202
  - **`components-conversation-model-settings-panel-model-settings-panel-contracts`** — Supporting data types and helpers for this component family.
@@ -38235,6 +39229,7 @@ These named interfaces and helper signatures are available to typed integrations
38235
39229
  readonly label: string;
38236
39230
  readonly description?: string;
38237
39231
  readonly icon?: string;
39232
+ readonly disabled?: boolean;
38238
39233
  }`
38239
39234
 
38240
39235
  - **`components-conversation-prompt-queue-prompt-queue-contracts`** — Supporting data types and helpers for this component family.
@@ -38275,6 +39270,7 @@ These named interfaces and helper signatures are available to typed integrations
38275
39270
  label: string;
38276
39271
  icon?: string;
38277
39272
  detail?: string;
39273
+ disabled?: boolean;
38278
39274
  }`
38279
39275
 
38280
39276
  - **`components-conversation-transcript-feed-transcript-feed-contracts`** — Supporting data types and helpers for this component family.
@@ -38296,6 +39292,7 @@ These named interfaces and helper signatures are available to typed integrations
38296
39292
  // Inherited from LyraCatalogEntry.
38297
39293
  id: string;
38298
39294
  label: string;
39295
+ disabled?: boolean;
38299
39296
  }`
38300
39297
 
38301
39298
  - **`internal-catalog-picker-contracts`** — Shared utility contracts.
@@ -38303,6 +39300,7 @@ These named interfaces and helper signatures are available to typed integrations
38303
39300
  `LyraCatalogEntry {
38304
39301
  id: string;
38305
39302
  label: string;
39303
+ disabled?: boolean;
38306
39304
  }`
38307
39305
 
38308
39306
  # Agent tooling, tool calls & evaluation (`components/agent-tools/`)
@@ -38413,6 +39411,12 @@ The anchored detail tooltip is a floating surface and paints from the **shared o
38413
39411
  declared on `:host`, so one declaration on `:root` — or on any ancestor, to scope it — retints this
38414
39412
  surface together with every other floating surface in the library. `--lr-overlay-radius` (default `var(--lr-radius)`) is the matching corner radius.
38415
39413
 
39414
+ `--lr-positioning-strategy` (16.0.0) — the detail tooltip reads this same cascading
39415
+ `absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
39416
+ its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
39417
+ property on `<lr-tool-call-chip>`; set the custom property on `:root`, a theme, or one clipping
39418
+ ancestor to change every unset chip beneath it.
39419
+
38416
39420
  **Optional peer deps:** none.
38417
39421
 
38418
39422
  ```html
@@ -40486,13 +41490,21 @@ real overlays use.
40486
41490
  `returnFocusTo: ConfirmBarReturnFocusTarget = null` (attribute: false) — where focus goes once a
40487
41491
  decision lands, instead of parking on `[part="status"]`.
40488
41492
  `ConfirmBarReturnFocusTarget = HTMLElement | null | (() => HTMLElement | null)`; the thunk form is
40489
- resolved at handoff time, because a host that swaps a focused control out for this bar often
40490
- re-creates that control on the way back. It applies to every path that reaches a decision, a
40491
- `pending` decision finalized externally included. A named target that is missing, detached, `inert`,
40492
- or otherwise refuses focus falls back to `[part="status"]` rather than to `<body>` an `inert`
40493
- element refuses `focus()` silently. Left unset, the handoff is byte-identical to the shipped one.
40494
- The pending state is deliberately *not* affected: while a decision is awaiting resolution, focus
40495
- still parks on `[part="status"]`, because that is not the return journey yet.
41493
+ called at handoff time, because a host that swaps a focused control out for this bar often
41494
+ re-creates that control on the way back and, because every supported host framework re-renders
41495
+ asynchronously relative to that synchronous handoff, the control frequently does not exist yet at
41496
+ that first call. When the first call does not yet name a live, focusable element, the same handoff
41497
+ calls the thunk again once the host has had a real chance to react (its own re-render committed),
41498
+ and moves focus there if it has since appeared and nothing else has claimed focus in the meantime —
41499
+ this is what makes the swap-a-trigger-for-this-bar case actually work, rather than only working when
41500
+ the host happens to re-create its control before the decision lands. A plain element value is
41501
+ resolved once, synchronously, and never retried: it names something that either already exists or
41502
+ never will. It applies to every path that reaches a decision, a `pending` decision finalized
41503
+ externally included. A named target that is missing, detached, `inert`, or otherwise refuses focus
41504
+ falls back to `[part="status"]` rather than to `<body>` — an `inert` element refuses `focus()`
41505
+ silently. Left unset, the handoff is byte-identical to the shipped one. The pending state is
41506
+ deliberately *not* affected: while a decision is awaiting resolution, focus still parks on
41507
+ `[part="status"]`, because that is not the return journey yet.
40496
41508
 
40497
41509
  **Slots:** default — supplementary body content between the heading and the actions (e.g. a
40498
41510
  `lr-diff-view`). `footer` — extra content at the start of the action row.
@@ -40618,6 +41630,27 @@ bar.addEventListener("lr-approve", (e) => {
40618
41630
  });
40619
41631
  ```
40620
41632
 
41633
+ A host that reveals this bar in place of a control it just hid — the `returnFocusTo` motivating
41634
+ case — does not need to order that swap relative to the line above. A reactive host's own re-render
41635
+ (replacing this bar with its trigger again) runs on its own update cycle, which lands asynchronously
41636
+ either way, so `returnFocusTo`'s thunk is written to be called twice: once immediately, in case the
41637
+ control already exists, and once more after the host has had a chance to react if the first call
41638
+ found nothing yet:
41639
+
41640
+ ```ts
41641
+ bar.returnFocusTo = () => document.querySelector('[data-action="delete"]');
41642
+ bar.addEventListener("lr-approve", (e) => {
41643
+ e.preventDefault();
41644
+ runApproval(e.detail.args)
41645
+ .then(() => {
41646
+ bar.decision = "approved"; // the host's own state clear can happen before or after this
41647
+ })
41648
+ .catch(() => {
41649
+ bar.pending = null;
41650
+ });
41651
+ });
41652
+ ```
41653
+
40621
41654
  ## `lr-browser-frame`
40622
41655
 
40623
41656
  Presentational "agent computer" viewport: a screenshot/frame stream (or slotted live media), a
@@ -42857,6 +43890,12 @@ The anchored detail popover is a floating surface and paints from the **shared o
42857
43890
  declared on `:host`, so one declaration on `:root` — or on any ancestor, to scope it — retints this
42858
43891
  surface together with every other floating surface in the library. `--lr-overlay-radius` (default `var(--lr-radius)`) is the matching corner radius.
42859
43892
 
43893
+ `--lr-positioning-strategy` (16.0.0) — the preview popover reads this same cascading
43894
+ `absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
43895
+ its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
43896
+ property on `<lr-entity-chip>`; set the custom property on `:root`, a theme, or one clipping
43897
+ ancestor to change every unset entity chip beneath it.
43898
+
42860
43899
  **Optional peer deps:** none.
42861
43900
 
42862
43901
  ```html
@@ -43483,6 +44522,12 @@ The anchored source-preview popover is a floating surface and paints from the **
43483
44522
  declared on `:host`, so one declaration on `:root` — or on any ancestor, to scope it — retints this
43484
44523
  surface together with every other floating surface in the library. `--lr-overlay-radius` (default `var(--lr-radius)`) is the matching corner radius.
43485
44524
 
44525
+ `--lr-positioning-strategy` (16.0.0) — the source-preview popover reads this same cascading
44526
+ `absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
44527
+ its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
44528
+ property on `<lr-citation-badge>`; set the custom property on `:root`, a theme, or one clipping
44529
+ ancestor to change every unset citation badge beneath it.
44530
+
43486
44531
  **Optional peer deps:** none.
43487
44532
 
43488
44533
  ```html