@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.
- package/custom-elements.json +1729 -50
- package/dist/canvas/root/canvas-root.d.ts +59 -2
- package/dist/canvas/root/canvas-root.js +123 -8
- package/dist/chat/message/chat-message.d.ts +8 -0
- package/dist/chat/message/chat-message.js +11 -0
- package/dist/chat/root/chat-root.d.ts +47 -1
- package/dist/chat/root/chat-root.js +130 -4
- package/dist/code-block/lines.d.ts +12 -0
- package/dist/code-block/lines.js +41 -0
- package/dist/code-block/root/code-block-root.d.ts +88 -5
- package/dist/code-block/root/code-block-root.js +154 -10
- package/dist/element-events.generated.js +4 -0
- package/dist/splitter/root/splitter-root.d.ts +15 -0
- package/dist/splitter/root/splitter-root.js +16 -2
- package/dist/table/grid-controller.d.ts +7 -1
- package/dist/table/grid-controller.js +9 -1
- package/dist/table/root/table-root.d.ts +122 -0
- package/dist/table/root/table-root.js +293 -17
- package/dist/table/row/table-row.d.ts +41 -0
- package/dist/table/row/table-row.js +56 -2
- package/dist/timeline/item/timeline-item.d.ts +47 -10
- package/dist/timeline/item/timeline-item.js +36 -4
- package/dist/video-playlist/item/video-playlist-item.d.ts +11 -0
- package/dist/video-playlist/item/video-playlist-item.js +5 -0
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +6 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +11 -0
- package/dist/video-playlist/root/video-playlist-root.d.ts +59 -5
- package/dist/video-playlist/root/video-playlist-root.js +82 -10
- package/dist/virtual-list/fill.d.ts +6 -1
- package/dist/virtual-list/fill.js +1 -1
- package/dist/virtual-list/keys.d.ts +8 -0
- package/dist/virtual-list/keys.js +20 -0
- package/dist/virtual-list/root/virtual-list-root.d.ts +98 -13
- package/dist/virtual-list/root/virtual-list-root.js +195 -20
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +936 -68
- package/types/vue.d.ts +226 -12
- 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
|
-
*
|
|
55
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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. `
|
|
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.**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
/**
|