@fusion.dev/helix-headless 0.23.0 → 0.24.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 (102) hide show
  1. package/custom-elements.json +48376 -26638
  2. package/dist/announcement/root/announcement-root.d.ts +19 -5
  3. package/dist/area-chart/index.d.ts +1 -0
  4. package/dist/area-chart/index.js +1 -0
  5. package/dist/area-chart/root/area-chart-root.d.ts +182 -0
  6. package/dist/area-chart/root/area-chart-root.js +205 -0
  7. package/dist/carousel/root/carousel-root.d.ts +1 -0
  8. package/dist/carousel/root/carousel-root.js +18 -2
  9. package/dist/chat/index.d.ts +1 -1
  10. package/dist/chat/message/chat-message.d.ts +7 -0
  11. package/dist/chat/message/chat-message.js +2 -1
  12. package/dist/chat/root/chat-root.d.ts +195 -4
  13. package/dist/chat/root/chat-root.js +316 -23
  14. package/dist/checkbox-group/root/checkbox-group-root.d.ts +2 -0
  15. package/dist/checkbox-group/root/checkbox-group-root.js +12 -2
  16. package/dist/code-block/index.d.ts +1 -0
  17. package/dist/code-block/index.js +1 -0
  18. package/dist/code-block/patch.d.ts +62 -0
  19. package/dist/code-block/patch.js +93 -0
  20. package/dist/code-block/root/code-block-root.d.ts +82 -17
  21. package/dist/code-block/root/code-block-root.js +83 -6
  22. package/dist/color-picker/root/color-picker-root.d.ts +4 -3
  23. package/dist/color-picker/root/color-picker-root.js +4 -1
  24. package/dist/data-table/root/data-table-root.d.ts +195 -23
  25. package/dist/data-table/root/data-table-root.js +308 -27
  26. package/dist/description-list/index.d.ts +2 -0
  27. package/dist/description-list/index.js +2 -0
  28. package/dist/description-list/item/description-list-item.d.ts +39 -0
  29. package/dist/description-list/item/description-list-item.js +26 -0
  30. package/dist/description-list/root/description-list-root.d.ts +56 -0
  31. package/dist/description-list/root/description-list-root.js +21 -0
  32. package/dist/element-events.generated.js +27 -6
  33. package/dist/filter-input/chip/filter-chip.d.ts +114 -0
  34. package/dist/filter-input/chip/filter-chip.js +61 -0
  35. package/dist/filter-input/field/filter-field.d.ts +128 -0
  36. package/dist/filter-input/field/filter-field.js +70 -0
  37. package/dist/filter-input/grammar.d.ts +96 -0
  38. package/dist/filter-input/grammar.js +133 -0
  39. package/dist/filter-input/index.d.ts +5 -0
  40. package/dist/filter-input/index.js +5 -0
  41. package/dist/filter-input/menu/filter-menu.d.ts +153 -0
  42. package/dist/filter-input/menu/filter-menu.js +164 -0
  43. package/dist/filter-input/operator/filter-operator.d.ts +105 -0
  44. package/dist/filter-input/operator/filter-operator.js +39 -0
  45. package/dist/filter-input/root/filter-input-root.d.ts +118 -9
  46. package/dist/filter-input/root/filter-input-root.js +96 -2
  47. package/dist/format-date/root/format-date-root.js +1 -1
  48. package/dist/index.d.ts +5 -0
  49. package/dist/index.js +5 -0
  50. package/dist/line-chart/root/line-chart-root.d.ts +9 -2
  51. package/dist/listbox/root/listbox-root.d.ts +8 -7
  52. package/dist/listbox/root/listbox-root.js +7 -2
  53. package/dist/menu/item/menu-item.d.ts +31 -6
  54. package/dist/menu/item/menu-item.js +18 -5
  55. package/dist/menu/root/menu-root.js +7 -2
  56. package/dist/notifications/group/notification-group.d.ts +129 -0
  57. package/dist/notifications/group/notification-group.js +106 -0
  58. package/dist/notifications/index.d.ts +1 -0
  59. package/dist/notifications/index.js +1 -0
  60. package/dist/notifications/notification/notification.d.ts +39 -13
  61. package/dist/notifications/notification/notification.js +11 -8
  62. package/dist/notifications/root/notifications-root.d.ts +57 -1
  63. package/dist/notifications/root/notifications-root.js +35 -4
  64. package/dist/number-input/root/number-input-root.d.ts +21 -1
  65. package/dist/number-input/root/number-input-root.js +18 -5
  66. package/dist/object-inspector/index.d.ts +1 -0
  67. package/dist/object-inspector/index.js +1 -0
  68. package/dist/object-inspector/root/object-inspector-root.d.ts +183 -0
  69. package/dist/object-inspector/root/object-inspector-root.js +348 -0
  70. package/dist/outline/root/outline-root.d.ts +16 -1
  71. package/dist/outline/root/outline-root.js +27 -9
  72. package/dist/radio/group/radio-group.d.ts +1 -0
  73. package/dist/scatter-chart/index.d.ts +1 -0
  74. package/dist/scatter-chart/index.js +1 -0
  75. package/dist/scatter-chart/root/scatter-chart-root.d.ts +144 -0
  76. package/dist/scatter-chart/root/scatter-chart-root.js +205 -0
  77. package/dist/skip-link/index.d.ts +1 -0
  78. package/dist/skip-link/index.js +1 -0
  79. package/dist/skip-link/root/skip-link-root.d.ts +81 -0
  80. package/dist/skip-link/root/skip-link-root.js +58 -0
  81. package/dist/speed-dial/root/speed-dial-root.d.ts +1 -0
  82. package/dist/speed-dial/root/speed-dial-root.js +6 -2
  83. package/dist/steps/root/steps-root.d.ts +1 -0
  84. package/dist/table/column/table-column.d.ts +79 -0
  85. package/dist/table/column/table-column.js +125 -5
  86. package/dist/table/root/table-root.d.ts +2 -0
  87. package/dist/table/root/table-root.js +12 -3
  88. package/dist/time-picker/root/time-picker-root.d.ts +9 -0
  89. package/dist/time-picker/root/time-picker-root.js +5 -0
  90. package/dist/toast/root/toast-root.d.ts +2 -0
  91. package/dist/tooltip/root/tooltip-root.js +1 -1
  92. package/dist/tour/root/tour-root.d.ts +1 -0
  93. package/dist/tree/item/tree-item.d.ts +7 -4
  94. package/dist/tree/item/tree-item.js +4 -1
  95. package/dist/video/root/video-root.d.ts +23 -1
  96. package/dist/video/root/video-root.js +42 -22
  97. package/dist/virtual-list/root/virtual-list-root.d.ts +10 -6
  98. package/dist/virtual-list/root/virtual-list-root.js +10 -4
  99. package/package.json +5 -5
  100. package/types/jsx.d.ts +4535 -346
  101. package/types/svelte.d.ts +11 -0
  102. package/types/vue.d.ts +737 -39
