@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 { valueAsDate as asDate, customElement, dateAsValue as fromDate, HelixFormElement, HelixNavigateEvent, hostAria, inlineStep, MixinControllerValue, reportValues, requestChange, } from '@fusion.dev/helix-core';
7
+ import { valueAsDate as asDate, customElement, fillTemplate, dateAsValue as fromDate, HelixFormElement, HelixNavigateEvent, hostAria, inlineStep, MixinControllerValue, reportValues, requestChange, writtenList, } from '@fusion.dev/helix-core';
8
8
  import { caretLeft, caretRight } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -31,6 +31,17 @@ const list = (value) => value
31
31
  .split(',')
32
32
  .map((entry) => entry.trim())
33
33
  .filter(Boolean);
34
+ const weekStartOf = (locale) => {
35
+ try {
36
+ const tag = new Intl.Locale(locale);
37
+ const first = tag.getWeekInfo?.().firstDay ?? tag.weekInfo?.firstDay;
38
+ return first === undefined ? 0 : first % 7;
39
+ }
40
+ catch {
41
+ return 0;
42
+ }
43
+ };
44
+ const monthOf = (date) => `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
34
45
  const addDays = (date, days) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
35
46
  const sameMonth = (left, right) => left.getUTCFullYear() === right.getUTCFullYear() && left.getUTCMonth() === right.getUTCMonth();
36
47
  const addMonths = (date, months) => {
@@ -44,7 +55,6 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
44
55
  this.defaultMonth = '';
45
56
  this.disableDates = '';
46
57
  this.disableDays = '';
47
- this.firstDayOfWeek = 0;
48
58
  this.invalid = false;
49
59
  this.label = 'Date Picker';
50
60
  this.locale = '';
@@ -54,10 +64,10 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
54
64
  this.months = 1;
55
65
  this.nextLabel = 'Next month';
56
66
  this.previousLabel = 'Previous month';
57
- this.readOnly = false;
58
67
  this.focused = '';
68
+ this.choosing = false;
59
69
  this.handleKeydown = (event) => {
60
- if (!KEYS.has(event.key) || this.disabled) {
70
+ if (!KEYS.has(event.key) || this.isDisabled) {
61
71
  return;
62
72
  }
63
73
  const path = event.composedPath();
@@ -80,10 +90,10 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
80
90
  this.moveTo(addDays(from, 7));
81
91
  }
82
92
  else if (event.key === 'Home') {
83
- this.moveTo(addDays(from, -((from.getUTCDay() - this.firstDayOfWeek + 7) % 7)));
93
+ this.moveTo(addDays(from, -((from.getUTCDay() - this.weekStart + 7) % 7)));
84
94
  }
85
95
  else if (event.key === 'End') {
86
- this.moveTo(addDays(from, 6 - ((from.getUTCDay() - this.firstDayOfWeek + 7) % 7)));
96
+ this.moveTo(addDays(from, 6 - ((from.getUTCDay() - this.weekStart + 7) % 7)));
87
97
  }
88
98
  else {
89
99
  const step = event.key === 'PageUp' ? -1 : 1;
@@ -91,6 +101,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
91
101
  }
92
102
  event.preventDefault();
93
103
  };
104
+ this.readOnly = false;
94
105
  this.required = false;
95
106
  this.step = 1;
96
107
  }
@@ -105,6 +116,12 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
105
116
  get selected() {
106
117
  return list(this.value);
107
118
  }
119
+ get weekStart() {
120
+ if (this.firstDayOfWeek !== undefined && Number.isFinite(this.firstDayOfWeek)) {
121
+ return ((Math.floor(this.firstDayOfWeek) % 7) + 7) % 7;
122
+ }
123
+ return this.locale === '' ? 0 : weekStartOf(this.locale);
124
+ }
108
125
  get range() {
109
126
  const dates = this.selected;
110
127
  if (this.mode !== 'range' || dates.length < 2) {
@@ -141,7 +158,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
141
158
  return shown;
142
159
  }
143
160
  weeksOf(month) {
144
- const offset = (month.getUTCDay() - this.firstDayOfWeek + 7) % 7;
161
+ const offset = (month.getUTCDay() - this.weekStart + 7) % 7;
145
162
  const start = addDays(month, -offset);
146
163
  return Array.from({ length: 6 }, (_, week) => Array.from({ length: 7 }, (_, day) => addDays(start, week * 7 + day)));
147
164
  }
@@ -153,7 +170,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
153
170
  if (this.max !== '' && iso > this.max) {
154
171
  return true;
155
172
  }
156
- if (list(this.disableDays).includes(String(date.getUTCDay()))) {
173
+ if (writtenList(this.disableDays).includes(String(date.getUTCDay()))) {
157
174
  return true;
158
175
  }
159
176
  const step = Math.max(1, Math.floor(this.step) || 1);
@@ -164,13 +181,32 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
164
181
  return true;
165
182
  }
166
183
  }
167
- return list(this.disableDates).includes(iso);
184
+ return writtenList(this.disableDates).includes(iso);
185
+ }
186
+ clamp(date) {
187
+ const iso = fromDate(date);
188
+ const min = asDate(this.min);
189
+ const max = asDate(this.max);
190
+ if (min && iso < this.min) {
191
+ return min;
192
+ }
193
+ if (max && iso > this.max) {
194
+ return max;
195
+ }
196
+ return date;
197
+ }
198
+ get atStart() {
199
+ return asDate(this.min) !== null && monthOf(addMonths(this.shown, -1)) < this.min.slice(0, 7);
200
+ }
201
+ get atEnd() {
202
+ return (asDate(this.max) !== null &&
203
+ monthOf(addMonths(this.shown, this.monthCount)) > this.max.slice(0, 7));
168
204
  }
169
205
  show(date) {
170
- if (this.disabled) {
206
+ if (this.isDisabled) {
171
207
  return;
172
208
  }
173
- const next = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
209
+ const next = monthOf(date);
174
210
  if (next === this.month) {
175
211
  return;
176
212
  }
@@ -185,7 +221,8 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
185
221
  next,
186
222
  });
187
223
  }
188
- moveTo(date) {
224
+ moveTo(wanted) {
225
+ const date = this.clamp(wanted);
189
226
  this.focused = fromDate(date);
190
227
  this.show(date);
191
228
  this.updateComplete.then(() => {
@@ -193,7 +230,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
193
230
  });
194
231
  }
195
232
  select(date) {
196
- if (this.disabled) {
233
+ if (this.isDisabled) {
197
234
  return;
198
235
  }
199
236
  if (this.barred(date)) {
@@ -212,8 +249,12 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
212
249
  next = [iso];
213
250
  }
214
251
  this.focused = iso;
252
+ this.choosing = true;
215
253
  this.requestValue(next.join(','));
216
254
  }
255
+ focus(options) {
256
+ this.renderRoot?.querySelector('[part~="cell"][tabindex="0"]')?.focus(options);
257
+ }
217
258
  connectedCallback() {
218
259
  super.connectedCallback();
219
260
  this.addEventListener('keydown', this.handleKeydown);
@@ -228,6 +269,37 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
228
269
  }
229
270
  return super.requestValue(next);
230
271
  }
272
+ spoken(iso) {
273
+ const date = asDate(iso);
274
+ return date ? this.formats.long.format(date) : iso;
275
+ }
276
+ fault(iso) {
277
+ if (this.min !== '' && iso < this.min) {
278
+ return [
279
+ 'rangeUnderflow',
280
+ fillTemplate(this.rangeUnderflowMessage || 'Choose a date on or after {min}.', {
281
+ min: this.spoken(this.min),
282
+ }),
283
+ ];
284
+ }
285
+ if (this.max !== '' && iso > this.max) {
286
+ return [
287
+ 'rangeOverflow',
288
+ fillTemplate(this.rangeOverflowMessage || 'Choose a date on or before {max}.', {
289
+ max: this.spoken(this.max),
290
+ }),
291
+ ];
292
+ }
293
+ const date = asDate(iso);
294
+ if (!date || !this.barred(date)) {
295
+ return undefined;
296
+ }
297
+ const listed = writtenList(this.disableDates).includes(iso) ||
298
+ writtenList(this.disableDays).includes(String(date.getUTCDay()));
299
+ return listed
300
+ ? ['badInput', this.unavailableMessage || 'Choose a date that is available.']
301
+ : ['stepMismatch', this.stepMismatchMessage || 'Choose one of the dates the calendar offers.'];
302
+ }
231
303
  reportRequired() {
232
304
  const anchor = this.partNamed('grid') ?? undefined;
233
305
  if (this.required && this.value === '') {
@@ -242,11 +314,27 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
242
314
  : 'Enter dates as YYYY-MM-DD, separated by commas.'), anchor);
243
315
  return;
244
316
  }
317
+ for (const one of held) {
318
+ const fault = this.fault(one);
319
+ if (fault) {
320
+ this.internals.setValidity({ [fault[0]]: true }, fault[1], anchor);
321
+ return;
322
+ }
323
+ }
245
324
  this.internals.setValidity({});
246
325
  }
247
326
  willUpdate(changed) {
248
327
  super.willUpdate(changed);
249
- hostAria(this.internals, { label: this.label, role: 'group' });
328
+ if (changed.has('value') && !this.choosing) {
329
+ this.focused = '';
330
+ }
331
+ this.choosing = false;
332
+ const outside = this.label === 'Date Picker' ? [...(this.internals.labels ?? [])] : [];
333
+ hostAria(this.internals, {
334
+ label: this.label,
335
+ labelledBy: outside,
336
+ role: 'group',
337
+ });
250
338
  this.internals.ariaDisabled = this.isDisabled ? 'true' : null;
251
339
  this.internals.ariaInvalid = this.invalid ? 'true' : null;
252
340
  this.report();
@@ -256,7 +344,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
256
344
  this.reportRequired();
257
345
  }
258
346
  report() {
259
- if (this.disabled) {
347
+ if (this.isDisabled) {
260
348
  this.internals.setFormValue(null);
261
349
  return;
262
350
  }
@@ -274,6 +362,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
274
362
  full: new Intl.DateTimeFormat(locale, { dateStyle: 'full', timeZone: 'UTC' }),
275
363
  heading: new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC', year: 'numeric' }),
276
364
  locale: this.locale,
365
+ long: new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeZone: 'UTC' }),
277
366
  weekday: new Intl.DateTimeFormat(locale, { timeZone: 'UTC', weekday: 'short' }),
278
367
  };
279
368
  this.cachedFormats = built;
@@ -308,17 +397,20 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
308
397
  part="previous"
309
398
  type="button"
310
399
  aria-label=${this.previousLabel}
400
+ ?disabled=${this.isDisabled || this.atStart}
311
401
  @click=${() => this.show(addMonths(this.shown, -1))}
312
402
  >
313
403
  ${this.renderPreviousMark()}
314
404
  </button>`
