@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
@@ -11,12 +11,19 @@ import { type TemplateResult } from 'lit';
11
11
  * was loading, and the announcement tells a screen reader user that the search
12
12
  * returned nothing. It is announced from a status region of its own, which is
13
13
  * rendered empty and filled a frame after the view appears. Screen readers read
14
- * a region that arrives with its text already in it inconsistently.
14
+ * a region that arrives with its text already in it inconsistently. For the
15
+ * seven seconds the region holds its copy, the heading and the message are
16
+ * hidden from screen readers, so the words are in the page once.
15
17
  *
16
18
  * Every region is a bare slot. There is no `empty-state-heading` or
17
19
  * `empty-state-actions` element: assign your own heading and actions to the
18
20
  * slots.
19
21
  *
22
+ * **Or give the heading as words.** `heading` draws them in a `heading` part
23
+ * that is a heading at `heading-level`, 3 where it is not set, so a page
24
+ * rendering from data or on a server gets a heading a reader finds in the
25
+ * outline without writing the element. A heading slotted in replaces it.
26
+ *
20
27
  * @since 0.1.0
21
28
  * @status stable
22
29
  * @category feedback
@@ -25,12 +32,27 @@ import { type TemplateResult } from 'lit';
25
32
  *
26
33
  * @slot - The message: why the region is empty, and what to do about it.
27
34
  * @slot actions - The action that would put something in the region.
28
- * @slot heading - The heading, which names what is missing. Slot a heading element, such as an `<h3>`, so a reader can find it in the page's outline.
35
+ * @slot heading - The heading, which names what is missing. Slot a heading element, such as an `<h3>`, so a reader can find it in the page's outline. Replaces the one `heading` draws.
29
36
  * @slot icon - An icon above the heading.
30
37
  *
38
+ * @csspart heading - The heading `heading` draws, a heading at `heading-level`. Drawn only while `heading` holds words and nothing is slotted into `heading`.
31
39
  * @csspart live - A copy of the heading and the message, in a live region that screen readers announce when the view appears. Visually hidden in the styled tier.
32
40
  */
33
41
  export declare class HelixHeadlessEmpty extends HelixElement {
42
+ /**
43
+ * The heading as words, such as "No results", drawn as a heading when
44
+ * nothing is slotted into `heading`.
45
+ *
46
+ * Absent draws none, and the `heading` slot is the only heading.
47
+ */
48
+ heading: string;
49
+ /**
50
+ * The level of the heading `heading` draws, from 1 to 6.
51
+ *
52
+ * Absent is 3, the level of a heading inside a page's section. Set it to fit
53
+ * the outline the empty view sits in. A slotted heading keeps its own.
54
+ */
55
+ headingLevel?: number;
34
56
  /**
35
57
  * Whether the state is announced when it appears.
36
58
  *
@@ -40,6 +62,16 @@ export declare class HelixHeadlessEmpty extends HelixElement {
40
62
  */
41
63
  noAnnounce: boolean;
42
64
  protected readonly arrival: ArrivalController;
65
+ /**
66
+ * The `heading` slot, with the heading `heading` draws as its fallback, so
67
+ * one slotted in replaces it; no fallback while `heading` holds no words.
68
+ *
69
+ * A method, so a tier above can draw its own, as the slot lets a page.
70
+ *
71
+ * @param hidden Whether the live copy holds the heading's words, which
72
+ * hides them from readers until it empties.
73
+ */
74
+ protected renderHeading(hidden: boolean): TemplateResult;
43
75
  protected render(): TemplateResult;
44
76
  }
