@fusion.dev/helix-headless 0.144.0 → 0.145.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 (155) hide show
  1. package/custom-elements.json +8312 -881
  2. package/dist/accordion/item/accordion-item.d.ts +48 -12
  3. package/dist/accordion/item/accordion-item.js +26 -5
  4. package/dist/accordion/root/accordion-root.d.ts +6 -0
  5. package/dist/accordion/root/accordion-root.js +7 -1
  6. package/dist/announcement/root/announcement-root.d.ts +21 -1
  7. package/dist/announcement/root/announcement-root.js +36 -1
  8. package/dist/app-header/root/app-header-root.d.ts +7 -0
  9. package/dist/app-header/root/app-header-root.js +5 -1
  10. package/dist/app-header/sectioning.d.ts +7 -0
  11. package/dist/app-header/sectioning.js +26 -0
  12. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +32 -1
  13. package/dist/breadcrumbs/root/breadcrumbs-root.js +25 -2
  14. package/dist/canvas/root/canvas-root.d.ts +38 -3
  15. package/dist/canvas/root/canvas-root.js +121 -40
  16. package/dist/carousel/root/carousel-root.d.ts +90 -0
  17. package/dist/carousel/root/carousel-root.js +134 -12
  18. package/dist/chat/composer/chat-composer.d.ts +38 -6
  19. package/dist/chat/composer/chat-composer.js +42 -9
  20. package/dist/chat/message/chat-message.d.ts +32 -1
  21. package/dist/chat/message/chat-message.js +25 -9
  22. package/dist/chat/naming.d.ts +22 -0
  23. package/dist/chat/naming.js +12 -0
  24. package/dist/chat/relative-clock.d.ts +39 -0
  25. package/dist/chat/relative-clock.js +31 -0
  26. package/dist/chat/root/chat-root.d.ts +36 -0
  27. package/dist/chat/root/chat-root.js +24 -1
  28. package/dist/code-block/root/code-block-root.d.ts +8 -1
  29. package/dist/code-block/root/code-block-root.js +5 -5
  30. package/dist/copy-button/root/copy-button-root.d.ts +21 -1
  31. package/dist/copy-button/root/copy-button-root.js +2 -2
  32. package/dist/data-table/root/data-table-root.d.ts +150 -2
  33. package/dist/data-table/root/data-table-root.js +303 -29
  34. package/dist/data-table/tree.d.ts +40 -0
  35. package/dist/data-table/tree.js +55 -0
  36. package/dist/description-list/item/description-list-item.d.ts +17 -2
  37. package/dist/description-list/item/description-list-item.js +15 -2
  38. package/dist/description-list/root/description-list-root.d.ts +27 -1
  39. package/dist/description-list/root/description-list-root.js +26 -1
  40. package/dist/drilldown/item/drilldown-item.d.ts +11 -6
  41. package/dist/drilldown/item/drilldown-item.js +8 -5
  42. package/dist/drilldown/root/drilldown-root.d.ts +11 -0
  43. package/dist/drilldown/root/drilldown-root.js +8 -3
  44. package/dist/element-events.generated.js +53 -8
  45. package/dist/filter-input/menu/filter-menu.d.ts +3 -0
  46. package/dist/filter-input/menu/filter-menu.js +8 -4
  47. package/dist/image/root/image-root.js +9 -1
  48. package/dist/input/root/input-root.d.ts +1 -1
  49. package/dist/input/root/input-root.js +2 -2
  50. package/dist/list/item/list-item.d.ts +68 -1
  51. package/dist/list/item/list-item.js +66 -4
  52. package/dist/list/numbering.d.ts +14 -0
  53. package/dist/list/numbering.js +8 -0
  54. package/dist/list/root/list-root.d.ts +106 -9
  55. package/dist/list/root/list-root.js +189 -4
  56. package/dist/list/selection.d.ts +19 -0
  57. package/dist/list/selection.js +21 -0
  58. package/dist/listbox/root/listbox-root.js +1 -2
  59. package/dist/map/root/map-root.d.ts +6 -2
  60. package/dist/map/root/map-root.js +5 -2
  61. package/dist/megamenu/root/megamenu-root.d.ts +21 -0
  62. package/dist/megamenu/root/megamenu-root.js +16 -2
  63. package/dist/menu/index.d.ts +1 -0
  64. package/dist/menu/index.js +1 -0
  65. package/dist/menu/item/menu-item.d.ts +38 -11
  66. package/dist/menu/item/menu-item.js +33 -1
  67. package/dist/menu/root/menu-root.d.ts +25 -1
  68. package/dist/menu/root/menu-root.js +63 -6
  69. package/dist/menu/section/menu-section.d.ts +55 -0
  70. package/dist/menu/section/menu-section.js +60 -0
  71. package/dist/navigation/root/navigation-root.d.ts +16 -1
  72. package/dist/navigation/root/navigation-root.js +27 -1
  73. package/dist/notifications/root/notifications-root.d.ts +18 -0
  74. package/dist/notifications/root/notifications-root.js +17 -1
  75. package/dist/object-inspector/root/object-inspector-root.d.ts +97 -16
  76. package/dist/object-inspector/root/object-inspector-root.js +179 -79
  77. package/dist/object-inspector/rows.d.ts +57 -0
  78. package/dist/object-inspector/rows.js +86 -0
  79. package/dist/outline/levels.d.ts +31 -0
  80. package/dist/outline/levels.js +23 -0
  81. package/dist/outline/root/outline-root.d.ts +68 -3
  82. package/dist/outline/root/outline-root.js +73 -8
  83. package/dist/pagination/size/pagination-size.d.ts +11 -2
  84. package/dist/pagination/size/pagination-size.js +19 -4
  85. package/dist/radio/group/radio-group.d.ts +4 -10
  86. package/dist/radio/group/radio-group.js +5 -10
  87. package/dist/rating/root/rating-root.d.ts +1 -1
  88. package/dist/rating/root/rating-root.js +2 -2
  89. package/dist/scroll-area/root/scroll-area-root.d.ts +113 -0
  90. package/dist/scroll-area/root/scroll-area-root.js +218 -3
  91. package/dist/scroll-area/scrollbar.d.ts +65 -0
  92. package/dist/scroll-area/scrollbar.js +24 -0
  93. package/dist/select/root/select-root.js +1 -1
  94. package/dist/splitter/root/splitter-root.d.ts +13 -0
  95. package/dist/splitter/root/splitter-root.js +20 -2
  96. package/dist/splitter/snap.d.ts +9 -0
  97. package/dist/splitter/snap.js +12 -0
  98. package/dist/steps/reach.d.ts +15 -0
  99. package/dist/steps/reach.js +12 -0
  100. package/dist/steps/root/steps-root.d.ts +30 -0
  101. package/dist/steps/root/steps-root.js +53 -0
  102. package/dist/steps/step/step.d.ts +38 -2
  103. package/dist/steps/step/step.js +28 -6
  104. package/dist/table/column/table-column.d.ts +11 -0
  105. package/dist/table/column/table-column.js +6 -1
  106. package/dist/table/root/table-root.d.ts +101 -0
  107. package/dist/table/root/table-root.js +166 -5
  108. package/dist/table/row/table-row.d.ts +47 -1
  109. package/dist/table/row/table-row.js +39 -2
  110. package/dist/tabs/panel/tab-panel.d.ts +9 -5
  111. package/dist/tabs/panel/tab-panel.js +3 -4
  112. package/dist/tabs/root/tabs-root.d.ts +25 -1
  113. package/dist/tabs/root/tabs-root.js +21 -3
  114. package/dist/tabs/tab/tab.d.ts +36 -4
  115. package/dist/tabs/tab/tab.js +46 -3
  116. package/dist/timeline/group/timeline-group.d.ts +46 -0
  117. package/dist/timeline/group/timeline-group.js +37 -0
  118. package/dist/timeline/index.d.ts +1 -0
  119. package/dist/timeline/index.js +1 -0
  120. package/dist/timeline/root/timeline-root.d.ts +10 -3
  121. package/dist/toast/item/toast-item.d.ts +10 -17
  122. package/dist/toast/item/toast-item.js +15 -36
  123. package/dist/toolbar/root/toolbar-root.d.ts +13 -0
  124. package/dist/toolbar/root/toolbar-root.js +15 -5
  125. package/dist/tree/item/tree-item.d.ts +54 -1
  126. package/dist/tree/item/tree-item.js +89 -20
  127. package/dist/tree/root/tree-root.d.ts +37 -0
  128. package/dist/tree/root/tree-root.js +51 -6
  129. package/dist/tree/selection.d.ts +24 -0
  130. package/dist/tree/selection.js +26 -0
  131. package/dist/video/media-events.d.ts +49 -0
  132. package/dist/video/media-events.js +39 -0
  133. package/dist/video/root/video-root.d.ts +99 -13
  134. package/dist/video/root/video-root.js +84 -47
  135. package/dist/video-playlist/menu/video-playlist-menu.d.ts +3 -0
  136. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  137. package/dist/video-playlist/order.d.ts +41 -0
  138. package/dist/video-playlist/order.js +34 -0
  139. package/dist/video-playlist/root/video-playlist-root.d.ts +70 -2
  140. package/dist/video-playlist/root/video-playlist-root.js +60 -16
  141. package/dist/virtual-list/controls.d.ts +4 -0
  142. package/dist/virtual-list/controls.js +30 -0
  143. package/dist/virtual-list/fill.d.ts +30 -0
  144. package/dist/virtual-list/fill.js +39 -0
  145. package/dist/virtual-list/keys.d.ts +15 -0
  146. package/dist/virtual-list/keys.js +24 -0
  147. package/dist/virtual-list/root/virtual-list-root.d.ts +155 -23
  148. package/dist/virtual-list/root/virtual-list-root.js +175 -34
  149. package/lint.json +1 -1
  150. package/package.json +5 -5
  151. package/types/jsx.d.ts +3778 -801
  152. package/types/svelte.d.ts +2 -0
  153. package/types/vue.d.ts +782 -215
  154. package/dist/listbox/range.d.ts +0 -17
  155. package/dist/listbox/range.js +0 -5
