@fusion.dev/helix-headless 0.146.0 → 0.148.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 (288) hide show
  1. package/custom-elements.json +9287 -1084
  2. package/dist/accordion/item/accordion-item.d.ts +43 -21
  3. package/dist/accordion/item/accordion-item.js +37 -11
  4. package/dist/accordion/root/accordion-root.d.ts +8 -2
  5. package/dist/accordion/root/accordion-root.js +6 -8
  6. package/dist/announcement/root/announcement-root.d.ts +66 -6
  7. package/dist/announcement/root/announcement-root.js +144 -13
  8. package/dist/area-chart/root/area-chart-root.d.ts +18 -1
  9. package/dist/area-chart/root/area-chart-root.js +14 -7
  10. package/dist/avatar/root/avatar-root.d.ts +31 -1
  11. package/dist/avatar/root/avatar-root.js +11 -1
  12. package/dist/badge/count.d.ts +14 -0
  13. package/dist/badge/count.js +20 -0
  14. package/dist/badge/root/badge-root.d.ts +57 -6
  15. package/dist/badge/root/badge-root.js +77 -8
  16. package/dist/bar-chart/root/bar-chart-root.js +11 -20
  17. package/dist/bar-chart/stack.d.ts +10 -0
  18. package/dist/bar-chart/stack.js +8 -0
  19. package/dist/blockquote/root/blockquote-root.d.ts +66 -8
  20. package/dist/blockquote/root/blockquote-root.js +85 -8
  21. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +14 -0
  22. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +12 -2
  23. package/dist/breadcrumbs/root/breadcrumbs-root.js +2 -2
  24. package/dist/button/root/button-root.d.ts +24 -38
  25. package/dist/button/root/button-root.js +6 -37
  26. package/dist/button/submitter.d.ts +27 -0
  27. package/dist/button/submitter.js +25 -0
  28. package/dist/button-group/arrows.d.ts +14 -0
  29. package/dist/button-group/arrows.js +10 -0
  30. package/dist/button-group/root/button-group-root.d.ts +141 -3
  31. package/dist/button-group/root/button-group-root.js +142 -2
  32. package/dist/canvas/edge-pan.d.ts +61 -0
  33. package/dist/canvas/edge-pan.js +63 -0
  34. package/dist/canvas/node/canvas-node.d.ts +12 -2
  35. package/dist/canvas/node/canvas-node.js +6 -2
  36. package/dist/canvas/root/canvas-document.d.ts +10 -0
  37. package/dist/canvas/root/canvas-document.js +1 -0
  38. package/dist/canvas/root/canvas-root.d.ts +107 -5
  39. package/dist/canvas/root/canvas-root.js +140 -36
  40. package/dist/canvas/snap.d.ts +42 -0
  41. package/dist/canvas/snap.js +32 -0
  42. package/dist/card/root/card-root.d.ts +12 -1
  43. package/dist/card/root/card-root.js +11 -4
  44. package/dist/carousel/root/carousel-root.d.ts +4 -3
  45. package/dist/carousel/root/carousel-root.js +2 -6
  46. package/dist/chat/root/chat-root.d.ts +28 -4
  47. package/dist/chat/root/chat-root.js +12 -7
  48. package/dist/checkbox/root/checkbox-root.js +5 -7
  49. package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
  50. package/dist/checkbox-group/root/checkbox-group-root.js +16 -7
  51. package/dist/checkbox-group/select-all.d.ts +9 -4
  52. package/dist/checkbox-group/select-all.js +1 -1
  53. package/dist/code-block/root/code-block-root.d.ts +41 -18
  54. package/dist/code-block/root/code-block-root.js +21 -6
  55. package/dist/color-input/root/color-input-root.d.ts +11 -0
  56. package/dist/color-input/root/color-input-root.js +12 -5
  57. package/dist/color-picker/channels.js +1 -2
  58. package/dist/color-picker/root/color-picker-root.d.ts +32 -24
  59. package/dist/color-picker/root/color-picker-root.js +99 -55
  60. package/dist/color-picker/wheel.d.ts +28 -0
  61. package/dist/color-picker/wheel.js +15 -0
  62. package/dist/combobox/root/combobox-root.d.ts +83 -11
  63. package/dist/combobox/root/combobox-root.js +81 -18
  64. package/dist/command/root/command-root.d.ts +7 -4
  65. package/dist/command/root/command-root.js +2 -19
  66. package/dist/copy-button/root/copy-button-root.d.ts +16 -1
  67. package/dist/copy-button/root/copy-button-root.js +7 -2
  68. package/dist/data-table/keys.d.ts +0 -18
  69. package/dist/data-table/keys.js +1 -11
  70. package/dist/data-table/root/data-table-root.js +29 -50
  71. package/dist/date-input/root/date-input-root.d.ts +8 -1
  72. package/dist/date-input/root/date-input-root.js +8 -5
  73. package/dist/date-picker/root/date-picker-root.d.ts +14 -9
  74. package/dist/date-picker/root/date-picker-root.js +21 -16
  75. package/dist/description-list/item/description-list-item.d.ts +22 -4
  76. package/dist/description-list/item/description-list-item.js +14 -4
  77. package/dist/divider/root/divider-root.d.ts +45 -16
  78. package/dist/divider/root/divider-root.js +28 -4
  79. package/dist/donut-chart/root/donut-chart-root.js +13 -6
  80. package/dist/donut-chart/slice-label.d.ts +35 -0
  81. package/dist/donut-chart/slice-label.js +11 -0
  82. package/dist/drawer/root/drawer-root.d.ts +4 -2
  83. package/dist/drawer/root/drawer-root.js +15 -3
  84. package/dist/drilldown/item/drilldown-item.d.ts +21 -1
  85. package/dist/drilldown/item/drilldown-item.js +38 -4
  86. package/dist/drilldown/root/drilldown-root.js +3 -2
  87. package/dist/element-events.generated.js +5 -1
  88. package/dist/empty/root/empty-root.d.ts +46 -2
  89. package/dist/empty/root/empty-root.js +50 -5
  90. package/dist/file-input/root/file-input-root.d.ts +34 -6
  91. package/dist/file-input/root/file-input-root.js +55 -21
  92. package/dist/file-input/status.d.ts +24 -0
  93. package/dist/file-input/status.js +11 -0
  94. package/dist/filter-input/chip/filter-chip.d.ts +23 -0
  95. package/dist/filter-input/chip/filter-chip.js +10 -2
  96. package/dist/filter-input/root/filter-input-root.d.ts +8 -0
  97. package/dist/filter-input/root/filter-input-root.js +17 -27
  98. package/dist/format-date/fields.d.ts +20 -0
  99. package/dist/format-date/fields.js +14 -0
  100. package/dist/format-date/options.d.ts +5 -5
  101. package/dist/format-date/options.js +10 -39
  102. package/dist/format-date/root/format-date-root.d.ts +53 -4
  103. package/dist/format-date/root/format-date-root.js +38 -50
  104. package/dist/graph/root/graph-root.d.ts +23 -5
  105. package/dist/graph/root/graph-root.js +17 -20
  106. package/dist/heatmap/root/buckets.d.ts +21 -0
  107. package/dist/heatmap/root/buckets.js +34 -0
  108. package/dist/heatmap/root/heatmap-root.d.ts +76 -3
  109. package/dist/heatmap/root/heatmap-root.js +105 -20
  110. package/dist/icon/index.d.ts +1 -1
  111. package/dist/icon/index.js +1 -1
  112. package/dist/icon/registry.d.ts +26 -1
  113. package/dist/icon/registry.js +12 -1
  114. package/dist/icon/root/icon-root.d.ts +13 -0
  115. package/dist/icon/root/icon-root.js +23 -2
  116. package/dist/icon/root/index.d.ts +1 -1
  117. package/dist/icon/root/index.js +1 -1
  118. package/dist/image/root/image-root.d.ts +20 -0
  119. package/dist/image/root/image-root.js +6 -1
  120. package/dist/input/root/input-root.d.ts +23 -2
  121. package/dist/input/root/input-root.js +12 -8
  122. package/dist/kbd/root/kbd-root.d.ts +20 -3
  123. package/dist/kbd/root/kbd-root.js +23 -50
  124. package/dist/legend/hidden-series.d.ts +10 -2
  125. package/dist/legend/hidden-series.js +5 -2
  126. package/dist/legend/root/legend-root.d.ts +25 -7
  127. package/dist/legend/root/legend-root.js +25 -26
  128. package/dist/lightbox/root/lightbox-root.d.ts +50 -5
  129. package/dist/lightbox/root/lightbox-root.js +76 -8
  130. package/dist/lightbox/zoom.d.ts +2 -0
  131. package/dist/lightbox/zoom.js +5 -3
  132. package/dist/line-chart/root/line-chart-root.d.ts +16 -0
  133. package/dist/line-chart/root/line-chart-root.js +10 -2
  134. package/dist/list/root/list-root.d.ts +10 -2
  135. package/dist/list/root/list-root.js +31 -24
  136. package/dist/list/selection.d.ts +17 -0
  137. package/dist/list/selection.js +16 -0
  138. package/dist/listbox/root/listbox-root.d.ts +125 -26
  139. package/dist/listbox/root/listbox-root.js +156 -166
  140. package/dist/map/marker/map-marker.d.ts +2 -1
  141. package/dist/map/root/map-root.d.ts +40 -2
  142. package/dist/map/root/map-root.js +64 -23
  143. package/dist/megamenu/hand-over.d.ts +75 -0
  144. package/dist/megamenu/hand-over.js +67 -0
  145. package/dist/megamenu/root/megamenu-root.d.ts +38 -14
  146. package/dist/megamenu/root/megamenu-root.js +29 -50
  147. package/dist/menu/item/menu-item.js +4 -4
  148. package/dist/menu/root/menu-root.d.ts +5 -2
  149. package/dist/meter/root/meter-root.d.ts +42 -0
  150. package/dist/meter/root/meter-root.js +33 -7
  151. package/dist/navigation/root/navigation-root.js +5 -8
  152. package/dist/notifications/group/notification-group.d.ts +8 -1
  153. package/dist/notifications/group/notification-group.js +12 -6
  154. package/dist/notifications/notification/notification.d.ts +12 -1
  155. package/dist/notifications/notification/notification.js +3 -1
  156. package/dist/notifications/root/notifications-root.d.ts +48 -3
  157. package/dist/notifications/root/notifications-root.js +36 -6
  158. package/dist/number-input/grain.d.ts +28 -0
  159. package/dist/number-input/grain.js +14 -0
  160. package/dist/number-input/root/number-input-root.d.ts +17 -1
  161. package/dist/number-input/root/number-input-root.js +29 -18
  162. package/dist/object-inspector/root/object-inspector-root.d.ts +17 -6
  163. package/dist/object-inspector/root/object-inspector-root.js +31 -20
  164. package/dist/option/index.d.ts +1 -0
  165. package/dist/option/index.js +1 -0
  166. package/dist/option/separator/option-separator.d.ts +30 -0
  167. package/dist/option/separator/option-separator.js +21 -0
  168. package/dist/outline/root/outline-root.d.ts +12 -0
  169. package/dist/outline/root/outline-root.js +5 -19
  170. package/dist/pagination/size/pagination-size.d.ts +6 -0
  171. package/dist/pagination/size/pagination-size.js +5 -3
  172. package/dist/pin-input/code.d.ts +51 -1
  173. package/dist/pin-input/code.js +37 -0
  174. package/dist/pin-input/root/pin-input-root.d.ts +98 -121
  175. package/dist/pin-input/root/pin-input-root.js +151 -156
  176. package/dist/popover/hide-outside.js +2 -9
  177. package/dist/popover/root/popover-root.d.ts +7 -1
  178. package/dist/popover/root/popover-root.js +9 -1
  179. package/dist/progress/root/progress-root.d.ts +27 -0
  180. package/dist/progress/root/progress-root.js +25 -7
  181. package/dist/qr-code/export.d.ts +19 -0
  182. package/dist/qr-code/export.js +14 -0
  183. package/dist/qr-code/name.d.ts +11 -0
  184. package/dist/qr-code/name.js +7 -0
  185. package/dist/qr-code/root/qr-code-root.d.ts +31 -9
  186. package/dist/qr-code/root/qr-code-root.js +29 -17
  187. package/dist/radio/group/radio-group.d.ts +15 -0
  188. package/dist/radio/root/radio-root.js +2 -2
  189. package/dist/rating/root/rating-root.d.ts +22 -9
  190. package/dist/rating/root/rating-root.js +15 -39
  191. package/dist/scatter-chart/measures.d.ts +22 -0
  192. package/dist/scatter-chart/measures.js +11 -0
  193. package/dist/scatter-chart/root/scatter-chart-root.d.ts +26 -0
  194. package/dist/scatter-chart/root/scatter-chart-root.js +21 -7
  195. package/dist/scroll-area/scrollbar.d.ts +0 -8
  196. package/dist/scroll-area/scrollbar.js +1 -1
  197. package/dist/select/align.d.ts +47 -0
  198. package/dist/select/align.js +7 -0
  199. package/dist/select/root/select-root.d.ts +115 -11
  200. package/dist/select/root/select-root.js +104 -20
  201. package/dist/slider/root/slider-root.d.ts +18 -1
  202. package/dist/slider/root/slider-root.js +22 -0
  203. package/dist/sparkline/readings.d.ts +30 -12
  204. package/dist/sparkline/readings.js +32 -15
  205. package/dist/sparkline/root/sparkline-root.d.ts +46 -3
  206. package/dist/sparkline/root/sparkline-root.js +60 -21
  207. package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
  208. package/dist/speed-dial/group/speed-dial-group.js +43 -0
  209. package/dist/speed-dial/index.d.ts +1 -0
  210. package/dist/speed-dial/index.js +1 -0
  211. package/dist/speed-dial/item/speed-dial-item.d.ts +27 -2
  212. package/dist/speed-dial/item/speed-dial-item.js +43 -5
  213. package/dist/speed-dial/long-press.d.ts +50 -0
  214. package/dist/speed-dial/long-press.js +48 -0
  215. package/dist/speed-dial/root/speed-dial-root.d.ts +86 -1
  216. package/dist/speed-dial/root/speed-dial-root.js +123 -6
  217. package/dist/splitter/root/splitter-root.d.ts +9 -2
  218. package/dist/splitter/root/splitter-root.js +18 -3
  219. package/dist/steps/root/steps-root.d.ts +8 -1
  220. package/dist/steps/root/steps-root.js +6 -1
  221. package/dist/steps/step/step.d.ts +1 -0
  222. package/dist/steps/step/step.js +1 -1
  223. package/dist/table/cell/table-cell.d.ts +8 -1
  224. package/dist/table/cell/table-cell.js +6 -2
  225. package/dist/table/column/table-column.d.ts +6 -0
  226. package/dist/table/column/table-column.js +5 -1
  227. package/dist/table/grid-controller.d.ts +117 -0
  228. package/dist/table/grid-controller.js +293 -0
  229. package/dist/table/root/table-root.d.ts +47 -3
  230. package/dist/table/root/table-root.js +93 -29
  231. package/dist/table/row/table-row.d.ts +36 -0
  232. package/dist/table/row/table-row.js +52 -2
  233. package/dist/tabs/root/tabs-root.d.ts +5 -3
  234. package/dist/tag/group/tag-group.d.ts +16 -4
  235. package/dist/tag/group/tag-group.js +23 -20
  236. package/dist/textarea/root/textarea-root.d.ts +14 -2
  237. package/dist/textarea/root/textarea-root.js +6 -7
  238. package/dist/time-input/root/time-input-root.d.ts +5 -1
  239. package/dist/time-input/root/time-input-root.js +10 -7
  240. package/dist/time-picker/root/time-picker-root.d.ts +12 -14
  241. package/dist/time-picker/root/time-picker-root.js +18 -23
  242. package/dist/toast/item/toast-item.d.ts +9 -6
  243. package/dist/toast/item/toast-item.js +1 -1
  244. package/dist/toast/root/toast-root.d.ts +40 -4
  245. package/dist/toast/root/toast-root.js +68 -16
  246. package/dist/toast/words.d.ts +12 -0
  247. package/dist/toast/words.js +25 -0
  248. package/dist/toggle/group/toggle-group.d.ts +29 -5
  249. package/dist/toggle/group/toggle-group.js +26 -43
  250. package/dist/toggle/root/toggle-root.js +2 -4
  251. package/dist/toolbar/root/toolbar-root.d.ts +52 -5
  252. package/dist/toolbar/root/toolbar-root.js +88 -13
  253. package/dist/tooltip/root/tooltip-root.d.ts +7 -1
  254. package/dist/tooltip/root/tooltip-root.js +10 -2
  255. package/dist/tour/root/tour-root.d.ts +15 -0
  256. package/dist/tour/root/tour-root.js +20 -1
  257. package/dist/tour/stop/tour-stop.d.ts +3 -0
  258. package/dist/tour/stop/tour-stop.js +3 -3
  259. package/dist/tree/root/tree-root.js +2 -2
  260. package/dist/trend/figure.d.ts +16 -0
  261. package/dist/trend/figure.js +8 -0
  262. package/dist/trend/root/trend-root.d.ts +41 -2
  263. package/dist/trend/root/trend-root.js +29 -3
  264. package/dist/video/root/extra-controls.d.ts +12 -0
  265. package/dist/video/root/extra-controls.js +6 -2
  266. package/dist/video/root/video-root.d.ts +16 -1
  267. package/dist/video/root/video-root.js +15 -1
  268. package/dist/video-playlist/root/video-playlist-root.d.ts +11 -1
  269. package/dist/video-playlist/root/video-playlist-root.js +3 -0
  270. package/dist/virtual-list/fill.d.ts +11 -0
  271. package/dist/virtual-list/fill.js +9 -0
  272. package/dist/virtual-list/keys.d.ts +16 -7
  273. package/dist/virtual-list/keys.js +17 -6
  274. package/dist/virtual-list/plan.d.ts +17 -1
  275. package/dist/virtual-list/plan.js +13 -2
  276. package/dist/virtual-list/root/virtual-list-root.d.ts +25 -3
  277. package/dist/virtual-list/root/virtual-list-root.js +53 -41
  278. package/lint.json +1 -1
  279. package/package.json +5 -5
  280. package/types/jsx.d.ts +4934 -954
  281. package/types/svelte.d.ts +2 -0
  282. package/types/vue.d.ts +946 -253
  283. package/dist/color-picker/swatches.d.ts +0 -12
  284. package/dist/color-picker/swatches.js +0 -26
  285. package/dist/rating/steps.d.ts +0 -7
  286. package/dist/rating/steps.js +0 -9
  287. package/dist/tooltip/focus-target.d.ts +0 -6
  288. package/dist/tooltip/focus-target.js +0 -9
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, drawIcon, HelixClearEvent, HelixFormElement, holdCaret, isRtl, MixinControllerValue, MixinField, restoreValue, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, applyOwnRule, customElement, drawIcon, HelixClearEvent, HelixFormElement, holdCaret, isRtl, MixinControllerValue, MixinField, restoreValue, } from '@fusion.dev/helix-core';
8
8
  import { x } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
