@adia-ai/web-components 0.8.39 → 0.8.41

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 (424) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/MIGRATION.md +1269 -3
  3. package/README.md +25 -30
  4. package/USAGE.md +26 -0
  5. package/bin/audit-template-children.mjs +556 -0
  6. package/components/accordion/accordion-item.a2ui.json +4 -0
  7. package/components/accordion/accordion-item.yaml +2 -0
  8. package/components/accordion/accordion.a2ui.json +4 -0
  9. package/components/accordion/accordion.yaml +2 -0
  10. package/components/action-list/action-item.a2ui.json +24 -2
  11. package/components/action-list/action-item.yaml +28 -3
  12. package/components/action-list/action-list.a2ui.json +6 -1
  13. package/components/action-list/action-list.class.js +52 -11
  14. package/components/action-list/action-list.css +43 -3
  15. package/components/action-list/action-list.d.ts +4 -2
  16. package/components/action-list/action-list.yaml +5 -0
  17. package/components/adia-mark/adia-mark.a2ui.json +1 -0
  18. package/components/adia-wordmark/adia-wordmark.a2ui.json +1 -0
  19. package/components/agent-artifact/agent-artifact.a2ui.json +1 -0
  20. package/components/agent-artifact/agent-artifact.css +1 -1
  21. package/components/agent-feedback-bar/agent-feedback-bar.a2ui.json +6 -5
  22. package/components/agent-feedback-bar/agent-feedback-bar.yaml +6 -2
  23. package/components/agent-questions/agent-questions.a2ui.json +1 -0
  24. package/components/agent-reasoning/agent-reasoning.a2ui.json +6 -5
  25. package/components/agent-reasoning/agent-reasoning.css +2 -2
  26. package/components/agent-reasoning/agent-reasoning.yaml +6 -2
  27. package/components/agent-suggestions/agent-suggestions.a2ui.json +11 -2
  28. package/components/agent-suggestions/agent-suggestions.class.js +2 -1
  29. package/components/agent-suggestions/agent-suggestions.d.ts +4 -4
  30. package/components/agent-suggestions/agent-suggestions.yaml +4 -2
  31. package/components/agent-trace/agent-trace.a2ui.json +6 -5
  32. package/components/agent-trace/agent-trace.css +3 -3
  33. package/components/agent-trace/agent-trace.yaml +6 -2
  34. package/components/alert/alert.a2ui.json +1 -0
  35. package/components/alert/alert.css +2 -2
  36. package/components/anchor-bar/anchor-bar.a2ui.json +1 -0
  37. package/components/aside/aside.a2ui.json +1 -0
  38. package/components/avatar/avatar-group.a2ui.json +7 -1
  39. package/components/avatar/avatar-group.yaml +1 -0
  40. package/components/avatar/avatar.a2ui.json +12 -11
  41. package/components/avatar/avatar.css +1 -1
  42. package/components/avatar/avatar.d.ts +1 -1
  43. package/components/avatar/avatar.yaml +4 -4
  44. package/components/badge/badge.a2ui.json +1 -0
  45. package/components/badge/badge.css +1 -1
  46. package/components/block/block.a2ui.json +1 -0
  47. package/components/blockquote/blockquote.a2ui.json +1 -0
  48. package/components/breadcrumb/breadcrumb.a2ui.json +1 -0
  49. package/components/button/button.a2ui.json +1 -3
  50. package/components/button/button.css +6 -5
  51. package/components/button/button.yaml +1 -3
  52. package/components/calendar-grid/calendar-grid.a2ui.json +1 -0
  53. package/components/calendar-grid/calendar-grid.class.js +2 -1
  54. package/components/calendar-picker/calendar-picker.a2ui.json +143 -24
  55. package/components/calendar-picker/calendar-picker.class.js +490 -197
  56. package/components/calendar-picker/calendar-picker.css +121 -172
  57. package/components/calendar-picker/calendar-picker.yaml +225 -47
  58. package/components/canvas/canvas.a2ui.json +6 -5
  59. package/components/canvas/canvas.d.ts +37 -0
  60. package/components/canvas/canvas.js +72 -3
  61. package/components/canvas/canvas.yaml +6 -2
  62. package/components/card/card.a2ui.json +25 -2
  63. package/components/card/card.class.js +3 -0
  64. package/components/card/card.css +34 -4
  65. package/components/card/card.d.ts +2 -2
  66. package/components/card/card.examples.md +3 -3
  67. package/components/card/card.yaml +28 -4
  68. package/components/chart/chart.a2ui.json +13 -8
  69. package/components/chart/chart.class.js +10 -10
  70. package/components/chart/chart.css +12 -10
  71. package/components/chart/chart.d.ts +2 -2
  72. package/components/chart/chart.yaml +8 -5
  73. package/components/chart-legend/chart-legend.a2ui.json +1 -0
  74. package/components/chat-thread/chat-input.a2ui.json +1 -0
  75. package/components/chat-thread/chat-thread.a2ui.json +10 -9
  76. package/components/chat-thread/chat-thread.css +1 -1
  77. package/components/chat-thread/chat-thread.yaml +13 -20
  78. package/components/check/check.a2ui.json +10 -9
  79. package/components/check/check.yaml +4 -4
  80. package/components/code/code.a2ui.json +1 -0
  81. package/components/code/code.css +6 -6
  82. package/components/col/col.a2ui.json +1 -0
  83. package/components/color-input/color-input.a2ui.json +1 -0
  84. package/components/color-picker/color-picker.a2ui.json +1 -0
  85. package/components/combobox/combobox.a2ui.json +3 -2
  86. package/components/combobox/combobox.css +2 -2
  87. package/components/combobox/combobox.yaml +2 -2
  88. package/components/command/command.a2ui.json +12 -10
  89. package/components/command/command.class.js +3 -2
  90. package/components/command/command.css +3 -3
  91. package/components/command/command.yaml +17 -4
  92. package/components/context-menu/context-menu.a2ui.json +1 -0
  93. package/components/date-range-picker/date-range-picker.a2ui.json +2 -1
  94. package/components/date-range-picker/date-range-picker.css +1 -1
  95. package/components/date-range-picker/date-range-picker.yaml +1 -1
  96. package/components/datetime-picker/datetime-picker.a2ui.json +15 -108
  97. package/components/datetime-picker/datetime-picker.class.js +37 -690
  98. package/components/datetime-picker/datetime-picker.css +11 -183
  99. package/components/datetime-picker/datetime-picker.d.ts +4 -5
  100. package/components/datetime-picker/datetime-picker.yaml +44 -135
  101. package/components/demo-toggle/demo-toggle.a2ui.json +1 -0
  102. package/components/demo-toggle/demo-toggle.css +1 -1
  103. package/components/description-list/description-list.a2ui.json +1 -0
  104. package/components/description-list/description-list.css +2 -2
  105. package/components/display-field/display-field.a2ui.json +1 -0
  106. package/components/display-field/display-field.css +3 -3
  107. package/components/divider/divider.a2ui.json +1 -0
  108. package/components/divider/divider.css +1 -1
  109. package/components/drawer/drawer.a2ui.json +1 -3
  110. package/components/drawer/drawer.css +3 -3
  111. package/components/drawer/drawer.yaml +0 -2
  112. package/components/drilldown/drilldown.a2ui.json +22 -20
  113. package/components/drilldown/drilldown.class.js +4 -3
  114. package/components/drilldown/drilldown.css +8 -8
  115. package/components/drilldown/drilldown.yaml +40 -9
  116. package/components/embed/embed.a2ui.json +6 -5
  117. package/components/embed/embed.yaml +2 -2
  118. package/components/empty-state/empty-state.a2ui.json +1 -0
  119. package/components/empty-state/empty-state.css +2 -2
  120. package/components/feed/feed-item.a2ui.json +25 -5
  121. package/components/feed/feed-item.yaml +28 -7
  122. package/components/feed/feed.a2ui.json +9 -2
  123. package/components/feed/feed.class.js +19 -12
  124. package/components/feed/feed.css +8 -8
  125. package/components/feed/feed.yaml +14 -1
  126. package/components/field/field.a2ui.json +1 -0
  127. package/components/field/field.css +1 -1
  128. package/components/field/field.examples.md +1 -1
  129. package/components/fields/fields.a2ui.json +1 -0
  130. package/components/fields/fields.examples.md +1 -1
  131. package/components/footer/footer.a2ui.json +1 -0
  132. package/components/frame/frame.a2ui.json +1 -0
  133. package/components/grid/grid.a2ui.json +1 -0
  134. package/components/header/header.a2ui.json +3 -2
  135. package/components/header/header.yaml +3 -3
  136. package/components/heatmap/heatmap.a2ui.json +2 -1
  137. package/components/heatmap/heatmap.class.js +1 -1
  138. package/components/heatmap/heatmap.css +6 -6
  139. package/components/heatmap/heatmap.yaml +1 -1
  140. package/components/icon/icon.a2ui.json +1 -0
  141. package/components/image/image.a2ui.json +28 -14
  142. package/components/image/image.class.js +38 -23
  143. package/components/image/image.css +18 -8
  144. package/components/image/image.d.ts +2 -2
  145. package/components/image/image.examples.md +3 -3
  146. package/components/image/image.yaml +23 -7
  147. package/components/inline-edit/inline-edit.a2ui.json +1 -0
  148. package/components/inline-message/inline-message.a2ui.json +3 -1
  149. package/components/inline-message/inline-message.class.js +3 -1
  150. package/components/inline-message/inline-message.css +1 -1
  151. package/components/inline-message/inline-message.d.ts +2 -2
  152. package/components/inline-message/inline-message.yaml +2 -1
  153. package/components/input/input.a2ui.json +66 -16
  154. package/components/input/input.class.js +64 -7
  155. package/components/input/input.css +19 -18
  156. package/components/input/input.d.ts +9 -4
  157. package/components/input/input.yaml +83 -10
  158. package/components/inspector/inspector.a2ui.json +1 -0
  159. package/components/integration-card/integration-card.a2ui.json +1 -0
  160. package/components/integration-card/integration-card.css +4 -4
  161. package/components/kbd/kbd.a2ui.json +1 -0
  162. package/components/kbd/kbd.css +1 -1
  163. package/components/link/link.a2ui.json +2 -1
  164. package/components/link/link.yaml +1 -1
  165. package/components/list/list-item.a2ui.json +9 -0
  166. package/components/list/list-item.yaml +12 -0
  167. package/components/list/list.a2ui.json +9 -3
  168. package/components/list/list.class.js +14 -1
  169. package/components/list/list.css +14 -3
  170. package/components/list/list.d.ts +7 -5
  171. package/components/list/list.yaml +16 -6
  172. package/components/list-window/list-window.a2ui.json +1 -0
  173. package/components/list-window/list-window.class.js +2 -1
  174. package/components/loading-overlay/loading-overlay.a2ui.json +1 -0
  175. package/components/mark/mark.a2ui.json +2 -1
  176. package/components/mark/mark.yaml +1 -1
  177. package/components/menu/menu-divider.a2ui.json +1 -0
  178. package/components/menu/menu-item.a2ui.json +3 -1
  179. package/components/menu/menu-item.yaml +6 -0
  180. package/components/menu/menu-label.a2ui.json +1 -0
  181. package/components/menu/menu.a2ui.json +3 -2
  182. package/components/menu/menu.class.js +2 -2
  183. package/components/menu/menu.d.ts +2 -2
  184. package/components/menu/menu.yaml +3 -3
  185. package/components/modal/modal.a2ui.json +1 -0
  186. package/components/nav/nav.a2ui.json +1 -0
  187. package/components/nav/nav.class.js +14 -3
  188. package/components/nav/nav.css +2 -2
  189. package/components/nav-group/nav-group.a2ui.json +1 -0
  190. package/components/nav-group/nav-group.class.js +3 -1
  191. package/components/nav-group/nav-group.css +3 -3
  192. package/components/nav-item/nav-item.a2ui.json +1 -0
  193. package/components/nav-item/nav-item.css +2 -2
  194. package/components/noodles/noodles.a2ui.json +1 -0
  195. package/components/noodles/noodles.class.js +5 -1
  196. package/components/number-format/number-format.a2ui.json +1 -0
  197. package/components/option-card/option-card.a2ui.json +1 -0
  198. package/components/option-card/option-card.css +1 -1
  199. package/components/otp-input/otp-input.a2ui.json +3 -2
  200. package/components/otp-input/otp-input.css +1 -1
  201. package/components/otp-input/otp-input.yaml +9 -2
  202. package/components/page/page.a2ui.json +13 -0
  203. package/components/page/page.css +2 -2
  204. package/components/page/page.d.ts +1 -1
  205. package/components/page/page.yaml +1 -0
  206. package/components/pagination/pagination.a2ui.json +1 -0
  207. package/components/pagination/pagination.class.js +3 -2
  208. package/components/pagination/pagination.css +1 -1
  209. package/components/pane/pane.a2ui.json +6 -0
  210. package/components/pane/pane.class.js +3 -3
  211. package/components/pane/pane.css +3 -3
  212. package/components/pane/pane.yaml +3 -0
  213. package/components/password-strength/password-strength.a2ui.json +1 -0
  214. package/components/pipeline-status/pipeline-status.a2ui.json +1 -0
  215. package/components/popover/popover.a2ui.json +7 -6
  216. package/components/popover/popover.class.js +2 -2
  217. package/components/popover/popover.d.ts +2 -2
  218. package/components/popover/popover.yaml +4 -4
  219. package/components/preview/preview.a2ui.json +1 -0
  220. package/components/preview/preview.class.js +5 -1
  221. package/components/progress/progress.a2ui.json +47 -24
  222. package/components/progress/progress.class.js +66 -11
  223. package/components/progress/progress.css +44 -26
  224. package/components/progress/progress.d.ts +7 -5
  225. package/components/progress/progress.examples.md +2 -2
  226. package/components/progress/progress.yaml +235 -53
  227. package/components/progress-row/progress-row.a2ui.json +16 -12
  228. package/components/progress-row/progress-row.css +2 -2
  229. package/components/progress-row/progress-row.d.ts +2 -2
  230. package/components/progress-row/progress-row.examples.md +2 -14
  231. package/components/progress-row/progress-row.yaml +28 -36
  232. package/components/qr-code/qr-code.a2ui.json +13 -12
  233. package/components/qr-code/qr-code.class.js +7 -7
  234. package/components/qr-code/qr-code.css +1 -1
  235. package/components/qr-code/qr-code.d.ts +10 -7
  236. package/components/qr-code/qr-code.yaml +18 -12
  237. package/components/radio/radio.a2ui.json +8 -7
  238. package/components/radio/radio.class.js +8 -0
  239. package/components/radio/radio.yaml +3 -3
  240. package/components/radio-group/radio-group.a2ui.json +5 -3
  241. package/components/radio-group/radio-group.class.js +11 -0
  242. package/components/radio-group/radio-group.yaml +7 -1
  243. package/components/range/range.a2ui.json +6 -0
  244. package/components/range/range.class.js +2 -2
  245. package/components/range/range.css +3 -3
  246. package/components/range/range.yaml +3 -0
  247. package/components/rating/rating.a2ui.json +1 -0
  248. package/components/rating/rating.css +1 -1
  249. package/components/relative-time/relative-time.a2ui.json +1 -0
  250. package/components/richtext/richtext.a2ui.json +2 -1
  251. package/components/richtext/richtext.css +3 -3
  252. package/components/richtext/richtext.yaml +1 -1
  253. package/components/row/row.a2ui.json +1 -0
  254. package/components/search/search.a2ui.json +1 -0
  255. package/components/search/search.class.js +4 -1
  256. package/components/section/section.a2ui.json +1 -0
  257. package/components/segment/segment.a2ui.json +21 -10
  258. package/components/segment/segment.css +20 -4
  259. package/components/segment/segment.d.ts +2 -1
  260. package/components/segment/segment.yaml +20 -13
  261. package/components/segmented/segmented.a2ui.json +36 -7
  262. package/components/segmented/segmented.class.js +113 -22
  263. package/components/segmented/segmented.css +3 -0
  264. package/components/segmented/segmented.d.ts +3 -1
  265. package/components/segmented/segmented.yaml +80 -8
  266. package/components/select/select.a2ui.json +18 -13
  267. package/components/select/select.class.js +61 -37
  268. package/components/select/select.css +18 -16
  269. package/components/select/select.yaml +18 -7
  270. package/components/skeleton/skeleton.a2ui.json +8 -7
  271. package/components/skeleton/skeleton.class.js +8 -7
  272. package/components/skeleton/skeleton.css +21 -6
  273. package/components/skeleton/skeleton.d.ts +2 -2
  274. package/components/skeleton/skeleton.examples.md +2 -2
  275. package/components/skeleton/skeleton.yaml +3 -2
  276. package/components/skip-nav/skip-nav.a2ui.json +1 -0
  277. package/components/slider/slider.a2ui.json +6 -0
  278. package/components/slider/slider.class.js +20 -3
  279. package/components/slider/slider.css +2 -2
  280. package/components/slider/slider.yaml +3 -0
  281. package/components/spinner/spinner.a2ui.json +1 -0
  282. package/components/spinner/spinner.css +1 -1
  283. package/components/stack/stack.a2ui.json +1 -0
  284. package/components/stat/stat.a2ui.json +1 -0
  285. package/components/stat/stat.css +3 -3
  286. package/components/stat/stat.js +2 -2
  287. package/components/step-progress/step-progress.a2ui.json +1 -0
  288. package/components/step-progress/step-progress.css +1 -1
  289. package/components/step-progress/step-progress.yaml +1 -1
  290. package/components/stepper/stepper-item.a2ui.json +7 -3
  291. package/components/stepper/stepper-item.yaml +15 -5
  292. package/components/stepper/stepper.a2ui.json +5 -1
  293. package/components/stepper/stepper.class.js +31 -16
  294. package/components/stepper/stepper.css +2 -2
  295. package/components/stepper/stepper.d.ts +1 -1
  296. package/components/stepper/stepper.yaml +4 -2
  297. package/components/stream/stream.a2ui.json +1 -0
  298. package/components/swatch/swatch.a2ui.json +1 -0
  299. package/components/swatch/swatch.css +2 -2
  300. package/components/swiper/swiper.a2ui.json +1 -0
  301. package/components/swiper/swiper.class.js +5 -1
  302. package/components/switch/switch.a2ui.json +12 -11
  303. package/components/switch/switch.css +1 -1
  304. package/components/switch/switch.yaml +6 -6
  305. package/components/table/table.a2ui.json +1 -10
  306. package/components/table/table.class.js +18 -16
  307. package/components/table/table.css +17 -18
  308. package/components/table/table.d.ts +0 -2
  309. package/components/table/table.yaml +0 -9
  310. package/components/table-toolbar/table-toolbar.a2ui.json +1 -0
  311. package/components/table-toolbar/table-toolbar.class.js +38 -19
  312. package/components/table-toolbar/table-toolbar.css +2 -2
  313. package/components/tabs/tab.a2ui.json +9 -0
  314. package/components/tabs/tab.js +4 -0
  315. package/components/tabs/tab.yaml +15 -0
  316. package/components/tabs/tabs.a2ui.json +9 -4
  317. package/components/tabs/tabs.class.js +13 -1
  318. package/components/tabs/tabs.css +9 -1
  319. package/components/tabs/tabs.d.ts +2 -0
  320. package/components/tabs/tabs.yaml +9 -2
  321. package/components/tag/tag.a2ui.json +1 -0
  322. package/components/tag/tag.css +2 -2
  323. package/components/tags-input/tags-input.a2ui.json +22 -17
  324. package/components/tags-input/tags-input.class.js +13 -11
  325. package/components/tags-input/tags-input.css +11 -7
  326. package/components/tags-input/tags-input.d.ts +16 -7
  327. package/components/tags-input/tags-input.examples.md +2 -2
  328. package/components/tags-input/tags-input.yaml +25 -8
  329. package/components/text/text.a2ui.json +6 -5
  330. package/components/text/text.css +10 -5
  331. package/components/text/text.d.ts +2 -2
  332. package/components/text/text.yaml +9 -4
  333. package/components/textarea/textarea.a2ui.json +2 -1
  334. package/components/textarea/textarea.css +1 -1
  335. package/components/textarea/textarea.yaml +1 -1
  336. package/components/theme-provider/theme-provider.a2ui.json +2 -1
  337. package/components/theme-provider/theme-provider.d.ts +5 -3
  338. package/components/theme-provider/theme-provider.yaml +5 -3
  339. package/components/time-picker/time-picker.a2ui.json +1 -0
  340. package/components/time-picker/time-picker.class.js +4 -2
  341. package/components/time-picker/time-picker.css +1 -1
  342. package/components/timeline/timeline-item.a2ui.json +26 -3
  343. package/components/timeline/timeline-item.yaml +22 -7
  344. package/components/timeline/timeline.a2ui.json +4 -0
  345. package/components/timeline/timeline.class.js +29 -18
  346. package/components/timeline/timeline.css +4 -4
  347. package/components/timeline/timeline.d.ts +3 -3
  348. package/components/timeline/timeline.yaml +2 -0
  349. package/components/toast/toast.a2ui.json +10 -3
  350. package/components/toast/toast.yaml +20 -13
  351. package/components/toc/toc.a2ui.json +1 -0
  352. package/components/toc/toc.css +1 -1
  353. package/components/toggle-group/toggle-group.a2ui.json +7 -3
  354. package/components/toggle-group/toggle-group.class.js +43 -12
  355. package/components/toggle-group/toggle-group.css +7 -4
  356. package/components/toggle-group/toggle-group.d.ts +3 -4
  357. package/components/toggle-group/toggle-group.yaml +22 -16
  358. package/components/toggle-group/toggle-option.a2ui.json +8 -4
  359. package/components/toggle-group/toggle-option.yaml +15 -13
  360. package/components/toggle-scheme/toggle-scheme.a2ui.json +2 -1
  361. package/components/toggle-scheme/toggle-scheme.yaml +1 -1
  362. package/components/toolbar/toolbar-group.a2ui.json +1 -0
  363. package/components/toolbar/toolbar.a2ui.json +1 -0
  364. package/components/toolbar/toolbar.class.js +3 -0
  365. package/components/tooltip/tooltip.a2ui.json +1 -0
  366. package/components/tooltip/tooltip.css +1 -1
  367. package/components/tour/tour-step.a2ui.json +4 -0
  368. package/components/tour/tour-step.yaml +2 -0
  369. package/components/tour/tour.a2ui.json +4 -0
  370. package/components/tour/tour.css +2 -2
  371. package/components/tour/tour.yaml +2 -0
  372. package/components/tree/tree-item.a2ui.json +5 -0
  373. package/components/tree/tree-item.yaml +3 -0
  374. package/components/tree/tree.a2ui.json +4 -0
  375. package/components/tree/tree.class.js +2 -1
  376. package/components/tree/tree.css +1 -1
  377. package/components/tree/tree.yaml +2 -0
  378. package/components/upload/upload.a2ui.json +8 -1
  379. package/components/upload/upload.class.js +35 -5
  380. package/components/upload/upload.css +28 -0
  381. package/components/upload/upload.d.ts +16 -0
  382. package/components/upload/upload.yaml +27 -7
  383. package/components/visually-hidden/visually-hidden.a2ui.json +1 -0
  384. package/core/dom.d.ts +11 -0
  385. package/core/dom.js +34 -0
  386. package/core/form.js +7 -0
  387. package/core/index.d.ts +1 -0
  388. package/core/index.js +1 -0
  389. package/core/responsive.js +16 -4
  390. package/core/template.js +2 -1
  391. package/custom-elements.json +442 -502
  392. package/dist/host.min.css +1 -1
  393. package/dist/host.sheet.js +1 -1
  394. package/dist/theme-provider.min.js +5 -5
  395. package/dist/web-components.min.css +1 -1
  396. package/dist/web-components.min.js +91 -104
  397. package/dist/web-components.sheet.js +1 -1
  398. package/package.json +4 -2
  399. package/patterns/conversion-funnel/conversion-funnel.examples.html +15 -15
  400. package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
  401. package/patterns/inline-dialog/inline-dialog.html +4 -0
  402. package/patterns/timezone-selector/timezone-selector.examples.html +1 -1
  403. package/patterns/timezone-selector/timezone-selector.html +2 -2
  404. package/styles/api/text.css +7 -0
  405. package/styles/type/scale.css +6 -7
  406. package/traits/_catalog.json +129 -41
  407. package/traits/anchor-positioning/anchor-positioning.js +1 -0
  408. package/traits/announcer/announcer.js +1 -0
  409. package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -0
  410. package/traits/attention-pulse/attention-pulse.js +1 -0
  411. package/traits/count-up/count-up.js +1 -0
  412. package/traits/define.js +78 -0
  413. package/traits/focus-restore/focus-restore.js +2 -1
  414. package/traits/haptic-feedback/haptic-feedback.js +1 -0
  415. package/traits/input-mask/input-mask.js +1 -0
  416. package/traits/intersection-observer/intersection-observer.js +1 -0
  417. package/traits/layout-animation/layout-animation.js +1 -0
  418. package/traits/noise-texture/noise-texture.js +1 -0
  419. package/traits/resize-observer/resize-observer.js +1 -0
  420. package/traits/scroll-progress/scroll-progress.js +1 -0
  421. package/traits/snap-to-grid/snap-to-grid.js +1 -0
  422. package/traits/sound-feedback/sound-feedback.js +1 -0
  423. package/traits/spring-animate/spring-animate.js +1 -0
  424. package/traits/validation/validation.js +1 -0