@@ -5,14 +5,17 @@ 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 { customElement, deepActiveElement, deepContains, fillTemplate, formatNumbers, HelixActivateEvent, HelixElement, HelixFilterEvent, HelixFilteredEvent, HelixNavigateEvent, HelixReorderEvent, HelixReorderedEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, hostAria, inlineStep, isRtl, MixinCollection, markRegion, reportRuntime, requestChange, VirtualizeController, writtenList, } from '@fusion.dev/helix-core';
8
+ import { countLabel, customElement, deepActiveElement, deepContains, fillTemplate, formatNumbers, HelixActivateEvent, HelixElement, HelixExpandEvent, HelixFilterEvent, HelixFilteredEvent, HelixLoadMoreEvent, HelixNavigateEvent, HelixReorderEvent, HelixReorderedEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, hostAria, inlineStep, isRtl, MixinCollection, markRegion, rangeAdd, reportRuntime, requestChange, Typeahead, typesCharacter, VirtualizeController, writtenList, } from '@fusion.dev/helix-core';
9
+ import { caretDown, caretRight } from '@fusion.dev/helix-icons';
9
10
  import { nothing } from 'lit';
10
11
  import { property, state } from 'lit/decorators.js';
11
12
  import { repeat } from 'lit/directives/repeat.js';
