@fusion.dev/helix-headless 0.142.0 → 0.143.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 (217) hide show
  1. package/custom-elements.json +16710 -707
  2. package/dist/accordion/item/accordion-item.d.ts +38 -2
  3. package/dist/accordion/item/accordion-item.js +38 -6
  4. package/dist/accordion/root/accordion-root.d.ts +23 -1
  5. package/dist/accordion/root/accordion-root.js +39 -3
  6. package/dist/announcement/root/announcement-root.d.ts +11 -7
  7. package/dist/announcement/root/announcement-root.js +1 -1
  8. package/dist/app-header/root/app-header-root.d.ts +21 -0
  9. package/dist/app-header/root/app-header-root.js +31 -5
  10. package/dist/area-chart/root/area-chart-root.js +16 -5
  11. package/dist/avatar/group/avatar-group.d.ts +24 -3
  12. package/dist/avatar/group/avatar-group.js +34 -2
  13. package/dist/avatar/root/avatar-root.d.ts +3 -1
  14. package/dist/avatar/root/avatar-root.js +8 -1
  15. package/dist/badge/root/badge-root.d.ts +8 -0
  16. package/dist/bar-chart/root/bar-chart-root.d.ts +19 -3
  17. package/dist/bar-chart/root/bar-chart-root.js +43 -13
  18. package/dist/blockquote/root/blockquote-root.d.ts +12 -7
  19. package/dist/blockquote/root/blockquote-root.js +6 -4
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -3
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +9 -1
  22. package/dist/button/root/button-root.d.ts +37 -0
  23. package/dist/button/root/button-root.js +44 -0
  24. package/dist/callout/root/callout-root.d.ts +14 -5
  25. package/dist/callout/root/callout-root.js +11 -3
  26. package/dist/canvas/root/canvas-root.d.ts +1 -0
  27. package/dist/canvas/root/canvas-root.js +10 -1
  28. package/dist/carousel/root/carousel-root.d.ts +47 -5
  29. package/dist/carousel/root/carousel-root.js +53 -21
  30. package/dist/chat/composer/chat-composer.d.ts +13 -11
  31. package/dist/chat/composer/chat-composer.js +6 -12
  32. package/dist/chat/message/chat-message.js +8 -1
  33. package/dist/chat/root/chat-root.d.ts +1 -0
  34. package/dist/chat/root/chat-root.js +2 -1
  35. package/dist/checkbox/root/checkbox-root.d.ts +7 -0
  36. package/dist/checkbox/root/checkbox-root.js +7 -3
  37. package/dist/checkbox-group/root/checkbox-group-root.d.ts +65 -32
  38. package/dist/checkbox-group/root/checkbox-group-root.js +85 -37
  39. package/dist/code/root/code-root.d.ts +1 -1
  40. package/dist/code-block/root/code-block-root.d.ts +19 -0
  41. package/dist/code-block/root/code-block-root.js +21 -1
  42. package/dist/color-input/root/color-input-root.d.ts +26 -1
  43. package/dist/color-input/root/color-input-root.js +32 -3
  44. package/dist/color-picker/root/color-picker-root.d.ts +37 -7
  45. package/dist/color-picker/root/color-picker-root.js +83 -6
  46. package/dist/combobox/root/combobox-root.d.ts +90 -2
  47. package/dist/combobox/root/combobox-root.js +160 -19
  48. package/dist/command/root/command-root.d.ts +17 -6
  49. package/dist/command/root/command-root.js +12 -1
  50. package/dist/copy-button/root/copy-button-root.d.ts +21 -6
  51. package/dist/copy-button/root/copy-button-root.js +11 -1
  52. package/dist/data-table/root/data-table-root.d.ts +164 -10
  53. package/dist/data-table/root/data-table-root.js +488 -57
  54. package/dist/date-input/root/date-input-root.d.ts +53 -8
  55. package/dist/date-input/root/date-input-root.js +70 -18
  56. package/dist/date-picker/root/date-picker-root.d.ts +84 -17
  57. package/dist/date-picker/root/date-picker-root.js +125 -20
  58. package/dist/dialog/root/dialog-root.d.ts +38 -10
  59. package/dist/dialog/root/dialog-root.js +55 -2
  60. package/dist/donut-chart/root/donut-chart-root.d.ts +39 -15
  61. package/dist/donut-chart/root/donut-chart-root.js +30 -10
  62. package/dist/drawer/root/drawer-root.d.ts +32 -7
  63. package/dist/drawer/root/drawer-root.js +50 -5
  64. package/dist/drilldown/item/drilldown-item.d.ts +16 -13
  65. package/dist/drilldown/item/drilldown-item.js +6 -2
  66. package/dist/drilldown/root/drilldown-root.d.ts +21 -5
  67. package/dist/drilldown/root/drilldown-root.js +26 -10
  68. package/dist/element-events.generated.js +35 -9
  69. package/dist/file-input/root/file-input-root.d.ts +18 -1
  70. package/dist/file-input/root/file-input-root.js +34 -8
  71. package/dist/filter-input/menu/filter-menu.d.ts +9 -0
  72. package/dist/filter-input/menu/filter-menu.js +8 -0
  73. package/dist/filter-input/root/filter-input-root.d.ts +5 -2
  74. package/dist/filter-input/root/filter-input-root.js +22 -7
  75. package/dist/format-date/root/format-date-root.d.ts +11 -3
  76. package/dist/format-date/root/format-date-root.js +26 -5
  77. package/dist/graph/root/graph-root.d.ts +24 -3
  78. package/dist/graph/root/graph-root.js +66 -12
  79. package/dist/heatmap/root/buckets.d.ts +2 -1
  80. package/dist/heatmap/root/buckets.js +5 -1
  81. package/dist/heatmap/root/heatmap-root.d.ts +27 -6
  82. package/dist/heatmap/root/heatmap-root.js +23 -6
  83. package/dist/icon/root/icon-root.d.ts +24 -3
  84. package/dist/icon/root/icon-root.js +34 -1
  85. package/dist/icon-button/root/icon-button-root.d.ts +1 -1
  86. package/dist/image/root/image-root.d.ts +9 -2
  87. package/dist/image/root/image-root.js +8 -2
  88. package/dist/input/root/input-root.d.ts +51 -9
  89. package/dist/input/root/input-root.js +46 -5
  90. package/dist/kbd/root/kbd-root.d.ts +12 -4
  91. package/dist/kbd/root/kbd-root.js +10 -11
  92. package/dist/legend/root/legend-root.d.ts +7 -0
  93. package/dist/legend/root/legend-root.js +7 -2
  94. package/dist/lightbox/item/lightbox-item.d.ts +2 -1
  95. package/dist/lightbox/root/lightbox-root.d.ts +16 -5
  96. package/dist/lightbox/root/lightbox-root.js +24 -3
  97. package/dist/line-chart/root/line-chart-root.d.ts +19 -2
  98. package/dist/line-chart/root/line-chart-root.js +40 -13
  99. package/dist/list/item/list-item.d.ts +11 -5
  100. package/dist/list/item/list-item.js +5 -6
  101. package/dist/listbox/root/listbox-root.d.ts +111 -2
  102. package/dist/listbox/root/listbox-root.js +144 -16
  103. package/dist/map/root/map-root.d.ts +19 -4
  104. package/dist/map/root/map-root.js +36 -7
  105. package/dist/megamenu/root/megamenu-root.d.ts +10 -1
  106. package/dist/megamenu/root/megamenu-root.js +10 -1
  107. package/dist/menu/item/menu-item.d.ts +41 -12
  108. package/dist/menu/item/menu-item.js +46 -3
  109. package/dist/menu/root/menu-root.d.ts +32 -2
  110. package/dist/menu/root/menu-root.js +48 -8
  111. package/dist/meter/root/meter-root.d.ts +6 -0
  112. package/dist/meter/root/meter-root.js +18 -2
  113. package/dist/navigation/root/navigation-root.d.ts +10 -2
  114. package/dist/navigation/root/navigation-root.js +13 -2
  115. package/dist/notifications/group/notification-group.d.ts +1 -0
  116. package/dist/notifications/notification/notification.d.ts +12 -0
  117. package/dist/notifications/notification/notification.js +31 -2
  118. package/dist/notifications/root/notifications-root.d.ts +7 -0
  119. package/dist/notifications/root/notifications-root.js +17 -2
  120. package/dist/number-input/root/number-input-root.d.ts +62 -7
  121. package/dist/number-input/root/number-input-root.js +68 -16
  122. package/dist/object-inspector/root/object-inspector-root.d.ts +48 -5
  123. package/dist/object-inspector/root/object-inspector-root.js +91 -11
  124. package/dist/option/root/option-root.d.ts +4 -1
  125. package/dist/option/root/option-root.js +1 -0
  126. package/dist/outline/root/outline-root.d.ts +13 -2
  127. package/dist/outline/root/outline-root.js +4 -2
  128. package/dist/pagination/root/pagination-root.d.ts +19 -1
  129. package/dist/pagination/root/pagination-root.js +49 -9
  130. package/dist/pagination/size/pagination-size.d.ts +13 -3
  131. package/dist/pagination/size/pagination-size.js +7 -0
  132. package/dist/pin-input/root/pin-input-root.d.ts +16 -0
  133. package/dist/pin-input/root/pin-input-root.js +19 -5
  134. package/dist/popover/root/popover-root.d.ts +19 -2
  135. package/dist/popover/root/popover-root.js +22 -1
  136. package/dist/progress/root/progress-root.d.ts +16 -1
  137. package/dist/progress/root/progress-root.js +31 -7
  138. package/dist/qr-code/root/qr-code-root.d.ts +19 -0
  139. package/dist/qr-code/root/qr-code-root.js +29 -1
  140. package/dist/radio/group/radio-group.d.ts +41 -0
  141. package/dist/radio/group/radio-group.js +50 -14
  142. package/dist/radio/root/radio-root.d.ts +8 -5
  143. package/dist/radio/root/radio-root.js +1 -2
  144. package/dist/rating/root/rating-root.d.ts +24 -1
  145. package/dist/rating/root/rating-root.js +24 -7
  146. package/dist/scatter-chart/root/scatter-chart-root.js +15 -7
  147. package/dist/select/root/select-root.d.ts +69 -3
  148. package/dist/select/root/select-root.js +92 -8
  149. package/dist/skeleton/root/skeleton-root.d.ts +14 -3
  150. package/dist/skeleton/root/skeleton-root.js +17 -3
  151. package/dist/skip-link/root/skip-link-root.d.ts +10 -0
  152. package/dist/skip-link/root/skip-link-root.js +15 -1
  153. package/dist/slider/root/slider-root.d.ts +20 -2
  154. package/dist/slider/root/slider-root.js +41 -3
  155. package/dist/sparkline/root/sparkline-root.d.ts +9 -10
  156. package/dist/speed-dial/item/speed-dial-item.d.ts +11 -5
  157. package/dist/speed-dial/item/speed-dial-item.js +7 -2
  158. package/dist/speed-dial/root/speed-dial-root.d.ts +15 -4
  159. package/dist/speed-dial/root/speed-dial-root.js +19 -5
  160. package/dist/splitter/root/splitter-root.d.ts +28 -6
  161. package/dist/splitter/root/splitter-root.js +35 -1
  162. package/dist/stat/root/stat-root.d.ts +7 -1
  163. package/dist/stat/root/stat-root.js +14 -1
  164. package/dist/steps/root/steps-root.js +1 -1
  165. package/dist/switch/root/switch-root.d.ts +2 -0
  166. package/dist/switch/root/switch-root.js +1 -0
  167. package/dist/table/cell/table-cell.d.ts +24 -4
  168. package/dist/table/cell/table-cell.js +21 -1
  169. package/dist/table/column/table-column.d.ts +32 -6
  170. package/dist/table/column/table-column.js +36 -6
  171. package/dist/table/root/table-root.d.ts +94 -3
  172. package/dist/table/root/table-root.js +128 -5
  173. package/dist/table/row/table-row.d.ts +7 -0
  174. package/dist/table/row/table-row.js +9 -1
  175. package/dist/tabs/root/tabs-root.d.ts +6 -1
  176. package/dist/tabs/root/tabs-root.js +6 -2
  177. package/dist/tag/root/tag-root.d.ts +23 -1
  178. package/dist/tag/root/tag-root.js +16 -2
  179. package/dist/textarea/root/textarea-root.d.ts +4 -0
  180. package/dist/textarea/root/textarea-root.js +1 -1
  181. package/dist/time-input/root/time-input-root.d.ts +28 -2
  182. package/dist/time-input/root/time-input-root.js +46 -10
  183. package/dist/time-picker/root/time-picker-root.d.ts +101 -18
  184. package/dist/time-picker/root/time-picker-root.js +115 -16
  185. package/dist/toast/root/toast-root.d.ts +64 -1
  186. package/dist/toast/root/toast-root.js +147 -2
  187. package/dist/toggle/group/toggle-group.d.ts +38 -1
  188. package/dist/toggle/group/toggle-group.js +34 -8
  189. package/dist/toggle/root/toggle-root.d.ts +14 -10
  190. package/dist/toggle/root/toggle-root.js +3 -3
  191. package/dist/toolbar/root/toolbar-root.d.ts +55 -15
  192. package/dist/toolbar/root/toolbar-root.js +99 -16
  193. package/dist/tooltip/root/tooltip-root.d.ts +37 -4
  194. package/dist/tooltip/root/tooltip-root.js +48 -0
  195. package/dist/tour/root/tour-root.d.ts +34 -5
  196. package/dist/tour/root/tour-root.js +51 -14
  197. package/dist/tour/stop/tour-stop.d.ts +18 -1
  198. package/dist/tour/stop/tour-stop.js +25 -5
  199. package/dist/tree/item/tree-item.d.ts +21 -5
  200. package/dist/tree/item/tree-item.js +14 -4
  201. package/dist/tree/root/tree-root.d.ts +29 -5
  202. package/dist/tree/root/tree-root.js +64 -8
  203. package/dist/trend/root/trend-root.d.ts +23 -1
  204. package/dist/trend/root/trend-root.js +28 -7
  205. package/dist/video/root/keys.d.ts +5 -0
  206. package/dist/video/root/keys.js +13 -1
  207. package/dist/video/root/video-root.d.ts +19 -0
  208. package/dist/video/root/video-root.js +48 -4
  209. package/dist/video-playlist/menu/video-playlist-menu.d.ts +20 -2
  210. package/dist/video-playlist/menu/video-playlist-menu.js +27 -5
  211. package/dist/video-playlist/root/video-playlist-root.js +6 -3
  212. package/dist/virtual-list/root/virtual-list-root.d.ts +52 -4
  213. package/dist/virtual-list/root/virtual-list-root.js +103 -21
  214. package/lint.json +1 -1
  215. package/package.json +5 -5
  216. package/types/jsx.d.ts +4348 -965
  217. package/types/vue.d.ts +972 -232
