@fusion.dev/helix-headless 0.160.0 → 0.161.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 (39) hide show
  1. package/custom-elements.json +1729 -50
  2. package/dist/canvas/root/canvas-root.d.ts +59 -2
  3. package/dist/canvas/root/canvas-root.js +123 -8
  4. package/dist/chat/message/chat-message.d.ts +8 -0
  5. package/dist/chat/message/chat-message.js +11 -0
  6. package/dist/chat/root/chat-root.d.ts +47 -1
  7. package/dist/chat/root/chat-root.js +130 -4
  8. package/dist/code-block/lines.d.ts +12 -0
  9. package/dist/code-block/lines.js +41 -0
  10. package/dist/code-block/root/code-block-root.d.ts +88 -5
  11. package/dist/code-block/root/code-block-root.js +154 -10
  12. package/dist/element-events.generated.js +4 -0
  13. package/dist/splitter/root/splitter-root.d.ts +15 -0
  14. package/dist/splitter/root/splitter-root.js +16 -2
  15. package/dist/table/grid-controller.d.ts +7 -1
  16. package/dist/table/grid-controller.js +9 -1
  17. package/dist/table/root/table-root.d.ts +122 -0
  18. package/dist/table/root/table-root.js +293 -17
  19. package/dist/table/row/table-row.d.ts +41 -0
  20. package/dist/table/row/table-row.js +56 -2
  21. package/dist/timeline/item/timeline-item.d.ts +47 -10
  22. package/dist/timeline/item/timeline-item.js +36 -4
  23. package/dist/video-playlist/item/video-playlist-item.d.ts +11 -0
  24. package/dist/video-playlist/item/video-playlist-item.js +5 -0
  25. package/dist/video-playlist/menu/video-playlist-menu.d.ts +6 -0
  26. package/dist/video-playlist/menu/video-playlist-menu.js +11 -0
  27. package/dist/video-playlist/root/video-playlist-root.d.ts +59 -5
  28. package/dist/video-playlist/root/video-playlist-root.js +82 -10
  29. package/dist/virtual-list/fill.d.ts +6 -1
  30. package/dist/virtual-list/fill.js +1 -1
  31. package/dist/virtual-list/keys.d.ts +8 -0
  32. package/dist/virtual-list/keys.js +20 -0
  33. package/dist/virtual-list/root/virtual-list-root.d.ts +98 -13
  34. package/dist/virtual-list/root/virtual-list-root.js +195 -20
  35. package/lint.json +1 -1
  36. package/package.json +5 -5
  37. package/types/jsx.d.ts +936 -68
  38. package/types/vue.d.ts +226 -12
  39. package/types/words.d.ts +79 -0
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { customElement, followLink, HelixElement, hostAria, pressedOwnControl, } from '@fusion.dev/helix-core';
8
- import { dotsSixVertical } from '@fusion.dev/helix-icons';
8
+ import { caretRight, dotsSixVertical } 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';
@@ -17,6 +17,7 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
17
17
  this.isTableRow = true;
18
18
  this.disabled = false;
19
19
  this.href = '';
20
+ this.parent = '';
20
21
  this.rel = '';
21
22
  this.target = '';
22
23
  this.value = '';
@@ -29,6 +30,12 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
29
30
  this.move = undefined;
30
31
  this.gridCell = false;
31
32
  this.selectStop = false;
33
+ this.level = 0;
34
+ this.branch = false;
35
+ this.open = false;
36
+ this.expandLabel = '';
37
+ this.expandStop = false;
38
+ this.expandWith = undefined;
32
39
  this.columnRules = undefined;