13
+ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
14
  import { html } from 'lit/static-html.js';
13
15
  import { at, edgeOf } from '../../table/column/table-column.js';
14
16
  import '../../checkbox/index.js';
15
17
  import '../../pagination/index.js';
18
+ import { grow } from '../tree.js';
16
19
  import { arranged, cycle, format, ordered, parse, window_, } from './order.js';
17
20
  const CONTROL = [
18
21
  'a[href]',
@@ -28,6 +31,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
28
31
  constructor() {
29
32
  super(...arguments);
30
33
  this.ascendingLabel = 'ascending';
34
+ this.defaultExpanded = '';
31
35
  this.defaultFilter = '';
32
36
  this.defaultOrder = '';
33
37
  this.defaultPage = 1;
@@ -38,6 +42,8 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
38
42
  this.estimate = 32;
39
43
  this.gapLabel = 'Skip to page {page}';
40
44
  this.label = '';
45
+ this.loadMore = false;
46
+ this.loadMoreLabel = 'Load more';
41
47
  this.mode = 'client';
42
48
  this.nextLabel = 'Next page';
43
49
  this.pageLabel = 'Page {page}';
@@ -63,11 +69,17 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
63
69
  this.paginatedState = undefined;
64
70
  this.orderState = undefined;
65
71
  this.selectedState = undefined;
72
+ this.expandedState = undefined;
66
73
  this.activeRow = undefined;
67
74
  this.activeColumn = 0;
68
75
  this.activeKey = undefined;
69
76
  this.focusWanted = false;
70
77
  this.drawnPage = [];
78
+ this.moreDrawn = false;
79
+ this.typeahead = new Typeahead();
80
+ this.anchorKey = undefined;
81
+ this.shiftPressed = false;
82
+ this.askedAt = -1;
71
83
  this.counts = {
72
84
  columns: undefined,
73
85
  rows: undefined,
@@ -84,6 +96,11 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
84
96
  this.warnedAboutSize = false;
85
97
  this.toggleRow = (key, event) => {
86
98
  event.preventDefault();
99
+ const to = this.pageKeys.indexOf(key);
100
+ if (this.shiftPressed && this.anchorKey !== undefined && to >= 0) {
101
+ this.selectRange(to);
102
+ return;
103
+ }
87
104
  this.chooseRow(key, event.detail.checked);
88
105
  };
89
106
  this.toggleAll = (event) => {
@@ -100,8 +117,12 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
100
117
  this.handleScroll = () => {
101
118
  if (this.windowed) {
102
119
  this.virtualize.measure();
120
+ this.askNearEnd();
103
121
  }
104
122
  };
123
+ this.notePress = (event) => {
124
+ this.shiftPressed = event.shiftKey;
125
+ };
105
126
  this.handleHeadClick = (event) => {
106
127
  if (this.partIn(event, 'grip') || this.partIn(event, 'resizer')) {
107
128
  return;
@@ -144,9 +165,22 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
144
165
  this.handleControlKey(event, place);
145
166
  return;
146
167
  }
147
- const last = this.drawnPage.length - 1;
168
+ const last = this.drawnPage.length - 1 + (this.moreDrawn ? 1 : 0);
148
169
  const edge = this.width - 1;
149
170
  const control = event.ctrlKey || event.metaKey;
171
+ if (control && !event.shiftKey && event.key.toLowerCase() === 'a' && this.selectable) {
172
+ event.preventDefault();
173
+ this.requestSelected(new Set([...this.chosen, ...this.everyKey]));
174
+ return;
175
+ }
176
+ if (typesCharacter(event) && (event.key !== ' ' || this.typeahead.typing())) {
177
+ const found = this.typeahead.match(event.key, this.rowWords, place.row);
178
+ if (found >= 0) {
179
+ event.preventDefault();
180
+ this.moveTo(found, place.column);
181
+ }
182
+ return;
183
+ }
150
184
  let { column, row } = place;
151
185
  switch (event.key) {
152
186
  case 'ArrowUp':
@@ -157,6 +191,9 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
157
191
  break;
158
192
  case 'ArrowLeft':
159
193
  case 'ArrowRight':
194
+ if (this.treeKey(event, place)) {
195
+ return;
196
+ }
160
197
  column = Math.max(0, Math.min(edge, column + inlineStep(this, event.key)));
161
198
  break;
162
199
  case 'Home':
@@ -182,6 +219,14 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
182
219
  return;
183
220
  }
184
221
  event.preventDefault();
222
+ if (event.shiftKey &&
223
+ this.selectable &&
224
+ (event.key === 'ArrowUp' || event.key === 'ArrowDown') &&
225
+ place.row >= 0 &&
226
+ row >= 0 &&
227
+ row < this.drawnPage.length) {
228
+ this.selectRange(row, place.row);
229
+ }
185
230
  if (row !== place.row || column !== place.column) {
186
231
  this.moveTo(row, column);
187
232
  }
@@ -264,6 +309,14 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
264
309
  ...HelixElement.shadowRootOptions,
265
310
  slotAssignment: 'manual',
266
311
  }; }
312
+ get expanded() {
313
+ return this.expandedState ?? this.defaultExpanded;
314
+ }
315
+ set expanded(next) {
316
+ const previous = this.expanded;
317
+ this.expandedState = next;
318
+ this.requestUpdate('expanded', previous);
319
+ }
267
320
  get filter() {
268
321
  return this.filterState ?? this.defaultFilter;
269
322
  }
@@ -331,15 +384,58 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
331
384
  }
332
385
  get available() {
333
386
  if (this.mode === 'server') {
334
- return this.rows.length;
387
+ return this.childField ? this.tree().rows.length : this.rows.length;
335
388
  }
336
- const matched = this.windowed ? this.arrange().rows.length : this.matched;
389
+ const matched = this.windowed ? this.flat().rows.length : this.matched;
337
390
  const size = this.pageSize;
338
391
  if (size <= 0) {
339
392
  return matched;
340
393
  }
341
394
  return Math.max(0, Math.min(size, matched - (this.page - 1) * size));
342
395
  }
396
+ tree() {
397
+ const axes = this.axes;
398
+ const server = this.mode === 'server';
399
+ const query = server ? '' : this.filter;
400
+ const spec = server ? '' : this.sort;
401
+ const key = [
402
+ query,
403
+ spec,
404
+ this.childField,
405
+ this.expanded,
406
+ axes.map((axis) => axis.field).join('\u0001'),
407
+ ].join('\u0000');
408
+ if (this.grown?.key === key && this.grown.source === this.rows) {
409
+ return this.grown.value;
410
+ }
411
+ if (!this.rowKey && !this.warnedAboutKey && typeof window !== 'undefined') {
412
+ this.warnedAboutKey = true;
413
+ reportRuntime({
414
+ kind: 'missing-row-key',
415
+ message: `<${this.localName} child-field> has no \`row-key\`, so it cannot tell which rows are open. Set \`row-key\` to the field that identifies a row.`,
416
+ tag: this.localName,
417
+ });
418
+ }
419
+ const open = this.opened;
420
+ const value = grow(this.rows, axes, {
421
+ field: this.childField ?? '',
422
+ isOpen: (row) => open.has(this.rowKeyOf(row, -1)),
423
+ query,
424
+ spec,
425
+ });
426
+ this.grown = { key, source: this.rows, value };
427
+ return value;
428
+ }
429
+ get opened() {
430
+ return new Set(writtenList(this.expanded.replaceAll(',', ' ')));
431
+ }
432
+ flat() {
433
+ if (this.childField) {
434
+ const { rows, total } = this.tree();
435
+ return { rows, total };
436
+ }
437
+ return this.arrange();
438
+ }
343
439
  arrange() {
344
440
  const axes = this.axes;
345
441
  const key = `${this.filter}\u0000${this.sort}\u0000${axes.map((axis) => axis.field).join('\u0001')}`;
@@ -370,7 +466,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
370
466
  }
371
467
  const row = this.currentRow(rows);
372
468
  const { first, last } = this.virtualize.window;
373
- return row >= 0 && (row < first || row >= last) ? row : undefined;
469
+ return row >= 0 && row < rows.length && (row < first || row >= last) ? row : undefined;
374
470
  }
375
471
  rowKeyOf(row, index) {
376
472
  if (!this.rowKey) {
@@ -391,7 +487,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
391
487
  return found;
392
488
  }
393
489
  }
394
- return Math.min(asked, rows.length - 1);
490
+ return Math.min(asked, rows.length - 1 + (this.moreDrawn ? 1 : 0));
395
491
  }
