@fusion.dev/helix-headless 0.145.0 → 0.147.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 (343) hide show
  1. package/custom-elements.json +12286 -4281
  2. package/dist/accordion/item/accordion-item.d.ts +51 -18
  3. package/dist/accordion/item/accordion-item.js +45 -13
  4. package/dist/accordion/root/accordion-root.d.ts +16 -7
  5. package/dist/accordion/root/accordion-root.js +3 -3
  6. package/dist/announcement/root/announcement-root.d.ts +16 -6
  7. package/dist/announcement/root/announcement-root.js +37 -7
  8. package/dist/app-header/root/app-header-root.d.ts +7 -2
  9. package/dist/app-header/root/app-header-root.js +9 -1
  10. package/dist/app-header/sectioning.d.ts +3 -1
  11. package/dist/app-header/sectioning.js +3 -2
  12. package/dist/area-chart/root/area-chart-root.d.ts +18 -1
  13. package/dist/area-chart/root/area-chart-root.js +11 -3
  14. package/dist/avatar/group/avatar-group.d.ts +20 -14
  15. package/dist/avatar/group/avatar-group.js +17 -19
  16. package/dist/avatar/root/avatar-root.d.ts +31 -1
  17. package/dist/avatar/root/avatar-root.js +11 -1
  18. package/dist/badge/root/badge-root.d.ts +60 -7
  19. package/dist/badge/root/badge-root.js +89 -6
  20. package/dist/bar-chart/root/bar-chart-root.d.ts +18 -1
  21. package/dist/bar-chart/root/bar-chart-root.js +48 -20
  22. package/dist/bar-chart/stack.d.ts +25 -0
  23. package/dist/bar-chart/stack.js +27 -0
  24. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +14 -0
  25. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +12 -2
  26. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +25 -0
  27. package/dist/breadcrumbs/root/breadcrumbs-root.js +51 -4
  28. package/dist/button/root/button-root.d.ts +50 -38
  29. package/dist/button/root/button-root.js +42 -53
  30. package/dist/button/submitter.d.ts +27 -0
  31. package/dist/button/submitter.js +25 -0
  32. package/dist/button-group/root/button-group-root.d.ts +2 -1
  33. package/dist/button-group/root/button-group-root.js +5 -1
  34. package/dist/canvas/node/canvas-node.d.ts +4 -3
  35. package/dist/canvas/node/canvas-node.js +3 -2
  36. package/dist/canvas/root/canvas-root.js +2 -2
  37. package/dist/card/root/card-root.d.ts +36 -4
  38. package/dist/card/root/card-root.js +29 -10
  39. package/dist/carousel/root/carousel-root.d.ts +43 -4
  40. package/dist/carousel/root/carousel-root.js +40 -12
  41. package/dist/carousel/stops.d.ts +20 -0
  42. package/dist/carousel/stops.js +30 -0
  43. package/dist/chat/message/chat-message.js +5 -4
  44. package/dist/chat/naming.d.ts +5 -1
  45. package/dist/chat/naming.js +4 -2
  46. package/dist/chat/root/chat-root.d.ts +40 -4
  47. package/dist/chat/root/chat-root.js +32 -6
  48. package/dist/checkbox/root/checkbox-root.d.ts +20 -0
  49. package/dist/checkbox/root/checkbox-root.js +18 -7
  50. package/dist/checkbox-group/root/checkbox-group-root.d.ts +8 -2
  51. package/dist/checkbox-group/root/checkbox-group-root.js +14 -11
  52. package/dist/checkbox-group/select-all.d.ts +21 -0
  53. package/dist/checkbox-group/select-all.js +7 -0
  54. package/dist/code-block/highlight.d.ts +15 -0
  55. package/dist/code-block/highlight.js +4 -0
  56. package/dist/code-block/root/code-block-root.d.ts +79 -10
  57. package/dist/code-block/root/code-block-root.js +74 -5
  58. package/dist/color-input/root/color-input-root.d.ts +14 -0
  59. package/dist/color-input/root/color-input-root.js +17 -7
  60. package/dist/color-picker/channels.js +1 -2
  61. package/dist/color-picker/root/color-picker-root.d.ts +53 -16
  62. package/dist/color-picker/root/color-picker-root.js +155 -46
  63. package/dist/color-picker/wheel.d.ts +17 -0
  64. package/dist/color-picker/wheel.js +6 -0
  65. package/dist/combobox/root/combobox-root.d.ts +56 -0
  66. package/dist/combobox/root/combobox-root.js +92 -15
  67. package/dist/command/root/command-root.d.ts +19 -2
  68. package/dist/command/root/command-root.js +24 -23
  69. package/dist/copy-button/root/copy-button-root.d.ts +21 -1
  70. package/dist/copy-button/root/copy-button-root.js +16 -4
  71. package/dist/data-table/index.d.ts +1 -1
  72. package/dist/data-table/index.js +1 -1
  73. package/dist/data-table/keys.d.ts +6 -0
  74. package/dist/data-table/keys.js +14 -0
  75. package/dist/data-table/root/data-table-root.d.ts +27 -18
  76. package/dist/data-table/root/data-table-root.js +31 -57
  77. package/dist/data-table/tree.d.ts +16 -1
  78. package/dist/data-table/tree.js +2 -3
  79. package/dist/date-input/root/date-input-root.d.ts +11 -2
  80. package/dist/date-input/root/date-input-root.js +26 -12
  81. package/dist/date-picker/root/date-picker-root.d.ts +14 -9
  82. package/dist/date-picker/root/date-picker-root.js +33 -22
  83. package/dist/description-list/item/description-list-item.d.ts +18 -4
  84. package/dist/description-list/item/description-list-item.js +8 -4
  85. package/dist/dialog/description.js +15 -1
  86. package/dist/dialog/root/dialog-root.d.ts +8 -2
  87. package/dist/dialog/root/dialog-root.js +16 -1
  88. package/dist/divider/root/divider-root.d.ts +3 -0
  89. package/dist/divider/root/divider-root.js +2 -1
  90. package/dist/donut-chart/root/donut-chart-root.d.ts +31 -0
  91. package/dist/donut-chart/root/donut-chart-root.js +55 -1
  92. package/dist/donut-chart/slice-label.d.ts +35 -0
  93. package/dist/donut-chart/slice-label.js +11 -0
  94. package/dist/drawer/reveal.d.ts +8 -0
  95. package/dist/drawer/reveal.js +1 -0
  96. package/dist/drawer/root/drawer-root.d.ts +63 -2
  97. package/dist/drawer/root/drawer-root.js +75 -4
  98. package/dist/drilldown/item/drilldown-item.d.ts +21 -1
  99. package/dist/drilldown/item/drilldown-item.js +40 -5
  100. package/dist/drilldown/root/drilldown-root.d.ts +21 -3
  101. package/dist/drilldown/root/drilldown-root.js +22 -3
  102. package/dist/element-events.generated.js +35 -6
  103. package/dist/empty/root/empty-root.d.ts +34 -2
  104. package/dist/empty/root/empty-root.js +26 -4
  105. package/dist/file-input/root/file-input-root.d.ts +99 -8
  106. package/dist/file-input/root/file-input-root.js +120 -22
  107. package/dist/file-input/screen.d.ts +2 -1
  108. package/dist/file-input/status.d.ts +24 -0
  109. package/dist/file-input/status.js +10 -0
  110. package/dist/filter-input/chip/filter-chip.d.ts +23 -0
  111. package/dist/filter-input/chip/filter-chip.js +11 -2
  112. package/dist/filter-input/menu/filter-menu.d.ts +2 -0
  113. package/dist/filter-input/menu/filter-menu.js +12 -4
  114. package/dist/filter-input/root/filter-input-root.d.ts +38 -6
  115. package/dist/filter-input/root/filter-input-root.js +18 -28
  116. package/dist/filter-input/suggest.d.ts +28 -3
  117. package/dist/filter-input/suggest.js +18 -8
  118. package/dist/format-date/options.d.ts +3 -4
  119. package/dist/format-date/options.js +2 -2
  120. package/dist/format-date/root/format-date-root.d.ts +27 -17
  121. package/dist/format-date/root/format-date-root.js +27 -48
  122. package/dist/heatmap/root/buckets.d.ts +21 -0
  123. package/dist/heatmap/root/buckets.js +34 -0
  124. package/dist/heatmap/root/heatmap-root.d.ts +83 -11
  125. package/dist/heatmap/root/heatmap-root.js +104 -12
  126. package/dist/icon/index.d.ts +1 -1
  127. package/dist/icon/index.js +1 -1
  128. package/dist/icon/registry.d.ts +26 -1
  129. package/dist/icon/registry.js +12 -1
  130. package/dist/icon/root/icon-root.d.ts +25 -1
  131. package/dist/icon/root/icon-root.js +44 -12
  132. package/dist/icon/root/index.d.ts +1 -1
  133. package/dist/icon/root/index.js +1 -1
  134. package/dist/icon/settle.d.ts +7 -0
  135. package/dist/icon/settle.js +10 -0
  136. package/dist/icon-button/root/icon-button-root.d.ts +2 -0
  137. package/dist/image/root/image-root.d.ts +14 -1
  138. package/dist/image/root/image-root.js +7 -2
  139. package/dist/input/root/input-root.d.ts +34 -3
  140. package/dist/input/root/input-root.js +17 -4
  141. package/dist/kbd/keys.d.ts +29 -0
  142. package/dist/kbd/keys.js +52 -0
  143. package/dist/kbd/root/kbd-root.d.ts +52 -1
  144. package/dist/kbd/root/kbd-root.js +40 -47
  145. package/dist/legend/hidden-series.d.ts +10 -2
  146. package/dist/legend/hidden-series.js +5 -2
  147. package/dist/legend/root/legend-root.d.ts +77 -12
  148. package/dist/legend/root/legend-root.js +71 -17
  149. package/dist/lightbox/root/lightbox-root.d.ts +98 -10
  150. package/dist/lightbox/root/lightbox-root.js +153 -18
  151. package/dist/lightbox/zoom-controller.d.ts +59 -0
  152. package/dist/lightbox/zoom-controller.js +128 -0
  153. package/dist/lightbox/zoom.d.ts +24 -0
  154. package/dist/lightbox/zoom.js +18 -0
  155. package/dist/line-chart/root/line-chart-root.d.ts +38 -1
  156. package/dist/line-chart/root/line-chart-root.js +31 -3
  157. package/dist/list/item/list-item.d.ts +2 -0
  158. package/dist/list/item/list-item.js +11 -2
  159. package/dist/list/root/list-root.d.ts +70 -13
  160. package/dist/list/root/list-root.js +91 -13
  161. package/dist/list/selection.d.ts +54 -7
  162. package/dist/list/selection.js +32 -7
  163. package/dist/listbox/root/listbox-root.d.ts +44 -4
  164. package/dist/listbox/root/listbox-root.js +72 -34
  165. package/dist/loader/busy.d.ts +10 -0
  166. package/dist/loader/busy.js +11 -0
  167. package/dist/loader/root/loader-root.d.ts +9 -2
  168. package/dist/loader/root/loader-root.js +23 -3
  169. package/dist/megamenu/hand-over.d.ts +75 -0
  170. package/dist/megamenu/hand-over.js +67 -0
  171. package/dist/megamenu/root/megamenu-root.d.ts +80 -15
  172. package/dist/megamenu/root/megamenu-root.js +76 -19
  173. package/dist/menu/item/menu-item.d.ts +51 -2
  174. package/dist/menu/item/menu-item.js +50 -26
  175. package/dist/menu/root/menu-root.d.ts +17 -0
  176. package/dist/menu/root/menu-root.js +2 -2
  177. package/dist/meter/root/meter-root.d.ts +62 -1
  178. package/dist/meter/root/meter-root.js +38 -16
  179. package/dist/navigation/root/navigation-root.d.ts +2 -1
  180. package/dist/navigation/root/navigation-root.js +2 -2
  181. package/dist/notifications/root/notifications-root.d.ts +20 -4
  182. package/dist/notifications/root/notifications-root.js +30 -8
  183. package/dist/number-input/grain.d.ts +28 -0
  184. package/dist/number-input/grain.js +14 -0
  185. package/dist/number-input/root/number-input-root.d.ts +34 -9
  186. package/dist/number-input/root/number-input-root.js +31 -25
  187. package/dist/object-inspector/root/object-inspector-root.d.ts +88 -33
  188. package/dist/object-inspector/root/object-inspector-root.js +134 -92
  189. package/dist/object-inspector/rows.d.ts +18 -0
  190. package/dist/object-inspector/rows.js +33 -0
  191. package/dist/option/index.d.ts +1 -0
  192. package/dist/option/index.js +1 -0
  193. package/dist/option/root/option-root.d.ts +5 -3
  194. package/dist/option/separator/option-separator.d.ts +30 -0
  195. package/dist/option/separator/option-separator.js +21 -0
  196. package/dist/outline/root/outline-root.d.ts +30 -0
  197. package/dist/outline/root/outline-root.js +17 -13
  198. package/dist/pagination/size/pagination-size.d.ts +6 -0
  199. package/dist/pagination/size/pagination-size.js +5 -3
  200. package/dist/pin-input/code.d.ts +37 -0
  201. package/dist/pin-input/code.js +25 -0
  202. package/dist/pin-input/root/pin-input-root.d.ts +116 -115
  203. package/dist/pin-input/root/pin-input-root.js +163 -155
  204. package/dist/popover/hide-outside.d.ts +14 -0
  205. package/dist/popover/hide-outside.js +34 -0
  206. package/dist/popover/root/popover-root.d.ts +64 -8
  207. package/dist/popover/root/popover-root.js +67 -4
  208. package/dist/progress/root/progress-root.d.ts +47 -0
  209. package/dist/progress/root/progress-root.js +34 -16
  210. package/dist/qr-code/export.d.ts +45 -1
  211. package/dist/qr-code/export.js +28 -0
  212. package/dist/qr-code/name.d.ts +11 -0
  213. package/dist/qr-code/name.js +7 -0
  214. package/dist/qr-code/root/qr-code-root.d.ts +36 -11
  215. package/dist/qr-code/root/qr-code-root.js +58 -11
  216. package/dist/quote/marks.d.ts +8 -0
  217. package/dist/quote/marks.js +5 -0
  218. package/dist/quote/root/quote-root.d.ts +14 -2
  219. package/dist/quote/root/quote-root.js +23 -1
  220. package/dist/radio/group/radio-group.d.ts +15 -0
  221. package/dist/radio/root/radio-root.d.ts +6 -2
  222. package/dist/radio/root/radio-root.js +7 -2
  223. package/dist/rating/root/rating-root.d.ts +30 -12
  224. package/dist/rating/root/rating-root.js +27 -27
  225. package/dist/scatter-chart/fit.d.ts +15 -0
  226. package/dist/scatter-chart/fit.js +35 -0
  227. package/dist/scatter-chart/root/scatter-chart-root.d.ts +51 -1
  228. package/dist/scatter-chart/root/scatter-chart-root.js +64 -24
  229. package/dist/scroll-area/root/scroll-area-root.d.ts +22 -51
  230. package/dist/scroll-area/root/scroll-area-root.js +57 -173
  231. package/dist/scroll-area/scrollbar-controller.d.ts +111 -0
  232. package/dist/scroll-area/scrollbar-controller.js +172 -0
  233. package/dist/scroll-area/scrollbar.d.ts +27 -8
  234. package/dist/scroll-area/scrollbar.js +10 -1
  235. package/dist/select/align.d.ts +47 -0
  236. package/dist/select/align.js +7 -0
  237. package/dist/select/root/select-root.d.ts +71 -0
  238. package/dist/select/root/select-root.js +78 -6
  239. package/dist/skeleton/root/skeleton-root.d.ts +3 -0
  240. package/dist/skeleton/root/skeleton-root.js +4 -2
  241. package/dist/skip-link/root/skip-link-root.js +2 -2
  242. package/dist/slider/root/slider-root.d.ts +59 -5
  243. package/dist/slider/root/slider-root.js +45 -3
  244. package/dist/sparkline/readings.d.ts +36 -0
  245. package/dist/sparkline/readings.js +36 -0
  246. package/dist/sparkline/root/sparkline-root.d.ts +121 -7
  247. package/dist/sparkline/root/sparkline-root.js +160 -26
  248. package/dist/speed-dial/item/speed-dial-item.d.ts +1 -1
  249. package/dist/speed-dial/item/speed-dial-item.js +2 -2
  250. package/dist/speed-dial/root/speed-dial-root.d.ts +6 -0
  251. package/dist/speed-dial/root/speed-dial-root.js +11 -3
  252. package/dist/splitter/root/splitter-root.d.ts +10 -12
  253. package/dist/splitter/root/splitter-root.js +211 -204
  254. package/dist/stat/root/stat-root.d.ts +14 -5
  255. package/dist/stat/root/stat-root.js +21 -5
  256. package/dist/steps/reach.d.ts +5 -0
  257. package/dist/steps/reach.js +3 -0
  258. package/dist/steps/root/steps-root.d.ts +56 -3
  259. package/dist/steps/root/steps-root.js +50 -6
  260. package/dist/steps/step/step.d.ts +8 -0
  261. package/dist/steps/step/step.js +8 -3
  262. package/dist/switch/root/switch-root.d.ts +10 -0
  263. package/dist/table/column/table-column.d.ts +6 -10
  264. package/dist/table/column/table-column.js +8 -20
  265. package/dist/table/root/table-root.d.ts +31 -10
  266. package/dist/table/root/table-root.js +14 -18
  267. package/dist/table/row/table-row.d.ts +24 -0
  268. package/dist/table/row/table-row.js +37 -1
  269. package/dist/tabs/panel/tab-panel.d.ts +16 -3
  270. package/dist/tabs/panel/tab-panel.js +34 -5
  271. package/dist/tabs/root/tabs-root.d.ts +17 -3
  272. package/dist/tabs/root/tabs-root.js +1 -1
  273. package/dist/tabs/tab/tab.d.ts +5 -0
  274. package/dist/tabs/tab/tab.js +5 -2
  275. package/dist/tag/group/tag-group.d.ts +44 -5
  276. package/dist/tag/group/tag-group.js +60 -9
  277. package/dist/tag/root/tag-root.js +9 -2
  278. package/dist/textarea/root/textarea-root.d.ts +41 -2
  279. package/dist/textarea/root/textarea-root.js +39 -1
  280. package/dist/time-input/root/time-input-root.d.ts +10 -2
  281. package/dist/time-input/root/time-input-root.js +24 -13
  282. package/dist/time-picker/root/time-picker-root.d.ts +28 -17
  283. package/dist/time-picker/root/time-picker-root.js +18 -29
  284. package/dist/time-picker/wheel-name.d.ts +9 -0
  285. package/dist/time-picker/wheel-name.js +15 -0
  286. package/dist/timeline/group/timeline-group.js +2 -2
  287. package/dist/toast/item/toast-item.d.ts +16 -3
  288. package/dist/toast/item/toast-item.js +22 -6
  289. package/dist/toast/root/toast-root.d.ts +34 -4
  290. package/dist/toast/root/toast-root.js +74 -13
  291. package/dist/toast/words.d.ts +12 -0
  292. package/dist/toast/words.js +25 -0
  293. package/dist/toggle/group/toggle-group.d.ts +52 -7
  294. package/dist/toggle/group/toggle-group.js +58 -44
  295. package/dist/toggle/root/toggle-root.js +2 -4
  296. package/dist/toolbar/root/toolbar-root.d.ts +21 -5
  297. package/dist/toolbar/root/toolbar-root.js +52 -8
  298. package/dist/tooltip/root/tooltip-root.d.ts +20 -3
  299. package/dist/tooltip/root/tooltip-root.js +18 -1
  300. package/dist/tour/back.d.ts +10 -0
  301. package/dist/tour/back.js +8 -0
  302. package/dist/tour/root/tour-root.d.ts +45 -2
  303. package/dist/tour/root/tour-root.js +53 -8
  304. package/dist/tour/stop/tour-stop.d.ts +3 -0
  305. package/dist/tour/stop/tour-stop.js +3 -3
  306. package/dist/tree/item/tree-item.d.ts +7 -3
  307. package/dist/tree/item/tree-item.js +3 -0
  308. package/dist/tree/root/tree-root.d.ts +24 -3
  309. package/dist/tree/root/tree-root.js +32 -13
  310. package/dist/tree/selection.js +2 -15
  311. package/dist/trend/figure.d.ts +16 -0
  312. package/dist/trend/figure.js +8 -0
  313. package/dist/trend/root/trend-root.d.ts +83 -19
  314. package/dist/trend/root/trend-root.js +59 -8
  315. package/dist/video/media-events.d.ts +13 -18
  316. package/dist/video/media-events.js +1 -5
  317. package/dist/video/root/extra-controls.d.ts +12 -0
  318. package/dist/video/root/extra-controls.js +6 -2
  319. package/dist/video/root/video-root.d.ts +23 -6
  320. package/dist/video/root/video-root.js +19 -10
  321. package/dist/video-playlist/menu/video-playlist-menu.d.ts +2 -0
  322. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  323. package/dist/video-playlist/root/video-playlist-root.d.ts +17 -1
  324. package/dist/video-playlist/root/video-playlist-root.js +3 -0
  325. package/dist/virtual-list/fill.d.ts +11 -8
  326. package/dist/virtual-list/fill.js +10 -17
  327. package/dist/virtual-list/keys.d.ts +16 -7
  328. package/dist/virtual-list/keys.js +17 -6
  329. package/dist/virtual-list/plan.d.ts +65 -0
  330. package/dist/virtual-list/plan.js +27 -0
  331. package/dist/virtual-list/root/virtual-list-root.d.ts +136 -13
  332. package/dist/virtual-list/root/virtual-list-root.js +218 -51
  333. package/lint.json +1 -1
  334. package/package.json +8 -8
  335. package/types/jsx.d.ts +7441 -1458
  336. package/types/svelte.d.ts +1 -0
  337. package/types/vue.d.ts +1501 -326
  338. package/dist/chat/relative-clock.d.ts +0 -39
  339. package/dist/chat/relative-clock.js +0 -31
  340. package/dist/data-table/root/order.d.ts +0 -141
  341. package/dist/data-table/root/order.js +0 -76
  342. package/dist/tag/selection.d.ts +0 -10
  343. package/dist/tag/selection.js +0 -12
