@fusion.dev/helix-headless 0.160.0 → 0.162.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 (39) hide show
  1. package/custom-elements.json +1729 -50
  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/element-events.generated.js +4 -0
  13. package/dist/splitter/root/splitter-root.d.ts +15 -0
  14. package/dist/splitter/root/splitter-root.js +16 -2
  15. package/dist/table/grid-controller.d.ts +7 -1
  16. package/dist/table/grid-controller.js +9 -1
  17. package/dist/table/root/table-root.d.ts +122 -0
  18. package/dist/table/root/table-root.js +293 -17
  19. package/dist/table/row/table-row.d.ts +41 -0
  20. package/dist/table/row/table-row.js +56 -2
  21. package/dist/timeline/item/timeline-item.d.ts +47 -10
  22. package/dist/timeline/item/timeline-item.js +36 -4
  23. package/dist/video-playlist/item/video-playlist-item.d.ts +11 -0
  24. package/dist/video-playlist/item/video-playlist-item.js +5 -0
  25. package/dist/video-playlist/menu/video-playlist-menu.d.ts +6 -0
  26. package/dist/video-playlist/menu/video-playlist-menu.js +11 -0
  27. package/dist/video-playlist/root/video-playlist-root.d.ts +59 -5
  28. package/dist/video-playlist/root/video-playlist-root.js +82 -10
  29. package/dist/virtual-list/fill.d.ts +6 -1
  30. package/dist/virtual-list/fill.js +1 -1
  31. package/dist/virtual-list/keys.d.ts +8 -0
  32. package/dist/virtual-list/keys.js +20 -0
  33. package/dist/virtual-list/root/virtual-list-root.d.ts +98 -13
  34. package/dist/virtual-list/root/virtual-list-root.js +195 -20
  35. package/lint.json +1 -1
  36. package/package.json +5 -5
  37. package/types/jsx.d.ts +936 -68
  38. package/types/vue.d.ts +226 -12
  39. package/types/words.d.ts +79 -0
@@ -4,14 +4,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, formatWords, HelixElement, words } from '@fusion.dev/helix-core';
7
+ import { customElement, formatWords, HelixBeforeToggleEvent, HelixElement, HelixToggleEvent, requestChange, words, } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
11
11
  import { html } from 'lit/static-html.js';
12
12
  import '../../copy-button/index.js';
13
13
  import { highlightedLines } from '../highlight.js';
14
- import { gutterNumbers, lineCount } from '../lines.js';
14
+ import { gutterNumbers, lineCount, lineRuns, lineSpans } from '../lines.js';
15
15
  import { pairLines, parsePatch } from '../patch.js';
16
16
  let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
17
17
  constructor() {
@@ -28,12 +28,17 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
28
28
  this.label = '';
29
29
  this.language = '';
30
30
  this.lineNumbers = false;
31
+ this.markLines = '';
31
32
  this.noCopy = false;
33
+ this.wrap = false;
34
+ this.wrapLabel = 'Wrap lines';
35
+ this.wrappable = false;
32
36
  this.parsed = { files: [], text: '' };
33
37
  this.capped = false;
34
38
  this.expanded = false;
35
39
  this.measure = () => {
36
40
  this.markScrollers();
41
+ this.placeLines();
37
42
  if (!this.expanded) {
38
43
  this.capped =
39
44
  this.expandable &&
@@ -43,6 +48,20 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
43
48
  };
44
49
  }
45
50
  static { this.words = 'helix-code-block'; }
51
+ toggleWrap() {
52
+ const next = !this.wrap;
53
+ const applied = requestChange({
54
+ apply: (checked) => {
55
+ this.wrap = checked;
56
+ },
57
+ event: (checked) => new HelixBeforeToggleEvent({ checked }),
58
+ host: this,
59
+ next,
60
+ });
61
+ if (applied) {
62
+ this.dispatchEvent(new HelixToggleEvent({ checked: next }));
63
+ }
64
+ }
46
65
  get files() {
47
66
  if (!this.diff) {
48
67
  return [];
@@ -85,6 +104,89 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
85
104
  }
86
105
  }
87
106
  }
