@fusion.dev/helix-headless 0.25.1 → 0.30.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 (89) hide show
  1. package/custom-elements.json +4966 -755
  2. package/dist/accordion/root/accordion-root.d.ts +5 -6
  3. package/dist/accordion/root/accordion-root.js +13 -36
  4. package/dist/announcement/root/announcement-root.js +1 -1
  5. package/dist/avatar/root/avatar-root.d.ts +17 -3
  6. package/dist/avatar/root/avatar-root.js +27 -8
  7. package/dist/blockquote/root/blockquote-root.js +1 -1
  8. package/dist/button/root/button-root.js +1 -1
  9. package/dist/carousel/root/carousel-root.js +2 -2
  10. package/dist/chat/root/chat-root.js +1 -1
  11. package/dist/checkbox/root/checkbox-root.js +2 -2
  12. package/dist/checkbox-group/root/checkbox-group-root.js +2 -2
  13. package/dist/color-input/root/color-input-root.d.ts +12 -14
  14. package/dist/color-input/root/color-input-root.js +4 -52
  15. package/dist/color-picker/root/color-picker-root.js +2 -2
  16. package/dist/combobox/root/combobox-root.js +11 -14
  17. package/dist/command/root/command-root.js +2 -3
  18. package/dist/data-table/root/data-table-root.js +1 -1
  19. package/dist/date-input/root/date-input-root.d.ts +5 -77
  20. package/dist/date-input/root/date-input-root.js +16 -108
  21. package/dist/date-picker/root/date-picker-root.js +2 -2
  22. package/dist/divider/root/divider-root.js +1 -1
  23. package/dist/drawer/root/drawer-root.js +1 -1
  24. package/dist/drilldown/item/drilldown-item.js +1 -1
  25. package/dist/file-input/root/file-input-root.js +4 -7
  26. package/dist/filter-input/field/filter-field.d.ts +1 -1
  27. package/dist/filter-input/root/filter-input-root.d.ts +2 -2
  28. package/dist/filter-input/root/filter-input-root.js +12 -15
  29. package/dist/heatmap/root/heatmap-root.js +1 -1
  30. package/dist/image/root/image-root.d.ts +7 -0
  31. package/dist/image/root/image-root.js +6 -2
  32. package/dist/input/root/input-root.js +3 -3
  33. package/dist/listbox/root/listbox-root.js +3 -3
  34. package/dist/megamenu/root/megamenu-root.d.ts +12 -2
  35. package/dist/megamenu/root/megamenu-root.js +20 -15
  36. package/dist/menu/item/menu-item.js +1 -1
  37. package/dist/menu/root/menu-root.js +3 -10
  38. package/dist/navigation/root/navigation-root.d.ts +1 -2
  39. package/dist/navigation/root/navigation-root.js +3 -9
  40. package/dist/number-input/root/number-input-root.js +5 -5
  41. package/dist/object-inspector/root/object-inspector-root.js +4 -4
  42. package/dist/outline/root/outline-root.d.ts +0 -1
  43. package/dist/outline/root/outline-root.js +6 -12
  44. package/dist/pin-input/root/pin-input-root.js +2 -2
  45. package/dist/popover/root/popover-root.js +2 -10
  46. package/dist/radio/root/radio-root.js +1 -1
  47. package/dist/scatter-chart/root/scatter-chart-root.js +1 -1
  48. package/dist/scroll-area/root/scroll-area-root.js +1 -1
  49. package/dist/select/root/select-root.js +8 -9
  50. package/dist/slider/root/slider-root.js +2 -2
  51. package/dist/sparkline/root/sparkline-root.d.ts +2 -1
  52. package/dist/sparkline/root/sparkline-root.js +2 -2
  53. package/dist/speed-dial/item/speed-dial-item.js +1 -1
  54. package/dist/speed-dial/root/speed-dial-root.d.ts +2 -2
  55. package/dist/speed-dial/root/speed-dial-root.js +4 -7
  56. package/dist/switch/root/switch-root.js +3 -3
  57. package/dist/table/column/table-column.js +1 -1
  58. package/dist/tabs/root/tabs-root.d.ts +2 -17
  59. package/dist/tabs/root/tabs-root.js +3 -21
  60. package/dist/tabs/tab/tab.js +1 -1
  61. package/dist/textarea/root/textarea-root.js +3 -3
  62. package/dist/time-input/root/time-input-root.d.ts +5 -64
  63. package/dist/time-input/root/time-input-root.js +15 -107
  64. package/dist/time-picker/root/time-picker-root.js +2 -2
  65. package/dist/toggle/group/toggle-group.d.ts +0 -13
  66. package/dist/toggle/group/toggle-group.js +3 -23
  67. package/dist/toggle/root/toggle-root.js +1 -1
  68. package/dist/tooltip/root/tooltip-root.d.ts +0 -13
  69. package/dist/tooltip/root/tooltip-root.js +8 -16
  70. package/dist/tour/root/tour-root.js +6 -6
  71. package/dist/tour/stop/tour-stop.js +2 -2
  72. package/dist/video/root/captions.d.ts +85 -0
  73. package/dist/video/root/captions.js +45 -0
  74. package/dist/video/root/fullscreen.d.ts +28 -0
  75. package/dist/video/root/fullscreen.js +28 -0
  76. package/dist/video/root/idle.d.ts +34 -0
  77. package/dist/video/root/idle.js +24 -0
  78. package/dist/video/root/media-session.d.ts +54 -0
  79. package/dist/video/root/media-session.js +49 -0
  80. package/dist/video/root/remote-playback.d.ts +34 -0
  81. package/dist/video/root/remote-playback.js +38 -0
  82. package/dist/video/root/video-root.d.ts +11 -89
  83. package/dist/video/root/video-root.js +45 -139
  84. package/dist/video-playlist/menu/video-playlist-menu.d.ts +2 -1
  85. package/dist/video-playlist/menu/video-playlist-menu.js +8 -10
  86. package/dist/virtual-list/root/virtual-list-root.js +1 -1
  87. package/package.json +8 -7
  88. package/types/jsx.d.ts +264 -192
  89. package/types/vue.d.ts +78 -69
