@fusion.dev/helix-headless 0.103.3 → 0.104.1

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 (107) hide show
  1. package/custom-elements.json +9260 -880
  2. package/dist/announcement/root/announcement-root.d.ts +9 -2
  3. package/dist/announcement/root/announcement-root.js +9 -6
  4. package/dist/badge/root/badge-root.js +2 -3
  5. package/dist/canvas/edge/canvas-edge.d.ts +112 -0
  6. package/dist/canvas/edge/canvas-edge.js +66 -0
  7. package/dist/canvas/index.d.ts +4 -0
  8. package/dist/canvas/index.js +3 -0
  9. package/dist/canvas/node/canvas-node.d.ts +211 -0
  10. package/dist/canvas/node/canvas-node.js +148 -0
  11. package/dist/canvas/root/canvas-document.d.ts +215 -0
  12. package/dist/canvas/root/canvas-document.js +284 -0
  13. package/dist/canvas/root/canvas-root.d.ts +924 -0
  14. package/dist/canvas/root/canvas-root.js +1831 -0
  15. package/dist/carousel/root/carousel-root.d.ts +53 -16
  16. package/dist/carousel/root/carousel-root.js +46 -11
  17. package/dist/chat/root/chat-root.d.ts +4 -0
  18. package/dist/chat/root/chat-root.js +11 -7
  19. package/dist/checkbox/root/checkbox-root.js +1 -0
  20. package/dist/checkbox-group/root/checkbox-group-root.d.ts +10 -0
  21. package/dist/checkbox-group/root/checkbox-group-root.js +5 -0
  22. package/dist/color-input/root/color-input-root.js +21 -5
  23. package/dist/color-picker/root/color-picker-root.d.ts +55 -0
  24. package/dist/color-picker/root/color-picker-root.js +61 -12
  25. package/dist/combobox/root/combobox-root.d.ts +21 -2
  26. package/dist/combobox/root/combobox-root.js +23 -9
  27. package/dist/command/root/command-root.d.ts +1 -0
  28. package/dist/command/root/command-root.js +1 -1
  29. package/dist/data-table/root/data-table-root.d.ts +43 -42
  30. package/dist/data-table/root/data-table-root.js +35 -34
  31. package/dist/date-input/root/date-input-root.d.ts +27 -0
  32. package/dist/date-input/root/date-input-root.js +38 -9
  33. package/dist/date-picker/root/date-picker-root.d.ts +28 -16
  34. package/dist/date-picker/root/date-picker-root.js +17 -15
  35. package/dist/drilldown/root/drilldown-root.d.ts +1 -0
  36. package/dist/drilldown/root/drilldown-root.js +5 -2
  37. package/dist/element-events.generated.js +15 -0
  38. package/dist/empty/root/empty-root.d.ts +12 -7
  39. package/dist/empty/root/empty-root.js +13 -10
  40. package/dist/file-input/root/file-input-root.d.ts +5 -1
  41. package/dist/file-input/root/file-input-root.js +6 -4
  42. package/dist/filter-input/menu/filter-menu.d.ts +1 -0
  43. package/dist/filter-input/menu/filter-menu.js +1 -1
  44. package/dist/filter-input/root/filter-input-root.js +5 -0
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.js +1 -0
  47. package/dist/input/root/input-root.js +1 -1
  48. package/dist/lightbox/root/lightbox-root.d.ts +9 -1
  49. package/dist/lightbox/root/lightbox-root.js +17 -7
  50. package/dist/listbox/root/listbox-root.d.ts +16 -14
  51. package/dist/listbox/root/listbox-root.js +24 -15
  52. package/dist/map/root/map-root.d.ts +6 -3
  53. package/dist/map/root/map-root.js +8 -2
  54. package/dist/menu/root/menu-root.d.ts +1 -0
  55. package/dist/menu/root/menu-root.js +1 -1
  56. package/dist/notifications/group/notification-group.js +5 -4
  57. package/dist/notifications/notification/notification.d.ts +10 -10
  58. package/dist/notifications/notification/notification.js +3 -3
  59. package/dist/notifications/root/notifications-root.d.ts +1 -0
  60. package/dist/notifications/root/notifications-root.js +1 -0
  61. package/dist/number-input/root/number-input-root.d.ts +17 -0
  62. package/dist/number-input/root/number-input-root.js +22 -6
  63. package/dist/object-inspector/root/object-inspector-root.d.ts +10 -0
  64. package/dist/object-inspector/root/object-inspector-root.js +10 -2
  65. package/dist/outline/root/outline-root.d.ts +15 -0
  66. package/dist/outline/root/outline-root.js +12 -7
  67. package/dist/pagination/root/pagination-root.d.ts +18 -18
  68. package/dist/pagination/root/pagination-root.js +5 -5
  69. package/dist/pagination/size/pagination-size.d.ts +10 -9
  70. package/dist/pagination/size/pagination-size.js +4 -4
  71. package/dist/pagination/summary/pagination-summary.js +6 -5
  72. package/dist/pin-input/root/pin-input-root.d.ts +5 -0
  73. package/dist/pin-input/root/pin-input-root.js +10 -6
  74. package/dist/rating/root/rating-root.js +3 -2
  75. package/dist/select/root/select-root.d.ts +1 -0
  76. package/dist/select/root/select-root.js +8 -3
  77. package/dist/slider/root/slider-root.d.ts +12 -0
  78. package/dist/slider/root/slider-root.js +13 -1
  79. package/dist/splitter/root/splitter-root.d.ts +44 -35
  80. package/dist/splitter/root/splitter-root.js +27 -22
  81. package/dist/switch/root/switch-root.js +1 -0
  82. package/dist/table/column/table-column.d.ts +17 -0
  83. package/dist/table/column/table-column.js +16 -4
  84. package/dist/table/root/table-root.d.ts +1 -0
  85. package/dist/table/root/table-root.js +1 -1
  86. package/dist/textarea/root/textarea-root.js +1 -1
  87. package/dist/time-input/root/time-input-root.d.ts +29 -0
  88. package/dist/time-input/root/time-input-root.js +31 -10
  89. package/dist/time-picker/root/time-picker-root.d.ts +14 -0
  90. package/dist/time-picker/root/time-picker-root.js +16 -14
  91. package/dist/toast/item/toast-item.d.ts +12 -11
  92. package/dist/toast/item/toast-item.js +11 -5
  93. package/dist/toolbar/root/toolbar-root.js +2 -16
  94. package/dist/tooltip/root/tooltip-root.d.ts +1 -0
  95. package/dist/tooltip/root/tooltip-root.js +6 -0
  96. package/dist/tour/root/tour-root.d.ts +10 -10
  97. package/dist/tour/root/tour-root.js +5 -5
  98. package/dist/tree/root/tree-root.d.ts +1 -0
  99. package/dist/tree/root/tree-root.js +1 -1
  100. package/dist/video/root/video-root.d.ts +84 -0
  101. package/dist/video/root/video-root.js +116 -41
  102. package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
  103. package/dist/video-playlist/menu/video-playlist-menu.js +4 -3
  104. package/package.json +5 -5
  105. package/types/jsx.d.ts +5419 -716
  106. package/types/svelte.d.ts +3 -0
  107. package/types/vue.d.ts +1607 -213
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, valueAsDate as asDate, customElement, dateAsValue as fromDate, HelixFormElement, isDateValue, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinTokens, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, valueAsDate as asDate, customElement, fillTemplate, dateAsValue as fromDate, HelixFormElement, isDateValue, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinTokens, } from '@fusion.dev/helix-core';
8
8
  import { calendarBlank } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -35,6 +35,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