315
405
  : nothing}
316
- <span part="heading">${heading.format(month)}</span>
406
+ ${''}
407
+ <span part="heading" aria-live="polite">${heading.format(month)}</span>
317
408
  ${last
318
409
  ? html `<button
319
410
  part="next"
320
411
  type="button"
321
412
  aria-label=${this.nextLabel}
413
+ ?disabled=${this.isDisabled || this.atEnd}
322
414
  @click=${() => this.show(addMonths(this.shown, 1))}
323
415
  >
324
416
  ${this.renderNextMark()}
@@ -329,6 +421,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
329
421
  part="grid"
330
422
  role="grid"
331
423
  aria-label=${heading.format(month)}
424
+ aria-readonly=${this.readOnly ? 'true' : nothing}
332
425
  data-unset=${this.value === '' ? '' : nothing}
333
426
  >
334
427
  <div part="week" role="row">
@@ -346,7 +439,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
346
439
  return html `<span
347
440
  part="cell${start ? ' cell-range-start' : ''}${middle ? ' cell-range-middle' : ''}${end ? ' cell-range-end' : ''}"
348
441
  role="gridcell"
349
- aria-disabled=${disabled ? 'true' : nothing}
442
+ aria-disabled=${disabled || outside ? 'true' : nothing}
350
443
  aria-label=${full.format(date)}
351
444
  aria-current=${iso === today ? 'date' : nothing}
352
445
  aria-selected=${!outside && selected ? 'true' : nothing}
@@ -355,7 +448,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
355
448
  data-range-middle=${middle ? '' : nothing}
356
449
  data-range-start=${start ? '' : nothing}
357
450
  data-today=${iso === today ? '' : nothing}
358
- tabindex=${!outside && iso === anchor ? '0' : '-1'}
451
+ tabindex=${!outside && iso === anchor && !this.isDisabled ? '0' : '-1'}
359
452
  @click=${outside ? nothing : () => this.select(date)}
360
453
  ><span
361
454
  part="day${!outside && selected && !middle ? ' day-selected' : ''}${iso === today ? ' day-today' : ''}${outside ? ' day-outside' : ''}${disabled ? ' day-disabled' : ''}"
@@ -433,17 +526,29 @@ __decorate([
433
526
  property({ attribute: 'previous-label' })
434
527
  ], HelixHeadlessDatePicker.prototype, "previousLabel", void 0);
435
528
  __decorate([
436
- property({ attribute: 'readonly', reflect: true, type: Boolean })
437
- ], HelixHeadlessDatePicker.prototype, "readOnly", void 0);
529
+ property({ attribute: 'range-overflow-message' })
530
+ ], HelixHeadlessDatePicker.prototype, "rangeOverflowMessage", void 0);
438
531
  __decorate([
439
532
  state()
440
533
  ], HelixHeadlessDatePicker.prototype, "focused", void 0);