45
77
  declare global {
@@ -4,29 +4,51 @@ 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 { ArrivalController, customElement, HelixElement, slottedWords, } from '@fusion.dev/helix-core';
7
+ import { ArrivalController, customElement, HelixElement, headingLevel, slottedWords, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
+ this.heading = '';
13
14
  this.noAnnounce = false;
14
15
  this.arrival = new ArrivalController(this, {
15
16
  arriving: () => !this.serverRendered && document.readyState === 'complete',
16
- text: () => slottedWords(this, ['heading', '']),
17
+ text: () => [slottedWords(this, ['heading']) || this.heading.trim(), slottedWords(this, [''])]
18
+ .filter(Boolean)
19
+ .join(' '),
17
20
  });
18
21
  }
22
+ renderHeading(hidden) {
23
+ return html `<slot
24
+ aria-hidden=${hidden ? 'true' : nothing}
25
+ name="heading"
26
+ @slotchange=${() => this.arrival.say()}
27
+ >${this.heading.trim()
28
+ ? html `<div aria-level=${headingLevel(this.headingLevel)} part="heading" role="heading">
29
+ ${this.heading}
30
+ </div>`
31
+ : nothing}</slot
32
+ >`;
33
+ }
19
34
  render() {
20
35
  const say = () => this.arrival.say();
36
+ const held = this.arrival.said !== '' && !this.noAnnounce;
21
37
  return html `<slot name="icon"></slot>
22
- <slot name="heading" @slotchange=${say}></slot>
23
- <slot @slotchange=${say}></slot>
38
+ ${this.renderHeading(held)}
39
+ <slot aria-hidden=${held ? 'true' : nothing} @slotchange=${say}></slot>
24
40
  <slot name="actions"></slot>
25
41
  ${this.noAnnounce
26
42
  ? nothing
27
43
  : html `<span part="live" role="status">${this.arrival.said}</span>`}`;
28
44
  }
29
45
  };
46
+ __decorate([
47
+ property()
48
+ ], HelixHeadlessEmpty.prototype, "heading", void 0);
49
+ __decorate([
50
+ property({ attribute: 'heading-level', type: Number })
51
+ ], HelixHeadlessEmpty.prototype, "headingLevel", void 0);
30
52
  __decorate([
31
53
  property({ attribute: 'no-announce', reflect: true, type: Boolean })
32
54
  ], HelixHeadlessEmpty.prototype, "noAnnounce", void 0);
@@ -1,5 +1,7 @@
1
1
  import { AriaForwardController, HelixFormElement } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
2
+ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
+ import { type FileStatus } from '../status.js';
4
+ export type { FileStatus } from '../status.js';
3
5
  import '../../tag/index.js';
4
6
  declare const HelixHeadlessFileInput_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
