@fusion.dev/helix-headless 0.23.0 → 0.24.1

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 (124) hide show
  1. package/custom-elements.json +50579 -26954
  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/avatar/group/avatar-group.d.ts +1 -0
  8. package/dist/avatar/group/avatar-group.js +5 -1
  9. package/dist/avatar/root/avatar-root.d.ts +1 -0
  10. package/dist/avatar/root/avatar-root.js +11 -0
  11. package/dist/carousel/root/carousel-root.d.ts +3 -0
  12. package/dist/carousel/root/carousel-root.js +23 -4
  13. package/dist/chat/index.d.ts +1 -1
  14. package/dist/chat/message/chat-message.d.ts +7 -0
  15. package/dist/chat/message/chat-message.js +2 -1
  16. package/dist/chat/root/chat-root.d.ts +195 -4
  17. package/dist/chat/root/chat-root.js +316 -23
  18. package/dist/checkbox-group/root/checkbox-group-root.d.ts +2 -0
  19. package/dist/checkbox-group/root/checkbox-group-root.js +12 -2
  20. package/dist/code-block/index.d.ts +1 -0
  21. package/dist/code-block/index.js +1 -0
  22. package/dist/code-block/patch.d.ts +62 -0
  23. package/dist/code-block/patch.js +93 -0
  24. package/dist/code-block/root/code-block-root.d.ts +82 -17
  25. package/dist/code-block/root/code-block-root.js +83 -6
  26. package/dist/color-picker/root/color-picker-root.d.ts +4 -3
  27. package/dist/color-picker/root/color-picker-root.js +4 -1
  28. package/dist/combobox/root/combobox-root.d.ts +1 -0
  29. package/dist/combobox/root/combobox-root.js +6 -1
  30. package/dist/command/root/command-root.d.ts +7 -0
  31. package/dist/command/root/command-root.js +10 -0
  32. package/dist/data-table/root/data-table-root.d.ts +195 -23
  33. package/dist/data-table/root/data-table-root.js +308 -27
  34. package/dist/description-list/index.d.ts +2 -0
  35. package/dist/description-list/index.js +2 -0
  36. package/dist/description-list/item/description-list-item.d.ts +39 -0
  37. package/dist/description-list/item/description-list-item.js +26 -0
  38. package/dist/description-list/root/description-list-root.d.ts +56 -0
  39. package/dist/description-list/root/description-list-root.js +21 -0
  40. package/dist/element-events.generated.js +27 -6
  41. package/dist/filter-input/chip/filter-chip.d.ts +114 -0
  42. package/dist/filter-input/chip/filter-chip.js +61 -0
  43. package/dist/filter-input/field/filter-field.d.ts +128 -0
  44. package/dist/filter-input/field/filter-field.js +70 -0
  45. package/dist/filter-input/grammar.d.ts +96 -0
  46. package/dist/filter-input/grammar.js +133 -0
  47. package/dist/filter-input/index.d.ts +5 -0
  48. package/dist/filter-input/index.js +5 -0
  49. package/dist/filter-input/menu/filter-menu.d.ts +153 -0
  50. package/dist/filter-input/menu/filter-menu.js +164 -0
  51. package/dist/filter-input/operator/filter-operator.d.ts +105 -0
  52. package/dist/filter-input/operator/filter-operator.js +39 -0
  53. package/dist/filter-input/root/filter-input-root.d.ts +118 -9
  54. package/dist/filter-input/root/filter-input-root.js +96 -2
  55. package/dist/format-date/root/format-date-root.js +5 -2
  56. package/dist/image/root/image-root.d.ts +9 -0
  57. package/dist/image/root/image-root.js +16 -3
  58. package/dist/index.d.ts +5 -0
  59. package/dist/index.js +5 -0
  60. package/dist/kbd/root/kbd-root.d.ts +9 -2
  61. package/dist/kbd/root/kbd-root.js +7 -2
  62. package/dist/line-chart/root/line-chart-root.d.ts +9 -2
  63. package/dist/listbox/root/listbox-root.d.ts +8 -7
  64. package/dist/listbox/root/listbox-root.js +7 -2
  65. package/dist/menu/item/menu-item.d.ts +31 -6
  66. package/dist/menu/item/menu-item.js +18 -5
  67. package/dist/menu/root/menu-root.js +7 -2
  68. package/dist/notifications/group/notification-group.d.ts +129 -0
  69. package/dist/notifications/group/notification-group.js +106 -0
  70. package/dist/notifications/index.d.ts +1 -0
  71. package/dist/notifications/index.js +1 -0
  72. package/dist/notifications/notification/notification.d.ts +39 -13
  73. package/dist/notifications/notification/notification.js +11 -8
  74. package/dist/notifications/root/notifications-root.d.ts +57 -1
  75. package/dist/notifications/root/notifications-root.js +35 -4
  76. package/dist/number-input/root/number-input-root.d.ts +21 -1
  77. package/dist/number-input/root/number-input-root.js +18 -5
  78. package/dist/object-inspector/index.d.ts +1 -0
  79. package/dist/object-inspector/index.js +1 -0
  80. package/dist/object-inspector/root/object-inspector-root.d.ts +183 -0
  81. package/dist/object-inspector/root/object-inspector-root.js +348 -0
  82. package/dist/option/root/option-root.d.ts +17 -0
  83. package/dist/option/root/option-root.js +4 -0
  84. package/dist/outline/root/outline-root.d.ts +16 -1
  85. package/dist/outline/root/outline-root.js +27 -9
  86. package/dist/qr-code/root/qr-code-root.d.ts +3 -1
  87. package/dist/qr-code/root/qr-code-root.js +11 -1
  88. package/dist/radio/group/radio-group.d.ts +1 -0
  89. package/dist/rating/root/rating-root.d.ts +3 -0
  90. package/dist/rating/root/rating-root.js +9 -0
  91. package/dist/scatter-chart/index.d.ts +1 -0
  92. package/dist/scatter-chart/index.js +1 -0
  93. package/dist/scatter-chart/root/scatter-chart-root.d.ts +144 -0
  94. package/dist/scatter-chart/root/scatter-chart-root.js +205 -0
  95. package/dist/select/root/select-root.d.ts +8 -0
  96. package/dist/select/root/select-root.js +8 -6
  97. package/dist/skip-link/index.d.ts +1 -0
  98. package/dist/skip-link/index.js +1 -0
  99. package/dist/skip-link/root/skip-link-root.d.ts +81 -0
  100. package/dist/skip-link/root/skip-link-root.js +58 -0
  101. package/dist/speed-dial/root/speed-dial-root.d.ts +1 -0
  102. package/dist/speed-dial/root/speed-dial-root.js +6 -2
  103. package/dist/steps/root/steps-root.d.ts +1 -0
  104. package/dist/steps/root/steps-root.js +5 -2
  105. package/dist/table/column/table-column.d.ts +79 -0
  106. package/dist/table/column/table-column.js +125 -5
  107. package/dist/table/root/table-root.d.ts +2 -0
  108. package/dist/table/root/table-root.js +12 -3
  109. package/dist/time-picker/root/time-picker-root.d.ts +9 -0
  110. package/dist/time-picker/root/time-picker-root.js +5 -0
  111. package/dist/toast/root/toast-root.d.ts +2 -0
  112. package/dist/tooltip/root/tooltip-root.js +1 -1
  113. package/dist/tour/root/tour-root.d.ts +14 -0
  114. package/dist/tour/root/tour-root.js +43 -18
  115. package/dist/tree/item/tree-item.d.ts +7 -4
  116. package/dist/tree/item/tree-item.js +4 -1
  117. package/dist/video/root/video-root.d.ts +23 -1
  118. package/dist/video/root/video-root.js +46 -23
  119. package/dist/virtual-list/root/virtual-list-root.d.ts +10 -6
  120. package/dist/virtual-list/root/virtual-list-root.js +10 -4
  121. package/package.json +5 -5
  122. package/types/jsx.d.ts +4536 -346
  123. package/types/svelte.d.ts +11 -0
  124. package/types/vue.d.ts +738 -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.
