@aceshooting/lyra-ui 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/README.md +11 -0
  2. package/custom-elements.json +23341 -11202
  3. package/dist/components/app-rail/app-rail.d.ts +171 -0
  4. package/dist/components/app-rail/app-rail.js +483 -0
  5. package/dist/components/app-rail/app-rail.styles.d.ts +1 -0
  6. package/dist/components/app-rail/app-rail.styles.js +125 -0
  7. package/dist/components/attachment-chip/attachment-chip.d.ts +114 -0
  8. package/dist/components/attachment-chip/attachment-chip.js +312 -0
  9. package/dist/components/attachment-chip/attachment-chip.styles.d.ts +1 -0
  10. package/dist/components/attachment-chip/attachment-chip.styles.js +184 -0
  11. package/dist/components/attachment-trigger/attachment-trigger.d.ts +83 -0
  12. package/dist/components/attachment-trigger/attachment-trigger.js +258 -0
  13. package/dist/components/attachment-trigger/attachment-trigger.styles.d.ts +1 -0
  14. package/dist/components/attachment-trigger/attachment-trigger.styles.js +68 -0
  15. package/dist/components/chart/box-plot.js +11 -1
  16. package/dist/components/chart/chart-loader.d.ts +44 -13
  17. package/dist/components/chart/chart-loader.js +65 -18
  18. package/dist/components/chart/chart.d.ts +1 -2
  19. package/dist/components/chart/chart.js +67 -12
  20. package/dist/components/chart/histogram-bin.d.ts +4 -1
  21. package/dist/components/chart/histogram-bin.js +24 -14
  22. package/dist/components/chart/lite-chart.d.ts +54 -0
  23. package/dist/components/chart/lite-chart.js +140 -22
  24. package/dist/components/chart/lite-chart.styles.js +9 -0
  25. package/dist/components/chat-composer/chat-composer.d.ts +92 -0
  26. package/dist/components/chat-composer/chat-composer.js +285 -0
  27. package/dist/components/chat-composer/chat-composer.styles.d.ts +1 -0
  28. package/dist/components/chat-composer/chat-composer.styles.js +135 -0
  29. package/dist/components/chat-message/chat-message.d.ts +113 -0
  30. package/dist/components/chat-message/chat-message.js +291 -0
  31. package/dist/components/chat-message/chat-message.styles.d.ts +1 -0
  32. package/dist/components/chat-message/chat-message.styles.js +203 -0
  33. package/dist/components/checkbox/checkbox.d.ts +59 -0
  34. package/dist/components/checkbox/checkbox.js +230 -0
  35. package/dist/components/checkbox/checkbox.styles.d.ts +1 -0
  36. package/dist/components/checkbox/checkbox.styles.js +70 -0
  37. package/dist/components/chip/chip-group.d.ts +56 -0
  38. package/dist/components/chip/chip-group.js +124 -0
  39. package/dist/components/chip/chip-group.styles.d.ts +1 -0
  40. package/dist/components/chip/chip-group.styles.js +57 -0
  41. package/dist/components/chip/chip.d.ts +64 -0
  42. package/dist/components/chip/chip.js +115 -0
  43. package/dist/components/chip/chip.styles.d.ts +1 -0
  44. package/dist/components/chip/chip.styles.js +115 -0
  45. package/dist/components/citation-badge/citation-badge.d.ts +113 -0
  46. package/dist/components/citation-badge/citation-badge.js +300 -0
  47. package/dist/components/citation-badge/citation-badge.styles.d.ts +1 -0
  48. package/dist/components/citation-badge/citation-badge.styles.js +96 -0
  49. package/dist/components/code-block/code-block.d.ts +89 -0
  50. package/dist/components/code-block/code-block.js +317 -0
  51. package/dist/components/code-block/code-block.styles.d.ts +1 -0
  52. package/dist/components/code-block/code-block.styles.js +187 -0
  53. package/dist/components/code-block/code-loader.d.ts +44 -0
  54. package/dist/components/code-block/code-loader.js +79 -0
  55. package/dist/components/combobox/combobox.d.ts +19 -1
  56. package/dist/components/combobox/combobox.js +84 -14
  57. package/dist/components/combobox/option.d.ts +5 -1
  58. package/dist/components/combobox/option.js +35 -0
  59. package/dist/components/context-meter/context-meter.d.ts +53 -0
  60. package/dist/components/context-meter/context-meter.js +150 -0
  61. package/dist/components/context-meter/context-meter.styles.d.ts +1 -0
  62. package/dist/components/context-meter/context-meter.styles.js +120 -0
  63. package/dist/components/conversation-item/conversation-item.d.ts +128 -0
  64. package/dist/components/conversation-item/conversation-item.js +322 -0
  65. package/dist/components/conversation-item/conversation-item.styles.d.ts +1 -0
  66. package/dist/components/conversation-item/conversation-item.styles.js +155 -0
  67. package/dist/components/date-picker/date-input.d.ts +24 -0
  68. package/dist/components/date-picker/date-input.js +134 -22
  69. package/dist/components/date-picker/date-picker.d.ts +7 -0
  70. package/dist/components/date-picker/date-picker.js +85 -16
  71. package/dist/components/dialog/confirm.d.ts +40 -0
  72. package/dist/components/dialog/confirm.js +79 -0
  73. package/dist/components/dialog/dialog.d.ts +85 -0
  74. package/dist/components/dialog/dialog.js +272 -0
  75. package/dist/components/dialog/dialog.styles.d.ts +1 -0
  76. package/dist/components/dialog/dialog.styles.js +52 -0
  77. package/dist/components/dock-panel/dock-panel.d.ts +134 -0
  78. package/dist/components/dock-panel/dock-panel.js +345 -0
  79. package/dist/components/dock-panel/dock-panel.styles.d.ts +1 -0
  80. package/dist/components/dock-panel/dock-panel.styles.js +145 -0
  81. package/dist/components/document-preview/document-preview.d.ts +135 -0
  82. package/dist/components/document-preview/document-preview.js +372 -0
  83. package/dist/components/document-preview/document-preview.styles.d.ts +1 -0
  84. package/dist/components/document-preview/document-preview.styles.js +191 -0
  85. package/dist/components/export-button/csv.js +1 -1
  86. package/dist/components/export-button/export-button.js +2 -0
  87. package/dist/components/flag/flag.d.ts +29 -13
  88. package/dist/components/flag/flag.js +39 -13
  89. package/dist/components/gauge/gauge.js +19 -10
  90. package/dist/components/generation-status/generation-status.d.ts +109 -0
  91. package/dist/components/generation-status/generation-status.js +273 -0
  92. package/dist/components/generation-status/generation-status.styles.d.ts +1 -0
  93. package/dist/components/generation-status/generation-status.styles.js +84 -0
  94. package/dist/components/heatmap/calendar-grid.d.ts +4 -1
  95. package/dist/components/heatmap/calendar-grid.js +25 -12
  96. package/dist/components/heatmap/heatmap-scale.d.ts +6 -0
  97. package/dist/components/heatmap/heatmap-scale.js +7 -1
  98. package/dist/components/heatmap/heatmap.d.ts +74 -0
  99. package/dist/components/heatmap/heatmap.js +135 -23
  100. package/dist/components/json-viewer/json-viewer.d.ts +53 -0
  101. package/dist/components/json-viewer/json-viewer.js +283 -0
  102. package/dist/components/json-viewer/json-viewer.styles.d.ts +1 -0
  103. package/dist/components/json-viewer/json-viewer.styles.js +176 -0
  104. package/dist/components/kbd/kbd.d.ts +77 -0
  105. package/dist/components/kbd/kbd.js +214 -0
  106. package/dist/components/kbd/kbd.styles.d.ts +1 -0
  107. package/dist/components/kbd/kbd.styles.js +52 -0
  108. package/dist/components/live-region/live-region.d.ts +81 -0
  109. package/dist/components/live-region/live-region.js +153 -0
  110. package/dist/components/live-region/live-region.styles.d.ts +1 -0
  111. package/dist/components/live-region/live-region.styles.js +10 -0
  112. package/dist/components/map/map.d.ts +8 -6
  113. package/dist/components/map/map.js +44 -8
  114. package/dist/components/markdown/markdown-loader.d.ts +34 -0
  115. package/dist/components/markdown/markdown-loader.js +43 -0
  116. package/dist/components/markdown/markdown.d.ts +81 -0
  117. package/dist/components/markdown/markdown.js +284 -0
  118. package/dist/components/markdown/markdown.styles.d.ts +1 -0
  119. package/dist/components/markdown/markdown.styles.js +82 -0
  120. package/dist/components/media-card/media-card.d.ts +122 -0
  121. package/dist/components/media-card/media-card.js +295 -0
  122. package/dist/components/media-card/media-card.styles.d.ts +1 -0
  123. package/dist/components/media-card/media-card.styles.js +132 -0
  124. package/dist/components/mention-popover/mention-popover.d.ts +200 -0
  125. package/dist/components/mention-popover/mention-popover.js +475 -0
  126. package/dist/components/mention-popover/mention-popover.styles.d.ts +1 -0
  127. package/dist/components/mention-popover/mention-popover.styles.js +101 -0
  128. package/dist/components/menu/menu-item.d.ts +62 -0
  129. package/dist/components/menu/menu-item.js +119 -0
  130. package/dist/components/menu/menu-item.styles.d.ts +1 -0
  131. package/dist/components/menu/menu-item.styles.js +67 -0
  132. package/dist/components/menu/menu.d.ts +143 -0
  133. package/dist/components/menu/menu.js +365 -0
  134. package/dist/components/menu/menu.styles.d.ts +1 -0
  135. package/dist/components/menu/menu.styles.js +66 -0
  136. package/dist/components/model-select/model-select.d.ts +125 -0
  137. package/dist/components/model-select/model-select.js +490 -0
  138. package/dist/components/model-select/model-select.styles.d.ts +1 -0
  139. package/dist/components/model-select/model-select.styles.js +189 -0
  140. package/dist/components/model-settings-panel/model-settings-panel.d.ts +69 -0
  141. package/dist/components/model-settings-panel/model-settings-panel.js +145 -0
  142. package/dist/components/model-settings-panel/model-settings-panel.styles.d.ts +1 -0
  143. package/dist/components/model-settings-panel/model-settings-panel.styles.js +82 -0
  144. package/dist/components/playback/playback.d.ts +13 -1
  145. package/dist/components/playback/playback.js +47 -14
  146. package/dist/components/responsive-panel/responsive-panel.d.ts +150 -0
  147. package/dist/components/responsive-panel/responsive-panel.js +365 -0
  148. package/dist/components/responsive-panel/responsive-panel.styles.d.ts +1 -0
  149. package/dist/components/responsive-panel/responsive-panel.styles.js +103 -0
  150. package/dist/components/result-card/result-card.d.ts +44 -0
  151. package/dist/components/result-card/result-card.js +83 -0
  152. package/dist/components/result-card/result-card.styles.d.ts +1 -0
  153. package/dist/components/result-card/result-card.styles.js +50 -0
  154. package/dist/components/result-card/result-field.d.ts +36 -0
  155. package/dist/components/result-card/result-field.js +87 -0
  156. package/dist/components/result-card/result-field.styles.d.ts +1 -0
  157. package/dist/components/result-card/result-field.styles.js +32 -0
  158. package/dist/components/select/select.d.ts +43 -2
  159. package/dist/components/select/select.js +150 -17
  160. package/dist/components/slider/slider.d.ts +89 -0
  161. package/dist/components/slider/slider.js +352 -0
  162. package/dist/components/slider/slider.styles.d.ts +1 -0
  163. package/dist/components/slider/slider.styles.js +87 -0
  164. package/dist/components/source-card/source-card.d.ts +92 -0
  165. package/dist/components/source-card/source-card.js +148 -0
  166. package/dist/components/source-card/source-card.styles.d.ts +1 -0
  167. package/dist/components/source-card/source-card.styles.js +69 -0
  168. package/dist/components/source-list/source-list.d.ts +63 -0
  169. package/dist/components/source-list/source-list.js +122 -0
  170. package/dist/components/source-list/source-list.styles.d.ts +1 -0
  171. package/dist/components/source-list/source-list.styles.js +59 -0
  172. package/dist/components/sparkline/sparkline.js +27 -13
  173. package/dist/components/split/split.d.ts +93 -1
  174. package/dist/components/split/split.js +284 -15
  175. package/dist/components/split/split.styles.js +30 -0
  176. package/dist/components/stat/stat.d.ts +6 -1
  177. package/dist/components/stat/stat.js +8 -2
  178. package/dist/components/stream-status/stream-status.d.ts +110 -0
  179. package/dist/components/stream-status/stream-status.js +210 -0
  180. package/dist/components/stream-status/stream-status.styles.d.ts +1 -0
  181. package/dist/components/stream-status/stream-status.styles.js +103 -0
  182. package/dist/components/streaming-text/streaming-text.d.ts +98 -0
  183. package/dist/components/streaming-text/streaming-text.js +209 -0
  184. package/dist/components/streaming-text/streaming-text.styles.d.ts +1 -0
  185. package/dist/components/streaming-text/streaming-text.styles.js +62 -0
  186. package/dist/components/switch/switch.d.ts +58 -0
  187. package/dist/components/switch/switch.js +179 -0
  188. package/dist/components/switch/switch.styles.d.ts +1 -0
  189. package/dist/components/switch/switch.styles.js +75 -0
  190. package/dist/components/table/table.d.ts +73 -4
  191. package/dist/components/table/table.js +169 -20
  192. package/dist/components/table/table.styles.js +6 -1
  193. package/dist/components/tabs/tabs.d.ts +51 -0
  194. package/dist/components/tabs/tabs.js +190 -0
  195. package/dist/components/tabs/tabs.styles.d.ts +1 -0
  196. package/dist/components/tabs/tabs.styles.js +63 -0
  197. package/dist/components/thinking-panel/thinking-panel.d.ts +121 -0
  198. package/dist/components/thinking-panel/thinking-panel.js +235 -0
  199. package/dist/components/thinking-panel/thinking-panel.styles.d.ts +1 -0
  200. package/dist/components/thinking-panel/thinking-panel.styles.js +117 -0
  201. package/dist/components/toast/toast-item.d.ts +1 -0
  202. package/dist/components/toast/toast-item.js +31 -6
  203. package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +130 -0
  204. package/dist/components/tool-approval-dialog/tool-approval-dialog.js +403 -0
  205. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.d.ts +1 -0
  206. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +160 -0
  207. package/dist/components/tool-call-chip/tool-call-chip.d.ts +89 -0
  208. package/dist/components/tool-call-chip/tool-call-chip.js +288 -0
  209. package/dist/components/tool-call-chip/tool-call-chip.styles.d.ts +1 -0
  210. package/dist/components/tool-call-chip/tool-call-chip.styles.js +203 -0
  211. package/dist/components/tool-param-form/tool-param-form.d.ts +145 -0
  212. package/dist/components/tool-param-form/tool-param-form.js +366 -0
  213. package/dist/components/tool-param-form/tool-param-form.styles.d.ts +1 -0
  214. package/dist/components/tool-param-form/tool-param-form.styles.js +75 -0
  215. package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +102 -0
  216. package/dist/components/tool-result-dialog/tool-result-dialog.js +393 -0
  217. package/dist/components/tool-result-dialog/tool-result-dialog.styles.d.ts +1 -0
  218. package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +176 -0
  219. package/dist/components/tool-result-view/registry.d.ts +81 -0
  220. package/dist/components/tool-result-view/registry.js +84 -0
  221. package/dist/components/tool-result-view/tool-result-view.d.ts +55 -0
  222. package/dist/components/tool-result-view/tool-result-view.js +138 -0
  223. package/dist/components/tool-result-view/tool-result-view.styles.d.ts +1 -0
  224. package/dist/components/tool-result-view/tool-result-view.styles.js +13 -0
  225. package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +151 -0
  226. package/dist/components/tool-select-dialog/tool-select-dialog.js +439 -0
  227. package/dist/components/tool-select-dialog/tool-select-dialog.styles.d.ts +1 -0
  228. package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +182 -0
  229. package/dist/components/tree/tree-node.d.ts +11 -2
  230. package/dist/components/tree/tree-node.js +16 -6
  231. package/dist/components/tree/tree.d.ts +8 -2
  232. package/dist/components/tree/tree.js +18 -11
  233. package/dist/components/typing-indicator/typing-indicator.d.ts +60 -0
  234. package/dist/components/typing-indicator/typing-indicator.js +96 -0
  235. package/dist/components/typing-indicator/typing-indicator.styles.d.ts +1 -0
  236. package/dist/components/typing-indicator/typing-indicator.styles.js +132 -0
  237. package/dist/components/virtual-list/virtual-list.d.ts +173 -0
  238. package/dist/components/virtual-list/virtual-list.js +461 -0
  239. package/dist/components/virtual-list/virtual-list.styles.d.ts +1 -0
  240. package/dist/components/virtual-list/virtual-list.styles.js +44 -0
  241. package/dist/components/word-cloud/word-cloud-layout.js +14 -1
  242. package/dist/components/word-cloud/word-cloud.d.ts +6 -0
  243. package/dist/components/word-cloud/word-cloud.js +25 -14
  244. package/dist/components/word-cloud/word-cloud.styles.js +3 -2
  245. package/dist/internal/announcer.d.ts +57 -0
  246. package/dist/internal/announcer.js +71 -0
  247. package/dist/internal/form-associated.d.ts +1 -0
  248. package/dist/internal/form-associated.js +55 -11
  249. package/dist/internal/group-by-recency.d.ts +50 -0
  250. package/dist/internal/group-by-recency.js +80 -0
  251. package/dist/internal/rtl.d.ts +23 -0
  252. package/dist/internal/rtl.js +28 -0
  253. package/dist/internal/scroll-lock.d.ts +10 -5
  254. package/dist/internal/scroll-lock.js +30 -14
  255. package/dist/internal/tokens.styles.js +6 -0
  256. package/dist/lyra.d.ts +130 -31
  257. package/dist/lyra.js +94 -26
  258. package/llms-full.txt +3836 -17
  259. package/llms.txt +16 -1
  260. package/package.json +23 -5
  261. package/dist/components/chart/bar-chart.stories.d.ts +0 -5
  262. package/dist/components/chart/bar-chart.stories.js +0 -20
  263. package/dist/components/chart/box-plot.stories.d.ts +0 -5
  264. package/dist/components/chart/box-plot.stories.js +0 -29
  265. package/dist/components/chart/bubble-chart.stories.d.ts +0 -5
  266. package/dist/components/chart/bubble-chart.stories.js +0 -26
  267. package/dist/components/chart/chart.stories.d.ts +0 -32
  268. package/dist/components/chart/chart.stories.js +0 -141
  269. package/dist/components/chart/doughnut-chart.stories.d.ts +0 -5
  270. package/dist/components/chart/doughnut-chart.stories.js +0 -26
  271. package/dist/components/chart/histogram.stories.d.ts +0 -5
  272. package/dist/components/chart/histogram.stories.js +0 -17
  273. package/dist/components/chart/line-chart.stories.d.ts +0 -22
  274. package/dist/components/chart/line-chart.stories.js +0 -99
  275. package/dist/components/chart/lite-chart.stories.d.ts +0 -14
  276. package/dist/components/chart/lite-chart.stories.js +0 -114
  277. package/dist/components/chart/pie-chart.stories.d.ts +0 -5
  278. package/dist/components/chart/pie-chart.stories.js +0 -26
  279. package/dist/components/chart/polar-area-chart.stories.d.ts +0 -5
  280. package/dist/components/chart/polar-area-chart.stories.js +0 -20
  281. package/dist/components/chart/radar-chart.stories.d.ts +0 -5
  282. package/dist/components/chart/radar-chart.stories.js +0 -20
  283. package/dist/components/chart/scatter-chart.stories.d.ts +0 -5
  284. package/dist/components/chart/scatter-chart.stories.js +0 -31
  285. package/dist/components/combobox/combobox.stories.d.ts +0 -24
  286. package/dist/components/combobox/combobox.stories.js +0 -102
  287. package/dist/components/date-picker/date-input.stories.d.ts +0 -10
  288. package/dist/components/date-picker/date-input.stories.js +0 -48
  289. package/dist/components/date-picker/date-picker.stories.d.ts +0 -10
  290. package/dist/components/date-picker/date-picker.stories.js +0 -25
  291. package/dist/components/empty/empty.stories.d.ts +0 -7
  292. package/dist/components/empty/empty.stories.js +0 -34
  293. package/dist/components/export-button/export-button.stories.d.ts +0 -7
  294. package/dist/components/export-button/export-button.stories.js +0 -37
  295. package/dist/components/file-input/file-input.stories.d.ts +0 -7
  296. package/dist/components/file-input/file-input.stories.js +0 -19
  297. package/dist/components/flag/flag.stories.d.ts +0 -6
  298. package/dist/components/flag/flag.stories.js +0 -31
  299. package/dist/components/gauge/gauge.stories.d.ts +0 -8
  300. package/dist/components/gauge/gauge.stories.js +0 -19
  301. package/dist/components/graph/graph.stories.d.ts +0 -8
  302. package/dist/components/graph/graph.stories.js +0 -68
  303. package/dist/components/heatmap/heatmap.stories.d.ts +0 -27
  304. package/dist/components/heatmap/heatmap.stories.js +0 -179
  305. package/dist/components/map/map.stories.d.ts +0 -15
  306. package/dist/components/map/map.stories.js +0 -135
  307. package/dist/components/playback/playback.stories.d.ts +0 -7
  308. package/dist/components/playback/playback.stories.js +0 -16
  309. package/dist/components/select/select.stories.d.ts +0 -21
  310. package/dist/components/select/select.stories.js +0 -91
  311. package/dist/components/skeleton/skeleton.stories.d.ts +0 -5
  312. package/dist/components/skeleton/skeleton.stories.js +0 -16
  313. package/dist/components/sparkline/sparkline.stories.d.ts +0 -11
  314. package/dist/components/sparkline/sparkline.stories.js +0 -39
  315. package/dist/components/split/split.stories.d.ts +0 -7
  316. package/dist/components/split/split.stories.js +0 -36
  317. package/dist/components/stat/stat.stories.d.ts +0 -9
  318. package/dist/components/stat/stat.stories.js +0 -81
  319. package/dist/components/table/table.stories.d.ts +0 -12
  320. package/dist/components/table/table.stories.js +0 -67
  321. package/dist/components/time-range/time-range.stories.d.ts +0 -8
  322. package/dist/components/time-range/time-range.stories.js +0 -26
  323. package/dist/components/toast/toast.stories.d.ts +0 -8
  324. package/dist/components/toast/toast.stories.js +0 -72
  325. package/dist/components/tree/tree.stories.d.ts +0 -9
  326. package/dist/components/tree/tree.stories.js +0 -52
  327. package/dist/components/widget/widget.stories.d.ts +0 -9
  328. package/dist/components/widget/widget.stories.js +0 -74
  329. package/dist/components/word-cloud/word-cloud.stories.d.ts +0 -10
  330. package/dist/components/word-cloud/word-cloud.stories.js +0 -55
