@fusion.dev/helix-headless 0.159.0 → 0.161.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 (61) hide show
  1. package/custom-elements.json +2980 -189
  2. package/dist/canvas/root/canvas-root.d.ts +59 -2
  3. package/dist/canvas/root/canvas-root.js +123 -8
  4. package/dist/chat/message/chat-message.d.ts +8 -0
  5. package/dist/chat/message/chat-message.js +11 -0
  6. package/dist/chat/root/chat-root.d.ts +47 -1
  7. package/dist/chat/root/chat-root.js +130 -4
  8. package/dist/code-block/lines.d.ts +12 -0
  9. package/dist/code-block/lines.js +41 -0
  10. package/dist/code-block/root/code-block-root.d.ts +88 -5
  11. package/dist/code-block/root/code-block-root.js +154 -10
  12. package/dist/date-input/root/date-input-root.d.ts +108 -19
  13. package/dist/date-input/root/date-input-root.js +112 -13
  14. package/dist/date-picker/root/date-picker-root.d.ts +81 -16
  15. package/dist/date-picker/root/date-picker-root.js +140 -36
  16. package/dist/element-events.generated.js +6 -0
  17. package/dist/format-date/fields.js +1 -0
  18. package/dist/format-date/options.d.ts +3 -1
  19. package/dist/format-date/options.js +4 -1
  20. package/dist/format-date/root/format-date-root.d.ts +27 -11
  21. package/dist/format-date/root/format-date-root.js +6 -0
  22. package/dist/listbox/root/listbox-root.js +5 -0
  23. package/dist/notifications/notification/notification.d.ts +1 -0
  24. package/dist/notifications/notification/notification.js +2 -1
  25. package/dist/slider/root/slider-root.d.ts +54 -3
  26. package/dist/slider/root/slider-root.js +124 -2
  27. package/dist/splitter/root/splitter-root.d.ts +15 -0
  28. package/dist/splitter/root/splitter-root.js +16 -2
  29. package/dist/table/grid-controller.d.ts +7 -1
  30. package/dist/table/grid-controller.js +9 -1
  31. package/dist/table/root/table-root.d.ts +122 -0
  32. package/dist/table/root/table-root.js +293 -17
  33. package/dist/table/row/table-row.d.ts +41 -0
  34. package/dist/table/row/table-row.js +56 -2
  35. package/dist/time-input/root/time-input-root.d.ts +55 -7
  36. package/dist/time-input/root/time-input-root.js +70 -10
  37. package/dist/time-picker/root/time-picker-root.d.ts +27 -5
  38. package/dist/time-picker/root/time-picker-root.js +52 -13
  39. package/dist/timeline/item/timeline-item.d.ts +47 -10
  40. package/dist/timeline/item/timeline-item.js +36 -4
  41. package/dist/toggle/group/toggle-group.d.ts +18 -1
  42. package/dist/toggle/group/toggle-group.js +8 -2
  43. package/dist/toggle/root/toggle-root.d.ts +2 -2
  44. package/dist/toggle/root/toggle-root.js +2 -1
  45. package/dist/video-playlist/item/video-playlist-item.d.ts +11 -0
  46. package/dist/video-playlist/item/video-playlist-item.js +5 -0
  47. package/dist/video-playlist/menu/video-playlist-menu.d.ts +6 -0
  48. package/dist/video-playlist/menu/video-playlist-menu.js +11 -0
  49. package/dist/video-playlist/root/video-playlist-root.d.ts +59 -5
  50. package/dist/video-playlist/root/video-playlist-root.js +82 -10
  51. package/dist/virtual-list/fill.d.ts +6 -1
  52. package/dist/virtual-list/fill.js +1 -1
  53. package/dist/virtual-list/keys.d.ts +8 -0
  54. package/dist/virtual-list/keys.js +20 -0
  55. package/dist/virtual-list/root/virtual-list-root.d.ts +98 -13
  56. package/dist/virtual-list/root/virtual-list-root.js +195 -20
  57. package/lint.json +1 -1
  58. package/package.json +5 -5
  59. package/types/jsx.d.ts +1596 -224
  60. package/types/vue.d.ts +404 -49
  61. package/types/words.d.ts +151 -0
@@ -28,6 +28,10 @@ declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion
28
28
  * `min-steps-between-thumbs` keeps them that many steps apart, and the readout
29
29
  * joins the two as `range-text` writes them.
30
30
  *
31
+ * A press on a range's track takes the nearest thumb to the point pressed,
32
+ * and moving the pointer drags it, as React Aria's and Web Awesome's sliders
33
+ * do. The thumb takes focus, and `change` is sent as the press ends.
34
+ *
31
35
  * `origin` sets where a single thumb's fill starts: at `min`, at the middle of