534
+ __decorate([
535
+ property({ attribute: 'range-underflow-message' })
536
+ ], HelixHeadlessDatePicker.prototype, "rangeUnderflowMessage", void 0);
537
+ __decorate([
538
+ property({ attribute: 'readonly', reflect: true, type: Boolean })
539
+ ], HelixHeadlessDatePicker.prototype, "readOnly", void 0);
441
540
  __decorate([
442
541
  property({ reflect: true, type: Boolean })
443
542
  ], HelixHeadlessDatePicker.prototype, "required", void 0);
444
543
  __decorate([
445
544
  property({ type: Number })
446
545
  ], HelixHeadlessDatePicker.prototype, "step", void 0);
546
+ __decorate([
547
+ property({ attribute: 'step-mismatch-message' })
548
+ ], HelixHeadlessDatePicker.prototype, "stepMismatchMessage", void 0);
549
+ __decorate([
550
+ property({ attribute: 'unavailable-message' })
551
+ ], HelixHeadlessDatePicker.prototype, "unavailableMessage", void 0);
447
552
  __decorate([
448
553
  property({ attribute: 'value-missing-message' })
449
554
  ], HelixHeadlessDatePicker.prototype, "valueMissingMessage", void 0);
@@ -1,4 +1,4 @@
1
- import { CommandController, HelixElement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, CommandController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessDialog_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
4
4
  /**
@@ -19,6 +19,10 @@ declare const HelixHeadlessDialog_base: (new (...args: any[]) => import("@fusion
19
19
  * keeps your state in step, so the dialog never closes while your state still
20
20
  * says it is open, which would leave it impossible to reopen.
21
21
  *
22
+ * The dialog's name is `label` when you set it. Otherwise it is the heading in
23
+ * the `header` slot, or the elements an `aria-labelledby` on the host names.
24
+ * While it is open, the page behind it does not scroll.
25
+ *
22
26
  * @since 0.1.0
23
27
  * @status stable
24
28
  * @category overlays
@@ -33,7 +37,7 @@ declare const HelixHeadlessDialog_base: (new (...args: any[]) => import("@fusion
33
37
  * @event {Event} close - Sent from the host after the dialog closes, because the native `close` event does not leave the shadow root.
34
38
  * @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes, and cancelable.
35
39
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before it opens, and cancelable.
36
- * @event {Event} toggle - Sent when the dialog opens or closes, with `newState` and `oldState`.
40
+ * @event {ToggleEvent} toggle - Sent when the dialog opens or closes, with `newState` and `oldState`.
37
41
  *
38
42
  * @csspart dialog - The native dialog element.
39
43
  *
@@ -54,9 +58,11 @@ export declare class HelixHeadlessDialog extends HelixHeadlessDialog_base {
54
58
  */
55
59
  closedBy?: 'any' | 'closerequest' | 'none';
56
60
  /**
57
- * Accessible name for the dialog.
61
+ * Accessible name for the dialog. When it is empty, the heading in the
62
+ * `header` slot names the dialog.
58
63
  */
59
64
  label: string;
65
+ protected readonly aria: AriaForwardController;
60
66
  protected readonly commands: CommandController;
61
67
  /**
62
68
  * Stops Escape and a press on the backdrop from closing the dialog.
@@ -120,20 +126,42 @@ export declare class HelixHeadlessDialog extends HelixHeadlessDialog_base {
120
126
  * saying otherwise would be a lie the next `showModal()` would trip over.
121
127
  */
122
128
  private readonly handleClose;
129
+ /**
130
+ * A `<form method="dialog">` slotted into the dialog, closed the way the
131
+ * platform closes its own.
132
+ *
133
+ * **The platform cannot find this dialog from the form.** A submit with the
134
+ * `dialog` method closes the nearest `<dialog>` ancestor, and a slotted form's
135
+ * ancestors are the light DOM, where there is none. So the submit is read
136
+ * here, and the submitter's `value` is the `returnValue`, as it would be.
137
+ */
138
+ private readonly handleSubmit;
139
+ /** Whether the `header` slot's heading is what currently names the dialog. */
140
+ private namedByHeader;
141
+ /**
142
+ * Names the dialog by its heading, where nothing else names it.
143
+ *
144
+ * An element reference, because the heading is in the light DOM and the
145
+ * `<dialog>` is in this shadow root, which an IDREF cannot cross. `label` and
146
+ * an `aria-labelledby` on the host come first, so this steps aside for
147
+ * either. A browser without element reflection keeps the fallback name.
148
+ */
149
+ private nameByHeader;
123
150
  protected willUpdate(changed: PropertyValues): void;
151
+ connectedCallback(): void;
152
+ disconnectedCallback(): void;
124
153
  protected updated(changed: PropertyValues): void;
125
154
  /** Opens once anything slotted has had its first render. See `updated`. */
126
155
  private openWhenSlotted;
127
156
  /**
128
157
  * A press on the backdrop, which lands on the dialog element itself.
129
158
  *
130
- * **The target alone is not enough here, where it is for `Drawer`.** A modal
131
- * `<dialog>` attributes backdrop clicks to itself, which is the usual trick,
132
- * and this panel also carries `--dialog-padding` of padding: a press in that
133
- * padding has the same target as a press on the backdrop, so the trick alone
134
- * closes a dialog when a reader clicks just inside its own edge. The drawer
135
- * escapes that because its panel is flush against the window and its content
136
- * fills it. So the point is compared against the panel's box.
159
+ * **The target alone is not enough.** A modal `<dialog>` attributes backdrop
160
+ * clicks to itself, which is the usual trick, and this panel also carries
161
+ * `--dialog-padding` of padding: a press in that padding has the same target
162
+ * as a press on the backdrop, so the trick alone closes a dialog when a reader
163
+ * clicks just inside its own edge. So the point is compared against the
164
+ * panel's box. `Drawer` does the same.
137
165
  */
138
166
  private readonly handleBackdrop;
139
167
  /**
@@ -4,13 +4,14 @@ 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 { CommandController, customElement, HelixElement, hostAria, MixinControllerOpen, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, CommandController, customElement, HelixElement, hostAria, lockScroll, MixinControllerOpen, unlockScroll, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(HelixElement) {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.label = '';
14
+ this.aria = new AriaForwardController(this, { target: () => this.dialog });
14
15
  this.commands = new CommandController(this, {
15
16
  commands: {
16
17
  '--close': () => this.close(),
@@ -33,6 +34,20 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
33
34
  }
34
35
  this.relayNativeEvent(event);
35
36
  };
37
+ this.handleSubmit = (event) => {
38
+ const form = event.target;
39
+ if (!(form instanceof HTMLFormElement) || form.closest('dialog')) {
40
+ return;
41
+ }
42
+ const submitter = event.submitter;
43
+ const method = submitter?.hasAttribute('formmethod') ? submitter.formMethod : form.method;
44
+ if (method !== 'dialog') {
45
+ return;
46
+ }
47
+ event.preventDefault();
48
+ this.close(submitter?.hasAttribute('value') ? submitter.value : undefined);
49
+ };
50
+ this.namedByHeader = false;
36
51
  this.handleBackdrop = (event) => {
37
52
  const panel = this.dialog;
38
53
  if (!panel || event.target !== panel) {
@@ -77,12 +92,44 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
77
92
  this.returnValue = previous;
78
93
  }
79
94
  }
95
+ nameByHeader() {
96
+ const dialog = this.dialog;
97
+ if (!dialog || !('ariaLabelledByElements' in dialog)) {
98
+ return;
99
+ }
100
+ const slot = this.renderRoot.querySelector('slot[name="header"]');
101
+ const heading = this.label || this.hasAttribute('aria-labelledby') ? [] : headingsIn(slot);
102
+ if (heading.length > 0) {
103
+ dialog.ariaLabelledByElements = heading;
104
+ this.namedByHeader = true;
105
+ }
106
+ else if (this.namedByHeader) {
107
+ dialog.ariaLabelledByElements = null;
108
+ this.namedByHeader = false;
109
+ }
110
+ }
80
111
  willUpdate(changed) {
81
112
  super.willUpdate(changed);
82
113
  hostAria(this.internals, { label: this.label });
83
114
  }
115
+ connectedCallback() {
116
+ super.connectedCallback();
117
+ this.addEventListener('submit', this.handleSubmit);
118
+ }
119
+ disconnectedCallback() {
120
+ super.disconnectedCallback();
121
+ this.removeEventListener('submit', this.handleSubmit);
122
+ unlockScroll(this);
123
+ }
84
124
  updated(changed) {
85
125
  super.updated(changed);
126
+ this.nameByHeader();
127
+ if (this.open) {
128
+ lockScroll(this);
129
+ }
130
+ else {
131
+ unlockScroll(this);
132
+ }
86
133
  const dialog = this.dialog;
87
134
  if (!dialog) {
88
135
  return;
@@ -132,7 +179,7 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
132
179
  @close=${this.handleClose}
133
180
  >
134
181
  <slot name="icon"></slot>
135
- <slot name="header"></slot>
182
+ <slot name="header" @slotchange=${this.nameByHeader}></slot>
136
183
  <slot></slot>
137
184
  <slot name="footer"></slot>
138
185
  </dialog>`;
@@ -151,3 +198,9 @@ HelixHeadlessDialog = __decorate([
151
198
  customElement('helix-headless-dialog')
152
199
  ], HelixHeadlessDialog);
153
200
  export { HelixHeadlessDialog };
201
+ const headingsIn = (slot) => {
202
+ const assigned = slot?.assignedElements({ flatten: true }) ?? [];
203
+ const heading = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
204
+ const found = assigned.flatMap((one) => one.matches(heading) ? [one] : [...one.querySelectorAll(heading)].slice(0, 1));
205
+ return found.length > 0 ? found.slice(0, 1) : assigned;
206
+ };
@@ -1,6 +1,15 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { nothing, type TemplateResult } from 'lit';
3
3
  import '../../tooltip/index.js';
4
+ /** One slice: its share of the whole, where it starts, and the row it was read from. */
5
+ export interface DonutShare {
6
+ /** The slice's share of the whole, from 0 to 1. */
7
+ fraction: number;
8
+ /** The table row the slice was read from, counting from zero. */
9
+ row: number;
10
+ /** Where the slice starts, as a share of the whole. */
11
+ start: number;
12
+ }
4
13
  declare const HelixHeadlessDonutChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface);