33
40
  this.handleClick = (event) => {
34
41
  if (this.disabled || this.href === '' || event.button !== 0 || event.defaultPrevented) {
@@ -70,6 +77,10 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
70
77
  hostAria(this.internals, { role: 'row' });
71
78
  this.internals.ariaSelected = this.gridCell && this.selectable ? String(this.selected) : null;
72
79
  this.internals.ariaDisabled = this.disabled ? 'true' : null;
80
+ const tree = this.gridCell && this.level > 0;
81
+ this.internals.ariaLevel = tree ? String(this.level) : null;
82
+ this.internals.ariaExpanded = tree && this.branch ? String(this.open) : null;
83
+ this.toggleState('expanded', this.branch && this.open);
73
84
  this.toggleState('selected', this.selectable && this.selected);
74
85
  this.toggleState('link', this.href !== '' && !this.disabled);
75
86
  this.toggleState('dragging', this.move === 'dragging');
@@ -125,8 +136,30 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
125
136
  ><svg aria-hidden="true" fill="currentColor" part="grip-mark" viewBox=${dotsSixVertical.viewBox}>${unsafeSVG(dotsSixVertical.content)}</svg></button
126
137
  ></span>`;
127
138
  }
139
+ renderExpand() {
140
+ if (this.level === 0) {
141
+ return nothing;
142
+ }
143
+ const grid = this.gridCell;
144
+ return html `<span
145
+ part="expand"
146
+ role=${grid ? 'gridcell' : 'cell'}
147
+ style="--table-row-level: ${this.level}"
148
+ tabindex=${grid ? (this.expandStop ? '0' : '-1') : nothing}
149
+ >${this.branch
150
+ ? html `<button
151
+ part="expand-button"
152
+ type="button"
153
+ tabindex=${grid ? '-1' : nothing}
154
+ aria-label=${this.expandLabel}
155
+ aria-expanded=${this.open ? 'true' : 'false'}
156
+ @click=${() => this.expandWith?.()}
157
+ ><svg aria-hidden="true" data-mirror fill="currentColor" part="expand-mark" viewBox=${caretRight.viewBox}>${unsafeSVG(caretRight.content)}</svg></button>`
158
+ : nothing}</span
159
+ >`;
160
+ }
128
161
  render() {
129
- return html `${this.renderGrip()}${this.renderSelect()}<slot @slotchange=${this.handleSlotChange}></slot>`;
162
+ return html `${this.renderGrip()}${this.renderSelect()}${this.renderExpand()}<slot @slotchange=${this.handleSlotChange}></slot>`;
130
163
  }
131
164
  };
132
165
  __decorate([
@@ -135,6 +168,9 @@ __decorate([
135
168
  __decorate([
136
169
  property()
137
170
  ], HelixHeadlessTableRow.prototype, "href", void 0);
171
+ __decorate([
172
+ property()
173
+ ], HelixHeadlessTableRow.prototype, "parent", void 0);
138
174
  __decorate([
139
175
  property()
140
176
  ], HelixHeadlessTableRow.prototype, "rel", void 0);
@@ -171,6 +207,24 @@ __decorate([
171
207
  __decorate([
172
208
  state()
173
209
  ], HelixHeadlessTableRow.prototype, "selectStop", void 0);
210
+ __decorate([
211
+ state()
212
+ ], HelixHeadlessTableRow.prototype, "level", void 0);
213
+ __decorate([
214
+ state()
215
+ ], HelixHeadlessTableRow.prototype, "branch", void 0);
216
+ __decorate([
217
+ state()
218
+ ], HelixHeadlessTableRow.prototype, "open", void 0);
219
+ __decorate([
220
+ state()
221
+ ], HelixHeadlessTableRow.prototype, "expandLabel", void 0);
222
+ __decorate([
223
+ state()
224
+ ], HelixHeadlessTableRow.prototype, "expandStop", void 0);
225
+ __decorate([
226
+ state()
227
+ ], HelixHeadlessTableRow.prototype, "expandWith", void 0);
174
228
  HelixHeadlessTableRow = __decorate([
175
229
  customElement('helix-headless-table-row')
176
230
  ], HelixHeadlessTableRow);
@@ -1,5 +1,7 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
+ /** The kinds of event a timeline entry can be, which `tone` takes. */
4
+ type TimelineTone = 'danger' | 'info' | 'neutral' | 'primary' | 'success' | 'warning';
3
5
  /**
4
6
  * @summary One event on a timeline: when it happened, and what it was.
5
7
  *
@@ -19,7 +21,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
19
21
  * counting parent, and no script. `Steps` and `Breadcrumbs` work the same way.
20
22
  *
21
23
  * `tone` sets the kind of event and is the only thing that colors the mark. It
22
- * uses the same values as `Announcement`.
24
+ * uses the same values as `Announcement`. **A screen reader hears the tone as
25
+ * a word**, from `tone-labels`: the mark is an image named "Error",
26
+ * "Information", "Success" or "Warning", so the color is not the only place
27
+ * the meaning is. `neutral` and `primary` say nothing unless you name them.
28
+ *
29
+ * **`pending` marks an event that has not happened yet**, such as one that is
30
+ * scheduled. The mark is the open circle, and a screen reader hears
31
+ * `pending-label`, "Scheduled", with the tone's word.
23
32
  *
24
33
  * @since 0.1.0
25
34
  * @status stable
@@ -29,18 +38,23 @@ import { type PropertyValues, type TemplateResult } from 'lit';
29
38
  *
30
39
  * @slot - What happened.
31
40
  * @slot marker - The mark on the line. Defaults to a dot, which is the slot's fallback
32
- * content.
41
+ * content. A mark you slot is read as the tone's word, where the entry has one.
33
42
  * @slot time - When it happened, written the way you want it shown.
34
43
  *
35
44
  * @csspart content - What happened, and the time above it.
36
45
  * @csspart dot - The dot drawn when no marker is slotted.
37
- * @csspart marker - The mark on the line.
46
+ * @csspart marker - The mark on the line. An image named by the tone's word, and by `pending-label`, where either applies.
38
47
  * @csspart rail - The column that holds the mark and the line. Draw the two halves of the
39
48
  * line with `::part(rail)::before` and `::part(rail)::after` in your stylesheet. This
40
49
  * tier ships no CSS and renders no extra box for the line.
41
50
  * @csspart time - When it happened.
42
51
  */
43
52
  export declare class HelixHeadlessTimelineItem extends HelixElement {
53
+ /**
54
+ * The key this part's words are registered under, its styled tag:
55
+ * `registerWords('de', { 'helix-timeline-item': { toneLabels: { danger: 'Fehler' } } })`.
56
+ */
57
+ static words: string;
44
58
  /**
45
59
  * When it happened, in machine-readable form. Setting it wraps the time in a
46
60
  * `<time>` element.
@@ -51,12 +65,10 @@ export declare class HelixHeadlessTimelineItem extends HelixElement {
51
65
  */
52
66
  datetime: string;
53
67
  /**
54
- * Writes the time as how long ago it was, or how long until it, from
55
- * `datetime`, where the `time` slot is empty: `long` ("3 hours ago"),
56
- * `short` ("3 hr. ago") or `narrow` ("3h ago"). Set with no value, it is
57
- * `long`. The words move on as they go stale.
68
+ * Marks an event that has not happened yet, such as one that is scheduled.
69
+ * The mark is the open circle, and a screen reader hears `pending-label`.
58
70
  */
59
- relative?: 'long' | 'narrow' | 'short';
71
+ pending: boolean;
60
72
  /** The time the reading is drawn against, moved on when the words go stale. */
61
73
  private readonly clock;
62
74
  /**
@@ -69,10 +81,33 @@ export declare class HelixHeadlessTimelineItem extends HelixElement {
69
81
  private get moment();
70
82
  /** What the reading says at `now`, and when it stops being true. */
71
83
  private readingAt;
84
+ /** What a screen reader hears for an entry that is `pending`. */
85
+ pendingLabel: string;
72
86
  /**
73
- * The kind of event. It sets the color of the mark and nothing else.
87
+ * Writes the time as how long ago it was, or how long until it, from
88
+ * `datetime`, where the `time` slot is empty: `long` ("3 hours ago"),
89
+ * `short` ("3 hr. ago") or `narrow` ("3h ago"). Set with no value, it is
90
+ * `long`. The words move on as they go stale.
91
+ */
92
+ relative?: 'long' | 'narrow' | 'short';
93
+ /**
94
+ * The kind of event. It sets the color of the mark, and a screen reader
95
+ * hears it as the word `tone-labels` gives it.
74
96
  */
75
97
  tone?: 'danger' | 'info' | 'neutral' | 'primary' | 'success' | 'warning';
98
+ /**
99
+ * What a screen reader is told this entry's tone is, such as "Failed".
100
+ * Absent is the tone's word in `tone-labels`.
101
+ */
102
+ toneLabel: string;
103
+ /**
104
+ * What a screen reader is told each tone is, by the tone's name, written as
105
+ * JSON in the attribute: `tone-labels='{"danger":"Failed"}'`. A tone with no
106
+ * word is not read, which is `neutral` and `primary` in English.
107
+ */
108
+ toneLabels: Partial<Record<TimelineTone, string>>;
109
+ /** What the mark says: the tone's word and the pending word, where either applies. */
110
+ private get markName();
76
111
  protected willUpdate(changed: PropertyValues): void;
77
112
  /**
78
113
  * The mark for an entry that slotted none, which the tone decides.
@@ -86,7 +121,8 @@ export declare class HelixHeadlessTimelineItem extends HelixElement {
86
121
  * Toned entries are reporting something, so they take the circle with a
87
122
  * center, which is `Steps` drawing the stage a reader is standing on. Untoned
88
123
  * ones are just things that happened and take the open circle, which is
89
- * `Steps` drawing a stage nobody has reached. The same two glyphs in both
124
+ * `Steps` drawing a stage nobody has reached, and so does a `pending`
125
+ * entry, which nobody has reached yet either. The same two glyphs in both
90
126
  * components rather than two that resemble each other: a shape drawn twice
91
127
  * drifts, and these are already the marks a reader has learned one page over.
92
128
  */
@@ -98,3 +134,4 @@ declare global {
98
134
  'helix-headless-timeline-item': HelixHeadlessTimelineItem;
99
135
  }
100
136
  }
137
+ export {};
@@ -4,15 +4,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, hostAria, RelativeClockController, relativeTime, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, hostAria, RelativeClockController, relativeTime, words, wordsConverter, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { circle, record } from '@fusion.dev/helix-icons';
9
- import { html } from 'lit';
9
+ import { html, nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixElement {
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.datetime = '';
16
+ this.pending = false;
16
17
  this.clock = new RelativeClockController(this, {
17
18
  expires: (now) => {
18
19
  const moment = this.moment;
@@ -20,7 +21,16 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
20
21
  },
21
22
  });
22
23
  this.reading = '';
24
+ this.pendingLabel = 'Scheduled';
25
+ this.toneLabel = '';
26
+ this.toneLabels = {
27
+ danger: 'Error',
28
+ info: 'Information',
29
+ success: 'Success',
30
+ warning: 'Warning',
31
+ };
23
32
  }
33
+ static { this.words = 'helix-timeline-item'; }
24
34
  get moment() {
25
35
  if (this.relative === undefined || this.datetime === '') {
26
36
  return undefined;
@@ -31,6 +41,10 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
31
41
  readingAt(moment, now) {
32
42
  return relativeTime(wordsLocale(this), { style: this.relative || 'long' })(moment, now);
33
43
  }
44
+ get markName() {
45
+ const tone = this.tone === undefined ? '' : this.toneLabel || (this.toneLabels[this.tone] ?? '');
46
+ return [tone, this.pending ? this.pendingLabel : ''].filter((word) => word !== '').join(', ');
47
+ }
34
48
  willUpdate(changed) {
35
49
  super.willUpdate(changed);
36
50
  if (['datetime', 'relative'].some((key) => changed.has(key))) {
@@ -41,8 +55,12 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
41
55
  hostAria(this.internals, { role: 'listitem' });
42
56
  }
43
57
  renderMarker() {
44
- const icon = this.tone === undefined ? circle : record;
45
- return html `<span part="marker"
58
+ const icon = this.tone === undefined || this.pending ? circle : record;
59
+ const name = this.markName;
60
+ return html `<span
61
+ part="marker"
62
+ role=${name === '' ? nothing : 'img'}
63
+ aria-label=${name === '' ? nothing : name}
46
64
  ><slot name="marker"
47
65
  ><svg part="dot" fill="currentColor" viewBox=${icon.viewBox} aria-hidden="true">
48
66
  ${unsafeSVG(icon.content)}
@@ -68,12 +86,26 @@ let HelixHeadlessTimelineItem = class HelixHeadlessTimelineItem extends HelixEle
68
86
  __decorate([
69
87
  property()
70
88
  ], HelixHeadlessTimelineItem.prototype, "datetime", void 0);
89
+ __decorate([
90
+ property({ reflect: true, type: Boolean })
91
+ ], HelixHeadlessTimelineItem.prototype, "pending", void 0);
92
+ __decorate([
93
+ words,
94
+ property({ attribute: 'pending-label' })
95
+ ], HelixHeadlessTimelineItem.prototype, "pendingLabel", void 0);
71
96
  __decorate([
72
97
  property()
73
98
  ], HelixHeadlessTimelineItem.prototype, "relative", void 0);
74
99
  __decorate([
75
100
  property({ reflect: true })
76
101
  ], HelixHeadlessTimelineItem.prototype, "tone", void 0);
102
+ __decorate([
103
+ property({ attribute: 'tone-label' })
104
+ ], HelixHeadlessTimelineItem.prototype, "toneLabel", void 0);
105
+ __decorate([
106
+ words,
107
+ property({ attribute: 'tone-labels', converter: wordsConverter })
108
+ ], HelixHeadlessTimelineItem.prototype, "toneLabels", void 0);
77
109
  HelixHeadlessTimelineItem = __decorate([
78
110
  customElement('helix-headless-timeline-item')
79
111
  ], HelixHeadlessTimelineItem);
@@ -23,12 +23,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
23
23
  * @documentation https://fusion.dev/helix/components/video-playlist
24
24
  *
25
25
  * @slot - The entry's name.
26
+ * @slot indicator - A mark shown on the entry that is playing, such as an equalizer icon. Rendered only while the entry is current.
26
27
  * @slot media - A thumbnail. Defaults to an image of `poster`.
27
28
  * @slot meta - Content after the name, such as a duration or a date.
28
29
  *
29
30
  * @event {HelixSelectEvent} helix-select - The entry was chosen. Cancelable: cancel it to keep the current entry.
30
31
  *
31
32
  * @csspart container - The list item that holds the button.
33
+ * @csspart indicator - The `indicator` slot, at the end of the row, rendered only while the entry is playing. It draws no box until you give it one.
32
34
  * @csspart item - The row, which is a button.
33
35
  * @csspart item-current - Added to `item` for the entry that is playing.
34
36
  * @csspart label - The name and the content after it.
@@ -50,6 +52,15 @@ export declare class HelixHeadlessVideoPlaylistItem extends HelixElement {
50
52
  * Prevents the entry from being chosen.
51
53
  */
52
54
  disabled: boolean;
55
+ /**
56
+ * Whether the entry holds the playlist's tab stop. Unset, every entry is in
57
+ * the tab order, which is what a server renders.
58
+ *
59
+ * **Written by the playlist, with no attribute, and it reaches the button.**
60
+ * The playlist gives the stop to the entry a reader last focused, or to the
61
+ * current one, and the arrow keys reach the rest.
62
+ */
63
+ focusable?: boolean;
53
64
  /**
54
65
  * The image shown before this entry plays, also used as the row's thumbnail.
55
66
  *
@@ -48,6 +48,7 @@ let HelixHeadlessVideoPlaylistItem = class HelixHeadlessVideoPlaylistItem extend
48
48
  type="button"
49
49
  aria-current=${this.current ? 'true' : nothing}
50
50
  ?disabled=${this.disabled}
51
+ tabindex=${this.focusable === false ? '-1' : nothing}
51
52
  @click=${this.choose}
52
53
  >
53
54
  <span part="media">${this.renderMedia()}</span>
@@ -55,6 +56,7 @@ let HelixHeadlessVideoPlaylistItem = class HelixHeadlessVideoPlaylistItem extend
55
56
  <slot></slot>
56
57
  <slot name="meta"></slot>
57
58
  </span>
59
+ ${this.current ? html `<slot name="indicator" part="indicator"></slot>` : nothing}
58
60
  </button>
59
61
  </div>`;
60
62
  }
@@ -65,6 +67,9 @@ __decorate([
65
67
  __decorate([
66
68
  property({ reflect: true, type: Boolean })
67
69
  ], HelixHeadlessVideoPlaylistItem.prototype, "disabled", void 0);
70
+ __decorate([
71
+ property({ attribute: false })
72
+ ], HelixHeadlessVideoPlaylistItem.prototype, "focusable", void 0);
68
73
  __decorate([
69
74
  property()
70
75
  ], HelixHeadlessVideoPlaylistItem.prototype, "poster", void 0);
@@ -72,6 +72,12 @@ export declare class HelixHeadlessVideoPlaylistMenu extends HelixElement {
72
72
  * here. The menu cannot write onto a plain button slotted into it.
73
73
  */
74
74
  private readonly heardToggle;
75
+ /**
76
+ * Puts focus on the entry playing once the menu has opened, as a list of
77
+ * choices opens on the one chosen. The menu focuses its first row as it
78
+ * opens, so this follows that.
79
+ */
80
+ private focusPlaying;
75
81
  private observer;
76
82
  connectedCallback(): void;
77
83
  disconnectedCallback(): void;
@@ -21,6 +21,9 @@ let HelixHeadlessVideoPlaylistMenu = class HelixHeadlessVideoPlaylistMenu extend
21
21
  this.heardToggle = (event) => {
22
22
  if (event.target === event.currentTarget) {
23
23
  this.open = event.newState === 'open';
24
+ if (this.open) {
25
+ this.focusPlaying(event.currentTarget);
26
+ }
24
27
  }
25
28
  };
26
29
  this.reread = () => {
@@ -37,6 +40,14 @@ let HelixHeadlessVideoPlaylistMenu = class HelixHeadlessVideoPlaylistMenu extend
37
40
  };
38
41
  }
39
42
  static { this.words = 'helix-video-playlist-menu'; }
43
+ focusPlaying(menu) {
44
+ void Promise.all([this.updateComplete, menu.updateComplete]).then(() => {
45
+ const playing = this.renderRoot.querySelector('[part~="entry"][checked]');
46
+ if (playing && !playing.hasAttribute('disabled')) {
47
+ playing.focus();
48
+ }
49
+ });
50
+ }
40
51
  connectedCallback() {
41
52
  super.connectedCallback();
42
53
  this.updateComplete.then(() => this.watch());
@@ -24,19 +24,31 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
24
24
  * - the player reaches the end.
25
25
  *
26
26
  * On `ended` the playlist advances and the next clip starts playing, because
27
- * the user already pressed play once. `repeat` decides what happens at the
27
+ * the user already pressed play once. `no-advance` turns that off, and the
28
+ * playlist stays on the entry that ended. `repeat` decides what happens at the
28
29
  * end: `one` plays the same entry again, and `all` goes from the last entry
29
30
  * back to the first. Without it, the playlist stops on the last entry and
30
31
  * sends `helix-end`. `shuffle` plays the entries in a random order, starting
31
32
  * from the one that is current, and `previous` retraces that order.
32
33
  *
34
+ * **`helix-before-activate` says why the playlist is moving**, in
35
+ * `detail.cause`: `press` for an entry or a menu choice, `next` and
36
+ * `previous` for the player's controls and the methods, `ended` at the end of
37
+ * a clip, and `removed` when the current entry leaves the playlist. Cancel an
38
+ * `ended` move to show your own "Up next" countdown, then call `next()`.
39
+ *
40
+ * **Removing the current entry moves the playlist on**, to the entry after it,
41
+ * or to the one before it when it was the last. A clip that was playing keeps
42
+ * playing in the entry that takes its place.
43
+ *
33
44
  * **The player is named by the current entry**, and the device's own media
34
45
  * controls show the entry as the title. A player you gave a `label` keeps
35
46
  * your name, and only the title follows the entry.
36
47
  *
37
- * **Keyboard.** Each entry is a button in the tab order, and Enter or Space
38
- * plays it. The arrow keys move focus to the entry before or after, and Home
39
- * and End to the first and the last, passing over disabled entries. The
48
+ * **Keyboard.** The entries take one stop in the tab order: the entry you
49
+ * last focused, or the current entry before you have focused one. Enter or
50
+ * Space plays it. The arrow keys move focus to the entry before or after, and
51
+ * Home and End to the first and the last, passing over disabled entries. The
40
52
  * current entry carries `aria-current`. While focus is on the player, the
41
53
  * player's own keys work, and `Video` lists them, and Shift with N or P plays
42
54
  * the next or the previous entry. The player's previous and next controls
@@ -54,12 +66,14 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
54
66
  * @documentation https://fusion.dev/helix/components/video-playlist
55
67
  *
56
68
  * @slot - The entries.
69
+ * @slot empty - Shown while the playlist has no entries. Defaults to `empty-label`.
57
70
  * @slot player - The player that plays the entries.
58
71
  *
59
72
  * @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.
60
- * @event {HelixBeforeActivateEvent} helix-before-activate - The current entry is about to change. Cancelable: cancel it to keep the current entry.
73
+ * @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.
61
74
  * @event {HelixEndEvent} helix-end - The last entry finished playing, and the playlist stopped there. Not sent while `repeat` is set.
62
75
  *
76
+ * @csspart empty - The box that holds the `empty` slot, drawn while there are no entries.
63
77
  * @csspart items - The list of entries.
64
78
  * @csspart live - A live region that says which entry plays after the playlist moves.
65
79
  * @csspart player - The region that holds the player.
@@ -72,10 +86,20 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
72
86
  * `registerWords('de', { 'helix-video-playlist': { label: 'Wiedergabeliste' } })`.
73
87
  */
74
88
  static words: string;
89
+ /**
90
+ * What the playlist says while it has no entries. The `empty` slot replaces it.
91
+ */
92
+ emptyLabel: string;
75
93
  /**
76
94
  * The list's name, read by screen readers.
77
95
  */
78
96
  label: string;
97
+ /**
98
+ * Stays on an entry when its clip ends, where the playlist would otherwise
99
+ * play the next one. The player's controls, a press, and `next()` still
100
+ * move. `repeat="one"` still plays the entry again.
101
+ */
102
+ noAdvance: boolean;
79
103
  /**
80
104
  * What the live region says when the playlist moves to another entry.
81
105
  * `{title}` is the entry's name.
@@ -140,6 +164,21 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
140
164
  * shows its start control and waits.
141
165
  */
142
166
  private chased;
167
+ /**
168
+ * The entries' values in the order of the last update, and the one that was
169
+ * current then. Read when entries leave, to find the neighbor of a current
170
+ * entry that has gone.
171
+ */
172
+ private known;
173
+ /**
174
+ * The entry that holds the list's tab stop: the one a reader last focused.
175
+ * Before they focus one, the current entry holds it.
176
+ */
177
+ private stop;
178
+ /** The entry the player was last pointed at. */
179
+ private pointed;
180
+ /** Hears entries arrive and leave, which change no property here. */
181
+ private watcher;
143
182
  /**
144
183
  * The player, whichever tag the consumer slotted.
145
184
  *
@@ -189,6 +228,19 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
189
228
  */
190
229
  previous(): void;
191
230
  private readonly handleSelect;
231
+ /** An entry that takes focus takes the tab stop with it. */
232
+ private readonly handleFocusIn;
233
+ /**
234
+ * Entries arrived or left. Where the current entry left, the playlist moves
235
+ * to the entry after it, or to the one before it when it was the last, and
236
+ * keeps playing where it was playing.
237
+ */
238
+ private readonly handleEntries;
239
+ /**
240
+ * Gives one entry the tab stop and takes it from the rest: the entry last
241
+ * focused, then the current entry, then the first that can be chosen.
242
+ */
243
+ private placeStop;
192
244
  /**
193
245
  * The arrow keys, Home and End on an entry move focus to another one.
194
246
  *
@@ -220,6 +272,8 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
220
272
  private readonly handleEnded;
221
273
  connectedCallback(): void;
222
274
  disconnectedCallback(): void;
275
+ /** Asks for the render after a hydration, which can see the entries. */
276
+ protected firstUpdated(changed: PropertyValues): void;
223
277
  /** A shuffle turned off forgets its order, so the next one is new. */
224
278
  protected willUpdate(changed: PropertyValues): void;
225
279
  /**