@@ -9,6 +9,7 @@ import { caretLeft, caretRight, play, stop } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
+ import { stepStop, stopFor, stopsOf } from '../stops.js';
12
13
  const COUNT_OR_AUTO = {
13
14
  fromAttribute: (value) => {
14
15
  if (value === null) {
@@ -59,7 +60,7 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
59
60
  ? -1
60
61
  : 0
61
62
  : inlineStep(this, event.key);
62
- let to = this.index + step;
63
+ let to;
63
64
  if (event.key === 'Home') {
64
65
  to = 0;
65
66
  }
@@ -69,6 +70,9 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
69
70
  else if (step === 0) {
70
71
  return;
71
72
  }
73
+ else {
74
+ to = this.move(step);
75
+ }
72
76
  event.preventDefault();
73
77
  this.goTo(to);
74
78
  this.updateComplete.then(() => {
@@ -213,6 +217,9 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
213
217
  get positions() {
214
218
  return Math.max(1, this.slides.length - (this.across ?? 1) + 1);
215
219
  }
220
+ get stops() {
221
+ return stopsOf(this.positions, this.slidesPerMove);
222
+ }
216
223
  measure() {
217
224
  const track = this.partNamed('track');
218
225
  const pane = this.panes[0]?.getBoundingClientRect().width;
@@ -281,6 +288,9 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
281
288
  this.requestUpdate('index', previous);
282
289
  this.dispatchEvent(new HelixActivateEvent({ value: String(value) }));
283
290
  }
291
+ move(step) {
292
+ return stepStop(this.stops, this.index, step < 0 ? -1 : 1, this.loop);
293
+ }
284
294
  goTo(index) {
285
295
  if (this.slides.length === 0) {
286
296
  return;
@@ -357,7 +367,7 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
357
367
  if (!this.rotating || this.slides.length < 2) {
358
368
  return;
359
369
  }
360
- this.timer = setInterval(() => this.goTo(this.index >= this.positions - 1 ? 0 : this.index + 1), this.interval);
370
+ this.timer = setInterval(() => this.goTo(this.index >= this.positions - 1 ? 0 : this.move(1)), this.interval);
361
371
  }
362
372
  nearest() {
363
373
  const track = this.partNamed('track');
@@ -366,8 +376,11 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
366
376
  }
367
377
  const bleed = this.bleed(track);
368
378
  const at = this.orientation === 'vertical' ? track.scrollTop : track.scrollLeft;
369
- return this.panes.slice(0, this.positions).reduce((best, slide, index) => {
370
- const distance = Math.abs(this.edgeOf(slide, track, bleed) - at);
379
+ return this.stops.reduce((best, index) => {
380
+ const slide = this.panes[index];
381
+ const distance = slide
382
+ ? Math.abs(this.edgeOf(slide, track, bleed) - at)
383
+ : Number.POSITIVE_INFINITY;
371
384
  return distance < best.distance ? { distance, index } : best;
372
385
  }, { distance: Number.POSITIVE_INFINITY, index: this.index }).index;
373
386
  }
@@ -484,9 +497,21 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
484
497
  </svg></slot
485
498
  >`;
486
499
  }
500
+ paneStyle(index, last, stops) {
501
+ const declared = [
502
+ index === last ? this.runEnd() : nothing,
503
+ (this.slidesPerMove ?? 1) > 1 && !stops.includes(index)
504
+ ? 'scroll-snap-align: none !important'
505
+ : nothing,
506
+ ].filter((one) => one !== nothing);
507
+ return declared.length > 0 ? declared.join('; ') : nothing;
508
+ }
487
509
  render() {
488
510
  const rotating = this.rotating;
489
511
  const last = this.slides.length - 1;
512
+ const stops = this.stops;
513
+ const place = stopFor(stops, this.index);
514
+ const showing = stopFor(stops, this.showing ?? this.index);
490
515
  return html `<div part="controls">
491
516
  ${this.autoplay
492
517
  ? html `<button
@@ -511,7 +536,7 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
511
536
  type="button"
512
537
  aria-label=${this.previousLabel}
513
538
  aria-disabled=${this.atEnd('start') ? 'true' : nothing}
514
- @click=${() => this.goTo(this.index - 1)}
539
+ @click=${() => this.goTo(this.move(-1))}
515
540
  >
516
541
  ${this.renderPreviousMark()}
517
542
  </button>
@@ -520,7 +545,7 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
520
545
  type="button"
521
546
  aria-label=${this.nextLabel}
522
547
  aria-disabled=${this.atEnd('end') ? 'true' : nothing}
523
- @click=${() => this.goTo(this.index + 1)}
548
+ @click=${() => this.goTo(this.move(1))}
524
549
  >
525
550
  ${this.renderNextMark()}
526
551
  </button>`}
@@ -545,7 +570,7 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
545
570
  role="group"
546
571
  aria-roledescription=${this.slideRoleLabel || nothing}
547
572
  aria-label=${fillTemplate(this.slideLabel, { count: this.slides.length, index: index + 1 })}
548
- style=${index === last ? this.runEnd() : nothing}
573
+ style=${this.paneStyle(index, last, stops)}
549
574
  >
550
575
  <slot
551
576
  name=${this.assignsByName ? `slide-${index + 1}` : nothing}
@@ -557,17 +582,17 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
557
582
  ${this.slides.length > 1 && this.across !== undefined
558
583
  ? html `<div part="indicators" role="group" aria-label=${fillTemplate(this.indicatorsLabel, { label: this.label })}>
559
584
  ${''}
560
- ${Array.from({ length: this.positions }).map((_place, at) => {
561
- const current = at === (this.showing ?? this.index);
585
+ ${stops.map((stop, at) => {
586
+ const current = at === showing;
562
587
  return html `<button
563
588
  part="indicator${current ? ' indicator-current' : ''}"
564
589
  type="button"
565
590
  aria-label=${fillTemplate(this.indicatorLabel, { index: at + 1 })}
566
- aria-current=${at === this.index ? 'true' : nothing}
567
- tabindex=${at === this.index ? '0' : '-1'}
591
+ aria-current=${at === place ? 'true' : nothing}
592
+ tabindex=${at === place ? '0' : '-1'}
568
593
  ?data-current=${current}
569
594
  @keydown=${this.handleIndicatorKeydown}
570
- @click=${() => this.goTo(at)}
595
+ @click=${() => this.goTo(stop)}
571
596
  ></button>`;
572
597
  })}
573
598
  </div>`
@@ -628,6 +653,9 @@ __decorate([
628
653
  __decorate([
629
654
  property({ attribute: 'slide-role-label' })
630
655
  ], HelixHeadlessCarousel.prototype, "slideRoleLabel", void 0);
656
+ __decorate([
657
+ property({ attribute: 'slides-per-move', type: Number })
658
+ ], HelixHeadlessCarousel.prototype, "slidesPerMove", void 0);
631
659
  __decorate([
632
660
  property({ attribute: 'start-label' })
633
661
  ], HelixHeadlessCarousel.prototype, "startLabel", void 0);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Where a carousel's track stops when it moves several slides at a time, as
3
+ * arithmetic over its positions.
4
+ *
5
+ * A position is a slide the track can start at: six slides four across have
6
+ * three, 0, 1 and 2. Moving `perMove` slides at a time stops at every
7
+ * `perMove`th of them from the first, and always at the last, so the end of
8
+ * the run is reachable whatever the count.
9
+ */
10
+ /** The positions the track stops at, in order: every `perMove`th from 0, and the last. */
11
+ export declare const stopsOf: (positions: number, perMove?: number) => number[];
12
+ /** Which stop a position is at or past: the last stop at or before it, as an index into `stops`. */
13
+ export declare const stopFor: (stops: readonly number[], position: number) => number;
14
+ /**
15
+ * The position a move of one stop forward (`1`) or back (`-1`) lands on.
16
+ *
17
+ * From a position between two stops, back goes to the stop before it. Past
18
+ * either end it wraps where `loop` says so, and stays at the end otherwise.
19
+ */
20
+ export declare const stepStop: (stops: readonly number[], position: number, step: -1 | 1, loop: boolean) => number;
@@ -0,0 +1,30 @@
1
+ export const stopsOf = (positions, perMove = 1) => {
2
+ const step = Number.isFinite(perMove) ? Math.max(1, Math.floor(perMove)) : 1;
3
+ const last = Math.max(0, positions - 1);
4
+ const stops = [];
5
+ for (let at = 0; at < last; at += step) {
6
+ stops.push(at);
7
+ }
8
+ stops.push(last);
9
+ return stops;
10
+ };
11
+ export const stopFor = (stops, position) => {
12
+ let found = 0;
13
+ stops.forEach((stop, at) => {
14
+ if (stop <= position) {
15
+ found = at;
16
+ }
17
+ });
18
+ return found;
19
+ };
20
+ export const stepStop = (stops, position, step, loop) => {
21
+ const at = stopFor(stops, position);
22
+ let next = step < 0 && stops[at] !== position ? at : at + step;
23
+ if (loop) {
24
+ next = (next + stops.length) % stops.length;
25
+ }
26
+ else {
27
+ next = Math.min(Math.max(next, 0), stops.length - 1);
28
+ }
29
+ return stops[next] ?? 0;
30
+ };
@@ -4,12 +4,11 @@ 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 { customElement, HelixElement, HelixSendEvent, hostAria, markRegion, relativeTime, requestChange, TONE_MARKS, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HelixSendEvent, hostAria, markRegion, RelativeClockController, relativeTime, requestChange, TONE_MARKS, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
11
11
  import { authorWords, TURN_WORDS, turnName } from '../naming.js';
12
- import { RelativeClockController } from '../relative-clock.js';
13
12
  export const FAILED_LABEL = 'Not sent';
14
13
  let HelixHeadlessChatMessage = class HelixHeadlessChatMessage extends HelixElement {
15
14
  constructor() {
@@ -23,8 +22,10 @@ let HelixHeadlessChatMessage = class HelixHeadlessChatMessage extends HelixEleme
23
22
  this.turnWords = TURN_WORDS;
24
23
  this.isChatMessage = true;
25
24
  this.clock = new RelativeClockController(this, {
26
- locale: () => this.locale,
27
- moment: () => this.moment,
25
+ expires: (now) => {
26
+ const moment = this.moment;
27
+ return moment ? relativeTime(this.locale)(moment, now).next : undefined;
28
+ },
28
29
  });
29
30
  this.handleFill = () => {
30
31
  this.markEmpty();
@@ -4,6 +4,8 @@ export interface TurnWords {
4
4
  me: string;
5
5
  /** The sentence for a turn with an author and a time, with `{author}` and `{time}`. */
6
6
  sentence: string;
7
+ /** The name of a turn with neither an author a reader can be told nor a time. */
8
+ turn: string;
7
9
  }
8
10
  /** The words a chat says its turns with, where nobody said otherwise. */
9
11
  export declare const TURN_WORDS: TurnWords;
@@ -17,6 +19,8 @@ export declare const TURN_WORDS: TurnWords;
17
19
  export declare const authorWords: (author: string | undefined, authorLabel: string, words: TurnWords) => string;
18
20
  /**
19
21
  * The turn's accessible name: the sentence where it has an author and a time,
20
- * and whichever of the two it has otherwise. Empty where it has neither.
22
+ * and whichever of the two it has otherwise. Where it has neither, a turn by
23
+ * `them` with no `author-label` and no time, the chat's word for a turn, so
24
+ * no turn is an `article` with no name.
21
25
  */
22
26
  export declare const turnName: (author: string, time: string, words: TurnWords) => string;
@@ -1,5 +1,5 @@
1
1
  import { fillTemplate } from '@fusion.dev/helix-core';
2
- export const TURN_WORDS = { me: 'You', sentence: '{author}, {time}' };
2
+ export const TURN_WORDS = { me: 'You', sentence: '{author}, {time}', turn: 'Message' };
3
3
  export const authorWords = (author, authorLabel, words) => {
4
4
  if (authorLabel !== '') {
5
5
  return authorLabel;
@@ -9,4 +9,6 @@ export const authorWords = (author, authorLabel, words) => {
9
9
  }
10
10
  return author === undefined || author === 'them' ? '' : author;
11
11
  };
12
- export const turnName = (author, time, words) => author !== '' && time !== '' ? fillTemplate(words.sentence, { author, time }) : author || time;
12
+ export const turnName = (author, time, words) => author !== '' && time !== ''
13
+ ? fillTemplate(words.sentence, { author, time })
14
+ : author || time || words.turn;
@@ -53,18 +53,25 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
53
53
  * `sent` or `failed` when your server answers. Add the reply as a turn with
54
54
  * `state="streaming"`, write its text as it arrives, and set `state` to `sent`
55
55
  * when it is complete. While the user types, the composer sends
56
- * `helix-typing`, which you can pass on to the other side. Show the other
57
- * side's typing in the `status` slot.
56
+ * `helix-typing`, which you can pass on to the other side. When the other side
57
+ * types, set `typing` to their name: the status band draws three dots and
58
+ * "Ada is typing" (`typing-label`), which a screen reader hears, and clearing
59
+ * it takes them away. Anything slotted into `status` is drawn first.
58
60
  *
59
61
  * A screen reader names each turn by its author and its time, such as "You, 2
60
62
  * minutes ago". Set `me-label` and `message-label` to say it in your language,
61
- * and give each turn by `them` an `author-label`.
63
+ * and give each turn by `them` an `author-label`. A turn with neither an author
64
+ * label nor a time is named by `turn-label`, "Message".
62
65
  *
63
66
  * A turn from `turns` is drawn in the chat's shadow root, so style it through
64
67
  * the forwarded `message-*` parts. A turn by `me` also gets each part with
65
68
  * `-mine` added, and the turn's state adds `message-meta-failed`,
66
69
  * `message-content-sending`, and `message-content-streaming`. To style each
67
- * participant differently, pass the turns as children.
70
+ * participant differently, give `author-styles` the declarations for each
71
+ * author, such as `{"ada": "--chat-message-background-color: …"}`: the message
72
+ * drawn for each of their turns takes them as its `style`, so the message's
73
+ * custom properties change for that author alone. Turns passed as children are
74
+ * styled by their `author`, such as `helix-chat-message[author="ada"]`.
68
75
  *
69
76
  * @since 0.1.0
70
77
  * @status stable
@@ -91,6 +98,7 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
91
98
  * @csspart empty - Shown when the conversation has no turns.
92
99
  * @csspart header - The region above the transcript.
93
100
  * @csspart history - Shown at the top of the transcript while older turns load.
101
+ * @csspart indicator - The three dots before the words that say who is typing, which the styled tier animates as a message's are. Hidden from screen readers.
94
102
  * @csspart live - The region where screen readers announce each turn once it has arrived.
95
103
  * @csspart loading - Shown while the history loads.
96
104
  * @csspart log - The transcript, which scrolls.
@@ -117,6 +125,7 @@ declare const HelixHeadlessChat_base: (new (...args: any[]) => import("@fusion.d
117
125
  * @csspart resume - The control shown while the user is scrolled away from the bottom.
118
126
  * @csspart status - The status region, outside the log.
119
127
  * @csspart turn - The wrapper around the message drawn for one turn from `turns`.
128
+ * @csspart typing - The words that say who is typing, in the status band, while `typing` names someone.
120
129
  */
121
130
  export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
122
131
  /**
@@ -131,6 +140,16 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
131
140
  static shadowRootOptions: ShadowRootInit;
132
141
  /** How tall a turn is assumed to be before it has been drawn, in pixels. */
133
142
  static readonly ESTIMATE = 72;
143
+ /**
144
+ * Declarations for the turns from `turns` by each author, keyed by `author`,
145
+ * as JSON in the attribute: `{"ada": "--chat-message-background-color: …"}`.
146
+ *
147
+ * The message drawn for each turn by that author takes them as its `style`,
148
+ * so the custom properties a message reads, its fill, text color, radius and
149
+ * padding, change for that author alone. A turn by an author with no entry
150
+ * takes none, and looks as it did. JSON that does not parse is no entries.
151
+ */
152
+ authorStyles: Record<string, string>;
134
153
  /**
135
154
  * Whether the transcript opens pinned to the bottom, when `pinned` is not
136
155
  * set.
@@ -197,6 +216,11 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
197
216
  * history.
198
217
  */
199
218
  threshold: number;
219
+ /**
220
+ * The name a screen reader gives a turn with neither an author it can say
221
+ * nor a time: a turn by `them` with no `author-label`, say.
222
+ */
223
+ turnLabel: string;
200
224
  /**
201
225
  * The turns as data, for a transcript too long to hold as elements.
202
226
  *
@@ -208,6 +232,18 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
208
232
  * element to look again.
209
233
  */
210
234
  turns: readonly ChatTurn[];
235
+ /**
236
+ * Who on the other side is typing, by name, or empty while nobody is.
237
+ *
238
+ * Set it from your connection, and clear it when they stop or send. The
239
+ * status band says it with `typing-label`.
240
+ */
241
+ typing: string;
242
+ /**
243
+ * What the status band says while someone is typing. `{author}` is the
244
+ * name in `typing`.
245
+ */
246
+ typingLabel: string;
211
247
  private pinnedState;
212
248
  /** The last thing said, and how many times something has been. */
213
249
  private announcement;
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessChat_1;
8
- import { customElement, HelixBeforePinEvent, HelixElement, HelixHistoryEvent, HelixPinEvent, hostAria, MixinCollection, onUnlessFalse, requestChange, VirtualizeController, whenHydrated, } from '@fusion.dev/helix-core';
8
+ import { customElement, fillTemplate, HelixBeforePinEvent, HelixElement, HelixHistoryEvent, HelixPinEvent, hostAria, MixinCollection, onUnlessFalse, requestChange, VirtualizeController, whenHydrated, wordsConverter, } from '@fusion.dev/helix-core';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { keyed } from 'lit/directives/keyed.js';
@@ -24,7 +24,11 @@ const SECOND_NAMES = {
24
24
  streaming: 'content: message-content-streaming',
25
25
  },
26
26
  };
27
- const secondNames = (table, key) => key !== undefined && Object.hasOwn(table, key) ? `, ${table[key]}` : '';
27
+ const ownEntry = (table, key) => (key !== undefined && Object.hasOwn(table, key) ? table[key] : undefined);
28
+ const secondNames = (table, key) => {
29
+ const names = ownEntry(table, key);
30
+ return names === undefined ? '' : `, ${names}`;
31
+ };
28
32
  const settled = (turn) => turn.state !== 'sending' && turn.state !== 'streaming';
29
33
  const newsworthy = (turn) => turn.author !== 'me' || turn.state === 'failed';
30
34
  const spoken = (message) => [...message.childNodes]
@@ -36,6 +40,7 @@ const spoken = (message) => [...message.childNodes]
36
40
  let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixElement) {
37
41
  constructor() {
38
42
  super(...arguments);
43
+ this.authorStyles = {};
39
44
  this.defaultPinned = true;
40
45
  this.history = '';
41
46
  this.historyText = 'Loading earlier messages';
@@ -43,7 +48,10 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
43
48
  this.meLabel = 'You';
44
49
  this.messageLabel = '{author}, {time}';
45
50
  this.threshold = 48;
51
+ this.turnLabel = 'Message';
46
52
  this.turns = [];
53
+ this.typing = '';
54
+ this.typingLabel = '{author} is typing';
47
55
  this.pinnedState = undefined;
48
56
  this.announcement = '';
49
57
  this.said = 0;
@@ -365,8 +373,8 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
365
373
  willUpdate(changed) {
366
374
  super.willUpdate(changed);
367
375
  hostAria(this.internals, { label: this.label || 'Conversation', role: 'region' });
368
- if (changed.has('meLabel') || changed.has('messageLabel')) {
369
- this.words = { me: this.meLabel, sentence: this.messageLabel };
376
+ if (changed.has('meLabel') || changed.has('messageLabel') || changed.has('turnLabel')) {
377
+ this.words = { me: this.meLabel, sentence: this.messageLabel, turn: this.turnLabel };
370
378
  }
371
379
  this.countRegions();
372
380
  if (changed.has('history')) {
@@ -431,6 +439,7 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
431
439
  author=${turn.author ?? nothing}
432
440
  author-label=${turn.authorLabel ?? nothing}
433
441
  at=${turn.at ?? nothing}
442
+ style=${ownEntry(this.authorStyles, turn.author) ?? nothing}
434
443
  .turnWords=${this.words}
435
444
  state=${turn.state ?? nothing}
436
445
  >${turn.text}</${message}>
@@ -466,14 +475,22 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
466
475
  <div part="resume" ?hidden=${!away || !this.has('resume')} @click=${this.handleResume}>
467
476
  <slot name="resume"></slot>
468
477
  </div>
469
- <div part="status" role="status" ?data-empty=${!this.has('status')}>
470
- <slot name="status"></slot>
478
+ <div part="status" role="status" ?data-empty=${!this.has('status') && this.typing === ''}>
479
+ <slot name="status"></slot>${this.typing === ''
480
+ ? nothing
481
+ : html `<span part="typing"
482
+ ><span part="indicator" aria-hidden="true"></span
483
+ >${fillTemplate(this.typingLabel, { author: this.typing })}</span
484
+ >`}
471
485
  </div>
472
486
  <div part="composer" ?hidden=${!this.has('composer')}>
473
487
  <slot name="composer"></slot>
474
488
  </div>`;
475
489
  }
476
490
  };
491
+ __decorate([
492
+ property({ attribute: 'author-styles', converter: wordsConverter })
493
+ ], HelixHeadlessChat.prototype, "authorStyles", void 0);
477
494
  __decorate([
478
495
  property({ attribute: 'default-pinned', converter: onUnlessFalse })
479
496
  ], HelixHeadlessChat.prototype, "defaultPinned", void 0);
@@ -498,9 +515,18 @@ __decorate([
498
515
  __decorate([
499
516
  property({ type: Number })
500
517
  ], HelixHeadlessChat.prototype, "threshold", void 0);
518
+ __decorate([
519
+ property({ attribute: 'turn-label' })
520
+ ], HelixHeadlessChat.prototype, "turnLabel", void 0);
501
521
  __decorate([
502
522
  property({ attribute: false })
503
523
  ], HelixHeadlessChat.prototype, "turns", void 0);
524
+ __decorate([
525
+ property()
526
+ ], HelixHeadlessChat.prototype, "typing", void 0);
527
+ __decorate([
528
+ property({ attribute: 'typing-label' })
529
+ ], HelixHeadlessChat.prototype, "typingLabel", void 0);
504
530
  __decorate([
505
531
  state()
506
532
  ], HelixHeadlessChat.prototype, "pinnedState", void 0);
@@ -21,6 +21,16 @@ declare const HelixHeadlessCheckbox_base: (new (...args: any[]) => import("@fusi
21
21
  * it as neither checked nor unchecked, it never submits, and the first click
22
22
  * resolves it.
23
23
  *
24
+ * **Keyboard.** The box is one tab stop.
25
+ *
26
+ * | Key | What it does |
27
+ * |---|---|
28
+ * | Space | Checks or unchecks the box, and resolves `indeterminate`. |
29
+ *
30
+ * The native input reports checked, unchecked, or mixed. `aria-readonly` is set
31
+ * under `readonly`, and `aria-invalid` and `aria-describedby` point at the
32
+ * message while one shows.
33
+ *
24
34
  * @since 0.1.0
25
35
  * @status stable
26
36
  * @category forms
@@ -28,6 +38,8 @@ declare const HelixHeadlessCheckbox_base: (new (...args: any[]) => import("@fusi
28
38
  * @documentation https://fusion.dev/helix/components/checkbox
29
39
  *
30
40
  * @slot - The label.
41
+ * @slot check - An icon drawn as the mark while the box is checked, in place of the tick.
42
+ * @slot dash - An icon drawn as the mark while the box is indeterminate, in place of the dash.
31
43
  * @slot feedback - The message that says what is wrong, for content richer than a string. It shows while there is a message.
32
44
  * @slot hint - Help under the control, for content richer than the `hint` text.
33
45
  *
@@ -47,6 +59,8 @@ declare const HelixHeadlessCheckbox_base: (new (...args: any[]) => import("@fusi
47
59
  * @csspart mark-indeterminate - Added to `mark` while the box is indeterminate.
48
60
  *
49
61
  * @cssstate checked - While the box is checked.
62
+ * @cssstate has-check - An icon is slotted into `check`.
63
+ * @cssstate has-dash - An icon is slotted into `dash`.
50
64
  * @cssstate has-label - The label slot has content, so the label is drawn.
51
65
  * @cssstate indeterminate - While the box shows the dash, neither checked nor unchecked.
52
66
  * @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
@@ -107,6 +121,12 @@ export declare class HelixHeadlessCheckbox extends HelixHeadlessCheckbox_base {
107
121
  * worse problem than its spacing.
108
122
  */
109
123
  private readonly handleLabelChange;
124
+ /**
125
+ * Records which of the mark's slots hold an icon, as `:state(has-check)` and
126
+ * `:state(has-dash)`, so a stylesheet drops its own drawing of that shape.
127
+ * A state, for the reason `:state(has-label)` is one.
128
+ */
129
+ private readonly handleMarkChange;
110
130
  /**
111
131
  * Asks the slot, and records the answer as a state.
112
132
  *
@@ -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 { AriaForwardController, customElement, HelixFormElement, HostClickController, MixinControllerChecked, MixinField, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, HelixFormElement, HostClickController, MixinControllerChecked, MixinField, slotFilled, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessCheckbox = class HelixHeadlessCheckbox extends MixinControllerChecked(MixinField(HelixFormElement)) {
@@ -32,6 +32,16 @@ let HelixHeadlessCheckbox = class HelixHeadlessCheckbox extends MixinControllerC
32
32
  this.handleLabelChange = () => {
33
33
  this.syncLabelPresence();
34
34
  };
35
+ this.handleMarkChange = (event) => {
36
+ const slot = event.target;
37
+ const filled = slotFilled(slot);
38
+ if (slot.name === 'dash') {
39
+ this.toggleState('has-dash', filled);
40
+ }
41
+ else {
42
+ this.toggleState('has-check', filled);
43
+ }
44
+ };
35
45
  }
36
46
  static { this.shadowRootOptions = {
37
47
  ...HelixFormElement.shadowRootOptions,
@@ -64,11 +74,9 @@ let HelixHeadlessCheckbox = class HelixHeadlessCheckbox extends MixinControllerC
64
74
  }
65
75
  syncLabelPresence() {
66
76
  const slot = this.renderRoot.querySelector('[part="label"] slot');
67
- const meaningful = (node) => node.textContent?.trim() !== '' || node.nodeType === Node.ELEMENT_NODE;
68
- const filled = (slot?.assignedNodes({ flatten: true }) ?? []).some(meaningful);
69
- const hidden = this.label === '' &&
70
- Boolean(this.getAttribute('aria-label')) &&
71
- !(slot?.assignedNodes() ?? []).some(meaningful);
77
+ const filled = slotFilled(slot);
78
+ const written = (slot?.assignedNodes() ?? []).some((node) => node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim() !== '');
79
+ const hidden = this.label === '' && Boolean(this.getAttribute('aria-label')) && !written;
72
80
  this.partNamed('label')?.toggleAttribute('data-hidden', hidden);
73
81
  this.toggleState('has-label', filled && !hidden);
74
82
  this.partNamed('label')?.toggleAttribute('hidden', !filled);
@@ -94,7 +102,10 @@ let HelixHeadlessCheckbox = class HelixHeadlessCheckbox extends MixinControllerC
94
102
  <span
95
103
  part="mark${this.checked ? ' mark-checked' : ''}${this.indeterminate ? ' mark-indeterminate' : ''}${this.dashed ? ' mark-dash' : ''}"
96
104
  aria-hidden="true"
97
- ></span>
105
+ >${this.dashed
106
+ ? html `<slot name="dash" @slotchange=${this.handleMarkChange}></slot>`
107
+ : html `<slot name="check" @slotchange=${this.handleMarkChange}></slot>`}</span
108
+ >
98
109
  </span>
99
110
  <span part="label" ?data-hidden=${named !== null}
100
111
  ><slot @slotchange=${this.handleLabelChange}>${this.label || named}</slot></span
@@ -34,6 +34,9 @@ declare const HelixHeadlessCheckboxGroup_base: (new (...args: any[]) => import("
34
34
  * only for validity and never calls `setFormValue`, so the browser does not
35
35
  * generate a message for it.
36
36
  *
37
+ * **Keyboard.** Each box is its own tab stop, as a set of native checkboxes
38
+ * is, and Space checks the focused one. The select-all box comes first.
39
+ *
37
40
  * @since 0.1.0
38
41
  * @status stable
39
42
  * @category forms
@@ -221,12 +224,15 @@ export declare class HelixHeadlessCheckboxGroup extends HelixHeadlessCheckboxGro
221
224
  * for the whole set, as a box moves it.
222
225
  */
223
226
  private readonly handleSelectAll;
227
+ /** The set as the select-all helper reads it. */
228
+ private setBoxes;
224
229
  /**
225
230
  * The box above the set that checks or unchecks every box, under
226
231
  * `select-all`, behind a seam a tier can move.
227
232
  *
228
- * Checked while every box is, and showing a dash while only some are. It is
229
- * in this shadow root, so it is no member of the set and submits nothing.
233
+ * Checked while every enabled box is, and showing a dash while only some
234
+ * are, so its state says what a press does. It is in this shadow root, so
235
+ * it is no member of the set and submits nothing.
230
236
  */
231
237
  protected renderSelectAll(): TemplateResult | typeof nothing;
232
238
  protected render(): TemplateResult;
@@ -5,12 +5,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessCheckboxGroup_1;
8
- import { commaList, customElement, DisabledCascadeController, HelixBeforeChangeEvent, HelixFormElement, hostAria, MixinField, } from '@fusion.dev/helix-core';
8
+ import { commaList, customElement, DisabledCascadeController, HelixBeforeChangeEvent, HelixFormElement, hostAria, MixinField, toggleEvery, } from '@fusion.dev/helix-core';
9
9
  import { nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
11
11
  import { html } from 'lit/static-html.js';
12
12
  import { HelixHeadlessCheckbox } from '../../checkbox/root/checkbox-root.js';
13
13
  import '../../checkbox/index.js';
14
+ import { selectAllState } from '../select-all.js';
14
15
  let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadlessCheckboxGroup extends MixinField(HelixFormElement) {
15
16
  constructor() {
16
17
  super(...arguments);
@@ -59,11 +60,8 @@ let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadl
59
60
  this.handleSelectAll = (event) => {
60
61
  event.preventDefault();
61
62
  event.stopPropagation();
62
- const enabled = this.boxes.filter((box) => !box.isDisabled);
63
- const every = enabled.length > 0 && enabled.every((box) => box.checked);
64
- const next = this.boxes
65
- .filter((box) => (enabled.includes(box) ? !every : box.checked))
66
- .map((box) => box.value);
63
+ const boxes = this.setBoxes();
64
+ const next = toggleEvery(boxes.filter((box) => box.checked).map((box) => box.value), boxes.filter((box) => !box.disabled).map((box) => box.value), boxes.map((box) => box.value));
67
65
  if (!this.dispatchEvent(new HelixBeforeChangeEvent({ value: next.join(',') }))) {
68
66
  return;
69
67
  }
@@ -177,13 +175,18 @@ let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadl
177
175
  this.validate();
178
176
  this.linkHost();
179
177
  }
178
+ setBoxes() {
179
+ return this.boxes.map((box) => ({
180
+ checked: box.checked,
181
+ disabled: box.isDisabled,
182
+ value: box.value,
183
+ }));
184
+ }
180
185
  renderSelectAll() {
181
186
  if (!this.selectAll) {
182
187
  return nothing;
183
188
  }
184
- const boxes = this.childrenKnown ? this.boxes : [];
185
- const ticked = boxes.filter((box) => box.checked).length;
186
- const every = boxes.length > 0 && ticked === boxes.length;
189
+ const { checked, indeterminate } = selectAllState(this.childrenKnown ? this.setBoxes() : []);
187
190
  const box = this.tag('checkbox');
188
191
  return html `<${box}
189
192
  part="select-all"
@@ -191,8 +194,8 @@ let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadl
191
194
  feedback: select-all-feedback, field: select-all-field, hint: select-all-hint,
192
195
  label: select-all-label, mark: select-all-mark, mark-checked: select-all-mark-checked,
193
196
  mark-dash: select-all-mark-dash, mark-indeterminate: select-all-mark-indeterminate"
194
- .checked=${every}
195
- .indeterminate=${ticked > 0 && !every}
197
+ .checked=${checked}
198
+ .indeterminate=${indeterminate}
196
199
  ?disabled=${this.isDisabled}
197
200
  label=${this.selectAllLabel}
198
201
  @helix-before-toggle=${this.handleSelectAll}