@fusion.dev/helix-headless 0.147.0 → 0.148.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 (141) hide show
  1. package/custom-elements.json +4512 -790
  2. package/dist/accordion/item/accordion-item.d.ts +5 -2
  3. package/dist/accordion/item/accordion-item.js +11 -7
  4. package/dist/accordion/root/accordion-root.js +4 -6
  5. package/dist/announcement/root/announcement-root.d.ts +56 -5
  6. package/dist/announcement/root/announcement-root.js +124 -21
  7. package/dist/area-chart/root/area-chart-root.js +3 -4
  8. package/dist/badge/count.d.ts +14 -0
  9. package/dist/badge/count.js +20 -0
  10. package/dist/badge/root/badge-root.js +8 -22
  11. package/dist/blockquote/root/blockquote-root.d.ts +66 -8
  12. package/dist/blockquote/root/blockquote-root.js +85 -8
  13. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +2 -2
  14. package/dist/button-group/arrows.d.ts +14 -0
  15. package/dist/button-group/arrows.js +10 -0
  16. package/dist/button-group/root/button-group-root.d.ts +141 -3
  17. package/dist/button-group/root/button-group-root.js +142 -2
  18. package/dist/canvas/edge-pan.d.ts +61 -0
  19. package/dist/canvas/edge-pan.js +63 -0
  20. package/dist/canvas/node/canvas-node.d.ts +12 -2
  21. package/dist/canvas/node/canvas-node.js +6 -2
  22. package/dist/canvas/root/canvas-document.d.ts +10 -0
  23. package/dist/canvas/root/canvas-document.js +1 -0
  24. package/dist/canvas/root/canvas-root.d.ts +107 -5
  25. package/dist/canvas/root/canvas-root.js +140 -36
  26. package/dist/canvas/snap.d.ts +42 -0
  27. package/dist/canvas/snap.js +32 -0
  28. package/dist/carousel/root/carousel-root.d.ts +4 -3
  29. package/dist/carousel/root/carousel-root.js +2 -6
  30. package/dist/chat/root/chat-root.d.ts +13 -3
  31. package/dist/chat/root/chat-root.js +2 -6
  32. package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
  33. package/dist/checkbox-group/root/checkbox-group-root.js +18 -10
  34. package/dist/checkbox-group/select-all.d.ts +11 -0
  35. package/dist/checkbox-group/select-all.js +5 -0
  36. package/dist/code-block/root/code-block-root.d.ts +8 -6
  37. package/dist/code-block/root/code-block-root.js +4 -3
  38. package/dist/color-picker/root/color-picker-root.js +6 -12
  39. package/dist/color-picker/wheel.d.ts +11 -0
  40. package/dist/color-picker/wheel.js +9 -0
  41. package/dist/combobox/root/combobox-root.d.ts +70 -11
  42. package/dist/combobox/root/combobox-root.js +68 -18
  43. package/dist/command/root/command-root.d.ts +4 -3
  44. package/dist/data-table/root/data-table-root.js +17 -44
  45. package/dist/description-list/item/description-list-item.d.ts +4 -0
  46. package/dist/description-list/item/description-list-item.js +7 -1
  47. package/dist/divider/root/divider-root.d.ts +45 -16
  48. package/dist/divider/root/divider-root.js +28 -4
  49. package/dist/drilldown/item/drilldown-item.js +3 -4
  50. package/dist/element-events.generated.js +1 -0
  51. package/dist/empty/root/empty-root.d.ts +17 -3
  52. package/dist/empty/root/empty-root.js +29 -5
  53. package/dist/file-input/root/file-input-root.js +3 -4
  54. package/dist/file-input/status.js +2 -1
  55. package/dist/filter-input/chip/filter-chip.js +2 -3
  56. package/dist/format-date/fields.d.ts +20 -0
  57. package/dist/format-date/fields.js +14 -0
  58. package/dist/format-date/options.d.ts +2 -1
  59. package/dist/format-date/options.js +9 -38
  60. package/dist/format-date/root/format-date-root.d.ts +35 -3
  61. package/dist/format-date/root/format-date-root.js +24 -32
  62. package/dist/graph/root/graph-root.d.ts +23 -5
  63. package/dist/graph/root/graph-root.js +17 -20
  64. package/dist/heatmap/root/heatmap-root.d.ts +12 -0
  65. package/dist/heatmap/root/heatmap-root.js +26 -11
  66. package/dist/image/root/image-root.d.ts +11 -0
  67. package/dist/kbd/root/kbd-root.js +1 -2
  68. package/dist/legend/root/legend-root.js +4 -17
  69. package/dist/list/root/list-root.js +8 -22
  70. package/dist/listbox/root/listbox-root.d.ts +119 -24
  71. package/dist/listbox/root/listbox-root.js +130 -132
  72. package/dist/map/marker/map-marker.d.ts +2 -1
  73. package/dist/map/root/map-root.d.ts +40 -2
  74. package/dist/map/root/map-root.js +64 -23
  75. package/dist/megamenu/root/megamenu-root.js +4 -11
  76. package/dist/menu/root/menu-root.d.ts +2 -2
  77. package/dist/navigation/root/navigation-root.js +5 -8
  78. package/dist/notifications/group/notification-group.d.ts +8 -1
  79. package/dist/notifications/group/notification-group.js +12 -6
  80. package/dist/notifications/notification/notification.d.ts +12 -1
  81. package/dist/notifications/notification/notification.js +3 -1
  82. package/dist/notifications/root/notifications-root.d.ts +41 -5
  83. package/dist/notifications/root/notifications-root.js +27 -7
  84. package/dist/object-inspector/root/object-inspector-root.d.ts +5 -1
  85. package/dist/object-inspector/root/object-inspector-root.js +21 -11
  86. package/dist/pagination/size/pagination-size.d.ts +1 -0
  87. package/dist/pagination/size/pagination-size.js +1 -1
  88. package/dist/pin-input/code.d.ts +23 -0
  89. package/dist/pin-input/code.js +25 -0
  90. package/dist/pin-input/root/pin-input-root.js +9 -23
  91. package/dist/qr-code/root/qr-code-root.d.ts +14 -3
  92. package/dist/qr-code/root/qr-code-root.js +19 -5
  93. package/dist/scatter-chart/measures.d.ts +22 -0
  94. package/dist/scatter-chart/measures.js +11 -0
  95. package/dist/scatter-chart/root/scatter-chart-root.d.ts +2 -6
  96. package/dist/scatter-chart/root/scatter-chart-root.js +6 -19
  97. package/dist/select/root/select-root.d.ts +56 -8
  98. package/dist/select/root/select-root.js +33 -9
  99. package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
  100. package/dist/speed-dial/group/speed-dial-group.js +43 -0
  101. package/dist/speed-dial/index.d.ts +1 -0
  102. package/dist/speed-dial/index.js +1 -0
  103. package/dist/speed-dial/item/speed-dial-item.d.ts +26 -1
  104. package/dist/speed-dial/item/speed-dial-item.js +41 -3
  105. package/dist/speed-dial/long-press.d.ts +50 -0
  106. package/dist/speed-dial/long-press.js +48 -0
  107. package/dist/speed-dial/root/speed-dial-root.d.ts +80 -1
  108. package/dist/speed-dial/root/speed-dial-root.js +114 -5
  109. package/dist/splitter/root/splitter-root.d.ts +7 -3
  110. package/dist/splitter/root/splitter-root.js +16 -3
  111. package/dist/table/cell/table-cell.d.ts +8 -1
  112. package/dist/table/cell/table-cell.js +6 -2
  113. package/dist/table/grid-controller.d.ts +117 -0
  114. package/dist/table/grid-controller.js +293 -0
  115. package/dist/table/root/table-root.d.ts +47 -3
  116. package/dist/table/root/table-root.js +81 -24
  117. package/dist/table/row/table-row.d.ts +13 -1
  118. package/dist/table/row/table-row.js +20 -6
  119. package/dist/tag/group/tag-group.js +6 -15
  120. package/dist/time-picker/root/time-picker-root.d.ts +1 -0
  121. package/dist/time-picker/root/time-picker-root.js +3 -0
  122. package/dist/toast/item/toast-item.d.ts +6 -3
  123. package/dist/toast/item/toast-item.js +1 -0
  124. package/dist/toast/root/toast-root.d.ts +13 -1
  125. package/dist/toast/root/toast-root.js +20 -14
  126. package/dist/toggle/group/toggle-group.js +3 -5
  127. package/dist/toolbar/root/toolbar-root.d.ts +31 -0
  128. package/dist/toolbar/root/toolbar-root.js +39 -8
  129. package/dist/tooltip/root/tooltip-root.js +1 -2
  130. package/dist/video/root/video-root.d.ts +11 -0
  131. package/dist/video/root/video-root.js +15 -1
  132. package/dist/virtual-list/root/virtual-list-root.js +6 -18
  133. package/lint.json +1 -1
  134. package/package.json +5 -5
  135. package/types/jsx.d.ts +2005 -266
  136. package/types/svelte.d.ts +1 -0
  137. package/types/vue.d.ts +338 -62
  138. package/dist/kbd/keys.d.ts +0 -29
  139. package/dist/kbd/keys.js +0 -52
  140. package/dist/tooltip/focus-target.d.ts +0 -6
  141. package/dist/tooltip/focus-target.js +0 -9
