@fusion.dev/helix-headless 0.150.0 → 0.151.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 +741 -66
- 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/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/divider/root/divider-root.d.ts +8 -6
- package/dist/divider/root/divider-root.js +10 -2
- 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/line-chart/root/line-chart-root.d.ts +36 -0
- package/dist/line-chart/root/line-chart-root.js +15 -2
- 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/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/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 +648 -98
- package/types/vue.d.ts +195 -43
- package/types/words.d.ts +4 -2
|
@@ -24,6 +24,16 @@ export interface TableGridOptions {
|
|
|
24
24
|
part: (name: string) => HTMLElement | null;
|
|
25
25
|
/** The rows in the order they are drawn. */
|
|
26
26
|
rows: () => Element[];
|
|
27
|
+
/**
|
|
28
|
+
* The words each row is found by when a reader types, in the order the rows
|
|
29
|
+
* are drawn.
|
|
30
|
+
*/
|
|
31
|
+
rowWords: () => string[];
|
|
32
|
+
/**
|
|
33
|
+
* Selects every row from the row last chosen, or from row `from` where none
|
|
34
|
+
* was, to row `to`, and says whether the table selects at all.
|
|
35
|
+
*/
|
|
36
|
+
selectRange: (to: number, from: number) => boolean;
|
|
27
37
|
/** Selects every row for Control or Command with A, and says whether it did. */
|
|
28
38
|
selectAll: () => boolean;
|
|
29
39
|
/** Whether the table is a grid now. */
|
|
@@ -31,8 +41,8 @@ export interface TableGridOptions {
|
|
|
31
41
|
}
|
|
32
42
|
/**
|
|
33
43
|
* The keyboard of a `grid` table: one tab stop among its cells, the arrow
|
|
34
|
-
* keys, Home, End and the Page keys between them,
|
|
35
|
-
*
|
|
44
|
+
* keys, Home, End and the Page keys between them, Enter or F2 into a cell's
|
|
45
|
+
* controls, typeahead over the rows, and Shift ranges of rows.
|
|
36
46
|
*
|
|
37
47
|
* **Tab leaves the grid.** The controls in a cell are the consumer's, so their
|
|
38
48
|
* tab order is left as written: Tab on a cell, or past the last control of a
|
|
@@ -59,6 +69,8 @@ export declare class TableGridController implements ReactiveController {
|
|
|
59
69
|
private pointing;
|
|
60
70
|
/** The cells given a `tabindex`, so a table that stops being a grid takes it back. */
|
|
61
71
|
private readonly given;
|
|
72
|
+
/** The letters a reader has typed at the rows. */
|
|
73
|
+
private readonly typeahead;
|
|
62
74
|
constructor(host: ReactiveControllerHost & HTMLElement, options: TableGridOptions);
|
|
63
75
|
hostConnected(): void;
|
|
64
76
|
hostDisconnected(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { deepActiveElement, deepContains, focusableElements, gridMove, inlineStep, pageRows, scrollingAncestor, scrollRoom, selectsAll, whenHydrated, } from '@fusion.dev/helix-core';
|
|
1
|
+
import { deepActiveElement, deepContains, focusableElements, gridMove, inlineStep, pageRows, scrollingAncestor, scrollRoom, selectsAll, Typeahead, typesCharacter, whenHydrated, } from '@fusion.dev/helix-core';
|
|
2
2
|
const HEADS = { grip: 'grip-head', select: 'select-all' };
|
|
3
3
|
const shown = (element) => {
|
|
4
4
|
const hidden = element.getAttribute('hidden');
|
|
@@ -13,6 +13,7 @@ export class TableGridController {
|
|
|
13
13
|
this.focusWanted = false;
|
|
14
14
|
this.pointing = false;
|
|
15
15
|
this.given = new Set();
|
|
16
|
+
this.typeahead = new Typeahead();
|
|
16
17
|
this.handleKeydown = (event) => {
|
|
17
18
|
if (!this.options.on() || event.defaultPrevented || event.altKey) {
|
|
18
19
|
return;
|
|
@@ -35,6 +36,25 @@ export class TableGridController {
|
|
|
35
36
|
}
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
39
|
+
if (typesCharacter(event) && (event.key !== ' ' || this.typeahead.typing())) {
|
|
40
|
+
const found = this.typeahead.match(event.key, this.options.rowWords(), place.row);
|
|
41
|
+
if (found >= 0) {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
if (found !== place.row) {
|
|
44
|
+
this.moveTo({
|
|
45
|
+
column: Math.max(0, Math.min(place.column, this.widthOf(found) - 1)),
|
|
46
|
+
row: found,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (event.key === ' ' && event.shiftKey && place.row >= 0) {
|
|
53
|
+
if (this.options.selectRange(place.row, place.row)) {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
38
58
|
if (event.key === 'Enter' || event.key === ' ' || event.key === 'F2') {
|
|
39
59
|
if (event.key !== 'F2' && this.options.activate(place, event)) {
|
|
40
60
|
event.preventDefault();
|
|
@@ -57,6 +77,12 @@ export class TableGridController {
|
|
|
57
77
|
}
|
|
58
78
|
event.preventDefault();
|
|
59
79
|
to.column = Math.max(0, Math.min(to.column, this.widthOf(to.row) - 1));
|
|
80
|
+
if (event.shiftKey &&
|
|
81
|
+
(event.key === 'ArrowUp' || event.key === 'ArrowDown') &&
|
|
82
|
+
place.row >= 0 &&
|
|
83
|
+
to.row >= 0) {
|
|
84
|
+
this.options.selectRange(to.row, place.row);
|
|
85
|
+
}
|
|
60
86
|
if (to.row !== place.row || to.column !== place.column) {
|
|
61
87
|
this.moveTo(to);
|
|
62
88
|
}
|
|
@@ -76,7 +76,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
76
76
|
* through cell by cell, the headings included, as React Aria's table is. The
|
|
77
77
|
* table is then a `grid` and each value a `gridcell`, and the table gives
|
|
78
78
|
* every cell a `tabindex` so focus can rest on it: set `grid` only where the
|
|
79
|
-
* cells may carry one.
|
|
79
|
+
* cells may carry one.
|
|
80
80
|
*
|
|
81
81
|
* | Key | What it does |
|
|
82
82
|
* |---|---|
|
|
@@ -90,7 +90,10 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
90
90
|
* | Enter, Space on a sortable heading | Sorts by the column. |
|
|
91
91
|
* | Space on a row, Enter or Space on its checkbox cell | Selects the row, in a `selectable` table. |
|
|
92
92
|
* | Enter or Space on a grip cell | Picks the row up, or drops it, in a `reorderable` table. Up, Down, Home, and End move a row picked up, and Escape puts it back. |
|
|
93
|
+
* | Shift with Down or Up | Selects the rows it moves across, in a `selectable` table. |
|
|
94
|
+
* | Shift with Space | Selects every row from the one last chosen, in a `selectable` table. |
|
|
93
95
|
* | Control or Command with A | Selects every row, in a `selectable` table. |
|
|
96
|
+
* | A letter or a word | Moves to the next row whose first cell starts with what you typed. |
|
|
94
97
|
* | Tab | Leaves the table. |
|
|
95
98
|
*
|
|
96
99
|
* The CSS custom properties listed for this component are written by it and
|
|
@@ -105,7 +108,9 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
105
108
|
* @documentation https://fusion.dev/helix/components/table
|
|
106
109
|
*
|
|
107
110
|
* @slot - The table's rows.
|
|
111
|
+
* @slot empty - Content shown when there are no rows and none are loading, such as a picture and a link. Falls back to `empty-text`, which the live region still says.
|
|
108
112
|
* @slot heading - Words that name the table, shown above it, such as a heading.
|
|
113
|
+
* @slot loading - Content shown while the rows load, such as a spinner. Falls back to `loading-text`, which the live region still says.
|
|
109
114
|
*
|
|
110
115
|
* @event {HelixBeforeReorderEvent} helix-before-reorder - Fired when a row is dropped at a new place, in a `reorderable` table, with every row's key in the new order. Cancelable: cancel it to keep the row where it is.
|
|
111
116
|
* @event {HelixReorderEvent} helix-reorder - Fired after `helix-before-reorder` when nobody canceled it. Move your rows to the order in `keys`. Cancelable: cancel it to say the move did not happen.
|
|
@@ -144,6 +149,8 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
144
149
|
* `registerWords('de', { 'helix-table': { selectAllLabel: 'Alle Zeilen auswählen' } })`.
|
|
145
150
|
*/
|
|
146
151
|
static words: string;
|
|
152
|
+
/** The empty and loading lines are the `empty` and `loading` slots. */
|
|
153
|
+
protected statusSlots: boolean;
|
|
147
154
|
/**
|
|
148
155
|
* The word for an ascending sort, in a `sorted-label` that writes `{order}`.
|
|
149
156
|
*
|
|
@@ -383,9 +390,25 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
383
390
|
private readonly handleToggle;
|
|
384
391
|
/** Every row's key, in the order the rows are written. */
|
|
385
392
|
private get everyKey();
|
|
386
|
-
/** The
|
|
393
|
+
/** The keys of the rows that can be chosen: every row that is not `disabled`. */
|
|
394
|
+
private get enabledKeys();
|
|
395
|
+
/** Whether the row of that key is `disabled`. */
|
|
396
|
+
private isDisabled;
|
|
397
|
+
/**
|
|
398
|
+
* The checkbox in the heading: every row that can be chosen, or none of
|
|
399
|
+
* them where each already is. A `disabled` row keeps what it had.
|
|
400
|
+
*/
|
|
387
401
|
private chooseAll;
|
|
388
|
-
/**
|
|
402
|
+
/** The key of the row a Shift range runs from: the one last chosen. */
|
|
403
|
+
private anchorKey;
|
|
404
|
+
/**
|
|
405
|
+
* Selects every row from the one last chosen to the drawn row `to`, keeping
|
|
406
|
+
* what was already selected. With no row to run from, the range is the row
|
|
407
|
+
* the reader is on, `from`, and it becomes the one to run from. Says whether
|
|
408
|
+
* the table selects at all.
|
|
409
|
+
*/
|
|
410
|
+
private selectRange;
|
|
411
|
+
/** One row's checkbox, ticked or not. It becomes the row a range runs from. */
|
|
389
412
|
private chooseRow;
|
|
390
413
|
/**
|
|
391
414
|
* Enter, Space or F2 on a cell of a `grid` table, and whether it did
|
|
@@ -498,7 +521,10 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
498
521
|
* assignment has not happened.
|
|
499
522
|
*/
|
|
500
523
|
static shadowRootOptions: ShadowRootInit;
|
|
501
|
-
/**
|
|
524
|
+
/**
|
|
525
|
+
* The rows: every child that is not a column, a heading, or the content of
|
|
526
|
+
* the `empty` or `loading` slot, which is what the body slot is assigned.
|
|
527
|
+
*/
|
|
502
528
|
protected get slotted(): Element[];
|
|
503
529
|
private assign;
|
|
504
530
|
/**
|
|
@@ -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, assignSlots, commaList, customElement, cycle, everyState, followLink, format, formatWords, HelixElement, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, inRowOrder, MixinCollection, onAndroid, parse, ReorderController, reflectElements, requestChange, sort, splitValues, toggleEvery, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, anchorAt, assignSlots, commaList, customElement, cycle, everyState, followLink, format, formatWords, HelixElement, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, inRowOrder, MixinCollection, onAndroid, parse, ReorderController, rangeAdd, reflectElements, requestChange, sort, splitValues, toggleEvery, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
@@ -44,9 +44,12 @@ const SAMPLE = 200;
|
|
|
44
44
|
const textOf = (element) => (element?.textContent ?? '').replace(/\s+/g, ' ').trim();
|
|
45
45
|
const lengthOf = (element) => textOf(element).length;
|
|
46
46
|
const numberOf = (value) => Number(value.replaceAll(',', ''));
|
|
47
|
+
const STATUS = new Set(['empty', 'loading']);
|
|
48
|
+
const NAMED = new Set([...STATUS, 'heading']);
|
|
47
49
|
let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixElement) {
|
|
48
50
|
constructor() {
|
|
49
51
|
super(...arguments);
|
|
52
|
+
this.statusSlots = true;
|
|
50
53
|
this.ascendingLabel = 'ascending';
|
|
51
54
|
this.canceledLabel = 'Canceled';
|
|
52
55
|
this.columns = '';
|
|
@@ -82,7 +85,9 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
82
85
|
on: () => this.grid,
|
|
83
86
|
part: (name) => this.partNamed(name),
|
|
84
87
|
rows: () => (this.childrenKnown ? this.arrangedRows : []),
|
|
85
|
-
|
|
88
|
+
rowWords: () => this.arrangedRows.map((row) => textOf(row.children[0])),
|
|
89
|
+
selectAll: () => this.selectable && this.requestSelected(new Set([...this.chosen, ...this.enabledKeys])),
|
|
90
|
+
selectRange: (to, from) => this.selectRange(to, from),
|
|
86
91
|
});
|
|
87
92
|
this.reorder = new ReorderController(this, {
|
|
88
93
|
axis: 'block',
|
|
@@ -125,6 +130,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
125
130
|
event.preventDefault();
|
|
126
131
|
this.chooseRow(found.key, event.detail.checked);
|
|
127
132
|
};
|
|
133
|
+
this.anchorKey = undefined;
|
|
128
134
|
this.handleHeadClick = (event) => {
|
|
129
135
|
const path = event.composedPath();
|
|
130
136
|
if (!path.some((node) => node instanceof Element && node.getAttribute('part') === 'sort')) {
|
|
@@ -260,10 +266,35 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
260
266
|
get everyKey() {
|
|
261
267
|
return this.keyed.map(({ key }) => key);
|
|
262
268
|
}
|
|
269
|
+
get enabledKeys() {
|
|
270
|
+
return this.keyed.filter(({ row }) => !row.disabled).map(({ key }) => key);
|
|
271
|
+
}
|
|
272
|
+
isDisabled(key) {
|
|
273
|
+
return this.keyed.find((one) => one.key === key)?.row.disabled === true;
|
|
274
|
+
}
|
|
263
275
|
chooseAll() {
|
|
264
|
-
this.requestSelected(new Set(toggleEvery(this.chosen, this.
|
|
276
|
+
this.requestSelected(new Set(toggleEvery(this.chosen, this.enabledKeys)));
|
|
277
|
+
}
|
|
278
|
+
selectRange(to, from) {
|
|
279
|
+
if (!this.selectable) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
const keyOf = new Map(this.keyed.map(({ key, row }) => [row, key]));
|
|
283
|
+
const keys = this.arrangedRows.map((row) => keyOf.get(row) ?? '');
|
|
284
|
+
const { anchor, anchorKey } = anchorAt(keys, this.anchorKey, from);
|
|
285
|
+
this.anchorKey = anchorKey;
|
|
286
|
+
const now = this.chosen;
|
|
287
|
+
const next = rangeAdd([...now], keys, anchor, to)?.filter((key) => now.has(key) || !this.isDisabled(key));
|
|
288
|
+
if (next && next.length > now.size) {
|
|
289
|
+
this.requestSelected(new Set(next));
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
265
292
|
}
|
|
266
293
|
chooseRow(key, on) {
|
|
294
|
+
if (this.isDisabled(key)) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.anchorKey = key;
|
|
267
298
|
const keys = this.chosen;
|
|
268
299
|
if (on) {
|
|
269
300
|
keys.add(key);
|
|
@@ -298,7 +329,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
298
329
|
this.chooseRow(found.key, !this.chosen.has(found.key));
|
|
299
330
|
return true;
|
|
300
331
|
}
|
|
301
|
-
if (event.key === 'Enter' && row?.href) {
|
|
332
|
+
if (event.key === 'Enter' && row?.href && !row.disabled) {
|
|
302
333
|
followLink(this.ownerDocument, { href: row.href, rel: row.rel, target: row.target }, event);
|
|
303
334
|
return true;
|
|
304
335
|
}
|
|
@@ -418,7 +449,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
418
449
|
}; }
|
|
419
450
|
get slotted() {
|
|
420
451
|
const columns = new Set(this.tableColumns);
|
|
421
|
-
return [...(this.children ?? [])].filter((child) => !columns.has(child) && child.getAttribute('slot')
|
|
452
|
+
return [...(this.children ?? [])].filter((child) => !columns.has(child) && !NAMED.has(child.getAttribute('slot') ?? ''));
|
|
422
453
|
}
|
|
423
454
|
assign() {
|
|
424
455
|
if (this.assignsByName) {
|
|
@@ -429,7 +460,14 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
429
460
|
}
|
|
430
461
|
const columns = new Set(this.tableColumns);
|
|
431
462
|
const headings = new Set(this.headings);
|
|
432
|
-
|
|
463
|
+
const status = [...(this.children ?? [])].filter((child) => STATUS.has(child.getAttribute('slot') ?? ''));
|
|
464
|
+
assignSlots(this.renderRoot, [...columns, ...headings, ...status, ...this.arrangedRows], (child) => columns.has(child)
|
|
465
|
+
? 'columns'
|
|
466
|
+
: headings.has(child)
|
|
467
|
+
? 'heading'
|
|
468
|
+
: status.includes(child)
|
|
469
|
+
? (child.getAttribute('slot') ?? '')
|
|
470
|
+
: '');
|
|
433
471
|
}
|
|
434
472
|
alignCells() {
|
|
435
473
|
if (typeof CSSStyleSheet === 'undefined' || !('replaceSync' in CSSStyleSheet.prototype)) {
|
|
@@ -483,7 +521,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
483
521
|
if (!this.selectable) {
|
|
484
522
|
return nothing;
|
|
485
523
|
}
|
|
486
|
-
const every = this.childrenKnown ? this.
|
|
524
|
+
const every = this.childrenKnown ? this.enabledKeys : [];
|
|
487
525
|
const { checked, indeterminate } = everyState(this.chosen, every);
|
|
488
526
|
const checkbox = this.tag('checkbox');
|
|
489
527
|
const grid = this.grid;
|
|
@@ -13,7 +13,8 @@ export type TableRowMove = 'dragging' | 'drop-after' | 'drop-before';
|
|
|
13
13
|
* it, or make it interactive.
|
|
14
14
|
*
|
|
15
15
|
* In a `selectable` table the row draws a checkbox cell before its own cells,
|
|
16
|
-
* and `value` is the key the table's `selected` lists it by.
|
|
16
|
+
* and `value` is the key the table's `selected` lists it by. `disabled` keeps
|
|
17
|
+
* the row out of the selection and out of its link.
|
|
17
18
|
*
|
|
18
19
|
* In a `reorderable` table the row draws a grip cell before those, holding a
|
|
19
20
|
* button named by the table's `move-row-label`, and `value` is the key
|
|
@@ -56,7 +57,7 @@ export type TableRowMove = 'dragging' | 'drop-after' | 'drop-before';
|
|
|
56
57
|
* @cssstate dragging - The row is being moved, in a `reorderable` table.
|
|
57
58
|
* @cssstate drop-after - The row being moved goes after this one when it is dropped.
|
|
58
59
|
* @cssstate drop-before - The row being moved goes before this one when it is dropped.
|
|
59
|
-
* @cssstate link - While the row has `href`, so a press on it follows the link.
|
|
60
|
+
* @cssstate link - While the row has `href` and is not `disabled`, so a press on it follows the link.
|
|
60
61
|
* @cssstate selected - While the row is selected, in a `selectable` table.
|
|
61
62
|
*/
|
|
62
63
|
export declare class HelixHeadlessTableRow extends HelixElement {
|
|
@@ -65,6 +66,15 @@ export declare class HelixHeadlessTableRow extends HelixElement {
|
|
|
65
66
|
* finds its columns. Not a consumer's to read or set.
|
|
66
67
|
*/
|
|
67
68
|
readonly isTableRow = true;
|
|
69
|
+
/**
|
|
70
|
+
* Takes the row out of the table's selection and out of its link: the row's
|
|
71
|
+
* checkbox is disabled, Space, Shift ranges, and the checkbox in the heading
|
|
72
|
+
* pass over it, and a press does not follow `href`. A row already selected
|
|
73
|
+
* stays selected. A screen reader hears it as unavailable, and in a `grid`
|
|
74
|
+
* table the arrow keys still reach it, as a disabled cell in a grid is
|
|
75
|
+
* reached.
|
|
76
|
+
*/
|
|
77
|
+
disabled: boolean;
|
|
68
78
|
/**
|
|
69
79
|
* The URL a press on the row follows, which makes the whole row a link.
|
|
70
80
|
* Empty, the default, leaves presses to the row's cells.
|
|
@@ -15,6 +15,7 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
|
|
|
15
15
|
constructor() {
|
|
16
16
|
super(...arguments);
|
|
17
17
|
this.isTableRow = true;
|
|
18
|
+
this.disabled = false;
|
|
18
19
|
this.href = '';
|
|
19
20
|
this.rel = '';
|
|
20
21
|
this.target = '';
|
|
@@ -30,7 +31,7 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
|
|
|
30
31
|
this.selectStop = false;
|
|
31
32
|
this.columnRules = undefined;
|
|
32
33
|
this.handleClick = (event) => {
|
|
33
|
-
if (this.href === '' || event.button !== 0 || event.defaultPrevented) {
|
|
34
|
+
if (this.disabled || this.href === '' || event.button !== 0 || event.defaultPrevented) {
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
const cell = event
|
|
@@ -68,8 +69,9 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
|
|
|
68
69
|
super.willUpdate(changed);
|
|
69
70
|
hostAria(this.internals, { role: 'row' });
|
|
70
71
|
this.internals.ariaSelected = this.gridCell && this.selectable ? String(this.selected) : null;
|
|
72
|
+
this.internals.ariaDisabled = this.disabled ? 'true' : null;
|
|
71
73
|
this.toggleState('selected', this.selectable && this.selected);
|
|
72
|
-
this.toggleState('link', this.href !== '');
|
|
74
|
+
this.toggleState('link', this.href !== '' && !this.disabled);
|
|
73
75
|
this.toggleState('dragging', this.move === 'dragging');
|
|
74
76
|
this.toggleState('drop-after', this.move === 'drop-after');
|
|
75
77
|
this.toggleState('drop-before', this.move === 'drop-before');
|
|
@@ -100,6 +102,7 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
|
|
|
100
102
|
mark-checked: checkbox-mark-checked, mark-dash: checkbox-mark-dash,
|
|
101
103
|
mark-indeterminate: checkbox-mark-indeterminate"
|
|
102
104
|
aria-label=${this.selectLabel}
|
|
105
|
+
?disabled=${this.disabled}
|
|
103
106
|
.checked=${this.selected}
|
|
104
107
|
></${checkbox}
|
|
105
108
|
></span>`;
|
|
@@ -126,6 +129,9 @@ let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
|
|
|
126
129
|
return html `${this.renderGrip()}${this.renderSelect()}<slot @slotchange=${this.handleSlotChange}></slot>`;
|
|
127
130
|
}
|
|
128
131
|
};
|
|
132
|
+
__decorate([
|
|
133
|
+
property({ reflect: true, type: Boolean })
|
|
134
|
+
], HelixHeadlessTableRow.prototype, "disabled", void 0);
|
|
129
135
|
__decorate([
|
|
130
136
|
property()
|
|
131
137
|
], HelixHeadlessTableRow.prototype, "href", void 0);
|
|
@@ -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);
|