@@ -12,700 +12,47 @@
12
12
  */
13
13
 
14
14
  /**
15
- * <datetime-picker-ui> — SPEC-038
16
- *
17
- * Single-control date + time selection. Composes:
18
- * <calendar-picker-ui> — the date pane (left)
19
- * <time-picker-ui> — the time pane (right; SPEC-043)
20
- * <popover-ui> — the floating panel surface (manual popover)
21
- * <button-ui> — default trigger + optional footer actions
22
- * <divider-ui> — visual separator between panes
23
- *
24
- * Form participation:
25
- * ElementInternals serializes the picked ISO 8601 datetime ("YYYY-MM-
26
- * DDTHH:mm" or "...:ss") as the form value under [name]. Per ADR-0025
27
- * we never wrap a native <input type="datetime-local">; the calendar
28
- * pane + time pane provide the affordance, ElementInternals provides
29
- * the form contract.
30
- *
31
- * Keyboard model (composed from the underlying primitives):
32
- * Enter/Space (trigger) open popover; focus the calendar grid
33
- * Escape (open) — close popover (no commit) + return focus
34
- * Tab (open) — cycle trigger → calendar → time pane → footer
35
- * Arrows (calendar) — delegated to <calendar-picker-ui> APG model
36
- * Arrows (time segment) — delegated to <time-picker-ui> Spinbutton model
37
- *
38
- * A11y:
39
- * role=combobox on host + aria-haspopup=dialog + aria-expanded.
40
- * role=dialog on the popover. Focus is trapped while open; returns to
41
- * trigger on close. The calendar pane and time pane each carry their
42
- * own inherited WAI-APG model (Date Picker Dialog + Spinbutton).
43
- *
44
- * Static parts:
45
- * trigger / popover / calPane / timePane / divider — all stamped on
46
- * first connect via this.ensure(). No HTML template (light-DOM authored
47
- * slot="trigger" or slot="footer" must survive render()).
15
+ * <datetime-picker-ui> — DEPRECATED (gh#1372, #1363 C3c).
16
+ *
17
+ * `<datetime-picker-ui>`'s full surface — trigger + popover, a
18
+ * `<calendar-grid-ui>` date pane + `<divider-ui>` + `<time-picker-ui>` time
19
+ * pane, form participation, constraint validation, the
20
+ * open/close/input/invalid event set folded into `<calendar-picker-ui>`
21
+ * behind its widened `precision` axis (`date` | `minute` | `second`).
22
+ * `<datetime-picker-ui>` is now a thin compatibility alias per ADR-0057
23
+ * (`status: deprecated`): it subclasses `UICalendarPicker` directly rather
24
+ * than re-implementing any of that behavior, and only overrides the two
25
+ * defaults that made it feel like a distinct primitive — `precision`
26
+ * defaults to `"minute"` (not `"date"`) and `placeholder` defaults to
27
+ * `"Select date and time"`.
28
+ *
29
+ * Migrate: `<datetime-picker-ui>` → `<calendar-picker-ui precision="minute">`
30
+ * (or `precision="second"` for the former second-precision mode). This tag
31
+ * keeps working it IS `<calendar-picker-ui>` under the hood — but will be
32
+ * removed in a future major; new markup should reach for
33
+ * `<calendar-picker-ui>` directly.
34
+ *
35
+ * @see ../calendar-picker/calendar-picker.class.js
48
36
  */