@@ -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, afterLinkFollowed, customElement, DelayController, fillTemplate, HelixCreateEvent, HelixFilterEvent, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, reportValues, submitImplicitly, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, afterLinkFollowed, countLabel, customElement, DelayController, drawAutofill, drawStatus, fillTemplate, HelixCreateEvent, HelixFilterEvent, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, onApple, PopoverController, reportValues, StatusController, submitImplicitly, } from '@fusion.dev/helix-core';
8
8
  import { caretDown } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -21,6 +21,8 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
21
21
  this.aria = new AriaForwardController(this, {
22
22
  target: () => this.partNamed('control'),
23
23
  });
24
+ this.status = new StatusController(this);
25
+ this.told = undefined;
24
26
  this.allowsCustomValue = false;
25
27
  this.autocomplete = 'off';
26
28
  this.creatable = false;
@@ -38,6 +40,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
38
40
  this.removeLabel = 'Remove {name}';
39
41
  this.resultLabel = '{count} result';
40
42
  this.resultsLabel = '{count} results';
43
+ this.selectedLabel = '{name}, selected';
41
44
  this.suggest = false;
42
45
  this.typed = '';
43
46
  this.suggestion = '';
@@ -166,12 +169,17 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
166
169
  submitImplicitly(this.form);
167
170
  }