@@ -10,6 +10,16 @@ import { type PropertyValues, type TemplateResult } from 'lit';
10
10
  * `undefined` falls back to the field.
11
11
  */
12
12
  export type CellReader = (row: Readonly<Record<string, unknown>>) => unknown;
13
+ /**
14
+ * A dot path resolved against a row.
15
+ *
16
+ * Written here rather than taken from a library, because the whole of what is
17
+ * needed is "walk the keys and stop at the first thing that is not an object".
18
+ * Returns `undefined` for a path that does not resolve, which the table renders
19
+ * as an empty cell: a missing key is a hole in the data and drawing `undefined`
20
+ * into a table tells a reader the software is broken rather than the row is.
21
+ */
22
+ export declare const at: (row: Readonly<Record<string, unknown>>, path: string) => unknown;
13
23
  /**
14
24
  * @summary One column of a table: its heading, how wide it is, and what it reads.
15
25
  *
@@ -37,6 +47,14 @@ export type CellReader = (row: Readonly<Record<string, unknown>>) => unknown;
37
47
  * item of the table, so it lines up with the cells under it rather than sitting
38
48
  * in a wrapper that has to be kept the same width.
39
49
  *
50
+ * **A heading that does something holds a control that does it.** The host is
51
+ * the column header, and a header is not focusable, so a sortable heading is a
52
+ * `<button>` inside it: the pattern for a sortable table, and the only way a
53
+ * reader on a keyboard reaches the sort at all. A column a reader may resize
54
+ * carries a `separator` on its end edge, moved by dragging it or with the arrow
55
+ * keys, and a column in a table that lets a reader reorder its columns carries
56
+ * a grip the table moves.
57
+ *
40
58
  * @since 0.1.0
41
59
  * @status experimental
42
60
  * @aliases Column header, Th
@@ -44,7 +62,13 @@ export type CellReader = (row: Readonly<Record<string, unknown>>) => unknown;
44
62
  *
45
63
  * @slot - The heading.
46
64
  *
65
+ * @event {HelixBeforeResizeEvent} helix-before-resize - Fired as a reader resizes the column, with the width asked for, and cancelable.
66
+ *
47
67
  * @csspart column - The heading cell.
68
+ * @csspart grip - The handle a reader moves the column by, where the table allows it.
69
+ * @csspart grip-mark - The glyph on it.
70
+ * @csspart resizer - The edge a reader drags to change the width, where the column allows it.
71
+ * @csspart sort - The button a sortable heading is.
48
72
  */
