@fusion.dev/helix-headless 0.143.0 → 0.144.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 (191) hide show
  1. package/custom-elements.json +8551 -375
  2. package/dist/area-chart/root/area-chart-root.d.ts +12 -4
  3. package/dist/area-chart/root/area-chart-root.js +24 -7
  4. package/dist/avatar/root/avatar-root.d.ts +14 -0
  5. package/dist/avatar/root/avatar-root.js +23 -9
  6. package/dist/badge/root/badge-root.d.ts +11 -7
  7. package/dist/badge/root/badge-root.js +1 -1
  8. package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
  9. package/dist/bar-chart/root/bar-chart-root.js +17 -10
  10. package/dist/button/root/button-root.d.ts +47 -1
  11. package/dist/button/root/button-root.js +38 -4
  12. package/dist/callout/root/callout-root.d.ts +18 -1
  13. package/dist/callout/root/callout-root.js +15 -4
  14. package/dist/card/root/card-root.d.ts +30 -1
  15. package/dist/card/root/card-root.js +38 -5
  16. package/dist/checkbox/root/checkbox-root.js +2 -0
  17. package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
  18. package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
  19. package/dist/code-block/lines.d.ts +13 -0
  20. package/dist/code-block/lines.js +2 -0
  21. package/dist/code-block/root/code-block-root.d.ts +30 -0
  22. package/dist/code-block/root/code-block-root.js +23 -2
  23. package/dist/color-input/root/color-input-root.d.ts +34 -1
  24. package/dist/color-input/root/color-input-root.js +23 -4
  25. package/dist/color-picker/channels.d.ts +26 -0
  26. package/dist/color-picker/channels.js +84 -0
  27. package/dist/color-picker/root/color-picker-root.d.ts +81 -5
  28. package/dist/color-picker/root/color-picker-root.js +103 -10
  29. package/dist/combobox/root/combobox-root.d.ts +38 -3
  30. package/dist/combobox/root/combobox-root.js +64 -2
  31. package/dist/command/root/command-root.d.ts +30 -4
  32. package/dist/command/root/command-root.js +33 -3
  33. package/dist/copy-button/from.d.ts +22 -0
  34. package/dist/copy-button/from.js +28 -0
  35. package/dist/copy-button/root/copy-button-root.d.ts +27 -1
  36. package/dist/copy-button/root/copy-button-root.js +43 -7
  37. package/dist/date-input/root/date-input-root.d.ts +73 -4
  38. package/dist/date-input/root/date-input-root.js +93 -43
  39. package/dist/date-picker/root/date-picker-root.d.ts +145 -1
  40. package/dist/date-picker/root/date-picker-root.js +239 -48
  41. package/dist/dialog/description.d.ts +9 -0
  42. package/dist/dialog/description.js +14 -0
  43. package/dist/dialog/root/dialog-root.d.ts +64 -1
  44. package/dist/dialog/root/dialog-root.js +49 -23
  45. package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
  46. package/dist/donut-chart/root/donut-chart-root.js +21 -6
  47. package/dist/drawer/root/drawer-root.d.ts +63 -2
  48. package/dist/drawer/root/drawer-root.js +95 -19
  49. package/dist/drawer/swipe-start.d.ts +9 -0
  50. package/dist/drawer/swipe-start.js +12 -0
  51. package/dist/element-events.generated.js +8 -5
  52. package/dist/empty/root/empty-root.d.ts +1 -1
  53. package/dist/file-input/root/file-input-root.d.ts +47 -0
  54. package/dist/file-input/root/file-input-root.js +60 -22
  55. package/dist/file-input/screen.d.ts +35 -0
  56. package/dist/file-input/screen.js +49 -0
  57. package/dist/filter-input/root/filter-input-root.d.ts +38 -4
  58. package/dist/filter-input/root/filter-input-root.js +67 -3
  59. package/dist/filter-input/suggest.d.ts +17 -0
  60. package/dist/filter-input/suggest.js +23 -0
  61. package/dist/format-date/options.d.ts +33 -0
  62. package/dist/format-date/options.js +42 -0
  63. package/dist/format-date/root/format-date-root.d.ts +51 -0
  64. package/dist/format-date/root/format-date-root.js +58 -38
  65. package/dist/heatmap/root/heatmap-root.d.ts +23 -6
  66. package/dist/heatmap/root/heatmap-root.js +24 -13
  67. package/dist/icon/index.d.ts +2 -0
  68. package/dist/icon/index.js +2 -0
  69. package/dist/icon/registry.d.ts +37 -0
  70. package/dist/icon/registry.js +83 -0
  71. package/dist/icon/root/icon-root.d.ts +30 -4
  72. package/dist/icon/root/icon-root.js +41 -6
  73. package/dist/icon/root/index.d.ts +2 -0
  74. package/dist/icon/root/index.js +2 -0
  75. package/dist/icon/set.d.ts +1 -0
  76. package/dist/icon/set.js +3 -0
  77. package/dist/image/root/image-root.d.ts +14 -0
  78. package/dist/image/root/image-root.js +15 -2
  79. package/dist/input/root/input-root.d.ts +11 -0
  80. package/dist/input/root/input-root.js +15 -0
  81. package/dist/kbd/root/kbd-root.d.ts +20 -13
  82. package/dist/kbd/root/kbd-root.js +35 -37
  83. package/dist/kbd/shortcut.d.ts +18 -0
  84. package/dist/kbd/shortcut.js +35 -0
  85. package/dist/legend/hidden-series.d.ts +8 -0
  86. package/dist/legend/hidden-series.js +13 -0
  87. package/dist/legend/root/legend-root.d.ts +56 -0
  88. package/dist/legend/root/legend-root.js +54 -5
  89. package/dist/lightbox/item/lightbox-item.d.ts +15 -0
  90. package/dist/lightbox/item/lightbox-item.js +10 -1
  91. package/dist/lightbox/root/lightbox-root.d.ts +61 -1
  92. package/dist/lightbox/root/lightbox-root.js +94 -4
  93. package/dist/line-chart/root/line-chart-root.d.ts +11 -3
  94. package/dist/line-chart/root/line-chart-root.js +19 -5
  95. package/dist/listbox/range.d.ts +17 -0
  96. package/dist/listbox/range.js +5 -0
  97. package/dist/listbox/root/listbox-root.d.ts +74 -3
  98. package/dist/listbox/root/listbox-root.js +87 -9
  99. package/dist/loader/busy.d.ts +17 -0
  100. package/dist/loader/busy.js +24 -0
  101. package/dist/loader/root/loader-root.d.ts +37 -0
  102. package/dist/loader/root/loader-root.js +52 -3
  103. package/dist/meter/root/meter-root.d.ts +7 -0
  104. package/dist/meter/root/meter-root.js +6 -3
  105. package/dist/notifications/feed-keys.d.ts +10 -0
  106. package/dist/notifications/feed-keys.js +14 -0
  107. package/dist/notifications/notification/notification.d.ts +17 -0
  108. package/dist/notifications/notification/notification.js +21 -2
  109. package/dist/notifications/root/notifications-root.d.ts +42 -0
  110. package/dist/notifications/root/notifications-root.js +60 -1
  111. package/dist/number-input/root/number-input-root.d.ts +47 -0
  112. package/dist/number-input/root/number-input-root.js +58 -22
  113. package/dist/object-inspector/root/object-inspector-root.d.ts +1 -0
  114. package/dist/object-inspector/root/object-inspector-root.js +1 -1
  115. package/dist/option/root/option-root.d.ts +54 -5
  116. package/dist/option/root/option-root.js +34 -5
  117. package/dist/pin-input/format.d.ts +7 -0
  118. package/dist/pin-input/format.js +16 -0
  119. package/dist/pin-input/root/pin-input-root.d.ts +21 -0
  120. package/dist/pin-input/root/pin-input-root.js +29 -2
  121. package/dist/popover/root/popover-root.d.ts +62 -4
  122. package/dist/popover/root/popover-root.js +53 -5
  123. package/dist/progress/root/progress-root.d.ts +7 -0
  124. package/dist/progress/root/progress-root.js +9 -3
  125. package/dist/qr-code/export.d.ts +31 -0
  126. package/dist/qr-code/export.js +10 -0
  127. package/dist/qr-code/root/qr-code-root.d.ts +26 -0
  128. package/dist/qr-code/root/qr-code-root.js +49 -2
  129. package/dist/radio/group/radio-group.d.ts +23 -0
  130. package/dist/radio/group/radio-group.js +18 -0
  131. package/dist/rating/root/rating-root.d.ts +25 -0
  132. package/dist/rating/root/rating-root.js +45 -3
  133. package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
  134. package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
  135. package/dist/select/root/select-root.d.ts +24 -0
  136. package/dist/select/root/select-root.js +20 -6
  137. package/dist/skeleton/root/skeleton-root.d.ts +25 -0
  138. package/dist/skeleton/root/skeleton-root.js +17 -2
  139. package/dist/slider/root/slider-root.d.ts +39 -0
  140. package/dist/slider/root/slider-root.js +45 -9
  141. package/dist/stat/root/stat-root.d.ts +2 -0
  142. package/dist/stat/root/stat-root.js +1 -1
  143. package/dist/steps/step/step.d.ts +1 -0
  144. package/dist/steps/step/step.js +1 -1
  145. package/dist/switch/root/switch-root.d.ts +12 -2
  146. package/dist/switch/root/switch-root.js +6 -1
  147. package/dist/tag/group/tag-group.d.ts +107 -0
  148. package/dist/tag/group/tag-group.js +157 -0
  149. package/dist/tag/index.d.ts +1 -0
  150. package/dist/tag/index.js +1 -0
  151. package/dist/tag/root/tag-root.d.ts +60 -0
  152. package/dist/tag/root/tag-root.js +74 -18
  153. package/dist/tag/selection.d.ts +10 -0
  154. package/dist/tag/selection.js +12 -0
  155. package/dist/textarea/grow.d.ts +22 -0
  156. package/dist/textarea/grow.js +20 -0
  157. package/dist/textarea/root/textarea-root.d.ts +9 -0
  158. package/dist/textarea/root/textarea-root.js +9 -1
  159. package/dist/time-input/root/time-input-root.d.ts +44 -5
  160. package/dist/time-input/root/time-input-root.js +69 -39
  161. package/dist/time-picker/root/time-picker-root.d.ts +54 -8
  162. package/dist/time-picker/root/time-picker-root.js +121 -91
  163. package/dist/toast/hotkey.d.ts +22 -0
  164. package/dist/toast/hotkey.js +23 -0
  165. package/dist/toast/item/toast-item.d.ts +16 -1
  166. package/dist/toast/item/toast-item.js +41 -13
  167. package/dist/toast/root/toast-root.d.ts +42 -8
  168. package/dist/toast/root/toast-root.js +64 -4
  169. package/dist/toggle/group/toggle-group.d.ts +57 -15
  170. package/dist/toggle/group/toggle-group.js +69 -9
  171. package/dist/toggle/root/toggle-root.d.ts +12 -1
  172. package/dist/toggle/root/toggle-root.js +11 -3
  173. package/dist/tooltip/focus-target.d.ts +6 -0
  174. package/dist/tooltip/focus-target.js +9 -0
  175. package/dist/tooltip/root/tooltip-root.d.ts +42 -17
  176. package/dist/tooltip/root/tooltip-root.js +50 -9
  177. package/dist/tour/naming.d.ts +30 -0
  178. package/dist/tour/naming.js +10 -0
  179. package/dist/tour/root/tour-root.d.ts +35 -0
  180. package/dist/tour/root/tour-root.js +56 -5
  181. package/dist/tour/stop/tour-stop.d.ts +53 -8
  182. package/dist/tour/stop/tour-stop.js +45 -15
  183. package/dist/tour/wait.d.ts +11 -0
  184. package/dist/tour/wait.js +19 -0
  185. package/dist/trend/root/trend-root.d.ts +13 -0
  186. package/dist/trend/root/trend-root.js +14 -9
  187. package/lint.json +1 -1
  188. package/package.json +5 -5
  189. package/types/jsx.d.ts +4862 -254
  190. package/types/svelte.d.ts +1 -0
  191. package/types/vue.d.ts +1149 -67