168
171
  };
172
+ this.counted = '';
169
173
  this.handleChange = (event) => {
170
174
  event.stopPropagation();
171
175
  const { value } = event.detail;
172
176
  if (!this.requestValue(value)) {
173
177
  return;
174
178
  }
179
+ const chosen = this.listbox?.options.find((option) => option.value === value);
180
+ if (chosen && onApple()) {
181
+ this.status.say(fillTemplate(this.selectedLabel, { name: chosen.text }));
182
+ }
175
183
  this.emptied = false;
176
184
  this.typed = '';
177
185
  this.suggestion = '';
@@ -247,7 +255,9 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
247
255
  }
248
256
  markStatus() {
249
257
  const list = this.listbox;
250
- this.toggleState('error', this.loadError !== '');
258
+ this.toggleState('blank', this.value === '');
259
+ this.toggleState('loading', this.loading);
260
+ this.toggleState('error', Boolean(this.loadError));
251
261
  this.toggleState('no-results', list !== null &&
252
262
  !this.loading &&
253
263
  list.filter !== '' &&
@@ -382,16 +392,47 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
382
392
  }
383
393
  linkActive() {
384
394
  const control = this.partNamed('control');
385
- if (!control || !('ariaActiveDescendantElement' in control)) {
386
- return;
387
- }
388
395
  const active = !this.open
389
396
  ? undefined
390
397
  : this.onCreate
391
398
  ? this.partNamed('create')
392
399
  : (this.listbox?.options ?? []).find((option) => option.active && !option.hidden);
400
+ this.tell(active);
401
+ if (!control || !('ariaActiveDescendantElement' in control)) {
402
+ return;
403
+ }
404
+ ;
393
405
  control.ariaActiveDescendantElement = active ?? null;
394
406
  }
407
+ tell(active) {
408
+ if (!this.open) {
409
+ this.told = undefined;
410
+ return;
411
+ }
412
+ if ((active ?? null) === this.told ||
413
+ !onApple() ||
414
+ this.renderRoot.activeElement !== this.partNamed('control')) {
415
+ return;
416
+ }
417
+ if (this.listbox?.isUpdatePending) {
418
+ void this.listbox.updateComplete.then(() => this.linkActive());
419
+ return;
420
+ }
421
+ if (this.told === undefined) {
422
+ this.counted = countLabel(this.reachable.length, this.resultLabel, this.resultsLabel, this.lang);
423
+ }
424
+ this.told = active ?? null;
425
+ const option = active;
426
+ const words = option?.text === undefined
427
+ ? (active?.textContent?.trim() ?? '')
428
+ : option.selected
429
+ ? fillTemplate(this.selectedLabel, { name: option.text })
430
+ : option.text;
431
+ this.status.say([this.counted, words].filter(Boolean).join('. '));
432
+ if (active) {
433
+ this.counted = '';
434
+ }
435
+ }
395
436
  removeLastToken() {
396
437
  const last = this.values.at(-1);
397
438
  if (last !== undefined) {
@@ -468,7 +509,11 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
468
509
  this.opening = undefined;
469
510
  void this.listbox?.updateComplete.then(() => this.linkActive());
470
511
  }
471
- if (control && !this.editing && !this.multiple && control.value !== '') {
512
+ if (control &&
513
+ !this.editing &&
514
+ !this.multiple &&
515
+ control.value !== '' &&
516
+ this.renderRoot.activeElement === control) {
472
517
  control.select();
473
518
  }
474
519
  }
@@ -497,25 +542,16 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
497
542
  ?loading=${this.loading}
498
543
  ?multiple=${this.multiple}
499
544
  .value=${this.value}
545
+ .chosenAgain=${() => this.close()}
500
546
  @helix-before-change=${this.handleChange}
501
547
  @change=${this.stopInner}
502
548
  @helix-filter=${this.stopInner}
503
549
  @input=${this.stopInner}
504
550
  >
505
- <!-- The field's words are read off the options, and on the first
506
- render there are none: the slot has not been assigned yet, so a
507
- combobox with a value comes up empty. Assignment changes no
508
- property, so nothing else would schedule the update that fixes
509
- it. -->
510
551
  <slot @slotchange=${() => this.requestUpdate()}></slot>
511
- <!-- Handed on to the list's own, with the text as this slot's fallback:
512
- a slot passed into another is assigned there whether or not anything
513
- fills it, so the list's fallback would never show. -->
514
552
  <slot name="empty" slot="empty">${this.emptyText}</slot>
515
553
  <slot name="loading" slot="loading">${this.loadingText}</slot>
516
554
  <slot name="error" slot="error">${this.loadError}</slot>
517
- <!-- Handed on only while something fills it: an assigned slot hides the
518
- list's own fallback, which is no-results-text or empty-text. -->
519
555
  <slot
520
556
  name="no-results"
521
557
  slot=${this.querySelector?.(':scope > [slot="no-results"]') ? 'no-results' : nothing}
@@ -565,7 +601,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
565
601
  id=${this.controlId}
566
602
  type="text"
567
603
  role="combobox"
568
- autocomplete=${this.autocomplete || nothing}
604
+ autocomplete=${(this.fills ? 'off' : this.autocomplete) || nothing}
569
605
  spellcheck="false"
570
606
  aria-controls="listbox"
571
607
  aria-autocomplete=${this.suggest ? 'both' : 'list'}
@@ -588,7 +624,18 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
588
624
  ${this.renderListbox()}
589
625
  ${this.renderCreate()}
590
626
  </div>
591
- ${this.renderHint()}${this.renderFeedback()}`;
627
+ ${this.renderHint()}${this.renderFeedback()}${drawStatus(this.status)}
628
+ ${drawAutofill({
629
+ autocomplete: this.fills ? this.autocomplete : '',
630
+ disabled: this.isDisabled || this.readOnly,
631
+ multiple: this.multiple,
632
+ onFill: (value) => this.requestValue(value),
633
+ options: optionsAmong(this.knownChildren),
634
+ value: this.value,
635
+ })}`;
636
+ }
637
+ get fills() {
638
+ return !this.allowsCustomValue && this.autocomplete !== 'off' && this.autocomplete !== '';
592
639
  }
593
640
  };
