@fusion.dev/helix-headless 0.170.0 → 0.172.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 (57) hide show
  1. package/custom-elements.json +1045 -154
  2. package/dist/chat/composer/chat-composer.d.ts +30 -2
  3. package/dist/chat/composer/chat-composer.js +29 -4
  4. package/dist/chat/root/chat-root.d.ts +73 -7
  5. package/dist/chat/root/chat-root.js +90 -5
  6. package/dist/code-block/root/code-block-root.d.ts +20 -2
  7. package/dist/code-block/root/code-block-root.js +29 -8
  8. package/dist/color-input/root/color-input-root.d.ts +15 -1
  9. package/dist/color-input/root/color-input-root.js +19 -1
  10. package/dist/color-picker/root/color-picker-root.d.ts +54 -7
  11. package/dist/color-picker/root/color-picker-root.js +62 -1
  12. package/dist/combobox/root/combobox-root.d.ts +16 -6
  13. package/dist/combobox/root/combobox-root.js +9 -2
  14. package/dist/command/root/command-root.d.ts +16 -1
  15. package/dist/command/root/command-root.js +8 -0
  16. package/dist/data-table/root/data-table-root.d.ts +5 -1
  17. package/dist/data-table/root/data-table-root.js +1 -0
  18. package/dist/dialog/root/dialog-root.js +1 -0
  19. package/dist/drawer/root/drawer-root.d.ts +4 -0
  20. package/dist/drawer/root/drawer-root.js +1 -0
  21. package/dist/element-events.generated.js +1 -0
  22. package/dist/filter-input/chip/filter-chip.d.ts +13 -3
  23. package/dist/filter-input/chip/filter-chip.js +5 -0
  24. package/dist/filter-input/menu/filter-menu.js +0 -1
  25. package/dist/filter-input/root/filter-input-root.d.ts +71 -4
  26. package/dist/filter-input/root/filter-input-root.js +87 -2
  27. package/dist/format-date/root/format-date-root.d.ts +3 -1
  28. package/dist/input/root/input-root.d.ts +6 -0
  29. package/dist/input/root/input-root.js +13 -0
  30. package/dist/lightbox/root/lightbox-root.js +1 -0
  31. package/dist/listbox/root/listbox-root.d.ts +8 -1
  32. package/dist/listbox/root/listbox-root.js +9 -3
  33. package/dist/menu/item/menu-item.d.ts +2 -0
  34. package/dist/menu/item/menu-item.js +2 -0
  35. package/dist/menu/root/menu-root.d.ts +73 -12
  36. package/dist/menu/root/menu-root.js +103 -7
  37. package/dist/number-input/root/number-input-root.d.ts +16 -6
  38. package/dist/number-input/root/number-input-root.js +15 -4
  39. package/dist/popover/root/popover-root.d.ts +59 -6
  40. package/dist/popover/root/popover-root.js +51 -3
  41. package/dist/select/root/select-root.d.ts +2 -2
  42. package/dist/select/root/select-root.js +4 -2
  43. package/dist/steps/root/steps-root.d.ts +32 -8
  44. package/dist/steps/root/steps-root.js +70 -9
  45. package/dist/steps/step/step.d.ts +9 -1
  46. package/dist/steps/step/step.js +5 -0
  47. package/dist/textarea/root/textarea-root.d.ts +8 -0
  48. package/dist/textarea/root/textarea-root.js +16 -0
  49. package/dist/video-playlist/root/video-playlist-root.d.ts +19 -1
  50. package/dist/video-playlist/root/video-playlist-root.js +14 -3
  51. package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
  52. package/dist/virtual-list/root/virtual-list-root.js +7 -2
  53. package/lint.json +1 -1
  54. package/package.json +5 -5
  55. package/types/jsx.d.ts +800 -144
  56. package/types/vue.d.ts +165 -31
  57. package/types/words.d.ts +20 -2
