@fusion.dev/helix-headless 0.182.2 → 0.184.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.
@@ -1,5 +1,5 @@
1
1
  import { __decorate } from "tslib";
2
- import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, filterKeeps, focusTarget, formatWords, HelixElement, HelixFilterEvent, inlineStep, KeyboardNavigationController, linkDescription, MIRROR_RULE, MixinCollection, MixinControllerOpen, markMatches, OpenPhaseController, onUnlessFalse, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
2
+ import { AriaForwardController, anchorElement, auditEntry, customElement, DrilldownController, deepActiveElement, deepContains, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MIRROR_RULE, MixinCollection, MixinControllerOpen, OpenPhaseController, onUnlessFalse, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
3
3
  import { caretLeft } from '@fusion.dev/helix-icons';
4
4
  import { html, nothing } from 'lit';
5
5
  import { property } from 'lit/decorators.js';
@@ -98,18 +98,13 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
98
98
  surface: () => this.partNamed('surface'),
99
99
  });
100
100
  this.settledOnce = false;
101
- this.filterPage = null;
102
- this.handleFilterInput = (event) => {
103
- event.stopPropagation();
104
- this.filterBy(event.target.value);
105
- };
106
101
  this.followRemoved = (records) => {
107
102
  if (!this.open) {
108
103
  return;
109
104
  }
110
105
  const active = deepActiveElement();
111
106
  const lost = active === null || active === document.body || !active.isConnected;
112
- const record = records.find((one) => [...one.removedNodes].some((node) => isItem(node) && (this.filterable ? node === this.highlighted : lost)));
107
+ const record = records.find((one) => [...one.removedNodes].some((node) => isItem(node) && (this.filters ? node === this.filters.highlighted : lost)));
113
108
  if (!record) {
114
109
  return;
115
110
  }
@@ -125,7 +120,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
125
120
  const next = after(record.nextSibling, 'nextSibling') ??
126
121
  after(record.previousSibling, 'previousSibling') ??
127
122
  items.at(-1);
128
- if (this.filterable || next) {
123
+ if (this.filters || next) {
129
124
  this.reach(next);
130
125
  return;
131
126
  }
@@ -188,23 +183,13 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
188
183
  const items = this.items;
189
184
  const at = items.findIndex((item) => item.active);
190
185
  const along = inlineStep(this, event.key);
191
- const typing = this.filterable && event.composedPath()[0] === this.field;
192
- const step = event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0;
186
+ if (this.filters?.keydown(event, items, at)) {
187
+ event.preventDefault();
188
+ return;
189
+ }
193
190
  if (event.key === 'Escape') {
194
191
  event.stopPropagation();
195
- if (!this.filterable || this.filter === '') {
196
- this.leave();
197
- }
198
- else {
199
- this.filterBy('');
200
- }
201
- }
202
- else if (typing && step !== 0) {
203
- const count = items.length;
204
- this.reach(items[at < 0 ? (step > 0 ? 0 : count - 1) : (at + step + count) % count]);
205
- }
206
- else if (typing && event.key === 'Enter') {
207
- items[at]?.renderRoot.querySelector('[part="item"]')?.click();
192
+ this.leave();
208
193
  }
209
194
  else if (along === 1 && items[at]?.hasSubmenu) {
210
195
  this.enter(items[at]);
@@ -244,14 +229,6 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
244
229
  this.longPress.down(event);
245
230
  }
246
231
  };
247
- this.handleMouseDown = (event) => {
248
- if (this.filterable &&
249
- this.open &&
250
- event.composedPath()[0] !== this.field &&
251
- !this.fromTrigger(event)) {
252
- event.preventDefault();
253
- }
254
- };
255
232
  this.handlePointerMove = (event) => {
256
233
  this.longPress.move(event);
257
234
  };
@@ -406,48 +383,27 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
406
383
  return rows.filter((row) => !row.disabled && this.kept(row));
407
384
  }
408
385
  get menuBox() {
409
- return this.filterable
386
+ return this.filters
410
387
  ? (this.renderRoot?.querySelector('#items') ?? null)
411
388
  : this.partNamed('surface');
412
389
  }