5
14
  /**
6
15
  * @summary A chart that shows parts of a whole as a ring, read from a table.
@@ -15,13 +24,17 @@ declare const HelixHeadlessDonutChart_base: typeof HelixElement & (new (...args:
15
24
  * **Negative values are skipped.** An arc cannot show a negative number, so
16
25
  * those rows are left out and the total is the sum of the remaining rows.
17
26
  *
27
+ * The ring is `aria-hidden`. The `summary` part names the chart, from `label`
28
+ * or the table's caption, and gives each slice's share, and the table beside
29
+ * the chart has every amount.
30
+ *
18
31
  * @since 0.1.0
19
32
  * @status stable
20
33
  * @category charts
21
34
  * @aliases Pie Chart, Ring Chart, Share Chart
22
35
  * @documentation https://fusion.dev/helix/components/donut-chart
23
36
  *
24
- * @slot - Content in the middle of the ring, usually a total.
37
+ * @slot - Content in the middle of the ring, usually a total. It is read by screen readers, while the ring itself is hidden from them.
25
38
  *
26
39
  * @csspart center - The middle of the ring, holding whatever is slotted.
27
40
  * @csspart mark - The invisible point the tooltip is anchored to.
@@ -29,6 +42,7 @@ declare const HelixHeadlessDonutChart_base: typeof HelixElement & (new (...args:
29
42
  * @csspart segment - One slice.
30
43
  * @csspart segment-active - The slice under the pointer. Set alongside `segment`.
31
44
  * @csspart segment-edge - One slice as a closed shape, used to draw its outline.
45
+ * @csspart summary - The chart's name and each slice's share, written as a sentence for screen readers. Hide it visually.
32
46
  * @csspart tooltip - The bubble, forwarded from `Tooltip`.
33
47
  * @csspart tooltip-anchor - The `Tooltip` element itself.
34
48
  * @csspart tooltip-arrow - The bubble's point, forwarded from `Tooltip`.
@@ -131,18 +145,34 @@ export declare class HelixHeadlessDonutChart extends HelixHeadlessDonutChart_bas
131
145
  private get thickness();
132
146
  /** The ring's radius, which follows its thickness so the outer edge holds. */