5
7
  /**
@@ -20,7 +22,22 @@ declare const HelixHeadlessFileInput_base: (new (...args: any[]) => import("@fus
20
22
  * is screened: `accept`, `max-file-size`, `max-files` and your own `validate`
21
23
  * can refuse it. A refused file is left out, `helix-reject` lists each one with
22
24
  * its reason, and a reader hears `rejected-text` with the names filled in.
23
- * `directory` asks the picker for a folder.
25
+ * A reader also hears `added-text` for the files a drop or a paste adds, and
26
+ * `removed-text` for a file a remove button takes out, since neither moves
27
+ * focus or says anything on its own. `directory` asks the picker for a folder. `transformFiles` changes the files
28
+ * a field takes before it holds them, such as shrinking an image.
29
+ *
30
+ * Each chosen file is a row: its name, its size and type as tags, and a
31
+ * remove button. `renderFile` draws the row's details your own way, and the
32
+ * `remove-icon` slot replaces the button's icon.
33
+ *
34
+ * **Keyboard.** The input is the tab stop, and each file's remove button is
35
+ * one more.
36
+ *
37
+ * | Key | What it does |
38
+ * |---|---|
39
+ * | Enter, Space | On the input, opens the picker. On a remove button, removes that file and moves focus to the button of the file in its place, or back to the input. |
40
+ * | Control+V, Command+V | Takes files from the clipboard, as a drop does. |
24
41
  *
25
42
  * @since 0.1.0
26
43
  * @status stable
@@ -31,6 +48,7 @@ declare const HelixHeadlessFileInput_base: (new (...args: any[]) => import("@fus
31
48
  * @slot feedback - The message that says what is wrong with the chosen files.
32
49
  * @slot hint - Help under the field.
33
50
  * @slot label - The label, for content richer than the `label` text.
51
+ * @slot remove-icon - The icon on each file's remove button, in place of the bin. Each button shows a copy.
34
52
  * @slot trigger - The content that opens the picker when pressed.
35
53
  *
36
54
  * @event {Event} change - Fired after the value changes.
@@ -43,6 +61,9 @@ declare const HelixHeadlessFileInput_base: (new (...args: any[]) => import("@fus
43
61
  * @csspart feedback - The message that says what is wrong.
44
62
  * @csspart field - The box you press or drop files on.
45
63
  * @csspart file - One chosen file.
64
+ * @csspart file-done - A file whose upload `setFileStatus` says is done. Set together with `file`.
65
+ * @csspart file-error - A file whose upload `setFileStatus` says failed. Set together with `file`.
66
+ * @csspart file-uploading - A file whose upload `setFileStatus` says is under way. Set together with `file`.
46
67
  * @csspart hint - Help under the field, while nothing is wrong.
47
68
  * @csspart kind - A file's type.
48
69
  * @csspart kind-cell - The grid cell around the tag, rendered only when the tag sits in a `Tag.Group`.
@@ -53,9 +74,10 @@ declare const HelixHeadlessFileInput_base: (new (...args: any[]) => import("@fus
53
74
  * @csspart kind-start - The category before the label.
54
75
  * @csspart label - The label above the field.
55
76
  * @csspart list - The chosen files.
56
- * @csspart live - The live region that says `rejected-text` when files are refused. It is rendered from the first refusal.
77
+ * @csspart live - The live region that says `added-text`, `rejected-text`, and `removed-text`. It is rendered from the first time it has something to say.
57
78
  * @csspart meta - The row that holds a file's size and type.
58
79
  * @csspart name - A file's name.
80
+ * @csspart progress - A file's upload bar, a `<progress>`, while `setFileStatus` gives it a measure or says it is under way.
59
81
  * @csspart remove - The control that removes a file.
60
82
  * @csspart size - A file's size.
61
83
  * @csspart size-cell - The grid cell around the tag, rendered only when the tag sits in a `Tag.Group`.
@@ -64,8 +86,12 @@ declare const HelixHeadlessFileInput_base: (new (...args: any[]) => import("@fus
64
86
  * @csspart size-label - The label, trimmed to its capitals and baseline.
65
87
  * @csspart size-link - The link around the label, when the tag has an `href`.
66
88
  * @csspart size-start - The category before the label.
89
+ * @csspart status - How a file's upload is going, from `setFileStatus`: the bar and the words.
90
+ * @csspart status-text - The words `setFileStatus` gives a file, such as the reason it failed.
67
91
  * @csspart trigger - The label that opens the picker.
68
92
  *
93
+ * @cssstate dropping - Files are being dragged over the field.
94
+ * @cssstate has-files - The field holds at least one file.
69
95
  * @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
70
96
  */