@@ -0,0 +1,155 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ font-size: 0.875rem;
6
+ line-height: 1.4;
7
+ }
8
+
9
+ /* [part='base'] is a plain layout wrapper (no ARIA role of its own) --
10
+ the interactive/selectable region is [part='option'] alone, kept free
11
+ of focusable descendants (see the class doc's nested-interactive note).
12
+ The row-level hover/active background treatments still live here so
13
+ hovering anywhere across the row -- including over the trailing
14
+ rename/actions controls -- highlights the whole thing as one row. */
15
+ [part='base'] {
16
+ display: flex;
17
+ align-items: flex-start;
18
+ gap: var(--lyra-space-xs);
19
+ padding: var(--lyra-space-s) var(--lyra-space-m);
20
+ border-radius: var(--lyra-radius);
21
+ transition: background-color var(--lyra-transition-fast);
22
+ }
23
+ :host(:hover) [part='base'] {
24
+ background: color-mix(in srgb, var(--lyra-color-text) 6%, transparent);
25
+ }
26
+ /* Placed after the :hover rule (equal selector specificity) so an
27
+ active-and-hovered row keeps the stronger active tint instead of the
28
+ two backgrounds visually competing. */
29
+ :host([active]) [part='base'] {
30
+ background: var(--lyra-color-brand-quiet);
31
+ }
32
+
33
+ /* text-quiet's contrast ratio against brand-quiet lands at ~4.25:1 --
34
+ just under the WCAG AA 4.5:1 floor for normal-size text -- even though
35
+ it comfortably passes against the plain (non-active) background used
36
+ the rest of the time. Same class of bug already hit and fixed in
37
+ lyra-attachment-chip's [part='size'] and lyra-chat-message's
38
+ [part='footer']; same fix, full-strength text color once active. */
39
+ :host([active]) [part='excerpt'],
40
+ :host([active]) [part='timestamp'] {
41
+ color: var(--lyra-color-text);
42
+ }
43
+
44
+ [part='option'] {
45
+ display: flex;
46
+ align-items: flex-start;
47
+ gap: var(--lyra-space-s);
48
+ flex: 1 1 auto;
49
+ min-inline-size: 0;
50
+ cursor: pointer;
51
+ outline: none; /* the visible ring below targets [part='option'] directly */
52
+ -webkit-tap-highlight-color: transparent;
53
+ }
54
+ [part='option']:focus-visible {
55
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
56
+ outline-offset: var(--lyra-focus-ring-offset);
57
+ }
58
+
59
+ [part='content'] {
60
+ flex: 1 1 auto;
61
+ min-inline-size: 0;
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 0.125rem;
65
+ }
66
+
67
+ [part='title'] {
68
+ display: block;
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ white-space: nowrap;
72
+ font-weight: 600;
73
+ color: var(--lyra-color-text);
74
+ }
75
+
76
+ [part='title-input'] {
77
+ display: block;
78
+ inline-size: 100%;
79
+ box-sizing: border-box;
80
+ padding: 0.125rem var(--lyra-space-xs);
81
+ border: 1px solid var(--lyra-color-brand);
82
+ border-radius: calc(var(--lyra-radius) * 0.6);
83
+ background: var(--lyra-color-surface);
84
+ color: var(--lyra-color-text);
85
+ font: inherit;
86
+ font-weight: 600;
87
+ }
88
+ [part='title-input']:focus-visible {
89
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
90
+ outline-offset: var(--lyra-focus-ring-offset);
91
+ }
92
+
93
+ [part='excerpt'] {
94
+ display: block;
95
+ overflow: hidden;
96
+ text-overflow: ellipsis;
97
+ white-space: nowrap;
98
+ color: var(--lyra-color-text-quiet);
99
+ font-size: 0.8125rem;
100
+ }
101
+
102
+ [part='timestamp'] {
103
+ flex: 0 0 auto;
104
+ align-self: flex-start;
105
+ white-space: nowrap;
106
+ color: var(--lyra-color-text-quiet);
107
+ font-size: 0.75rem;
108
+ font-variant-numeric: tabular-nums;
109
+ }
110
+
111
+ [part='rename-button'] {
112
+ flex: 0 0 auto;
113
+ display: inline-flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ min-inline-size: min(var(--lyra-icon-button-size), 1.75rem);
117
+ min-block-size: min(var(--lyra-icon-button-size), 1.75rem);
118
+ padding: 0;
119
+ border: none;
120
+ border-radius: calc(var(--lyra-radius) * 0.6);
121
+ background: transparent;
122
+ color: var(--lyra-color-text-quiet);
123
+ cursor: pointer;
124
+ -webkit-tap-highlight-color: transparent;
125
+ transition: background-color var(--lyra-transition-fast);
126
+ }
127
+ [part='rename-button']:hover {
128
+ background: color-mix(in srgb, var(--lyra-color-text) 8%, transparent);
129
+ color: var(--lyra-color-text);
130
+ }
131
+ [part='rename-button']:focus-visible {
132
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
133
+ outline-offset: var(--lyra-focus-ring-offset);
134
+ }
135
+ [part='rename-button'] svg {
136
+ display: block;
137
+ }
138
+
139
+ [part='actions'] {
140
+ flex: 0 0 auto;
141
+ display: flex;
142
+ align-items: center;
143
+ gap: var(--lyra-space-xs);
144
+ }
145
+ [part='actions'][hidden] {
146
+ display: none;
147
+ }
148
+
149
+ @media (prefers-reduced-motion: reduce) {
150
+ [part='base'],
151
+ [part='rename-button'] {
152
+ transition: none !important;
153
+ }
154
+ }
155
+ `;
@@ -59,6 +59,30 @@ export declare class LyraDateInput extends LyraDateInput_base {
59
59
  disconnectedCallback(): void;
60
60
  protected updated(changed: PropertyValues): void;
61
61
  private onInputChange;
62
+ /** Parses one date, ISO-first (so a calendar-invalid ISO string like
63
+ * "2026-02-30" is rejected rather than silently rolled over by Date.parse()).
64
+ * A 3-part, ambiguous slash/dot/dash-separated date (e.g. "15/07/2026") is
65
+ * parsed according to the locale's own day/month/year order (via a real
66
+ * Intl.DateTimeFormat sample, see localeDateOrder()) rather than
67
+ * Date.parse()'s implementation-defined heuristics -- this is what prevents
68
+ * e.g. an en-GB "15/07/2026" from being silently misread as some other day.
69
+ * A 4-digit first group (e.g. "2026-7-15") is never locale-guessed, though:
70
+ * it's unambiguously a year regardless of locale/separator (ISO's own
71
+ * year-first convention, just without zero-padding), so it's routed
72
+ * straight through parseISO() instead -- this is what lets a non-padded
73
+ * ISO-ish date keep parsing correctly (it did via Date.parse() before the
74
+ * ambiguous-date regex below existed at all).
75
+ * Anything else (e.g. "July 15, 2026") still falls through to Date.parse(). */
76
+ private parseOneDate;
77
+ private parseSingleText;
78
+ /** Only round-trips the exact `displayText` shape this component itself
79
+ * renders (`"<locale from> – <locale to>"`) — a raw ISO range typed
80
+ * directly (`"2026-05-01/2026-05-15"`) is also accepted as a convenience.
81
+ * A reversed typed range (`to` before `from`) is normalized into
82
+ * from-before-to order, matching what the date-picker's own UI-driven
83
+ * `commit()` already does for a UI-picked range. */
84
+ private parseRangeText;
85
+ private isWithinBounds;
62
86
  private onInputKey;
63
87
  private onFormControlKey;
64
88
  private onInputBlur;
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { html } from 'lit';
7
+ import { html, nothing } from 'lit';
8
8
  import { property, state } from 'lit/decorators.js';
9
9
  import { LyraElement } from '../../internal/lyra-element.js';
10
10
  import { FormAssociated } from '../../internal/form-associated.js';
@@ -16,6 +16,26 @@ import { parseISO, formatISO } from './calendar-core.js';
16
16
  import { styles } from './date-input.styles.js';
17
17
  import { LyraDatePicker } from './date-picker.js';
18
18
  import './date-picker.js';
19
+ /** Determines the locale's day/month/year field order from a real formatted
20
+ * sample (Jan 2, 2026 -- a date where day/month/year are all numerically
21
+ * distinguishable), instead of relying on Date.parse()'s implementation-defined
22
+ * (commonly mm/dd/yyyy-biased) heuristics for an ambiguous separated date. */
23
+ function localeDateOrder(locale) {
24
+ try {
25
+ const parts = new Intl.DateTimeFormat(locale || undefined, {
26
+ year: 'numeric',
27
+ month: '2-digit',
28
+ day: '2-digit',
29
+ }).formatToParts(new Date(2026, 0, 2));
30
+ const order = parts
31
+ .filter((p) => p.type === 'day' || p.type === 'month' || p.type === 'year')
32
+ .map((p) => p.type);
33
+ return order.length === 3 ? order : ['month', 'day', 'year'];
34
+ }
35
+ catch {
36
+ return ['month', 'day', 'year']; // Date.parse()'s own bias, as a last-resort fallback
37
+ }
38
+ }
19
39
  /**
20
40
  * `<lyra-date-input>` — a date field with an attached calendar popover.
21
41
  * Mirrors the core `<wa-date-input>` API under `lyra-`. Value is ISO 8601
@@ -84,24 +104,18 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
84
104
  this.emit('change');
85
105
  return;
86
106
  }
87
- // A string that already looks like strict ISO (YYYY-MM-DD) must be judged
88
- // by parseISO alone: it validates calendar correctness (rejects e.g.
89
- // "2026-02-30"), but Date.parse() doesn't -- falling through to it for an
90
- // ISO-shaped string would silently resurrect the same rollover parseISO
91
- // guards against (Date.parse('2026-02-30') also normalizes to March 2).
92
- const looksLikeISO = /^\d{4}-\d{2}-\d{2}$/.test(raw);
93
- const parsed = looksLikeISO ? parseISO(raw) : isNaN(Date.parse(raw)) ? null : new Date(Date.parse(raw));
94
- if (parsed) {
95
- this.value = formatISO(parsed);
107
+ const parsed = this.mode === 'range' ? this.parseRangeText(raw) : this.parseSingleText(raw);
108
+ if (parsed && this.isWithinBounds(parsed)) {
109
+ this.value = parsed;
96
110
  this.emit('input');
97
111
  this.emit('change');
98
112
  }
99
113
  else {
100
- // Unparseable text: don't silently keep the committed value while the
101
- // field still shows garbage -- revert the displayed text back to the
102
- // last valid commit and flag the constraint-validation state (mirrors
103
- // how `required` is already wired via internals.setValidity(), see
104
- // form-associated.ts).
114
+ // Unparseable (or out-of-bounds) text: don't silently keep the
115
+ // committed value while the field still shows garbage -- revert the
116
+ // displayed text back to the last valid commit and flag the
117
+ // constraint-validation state (mirrors how `required` is already
118
+ // wired via internals.setValidity(), see form-associated.ts).
105
119
  target.value = this.displayText;
106
120
  this.internals.setValidity({ badInput: true }, 'Enter a valid date');
107
121
  }
@@ -175,7 +189,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
175
189
  }
176
190
  /** Open the calendar popover. */
177
191
  show() {
178
- if (this.open || this.disabled || this.readonly)
192
+ if (this.open || this.effectiveDisabled || this.readonly)
179
193
  return;
180
194
  this.open = true;
181
195
  this.emit('lyra-show');
@@ -199,7 +213,14 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
199
213
  disconnectedCallback() {
200
214
  super.disconnectedCallback();
201
215
  this.cleanupFn?.();
216
+ this.cleanupFn = undefined;
202
217
  document.removeEventListener('pointerdown', this.onDocPointer);
218
+ // Reset so a reconnect (e.g. a drag-drop reparent) re-triggers
219
+ // `updated()`'s `open`-driven branch -- without this, `open` stays
220
+ // stuck `true` across the disconnect, `updated()` never sees it
221
+ // *change*, and `place()` never gets called again to re-bind
222
+ // positioning to the (possibly relocated) anchor.
223
+ this.open = false;
203
224
  }
204
225
  updated(changed) {
205
226
  super.updated(changed);
@@ -217,11 +238,100 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
217
238
  this.toggleAttribute('data-invalid', this.touched && !this.internals.validity.valid);
218
239
  }
219
240
  }
241
+ /** Parses one date, ISO-first (so a calendar-invalid ISO string like
242
+ * "2026-02-30" is rejected rather than silently rolled over by Date.parse()).
243
+ * A 3-part, ambiguous slash/dot/dash-separated date (e.g. "15/07/2026") is
244
+ * parsed according to the locale's own day/month/year order (via a real
245
+ * Intl.DateTimeFormat sample, see localeDateOrder()) rather than
246
+ * Date.parse()'s implementation-defined heuristics -- this is what prevents
247
+ * e.g. an en-GB "15/07/2026" from being silently misread as some other day.
248
+ * A 4-digit first group (e.g. "2026-7-15") is never locale-guessed, though:
249
+ * it's unambiguously a year regardless of locale/separator (ISO's own
250
+ * year-first convention, just without zero-padding), so it's routed
251
+ * straight through parseISO() instead -- this is what lets a non-padded
252
+ * ISO-ish date keep parsing correctly (it did via Date.parse() before the
253
+ * ambiguous-date regex below existed at all).
254
+ * Anything else (e.g. "July 15, 2026") still falls through to Date.parse(). */
255
+ parseOneDate(raw) {
256
+ const looksLikeISO = /^\d{4}-\d{2}-\d{2}$/.test(raw);
257
+ if (looksLikeISO)
258
+ return parseISO(raw);
259
+ const ambiguous = /^(\d{1,4})[/.-](\d{1,4})[/.-](\d{1,4})$/.exec(raw);
260
+ if (ambiguous) {
261
+ const [, g1, g2, g3] = ambiguous;
262
+ if (g1.length === 4) {
263
+ return parseISO(`${g1}-${g2.padStart(2, '0')}-${g3.padStart(2, '0')}`);
264
+ }
265
+ const order = localeDateOrder(this.locale);
266
+ const values = [Number(g1), Number(g2), Number(g3)];
267
+ const fields = {};
268
+ order.forEach((type, i) => {
269
+ fields[type] = values[i];
270
+ });
271
+ if (fields.day != null && fields.month != null && fields.year != null) {
272
+ const year = fields.year < 100 ? 2000 + fields.year : fields.year;
273
+ return parseISO(`${String(year).padStart(4, '0')}-${String(fields.month).padStart(2, '0')}-${String(fields.day).padStart(2, '0')}`);
274
+ }
275
+ }
276
+ return isNaN(Date.parse(raw)) ? null : new Date(Date.parse(raw));
277
+ }
278
+ parseSingleText(raw) {
279
+ const parsed = this.parseOneDate(raw);
280
+ return parsed ? formatISO(parsed) : null;
281
+ }
282
+ /** Only round-trips the exact `displayText` shape this component itself
283
+ * renders (`"<locale from> – <locale to>"`) — a raw ISO range typed
284
+ * directly (`"2026-05-01/2026-05-15"`) is also accepted as a convenience.
285
+ * A reversed typed range (`to` before `from`) is normalized into
286
+ * from-before-to order, matching what the date-picker's own UI-driven
287
+ * `commit()` already does for a UI-picked range. */
288
+ parseRangeText(raw) {
289
+ if (/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(raw)) {
290
+ const [a, b] = raw.split('/');
291
+ const from = parseISO(a);
292
+ const to = parseISO(b);
293
+ if (!from || !to)
294
+ return null;
295
+ return from <= to ? raw : `${b}/${a}`;
296
+ }
297
+ const separator = ' – '; // matches displayText's ` – ` join exactly
298
+ const idx = raw.indexOf(separator);
299
+ if (idx === -1)
300
+ return null;
301
+ const from = this.parseOneDate(raw.slice(0, idx).trim());
302
+ const to = this.parseOneDate(raw.slice(idx + separator.length).trim());
303
+ if (!from || !to)
304
+ return null;
305
+ return from <= to ? `${formatISO(from)}/${formatISO(to)}` : `${formatISO(to)}/${formatISO(from)}`;
306
+ }
307
+ isWithinBounds(isoValue) {
308
+ const min = parseISO(this.min);
309
+ const max = parseISO(this.max);
310
+ const today = new Date();
311
+ today.setHours(0, 0, 0, 0);
312
+ return isoValue.split('/').every((part) => {
313
+ const d = parseISO(part);
314
+ if (!d)
315
+ return false;
316
+ if (min && d < min)
317
+ return false;
318
+ if (max && d > max)
319
+ return false;
320
+ if (this.disablePast && d < today)
321
+ return false;
322
+ if (this.disableFuture && d > today)
323
+ return false;
324
+ return true;
325
+ });
326
+ }
220
327
  render() {
221
328
  const hasValue = this.value.length > 0;
222
329
  const hasHint = this.hasHintSlot || this.hint.length > 0;
223
330
  const hasError = this.hasErrorSlot || this.errorText.length > 0;
224
331
  const hasLabel = this.hasLabelSlot || this.label.length > 0;
332
+ const describedBy = [hasError ? 'date-input-error' : '', hasHint ? 'date-input-hint' : '']
333
+ .filter(Boolean)
334
+ .join(' ');
225
335
  return html `