35
35
  this.fitted = undefined;
36
36
  this.wide = undefined;
37
37
  this.placeholder = 'YYYY-MM-DD';
38
+ this.removeLabel = 'Remove {name}';
38
39
  this.step = 1;
39
40
  this.draft = '';
40
41
  this.handleInput = (event) => {
@@ -138,21 +139,29 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
138
139
  get restCount() {
139
140
  return Math.max(0, dates(this.value).length - this.shownDates.length);
140
141
  }
142
+ get answers() {
143
+ return this.mode === undefined ? undefined : dates(this.value);
144
+ }
141
145
  validate() {
142
146
  const held = dates(this.value);
143
147
  if (held.length > 0 && !held.every(isDateValue)) {
144
- return this.mode === undefined
145
- ? 'Enter a date as YYYY-MM-DD.'
146
- : 'Enter dates as YYYY-MM-DD, separated by commas.';
148
+ return (this.badInputMessage ||
149
+ (this.mode === undefined
150
+ ? 'Enter a date as YYYY-MM-DD.'
151
+ : 'Enter dates as YYYY-MM-DD, separated by commas.'));
147
152
  }
148
153
  if (this.min !== '' && held.some((one) => one < this.min)) {
149
- return `Choose a date on or after ${this.min}.`;
154
+ return fillTemplate(this.rangeUnderflowMessage || 'Choose a date on or after {min}.', {
155
+ min: this.written(this.min),
156
+ });
150
157
  }
151
158
  if (this.max !== '' && held.some((one) => one > this.max)) {
152
- return `Choose a date on or before ${this.max}.`;
159
+ return fillTemplate(this.rangeOverflowMessage || 'Choose a date on or before {max}.', {
160
+ max: this.written(this.max),
161
+ });
153
162
  }
154
163
  if (this.mode === 'range' && held.length > 2) {
155
- return 'A range is two dates.';
164
+ return this.rangeLengthMessage || 'A range is two dates.';
156
165
  }
157
166
  return '';
158
167
  }
@@ -194,7 +203,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
194
203
  part="date"
195
204
  exportparts="dismiss: date-dismiss, dismiss-mark: date-dismiss-mark, start: date-start"
196
205
  dismissible
197
- dismiss-label=${`Remove ${date}`}
206
+ dismiss-label=${fillTemplate(this.removeLabel, { name: this.written(date) })}
198
207
  ?disabled=${this.isDisabled}
199
208
  @helix-dismiss=${(event) => {
200
209
  event.stopPropagation();
@@ -248,7 +257,15 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
248
257
  </button>
249
258
  </div>
250
259
 
251
- <div part="surface" id="surface" popover="manual" ?hidden=${!this.open}>
260
+ ${''}
261
+ <div
262
+ part="surface"
263
+ id="surface"
264
+ role="dialog"
265
+ aria-label=${this.triggerLabel}
266
+ popover="manual"
267
+ ?hidden=${!this.open}
268
+ >
252
269
  ${this.renderPicker()}
253
270
  </div>
254
271
  ${this.renderHint()}${this.renderFeedback()}`;
@@ -302,6 +319,18 @@ __decorate([
302
319
  __decorate([
303
320
  property()
304
321
  ], HelixHeadlessDateInput.prototype, "placeholder", void 0);
322
+ __decorate([
323
+ property({ attribute: 'range-length-message' })
324
+ ], HelixHeadlessDateInput.prototype, "rangeLengthMessage", void 0);
325
+ __decorate([
326
+ property({ attribute: 'range-overflow-message' })
327
+ ], HelixHeadlessDateInput.prototype, "rangeOverflowMessage", void 0);
328
+ __decorate([
329
+ property({ attribute: 'range-underflow-message' })
330
+ ], HelixHeadlessDateInput.prototype, "rangeUnderflowMessage", void 0);
331
+ __decorate([
332
+ property({ attribute: 'remove-label' })
333
+ ], HelixHeadlessDateInput.prototype, "removeLabel", void 0);
305
334
  __decorate([
306
335
  property({ type: Number })
307
336
  ], HelixHeadlessDateInput.prototype, "step", void 0);
@@ -58,6 +58,14 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
58
58
  * @cssproperty --date-picker-column-count - How many month columns are drawn, capped at two. `--date-picker-columns` overrides it.
59
59
  */
60
60
  export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_base {
61
+ /**
62
+ * What a reader is told when the value holds something that is not a date.
63
+ *
64
+ * **Every sentence a control raises in the browser's bubble is a property**,
65
+ * named for the validity flag it reports, so a form in another language
66
+ * speaks it. Absent is the control's own sentence, in English.
67
+ */
68
+ badInputMessage?: string;
61
69
  /**
62
70
  * Month to open on, where nothing declared one.
63
71
  */
@@ -82,15 +90,6 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
82
90
  * Accessible name for the grid.
83
91
  */
84
92
  label: string;
85
- /**
86
- * The month on show, as `YYYY-MM`. Follows `value`, then the current month.
87
- *
88
- * Separate from `value` on purpose, so paging selects nothing and a consumer
89
- * can drive the view. Reading gives the month on show; writing sets it, and
90
- * `default-month` seeds one the calendar keeps until somebody does.
91
- */
92
- get month(): string;
93
- set month(next: string);
94
93
  /**
95
94
  * Locale for month and day names.
96
95
  *
@@ -105,12 +104,21 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
105
104
  * Earliest selectable date, as `YYYY-MM-DD`.
106
105
  */
107
106
  min: string;
108
- /** The month on show, whoever set it, and `undefined` until somebody has. */
109
- private monthState;
110
107
  /**
111
108
  * How many dates can be held at once.
112
109
  */
113
110
  mode?: 'range' | 'multiple';
111
+ /** The month on show, whoever set it, and `undefined` until somebody has. */
112
+ private monthState;
113
+ /**
114
+ * The month on show, as `YYYY-MM`. Follows `value`, then the current month.
115
+ *
116
+ * Separate from `value` on purpose, so paging selects nothing and a consumer
117
+ * can drive the view. Reading gives the month on show; writing sets it, and
118
+ * `default-month` seeds one the calendar keeps until somebody does.
119
+ */
120
+ get month(): string;
121
+ set month(next: string);
114
122
  /**
115
123
  * How many months to show, side by side.
116
124
  */
@@ -229,6 +237,10 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
229
237
  * date including the one already chosen.
230
238
  */
231
239
  step: number;
240
+ /**
241
+ * What a reader is told when a date is required and none is chosen.
242
+ */
243
+ valueMissingMessage?: string;
232
244
  /**
233
245
  * Reports "required and empty", or clears it.
234
246
  *
@@ -261,11 +273,11 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
261
273
  *
262
274
  * **Nothing at all without a `name`, and that is the mechanism rather than a
263
275
  * detail.** It is what lets a calendar sit inside something that owns the
264
- * form value, which is how `DatePicker` will hold one. The guard has to be
265
- * explicit here in a way it does not for `Listbox`: a control reporting a
266
- * plain string is skipped by the form when it has no name, but a `FormData`
267
- * carries its own names and its entries are appended whatever the element is
268
- * called, so an unnamed calendar would submit empty-named dates.
276
+ * form value, which is how `Date Input` holds one. `reportValues` keeps the
277
+ * guard, for every control that sends several answers: a plain string is
278
+ * skipped by the form when its control has no name, but a `FormData` carries
279
+ * its own names and its entries are appended whatever the element is called,
280
+ * so an unnamed calendar would submit empty-named dates.
269
281
  */
270
282
  private report;
271
283
  /**
@@ -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, requestChange, } from '@fusion.dev/helix-core';
7
+ import { valueAsDate as asDate, customElement, dateAsValue as fromDate, HelixFormElement, HelixNavigateEvent, hostAria, inlineStep, MixinControllerValue, reportValues, requestChange, } 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';
@@ -231,14 +231,15 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
231
231
  reportRequired() {
232
232
  const anchor = this.partNamed('grid') ?? undefined;
233
233
  if (this.required && this.value === '') {
234
- this.internals.setValidity({ valueMissing: true }, 'Choose a date.', anchor);
234
+ this.internals.setValidity({ valueMissing: true }, this.valueMissingMessage || 'Choose a date.', anchor);
235
235
  return;
236
236
  }
237
237
  const held = this.selected;
238
238
  if (held.length > 0 && held.some((one) => asDate(one) === null)) {
239
- this.internals.setValidity({ badInput: true }, this.mode === undefined
240
- ? 'Enter a date as YYYY-MM-DD.'
241
- : 'Enter dates as YYYY-MM-DD, separated by commas.', anchor);
239
+ this.internals.setValidity({ badInput: true }, this.badInputMessage ||
240
+ (this.mode === undefined
241
+ ? 'Enter a date as YYYY-MM-DD.'
242
+ : 'Enter dates as YYYY-MM-DD, separated by commas.'), anchor);
242
243
  return;
243
244
  }
244
245
  this.internals.setValidity({});
@@ -255,16 +256,11 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
255
256
  this.reportRequired();
256
257
  }
257
258
  report() {
258
- const dates = this.selected;
259
- if (this.name === '' || this.disabled || dates.length === 0) {
259
+ if (this.disabled) {
260
260
  this.internals.setFormValue(null);
261
261
  return;
262
262
  }
263
- const data = new FormData();
264
- for (const date of dates) {
265
- data.append(this.name, date);
266
- }
267
- this.internals.setFormValue(data);
263
+ reportValues(this.internals, this.name, this.selected);
268
264
  }
269
265
  get monthCount() {
270
266
  return Math.max(1, Math.floor(this.months) || 1);
@@ -391,6 +387,9 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
391
387
  this.value = Number.isNaN(ms) ? '' : fromDate(new Date(ms));
392
388
  }
393
389
  };
390
+ __decorate([
391
+ property({ attribute: 'bad-input-message' })
392
+ ], HelixHeadlessDatePicker.prototype, "badInputMessage", void 0);
394
393
  __decorate([
395
394
  property({ attribute: 'default-month' })
396
395
  ], HelixHeadlessDatePicker.prototype, "defaultMonth", void 0);
@@ -409,9 +408,6 @@ __decorate([
409
408
  __decorate([
410
409
  property()
411
410
  ], HelixHeadlessDatePicker.prototype, "label", void 0);
412
- __decorate([
413
- property()
414
- ], HelixHeadlessDatePicker.prototype, "month", null);
415
411
  __decorate([
416
412
  property()
417
413
  ], HelixHeadlessDatePicker.prototype, "locale", void 0);
@@ -424,6 +420,9 @@ __decorate([
424
420
  __decorate([
425
421
  property({ reflect: true })
426
422
  ], HelixHeadlessDatePicker.prototype, "mode", void 0);
423
+ __decorate([
424
+ property()
425
+ ], HelixHeadlessDatePicker.prototype, "month", null);
427
426
  __decorate([
428
427
  property({ type: Number })
429
428
  ], HelixHeadlessDatePicker.prototype, "months", void 0);
@@ -445,6 +444,9 @@ __decorate([
445
444
  __decorate([
446
445
  property({ type: Number })
447
446
  ], HelixHeadlessDatePicker.prototype, "step", void 0);
447
+ __decorate([
448
+ property({ attribute: 'value-missing-message' })
449
+ ], HelixHeadlessDatePicker.prototype, "valueMissingMessage", void 0);
448
450
  HelixHeadlessDatePicker = __decorate([
449
451
  customElement('helix-headless-date-picker')
450
452
  ], HelixHeadlessDatePicker);
@@ -40,6 +40,7 @@ declare const HelixHeadlessDrilldown_base: (new (...args: any[]) => import("@fus
40
40
  * @csspart back-mark - The glyph on the back control.
41
41
  * @csspart empty - Shown when there are no entries to show.
42
42
  * @csspart header - The bar above a page, holding the way back and its name.
43
+ * @csspart live - The empty and loading lines again, in a region a screen reader hears when they change. Visually hidden in the styled tier.
43
44
  * @csspart loading - Shown while the entries are still on their way.
44
45
  * @csspart page - The region the rows are in.
45
46
  * @csspart title - The name of the page showing.
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, DrilldownController, HelixElement, hostAria, inlineStep, KeyboardNavigationController, MixinCollection, } from '@fusion.dev/helix-core';
7
+ import { customElement, DrilldownController, HelixElement, hostAria, inlineStep, KeyboardNavigationController, keyClaimed, MixinCollection, } from '@fusion.dev/helix-core';
8
8
  import { caretLeft } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -48,6 +48,9 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
48
48
  }
49
49
  };
50
50
  this.handleKeydown = (event) => {
51
+ if (event.defaultPrevented || (event.key !== 'Escape' && keyClaimed(event))) {
52
+ return;
53
+ }
51
54
  const items = this.items;
52
55
  const current = items.findIndex((item) => item.matches(':focus-within'));
53
56
  const along = inlineStep(this, event.key);
@@ -151,7 +154,7 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
151
154
  <div aria-busy=${this.loading ? 'true' : nothing} part="page">
152
155
  <slot @slotchange=${(event) => this.countRows(event)}></slot>
153
156
  ${this.renderStatus(this.hasRows)}
154
- </div>`;
157
+ </div>${this.renderLive(this.hasRows)}`;
155
158
  }
156
159
  };
157
160
  __decorate([
@@ -4,6 +4,21 @@ registerElementEvents({
4
4
  'helix-headless-announcement': ['helix-dismiss'],
5
5
  'helix-headless-app-header': ['helix-scrolled'],
6
6
  'helix-headless-avatar': ['error', 'load'],
7
+ 'helix-headless-canvas': [
8
+ 'helix-before-move',
9
+ 'helix-before-reconnect',
10
+ 'helix-before-relabel',
11
+ 'helix-canvas-edit',
12
+ 'helix-canvas-pan',
13
+ 'helix-canvas-view',
14
+ 'helix-canvas-zoom',
15
+ 'helix-connect',
16
+ 'helix-move',
17
+ 'helix-reconnect',
18
+ 'helix-relabel',
19
+ 'helix-remove',
20
+ 'helix-select',
21
+ ],
7
22
  'helix-headless-carousel': ['helix-select'],
8
23
  'helix-headless-chat': ['helix-before-pin', 'helix-history', 'helix-send'],
9
24
  'helix-headless-chat-composer': ['helix-send'],
@@ -1,5 +1,5 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
1
+ import { ArrivalController, HelixElement } from '@fusion.dev/helix-core';
2
+ import { type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary Says that a region has nothing in it, and what to do about it.
5
5
  *
@@ -8,10 +8,13 @@ import { type PropertyValues, type TemplateResult } from 'lit';
8
8
  * to load, so this exists to tell the difference: what would be here, and the
9
9
  * one action that would put something here.
10
10
  *
11
- * `role="status"` on a polite live region, because an empty view usually
12
- * arrives in place of content that was being fetched. Announcing it is the only
13
- * way a screen-reader user learns the search returned nothing, rather than
14
- * being left waiting.
11
+ * Announced, because an empty view usually arrives in place of content that was
12
+ * being fetched: saying so is the only way a screen-reader user learns the
13
+ * search returned nothing, rather than being left waiting. **From a region of
14
+ * its own that is there before its words are**, filled a frame after the view
15
+ * appears. The host was the region, and a page inserts an empty state with its
16
+ * message already in it, which is a region arriving with its words: read
17
+ * inconsistently at best.
15
18
  *
16
19
  * Every region is a bare slot. The reference ships an `empty-state-heading` and
17
20
  * an `empty-state-actions` whose only job is to assign themselves a slot on
@@ -27,6 +30,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
27
30
  * @slot actions - The way out.
28
31
  * @slot heading - What is missing.
29
32
  * @slot icon - A mark above the heading.
33
+ *
34
+ * @csspart live - The heading and the message again, in a region a screen reader hears when the view appears. Visually hidden in the styled tier.
30
35
  */
31
36
  export declare class HelixHeadlessEmpty extends HelixElement {
32
37
  /**
@@ -37,7 +42,7 @@ export declare class HelixHeadlessEmpty extends HelixElement {
37
42
  * has nothing to announce because nothing changed.
38
43
  */
39
44
  noAnnounce: boolean;
40
- protected willUpdate(changed: PropertyValues): void;
45
+ protected readonly arrival: ArrivalController;
41
46
  protected render(): TemplateResult;
42
47
  }
43
48
  declare global {
@@ -4,24 +4,27 @@ 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';
8
- import { html } from 'lit';
7
+ import { ArrivalController, customElement, HelixElement, slottedWords, } from '@fusion.dev/helix-core';
8
+ import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.noAnnounce = false;
14
- }
15
- willUpdate(changed) {
16
- super.willUpdate(changed);
17
- hostAria(this.internals, { role: this.noAnnounce ? '' : 'status' });
18
- this.internals.ariaLive = this.noAnnounce ? null : 'polite';
14
+ this.arrival = new ArrivalController(this, {
15
+ arriving: () => !this.serverRendered && document.readyState === 'complete',
16
+ text: () => slottedWords(this, ['heading', '']),
17
+ });
19
18
  }
20
19
  render() {
20
+ const say = () => this.arrival.say();
21
21
  return html `<slot name="icon"></slot>
22
- <slot name="heading"></slot>
23
- <slot></slot>
24
- <slot name="actions"></slot>`;
22
+ <slot name="heading" @slotchange=${say}></slot>
23
+ <slot @slotchange=${say}></slot>
24
+ <slot name="actions"></slot>
25
+ ${this.noAnnounce
26
+ ? nothing
27
+ : html `<span part="live" role="status">${this.arrival.said}</span>`}`;
25
28
  }
26
29
  };
27
30
  __decorate([
@@ -75,7 +75,11 @@ export declare class HelixHeadlessFileInput extends HelixHeadlessFileInput_base
75
75
  */
76
76
  multiple: boolean;
77
77
  /**
78
- * What the control removing one file is called, per file.
78
+ * What the control removing one file is called, with `{name}` filled in.
79
+ *
80
+ * **The name goes in**, so a row of them is heard as which one each
81
+ * removes rather than as "Remove" said once per file. A label with no
82
+ * `{name}` has it added after it.
79
83
  */
80
84
  removeLabel: string;
81
85
  /**
@@ -4,12 +4,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, drawIcon, HelixBeforeChangeEvent, HelixFormElement, MixinField, requestChange, restoreValue, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, drawIcon, fillTemplate, HelixBeforeChangeEvent, HelixFormElement, MixinField, requestChange, restoreValue, } from '@fusion.dev/helix-core';
8
8
  import { trash } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { html } from 'lit/static-html.js';
12
12
  import '../../tag/index.js';
13
+ const named = (label, name) => label.includes('{name}') ? fillTemplate(label, { name }) : `${label} ${name}`;
13
14
  const size = (bytes, locale) => {
14
15
  const units = ['bytes', 'KB', 'MB', 'GB', 'TB'];
15
16
  let value = bytes;
@@ -41,7 +42,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
41
42
  this.accept = '';
42
43
  this.locale = '';
43
44
  this.multiple = false;
44
- this.removeLabel = 'Remove';
45
+ this.removeLabel = 'Remove {name}';
45
46
  this.triggerLabel = 'Choose files';
46
47
  this.dropping = false;
47
48
  this.committed = [];
@@ -143,7 +144,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
143
144
  if (!control) {
144
145
  return;
145
146
  }
146
- this.internals.setValidity(control.validity, control.validationMessage, control);
147
+ this.copyValidity(control);
147
148
  this.refreshFeedback();
148
149
  }
149
150
  firstUpdated(changed) {
@@ -155,6 +156,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
155
156
  this.control?.focus(options);
156
157
  }
157
158
  formResetCallback() {
159
+ super.formResetCallback();
158
160
  if (this.control) {
159
161
  restoreValue(this.control, '');
160
162
  }
@@ -212,7 +214,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
212
214
  <button
213
215
  part="remove"
214
216
  type="button"
215
- aria-label=${`${this.removeLabel} ${file.name}`}
217
+ aria-label=${named(this.removeLabel, file.name)}
216
218
  data-at=${at}
217
219
  ?disabled=${this.isDisabled}
218
220
  @click=${this.handleRemove}
@@ -72,6 +72,7 @@ import '../../menu/index.js';
72
72
  * @csspart menu-back-mark - The glyph on the back control.
73
73
  * @csspart menu-empty - Shown when there are no fields to offer.
74
74
  * @csspart menu-header - The bar above a field's page, holding the way back and its name.
75
+ * @csspart menu-live - The inner menu's `live`, the region a screen reader hears its empty and loading lines from.
75
76
  * @csspart menu-loading - Shown while the fields are still on their way.
76
77
  * @csspart menu-surface - The floating panel the menu is drawn on.
77
78
  * @csspart menu-title - The name of the field whose page is showing.
@@ -119,7 +119,7 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
119
119
  return html `<${menu}
120
120
  part="menu"
121
121
  exportparts="back: menu-back, back-mark: menu-back-mark, empty: menu-empty, header: menu-header,
122
- loading: menu-loading, surface: menu-surface, title: menu-title"
122
+ live: menu-live, loading: menu-loading, surface: menu-surface, title: menu-title"
123
123
  label=${this.label}
124
124
  @helix-select=${this.handleSelect}
125
125
  >
@@ -304,6 +304,11 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
304
304
  this.releaseCaret?.();
305
305
  this.releaseCaret = undefined;
306
306
  this.internals.setFormValue(this.value);
307
+ const control = this.control;
308
+ if (control) {
309
+ this.copyValidity(control);
310
+ this.refreshFeedback();
311
+ }
307
312
  this.markActive();
308
313
  this.settleOpen();
309
314
  }
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export * from './breadcrumbs/index.js';
21
21
  export * from './button/index.js';
22
22
  export * from './button-group/index.js';
23
23
  export * from './callout/index.js';
24
+ export * from './canvas/index.js';
24
25
  export * from './card/index.js';
25
26
  export * from './carousel/index.js';
26
27
  export * from './chat/index.js';
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export * from './breadcrumbs/index.js';
12
12
  export * from './button/index.js';
13
13
  export * from './button-group/index.js';
14
14
  export * from './callout/index.js';
15
+ export * from './canvas/index.js';
15
16
  export * from './card/index.js';
16
17
  export * from './carousel/index.js';
17
18
  export * from './chat/index.js';
@@ -94,7 +94,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
94
94
  if (!control) {
95
95
  return;
96
96
  }
97
- this.internals.setValidity(control.validity, control.validationMessage, control);
97
+ this.copyValidity(control);
98
98
  this.refreshFeedback();
99
99
  }
100
100
  focus(options) {
@@ -90,7 +90,13 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
90
90
  private readonly fullscreen;
91
91
  private readonly idle;
92
92
  protected readonly commands: CommandController;
93
- /** The items, read from the host so the server has them too. */
93
+ /**
94
+ * The items, read from the host.
95
+ *
96
+ * **`knownChildren` and not `children`**, which is undefined on the server:
97
+ * spreading it threw the first time a lightbox was rendered there open,
98
+ * which is the first time anything asked it for its items on a server.
99
+ */
94
100
  private get items();
95
101
  /** The native dialog, or nothing where there is no DOM to hold one. */
96
102
  private get dialog();
@@ -139,6 +145,8 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
139
145
  private readonly handleKeydown;
140
146
  /** Home and End, which are an index rather than a step. */
141
147
  private toEnd;
148
+ /** The item on screen: the one `active` names, or the first where it names none. */
149
+ private get shown();
142
150
  /**
143
151
  * A press on the backdrop, which lands on the dialog element itself.
144
152
  *
@@ -4,11 +4,10 @@ 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, FullscreenController, HelixElement, hostAria, IdleController, inlineStep, MixinControllerActive, MixinControllerOpen, resolveActiveItem, } from '@fusion.dev/helix-core';
7
+ import { CommandController, customElement, FullscreenController, fillTemplate, HelixElement, hostAria, IdleController, inlineStep, keyClaimed, MixinControllerActive, MixinControllerOpen, resolveActiveItem, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { HelixHeadlessLightboxItem } from '../item/lightbox-item.js';
11
- const fill = (template, index, count) => template.replaceAll('{index}', String(index)).replaceAll('{count}', String(count));
12
11
  let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerActive(MixinControllerOpen(HelixElement)) {
13
12
  constructor() {
14
13
  super(...arguments);
@@ -46,6 +45,9 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
46
45
  this.relayNativeEvent(event);
47
46
  };
48
47
  this.handleKeydown = (event) => {
48
+ if (keyClaimed(event) || event.altKey || event.ctrlKey || event.metaKey) {
49
+ return;
50
+ }
49
51
  const along = inlineStep(this, event.key);
50
52
  if (along === 1 || event.key === 'ArrowDown') {
51
53
  event.preventDefault();
@@ -80,7 +82,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
80
82
  };
81
83
  }
82
84
  get items() {
83
- return [...this.children].filter((child) => child instanceof HelixHeadlessLightboxItem);
85
+ return this.knownChildren.filter((child) => child instanceof HelixHeadlessLightboxItem);
84
86
  }
85
87
  get dialog() {
86
88
  return this.renderRoot?.querySelector('dialog') ?? null;
@@ -105,23 +107,27 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
105
107
  if (items.length === 0) {
106
108
  return;
107
109
  }
108
- const current = items.findIndex((item) => item.value === this.active);
110
+ const shown = this.shown;
111
+ const current = shown ? items.indexOf(shown) : -1;
109
112
  const from = current === -1 ? 0 : current;
110
113
  const last = items.length - 1;
111
114
  const next = this.loop
112
115
  ? (from + by + items.length) % items.length
113
116
  : Math.min(Math.max(from + by, 0), last);
114
117
  const target = items[next];
115
- if (target && target.value !== this.active) {
118
+ if (target && target !== shown) {
116
119
  this.requestActive(target.value);
117
120
  }
118
121
  }
119
122
  toEnd(at) {
120
123
  const target = this.items[at];
121
- if (target && target.value !== this.active) {
124
+ if (target && target !== this.shown) {
122
125
  this.requestActive(target.value);
123
126
  }
124
127
  }
128
+ get shown() {
129
+ return resolveActiveItem(this.active ?? '', this.items);
130
+ }
125
131
  willUpdate(changed) {
126
132
  super.willUpdate(changed);
127
133
  hostAria(this.internals, { label: this.label });
@@ -138,6 +144,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
138
144
  }
139
145
  if (this.open && !dialog.open) {
140
146
  dialog.showModal();
147
+ dialog.focus({ preventScroll: true });
141
148
  this.idle.stir();
142
149
  }
143
150
  else if (!this.open && dialog.open) {
@@ -162,12 +169,15 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
162
169
  const items = this.items;
163
170
  const current = resolveActiveItem(this.active, items);
164
171
  const at = current ? items.indexOf(current) : -1;
165
- this.announcement = at === -1 ? '' : fill(this.statusLabel, at + 1, items.length);
172
+ this.announcement =
173
+ at === -1 ? '' : fillTemplate(this.statusLabel, { count: items.length, index: at + 1 });
166
174
  }
167
175
  render() {
168
176
  return html `<dialog
169
177
  part="dialog"
170
178
  aria-label=${this.label || 'Lightbox'}
179
+ autofocus
180
+ tabindex="-1"
171
181
  @cancel=${this.handleCancel}
172
182
  @click=${this.handleBackdrop}
173
183
  @close=${this.handleClose}