@@ -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, fillTemplate, dateAsValue as fromDate, HelixFormElement, HelixNavigateEvent, hostAria, inlineStep, MixinControllerValue, reportValues, requestChange, writtenList, } from '@fusion.dev/helix-core';
7
+ import { addDays, addMonths, valueAsDate as asDate, barredBy, customElement, fillTemplate, dateAsValue as fromDate, HelixFormElement, HelixNavigateEvent, hostAria, inlineStep, commaList as list, localToday, MixinControllerValue, monthOf, monthOut, periodCells, periodKey, reportValues, requestChange, sameMonth, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } 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';
@@ -21,34 +21,6 @@ const KEYS = new Set([
21
21
  'PageUp',
22
22
  ' ',
23
23
  ]);
24
- const localToday = () => {
25
- const now = new Date();
26
- const month = `${now.getMonth() + 1}`.padStart(2, '0');
27
- const day = `${now.getDate()}`.padStart(2, '0');
28
- return `${now.getFullYear()}-${month}-${day}`;
29
- };
30
- const list = (value) => value
31
- .split(',')
32
- .map((entry) => entry.trim())
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')}`;
45
- const addDays = (date, days) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
46
- const sameMonth = (left, right) => left.getUTCFullYear() === right.getUTCFullYear() && left.getUTCMonth() === right.getUTCMonth();
47
- const addMonths = (date, months) => {
48
- const target = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1));
49
- const last = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
50
- return new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth(), Math.min(date.getUTCDate(), last)));
51
- };
52
24
  let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControllerValue(HelixFormElement) {
53
25
  constructor() {
54
26
  super(...arguments);
@@ -62,11 +34,20 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
62
34
  this.min = '';
63
35
  this.monthState = undefined;
64
36
  this.months = 1;
37
+ this.monthViewLabel = '{heading}. Choose a month';
65
38
  this.nextLabel = 'Next month';
39
+ this.nextYearLabel = 'Next year';
40
+ this.nextYearsLabel = 'Later years';
66
41
  this.previousLabel = 'Previous month';
42
+ this.previousYearLabel = 'Previous year';
43
+ this.previousYearsLabel = 'Earlier years';
67
44
  this.focused = '';
68
45
  this.choosing = false;
69
46
  this.handleKeydown = (event) => {
47
+ if (this.view !== 'day') {
48
+ this.handlePeriodKeydown(event);
49
+ return;
50
+ }
70
51
  if (!KEYS.has(event.key) || this.isDisabled) {
71
52
  return;
72
53
  }
@@ -101,9 +82,27 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
101
82
  }
102
83
  event.preventDefault();
103
84
  };
85
+ this.pageYear = undefined;
86
+ this.periodFocus = undefined;
87
+ this.handleHeading = () => {
88
+ if (this.isDisabled) {
89
+ return;
90
+ }
91
+ if (this.view === 'day') {
92
+ this.pageYear = this.shown.getUTCFullYear();
93
+ this.periodFocus = this.shown.getUTCMonth();
94
+ this.zoom('month');
95
+ }
96
+ else if (this.view === 'month' && this.maxView === 'year') {
97
+ this.periodFocus = this.periodYear;
98
+ this.zoom('year');
99
+ }
100
+ };
104
101
  this.readOnly = false;
105
102
  this.required = false;
106
103
  this.step = 1;
104
+ this.view = 'day';
105
+ this.yearViewLabel = '{heading}. Choose a year';
107
106
  }
