@fusion.dev/helix-headless 0.154.0 → 0.156.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 (104) hide show
  1. package/custom-elements.json +3213 -179
  2. package/dist/accordion/item/accordion-item.d.ts +15 -0
  3. package/dist/accordion/item/accordion-item.js +12 -1
  4. package/dist/accordion/root/accordion-root.d.ts +2 -1
  5. package/dist/accordion/root/accordion-root.js +1 -0
  6. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +36 -3
  7. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +42 -2
  8. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +15 -5
  9. package/dist/breadcrumbs/root/breadcrumbs-root.js +29 -4
  10. package/dist/button-group/root/button-group-root.d.ts +2 -0
  11. package/dist/button-group/root/button-group-root.js +1 -0
  12. package/dist/canvas/node/canvas-node.js +1 -1
  13. package/dist/canvas/root/canvas-root.d.ts +17 -2
  14. package/dist/canvas/root/canvas-root.js +23 -6
  15. package/dist/chat/composer/chat-composer.js +1 -1
  16. package/dist/chat/root/chat-root.d.ts +14 -1
  17. package/dist/chat/root/chat-root.js +14 -4
  18. package/dist/color-input/root/color-input-root.d.ts +9 -3
  19. package/dist/color-input/root/color-input-root.js +3 -1
  20. package/dist/color-picker/channels.d.ts +5 -3
  21. package/dist/color-picker/channels.js +79 -21
  22. package/dist/color-picker/root/color-picker-root.d.ts +58 -5
  23. package/dist/color-picker/root/color-picker-root.js +75 -13
  24. package/dist/combobox/root/combobox-root.d.ts +10 -0
  25. package/dist/combobox/root/combobox-root.js +18 -3
  26. package/dist/command/root/command-root.d.ts +116 -1
  27. package/dist/command/root/command-root.js +124 -15
  28. package/dist/data-table/root/data-table-root.d.ts +25 -0
  29. package/dist/data-table/root/data-table-root.js +13 -3
  30. package/dist/date-input/root/date-input-root.d.ts +33 -1
  31. package/dist/date-input/root/date-input-root.js +9 -2
  32. package/dist/date-picker/root/date-picker-root.d.ts +43 -13
  33. package/dist/date-picker/root/date-picker-root.js +54 -31
  34. package/dist/element-events.generated.js +3 -1
  35. package/dist/graph/root/graph-root.d.ts +13 -0
  36. package/dist/graph/root/graph-root.js +16 -0
  37. package/dist/list/root/list-root.d.ts +74 -1
  38. package/dist/list/root/list-root.js +92 -4
  39. package/dist/list/selection.js +3 -3
  40. package/dist/map/root/map-root.d.ts +6 -0
  41. package/dist/map/root/map-root.js +8 -1
  42. package/dist/navigation/root/navigation-root.d.ts +26 -0
  43. package/dist/navigation/root/navigation-root.js +58 -1
  44. package/dist/number-input/root/number-input-root.d.ts +63 -6
  45. package/dist/number-input/root/number-input-root.js +96 -11
  46. package/dist/object-inspector/root/object-inspector-root.d.ts +36 -1
  47. package/dist/object-inspector/root/object-inspector-root.js +26 -2
  48. package/dist/option/root/option-root.d.ts +32 -0
  49. package/dist/option/root/option-root.js +44 -5
  50. package/dist/outline/root/outline-root.d.ts +6 -2
  51. package/dist/outline/root/outline-root.js +7 -3
  52. package/dist/select/root/select-root.d.ts +29 -3
  53. package/dist/select/root/select-root.js +34 -9
  54. package/dist/slider/root/slider-root.d.ts +71 -9
  55. package/dist/slider/root/slider-root.js +129 -42
  56. package/dist/speed-dial/item/speed-dial-item.d.ts +29 -1
  57. package/dist/speed-dial/item/speed-dial-item.js +48 -7
  58. package/dist/speed-dial/root/speed-dial-root.d.ts +32 -6
  59. package/dist/speed-dial/root/speed-dial-root.js +50 -5
  60. package/dist/splitter/root/splitter-root.d.ts +22 -0
  61. package/dist/splitter/root/splitter-root.js +36 -7
  62. package/dist/steps/reach.d.ts +5 -1
  63. package/dist/steps/reach.js +2 -2
  64. package/dist/steps/root/steps-root.d.ts +37 -2
  65. package/dist/steps/root/steps-root.js +49 -7
  66. package/dist/steps/step/step.d.ts +6 -0
  67. package/dist/steps/step/step.js +4 -0
  68. package/dist/table/root/table-root.d.ts +20 -0
  69. package/dist/table/root/table-root.js +13 -3
  70. package/dist/tabs/root/tabs-root.d.ts +37 -0
  71. package/dist/tabs/root/tabs-root.js +92 -2
  72. package/dist/tabs/tab/tab.d.ts +38 -2
  73. package/dist/tabs/tab/tab.js +45 -4
  74. package/dist/tag/group/tag-group.d.ts +29 -3
  75. package/dist/tag/group/tag-group.js +42 -11
  76. package/dist/time-input/root/time-input-root.d.ts +32 -1
  77. package/dist/time-input/root/time-input-root.js +9 -2
  78. package/dist/time-picker/root/time-picker-root.d.ts +63 -1
  79. package/dist/time-picker/root/time-picker-root.js +58 -7
  80. package/dist/timeline/item/timeline-item.d.ts +25 -0
  81. package/dist/timeline/item/timeline-item.js +27 -2
  82. package/dist/toggle/root/toggle-root.d.ts +2 -0
  83. package/dist/toggle/root/toggle-root.js +1 -1
  84. package/dist/tree/item/tree-item.d.ts +10 -0
  85. package/dist/tree/item/tree-item.js +12 -0
  86. package/dist/tree/root/tree-root.d.ts +40 -1
  87. package/dist/tree/root/tree-root.js +120 -4
  88. package/dist/tree/selection.d.ts +6 -0
  89. package/dist/tree/selection.js +11 -0
  90. package/dist/video/root/keys.d.ts +6 -2
  91. package/dist/video/root/keys.js +18 -1
  92. package/dist/video/root/video-root.d.ts +60 -1
  93. package/dist/video/root/video-root.js +70 -2
  94. package/dist/video-playlist/item/video-playlist-item.d.ts +15 -1
  95. package/dist/video-playlist/item/video-playlist-item.js +7 -0
  96. package/dist/video-playlist/root/video-playlist-root.d.ts +43 -4
  97. package/dist/video-playlist/root/video-playlist-root.js +69 -2
  98. package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
  99. package/dist/virtual-list/root/virtual-list-root.js +3 -0
  100. package/lint.json +1 -1
  101. package/package.json +5 -5
  102. package/types/jsx.d.ts +3885 -383
  103. package/types/vue.d.ts +1045 -107
  104. package/types/words.d.ts +146 -1