32
36
  * the track, or at `max`. A balance or an offset from zero fills from the
33
37
  * middle, as Zag's slider `origin` does. The fill's ends are published as
@@ -52,7 +56,7 @@ declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion
52
56
  * |---|---|
53
57
  * | Right, Up | Moves the thumb up by `step`. |
54
58
  * | Left, Down | Moves the thumb down by `step`. |
55
- * | Page Up, Page Down | Moves the thumb by a larger step. |
59
+ * | Page Up, Page Down | Moves the thumb by `page-step`, or by the platform's larger step where it is not set. |
56
60
  * | Home, End | Moves the thumb to its lowest or highest value. |
57
61
  *
58
62
  * Under `readonly` none of these move the thumb, and Tab still reaches it.
@@ -97,6 +101,8 @@ declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion
97
101
  * @cssproperty --slider-marker-at - Where one marker sits, as a percentage of the track.
98
102
  * @cssproperty --slider-to - Where the filled span ends, as a fraction of the track, 0 to 1: a range's upper thumb, or the farther of a single thumb and its `origin`.
99
103
  *
104
+ * @cssstate dragging - A thumb is being dragged, by its thumb or from a press on the track.
105
+ * @cssstate focus-visible - A thumb has focus that the keyboard brought, where the browser draws a focus ring.
100
106
  * @cssstate invalid - The slider has an error to show, from the browser's validation or from your `feedback`.
101
107
  */
102
108
  export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
@@ -193,6 +199,12 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
193
199
  * between its thumbs and ignores it.
194
200
  */
195
201
  origin?: 'center' | 'end' | 'start';
202
+ /**
203
+ * How far Page Up and Page Down move a thumb, in the slider's own units, as
204
+ * React Aria's `pageSize` and Zag's `largeStep` do. Absent leaves the keys
205
+ * to the platform, which moves a tenth of the track.
206
+ */
207
+ pageStep?: number;
196
208
  /**
197
209
  * Adds a second thumb, so the value is a pair.
198
210
  *
@@ -298,13 +310,52 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
298
310
  /** Where one value sits along the track, as a fraction of it. */
299
311
  private along;
300
312
  private readonly handleInput;
313
+ /** Whether a pointer is dragging a thumb, which `:state(dragging)` says. */
314
+ private dragging;
315
+ /** Ends a drag when its pointer lifts, wherever it lifts. */
316
+ private readonly endDrag;
317
+ private startDrag;
318
+ /**
319
+ * Half a thumb, which is where the thumb's center stops short of each end
320
+ * of its input. Measured off the thumb's length as the stylesheet sets it,
321
+ * or a platform thumb's 16 pixels where none does.
322
+ */
323
+ private thumbInset;
324
+ /**
325
+ * The value under a pointer, along a thumb's input, less `inset` at each
326
+ * end. Upward on a vertical track, and from the right on a right-to-left
327
+ * page.
328
+ */
329
+ private valueAt;
330
+ /** Moves one of a range's thumbs, kept between its neighbors, and says whether it moved. */
331
+ private moveThumb;
332
+ /**
333
+ * A press on the field: on a thumb, the start of the platform's own drag,
334
+ * and on a range's track, the nearest thumb taken to the point and dragged
335
+ * from there.
336
+ *
337
+ * **A range's inputs take presses on their thumbs only**, being stacked on
338
+ * one track, so a press beside a thumb reached no input at all and did
339
+ * nothing. Measured before this: a press at 90 on a range holding 20 and 60
340
+ * left it at 20 and 60. React Aria's and Web Awesome's sliders move the
341
+ * nearest thumb there.
342
+ */
343
+ private readonly handlePress;
344
+ disconnectedCallback(): void;
301
345
  /** Relayed, because `change` does not cross a shadow boundary on its own. */
302
346
  private readonly handleChange;
303
347
  /**
304
- * Keeps a read-only thumb still under the keys that move it. Tab and every
305
- * other key pass, so focus moves on.
348
+ * Keeps a read-only thumb still under the keys that move it, and moves a
349
+ * thumb by `page-step` under Page Up and Page Down. Tab and every other key
350
+ * pass, so focus moves on.
306
351
  */
307
352
  private readonly handleKeydown;