71
97
  export declare class HelixHeadlessFileInput extends HelixHeadlessFileInput_base {
@@ -77,6 +103,11 @@ export declare class HelixHeadlessFileInput extends HelixHeadlessFileInput_base
77
103
  * filter is the only check the platform makes and a drop never meets it.
78
104
  */
79
105
  accept: string;
106
+ /**
107
+ * What a reader hears when a drop or a paste adds files, with `{names}` and
108
+ * `{count}` filled in.
109
+ */
110
+ addedText: string;
80
111
  /**
81
112
  * Which camera or microphone a mobile picker should offer, if any.
82
113
  */
@@ -105,11 +136,20 @@ export declare class HelixHeadlessFileInput extends HelixHeadlessFileInput_base
105
136
  * Whether more than one file can be chosen.
106
137
  */
107
138
  multiple: boolean;
139
+ /**
140
+ * The name of a file's progress bar, read by screen readers, with `{name}`
141
+ * filled in. A label with no `{name}` has it added after it.
142
+ */
143
+ progressLabel: string;
108
144
  /**
109
145
  * What a reader hears when files are refused, with `{count}` and `{names}`
110
146
  * filled in.
111
147
  */
112
148
  rejectedText: string;
149
+ /**
150
+ * What a reader hears when a file is removed, with `{name}` filled in.
151
+ */
152
+ removedText: string;
113
153
  /**
114
154
  * The accessible name of the control that removes a file. `{name}` is
115
155
  * replaced with the file's name.
@@ -119,6 +159,19 @@ export declare class HelixHeadlessFileInput extends HelixHeadlessFileInput_base
119
159
  * `{name}` has it added after it.
120
160
  */
121
161
  removeLabel: string;
162
+ /**
163
+ * Draws a file's row in place of its name, size, and type: a Lit template, a
164
+ * string, or a DOM node, given the file and its place in the list. The
165
+ * remove button stays beside it.
166
+ */
167
+ renderFile?: (file: File, index: number) => unknown;
168
+ /**
169
+ * Your own step between taking files and holding them, such as shrinking an
170
+ * image, as Zag's file upload `transformFiles`. It is given the files taken
171
+ * from one pick, drop, or paste and returns the files to hold, or a promise
172
+ * of them. A promise that rejects refuses those files, with its message.
173
+ */
174
+ transformFiles?: (files: File[]) => File[] | Promise<File[]>;
122
175
  /**
123
176
  * The trigger's text when nothing is slotted into it.
124
177
  */
@@ -128,15 +181,49 @@ export declare class HelixHeadlessFileInput extends HelixHeadlessFileInput_base
128
181
  * nothing to take it. A property, since a function cannot be an attribute.
129
182
  */
130
183
  validate?: (file: File) => string | null | undefined;
131
- /** The live region, rendered from the first refusal. */
184
+ /** The live region, rendered from the first time it has something to say. */
132
185
  private readonly status;
186
+ /** What the page has said of each file's upload, by file. */
187
+ private readonly statuses;
188
+ /**
189
+ * Shows how a file's upload is going, in its row: a bar for `progress`,
190
+ * from 0 to 1, or a bar without a measure while `state` is `uploading`, and
191
+ * `text` beside it. `state` names the row `file-uploading`, `file-done` or
192
+ * `file-error` beside `file`, and a reader hears `text` when the state
193
+ * becomes `done` or `error`. `null` clears it.
194
+ *
195
+ * The field uploads nothing, as Zag's file upload does not: your page sends
196
+ * the file and says here how it is going.
197
+ */
198
+ setFileStatus(file: File, status: FileStatus | null): void;
133
199
  /**
134
200
  * Screens arriving files and commits what is taken, after the files the
135
- * field keeps. Refused files are announced and said.
201
+ * field keeps, through `transformFiles` where it is set.
136
202
  */
137
203
  private take;
138
- /** Sends `helix-reject` and says which files were left out. */
139
- private refuse;
204
+ /**
205
+ * Commits what an arrival leaves, sends `helix-reject` for what it refused,
206
+ * and says both.
207
+ *
208
+ * Nothing taken and nothing moved, as when every dropped file is refused, is
209
+ * no change to announce. A pick is the platform's own `change`, relayed
210
+ * whatever it holds, and the picker itself tells a reader what was chosen,
211
+ * so `added-text` is said for a drop or a paste only.
212
+ */
213
+ private finish;
214
+ /**
215
+ * A sentence about some files, with `{count}` and `{names}` filled in.
216
+ *
217
+ * The names as the page's language lists them, which in English is the comma
218
+ * list it always was: `unit` and `short` join with commas and no "and". The
219
+ * count is written as the language writes a number.
220
+ */
221
+ private listed;
222
+ /** The element slotted into `remove-icon`, one copy per row's remove button. */
223
+ private readonly removeIcon;
224
+ private readonly handleRemoveIcon;
225
+ /** The icon on one row's remove button: a copy of `remove-icon`, or the bin. */
226
+ private removeMark;
140
227
  /** Takes files pasted onto the field, as a drop takes them. */
141
228
  private readonly handlePaste;
142
229
  /**
@@ -279,6 +366,11 @@ export declare class HelixHeadlessFileInput extends HelixHeadlessFileInput_base
279
366
  * renders the styled one from the same template.
280
367
  */
281
368
  protected renderMeta(file: File): TemplateResult;
369
+ /**
370
+ * How a file's upload is going, from `setFileStatus`, behind a seam a tier
371
+ * can move: a `<progress>` named by `progress-label` and the status words.
372
+ */
373
+ protected renderFileStatus(file: File): TemplateResult | typeof nothing;
282
374
  protected render(): TemplateResult;
283
375
  }
