@fusion.dev/helix-headless 0.144.0 → 0.146.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 (319) hide show
  1. package/custom-elements.json +15195 -4240
  2. package/dist/accordion/item/accordion-item.d.ts +62 -12
  3. package/dist/accordion/item/accordion-item.js +41 -10
  4. package/dist/accordion/root/accordion-root.d.ts +14 -5
  5. package/dist/accordion/root/accordion-root.js +7 -1
  6. package/dist/announcement/root/announcement-root.d.ts +24 -3
  7. package/dist/announcement/root/announcement-root.js +39 -2
  8. package/dist/app-header/root/app-header-root.d.ts +12 -0
  9. package/dist/app-header/root/app-header-root.js +13 -1
  10. package/dist/app-header/sectioning.d.ts +9 -0
  11. package/dist/app-header/sectioning.js +27 -0
  12. package/dist/avatar/group/avatar-group.d.ts +20 -14
  13. package/dist/avatar/group/avatar-group.js +17 -19
  14. package/dist/badge/root/badge-root.d.ts +3 -1
  15. package/dist/badge/root/badge-root.js +2 -2
  16. package/dist/bar-chart/root/bar-chart-root.d.ts +18 -1
  17. package/dist/bar-chart/root/bar-chart-root.js +52 -15
  18. package/dist/bar-chart/stack.d.ts +15 -0
  19. package/dist/bar-chart/stack.js +19 -0
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +57 -1
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +73 -3
  22. package/dist/button/root/button-root.d.ts +26 -0
  23. package/dist/button/root/button-root.js +38 -18
  24. package/dist/button-group/root/button-group-root.d.ts +2 -1
  25. package/dist/button-group/root/button-group-root.js +5 -1
  26. package/dist/canvas/node/canvas-node.d.ts +4 -3
  27. package/dist/canvas/node/canvas-node.js +3 -2
  28. package/dist/canvas/root/canvas-root.d.ts +38 -3
  29. package/dist/canvas/root/canvas-root.js +123 -42
  30. package/dist/card/root/card-root.d.ts +24 -3
  31. package/dist/card/root/card-root.js +20 -8
  32. package/dist/carousel/root/carousel-root.d.ts +133 -4
  33. package/dist/carousel/root/carousel-root.js +174 -24
  34. package/dist/carousel/stops.d.ts +20 -0
  35. package/dist/carousel/stops.js +30 -0
  36. package/dist/chat/composer/chat-composer.d.ts +38 -6
  37. package/dist/chat/composer/chat-composer.js +42 -9
  38. package/dist/chat/message/chat-message.d.ts +32 -1
  39. package/dist/chat/message/chat-message.js +27 -10
  40. package/dist/chat/naming.d.ts +26 -0
  41. package/dist/chat/naming.js +14 -0
  42. package/dist/chat/root/chat-root.d.ts +58 -0
  43. package/dist/chat/root/chat-root.js +43 -3
  44. package/dist/checkbox/root/checkbox-root.d.ts +20 -0
  45. package/dist/checkbox/root/checkbox-root.js +14 -1
  46. package/dist/checkbox-group/root/checkbox-group-root.d.ts +8 -2
  47. package/dist/checkbox-group/root/checkbox-group-root.js +12 -10
  48. package/dist/checkbox-group/select-all.d.ts +27 -0
  49. package/dist/checkbox-group/select-all.js +12 -0
  50. package/dist/code-block/highlight.d.ts +15 -0
  51. package/dist/code-block/highlight.js +4 -0
  52. package/dist/code-block/root/code-block-root.d.ts +57 -2
  53. package/dist/code-block/root/code-block-root.js +63 -8
  54. package/dist/color-input/root/color-input-root.d.ts +3 -0
  55. package/dist/color-input/root/color-input-root.js +6 -3
  56. package/dist/color-picker/root/color-picker-root.d.ts +33 -4
  57. package/dist/color-picker/root/color-picker-root.js +80 -21
  58. package/dist/color-picker/swatches.d.ts +12 -0
  59. package/dist/color-picker/swatches.js +26 -0
  60. package/dist/combobox/root/combobox-root.d.ts +43 -0
  61. package/dist/combobox/root/combobox-root.js +79 -15
  62. package/dist/command/root/command-root.d.ts +16 -1
  63. package/dist/command/root/command-root.js +22 -4
  64. package/dist/copy-button/root/copy-button-root.d.ts +26 -1
  65. package/dist/copy-button/root/copy-button-root.js +12 -5
  66. package/dist/data-table/index.d.ts +1 -1
  67. package/dist/data-table/index.js +1 -1
  68. package/dist/data-table/keys.d.ts +24 -0
  69. package/dist/data-table/keys.js +24 -0
  70. package/dist/data-table/root/data-table-root.d.ts +170 -13
  71. package/dist/data-table/root/data-table-root.js +294 -52
  72. package/dist/data-table/tree.d.ts +55 -0
  73. package/dist/data-table/tree.js +54 -0
  74. package/dist/date-input/root/date-input-root.d.ts +3 -1
  75. package/dist/date-input/root/date-input-root.js +18 -7
  76. package/dist/date-picker/root/date-picker-root.js +12 -6
  77. package/dist/description-list/item/description-list-item.d.ts +17 -2
  78. package/dist/description-list/item/description-list-item.js +15 -2
  79. package/dist/description-list/root/description-list-root.d.ts +27 -1
  80. package/dist/description-list/root/description-list-root.js +26 -1
  81. package/dist/dialog/description.js +15 -1
  82. package/dist/dialog/root/dialog-root.d.ts +8 -2
  83. package/dist/dialog/root/dialog-root.js +16 -1
  84. package/dist/divider/root/divider-root.d.ts +3 -0
  85. package/dist/divider/root/divider-root.js +2 -1
  86. package/dist/donut-chart/root/donut-chart-root.d.ts +31 -0
  87. package/dist/donut-chart/root/donut-chart-root.js +48 -1
  88. package/dist/drawer/reveal.d.ts +8 -0
  89. package/dist/drawer/reveal.js +1 -0
  90. package/dist/drawer/root/drawer-root.d.ts +59 -0
  91. package/dist/drawer/root/drawer-root.js +63 -4
  92. package/dist/drilldown/item/drilldown-item.d.ts +11 -6
  93. package/dist/drilldown/item/drilldown-item.js +8 -5
  94. package/dist/drilldown/root/drilldown-root.d.ts +29 -0
  95. package/dist/drilldown/root/drilldown-root.js +28 -5
  96. package/dist/element-events.generated.js +83 -12
  97. package/dist/empty/root/empty-root.d.ts +3 -1
  98. package/dist/empty/root/empty-root.js +3 -2
  99. package/dist/file-input/root/file-input-root.d.ts +69 -6
  100. package/dist/file-input/root/file-input-root.js +82 -19
  101. package/dist/file-input/screen.d.ts +2 -1
  102. package/dist/filter-input/menu/filter-menu.d.ts +5 -0
  103. package/dist/filter-input/menu/filter-menu.js +16 -4
  104. package/dist/filter-input/root/filter-input-root.d.ts +30 -6
  105. package/dist/filter-input/root/filter-input-root.js +1 -1
  106. package/dist/filter-input/suggest.d.ts +28 -3
  107. package/dist/filter-input/suggest.js +18 -8
  108. package/dist/format-date/root/format-date-root.d.ts +9 -16
  109. package/dist/format-date/root/format-date-root.js +10 -27
  110. package/dist/heatmap/root/heatmap-root.d.ts +24 -13
  111. package/dist/heatmap/root/heatmap-root.js +34 -12
  112. package/dist/icon/root/icon-root.d.ts +12 -1
  113. package/dist/icon/root/icon-root.js +21 -10
  114. package/dist/icon/settle.d.ts +7 -0
  115. package/dist/icon/settle.js +10 -0
  116. package/dist/icon-button/root/icon-button-root.d.ts +2 -0
  117. package/dist/image/root/image-root.d.ts +5 -1
  118. package/dist/image/root/image-root.js +10 -2
  119. package/dist/input/root/input-root.d.ts +13 -3
  120. package/dist/input/root/input-root.js +12 -3
  121. package/dist/kbd/root/kbd-root.d.ts +35 -1
  122. package/dist/kbd/root/kbd-root.js +23 -4
  123. package/dist/legend/root/legend-root.d.ts +55 -8
  124. package/dist/legend/root/legend-root.js +55 -13
  125. package/dist/lightbox/root/lightbox-root.d.ts +49 -6
  126. package/dist/lightbox/root/lightbox-root.js +86 -19
  127. package/dist/lightbox/zoom-controller.d.ts +59 -0
  128. package/dist/lightbox/zoom-controller.js +128 -0
  129. package/dist/lightbox/zoom.d.ts +22 -0
  130. package/dist/lightbox/zoom.js +16 -0
  131. package/dist/line-chart/root/line-chart-root.d.ts +22 -1
  132. package/dist/line-chart/root/line-chart-root.js +21 -1
  133. package/dist/list/item/list-item.d.ts +70 -1
  134. package/dist/list/item/list-item.js +77 -6
  135. package/dist/list/numbering.d.ts +14 -0
  136. package/dist/list/numbering.js +8 -0
  137. package/dist/list/root/list-root.d.ts +155 -9
  138. package/dist/list/root/list-root.js +246 -4
  139. package/dist/list/selection.d.ts +49 -0
  140. package/dist/list/selection.js +30 -0
  141. package/dist/listbox/root/listbox-root.d.ts +37 -1
  142. package/dist/listbox/root/listbox-root.js +49 -4
  143. package/dist/loader/busy.d.ts +10 -0
  144. package/dist/loader/busy.js +11 -0
  145. package/dist/loader/root/loader-root.d.ts +9 -2
  146. package/dist/loader/root/loader-root.js +23 -3
  147. package/dist/map/root/map-root.d.ts +6 -2
  148. package/dist/map/root/map-root.js +5 -2
  149. package/dist/megamenu/root/megamenu-root.d.ts +62 -0
  150. package/dist/megamenu/root/megamenu-root.js +90 -5
  151. package/dist/menu/index.d.ts +1 -0
  152. package/dist/menu/index.js +1 -0
  153. package/dist/menu/item/menu-item.d.ts +87 -11
  154. package/dist/menu/item/menu-item.js +78 -22
  155. package/dist/menu/root/menu-root.d.ts +39 -1
  156. package/dist/menu/root/menu-root.js +65 -8
  157. package/dist/menu/section/menu-section.d.ts +55 -0
  158. package/dist/menu/section/menu-section.js +60 -0
  159. package/dist/meter/root/meter-root.d.ts +20 -1
  160. package/dist/meter/root/meter-root.js +11 -15
  161. package/dist/navigation/root/navigation-root.d.ts +18 -2
  162. package/dist/navigation/root/navigation-root.js +29 -3
  163. package/dist/notifications/root/notifications-root.d.ts +25 -0
  164. package/dist/notifications/root/notifications-root.js +33 -5
  165. package/dist/number-input/root/number-input-root.d.ts +17 -8
  166. package/dist/number-input/root/number-input-root.js +2 -7
  167. package/dist/object-inspector/root/object-inspector-root.d.ts +161 -32
  168. package/dist/object-inspector/root/object-inspector-root.js +264 -123
  169. package/dist/object-inspector/rows.d.ts +75 -0
  170. package/dist/object-inspector/rows.js +119 -0
  171. package/dist/option/root/option-root.d.ts +5 -3
  172. package/dist/outline/levels.d.ts +31 -0
  173. package/dist/outline/levels.js +23 -0
  174. package/dist/outline/root/outline-root.d.ts +86 -3
  175. package/dist/outline/root/outline-root.js +95 -12
  176. package/dist/pagination/size/pagination-size.d.ts +12 -2
  177. package/dist/pagination/size/pagination-size.js +20 -5
  178. package/dist/pin-input/code.d.ts +10 -0
  179. package/dist/pin-input/code.js +13 -0
  180. package/dist/pin-input/root/pin-input-root.d.ts +25 -1
  181. package/dist/pin-input/root/pin-input-root.js +10 -11
  182. package/dist/popover/hide-outside.d.ts +14 -0
  183. package/dist/popover/hide-outside.js +41 -0
  184. package/dist/popover/root/popover-root.d.ts +57 -7
  185. package/dist/popover/root/popover-root.js +58 -3
  186. package/dist/progress/root/progress-root.d.ts +20 -0
  187. package/dist/progress/root/progress-root.js +15 -15
  188. package/dist/qr-code/export.d.ts +26 -1
  189. package/dist/qr-code/export.js +14 -0
  190. package/dist/qr-code/root/qr-code-root.d.ts +19 -5
  191. package/dist/qr-code/root/qr-code-root.js +58 -9
  192. package/dist/quote/marks.d.ts +8 -0
  193. package/dist/quote/marks.js +5 -0
  194. package/dist/quote/root/quote-root.d.ts +14 -2
  195. package/dist/quote/root/quote-root.js +23 -1
  196. package/dist/radio/group/radio-group.d.ts +4 -10
  197. package/dist/radio/group/radio-group.js +5 -10
  198. package/dist/radio/root/radio-root.d.ts +6 -2
  199. package/dist/radio/root/radio-root.js +6 -1
  200. package/dist/rating/root/rating-root.d.ts +10 -5
  201. package/dist/rating/root/rating-root.js +32 -8
  202. package/dist/rating/steps.d.ts +7 -0
  203. package/dist/rating/steps.js +9 -0
  204. package/dist/scatter-chart/fit.d.ts +15 -0
  205. package/dist/scatter-chart/fit.js +35 -0
  206. package/dist/scatter-chart/root/scatter-chart-root.d.ts +21 -1
  207. package/dist/scatter-chart/root/scatter-chart-root.js +30 -17
  208. package/dist/scroll-area/root/scroll-area-root.d.ts +84 -0
  209. package/dist/scroll-area/root/scroll-area-root.js +106 -7
  210. package/dist/scroll-area/scrollbar-controller.d.ts +111 -0
  211. package/dist/scroll-area/scrollbar-controller.js +172 -0
  212. package/dist/scroll-area/scrollbar.d.ts +92 -0
  213. package/dist/scroll-area/scrollbar.js +33 -0
  214. package/dist/select/root/select-root.d.ts +15 -0
  215. package/dist/select/root/select-root.js +18 -6
  216. package/dist/skeleton/root/skeleton-root.d.ts +3 -0
  217. package/dist/skeleton/root/skeleton-root.js +4 -2
  218. package/dist/skip-link/root/skip-link-root.js +2 -2
  219. package/dist/slider/root/slider-root.d.ts +41 -4
  220. package/dist/slider/root/slider-root.js +23 -3
  221. package/dist/sparkline/readings.d.ts +18 -0
  222. package/dist/sparkline/readings.js +19 -0
  223. package/dist/sparkline/root/sparkline-root.d.ts +78 -7
  224. package/dist/sparkline/root/sparkline-root.js +118 -23
  225. package/dist/speed-dial/root/speed-dial-root.js +2 -2
  226. package/dist/splitter/root/splitter-root.d.ts +20 -12
  227. package/dist/splitter/root/splitter-root.js +226 -203
  228. package/dist/splitter/snap.d.ts +9 -0
  229. package/dist/splitter/snap.js +12 -0
  230. package/dist/stat/root/stat-root.d.ts +14 -5
  231. package/dist/stat/root/stat-root.js +21 -5
  232. package/dist/steps/reach.d.ts +20 -0
  233. package/dist/steps/reach.js +15 -0
  234. package/dist/steps/root/steps-root.d.ts +76 -0
  235. package/dist/steps/root/steps-root.js +93 -1
  236. package/dist/steps/step/step.d.ts +45 -2
  237. package/dist/steps/step/step.js +33 -6
  238. package/dist/switch/root/switch-root.d.ts +10 -0
  239. package/dist/table/column/table-column.d.ts +11 -10
  240. package/dist/table/column/table-column.js +8 -19
  241. package/dist/table/root/table-root.d.ts +126 -4
  242. package/dist/table/root/table-root.js +156 -6
  243. package/dist/table/row/table-row.d.ts +47 -1
  244. package/dist/table/row/table-row.js +39 -2
  245. package/dist/tabs/panel/tab-panel.d.ts +22 -5
  246. package/dist/tabs/panel/tab-panel.js +34 -6
  247. package/dist/tabs/root/tabs-root.d.ts +37 -1
  248. package/dist/tabs/root/tabs-root.js +22 -4
  249. package/dist/tabs/tab/tab.d.ts +41 -4
  250. package/dist/tabs/tab/tab.js +49 -3
  251. package/dist/tag/group/tag-group.d.ts +31 -4
  252. package/dist/tag/group/tag-group.js +46 -7
  253. package/dist/tag/root/tag-root.js +9 -2
  254. package/dist/textarea/root/textarea-root.d.ts +28 -1
  255. package/dist/textarea/root/textarea-root.js +40 -1
  256. package/dist/time-input/root/time-input-root.d.ts +5 -1
  257. package/dist/time-input/root/time-input-root.js +16 -8
  258. package/dist/time-picker/root/time-picker-root.d.ts +18 -4
  259. package/dist/time-picker/root/time-picker-root.js +7 -10
  260. package/dist/time-picker/wheel-name.d.ts +9 -0
  261. package/dist/time-picker/wheel-name.js +15 -0
  262. package/dist/timeline/group/timeline-group.d.ts +46 -0
  263. package/dist/timeline/group/timeline-group.js +37 -0
  264. package/dist/timeline/index.d.ts +1 -0
  265. package/dist/timeline/index.js +1 -0
  266. package/dist/timeline/root/timeline-root.d.ts +10 -3
  267. package/dist/toast/item/toast-item.d.ts +24 -18
  268. package/dist/toast/item/toast-item.js +36 -40
  269. package/dist/toast/root/toast-root.d.ts +6 -0
  270. package/dist/toast/root/toast-root.js +16 -1
  271. package/dist/toggle/group/toggle-group.d.ts +24 -3
  272. package/dist/toggle/group/toggle-group.js +34 -5
  273. package/dist/toolbar/root/toolbar-root.d.ts +13 -0
  274. package/dist/toolbar/root/toolbar-root.js +15 -5
  275. package/dist/tooltip/root/tooltip-root.d.ts +13 -2
  276. package/dist/tooltip/root/tooltip-root.js +9 -1
  277. package/dist/tour/back.d.ts +10 -0
  278. package/dist/tour/back.js +8 -0
  279. package/dist/tour/root/tour-root.d.ts +30 -2
  280. package/dist/tour/root/tour-root.js +34 -8
  281. package/dist/tree/item/tree-item.d.ts +61 -4
  282. package/dist/tree/item/tree-item.js +92 -20
  283. package/dist/tree/root/tree-root.d.ts +58 -0
  284. package/dist/tree/root/tree-root.js +80 -16
  285. package/dist/tree/selection.d.ts +24 -0
  286. package/dist/tree/selection.js +13 -0
  287. package/dist/trend/root/trend-root.d.ts +43 -18
  288. package/dist/trend/root/trend-root.js +31 -6
  289. package/dist/video/media-events.d.ts +44 -0
  290. package/dist/video/media-events.js +35 -0
  291. package/dist/video/root/video-root.d.ts +117 -18
  292. package/dist/video/root/video-root.js +93 -47
  293. package/dist/video-playlist/menu/video-playlist-menu.d.ts +5 -0
  294. package/dist/video-playlist/menu/video-playlist-menu.js +3 -1
  295. package/dist/video-playlist/order.d.ts +41 -0
  296. package/dist/video-playlist/order.js +34 -0
  297. package/dist/video-playlist/root/video-playlist-root.d.ts +77 -3
  298. package/dist/video-playlist/root/video-playlist-root.js +60 -16
  299. package/dist/virtual-list/controls.d.ts +4 -0
  300. package/dist/virtual-list/controls.js +30 -0
  301. package/dist/virtual-list/fill.d.ts +22 -0
  302. package/dist/virtual-list/fill.js +23 -0
  303. package/dist/virtual-list/keys.d.ts +15 -0
  304. package/dist/virtual-list/keys.js +24 -0
  305. package/dist/virtual-list/plan.d.ts +49 -0
  306. package/dist/virtual-list/plan.js +16 -0
  307. package/dist/virtual-list/root/virtual-list-root.d.ts +259 -26
  308. package/dist/virtual-list/root/virtual-list-root.js +333 -49
  309. package/lint.json +1 -1
  310. package/package.json +8 -8
  311. package/types/jsx.d.ts +8113 -1394
  312. package/types/svelte.d.ts +2 -0
  313. package/types/vue.d.ts +1678 -353
  314. package/dist/data-table/root/order.d.ts +0 -141
  315. package/dist/data-table/root/order.js +0 -76
  316. package/dist/listbox/range.d.ts +0 -17
  317. package/dist/listbox/range.js +0 -5
  318. package/dist/tag/selection.d.ts +0 -10
  319. package/dist/tag/selection.js +0 -12