396
492
  get width() {
397
493
  return this.shownColumns.length + (this.selectable ? 1 : 0);
@@ -455,7 +551,16 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
455
551
  get view() {
456
552
  if (this.mode === 'server') {
457
553
  const total = Math.max(this.total, this.rows.length);
458
- return { available: this.rows.length, matched: total, page: this.rows, total };
554
+ const page = this.childField ? this.tree().rows : this.rows;
555
+ return { available: page.length, matched: total, page, total };
556
+ }
557
+ if (this.childField) {
558
+ const { rows: inOrder, total } = this.tree();
559
+ const page = window_(inOrder, this.page, this.pageSize);
560
+ if (!this.windowed) {
561
+ this.warnIfCrowded(page.length);
562
+ }
563
+ return { available: page.length, matched: inOrder.length, page, total };
459
564
  }
460
565
  if (!this.windowed) {
461
566
  const view = ordered(this.rows, this.axes, {
@@ -485,6 +590,9 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
485
590
  return Math.max(1, Math.ceil(count / size));
486
591
  }
487
592
  get matched() {
593
+ if (this.childField) {
594
+ return this.tree().rows.length;
595
+ }
488
596
  const query = this.filter.trim();
489
597
  if (query === '') {
490
598
  return this.rows.length;
@@ -565,10 +673,57 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
565
673
  return String(place);
566
674
  }
567
675
  get everyKey() {
676
+ if (this.childField) {
677
+ return this.tree().every.map((row, place) => this.keyOf(row, place));
678
+ }
568
679
  const rows = this.mode === 'server' ? this.rows : this.arrange().rows;
569
680
  return rows.map((row, place) => this.keyOf(row, place));
570
681
  }
682
+ get pageKeys() {
683
+ return this.drawnPage.map((row, index) => this.keyOf(row, this.rowIndex(index) - 2));
684
+ }
685
+ selectRange(to, from = to) {
686
+ const keys = this.pageKeys;
687
+ let anchor = this.anchorKey === undefined ? -1 : keys.indexOf(this.anchorKey);
688
+ if (anchor < 0) {
689
+ anchor = from;
690
+ this.anchorKey = keys[from];
691
+ }
692
+ const next = rangeAdd([...this.chosen], keys, anchor, to);
693
+ if (next) {
694
+ this.requestSelected(new Set(next));
695
+ }
696
+ }
697
+ get rowWords() {
698
+ const header = this.headerColumn;
699
+ return this.drawnPage.map((row) => {
700
+ const value = header?.order(row);
701
+ return value === undefined || value === null ? '' : String(value);
702
+ });
703
+ }
704
+ get headerColumn() {
705
+ const columns = this.shownColumns;
706
+ return (columns.find((column) => column.rowHeader) ?? columns.find((column) => column.field !== ''));
707
+ }
708
+ askForMore() {
709
+ this.dispatchEvent(new HelixLoadMoreEvent());
710
+ }
711
+ askNearEnd() {
712
+ if (!this.windowed || !this.loadMore || this.loading) {
713
+ return;
714
+ }
715
+ const screen = this.partNamed('table')?.clientHeight ?? 0;
716
+ const count = this.rows.length;
717
+ if (screen > 0 &&
718
+ count > 0 &&
719
+ this.askedAt !== count &&
720
+ this.virtualize.remaining() <= screen) {
721
+ this.askedAt = count;
722
+ this.askForMore();
723
+ }
724
+ }
571
725
  chooseRow(key, checked) {
726
+ this.anchorKey = key;
572
727
  const keys = this.chosen;
573
728
  if (checked) {
574
729
  keys.add(key);
@@ -609,7 +764,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
609
764
  }
610
765
  csv() {
611
766
  const columns = this.shownColumns.filter((column) => column.field !== '');
612
- const rows = this.mode === 'server' ? this.rows : this.arrange().rows;
767
+ const rows = this.mode === 'server' && !this.childField ? this.rows : this.flat().rows;
613
768
  const quote = (value) => {
614
769
  if (value === undefined || value === null) {
615
770
  return '';
@@ -711,10 +866,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
711
866
  }
712
867
  else if (this.hasUpdated && this.filter !== this.heardFilter) {
713
868
  const count = this.mode === 'server' ? Math.max(this.total, this.rows.length) : this.matched;
714
- const one = new Intl.PluralRules(this.lang || undefined).select(count) === 'one';
715
- said = fillTemplate(one ? this.resultLabel : this.resultsLabel, {
716
- count: formatNumbers(this.lang)(count),
717
- });
869
+ said = countLabel(count, this.resultLabel, this.resultsLabel, this.lang);
718
870
  }
719
871
  this.heardSort = sort;
720
872
  this.heardSelected = selected;
@@ -725,7 +877,10 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
725
877
  super.willUpdate(changed);
726
878
  this.hadFocus = this.hasUpdated && this.gridHasFocus();
727
879
  this.announced = this.news();
728
- hostAria(this.internals, { label: this.label || 'Table', role: 'grid' });
880
+ hostAria(this.internals, {
881
+ label: this.label || 'Table',
882
+ role: this.childField ? 'treegrid' : 'grid',
883
+ });
729
884
  this.internals.ariaMultiSelectable = this.selectable ? 'true' : null;
730
885
  const placement = this.placement;
731
886
  if (changed.has('rows') || placement !== this.measuredAgainst) {
@@ -750,6 +905,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
750
905
  if (this.windowed) {
751
906
  this.virtualize.measure();
752
907
  }
908
+ this.askNearEnd();
753
909
  }
754
910
  announceSort() {
755
911
  const current = parse(this.sort);
@@ -768,17 +924,48 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
768
924
  : undefined;
769
925
  }
770
926
  }
771
- renderCell(column, row, picked, place, stop) {
927
+ renderCell(column, row, picked, place, stop, header) {
772
928
  const edge = edgeOf(column.textAlign);
929
+ const placed = header && this.childField ? this.tree().placed.get(row) : undefined;
930
+ const style = [
931
+ edge ? `--data-table-cell-align: ${edge}` : '',
932
+ placed ? `--data-table-level: ${placed.level}` : '',
933
+ ]
934
+ .filter(Boolean)
935
+ .join('; ');
773
936
  return html `<span
774
- part="cell${picked ? ' cell-selected' : ''}"
775
- role="gridcell"
937
+ part="cell${picked ? ' cell-selected' : ''}${header ? ' cell-header' : ''}"
938
+ role=${header ? 'rowheader' : 'gridcell'}
776
939
  tabindex=${stop ? '0' : '-1'}
777
940
  data-column=${place}
778
- style=${edge ? `--data-table-cell-align: ${edge}` : nothing}
779
- >${column.read(row)}</span
941
+ style=${style || nothing}
942
+ >${placed?.branch ? this.renderToggle(row, placed.open) : nothing}${column.read(row)}</span
780
943
  >`;
781
944
  }
945
+ renderToggle(row, open) {
946
+ const glyph = open ? caretDown : caretRight;
947
+ return html `<span
948
+ part="toggle${open ? ' toggle-open' : ''}"
949
+ aria-hidden="true"
950
+ @click=${() => this.toggleOpen(row, !open)}
951
+ ><svg part="toggle-mark" ?data-mirror=${!open} fill="currentColor" viewBox=${glyph.viewBox}>${unsafeSVG(glyph.content)}</svg></span>`;
952
+ }
953
+ toggleOpen(row, open) {
954
+ const key = this.rowKeyOf(row, -1);
955
+ const keys = this.opened;
956
+ if (keys.has(key) === open) {
957
+ return false;
958
+ }
959
+ if (open) {
960
+ keys.add(key);
961
+ }
962
+ else {
963
+ keys.delete(key);
964
+ }
965
+ this.expanded = [...keys].join(',');
966
+ this.dispatchEvent(new HelixExpandEvent({ value: this.expanded }));
967
+ return true;
968
+ }
782
969
  renderCheckbox(name, checked, indeterminate, toggle) {
783
970
  const checkbox = this.tag('checkbox');
784
971
  return html `<${checkbox}
@@ -797,19 +984,24 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
797
984
  renderRows(rows, page) {
798
985
  const columns = this.shownColumns;
799
986
  const chosen = this.selectable ? this.chosen : undefined;
800
- const named = columns.find((column) => column.field !== '');
987
+ const named = this.headerColumn;
801
988
  const stopRow = this.currentRow(page);
802
989
  const stopColumn = this.currentColumn;
803
990
  const offset = this.selectable ? 1 : 0;
804
991
  return html `${repeat(rows, ({ index, row }) => this.rowKeyOf(row, index), ({ index, row }) => {
805
992
  const key = chosen ? this.keyOf(row, this.rowIndex(index) - 2) : '';
806
993
  const picked = chosen?.has(key) ?? false;
994
+ const placed = this.childField ? this.tree().placed.get(row) : undefined;
807
995
  const stop = (place) => index === stopRow && place === stopColumn;
808
996
  return html `<div
809
997
  part="row"
810
998
  role="row"
811
999
  aria-rowindex=${this.rowIndex(index)}
812
1000
  aria-selected=${chosen ? String(picked) : nothing}
1001
+ aria-level=${placed ? placed.level : nothing}
1002
+ aria-posinset=${placed ? placed.position : nothing}
1003
+ aria-setsize=${placed ? placed.siblings : nothing}
1004
+ aria-expanded=${placed?.branch ? String(placed.open) : nothing}
813
1005
  data-index=${this.windowed ? index : nothing}
814
1006
  data-row=${index}
815
1007
  >
@@ -823,7 +1015,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
823
1015
  row: named ? String(named.order(row) ?? key) : key,
824
1016
  }), picked, false, (event) => this.toggleRow(key, event))}</span
825
1017
  >`
826
- : nothing}${columns.map((column, place) => this.renderCell(column, row, picked, place + offset, stop(place + offset)))}
1018
+ : nothing}${columns.map((column, place) => this.renderCell(column, row, picked, place + offset, stop(place + offset), column === named))}
827
1019
  </div>`;
828
1020
  })}`;
829
1021
  }
@@ -833,16 +1025,21 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
833
1025
  return page + within + 2;
834
1026
  }
835
1027
  get placement() {
836
- return [this.mode, this.filter, this.sort, this.page, this.pageSize].join('\u0000');
1028
+ return [
1029
+ this.mode,
1030
+ this.filter,
1031
+ this.sort,
1032
+ this.page,
1033
+ this.pageSize,
1034
+ this.childField,
1035
+ this.expanded,
1036
+ ].join('\u0000');
837
1037
  }
838
1038
  renderSpacer(end, kept) {
839
1039
  if (!this.windowed) {
840
1040
  return nothing;
841
1041
  }
842
- const window = this.virtualize.window;
843
- const room = kept === undefined ? 0 : this.virtualize.sizeOf(kept);
844
- const before = Math.max(0, window.before - (kept !== undefined && kept < window.first ? room : 0));
845
- const after = Math.max(0, window.after - (kept !== undefined && kept >= window.last ? room : 0));
1042
+ const { after, before } = this.virtualize.spacers(kept);
846
1043
  return end === 'before'
847
1044
  ? html `<div part="before" role="presentation" style="block-size: ${before}px"></div>`
848
1045
  : html `<div part="after" role="presentation" style="block-size: ${after}px"></div>`;
@@ -858,9 +1055,26 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
858
1055
  data-column="0"
859
1056
  >${this.renderCheckbox(this.selectAllLabel, every.length > 0 && count === every.length, count > 0 && count < every.length, this.toggleAll)}</span>`;
860
1057
  }
1058
+ renderMore(page) {
1059
+ if (!this.moreDrawn) {
1060
+ return nothing;
1061
+ }
1062
+ const stop = this.currentRow(page) === page.length;
1063
+ return html `<div part="more" role="row" aria-rowindex=${this.rowIndex(page.length)} data-row=${page.length}>
1064
+ <span
1065
+ part="more-cell"
1066
+ role="gridcell"
1067
+ aria-colspan=${this.width}
1068
+ tabindex=${stop ? '0' : '-1'}
1069
+ data-column="0"
1070
+ ><button part="more-button" type="button" tabindex="-1" @click=${this.askForMore}>${this.loadMoreLabel}</button></span
1071
+ >
1072
+ </div>`;
1073
+ }
861
1074
  render() {
862
1075
  const { available, matched, page } = this.view;
863
1076
  this.drawnPage = page;
1077
+ this.moreDrawn = this.loadMore && !this.loading && page.length > 0;
864
1078
  const rows = this.windowOf(page);
865
1079
  const kept = this.keptRow(page);
866
1080
  const headStop = this.currentRow(page) === -1;
@@ -872,7 +1086,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
872
1086
  const countKnown = counted > 0 || this.sourceKnown;
873
1087
  this.counts = {
874
1088
  columns: columns.length ? columns.length + (this.selectable ? 1 : 0) : undefined,
875
- rows: countKnown ? counted + 1 : undefined,
1089
+ rows: countKnown ? counted + 1 + (this.moreDrawn ? 1 : 0) : undefined,
876
1090
  };
877
1091
  return html `<div part="toolbar"><slot name="toolbar"></slot></div>
878
1092
  <div
@@ -888,7 +1102,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
888
1102
  : html `<slot name="columns" @slotchange=${this.handleSlotChange}></slot>`}
889
1103
  </div>
890
1104
  ${this.renderSpacer('before', kept)}${this.renderRows(rows, page)}${this.renderSpacer('after', kept)}
891
- ${this.renderStatus(filled, 'gridcell')}
1105
+ ${this.renderMore(page)}${this.renderStatus(filled, 'gridcell')}
892
1106
  </div>
893
1107
  ${this.renderLive(filled)}
894
1108
  <div part="footer">
@@ -918,7 +1132,10 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
918
1132
  connectedCallback() {
919
1133
  super.connectedCallback();
920
1134
  this.addEventListener('click', this.handleHeadClick);
1135
+ this.addEventListener('pointerdown', this.notePress, true);
1136
+ this.addEventListener('keydown', this.notePress, true);
921
1137
  this.addEventListener('focusin', this.handleFocusIn);
1138
+ this.renderRoot.addEventListener('focusin', this.handleFocusIn);
922
1139
  this.addEventListener('keydown', this.handleGridKey);
923
1140
  this.addEventListener('keydown', this.handleGripKey);
924
1141
  this.addEventListener('pointerdown', this.handleGripDown);
@@ -927,7 +1144,10 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
927
1144
  disconnectedCallback() {
928
1145
  super.disconnectedCallback();
929
1146
  this.removeEventListener('click', this.handleHeadClick);
1147
+ this.removeEventListener('pointerdown', this.notePress, true);
1148
+ this.removeEventListener('keydown', this.notePress, true);
930
1149
  this.removeEventListener('focusin', this.handleFocusIn);
1150
+ this.renderRoot.removeEventListener('focusin', this.handleFocusIn);
931
1151
  this.removeEventListener('keydown', this.handleGridKey);
932
1152
  this.removeEventListener('keydown', this.handleGripKey);
933
1153
  this.removeEventListener('pointerdown', this.handleGripDown);
@@ -941,6 +1161,9 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
941
1161
  ? this.partNamed('select-all')
942
1162
  : (this.shownColumns[column - offset]?.cellBox ?? null);
943
1163
  }
1164
+ if (this.moreDrawn && row === this.drawnPage.length) {
1165
+ return this.partNamed('more-cell');
1166
+ }
944
1167
  const line = this.renderRoot.querySelector(`[part="row"][data-row="${row}"]`);
945
1168
  return line?.querySelector(`:scope > [data-column="${column}"]`) ?? null;
946
1169
  }
@@ -961,7 +1184,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
961
1184
  }
962
1185
  const role = node.getAttribute('role');
963
1186
  if (node.dataset.column !== undefined &&
964
- (role === 'gridcell' || role === 'columnheader') &&
1187
+ (role === 'gridcell' || role === 'columnheader' || role === 'rowheader') &&
965
1188
  node.getRootNode() === this.renderRoot) {
966
1189
  const line = node.parentElement?.dataset.row;
967
1190
  return {
@@ -1067,9 +1290,38 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1067
1290
  box.scrollLeft += rect.right - frame.right;
1068
1291
  }
1069
1292
  }
1293
+ treeKey(event, place) {
1294
+ const row = this.drawnPage[place.row];
1295
+ const header = this.headerColumn;
1296
+ if (!this.childField || !row || !header) {
1297
+ return false;
1298
+ }
1299
+ if (place.column !== this.shownColumns.indexOf(header) + (this.selectable ? 1 : 0)) {
1300
+ return false;
1301
+ }
1302
+ const placed = this.tree().placed.get(row);
1303
+ const forward = inlineStep(this, event.key) > 0;
1304
+ if (placed?.branch && placed.open !== forward) {
1305
+ event.preventDefault();
1306
+ this.toggleOpen(row, forward);
1307
+ return true;
1308
+ }
1309
+ const parent = placed?.parent ? this.drawnPage.indexOf(placed.parent) : -1;
1310
+ if (!forward && parent >= 0) {
1311
+ event.preventDefault();
1312
+ this.moveTo(parent, place.column);
1313
+ return true;
1314
+ }
1315
+ return false;
1316
+ }
1070
1317
  activate(event, place) {
1071
1318
  const offset = this.selectable ? 1 : 0;
1072
1319
  const heading = place.row === -1 ? this.shownColumns[place.column - offset] : undefined;
1320
+ if (this.moreDrawn && place.row === this.drawnPage.length && event.key !== 'F2') {
1321
+ event.preventDefault();
1322
+ this.askForMore();
1323
+ return;
1324
+ }
1073
1325
  if (event.key !== 'F2') {
1074
1326
  if (heading?.canSort) {
1075
1327
  event.preventDefault();
@@ -1083,7 +1335,11 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1083
1335
  }
1084
1336
  if (event.key === ' ') {
1085
1337
  const row = this.drawnPage[place.row];
1086
- if (this.selectable && row) {
1338
+ if (this.selectable && row && event.shiftKey && this.anchorKey !== undefined) {
1339
+ event.preventDefault();
1340
+ this.selectRange(place.row);
1341
+ }
1342
+ else if (this.selectable && row) {
1087
1343
  event.preventDefault();
1088
1344
  const key = this.keyOf(row, this.rowIndex(place.row) - 2);
1089
1345
  this.chooseRow(key, !this.chosen.has(key));
@@ -1140,6 +1396,12 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1140
1396
  __decorate([
1141
1397
  property({ attribute: 'ascending-label' })
1142
1398
  ], HelixHeadlessDataTable.prototype, "ascendingLabel", void 0);
1399
+ __decorate([
1400
+ property({ attribute: 'child-field', reflect: true })
1401
+ ], HelixHeadlessDataTable.prototype, "childField", void 0);
1402
+ __decorate([
1403
+ property({ attribute: 'default-expanded' })
1404
+ ], HelixHeadlessDataTable.prototype, "defaultExpanded", void 0);
1143
1405
  __decorate([
1144
1406
  property({ attribute: 'default-filter' })
1145
1407
  ], HelixHeadlessDataTable.prototype, "defaultFilter", void 0);
@@ -1164,6 +1426,9 @@ __decorate([
1164
1426
  __decorate([
1165
1427
  property({ type: Number })
1166
1428
  ], HelixHeadlessDataTable.prototype, "estimate", void 0);
1429
+ __decorate([
1430
+ property()
1431
+ ], HelixHeadlessDataTable.prototype, "expanded", null);
1167
1432
  __decorate([
1168
1433
  property()
1169
1434
  ], HelixHeadlessDataTable.prototype, "filter", null);
@@ -1173,6 +1438,12 @@ __decorate([
1173
1438
  __decorate([
1174
1439
  property()
1175
1440
  ], HelixHeadlessDataTable.prototype, "label", void 0);
1441
+ __decorate([
1442
+ property({ attribute: 'load-more', type: Boolean })
1443
+ ], HelixHeadlessDataTable.prototype, "loadMore", void 0);
1444
+ __decorate([
1445
+ property({ attribute: 'load-more-label' })
1446
+ ], HelixHeadlessDataTable.prototype, "loadMoreLabel", void 0);
1176
1447
  __decorate([
1177
1448
  property()
1178
1449
  ], HelixHeadlessDataTable.prototype, "mode", void 0);
@@ -1266,6 +1537,9 @@ __decorate([
1266
1537
  __decorate([
1267
1538
  state()
1268
1539
  ], HelixHeadlessDataTable.prototype, "selectedState", void 0);
1540
+ __decorate([
1541
+ state()
1542
+ ], HelixHeadlessDataTable.prototype, "expandedState", void 0);
1269
1543
  __decorate([
1270
1544
  state()
1271
1545
  ], HelixHeadlessDataTable.prototype, "activeRow", void 0);
@@ -0,0 +1,40 @@
1
+ import { type Axis, type DataRow } from './root/order.js';
2
+ /** Where a row sits in the tree. */
3
+ export interface Placement {
4
+ /** Whether it has children to show. */
5
+ branch: boolean;
6
+ /** How deep the row is, from 1 for a row with no parent. */
7
+ level: number;
8
+ /** Whether its children are shown. */
9
+ open: boolean;
10
+ /** The row it sits under, if any. */
11
+ parent: DataRow | undefined;
12
+ /** Its place among its siblings, from 1. */
13
+ position: number;
14
+ /** How many siblings it has, itself included. */
15
+ siblings: number;
16
+ }
17
+ /** The rows a tree shows, and the counts around them. */
18
+ export interface Grown {
19
+ /** Every row in the tree, shown or not, for a selection of every row. */
20
+ every: DataRow[];
21
+ /** Where each shown row sits. */
22
+ placed: Map<DataRow, Placement>;
23
+ /** The rows shown, in reading order. */
24
+ rows: DataRow[];
25
+ /** How many rows the tree holds before filtering. */
26
+ total: number;
27
+ }
28
+ /** The rows under `row`, read from `field`: an array, or none. */
29
+ export declare const childrenOf: (row: DataRow, field: string) => DataRow[];
30
+ /**
31
+ * The tree laid out flat: each shown row followed by its children where it is
32
+ * open, which `isOpen` says. While `query` is set, a row with a match under it
33
+ * is open whatever `isOpen` says.
34
+ */
35
+ export declare const grow: (rows: readonly DataRow[], axes: readonly Axis[], { field, isOpen, query, spec, }: {
36
+ field: string;
37
+ isOpen: (row: DataRow) => boolean;
38
+ query: string;
39
+ spec: string;
40
+ }) => Grown;