49
73
  export declare class HelixHeadlessTableColumn extends HelixElement {
50
74
  /**
@@ -90,6 +114,15 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
90
114
  * such a column is never sortable however it is marked.
91
115
  */
92
116
  field: string;
117
+ /**
118
+ * Whether a reader may change this column's width.
119
+ *
120
+ * By dragging the edge at the end of the heading, or with the arrow keys once
121
+ * it has focus, a step at a time and four with Shift. The width lands in
122
+ * `width` as pixels, and `helix-before-resize` asks first, so a consumer who
123
+ * stores the widths can take the decision.
124
+ */
125
+ resizable: boolean;
93
126
  /**
94
127
  * Whether a reader may reorder the set by this column.
95
128
  *
@@ -135,6 +168,28 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
135
168
  * not in.
136
169
  */
137
170
  sorted: 'ascending' | 'descending' | undefined;
171
+ /**
172
+ * @internal Whether the table this sits in lets a reader move it, written by
173
+ * that table. Not a consumer's to set: the order is the table's, and a grip
174
+ * on a column the table will not move is a control that does nothing.
175
+ */
176
+ movable: boolean;
177
+ /** How wide the heading drew, for the separator to report. */
178
+ private drawn;
179
+ /** Where a drag of the separator started, while one is running. */
180
+ private resizing;
181
+ /** Watches the heading's width, which changes with the table as well as with a drag. */
182
+ private watcher;
183
+ /**
184
+ * The narrowest a reader can drag a column, in pixels.
185
+ *
186
+ * Three characters and the inset around them. Narrower and the heading has
187
+ * nothing left to be read by, and the separator is a grab target with no
188
+ * column beside it.
189
+ */
190
+ static readonly NARROWEST = 48;
191
+ /** The step the arrow keys move the separator by, in pixels. */
192
+ static readonly STEP = 16;
138
193
  /** The track this column contributes, resolved. */
139
194
  get track(): string;
140
195
  /** Whether a reader can actually reorder by this column. */
@@ -150,6 +205,30 @@ export declare class HelixHeadlessTableColumn extends HelixElement {
150
205
  /** The value this column sorts and filters by, which is not always what it shows. */
151
206
  order(row: Readonly<Record<string, unknown>>): unknown;
152
207
  protected willUpdate(changed: PropertyValues): void;
208
+ /** The words of the heading, for naming the controls on it. */
209
+ private get name();
210
+ connectedCallback(): void;
211
+ private observeHeading;
212
+ disconnectedCallback(): void;
213
+ protected firstUpdated(changed: PropertyValues): void;
214
+ /**
215
+ * Asks for a width, in pixels, no narrower than a column can be read at.
216
+ *
217
+ * Through the same request every controlled value here uses: announced, and
218
+ * applied only where nobody refused it.
219
+ */
220
+ private resizeTo;
221
+ /** Which way a drag or an arrow key grows the column, in this writing direction. */
222
+ private get forward();
223
+ private readonly handleResizeStart;
224
+ private readonly handleResizeMove;
225
+ private readonly handleResizeEnd;
226
+ /**
227
+ * The separator's keys: a step each way, four with Shift, and Home for the
228
+ * narrowest. Arrows follow the writing direction, so the one pointing away
229
+ * from the heading's start always widens it.
230
+ */
231
+ private readonly handleResizeKey;
153
232
  protected render(): TemplateResult;
154
233
  }
155
234
  declare global {
@@ -4,10 +4,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
7
+ var HelixHeadlessTableColumn_1;
8
+ import { customElement, HelixBeforeResizeEvent, HelixElement, hostAria, requestChange, } from '@fusion.dev/helix-core';
9
+ import { dotsThreeVertical } from '@fusion.dev/helix-icons';
10
+ import { html, nothing } from 'lit';
9
11
  import { property, state } from 'lit/decorators.js';
10
- const at = (row, path) => {
12
+ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
13
+ export const at = (row, path) => {
11
14
  if (path === '') {
12
15
  return undefined;
13
16
  }
@@ -30,11 +33,50 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
30
33
  this.cell = undefined;
31
34
  this.compare = undefined;
32
35
  this.field = '';
36
+ this.resizable = false;
33
37
  this.sortable = false;
34
38
  this.width = '';
35
39
  this.isTableColumn = true;
36
40
  this.sorted = undefined;
41
+ this.movable = false;
42
+ this.drawn = 0;
43
+ this.handleResizeStart = (event) => {
44
+ if (event.button !== 0) {
45
+ return;
46
+ }
47
+ event.preventDefault();
48
+ try {
49
+ ;
50
+ event.currentTarget.setPointerCapture(event.pointerId);
51
+ }
52
+ catch {
53
+ }
54
+ this.resizing = { from: event.clientX, start: this.drawn };
55
+ };
56
+ this.handleResizeMove = (event) => {
57
+ if (this.resizing) {
58
+ this.resizeTo(this.resizing.start + (event.clientX - this.resizing.from) * this.forward);
59
+ }
60
+ };
61
+ this.handleResizeEnd = () => {
62
+ this.resizing = undefined;
63
+ };
64
+ this.handleResizeKey = (event) => {
65
+ const step = HelixHeadlessTableColumn_1.STEP * (event.shiftKey ? 4 : 1);
66
+ const grow = event.key === 'ArrowRight' ? this.forward : event.key === 'ArrowLeft' ? -this.forward : 0;
67
+ if (grow !== 0) {
68
+ event.preventDefault();
69
+ this.resizeTo(this.drawn + grow * step);
70
+ }
71
+ else if (event.key === 'Home') {
72
+ event.preventDefault();
73
+ this.resizeTo(HelixHeadlessTableColumn_1.NARROWEST);
74
+ }
75
+ };
37
76
  }
77
+ static { HelixHeadlessTableColumn_1 = this; }
78
+ static { this.NARROWEST = 48; }
79
+ static { this.STEP = 16; }
38
80
  get track() {
39
81
  return this.width || 'minmax(max-content, 1fr)';
40
82
  }
@@ -60,8 +102,77 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
60
102
  this.toggleState(name, on);
61
103
  }
62
104
  }
105
+ get name() {
106
+ return this.textContent?.trim() ?? '';
107
+ }
108
+ connectedCallback() {
109
+ super.connectedCallback();
110
+ if (typeof ResizeObserver !== 'undefined') {
111
+ this.watcher = new ResizeObserver(([entry]) => {
112
+ this.drawn = Math.round(entry?.borderBoxSize?.[0]?.inlineSize ?? 0);
113
+ });
114
+ this.observeHeading();
115
+ }
116
+ }
117
+ observeHeading() {
118
+ const heading = this.renderRoot?.querySelector('[part="column"]');
119
+ if (heading) {
120
+ this.watcher?.observe(heading);
121
+ }
122
+ }
123
+ disconnectedCallback() {
124
+ super.disconnectedCallback();
125
+ this.watcher?.disconnect();
126
+ }
127
+ firstUpdated(changed) {
128
+ super.firstUpdated?.(changed);
129
+ this.observeHeading();
130
+ }
131
+ resizeTo(px) {
132
+ const next = `${Math.max(HelixHeadlessTableColumn_1.NARROWEST, Math.round(px))}px`;
133
+ if (next === this.width) {
134
+ return;
135
+ }
136
+ requestChange({
137
+ apply: (value) => {
138
+ this.width = value;
139
+ },
140
+ event: (value) => new HelixBeforeResizeEvent({ value }),
141
+ host: this,
142
+ next,
143
+ });
144
+ }
145
+ get forward() {
146
+ return getComputedStyle(this).direction === 'rtl' ? -1 : 1;
147
+ }
63
148
  render() {
64
- return html `<span part="column"><slot></slot></span>`;
149
+ const name = this.name;
150
+ return html `<span part="column">${this.movable
151
+ ? html `<button part="grip" type="button" aria-label=${name ? `Move ${name}` : 'Move column'}>
152
+ <svg aria-hidden="true" fill="currentColor" part="grip-mark" viewBox=${dotsThreeVertical.viewBox}>
153
+ ${unsafeSVG(dotsThreeVertical.content)}
154
+ </svg>
155
+ </button>`
156
+ : nothing}${this.canSort
157
+ ? html `<button part="sort" type="button"><slot></slot></button>`
158
+ : html `<slot></slot>`}${this.resizable
159
+ ? html `<span
160
+ part="resizer"
161
+ role="separator"
162
+ tabindex="0"
163
+ aria-label=${name ? `Resize ${name}` : 'Resize column'}
164
+ aria-orientation="vertical"
165
+ aria-valuemin=${HelixHeadlessTableColumn_1.NARROWEST}
166
+ aria-valuemax="4096"
167
+ aria-valuenow=${Math.max(HelixHeadlessTableColumn_1.NARROWEST, this.drawn)}
168
+ aria-valuetext=${`${this.drawn} pixels`}
169
+ @keydown=${this.handleResizeKey}
170
+ @pointerdown=${this.handleResizeStart}
171
+ @pointermove=${this.handleResizeMove}
172
+ @pointerup=${this.handleResizeEnd}
173
+ @pointercancel=${this.handleResizeEnd}
174
+ ></span>`
175
+ : nothing}</span>`;
65
176
  }
66
177
  };