353
+ /**
354
+ * Says whether the keyboard brought focus to a thumb, as
355
+ * `:state(focus-visible)` on the host, where a stylesheet outside can reach
356
+ * the whole control: a ring around the track, or the readout in bold.
357
+ */
358
+ private readonly handleFocusChange;
308
359
  /**
309
360
  * Reports the value: one entry, or for a named range one entry per thumb.
310
361
  * The joined pair is the state the browser restores.
@@ -4,9 +4,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, formatNumber, formatWords, HelixFormElement, MixinControllerValue, MixinField, reportValues, restoreValue, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, formatNumber, formatWords, HelixFormElement, isRtl, MixinControllerValue, MixinField, reportValues, restoreValue, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
- import { property } from 'lit/decorators.js';
9
+ import { property, state } from 'lit/decorators.js';
10
10
  const SLIDER_KEYS = new Set([
11
11
  'ArrowDown',
12
12
  'ArrowLeft',
@@ -63,6 +63,54 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
63
63
  this.requestValue(next);
64
64
  restoreValue(control, this.value);
65
65
  };
66
+ this.dragging = false;
67
+ this.endDrag = () => {
68
+ this.dragging = false;
69
+ window.removeEventListener('pointerup', this.endDrag);
70
+ window.removeEventListener('pointercancel', this.endDrag);
71
+ };
72
+ this.handlePress = (event) => {
73
+ if (this.isDisabled || this.readOnly || event.button !== 0) {
74
+ return;
75
+ }
76
+ const inputs = [...this.renderRoot.querySelectorAll('input[part="control"]')];
77
+ if (inputs.includes(event.composedPath()[0])) {
78
+ this.startDrag();
79
+ return;
80
+ }
81
+ const first = inputs[0];
82
+ const field = event.currentTarget;
83
+ if (!this.range || !first) {
84
+ return;
85
+ }
86
+ const inset = this.thumbInset(field);
87
+ const wanted = this.valueAt(event, first, inset);
88
+ let at = 0;
89
+ this.points.forEach((one, index) => {
90
+ const distance = Math.abs(one - wanted);
91
+ const best = Math.abs((this.points[at] ?? 0) - wanted);
92
+ if (distance < best || (distance === best && wanted > one)) {
93
+ at = index;
94
+ }
95
+ });
96
+ event.preventDefault();
97
+ this.moveThumb(at, wanted);
98
+ inputs[at]?.focus({ focusVisible: false, preventScroll: true });
99
+ this.startDrag();
100
+ field.setPointerCapture?.(event.pointerId);
101
+ const move = (next) => {
102
+ this.moveThumb(at, this.valueAt(next, first, inset));
103
+ };
104
+ const end = () => {
105
+ field.removeEventListener('pointermove', move);
106
+ field.removeEventListener('pointerup', end);
107
+ field.removeEventListener('pointercancel', end);
108
+ this.announceChange();
109
+ };
110
+ field.addEventListener('pointermove', move);
111
+ field.addEventListener('pointerup', end);
112
+ field.addEventListener('pointercancel', end);
113
+ };
66
114
  this.handleChange = (event) => {
67
115
  event.stopPropagation();
68
116
  if (!this.readOnly) {
@@ -72,7 +120,31 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
72
120
  this.handleKeydown = (event) => {
73
121
  if (this.readOnly && SLIDER_KEYS.has(event.key)) {
74
122
  event.preventDefault();
123
+ return;
124
+ }
125
+ const by = this.pageStep;
126
+ if ((event.key !== 'PageUp' && event.key !== 'PageDown') || !by || this.isDisabled) {
127
+ return;
75
128
  }
129
+ event.preventDefault();
130
+ const sign = event.key === 'PageUp' ? 1 : -1;
131
+ if (this.range) {
132
+ const at = Number(event.target.dataset.index ?? 0);
133
+ if (!this.moveThumb(at, this.settled((this.points[at] ?? this.min) + sign * by))) {
134
+ return;
135
+ }
136
+ }
137
+ else {
138
+ const next = String(this.settled(Number(this.value) + sign * by));
139
+ if (next === this.value || !this.requestValue(next)) {
140
+ return;
141
+ }
142
+ }
143
+ this.announceChange();
144
+ };
145
+ this.handleFocusChange = (event) => {
146
+ const input = event.target;
147
+ this.toggleState('focus-visible', event.type === 'focusin' && input.matches?.(':focus-visible') === true);
76
148
  };
77
149
  this.valueText = '';
78
150
  this.handleThumbSlot = () => {
@@ -132,6 +204,46 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
132
204
  }
133
205
  return Math.min(1, Math.max(0, (at - this.min) / span));
134
206
  }
207
+ startDrag() {
208
+ this.dragging = true;
209
+ window.addEventListener('pointerup', this.endDrag);
210
+ window.addEventListener('pointercancel', this.endDrag);
211
+ }
212
+ thumbInset(field) {
213
+ const probe = document.createElement('span');
214
+ probe.style.cssText =
215
+ 'position:absolute;visibility:hidden;inline-size:var(--_slider-thumb-length,16px)';
216
+ field.append(probe);
217
+ const length = probe.getBoundingClientRect().width;
218
+ probe.remove();
219
+ return length / 2;
220
+ }
221
+ valueAt(event, input, inset) {
222
+ const box = input.getBoundingClientRect();
223
+ const vertical = this.orientation === 'vertical';
224
+ const length = vertical ? box.height : box.width;
225
+ const from = vertical
226
+ ? box.bottom - event.clientY
227
+ : isRtl(this)
228
+ ? box.right - event.clientX
229
+ : event.clientX - box.left;
230
+ const fraction = Math.min(1, Math.max(0, (from - inset) / Math.max(1, length - inset * 2)));
231
+ return this.settled(this.min + fraction * (this.max - this.min));
232
+ }
233
+ moveThumb(at, wanted) {
234
+ const points = this.points;
235
+ const gap = this.gap;
236
+ const low = at > 0 ? (points[at - 1] ?? this.min) + gap : Number.NEGATIVE_INFINITY;
237
+ const high = at < points.length - 1 ? (points[at + 1] ?? this.max) - gap : Number.POSITIVE_INFINITY;
238
+ const next = points
239
+ .map((one, index) => (index === at ? Math.min(high, Math.max(low, wanted)) : one))
240
+ .join(',');
241
+ return next !== points.join(',') && this.requestValue(next);
242
+ }
243
+ disconnectedCallback() {
244
+ super.disconnectedCallback();
245
+ this.endDrag();
246
+ }
135
247
  sync() {
136
248
  if (this.range && this.name) {
137
249
  reportValues(this.internals, this.name, this.points.map(String));
@@ -168,6 +280,7 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
168
280
  }
169
281
  willUpdate(changed) {
170
282
  super.willUpdate(changed);
283
+ this.toggleState('dragging', this.dragging);
171
284
  if (!this.hasUpdated && this.value === '') {
172
285
  this.defaultValue = this.range
173
286
  ? `${this.min},${this.max}`
@@ -306,6 +419,9 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
306
419
  : nothing}
307
420
  <div
308
421
  part="field"
422
+ @focusin=${this.handleFocusChange}
423
+ @focusout=${this.handleFocusChange}
424
+ @pointerdown=${this.handlePress}
309
425
  data-markers=${this.markerValues.length > 0 ? '' : nothing}
310
426
  data-range=${this.range ? '' : nothing}
311
427
  data-thumbs=${this.thumbContent ? '' : nothing}
@@ -405,6 +521,9 @@ __decorate([
405
521
  __decorate([
406
522
  property({ reflect: true })
407
523
  ], HelixHeadlessSlider.prototype, "origin", void 0);
524
+ __decorate([
525
+ property({ attribute: 'page-step', type: Number })
526
+ ], HelixHeadlessSlider.prototype, "pageStep", void 0);
408
527
  __decorate([
409
528
  property({ reflect: true, type: Boolean })
410
529
  ], HelixHeadlessSlider.prototype, "range", void 0);
@@ -428,6 +547,9 @@ __decorate([
428
547
  __decorate([
429
548
  property({ type: Boolean })
430
549
  ], HelixHeadlessSlider.prototype, "tooltip", void 0);
550
+ __decorate([
551
+ state()
552
+ ], HelixHeadlessSlider.prototype, "dragging", void 0);
431
553
  __decorate([
432
554
  property({ attribute: 'value-text' })
433
555
  ], HelixHeadlessSlider.prototype, "valueText", void 0);
@@ -123,6 +123,16 @@ export declare class HelixHeadlessSplitter extends HelixElement {
123
123
  * The name of the handle where two dividers cross, read by screen readers.
124
124
  */