@@ -1,8 +1,8 @@
1
- import { AriaForwardController, HelixFormElement, PopoverController } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, HelixFormElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../date-picker/index.js';
4
4
  import '../../tag/index.js';
5
- declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinTokensInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
5
+ declare const HelixHeadlessDateInput_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinPickerEditingInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinPickerFieldInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinTokensInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
6
6
  /**
7
7
  * @summary A date, typed or picked from a calendar.
8
8
  *
@@ -142,20 +142,6 @@ export declare class HelixHeadlessDateInput extends HelixHeadlessDateInput_base
142
142
  * What the field says before a date is in it.
143
143
  */
144
144
  placeholder: string;
145
- /**
146
- * Whether the control refuses changes while still submitting its value.
147
- *
148
- * **Enforced by refusing every request in one place**, which is the seam this
149
- * component already has: `requestValue` is what every path here comes through,
150
- * and gating it once is what stops the next call site from being written
151
- * without the check.
152
- *
153
- * Not `disabled`, which is the confusion worth naming: a disabled control
154
- * submits nothing and leaves the tab order, and a read-only one submits its
155
- * value and stays reachable, which is what a form showing a value somebody
156
- * else set needs.
157
- */
158
- readOnly: boolean;
159
145
  /**
160
146
  * How far apart the selectable dates are, in days.
161
147
  *
@@ -165,16 +151,6 @@ export declare class HelixHeadlessDateInput extends HelixHeadlessDateInput_base
165
151
  * platform's base.
166
152
  */
167
153
  step: number;