284
376
  declare global {
@@ -286,4 +378,3 @@ declare global {
286
378
  'helix-headless-file-input': HelixHeadlessFileInput;
287
379
  }
288
380
  }
289
- export {};
@@ -4,12 +4,13 @@ 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, drawIcon, drawStatus, fillTemplate, formatNumbers, HelixBeforeChangeEvent, HelixFormElement, HelixRejectEvent, MixinField, requestChange, restoreValue, StatusController, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, drawIcon, drawStatus, fillTemplate, formatNumbers, HelixBeforeChangeEvent, HelixFormElement, HelixRejectEvent, MixinField, requestChange, restoreValue, SlottedGlyph, StatusController, } from '@fusion.dev/helix-core';
8
8
  import { trash } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { html } from 'lit/static-html.js';
12
12
  import { screenFiles } from '../screen.js';
13
+ import { barOf, saysStatus } from '../status.js';
13
14
  import '../../tag/index.js';
14
15
  const named = (label, name) => label.includes('{name}') ? fillTemplate(label, { name }) : `${label} ${name}`;
15
16
  const size = (bytes, locale) => {
@@ -42,20 +43,29 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
42
43
  target: () => this.partNamed('control'),
43
44
  });
44
45
  this.accept = '';
46
+ this.addedText = 'Added: {names}';
45
47
  this.directory = false;
46
48
  this.locale = '';
47
49
  this.multiple = false;
50
+ this.progressLabel = 'Uploading {name}';
48
51
  this.rejectedText = 'Not added: {names}';
52
+ this.removedText = 'Removed: {name}';
49
53
  this.removeLabel = 'Remove {name}';
50
54
  this.triggerLabel = 'Choose files';
51
55
  this.status = new StatusController(this);
56
+ this.statuses = new WeakMap();
57
+ this.removeIcon = new SlottedGlyph();
58
+ this.handleRemoveIcon = (event) => {
59
+ this.removeIcon.read(event.target);
60
+ this.requestUpdate();
61
+ };
52
62
  this.handlePaste = (event) => {
53
63
  const pasted = [...(event.clipboardData?.files ?? [])];
54
64
  if (this.isDisabled || pasted.length === 0) {
55
65
  return;
56
66
  }
57
67
  event.preventDefault();
58
- this.take(pasted, this.multiple ? this.files : []);
68
+ this.take(pasted, this.files);
59
69
  };
60
70
  this.dropping = false;
61
71
  this.committed = [];
@@ -84,7 +94,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
84
94
  if (dropped.length === 0) {
85
95
  return;
86
96
  }
87
- this.take(dropped, this.multiple ? this.files : []);
97
+ this.take(dropped, this.files);
88
98
  };
89
99
  this.handleRemove = (event) => {
90
100
  const at = Number(event.currentTarget.dataset.at);
@@ -98,6 +108,19 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
98
108
  });
99
109
  };
100
110
  }