108
107
  get month() {
109
108
  return this.monthState ?? (this.defaultMonth || fromDate(this.derivedMonth).slice(0, 7));
@@ -170,18 +169,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
170
169
  if (this.max !== '' && iso > this.max) {
171
170
  return true;
172
171
  }
173
- if (writtenList(this.disableDays).includes(String(date.getUTCDay()))) {
174
- return true;
175
- }
176
- const step = Math.max(1, Math.floor(this.step) || 1);
177
- if (step > 1) {
178
- const base = this.min === '' ? 0 : (asDate(this.min)?.getTime() ?? 0);
179
- const days = Math.round((date.getTime() - base) / 86_400_000);
180
- if (days % step !== 0) {
181
- return true;
182
- }
183
- }
184
- return writtenList(this.disableDates).includes(iso);
172
+ return barredBy(date, this) !== null;
185
173
  }
186
174
  clamp(date) {
187
175
  const iso = fromDate(date);
@@ -252,8 +240,93 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
252
240
  this.choosing = true;
253
241
  this.requestValue(next.join(','));
254
242
  }
243
+ get periodYear() {
244
+ return this.pageYear ?? this.shown.getUTCFullYear();
245
+ }
246
+ get periodStop() {
247
+ if (this.periodFocus !== undefined) {
248
+ return this.periodFocus;
249
+ }
250
+ return this.view === 'year' ? this.periodYear : this.shown.getUTCMonth();
251
+ }
252
+ monthOut(first) {
253
+ return monthOut(first, this.min, this.max);
254
+ }
255
+ yearOut(year) {
256
+ return yearOut(year, this.min, this.max);
257
+ }
258
+ zoom(view) {
259
+ this.view = view;
260
+ void this.updateComplete.then(() => this.focus());
261
+ }
262
+ choosePeriod(at) {
263
+ if (this.isDisabled) {
264
+ return;
265
+ }
266
+ if (this.view === 'year') {
267
+ if (this.yearOut(at)) {
268
+ return;
269
+ }
270
+ this.pageYear = at;
271
+ this.periodFocus = at === this.shown.getUTCFullYear() ? this.shown.getUTCMonth() : 0;
272
+ this.zoom('month');
273
+ return;
274
+ }
275
+ const first = new Date(Date.UTC(this.periodYear, at, 1));
276
+ if (this.monthOut(first)) {
277
+ return;
278
+ }
279
+ this.show(first);
280
+ if (this.month !== monthOf(first)) {
281
+ return;
282
+ }
283
+ this.pageYear = undefined;
284
+ this.periodFocus = undefined;
285
+ this.focused = '';
286
+ this.zoom('day');
287
+ }
288
+ turnPage(direction) {
289
+ const by = this.view === 'year' ? YEARS_SHOWN : 1;
290
+ this.pageYear = this.periodYear + direction * by;
291
+ if (this.view === 'year') {
292
+ this.periodFocus = this.periodStop + direction * by;
293
+ }
294
+ }
295
+ handlePeriodKeydown(event) {
296
+ if (this.isDisabled) {
297
+ return;
298
+ }
299
+ if (event.key === 'Escape') {
300
+ event.preventDefault();
301
+ event.stopPropagation();
302
+ this.pageYear = undefined;
303
+ this.periodFocus = undefined;
304
+ this.zoom('day');
305
+ return;
306
+ }
307
+ const grid = this.partNamed('periods');
308
+ if (!grid || !event.composedPath().includes(grid)) {
309
+ return;
310
+ }
311
+ const stop = this.periodStop;
312
+ if (event.key === 'Enter' || event.key === ' ') {
313
+ event.preventDefault();
314
+ this.choosePeriod(stop);
315
+ return;
316
+ }
317
+ const moved = periodKey(this.view === 'year' ? 'year' : 'month', stop, this.periodYear, event.key, inlineStep(this, event.key));
318
+ if (moved === null) {
319
+ return;
320
+ }
321
+ event.preventDefault();
322
+ this.periodFocus = moved.focus;
323
+ this.pageYear = moved.pageYear;
324
+ void this.updateComplete.then(() => this.focus());
325
+ }
255
326
  focus(options) {
256
- this.renderRoot?.querySelector('[part~="cell"][tabindex="0"]')?.focus(options);
327
+ this.renderRoot
328
+ ?.querySelector('[part~="cell"][tabindex="0"], [part~="period"][tabindex="0"]')
329
+ ?.focus(options);
257
330
  }