107
+ lineBoxes(code) {
108
+ const slot = code.querySelector('slot');
109
+ const roots = slot && !slot.hidden ? slot.assignedNodes({ flatten: true }) : [code];
110
+ const texts = [];
111
+ for (const root of roots) {
112
+ if (root.nodeType === Node.TEXT_NODE) {
113
+ texts.push(root);
114
+ continue;
115
+ }
116
+ const walker = (root.ownerDocument ?? document).createTreeWalker(root, NodeFilter.SHOW_TEXT);
117
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
118
+ texts.push(node);
119
+ }
120
+ }
121
+ const starts = [];
122
+ let total = 0;
123
+ for (const text of texts) {
124
+ starts.push(total);
125
+ total += text.data.length;
126
+ }
127
+ const at = (offset) => {
128
+ for (let index = texts.length - 1; index >= 0; index -= 1) {
129
+ const start = starts[index] ?? 0;
130
+ if (offset >= start) {
131
+ const text = texts[index];
132
+ return [text, Math.min(offset - start, text.data.length)];
133
+ }
134
+ }
135
+ return undefined;
136
+ };
137
+ const box = code.getBoundingClientRect();
138
+ const origin = box.top + code.clientTop - code.scrollTop;
139
+ const row = Number.parseFloat(getComputedStyle(code).lineHeight) || 0;
140
+ const boxes = [];
141
+ for (const [from, to] of lineSpans(texts.map((text) => text.data).join(''))) {
142
+ const start = at(from);
143
+ const end = at(to === from ? Math.min(to + 1, total) : to);
144
+ const previous = boxes.at(-1);
145
+ let top = previous ? previous.bottom : 0;
146
+ let bottom = top + row;
147
+ if (start && end) {
148
+ const range = (code.ownerDocument ?? document).createRange();
149
+ range.setStart(start[0], start[1]);
150
+ range.setEnd(end[0], end[1]);
151
+ const rects = [...range.getClientRects()].filter((rect) => rect.height > 0);
152
+ if (rects.length > 0) {
153
+ const half = Math.max(0, (row - (rects[0]?.height ?? row)) / 2);
154
+ top = Math.min(...rects.map((rect) => rect.top)) - origin - half;
155
+ bottom = Math.max(...rects.map((rect) => rect.bottom)) - origin + half;
156
+ }
157
+ }
158
+ boxes.push({ bottom, top });
159
+ }
160
+ return boxes;
161
+ }
162
+ placeLines() {
163
+ const code = this.renderRoot.querySelector('[part="code"]');
164
+ const marks = [...this.renderRoot.querySelectorAll('[part="mark"]')];
165
+ const numbers = [
166
+ ...this.renderRoot.querySelectorAll('[part="gutter"] > [part="number"]'),
167
+ ];
168
+ if (!code || (marks.length === 0 && numbers.length === 0)) {
169
+ return;
170
+ }
171
+ const boxes = this.lineBoxes(code);
172
+ for (const mark of marks) {
173
+ const [first, last] = (mark.dataset.lines ?? '').split('-').map(Number);
174
+ const top = boxes[(first ?? 0) - 1];
175
+ const bottom = boxes[Math.min(last ?? 0, boxes.length) - 1];
176
+ mark.hidden = !top || !bottom;
177
+ if (top && bottom) {
178
+ mark.style.setProperty('--code-block-mark-start', `${top.top}px`);
179
+ mark.style.setProperty('--code-block-mark-size', `${bottom.bottom - top.top}px`);
180
+ mark.style.setProperty('--code-block-mark-inline-size', `${code.scrollWidth}px`);
181
+ }
182
+ }
183
+ numbers.forEach((number, index) => {
184
+ const line = boxes[index];
185
+ if (line) {
186
+ number.style.setProperty('--code-block-line-size', `${line.bottom - line.top}px`);
187
+ }
188
+ });
189
+ }
88
190
  updated(changed) {
89
191
  super.updated(changed);
90
192
  if (typeof ResizeObserver !== 'undefined') {
@@ -126,21 +228,29 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
126
228
  catch (error) {
127
229
  globalThis.reportError?.(error);
128
230
  }
231
+ const runs = this.childrenKnown ? lineRuns(this.markLines) : [];
232
+ const marks = runs.map(([first, last]) => html `<span part="mark" aria-hidden="true" data-lines="${first}-${last}"></span>`);
129
233
  const code = highlighted === undefined
130
- ? html `<code part="code"><slot @slotchange=${() => this.requestUpdate()}></slot></code>`
131
- : html `<code part="code">${unsafeHTML(highlighted)}</code><slot
234
+ ? html `<code part="code">${marks}<slot @slotchange=${() => this.requestUpdate()}></slot></code>`
235
+ : html `<code part="code">${marks}${unsafeHTML(highlighted)}</code><slot
132
236
  hidden
133
237
  @slotchange=${() => this.requestUpdate()}
134
238
  ></slot>`;
135
239
  const lines = lineCount(text);
240
+ const numbers = lines === 0
241
+ ? nothing
242
+ : this.wrap
243
+ ? Array.from({ length: lines }, (_, index) => html `<span part="number">${index + 1}
244
+ </span>`)
245
+ : gutterNumbers(lines);
136
246
  const gutter = this.lineNumbers
137
- ? html `<span part="gutter" aria-hidden="true">${lines === 0 ? nothing : gutterNumbers(lines)}</span>`
247
+ ? html `<span part="gutter" aria-hidden="true">${numbers}</span>`
138
248
  : nothing;
139
249
  const name = this.label || this.language;
140
250
  return html `${this.renderHeader(name)}
141
251
  <div part="frame">
142
252
  <pre id="block" part="block" translate="no">${gutter}${code}</pre>
143
- ${name ? nothing : this.renderCopy()}
253
+ ${this.headed(name) ? nothing : this.renderCopy()}
144
254
  </div>${this.renderExpand()}`;
145
255
  }
146
256
  renderExpand() {
@@ -160,11 +270,29 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
160
270
  ${this.expanded ? this.collapseLabel : this.expandLabel}
161
271
  </button>`;
162
272
  }
273
+ headed(name) {
274
+ return name !== '' || (this.wrappable && !this.diff) || this.slotted('actions');
275
+ }
163
276
  renderHeader(name, value) {
164
- return name
165
- ? html `<div part="header"><span part="label">${name}</span>${this.renderCopy(value, name)}</div>`
277
+ const acting = this.slotted('actions');
278
+ return this.headed(name)
279
+ ? html `<div part="header">${name ? html `<span part="label">${name}</span>` : nothing}${acting ? html `<span part="actions"><slot name="actions"></slot></span>` : nothing}${this.renderWrapToggle()}${this.renderCopy(value, name)}</div>`
166
280
  : nothing;
167
281
  }
282
+ renderWrapToggle() {
283
+ if (!this.wrappable || this.diff) {
284
+ return nothing;
285
+ }
286
+ return html `<button
287
+ part="wrap-toggle"
288
+ type="button"
289
+ aria-pressed=${this.wrap ? 'true' : 'false'}
290
+ @click=${() => this.toggleWrap()}
291
+ >${this.wrapLabel}</button>`;
292
+ }
293
+ slotted(name) {
294
+ return this.knownChildren.some((child) => child.getAttribute('slot') === name);
295
+ }
168
296
  renderCopy(value, name) {
169
297
  return this.noCopy ? nothing : this.renderCopyButton(value, name);
170
298
  }
@@ -179,7 +307,10 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
179
307
  copied-label=${this.copiedLabel}
180
308
  label=${name ? formatWords(this, 'copyNameLabel', { copy: this.copyLabel, name }) : this.copyLabel}
181
309
  .value=${value}
182
- ></${this.tag('copy-button')}>`;
310
+ >${this.renderCopyIcons()}</${this.tag('copy-button')}>`;
311
+ }
312
+ renderCopyIcons() {
313
+ return html `${this.slotted('copy-icon') ? html `<slot name="copy-icon"></slot>` : nothing}${this.slotted('copied-icon') ? html `<slot name="copied-icon" slot="copied"></slot>` : nothing}`;
183
314
  }