226
336
  <div part="form-control" @keydown=${this.onFormControlKey}>
227
337
  <label part="form-control-label" for=${this.inputId} ?hidden=${!hasLabel}>
@@ -232,10 +342,11 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
232
342
  id=${this.inputId}
233
343
  part="input"
234
344
  type="text"
235
- aria-label=${this.label || this.placeholder || 'Date'}
345
+ aria-label=${hasLabel ? nothing : this.placeholder || 'Date'}
346
+ aria-describedby=${describedBy || nothing}
236
347
  .value=${this.displayText}
237
348
  placeholder=${this.placeholder}
238
- ?disabled=${this.disabled}
349
+ ?disabled=${this.effectiveDisabled}
239
350
  ?readonly=${this.readonly}
240
351
  @change=${this.onInputChange}
241
352
  @keydown=${this.onInputKey}
@@ -245,6 +356,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
245
356
  ? html `<button
246
357
  part="clear-button"
247
358
  type="button"
359
+ ?disabled=${this.effectiveDisabled || this.readonly}
248
360
  aria-label="Clear"
249
361
  @click=${() => this.clear()}
250
362
  >
@@ -258,7 +370,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
258
370
  aria-haspopup="dialog"
259
371
  aria-expanded=${this.open ? 'true' : 'false'}
260
372
  aria-controls=${this.popupId}
261
- ?disabled=${this.disabled || this.readonly}
373
+ ?disabled=${this.effectiveDisabled || this.readonly}
262
374
  @click=${() => (this.open ? this.hide() : this.show())}
263
375
  >
264
376
  <span part="expand-icon" aria-hidden="true">${calendarIcon()}</span>
@@ -273,7 +385,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
273
385
  .max=${this.max}
274
386
  .months=${this.months}
275
387
  .locale=${this.locale}
276
- .disabled=${this.disabled}
388
+ .disabled=${this.effectiveDisabled}
277
389
  .readonly=${this.readonly}
278
390
  .disablePast=${this.disablePast}
279
391
  .disableFuture=${this.disableFuture}
@@ -284,10 +396,10 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
284
396
  @change=${this.onPickerChange}
285
397
  ></lyra-date-picker>
286
398
  </div>
287
- <div part="error" ?hidden=${!hasError}>
399
+ <div id="date-input-error" part="error" ?hidden=${!hasError}>
288
400
  ${this.errorText}<slot name="error" @slotchange=${this.onErrorSlotChange}></slot>
289
401
  </div>
290
- <div part="hint" ?hidden=${!hasHint}>
402
+ <div id="date-input-hint" part="hint" ?hidden=${!hasHint}>
291
403
  ${this.hint}<slot name="hint" @slotchange=${this.onHintSlotChange}></slot>
292
404
  </div>
293
405
  </div>
@@ -43,6 +43,13 @@ export declare class LyraDatePicker extends LyraElement {
43
43
  protected willUpdate(changed: PropertyValues): void;
44
44
  private get fdow();
45
45
  private isDisabled;
46
+ /** The first enabled day at/after the first visible month's start, scanning
47
+ * forward across all visible months -- used as the sole focusable day when
48
+ * there's no selection and no prior keyboard focus, so the empty-grid case
49
+ * never defaults to a possibly-disabled day 1. Returns null if every
50
+ * visible day is disabled (pathological but possible with a very narrow
51
+ * min/max window). */
52
+ private firstEnabledDate;
46
53
  private commit;
47
54
  private selectDate;
48
55
  /** Clear the selection and emit input + change. */
@@ -50,32 +50,54 @@ export class LyraDatePicker extends LyraElement {
50
50
  // `selection.from`'s month -- only an externally-set `value` should do that.
51
51
  this.suppressViewSync = false;
52
52
  this.onGridKey = (e) => {
53
- const current = this.focusedDate ?? this.selection.from ?? new Date();
53
+ const min = parseISO(this.min);
54
+ const max = parseISO(this.max);
55
+ const today = new Date();
56
+ today.setHours(0, 0, 0, 0);
57
+ const current = this.focusedDate ??
58
+ this.selection.from ??
59
+ this.firstEnabledDate(min, max, today) ??
60
+ new Date(this.viewDate.getFullYear(), this.viewDate.getMonth(), 1);
61
+ const step = (base, days) => new Date(base.getFullYear(), base.getMonth(), base.getDate() + days);
62
+ // Walks in the requested direction until an enabled day is found, bounded
63
+ // by a step cap (366 days -- more than a year) so an all-disabled range
64
+ // (e.g. min/max entirely in the past with disable-past set) can't loop
65
+ // forever; returns null rather than landing focus on a disabled cell,
66
+ // whose `.focus()` would be a silent no-op anyway.
67
+ const firstEnabledFrom = (base, dayStep) => {
68
+ let d = base;
69
+ for (let i = 0; i < 366; i++) {
70
+ if (!this.isDisabled(d, min, max, today))
71
+ return d;
72
+ d = step(d, dayStep);
73
+ }
74
+ return null;
75
+ };
54
76
  let next = null;
55
77
  switch (e.key) {
56
78
  case 'ArrowLeft':
57
- next = new Date(current.getFullYear(), current.getMonth(), current.getDate() - 1);
79
+ next = firstEnabledFrom(step(current, -1), -1);
58
80
  break;
59
81
  case 'ArrowRight':
60
- next = new Date(current.getFullYear(), current.getMonth(), current.getDate() + 1);
82
+ next = firstEnabledFrom(step(current, 1), 1);
61
83
  break;
62
84
  case 'ArrowUp':
63
- next = new Date(current.getFullYear(), current.getMonth(), current.getDate() - 7);
85
+ next = firstEnabledFrom(step(current, -7), -1);
64
86
  break;
65
87
  case 'ArrowDown':
66
- next = new Date(current.getFullYear(), current.getMonth(), current.getDate() + 7);
88
+ next = firstEnabledFrom(step(current, 7), 1);
67
89
  break;
68
90
  case 'PageUp':
69
- next = new Date(current.getFullYear(), current.getMonth() - 1, current.getDate());
91
+ next = firstEnabledFrom(new Date(current.getFullYear(), current.getMonth() - 1, current.getDate()), 1);
70
92
  break;
71
93
  case 'PageDown':
72
- next = new Date(current.getFullYear(), current.getMonth() + 1, current.getDate());
94
+ next = firstEnabledFrom(new Date(current.getFullYear(), current.getMonth() + 1, current.getDate()), 1);
73
95
  break;
74
96
  case 'Home':
75
- next = new Date(current.getFullYear(), current.getMonth(), 1);
97
+ next = firstEnabledFrom(new Date(current.getFullYear(), current.getMonth(), 1), 1);
76
98
  break;
77
99
  case 'End':
78
- next = new Date(current.getFullYear(), current.getMonth() + 1, 0);
100
+ next = firstEnabledFrom(new Date(current.getFullYear(), current.getMonth() + 1, 0), -1);
79
101
  break;
80
102
  case 'Enter':
81
103
  case ' ':
@@ -86,6 +108,8 @@ export class LyraDatePicker extends LyraElement {
86
108
  return;
87
109
  }
88
110
  e.preventDefault();
111
+ if (!next)
112
+ return;
89
113
  this.focusedDate = next;
90
114
  this.viewDate = this.viewDateForFocus(next);
91
115
  this.focusPending = true;
@@ -127,6 +151,23 @@ export class LyraDatePicker extends LyraElement {
127
151
  return true;
128
152
  return false;
129
153
  }
154
+ /** The first enabled day at/after the first visible month's start, scanning
155
+ * forward across all visible months -- used as the sole focusable day when
156
+ * there's no selection and no prior keyboard focus, so the empty-grid case
157
+ * never defaults to a possibly-disabled day 1. Returns null if every
158
+ * visible day is disabled (pathological but possible with a very narrow
159
+ * min/max window). */
160
+ firstEnabledDate(min, max, today) {
161
+ let d = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth(), 1);
162
+ const lastVisibleMonth = addMonths(d, this.months - 1);
163
+ const boundEnd = new Date(lastVisibleMonth.getFullYear(), lastVisibleMonth.getMonth() + 1, 0);
164
+ for (let i = 0; i < 732 && d <= boundEnd; i++) {
165
+ if (!this.isDisabled(d, min, max, today))
166
+ return d;
167
+ d = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1);
168
+ }
169
+ return null;
170
+ }
130
171
  commit(from, to, fire) {
131
172
  const next = this.mode === 'range'
132
173
  ? from && to
@@ -217,7 +258,7 @@ export class LyraDatePicker extends LyraElement {
217
258
  cell?.focus();
218
259
  }
219
260
  }
220
- renderDay(date, shownMonth, selection, min, max, today, rowHasVisibleDay) {
261
+ renderDay(date, shownMonth, selection, min, max, today, rowHasVisibleDay, fallbackFocusDate) {
221
262
  const outside = date.getMonth() !== shownMonth;
222
263
  // Outside-month days are empty placeholders by default (matches WA), keeping the
223
264
  // 6-row grid aligned without low-contrast faded numbers.
@@ -240,7 +281,19 @@ export class LyraDatePicker extends LyraElement {
240
281
  const isEnd = to && isSameDay(date, to);
241
282
  const selected = this.mode === 'single' ? isStart : isStart || isEnd;
242
283
  const inRange = this.mode === 'range' && from && to && date > from && date < to;
243
- const focused = this.focusedDate && isSameDay(date, this.focusedDate) ? true : !this.focusedDate && isStart;
284
+ // Falls back to the first *enabled* day across the visible month(s) (see
285
+ // firstEnabledDate()) when there's neither a focusedDate nor a selection,
286
+ // so the grid always has exactly one tabbable cell -- otherwise every
287
+ // cell computes `false` and keyboard users tabbing into an empty picker
288
+ // land nowhere. Must not just be "day 1 of the shown month" unconditionally:
289
+ // day 1 can itself be disabled (e.g. disable-past opened on any day other
290
+ // than the 1st), which would land the sole tabindex="0" cell on a button
291
+ // that can never actually receive focus.
292
+ const focused = this.focusedDate != null
293
+ ? isSameDay(date, this.focusedDate)
294
+ : isStart
295
+ ? true
296
+ : !from && fallbackFocusDate != null && isSameDay(date, fallbackFocusDate);
244
297
  const parts = ['day'];
245
298
  if (outside)
246
299
  parts.push('day-outside');
@@ -272,7 +325,7 @@ export class LyraDatePicker extends LyraElement {
272
325
  ${date.getDate()}
273
326
  </button>`;