@@ -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 { valueAsTime as asTime, customElement, timeAsValue as fromTime, HelixFormElement, MixinControllerValue, minutesAsValue, reportValues, valueAsMinutes, writeTime, } from '@fusion.dev/helix-core';
7
+ import { valueAsTime as asTime, customElement, elementById, fillTemplate, timeAsValue as fromTime, HelixFormElement, MixinControllerValue, minutesAsValue, reportValues, valueAsMinutes, writeTime, writtenList, } 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';
@@ -12,17 +12,22 @@ import '../../listbox/index.js';
12
12
  import '../../option/index.js';
13
13
  const isTwelveHour = (locale) => new Intl.DateTimeFormat(locale || undefined, { timeStyle: 'short' }).resolvedOptions().hour12 ===
14
14
  true;
15
+ const dayPeriod = (half, locale) => new Intl.DateTimeFormat(locale || undefined, { hour: 'numeric', hour12: true, timeZone: 'UTC' })
16
+ .formatToParts(new Date(Date.UTC(1970, 0, 1, half === 'AM' ? 9 : 21)))
17
+ .find((part) => part.type === 'dayPeriod')?.value ?? half;
15
18
  let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControllerValue(HelixFormElement) {
16
19
  constructor() {
17
20
  super(...arguments);
18
21
  this.disableTimes = '';
22
+ this.hourLabel = 'Hour';
19
23
  this.invalid = false;
20
24
  this.label = 'Time';
21
25
  this.locale = '';
26
+ this.manual = false;
22
27
  this.max = '23:59';
28
+ this.meridiemLabel = 'Before or after noon';
23
29
  this.min = '00:00';
24
- this.readOnly = false;
25
- this.required = false;
30
+ this.minuteLabel = 'Minute';
26
31
  this.handleListInput = (event) => {
27
32
  event.stopPropagation();
28
33
  };
@@ -69,14 +74,17 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
69
74
  const half = hour % 12;
70
75
  next = (half + (to === 'PM' ? 12 : 0)) * 60 + minute;
71
76
  }
72
- const whole = minutesAsValue(next);
77
+ const whole = minutesAsValue(this.nearest(next));
73
78
  if (this.barred(whole)) {
74
79
  return;
75
80
  }
76
81
  this.requestValue(whole);
77
82
  };