184
315
  renderNumber(value, width) {
185
316
  return html `<span part="number" aria-hidden="true">${String(value ?? '').padStart(width)} </span>`;
@@ -223,7 +354,7 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
223
354
  ${this.variant === 'split'
224
355
  ? this.renderSplit(file, width)
225
356
  : html `<pre part="block" translate="no"><code part="code">${file.hunks.map((hunk) => html `${this.renderHunk(hunk)}${hunk.lines.map((line) => this.renderLine(line, width))}`)}</code></pre>`}
226
- ${name ? nothing : this.renderCopy(now)}
357
+ ${this.headed(name) ? nothing : this.renderCopy(now)}
227
358
  </div>
228
359
  </div>`;
229
360
  })}<slot hidden @slotchange=${() => this.requestUpdate()}></slot>`;
@@ -275,12 +406,25 @@ __decorate([
275
406
  __decorate([
276
407
  property({ attribute: 'line-numbers', reflect: true, type: Boolean })
277
408
  ], HelixHeadlessCodeBlock.prototype, "lineNumbers", void 0);
409
+ __decorate([
410
+ property({ attribute: 'mark-lines' })
411
+ ], HelixHeadlessCodeBlock.prototype, "markLines", void 0);
278
412
  __decorate([
279
413
  property({ attribute: 'no-copy', reflect: true, type: Boolean })
280
414
  ], HelixHeadlessCodeBlock.prototype, "noCopy", void 0);
281
415
  __decorate([
282
416
  property({ reflect: true })
283
417
  ], HelixHeadlessCodeBlock.prototype, "variant", void 0);
418
+ __decorate([
419
+ property({ reflect: true, type: Boolean })
420
+ ], HelixHeadlessCodeBlock.prototype, "wrap", void 0);
421
+ __decorate([
422
+ words,
423
+ property({ attribute: 'wrap-label' })
424
+ ], HelixHeadlessCodeBlock.prototype, "wrapLabel", void 0);
425
+ __decorate([
426
+ property({ type: Boolean })
427
+ ], HelixHeadlessCodeBlock.prototype, "wrappable", void 0);
284
428
  __decorate([
285
429
  state()
286
430
  ], HelixHeadlessCodeBlock.prototype, "capped", void 0);
@@ -36,6 +36,7 @@ registerElementEvents({
36
36
  'helix-headless-chat-message': ['helix-send'],
37
37
  'helix-headless-checkbox': ['change', 'helix-before-toggle', 'input'],
38
38
  'helix-headless-checkbox-group': ['change', 'helix-before-change', 'input'],
39
+ 'helix-headless-code-block': ['helix-before-toggle', 'helix-toggle'],
39
40
  'helix-headless-color-input': [
40
41
  'change',
41
42
  'helix-before-change',
@@ -253,11 +254,14 @@ registerElementEvents({
253
254
  'helix-headless-tab-panel': ['helix-load'],
254
255
  'helix-headless-table': [
255
256
  'helix-before-reorder',
257
+ 'helix-before-toggle',
258
+ 'helix-load-more',
256
259
  'helix-reorder',
257
260
  'helix-select',
258
261
  'helix-selected',
259
262
  'helix-sort',
260
263
  'helix-sorted',
264
+ 'helix-toggle',
261
265
  ],
262
266
  'helix-headless-table-column': ['helix-before-resize', 'helix-resized'],
263
267
  'helix-headless-tabs': ['helix-activate', 'helix-before-activate', 'helix-select'],
@@ -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;