594
641
  __decorate([
@@ -645,6 +692,9 @@ __decorate([
645
692
  __decorate([
646
693
  property({ attribute: 'results-label' })
647
694
  ], HelixHeadlessCombobox.prototype, "resultsLabel", void 0);
695
+ __decorate([
696
+ property({ attribute: 'selected-label' })
697
+ ], HelixHeadlessCombobox.prototype, "selectedLabel", void 0);
648
698
  __decorate([
649
699
  property({ type: Boolean })
650
700
  ], HelixHeadlessCombobox.prototype, "suggest", void 0);
@@ -96,10 +96,11 @@ export declare class HelixHeadlessCommand extends HelixHeadlessCommand_base {
96
96
  label: string;
97
97
  /**
98
98
  * What the list says when its commands could not be loaded, such as after a
99
- * request to a server fails. Empty is no error. Passed to the list, which
100
- * shows it under any commands there are and tells screen readers.
99
+ * request to a server fails. Empty, null or no attribute is no error, so
100
+ * removing the attribute clears it. Passed to the list, which shows it
101
+ * under any commands there are and tells screen readers.
101
102
  */
102
- loadError: string;
103
+ loadError: string | null;
103
104
  /**
104
105
  * Whether the commands are still loading, for a palette that asks a server
105
106
  * for them as the user types.
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessDataTable_1;
8
- import { anchorAt, arranged, commaList, countLabel, customElement, cycle, deepActiveElement, deepContains, everyState, fillTemplate, format, formatNumbers, HelixActivateEvent, HelixElement, HelixExpandEvent, HelixFilterEvent, HelixFilteredEvent, HelixLoadMoreEvent, HelixNavigateEvent, HelixReorderEvent, HelixReorderedEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, hostAria, inlineStep, inRowOrder, isRtl, MixinCollection, markRegion, onAndroid, ordered, parse, rangeAdd, reportRuntime, requestChange, selectsAll, Typeahead, toggleEvery, treeArrow, typesCharacter, VirtualizeController, valueAt, window_, writtenList, } from '@fusion.dev/helix-core';
8
+ import { anchorAt, arranged, commaList, countLabel, customElement, cycle, deepActiveElement, deepContains, everyState, fillTemplate, format, formatNumbers, gridMove, HelixActivateEvent, HelixElement, HelixExpandEvent, HelixFilterEvent, HelixFilteredEvent, HelixLoadMoreEvent, HelixNavigateEvent, HelixReorderEvent, HelixReorderedEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, hostAria, inlineStep, inRowOrder, isRtl, MixinCollection, markRegion, onAndroid, ordered, pageRows, parse, rangeAdd, reportRuntime, requestChange, scrollRoom, selectsAll, Typeahead, toggleEvery, treeArrow, typesCharacter, VirtualizeController, valueAt, window_, writtenList, } from '@fusion.dev/helix-core';
9
9
  import { caretDown, caretRight } from '@fusion.dev/helix-icons';
10
10
  import { nothing } from 'lit';
11
11
  import { property, state } from 'lit/decorators.js';
@@ -165,8 +165,6 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
165
165
  return;
166
166
  }
167
167
  const last = this.drawnPage.length - 1 + (this.moreDrawn ? 1 : 0);
168
- const edge = this.width - 1;
169
- const control = event.ctrlKey || event.metaKey;
170
168
  if (!event.shiftKey && selectsAll(event) && this.selectable) {
171
169
  event.preventDefault();
172
170
  this.requestSelected(new Set([...this.chosen, ...this.everyKey]));
@@ -180,43 +178,22 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
180
178
  }
181
179
  return;
182
180
  }
183
- let { column, row } = place;
184
- switch (event.key) {
185
- case 'ArrowUp':
186
- row = Math.max(-1, row - 1);
187
- break;
188
- case 'ArrowDown':
189
- row = Math.min(last, row + 1);
190
- break;
191
- case 'ArrowLeft':
192
- case 'ArrowRight':
193
- if (this.treeKey(event, place)) {
194
- return;
195
- }
196
- column = Math.max(0, Math.min(edge, column + inlineStep(this, event.key)));
197
- break;
198
- case 'Home':
199
- row = control ? -1 : row;
200
- column = 0;
201
- break;
202
- case 'End':
203
- row = control ? Math.max(row, last) : row;
204
- column = edge;
205
- break;
206
- case 'PageUp':
207
- row = Math.max(-1, row - this.pageStep(place.cell));
208
- break;
209
- case 'PageDown':
210
- row = Math.min(last, row + this.pageStep(place.cell));
211
- break;
212
- case 'Enter':
213
- case ' ':
214
- case 'F2':
215
- this.activate(event, place);
216
- return;
217
- default:
218
- return;
181
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'F2') {
182
+ this.activate(event, place);
183
+ return;
184
+ }
185
+ if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight') && this.treeKey(event, place)) {
186
+ return;
219
187
  }
188
+ const to = gridMove(event.key, place, { last, top: -1, width: this.width }, {
189
+ control: event.ctrlKey || event.metaKey,
190
+ page: event.key.startsWith('Page') ? this.pageStep(place.cell) : 1,
191
+ step: inlineStep(this, event.key),
192
+ });
193
+ if (!to) {
194
+ return;
195
+ }
196
+ const { column, row } = to;
220
197
  event.preventDefault();
221
198
  if (event.shiftKey &&
222
199
  this.selectable &&
@@ -1204,11 +1181,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1204
1181
  this.focusWanted = true;
1205
1182
  }
1206
1183
  pageStep(cell) {
1207
- const box = this.partNamed('table');
1208
- const tall = cell.getBoundingClientRect().height || this.estimate;
1209
- const scrolls = box !== null && box.scrollHeight > box.clientHeight + 1;
1210
- const room = scrolls ? box.clientHeight - this.headHeight() : window.innerHeight;
1211
- return Math.max(1, Math.floor(room / tall) - 1);
1184
+ return pageRows(scrollRoom(this.partNamed('table'), { covered: this.headHeight() }), cell.getBoundingClientRect().height || this.estimate);
1212
1185
  }
1213
1186
  headHeight() {
1214
1187
  return this.cellAt(-1, 0)?.getBoundingClientRect().height ?? 0;
@@ -33,8 +33,11 @@ import { type PropertyValues, type TemplateResult } from 'lit';
33
33
  * @slot actions - Controls that act on the value, such as a copy button, after it inside its `<dd>`.
34
34
  * @slot label - The label, when it is more than text. Falls back to `label`.
35
35
  *
36
+ * @csspart actions - The box after the value inside its `<dd>`, holding the `actions` slot.
36
37
  * @csspart label - The `<dt>`, holding the label.
37
38
  * @csspart value - The `<dd>`, holding the value.
39
+ *
40
+ * @cssstate several - The values are several `<dd>` elements of your own, so the item draws no `value` part.
38
41
  */
39
42
  export declare class HelixHeadlessDescriptionListItem extends HelixElement {
40
43
  /**
@@ -58,6 +61,7 @@ export declare class HelixHeadlessDescriptionListItem extends HelixElement {
58
61
  */
59
62
  private get ownValues();
60
63
  protected firstUpdated(changed: PropertyValues): void;
64
+ protected updated(changed: PropertyValues): void;
61
65
  protected render(): TemplateResult;
62
66
  /** A value arriving or leaving can change whether the item wraps them. */
63
67
  private readonly handleSlotChange;
@@ -23,12 +23,18 @@ let HelixHeadlessDescriptionListItem = class HelixHeadlessDescriptionListItem ex
23
23
  super.firstUpdated(changed);
24
24
  this.finishHydration();
25
25
  }
26
+ updated(changed) {
27
+ super.updated(changed);
28
+ this.toggleState('several', this.ownValues);
29
+ }
26
30
  render() {
27
31
  const label = html `<dt part="label"><slot name="label">${this.label}</slot></dt>`;
28
32
  return this.ownValues
29
33
  ? html `${label}<slot @slotchange=${this.handleSlotChange}></slot>`
30
34
  : html `${label}
31
- <dd part="value"><slot @slotchange=${this.handleSlotChange}></slot><slot name="actions"></slot></dd>`;
35
+ <dd part="value"><slot @slotchange=${this.handleSlotChange}></slot><span part="actions"
36
+ ><slot name="actions"></slot></span
37
+ ></dd>`;
32
38
  }
33
39
  };
34
40
  __decorate([
@@ -9,12 +9,22 @@ import { type PropertyValues, type TemplateResult } from 'lit';
9
9
  * rendered element. The semantics are then in the server-rendered markup, with
10
10
  * no attribute written by script after the element connects.
11
11
  *
12
- * Set `announced` for a line that divides one thing from another, and it is
13
- * read as a separator, named by any content set into it.
12
+ * **The line is a separator**, as `<hr>` is, and as Radix Separator, React
13
+ * Aria Separator, and Web Awesome's divider are. It is named by any content
14
+ * set into it, so a screen reader says "Or, separator" where you wrote "Or" on
15
+ * the line. Set `decorative` on a rule that separates nothing, such as one
16
+ * under a heading or between the facts of a byline, and it leaves the
17
+ * accessibility tree.
14
18
  *
15
- * Use `orientation="vertical"` between items on one line, such as inside a
16
- * toolbar. A vertical divider stretches to fit its flex parent and sets no
17
- * height of its own.
19
+ * Use `orientation="vertical"` between items on one line. A vertical divider
20
+ * stretches to fit its flex parent and sets no height of its own.
21
+ *
22
+ * **Inside a toolbar it runs across the toolbar** without being told: vertical
23
+ * in a horizontal toolbar and horizontal in a vertical one, with
24
+ * `aria-orientation` to match, as React Aria's separator does inside a
25
+ * toolbar. Inside means a child of Toolbar or of the page's own
26
+ * `role="toolbar"`. An `orientation` you write still wins. A server cannot see
27
+ * the toolbar, so the turn arrives with hydration, inside the shadow root.
18
28
  *
19
29
  * @since 0.1.0
20
30
  * @status stable
@@ -26,27 +36,44 @@ import { type PropertyValues, type TemplateResult } from 'lit';
26
36
  *
27
37
  * @csspart content - The content set into the line.
28
38
  * @csspart line - The line, or each half of it when content is set into it.
39
+ *
40
+ * @cssstate vertical - The line runs vertically: `orientation="vertical"`, or no `orientation` inside a horizontal toolbar.
29
41
  */
30
42
  export declare class HelixHeadlessDivider extends HelixElement {
31
43
  /**
32
- * Whether screen readers announce the line as a separator.
33
- *
34
- * Off by default, and out of the accessibility tree. Most rules on a page
35
- * separate nothing: they underline a heading or close a section that space
36
- * had already closed, and announcing every one as a separator is noise a
37
- * screen-reader user has to walk past.
44
+ * Whether screen readers announce the line as a separator, which they now do
45
+ * by default.
38
46
  *
39
- * An opt-in, not a `decorative` opt-out, because a boolean attribute cannot
40
- * be turned off: `decorative="false"` parses as `true`, which would put the
41
- * quiet default out of reach from HTML and leave it settable only as a
42
- * property. An opt-in is absent by default, which every reflected property
43
- * here has to be.
47
+ * It dates from when the line was decorative by default. It is still read,
48
+ * and it wins over `decorative`, so a divider written with it stays a
49
+ * separator.
44
50
  */
45
51
  announced: boolean;
52
+ /**
53
+ * Takes the line out of the accessibility tree, for a rule that separates
54
+ * nothing: one under a heading, or between the facts of a byline.
55
+ *
56
+ * Absent, the line is a separator, as `<hr>` is. Radix Separator's opt-out
57
+ * has the same name. It turns the role off because a boolean attribute that
58
+ * is on by default cannot be turned off from HTML.
59
+ */
60
+ decorative: boolean;
46
61
  /**
47
62
  * The direction the line runs: `horizontal` or `vertical`.
63
+ *
64
+ * Unset is horizontal, or across the toolbar the divider sits in.
48
65
  */
49
66
  orientation?: 'horizontal' | 'vertical';
67
+ /**
68
+ * The direction the line is drawn and announced in: `orientation`, else
69
+ * across the toolbar around it, else horizontal.
70
+ *
71
+ * The toolbar is read only where the tree is known, never while hydrating
72
+ * what a server sent, whose render has to be the server's.
73
+ */
74
+ private get drawn();
75
+ /** Reads the toolbar again when the divider moves into or out of one. */
76
+ connectedCallback(): void;
50
77
  /**
51
78
  * Marks the middle region when nothing has been set into it.
52
79
  *
@@ -59,6 +86,8 @@ export declare class HelixHeadlessDivider extends HelixElement {
59
86
  * attribute written onto the host is a change to markup the consumer rendered.
60
87
  */
61
88
  private markEmpty;
89
+ protected willUpdate(changed: PropertyValues): void;
90
+ protected firstUpdated(changed: PropertyValues): void;
62
91
  protected updated(changed: PropertyValues): void;
63
92
  protected render(): TemplateResult;
64
93
  }
@@ -4,27 +4,48 @@ 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, markRegion } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, markRegion, toolbarAround, toolbarAxis, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessDivider = class HelixHeadlessDivider extends HelixElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.announced = false;
14
+ this.decorative = false;
15
+ }
16
+ get drawn() {
17
+ const toolbar = this.orientation || this.hydrating ? null : toolbarAround(this);
18
+ if (toolbar) {
19
+ return toolbarAxis(toolbar) === 'vertical' ? 'horizontal' : 'vertical';
20
+ }
21
+ return this.orientation ?? 'horizontal';
22
+ }
23
+ connectedCallback() {
24
+ super.connectedCallback();
25
+ this.requestUpdate();
14
26
  }
15
27
  markEmpty() {
16
28
  markRegion(this.renderRoot, 'content', ['']);
17
29
  }
30
+ willUpdate(changed) {
31
+ super.willUpdate(changed);
32
+ this.toggleState('vertical', this.drawn === 'vertical');
33
+ }
34
+ firstUpdated(changed) {
35
+ super.firstUpdated(changed);
36
+ this.finishHydration();
37
+ }
18
38
  updated(changed) {
19
39
  super.updated(changed);
20
40
  this.markEmpty();
21
41
  }
22
42
  render() {
43
+ const separator = this.announced || !this.decorative;
23
44
  return html `<div
24
45
  part="line"
25
- role=${this.announced ? 'separator' : 'presentation'}
26
- aria-labelledby=${this.announced ? 'content' : nothing}
27
- aria-orientation=${this.announced ? (this.orientation ?? 'horizontal') : nothing}
46
+ role=${separator ? 'separator' : 'presentation'}
47
+ aria-labelledby=${separator ? 'content' : nothing}
48
+ aria-orientation=${separator ? this.drawn : nothing}
28
49
  ></div>
29
50
  <span id="content" part="content"><slot @slotchange=${() => this.markEmpty()}></slot></span>
30
51
  <div part="line" role="presentation"></div>`;
@@ -33,6 +54,9 @@ let HelixHeadlessDivider = class HelixHeadlessDivider extends HelixElement {
33
54
  __decorate([
34
55
  property({ reflect: true, type: Boolean })
35
56
  ], HelixHeadlessDivider.prototype, "announced", void 0);
57
+ __decorate([
58
+ property({ reflect: true, type: Boolean })
59
+ ], HelixHeadlessDivider.prototype, "decorative", void 0);
36
60
  __decorate([
37
61
  property({ reflect: true })
38
62
  ], HelixHeadlessDivider.prototype, "orientation", void 0);
@@ -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, deepActiveElement, HelixElement, HelixSelectEvent, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { customElement, deepActiveElement, HelixElement, HelixSelectEvent, slotFilled, slotNamed, } from '@fusion.dev/helix-core';
8
8
  import { caretRight } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -27,9 +27,8 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
27
27
  this.page = (slot?.assignedElements().length ?? 0) > 0;
28
28
  };
29
29
  this.readWords = () => {
30
- const slot = (name) => this.renderRoot.querySelector(`slot[name="${name}"]`);
31
- this.description = slotFilled(slot('description'));
32
- this.end = slotFilled(slot('end'));
30
+ this.description = slotFilled(slotNamed(this.renderRoot, 'description'));
31
+ this.end = slotFilled(slotNamed(this.renderRoot, 'end'));
33
32
  };
34
33
  }
35
34
  willUpdate(changed) {
@@ -5,6 +5,7 @@ registerElementEvents({
5
5
  'helix-headless-app-header': ['helix-scrolled'],
6
6
  'helix-headless-avatar': ['error', 'load'],
7
7
  'helix-headless-button': ['helix-press'],
8
+ 'helix-headless-button-group': ['change', 'helix-before-change'],
8
9
  'helix-headless-callout': ['helix-dismiss'],
9
10
  'helix-headless-canvas': [
10
11
  'helix-before-move',
@@ -1,5 +1,5 @@
1
1
  import { ArrivalController, HelixElement } from '@fusion.dev/helix-core';
2
- import { type TemplateResult } from 'lit';
2
+ import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary A message that a region has nothing in it, and the next step to take.
5
5
  *
@@ -24,6 +24,13 @@ import { type TemplateResult } from 'lit';
24
24
  * rendering from data or on a server gets a heading a reader finds in the
25
25
  * outline without writing the element. A heading slotted in replaces it.
26
26
  *
27
+ * **Whatever is slotted into `heading` is read as a heading.** An element that
28
+ * is no heading, such as a `<span>` or a `<p>`, is set inside the `heading`
29
+ * part at `heading-level`, so a screen reader still finds it in the outline.
30
+ * A heading element, or an element holding one, keeps its own level. A server
31
+ * cannot see the slotted element, so a served `<span>` becomes a heading once
32
+ * the script runs.
33
+ *
27
34
  * @since 0.1.0
28
35
  * @status stable
29
36
  * @category feedback
@@ -32,10 +39,10 @@ import { type TemplateResult } from 'lit';
32
39
  *
33
40
  * @slot - The message: why the region is empty, and what to do about it.
34
41
  * @slot actions - The action that would put something in the region.
35
- * @slot heading - The heading, which names what is missing. Slot a heading element, such as an `<h3>`, so a reader can find it in the page's outline. Replaces the one `heading` draws.
42
+ * @slot heading - The heading, which names what is missing. A heading element, such as an `<h3>`, keeps its level; any other element is read as a heading at `heading-level`. Replaces the one `heading` draws.
36
43
  * @slot icon - An icon above the heading.
37
44
  *
38
- * @csspart heading - The heading `heading` draws, a heading at `heading-level`. Drawn only while `heading` holds words and nothing is slotted into `heading`.
45
+ * @csspart heading - The heading at `heading-level`: the one `heading` draws while nothing is slotted into `heading`, or the one around a slotted element that is no heading.
39
46
  * @csspart live - A copy of the heading and the message, in a live region that screen readers announce when the view appears. Visually hidden in the styled tier.
40
47
  */
41
48
  export declare class HelixHeadlessEmpty extends HelixElement {
@@ -61,6 +68,13 @@ export declare class HelixHeadlessEmpty extends HelixElement {
61
68
  * has nothing to announce because nothing changed.
62
69
  */
63
70
  noAnnounce: boolean;
71
+ /**
72
+ * Whether the `heading` slot holds elements and none of them is or holds a
73
+ * heading, read from the children before each render.
74
+ */
75
+ private looseHeading;
76
+ protected willUpdate(changed: PropertyValues): void;
77
+ protected firstUpdated(changed: PropertyValues): void;
64
78
  protected readonly arrival: ArrivalController;
65
79
  /**
66
80
  * The `heading` slot, with the heading `heading` draws as its fallback, so
@@ -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 { ArrivalController, customElement, HelixElement, headingLevel, slottedWords, } from '@fusion.dev/helix-core';
7
+ import { ArrivalController, customElement, HelixElement, headingIn, headingLevel, slottedWords, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
@@ -12,6 +12,7 @@ let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
12
12
  super(...arguments);
13
13
  this.heading = '';
14
14
  this.noAnnounce = false;
15
+ this.looseHeading = false;
15
16
  this.arrival = new ArrivalController(this, {
16
17
  arriving: () => !this.serverRendered && document.readyState === 'complete',
17
18
  text: () => [slottedWords(this, ['heading']) || this.heading.trim(), slottedWords(this, [''])]
@@ -19,17 +20,40 @@ let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
19
20
  .join(' '),
20
21
  });
21
22
  }
23
+ willUpdate(changed) {
24
+ super.willUpdate(changed);
25
+ if (this.childrenKnown) {
26
+ const slotted = this.knownChildren.filter((child) => child.slot === 'heading');
27
+ this.looseHeading = slotted.length > 0 && headingIn(slotted) === null;
28
+ }
29
+ }
30
+ firstUpdated(changed) {
31
+ super.firstUpdated(changed);
32
+ this.finishHydration();
33
+ }
22
34
  renderHeading(hidden) {
23
- return html `<slot
24
- aria-hidden=${hidden ? 'true' : nothing}
35
+ const level = headingLevel(this.headingLevel);
36
+ const slot = html `<slot
37
+ aria-hidden=${hidden && !this.looseHeading ? 'true' : nothing}
25
38
  name="heading"
26
- @slotchange=${() => this.arrival.say()}
39
+ @slotchange=${() => {
40
+ this.arrival.say();
41
+ this.requestUpdate();
42
+ }}
27
43
  >${this.heading.trim()
28
- ? html `<div aria-level=${headingLevel(this.headingLevel)} part="heading" role="heading">
44
+ ? html `<div aria-level=${level} part="heading" role="heading">
29
45
  ${this.heading}
30
46
  </div>`
31
47
  : nothing}</slot
32
48
  >`;
49
+ return this.looseHeading
50
+ ? html `<div
51
+ aria-hidden=${hidden ? 'true' : nothing}
52
+ aria-level=${level}
53
+ part="heading"
54
+ role="heading"
55
+ >${slot}</div>`
56
+ : slot;
33
57
  }
34
58
  render() {
35
59
  const say = () => this.arrival.say();