83
+ this.readOnly = false;
84
+ this.required = false;
78
85
  this.step = 15;
79
86
  this.listRevealed = false;
87
+ this.naming = undefined;
80
88
  }
81
89
  get times() {
82
90
  const from = valueAsMinutes(this.min) ?? 0;
@@ -89,11 +97,17 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
89
97
  return found;
90
98
  }
91
99
  barred(at) {
92
- return this.disableTimes
93
- .split(',')
94
- .map((one) => one.trim())
95
- .filter(Boolean)
96
- .includes(at);
100
+ return writtenList(this.disableTimes).includes(at);
101
+ }
102
+ nearest(minutes) {
103
+ const times = this.times;
104
+ let best = times[0] ?? minutes;
105
+ for (const at of times) {
106
+ if (Math.abs(at - minutes) < Math.abs(best - minutes)) {
107
+ best = at;
108
+ }
109
+ }
110
+ return best;
97
111
  }
98
112
  wholeHourBarred(shown, half) {
99
113
  const whole = half === undefined ? shown : (shown % 12) + (half === 'PM' ? 12 : 0);
@@ -148,6 +162,34 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
148
162
  }
149
163
  return super.requestValue(next);
150
164
  }
165
+ fault(at) {
166
+ const minutes = valueAsMinutes(at) ?? 0;
167
+ const floor = valueAsMinutes(this.min) ?? 0;
168
+ const ceiling = valueAsMinutes(this.max) ?? 1439;
169
+ if (minutes < floor) {
170
+ return [
171
+ 'rangeUnderflow',
172
+ fillTemplate(this.rangeUnderflowMessage || 'Choose a time at or after {min}.', {
173
+ min: writeTime(this.min, this.locale),
174
+ }),
175
+ ];
176
+ }
177
+ if (minutes > ceiling) {
178
+ return [
179
+ 'rangeOverflow',
180
+ fillTemplate(this.rangeOverflowMessage || 'Choose a time at or before {max}.', {
181
+ max: writeTime(this.max, this.locale),
182
+ }),
183
+ ];
184
+ }
185
+ if (this.barred(at)) {
186
+ return ['badInput', this.unavailableMessage || 'Choose a time that is available.'];
187
+ }
188
+ if (!this.times.includes(minutes)) {
189
+ return ['stepMismatch', this.stepMismatchMessage || 'Choose one of the times on offer.'];
190
+ }
191
+ return undefined;
192
+ }
151
193
  reportRequired() {
152
194
  const anchor = this.partNamed('list') ?? undefined;
153
195
  if (this.required && this.value === '') {
@@ -162,10 +204,17 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
162
204
  : 'Enter times as HH:mm, separated by commas.'), anchor);