258
331
  connectedCallback() {
259
332
  super.connectedCallback();
@@ -291,12 +364,11 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
291
364
  ];
292
365
  }
293
366
  const date = asDate(iso);
294
- if (!date || !this.barred(date)) {
367
+ const why = date ? barredBy(date, this) : null;
368
+ if (why === null) {
295
369
  return undefined;
296
370
  }
297
- const listed = writtenList(this.disableDates).includes(iso) ||
298
- writtenList(this.disableDays).includes(String(date.getUTCDay()));
299
- return listed
371
+ return why === 'listed'
300
372
  ? ['badInput', this.unavailableMessage || 'Choose a date that is available.']
301
373
  : ['stepMismatch', this.stepMismatchMessage || 'Choose one of the dates the calendar offers.'];
302
374
  }
@@ -363,7 +435,9 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
363
435
  heading: new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC', year: 'numeric' }),
364
436
  locale: this.locale,
365
437
  long: new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeZone: 'UTC' }),
438
+ monthName: new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' }),
366
439
  weekday: new Intl.DateTimeFormat(locale, { timeZone: 'UTC', weekday: 'short' }),
440
+ year: new Intl.DateTimeFormat(locale, { timeZone: 'UTC', year: 'numeric' }),
367
441
  };
368
442
  this.cachedFormats = built;