@@ -13,12 +13,36 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
13
13
  * its tokens show a selection it owns, drawn from the options it was given.
14
14
  *
15
15
  * `value` is the typed text. Every keystroke fires `input` with the text on the
16
- * element, so a table can narrow as the user types. A form receives the text
17
- * under `name`, and then one more entry under `name` for each token: its
18
- * `value` attribute, or for a `FilterChip`, its field and values written
19
- * `field:values` as the field reads them typed. Backspace in an empty field fires
20
- * `helix-dismiss` on the last token, the same event its remove control fires,
21
- * so one handler covers the pointer and the keyboard.
16
+ * element, so a table can narrow as the user types. Backspace in an empty field
17
+ * fires `helix-dismiss` on the last token, the same event its remove control
18
+ * fires, so one handler covers the pointer and the keyboard.
19
+ *
20
+ * **What a form receives.** The text under `name`, and then one more entry
21
+ * under `name` for each token, in the order the tokens stand:
22
+ *
23
+ * | Token | Entry |
24
+ * |---|---|
25
+ * | Any element with a `value` attribute | The value as written: `saved:mine` |
26
+ * | `FilterChip` with `name` and values | `status:open,pending` |
27
+ * | The same, with `operator="none"` (what a typed `-status:` makes) | `-status:open,pending` |
28
+ * | The same, with any other `operator` except `any` | `created[after]:2024` |
29
+ * | A token with neither | Nothing |
30
+ *
31
+ * The values are the chip's text outside its `field` and `operator` slots,
32
+ * with the spaces around each comma removed. A server splits an entry at its
33
+ * first colon, reads a leading `-` as the negation, and reads a word in
34
+ * brackets before the colon as the comparison.
35
+ *
36
+ * **Keyboard.** Focus stays in the text. The text names the highlighted
37
+ * suggestion as its `aria-activedescendant`.
38
+ *
39
+ * | Key | What it does |
40
+ * |---|---|
41
+ * | Enter | Presses the highlighted suggestion. With none highlighted, reads the typed filters into chips, or submits the form when there are none. |
42
+ * | Down, Up | Moves the highlight through the suggestions, wrapping, and opens the panel. |
43
+ * | Home, End | Highlights the first or the last suggestion while the panel is open. |
44
+ * | Escape | Closes the panel and stops there, so a dialog around the field stays open. |
45
+ * | Backspace | In an empty field, asks the last token to go. |
22
46
  *