163
205
  return;
164
206
  }
207
+ for (const one of held) {
208
+ const fault = this.fault(one);
209
+ if (fault) {
210
+ this.internals.setValidity({ [fault[0]]: true }, fault[1], anchor);
211
+ return;
212
+ }
213
+ }
165
214
  this.internals.setValidity({});
166
215
  }
167
216
  report() {
168
- if (this.disabled) {
217
+ if (this.isDisabled) {
169
218
  this.internals.setFormValue(null);
170
219
  return;
171
220
  }
@@ -178,6 +227,25 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
178
227
  await Promise.all(settling.map((one) => one.updateComplete));
179
228
  this.reveal();
180
229
  }
230
+ get listName() {
231
+ const named = (this.getAttribute('aria-labelledby') ?? '')
232
+ .split(/\s+/)
233
+ .filter(Boolean)
234
+ .map((id) => elementById(this, id)?.textContent?.trim() ?? '')
235
+ .filter(Boolean)
236
+ .join(' ');
237
+ return named || this.getAttribute('aria-label') || this.label;
238
+ }
239
+ connectedCallback() {
240
+ super.connectedCallback();
241
+ this.naming = new MutationObserver(() => this.requestUpdate());
242
+ this.naming.observe(this, { attributeFilter: ['aria-label', 'aria-labelledby'] });
243
+ }
244
+ disconnectedCallback() {
245
+ super.disconnectedCallback();
246
+ this.naming?.disconnect();
247
+ this.naming = undefined;
248
+ }
181
249
  willUpdate(changed) {
182
250
  super.willUpdate(changed);
183
251
  this.internals.ariaInvalid = this.invalid ? 'true' : null;
@@ -196,7 +264,8 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
196
264
  }
197
265
  }
198
266
  focus(options) {
199
- this.partNamed('list')?.focus(options);
267
+ ;
268
+ (this.partNamed('list') ?? this.partNamed('column'))?.focus(options);
200
269
  }