413
- get field() {
414
- return this.partNamed('search-field');
415
- }
416
- get filtering() {
417
- return this.filterable && this.filter.trim() !== '' && this.filter !== this.refused;
390
+ get filters() {
391
+ return this.filterable ? this.filterer : undefined;
418
392
  }
419
393
  kept(row) {
420
- return (!this.filtering ||
421
- this.holderOf(row) !== this.filterPage ||
422
- filterKeeps(slottedWords(row, ['']), this.filter.trim(), this.filterMatch));
394
+ return this.filters?.kept(row) ?? true;
423
395
  }
424
396
  requestFilter(value) {
425
- if (value === this.filter) {
426
- return;
427
- }
428
- this.filterPage = this.flies ? this.keyPage : this.drilldown.page;
429
- this.refused = this.dispatchEvent(new HelixFilterEvent({ value })) ? undefined : value;
430
- this.filter = value;
431
- }
432
- filterBy(value) {
433
- this.requestFilter(value);
434
- void this.updateComplete.then(() => this.reach(this.items[0]));
397
+ this.filterer?.request(value);
435
398
  }
436
399
  reach(row) {
437
- if (!this.filterable) {
400
+ const filters = this.filters;
401
+ if (!filters) {
438
402
  row?.focus();
439
403
  return;
440
404
  }
441
- if (this.highlighted && this.highlighted !== row) {
442
- this.highlighted.highlighted = false;
443
- }
444
- this.highlighted = row;
445
- const field = this.field;
446
- if (field && 'ariaActiveDescendantElement' in field) {
447
- field.ariaActiveDescendantElement = row ?? null;
448
- }
405
+ filters.highlight(row);
449
406
  if (row) {
450
- row.highlighted = true;
451
407
  this.revealRow(row);
452
408
  }
453
409
  }
@@ -460,7 +416,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
460
416
  }
461
417
  this.updateComplete.then(() => {
462
418
  const items = this.items;
463
- this.field?.focus();
419
+ this.filters?.field?.focus();
464
420
  this.reach(end === 'first' ? items[0] : items[items.length - 1]);
465
421
  });
466
422
  }
@@ -610,7 +566,6 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
610
566
  this.addEventListener('focusin', this.handleFocusIn);
611
567
  this.addEventListener('contextmenu', this.handleContextMenu);
612
568
  this.addEventListener('pointerdown', this.handlePointerDown);
613
- this.addEventListener('mousedown', this.handleMouseDown);
614
569
  this.addEventListener('pointermove', this.handlePointerMove);
615
570
  this.addEventListener('pointerup', this.handlePointerUp);
616
571
  this.addEventListener('pointercancel', this.handlePointerCancel);
@@ -635,10 +590,6 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
635
590
  this.removeEventListener('focusin', this.handleFocusIn);
636
591
  this.removeEventListener('contextmenu', this.handleContextMenu);
637
592
  this.removeEventListener('pointerdown', this.handlePointerDown);
638
- this.removeEventListener('mousedown', this.handleMouseDown);
639
- if (this.filterable) {
640
- markMatches(this, [], '');
641
- }
642
593
  this.removeEventListener('pointermove', this.handlePointerMove);
643
594
  this.removeEventListener('pointerup', this.handlePointerUp);
644
595
  this.removeEventListener('pointercancel', this.handlePointerCancel);