@@ -126,11 +126,7 @@ let HelixHeadlessTextarea = class HelixHeadlessTextarea extends MixinControllerV
126
126
  return;
127
127
  }
128
128
  this.copyValidity(control);
129
- const own = control.validity.valid ? this.validate?.(this.value) : undefined;
130
- const message = Array.isArray(own) ? own.join(' ') : typeof own === 'string' ? own : '';
131
- if (message !== '') {
132
- this.internals.setValidity({ customError: true }, message, control);
133
- }
129
+ applyOwnRule(this.internals, control, this.validate, this.value);
134
130
  this.refreshFeedback();
135
131
  }
136
132
  connectedCallback() {
@@ -175,6 +171,9 @@ let HelixHeadlessTextarea = class HelixHeadlessTextarea extends MixinControllerV
175
171
  this.sync();
176
172
  this.grow();
177
173
  }
174
+ renderClearIcon() {
175
+ return html `<slot name="clear-icon">${drawIcon(x)}</slot>`;
176
+ }
178
177
  render() {
179
178
  return html `${this.renderLabel()}
180
179
  <div part="field">
@@ -207,7 +206,7 @@ let HelixHeadlessTextarea = class HelixHeadlessTextarea extends MixinControllerV
207
206
  aria-label=${this.clearLabel}
208
207
  @click=${this.handleClear}
209
208
  >
210
- ${drawIcon(x)}
209
+ ${this.renderClearIcon()}
211
210
  </button>`
212
211
  : nothing}