67
178
  __decorate([
@@ -76,6 +187,9 @@ __decorate([
76
187
  __decorate([
77
188
  property()
78
189
  ], HelixHeadlessTableColumn.prototype, "field", void 0);
190
+ __decorate([
191
+ property({ type: Boolean })
192
+ ], HelixHeadlessTableColumn.prototype, "resizable", void 0);
79
193
  __decorate([
80
194
  property({ type: Boolean })
81
195
  ], HelixHeadlessTableColumn.prototype, "sortable", void 0);
@@ -85,7 +199,13 @@ __decorate([
85
199
  __decorate([
86
200
  state()
87
201
  ], HelixHeadlessTableColumn.prototype, "sorted", void 0);
88
- HelixHeadlessTableColumn = __decorate([
202
+ __decorate([
203
+ state()
204
+ ], HelixHeadlessTableColumn.prototype, "movable", void 0);
205
+ __decorate([
206
+ state()
207
+ ], HelixHeadlessTableColumn.prototype, "drawn", void 0);
208
+ HelixHeadlessTableColumn = HelixHeadlessTableColumn_1 = __decorate([
89
209
  customElement('helix-headless-table-column')
90
210
  ], HelixHeadlessTableColumn);
91
211
  export { HelixHeadlessTableColumn };
@@ -85,6 +85,8 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
85
85
  * assignment has not happened.
86
86
  */
87
87
  static shadowRootOptions: ShadowRootInit;
88
+ /** The rows: every child that is not a column, which is what the body slot is assigned. */
89
+ protected get slotted(): Element[];
88
90
  private assign;
89
91
  protected updated(changed: PropertyValues): void;
90
92
  protected render(): TemplateResult;
@@ -17,7 +17,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
17
17
  };
18
18
  }
19
19
  get tableColumns() {
20
- return [...(this.children ?? [])].filter((child) => 'isTableColumn' in child && child.isTableColumn === true);
20
+ return this.knownChildren.filter((child) => 'isTableColumn' in child && child.isTableColumn === true);
21
21
  }
22
22
  get template() {
23
23
  const declared = this.tableColumns;
@@ -30,15 +30,24 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
30
30
  ...HelixElement.shadowRootOptions,
31
31
  slotAssignment: 'manual',
32
32
  }; }
33
+ get slotted() {
34
+ const columns = new Set(this.tableColumns);
35
+ return [...(this.children ?? [])].filter((child) => !columns.has(child));
36
+ }
33
37
  assign() {
38
+ if (this.assignsByName) {
39
+ for (const column of this.tableColumns) {
40
+ this.route(column, 'columns');
41
+ }
42
+ return;
43
+ }
34
44
  const head = this.renderRoot?.querySelector('slot[name="columns"]');
35
45
  const body = this.renderRoot?.querySelector('slot:not([name])');
36
46
  if (!head || !body) {
37
47
  return;
38
48
  }
39
- const columns = new Set(this.tableColumns);
40
49
  head.assign(...this.tableColumns);
41
- body.assign(...[...this.children].filter((child) => !columns.has(child)));
50
+ body.assign(...this.slotted);
42
51
  }
43
52
  updated(changed) {
44
53
  super.updated(changed);
@@ -195,6 +195,15 @@ export declare class HelixHeadlessTimePicker extends HelixHeadlessTimePicker_bas
195
195
  private halfBarred;
196
196
  /** The times held, which is a list whether or not a mode is set. */
197
197
  private get held();
198
+ /**
199
+ * The list's own `input`, stopped at the boundary.
200
+ *
201
+ * It is composed, so it crosses out of this shadow root on its own, and this
202
+ * element announces the time itself: a page listening here heard two `input`s
203
+ * for one choice, the second from the list inside, after the `change` that
204
+ * should have been the last word. Found running the events example.
205
+ */
206
+ private readonly handleListInput;
198
207
  /**
199
208
  * Takes what the list announced and constrains it to what the mode allows.
200
209
  *
@@ -46,6 +46,9 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
46
46
  this.min = '00:00';
47
47
  this.readOnly = false;
48
48
  this.required = false;
49
+ this.handleListInput = (event) => {
50
+ event.stopPropagation();
51
+ };
49
52
  this.handleChange = (event) => {
50
53
  event.stopPropagation();
51
54
  if (this.isDisabled) {
@@ -225,6 +228,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
225
228
  event.stopPropagation();
226
229
  this.moveColumn(part, event.detail.value);
227
230
  }}
231
+ @input=${this.handleListInput}
228
232
  >
229
233
  ${values.map((one) => html `<${this.tag('option')} part="option"
230
234
  exportparts="description: option-description, item: option-item, label: option-label, text: option-text" value=${one} ?disabled=${barred(one)}
@@ -259,6 +263,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
259
263
  ?multiple=${this.mode !== undefined}
260
264
  .value=${this.value}
261
265
  @helix-before-change=${this.handleChange}
266
+ @input=${this.handleListInput}
262
267
  >
263
268
  ${this.times.map((minutes) => {
264
269
  const at = fromMinutes(minutes);
@@ -36,6 +36,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
36
36
  *
37
37
  * @slot - The messages, as `helix-toast-item` elements.
38
38
  *
39
+ * @event {HelixDismissEvent} helix-dismiss - A message asking to go, by its control or by its timer, dispatched by the message and heard here because the stack is what a consumer holds. Cancelable: refusing it takes the decision and the message stays.
40
+ *
39
41
  * @csspart region - The stack the messages are in.
40
42
  */
41
43
  export declare class HelixHeadlessToast extends HelixElement {
@@ -48,7 +48,7 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
48
48
  this.timer = setTimeout(() => this.release(), this.delayOut);
49
49
  };
50
50
  this.showNow = (event) => {
51
- const focused = event.target;
51
+ const focused = event.composedPath()[0] ?? event.target;
52
52
  if (focused instanceof Element && !focused.matches(':focus-visible')) {
53
53
  return;
54
54
  }
@@ -31,6 +31,7 @@ import type { HelixHeadlessTourStop } from '../stop/tour-stop.js';
31
31
  * @event {HelixBeforeCloseEvent} helix-before-close - Fired before the reader leaves early. Cancelable: refusing it takes the decision and nothing moves.
32
32
  * @event {HelixCompleteEvent} helix-complete - Fired when the last stop is
33
33
  * finished.
34
+ * @event {HelixTourAskEvent} helix-tour-ask - A stop's own controls asking the tour to move, dispatched by the stop and heard here because the tour is what moves.
34
35
  * @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
35
36
  *
36
37
  * @csspart spotlight - The box over the current target, which paints the scrim.
@@ -14,9 +14,9 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
14
14
  * An element rather than a slot, because it owns state: whether it is open,
15
15
  * selected, and whether it holds focus.
16
16
  *
17
- * A branch is any item with something slotted into `children`, detected in
18
- * `firstUpdated` because the server cannot inspect children. The server renders
19
- * the node as a leaf and the twisty appears on hydration, which is the harmless
17
+ * A branch is any item with something slotted into `children`, detected on the
18
+ * client because the server cannot inspect children. The server renders the
19
+ * node as a leaf and the twisty appears on hydration, which is the harmless
20
20
  * direction.
21
21
  *
22
22
  * **`href` makes the row itself the link.** With the anchor slotted into the
@@ -100,7 +100,10 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
100
100
  /**
101
101
  * Whether anything is slotted into `children`.
102
102
  *
103
- * Read after first render.
103
+ * Read from the children before each render, so the first one draws the
104
+ * twisty, and from the slot, which is what hears a child arrive later. Not on
105
+ * the server, where `children` is undefined, nor while hydrating what one
106
+ * sent.
104
107
  */
105
108
  private branch;
106
109
  protected firstUpdated(changed: PropertyValues): void;
@@ -40,8 +40,8 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
40
40
  }
41
41
  firstUpdated(changed) {
42
42
  super.firstUpdated(changed);
43
+ this.finishHydration();
43
44
  const slot = this.renderRoot.querySelector('slot[name="children"]');
44
- this.branch = (slot?.assignedElements().length ?? 0) > 0;
45
45
  slot?.addEventListener('slotchange', () => {
46
46
  this.branch = slot.assignedElements().length > 0;
47
47
  });
@@ -97,6 +97,9 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
97
97
  willUpdate(changed) {
98
98
  super.willUpdate(changed);
99
99
  this.toggleState('selected', this.selected);
100
+ if (this.children !== undefined && !this.hydrating) {
101
+ this.branch = [...this.children].some((child) => child.slot === 'children');
102
+ }
100
103
  }
101
104
  render() {
102
105
  const linked = this.href !== '';
@@ -460,14 +460,23 @@ export declare class HelixHeadlessVideo extends HelixElement {
460
460
  private takeScrub;
461
461
  /** The volume rail's new level, stopped for the reason `takeScrub` carries. */
462
462
  private takeVolume;
463
+ /** Whether the first set of copies has been read from the children. */
464
+ private mediaRead;
463
465
  /**
464
- * Rebuilds the copies from whatever the consumer has slotted.
466
+ * The copies to make of what the default slot is given.
465
467
  *
466
468
  * The attribute list is explicit rather than a blind copy of every attribute:
467
469
  * these are two elements with a known and small surface, and copying anything
468
470
  * a consumer happened to put on one would carry their framework's own markers
469
471
  * into a shadow root that has no use for them.
470
472
  */
473
+ private copiesOf;
474
+ /**
475
+ * Rebuilds the copies when what is slotted changes after the first read.
476
+ *
477
+ * The same set again is not a change. A video reloads for every new list, and
478
+ * the first `slotchange` reports the children this element already read.
479
+ */
471
480
  private readonly handleMedia;
472
481
  private readonly handleLoaded;
473
482
  private readonly handleTime;
@@ -632,6 +641,19 @@ export declare class HelixHeadlessVideo extends HelixElement {
632
641
  private readonly handleRemoteState;
633
642
  connectedCallback(): void;
634
643
  disconnectedCallback(): void;
644
+ /** The caption tracks among the copies, named as the menu names them. */
645
+ private captions;
646
+ /**
647
+ * Finds the caption tracks among the copies, before the render that draws
648
+ * them.
649
+ *
650
+ * By the platform's own reading of `kind`, which is what makes a `<track>` a
651
+ * caption at all: missing is `subtitles` and anything it does not know is
652
+ * `metadata`. Read from the video's tracks after the render instead, the count
653
+ * arrived a render late and asked for one more. Choosing one still goes
654
+ * through the video's tracks, which hold them in the same order.
655
+ */
656
+ protected willUpdate(changed: PropertyValues): void;
635
657
  protected firstUpdated(changed: PropertyValues): void;
636
658
  /**
637
659
  * Mirrors playback onto the host as `data-playing`.
@@ -64,24 +64,12 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
64
64
  this.remoteAvailable = false;
65
65
  this.remoteConnected = false;
66
66
  this.media = [];
67
+ this.mediaRead = false;
67
68
  this.handleMedia = (event) => {
68
- const assigned = event.target.assignedElements();
69
- this.media = assigned
70
- .filter((node) => node instanceof HTMLSourceElement || node instanceof HTMLTrackElement)
71
- .map((node) => {
72
- const tag = node instanceof HTMLTrackElement ? 'track' : 'source';
73
- const names = tag === 'track'
74
- ? ['default', 'kind', 'label', 'src', 'srclang']
75
- : ['media', 'sizes', 'src', 'srcset', 'type'];
76
- const attrs = {};
77
- for (const name of names) {
78
- const value = node.getAttribute(name);
79
- if (value !== null) {
80
- attrs[name] = value;
81
- }
82
- }
83
- return { attrs, tag };
84
- });
69
+ const next = this.copiesOf(event.target.assignedElements());
70
+ if (JSON.stringify(next) !== JSON.stringify(this.media)) {
71
+ this.media = next;
72
+ }
85
73
  };
86
74
  this.handleLoaded = () => {
87
75
  const video = this.video;
@@ -92,7 +80,6 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
92
80
  if (video.videoWidth > 0 && video.videoHeight > 0) {
93
81
  this.style.setProperty('--video-intrinsic-ratio', `${video.videoWidth} / ${video.videoHeight}`);
94
82
  }
95
- this.captionCount = this.captionTracks.length;
96
83
  const track = video.textTracks[0];
97
84
  if (track) {
98
85
  if (track.mode === 'showing') {
@@ -229,6 +216,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
229
216
  this.handleRemoteState = () => {
230
217
  this.remoteConnected = this.video?.remote?.state === 'connected';
231
218
  };
219
+ this.captions = [];
232
220
  }
233
221
  get localFile() {
234
222
  if (this.src === '') {
@@ -395,6 +383,24 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
395
383
  video.volume = Number(event.target.value);
396
384
  video.muted = false;
397
385
  }
386
+ copiesOf(elements) {
387
+ return elements
388
+ .filter((node) => node instanceof HTMLSourceElement || node instanceof HTMLTrackElement)
389
+ .map((node) => {
390
+ const tag = node instanceof HTMLTrackElement ? 'track' : 'source';
391
+ const names = tag === 'track'
392
+ ? ['default', 'kind', 'label', 'src', 'srclang']
393
+ : ['media', 'sizes', 'src', 'srcset', 'type'];
394
+ const attrs = {};
395
+ for (const name of names) {
396
+ const value = node.getAttribute(name);
397
+ if (value !== null) {
398
+ attrs[name] = value;
399
+ }
400
+ }
401
+ return { attrs, tag };
402
+ });
403
+ }
398
404
  claimSession() {
399
405
  if (typeof navigator === 'undefined' || !navigator.mediaSession) {
400
406
  return;
@@ -533,8 +539,24 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
533
539
  }
534
540
  }
535
541
  }
542
+ willUpdate(changed) {
543
+ super.willUpdate(changed);
544
+ if (!this.mediaRead && this.children !== undefined && !this.hydrating) {
545
+ this.mediaRead = true;
546
+ this.media = this.copiesOf([...this.children].filter((child) => child.slot === ''));
547
+ }
548
+ if (changed.has('media')) {
549
+ this.captions = this.media
550
+ .filter((one) => one.tag === 'track' &&
551
+ (one.attrs.kind === undefined ||
552
+ ['captions', 'subtitles'].includes(one.attrs.kind.toLowerCase())))
553
+ .map((one) => ({ label: one.attrs.label ?? '', language: one.attrs.srclang ?? '' }));
554
+ this.captionCount = this.captions.length;
555
+ }
556
+ }
536
557
  firstUpdated(changed) {
537
558
  super.firstUpdated?.(changed);
559
+ this.finishHydration();
538
560
  const video = this.video;
539
561
  if (video) {
540
562
  this.silent = video.muted;
@@ -549,9 +571,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
549
571
  this.toggleAttribute('data-playing', this.playing);
550
572
  this.toggleAttribute('data-idle', this.idle && this.playing);
551
573
  if (changed.has('media')) {
552
- const video = this.video;
553
- video?.load();
554
- this.captionCount = this.captionTracks.length;
574
+ this.video?.load();
555
575
  }
556
576
  }
557
577
  renderExtra(one) {
@@ -639,7 +659,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
639
659
  </button>`;
640
660
  }
641
661
  renderCaptions() {
642
- const tracks = this.captionTracks;
662
+ const tracks = this.captions;
643
663
  if (this.captionCount === 0) {
644
664
  return nothing;
645
665
  }
@@ -61,6 +61,8 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
61
61
  * Empty on a server, which has nothing to read.
62
62
  */
63
63
  private rows;
64
+ /** The `for` that `rows` came from, so it is read again only when that changes. */
65
+ private readFor;
64
66
  private readonly virtualize;
65
67
  /**
66
68
  * The rows in the tree, each with the item it drew.
@@ -73,14 +75,16 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
73
75
  */
74
76
  private drawn;
75
77
  /**
76
- * Reads the source named by `for`, once the document exists to read.
78
+ * Reads the source named by `for`, before the render that draws it.
77
79
  *
78
- * `firstUpdated` rather than `connectedCallback`, per the server rules: the
79
- * shim has attributes and no tree, so `getElementById` is undefined there.
80
- * A source that is missing or not an array leaves the list empty rather than
81
- * throwing, because a consumer whose data has not arrived yet is the ordinary
82
- * case and an empty list says so by itself.
80
+ * Not on the server, per the server rules: the shim has attributes and no
81
+ * tree, so `getElementById` is undefined there. Nor while hydrating what a
82
+ * server sent, which it drew with nothing read. A source that is missing or
83
+ * not an array leaves the list empty rather than throwing, because a consumer
84
+ * whose data has not arrived yet is the ordinary case and an empty list says
85
+ * so by itself.
83
86
  */
87
+ protected willUpdate(changed: PropertyValues): void;
84
88
  protected firstUpdated(changed: PropertyValues): void;
85
89
  protected updated(changed: PropertyValues): void;
86
90
  private read;