@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
|
@@ -11,6 +11,7 @@ import { property, state } from 'lit/decorators.js';
|
|
|
11
11
|
import { snapTo } from '../snap.js';
|
|
12
12
|
const STACK_BELOW = 512;
|
|
13
13
|
const channel = new WeakMap();
|
|
14
|
+
const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
14
15
|
let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
15
16
|
static { HelixHeadlessSplitter_1 = this; }
|
|
16
17
|
static { this.words = 'helix-splitter'; }
|
|
@@ -69,6 +70,41 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
69
70
|
this.reached = undefined;
|
|
70
71
|
this.requestUpdate('position', previous);
|
|
71
72
|
}
|
|
73
|
+
watchLength() {
|
|
74
|
+
if (!this.primary || typeof ResizeObserver === 'undefined') {
|
|
75
|
+
this.lengths?.disconnect();
|
|
76
|
+
this.lengths = undefined;
|
|
77
|
+
this.length = 0;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (this.lengths) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.lengths = new ResizeObserver(([entry]) => {
|
|
84
|
+
const box = entry?.borderBoxSize?.[0];
|
|
85
|
+
const length = (this.vertical
|
|
86
|
+
? (box?.blockSize ?? entry?.contentRect.height)
|
|
87
|
+
: (box?.inlineSize ?? entry?.contentRect.width)) ?? 0;
|
|
88
|
+
this.holdPrimary(length);
|
|
89
|
+
});
|
|
90
|
+
this.lengths.observe(this);
|
|
91
|
+
}
|
|
92
|
+
holdPrimary(length) {
|
|
93
|
+
const before = this.length;
|
|
94
|
+
this.length = length;
|
|
95
|
+
if (!this.primary || before <= 0 || length <= 0 || length === before) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (this.collapsed || this.dragging) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const position = this.position;
|
|
102
|
+
const kept = ((this.primary === 'start' ? position : 100 - position) / 100) * before;
|
|
103
|
+
const share = (kept / length) * 100;
|
|
104
|
+
const previous = position;
|
|
105
|
+
this.positionState = this.clamp(this.primary === 'start' ? share : 100 - share);
|
|
106
|
+
this.requestUpdate('position', previous);
|
|
107
|
+
}
|
|
72
108
|
get showing() {
|
|
73
109
|
return this.reached === undefined ? this.position : this.clamp(this.reached);
|
|
74
110
|
}
|
|
@@ -225,6 +261,34 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
225
261
|
});
|
|
226
262
|
this.max = 100;
|
|
227
263
|
this.min = 0;
|
|
264
|
+
this.length = 0;
|
|
265
|
+
this.handlePaneKeys = (event) => {
|
|
266
|
+
if (event.key !== 'F6' ||
|
|
267
|
+
event.altKey ||
|
|
268
|
+
event.ctrlKey ||
|
|
269
|
+
event.metaKey ||
|
|
270
|
+
event.defaultPrevented) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const path = event.composedPath();
|
|
274
|
+
const pane = (name) => this.renderRoot
|
|
275
|
+
?.querySelector(`slot[name="${name}"]`)
|
|
276
|
+
?.assignedElements({ flatten: true }) ?? [];
|
|
277
|
+
const inner = path.find((node) => node instanceof HelixHeadlessSplitter_1);
|
|
278
|
+
if (inner !== this) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const fromStart = pane('start').some((one) => path.includes(one));
|
|
282
|
+
const to = pane(fromStart ? 'end' : 'start');
|
|
283
|
+
const first = to.flatMap((one) => [
|
|
284
|
+
...(one instanceof HTMLElement && one.matches(FOCUSABLE) ? [one] : []),
|
|
285
|
+
...one.querySelectorAll(FOCUSABLE),
|
|
286
|
+
])[0];
|
|
287
|
+
if (first) {
|
|
288
|
+
event.preventDefault();
|
|
289
|
+
first.focus();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
228
292
|
this.snap = '';
|
|
229
293
|
this.snapThreshold = 12;
|
|
230
294
|
this.step = 2;
|
|
@@ -435,13 +499,19 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
435
499
|
super.connectedCallback();
|
|
436
500
|
if (this.hasUpdated) {
|
|
437
501
|
this.watchWidth();
|
|
502
|
+
this.watchLength();
|
|
438
503
|
this.findOuter();
|
|
439
504
|
}
|
|
505
|
+
this.addEventListener('keydown', this.handlePaneKeys);
|
|
440
506
|
}
|
|
441
507
|
disconnectedCallback() {
|
|
442
508
|
super.disconnectedCallback();
|
|
509
|
+
this.removeEventListener('keydown', this.handlePaneKeys);
|
|
443
510
|
this.sizes?.disconnect();
|
|
444
511
|
this.sizes = undefined;
|
|
512
|
+
this.lengths?.disconnect();
|
|
513
|
+
this.lengths = undefined;
|
|
514
|
+
this.length = 0;
|
|
445
515
|
if (this.outer) {
|
|
446
516
|
channel.get(this.outer)?.uncross(this);
|
|
447
517
|
}
|
|
@@ -451,6 +521,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
451
521
|
firstUpdated(changed) {
|
|
452
522
|
super.firstUpdated(changed);
|
|
453
523
|
this.watchWidth();
|
|
524
|
+
this.watchLength();
|
|
454
525
|
this.findOuter();
|
|
455
526
|
this.finishHydration();
|
|
456
527
|
}
|
|
@@ -459,6 +530,9 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
459
530
|
if (changed.has('orientation')) {
|
|
460
531
|
this.watchWidth();
|
|
461
532
|
}
|
|
533
|
+
if (changed.has('primary')) {
|
|
534
|
+
this.watchLength();
|
|
535
|
+
}
|
|
462
536
|
if (this.collapsed) {
|
|
463
537
|
this.dataset.collapsed = this.collapsed;
|
|
464
538
|
}
|
|
@@ -578,6 +652,9 @@ __decorate([
|
|
|
578
652
|
__decorate([
|
|
579
653
|
property({ type: Number })
|
|
580
654
|
], HelixHeadlessSplitter.prototype, "position", null);
|
|
655
|
+
__decorate([
|
|
656
|
+
property()
|
|
657
|
+
], HelixHeadlessSplitter.prototype, "primary", void 0);
|
|
581
658
|
__decorate([
|
|
582
659
|
property()
|
|
583
660
|
], HelixHeadlessSplitter.prototype, "snap", void 0);
|
|
@@ -174,6 +174,7 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
|
|
|
174
174
|
const named = { name, named: name ? 'yes' : 'no' };
|
|
175
175
|
const grid = this.gridCell;
|
|
176
176
|
const control = grid ? '-1' : nothing;
|
|
177
|
+
const width = Math.min(4096, Math.max(HelixHeadlessTableColumn_1.NARROWEST, this.drawn));
|
|
177
178
|
return html `<span
|
|
178
179
|
part="column"
|
|
179
180
|
role=${grid ? 'columnheader' : nothing}
|
|
@@ -197,8 +198,8 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
|
|
|
197
198
|
aria-orientation="vertical"
|
|
198
199
|
aria-valuemin=${HelixHeadlessTableColumn_1.NARROWEST}
|
|
199
200
|
aria-valuemax="4096"
|
|
200
|
-
aria-valuenow=${
|
|
201
|
-
aria-valuetext=${formatWords(this, 'widthLabel', { width
|
|
201
|
+
aria-valuenow=${width}
|
|
202
|
+
aria-valuetext=${formatWords(this, 'widthLabel', { width })}
|
|
202
203
|
@keydown=${this.handleResizeKey}
|
|
203
204
|
@pointerdown=${this.handleResizeStart}
|
|
204
205
|
@pointermove=${this.handleResizeMove}
|
|
@@ -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);
|
|
@@ -36,6 +36,20 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
|
|
|
36
36
|
* Disabled tabs are passed over. Tab leaves the list for the selected panel,
|
|
37
37
|
* which is a tab stop only while it holds nothing focusable.
|
|
38
38
|
*
|
|
39
|
+
* **Keyboard.**
|
|
40
|
+
*
|
|
41
|
+
* | Key | What it does |
|
|
42
|
+
* |---|---|
|
|
43
|
+
* | Right, Down | Moves to the next tab, from the last round to the first. Selects it, unless `manual` is set or a tab is a link. |
|
|
44
|
+
* | Left, Up | Moves to the tab before, from the first round to the last. |
|
|
45
|
+
* | Home, End | Moves to the first or the last tab. |
|
|
46
|
+
* | Enter, Space | Selects the tab with focus, or follows its link. |
|
|
47
|
+
* | Tab | Leaves the list for the selected panel. |
|
|
48
|
+
*
|
|
49
|
+
* Removing the selected tab selects the first enabled tab that is left, and
|
|
50
|
+
* `active` says so, with `helix-activate`. Where the removed tab had focus,
|
|
51
|
+
* focus moves to the tab now selected.
|
|
52
|
+
*
|
|
39
53
|
* The `indicator` part moves to the selected tab and carries its place and
|
|
40
54
|
* size, so a mark of your own can be drawn there: style the part, or slot an
|
|
41
55
|
* element into `indicator`, which sits inside the part and reads the same
|
|
@@ -125,8 +139,19 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
|
125
139
|
private get linked();
|
|
126
140
|
private get panels();
|
|
127
141
|
private readonly handleSelection;
|
|
142
|
+
/** The values of the tabs the last pairing read, to tell a removed tab from one not yet here. */
|
|
143
|
+
private held;
|
|
128
144
|
/** Pushes the current value down to whichever tab and panel own it. */
|
|
129
145
|
private sync;
|
|
146
|
+
/** The tab that last had focus, so its removal can hand focus on. */
|
|
147
|
+
private focused;
|
|
148
|
+
private readonly handleFocusIn;
|
|
149
|
+
/**
|
|
150
|
+
* Puts focus on the selected tab where the tab that had it was removed and
|
|
151
|
+
* focus fell to the page, which is where removing a focused element leaves
|
|
152
|
+
* it.
|
|
153
|
+
*/
|
|
154
|
+
private refocus;
|
|
130
155
|
connectedCallback(): void;
|
|
131
156
|
disconnectedCallback(): void;
|
|
132
157
|
protected firstUpdated(changed: PropertyValues): void;
|
|
@@ -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, customElement, HelixElement, IndicatorController, KeyboardNavigationController, MixinControllerActive, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, customElement, deepActiveElement, HelixActivateEvent, HelixElement, IndicatorController, KeyboardNavigationController, MixinControllerActive, 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 { HelixHeadlessTabPanel } from '../panel/tab-panel.js';
|
|
@@ -35,9 +35,15 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
35
35
|
}
|
|
36
36
|
this.requestActive(event.detail.value);
|
|
37
37
|
};
|
|
38
|
+
this.held = [];
|
|
39
|
+
this.handleFocusIn = (event) => {
|
|
40
|
+
const path = event.composedPath();
|
|
41
|
+
this.focused = this.tabs.find((tab) => path.includes(tab));
|
|
42
|
+
};
|
|
38
43
|
this.handleSlotChange = () => {
|
|
39
44
|
this.sync();
|
|
40
45
|
this.indicator.place();
|
|
46
|
+
this.refocus();
|
|
41
47
|
};
|
|
42
48
|
}
|
|
43
49
|
static { this.words = 'helix-tabs'; }
|
|
@@ -54,6 +60,13 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
54
60
|
const tabs = this.tabs;
|
|
55
61
|
const declared = this.active === '' ? tabs.find((tab) => tab.selected) : undefined;
|
|
56
62
|
const value = (declared ?? this.resolveActive(tabs))?.value ?? '';
|
|
63
|
+
const previous = this.active;
|
|
64
|
+
const gone = this.held.includes(previous) && !tabs.some((tab) => tab.value === previous);
|
|
65
|
+
this.held = tabs.map((tab) => tab.value);
|
|
66
|
+
if (gone && value !== '') {
|
|
67
|
+
this.active = value;
|
|
68
|
+
this.dispatchEvent(new HelixActivateEvent({ previous, value }));
|
|
69
|
+
}
|
|
57
70
|
for (const tab of tabs) {
|
|
58
71
|
const selected = tab.value === value;
|
|
59
72
|
whenHydrated(tab, () => {
|
|
@@ -77,13 +90,27 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
77
90
|
});
|
|
78
91
|
}
|
|
79
92
|
}
|
|
93
|
+
refocus() {
|
|
94
|
+
const gone = this.focused;
|
|
95
|
+
if (!gone || this.tabs.includes(gone)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.focused = undefined;
|
|
99
|
+
const active = deepActiveElement();
|
|
100
|
+
if (active !== null && active !== document.body) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
void this.updateComplete.then(() => this.tabs.find((tab) => tab.selected)?.focus());
|
|
104
|
+
}
|
|
80
105
|
connectedCallback() {
|
|
81
106
|
super.connectedCallback();
|
|
82
107
|
this.addEventListener('helix-select', this.handleSelection);
|
|
108
|
+
this.addEventListener('focusin', this.handleFocusIn);
|
|
83
109
|
}
|
|
84
110
|
disconnectedCallback() {
|
|
85
111
|
super.disconnectedCallback();
|
|
86
112
|
this.removeEventListener('helix-select', this.handleSelection);
|
|
113
|
+
this.removeEventListener('focusin', this.handleFocusIn);
|
|
87
114
|
}
|
|
88
115
|
firstUpdated(changed) {
|
|
89
116
|
super.firstUpdated(changed);
|
package/dist/tour/naming.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export interface StopWords {
|
|
|
15
15
|
heading: readonly Element[];
|
|
16
16
|
/** Whether the stop has a `label` of its own. */
|
|
17
17
|
labeled: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The region that holds the content, given where the content holds words
|
|
20
|
+
* with no element around them, which then describes the panel.
|
|
21
|
+
*/
|
|
22
|
+
loose?: Element | null;
|
|
18
23
|
/** The element that says the stop's place in the tour, such as "2 of 5". */
|
|
19
24
|
progress: Element | null;
|
|
20
25
|
}
|
|
@@ -24,7 +29,8 @@ export interface StopWords {
|
|
|
24
29
|
* Named by the `heading` slot, then by the first heading in the content, then
|
|
25
30
|
* by the stop's own `label`, and where there is none of those, by its place in
|
|
26
31
|
* the tour, so no stop is an unnamed group. Described by the `description`
|
|
27
|
-
* slot
|
|
28
|
-
*
|
|
32
|
+
* slot, or where it is empty by the words at the start of the content, as a
|
|
33
|
+
* `Dialog` is, and then by its place, which is how a screen reader hears where
|
|
34
|
+
* it is as focus moves from one stop to the next.
|
|
29
35
|
*/
|
|
30
|
-
export declare const stopNaming: ({ content, description, heading, labeled, progress, }: StopWords) => StopNaming;
|
|
36
|
+
export declare const stopNaming: ({ content, description, heading, labeled, loose, progress, }: StopWords) => StopNaming;
|