213
212
  </div>
@@ -74,6 +74,7 @@ declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fus
74
74
  * @csspart picker-column-search - The region that holds the filter field.
75
75
  * @csspart picker-column-search-field - The filter field.
76
76
  * @csspart picker-columns - The row of columns, in `wheels`.
77
+ * @csspart picker-feedback - The picker's own message, under the times, while the picker's `feedback` is set.
77
78
  * @csspart picker-list - The listbox holding the times, in `list`.
78
79
  * @csspart picker-list-empty - The text shown when the filter hides every option.
79
80
  * @csspart picker-list-error - The list's `error` part, which the list never sets: its times do not load.
@@ -142,9 +143,12 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
142
143
  /**
143
144
  * Language tag the times are written in.
144
145
  *
145
- * Absent uses the runtime's own.
146
+ * Absent is the page's: the `lang` of the nearest element with one, as
147
+ * Shoelace and Web Awesome read it, then the runtime's own.
146
148
  */
147
149
  locale: string;
150
+ /** The locale the words are written in: `locale`, or the page's `lang`. */
151
+ private get language();
148
152
  /**
149
153
  * Latest time that can be chosen, as `HH:mm`.
150
154
  */
@@ -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, valueAsTime as asTime, clockLocale, customElement, fillTemplate, timeAsValue as fromTime, HelixFormElement, isClockValue, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinSegmentedField, MixinTokens, timeFault, writeClock, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, valueAsTime as asTime, clockLocale, customElement, fillTemplate, timeAsValue as fromTime, HelixFormElement, isClockValue, localeOf, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinSegmentedField, MixinTokens, timeFault, writeClock, } from '@fusion.dev/helix-core';
8
8
  import { clock } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -88,6 +88,9 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