@@ -8,18 +8,31 @@ import { customElement, HelixElement, HelixSelectEvent, MixinControllerOpen, mat
8
8
  import { nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { html } from 'lit/static-html.js';
11
+ import { optionsAmong } from '../../option/root/option-root.js';
11
12
  import '../../dialog/index.js';
12
13
  import '../../listbox/index.js';
14
+ const says = (element, state) => {
15
+ try {
16
+ return element?.matches(`:state(${state})`) ?? false;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ };
13
22
  let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpen(HelixElement) {
14
23
  constructor() {
15
24
  super(...arguments);
16
25
  this.emptyText = 'No commands';
17
26
  this.filterDelay = 0;
18
27
  this.hotkey = 'mod+k';
28
+ this.inline = false;
29
+ this.items = undefined;
19
30
  this.label = 'Commands';
20
31
  this.loadError = '';
21
32
  this.loading = false;
22
33
  this.loadingText = 'Loading';
34
+ this.page = '';
35
+ this.trail = [];
23
36
  this.placeholder = 'Search commands';
24
37
  this.resultsLabel = '{count, plural, one {# command} other {# commands}}';
25
38
  this.footed = false;
@@ -28,6 +41,10 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
28
41
  return;
29
42
  }
30
43
  event.preventDefault();
44
+ if (this.inline) {
45
+ this.searchField?.focus();
46
+ return;
47
+ }
31
48
  this.requestOpen(!this.open);
32
49
  };
33
50
  this.handleChange = (event) => {
@@ -37,10 +54,26 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
37
54
  if (!this.dispatchEvent(new HelixSelectEvent({ value }))) {
38
55
  return;
39
56
  }
57
+ const opens = (this.listbox?.options ?? []).find((option) => option.value === value)?.opens;
58
+ if (opens) {
59
+ this.goTo([...this.trail, this.page], opens);
60
+ return;
61
+ }
40
62
  this.requestOpen(false);
41
63
  };
64
+ this.handleBack = (event) => {
65
+ if (event.key !== 'Backspace' ||
66
+ this.page === '' ||
67
+ event.composedPath()[0] !== this.searchField ||
68
+ this.searchField?.value !== '') {
69
+ return;
70
+ }
71
+ event.preventDefault();
72
+ const trail = [...this.trail];
73
+ this.goTo(trail, trail.pop() ?? '');
74
+ };
42
75
  this.handleAlternate = (event) => {
43
- if (!(event.metaKey || event.ctrlKey) || !this.open) {
76
+ if (!(event.metaKey || event.ctrlKey) || !(this.open || this.inline)) {
44
77
  return;
45
78
  }
46
79
  if (event instanceof KeyboardEvent && event.key !== 'Enter') {
@@ -74,11 +107,32 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
74
107
  }
75
108
  this.requestOpen(false);
76
109
  };
110
+ this.handleFilter = () => {
111
+ void Promise.resolve()
112
+ .then(() => this.listbox?.updateComplete)
113
+ .then(() => this.markStatus());
114
+ };
77
115
  }
78
116
  static { this.words = 'helix-command'; }
79
117
  get listbox() {
80
118
  return this.partNamed('listbox');
81
119
  }
120
+ get searchField() {
121
+ return (this.listbox?.renderRoot?.querySelector('[part="search-field"]') ?? null);
122
+ }
123
+ goTo(trail, page) {
124
+ this.trail = trail;
125
+ this.page = page;
126
+ this.listbox?.requestFilter('');
127
+ void this.updateComplete.then(() => this.searchField?.focus());
128
+ }
129
+ get listName() {
130
+ if (this.page === '') {
131
+ return this.label;
132
+ }
133
+ const opener = optionsAmong(this.knownChildren).find((option) => option.opens === this.page);
134
+ return opener?.text || this.label;
135
+ }
82
136
  get activeOption() {
83
137
  return (this.listbox?.options.find((option) => option.active && !option.hidden) ??
84
138
  this.querySelector('[data-active]'));
@@ -87,6 +141,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
87
141
  super.connectedCallback();
88
142
  this.addEventListener('helix-select', this.swallowOptionSelect);
89
143
  this.addEventListener('keydown', this.handleAlternate, { capture: true });
144
+ this.addEventListener('keydown', this.handleBack);
90
145
  this.addEventListener('click', this.handleAlternate, { capture: true });
91
146
  document.addEventListener('keydown', this.handleHotkey);
92
147
  }
@@ -94,11 +149,16 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
94
149
  super.disconnectedCallback();
95
150
  this.removeEventListener('helix-select', this.swallowOptionSelect);
96
151
  this.removeEventListener('keydown', this.handleAlternate, { capture: true });
152
+ this.removeEventListener('keydown', this.handleBack);
97
153
  this.removeEventListener('click', this.handleAlternate, { capture: true });
98
154
  document.removeEventListener('keydown', this.handleHotkey);
99
155
  }
100
156
  willUpdate(changed) {
101
157
  super.willUpdate(changed);
158
+ if (changed.get('open') === true && !this.open) {
159
+ this.trail = [];
160
+ this.page = '';
161
+ }
102
162
  if (this.childrenKnown) {
103
163
  this.footed = this.knownChildren.some((child) => child.slot === 'footer');
104
164
  }
@@ -107,15 +167,32 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
107
167
  super.firstUpdated(changed);
108
168
  this.finishHydration();
109
169
  }
170
+ markStatus() {
171
+ const list = this.listbox;
172
+ const options = list?.options ?? [];
173
+ const shown = options.some((option) => !option.hidden);
174
+ const settled = list !== null && !this.loading;
175
+ this.toggleState('loading', this.loading);
176
+ this.toggleState('error', Boolean(this.loadError));
177
+ if (this.items !== undefined) {
178
+ this.toggleState('empty', settled && says(list, 'empty'));
179
+ this.toggleState('no-results', settled && says(list, 'no-results'));
180
+ return;
181
+ }
182
+ this.toggleState('empty', settled && !shown);
183
+ this.toggleState('no-results', settled && list.filter !== '' && options.length > 0 && !shown);
184
+ }
185
+ fills(name) {
186
+ return this.querySelector?.(`:scope > [slot="${name}"]`) != null;
187
+ }
110
188
  updated(changed) {
111
189
  super.updated?.(changed);
190
+ void (this.listbox?.updateComplete ?? Promise.resolve()).then(() => this.markStatus());
112
191
  if (!changed.has('open')) {
113
192
  return;
114
193
  }
115
194
  if (this.open) {
116
- this.partNamed('listbox')
117
- ?.renderRoot?.querySelector('[part="search-field"]')
118
- ?.focus();
195
+ this.searchField?.focus();
119
196
  }
120
197
  else {
121
198
  this.listbox?.requestFilter('');
@@ -133,13 +210,25 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
133
210
  }
134
211
  }}
135
212
  >
136
- <${this.tag('listbox')}
213
+ ${this.renderListbox()}
214
+ <!-- Handed on only while something fills it, as the list's slots are,
215
+ so an empty header draws nothing in the dialog. -->
216
+ <slot
217
+ name="header"
218
+ slot=${this.fills('header') ? 'header' : nothing}
219
+ @slotchange=${() => this.requestUpdate()}
220
+ ></slot>
221
+ ${this.renderFooter('footer')}
222
+ </${this.tag('dialog')}>`;
223
+ }
224
+ renderListbox() {
225
+ return html `<${this.tag('listbox')}
137
226
  part="listbox"
138
227
  exportparts="empty: listbox-empty, error: listbox-error, feedback: listbox-feedback,
139
228
  filter-icon: listbox-filter-icon, filter-mark: listbox-filter-mark, hint: listbox-hint,
140
229
  items: listbox-items, label: listbox-label, live: listbox-live, loading: listbox-loading,
141
230
  search: listbox-search, search-field: listbox-search-field"
142
- aria-label=${this.label}
231
+ aria-label=${this.listName}
143
232
  empty-text=${this.emptyText}
144
233
  filter-delay=${this.filterDelay}
145
234
  filter-label=${this.placeholder}
@@ -149,12 +238,14 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
149
238
  result-label=${this.resultLabel ?? ''}
150
239
  results-label=${this.resultsLabel}
151
240
  ?loading=${this.loading}
241
+ .items=${this.items}
152
242
  filterable
153
243
  @helix-before-change=${this.handleChange}
244
+ @helix-filter=${this.handleFilter}
154
245
  @change=${this.stopInner}
155
246
  @input=${this.stopInner}
156
247
  >
157
- <slot></slot>
248
+ ${this.page === '' ? html `<slot></slot>` : html `<slot name=${`page:${this.page}`}></slot>`}
158
249
  <!-- Handed on with the text as the fallback, for the reason Combobox
159
250
  gives: the list's own fallback never shows behind a passed slot. -->
160
251
  <slot name="empty" slot="empty">${this.emptyText}</slot>
@@ -164,21 +255,30 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
164
255
  list's own fallback, which is no-results-text or empty-text. -->
165
256
  <slot
166
257
  name="no-results"
167
- slot=${this.querySelector?.(':scope > [slot="no-results"]') ? 'no-results' : nothing}
258
+ slot=${this.fills('no-results') ? 'no-results' : nothing}
168
259
  @slotchange=${() => this.requestUpdate()}
169
260
  ></slot>
170
- </${this.tag('listbox')}>
171
- <div ?hidden=${!this.footed} part="footer" slot="footer">
172
261
  <slot
173
- name="footer"
174
- @slotchange=${(event) => {
262
+ name="filter-icon"
263
+ slot=${this.fills('filter-icon') ? 'filter-icon' : nothing}
264
+ @slotchange=${() => this.requestUpdate()}
265
+ ></slot>
266
+ </${this.tag('listbox')}>`;
267
+ }
268
+ renderFooter(slot) {
269
+ return html `<div ?hidden=${!this.footed} part="footer" slot=${slot}>
270
+ <slot
271
+ name="footer"
272
+ @slotchange=${(event) => {
175
273
  this.footed = event.target.assignedNodes().length > 0;
176
274
  }}
177
- ></slot>
178
- </div>
179
- </${this.tag('dialog')}>`;
275
+ ></slot>
276
+ </div>`;
180
277
  }
181
278
  render() {
279
+ if (this.inline) {
280
+ return html `<slot name="header"></slot>${this.renderListbox()}${this.renderFooter(nothing)}`;
281
+ }
182
282
  return html `${this.renderDialog()}`;
183
283
  }
184
284
  };
@@ -192,6 +292,12 @@ __decorate([
192
292
  __decorate([
193
293
  property()
194
294
  ], HelixHeadlessCommand.prototype, "hotkey", void 0);
295
+ __decorate([
296
+ property({ reflect: true, type: Boolean })
297
+ ], HelixHeadlessCommand.prototype, "inline", void 0);
298
+ __decorate([
299
+ property({ attribute: false })
300
+ ], HelixHeadlessCommand.prototype, "items", void 0);
195
301
  __decorate([
196
302
  words,
197
303
  property()
@@ -209,6 +315,9 @@ __decorate([
209
315
  __decorate([
210
316
  property({ attribute: 'no-results-text' })
211
317
  ], HelixHeadlessCommand.prototype, "noResultsText", void 0);
318
+ __decorate([
319
+ property()
320
+ ], HelixHeadlessCommand.prototype, "page", void 0);
212
321
  __decorate([
213
322
  words,
214
323
  property()
@@ -89,6 +89,11 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
89
89
  * scrolls within one screen of the end, once for each number of rows. Add the
90
90
  * next rows to `rows`, and set `loading` while they are on their way.
91
91
  *
92
+ * When a fetch fails, set `error-text`, such as "The files did not load", and
93
+ * put a button that tries again in the `error` slot. The line shows after the
94
+ * rows in place of the empty and loading lines, the live region says it, and
95
+ * the table takes the `error` state. Clear it once you try again.
96
+ *
92
97
  * The controls inside the cells leave the tab order, so Tab moves past the
93
98
  * whole grid. A windowed table keeps the row holding focus drawn while the
94
99
  * rest scroll away.
@@ -129,6 +134,7 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
129
134
  *
130
135
  * @slot - The columns, as `TableColumn` elements.
131
136
  * @slot empty - Content shown when there are no rows to show and none are loading, such as a picture and a link. Falls back to `empty-text`, which the live region still says.
137
+ * @slot error - Content shown while `error-text` is set, such as a button that tries again. Falls back to `error-text`, which the live region still says.
132
138
  * @slot footer - Content below the grid. It replaces the built-in pagination.
133
139
  * @slot loading - Content shown while the rows load, such as a spinner. Falls back to `loading-text`, which the live region still says.
134
140
  * @slot toolbar - Content above the grid, such as a search field or actions.
@@ -165,6 +171,7 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
165
171
  * @csspart checkbox-mark-indeterminate - Added to `checkbox-mark` while only some rows are selected.
166
172
  * @csspart drag-preview - A copy of the row a pointer drags by its grip, which follows the pointer in the top layer until the row is let go, with `reorderable-rows`. A column's grip moves the column itself. Hidden from assistive technology.
167
173
  * @csspart empty - Shown when there are no rows, because none arrived or the filter matches none. While rows are loading, the `loading` part is shown in its place.
174
+ * @csspart error - Shown after the rows while `error-text` is set, in place of the empty and loading lines.
168
175
  * @csspart footer - The region below the grid.
169
176
  * @csspart grip-head - The heading over the row grips, with `reorderable-rows`.
170
177
  * @csspart head - The row that holds the column headings. It is `display: contents`, so it takes no space.
@@ -205,6 +212,9 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
205
212
  * @cssproperty --data-table-cell-align - The alignment of the cell's column: `start`, `center`, or `end`. Unset when the column sets none. The component writes it on each cell it draws, and the stylesheet reads it. `Table` sets `--table-cell-align` for the same purpose.
206
213
  * @cssproperty --data-table-level - How deep a row is in a tree grid, from 1. The component writes it on each row header cell, and the stylesheet indents by it.
207
214
  * @cssproperty --data-table-template - The column track list, worked out from the columns. The component writes it inline on the element that reads it, so setting it has no effect. Set a column's `width` to change it.
215
+ *
216
+ * @cssstate empty - While there are no rows to show and none are loading.
217
+ * @cssstate error - While `error-text` is set.
208
218
  */
209
219
  export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base {
210
220
  /**
@@ -286,6 +296,16 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
286
296
  descendingLabel: string;
287
297
  /** What screen readers hear when a row is dropped, with `reorderable-rows`. */
288
298
  droppedLabel: string;
299
+ /**
300
+ * The text shown and announced when the rows could not load, such as "The
301
+ * files did not load". Set it when a fetch fails, and clear it once you try
302
+ * again. Empty, the default, shows nothing.
303
+ *
304
+ * While it is set, a line after the rows shows it, or your own content in the
305
+ * `error` slot, in place of the empty and loading lines, and the live region
306
+ * says it.
307
+ */
308
+ errorText: string;
289
309
  /**
290
310
  * The estimated height of a row before it is drawn, in pixels.
291
311
  *
@@ -1119,6 +1139,11 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
1119
1139
  * after the last row and Enter or Space on it asks.
1120
1140
  */
1121
1141
  private renderMore;
1142
+ /**
1143
+ * The line shown while `error-text` is set: a row holding one cell, as the
1144
+ * empty and loading lines are, since a grid holds rows and nothing else.
1145
+ */
1146
+ private renderError;
1122
1147
  protected render(): TemplateResult;
1123
1148
  /**
1124
1149
  * Listens on the host, not on the slot the columns are assigned to.
@@ -42,6 +42,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
42
42
  this.defaultSort = '';
43
43
  this.descendingLabel = 'descending';
44
44
  this.droppedLabel = 'Dropped';
45
+ this.errorText = '';
45
46
  this.estimate = 32;
46
47
  this.gapLabel = 'Skip to page {page}';
47
48
  this.label = '';
@@ -913,6 +914,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
913
914
  ['toolbar', true],
914
915
  ['footer', true],
915
916
  ['empty', true],
917
+ ['error', true],
916
918
  ['loading', true],
917
919
  ]) {
918
920
  const slot = root.querySelector(`slot[name="${name}"]`);
@@ -980,6 +982,8 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
980
982
  this.internals.ariaColCount =
981
983
  this.counts.columns === undefined ? null : String(this.counts.columns);
982
984
  this.markEmpty();
985
+ this.toggleState('error', this.errorText !== '');
986
+ this.toggleState('empty', !this.loading && !this.hasRows);
983
987
  this.assign();
984
988
  this.announceSort();
985
989
  this.quietControls();
@@ -1173,10 +1177,13 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1173
1177
  >
1174
1178
  </div>`;
1175
1179
  }
1180
+ renderError() {
1181
+ return html `<p part="error" role="row"><span role="gridcell"><slot name="error">${this.errorText}</slot></span></p>`;
1182
+ }
1176
1183
  render() {
1177
1184
  const { available, matched, page } = this.view;
1178
1185
  this.drawnPage = page;
1179
- this.moreDrawn = this.loadMore && !this.loading && page.length > 0;
1186
+ this.moreDrawn = this.loadMore && !this.loading && this.errorText === '' && page.length > 0;
1180
1187
  const rows = this.windowOf(page);
1181
1188
  const kept = this.keptRow(page);
1182
1189
  const headStop = this.currentRow(page) === -1;
@@ -1206,9 +1213,9 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1206
1213
  : html `<slot name="columns" @slotchange=${this.handleSlotChange}></slot>`}
1207
1214
  </div>
1208
1215
  ${this.renderSpacer('before', kept)}${this.renderRows(rows, page)}${this.renderSpacer('after', kept)}
1209
- ${this.renderMore(page)}${this.renderStatus(filled, 'gridcell')}
1216
+ ${this.renderMore(page)}${this.errorText === '' ? this.renderStatus(filled, 'gridcell') : this.renderError()}
1210
1217
  </div>
1211
- ${this.renderLive(filled)}
1218
+ ${this.errorText === '' ? this.renderLive(filled) : html `<span part="live" role="status">${this.errorText}</span>`}
1212
1219
  <div part="footer">
1213
1220
  <slot name="footer"></slot>
1214
1221
  ${this.paginated
@@ -1547,6 +1554,9 @@ __decorate([
1547
1554
  words,
1548
1555
  property({ attribute: 'dropped-label' })
1549
1556
  ], HelixHeadlessDataTable.prototype, "droppedLabel", void 0);
1557
+ __decorate([
1558
+ property({ attribute: 'error-text' })
1559
+ ], HelixHeadlessDataTable.prototype, "errorText", void 0);
1550
1560
  __decorate([
1551
1561
  property({ type: Number })
1552
1562
  ], HelixHeadlessDataTable.prototype, "estimate", void 0);
@@ -27,12 +27,37 @@ declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fus
27
27
  * month, a day and a year in the order the locale writes them, each set with
28
28
  * digits or the arrow keys. Alt+Down opens the calendar from a segment.
29
29
  *
30
+ * Text that is not a date gives the form an empty value, as a native date
31
+ * field does, and `value` keeps the text so it can be corrected.
32
+ *
33
+ * **Keyboard.** The field is one tab stop, or each segment is with
34
+ * `segmented`, and the trigger is one more.
35
+ *
36
+ * | Key | What it does |
37
+ * |---|---|
38
+ * | Down | In the text field, opens the calendar with focus on the chosen day. |
39
+ * | Alt with Down | Opens the calendar, from the text field or a segment. |
40
+ * | Enter | In the text field, takes the typed date and submits the form, as a native field does. |
41
+ * | Escape | Closes the calendar and puts focus back in the field. |
42
+ * | Tab | In the open calendar, moves between its controls. Out of the field, closes it. |
43
+ * | Up, Down on a segment | Steps the segment by one, round from its last value to its first. |
44
+ * | Page Up, Page Down on a segment | Steps by a page: a week of days, two months, or five years. |
45
+ * | Home, End on a segment | Goes to the segment's first or last value. |
46
+ * | Digits on a segment | Fill it, and move to the next once it can take no more. |
47
+ * | Backspace, Delete on a segment | Empty the segment. |
48
+ * | Left, Right on a segment | Move to the previous or the next segment, along the line of text. |
49
+ *
50
+ * In the calendar, the keys are `DatePicker`'s: the arrows move by a day or a
51
+ * week, Page Up and Page Down by a month, with Shift by a year, and Home and
52
+ * End to the start and the end of the week.
53
+ *
30
54
  * @since 0.1.0
31
55
  * @status stable
32
56
  * @category forms
33
57
  * @aliases DatePicker, DateField, InputDate
34
58
  * @documentation https://fusion.dev/helix/components/date-input
35
59
  *
60
+ * @slot end - Content after the date, before the trigger, such as a clear button.
36
61
  * @slot feedback - The error message under the field.
37
62
  * @slot hint - The help text under the field.
38
63
  * @slot label - The label, for content richer than the `label` text.
@@ -40,6 +65,7 @@ declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fus
40
65
  * @slot panel-footer - Content in the panel under the calendar, such as a note or an action.
41
66
  * @slot panel-header - Content in the panel above the calendar, such as a heading or a shortcut.
42
67
  * @slot previous-icon - The icon on the calendar's previous month control.
68
+ * @slot start - Content before the date, such as an icon.
43
69
  * @slot trigger - The icon on the button that opens the calendar.
44
70
  *
45
71
  * @event {Event} change - Fired after the value changes.
@@ -72,6 +98,7 @@ declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fus
72
98
  * @csspart picker-day-outside - A day from a neighboring month. Applied with `picker-day`.
73
99
  * @csspart picker-day-selected - The chosen day, or either end of a chosen range. Applied with `picker-day`.
74
100
  * @csspart picker-day-today - Today. Applied with `picker-day`.
101
+ * @csspart picker-day-weekend - A day of the weekend where the calendar's language is spoken, beside `picker-day`.
75
102
  * @csspart picker-feedback - The calendar's own message, under it, while the calendar's `feedback` is set.
76
103
  * @csspart picker-grid - The grid of days.
77
104
  * @csspart picker-header - The month name and its controls.
@@ -383,7 +410,12 @@ export declare class HelixHeadlessDateInput extends HelixHeadlessDateInput_base
383
410
  /** Takes one date back out of the set. */
384
411
  private readonly removeDate;
385
412
  private readonly handleCalendarChange;
386
- /** Each date, where `mode` holds several, sent one entry each. */
413
+ /**
414
+ * Each date, where `mode` holds several, sent one entry each.
415
+ *
416
+ * One date that is text and no date, such as `August 5`, is sent as an empty
417
+ * entry, as a native date field sends a date it cannot read.
418
+ */
387
419
  protected get answers(): readonly string[] | undefined;
388
420
  /** What is wrong with the dates, which the text input cannot say. */
389
421
  protected validate(): string | readonly [keyof ValidityStateFlags, string];
@@ -175,7 +175,11 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
175
175
  return Math.max(0, dates(this.value).length - this.shownDates.length);
176
176
  }
177
177
  get answers() {
178
- return this.mode === undefined ? undefined : dates(this.value);
178
+ if (this.mode !== undefined) {
179
+ return dates(this.value);
180
+ }
181
+ const text = this.value.trim();
182
+ return text === '' || isDateValue(text) ? undefined : [''];
179
183
  }
180
184
  validate() {
181
185
  const held = dates(this.value);
@@ -218,7 +222,8 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
218
222
  exportparts="cell: picker-cell, cell-range-end: picker-cell-range-end,
219
223
  cell-range-middle: picker-cell-range-middle, cell-range-start: picker-cell-range-start,
220
224
  day: picker-day, day-disabled: picker-day-disabled, day-outside: picker-day-outside,
221
- day-selected: picker-day-selected, day-today: picker-day-today, feedback: picker-feedback, grid: picker-grid,
225
+ day-selected: picker-day-selected, day-today: picker-day-today, day-weekend: picker-day-weekend,
226
+ feedback: picker-feedback, grid: picker-grid,
222
227
  header: picker-header, heading: picker-heading, heading-button: picker-heading-button,
223
228
  month: picker-month, months: picker-months, next: picker-next,
224
229
  next-mark: picker-next-mark, period: picker-period, period-current: picker-period-current,
@@ -313,10 +318,12 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinSegmented
313
318
  render() {
314
319
  return html `${this.renderLabel()}
315
320
  <div part="field" @keydown=${this.handleKeydown}>
321
+ <slot name="start"></slot>
316
322
  ${this.mode === 'multiple'
317
323
  ? this.renderTokenRow(this.shownDates.map((date) => this.renderTag(date)), this.restCount)
318
324
  : nothing}
319
325
  ${this.segmentsShown ? this.renderSegments() : this.renderControl()}
326
+ <slot name="end"></slot>
320
327
  <button
321
328
  part="trigger"
322
329
  type="button"
@@ -1,4 +1,4 @@
1
- import { type CalendarView, type DateCalendar, HelixFormElement } from '@fusion.dev/helix-core';
1
+ import { type CalendarView, type DateCalendar, HelixFormElement, type MonthCalendar } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinPickerFeedbackInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & typeof HelixFormElement;
4
4
  /**
@@ -47,6 +47,8 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
47
47
  * @aliases Calendar, Date grid, Month view
48
48
  * @documentation https://fusion.dev/helix/components/date-picker
49
49
  *
50
+ * @slot footer - Content under the calendar, such as buttons for today or a common range.
51
+ * @slot header - Content above the calendar, such as a note on what the date is for.
50
52
  * @slot next-icon - The glyph on the control that moves forward a month, usually a caret.
51
53
  * @slot previous-icon - The glyph on the control that moves back a month, usually a caret.
52
54
  *
@@ -64,6 +66,7 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
64
66
  * @csspart day-outside - A day from a neighboring month, filling the grid out, beside `day`.
65
67
  * @csspart day-selected - The chosen day, or either end of a chosen range, beside `day`.
66
68
  * @csspart day-today - Today, beside `day`.
69
+ * @csspart day-weekend - A day of the weekend where the calendar's language is spoken, beside `day`: Saturday and Sunday in `en-US`, Friday and Saturday in `ar-EG`.
67
70
  * @csspart feedback - The message under the calendar, while `feedback` is set.
68
71
  * @csspart grid - The grid of days.
69
72
  * @csspart header - The month name and its controls.
@@ -82,7 +85,7 @@ declare const HelixHeadlessDatePicker_base: (new (...args: any[]) => import("@fu
82
85
  * @csspart previous - The control that moves to the previous month.
83
86
  * @csspart previous-mark - The glyph on the previous month control, where none is slotted.
84
87
  * @csspart week - One row of days.
85
- * @csspart weekday - A column header naming a day of the week.
88
+ * @csspart weekday - A column header naming a day of the week. It shows the short name and says the full one.
86
89
  *
87
90
  * @cssproperty --date-picker-column-count - How many month columns are drawn, capped at two. `--date-picker-columns` overrides it.
88
91
  *
@@ -105,17 +108,24 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
105
108
  */
106
109
  badInputMessage: string;
107
110
  /**
108
- * The calendar the heading, the days, and the months are written in:
109
- * `gregory`, `buddhist`, `japanese`, `roc`, or `iso8601`, the five whose
110
- * months are the Gregorian months.
111
+ * The calendar the heading, the days, and the months are written in.
111
112
  *
112
- * Absent is the locale's own where it is one of the five, such as
113
- * `buddhist` for `th-TH` or `japanese` for `ja-JP-u-ca-japanese`, and
114
- * `gregory` for every other, such as `persian` for `fa-IR`, so the heading
115
- * names the Gregorian month the grid shows. The value is `YYYY-MM-DD` in
116
- * every calendar.
113
+ * `gregory`, `buddhist`, `japanese`, `roc`, and `iso8601` are the five
114
+ * whose months are the Gregorian months. Absent is the locale's own where
115
+ * it is one of the five, such as `buddhist` for `th-TH` or `japanese` for
116
+ * `ja-JP-u-ca-japanese`, and `gregory` for every other, such as `persian`
117
+ * for `fa-IR`.
118
+ *
119
+ * `persian`, `hebrew`, `islamic-umalqura`, `islamic-civil`, `ethiopic`,
120
+ * `coptic`, and `indian` draw a calendar with months of its own, as React
121
+ * Aria's calendars do: each grid is one of that calendar's months, the
122
+ * Hebrew leap month and Ethiopia's five-day thirteenth month included, and
123
+ * Previous, Next and the page keys move by those months. `month` and
124
+ * `helix-navigate` then name a month by its first day, `YYYY-MM-DD`. The
125
+ * heading stays text, as the months and years views are drawn in Gregorian
126
+ * months only. The value is `YYYY-MM-DD` in every calendar.
117
127
  */
118
- calendar?: DateCalendar;
128
+ calendar?: DateCalendar | MonthCalendar;
119
129
  /**
120
130
  * What each day shows, in place of its number: a function given the date as
121
131
  * `YYYY-MM-DD` that returns text, an element or a Lit template.
@@ -198,8 +208,9 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
198
208
  /** The month on show, whoever set it, and `undefined` until somebody has. */
199
209
  private monthState;
200
210
  /**
201
- * The month shown, as `YYYY-MM`. When it is not set, it follows `value`,
202
- * then the current month.
211
+ * The month shown, as `YYYY-MM`, or as its first day, `YYYY-MM-DD`, under a
212
+ * `calendar` with months of its own. When it is not set, it follows
213
+ * `value`, then the current month.
203
214
  *
204
215
  * Separate from `value` on purpose, so paging selects nothing and a consumer
205
216
  * can drive the view. Reading gives the month on show; writing sets it, and
@@ -258,6 +269,8 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
258
269
  private focused;
259
270
  /** The selected dates, in order. */
260
271
  private get selected();
272
+ /** The days of the weekend, `0` for Sunday: the locale's, or Saturday and Sunday where none is named. */
273
+ private get weekend();
261
274
  /** The day the week starts on: `firstDayOfWeek`, or the locale's. */
262
275
  private get weekStart();
263
276
  /**
@@ -274,6 +287,13 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
274
287
  private isSelected;
275
288
  /** The month being shown, resolved from `month`, then `value`, then today. */
276
289
  private get shown();
290
+ /**
291
+ * The months the calendar pages through: its own under a `calendar` with
292
+ * months of its own, and the Gregorian months otherwise.
293
+ */
294
+ private get system();
295
+ /** Whether `calendar` names a calendar with months of its own. */
296
+ private get ownMonths();
277
297
  /**
278
298
  * The month to open on when nothing has declared one.
279
299
  *
@@ -432,6 +452,16 @@ export declare class HelixHeadlessDatePicker extends HelixHeadlessDatePicker_bas
432
452
  * The validation message for a date that `step` skips.
433
453
  */
434
454
  stepMismatchMessage: string;
455
+ /**
456
+ * The time zone today is read in, such as `America/New_York`, as React
457
+ * Aria's `today(timeZone)` reads it. Empty is the reader's own.
458
+ *
459
+ * Dates are plain `YYYY-MM-DD` and carry no zone, so this moves only today:
460
+ * the day marked `aria-current` and `day-today`, and the month a picker
461
+ * with no value opens on. A desk in Tokyo booking for an office in New York
462
+ * sets `America/New_York`, and today is the office's.
463
+ */
464
+ timeZone: string;
435
465
  /**
436
466
  * The validation message for a date that `disable-dates` or `disable-days`
437
467
  * bars.