@@ -667,7 +618,8 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
667
618
  if (isSection(child)) {
668
619
  child.onPage =
669
620
  top &&
670
- (!this.filtering || [...child.children].some((row) => isItem(row) && this.kept(row)));
621
+ (!this.filters?.filtering ||
622
+ [...child.children].some((row) => isItem(row) && this.kept(row)));
671
623
  for (const row of child.children) {
672
624
  if (row.isMenuItem === true) {
673
625
  this.mark(row, top);
@@ -684,14 +636,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
684
636
  this.toggleState('drilled', !this.flies && !this.drilldown.atRoot);
685
637
  this.markColumns();
686
638
  this.nameSurface();
687
- if (this.filterable) {
688
- markMatches(this, this.rowsOf(this.filterPage).filter((row) => this.kept(row)), this.filtering ? this.filter.trim() : '');
689
- const items = this.items;
690
- if (changed.has('filter') &&
691
- this.open &&
692
- !items.includes(this.highlighted)) {
693
- this.reach(items[0]);
694
- }
639
+ this.filterer?.updated();
640
+ if (this.filterable && !this.filterer) {
641
+ auditEntry(this, 'filterable', 'menu/filter', 'it shows no filter field', () => this.filterable && !this.filterer);
695
642
  }
696
643
  }
697
644
  nameSurface() {
@@ -729,7 +676,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
729
676
  mark(item, onPage) {
730
677
  const page = this.drilldown.page;
731
678
  item.onPage = onPage && this.kept(item);
732
- item.virtual = this.filterable;
679
+ item.virtual = this.filters !== undefined;
733
680
  const holds = page !== null && item.contains(page);
734
681
  item.flyout = this.flies;
735
682
  item.expanded = this.flies && holds;
@@ -753,16 +700,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
753
700
  }
754
701
  render() {
755
702
  const point = this.context && this.point;
756
- const filtering = this.filtering;
757
- const count = filtering
758
- ? this.rowsOf(this.filterPage).filter((row) => this.kept(row)).length
759
- : 0;
760
- const none = filtering && count === 0;
761
- this.announced = none
762
- ? this.noResultsText
763
- : filtering
764
- ? formatWords(this, 'resultsLabel', { count })
765
- : '';
703
+ const filters = this.filters;
704
+ this.announced = '';
705
+ const none = filters?.status();
766
706
  const rows = html `${this.flies || this.drilldown.atRoot
767
707
  ? nothing
768
708
  : html `<div part="header">
@@ -781,9 +721,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
781
721
  <span part="title" id="title">${this.drilldown.label}</span>
782
722
  </div>`}
783
723
  <slot @slotchange=${(event) => this.countRows(event)}></slot>
784
- ${none
785
- ? html `<p part="empty"><slot name="no-results">${this.noResultsText}</slot></p>`
786
- : this.renderStatus(this.hasRows)}`;
724
+ ${none ?? this.renderStatus(this.hasRows)}`;
787
725
  const busy = this.loading ? 'true' : nothing;
788
726
  const label = this.label || nothing;
789
727
  return html `<slot name="trigger" @click=${this.handleTriggerClick}></slot>${point
@@ -796,27 +734,14 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
796
734
  <div
797
735
  part="surface"
798
736
  popover="manual"
799
- role=${this.filterable ? nothing : 'menu'}
800
- aria-busy=${this.filterable ? nothing : busy}
801
- aria-label=${this.filterable ? nothing : label}
737
+ role=${filters ? nothing : 'menu'}
738
+ aria-busy=${filters ? nothing : busy}
739
+ aria-label=${filters ? nothing : label}
802
740
  data-drilled=${this.flies || this.drilldown.atRoot ? nothing : ''}
803
- data-filtered=${filtering ? '' : nothing}
741
+ data-filtered=${filters?.filtering ? '' : nothing}
804
742
  ?hidden=${!this.open}
805
743
  >
806
- ${this.filterable
807
- ? html `<input
808
- part="search-field"
809
- type="text"
810
- autocomplete="off"
811
- spellcheck="false"
812
- .value=${this.filter}
813
- placeholder=${this.filterLabel}
814
- aria-label=${this.filterLabel}
815
- aria-autocomplete="list"
816
- aria-controls="items"
817
- @input=${this.handleFilterInput}
818
- /><div id="items" role="menu" aria-busy=${busy} aria-label=${label}>${rows}</div>`
819
- : rows}
744
+ ${filters ? filters.drawFilter(rows, busy) : rows}
820
745
  </div>${this.renderLive(this.hasRows)}`;
821
746
  }
822
747
  };
@@ -217,6 +217,11 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
217
217
  * on why a wheel cannot do either.
218
218
  */
219
219
  mode?: 'multiple' | 'range';
220
+ /**
221
+ * The words on the list's button that chooses the current time, drawn with
222
+ * `show-now`.
223
+ */
224
+ nowLabel: string;
220
225
  /**
221
226
  * Placeholder text shown before a time is entered.
222
227
  */
@@ -252,6 +257,12 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
252
257
  * See Time Picker's `seconds`.
253
258
  */
254
259
  seconds: boolean;
260
+ /**
261
+ * Draws a button under the times in the list that chooses the current time,
262
+ * moved to the nearest time on offer. See Time Picker's `show-now`. Its words
263
+ * are `now-label`.
264
+ */
265
+ showNow: boolean;
255
266
  /**
256
267
  * How far apart the offered times are, in minutes, or in seconds with
257
268
  * `seconds`.
@@ -21,12 +21,14 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
21
21
  this.locale = '';
22
22
  this.max = '23:59';
23
23
  this.min = '00:00';
24
+ this.nowLabel = 'Now';
24
25
  this.placeholder = 'HH:mm';
25
26
  this.rangeLengthMessage = 'A range is two times.';
26
27
  this.rangeOverflowMessage = 'Choose a time at or before {max}.';
27
28
  this.rangeUnderflowMessage = 'Choose a time at or after {min}.';
28
29
  this.removeLabel = 'Remove {name}';
29
30
  this.seconds = false;
31
+ this.showNow = false;
30
32
  this.step = 15;
31
33
  this.timeStyle = 'local';
32
34
  this.triggerLabel = 'Choose a time';
@@ -246,7 +248,9 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
246
248
  locale=${handedLocale(this) || nothing}
247
249
  max=${this.max}
248
250
  min=${this.min}
251
+ now-label=${this.nowLabel}
249
252
  ?seconds=${this.seconds}
253
+ ?show-now=${this.showNow}
250
254
  step=${this.step}
251
255
  .value=${this.picked}
252
256
  @helix-before-change=${this.handlePicked}
@@ -400,6 +404,10 @@ __decorate([
400
404
  __decorate([
401
405
  property({ reflect: true })
402
406
  ], HelixHeadlessTimeInput.prototype, "mode", void 0);
407
+ __decorate([
408
+ words,
409
+ property({ attribute: 'now-label' })
410
+ ], HelixHeadlessTimeInput.prototype, "nowLabel", void 0);
403
411
  __decorate([
404
412
  words,
405
413
  property()
@@ -423,6 +431,9 @@ __decorate([
423
431
  __decorate([
424
432
  property({ type: Boolean })
425
433
  ], HelixHeadlessTimeInput.prototype, "seconds", void 0);
434
+ __decorate([
435
+ property({ attribute: 'show-now', type: Boolean })
436
+ ], HelixHeadlessTimeInput.prototype, "showNow", void 0);
426
437
  __decorate([
427
438
  property({ type: Number })
428
439
  ], HelixHeadlessTimeInput.prototype, "step", void 0);
@@ -57,7 +57,11 @@ export class VirtualListReorderController {
57
57
  }
58
58
  get enabled() {
59
59
  const host = this.host;
60
- return (host.reorderable && host.columns <= 1 && host.minColumnWidth <= 0 && host.sectionField === '');
60
+ return (host.reorderable &&
61
+ host.columns <= 1 &&
62
+ host.minColumnWidth <= 0 &&
63
+ host.sectionField === '' &&
64
+ host.layout !== 'waterfall');
61
65
  }
62
66
  hostUpdated() {
63
67
  queueMicrotask(() => this.mark());
@@ -61,6 +61,32 @@ export interface VirtualListRange {
61
61
  * smallest item size, and follows the box as it resizes. `gap` spaces the rows, and the columns
62
62
  * of a grid, by a number of pixels the list counts in each row's place.
63
63
  *
64
+ * A grid without `selection-mode` is a grid to a reader, as the APG's grid
65
+ * and React Aria's grid list are: the box is a `grid` with `aria-rowcount`
66
+ * and `aria-colcount`, each line drawn is a `row` with `aria-rowindex`, and
67
+ * each item a `gridcell` with `aria-colindex`. Each line is a slot of the
68
+ * list's own, so your rows stay its children: give an item one element. With
69
+ * `selection-mode` it stays a list box of options, as React Aria's list box
70
+ * in a grid layout does.
71
+ *
72
+ * **A waterfall.** `layout="waterfall"` puts each item, in the order of the
73
+ * set, under the column that ends highest so far, the leftmost of a tie, as a
74
+ * wall of photos of different heights is, and as React Aria's waterfall
75
+ * layout and TanStack Virtual's lanes do. `columns`, or `min-column-width`,
76
+ * says how many columns. Every item is measured, at the width of its column,
77
+ * and placed at its column and its height, so items of any height leave no
78
+ * holes. The list draws the items that reach into the box, and the item
79
+ * holding focus. A reader hears a list, or a list box, in the order of the
80
+ * set, which is also the order Tab and a screen reader's own keys take.
81
+ *
82
+ * In a waterfall, Down and Up move along the column to the next or the
83
+ * previous item in it, Right and Left move to the next or the previous
84
+ * column, to the item there that sits beside this one the most, or the
85
+ * nearest above where none does, and Page Down and Page Up move a box down
86
+ * or up the column. Home and End go to the first and the last item of the
87
+ * set, and Shift with a key takes in the items between in the order of the
88
+ * set.
89
+ *
64
90
  * **Across.** `orientation="horizontal"` lays the items out along a line and
65
91
  * scrolls it sideways, as a strip of cards does, windowed the same way:
66
92
  * `estimate` is then an item's width, and the box needs an inline size.
@@ -79,10 +105,11 @@ export interface VirtualListRange {
79
105
  *
80
106
  * | Key | What it does |
81
107
  * |---|---|
82
- * | Down, Up | Moves to the next or the previous item, or in a grid to the item in the same column on the next or the previous line. |
83
- * | Right, Left | In a grid, moves to the next or the previous item along the line, the other way round on a right-to-left page. In a list that runs across, moves to the next or the previous item the same way, and Down and Up are left to the row. |
84
- * | Home, End | Moves to the first or the last item. |
85
- * | Page Down, Page Up | Moves by the number of items, or of lines in a grid, on screen. |
108
+ * | Down, Up | Moves to the next or the previous item, or in a grid to the item in the same column on the next or the previous line, or in a waterfall to the next or the previous item down the same column. |
109
+ * | Right, Left | In a grid, moves to the next or the previous item along the line, the other way round on a right-to-left page. In a waterfall, moves to the item beside in the next or the previous column. In a list that runs across, moves to the next or the previous item the same way, and Down and Up are left to the row. |
110
+ * | Home, End | Moves to the first or the last item. In a grid without `selection-mode`, to the first or the last item of the line. |
111
+ * | Control or Command with Home or End | In a grid without `selection-mode`, moves to the first or the last item. |
112
+ * | Page Down, Page Up | Moves by the number of items, or of lines in a grid, on screen, or in a waterfall a box down or up the column. |
86
113
  * | A letter or a word | Moves to the next item whose text starts with what you typed. |
87
114
  * | Escape | Lets go of every chosen item, in a list with `selection-mode`. |
88
115
  * | Alt with Up or Down | In a `reorderable` list, moves the item a place. Alt with Home or End moves it to either end. |
@@ -97,8 +124,9 @@ export interface VirtualListRange {
97
124
  * or the item itself when it is a string or a number.
98
125
  *
99
126
  * Each row says its place in the whole set with `aria-posinset` and
100
- * `aria-setsize`, so a screen reader says "4,000 of 10,000". The row holding
101
- * focus stays drawn when it scrolls out.
127
+ * `aria-setsize`, so a screen reader says "4,000 of 10,000", and each cell of
128
+ * a grid its line and its column. The row holding focus stays drawn when it
129
+ * scrolls out.
102
130
  *
103
131
  * **Selecting.** `selection-mode` makes the list a list box a reader chooses
104
132
  * from: `single` selects one item and `multiple` any number. Space or a press
@@ -267,6 +295,15 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
267
295
  * wins over this.
268
296
  */
269
297
  label: string;
298
+ /**
299
+ * How items of more than one column are laid out. `grid`, the default, puts
300
+ * them in lines of equal columns. `waterfall` puts each item, in the order
301
+ * of the set, under the column that ends highest, as a wall of photos of
302
+ * different heights is: `columns`, or `min-column-width`, says how many
303
+ * columns. Rows of a waterfall are measured, and Up and Down move along a
304
+ * column.
305
+ */
306
+ layout: 'grid' | 'waterfall';
270
307
  /**
271
308
  * Marks the list as busy fetching more items, and draws a row after the last
272
309
  * item that says so.
@@ -300,7 +337,7 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
300
337
  * Which way the list runs. `horizontal` lays the items out along a line
301
338
  * and scrolls it sideways: Left and Right move between items, the other
302
339
  * way round on a right-to-left page, and `estimate` is an item's width. A
303
- * grid always runs down.
340
+ * grid and a waterfall always run down.
304
341
  */
305
342
  orientation: 'horizontal' | 'vertical';
306
343
  /**
@@ -324,7 +361,8 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
324
361
  * with an arrow moves the row the keys are on, and a pointer drags a row.
325
362
  * The list moves no item itself: you move your `items` on `helix-reorder`.
326
363
  * Without that import it does nothing, so a list that never moves items
327
- * carries none of the code. A list of one column, without sections.
364
+ * carries none of the code. A list of one column, without sections or a
365
+ * waterfall.
328
366
  */
329
367
  reorderable: boolean;
330
368
  /**
@@ -422,24 +460,42 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
422
460
  get range(): VirtualListRange;
423
461
  /** The gap in pixels, none where it is not a positive number. */
424
462
  private get space();
463
+ /** Where each item of a waterfall stands, which the controller places. */
464
+ private readonly lanes;
465
+ /** Whether the items are laid out as a waterfall, each down a column. */
466
+ private get waterfall();
425
467
  /**
426
- * How many items a line holds: as many as fit at `min-column-width` where
427
- * it is set and the box has a width, and `columns` otherwise, at least one.
468
+ * Whether the list is a grid a reader reads, and not a list box: lines of
469
+ * columns with no `selection-mode`, which are `grid`, `row` and `gridcell`
470
+ * to a reader, as the APG's grid and React Aria's grid list are.
471
+ */
472
+ private get lined();
473
+ /**
474
+ * How many items a line holds, which a waterfall draws one at a time: its
475
+ * columns are not lines.
428
476
  */
429
477
  private get across();
478
+ /**
479
+ * How many columns there are: as many as fit at `min-column-width` where
480
+ * it is set and the box has a width, and `columns` otherwise, at least one.
481
+ */
482
+ private get columnCount();
430
483
  /** How wide the box is, read for `min-column-width`. */
431
484
  private boxWidth;
432
485
  /** Watches the box's width, for `min-column-width`. */
433
486
  private widthWatch;
434
- /** The items to a line the measurements were taken at. */
487
+ /** The items to a line the measurements were taken at, or a waterfall's columns and width. */
435
488
  private measuredAcross;
436
- /** Watches the box for its width while `min-column-width` asks for it. */
489
+ /**
490
+ * Watches the box for its width while `min-column-width` asks for it, or a
491
+ * waterfall, whose items are as tall as their width makes them.
492
+ */
437
493
  private watchWidth;
438
494
  /** The items a span of lines holds. */
439
495
  private itemsOn;
440
496
  /** The line item `at` is on: its entry in a list with sections. */
441
497
  private lineOf;
442
- /** Whether the list runs across, which a grid never does. */
498
+ /** Whether the list runs across, which a grid and a waterfall never do. */
443
499
  private get horizontal();
444
500
  /** The sections and the items they were read from, kept until either changes. */
445
501
  private sectionsRead;
@@ -541,6 +597,14 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
541
597
  * is that control's, so a field in a row keeps its arrows.
542
598
  */
543
599
  private readonly handleKeydown;
600
+ /**
601
+ * Where a key goes in a waterfall, through the columns `Waterfall.step`
602
+ * reads: along the column for Down, Up, Page Down and Page Up, and to the
603
+ * item beside in the next column or the one before for the keys along the
604
+ * line, the other way round on a right-to-left page. Past a disabled item,
605
+ * on the same way.
606
+ */
607
+ private inWaterfall;
544
608
  /** The key of item `at` in `value`. See `keyField`. */
545
609
  private keyAt;
546
610
  /** @internal Every item's key, in the order of the set, which the reorder entry moves. */
@@ -600,15 +664,25 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
600
664
  private keptIndex;
601
665
  /**
602
666
  * Writes a row's role, and whether it is chosen in a list that selects:
603
- * `option` with `aria-selected` there, and `listitem` otherwise, and
604
- * `aria-disabled` on an item `disabled-keys` names. Every pass,
605
- * since a choice changes no row's content.
667
+ * `option` with `aria-selected` there, `gridcell` in a grid, and `listitem`
668
+ * otherwise, and `aria-disabled` on an item `disabled-keys` names. Every
669
+ * pass, since a choice changes no row's content.
670
+ *
671
+ * Its place too: `aria-posinset` and `aria-setsize` in a list, and in a
672
+ * grid its column, with the line's `row` given by the slot it is drawn in.
606
673
  *
607
674
  * `data-selected` and `data-disabled` say the same to a stylesheet, as
608
675
  * React Aria's rows do, so one rule reads the same in either kind of list.
609
676
  */
610
677
  private mark;
611
- /** Writes a row's place in the set. */
678
+ /**
679
+ * Writes which item a row drew, for the controller to measure it against:
680
+ * the real index, the same one `aria-posinset` carries one higher.
681
+ *
682
+ * `mark` writes the place a reader hears from it, the real index and the
683
+ * real total, not the position in the window. A reader arrowing through
684
+ * this should hear where they are in the set.
685
+ */
612
686
  private stamp;
613
687
  /** Fills a drawn row from its item, by `renderItem` or by the template's holes. */
614
688
  private fill;
@@ -642,6 +716,10 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
642
716
  private place;
643
717
  /** The sections the rows were last placed in. */
644
718
  private placedSections;
719
+ /** Whether the rows were last placed in a waterfall. */
720
+ private placedWaterfall;
721
+ /** The lines of a grid drawn on the last pass, each a `row` the render draws a slot in. */
722
+ private drawnLines;
645
723
  /** The function the drawn headers were drawn by, so a new one draws them again. */
646
724
  private headedBy;
647
725
  /** Ties each header's id to this list. */