201
270
  renderColumn(part, label, values, written, value, barred = () => false) {
202
271
  return html `<${this.tag('listbox')}
@@ -234,11 +303,16 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
234
303
  const hours = twelve
235
304
  ? [...new Set(this.hours.map((one) => (one % 12 === 0 ? 12 : one % 12)))].sort((a, b) => a - b)
236
305
  : this.hours;
237
- return html `<div part="columns" data-unset=${this.value === '' ? '' : nothing}>
238
- ${this.renderColumn('hour', 'Hour', hours.map(String), (one) => (twelve ? one : one.padStart(2, '0')), shownHour, (one) => this.wholeHourBarred(Number(one), twelve ? meridiem : undefined))}
239
- ${this.renderColumn('minute', 'Minute', this.minutes.map(String), (one) => one.padStart(2, '0'), String(minute), (one) => this.barred(minutesAsValue(hour * 60 + Number(one))))}
306
+ return html `<div
307
+ part="columns"
308
+ role="group"
309
+ aria-label=${this.listName}
310
+ data-unset=${this.value === '' ? '' : nothing}
311
+ >
312
+ ${this.renderColumn('hour', this.hourLabel, hours.map(String), (one) => (twelve ? one : one.padStart(2, '0')), shownHour, (one) => this.wholeHourBarred(Number(one), twelve ? meridiem : undefined))}
313
+ ${this.renderColumn('minute', this.minuteLabel, this.minutes.map(String), (one) => one.padStart(2, '0'), String(minute), (one) => this.barred(minutesAsValue(hour * 60 + Number(one))))}
240
314
  ${twelve
241
- ? this.renderColumn('meridiem', 'Before or after noon', ['AM', 'PM'], (one) => one, meridiem, (one) => this.halfBarred(one))
315
+ ? this.renderColumn('meridiem', this.meridiemLabel, ['AM', 'PM'], (one) => dayPeriod(one, this.locale), meridiem, (one) => this.halfBarred(one))
242
316
  : nothing}
243
317
  </div>`;
244
318
  }
@@ -249,9 +323,10 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
249
323
  exportparts="empty: list-empty, feedback: list-feedback, filter-icon: list-filter-icon,
250
324
  filter-mark: list-filter-mark, hint: list-hint, items: list-items, label: list-label,
251
325
  live: list-live, loading: list-loading, search: list-search, search-field: list-search-field"
252
- aria-label=${this.label}
326
+ aria-label=${this.listName}
253
327
  data-unset=${this.value === '' ? '' : nothing}
254
328
  ?disabled=${this.isDisabled}
329
+ ?manual=${this.manual}
255
330
  ?multiple=${this.mode !== undefined}
256
331
  .value=${this.value}
257
332
  @helix-before-change=${this.handleChange}