274
327
  }
275
- renderMonth(offset, selection, min, max, today, fdow, labels) {
328
+ renderMonth(offset, selection, min, max, today, fdow, labels, fallbackFocusDate) {
276
329
  const base = addMonths(this.viewDate, offset);
277
330
  const year = base.getFullYear();
278
331
  const month = base.getMonth();
@@ -282,13 +335,25 @@ export class LyraDatePicker extends LyraElement {
282
335
  return html `<div part="month">
283
336
  <div part="header">
284
337
  ${isFirst
285
- ? html `<button part="previous" type="button" aria-label="Previous month" @click=${() => this.nav(-1)}>
338
+ ? html `<button
339
+ part="previous"
340
+ type="button"
341
+ aria-label="Previous month"
342
+ ?disabled=${this.disabled || this.readonly}
343
+ @click=${() => this.nav(-1)}
344
+ >
286
345
  ${chevronIcon()}
287
346
  </button>`
288
347
  : html `<span></span>`}
289
348
  <div part="title">${monthTitle(year, month, this.locale)}</div>
290
349
  ${isLast
291
- ? html `<button part="next" type="button" aria-label="Next month" @click=${() => this.nav(1)}>
350
+ ? html `<button
351
+ part="next"
352
+ type="button"
353
+ aria-label="Next month"
354
+ ?disabled=${this.disabled || this.readonly}
355
+ @click=${() => this.nav(1)}
356
+ >
292
357
  ${chevronIcon()}
293
358
  </button>`
294
359
  : html `<span></span>`}
@@ -297,7 +362,7 @@ export class LyraDatePicker extends LyraElement {
297
362
  <div part="grid" role="grid" @keydown=${this.onGridKey}>
298
363
  ${matrix.map((week) => {
299
364
  const rowHasVisibleDay = week.some((d) => d.getMonth() === month);
300
- return html `<div part="week" role="row">${week.map((d) => this.renderDay(d, month, selection, min, max, today, rowHasVisibleDay))}</div>`;
365
+ return html `<div part="week" role="row">${week.map((d) => this.renderDay(d, month, selection, min, max, today, rowHasVisibleDay, fallbackFocusDate))}</div>`;
301
366
  })}
302
367
  </div>
303
368
  </div>`;
@@ -310,9 +375,13 @@ export class LyraDatePicker extends LyraElement {
310
375
  today.setHours(0, 0, 0, 0);
311
376
  const fdow = this.fdow;
312
377
  const labels = weekdayLabels(fdow, this.weekdayFormat, this.locale);
378
+ // Only bother scanning for a fallback focus day when it's actually
379
+ // needed: there's no point walking the visible days if a focusedDate or
380
+ // an existing selection already determines the sole tabbable cell.
381
+ const fallbackFocusDate = this.focusedDate || selection.from ? null : this.firstEnabledDate(min, max, today);
313
382
  const monthEls = [];
314
383
  for (let i = 0; i < this.months; i++) {
315
- monthEls.push(this.renderMonth(i, selection, min, max, today, fdow, labels));
384
+ monthEls.push(this.renderMonth(i, selection, min, max, today, fdow, labels, fallbackFocusDate));
316
385
  }
317
386
  return html `<div part="base">${monthEls}</div>`;
318
387
  }