@fusion.dev/helix-headless 0.159.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 +2980 -189
- 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/date-input/root/date-input-root.d.ts +108 -19
- package/dist/date-input/root/date-input-root.js +112 -13
- package/dist/date-picker/root/date-picker-root.d.ts +81 -16
- package/dist/date-picker/root/date-picker-root.js +140 -36
- package/dist/element-events.generated.js +6 -0
- package/dist/format-date/fields.js +1 -0
- package/dist/format-date/options.d.ts +3 -1
- package/dist/format-date/options.js +4 -1
- package/dist/format-date/root/format-date-root.d.ts +27 -11
- package/dist/format-date/root/format-date-root.js +6 -0
- package/dist/listbox/root/listbox-root.js +5 -0
- package/dist/notifications/notification/notification.d.ts +1 -0
- package/dist/notifications/notification/notification.js +2 -1
- package/dist/slider/root/slider-root.d.ts +54 -3
- package/dist/slider/root/slider-root.js +124 -2
- 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/time-input/root/time-input-root.d.ts +55 -7
- package/dist/time-input/root/time-input-root.js +70 -10
- package/dist/time-picker/root/time-picker-root.d.ts +27 -5
- package/dist/time-picker/root/time-picker-root.js +52 -13
- package/dist/timeline/item/timeline-item.d.ts +47 -10
- package/dist/timeline/item/timeline-item.js +36 -4
- package/dist/toggle/group/toggle-group.d.ts +18 -1
- package/dist/toggle/group/toggle-group.js +8 -2
- package/dist/toggle/root/toggle-root.d.ts +2 -2
- package/dist/toggle/root/toggle-root.js +2 -1
- 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 +1596 -224
- package/types/vue.d.ts +404 -49
- package/types/words.d.ts +151 -0
|
@@ -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);
|
|
@@ -37,7 +37,7 @@ declare const HelixHeadlessToggleGroup_base: (new (...args: any[]) => import("@f
|
|
|
37
37
|
* | Right, Down | Moves to the next item, from the last back to the first. In a single group, chooses it. |
|
|
38
38
|
* | Left, Up | Moves to the item before, from the first round to the last. In a single group, chooses it. |
|
|
39
39
|
* | Home, End | Moves to the first or the last item. In a single group, chooses it. |
|
|
40
|
-
* | Space, Enter | Chooses the focused item, or presses or releases it in a multiple group. |
|
|
40
|
+
* | Space, Enter | Chooses the focused item, or presses or releases it in a multiple group. With `allow-empty-selection`, takes back the chosen item. |
|
|
41
41
|
*
|
|
42
42
|
* Left and Right swap on a right-to-left page, and a disabled item is passed
|
|
43
43
|
* over. A single group is a `radiogroup` of items with `role="radio"` and
|
|
@@ -117,6 +117,15 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
|
|
|
117
117
|
* `MixinCollection` records for the same reason.
|
|
118
118
|
*/
|
|
119
119
|
private adopt;
|
|
120
|
+
/**
|
|
121
|
+
* The items whose `selected` this group has written, which `adopt` passes
|
|
122
|
+
* over: the mark is this group's answer, and the markup declared nothing.
|
|
123
|
+
*
|
|
124
|
+
* Measured before this: in a group with no default, choosing List and then
|
|
125
|
+
* resetting the form left List chosen and submitted, because `adopt` read
|
|
126
|
+
* the mark `sync` had put on it as a declaration and made it the default.
|
|
127
|
+
*/
|
|
128
|
+
private readonly marked;
|
|
120
129
|
/** Marks the chosen item, separately from placing the indicator. */
|
|
121
130
|
private sync;
|
|
122
131
|
/**
|
|
@@ -139,6 +148,14 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
|
|
|
139
148
|
private get chosenItem();
|
|
140
149
|
/** Whether several items can be pressed at once. */
|
|
141
150
|
private get multiple();
|
|
151
|
+
/**
|
|
152
|
+
* Whether a press on the chosen item, or Space or Enter on it, takes it
|
|
153
|
+
* back so nothing is chosen, as React Aria's `ToggleButtonGroup` and
|
|
154
|
+
* Radix's `ToggleGroup` do in a single group. Absent, a single group always
|
|
155
|
+
* keeps its choice once it has one, as a radio group does. A multiple group
|
|
156
|
+
* releases a pressed item either way.
|
|
157
|
+
*/
|
|
158
|
+
allowEmptySelection: boolean;
|
|
142
159
|
/**
|
|
143
160
|
* Whether the group holds a tab stop of its own.
|
|
144
161
|
*
|
|
@@ -11,6 +11,7 @@ import { HelixHeadlessToggle } from '../root/toggle-root.js';
|
|
|
11
11
|
let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinControllerValue(MixinField(HelixFormElement)) {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
|
+
this.marked = new WeakSet();
|
|
14
15
|
this.lastFocused = undefined;
|
|
15
16
|
this.noteFocus = (event) => {
|
|
16
17
|
const item = event.target;
|
|
@@ -19,6 +20,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
19
20
|
this.sync();
|
|
20
21
|
}
|
|
21
22
|
};
|
|
23
|
+
this.allowEmptySelection = false;
|
|
22
24
|
this.focusable = true;
|
|
23
25
|
this.indicator = new IndicatorController(this, {
|
|
24
26
|
active: () => this.chosenItem,
|
|
@@ -40,7 +42,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
40
42
|
}
|
|
41
43
|
const value = event.detail.value;
|
|
42
44
|
if (!this.multiple) {
|
|
43
|
-
this.requestValue(value);
|
|
45
|
+
this.requestValue(this.allowEmptySelection && value === this.value ? '' : value);
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
48
|
const next = toggleValue(this.values, value, this.items.map((item) => item.value));
|
|
@@ -63,7 +65,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
63
65
|
if (this.value !== '' || this.children === undefined) {
|
|
64
66
|
return;
|
|
65
67
|
}
|
|
66
|
-
const declared = this.items.find((item) => item.selected);
|
|
68
|
+
const declared = this.items.find((item) => item.defaultSelected || (item.selected && !this.marked.has(item)));
|
|
67
69
|
if (declared) {
|
|
68
70
|
this.defaultValue = declared.value;
|
|
69
71
|
}
|
|
@@ -73,6 +75,7 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
73
75
|
const pressed = new Set(this.values);
|
|
74
76
|
const entry = this.focusable ? this.entry() : undefined;
|
|
75
77
|
for (const item of this.items) {
|
|
78
|
+
this.marked.add(item);
|
|
76
79
|
item.selected = item === active;
|
|
77
80
|
item.pressed = this.multiple ? pressed.has(item.value) : undefined;
|
|
78
81
|
item.focusable = item === entry;
|
|
@@ -192,6 +195,9 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
192
195
|
return html `${inside ? nothing : label}<div part="row"><slot name="start"></slot><div part="track">${inside ? label : nothing}<div part="indicator" aria-hidden="true"></div><slot @slotchange=${this.handleItems}></slot></div><slot name="end"></slot></div>${this.renderHint()}${this.renderFeedback()}`;
|
|
193
196
|
}
|
|
194
197
|
};
|
|
198
|
+
__decorate([
|
|
199
|
+
property({ attribute: 'allow-empty-selection', type: Boolean })
|
|
200
|
+
], HelixHeadlessToggleGroup.prototype, "allowEmptySelection", void 0);
|
|
195
201
|
__decorate([
|
|
196
202
|
property({ attribute: false })
|
|
197
203
|
], HelixHeadlessToggleGroup.prototype, "focusable", void 0);
|
|
@@ -31,9 +31,9 @@ declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion
|
|
|
31
31
|
* @event {Event} change - Fired after a toggle button on its own is pressed or released.
|
|
32
32
|
* @event {HelixSelectEvent} helix-select - Fired when the item is chosen. Cancelable: call `preventDefault()` to keep the current choice.
|
|
33
33
|
*
|
|
34
|
-
* @csspart item - The radio button.
|
|
34
|
+
* @csspart item - The button: a radio in a group where one is chosen, and a button with `aria-pressed` on its own or in a group where several can be.
|
|
35
35
|
*
|
|
36
|
-
* @cssstate pressed - The item is pressed, in a group where several can be.
|
|
36
|
+
* @cssstate pressed - The item is pressed, on its own or in a group where several can be.
|
|
37
37
|
* @cssstate selected - The item is chosen.
|
|
38
38
|
*/
|
|
39
39
|
export declare class HelixHeadlessToggle extends HelixHeadlessToggle_base {
|
|
@@ -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, HelixElement, HostClickController, MixinSelectableItem, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, customElement, HelixElement, HostClickController, MixinSelectableItem, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(HelixElement) {
|
|
@@ -39,6 +39,7 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
|
|
|
39
39
|
firstUpdated(changed) {
|
|
40
40
|
super.firstUpdated(changed);
|
|
41
41
|
this.finishHydration();
|
|
42
|
+
auditName(this, () => this.partNamed('item'), 'Give it words in its default slot, an `aria-label`, or a label on the icon it holds.');
|
|
42
43
|
}
|
|
43
44
|
render() {
|
|
44
45
|
const alone = this.alone;
|
|
@@ -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
|
/**
|