168
- /**
169
- * The overlay, placed against the field rather than the trigger.
170
- *
171
- * Against the field because that is the box a reader sees as the control: a
172
- * calendar hung off a small button at one end of a wide field reads as
173
- * belonging to the button. `Select` anchors to its trigger for the opposite
174
- * reason, its trigger being the whole control.
175
- */
176
- protected readonly panel: PopoverController;
177
- private get control();
178
154
  /**
179
155
  * What the calendar should show, which is only ever a whole date.
180
156
  *
@@ -186,11 +162,7 @@ export declare class HelixHeadlessDateInput extends HelixHeadlessDateInput_base
186
162
  private get picked();
187
163
  /** One date, written the way `dateStyle` asks for. */
188
164
  private written;
189
- private get display();
190
- /**
191
- * Whether the caret is in the field, which is when it holds the value.
192
- */
193
- private editing;
165
+ protected get display(): string;
194
166
  /**
195
167
  * What is being typed but not yet held, for `multiple` only.
196
168
  *
@@ -212,57 +184,13 @@ export declare class HelixHeadlessDateInput extends HelixHeadlessDateInput_base
212
184
  /** Takes one date back out of the set. */
213
185
  private readonly removeDate;
214
186
  private readonly handleCalendarChange;
215
- /**
216
- * Puts the field back where the answer differs from what was typed.
217
- *
218
- * `requestValue` is the mixin's, and it announces and decides; this is the
219
- * other half, which `Input` needs for the same reason: the native control has
220
- * already changed, so wherever the answer does not follow it, it has to be
221
- * restored. A refusal and a consumer who owns `value` and has not given a new
222
- * one are the same thing from here.
223
- */
224
- private restore;
225
- /** Reports the value and the validity the host stands behind. */
226
- private sync;
227
- /** Escape shuts the calendar without moving the date. */
228
- private readonly handleKeydown;
187
+ /** What is wrong with the dates, which the text input cannot say. */
188
+ protected validate(): string;
229
189
  /**
230
190
  * Accessible name for the control that opens the calendar.
231
191
  */
232
192
  triggerLabel: string;
233
- /** Refuses every change while read-only. See `readOnly`. */
234
- requestValue(next: string): boolean;
235
- protected firstUpdated(changed: PropertyValues): void;
236
193
  protected updated(changed: PropertyValues): void;