88
88
  }
89
89
  };
90
90
  }
91
+ get language() {
92
+ return localeOf(this, this.locale);
93
+ }
91
94
  get held() {
92
95
  return this.value
93
96
  .replace(/\s+[–-]\s+/g, ',')
@@ -102,7 +105,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
102
105
  return 'time';
103
106
  }
104
107
  get segmentLocale() {
105
- return this.locale;
108
+ return this.language;
106
109
  }
107
110
  get segmentOptions() {
108
111
  return { hourCycle: this.hourCycle, seconds: this.seconds };
@@ -113,7 +116,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
113
116
  written(at) {
114
117
  return this.timeStyle === 'iso' || !this.isTime(at)
115
118
  ? at
116
- : writeClock(at, clockLocale(this.locale, this.hourCycle), this.seconds);
119
+ : writeClock(at, clockLocale(this.language, this.hourCycle), this.seconds);
117
120
  }
118
121
  get display() {
119
122
  if (this.mode === 'multiple') {
@@ -155,7 +158,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
155
158
  return [
156
159
  'rangeUnderflow',
157
160
  fillTemplate(this.rangeUnderflowMessage || 'Choose a time at or after {min}.', {
158
- min: writeClock(this.min, clockLocale(this.locale, this.hourCycle), this.seconds),
161
+ min: writeClock(this.min, clockLocale(this.language, this.hourCycle), this.seconds),
159
162
  }),
160
163
  ];
161
164
  }
@@ -163,7 +166,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
163
166
  return [
164
167
  'rangeOverflow',
165
168
  fillTemplate(this.rangeOverflowMessage || 'Choose a time at or before {max}.', {
166
- max: writeClock(this.max, clockLocale(this.locale, this.hourCycle), this.seconds),
169
+ max: writeClock(this.max, clockLocale(this.language, this.hourCycle), this.seconds),
167
170
  }),
168
171
  ];
169
172
  }
@@ -189,7 +192,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
189
192
  column-filter-mark: picker-column-filter-mark, column-hint: picker-column-hint,
190
193
  column-items: picker-column-items, column-label: picker-column-label,
191
194
  column-live: picker-column-live, column-loading: picker-column-loading, column-search: picker-column-search,
192
- column-search-field: picker-column-search-field, columns: picker-columns, list: picker-list,
195
+ column-search-field: picker-column-search-field, columns: picker-columns, feedback: picker-feedback, list: picker-list,
193
196
  list-empty: picker-list-empty, list-error: picker-list-error, list-feedback: picker-list-feedback,
194
197
  list-filter-icon: picker-list-filter-icon, list-filter-mark: picker-list-filter-mark,
195
198
  list-hint: picker-list-hint, list-items: picker-list-items, list-label: picker-list-label,
@@ -206,7 +209,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
206
209
  manual
207
210
  label=${this.label || 'Time'}
208
211
  hour-cycle=${this.hourCycle ?? nothing}
209
- locale=${this.locale || nothing}
212
+ locale=${this.language || nothing}
210
213
  max=${this.max}
211
214
  min=${this.min}
212
215
  ?seconds=${this.seconds}
@@ -2,7 +2,7 @@ import { HelixFormElement, type HourCycle } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../listbox/index.js';
4
4
  import '../../option/index.js';
5
- declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & typeof HelixFormElement;
5
+ declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinPickerFeedbackInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & typeof HelixFormElement;
6
6
  /**
7
7
  * @summary A control for choosing a time from a list or from wheels.
8
8
  *
@@ -59,6 +59,7 @@ declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fu
59
59
  * @csspart column-search - The region that holds the filter field.
60
60
  * @csspart column-search-field - The filter field.
61
61
  * @csspart columns - The row of columns, in `wheels`.
62
+ * @csspart feedback - The message under the times, while `feedback` is set.
62
63
  * @csspart list - The listbox holding the times, in `list`.
63
64
  * @csspart list-empty - The text shown when the filter hides every option.
64
65
  * @csspart list-error - The list's `error` part, which Time Picker never sets: its times do not load.
@@ -80,6 +81,9 @@ declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fu
80
81
  * @csspart option-item-selected - The row of a chosen time: the one time, or either end of a range.
81
82
  * @csspart option-label - The time's label.
82
83
  * @csspart option-text - The label and the caption, as one column.
84
+ *
85
+ * @cssstate blank - While no time is chosen.
86
+ * @cssstate invalid - While `invalid` is set or `feedback` says what is wrong.
83
87
  */
84
88
  export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_base {
85
89
  /**
@@ -119,16 +123,6 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
119
123
  * English, "Stunde" in German.
120
124
  */
121
125
  hourLabel?: string;
122
- /**
123
- * Whether the current selection fails validation.
124
- *
125
- * A flag rather than a message, the same split Date Picker draws. A flat
126
- * picker has no room of its own for a sentence: the field that wraps one owns
127
- * the label, the hint and the feedback, and a second message inside the panel
128
- * would be the same error said twice. What is left for the picker is the one
129
- * thing the field cannot draw from outside, the mark on the answer itself.
130
- */
131
- invalid: boolean;
132
126
  /**
133
127
  * Accessible name for the list, which has no visible one of its own.
134
128
  *
@@ -136,11 +130,14 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
136
130
  */
137
131
  label: string;
138
132
  /**
139
- * Language tag the times are written in.
133
+ * Language tag the times and the wheels' names are written in.
140
134
  *
141
- * Absent uses the runtime's own.
135
+ * Absent is the page's: the `lang` of the nearest element with one, as
136
+ * Shoelace and Web Awesome read it, then the runtime's own.
142
137
  */
143
138
  locale: string;
139
+ /** The locale the words are written in: `locale`, or the page's `lang`. */
140
+ private get language();
144
141
  /**
145
142
  * Stops the arrow keys in the list from choosing each time they move to.
146
143
  * Enter, Space or a click chooses.
@@ -460,7 +457,8 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
460
457
  private listRevealed;
461
458
  /**
462
459
  * The list's name: the host's `aria-labelledby` read out, then its
463
- * `aria-label`, then `label`.
460
+ * `aria-label`, then `label`, through core's `hostWords`, which Select reads
461
+ * its list's name through too.
464
462
  *
465
463
  * Read out as words, because the list is inside two shadow roots and an id on
466
464
  * the page reaches neither. Measured before this: `<helix-time-picker
@@ -4,18 +4,17 @@ 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 { clockLocale, commaList, customElement, dayPeriod, elementById, fillTemplate, HelixFormElement, inlineStep, isTwelveHour, MixinControllerValue, nearestTime, offeredTimes, reportValues, secondsAsValue, timeBarred, timeFault, valueAsSeconds, wheelParts, wholeHour, writeClock, } from '@fusion.dev/helix-core';
7
+ import { clockLocale, commaList, customElement, dayPeriod, fillTemplate, HelixFormElement, hostWords, inlineStep, isTwelveHour, localeOf, MixinControllerValue, MixinPickerFeedback, nearestTime, offeredTimes, reportValues, secondsAsValue, timeBarred, timeFault, valueAsSeconds, wheelParts, wholeHour, writeClock, } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { html } from 'lit/static-html.js';
11
11
  import { wheelName } from '../wheel-name.js';
12
12
  import '../../listbox/index.js';
13
13
  import '../../option/index.js';
14
- let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControllerValue(HelixFormElement) {
14
+ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerFeedback(MixinControllerValue(HelixFormElement)) {
15
15
  constructor() {
16
16
  super(...arguments);
17
17
  this.disableTimes = '';
18
- this.invalid = false;
19
18
  this.label = 'Time';
20
19
  this.locale = '';
21
20
  this.manual = false;
@@ -94,11 +93,14 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
94
93
  next.focus();
95
94
  };
96
95
  }
96
+ get language() {
97
+ return localeOf(this, this.locale);
98
+ }
97
99
  get times() {
98
100
  return offeredTimes(this.min, this.max, this.step, this.seconds);
99
101
  }
100
102
  get clock() {
101
- return clockLocale(this.locale, this.hourCycle);
103
+ return clockLocale(this.language, this.hourCycle);
102
104
  }
103
105
  valueFor(at) {
104
106
  return secondsAsValue(at, this.seconds);
@@ -239,13 +241,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
239
241
  this.reveal();
240
242
  }
241
243
  get listName() {
242
- const named = (this.getAttribute('aria-labelledby') ?? '')
243
- .split(/\s+/)
244
- .filter(Boolean)
245
- .map((id) => elementById(this, id)?.textContent?.trim() ?? '')
246
- .filter(Boolean)
247
- .join(' ');
248
- return named || this.getAttribute('aria-label') || this.label;
244
+ return hostWords(this, this.label);
249
245
  }
250
246
  connectedCallback() {
251
247
  super.connectedCallback();
@@ -259,8 +255,8 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
259
255
  }
260
256
  willUpdate(changed) {
261
257
  super.willUpdate(changed);
262
- this.internals.ariaInvalid = this.invalid ? 'true' : null;
263
258
  this.report();
259
+ this.toggleState('blank', this.value === '');
264
260
  }
265
261
  updated(changed) {
266
262
  super.updated(changed);
@@ -282,7 +278,8 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
282
278
  return html `<${this.tag('listbox')}
283
279
  part="column"
284
280
  data-column=${part}
285
- aria-invalid=${this.invalid ? 'true' : nothing}
281
+ aria-describedby=${this.feedback === '' ? nothing : 'feedback'}
282
+ aria-invalid=${this.marked ? 'true' : nothing}
286
283
  exportparts="empty: column-empty, error: column-error, feedback: column-feedback, filter-icon: column-filter-icon,
287
284
  filter-mark: column-filter-mark, hint: column-hint, items: column-items, label: column-label,
288
285
  live: column-live, loading: column-loading, search: column-search, search-field: column-search-field"
@@ -320,20 +317,21 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
320
317
  data-unset=${this.value === '' ? '' : nothing}
321
318
  @keydown=${this.handleColumnsKeydown}
322
319
  >
323
- ${this.renderColumn('hour', this.hourLabel ?? wheelName('hour', this.locale), hours.map(String), (one) => (twelve ? one : one.padStart(2, '0')), String(shown), (one) => this.wholeHourBarred(Number(one), twelve ? meridiem : undefined))}
324
- ${this.renderColumn('minute', this.minuteLabel ?? wheelName('minute', this.locale), this.minutes.map(String), (one) => one.padStart(2, '0'), String(minute), (one) => this.barredAt(hour * 3600 + Number(one) * 60))}
320
+ ${this.renderColumn('hour', this.hourLabel ?? wheelName('hour', this.language), hours.map(String), (one) => (twelve ? one : one.padStart(2, '0')), String(shown), (one) => this.wholeHourBarred(Number(one), twelve ? meridiem : undefined))}
321
+ ${this.renderColumn('minute', this.minuteLabel ?? wheelName('minute', this.language), this.minutes.map(String), (one) => one.padStart(2, '0'), String(minute), (one) => this.barredAt(hour * 3600 + Number(one) * 60))}
325
322
  ${this.seconds
326
- ? this.renderColumn('second', this.secondLabel ?? wheelName('second', this.locale), this.secondsShown.map(String), (one) => one.padStart(2, '0'), String(second), (one) => this.barredAt(hour * 3600 + minute * 60 + Number(one)))
323
+ ? this.renderColumn('second', this.secondLabel ?? wheelName('second', this.language), this.secondsShown.map(String), (one) => one.padStart(2, '0'), String(second), (one) => this.barredAt(hour * 3600 + minute * 60 + Number(one)))
327
324
  : nothing}
328
325
  ${twelve
329
- ? this.renderColumn('meridiem', this.meridiemLabel ?? wheelName('dayPeriod', this.locale), ['AM', 'PM'], (one) => dayPeriod(one, this.clock), meridiem, (one) => this.halfBarred(one))
326
+ ? this.renderColumn('meridiem', this.meridiemLabel ?? wheelName('dayPeriod', this.language), ['AM', 'PM'], (one) => dayPeriod(one, this.clock), meridiem, (one) => this.halfBarred(one))
330
327
  : nothing}
331
328
  </div>`;
332
329
  }
333
330
  renderList() {
334
331
  return html `<${this.tag('listbox')}
335
332
  part="list"
336
- aria-invalid=${this.invalid ? 'true' : nothing}
333
+ aria-describedby=${this.feedback === '' ? nothing : 'feedback'}
334
+ aria-invalid=${this.marked ? 'true' : nothing}
337
335
  exportparts="empty: list-empty, error: list-error, feedback: list-feedback, filter-icon: list-filter-icon,
338
336
  filter-mark: list-filter-mark, hint: list-hint, items: list-items, label: list-label,
339
337
  live: list-live, loading: list-loading, search: list-search, search-field: list-search-field"
@@ -365,9 +363,9 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
365
363
  }
366
364
  render() {
367
365
  if (this.variant === 'wheels' && this.mode === undefined) {
368
- return this.renderSpinner();
366
+ return html `${this.renderSpinner()}${this.renderPickerFeedback()}`;
369
367
  }
370
- return html `${this.renderList()}`;
368
+ return html `${this.renderList()}${this.renderPickerFeedback()}`;
371
369
  }
372
370
  get valueAsDate() {
373
371
  const at = valueAsSeconds(this.value);
@@ -398,9 +396,6 @@ __decorate([
398
396
  __decorate([
399
397
  property({ attribute: 'hour-label' })
400
398
  ], HelixHeadlessTimePicker.prototype, "hourLabel", void 0);
401
- __decorate([
402
- property({ reflect: true, type: Boolean })
403
- ], HelixHeadlessTimePicker.prototype, "invalid", void 0);
404
399
  __decorate([
405
400
  property()
406
401
  ], HelixHeadlessTimePicker.prototype, "label", void 0);
@@ -10,14 +10,15 @@ declare const HelixHeadlessToastItem_base: (new (...args: any[]) => import("@fus
10
10
  *
11
11
  * **Each item runs its own timer.** A timer held by the region would restart
12
12
  * with each new message, and the last of three messages would stay for nine
13
- * seconds.
13
+ * seconds. It pauses while the pointer or focus is on the message, and while
14
+ * the page is hidden, so a message is not spent in a background tab.
14
15
  *
15
16
  * **Each message is an `alertdialog` named by its words**, which is how a reader
16
17
  * who moves into the stack hears which message they are on. Give it a
17
18
  * `heading` and a `description`, and the heading names it and the description
18
- * describes it, for a message with a title and a line of detail. A `danger` message
19
- * is its own assertive live region, so it is announced at once; every other one
20
- * is announced politely by the region.
19
+ * describes it, for a message with a title and a line of detail. The region
20
+ * says each message once as it arrives, a `danger` one assertively, so a
21
+ * message is never a live region of its own.
21
22
  *
22
23
  * A touch or a pen swipes a message toward the end of the line, and past
23
24
  * halfway, or quickly, dismisses it through the same `helix-dismiss` as its
@@ -85,8 +86,10 @@ export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base
85
86
  /** Writes how far a swipe has moved the message toward the end of the line. */
86
87
  private slide;
87
88
  /**
88
- * The dismiss timer, which pauses while a reader is on the message and
89
- * continues from what was left.
89
+ * The dismiss timer, which pauses while a reader is on the message, and
90
+ * while the page is hidden, and continues from what was left. A message that
91
+ * arrives in a background tab waits for the reader to come back, as React
92
+ * Aria's toast queue does.
90
93
  *
91
94
  * Paused is said on the host so the indicator can freeze with the clock. A
92
95
  * `data-` attribute rather than a property, because it is written after
@@ -35,6 +35,7 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
35
35
  done: () => {
36
36
  this.dismiss();
37
37
  },
38
+ holdWhileHidden: true,
38
39
  paused: (paused) => {
39
40
  this.toggleAttribute('data-paused', paused);
40
41
  },
@@ -104,7 +105,6 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
104
105
  this.toggleAttribute('hidden', !this.open);
105
106
  hostAria(this.internals, { role: 'alertdialog' });
106
107
  this.internals.ariaModal = 'false';
107
- this.internals.ariaLive = this.tone === 'danger' ? 'assertive' : null;
108
108
  }
109
109
  updated(changed) {
110
110
  super.updated(changed);
@@ -9,10 +9,15 @@ import { type PropertyValues, type TemplateResult } from 'lit';
9
9
  * announce a region that arrives already holding its message. `Radio.Group` is
10
10
  * part of its family for the same reason.
11
11
  *
12
- * **The region is a landmark named by `label`, and a polite live region.** A
13
- * `danger` message is announced assertively, and every other one politely.
14
- * Each message is an `alertdialog` named by its words, so a reader who moves
15
- * into the stack hears which message they are on.
12
+ * **The region is a landmark named by `label`, and it says each message once
13
+ * as it arrives**: a `danger` message assertively, and every other one
14
+ * politely. It says the word for the tone, the heading, the description and
15
+ * the text, and not the actions, through two live regions of its own, the
16
+ * `live` parts, which it empties again a moment later. The stack itself is not
17
+ * a live region, so a message is never said twice, and changing the words of a
18
+ * message already showing says nothing. Each message is an `alertdialog` named
19
+ * by its words, so a reader who moves into the stack hears which message they
20
+ * are on.
16
21
  *
17
22
  * **`hotkey` takes focus to the stack and back**, F8 unless you set it, and
18
23
  * `label` says which key: `{hotkey}` in it is filled in, so the default name
@@ -54,6 +59,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
54
59
  *
55
60
  * @event {HelixDismissEvent} helix-dismiss - A message is being dismissed, by its control or by its timer. The message sends it, and you can listen for it on the region. Cancelable: canceling it keeps the message.
56
61
  *
62
+ * @csspart live - The two regions that say each message as it arrives, a polite `status` and an assertive `alert`, inside the stack. Empty at rest, and hide them with the standard clip, never `display: none`.
57
63
  * @csspart region - The stack the messages are in. It is in the top layer while a message shows, and takes focus from `hotkey` where no message holds a control.
58
64
  */
59
65
  export declare class HelixHeadlessToast extends HelixElement {
@@ -95,6 +101,36 @@ export declare class HelixHeadlessToast extends HelixElement {
95
101
  */
96
102
  placement?: 'bottom-center' | 'bottom-end' | 'bottom-start' | 'top-center' | 'top-end' | 'top-start';
97
103
  protected willUpdate(changed: PropertyValues): void;
104
+ /** What the polite region is saying. */
105
+ private said;
106
+ /** What the assertive region is saying. */
107
+ private urgent;
108
+ private readonly hush;
109
+ /**
110
+ * Words waiting for the next frame: the polite ones and the urgent ones.
111
+ *
112
+ * Collected rather than written straight in, because the accessibility tree
113
+ * is built once a frame: two messages that arrive in separate tasks of one
114
+ * frame would otherwise each overwrite the region, and only the second
115
+ * would be heard.
116
+ */
117
+ private readonly pending;
118
+ /** Whether a frame is already coming to say what is pending. */
119
+ private filling;
120
+ /**
121
+ * Says messages that have just arrived, each once, `danger` ones assertively.
122
+ *
123
+ * Emptied first and filled a frame later, so a message with the same words
124
+ * as the one before is still a change, and so a region the stack has just
125
+ * shown is in the accessibility tree before its words land. Every message
126
+ * that arrives before that frame is said in it, each as its own sentence.
127
+ */
128
+ private announce;
129
+ /**
130
+ * Says the messages that have just arrived: through the regions, or through
131
+ * `ariaNotify` where a modal dialog has made them inert.
132
+ */
133
+ private tell;
98
134
  /** The element focus came from when it entered the stack. */
99
135
  private focusOrigin;
100
136
  private readonly scope;
@@ -4,15 +4,21 @@ 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, deepActiveElement, deepContains, FocusScopeController, fillTemplate, focusableElements, HelixElement, hostAria, scrollHolders, } from '@fusion.dev/helix-core';
7
+ import { animationsFinished, customElement, DelayController, deepActiveElement, deepContains, FocusScopeController, fillTemplate, focusableElements, HelixElement, hostAria, scrollHolders, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
- import { property } from 'lit/decorators.js';
9
+ import { property, state } from 'lit/decorators.js';
10
10
  import { matchesHotkey, parseHotkey } from '../hotkey.js';
11
+ import { joinSaid, messageWords } from '../words.js';
11
12
  let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
12
13
  constructor() {
13
14
  super(...arguments);
14
15
  this.hotkey = 'F8';
15
16
  this.label = 'Notifications ({hotkey})';
17
+ this.said = '';
18
+ this.urgent = '';
19
+ this.hush = new DelayController(this, { renders: false });
20
+ this.pending = { said: [], urgent: [] };
21
+ this.filling = false;
16
22
  this.scope = new FocusScopeController(this, {
17
23
  contents: () => this.showing.flatMap((one) => {
18
24
  const target = focusTarget(one);
@@ -85,9 +91,51 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
85
91
  label: fillTemplate(this.label, { hotkey: this.hotkey }),
86
92
  role: 'region',
87
93
  });
88
- this.internals.ariaLive = 'polite';
89
- this.internals.ariaRelevant = 'additions';
90
- this.internals.ariaAtomic = 'false';
94
+ }
95
+ announce(arrived) {
96
+ for (const one of arrived) {
97
+ const words = messageWords(one);
98
+ if (words !== '') {
99
+ ;
100
+ (isDanger(one) ? this.pending.urgent : this.pending.said).push(words);
101
+ }
102
+ }
103
+ if (this.filling || (this.pending.said.length === 0 && this.pending.urgent.length === 0)) {
104
+ return;
105
+ }
106
+ this.filling = true;
107
+ this.said = '';
108
+ this.urgent = '';
109
+ const fill = () => {
110
+ this.filling = false;
111
+ this.said = joinSaid(this.pending.said);
112
+ this.urgent = joinSaid(this.pending.urgent);
113
+ this.pending.said = [];
114
+ this.pending.urgent = [];
115
+ this.hush.start(1000, () => {
116
+ this.said = '';
117
+ this.urgent = '';
118
+ });
119
+ };
120
+ if (typeof requestAnimationFrame === 'function') {
121
+ requestAnimationFrame(fill);
122
+ }
123
+ else {
124
+ fill();
125
+ }
126
+ }
127
+ tell(added) {
128
+ const showing = this.showing;
129
+ const arrived = added.filter((one) => one instanceof HTMLElement && showing.includes(one));
130
+ if (arrived.length === 0) {
131
+ return;
132
+ }
133
+ if (this.blocked()) {
134
+ this.notify(arrived);
135
+ }
136
+ else {
137
+ this.announce(arrived);
138
+ }
91
139
  }
92
140
  add(message, options = {}) {
93
141
  const tier = this.constructor.tier;
@@ -121,8 +169,7 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
121
169
  continue;
122
170
  }
123
171
  this.made.delete(one);
124
- const running = typeof one.getAnimations === 'function' ? one.getAnimations({ subtree: true }) : [];
125
- void Promise.allSettled(running.map((animation) => animation.finished)).then(() => one.remove());
172
+ void animationsFinished(one).then(() => one.remove());
126
173
  }
127
174
  }
128
175
  get showing() {
@@ -152,13 +199,9 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
152
199
  if (!open || blocked) {
153
200
  region.showPopover();
154
201
  }
155
- if (blocked) {
156
- this.notify(arrived);
157
- }
158
202
  }
159
203
  lowerWhenStill(region) {
160
- const running = typeof this.getAnimations === 'function' ? this.getAnimations({ subtree: true }) : [];
161
- void Promise.allSettled(running.map((one) => one.finished)).then(() => {
204
+ void animationsFinished(this).then(() => {
162
205
  if (this.showing.length === 0 && popoverOpen(region)) {
163
206
  region.hidePopover();
164
207
  }
@@ -182,9 +225,9 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
182
225
  return;
183
226
  }
184
227
  for (const one of arrived) {
185
- const words = (one.textContent ?? '').replace(/\s+/gu, ' ').trim();
228
+ const words = messageWords(one);
186
229
  if (words !== '') {
187
- speaker.ariaNotify(words);
230
+ speaker.ariaNotify(words, { priority: isDanger(one) ? 'high' : 'normal' });
188
231
  }
189
232
  }
190
233
  }
@@ -194,7 +237,9 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
194
237
  this.addEventListener('helix-dismiss', this.handleDismiss);
195
238
  document.addEventListener('keydown', this.handleHotkey);
196
239
  this.watcher = new MutationObserver((records) => {
197
- this.place(records.flatMap((record) => [...record.addedNodes]));
240
+ const added = records.flatMap((record) => [...record.addedNodes]);
241
+ this.place(added);
242
+ this.tell(added);
198
243
  this.sweep();
199
244
  });
200
245
  this.watcher.observe(this, {
@@ -220,7 +265,7 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
220
265
  this.place();
221
266
  }
222
267
  render() {
223
- return html `<div part="region" popover="manual" tabindex="-1"><slot></slot></div>`;
268
+ return html `<div part="region" popover="manual" tabindex="-1"><slot></slot><span part="live" role="status">${this.said}</span><span part="live" role="alert">${this.urgent}</span></div>`;
224
269
  }
225
270
  };
226
271
  __decorate([
@@ -232,10 +277,17 @@ __decorate([
232
277
  __decorate([
233
278
  property({ reflect: true })
234
279
  ], HelixHeadlessToast.prototype, "placement", void 0);
280
+ __decorate([
281
+ state()
282
+ ], HelixHeadlessToast.prototype, "said", void 0);
283
+ __decorate([
284
+ state()
285
+ ], HelixHeadlessToast.prototype, "urgent", void 0);
235
286
  HelixHeadlessToast = __decorate([
236
287
  customElement('helix-headless-toast')
237
288
  ], HelixHeadlessToast);
238
289
  export { HelixHeadlessToast };
290
+ const isDanger = (item) => (item.tone ?? item.getAttribute('tone')) === 'danger';
239
291
  const popoverOpen = (region) => {
240
292
  try {
241
293
  return region.matches(':popover-open');
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pieces of a message joined into one line a screen reader says, with a full
3
+ * stop between two where the first does not already end a sentence, so a
4
+ * heading and its detail are heard as two sentences and not run together.
5
+ */
6
+ export declare const joinSaid: (pieces: readonly string[]) => string;
7
+ /**
8
+ * What a message says as it arrives: the word for its tone, then its heading,
9
+ * its description and its text, and never its actions or its dismiss control,
10
+ * which a reader finds by moving into the message.
11
+ */
12
+ export declare const messageWords: (item: Element) => string;
@@ -0,0 +1,25 @@
1
+ import { toneMark } from '@fusion.dev/helix-core';
2
+ const UNSAID = new Set(['actions', 'dismiss-icon', 'icon']);
3
+ export const joinSaid = (pieces) => pieces
4
+ .map((piece) => piece.replace(/\s+/gu, ' ').trim())
5
+ .filter((piece) => piece !== '')
6
+ .reduce((line, piece) => line === '' ? piece : `${line}${/[.!?:;]$/u.test(line) ? '' : '.'} ${piece}`, '');
7
+ export const messageWords = (item) => {
8
+ const tone = item.tone ?? item.getAttribute('tone') ?? undefined;
9
+ const label = toneMark(tone)?.label;
10
+ const heading = [];
11
+ const rest = [];
12
+ for (const node of item.childNodes) {
13
+ if (node instanceof Element) {
14
+ const slot = node.getAttribute('slot') ?? '';
15
+ if (!UNSAID.has(slot)) {
16
+ ;
17
+ (slot === 'heading' ? heading : rest).push(node.textContent ?? '');
18
+ }
19
+ }
20
+ else if (node.nodeType === 3) {
21
+ rest.push(node.textContent ?? '');
22
+ }
23
+ }
24
+ return joinSaid([...(label ? [label] : []), ...heading, ...rest]);
25
+ };