@@ -89,6 +90,15 @@ export declare class HelixHeadlessTour extends HelixElement {
89
90
  * Where the light falls, read from the current target.
90
91
  */
91
92
  private spot;
93
+ /**
94
+ * Watches the lit target's own box, which moves the light without this
95
+ * element updating or the window changing: a label whose font arrives late,
96
+ * words a page rewrites. Measured on a server's page, the spotlight sat two
97
+ * pixels inside each side of a button that widened once its font loaded.
98
+ */
99
+ private sizer;
100
+ /** What `sizer` is watching, so a render that changed nothing re-observes nothing. */
101
+ private sized;
92
102
  /** The stops, in the order they were written. */
93
103
  get stops(): HelixHeadlessTourStop[];
94
104
  /** How many stops there are. */
@@ -123,8 +133,12 @@ export declare class HelixHeadlessTour extends HelixElement {
123
133
  * localised the tour.
124
134
  */
125
135
  private settle;
136
+ /** The current stop's target while the run is open, which is what the light falls on. */
137
+ private get lit();
126
138
  /** Reads the current target's box, which is where the light falls. */
127
139
  private light;
140
+ /** Keeps `sizer` on the lit target, and on nothing once there is none. */
141
+ private watch;
128
142
  protected render(): TemplateResult;
129
143
  }
130
144
  declare global {
@@ -4,7 +4,7 @@ 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, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixCompleteEvent, HelixElement, hostAria, requestChange, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixCompleteEvent, HelixElement, hostAria, requestChange, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, queryAssignedElements, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
@@ -19,6 +19,8 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
19
19
  this.open = false;
20
20
  this.skipLabel = '';
21
21
  this.spotlightPadding = 8;
22
+ this.sizer = undefined;
23
+ this.sized = undefined;
22
24
  this.handleAsk = (event) => {
23
25
  const what = event.detail?.what;
24
26
  if (what === 'next') {
@@ -96,6 +98,9 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
96
98
  this.ownerDocument.addEventListener('keydown', this.handleKeydown);
97
99
  globalThis.addEventListener('resize', this.handleViewport, { passive: true });
98
100
  globalThis.addEventListener('scroll', this.handleViewport, { capture: true, passive: true });
101
+ if (this.hasUpdated) {
102
+ this.watch(this.lit);
103
+ }
99
104
  }
100
105
  disconnectedCallback() {
101
106
  super.disconnectedCallback();
@@ -103,6 +108,8 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
103
108
  this.ownerDocument.removeEventListener('keydown', this.handleKeydown);
104
109
  globalThis.removeEventListener('resize', this.handleViewport);
105
110
  globalThis.removeEventListener('scroll', this.handleViewport, { capture: true });
111
+ this.sizer?.disconnect();
112
+ this.sized = undefined;
106
113
  }
107
114
  willUpdate(changed) {
108
115
  super.willUpdate(changed);
@@ -117,28 +124,34 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
117
124
  const stops = this.stops;
118
125
  const at = Math.max(0, Math.min(this.index, stops.length - 1));
119
126
  stops.forEach((stop, index) => {
120
- stop.current = this.open && index === at;
121
- stop.first = index === 0;
122
- stop.last = index === stops.length - 1;
123
- if (this.backLabel !== '') {
124
- stop.backLabel = this.backLabel;
125
- }
126
- if (this.nextLabel !== '') {
127
- stop.nextLabel = this.nextLabel;
128
- }
129
- if (this.doneLabel !== '') {
130
- stop.doneLabel = this.doneLabel;
131
- }
132
- if (this.skipLabel !== '') {
133
- stop.skipLabel = this.skipLabel;
134
- }
127
+ whenHydrated(stop, () => {
128
+ stop.current = this.open && index === at;
129
+ stop.first = index === 0;
130
+ stop.last = index === stops.length - 1;
131
+ if (this.backLabel !== '') {
132
+ stop.backLabel = this.backLabel;
133
+ }
134
+ if (this.nextLabel !== '') {
135
+ stop.nextLabel = this.nextLabel;
136
+ }
137
+ if (this.doneLabel !== '') {
138
+ stop.doneLabel = this.doneLabel;
139
+ }
140
+ if (this.skipLabel !== '') {
141
+ stop.skipLabel = this.skipLabel;
142
+ }
143
+ });
135
144
  });
136
145
  }
137
- light() {
146
+ get lit() {
138
147
  const stops = this.stops;
139
- const target = this.open
148
+ return this.open
140
149
  ? stops[Math.max(0, Math.min(this.index, stops.length - 1))]?.target
141
150
  : undefined;
151
+ }
152
+ light() {
153
+ const target = this.lit;
154
+ this.watch(target);
142
155
  if (!target || typeof target.getBoundingClientRect !== 'function') {
143
156
  this.spot = undefined;
144
157
  return;
@@ -160,6 +173,18 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
160
173
  this.spot = next;
161
174
  }
162
175
  }
176
+ watch(target) {
177
+ const next = target ?? undefined;
178
+ if (next === this.sized || typeof ResizeObserver === 'undefined') {
179
+ return;
180
+ }
181
+ this.sizer ??= new ResizeObserver(() => this.light());
182
+ this.sizer.disconnect();
183
+ if (next) {
184
+ this.sizer.observe(next);
185
+ }
186
+ this.sized = next;
187
+ }
163
188
  render() {
164
189
  const spot = this.spot;
165
190
  return html `${spot === undefined
@@ -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`.