125
125
  cornerLabel: string;
126
+ /**
127
+ * What the handle where two dividers cross says it is set to, both dividers
128
+ * at once: `{across}` is where the divider that runs down stands, and
129
+ * `{down}` where the one that runs across stands, each a percentage.
130
+ *
131
+ * **Both, because the handle moves both**, and a number names one: the
132
+ * handle's `aria-valuenow` is the divider its `aria-orientation` names, and
133
+ * these words are the whole of where it is.
134
+ */
135
+ cornerValueLabel: string;
126
136
  /**
127
137
  * Which pane starts shut, when `collapsed` is not set.
128
138
  */
@@ -410,6 +420,11 @@ export declare class HelixHeadlessSplitter extends HelixElement {
410
420
  * a splitter told `horizontal` has been given its answer already.
411
421
  */
412
422
  private watchWidth;
423
+ /**
424
+ * Where a crossing stands in words: this splitter's divider at `own` and the
425
+ * nested one's at `theirs`, as `corner-value-label` puts them.
426
+ */
427
+ private cornerWords;
413
428
  /**
414
429
  * Whether the panes are stacked. Public because a nested splitter compares
415
430
  * its axis with the one it is in.
@@ -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 HelixHeadlessSplitter_1;
8
- import { AriaForwardController, capturePointer, closestInstance, commaList, customElement, formatPercent, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixCollapseEvent, HelixElement, HelixResizedEvent, inlineStep, isRtl, requestChange, words, wordsLocale, } from '@fusion.dev/helix-core';
8
+ import { AriaForwardController, capturePointer, closestInstance, commaList, customElement, formatPercent, formatWords, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixCollapseEvent, HelixElement, HelixResizedEvent, inlineStep, isRtl, requestChange, words, wordsLocale, } from '@fusion.dev/helix-core';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { snapTo } from '../snap.js';
@@ -168,6 +168,15 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
168
168
  });
169
169
  this.sizes.observe(this);
170
170
  }
171
+ cornerWords(own, theirs) {
172
+ const locale = wordsLocale(this);
173
+ const mine = formatPercent(Math.round(own) / 100, locale);
174
+ const nested = formatPercent(Math.round(theirs) / 100, locale);
175
+ return formatWords(this, 'cornerValueLabel', {
176
+ across: this.vertical ? nested : mine,
177
+ down: this.vertical ? mine : nested,
178
+ });
179
+ }
171
180
  get vertical() {
172
181
  return this.orientation === 'vertical' || (this.orientation === 'auto' && this.stacked);
173
182
  }
@@ -269,6 +278,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
269
278
  this.collapsedLabel = 'Collapsed';
270
279
  this.collapsedSize = 0;
271
280
  this.cornerLabel = 'Resize both';
281
+ this.cornerValueLabel = '{across} across, {down} down';
272
282
  this.defaultPosition = 50;
273
283
  this.label = 'Resize panes';
274
284
  this.aria = new AriaForwardController(this, {
@@ -633,7 +643,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
633
643
  aria-valuemax="100"
634
644
  aria-valuemin="0"
635
645
  aria-valuenow=${Math.round(crossing.at)}
636
- aria-valuetext=${formatPercent(Math.round(crossing.at) / 100, wordsLocale(this))}
646
+ aria-valuetext=${this.cornerWords(position, crossing.at)}
637
647
  data-cross=${index}
638
648
  style="--splitter-corner-at: ${crossing.at}%; --splitter-position: ${position}%"
639
649
  @dblclick=${this.handleCornerDoubleClick}
@@ -659,6 +669,10 @@ __decorate([
659
669
  words,
660
670
  property({ attribute: 'corner-label' })
661
671
  ], HelixHeadlessSplitter.prototype, "cornerLabel", void 0);
672
+ __decorate([
673
+ words,
674
+ property({ attribute: 'corner-value-label' })
675
+ ], HelixHeadlessSplitter.prototype, "cornerValueLabel", void 0);
662
676
  __decorate([
663
677
  property({ attribute: 'default-collapsed' })
664
678
  ], HelixHeadlessSplitter.prototype, "defaultCollapsed", void 0);
@@ -5,7 +5,7 @@ import type { HelixHeadlessTableColumn } from './column/table-column.js';
5
5
  * A column the table draws before the consumer's cells: the grips of a
6
6
  * `reorderable` table, and the checkboxes of a `selectable` one.
7
7
  */
8
- export type LeadColumn = 'grip' | 'select';
8
+ export type LeadColumn = 'expand' | 'grip' | 'select';
9
9
  /** A place in the grid with the element that holds focus there. */
10
10
  export interface GridCell extends GridPlace {
11
11
  cell: HTMLElement;
@@ -36,6 +36,12 @@ export interface TableGridOptions {
36
36
  selectRange: (to: number, from: number) => boolean;
37
37
  /** Selects every row for Control or Command with A, and says whether it did. */
38
38
  selectAll: () => boolean;
39
+ /**
40
+ * Focus moved to another row by a key with no Shift, row `to`, and
41
+ * `control` says whether Control or Command was held. A table whose moves
42
+ * carry the selection selects the row there.
43
+ */
44
+ arrive?: (to: number, control: boolean) => void;
39
45
  /** Whether the table is a grid now. */
40
46
  on: () => boolean;
41
47
  }
@@ -1,5 +1,9 @@
1
1
  import { deepActiveElement, deepContains, focusableElements, gridMove, inlineStep, pageRows, scrollingAncestor, scrollRoom, selectsAll, Typeahead, typesCharacter, whenHydrated, } from '@fusion.dev/helix-core';
2
- const HEADS = { grip: 'grip-head', select: 'select-all' };
2
+ const HEADS = {
3
+ expand: 'expand-head',
4
+ grip: 'grip-head',
5
+ select: 'select-all',
6
+ };
3
7
  const shown = (element) => {
4
8
  const hidden = element.getAttribute('hidden');
5
9
  return hidden === null || hidden === 'until-found';
@@ -83,6 +87,9 @@ export class TableGridController {
83
87
  to.row >= 0) {
84
88
  this.options.selectRange(to.row, place.row);
85
89
  }
90
+ else if (!event.shiftKey && to.row >= 0 && to.row !== place.row) {
91
+ this.options.arrive?.(to.row, event.ctrlKey || event.metaKey);
92
+ }
86
93
  if (to.row !== place.row || to.column !== place.column) {
87
94
  this.moveTo(to);
88
95
  }
@@ -246,6 +253,7 @@ export class TableGridController {
246
253
  row.gridCell = on;
247
254
  row.gripStop = here && lead[at.column] === 'grip';
248
255
  row.selectStop = here && lead[at.column] === 'select';
256
+ row.expandStop = here && lead[at.column] === 'expand';
249
257
  });
250
258
  }
251
259
  for (const [place, cell] of this.cellsOf(line).entries()) {
@@ -53,6 +53,27 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
53
53
  * Tab reaches each checkbox, and Space ticks it. To act on one row, put a
54
54
  * link or a button in a cell, or give the row an `href`.
55
55
  *
56
+ * `selection-mode="single"` selects one row at a time: ticking a row lets go
57
+ * of the one before, the heading holds no checkbox, and Shift and Control with
58
+ * A take in nothing more. `selection-behavior="replace"` makes a press on a
59
+ * row, anywhere but its own controls, select it alone, as a file list does,
60
+ * and in a `grid` table the arrow keys carry the selection with them. Control
61
+ * or Command with a press adds or takes out the row, and with an arrow moves
62
+ * without selecting. Shift with a press takes in the rows from the one last
63
+ * chosen.
64
+ *
65
+ * **Nesting rows.** A row with `parent` sits under the row of that key, as
66
+ * a file sits in a folder. The table then draws a column of disclosure
67
+ * buttons, under a heading named by `expand-column-label`: a row with rows
68
+ * under it has a button named by `expand-row-label` ("Expand {row}") that
69
+ * says with `aria-expanded` whether it is open, and the rows under a shut row
70
+ * are not drawn. `expanded` holds the open rows' keys, comma separated, and
71
+ * `default-expanded` is where it starts. A press asks with
72
+ * `helix-before-toggle` and tells with `helix-toggle`, both from the row, so
73
+ * you can refuse it or load the rows on the way. A sort orders the rows
74
+ * under each row among themselves, and in a `grid` table the table is a
75
+ * `treegrid` whose rows say their depth.
76
+ *
56
77
  * **Moving rows.** `reorderable` adds a column of grips before the checkboxes,
57
78
  * under a heading named by `row-grips-label`.
58
79
  *
@@ -89,13 +110,20 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
89
110
  * | Enter on a cell of a row with `href` | Follows the row's link. |
90
111
  * | Enter, Space on a sortable heading | Sorts by the column. |
91
112
  * | Space on a row, Enter or Space on its checkbox cell | Selects the row, in a `selectable` table. |
113
+ * | Enter or Space on a disclosure cell | Shows or hides the rows under the row, in a table whose rows nest. |
92
114
  * | Enter or Space on a grip cell | Picks the row up, or drops it, in a `reorderable` table. Up, Down, Home, and End move a row picked up, and Escape puts it back. |
115
+ * | Down, Up, Page Down, Page Up under `selection-behavior="replace"` | Selects the row it moves to alone. Control or Command with the key moves without selecting. |
93
116
  * | Shift with Down or Up | Selects the rows it moves across, in a `selectable` table. |
94
117
  * | Shift with Space | Selects every row from the one last chosen, in a `selectable` table. |
95
118
  * | Control or Command with A | Selects every row, in a `selectable` table. |
96
119
  * | A letter or a word | Moves to the next row whose first cell starts with what you typed. |
97
120
  * | Tab | Leaves the table. |
98
121
  *
122
+ * **Loading more.** `load-more` sends `helix-load-more` once the end of the
123
+ * table comes within a screen of view, in the box that scrolls it or the
124
+ * page, once for each number of rows: add rows and it can send it again. Set
125
+ * `loading` while you fetch, and the table asks for nothing until it clears.
126
+ *
99
127
  * When the rows did not load, set `error-text`, such as "The orders did not
100
128
  * load", and put a button that tries again in the `error` slot. The line shows
101
129
  * after the rows in place of the empty and loading lines, the live region says
@@ -119,11 +147,14 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
119
147
  * @slot loading - Content shown while the rows load, such as a spinner. Falls back to `loading-text`, which the live region still says.
120
148
  *
121
149
  * @event {HelixBeforeReorderEvent} helix-before-reorder - Fired when a row is dropped at a new place, in a `reorderable` table, with every row's key in the new order. Cancelable: cancel it to keep the row where it is.
150
+ * @event {HelixBeforeToggleEvent} helix-before-toggle - Fired from a row when its disclosure button is pressed, with whether it would be open in `detail.checked`. Cancelable: cancel it to keep the row as it is.
151
+ * @event {HelixLoadMoreEvent} helix-load-more - Fired, in a table with `load-more`, when the end of the table comes within a screen of view, once for each number of rows.
122
152
  * @event {HelixReorderEvent} helix-reorder - Fired after `helix-before-reorder` when nobody canceled it. Move your rows to the order in `keys`. Cancelable: cancel it to say the move did not happen.
123
153
  * @event {HelixSelectEvent} helix-select - The selection is about to change. The event carries every selected key, separated by commas. Cancelable: cancel it to keep the current selection.
124
154
  * @event {HelixSelectedEvent} helix-selected - Fired after the selection changes, with every selected key, separated by commas.
125
155
  * @event {HelixSortEvent} helix-sort - A different sort order was requested, as `field:direction`, or empty for the order the rows were written in. Cancelable: cancel it to keep the current order.
126
156
  * @event {HelixSortedEvent} helix-sorted - Fired after the sort order changes, with the new order.
157
+ * @event {HelixToggleEvent} helix-toggle - Fired from a row once the rows under it are shown or hidden, with whether it is open in `detail.checked`.
127
158
  *
128
159
  * @csspart checkbox - The checkbox in the heading that selects every row, in a `selectable` table.
129
160
  * @csspart checkbox-box - The box drawn for the checkbox's input.
@@ -139,6 +170,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
139
170
  * @csspart drag-preview - A copy of the row a pointer drags by its grip, which follows the pointer in the top layer until the row is let go, in a `reorderable` table. Hidden from assistive technology.
140
171
  * @csspart empty - The text shown when there are no rows.
141
172
  * @csspart error - The line shown after the rows while `error-text` is set, in place of the empty and loading text.
173
+ * @csspart expand-head - The heading over the disclosure buttons, in a table whose rows nest.
142
174
  * @csspart grid-end - An empty box after the rows, in a `grid` table. Tab focuses it on its way out of the table, so the key leaves from the end.
143
175
  * @csspart grid-start - An empty box before the rows, in a `grid` table. Shift with Tab focuses it on its way out of the table.
144
176
  * @csspart grip-head - The heading over the row grips, in a `reorderable` table.
@@ -205,6 +237,11 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
205
237
  * declared in it has nowhere else to be sized from.
206
238
  */
207
239
  columns: string;
240
+ /**
241
+ * The keys of the rows whose rows are shown at the start, separated by
242
+ * commas. Used until `expanded` is set.
243
+ */
244
+ defaultExpanded: string;
208
245
  /**
209
246
  * The keys selected at the start, separated by commas. Used until `selected`
210
247
  * is set.
@@ -234,6 +271,17 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
234
271
  * says it.
235
272
  */
236
273
  errorText: string;
274
+ /** The name of the heading over the disclosure buttons. */
275
+ expandColumnLabel: string;
276
+ /**
277
+ * The keys of the rows whose rows are shown, separated by commas, in a table
278
+ * whose rows nest. A press on a row's disclosure button asks first, and
279
+ * `default-expanded` seeds what this element then owns.
280
+ */
281
+ get expanded(): string;
282
+ set expanded(next: string);
283
+ /** The name of a row's disclosure button. `{row}` is the words of the row's first cell. */
284
+ expandRowLabel: string;
237
285
  /**
238
286
  * The table's name where neither `label` nor an `aria-label` or
239
287
  * `aria-labelledby` on the host gives it one.
@@ -256,6 +304,11 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
256
304
  * wins over this.
257
305
  */
258
306
  label: string;
307
+ /**
308
+ * Sends `helix-load-more` once the end of the table comes within a screen
309
+ * of view, so you can add the next rows as a reader nears them.
310
+ */
311
+ loadMore: boolean;
259
312
  /**
260
313
  * What screen readers hear each time a key moves a held row, in a
261
314
  * `reorderable` table. `{name}` is the row's words, `{position}` the place it
@@ -286,6 +339,11 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
286
339
  selectable: boolean;
287
340
  /** The name of the checkbox in the heading that selects every row. */
288
341
  selectAllLabel: string;
342
+ /**
343
+ * The name of the heading over the checkboxes under
344
+ * `selection-mode="single"`, where it holds no checkbox of its own.
345
+ */
346
+ selectColumnLabel: string;
289
347
  /**
290
348
  * The keys of the selected rows, separated by commas.
291
349
  *
@@ -302,6 +360,17 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
302
360
  * {# wybranych} other {# wybranego}}`.
303
361
  */
304
362
  selectedLabel: string;
363
+ /**
364
+ * What a press on a row, or in a `grid` table a key that moves to one, does
365
+ * to the rows already selected: `toggle`, the default, leaves them to the
366
+ * checkboxes, and `replace` selects the row alone.
367
+ */
368
+ selectionBehavior: 'replace' | 'toggle';
369
+ /**
370
+ * How many rows a `selectable` table selects: `multiple`, the default, or
371
+ * `single`, one at a time.
372
+ */
373
+ selectionMode?: 'multiple' | 'single';
305
374
  /**
306
375
  * The name of a row's checkbox. `{row}` is replaced with the words of the
307
376
  * row's first cell.
@@ -329,6 +398,16 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
329
398
  unsortedLabel: string;
330
399
  private sortState;
331
400
  private selectedState;
401
+ private expandedState;
402
+ /** Whether any row sits under another, which makes the table a tree. */
403
+ private get nests();
404
+ /** The keys of the rows whose rows are shown. */
405
+ private get open();
406
+ /**
407
+ * Shows or hides the rows under the row of `key`, asking first from the row,
408
+ * and saying so from it once it stands.
409
+ */
410
+ private toggleRow;
332
411
  /** Whether the heading names the table, so taking it away clears only this element's own name. */
333
412
  private headed;
334
413
  /**
@@ -377,6 +456,15 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
377
456
  * which is what a framework holding those rows needs.
378
457
  */
379
458
  private get arrangedRows();
459
+ /**
460
+ * The rows of a tree in the order they are drawn: each row followed by the
461
+ * rows under it while it is open, at every depth, keeping the order `rows`
462
+ * is in among the rows under one row. A row under a key no row has is drawn
463
+ * at the top.
464
+ */
465
+ private nested;
466
+ /** How deep each row sits, from 1, and whether rows sit under it, by key. */
467
+ private shape;
380
468
  /** The sort last heard, so a render that changed nothing says nothing. */
381
469
  private heardSort;
382
470
  /**
@@ -419,6 +507,22 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
419
507
  * them where each already is. A `disabled` row keeps what it had.
420
508
  */
421
509
  private chooseAll;
510
+ /** Whether the table selects one row at a time. */
511
+ private get single();
512
+ /** Whether a press or a move on a row selects it alone. */
513
+ private get replaces();
514
+ /**
515
+ * Selects the drawn row `at` alone, and makes it where a range runs from.
516
+ * Asks nothing where it is already the only one selected.
517
+ */
518
+ private chooseOnly;
519
+ /**
520
+ * A press on a row under `selection-behavior="replace"`: it selects the row
521
+ * alone, Control or Command adds or takes out the row, and Shift takes in
522
+ * the rows from the one last chosen. A press on the row's own controls, its
523
+ * checkbox or its grip is theirs.
524
+ */
525
+ private readonly handleRowClick;
422
526
  /** The key of the row a Shift range runs from: the one last chosen. */
423
527
  private anchorKey;
424
528
  /**
@@ -588,8 +692,26 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
588
692
  private alignCells;
589
693
  protected willUpdate(changed: PropertyValues): void;
590
694
  protected updated(changed: PropertyValues): void;
695
+ /** Watches the end of a `load-more` table coming near the box that scrolls it. */
696
+ private endWatcher;
697
+ /** The end being watched, so a new one is watched again. */
698
+ private watchedEnd;
699
+ /** The number of rows `helix-load-more` was last sent for. */
700
+ private askedAt;
701
+ /**
702
+ * Starts or stops watching the end of the table, against the box that
703
+ * scrolls it, or the page, with a screen of room below it, so the next rows
704
+ * are asked for before a reader reaches the last. List does the same.
705
+ */
706
+ private watchEnd;
707
+ /** Whether the end of the table was in reach when last measured. */
708
+ private endInView;
709
+ /** Sends `helix-load-more`, once for each number of rows, and never while loading. */
710
+ private askForMore;
591
711
  /** The heading over the row grips, named by `row-grips-label`. */
592
712
  private renderGripHead;
713
+ /** The heading over the disclosure buttons, in a table whose rows nest. */
714
+ private renderExpandHead;
593
715
  /** The heading of the checkbox column, which selects every row. */
594
716
  private renderSelectAll;
595
717
  protected render(): TemplateResult;