133
147
  private get radius();
148
+ /**
149
+ * Each slice's share of the circle, where it starts, and the row it was read
150
+ * from.
151
+ *
152
+ * The row because a slice is not its row once a row is left out: a value
153
+ * that does not parse has no slice, and the tooltip counting slices against
154
+ * the table's rows named the row before the one under the pointer.
155
+ */
134
156
  private get shares();
135
157
  protected render(): TemplateResult;
158
+ /**
159
+ * What a reader who cannot see the ring is told: the chart's name and each
160
+ * slice's share of the whole.
161
+ *
162
+ * The shares, because they are what the ring shows and the table does not:
163
+ * the table holds the amounts, and the percentages are this chart's
164
+ * arithmetic. In the ring's order, which is the table's and the legend's.
165
+ */
166
+ protected renderSummary(shares: DonutShare[]): TemplateResult;
136
167
  /**
137
168
  * The ring itself, behind a seam the styled tier can move.
138
169
  *
139
- * `aria-hidden`, because the table it was read from is the readable version
140
- * and a screen reader hearing both hears the same numbers twice.
170
+ * The drawing is `aria-hidden`, because the table it was read from is the
171
+ * readable version and a screen reader hearing both hears the same numbers
172
+ * twice. The middle is not: a total slotted there is usually a number the
173
+ * table does not hold.
141
174
  */
