@fusion.dev/helix-headless 0.150.0 → 0.152.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 +1388 -93
- package/dist/app-header/root/app-header-root.d.ts +75 -0
- package/dist/app-header/root/app-header-root.js +140 -5
- package/dist/area-chart/root/area-chart-root.d.ts +36 -0
- package/dist/area-chart/root/area-chart-root.js +17 -2
- package/dist/bar-chart/root/bar-chart-root.d.ts +28 -4
- package/dist/bar-chart/root/bar-chart-root.js +17 -4
- package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +11 -2
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +11 -2
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +14 -2
- package/dist/breadcrumbs/root/breadcrumbs-root.js +16 -3
- package/dist/callout/root/callout-root.d.ts +20 -0
- package/dist/callout/root/callout-root.js +19 -4
- package/dist/chat/composer/chat-composer.d.ts +30 -0
- package/dist/chat/composer/chat-composer.js +47 -2
- package/dist/chat/root/chat-root.d.ts +10 -0
- package/dist/chat/root/chat-root.js +14 -0
- package/dist/data-table/root/data-table-root.d.ts +34 -2
- package/dist/data-table/root/data-table-root.js +63 -2
- package/dist/dialog/description.d.ts +10 -0
- package/dist/dialog/description.js +22 -8
- package/dist/dialog/root/dialog-root.d.ts +17 -1
- package/dist/dialog/root/dialog-root.js +2 -2
- package/dist/divider/root/divider-root.d.ts +8 -6
- package/dist/divider/root/divider-root.js +10 -2
- package/dist/drawer/root/drawer-root.d.ts +38 -2
- package/dist/drawer/root/drawer-root.js +33 -6
- package/dist/element-events.generated.js +4 -0
- package/dist/kbd/root/kbd-root.d.ts +7 -1
- package/dist/kbd/root/kbd-root.js +8 -4
- package/dist/lightbox/root/lightbox-root.d.ts +44 -6
- package/dist/lightbox/root/lightbox-root.js +59 -11
- package/dist/lightbox/zoom-controller.d.ts +5 -0
- package/dist/lightbox/zoom-controller.js +8 -0
- package/dist/line-chart/root/line-chart-root.d.ts +36 -0
- package/dist/line-chart/root/line-chart-root.js +15 -2
- package/dist/menu/root/menu-root.js +2 -0
- package/dist/navigation/root/navigation-root.d.ts +11 -1
- package/dist/navigation/root/navigation-root.js +10 -5
- package/dist/notifications/root/notifications-root.d.ts +17 -0
- package/dist/notifications/root/notifications-root.js +21 -1
- package/dist/popover/root/popover-root.d.ts +18 -7
- package/dist/popover/root/popover-root.js +8 -1
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +18 -0
- package/dist/scatter-chart/root/scatter-chart-root.js +38 -15
- package/dist/splitter/root/splitter-root.d.ts +41 -0
- package/dist/splitter/root/splitter-root.js +77 -0
- package/dist/table/column/table-column.js +3 -2
- package/dist/table/grid-controller.d.ts +14 -2
- package/dist/table/grid-controller.js +27 -1
- package/dist/table/root/table-root.d.ts +30 -4
- package/dist/table/root/table-root.js +45 -7
- package/dist/table/row/table-row.d.ts +12 -2
- package/dist/table/row/table-row.js +8 -2
- package/dist/tabs/root/tabs-root.d.ts +25 -0
- package/dist/tabs/root/tabs-root.js +28 -1
- package/dist/tour/naming.d.ts +9 -3
- package/dist/tour/naming.js +9 -3
- package/dist/tour/root/tour-root.d.ts +29 -5
- package/dist/tour/root/tour-root.js +13 -1
- package/dist/tour/stop/tour-stop.d.ts +2 -1
- package/dist/tour/stop/tour-stop.js +4 -1
- package/dist/tree/root/tree-root.d.ts +13 -0
- package/dist/tree/root/tree-root.js +42 -2
- package/dist/video/root/video-root.d.ts +6 -0
- package/dist/video/root/video-root.js +5 -1
- package/dist/video-playlist/root/video-playlist-root.d.ts +4 -0
- package/dist/video-playlist/root/video-playlist-root.js +4 -1
- package/dist/virtual-list/root/virtual-list-root.d.ts +23 -1
- package/dist/virtual-list/root/virtual-list-root.js +21 -2
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +1098 -128
- package/types/vue.d.ts +248 -59
- package/types/words.d.ts +12 -2
package/dist/tour/naming.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { headingIn } from '@fusion.dev/helix-core';
|
|
2
|
-
|
|
2
|
+
import { wordsAmong } from '../dialog/description.js';
|
|
3
|
+
export const stopNaming = ({ content, description, heading, labeled, loose = null, progress, }) => {
|
|
3
4
|
const first = heading.length > 0 ? null : headingIn(content);
|
|
4
5
|
const found = heading.length > 0 ? [...heading] : first ? [first] : [];
|
|
5
6
|
const place = progress ? [progress] : [];
|
|
7
|
+
const said = description.length > 0
|
|
8
|
+
? [...description]
|
|
9
|
+
: content.length === 0 && loose
|
|
10
|
+
? [loose]
|
|
11
|
+
: wordsAmong(content, first);
|
|
6
12
|
if (found.length === 0 && !labeled) {
|
|
7
|
-
return { describedBy:
|
|
13
|
+
return { describedBy: said, labelledBy: place };
|
|
8
14
|
}
|
|
9
|
-
return { describedBy: [...
|
|
15
|
+
return { describedBy: [...said, ...place], labelledBy: found };
|
|
10
16
|
};
|
|
@@ -40,11 +40,27 @@ declare const HelixHeadlessTour_base: (new (...args: any[]) => import("@fusion.d
|
|
|
40
40
|
* you have moved it somewhere else on the page. A stop does not trap focus, so
|
|
41
41
|
* you can still `Tab` out to the element it describes.
|
|
42
42
|
*
|
|
43
|
-
* **The
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
43
|
+
* **The target is scrolled into view** before its stop is shown, by the
|
|
44
|
+
* least distance that brings it on screen, so the stop that points at it is
|
|
45
|
+
* on screen too.
|
|
46
|
+
*
|
|
47
|
+
* **Keyboard.** Focus is on the current stop's panel, which is not in the
|
|
48
|
+
* `Tab` order itself.
|
|
49
|
+
*
|
|
50
|
+
* | Key | What it does |
|
|
51
|
+
* |---|---|
|
|
52
|
+
* | Right | Goes to the next stop. On the last stop it does nothing, and Next finishes the tour. |
|
|
53
|
+
* | Left | Goes back to the previous stop. |
|
|
54
|
+
* | Escape | Skips the tour, and puts focus back where it was when the tour opened. |
|
|
55
|
+
* | Tab, Shift+Tab | Moves through the controls in the stop, and on out to the page. A stop does not trap focus. |
|
|
56
|
+
* | Enter, Space | On Back, Next, or Skip, presses it. |
|
|
57
|
+
*
|
|
58
|
+
* The arrow that points the way the line runs goes on, and the other goes
|
|
59
|
+
* back, so on a right-to-left page Left goes on and Right goes back. A text field in a stop keeps
|
|
60
|
+
* its own arrows.
|
|
61
|
+
*
|
|
62
|
+
* The tour is a region named by `label` while it runs. A development build
|
|
63
|
+
* warns when a tour opens with no `label`.
|
|
48
64
|
*
|
|
49
65
|
* @since 0.1.0
|
|
50
66
|
* @status stable
|
|
@@ -186,6 +202,14 @@ export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
|
|
|
186
202
|
disconnectedCallback(): void;
|
|
187
203
|
protected willUpdate(changed: PropertyValues): void;
|
|
188
204
|
protected updated(changed: PropertyValues): void;
|
|
205
|
+
/** The target last scrolled into view, so a render that moved nothing scrolls nothing. */
|
|
206
|
+
private revealed;
|
|
207
|
+
/**
|
|
208
|
+
* Scrolls the current target into view, by the least distance that brings
|
|
209
|
+
* it on screen, before its stop is shown beside it. Zag's tour does the same.
|
|
210
|
+
* A target already on screen does not move.
|
|
211
|
+
*/
|
|
212
|
+
private reveal;
|
|
189
213
|
/** Where focus was when the tour opened, to give it back when it ends. */
|
|
190
214
|
private returnFocus;
|
|
191
215
|
/** Whether `follow` has seen the first update. */
|
|
@@ -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 { controlsIn, customElement, deepActiveElement, deepContains, formatWords, HelixActivateEvent, HelixBeforeActivateEvent, HelixCompleteEvent, HelixElement, HelixTourStatusEvent, hostAria, inlineStep, keyClaimed, MixinControllerOpen, requestChange, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, controlsIn, customElement, deepActiveElement, deepContains, formatWords, HelixActivateEvent, HelixBeforeActivateEvent, HelixCompleteEvent, HelixElement, HelixTourStatusEvent, hostAria, inlineStep, keyClaimed, MixinControllerOpen, requestChange, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property, queryAssignedElements, state } from 'lit/decorators.js';
|
|
10
10
|
import { stopBefore } from '../back.js';
|
|
@@ -151,11 +151,23 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
|
|
|
151
151
|
}
|
|
152
152
|
updated(changed) {
|
|
153
153
|
super.updated(changed);
|
|
154
|
+
this.reveal();
|
|
154
155
|
this.settle();
|
|
155
156
|
this.light();
|
|
157
|
+
if (changed.has('open') && this.open) {
|
|
158
|
+
auditName(this, () => null, 'Give it a `label`, such as "Getting started".', () => this.label !== '');
|
|
159
|
+
}
|
|
156
160
|
this.follow(changed);
|
|
157
161
|
this.wait();
|
|
158
162
|
}
|
|
163
|
+
reveal() {
|
|
164
|
+
const target = this.lit ?? undefined;
|
|
165
|
+
if (target === this.revealed) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.revealed = target;
|
|
169
|
+
target?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
|
|
170
|
+
}
|
|
159
171
|
follow(changed) {
|
|
160
172
|
if (!this.followed) {
|
|
161
173
|
this.followed = true;
|
|
@@ -37,7 +37,8 @@ import '../../button/index.js';
|
|
|
37
37
|
* The panel is a `group`, and a `dialog` for a dialog stop, only while the
|
|
38
38
|
* stop is shown. It is named by the `heading` slot, then by the first heading
|
|
39
39
|
* in the content, then by `label`, and otherwise by its place in the tour. It
|
|
40
|
-
* is described by the `description` slot
|
|
40
|
+
* is described by the `description` slot, or where that is empty by the words
|
|
41
|
+
* at the start of the content, and by its place, such as "2 of 5",
|
|
41
42
|
* so a screen reader says where the reader is at every stop. `focus()` moves
|
|
42
43
|
* focus to the panel, which is what the tour does when it opens or moves on.
|
|
43
44
|
*
|
|
@@ -68,6 +68,9 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
|
|
|
68
68
|
description: slotted('description'),
|
|
69
69
|
heading: slotted('heading'),
|
|
70
70
|
labeled: this.label !== '',
|
|
71
|
+
loose: [...this.childNodes].some((one) => one.nodeType === Node.TEXT_NODE && (one.textContent ?? '').trim() !== '')
|
|
72
|
+
? this.partNamed('content')
|
|
73
|
+
: null,
|
|
71
74
|
progress: this.position === '' ? null : this.partNamed('progress'),
|
|
72
75
|
});
|
|
73
76
|
reflectElements(bubble, 'ariaLabelledByElements', labelledBy, true);
|
|
@@ -127,7 +130,7 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
|
|
|
127
130
|
></div>
|
|
128
131
|
<slot name="heading" part="heading" @slotchange=${this.nameByHeading}></slot>
|
|
129
132
|
<slot name="description" part="description" @slotchange=${this.nameByHeading}></slot>
|
|
130
|
-
<div part="content"><slot></slot></div>
|
|
133
|
+
<div part="content"><slot @slotchange=${this.nameByHeading}></slot></div>
|
|
131
134
|
${this.position === ''
|
|
132
135
|
? ''
|
|
133
136
|
: html `<span part="progress" ?hidden=${!this.progress}>${this.position}</span>`}
|
|
@@ -36,6 +36,9 @@ declare const HelixHeadlessTree_base: (new (...args: any[]) => import("@fusion.d
|
|
|
36
36
|
* tree of check marks, Control+A (Command+A on an Apple keyboard) checks every
|
|
37
37
|
* item or clears them all, and Shift with Up or Down Arrow moves and checks.
|
|
38
38
|
*
|
|
39
|
+
* Removing the item that has focus moves focus to the item after it among its
|
|
40
|
+
* siblings, or the one before, or its parent, so focus stays in the tree.
|
|
41
|
+
*
|
|
39
42
|
* **`reorderable` lets you move items among their siblings.** Alt with Up or
|
|
40
43
|
* Down moves the focused item a place, and Alt with Home or End to the first
|
|
41
44
|
* or the last place among its siblings, as the APG's rearrangeable listbox
|
|
@@ -183,6 +186,16 @@ export declare class HelixHeadlessTree extends HelixHeadlessTree_base {
|
|
|
183
186
|
/** Whether many items can be checked. */
|
|
184
187
|
private get multiple();
|
|
185
188
|
private watcher;
|
|
189
|
+
/** The item that last had focus, so its removal can hand focus on. */
|
|
190
|
+
private focused;
|
|
191
|
+
private readonly handleFocusIn;
|
|
192
|
+
/**
|
|
193
|
+
* Puts focus back in the tree where the item that had it was removed and
|
|
194
|
+
* focus fell to the page: on the item after it among its siblings, or the
|
|
195
|
+
* one before, or its parent. Read from the removal itself, which knows where
|
|
196
|
+
* the item stood.
|
|
197
|
+
*/
|
|
198
|
+
private refocus;
|
|
186
199
|
/**
|
|
187
200
|
* Every item, in the order a reader moving down would meet them.
|
|
188
201
|
*
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, closestInstance, commaList, customElement, HelixElement, inlineStep, keyClaimed, MixinCollection, MixinControllerActive, ReorderController, scrollItemIntoView, selectsAll, slottedWords, Typeahead, treeArrow, typesCharacter, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, closestInstance, commaList, customElement, deepActiveElement, HelixElement, inlineStep, keyClaimed, MixinCollection, MixinControllerActive, ReorderController, scrollItemIntoView, selectsAll, slottedWords, Typeahead, treeArrow, typesCharacter, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { fromGrip } from '../grip.js';
|
|
@@ -52,6 +52,11 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
|
|
|
52
52
|
});
|
|
53
53
|
this.pickedUpLabel = 'Picked up {name}';
|
|
54
54
|
this.reorderable = false;
|
|
55
|
+
this.handleFocusIn = (event) => {
|
|
56
|
+
this.focused = event
|
|
57
|
+
.composedPath()
|
|
58
|
+
.find((node) => node instanceof HelixHeadlessTreeItem);
|
|
59
|
+
};
|
|
55
60
|
this.handleSelection = (event) => {
|
|
56
61
|
const origin = event.target;
|
|
57
62
|
if (!(origin instanceof HelixHeadlessTreeItem) || !this.items.includes(origin)) {
|
|
@@ -162,6 +167,36 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
|
|
|
162
167
|
get multiple() {
|
|
163
168
|
return this.selectionMode === 'multiple';
|
|
164
169
|
}
|
|
170
|
+
refocus(records) {
|
|
171
|
+
const gone = this.focused;
|
|
172
|
+
if (!gone || this.items.includes(gone)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.focused = undefined;
|
|
176
|
+
const active = deepActiveElement();
|
|
177
|
+
if (active !== null && active !== document.body) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const record = records.find((one) => [...one.removedNodes].some((node) => node === gone || node.contains(gone)));
|
|
181
|
+
const near = (from, forward) => {
|
|
182
|
+
let node = from ?? null;
|
|
183
|
+
while (node) {
|
|
184
|
+
if (node instanceof HelixHeadlessTreeItem && !node.disabled && node.isConnected) {
|
|
185
|
+
return node;
|
|
186
|
+
}
|
|
187
|
+
node = forward ? node.nextSibling : node.previousSibling;
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
};
|
|
191
|
+
const parent = record ? closestInstance(record.target, HelixHeadlessTreeItem) : null;
|
|
192
|
+
const next = near(record?.nextSibling, true) ??
|
|
193
|
+
near(record?.previousSibling, false) ??
|
|
194
|
+
(parent instanceof HelixHeadlessTreeItem && this.items.includes(parent)
|
|
195
|
+
? parent
|
|
196
|
+
: undefined) ??
|
|
197
|
+
this.visibleItems.find((item) => !item.disabled);
|
|
198
|
+
this.focusOn(next);
|
|
199
|
+
}
|
|
165
200
|
get items() {
|
|
166
201
|
return [...this.querySelectorAll('*')].filter((node) => node instanceof HelixHeadlessTreeItem);
|
|
167
202
|
}
|
|
@@ -305,7 +340,11 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
|
|
|
305
340
|
this.addEventListener('helix-select', this.handleSelection);
|
|
306
341
|
this.addEventListener('toggle', this.handleBranch, true);
|
|
307
342
|
this.addEventListener('keydown', this.handleKeydown);
|
|
308
|
-
this.
|
|
343
|
+
this.addEventListener('focusin', this.handleFocusIn);
|
|
344
|
+
this.watcher = new MutationObserver((records) => {
|
|
345
|
+
this.syncItems();
|
|
346
|
+
this.refocus(records);
|
|
347
|
+
});
|
|
309
348
|
this.watcher.observe(this, { childList: true, subtree: true });
|
|
310
349
|
}
|
|
311
350
|
disconnectedCallback() {
|
|
@@ -313,6 +352,7 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
|
|
|
313
352
|
this.removeEventListener('helix-select', this.handleSelection);
|
|
314
353
|
this.removeEventListener('toggle', this.handleBranch, true);
|
|
315
354
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
355
|
+
this.removeEventListener('focusin', this.handleFocusIn);
|
|
316
356
|
this.watcher?.disconnect();
|
|
317
357
|
this.watcher = undefined;
|
|
318
358
|
}
|
|
@@ -316,6 +316,12 @@ export declare class HelixHeadlessVideo extends HelixElement {
|
|
|
316
316
|
* Whether the video starts again at the end.
|
|
317
317
|
*/
|
|
318
318
|
loop: boolean;
|
|
319
|
+
/**
|
|
320
|
+
* The title the device's own media controls show while the video plays,
|
|
321
|
+
* such as on a lock screen. Empty, the default, shows `label`. A playlist
|
|
322
|
+
* writes the current entry's name here.
|
|
323
|
+
*/
|
|
324
|
+
mediaTitle: string;
|
|
319
325
|
/**
|
|
320
326
|
* What the button that opens `overflow-controls` is called.
|
|
321
327
|
*/
|
|
@@ -47,6 +47,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
|
|
|
47
47
|
this.label = '';
|
|
48
48
|
this.loadingLabel = 'Loading';
|
|
49
49
|
this.loop = false;
|
|
50
|
+
this.mediaTitle = '';
|
|
50
51
|
this.moreLabel = 'More controls';
|
|
51
52
|
this.muteLabel = 'Mute';
|
|
52
53
|
this.nextLabel = 'Next';
|
|
@@ -351,7 +352,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
|
|
|
351
352
|
}
|
|
352
353
|
claimSession() {
|
|
353
354
|
const skips = writtenList(`${this.extraControls} ${this.overflowControls}`);
|
|
354
|
-
this.session.claim({ artwork: this.poster, title: this.label }, {
|
|
355
|
+
this.session.claim({ artwork: this.poster, title: this.mediaTitle || this.label }, {
|
|
355
356
|
next: skips.includes('next') ? () => this.skip(false) : null,
|
|
356
357
|
previous: skips.includes('previous') ? () => this.skip(true) : null,
|
|
357
358
|
seek: (seconds) => this.seek(seconds),
|
|
@@ -954,6 +955,9 @@ __decorate([
|
|
|
954
955
|
__decorate([
|
|
955
956
|
property({ reflect: true, type: Boolean })
|
|
956
957
|
], HelixHeadlessVideo.prototype, "loop", void 0);
|
|
958
|
+
__decorate([
|
|
959
|
+
property({ attribute: 'media-title' })
|
|
960
|
+
], HelixHeadlessVideo.prototype, "mediaTitle", void 0);
|
|
957
961
|
__decorate([
|
|
958
962
|
words,
|
|
959
963
|
property({ attribute: 'more-label' })
|
|
@@ -30,6 +30,10 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
|
|
|
30
30
|
* sends `helix-end`. `shuffle` plays the entries in a random order, starting
|
|
31
31
|
* from the one that is current, and `previous` retraces that order.
|
|
32
32
|
*
|
|
33
|
+
* **The player is named by the current entry**, and the device's own media
|
|
34
|
+
* controls show the entry as the title. A player you gave a `label` keeps
|
|
35
|
+
* your name, and only the title follows the entry.
|
|
36
|
+
*
|
|
33
37
|
* **Keyboard.** Each entry is a button in the tab order, and Enter or Space
|
|
34
38
|
* plays it. The current entry carries `aria-current`. While focus is on the
|
|
35
39
|
* player, the player's own keys work, and `Video` lists them. Its previous
|
|
@@ -125,7 +125,10 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
|
|
|
125
125
|
if (player.src !== current.src) {
|
|
126
126
|
player.src = current.src;
|
|
127
127
|
player.poster = current.poster;
|
|
128
|
-
player.
|
|
128
|
+
player.mediaTitle = current.text;
|
|
129
|
+
if (!player.hasAttribute('label')) {
|
|
130
|
+
player.label = current.text;
|
|
131
|
+
}
|
|
129
132
|
if (this.chased) {
|
|
130
133
|
void player.updateComplete?.then(() => player.play()).catch(() => { });
|
|
131
134
|
}
|
|
@@ -91,6 +91,12 @@ export interface VirtualListRange {
|
|
|
91
91
|
* the last item says "Loading more" (`loading-label`, or your own content in
|
|
92
92
|
* the `loading` slot). `scrollToIndex` takes a reader to an item.
|
|
93
93
|
*
|
|
94
|
+
* **An empty list.** Set `empty-label`, or put your own content in the `empty`
|
|
95
|
+
* slot, for a row shown when there are no items and none are loading, such as
|
|
96
|
+
* "No messages yet" and a link to write one. Without either, an empty list
|
|
97
|
+
* shows nothing, since a list whose items have not arrived yet has not said
|
|
98
|
+
* it has none.
|
|
99
|
+
*
|
|
94
100
|
* **Row height.** Rows are placed by `estimate` until they are drawn. Then the
|
|
95
101
|
* list reads the drawn rows' height once and places every row by it, so a
|
|
96
102
|
* wrong estimate still reaches the last item. Set `measured` for rows of
|
|
@@ -112,6 +118,7 @@ export interface VirtualListRange {
|
|
|
112
118
|
* @documentation https://fusion.dev/helix/components/virtual-list
|
|
113
119
|
*
|
|
114
120
|
* @slot - The `<template>` that defines one row. The list writes the rows it renders beside it, and this slot shows them between the two spacers.
|
|
121
|
+
* @slot empty - Content for the row shown when there are no items and none are loading. Falls back to `empty-label`.
|
|
115
122
|
* @slot loading - Content for the row after the last item while `loading` is set. Falls back to `loading-label`.
|
|
116
123
|
*
|
|
117
124
|
* @event {Event} change - Fired once a press or a key has changed which items are selected, in a list with `selection-mode`.
|
|
@@ -121,6 +128,7 @@ export interface VirtualListRange {
|
|
|
121
128
|
*
|
|
122
129
|
* @csspart after - The space that stands in for the rows below the rendered ones.
|
|
123
130
|
* @csspart before - The space that stands in for the rows above the rendered ones.
|
|
131
|
+
* @csspart empty - The row shown when there are no items and none are loading, where `empty-label` or the `empty` slot gives it content.
|
|
124
132
|
* @csspart loading - The row after the last item while `loading` is set.
|
|
125
133
|
* @csspart viewport - The scrolling box. On this tier, it needs only a block size.
|
|
126
134
|
*/
|
|
@@ -136,6 +144,11 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
|
|
|
136
144
|
* time, with Left and Right moving along a line.
|
|
137
145
|
*/
|
|
138
146
|
columns: number;
|
|
147
|
+
/**
|
|
148
|
+
* The text of the row shown when there are no items and none are loading,
|
|
149
|
+
* where the `empty` slot is empty. Empty, the default, shows no row.
|
|
150
|
+
*/
|
|
151
|
+
emptyLabel: string;
|
|
139
152
|
/**
|
|
140
153
|
* The height of one row, in pixels, used to find the row at a scroll offset.
|
|
141
154
|
*
|
|
@@ -376,7 +389,16 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
|
|
|
376
389
|
* first when it is not drawn.
|
|
377
390
|
*/
|
|
378
391
|
private focusIndex;
|
|
379
|
-
/**
|
|
392
|
+
/**
|
|
393
|
+
* The row kept for focus is let go once focus has left it.
|
|
394
|
+
*
|
|
395
|
+
* **A task later, and not at once.** Between `focusout` and `focusin` the
|
|
396
|
+
* page's focus is on `<body>`, and Chromium runs microtasks there when a key
|
|
397
|
+
* moves focus. An update asked for here ran in that gap: Tab from a kept row
|
|
398
|
+
* to its own link found no row holding focus, removed the row, and the link
|
|
399
|
+
* went with it, leaving focus on `<body>`. A move to a control inside this
|
|
400
|
+
* list asks for its own update in `handleFocusIn`, so it needs nothing here.
|
|
401
|
+
*/
|
|
380
402
|
private readonly handleFocusOut;
|
|
381
403
|
/** A row that takes focus, by a press or by Tab, takes the tab stop with it. */
|
|
382
404
|
private readonly handleFocusIn;
|
|
@@ -15,6 +15,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
15
15
|
constructor() {
|
|
16
16
|
super(...arguments);
|
|
17
17
|
this.columns = 1;
|
|
18
|
+
this.emptyLabel = '';
|
|
18
19
|
this.estimate = 32;
|
|
19
20
|
this.for = '';
|
|
20
21
|
this.keyField = '';
|
|
@@ -103,8 +104,14 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
103
104
|
}
|
|
104
105
|
this.press(at, event.shiftKey && this.selectionMode === 'multiple');
|
|
105
106
|
};
|
|
106
|
-
this.handleFocusOut = () => {
|
|
107
|
-
|
|
107
|
+
this.handleFocusOut = (event) => {
|
|
108
|
+
const next = event.relatedTarget;
|
|
109
|
+
if (next instanceof Node && this.contains(next)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
this.requestUpdate();
|
|
114
|
+
}, 0);
|
|
108
115
|
};
|
|
109
116
|
this.handleFocusIn = (event) => {
|
|
110
117
|
let node = event.target;
|
|
@@ -433,6 +440,10 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
433
440
|
const kept = this.hasUpdated ? this.keptIndex() : undefined;
|
|
434
441
|
const { after, before } = this.virtualize.spacers(kept === undefined ? undefined : Math.floor(kept / this.across));
|
|
435
442
|
const across = this.across;
|
|
443
|
+
const empty = this.readFor !== undefined &&
|
|
444
|
+
!this.loading &&
|
|
445
|
+
this.data.length === 0 &&
|
|
446
|
+
(this.emptyLabel !== '' || this.querySelector(':scope > [slot="empty"]') !== null);
|
|
436
447
|
return html `<div
|
|
437
448
|
data-scroller
|
|
438
449
|
part="viewport"
|
|
@@ -457,12 +468,20 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
457
468
|
<slot name="loading">${this.loadingLabel}</slot>
|
|
458
469
|
</div>`
|
|
459
470
|
: nothing}
|
|
471
|
+
${empty
|
|
472
|
+
? html `<div part="empty" role=${this.selectionMode ? 'option' : 'listitem'}>
|
|
473
|
+
<slot name="empty">${this.emptyLabel}</slot>
|
|
474
|
+
</div>`
|
|
475
|
+
: nothing}
|
|
460
476
|
</div>`;
|
|
461
477
|
}
|
|
462
478
|
};
|
|
463
479
|
__decorate([
|
|
464
480
|
property({ type: Number })
|
|
465
481
|
], HelixHeadlessVirtualList.prototype, "columns", void 0);
|
|
482
|
+
__decorate([
|
|
483
|
+
property({ attribute: 'empty-label' })
|
|
484
|
+
], HelixHeadlessVirtualList.prototype, "emptyLabel", void 0);
|
|
466
485
|
__decorate([
|
|
467
486
|
property({ type: Number })
|
|
468
487
|
], HelixHeadlessVirtualList.prototype, "estimate", void 0);
|