@fusion.dev/helix-headless 0.163.0 → 0.165.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 (47) hide show
  1. package/custom-elements.json +1068 -46
  2. package/dist/callout/root/callout-root.d.ts +43 -4
  3. package/dist/callout/root/callout-root.js +58 -2
  4. package/dist/code-block/patch.d.ts +42 -0
  5. package/dist/code-block/patch.js +118 -0
  6. package/dist/code-block/root/code-block-root.d.ts +37 -1
  7. package/dist/code-block/root/code-block-root.js +57 -9
  8. package/dist/color-picker/root/color-picker-root.d.ts +7 -3
  9. package/dist/color-picker/root/color-picker-root.js +7 -4
  10. package/dist/combobox/root/combobox-root.d.ts +1 -0
  11. package/dist/combobox/root/combobox-root.js +10 -1
  12. package/dist/data-table/root/data-table-root.d.ts +1 -0
  13. package/dist/data-table/root/data-table-root.js +1 -0
  14. package/dist/date-input/root/date-input-root.d.ts +3 -3
  15. package/dist/date-input/root/date-input-root.js +8 -5
  16. package/dist/element-events.generated.js +7 -1
  17. package/dist/format-date/root/format-date-root.d.ts +3 -1
  18. package/dist/format-date/root/format-date-root.js +4 -1
  19. package/dist/lightbox/item/lightbox-item.js +3 -1
  20. package/dist/lightbox/root/lightbox-root.d.ts +28 -0
  21. package/dist/lightbox/root/lightbox-root.js +21 -2
  22. package/dist/number-input/root/number-input-root.d.ts +32 -9
  23. package/dist/number-input/root/number-input-root.js +53 -9
  24. package/dist/object-inspector/root/object-inspector-root.d.ts +23 -0
  25. package/dist/object-inspector/root/object-inspector-root.js +19 -1
  26. package/dist/option/root/index.d.ts +1 -0
  27. package/dist/option/root/index.js +1 -0
  28. package/dist/table/grid-controller.d.ts +7 -0
  29. package/dist/table/grid-controller.js +15 -1
  30. package/dist/table/root/table-root.d.ts +10 -0
  31. package/dist/table/root/table-root.js +18 -1
  32. package/dist/tag/root/index.d.ts +1 -0
  33. package/dist/tag/root/index.js +1 -0
  34. package/dist/time-input/root/time-input-root.d.ts +4 -3
  35. package/dist/time-input/root/time-input-root.js +5 -4
  36. package/dist/time-picker/root/time-picker-root.d.ts +1 -1
  37. package/dist/time-picker/root/time-picker-root.js +5 -2
  38. package/dist/video/root/video-root.js +2 -1
  39. package/dist/video-playlist/item/video-playlist-item.d.ts +8 -0
  40. package/dist/video-playlist/item/video-playlist-item.js +7 -0
  41. package/dist/video-playlist/root/video-playlist-root.d.ts +60 -0
  42. package/dist/video-playlist/root/video-playlist-root.js +55 -1
  43. package/lint.json +1 -1
  44. package/package.json +5 -5
  45. package/types/jsx.d.ts +388 -40
  46. package/types/vue.d.ts +95 -13
  47. package/types/words.d.ts +58 -0
@@ -0,0 +1 @@
1
+ export * from './option-root.js';
@@ -0,0 +1 @@
1
+ export * from './option-root.js';
@@ -42,6 +42,13 @@ export interface TableGridOptions {
42
42
  * carry the selection selects the row there.
43
43
  */
44
44
  arrive?: (to: number, control: boolean) => void;
45
+ /**
46
+ * Right or Left on the disclosure cell of drawn row `row`, `forward` for
47
+ * the key that moves on along the line. Says that it opened or shut the
48
+ * row, names the drawn row to move to, or says it did neither, which moves
49
+ * along the row as the key does on any cell.
50
+ */
51
+ disclose?: (row: number, forward: boolean) => boolean | number;
45
52
  /** Whether the table is a grid now. */
46
53
  on: () => boolean;
47
54
  }
@@ -71,10 +71,24 @@ export class TableGridController {
71
71
  }
72
72
  return;
73
73
  }