142
- protected renderPlot(shares: {
143
- fraction: number;
144
- start: number;
145
- }[]): TemplateResult;
175
+ protected renderPlot(shares: DonutShare[]): TemplateResult;
146
176
  /**
147
177
  * The view box, cropped to what is actually drawn.
148
178
  *
@@ -155,10 +185,7 @@ export declare class HelixHeadlessDonutChart extends HelixHeadlessDonutChart_bas
155
185
  */
156
186
  private get frame();
157
187
  /** One slice, as a dash the length of its share. */
158
- protected renderSegment(share: {
159
- fraction: number;
160
- start: number;
161
- }, index: number): TemplateResult;
188
+ protected renderSegment(share: DonutShare, index: number): TemplateResult;
162
189
  /**
163
190
  * The same slice again, as a shape with an inside.
164
191
  *
@@ -189,10 +216,7 @@ export declare class HelixHeadlessDonutChart extends HelixHeadlessDonutChart_bas
189
216
  * `transform-box: fill-box`, so it spins each element about its own bounding
190
217
  * box, which is the ring's center for a circle and is not for a sector.
191
218
  */
192
- protected renderEdge(share: {
193
- fraction: number;
194
- start: number;
195
- }, index: number): TemplateResult;
219
+ protected renderEdge(share: DonutShare, index: number): TemplateResult;
196
220
  /**
197
221
  * What a pointer resting on a slice shows.
198
222
  *