111
+ setFileStatus(file, status) {
112
+ const was = this.statuses.get(file);
113
+ if (status) {
114
+ this.statuses.set(file, status);
115
+ if (saysStatus(status, was)) {
116
+ this.status.say(status.text ?? '');
117
+ }
118
+ }
119
+ else {
120
+ this.statuses.delete(file);
121
+ }
122
+ this.requestUpdate();
123
+ }
101
124
  take(arriving, keeping, picked = false) {
102
125
  const { kept, refused } = screenFiles(arriving, keeping, {
103
126
  accept: this.accept,
@@ -106,21 +129,45 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
106
129
  multiple: this.multiple || picked,
107
130
  validate: this.validate,
108
131
  });
109
- const same = (one, two) => one.length === two.length && one.every((file, at) => file === two[at]);
110
- if (picked || !(same(kept, this.committed) && same(kept, this.files))) {
111
- this.commit(kept);
132
+ const taken = kept.filter((file) => !keeping.includes(file));
133
+ const transform = this.transformFiles;
134
+ if (!transform || taken.length === 0) {
135
+ this.finish(kept, taken, refused, picked);
136
+ return;
112
137
  }
138
+ const held = kept.filter((file) => keeping.includes(file));
139
+ Promise.resolve()
140
+ .then(() => transform(taken))
141
+ .then((made) => this.finish([...held, ...made], made, refused, picked), (error) => {
142
+ const message = error instanceof Error ? error.message : String(error);
143
+ this.finish(held, [], [...refused, ...taken.map((file) => ({ file, message, reason: 'invalid' }))], picked);
144
+ });
145
+ }
146
+ finish(next, added, refused, picked) {
147
+ const same = (one, two) => one.length === two.length && one.every((file, at) => file === two[at]);
148
+ const moved = picked || !(same(next, this.committed) && same(next, this.files));
149
+ const applied = moved && this.commit(next);
113
150
  if (refused.length > 0) {
114
- this.refuse(refused);
151
+ this.dispatchEvent(new HelixRejectEvent({ files: refused }));
152
+ }
153
+ const words = [
154
+ applied && !picked && added.length > 0 ? this.listed(this.addedText, added) : '',
155
+ refused.length > 0
156
+ ? this.listed(this.rejectedText, refused.map((one) => one.file))
157
+ : '',
158
+ ].filter((one) => one !== '');
159
+ if (words.length > 0) {
160
+ this.status.say(words.join(' '));
115
161
  }
116
162
  }
117
- refuse(refused) {
118
- this.dispatchEvent(new HelixRejectEvent({ files: refused }));
119
- const words = fillTemplate(this.rejectedText, {
120
- count: formatNumbers(this.lang)(refused.length),
121
- names: new Intl.ListFormat(this.lang || undefined, { style: 'short', type: 'unit' }).format(refused.map((one) => one.file.name)),
163
+ listed(sentence, files) {
164
+ return fillTemplate(sentence, {
165
+ count: formatNumbers(this.lang)(files.length),
166
+ names: new Intl.ListFormat(this.lang || undefined, { style: 'short', type: 'unit' }).format(files.map((file) => file.name)),
122
167
  });
123
- this.status.say(words);
168
+ }
169
+ removeMark(at) {
170
+ return this.removeIcon.copyFor(at) ?? drawIcon(trash);
124
171
  }
125
172
  get files() {
126
173
  return [...(this.control?.files ?? [])];
@@ -132,12 +179,15 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
132
179
  this.commit([]);
133
180
  }
134
181
  removeAt(at) {
135
- this.commit(this.files.filter((_, index) => index !== at));
182
+ const gone = this.files[at];
183
+ if (gone && this.commit(this.files.filter((_, index) => index !== at))) {
184
+ this.status.say(fillTemplate(this.removedText, { name: gone.name }));
185
+ }
136
186
  }
137
187
  commit(next) {
138
188
  const control = this.control;
139
189
  if (!control) {
140
- return;
190
+ return false;
141
191
  }
142
192
  const previous = this.committed;
143
193
  const applied = requestChange({
@@ -152,6 +202,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
152
202
  if (!applied) {
153
203
  this.put(control, previous);
154
204
  }
205
+ return applied;
155
206
  }
156
207
  put(control, files) {
157
208
  if (files.length === 0) {
@@ -197,6 +248,8 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
197
248
  updated(changed) {
198
249
  super.updated(changed);
199
250
  this.sync();
251
+ this.toggleState('dropping', this.dropping);
252
+ this.toggleState('has-files', this.files.length > 0);
200
253
  }
201
254
  focus(options) {
202
255
  this.control?.focus(options);
@@ -215,6 +268,23 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
215
268
  part="kind"
216
269
  exportparts="cell: kind-cell, dismiss: kind-dismiss, dismiss-mark: kind-dismiss-mark, label: kind-label, link: kind-link, start: kind-start">${kind(file)}</${this.tag('tag')}>`;
217
270
  }
271
+ renderFileStatus(file) {
272
+ const status = this.statuses.get(file);
273
+ if (!status) {
274
+ return nothing;
275
+ }
276
+ const bar = barOf(status);
277
+ return html `<span part="status"
278
+ >${bar === undefined
279
+ ? nothing
280
+ : html `<progress
281
+ part="progress"
282
+ max="1"
283
+ value=${bar === 'unmeasured' ? nothing : bar}
284
+ aria-label=${named(this.progressLabel, file.name)}
285
+ ></progress>`}${status.text ? html `<span part="status-text">${status.text}</span>` : nothing}</span
286
+ >`;
287
+ }
218
288
  render() {
219
289
  const files = this.files;
220
290
  return html `${this.renderLabel()}
@@ -246,6 +316,9 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
246
316
  <slot name="trigger">${this.triggerLabel}</slot>
247
317
  </label>
248
318
  </div>
319
+ <!-- Hidden, because this is where the icon arrives: each remove button
320
+ shows a copy. -->
321
+ <slot name="remove-icon" hidden @slotchange=${this.handleRemoveIcon}></slot>
249
322
  <!-- The hint sits under the field and the list goes last, which is the
250
323
  one ordering decision this component makes differently from the way it
251
324
  reads. Reasoning above renderMeta. -->
@@ -254,12 +327,21 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
254
327
  ${files.length === 0
255
328
  ? nothing
256
329
  : html `<ul part="list">
257
- ${files.map((file, at) => html `<li part="file">
330
+ ${files.map((file, at) => {
331
+ const state = this.statuses.get(file)?.state;
332
+ const done = state === 'done';
333
+ const failed = state === 'error';
334
+ const sending = state === 'uploading';
335
+ return html `<li
336
+ part="file${done ? ' file-done' : ''}${failed ? ' file-error' : ''}${sending ? ' file-uploading' : ''}"
337
+ >
258
338
  <span part="detail">
259
- <span part="name">${file.name}</span>
260
- <span part="meta">
261
- ${this.renderMeta(file)}
262
- </span>
339
+ ${this.renderFile
340
+ ? this.renderFile(file, at)
341
+ : html `<span part="name">${file.name}</span>
342
+ <span part="meta">
343
+ ${this.renderMeta(file)}
344
+ </span>`}${this.renderFileStatus(file)}
263
345
  </span>
264
346
  <button
265
347
  part="remove"
@@ -269,15 +351,19 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
269
351
  ?disabled=${this.isDisabled}
270
352
  @click=${this.handleRemove}
271
353
  >
272
- ${drawIcon(trash)}
354
+ ${this.removeMark(at)}
273
355
  </button>
274
- </li>`)}
356
+ </li>`;
357
+ })}
275
358
  </ul>`}`;
276
359
  }
277
360
  };
278
361
  __decorate([
279
362
  property()
280
363
  ], HelixHeadlessFileInput.prototype, "accept", void 0);
364
+ __decorate([
365
+ property({ attribute: 'added-text' })
366
+ ], HelixHeadlessFileInput.prototype, "addedText", void 0);
281
367
  __decorate([
282
368
  property()
283
369
  ], HelixHeadlessFileInput.prototype, "capture", void 0);
@@ -296,12 +382,24 @@ __decorate([
296
382
  __decorate([
297
383
  property({ reflect: true, type: Boolean })
298
384
  ], HelixHeadlessFileInput.prototype, "multiple", void 0);
385
+ __decorate([
386
+ property({ attribute: 'progress-label' })
387
+ ], HelixHeadlessFileInput.prototype, "progressLabel", void 0);
299
388
  __decorate([
300
389
  property({ attribute: 'rejected-text' })
301
390
  ], HelixHeadlessFileInput.prototype, "rejectedText", void 0);
391
+ __decorate([
392
+ property({ attribute: 'removed-text' })
393
+ ], HelixHeadlessFileInput.prototype, "removedText", void 0);
302
394
  __decorate([
303
395
  property({ attribute: 'remove-label' })
304
396
  ], HelixHeadlessFileInput.prototype, "removeLabel", void 0);
397
+ __decorate([
398
+ property({ attribute: false })
399
+ ], HelixHeadlessFileInput.prototype, "renderFile", void 0);
400
+ __decorate([
401
+ property({ attribute: false })
402
+ ], HelixHeadlessFileInput.prototype, "transformFiles", void 0);
305
403
  __decorate([
306
404
  property({ attribute: 'trigger-label' })
307
405
  ], HelixHeadlessFileInput.prototype, "triggerLabel", void 0);
@@ -27,7 +27,8 @@ export declare const accepts: (file: File, accept: string) => boolean;
27
27
  /**
28
28
  * Screens arriving files against the rules, given the files the field keeps.
29
29
  * `kept` is what the field holds afterward: the files it keeps and the
30
- * arrivals it took, or for a single-file field the last arrival it took.
30
+ * arrivals it took, or for a single-file field the last arrival it took, and
31
+ * the file it keeps when it took none.
31
32
  */
32
33
  export declare const screenFiles: (arriving: File[], keeping: File[], rules: ScreenRules) => {
33
34
  kept: File[];
@@ -0,0 +1,24 @@
1
+ /**
2
+ * How one file's upload is going, as a page reports it to File Input. The
3
+ * field does not upload anything: the page does, and says how it is going.
4
+ */
5
+ export interface FileStatus {
6
+ /** How much is done, from 0 to 1. Left out, the bar moves without a measure while `state` is `uploading`. */
7
+ readonly progress?: number | undefined;
8
+ /** Where the upload is: under way, finished, or failed. */
9
+ readonly state?: 'done' | 'error' | 'uploading' | undefined;
10
+ /** Words beside the bar, such as "Uploaded" or the reason it failed. */
11
+ readonly text?: string | undefined;
12
+ }
13
+ /**
14
+ * The bar's value, held between 0 and 1, or nothing for a bar without a
15
+ * measure, and no bar at all for a status that is neither measured nor under
16
+ * way.
17
+ */
18
+ export declare const barOf: (status: FileStatus) => number | 'unmeasured' | undefined;
19
+ /**
20
+ * Whether a new status is worth saying: a finish or a failure with words, the
21
+ * first time the file reaches it. Progress is shown and not said, since a
22
+ * reader hearing every percent hears nothing else.
23
+ */
24
+ export declare const saysStatus: (next: FileStatus, was: FileStatus | undefined) => boolean;
@@ -0,0 +1,10 @@
1
+ export const barOf = (status) => {
2
+ const { progress, state } = status;
3
+ if (typeof progress === 'number' && Number.isFinite(progress)) {
4
+ return Math.min(1, Math.max(0, progress));
5
+ }
6
+ return state === 'uploading' ? 'unmeasured' : undefined;
7
+ };
8
+ export const saysStatus = (next, was) => Boolean(next.text) &&
9
+ (next.state === 'done' || next.state === 'error') &&
10
+ next.state !== was?.state;