237
- /**
238
- * Puts the caret back in the field without the field reopening the calendar.
239
- *
240
- * Focus opens the calendar, so handing focus back after a pick reopens the
241
- * thing that was just answered. `relatedTarget` was the guard and it is not
242
- * enough on its own: it catches a hand-off from the trigger, and it does not
243
- * catch this, because the cell that had focus is gone by the time the caret
244
- * moves and the document has fallen back to something outside this element.
245
- *
246
- * **Reasoned rather than measured, and worth saying which.** In a focused
247
- * document: the reader presses a day, the cell has focus, the panel is hidden,
248
- * the browser moves focus off the removed cell to the body, and the caret is
249
- * then put back here with `relatedTarget` pointing at the body, which is
250
- * outside this element and reads as an arrival. Every step is ordinary browser
251
- * behavior and none of it can be exercised here: `focus()` dispatches no
252
- * event in an unfocused document, which is what both the test runtime and the
253
- * available browser pane are. A spec for this passes with the guard removed,
254
- * so there is no spec: it would cover nothing while looking like proof.
255
- *
256
- * A flag rather than a better guess, because this element is the one moving
257
- * the focus and is the only thing that can know so. Cleared synchronously,
258
- * since `focus()` dispatches before it returns.
259
- */
260
- private returning;
261
- private returnFocus;
262
- private readonly handleBlur;
263
- private readonly handleFocus;
264
- /** Puts the caret in the field, the way a label pointing at one would. */
265
- focus(options?: FocusOptions): void;
266
194
  /**
267
195
  * The calendar the field opens, behind a seam the styled tier can move.
268
196
  *
@@ -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 { AriaForwardController, valueAsDate as asDate, customElement, dateAsValue as fromDate, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, valueAsDate as asDate, customElement, dateAsValue as fromDate, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinTokens, } from '@fusion.dev/helix-core';
8
8
  import { calendarBlank } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -18,11 +18,11 @@ const dates = (value) => value
18
18
  .split(',')
19
19
  .map((one) => one.trim())
20
20
  .filter(Boolean);
21
- let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinControllerOpen(MixinTokens(MixinControllerValue(MixinField(HelixFormElement)))) {
21
+ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEditing(MixinPickerField(MixinControllerOpen(MixinTokens(MixinControllerValue(MixinField(HelixFormElement)))))) {
22
22
  constructor() {
23
23
  super(...arguments);
24
24
  this.aria = new AriaForwardController(this, {
25
- target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
25
+ target: () => this.partNamed('control'),
26
26
  });
27
27
  this.autocomplete = '';
28
28
  this.dateStyle = 'long';
@@ -34,20 +34,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinControlle
34
34
  this.min = '';
35
35
  this.months = 1;
36
36
  this.placeholder = 'YYYY-MM-DD';
37
- this.readOnly = false;
38
37
  this.step = 1;
39
- this.panel = new PopoverController(this, {
40
- anchor: () => this.renderRoot?.querySelector('[part="field"]'),
41
- boundary: () => [
42
- this.renderRoot?.querySelector('[part="surface"]'),
43
- this.renderRoot?.querySelector('[part="trigger"]'),
44
- ],
45
- offset: () => 8,
46
- onDismiss: () => this.requestOpen(false),
47
- placement: 'bottom-start',
48
- surface: () => this.renderRoot?.querySelector('[part="surface"]'),
49
- });
50
- this.editing = false;
51
38
  this.draft = '';
52
39
  this.handleInput = (event) => {
53
40
  event.stopPropagation();
@@ -93,35 +80,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinControlle
93
80
  this.updateComplete.then(() => this.returnFocus());
94
81
  }
95
82
  };
96
- this.handleKeydown = (event) => {
97
- if (event.key === 'Escape' && this.open) {
98
- event.stopPropagation();
99
- this.requestOpen(false);
100
- this.returnFocus();
101
- }
102
- };
103
83
  this.triggerLabel = 'Choose a date';
104
- this.returning = false;
105
- this.handleBlur = () => {
106
- this.editing = false;
107
- };
108
- this.handleFocus = (event) => {
109
- this.editing = !this.returning;
110
- const from = event.relatedTarget;
111
- if (this.open || this.isDisabled || this.returning) {
112
- return;
113
- }
114
- if (from instanceof Node && this.renderRoot?.contains?.(from)) {
115
- return;
116
- }
117
- this.requestOpen(true);
118
- };
119
- }
120
- get control() {
121
- if (typeof this.renderRoot?.querySelector !== 'function') {
122
- return null;
123
- }
124
- return this.renderRoot.querySelector('[part="control"]');
125
84
  }
126
85
  get picked() {
127
86
  const held = dates(this.value);
@@ -157,74 +116,29 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinControlle
157
116
  get restCount() {
158
117
  return Math.max(0, dates(this.value).length - this.shownDates.length);
159
118
  }
160
- restore() {
161
- const control = this.control;
162
- if (control && control.value !== this.display) {
163
- control.value = this.display;
164
- }
165
- this.sync();
166
- }
167
- sync() {
168
- this.internals.setFormValue(this.value);
169
- const control = this.control;
170
- if (!control) {
171
- return;
172
- }
119
+ validate() {
173
120
  const held = dates(this.value);
174
- let message = '';
175
121
  if (held.length > 0 && !held.every(isIso)) {
176
- message =
177
- this.mode === undefined
178
- ? 'Enter a date as YYYY-MM-DD.'
179
- : 'Enter dates as YYYY-MM-DD, separated by commas.';
180
- }
181
- else if (this.min !== '' && held.some((one) => one < this.min)) {
182
- message = `Choose a date on or after ${this.min}.`;
122
+ return this.mode === undefined
123
+ ? 'Enter a date as YYYY-MM-DD.'
124
+ : 'Enter dates as YYYY-MM-DD, separated by commas.';
183
125
  }
184
- else if (this.max !== '' && held.some((one) => one > this.max)) {
185
- message = `Choose a date on or before ${this.max}.`;
126
+ if (this.min !== '' && held.some((one) => one < this.min)) {
127
+ return `Choose a date on or after ${this.min}.`;
186
128
  }
187
- else if (this.mode === 'range' && held.length > 2) {
188
- message = 'A range is two dates.';
129
+ if (this.max !== '' && held.some((one) => one > this.max)) {
130
+ return `Choose a date on or before ${this.max}.`;
189
131
  }
190
- if (message === '') {
191
- this.internals.setValidity(control.validity, control.validationMessage, control);
132
+ if (this.mode === 'range' && held.length > 2) {
133
+ return 'A range is two dates.';
192
134
  }
193
- else {
194
- this.internals.setValidity({ badInput: true }, message, control);
195
- }
196
- this.refreshFeedback();
197
- }
198
- requestValue(next) {
199
- if (this.readOnly) {
200
- return false;
201
- }
202
- return super.requestValue(next);
203
- }
204
- firstUpdated(changed) {
205
- super.firstUpdated(changed);
206
- this.sync();
135
+ return '';
207
136
  }
208
137
  updated(changed) {
209
138
  super.updated(changed);
210
- this.sync();
211
- if (this.open) {
212
- this.panel.show();
213
- if (changed.has('open')) {
214
- this.renderRoot?.querySelector('[part="picker"]')?.focus();
215
- }
139
+ if (this.open && changed.has('open')) {
140
+ this.partNamed('picker')?.focus();
216
141
  }
217
- else {
218
- this.panel.hide();
219
- }
220
- }
221
- returnFocus() {
222
- this.returning = true;
223
- this.control?.focus();
224
- this.returning = false;
225
- }
226
- focus(options) {
227
- this.control?.focus(options);
228
142
  }
229
143
  renderPicker() {
230
144
  return html `<${this.tag('date-picker')}
@@ -359,15 +273,9 @@ __decorate([
359
273
  __decorate([
360
274
  property()
361
275
  ], HelixHeadlessDateInput.prototype, "placeholder", void 0);
362
- __decorate([
363
- property({ attribute: 'readonly', reflect: true, type: Boolean })
364
- ], HelixHeadlessDateInput.prototype, "readOnly", void 0);
365
276
  __decorate([
366
277
  property({ type: Number })
367
278
  ], HelixHeadlessDateInput.prototype, "step", void 0);
368
- __decorate([
369
- state()
370
- ], HelixHeadlessDateInput.prototype, "editing", void 0);
371
279
  __decorate([
372
280
  state()
373
281
  ], HelixHeadlessDateInput.prototype, "draft", void 0);
@@ -70,7 +70,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
70
70
  return;
71
71
  }
72
72
  const path = event.composedPath();
73
- const grid = this.renderRoot?.querySelector('[part="grid"]');
73
+ const grid = this.partNamed('grid');
74
74
  if (!grid || !path.includes(grid)) {
75
75
  return;
76
76
  }
@@ -240,7 +240,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
240
240
  return super.requestValue(next);
241
241
  }
242
242
  reportRequired() {
243
- const anchor = this.renderRoot?.querySelector('[part="grid"]') ?? undefined;
243
+ const anchor = this.partNamed('grid') ?? undefined;
244
244
  if (this.required && this.value === '') {
245
245
  this.internals.setValidity({ valueMissing: true }, 'Choose a date.', anchor);
246
246
  return;
@@ -13,7 +13,7 @@ let HelixHeadlessDivider = class HelixHeadlessDivider extends HelixElement {
13
13
  this.announced = false;
14
14
  }
15
15
  markEmpty() {
16
- const region = this.renderRoot?.querySelector('[part="content"]');
16
+ const region = this.partNamed('content');
17
17
  const empty = !slotFilled(this.renderRoot?.querySelector('slot'));
18
18
  region?.toggleAttribute('data-empty', empty);
19
19
  region?.toggleAttribute('hidden', empty);
@@ -47,7 +47,7 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
47
47
  }
48
48
  }
49
49
  get panel() {
50
- return this.renderRoot?.querySelector('[part="panel"]') ?? null;
50
+ return this.partNamed('panel');
51
51
  }
52
52
  updated(changed) {
53
53
  super.updated(changed);
@@ -18,7 +18,7 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
18
18
  this.value = '';
19
19
  }
20
20
  focusItem() {
21
- this.renderRoot.querySelector('[part="item"]')?.focus();
21
+ this.partNamed('item')?.focus();
22
22
  }
23
23
  select() {
24
24
  if (this.disabled) {
@@ -36,7 +36,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
36
36
  constructor() {
37
37
  super(...arguments);
38
38
  this.aria = new AriaForwardController(this, {
39
- target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
39
+ target: () => this.partNamed('control'),
40
40
  });
41
41
  this.accept = '';
42
42
  this.locale = '';
@@ -87,10 +87,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
87
87
  return [...(this.control?.files ?? [])];
88
88
  }
89
89
  get control() {
90
- if (typeof this.renderRoot?.querySelector !== 'function') {
91
- return null;
92
- }
93
- return this.renderRoot.querySelector('[part="control"]');
90
+ return this.partNamed('control');
94
91
  }
95
92
  clear() {
96
93
  const control = this.control;
@@ -120,8 +117,8 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
120
117
  this.dispatchEvent(new HelixBeforeChangeEvent({
121
118
  value: this.files.map((file) => file.name).join(', '),
122
119
  }));
123
- this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
124
- this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
120
+ this.announceInput();
121
+ this.announceChange();
125
122
  }
126
123
  sync() {
127
124
  const data = new FormData();
@@ -33,7 +33,7 @@ import type { HelixHeadlessFilterOperator } from '../operator/filter-operator.js
33
33
  * mean, so declaring them was ceremony for the case every product has; the
34
34
  * example above is what a consumer writes when the words matter, not the price
35
35
  * of admission. Declaring one comparison replaces both rather than joining
36
- * them — see `vocabulary`, which is where that is decided once for the menu and
36
+ * them: see `vocabulary`, which is where that is decided once for the menu and
37
37
  * the grammar together.
38
38
  *
39
39
  * **Values are `Option`s, which is the element a consumer already has.** They
@@ -24,8 +24,8 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
24
24
  * pointer has already handled the keyboard.
25
25
  *
26
26
  * **It reads what was declared, and nothing that was not.** Write
27
- * `FilterField`s — usually inside the `FilterMenu` slotted into `start`, so the
28
- * picker and the text work off one set — and Enter reads the box against them:
27
+ * `FilterField`s (usually inside the `FilterMenu` slotted into `start`, so the
28
+ * picker and the text work off one set), and Enter reads the box against them:
29
29
  * `disposition:refunded,settled` and `-disposition:refunded` announce
30
30
  * `helix-facet`, exactly as ticking those values in the picker would, and the
31
31
  * text they were read from goes. Everything else stays where it was typed, so
@@ -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 { AriaForwardController, customElement, drawIcon, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, restoreValue, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, drawIcon, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, restoreValue, scrollItemIntoView, } from '@fusion.dev/helix-core';
8
8
  import { x } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -15,7 +15,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
15
15
  constructor() {
16
16
  super(...arguments);
17
17
  this.aria = new AriaForwardController(this, {
18
- target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
18
+ target: () => this.partNamed('control'),
19
19
  });
20
20
  this.announcesInput = true;
21
21
  this.clearable = false;
@@ -28,15 +28,16 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
28
28
  this.leadRead = false;
29
29
  this.dismissed = false;
30
30
  this.panel = new PopoverController(this, {
31
- anchor: () => this.renderRoot?.querySelector('[part="field"]'),
31
+ anchor: () => this.partNamed('field'),
32
32
  boundary: () => [
33
- this.renderRoot?.querySelector('[part="surface"]'),
34
- this.renderRoot?.querySelector('[part="field"]'),
33
+ this.partNamed('surface'),
34
+ this.partNamed('field'),
35
35
  ],
36
36
  offset: () => 4,
37
37
  onDismiss: () => this.requestOpen(false),
38
+ open: () => this.open,
38
39
  placement: 'bottom-start',
39
- surface: () => this.renderRoot?.querySelector('[part="surface"]'),
40
+ surface: () => this.partNamed('surface'),
40
41
  });
41
42
  this.handleInput = (event) => {
42
43
  event.stopPropagation();
@@ -48,7 +49,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
48
49
  };
49
50
  this.handleChange = (event) => {
50
51
  event.stopPropagation();
51
- this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
52
+ this.announceChange();
52
53
  };
53
54
  this.handleDismiss = (event) => {
54
55
  const chip = event.composedPath()[0];
@@ -154,7 +155,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
154
155
  delegatesFocus: true,
155
156
  }; }
156
157
  get control() {
157
- return this.renderRoot.querySelector('[part="control"]');
158
+ return this.partNamed('control');
158
159
  }
159
160
  get tokens() {
160
161
  return this.renderRoot?.querySelector('slot[name="tags"]') ?? null;
@@ -304,12 +305,6 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
304
305
  this.releaseCaret = undefined;
305
306
  this.internals.setFormValue(this.value);
306
307
  this.markActive();
307
- if (this.open) {
308
- this.panel.show();
309
- }
310
- else {
311
- this.panel.hide();
312
- }
313
308
  this.settleOpen();
314
309
  }
315
310
  markActive() {
@@ -323,7 +318,9 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
323
318
  ;
324
319
  control.ariaActiveDescendantElement = active ?? null;
325
320
  }
326
- active?.scrollIntoView({ block: 'nearest' });
321
+ if (active) {
322
+ scrollItemIntoView(active);
323
+ }
327
324
  }
328
325
  focus(options) {
329
326
  this.control?.focus(options);
@@ -51,7 +51,7 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
51
51
  return fits(this.points.length, this.width ?? Number.NaN, this.cellPx, this.gapPx, this.rowCount);
52
52
  }
53
53
  measure() {
54
- const strip = this.renderRoot?.querySelector('[part="strip"]');
54
+ const strip = this.partNamed('strip');
55
55
  if (!strip || typeof getComputedStyle !== 'function') {
56
56
  return;
57
57
  }
@@ -110,6 +110,13 @@ export declare class HelixHeadlessImage extends HelixElement {
110
110
  * Which `src` the current status belongs to, so a new one starts over.
111
111
  */