369
443
  return built;
@@ -404,7 +478,9 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
404
478
  </button>`
405
479
  : nothing}
406
480
  ${''}
407
- <span part="heading" aria-live="polite">${heading.format(month)}</span>
481
+ <span part="heading" aria-live="polite"
482
+ >${this.renderHeadingText(heading.format(month), this.monthViewLabel)}</span
483
+ >
408
484
  ${last
409
485
  ? html `<button
410
486
  part="next"
@@ -452,7 +528,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
452
528
  @click=${outside ? nothing : () => this.select(date)}
453
529
  ><span
454
530
  part="day${!outside && selected && !middle ? ' day-selected' : ''}${iso === today ? ' day-today' : ''}${outside ? ' day-outside' : ''}${disabled ? ' day-disabled' : ''}"
455
- >${date.getUTCDate()}</span
531
+ >${this.renderDay(date, outside)}</span
456
532
  ></span
457
533
  >`;
458
534
  })}
@@ -460,7 +536,86 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
460
536
  </div>
461
537
  </div>`;
462
538
  }
539
+ renderDay(date, outside) {
540
+ return this.dayContent && !outside ? this.dayContent(fromDate(date)) : date.getUTCDate();
541
+ }
542
+ renderHeadingText(text, label) {
543
+ const steps = this.maxView !== undefined &&
544
+ (this.view === 'day' || (this.view === 'month' && this.maxView === 'year'));
545
+ if (!steps) {
546
+ return text;
547
+ }
548
+ return html `<button
549
+ part="heading-button"
550
+ type="button"
551
+ aria-label=${fillTemplate(label, { heading: text })}
552
+ ?disabled=${this.isDisabled}
553
+ @click=${this.handleHeading}
554
+ >${text}</button>`;
555
+ }
556
+ renderPeriods() {
557
+ const { monthName, year: yearName } = this.formats;
558
+ const years = this.view === 'year';
559
+ const page = years ? yearPageOf(this.periodYear) : this.periodYear;
560
+ const heading = years
561
+ ? `${yearName.format(new Date(Date.UTC(page, 0, 1)))} – ${yearName.format(new Date(Date.UTC(page + YEARS_SHOWN - 1, 0, 1)))}`
562
+ : yearName.format(new Date(Date.UTC(page, 0, 1)));
563
+ const stop = this.periodStop;
564
+ const cells = periodCells({
565
+ max: this.max,
566
+ min: this.min,
567
+ page: this.periodYear,
568
+ shown: this.shown,
569
+ today: localToday(),
570
+ view: years ? 'year' : 'month',
571
+ }).map(({ at, chosen, current, first, out }) => {
572
+ const words = years ? yearName.format(first) : monthName.format(first);
573
+ return html `<span
574
+ part="period${chosen ? ' period-selected' : ''}${current ? ' period-current' : ''}${out ? ' period-disabled' : ''}"
575
+ role="gridcell"
576
+ aria-current=${current ? 'date' : nothing}
577
+ aria-disabled=${out ? 'true' : nothing}
578
+ aria-label=${years ? words : `${words} ${yearName.format(first)}`}
579
+ aria-selected=${chosen ? 'true' : nothing}
580
+ tabindex=${at === stop && !this.isDisabled ? '0' : '-1'}
581
+ @click=${() => this.choosePeriod(at)}
582
+ >${words}</span
583
+ >`;
584
+ });
585
+ const rows = [0, 3, 6, 9].map((from) => html `<div part="period-row" role="row">${cells.slice(from, from + 3)}</div>`);
586
+ return html `<div part="month">
587
+ <div part="header">
588
+ <button
589
+ part="previous"
590
+ type="button"
591
+ aria-label=${years ? this.previousYearsLabel : this.previousYearLabel}
592
+ ?disabled=${this.isDisabled || this.yearOut(page - 1)}
593
+ @click=${() => this.turnPage(-1)}
594
+ >
595
+ ${this.renderPreviousMark()}
596
+ </button>
597
+ <span part="heading" aria-live="polite"
598
+ >${this.renderHeadingText(heading, this.yearViewLabel)}</span
599
+ >
600
+ <button
601
+ part="next"
602
+ type="button"
603
+ aria-label=${years ? this.nextYearsLabel : this.nextYearLabel}
604
+ ?disabled=${this.isDisabled || this.yearOut(years ? page + YEARS_SHOWN : page + 1)}
605
+ @click=${() => this.turnPage(1)}
606
+ >
607
+ ${this.renderNextMark()}
608
+ </button>
609
+ </div>
610
+ <div part="periods" role="grid" aria-label=${heading}>${rows}</div>
611
+ </div>`;
612
+ }
463
613
  render() {
614
+ if (this.view !== 'day') {
615
+ return html `<div part="months" style="--date-picker-column-count: 1">
616
+ ${this.renderPeriods()}
617
+ </div>`;
618
+ }
464
619
  const shown = this.shown;
465
620
  const count = this.monthCount;
466
621
  return html `<div part="months" style="--date-picker-column-count: ${Math.min(count, 2)}">
@@ -483,6 +638,9 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
483
638
  __decorate([
484
639
  property({ attribute: 'bad-input-message' })
485
640
  ], HelixHeadlessDatePicker.prototype, "badInputMessage", void 0);
641
+ __decorate([
642
+ property({ attribute: false })
643
+ ], HelixHeadlessDatePicker.prototype, "dayContent", void 0);
486
644
  __decorate([
487
645
  property({ attribute: 'default-month' })
488
646
  ], HelixHeadlessDatePicker.prototype, "defaultMonth", void 0);
@@ -498,6 +656,9 @@ __decorate([
498
656
  __decorate([
499
657
  property({ reflect: true, type: Boolean })
500
658
  ], HelixHeadlessDatePicker.prototype, "invalid", void 0);
659
+ __decorate([
660
+ property({ attribute: false })
661
+ ], HelixHeadlessDatePicker.prototype, "isDateUnavailable", void 0);
501
662
  __decorate([
502
663
  property()
503
664
  ], HelixHeadlessDatePicker.prototype, "label", void 0);
@@ -507,6 +668,9 @@ __decorate([
507
668
  __decorate([
508
669
  property()
509
670
  ], HelixHeadlessDatePicker.prototype, "max", void 0);
671
+ __decorate([
672
+ property({ attribute: 'max-view' })
673
+ ], HelixHeadlessDatePicker.prototype, "maxView", void 0);
510
674
  __decorate([
511
675
  property()
512
676
  ], HelixHeadlessDatePicker.prototype, "min", void 0);
@@ -519,18 +683,39 @@ __decorate([
519
683
  __decorate([
520
684
  property({ type: Number })
521
685
  ], HelixHeadlessDatePicker.prototype, "months", void 0);
686
+ __decorate([
687
+ property({ attribute: 'month-view-label' })
688
+ ], HelixHeadlessDatePicker.prototype, "monthViewLabel", void 0);
522
689
  __decorate([
523
690
  property({ attribute: 'next-label' })
524
691
  ], HelixHeadlessDatePicker.prototype, "nextLabel", void 0);
692
+ __decorate([
693
+ property({ attribute: 'next-year-label' })
694
+ ], HelixHeadlessDatePicker.prototype, "nextYearLabel", void 0);
695
+ __decorate([
696
+ property({ attribute: 'next-years-label' })
697
+ ], HelixHeadlessDatePicker.prototype, "nextYearsLabel", void 0);
525
698
  __decorate([
526
699
  property({ attribute: 'previous-label' })
527
700
  ], HelixHeadlessDatePicker.prototype, "previousLabel", void 0);
701
+ __decorate([
702
+ property({ attribute: 'previous-year-label' })
703
+ ], HelixHeadlessDatePicker.prototype, "previousYearLabel", void 0);
704
+ __decorate([
705
+ property({ attribute: 'previous-years-label' })
706
+ ], HelixHeadlessDatePicker.prototype, "previousYearsLabel", void 0);
528
707
  __decorate([
529
708
  property({ attribute: 'range-overflow-message' })
530
709
  ], HelixHeadlessDatePicker.prototype, "rangeOverflowMessage", void 0);
531
710
  __decorate([
532
711
  state()
533
712
  ], HelixHeadlessDatePicker.prototype, "focused", void 0);
713
+ __decorate([
714
+ state()
715
+ ], HelixHeadlessDatePicker.prototype, "pageYear", void 0);
716
+ __decorate([
717
+ state()
718
+ ], HelixHeadlessDatePicker.prototype, "periodFocus", void 0);
534
719
  __decorate([
535
720
  property({ attribute: 'range-underflow-message' })
536
721
  ], HelixHeadlessDatePicker.prototype, "rangeUnderflowMessage", void 0);
@@ -552,6 +737,12 @@ __decorate([
552
737
  __decorate([
553
738
  property({ attribute: 'value-missing-message' })
554
739
  ], HelixHeadlessDatePicker.prototype, "valueMissingMessage", void 0);
740
+ __decorate([
741
+ property()
742
+ ], HelixHeadlessDatePicker.prototype, "view", void 0);
743
+ __decorate([
744
+ property({ attribute: 'year-view-label' })
745
+ ], HelixHeadlessDatePicker.prototype, "yearViewLabel", void 0);
555
746
  HelixHeadlessDatePicker = __decorate([
556
747
  customElement('helix-headless-date-picker')
557
748
  ], HelixHeadlessDatePicker);
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The elements at the start of a slot that hold words and no control, up to the
3
+ * first one that is or holds a control.
4
+ *
5
+ * What a dialog is described by. A question above a form describes the dialog,
6
+ * and the form does not: a description that read out every field label would
7
+ * repeat what a reader hears again as they reach each field.
8
+ */
9
+ export declare const wordsIn: (slot: HTMLSlotElement | null) => Element[];
@@ -0,0 +1,14 @@
1
+ const CONTROL = 'a[href], audio[controls], button, details, form, iframe, input, select, textarea, video[controls], [contenteditable], [tabindex]';
2
+ const isControl = (element) => element.localName.includes('-') || element.matches(CONTROL);
3
+ export const wordsIn = (slot) => {
4
+ const words = [];
5
+ for (const one of slot?.assignedElements({ flatten: true }) ?? []) {
6
+ if (isControl(one) || [...one.querySelectorAll('*')].some(isControl)) {
7
+ break;
8
+ }
9
+ if ((one.textContent ?? '').trim() !== '') {
10
+ words.push(one);
11
+ }
12
+ }
13
+ return words;
14
+ };
@@ -1,4 +1,4 @@
1
- import { AriaForwardController, CommandController, HelixElement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, CommandController, HelixElement, OpenPhaseController } 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
  /**
@@ -23,6 +23,16 @@ declare const HelixHeadlessDialog_base: (new (...args: any[]) => import("@fusion
23
23
  * the `header` slot, or the elements an `aria-labelledby` on the host names.
24
24
  * While it is open, the page behind it does not scroll.
25
25
  *
26
+ * The dialog is described by the words at the start of its body: the elements
27
+ * in the default slot before the first one that is or holds a control. A
28
+ * question above a form describes the dialog, and the form does not. An
29
+ * `aria-describedby` on the host replaces it.
30
+ *
31
+ * Set `alert` for a dialog that interrupts to ask for an answer, such as a
32
+ * confirmation before a deletion. It is an `alertdialog`, which a screen reader
33
+ * announces with its description as it opens. Set `dismissible` to draw a
34
+ * close control, labeled by `dismiss-label`.
35
+ *
26
36
  * @since 0.1.0
27
37
  * @status stable
28
38
  * @category overlays
@@ -30,6 +40,7 @@ declare const HelixHeadlessDialog_base: (new (...args: any[]) => import("@fusion
30
40
  * @documentation https://fusion.dev/helix/components/dialog
31
41
  *
32
42
  * @slot - The dialog's content.
43
+ * @slot dismiss-icon - Replaces the glyph inside the close control.
33
44
  * @slot footer - The dialog's actions.
34
45
  * @slot header - The title, and anything beside it.
35
46
  * @slot icon - An icon above the title, for a warning dialog.
@@ -39,11 +50,30 @@ declare const HelixHeadlessDialog_base: (new (...args: any[]) => import("@fusion
39
50
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before it opens, and cancelable.
40
51
  * @event {ToggleEvent} toggle - Sent when the dialog opens or closes, with `newState` and `oldState`.
41
52
  *
53
+ * @csspart body - The slot the content is in. It draws no box until you give it one.
42
54
  * @csspart dialog - The native dialog element.
55
+ * @csspart dismiss - The close control, drawn when the dialog is `dismissible`.
56
+ * @csspart dismiss-mark - The glyph on the close control, where none is slotted.
57
+ * @csspart footer - The slot the actions are in. It draws no box until you give it one.
58
+ * @csspart header - The slot the title is in. It draws no box until you give it one.
43
59
  *
60
+ * @cssstate entering - From the moment the dialog opens until its entrance has finished.
61
+ * @cssstate exiting - From the moment the dialog closes until its exit has finished.
44
62
  * @cssstate open - While the dialog is open.
45
63
  */
46
64
  export declare class HelixHeadlessDialog extends HelixHeadlessDialog_base {
65
+ /**
66
+ * Makes the dialog an alert dialog, for a message that interrupts to ask for
67
+ * an answer, such as a confirmation before something is deleted.
68
+ *
69
+ * The `<dialog>` takes `role="alertdialog"`, so a screen reader announces the
70
+ * dialog and its description as it opens. Everything else stays the same:
71
+ * the dialog is modal, Escape asks to close it, and `persistent` still
72
+ * refuses that.
73
+ *
74
+ * Not reflected, because no stylesheet selects on it.
75
+ */
76
+ alert: boolean;
47
77
  /**
48
78
  * Which close requests the browser handles on its own.
49
79
  *
@@ -57,11 +87,24 @@ export declare class HelixHeadlessDialog extends HelixHeadlessDialog_base {
57
87
  * that a resting value is an attribute's absence.
58
88
  */
59
89
  closedBy?: 'any' | 'closerequest' | 'none';
90
+ /**
91
+ * Draws a control that closes the dialog, in the corner where the title is.
92
+ *
93
+ * Pressing it is `close()`, so it sends `helix-before-close` like every other
94
+ * way out, and `persistent` does not disable it.
95
+ */
96
+ dismissible: boolean;
97
+ /**
98
+ * The close control's accessible name. Translate it with the rest of your
99
+ * interface.
100
+ */
101
+ dismissLabel: string;
60
102
  /**
61
103
  * Accessible name for the dialog. When it is empty, the heading in the
62
104
  * `header` slot names the dialog.
63
105
  */
64
106
  label: string;
107
+ protected readonly phases: OpenPhaseController;
65
108
  protected readonly aria: AriaForwardController;
66
109
  protected readonly commands: CommandController;
67
110
  /**
@@ -147,6 +190,19 @@ export declare class HelixHeadlessDialog extends HelixHeadlessDialog_base {
147
190
  * either. A browser without element reflection keeps the fallback name.
148
191
  */
149
192
  private nameByHeader;
193
+ /** Whether the body's words are what currently describe the dialog. */
194
+ private describedByBody;
195
+ /**
196
+ * Describes the dialog by the words at the start of its body, where nothing
197
+ * else describes it.
198
+ *
199
+ * An element reference, for the reason `nameByHeader` gives. Only the leading
200
+ * run of elements that hold no control, so a dialog asking a question above a
201
+ * form is described by the question: a description that read out every field
202
+ * label would repeat what a reader hears again as they reach each field. An
203
+ * `aria-describedby` on the host comes first.
204
+ */
205
+ private describeByBody;
150
206
  protected willUpdate(changed: PropertyValues): void;
151
207
  connectedCallback(): void;
152
208
  disconnectedCallback(): void;
@@ -203,6 +259,13 @@ export declare class HelixHeadlessDialog extends HelixHeadlessDialog_base {
203
259
  private refuse;
204
260
  /** Bounds the refusal listener to one, whether or not it ever fires. */
205
261
  private refusal;
262
+ /**
263
+ * The glyph on the close control, behind a seam.
264
+ *
265
+ * Slot fallback, so a consumer replaces it by slotting, and a method, so a
266
+ * tier above replaces it by overriding. The same pair `Toast.Item` draws.
267
+ */
268
+ protected renderDismissIcon(): TemplateResult;
206
269
  protected render(): TemplateResult;
207
270
  }
208
271
  declare global {