74
+ const step = inlineStep(this.host, event.key);
75
+ if (step !== 0 &&
76
+ place.row >= 0 &&
77
+ !(event.shiftKey || event.ctrlKey || event.metaKey) &&
78
+ this.options.lead()[place.column] === 'expand') {
79
+ const done = this.options.disclose?.(place.row, step > 0) ?? false;
80
+ if (done !== false) {
81
+ event.preventDefault();
82
+ if (typeof done === 'number') {
83
+ this.moveTo({ column: place.column, row: done });
84
+ }
85
+ return;
86
+ }
87
+ }
74
88
  const to = gridMove(event.key, place, { last: this.options.rows().length - 1, top: this.top, width: this.widthOf(place.row) }, {
75
89
  control: event.ctrlKey || event.metaKey,
76
90
  page: event.key.startsWith('Page') ? this.pageFrom(place.cell) : 1,
77
- step: inlineStep(this.host, event.key),
91
+ step,
78
92
  });
79
93
  if (!to) {
80
94
  return;
@@ -111,6 +111,8 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
111
111
  * | Enter, Space on a sortable heading | Sorts by the column. |
112
112
  * | Space on a row, Enter or Space on its checkbox cell | Selects the row, in a `selectable` table. |
113
113
  * | Enter or Space on a disclosure cell | Shows or hides the rows under the row, in a table whose rows nest. |
114
+ * | Right on a disclosure cell | Shows the rows under a shut row, and otherwise moves one cell along the row. Left does, on a right-to-left page. |
115
+ * | Left on a disclosure cell | Hides the rows under an open row, and moves to the row this one sits under from any other. |
114
116
  * | Enter or Space on a grip cell | Picks the row up, or drops it, in a `reorderable` table. Up, Down, Home, and End move a row picked up, and Escape puts it back. |
115
117
  * | Down, Up, Page Down, Page Up under `selection-behavior="replace"` | Selects the row it moves to alone. Control or Command with the key moves without selecting. |
116
118
  * | Shift with Down or Up | Selects the rows it moves across, in a `selectable` table. |
@@ -184,6 +186,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
184
186
  *
185
187
  * @cssstate empty - While there are no rows and none are loading.
186
188
  * @cssstate error - While `error-text` is set.
189
+ * @cssstate loading - While `loading` is set, as the rows are on their way.
187
190
  */
188
191
  export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
189
192
  /**
@@ -534,6 +537,13 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
534
537
  private selectRange;
535
538
  /** One row's checkbox, ticked or not. It becomes the row a range runs from. */
536
539
  private chooseRow;
540
+ /**
541
+ * Right or Left on a disclosure cell, as on a tree's item and as Data
542
+ * Table's row headers do: on to open a shut row, back to shut an open one.
543
+ * Back on a row that is shut, or has no rows under it, moves to the row it
544
+ * sits under. Anything else moves along the row.
545
+ */
546
+ private disclose;
537
547
  /**
538
548
  * Enter, Space or F2 on a cell of a `grid` table, and whether it did
539
549
  * anything. Left alone, Enter and F2 move into the cell's controls.
@@ -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, anchorAt, assignSlots, commaList, customElement, cycle, everyState, followLink, format, formatWords, HelixBeforeToggleEvent, HelixElement, HelixLoadMoreEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, HelixToggleEvent, inRowOrder, MixinCollection, onAndroid, parse, ReorderController, rangeAdd, reflectElements, requestChange, scrollingAncestor, sort, splitValues, toggleEvery, whenHydrated, words, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, anchorAt, assignSlots, commaList, customElement, cycle, everyState, followLink, format, formatWords, HelixBeforeToggleEvent, HelixElement, HelixLoadMoreEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, HelixToggleEvent, inRowOrder, MixinCollection, onAndroid, parse, ReorderController, rangeAdd, reflectElements, requestChange, scrollingAncestor, sort, splitValues, toggleEvery, treeArrow, whenHydrated, words, } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { html } from 'lit/static-html.js';
@@ -94,6 +94,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
94
94
  }
95
95
  },
96
96
  columns: () => this.tableColumns.filter((column) => column.shown),
97
+ disclose: (at, forward) => this.disclose(at, forward),
97
98
  lead: () => this.lead,
98
99
  on: () => this.grid,
99
100
  part: (name) => this.partNamed(name),
@@ -471,6 +472,21 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
471
472
  }
472
473
  this.requestSelected(keys);
473
474
  }
475
+ disclose(at, forward) {
476
+ const rows = this.arrangedRows;
477
+ const row = rows[at];
478
+ const found = this.keyed.find((one) => one.row === row);
479
+ if (!(row && found)) {
480
+ return false;
481
+ }
482
+ const arrow = treeArrow({ branch: row.branch ?? false, open: this.open.has(found.key) }, forward ? 1 : -1);
483
+ if (arrow === 'open' || arrow === 'close') {
484
+ this.toggleRow(found.key);
485
+ return true;
486
+ }
487
+ const parent = arrow === 'parent' ? rows.indexOf(this.rowFor(row.parent ?? '')) : -1;
488
+ return parent >= 0 ? parent : false;
489
+ }
474
490
  activate(place, event) {
475
491
  const leads = this.lead;
476
492
  const lead = leads[place.column] === 'select';
@@ -697,6 +713,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
697
713
  super.updated(changed);
698
714
  this.toggleState('error', this.errorText !== '');
699
715
  this.toggleState('empty', !this.loading && !this.hasRows);
716
+ this.toggleState('loading', this.loading);
700
717
  this.assign();
701
718
  this.alignCells();
702
719
  this.markSorted();
@@ -0,0 +1 @@
1
+ export * from './tag-root.js';
@@ -0,0 +1 @@
1
+ export * from './tag-root.js';
@@ -1,6 +1,6 @@
1
1
  import { AriaForwardController, HelixFormElement, type HourCycle } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
- import '../../tag/index.js';
3
+ import '../../tag/root/index.js';
4
4
  import '../../time-picker/index.js';
5
5
  declare const HelixHeadlessTimeInput_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSegmentedFieldInterface) & (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
  /**
@@ -349,8 +349,9 @@ export declare class HelixHeadlessTimeInput extends HelixHeadlessTimeInput_base
349
349
  /**
350
350
  * Each time, where `mode` holds several, sent one entry each.
351
351
  *
352
- * One time that is text and no time, such as `2:45 P`, is sent as an empty
353
- * entry, as a native time field sends a time it cannot read.
352
+ * Text that is no time, such as `2:45 P`, is sent as one empty entry, as a
353
+ * native time field sends a time it cannot read. Measured before this: a
354
+ * range written `09:00/10:30` was invalid and submitted the text.
354
355
  */
355
356
  protected get answers(): readonly string[] | undefined;
356
357
  /** What is wrong with the times, which the text input cannot say. */
@@ -4,13 +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 { AriaForwardController, valueAsTime as asTime, clockLocale, customElement, drawIcon, formatWords, timeAsValue as fromTime, HelixClearEvent, HelixFormElement, handedLocale, isClockValue, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinSegmentedField, MixinTokens, readWrittenTime, timeFault, words, wordsLocale, writeClock, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, valueAsTime as asTime, clockLocale, customElement, drawIcon, formatWords, timeAsValue as fromTime, HelixClearEvent, HelixFormElement, handedLocale, hostWords, isClockValue, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinSegmentedField, MixinTokens, readWrittenTime, slottedWords, timeFault, words, wordsLocale, writeClock, } from '@fusion.dev/helix-core';
8
8
  import { clock, x } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  import { html } from 'lit/static-html.js';
13
- import '../../tag/index.js';
13
+ import '../../tag/root/index.js';
14
14
  import '../../time-picker/index.js';
15
15
  let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmentedField(MixinPickerEditing(MixinPickerField(MixinControllerOpen(MixinTokens(MixinControllerValue(MixinField(HelixFormElement))))))) {
16
16
  constructor() {
@@ -174,7 +174,8 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
174
174
  }
175
175
  get answers() {
176
176
  if (this.mode !== undefined) {
177
- return this.held;
177
+ const held = this.held;
178
+ return held.every((one) => this.isTime(one)) ? held : [''];
178
179
  }
179
180
  const text = this.value.trim();
180
181
  return text === '' || this.isTime(text) ? undefined : [''];
@@ -242,7 +243,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinSegmented
242
243
  variant=${this.variant ?? nothing}
243
244
  disable-times=${this.disableTimes || nothing}
244
245
  manual
245
- label=${this.label || 'Time'}
246
+ label=${hostWords(this, slottedWords(this, ['label']) || this.label) || 'Time'}
246
247
  hour-cycle=${this.hourCycle ?? nothing}
247
248
  locale=${handedLocale(this) || nothing}
248
249
  max=${this.max}
@@ -1,7 +1,7 @@
1
1
  import { HelixFormElement, type HourCycle, type PickerFailure } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../listbox/index.js';
4
- import '../../option/index.js';
4
+ import '../../option/root/index.js';
5
5
  declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinPickerFeedbackInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & typeof HelixFormElement;
6
6
  /**
7
7
  * @summary A control for choosing a time from a list or from wheels.
@@ -10,7 +10,7 @@ import { property, state } from 'lit/decorators.js';
10
10
  import { html } from 'lit/static-html.js';
11
11
  import { wheelName } from '../wheel-name.js';
12
12
  import '../../listbox/index.js';
13
- import '../../option/index.js';
13
+ import '../../option/root/index.js';
14
14
  let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerFeedback(MixinControllerValue(HelixFormElement)) {
15
15
  constructor() {
16
16
  super(...arguments);
@@ -275,7 +275,8 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
275
275
  this.internals.setFormValue(null);
276
276
  return;
277
277
  }
278
- reportValues(this.internals, this.name, this.held);
278
+ const held = this.held;
279
+ reportValues(this.internals, this.name, held.every((one) => valueAsSeconds(one) !== null) ? held : ['']);
279
280
  }
280
281
  async revealWhenSettled() {
281
282
  const settling = [
@@ -356,6 +357,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
356
357
  data-column=${part}
357
358
  aria-describedby=${this.describedBy}
358
359
  aria-invalid=${this.marked ? 'true' : nothing}
360
+ aria-required=${this.required ? 'true' : nothing}
359
361
  exportparts="empty: column-empty, error: column-error, feedback: column-feedback, filter-icon: column-filter-icon,
360
362
  filter-mark: column-filter-mark, hint: column-hint, items: column-items, label: column-label,
361
363
  live: column-live, loading: column-loading, search: column-search, search-field: column-search-field"
@@ -408,6 +410,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinPickerF
408
410
  part="list"
409
411
  aria-describedby=${this.describedBy}
410
412
  aria-invalid=${this.marked ? 'true' : nothing}
413
+ aria-required=${this.required ? 'true' : nothing}
411
414
  exportparts="empty: list-empty, error: list-error, feedback: list-feedback, filter-icon: list-filter-icon,
412
415
  filter-mark: list-filter-mark, hint: list-hint, items: list-items, label: list-label,
413
416
  live: list-live, loading: list-loading, search: list-search, search-field: list-search-field"
@@ -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 { activeCue, adoptTrack, CueController, captionsAmong, captionTracks, copiesOf, customElement, drawIcon, FullscreenController, formatWords, HelixElement, HelixNextEvent, HelixPreviousEvent, IdleController, MediaSessionController, onUnlessFalse, RemotePlaybackController, reportRuntime, SKIP_SECONDS, sameCopies, showCaption, words, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
7
+ import { activeCue, adoptTrack, auditName, CueController, captionsAmong, captionTracks, copiesOf, customElement, drawIcon, FullscreenController, formatWords, HelixElement, HelixNextEvent, HelixPreviousEvent, IdleController, MediaSessionController, onUnlessFalse, RemotePlaybackController, reportRuntime, SKIP_SECONDS, sameCopies, showCaption, words, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
8
8
  import { airplay, arrowClockwise, arrowCounterClockwise, closedCaptioning, cornersIn, cornersOut, dotsThreeVertical, download, gauge, pause, pictureInPicture, play, skipBack, skipForward, speakerHigh, speakerX, } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -464,6 +464,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
464
464
  firstUpdated(changed) {
465
465
  super.firstUpdated?.(changed);
466
466
  this.finishHydration();
467
+ auditName(this, () => null, 'Give it a `label` that says what the video shows, such as "Product tour".', () => this.label !== '');
467
468
  const video = this.video;
468
469
  if (video) {
469
470
  if (this.defaultMuted || this.silent) {
@@ -38,6 +38,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
38
38
  * @csspart thumbnail - The image of `poster`, shown when the `media` slot is empty.
39
39
  *
40
40
  * @cssstate current - The entry that is playing.
41
+ * @cssstate dragging - The entry a pointer is dragging, in a `reorderable` playlist.
42
+ * @cssstate drop-after - The entry the dragged one would land after, which the drop indicator is drawn under.
43
+ * @cssstate drop-before - The entry the dragged one would land before, which the drop indicator is drawn over.
41
44
  */
42
45
  export declare class HelixHeadlessVideoPlaylistItem extends HelixElement {
43
46
  /**
@@ -61,6 +64,11 @@ export declare class HelixHeadlessVideoPlaylistItem extends HelixElement {
61
64
  * current one, and the arrow keys reach the rest.
62
65
  */
63
66
  focusable?: boolean;
67
+ /**
68
+ * @internal Set by a `reorderable` playlist: where this entry stands in a
69
+ * move, which it says through its states.
70
+ */
71
+ move: 'dragging' | 'drop-after' | 'drop-before' | undefined;
64
72
  /**
65
73
  * The image shown before this entry plays, also used as the row's thumbnail.
66
74
  *
@@ -12,6 +12,7 @@ let HelixHeadlessVideoPlaylistItem = class HelixHeadlessVideoPlaylistItem extend
12
12
  super(...arguments);
13
13
  this.current = false;
14
14
  this.disabled = false;
15
+ this.move = undefined;
15
16
  this.poster = '';
16
17
  this.src = '';
17
18
  this.value = '';
@@ -40,6 +41,9 @@ let HelixHeadlessVideoPlaylistItem = class HelixHeadlessVideoPlaylistItem extend
40
41
  willUpdate(changed) {
41
42
  super.willUpdate(changed);
42
43
  this.toggleState('current', this.current);
44
+ this.toggleState('dragging', this.move === 'dragging');
45
+ this.toggleState('drop-after', this.move === 'drop-after');
46
+ this.toggleState('drop-before', this.move === 'drop-before');
43
47
  }
44
48
  render() {
45
49
  return html `<div part="container" role="listitem">
@@ -70,6 +74,9 @@ __decorate([
70
74
  __decorate([
71
75
  property({ attribute: false })
72
76
  ], HelixHeadlessVideoPlaylistItem.prototype, "focusable", void 0);
77
+ __decorate([
78
+ property({ attribute: false })
79
+ ], HelixHeadlessVideoPlaylistItem.prototype, "move", void 0);
73
80
  __decorate([
74
81
  property()
75
82
  ], HelixHeadlessVideoPlaylistItem.prototype, "poster", void 0);
@@ -54,6 +54,21 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
54
54
  * the next or the previous entry. The player's previous and next controls
55
55
  * move through the entries in the same order the playlist does.
56
56
  *
57
+ * **Moving entries.** `reorderable` lets a reader change the order of the
58
+ * queue:
59
+ *
60
+ * - Alt with Up or Down moves the focused entry a place, and Alt with Home or
61
+ * End moves it to either end, as the APG's rearrangeable listbox does.
62
+ * Focus stays on it.
63
+ * - A pointer drags an entry, with a drop indicator, and the page scrolls near
64
+ * an edge. A press that does not travel still plays the entry.
65
+ * - A drop sends `helix-before-reorder`, which you can refuse, and then
66
+ * `helix-reorder`, whose `keys` hold every entry's `value` in the new order.
67
+ * **You move the entries**: the playlist moves no element of yours, and the
68
+ * next entry is the one after the current entry in the order you leave.
69
+ * - Each move is said in the live region, in words you can change:
70
+ * `moved-label`, `dropped-label`, `canceled-label`, and `picked-up-label`.
71
+ *
57
72
  * **What a screen reader hears.** A list named by `label`, with a button for
58
73
  * each entry. When the playlist moves to another entry, by a press, by the
59
74
  * player's controls, or at the end of a clip, a live region says
@@ -71,7 +86,9 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
71
86
  *
72
87
  * @event {HelixActivateEvent} helix-activate - The current entry changed. The playlist already holds the new entry when this is sent, and `detail.previous` is the entry it left. `currentEntry` is the entry itself, with its `src`, `poster` and name.
73
88
  * @event {HelixBeforeActivateEvent} helix-before-activate - The current entry is about to change, and `detail.cause` says why: `press`, `next`, `previous`, `ended`, or `removed`. Cancelable: cancel it to keep the current entry.
89
+ * @event {HelixBeforeReorderEvent} helix-before-reorder - An entry was moved to a new place, in a `reorderable` playlist, with every entry's value in the new order in `detail.keys`. Cancelable: cancel it to keep the entry where it is.
74
90
  * @event {HelixEndEvent} helix-end - The last entry finished playing, and the playlist stopped there. Not sent while `repeat` is set.
91
+ * @event {HelixReorderEvent} helix-reorder - Fired after `helix-before-reorder` when nobody canceled it. Move your entries to the order in `keys`. Cancelable: cancel it to say the move did not happen.
75
92
  *
76
93
  * @csspart empty - The box that holds the `empty` slot, drawn while there are no entries.
77
94
  * @csspart items - The list of entries.
@@ -86,6 +103,16 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
86
103
  * `registerWords('de', { 'helix-video-playlist': { label: 'Wiedergabeliste' } })`.
87
104
  */
88
105
  static words: string;
106
+ /**
107
+ * What screen readers hear when a move is put back, in a `reorderable`
108
+ * playlist. `{name}` is the entry's words.
109
+ */
110
+ canceledLabel: string;
111
+ /**
112
+ * What screen readers hear when a moved entry is let go, in a `reorderable`
113
+ * playlist. `{name}` is the entry's words.
114
+ */
115
+ droppedLabel: string;
89
116
  /**
90
117
  * What the playlist says while it has no entries. The `empty` slot replaces it.
91
118
  */
@@ -94,12 +121,23 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
94
121
  * The list's name, read by screen readers.
95
122
  */
96
123
  label: string;
124
+ /**
125
+ * What screen readers hear each time a key moves an entry, in a
126
+ * `reorderable` playlist. `{name}` is the entry's words, `{position}` its
127
+ * new place, counting from 1, and `{count}` the number of entries.
128
+ */
129
+ movedLabel: string;
97
130
  /**
98
131
  * Stays on an entry when its clip ends, where the playlist would otherwise
99
132
  * play the next one. The player's controls, a press, and `next()` still
100
133
  * move. `repeat="one"` still plays the entry again.
101
134
  */
102
135
  noAdvance: boolean;
136
+ /**
137
+ * What screen readers hear when a pointer picks up an entry, in a
138
+ * `reorderable` playlist. `{name}` is the entry's words.
139
+ */
140
+ pickedUpLabel: string;
103
141
  /**
104
142
  * What the live region says when the playlist moves to another entry.
105
143
  * `{title}` is the entry's name.
@@ -109,8 +147,30 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
109
147
  * `aria-current` on a button the reader is not on.
110
148
  */
111
149
  playingLabel: string;
150
+ /**
151
+ * Lets a reader move the entries, with Alt and the arrow keys or by
152
+ * dragging one. The playlist moves no entry itself: you move your entries
153
+ * on `helix-reorder`.
154
+ */
155
+ reorderable: boolean;
112
156
  /** The live region, rendered from the first move. */
113
157
  private readonly status;
158
+ /** The entry an event passed through, where it may move. */
159
+ private entryOf;
160
+ /** The entry with that value. */
161
+ private entryFor;
162
+ /**
163
+ * Moving the entries, as a listbox moves its options: Alt with the arrows
164
+ * on the focused entry, or a pointer dragging a whole entry, where a press
165
+ * that does not travel still plays it.
166
+ */
167
+ private readonly reorder;
168
+ /**
169
+ * Tells each entry where it stands in a move, which it says through its
170
+ * states for the styled tier to draw: the entry dragged, and the one the
171
+ * drop indicator is before or after.
172
+ */
173
+ private markMoves;
114
174
  /**
115
175
  * What happens when an entry ends with no next one to go to. `one` plays the
116
176
  * same entry again, and `all` goes from the last entry back to the first.
@@ -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, drawStatus, formatWords, HelixElement, HelixEndEvent, inlineStep, MixinControllerActive, StatusController, words, } from '@fusion.dev/helix-core';
7
+ import { customElement, drawStatus, formatWords, HelixElement, HelixEndEvent, inlineStep, MixinControllerActive, ReorderController, StatusController, words, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessVideoPlaylistItem } from '../item/video-playlist-item.js';
@@ -12,11 +12,30 @@ import { playOrder, stepTo } from '../order.js';
12
12
  let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinControllerActive(HelixElement) {
13
13
  constructor() {
14
14
  super(...arguments);
15
+ this.canceledLabel = 'Canceled';
16
+ this.droppedLabel = 'Dropped';
15
17
  this.emptyLabel = 'No videos';
16
18
  this.label = 'Playlist';
19
+ this.movedLabel = '{name} moved to position {position} of {count}';
17
20
  this.noAdvance = false;
21
+ this.pickedUpLabel = 'Picked up {name}';
18
22
  this.playingLabel = 'Now playing {title}';
23
+ this.reorderable = false;
19
24
  this.status = new StatusController(this);
25
+ this.reorder = new ReorderController(this, {
26
+ axis: 'block',
27
+ box: (value) => this.entryFor(value)?.getBoundingClientRect(),
28
+ current: (event) => this.entryOf(event)?.value,
29
+ grip: (event) => {
30
+ const entry = this.entryOf(event);
31
+ return entry ? { element: entry, key: entry.value } : undefined;
32
+ },
33
+ holds: false,
34
+ keys: () => this.items.map((one) => one.value),
35
+ name: (value) => this.entryFor(value)?.text || value,
36
+ refocus: (value) => this.entryFor(value)?.focus(),
37
+ say: (words) => this.status.say(words),
38
+ });
20
39
  this.shuffle = false;
21
40
  this.chased = false;
22
41
  this.known = {
@@ -116,6 +135,21 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
116
135
  };
117
136
  }
118
137
  static { this.words = 'helix-video-playlist'; }
138
+ entryOf(event) {
139
+ const items = this.items;
140
+ return event
141
+ .composedPath()
142
+ .find((node) => items.includes(node) &&
143
+ !node.disabled);
144
+ }
145
+ entryFor(value) {
146
+ return this.items.find((one) => one.value === value);
147
+ }
148
+ markMoves() {
149
+ for (const entry of this.items) {
150
+ entry.move = this.reorderable ? this.reorder.moveOf(entry.value) : undefined;
151
+ }
152
+ }
119
153
  get items() {
120
154
  return [...this.querySelectorAll('*')].filter((one) => one instanceof HelixHeadlessVideoPlaylistItem && one.value !== '');
121
155
  }
@@ -228,6 +262,7 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
228
262
  }
229
263
  updated(changed) {
230
264
  super.updated?.(changed);
265
+ this.markMoves();
231
266
  const items = this.items;
232
267
  const current = this.resolveActive(items);
233
268
  for (const item of items) {
@@ -270,6 +305,14 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
270
305
  ${drawStatus(this.status)}`;
271
306
  }
272
307
  };
308
+ __decorate([
309
+ words,
310
+ property({ attribute: 'canceled-label' })
311
+ ], HelixHeadlessVideoPlaylist.prototype, "canceledLabel", void 0);
312
+ __decorate([
313
+ words,
314
+ property({ attribute: 'dropped-label' })
315
+ ], HelixHeadlessVideoPlaylist.prototype, "droppedLabel", void 0);
273
316
  __decorate([
274
317
  words,
275
318
  property({ attribute: 'empty-label' })
@@ -278,13 +321,24 @@ __decorate([
278
321
  words,
279
322
  property()
280
323
  ], HelixHeadlessVideoPlaylist.prototype, "label", void 0);
324
+ __decorate([
325
+ words,
326
+ property({ attribute: 'moved-label' })
327
+ ], HelixHeadlessVideoPlaylist.prototype, "movedLabel", void 0);
281
328
  __decorate([
282
329
  property({ attribute: 'no-advance', type: Boolean })
283
330
  ], HelixHeadlessVideoPlaylist.prototype, "noAdvance", void 0);
331
+ __decorate([
332
+ words,
333
+ property({ attribute: 'picked-up-label' })
334
+ ], HelixHeadlessVideoPlaylist.prototype, "pickedUpLabel", void 0);
284
335
  __decorate([
285
336
  words,
286
337
  property({ attribute: 'playing-label' })
287
338
  ], HelixHeadlessVideoPlaylist.prototype, "playingLabel", void 0);
339
+ __decorate([
340
+ property({ reflect: true, type: Boolean })
341
+ ], HelixHeadlessVideoPlaylist.prototype, "reorderable", void 0);
288
342
  __decorate([
289
343
  property()
290
344
  ], HelixHeadlessVideoPlaylist.prototype, "repeat", void 0);