49
37
 
50
- import { UIFormElement } from '../../core/form.js';
51
- import { anchorPopover } from '../../core/anchor.js';
52
- import { untracked } from '../../core/signals.js';
38
+ import { UICalendarPicker } from '../calendar-picker/calendar-picker.class.js';
53
39
 
54
- const MONTHS_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
55
- const MONTHS_LONG = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
40
+ export class UIDatetimePicker extends UICalendarPicker {
41
+ // static labelDeprecated is inherited unchanged from UICalendarPicker
42
+ // this subclass adds no authored [label] attribute handling of its own.
56
43
 
57
- function pad2(n) { return String(n).padStart(2, '0'); }
58
-
59
- /** Parse "YYYY-MM-DDTHH:mm" or "YYYY-MM-DDTHH:mm:ss" {date, time} or null. */
60
- function parseDatetime(str) {
61
- if (!str) return null;
62
- const s = String(str).trim();
63
- // Tolerate space separator as a Generative-UI quality-of-life affordance;
64
- // canonical wire form is always "T".
65
- const m = /^(\d{4}-\d{2}-\d{2})[T ](\d{1,2}:\d{2}(?::\d{2})?)$/.exec(s);
66
- if (!m) return null;
67
- return { date: m[1], time: m[2] };
68
- }
69
-
70
- /** Compose canonical "YYYY-MM-DDTHH:mm[:ss]" from a {date, time} pair. */
71
- function formatDatetime(date, time, precision) {
72
- if (!date || !time) return '';
73
- // Coerce missing seconds when precision="second"; strip seconds otherwise.
74
- const tm = /^(\d{1,2}):(\d{2})(?::(\d{2}))?$/.exec(time);
75
- if (!tm) return '';
76
- const hh = pad2(Number(tm[1]));
77
- const mm = tm[2];
78
- const ss = tm[3] || '00';
79
- return precision === 'second' ? `${date}T${hh}:${mm}:${ss}` : `${date}T${hh}:${mm}`;
80
- }
44
+ // Same value as UICalendarPicker's — the alias stamps no icon of its own —
45
+ // but restated literally because scripts/release/check-required-icons.mjs
46
+ // text-scans each component's own class.js and cannot resolve inheritance
47
+ // (gh#1503).
48
+ static requiredIcons = ['calendar'];
81
49
 
82
- function formatDatePart(iso, fmt) {
83
- const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso || '');
84
- if (!m) return '';
85
- const y = +m[1];
86
- const mo = +m[2] - 1;
87
- const d = +m[3];
88
- switch (fmt) {
89
- case 'long': return `${MONTHS_LONG[mo]} ${d}, ${y}`;
90
- case 'iso': return iso;
91
- case 'short':
92
- default: return `${MONTHS_SHORT[mo]} ${d}, ${y}`;
93
- }
94
- }
95
-
96
- function formatTimePart(time, precision, hourcycle) {
97
- const m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?$/.exec(time || '');
98
- if (!m) return '';
99
- let h = +m[1];
100
- const mm = m[2];
101
- const ss = m[3];
102
- // Display in the requested cycle (h12 derives display from 24h storage).
103
- let hStr;
104
- let meridiem = '';
105
- if (hourcycle === 'h12') {
106
- const isPM = h >= 12;
107
- const dispH = (h % 12) || 12;
108
- hStr = pad2(dispH);
109
- meridiem = isPM ? ' PM' : ' AM';
110
- } else {
111
- hStr = pad2(h);
112
- }
113
- const sec = (precision === 'second' && ss != null) ? `:${ss}` : '';
114
- return `${hStr}:${mm}${sec}${meridiem}`;
115
- }
116
-
117
- export class UIDatetimePicker extends UIFormElement {
118
- // Per AGENTS.md / FEEDBACK guidance: label is NOT first-class here —
119
- // use <field-ui label> wrap. Mark deprecated so the base warns once.
120
- static labelDeprecated = true;
121
-
122
- // §154: Phosphor icons this primitive auto-stamps (without consumer
123
- // markup). Aggregated by installIconLoadersForRegistered() across all
124
- // defined elements. Audited by check-required-icons.mjs.
125
- static requiredIcons = ['calendar', 'caret-down'];
126
-
127
- static get properties() {
128
- return {
129
- ...UIFormElement.properties,
130
- min: { type: String, default: '', reflect: true },
131
- max: { type: String, default: '', reflect: true },
132
- step: { type: Number, default: 60, reflect: true },
133
- precision: { type: String, default: 'minute', reflect: true },
134
- hourcycle: { type: String, default: '', reflect: true, attribute: 'hour-cycle' },
135
- open: { type: Boolean, default: false, reflect: true },
136
- placeholder: { type: String, default: 'Select date and time', reflect: false },
137
- format: { type: String, default: 'short', reflect: true },
138
- locale: { type: String, default: '', reflect: false },
139
- // Popover placement — yaml documented reflect:true since v1.
140
- placement: { type: String, default: 'bottom', reflect: true },
141
- };
142
- }
143
-
144
- // Static parts — fixed structural skeleton stamped on first connect.
145
- // The popover is a `popover="manual"` element so we control
146
- // showPopover() / hidePopover() programmatically. Mirrors
147
- // date-range-picker's shape (the closest sibling).
148
- static parts = {
149
- trigger: '<button-ui slot="trigger" variant="outline" type="button" icon="calendar" icon-trailing="caret-down" aria-label="Open date and time picker"></button-ui>',
150
- popover: '<div slot="popover" popover="manual" role="dialog" aria-label="Date and time picker"></div>',
151
- // §FB-Wave1-QA — substrate primitive (extracted from calendar-picker-ui).
152
- calPane: '<calendar-grid-ui data-cal-pane aria-label="Date"></calendar-grid-ui>',
153
- divider: '<divider-ui data-pane-divider vertical></divider-ui>',
154
- timePane: '<time-picker-ui data-time-pane></time-picker-ui>',
50
+ static properties = {
51
+ ...UICalendarPicker.properties,
52
+ // The only two defaults that differ from the folded-into base class —
53
+ // everything else (min/max/step/hourcycle/locale/open/format/
54
+ // placement/readonly/etc.) is inherited unchanged.
55
+ precision: { type: String, default: 'minute', reflect: true },
56
+ placeholder: { type: String, default: 'Select date and time', reflect: true },
155
57
  };
156
-
157
- // No html`` template — light-DOM authored [slot="trigger"|"footer"]
158
- // must survive render() (an empty html`` result triggers stamp() →
159
- // replaceChildren() which would wipe authored slots).
160
- static template = () => null;
161
-
162
- // ── State ─────────────────────────────────────────────────────────
163
- #pending = null; // partial {date, time} during selection
164
- #previousFocus = null; // focus restoration target on close
165
- #bound = false;
166
- #popoverShown = false;
167
- #triggerRef = null;
168
- #popoverRef = null;
169
- #calPaneRef = null;
170
- #timePaneRef = null;
171
- #dividerRef = null;
172
- #anchorCleanup = null; // §FB-Wave1-QA — popover anchor positioning cleanup
173
- // Flag set by #onTriggerKey to mark the *next* click as keyboard-origin.
174
- // <button-ui> calls `this.click()` from its own keydown handler, so a
175
- // click after Enter/Space is synthetic. Mirrors date-range-picker.
176
- #keyboardOpen = false;
177
-
178
- // ── Public accessors ──────────────────────────────────────────────
179
-
180
- /** Parsed `{date, time}` object form of the value attribute (or null). */
181
- get datetimeValue() {
182
- return parseDatetime(this.value);
183
- }
184
- set datetimeValue(v) {
185
- untracked(() => {
186
- if (v == null) {
187
- this.value = '';
188
- } else if (typeof v === 'string') {
189
- this.value = v;
190
- } else if (v.date && v.time) {
191
- this.value = formatDatetime(v.date, v.time, this.precision);
192
- } else {
193
- this.value = '';
194
- }
195
- });
196
- }
197
-
198
- // ── Imperative API ────────────────────────────────────────────────
199
-
200
- /** Programmatically open the popover. Mirrors date-range-picker. */
201
- openPopover() {
202
- if (this.disabled || this.readonly) return;
203
- if (!this.open) {
204
- this.#previousFocus = document.activeElement;
205
- this.open = true;
206
- this.dispatchEvent(new CustomEvent('open', {
207
- bubbles: true,
208
- detail: { trigger: 'programmatic' },
209
- }));
210
- }
211
- }
212
-
213
- /** Programmatically close. */
214
- closePopover(reason = 'programmatic') {
215
- if (!this.open) return;
216
- this.open = false;
217
- this.dispatchEvent(new CustomEvent('close', {
218
- bubbles: true,
219
- detail: { reason },
220
- }));
221
- }
222
-
223
- /** Reset value to empty string. */
224
- clear() {
225
- this.value = '';
226
- this.#pending = null;
227
- this.syncValue('');
228
- }
229
-
230
- // ── Constraint validation ─────────────────────────────────────────
231
-
232
- /**
233
- * Override `syncValue` to run datetime-specific constraints. The base
234
- * UIFormElement::syncValue writes raw `value` to the form; we
235
- * additionally validate against parseability + min / max.
236
- */
237
- syncValue(val) {
238
- const baseVal = val ?? this.value ?? '';
239
- this.internals.setFormValue(baseVal);
240
- this.#runDatetimeConstraints(baseVal);
241
- }
242
-
243
- #runDatetimeConstraints(val) {
244
- if (this.required && !val) {
245
- this.internals.setValidity(
246
- { valueMissing: true },
247
- this.getAttribute('data-msg-required') || 'Please select a date and time.',
248
- this,
249
- );
250
- return false;
251
- }
252
- if (!val) {
253
- this.internals.setValidity({});
254
- return true;
255
- }
256
- const parsed = parseDatetime(val);
257
- if (!parsed) {
258
- this.internals.setValidity(
259
- { badInput: true },
260
- 'Invalid date or time format.',
261
- this,
262
- );
263
- return false;
264
- }
265
- if (this.min && val < this.min) {
266
- this.internals.setValidity(
267
- { rangeUnderflow: true },
268
- `Earliest selectable datetime is ${this.min}.`,
269
- this,
270
- );
271
- return false;
272
- }
273
- if (this.max && val > this.max) {
274
- this.internals.setValidity(
275
- { rangeOverflow: true },
276
- `Latest selectable datetime is ${this.max}.`,
277
- this,
278
- );
279
- return false;
280
- }
281
- this.internals.setValidity({});
282
- return true;
283
- }
284
-
285
- // ── Lifecycle ─────────────────────────────────────────────────────
286
-
287
- connected() {
288
- super.connected();
289
- this.setAttribute('role', 'combobox');
290
- this.setAttribute('aria-haspopup', 'dialog');
291
- this.setAttribute('aria-expanded', this.open ? 'true' : 'false');
292
-
293
- if (!this.#bound) {
294
- this.#bound = true;
295
- this.#ensureParts();
296
- this.#triggerRef.addEventListener('click', this.#onTriggerClick);
297
- // Capture-phase so we flag keyboard-origin BEFORE <button-ui>'s
298
- // own keydown synthesizes the click (button/button.class.js:148).
299
- this.#triggerRef.addEventListener('keydown', this.#onTriggerKey, true);
300
- this.#popoverRef.addEventListener('click', this.#onPopoverClick);
301
- this.#popoverRef.addEventListener('keydown', this.#onPopoverKey);
302
- this.#calPaneRef.addEventListener('change', this.#onCalPaneChange);
303
- this.#timePaneRef.addEventListener('change', this.#onTimePaneChange);
304
- this.#timePaneRef.addEventListener('input', this.#onTimePaneInput);
305
- this.#timePaneRef.addEventListener('invalid', this.#onPaneInvalid);
306
- }
307
- this.syncValue();
308
- }
309
-
310
- disconnected() {
311
- super.disconnected();
312
- if (this.#triggerRef) {
313
- this.#triggerRef.removeEventListener('click', this.#onTriggerClick);
314
- this.#triggerRef.removeEventListener('keydown', this.#onTriggerKey, true);
315
- }
316
- if (this.#popoverRef) {
317
- this.#popoverRef.removeEventListener('click', this.#onPopoverClick);
318
- this.#popoverRef.removeEventListener('keydown', this.#onPopoverKey);
319
- this.#popoverRef.hidePopover?.();
320
- }
321
- if (this.#calPaneRef) this.#calPaneRef.removeEventListener('change', this.#onCalPaneChange);
322
- if (this.#timePaneRef) {
323
- this.#timePaneRef.removeEventListener('change', this.#onTimePaneChange);
324
- this.#timePaneRef.removeEventListener('input', this.#onTimePaneInput);
325
- this.#timePaneRef.removeEventListener('invalid', this.#onPaneInvalid);
326
- }
327
- document.removeEventListener('pointerdown', this.#onOutside);
328
- document.removeEventListener('keydown', this.#onDocKey);
329
- this.#anchorCleanup?.();
330
- this.#anchorCleanup = null;
331
- this.#bound = false;
332
- this.#popoverShown = false;
333
- this.#triggerRef = null;
334
- this.#popoverRef = null;
335
- this.#calPaneRef = null;
336
- this.#timePaneRef = null;
337
- this.#dividerRef = null;
338
- this.#pending = null;
339
- }
340
-
341
- #ensureParts() {
342
- // Trigger — light DOM. Default <button-ui> stamped unless consumer
343
- // supplied [slot="trigger"].
344
- this.#triggerRef = this.ensure('trigger');
345
- // Popover is a div hosted in light DOM but lifted to the top layer
346
- // via the Popover API on showPopover().
347
- this.#popoverRef = this.ensure('popover');
348
- if (this.id) this.#popoverRef.setAttribute('aria-labelledby', this.id);
349
-
350
- // Stamp the two panes + divider inside the popover (idempotent).
351
- if (!this.#popoverRef.querySelector(':scope > [data-cal-pane]')) {
352
- const cal = this.constructor._pp.calPane.cloneNode(true);
353
- this.#popoverRef.appendChild(cal);
354
- }
355
- if (!this.#popoverRef.querySelector(':scope > [data-pane-divider]')) {
356
- const div = this.constructor._pp.divider.cloneNode(true);
357
- this.#popoverRef.appendChild(div);
358
- }
359
- if (!this.#popoverRef.querySelector(':scope > [data-time-pane]')) {
360
- const time = this.constructor._pp.timePane.cloneNode(true);
361
- this.#popoverRef.appendChild(time);
362
- }
363
- this.#calPaneRef = this.#popoverRef.querySelector(':scope > [data-cal-pane]');
364
- this.#dividerRef = this.#popoverRef.querySelector(':scope > [data-pane-divider]');
365
- this.#timePaneRef = this.#popoverRef.querySelector(':scope > [data-time-pane]');
366
-
367
- // Footer slot — consumer-supplied; lift into popover after the panes.
368
- const authorFooter = this.querySelector(':scope > [slot="footer"]');
369
- if (authorFooter && authorFooter.parentElement !== this.#popoverRef) {
370
- this.#popoverRef.appendChild(authorFooter);
371
- }
372
- }
373
-
374
- render() {
375
- if (!this.#triggerRef) return;
376
- // Re-stamp parts if they were wiped by a consumer innerHTML write
377
- // between the last connect and this render (FEEDBACK-31 invariant).
378
- if (!this.#calPaneRef || !this.#timePaneRef || !this.#calPaneRef.isConnected) {
379
- this.#ensureParts();
380
- }
381
-
382
- // Trigger reflection — text, disabled, ARIA expansion.
383
- this.#triggerRef.setAttribute('text', this.#displayText());
384
- // Mark a committed value so the trigger brightens to --a-ui-text (the
385
- // empty placeholder stays muted) — mirrors date-range-picker.
386
- this.#triggerRef.toggleAttribute('data-has-value', this.#displayText() !== this.placeholder);
387
- this.#triggerRef.toggleAttribute('disabled', this.disabled);
388
- this.#triggerRef.setAttribute('aria-haspopup', 'dialog');
389
- this.#triggerRef.setAttribute('aria-expanded', this.open ? 'true' : 'false');
390
- this.setAttribute('aria-expanded', this.open ? 'true' : 'false');
391
-
392
- // Cascade value + constraints into the two panes.
393
- const parsed = parseDatetime(this.value);
394
- const pending = this.#pending;
395
- const datePart = pending?.date ?? parsed?.date ?? '';
396
- const timePart = pending?.time ?? parsed?.time ?? '';
397
-
398
- if (this.#calPaneRef) {
399
- // Reflect min/max date portions onto the calendar pane.
400
- const minDate = this.min ? this.min.slice(0, 10) : '';
401
- const maxDate = this.max ? this.max.slice(0, 10) : '';
402
- if (minDate) this.#calPaneRef.setAttribute('min', minDate);
403
- else this.#calPaneRef.removeAttribute('min');
404
- if (maxDate) this.#calPaneRef.setAttribute('max', maxDate);
405
- else this.#calPaneRef.removeAttribute('max');
406
- if (this.#calPaneRef.value !== datePart) {
407
- this.#calPaneRef.value = datePart;
408
- }
409
- this.#calPaneRef.toggleAttribute('disabled', !!this.disabled);
410
- this.#calPaneRef.toggleAttribute('readonly', !!this.readonly);
411
- }
412
-
413
- if (this.#timePaneRef) {
414
- this.#timePaneRef.setAttribute('precision', this.precision);
415
- this.#timePaneRef.setAttribute('step', String(this.step));
416
- if (this.hourcycle) this.#timePaneRef.setAttribute('hour-cycle', this.hourcycle);
417
- else this.#timePaneRef.removeAttribute('hour-cycle');
418
- if (this.locale) this.#timePaneRef.setAttribute('locale', this.locale);
419
- else this.#timePaneRef.removeAttribute('locale');
420
- // Min/max time portions only apply when date matches the bound's
421
- // date. For v1 we cascade the time-of-day portion of the bound
422
- // unconditionally; the host-level invalid check rejects out-of-
423
- // range commits, so the time pane bound is an advisory affordance.
424
- const minTime = this.min ? this.min.slice(11) : '';
425
- const maxTime = this.max ? this.max.slice(11) : '';
426
- if (minTime && datePart === this.min.slice(0, 10)) this.#timePaneRef.setAttribute('min', minTime);
427
- else this.#timePaneRef.removeAttribute('min');
428
- if (maxTime && datePart === this.max.slice(0, 10)) this.#timePaneRef.setAttribute('max', maxTime);
429
- else this.#timePaneRef.removeAttribute('max');
430
- if (this.#timePaneRef.value !== timePart) {
431
- this.#timePaneRef.value = timePart;
432
- }
433
- this.#timePaneRef.toggleAttribute('disabled', !!this.disabled);
434
- this.#timePaneRef.toggleAttribute('readonly', !!this.readonly);
435
- }
436
-
437
- // Toggle popover via the Popover API. Mirrors date-range-picker.
438
- if (this.#popoverRef) {
439
- if (this.open && !this.#popoverShown) {
440
- if (!this.disabled && !this.readonly) {
441
- this.#popoverShown = true;
442
- this.#popoverRef.showPopover?.();
443
- // §FB-Wave1-QA — anchor the popover to the trigger via the canonical
444
- // helper. Without this, the popover renders at viewport (0,0).
445
- this.#anchorCleanup?.();
446
- this.#anchorCleanup = anchorPopover(this.#triggerRef, this.#popoverRef, {
447
- placement: this.getAttribute('placement') || 'bottom', // ADR-0034 Rule 2: panel >> trigger
448
- gap: 4,
449
- });
450
- document.addEventListener('pointerdown', this.#onOutside);
451
- document.addEventListener('keydown', this.#onDocKey);
452
- queueMicrotask(() => {
453
- if (!this.open) return;
454
- // Focus the calendar grid first (date-pending state).
455
- const grid = this.#calPaneRef?.querySelector?.('[data-cal-day]:not([disabled]):not([data-outside])');
456
- grid?.focus?.();
457
- });
458
- } else {
459
- this.open = false;
460
- }
461
- } else if (!this.open && this.#popoverShown) {
462
- this.#popoverShown = false;
463
- this.#anchorCleanup?.();
464
- this.#anchorCleanup = null;
465
- this.#popoverRef.hidePopover?.();
466
- document.removeEventListener('pointerdown', this.#onOutside);
467
- document.removeEventListener('keydown', this.#onDocKey);
468
- this.#triggerRef?.focus?.();
469
- }
470
- }
471
- }
472
-
473
- // ── Trigger text formatter ────────────────────────────────────────
474
-
475
- #displayText() {
476
- const parsed = parseDatetime(this.value);
477
- if (!parsed) return this.placeholder;
478
- const dPart = formatDatePart(parsed.date, this.format);
479
- const tPart = formatTimePart(parsed.time, this.precision, this.hourcycle);
480
- if (!dPart || !tPart) return this.placeholder;
481
- return `${dPart} ${tPart}`;
482
- }
483
-
484
- // ── Event handlers ────────────────────────────────────────────────
485
-
486
- #onTriggerClick = (e) => {
487
- if (this.disabled) return;
488
- if (this.readonly) return;
489
- e.stopPropagation();
490
- const fromKeyboard = this.#keyboardOpen;
491
- this.#keyboardOpen = false;
492
- if (this.open) {
493
- this.closePopover('outside');
494
- } else {
495
- this.#previousFocus = document.activeElement;
496
- this.open = true;
497
- this.dispatchEvent(new CustomEvent('open', {
498
- bubbles: true,
499
- detail: { trigger: fromKeyboard ? 'keyboard' : 'click' },
500
- }));
501
- }
502
- };
503
-
504
- #onTriggerKey = (e) => {
505
- if (this.disabled) return;
506
- if (e.key === 'Enter' || e.key === ' ') {
507
- if (this.readonly) {
508
- e.preventDefault();
509
- return;
510
- }
511
- this.#keyboardOpen = true;
512
- }
513
- };
514
-
515
- #onPopoverClick = (e) => {
516
- // Footer button handling — Apply / Cancel semantics if the consumer
517
- // wires data-action attrs onto authored slot="footer" buttons.
518
- const actionBtn = e.target.closest('[data-action]');
519
- if (actionBtn && this.contains(actionBtn)) {
520
- const action = actionBtn.dataset.action;
521
- if (action === 'apply') {
522
- this.#commitFromPanes('click');
523
- this.closePopover('apply');
524
- } else if (action === 'cancel') {
525
- // Discard pending; restore from value.
526
- this.#pending = null;
527
- this.closePopover('cancel');
528
- }
529
- }
530
- };
531
-
532
- #onPopoverKey = (e) => {
533
- if (e.key === 'Escape') {
534
- e.preventDefault();
535
- e.stopPropagation();
536
- this.closePopover('escape');
537
- return;
538
- }
539
- if (e.key === 'Tab') {
540
- // Focus trap — cycle inside the popover. Trigger is outside the
541
- // popover; Tab cycles inside until Escape closes.
542
- const focusables = this.#getFocusables();
543
- if (focusables.length === 0) return;
544
- const idx = focusables.indexOf(document.activeElement);
545
- if (e.shiftKey) {
546
- if (idx <= 0) {
547
- e.preventDefault();
548
- focusables[focusables.length - 1].focus();
549
- }
550
- } else {
551
- if (idx === -1 || idx === focusables.length - 1) {
552
- e.preventDefault();
553
- focusables[0].focus();
554
- }
555
- }
556
- }
557
- };
558
-
559
- #onDocKey = (e) => {
560
- // Belt-and-braces: Escape closes even when focus is outside (e.g.,
561
- // when the trigger absorbs it after some interaction).
562
- if (e.key === 'Escape' && this.open) {
563
- this.closePopover('escape');
564
- }
565
- };
566
-
567
- #onCalPaneChange = (e) => {
568
- if (this.readonly) return;
569
- e.stopPropagation();
570
- const iso = e.detail?.value || this.#calPaneRef?.value || '';
571
- if (!iso) return;
572
- if (!this.#pending) this.#pending = { date: '', time: '' };
573
- this.#pending.date = iso;
574
- // Live emit `input` for partial state.
575
- const existing = parseDatetime(this.value);
576
- const timeCandidate = this.#pending.time || existing?.time || '';
577
- const partial = timeCandidate
578
- ? formatDatetime(iso, timeCandidate, this.precision)
579
- : `${iso}T`;
580
- this.dispatchEvent(new CustomEvent('input', {
581
- bubbles: true,
582
- detail: { value: partial },
583
- }));
584
- if (timeCandidate) {
585
- this.#commitFromParts({ date: iso, time: timeCandidate });
586
- } else {
587
- // Advance focus to the time pane's first segment for the
588
- // pending-time state (a11y model: date → time → commit).
589
- queueMicrotask(() => {
590
- if (!this.open) return;
591
- const hourSeg = this.#timePaneRef?.querySelector?.('[data-segment="hour"]');
592
- hourSeg?.focus?.();
593
- });
594
- }
595
- };
596
-
597
- #onTimePaneChange = (e) => {
598
- if (this.readonly) return;
599
- e.stopPropagation();
600
- const time = e.detail?.value || this.#timePaneRef?.value || '';
601
- if (!time) return;
602
- if (!this.#pending) this.#pending = { date: '', time: '' };
603
- this.#pending.time = time;
604
- const existing = parseDatetime(this.value);
605
- const dateCandidate = this.#pending.date || existing?.date || '';
606
- if (dateCandidate) {
607
- this.#commitFromParts({ date: dateCandidate, time });
608
- } else {
609
- // Time chosen without a date — partial state.
610
- this.dispatchEvent(new CustomEvent('input', {
611
- bubbles: true,
612
- detail: { value: `T${time}` },
613
- }));
614
- }
615
- };
616
-
617
- #onTimePaneInput = (e) => {
618
- if (this.readonly) return;
619
- e.stopPropagation();
620
- // Surface the time-pane's intermediate edits as host `input` events
621
- // so consumers see a unified stream.
622
- const time = e.detail?.value || '';
623
- const existing = parseDatetime(this.value);
624
- const date = this.#pending?.date || existing?.date || '';
625
- const partial = (date && time) ? formatDatetime(date, time, this.precision) : (time ? `T${time}` : '');
626
- this.dispatchEvent(new CustomEvent('input', {
627
- bubbles: true,
628
- detail: { value: partial },
629
- }));
630
- };
631
-
632
- #onPaneInvalid = (e) => {
633
- e.stopPropagation();
634
- this.dispatchEvent(new CustomEvent('invalid', {
635
- bubbles: true,
636
- detail: { value: this.value, reason: e.detail?.reason || 'pane' },
637
- }));
638
- };
639
-
640
- #onOutside = (e) => {
641
- if (!this.open) return;
642
- if (this.contains(e.target)) return;
643
- if (this.#popoverRef && e.composedPath?.().includes(this.#popoverRef)) return;
644
- this.closePopover('outside');
645
- };
646
-
647
- // ── Commit ────────────────────────────────────────────────────────
648
-
649
- /**
650
- * Commit a candidate {date, time} pair as the new value if it passes
651
- * constraint validation. Mirrors date-range-picker's #commitRange but
652
- * for a single datetime string.
653
- */
654
- #commitFromParts(parts, source = 'click') {
655
- if (!parts || !parts.date || !parts.time) return;
656
- const next = formatDatetime(parts.date, parts.time, this.precision);
657
- if (!next) {
658
- this.dispatchEvent(new CustomEvent('invalid', {
659
- bubbles: true,
660
- detail: { value: next, reason: 'parse' },
661
- }));
662
- return;
663
- }
664
- if (this.min && next < this.min) {
665
- this.dispatchEvent(new CustomEvent('invalid', {
666
- bubbles: true,
667
- detail: { value: next, reason: 'below-min' },
668
- }));
669
- return;
670
- }
671
- if (this.max && next > this.max) {
672
- this.dispatchEvent(new CustomEvent('invalid', {
673
- bubbles: true,
674
- detail: { value: next, reason: 'above-max' },
675
- }));
676
- return;
677
- }
678
- this.value = next;
679
- this.#pending = null;
680
- this.syncValue(next);
681
- this.dispatchEvent(new CustomEvent('change', {
682
- bubbles: true,
683
- detail: { value: next, source },
684
- }));
685
- }
686
-
687
- /**
688
- * Commit using the panes' current values (Apply path).
689
- */
690
- #commitFromPanes(source = 'click') {
691
- const date = this.#calPaneRef?.value || '';
692
- const time = this.#timePaneRef?.value || '';
693
- if (!date || !time) return;
694
- this.#commitFromParts({ date, time }, source);
695
- }
696
-
697
- #getFocusables() {
698
- if (!this.#popoverRef) return [];
699
- const SEL = [
700
- 'button-ui:not([disabled])',
701
- 'button:not([disabled])',
702
- 'calendar-grid-ui:not([disabled])',
703
- 'time-picker-ui:not([disabled])',
704
- '[data-cal-day]:not([disabled]):not([data-outside])',
705
- '[data-segment]',
706
- '[tabindex="0"]',
707
- ].join(',');
708
- return Array.from(this.#popoverRef.querySelectorAll(SEL))
709
- .filter((el) => el.offsetParent !== null || el.matches(':popover-open *'));
710
- }
711
58
  }