23
47
  * **Typed filters.** The field parses only the fields you declare. Write
24
48
  * `FilterField` elements, usually inside the `FilterMenu` slotted into `start`,
@@ -364,7 +364,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
364
364
  super.updated(changed);
365
365
  this.releaseCaret?.();
366
366
  this.releaseCaret = undefined;
367
- this.internals.setFormValue(this.formValue());
367
+ this.internals.setFormValue(this.formValue(), this.value);
368
368
  this.sayCount();
369
369
  const control = this.control;
370
370
  if (control) {
@@ -9,9 +9,34 @@
9
9
  export declare const currentTerm: (text: string, caret?: number) => string;
10
10
  /** Whether a suggestion's words hold the term, ignoring case and accents. */
11
11
  export declare const suggests: (words: string, term: string) => boolean;
12
+ /** What a token says about the filter it shows, as `tokenEntry` reads it. */
13
+ export interface TokenWords {
14
+ /** The field it filters, from its `name` attribute. */
15
+ readonly field: string | null;
16
+ /** The comparison, from its `operator` attribute. */
17
+ readonly operator: string | null;
18
+ /** The values it shows, from its text. */
19
+ readonly text: string;
20
+ /** What it sends as written, from its `value` attribute. */
21
+ readonly value: string | null;
22
+ }
12
23
  /**
13
- * What a token sends a form: its `value` attribute, or for a token naming a
14
- * field, the field and the values it shows, written `field:values` the way the
15
- * field reads them typed. Nothing for a token that says neither.
24
+ * The form entry for a token's words: its `value` where it has one, or else
25
+ * `field:values`, the way the field reads a filter typed.
26
+ *
27
+ * - The values are the token's text, with the spaces around each comma taken
28
+ * out: `status:open,pending`.
29
+ * - The comparison named `none`, which a typed `-field:` asks for, leads with a
30
+ * `-`: `-team:ops`.
31
+ * - Any comparison other than `any` and `none` is written in brackets after
32
+ * the field, so a chip that says `is after` keeps it: `created[after]:2024`.
33
+ * `any`, or no `operator`, writes none.
34
+ * - A token naming no field, or showing no values, sends nothing.
35
+ */
36
+ export declare const entryFor: ({ field, operator, text, value }: TokenWords) => string | null;
37
+ /**
38
+ * The form entry for a slotted token. See `entryFor`. The values are the text
39
+ * of the token's unslotted content, so a name or a comparison slotted into a
40
+ * chip's `field` or `operator` is not read as a value.
16
41
  */
17
42
  export declare const tokenEntry: (token: Element) => string | null;
@@ -8,16 +8,26 @@ export const suggests = (words, term) => {
8
8
  const plain = (text) => text.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase();
9
9
  return term === '' || plain(words).includes(plain(term));
10
10
  };
11
- export const tokenEntry = (token) => {
12
- const written = token.getAttribute('value');
13
- if (written !== null) {
14
- return written;
11
+ export const entryFor = ({ field, operator, text, value }) => {
12
+ if (value !== null) {
13
+ return value;
15
14
  }
16
- const field = token.getAttribute('name');
17
- const values = (token.textContent ?? '').replace(/\s+/gu, ' ').trim();
15
+ const values = text.replace(/\s+/gu, ' ').trim();
18
16
  if (!field || values === '') {
19
17
  return null;
20
18
  }
21
- const negated = token.getAttribute('operator')?.startsWith('not') ? '-' : '';
22
- return `${negated}${field}:${values.replace(/\s*,\s*/gu, ',')}`;
19
+ const shown = values.replace(/\s*,\s*/gu, ',');
20
+ if (operator === 'none') {
21
+ return `-${field}:${shown}`;
22
+ }
23
+ return operator && operator !== 'any' ? `${field}[${operator}]:${shown}` : `${field}:${shown}`;
23
24
  };
25
+ export const tokenEntry = (token) => entryFor({
26
+ field: token.getAttribute('name'),
27
+ operator: token.getAttribute('operator'),
28
+ text: [...token.childNodes]
29
+ .filter((node) => !(node.nodeType === 1 && node.hasAttribute('slot')))
30
+ .map((node) => node.textContent ?? '')
31
+ .join(''),
32
+ value: token.getAttribute('value'),
33
+ });
@@ -154,18 +154,21 @@ export declare class HelixHeadlessFormatDate extends HelixElement {
154
154
  */
155
155
  year?: 'numeric' | '2-digit';
156
156
  /**
157
- * The clock this element last drew against.
157
+ * The clock this element last drew against, moved on when the reading goes
158
+ * stale.
158
159
  *
159
- * A state field and not a read of `Date.now()` inside `render`, which would
160
- * make the template a function of something Lit cannot see change: the element
161
- * would draw the right text once and never notice it had gone stale.
160
+ * A controller's time and not a read of `Date.now()` inside `render`, which
161
+ * would make the template a function of something Lit cannot see change: the
162
+ * element would draw the right text once and never notice it had gone stale.
163
+ * The formatter says when its own answer expires, so the clock wakes at
164
+ * exactly that moment rather than polling: an item minutes old once a minute,
165
+ * an item hours old once an hour, and an item showing a date never.
162
166
  */
163
- private clock;
167
+ private readonly clock;
164
168
  /**
165
169
  * The date read out of the light DOM, where no attribute carried one.
166
170
  */
167
171
  private written;
168
- private stopTicking;
169
172
  private watching;
170
173
  /** Whether the timestamp is a day with no time, which is formatted in UTC. */
171
174
  private get dateOnly();
@@ -184,16 +187,6 @@ export declare class HelixHeadlessFormatDate extends HelixElement {
184
187
  /** The light DOM's own text, which is the other way to write the value. */
185
188
  private read;
186
189
  private watch;
187
- /**
188
- * Schedules the next reading, and only the next one.
189
- *
190
- * The formatter says when its own answer expires, so this asks for exactly
191
- * that moment rather than polling: an item minutes old wakes once a minute, an
192
- * item hours old once an hour, and an item showing a date never, which is the
193
- * difference between a feed that costs nothing to leave open and one that
194
- * does.
195
- */
196
- private tick;
197
190
  protected render(): TemplateResult | typeof nothing;
198
191
  }
199
192
  declare global {
@@ -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 { customElement, elapsedTime, HelixElement, relativeTime, subscribe, } from '@fusion.dev/helix-core';
7
+ import { customElement, elapsedTime, HelixElement, RelativeClockController, relativeTime, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { formatOptions } from '../options.js';
@@ -15,9 +15,13 @@ let HelixHeadlessFormatDate = class HelixHeadlessFormatDate extends HelixElement
15
15
  this.date = '';
16
16
  this.elapsed = false;
17
17
  this.locale = '';
18
- this.clock = undefined;
18
+ this.clock = new RelativeClockController(this, {
19
+ expires: (now) => {
20
+ const moment = this.moment;
21
+ return moment && this.reading ? this.readingAt(moment, now).next : undefined;
22
+ },
23
+ });
19
24
  this.written = '';
20
- this.stopTicking = undefined;
21
25
  this.watching = undefined;
22
26
  }
23
27
  get dateOnly() {
@@ -62,15 +66,12 @@ let HelixHeadlessFormatDate = class HelixHeadlessFormatDate extends HelixElement
62
66
  }
63
67
  connectedCallback() {
64
68
  super.connectedCallback();
65
- this.tick();
66
69
  if (this.hasUpdated) {
67
70
  this.watch();
68
71
  }
69
72
  }
70
73
  disconnectedCallback() {
71
74
  super.disconnectedCallback();
72
- this.stopTicking?.();
73
- this.stopTicking = undefined;
74
75
  this.watching?.disconnect();
75
76
  this.watching = undefined;
76
77
  }
@@ -89,7 +90,7 @@ let HelixHeadlessFormatDate = class HelixHeadlessFormatDate extends HelixElement
89
90
  changed.has('locale') ||
90
91
  changed.has('numeric') ||
91
92
  changed.has('relative')) {
92
- this.tick();
93
+ this.clock.tick();
93
94
  }
94
95
  }
95
96
  read() {
@@ -99,7 +100,7 @@ let HelixHeadlessFormatDate = class HelixHeadlessFormatDate extends HelixElement
99
100
  const written = this.textContent?.trim() ?? '';
100
101
  if (written !== this.written) {
101
102
  this.written = written;
102
- this.tick();
103
+ this.clock.tick();
103
104
  }
104
105
  }
105
106
  watch() {
@@ -107,21 +108,6 @@ let HelixHeadlessFormatDate = class HelixHeadlessFormatDate extends HelixElement
107
108
  this.watching = new MutationObserver(() => this.read());
108
109
  this.watching.observe(this, { characterData: true, childList: true, subtree: true });
109
110
  }
110
- tick() {
111
- this.stopTicking?.();
112
- this.stopTicking = undefined;
113
- const moment = this.moment;
114
- if (!moment || !this.reading) {
115
- return;
116
- }
117
- const now = new Date();
118
- this.clock = now.getTime();
119
- const { next } = this.readingAt(moment, now);
120
- if (!Number.isFinite(next) || !this.isConnected) {
121
- return;
122
- }
123
- this.stopTicking = subscribe(next, () => this.tick());
124
- }
125
111
  render() {
126
112
  const moment = this.moment;
127
113
  if (!moment) {
@@ -132,7 +118,7 @@ let HelixHeadlessFormatDate = class HelixHeadlessFormatDate extends HelixElement
132
118
  const text = new Intl.DateTimeFormat(this.locale || undefined, this.options).format(moment);
133
119
  return html `<time part="time" datetime=${datetime}>${text}</time>`;
134
120
  }
135
- const now = new Date(this.clock ?? Date.now());
121
+ const now = new Date(this.clock.now);
136
122
  const zone = this.timeZone || (this.dateOnly ? 'UTC' : undefined);
137
123
  const whole = new Intl.DateTimeFormat(this.locale || undefined, {
138
124
  dateStyle: 'long',
@@ -203,9 +189,6 @@ __decorate([
203
189
  __decorate([
204
190
  property()
205
191
  ], HelixHeadlessFormatDate.prototype, "year", void 0);
206
- __decorate([
207
- state()
208
- ], HelixHeadlessFormatDate.prototype, "clock", void 0);
209
192
  __decorate([
210
193
  state()
211
194
  ], HelixHeadlessFormatDate.prototype, "written", void 0);
@@ -1,6 +1,7 @@
1
1
  import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../tooltip/index.js';
4
+ import { type Reading } from './buckets.js';
4
5
  declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface);
5
6
  /**
6
7
  * @summary A strip of cells, each colored by its reading, for uptime, contributions, or activity.
@@ -30,7 +31,13 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
30
31
  * down arrows move a cell, the left and right arrows move a column, which is a
31
32
  * week with `rows="7"`, and Home and End go to the first and the last. The
32
33
  * tooltip follows, the `live` part says the cell's row in words, and Escape
33
- * puts the tooltip away.
34
+ * puts the tooltip away. PageUp and PageDown move four columns, which is four
35
+ * weeks with `rows="7"`.
36
+ *
37
+ * **The strip describes itself.** The `overview` part says the busiest
38
+ * reading and the total of the readings drawn, "Busiest: 11 Jun 12; Total
39
+ * 84", and the strip is described by it, as every chart is by its summary.
40
+ * Hide it visually. The `summary` slot is still yours, for words of your own.
34
41
  *
35
42
  * @since 0.1.0
36
43
  * @status stable
@@ -51,6 +58,7 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
51
58
  * @csspart footer - The three-slot row under the strip.
52
59
  * @csspart live - The readout: a polite live region that says the cell the keys moved to. Hide it visually.
53
60
  * @csspart mark - The invisible point the tooltip is anchored to.
61
+ * @csspart overview - The busiest reading and the total of the readings drawn, written as a sentence for screen readers, which describes the strip. Rendered once there are cells. Hide it visually.
54
62
  * @csspart start - The leading slot's box.
55
63
  * @csspart strip - The grid of cells, which takes focus to be read by key. It is named by the host's `aria-labelledby` or `aria-label`, then by `label`, then by the caption of the table.
56
64
  * @csspart summary - The middle slot's box.
@@ -87,6 +95,10 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
87
95
  * @cssproperty --heatmap-tone-warning - The color of the `warning` tone.
88
96
  */
89
97
  export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
98
+ /**
99
+ * The word the `overview` puts before the busiest reading.
100
+ */
101
+ busiestLabel: string;
90
102
  /**
91
103
  * Shown when the table has no readings in it.
92
104
  *
@@ -151,6 +163,10 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
151
163
  * no order in it.
152
164
  */
153
165
  tones: string;
166
+ /**
167
+ * The word the `overview` puts before the total of the readings drawn.
168
+ */
169
+ totalLabel: string;
154
170
  /**
155
171
  * How many cells are actually drawn.
156
172
  *
@@ -197,18 +213,6 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
197
213
  * image a reader lands on. The host is a generic around it.
198
214
  */
199
215
  protected readonly aria: AriaForwardController;
200
- /**
201
- * Hears an `aria-label` written on the element, which no property mirrors.
202
- *
203
- * Lit observes the attributes of its own properties only, and `ariaLabel`
204
- * belongs to the platform.
205
- */
206
- static get observedAttributes(): string[];
207
- /**
208
- * Called by the browser when an observed attribute changes. A new
209
- * `aria-label` asks for an update, because it names the strip.
210
- */
211
- attributeChangedCallback(name: string, old: string | null, value: string | null): void;
212
216
  private observer;
213
217
  private announced;
214
218
  /**
@@ -314,6 +318,13 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
314
318
  private markEmpty;
315
319
  protected updated(changed: PropertyValues): void;
316
320
  protected render(): TemplateResult;
321
+ /**
322
+ * What the strip shows, in a sentence: the busiest reading drawn, as the
323
+ * table writes it, and the total of them all, in `locale`. The strip is
324
+ * described by it, so a reader landing on the strip hears what it holds
325
+ * before walking it.
326
+ */
327
+ protected renderOverview(cells: Reading[]): TemplateResult;
317
328
  /**
318
329
  * The bubble over the cell a pointer is resting on.
319
330
  *
@@ -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, byRow, CHART_PLOT, customElement, drawTooltip, HelixElement, HelixResizeEvent, MixinChartSource, markRegion, searchableById, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, byRow, CHART_PLOT, customElement, drawTooltip, formatNumbers, HelixElement, HelixResizeEvent, MixinChartSource, markRegion, searchableById, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
@@ -12,12 +12,14 @@ import { fits, toneOf } from './buckets.js';
12
12
  let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(HelixElement) {
13
13
  constructor() {
14
14
  super(...arguments);
15
+ this.busiestLabel = 'Busiest';
15
16
  this.emptyText = 'No readings';
16
17
  this.gap = 2;
17
18
  this.minCell = 8;
18
19
  this.rows = 1;
19
20
  this.thresholds = '';
20
21
  this.tones = '';
22
+ this.totalLabel = 'Total';
21
23
  this.width = undefined;
22
24
  this.metrics = undefined;
23
25
  this.aria = new AriaForwardController(this, {
@@ -42,15 +44,6 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
42
44
  get visible() {
43
45
  return Math.min(this.points.length, this.room);
44
46
  }
45
- static get observedAttributes() {
46
- return [...super.observedAttributes, 'aria-label'];
47
- }
48
- attributeChangedCallback(name, old, value) {
49
- super.attributeChangedCallback(name, old, value);
50
- if (name === 'aria-label') {
51
- this.requestUpdate();
52
- }
53
- }
54
47
  get points() {
55
48
  const series = this.series[0];
56
49
  if (!series) {
@@ -151,7 +144,8 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
151
144
  const cells = this.drawn;
152
145
  const readable = cells.length > 0;
153
146
  return html `<div
154
- aria-label=${this.getAttribute?.('aria-label') || this.named || 'Heatmap'}
147
+ aria-describedby=${readable ? 'helix-chart-summary' : nothing}
148
+ aria-label=${this.named || 'Heatmap'}
155
149
  part="strip"
156
150
  id=${CHART_PLOT}
157
151
  role=${readable ? 'application' : 'img'}
@@ -184,7 +178,29 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
184
178
  <span part="start"><slot name="start"></slot></span>
185
179
  <span part="summary"><slot name="summary"></slot></span>
186
180
  <span part="end"><slot name="end"></slot></span>
187
- </div>`;
181
+ </div>${readable ? this.renderOverview(cells) : nothing}`;
182
+ }
183
+ renderOverview(cells) {
184
+ const rows = this.sourceRows;
185
+ const offset = this.points.length - cells.length;
186
+ const column = this.plottedColumns[0] ?? -1;
187
+ let busiest = -1;
188
+ let total = 0;
189
+ cells.forEach((cell, index) => {
190
+ if (cell.value === undefined) {
191
+ return;
192
+ }
193
+ total += cell.value;
194
+ if (busiest < 0 || cell.value > (cells[busiest]?.value ?? 0)) {
195
+ busiest = index;
196
+ }
197
+ });
198
+ const row = rows[offset + busiest];
199
+ const most = row
200
+ ? `${this.busiestLabel}: ${row.label} ${row.cells[column]?.text ?? ''}`.trim()
201
+ : '';
202
+ const sum = `${this.totalLabel} ${formatNumbers(this.locale)(total)}`;
203
+ return html `<p id="helix-chart-summary" part="overview">${most ? `${most}; ${sum}` : sum}.</p>`;
188
204
  }
189
205
  renderTooltip() {
190
206
  const at = this.pointer.at;
@@ -203,6 +219,9 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
203
219
  }, undefined, this.pointer);
204
220
  }
205
221
  };
222
+ __decorate([
223
+ property({ attribute: 'busiest-label' })
224
+ ], HelixHeadlessHeatmap.prototype, "busiestLabel", void 0);
206
225
  __decorate([
207
226
  property({ attribute: 'empty-text' })
208
227
  ], HelixHeadlessHeatmap.prototype, "emptyText", void 0);
@@ -221,6 +240,9 @@ __decorate([
221
240
  __decorate([
222
241
  property()
223
242
  ], HelixHeadlessHeatmap.prototype, "tones", void 0);
243
+ __decorate([
244
+ property({ attribute: 'total-label' })
245
+ ], HelixHeadlessHeatmap.prototype, "totalLabel", void 0);
224
246
  __decorate([
225
247
  state()
226
248
  ], HelixHeadlessHeatmap.prototype, "width", void 0);
@@ -28,7 +28,8 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
28
28
  * rendered is drawn then.
29
29
  *
30
30
  * A name with no match renders nothing, and the element fires `error`, so a
31
- * typo in a name can be heard.
31
+ * typo in a name can be heard. It waits until the page has loaded and painted
32
+ * first, so a library a later module registers is not taken for a typo.
32
33
  *
33
34
  * An icon is decorative by default and hidden from assistive technology. Set
34
35
  * `label` only when the icon is the only carrier of meaning, such as in a
@@ -120,6 +121,16 @@ export declare class HelixHeadlessIcon extends HelixElement {
120
121
  private lookup;
121
122
  protected willUpdate(changed: PropertyValues): void;
122
123
  protected updated(changed: PropertyValues): void;
124
+ /** The name a report is waiting on the page to settle for, so it is asked once. */
125
+ private pending;
126
+ /**
127
+ * Says a name drew nothing, once the page has had its chance to register
128
+ * it: a library registered by a module further down the page, or by the
129
+ * page's own script after this element, is not a missing glyph. Asked again
130
+ * then, and said only where the name still draws nothing and nothing is
131
+ * still on its way.
132
+ */
133
+ private reportMissing;
123
134
  protected render(): TemplateResult | typeof nothing;
124
135
  }
125
136
  declare global {
@@ -9,6 +9,7 @@ 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 { resolveIcon, watchIcons } from '../registry.js';
12
+ import { afterPageSettles } from '../settle.js';
12
13
  let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
13
14
  constructor() {
14
15
  super(...arguments);
@@ -18,6 +19,7 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
18
19
  this.missed = '';
19
20
  this.waiting = false;
20
21
  this.arrived = '';
22
+ this.pending = '';
21
23
  }
22
24
  static get observedAttributes() {
23
25
  return [...super.observedAttributes, 'aria-label'];
@@ -69,22 +71,31 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
69
71
  }
70
72
  this.arrived = '';
71
73
  if (this.drawn === undefined && this.name !== '' && !this.waiting) {
72
- if (this.missed !== this.name) {
73
- this.missed = this.name;
74
- this.dispatchEvent(new Event('error', { bubbles: false, cancelable: false, composed: false }));
75
- reportRuntime({
76
- kind: 'unknown-icon',
77
- message: `<${this.localName} name="${this.name}"> names no registered icon, so it draws ` +
78
- 'nothing. Check the name, register the glyph with `registerIcons`, or import it ' +
79
- 'and pass it as `icon`.',
80
- tag: this.localName,
81
- });
74
+ if (this.missed !== this.name && this.pending !== this.name) {
75
+ this.pending = this.name;
76
+ afterPageSettles(() => this.reportMissing());
82
77
  }
83
78
  }
84
79
  else {
85
80
  this.missed = '';
86
81
  }
87
82
  }
83
+ reportMissing() {
84
+ const name = this.pending;
85
+ this.pending = '';
86
+ if (this.name !== name || this.drawn !== undefined || this.waiting || !this.isConnected) {
87
+ return;
88
+ }
89
+ this.missed = name;
90
+ this.dispatchEvent(new Event('error', { bubbles: false, cancelable: false, composed: false }));
91
+ reportRuntime({
92
+ kind: 'unknown-icon',
93
+ message: `<${this.localName} name="${name}"> names no registered icon, so it draws ` +
94
+ 'nothing. Check the name, register the glyph with `registerIcons`, or import it ' +
95
+ 'and pass it as `icon`.',
96
+ tag: this.localName,
97
+ });
98
+ }
88
99
  render() {
89
100
  const icon = this.drawn;
90
101
  if (!icon) {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Calls back once the page has loaded and painted a frame after it: every
3
+ * module a page lists has run by then, and with it every `registerIcons` and
4
+ * `registerIconLibrary` they hold. A page that has loaded already waits only
5
+ * the frame.
6
+ */
7
+ export declare const afterPageSettles: (callback: () => void) => void;
@@ -0,0 +1,10 @@
1
+ export const afterPageSettles = (callback) => {
2
+ const frame = () => {
3
+ requestAnimationFrame(() => callback());
4
+ };
5
+ if (document.readyState === 'complete') {
6
+ frame();
7
+ return;
8
+ }
9
+ window.addEventListener('load', frame, { once: true });
10
+ };
@@ -17,6 +17,8 @@ import { HelixHeadlessButton } from '../../button/root/button-root.js';
17
17
  * mean a part of a button, and an icon-only button is a complete button with
18
18
  * different content and a different shape.
19
19
  *
20
+ * **Keyboard.** Enter and Space press the button, as `Button`'s do.
21
+ *
20
22
  * @since 0.1.0
21
23
  * @status stable
22
24
  * @category actions
@@ -12,13 +12,17 @@ import { type PropertyValues, type TemplateResult } from 'lit';
12
12
  * The `placeholder` slot is empty by default, so the headless tier does not
13
13
  * depend on `Skeleton`, which it does not ship.
14
14
  *
15
+ * The placeholder is hidden from screen readers, the picture having a name of
16
+ * its own. The fallback is read once the picture fails, after the picture's
17
+ * `alt`, so a reader hears that it did not load, as a sighted reader sees.
18
+ *
15
19
  * @since 0.1.0
16
20
  * @status stable
17
21
  * @category media
18
22
  * @aliases Picture, Img, Figure
19
23
  * @documentation https://fusion.dev/helix/components/image
20
24
  *
21
- * @slot fallback - Shown in place of the image if it fails to load.
25
+ * @slot fallback - Shown in place of the image if it fails to load, and read by screen readers then, such as "Photo unavailable".
22
26
  * @slot placeholder - Shown until the image loads.
23
27
  *
24
28
  * @event {Event} error - The image failed to load. Relayed from the inner image, whose own event stays inside the shadow root.
@@ -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 { customElement, DelayController, HelixElement } from '@fusion.dev/helix-core';
7
+ import { customElement, DelayController, HelixElement, reportRuntime } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
@@ -100,6 +100,14 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
100
100
  firstUpdated(changed) {
101
101
  super.firstUpdated?.(changed);
102
102
  this.settle();
103
+ if (this.alt === undefined && this.getAttribute('aria-hidden') !== 'true') {
104
+ reportRuntime({
105
+ kind: 'missing-alt',
106
+ message: `<${this.localName}> has no \`alt\`, so a screen reader announces its file name. ` +
107
+ 'Describe the picture in `alt`, or write `alt=""` for one that is decoration.',
108
+ tag: this.localName,
109
+ });
110
+ }
103
111
  }
104
112
  get image() {
105
113
  return this.renderRoot?.querySelector('img') ?? null;
@@ -139,7 +147,7 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
139
147
  ></span>
140
148
  <span
141
149
  part="fallback"
142
- aria-hidden="true"
150
+ aria-hidden=${this.status === 'error' ? nothing : 'true'}
143
151
  ?hidden=${this.status !== 'error'}
144
152
  ><slot name="fallback"></slot
145
153
  ></span>`;