@@ -31,7 +31,8 @@ declare const HelixHeadlessColorInput_base: (new (...args: any[]) => import("@fu
31
31
  *
32
32
  * The trigger and ArrowDown open the panel with focus in the square. Tab moves
33
33
  * between the panel's controls without leaving it, and Escape closes it and
34
- * returns focus to the field.
34
+ * returns focus to the control that opened it: the trigger after a press, and
35
+ * the field after ArrowDown.
35
36
  *
36
37
  * The component sets `--color-input-swatch`, so do not set it yourself. It
37
38
  * holds the resolved color, so the styled tier can paint the swatch without
@@ -305,6 +306,19 @@ export declare class HelixHeadlessColorInput extends HelixHeadlessColorInput_bas
305
306
  */
306
307
  protected sync(): void;
307
308
  protected updated(changed: PropertyValues): void;
309
+ /** Whether the trigger's press opened the panel that is open now. */
310
+ private pressedOpen;
311
+ /**
312
+ * The trigger's press, which notes that it opened the panel, so Escape hands
313
+ * focus back to the trigger.
314
+ */
315
+ private readonly handlePress;
316
+ /**
317
+ * Hands focus back to the control that opened the panel: the trigger after
318
+ * its press, which is the APG date picker dialog's rule, and the text after
319
+ * ArrowDown from it.
320
+ */
321
+ protected returnFocus(): void;
308
322
  /**
309
323
  * The picker the field opens, behind a seam the styled tier can move.
310
324
  *
@@ -55,6 +55,12 @@ let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerF
55
55
  this.handleSampleEnd = () => {
56
56
  this.requestOpen(true);
57
57
  };
58
+ this.pressedOpen = false;
59
+ this.handlePress = () => {
60
+ const opening = !this.open;
61
+ this.handleTriggerClick();
62
+ this.pressedOpen = opening && this.open;
63
+ };
58
64
  }
59
65
  static { this.words = 'helix-color-input'; }
60
66
  requestValue(next) {
@@ -117,6 +123,18 @@ let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerF
117
123
  if (changed.has('value') && parseColor(this.value)) {
118
124
  this.settled = this.value;
119
125
  }
126
+ if (changed.has('open') && !this.open) {
127
+ this.pressedOpen = false;
128
+ }
129
+ }
130
+ returnFocus() {
131
+ const trigger = this.pressedOpen ? this.partNamed('trigger') : null;
132
+ this.pressedOpen = false;
133
+ if (trigger) {
134
+ trigger.focus();
135
+ return;
136
+ }
137
+ super.returnFocus();
120
138
  }
121
139
  renderPicker() {
122
140
  return html `<${this.tag('color-picker')}
@@ -204,7 +222,7 @@ let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerF
204
222
  : 'swatch-value'
205
223
  : nothing}
206
224
  ?disabled=${this.isDisabled}
207
- @click=${this.handleTriggerClick}
225
+ @click=${this.handlePress}
208
226
  >
209
227
  <span
210
228
  part="preview"
@@ -1,5 +1,5 @@
1
- import { type Color, type ColorFormat, type ColorWords, HelixFormElement, KeyboardNavigationController } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
1
+ import { type Color, type ColorFormat, type ColorWords, HelixFormElement, KeyboardNavigationController, type PickerFailure } from '@fusion.dev/helix-core';
2
+ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../button/index.js';
4
4
  import '../../button-group/index.js';
5
5
  import '../../slider/index.js';
@@ -82,7 +82,7 @@ declare const HelixHeadlessColorPicker_base: (new (...args: any[]) => import("@f
82
82
  * @csspart eyedropper-live - The live region of the button inside `eyedropper`, which says its pending label.
83
83
  * @csspart eyedropper-spinner - The ring the eyedropper button turns while it is pending.
84
84
  * @csspart eyedropper-text - The words on the button inside `eyedropper`, trimmed to their capitals and baseline.
85
- * @csspart feedback - The message under the picker, while `feedback` is set.
85
+ * @csspart feedback - The message under the picker: `feedback`, or what is wrong with the answer once the picker is marked invalid.
86
86
  * @csspart field - One channel's number field with its label, in `channel-fields`.
87
87
  * @csspart field-input - The number input in a channel's field.
88
88
  * @csspart field-label - The name above a channel's number input.
@@ -321,10 +321,11 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
321
321
  * the anchor the browser points its bubble at.
322
322
  *
323
323
  * **Text that is not a color is bad input**, so a form does not submit
324
- * `#00` from a half-typed value. The picker draws no message of its own, so
325
- * reporting it as each keystroke arrives shows nothing until the form asks;
326
- * Color Input shows its own message against the field the reader typed
327
- * into.
324
+ * `#00` from a half-typed value. The picker marks it, and says
325
+ * `badInputMessage` under itself, once the reader leaves the value field or
326
+ * presses Enter, and a missing color once the reader has committed one or a
327
+ * form has refused the picker. Color Input shows its own message against the
328
+ * field the reader typed into.
328
329
  */
329
330
  required: boolean;
330
331
  /**
@@ -404,6 +405,52 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
404
405
  private get hinted();
405
406
  /** The ids a control in the picker is described by: the help, then the message. */
406
407
  private get describedBy();
408
+ /**
409
+ * Whether the reader is typing into the value field, between the first
410
+ * keystroke and the `change` that ends it.
411
+ *
412
+ * Half-typed text is rarely a color: `#00ff00` passes through `#0` and
413
+ * `#00ff0` on the way. So text that is not a color is refused once the
414
+ * reader leaves the field or presses Enter, and not on every keystroke.
415
+ */
416
+ private typing;
417
+ /**
418
+ * Text in the value field that is not a color, or undefined while the field
419
+ * holds the value.
420
+ *
421
+ * **Kept in the field and out of the value**, as a native number field keeps
422
+ * "12e": the text stays where the reader can correct it, the value stays the
423
+ * last color, and the picker reports `badInput` until the text is a color or
424
+ * the value moves. Measured before this: typed "nonsense" became the value,
425
+ * so a form read `brand=nonsense` and the square had no color to show.
426
+ */
427
+ private draft;
428
+ /**
429
+ * A required picker with no color is missing, and text that is not a color
430
+ * is refused, once the reader has finished typing it. See
431
+ * `MixinPickerFeedback.marked`.
432
+ *
433
+ * Measured before this: typed "nonsense" and a required picker its form
434
+ * refused both failed `checkValidity()` while `:state(invalid)` stayed off,
435
+ * the value field had no `aria-invalid`, and nothing was said.
436
+ */
437
+ protected get failure(): PickerFailure | undefined;
438
+ /**
439
+ * The line under the picker: `feedback`, or, where the picker is marked by
440
+ * its own validation, what is wrong.
441
+ */
442
+ private get said();
443
+ /**
444
+ * A reset ends any typing and takes back text that was not a color, so the
445
+ * field shows the color the reset put back.
446
+ */
447
+ formResetCallback(): void;
448
+ /**
449
+ * The line `said` holds, or nothing while it is empty. `role="alert"`, as
450
+ * every field's feedback line has, so the message is announced when it
451
+ * appears.
452
+ */
453
+ protected renderPickerFeedback(): TemplateResult | typeof nothing;
407
454
  /** Whether a pointer is dragging the square's handle, said as `:state(dragging)`. */
408
455
  private dragging;
409
456
  /** Whether the eyedropper is open, said as `:state(sampling)`. */
@@ -56,6 +56,8 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
56
56
  this.handleNamingSlot = () => {
57
57
  this.requestUpdate();
58
58
  };
59
+ this.typing = false;
60
+ this.draft = undefined;
59
61
  this.dragging = false;
60
62
  this.picking = false;
61
63
  this.owned = false;
@@ -68,7 +70,14 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
68
70
  return;
69
71
  }
70
72
  const typed = event.target.value;
73
+ this.typing = true;
71
74
  const parsed = parseColor(typed);
75
+ if (!parsed && typed.trim() !== '') {
76
+ this.draft = typed;
77
+ this.sync();
78
+ return;
79
+ }
80
+ this.draft = undefined;
72
81
  if (parsed) {
73
82
  this.moved = parsed;
74
83
  this.owned = typed !== this.value;
@@ -261,9 +270,47 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
261
270
  return this.hint !== '' || this.slottedIn('hint').length > 0;
262
271
  }
263
272
  get describedBy() {
264
- const ids = [this.hinted ? 'hint' : '', this.feedback === '' ? '' : 'feedback'].filter(Boolean);
273
+ const ids = [this.hinted ? 'hint' : '', this.said === '' ? '' : 'feedback'].filter(Boolean);
265
274
  return ids.length > 0 ? ids.join(' ') : nothing;
266
275
  }
276
+ get failure() {
277
+ if (this.draft !== undefined) {
278
+ return this.typing ? undefined : 'refused';
279
+ }
280
+ if (this.value === '') {
281
+ return this.required ? 'missing' : undefined;
282
+ }
283
+ return parseColor(this.value) === null ? 'refused' : undefined;
284
+ }
285
+ get said() {
286
+ if (this.feedback !== '') {
287
+ return this.feedback;
288
+ }
289
+ if (!this.marked || this.invalid) {
290
+ return '';
291
+ }
292
+ const failure = this.failure;
293
+ return failure === 'refused'
294
+ ? this.badInputMessage
295
+ : failure === 'missing'
296
+ ? this.valueMissingMessage
297
+ : '';
298
+ }
299
+ formResetCallback() {
300
+ super.formResetCallback();
301
+ this.typing = false;
302
+ if (this.draft !== undefined) {
303
+ this.draft = undefined;
304
+ const field = this.partNamed('value');
305
+ if (field) {
306
+ field.value = this.value;
307
+ }
308
+ }
309
+ }
310
+ renderPickerFeedback() {
311
+ const said = this.said;
312
+ return said === '' ? nothing : html `<p part="feedback" id="feedback" role="alert">${said}</p>`;
313
+ }
267
314
  settle() {
268
315
  if (this.unsettled) {
269
316
  this.unsettled = false;
@@ -299,6 +346,10 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
299
346
  this.reportRequired();
300
347
  }
301
348
  reportRequired() {
349
+ if (this.draft !== undefined) {
350
+ this.internals.setValidity({ badInput: true }, this.badInputMessage, this.partNamed('value') ?? this.partNamed('grid') ?? undefined);
351
+ return;
352
+ }
302
353
  if (this.required && this.value === '') {
303
354
  this.internals.setValidity({ valueMissing: true }, this.valueMissingMessage, this.partNamed('grid') ?? undefined);
304
355
  return;
@@ -424,6 +475,9 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
424
475
  this.move({ [channel]: Number(control.value) });
425
476
  }
426
477
  willUpdate(changed) {
478
+ if (changed.has('value')) {
479
+ this.draft = undefined;
480
+ }
427
481
  super.willUpdate(changed);
428
482
  const outside = [...(this.internals.labels ?? [])];
429
483
  const named = this.named;
@@ -630,6 +684,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
630
684
  @input=${this.handleValueInput}
631
685
  @change=${(event) => {
632
686
  event.stopPropagation();
687
+ this.typing = false;
633
688
  this.settle();
634
689
  }}
635
690
  />`}
@@ -770,6 +825,12 @@ __decorate([
770
825
  __decorate([
771
826
  state()
772
827
  ], HelixHeadlessColorPicker.prototype, "swatchAt", void 0);
828
+ __decorate([
829
+ state()
830
+ ], HelixHeadlessColorPicker.prototype, "typing", void 0);
831
+ __decorate([
832
+ state()
833
+ ], HelixHeadlessColorPicker.prototype, "draft", void 0);
773
834
  __decorate([
774
835
  state()
775
836
  ], HelixHeadlessColorPicker.prototype, "dragging", void 0);
@@ -240,12 +240,13 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
240
240
  /**
241
241
  * Whether more than one option can be chosen.
242
242
  *
243
- * The list already does the work: `value` comes apart on commas, a press
244
- * toggles rather than replaces, and the arrows move without choosing. What
245
- * this adds is the tags, and keeping the panel open between picks.
243
+ * The list already does the work: `value` is the chosen values joined with
244
+ * commas, a press toggles rather than replaces, and the arrows move without
245
+ * choosing. What this adds is the tags, and keeping the panel open between
246
+ * picks.
246
247
  *
247
- * A comma is therefore not available inside an option's value, which is the
248
- * listbox's contract and not this element's to change.
248
+ * An option's value may hold a comma, and it is read as one value, as
249
+ * `Select` and `Listbox` read it. So may an answer the reader created.
249
250
  *
250
251
  * **The list is told, and the field is not.** `aria-multiselectable` belongs
251
252
  * to `listbox`, `grid`, `tree` and `tablist`, and `combobox` dropped it in
@@ -385,8 +386,17 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
385
386
  */
386
387
  protected readonly upgradeWatch: UpgradeController;
387
388
  protected readonly panel: PopoverController;
388
- /** The values chosen, in the order they were chosen. */
389
+ /**
390
+ * The values chosen, in the order they were chosen, read against the
391
+ * options' values and the answers created here, so a value that holds a
392
+ * comma is one value. See `chosenValues`.
393
+ *
394
+ * Measured before this: choosing `Paris, France` drew two tags, "Paris" and
395
+ * "France", and the form received two entries.
396
+ */
389
397
  private get values();
398
+ /** The answers created here, which no option holds, kept so their commas read as theirs. */
399
+ private readonly created;
390
400
  /**
391
401
  * The chosen options themselves, for their words.
392
402
  *
@@ -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, afterLinkFollowed, countLabel, customElement, DelayController, drawAutofill, drawStatus, formatWords, HelixCreateEvent, HelixFilterEvent, HelixFormElement, handedLocale, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, onApple, PopoverController, reportValues, StatusController, submitImplicitly, UpgradeController, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, afterLinkFollowed, chosenValues, countLabel, customElement, DelayController, drawAutofill, drawStatus, formatWords, HelixCreateEvent, HelixFilterEvent, HelixFormElement, handedLocale, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, onApple, PopoverController, reportValues, StatusController, submitImplicitly, UpgradeController, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { caretDown } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -69,6 +69,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
69
69
  placement: 'bottom-start',
70
70
  surface: () => this.partNamed('surface'),
71
71
  });
72
+ this.created = new Set();
72
73
  this.handleInput = (event) => {
73
74
  event.stopPropagation();
74
75
  const typed = event.target.value;
@@ -252,7 +253,11 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
252
253
  return this.partNamed('listbox');
253
254
  }
254
255
  get values() {
255
- return this.value === '' ? [] : this.value.split(',');
256
+ const known = new Set(this.created);
257
+ for (const option of optionsAmong(this.knownChildren)) {
258
+ known.add(option.value);
259
+ }
260
+ return chosenValues(this.value, known, this.multiple);
256
261
  }
257
262
  get chosen() {
258
263
  const options = optionsAmong(this.knownChildren);
@@ -296,6 +301,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
296
301
  return;
297
302
  }
298
303
  const next = this.multiple ? [...this.values, wanted].join(',') : wanted;
304
+ this.created.add(wanted);
299
305
  if (!this.requestValue(next)) {
300
306
  return;
301
307
  }
@@ -343,6 +349,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
343
349
  }
344
350
  const named = (this.listbox?.options ?? []).find((option) => option.text.trim().toLowerCase() === typed.toLowerCase());
345
351
  const answer = named?.value ?? typed;
352
+ this.created.add(answer);
346
353
  if (this.multiple) {
347
354
  if (!this.values.includes(answer)) {
348
355
  this.requestValue([...this.values, answer].join(','));
@@ -55,7 +55,8 @@ declare const HelixHeadlessCommand_base: (new (...args: any[]) => import("@fusio
55
55
  * | `hotkey`, Command or Control with K by default | Opens the palette from anywhere on the page, or closes it. With `inline`, puts focus in its filter field. |
56
56
  * | Down, Up | Moves the highlight to the next or the previous command, round from the last to the first. |
57
57
  * | Page Down, Page Up | Moves the highlight by as many commands as the list shows at once. |
58
- * | Home, End, Left, Right | Move the caret in the filter field. |
58
+ * | Home, End | Move the caret in the filter field. With `home-end="list"`, move the highlight to the first or the last command. |
59
+ * | Left, Right | Move the caret in the filter field. |
59
60
  * | Enter | Runs the highlighted command, or follows it where it is a link, and closes the palette. On a command with `opens`, shows its page. |
60
61
  * | Command or Control with Enter | Takes the highlighted command's `alternate` destination, where it has one. |
61
62
  * | Backspace | In an empty filter on a page, goes back to the page before. |
@@ -144,6 +145,14 @@ export declare class HelixHeadlessCommand extends HelixHeadlessCommand_base {
144
145
  * property only, there being no way to write a function in markup.
145
146
  */
146
147
  filterMatch?: ((text: string, filter: string) => boolean) | undefined;
148
+ /**
149
+ * Where Home and End go from the filter field.
150
+ *
151
+ * Absent, they move the caret, as APG's combobox has them. `list` moves the
152
+ * highlight to the first or the last command, as React Aria's Autocomplete
153
+ * does, and Shift with either still selects the text.
154
+ */
155
+ homeEnd?: 'list';
147
156
  /**
148
157
  * The key that opens the palette, written as `mod+k`, `shift+p`, or `k`,
149
158
  * in Kbd's chord spelling: `mod` is Command or Control, `ctrl`, `cmd`,
@@ -178,6 +187,12 @@ export declare class HelixHeadlessCommand extends HelixHeadlessCommand_base {
178
187
  * Accessible name for the overlay, which has no visible heading.
179
188
  */
180
189
  label: string;
190
+ /**
191
+ * How the commands are laid out, as Listbox's `layout`: `grid` draws them in
192
+ * rows and columns, for a palette of icons or emoji, and the arrow keys
193
+ * follow the rows and the columns. Absent is one command under another.
194
+ */
195
+ layout?: 'grid' | 'stack';
181
196
  /**
182
197
  * What the list says when its commands could not be loaded, such as after a
183
198
  * request to a server fails. Empty, null or no attribute is no error, so
@@ -246,6 +246,8 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
246
246
  results-label=${this.resultsLabel}
247
247
  ?loading=${this.loading}
248
248
  .items=${this.items}
249
+ .fieldHomeEnd=${this.homeEnd === 'list'}
250
+ layout=${this.layout ?? nothing}
249
251
  filterable
250
252
  @helix-before-change=${this.handleChange}
251
253
  @helix-filter=${this.handleFilter}
@@ -299,6 +301,9 @@ __decorate([
299
301
  __decorate([
300
302
  property({ attribute: false })
301
303
  ], HelixHeadlessCommand.prototype, "filterMatch", void 0);
304
+ __decorate([
305
+ property({ attribute: 'home-end' })
306
+ ], HelixHeadlessCommand.prototype, "homeEnd", void 0);
302
307
  __decorate([
303
308
  property()
304
309
  ], HelixHeadlessCommand.prototype, "hotkey", void 0);
@@ -312,6 +317,9 @@ __decorate([
312
317
  words,
313
318
  property()
314
319
  ], HelixHeadlessCommand.prototype, "label", void 0);
320
+ __decorate([
321
+ property({ reflect: true })
322
+ ], HelixHeadlessCommand.prototype, "layout", void 0);
315
323
  __decorate([
316
324
  property({ attribute: 'load-error' })
317
325
  ], HelixHeadlessCommand.prototype, "loadError", void 0);
@@ -208,7 +208,7 @@ declare const HelixHeadlessDataTable_base: (new (...args: any[]) => import("@fus
208
208
  * @csspart select - The cell that holds a row's checkbox.
209
209
  * @csspart select-all - The heading that holds the checkbox that selects every row.
210
210
  * @csspart select-selected - Added to `select` in a selected row.
211
- * @csspart table - The grid.
211
+ * @csspart table - The grid. While `windowed`, the box that scrolls, which needs only a block size on this tier.
212
212
  * @csspart toggle - The mark in a row header that a press opens and closes its row by, in a tree grid.
213
213
  * @csspart toggle-mark - The caret in `toggle`. It points along the line while the row is closed and down while it is open.
214
214
  * @csspart toggle-open - Added to `toggle` while its row is open.
@@ -586,6 +586,10 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
586
586
  * Off by default. Windowing costs a scroll listener, a measurement per row
587
587
  * drawn, and a `block-size` on the scrolling box, and a table of twenty rows
588
588
  * should pay none of them.
589
+ *
590
+ * While windowed, the `table` part scrolls on this tier: it carries
591
+ * `data-scroller`, and `core` ships `[data-scroller] { overflow: auto }` into
592
+ * every shadow root. You still need to give it a block size.
589
593
  */
590
594
  windowed: boolean;
591
595
  private sortState;
@@ -1228,6 +1228,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
1228
1228
  part="table"
1229
1229
  role="rowgroup"
1230
1230
  aria-busy=${this.loading ? 'true' : nothing}
1231
+ ?data-scroller=${this.windowed}
1231
1232
  style=${template ? `--data-table-template: ${template}` : nothing}
1232
1233
  @scroll=${this.handleScroll}
1233
1234
  >
@@ -31,6 +31,7 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
31
31
  '--show': () => this.show(),
32
32
  '--toggle': () => this.toggleOpen(),
33
33
  },
34
+ expanded: () => this.open,
34
35
  });
35
36
  this.persistent = false;
36
37
  this.returned = '';
@@ -74,6 +74,10 @@ declare const HelixHeadlessDrawer_base: (new (...args: any[]) => import("@fusion
74
74
  * <helix-headless-drawer id="nav">…</helix-headless-drawer>
75
75
  * ```
76
76
  *
77
+ * From its first press, a button that opens the drawer this way says whether
78
+ * the drawer is open: a Helix button through `expanded`, and any other
79
+ * element with `aria-expanded`.
80
+ *
77
81
  * @since 0.1.0
78
82
  * @status stable
79
83
  * @category overlays
@@ -19,6 +19,7 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
19
19
  '--show': () => this.requestOpen(true),
20
20
  '--toggle': () => this.toggleOpen(),
21
21
  },
22
+ expanded: () => this.open,
22
23
  });
23
24
  this.dismissible = false;
24
25
  this.dismissLabel = 'Close';
@@ -196,6 +196,7 @@ registerElementEvents({
196
196
  'helix-headless-map-terrain': ['helix-map-part-change'],
197
197
  'helix-headless-megamenu': ['helix-before-close', 'helix-before-open', 'toggle'],
198
198
  'helix-headless-menu': [
199
+ 'change',
199
200
  'helix-before-close',
200
201
  'helix-before-open',
201
202
  'helix-filter',
@@ -28,9 +28,10 @@ declare const HelixHeadlessFilterChip_base: (new (...args: any[]) => import("@fu
28
28
  * press. `requestChange` documents the pattern.
29
29
  *
30
30
  * **Keyboard.** The remove button is a tab stop of its own, and Enter or Space
31
- * on it asks to remove the filter. In a `FilterInput`, Backspace in the empty
32
- * field asks to remove the last chip, the arrow keys move between the field
33
- * and the chips, and Delete or Backspace on a chip asks to remove it.
31
+ * on it asks to remove the filter. In a `FilterInput`, the field's text is the
32
+ * one tab stop: Backspace in the empty field asks to remove the last chip, the
33
+ * arrow keys move between the field and the chips, and Delete or Backspace on
34
+ * a chip asks to remove it.
34
35
  *
35
36
  * The remove button is named `dismiss-label` followed by the chip's words,
36
37
  * such as "Remove filter Status is not open", so a row of chips says which
@@ -78,6 +79,15 @@ export declare class HelixHeadlessFilterChip extends HelixHeadlessFilterChip_bas
78
79
  * closing a message.
79
80
  */
80
81
  dismissLabel: string;
82
+ /**
83
+ * Whether the remove button is a tab stop of its own.
84
+ *
85
+ * `FilterInput` writes `false` on the chips in its `tags` slot: the field's
86
+ * text is its one tab stop, and the arrow keys move from the text to the
87
+ * chips, as in a `multiple` Combobox. No attribute: it is coordination
88
+ * between elements, as `Tag.dismissTabStop` is.
89
+ */
90
+ dismissTabStop: boolean;
81
91
  /**
82
92
  * The name of the field the filter is on, as shown on the chip.
83
93
  *
@@ -13,6 +13,7 @@ let HelixHeadlessFilterChip = class HelixHeadlessFilterChip extends MixinDismiss
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.dismissLabel = 'Remove filter';
16
+ this.dismissTabStop = true;
16
17
  this.label = '';
17
18
  this.operatorLabel = '';
18
19
  }
@@ -43,6 +44,7 @@ let HelixHeadlessFilterChip = class HelixHeadlessFilterChip extends MixinDismiss
43
44
  part="dismiss"
44
45
  type="button"
45
46
  aria-label=${this.removeName}
47
+ tabindex=${this.dismissTabStop ? nothing : '-1'}
46
48
  @click=${() => this.dismiss()}
47
49
  >
48
50
  <svg part="dismiss-mark" fill="currentColor" viewBox=${x.viewBox} aria-hidden="true">
@@ -56,6 +58,9 @@ __decorate([
56
58
  words,
57
59
  property({ attribute: 'dismiss-label' })
58
60
  ], HelixHeadlessFilterChip.prototype, "dismissLabel", void 0);
61
+ __decorate([
62
+ property({ attribute: false })
63
+ ], HelixHeadlessFilterChip.prototype, "dismissTabStop", void 0);
59
64
  __decorate([
60
65
  property()
61
66
  ], HelixHeadlessFilterChip.prototype, "label", void 0);
@@ -180,7 +180,6 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
180
180
  part="divider"
181
181
  exportparts="content: divider-content, line: divider-line"
182
182
  slot="submenu"
183
- announced
184
183
  ></${divider}>`
185
184
  : nothing}
186
185
  ${offered.map((option) => html `<${item}