@@ -299,6 +374,9 @@ __decorate([
299
374
  __decorate([
300
375
  property({ attribute: 'disable-times' })
301
376
  ], HelixHeadlessTimePicker.prototype, "disableTimes", void 0);
377
+ __decorate([
378
+ property({ attribute: 'hour-label' })
379
+ ], HelixHeadlessTimePicker.prototype, "hourLabel", void 0);
302
380
  __decorate([
303
381
  property({ reflect: true, type: Boolean })
304
382
  ], HelixHeadlessTimePicker.prototype, "invalid", void 0);
@@ -308,15 +386,30 @@ __decorate([
308
386
  __decorate([
309
387
  property()
310
388
  ], HelixHeadlessTimePicker.prototype, "locale", void 0);
389
+ __decorate([
390
+ property({ type: Boolean })
391
+ ], HelixHeadlessTimePicker.prototype, "manual", void 0);
311
392
  __decorate([
312
393
  property()
313
394
  ], HelixHeadlessTimePicker.prototype, "max", void 0);
395
+ __decorate([
396
+ property({ attribute: 'meridiem-label' })
397
+ ], HelixHeadlessTimePicker.prototype, "meridiemLabel", void 0);
314
398
  __decorate([
315
399
  property()
316
400
  ], HelixHeadlessTimePicker.prototype, "min", void 0);
401
+ __decorate([
402
+ property({ attribute: 'minute-label' })
403
+ ], HelixHeadlessTimePicker.prototype, "minuteLabel", void 0);
317
404
  __decorate([
318
405
  property({ reflect: true })
319
406
  ], HelixHeadlessTimePicker.prototype, "mode", void 0);
407
+ __decorate([
408
+ property({ attribute: 'range-overflow-message' })
409
+ ], HelixHeadlessTimePicker.prototype, "rangeOverflowMessage", void 0);
410
+ __decorate([
411
+ property({ attribute: 'range-underflow-message' })
412
+ ], HelixHeadlessTimePicker.prototype, "rangeUnderflowMessage", void 0);
320
413
  __decorate([
321
414
  property({ attribute: 'readonly', reflect: true, type: Boolean })
322
415
  ], HelixHeadlessTimePicker.prototype, "readOnly", void 0);
@@ -326,6 +419,12 @@ __decorate([
326
419
  __decorate([
327
420
  property({ type: Number })
328
421
  ], HelixHeadlessTimePicker.prototype, "step", void 0);
422
+ __decorate([
423
+ property({ attribute: 'step-mismatch-message' })
424
+ ], HelixHeadlessTimePicker.prototype, "stepMismatchMessage", void 0);
425
+ __decorate([
426
+ property({ attribute: 'unavailable-message' })
427
+ ], HelixHeadlessTimePicker.prototype, "unavailableMessage", void 0);
329
428
  __decorate([
330
429
  property({ attribute: 'value-missing-message' })
331
430
  ], HelixHeadlessTimePicker.prototype, "valueMissingMessage", void 0);
@@ -23,6 +23,19 @@ import { type PropertyValues, type TemplateResult } from 'lit';
23
23
  * your app's state, which also lets you render the same messages on the server,
24
24
  * in a test, or more than once.
25
25
  *
26
+ * **The stack is in the top layer.** The `region` part is a `popover="manual"`
27
+ * shown while a message is showing, and shown again above a modal dialog that
28
+ * opened after it. Position the `region` part, which is the box in the top
29
+ * layer. While a modal `Dialog`, `Drawer` or `Lightbox` is open, the browser
30
+ * makes everything outside it inert, the stack included, so a message stays
31
+ * visible and its actions cannot be pressed. Where the browser has
32
+ * `ariaNotify`, a message that arrives then is announced through it. For a
33
+ * message with an action, render a second region inside the dialog.
34
+ *
35
+ * When a message with focus inside it is dismissed, focus moves to the next
36
+ * message, then the one before it, then back to where it was before it entered
37
+ * the stack.
38
+ *
26
39
  * @since 0.1.0
27
40
  * @status stable
28
41
  * @category overlays
@@ -33,7 +46,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
33
46
  *
34
47
  * @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.
35
48
  *
36
- * @csspart region - The stack the messages are in.
49
+ * @csspart region - The stack the messages are in. It is in the top layer while a message shows.
37
50
  */
38
51
  export declare class HelixHeadlessToast extends HelixElement {
39
52
  /**
@@ -68,6 +81,56 @@ export declare class HelixHeadlessToast extends HelixElement {
68
81
  */
69
82
  placement?: 'bottom-center' | 'bottom-end' | 'bottom-start' | 'top-center' | 'top-end' | 'top-start';
70
83
  protected willUpdate(changed: PropertyValues): void;
84
+ /** The element focus came from when it entered the stack. */
85
+ private focusOrigin;
86
+ /** Watches the messages arrive and hide, to raise or lower the stack. */
87
+ private watcher;
88
+ /** The messages that are showing, in order. */
89
+ private get showing();
90
+ private get region();
91
+ /**
92
+ * Puts the stack in the top layer while a message shows, and on top of it.
93
+ *
94
+ * Shown again when a message arrives under a modal opened after it, because
95
+ * the top layer is ordered by when each element entered it. Only then,
96
+ * because showing it again starts every message's entrance over.
97
+ */
98
+ private place;
99
+ /**
100
+ * Takes the stack out of the top layer once the last message has finished
101
+ * leaving. Hidden at once, the stack would cut the exit short.
102
+ */
103
+ private lowerWhenStill;
104
+ /**
105
+ * Whether a modal dialog this stack is not inside is open, which makes the
106
+ * stack inert.
107
+ *
108
+ * Two places to look: the modal `Dialog` and `Drawer` the scroll lock
109
+ * records, whose `<dialog>` is in a shadow root, and an open modal `<dialog>`
110
+ * in the document itself.
111
+ */
112
+ private blocked;
113
+ /**
114
+ * Announces messages that arrived while the live region is inert.
115
+ *
116
+ * `ariaNotify` speaks without a live region in the accessibility tree, which
117
+ * is the one channel left under a modal. A browser without it says nothing,
118
+ * as it did before.
119
+ */
120
+ private notify;
121
+ /** Remembers where focus came from when it enters the stack. */
122
+ private readonly handleFocusIn;
123
+ /**
124
+ * Keeps focus on the stack when the message holding it goes.
125
+ *
126
+ * Read after the item's update, because the dismissal is a request a
127
+ * listener after this one can still refuse. A refused one leaves the
128
+ * message, and focus with it.
129
+ */
130
+ private readonly handleDismiss;
131
+ connectedCallback(): void;
132
+ disconnectedCallback(): void;
133
+ protected firstUpdated(changed: PropertyValues): void;
71
134
  protected render(): TemplateResult;
72
135
  }
73
136
  declare global {
@@ -4,13 +4,56 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
7
+ import { customElement, deepActiveElement, deepContains, focusableElements, HelixElement, hostAria, scrollHolders, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.label = 'Notifications';
14
+ this.handleFocusIn = (event) => {
15
+ const from = event.relatedTarget;
16
+ if (from instanceof HTMLElement && !deepContains(this, from)) {
17
+ this.focusOrigin = from;
18
+ }
19
+ };
20
+ this.handleDismiss = (event) => {
21
+ const item = event.target;
22
+ if (!(item instanceof HTMLElement) || item.parentElement !== this) {
23
+ return;
24
+ }
25
+ if (!deepContains(item, deepActiveElement())) {
26
+ return;
27
+ }
28
+ const before = this.showing;
29
+ const at = before.indexOf(item);
30
+ void Promise.resolve()
31
+ .then(() => item.updateComplete)
32
+ .then(() => {
33
+ if (!item.hidden) {
34
+ return;
35
+ }
36
+ const active = deepActiveElement();
37
+ if (active && active !== document.body && !deepContains(item, active)) {
38
+ return;
39
+ }
40
+ const after = before.slice(at + 1).filter((one) => !one.hidden);
41
+ const earlier = before
42
+ .slice(0, at)
43
+ .filter((one) => !one.hidden)
44
+ .reverse();
45
+ for (const next of [...after, ...earlier]) {
46
+ const target = focusTarget(next);
47
+ if (target) {
48
+ target.focus();
49
+ return;
50
+ }
51
+ }
52
+ if (this.focusOrigin?.isConnected) {
53
+ this.focusOrigin.focus();
54
+ }
55
+ });
56
+ };
14
57
  }
15
58
  willUpdate(changed) {
16
59
  super.willUpdate(changed);
@@ -19,8 +62,99 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
19
62
  this.internals.ariaRelevant = 'additions';
20
63
  this.internals.ariaAtomic = 'false';
21
64
  }
65
+ get showing() {
66
+ return [...this.children].filter((one) => one instanceof HTMLElement && !one.hidden);
67
+ }
68
+ get region() {
69
+ return this.partNamed('region');
70
+ }
71
+ place(added = []) {
72
+ const region = this.region;
73
+ if (!region || typeof region.showPopover !== 'function') {
74
+ return;
75
+ }
76
+ const showing = this.showing;
77
+ const open = popoverOpen(region);
78
+ if (showing.length === 0) {
79
+ if (open) {
80
+ this.lowerWhenStill(region);
81
+ }
82
+ return;
83
+ }
84
+ const arrived = added.filter((one) => one instanceof HTMLElement && showing.includes(one));
85
+ const blocked = arrived.length > 0 && this.blocked();
86
+ if (open && blocked) {
87
+ region.hidePopover();
88
+ }
89
+ if (!open || blocked) {
90
+ region.showPopover();
91
+ }
92
+ if (blocked) {
93
+ this.notify(arrived);
94
+ }
95
+ }
96
+ lowerWhenStill(region) {
97
+ const running = typeof this.getAnimations === 'function' ? this.getAnimations({ subtree: true }) : [];
98
+ void Promise.allSettled(running.map((one) => one.finished)).then(() => {
99
+ if (this.showing.length === 0 && popoverOpen(region)) {
100
+ region.hidePopover();
101
+ }
102
+ });
103
+ }
104
+ blocked() {
105
+ const holders = scrollHolders();
106
+ try {
107
+ const native = document.querySelector('dialog:modal');
108
+ if (native) {
109
+ holders.push(native);
110
+ }
111
+ }
112
+ catch {
113
+ }
114
+ return holders.some((holder) => !deepContains(holder, this));
115
+ }
116
+ notify(arrived) {
117
+ const speaker = document;
118
+ if (typeof speaker.ariaNotify !== 'function') {
119
+ return;
120
+ }
121
+ for (const one of arrived) {
122
+ const words = (one.textContent ?? '').replace(/\s+/gu, ' ').trim();
123
+ if (words !== '') {
124
+ speaker.ariaNotify(words);
125
+ }
126
+ }
127
+ }
128
+ connectedCallback() {
129
+ super.connectedCallback();
130
+ this.addEventListener('focusin', this.handleFocusIn);
131
+ this.addEventListener('helix-dismiss', this.handleDismiss);
132
+ this.watcher = new MutationObserver((records) => {
133
+ this.place(records.flatMap((record) => [...record.addedNodes]));
134
+ });
135
+ this.watcher.observe(this, {
136
+ attributeFilter: ['hidden'],
137
+ attributes: true,
138
+ childList: true,
139
+ subtree: true,
140
+ });
141
+ if (this.hasUpdated) {
142
+ this.place();
143
+ }
144
+ }
145
+ disconnectedCallback() {
146
+ super.disconnectedCallback();
147
+ this.removeEventListener('focusin', this.handleFocusIn);
148
+ this.removeEventListener('helix-dismiss', this.handleDismiss);
149
+ this.watcher?.disconnect();
150
+ this.watcher = undefined;
151
+ }
152
+ firstUpdated(changed) {
153
+ super.firstUpdated(changed);
154
+ this.place();
155
+ }
22
156
  render() {
23
- return html `<div part="region"><slot></slot></div>`;
157
+ return html `<div part="region" popover="manual"><slot></slot></div>`;
24
158
  }
25
159
  };
26
160
  __decorate([
@@ -33,3 +167,14 @@ HelixHeadlessToast = __decorate([
33
167
  customElement('helix-headless-toast')
34
168
  ], HelixHeadlessToast);
35
169
  export { HelixHeadlessToast };
170
+ const popoverOpen = (region) => {
171
+ try {
172
+ return region.matches(':popover-open');
173
+ }
174
+ catch {
175
+ return false;
176
+ }
177
+ };
178
+ const focusTarget = (item) => item.shadowRoot?.querySelector('[part="dismiss"]') ??
179
+ focusableElements(item)[0] ??
180
+ null;
@@ -21,10 +21,13 @@ declare const HelixHeadlessToggleGroup_base: (new (...args: any[]) => import("@f
21
21
  *
22
22
  * @slot - The group's items.
23
23
  * @slot end - Content after the track, on the same line, such as a count or a reset.
24
+ * @slot feedback - The message that says what is wrong, for content richer than a string. It shows while there is a message.
25
+ * @slot hint - Help under the control, for content richer than the `hint` text.
26
+ * @slot label - The label, for content richer than the `label` text.
24
27
  * @slot start - Content before the track, on the same line, such as an icon or a caption.
25
28
  *
26
29
  * @event {Event} change - Fired after the value changes.
27
- * @event {HelixBeforeChangeEvent} helix-before-change - Fired before the choice changes. Cancelable: call `preventDefault()` to keep the current choice. It is the only way to block a change.
30
+ * @event {HelixBeforeChangeEvent} helix-before-change - Fired before the choice changes. Cancelable: call `preventDefault()` to keep the current choice.
28
31
  * @event {HelixSelectEvent} helix-select - Fired when an item is chosen. The item sends it, and you listen for it on the group. Cancelable: call `preventDefault()` to keep the current choice.
29
32
  * @event {Event} input - Fired after the value changes.
30
33
  *
@@ -85,11 +88,45 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
85
88
  private adopt;
86
89
  /** Marks the chosen item, separately from placing the indicator. */
87
90
  private sync;
91
+ /**
92
+ * The item that holds the group's tab stop: the chosen one.
93
+ *
94
+ * With nothing chosen there is no selection to carry the tab stop, so the
95
+ * first item holds one until there is. Otherwise an unanswered group is a
96
+ * control the keyboard cannot enter, which is what the fallback was quietly
97
+ * also doing for it. A disabled answer takes no focus either, so the entry
98
+ * goes to the first item that can be chosen, as in `Radio.Group`.
99
+ */
100
+ private entry;
101
+ /**
102
+ * Whether the group holds a tab stop of its own.
103
+ *
104
+ * A toolbar is one tab stop, so a toolbar the group sits in sets this to
105
+ * `false` while its stop is on another control, and every item, the chosen
106
+ * one too, is then out of the tab order. The arrow keys inside the group
107
+ * work as before once focus is in it.
108
+ *
109
+ * No attribute: it is coordination between elements, and a consumer has no
110
+ * reason to set it.
111
+ */
112
+ focusable: boolean;
113
+ /**
114
+ * Focus goes to the item that holds the stop, and to the item that would
115
+ * hold it while a toolbar holds the stop instead, so a label press or a
116
+ * refused submission still lands in the group.
117
+ */
118
+ protected get focusTarget(): HTMLElement | null;
88
119
  private readonly indicator;
89
120
  protected readonly keys: KeyboardNavigationController;
90
121
  protected readonly cascade: DisabledCascadeController;
91
122
  private get items();
92
123
  private readonly handleSelection;
124
+ /**
125
+ * Whether the group holds an answer the form can submit: a value an item
126
+ * here carries. A chosen item that was removed is no longer an answer, and
127
+ * `value` keeps the name for an item that arrives later.
128
+ */
129
+ private get answered();
93
130
  connectedCallback(): void;
94
131
  disconnectedCallback(): void;
95
132
  protected firstUpdated(changed: PropertyValues): void;
@@ -11,6 +11,7 @@ import { HelixHeadlessToggle } from '../root/toggle-root.js';
11
11
  let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinControllerValue(MixinField(HelixFormElement)) {
12
12
  constructor() {
13
13
  super(...arguments);
14
+ this.focusable = true;
14
15
  this.indicator = new IndicatorController(this, {
15
16
  active: () => this.items.find((item) => item.value === this.value),
16
17
  indicator: () => this.partNamed('indicator'),
@@ -22,10 +23,12 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
22
23
  items: () => this.items,
23
24
  });
24
25
  this.handleSelection = (event) => {
25
- if (event.defaultPrevented) {
26
- return;
27
- }
28
- this.requestValue(event.detail.value);
26
+ queueMicrotask(() => {
27
+ if (event.defaultPrevented) {
28
+ return;
29
+ }
30
+ this.requestValue(event.detail.value);
31
+ });
29
32
  };
30
33
  }
31
34
  adopt() {
@@ -39,14 +42,33 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
39
42
  }
40
43
  sync() {
41
44
  const active = this.items.find((item) => item.value === this.value);
42
- const entry = active ? undefined : this.items.find((item) => !item.disabled);
45
+ const entry = this.focusable ? this.entry() : undefined;
43
46
  for (const item of this.items) {
44
47
  item.selected = item === active;
45
48
  item.focusable = item === entry;
46
49
  }
47
50
  }
51
+ entry() {
52
+ const active = this.items.find((item) => item.value === this.value);
53
+ return active && !active.disabled ? active : this.items.find((item) => !item.disabled);
54
+ }
55
+ get focusTarget() {
56
+ return (super.focusTarget ??
57
+ this.entry()?.shadowRoot?.querySelector('[part="item"]') ??
58
+ null);
59
+ }
48
60
  get items() {
49
- return [...this.children].filter((child) => child instanceof HelixHeadlessToggle);
61
+ return [...(this.children ?? [])].filter((child) => child instanceof HelixHeadlessToggle);
62
+ }
63
+ get answered() {
64
+ if (this.value === '') {
65
+ return false;
66
+ }
67
+ if (this.children === undefined) {
68
+ return true;
69
+ }
70
+ const items = this.items;
71
+ return items.length === 0 || items.some((item) => item.value === this.value);
50
72
  }
51
73
  connectedCallback() {
52
74
  super.connectedCallback();
@@ -71,10 +93,10 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
71
93
  super.willUpdate(changed);
72
94
  this.adopt();
73
95
  hostAria(this.internals, { label: this.label, role: 'radiogroup' });
74
- this.internals.setFormValue(this.value === '' ? null : this.value);
96
+ this.internals.setFormValue(this.answered ? this.value : null);
75
97
  }
76
98
  validate() {
77
- this.reportRequired(this.value === '', 'Choose one to continue.', 'track');
99
+ this.reportRequired(!this.answered, 'Choose one to continue.', 'track');
78
100
  }
79
101
  render() {
80
102
  const inside = this.labelPlacement === 'start';
@@ -90,6 +112,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
90
112
  this.adopt();
91
113
  this.sync();
92
114
  this.indicator.place();
115
+ this.requestUpdate();
93
116
  }}
94
117
  ></slot>
95
118
  </div>
@@ -98,6 +121,9 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
98
121
  ${this.renderHint()}${this.renderFeedback()}`;
99
122
  }
100
123
  };
124
+ __decorate([
125
+ property({ attribute: false })
126
+ ], HelixHeadlessToggleGroup.prototype, "focusable", void 0);
101
127
  __decorate([
102
128
  property({ attribute: 'label-placement', reflect: true })
103
129
  ], HelixHeadlessToggleGroup.prototype, "labelPlacement", void 0);