112
112
  private answered;
113
+ /**
114
+ * Whether this picture's answer has been given, or is on its way.
115
+ *
116
+ * `status` cannot say so for a picture a server sent, since it waits a task
117
+ * for the event it reports, and `settle` runs twice in the first update.
118
+ */
119
+ private reported;
113
120
  /**
114
121
  * Says so under the platform's own name, because the platform's cannot escape.
115
122
  *
@@ -20,6 +20,7 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
20
20
  this.srcset = '';
21
21
  this.status = 'loading';
22
22
  this.answered = '';
23
+ this.reported = false;
23
24
  this.handleLoad = (event) => {
24
25
  this.announce('loaded', event);
25
26
  };
@@ -28,13 +29,15 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
28
29
  };
29
30
  }
30
31
  announce(status, event) {
31
- this.status = status;
32
+ this.reported = true;
32
33
  const name = status === 'loaded' ? 'load' : 'error';
33
34
  if (event) {
35
+ this.status = status;
34
36
  this.relayNativeEvent(event);
35
37
  return;
36
38
  }
37
39
  const send = () => {
40
+ this.status = status;
38
41
  this.dispatchEvent(new Event(name, { bubbles: false, cancelable: false, composed: false }));
39
42
  };
40
43
  if (this.serverRendered) {
@@ -46,7 +49,7 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
46
49
  }
47
50
  settle() {
48
51
  const image = this.renderRoot?.querySelector('img');
49
- if (this.src === '' || this.status !== 'loading' || !image?.complete) {
52
+ if (this.src === '' || this.reported || !image?.complete) {
50
53
  return;
51
54
  }
52
55
  if (image.naturalWidth > 0) {
@@ -60,6 +63,7 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
60
63
  super.willUpdate(changed);
61
64
  if (changed.has('src') && this.answered !== this.src) {
62
65
  this.answered = this.src;
66
+ this.reported = false;
63
67
  this.status = 'loading';
64
68
  }
65
69
  }
@@ -12,7 +12,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
12
12
  constructor() {
13
13
  super(...arguments);
14
14
  this.aria = new AriaForwardController(this, {
15
- target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
15
+ target: () => this.partNamed('control'),
16
16
  });
17
17
  this.announcesInput = true;
18
18
  this.autocomplete = '';
@@ -39,7 +39,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
39
39
  };
40
40
  this.handleChange = (event) => {
41
41
  event.stopPropagation();
42
- this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
42
+ this.announceChange();
43
43
  };
44
44
  this.reveal = false;
45
45
  this.revealLabel = 'Show password';
@@ -86,7 +86,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
86
86
  this.control?.setSelectionRange(start, end, direction);
87
87
  }
88
88
  get control() {
89
- return this.renderRoot.querySelector('[part="control"]');
89
+ return this.partNamed('control');
90
90
  }
91
91
  sync() {
92
92
  this.internals.setFormValue(this.value);
@@ -59,7 +59,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
59
59
  constructor() {
60
60
  super(...arguments);
61
61
  this.aria = new AriaForwardController(this, {
62
- target: () => this.renderRoot?.querySelector('[part="items"]') ?? null,
62
+ target: () => this.partNamed('items'),
63
63
  });
64
64
  this.autocomplete = 'off';
65
65
  this.defaultFilter = '';
@@ -225,7 +225,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
225
225
  }
226
226
  }
227
227
  reveal() {
228
- const items = this.renderRoot?.querySelector('[part="items"]');
228
+ const items = this.partNamed('items');
229
229
  const chosen = this.options.find((option) => option.selected && !option.hidden);
230
230
  if (!items || !chosen) {
231
231
  return;
@@ -278,7 +278,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
278
278
  followActive() {
279
279
  const active = this.shown.find((option) => option.value === this.activeValue);
280
280
  if (active) {
281
- const items = this.renderRoot.querySelector('[part="items"]');
281
+ const items = this.partNamed('items');
282
282
  scrollItemIntoView(active, { padding: 4, ...(items ? { scroller: items } : {}) });
283
283
  }
284
284
  }
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { HelixElement, PopoverController } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
4
4
  /**
@@ -92,7 +92,7 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
92
92
  private get banded();
93
93
  private readonly handleWidth;
94
94
  private watchWidth;
95
- private readonly panel;
95
+ protected readonly panel: PopoverController;
96
96
  /** Tells a slotted trigger the disclosure state, by property. See `tellDisclosure`. */
97
97
  private tellTrigger;
98
98
  /** What the consumer slotted, if they slotted anything. */
@@ -202,6 +202,16 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
202
202
  private readonly handleContentClick;
203
203
  connectedCallback(): void;
204
204
  disconnectedCallback(): void;
205
+ /**
206
+ * Hands the bar's one open panel over before this one is shown.
207
+ *
208
+ * Before the render rather than after it, because the popover controller
209
+ * raises the panel from its own `hostUpdated`, which runs before `updated`:
210
+ * settled there, the two would be up together for a frame. The other closes
211
+ * through `requestOpen`, so a consumer holding it open is asked rather than
212
+ * overruled.
213
+ */
214
+ protected willUpdate(changed: PropertyValues): void;
205
215
  protected updated(changed: PropertyValues): void;
206
216
  /**
207
217
  * The control that opens it, behind a seam the styled tier can move.