@fusion.dev/helix-headless 0.165.0 → 0.167.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 +3235 -534
- package/dist/callout/root/callout-root.d.ts +8 -2
- package/dist/callout/root/callout-root.js +14 -1
- package/dist/canvas/arrange.d.ts +32 -0
- package/dist/canvas/arrange.js +144 -0
- package/dist/canvas/paste.d.ts +25 -0
- package/dist/canvas/paste.js +58 -0
- package/dist/canvas/root/canvas-root.d.ts +60 -3
- package/dist/canvas/root/canvas-root.js +166 -2
- package/dist/chat/composer/chat-composer.js +5 -1
- package/dist/code-block/root/code-block-root.d.ts +63 -0
- package/dist/code-block/root/code-block-root.js +117 -6
- package/dist/combobox/root/combobox-root.d.ts +6 -1
- package/dist/combobox/root/combobox-root.js +2 -2
- package/dist/date-input/root/date-input-root.d.ts +1 -1
- package/dist/element-events.generated.js +17 -2
- package/dist/file-input/root/file-input-root.d.ts +1 -1
- package/dist/file-input/root/file-input-root.js +1 -1
- package/dist/filter-input/menu/filter-menu.d.ts +1 -0
- package/dist/filter-input/menu/filter-menu.js +1 -1
- package/dist/format-date/root/format-date-root.d.ts +10 -3
- package/dist/format-date/root/format-date-root.js +6 -1
- package/dist/listbox/root/listbox-root.d.ts +12 -1
- package/dist/listbox/root/listbox-root.js +15 -6
- package/dist/menu/item/menu-item.d.ts +20 -0
- package/dist/menu/item/menu-item.js +53 -20
- package/dist/menu/root/menu-root.d.ts +84 -1
- package/dist/menu/root/menu-root.js +187 -44
- package/dist/object-inspector/root/object-inspector-root.d.ts +5 -0
- package/dist/object-inspector/root/object-inspector-root.js +5 -0
- package/dist/select/root/select-root.js +1 -1
- package/dist/steps/root/steps-root.d.ts +7 -0
- package/dist/steps/root/steps-root.js +7 -0
- package/dist/table/grid-controller.d.ts +10 -0
- package/dist/table/grid-controller.js +7 -0
- package/dist/table/root/table-root.d.ts +71 -1
- package/dist/table/root/table-root.js +122 -7
- package/dist/table/row/table-row.d.ts +17 -0
- package/dist/table/row/table-row.js +23 -0
- package/dist/time-input/root/time-input-root.d.ts +2 -1
- package/dist/time-input/root/time-input-root.js +1 -1
- package/dist/time-picker/root/time-picker-root.d.ts +48 -1
- package/dist/time-picker/root/time-picker-root.js +79 -21
- package/dist/video/root/video-root.d.ts +2 -0
- package/dist/video/root/video-root.js +4 -2
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
- package/dist/video-playlist/root/video-playlist-root.js +15 -1
- package/dist/virtual-list/root/virtual-list-root.d.ts +70 -2
- package/dist/virtual-list/root/virtual-list-root.js +216 -32
- package/dist/virtual-list/sections.d.ts +27 -0
- package/dist/virtual-list/sections.js +43 -0
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +716 -96
- package/types/vue.d.ts +148 -12
- package/types/words.d.ts +45 -0
|
@@ -594,7 +594,8 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
|
|
|
594
594
|
}
|
|
595
595
|
return html `<${this.tag('menu')} part="captions-menu"
|
|
596
596
|
exportparts="back: captions-menu-back, back-mark: captions-menu-back-mark, empty: captions-menu-empty,
|
|
597
|
-
header: captions-menu-header, live: captions-menu-live, loading: captions-menu-loading,
|
|
597
|
+
header: captions-menu-header, live: captions-menu-live, loading: captions-menu-loading,
|
|
598
|
+
search-field: captions-menu-search-field, surface: captions-menu-surface,
|
|
598
599
|
title: captions-menu-title" label=${this.subtitlesLabel} placement="top-end"
|
|
599
600
|
@toggle=${(event) => this.heardMenu('captions', event)}>
|
|
600
601
|
<button
|
|
@@ -804,7 +805,8 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
|
|
|
804
805
|
? nothing
|
|
805
806
|
: html `<${this.tag('menu')} part="overflow"
|
|
806
807
|
exportparts="back: overflow-back, back-mark: overflow-back-mark, empty: overflow-empty,
|
|
807
|
-
header: overflow-header, live: overflow-live, loading: overflow-loading,
|
|
808
|
+
header: overflow-header, live: overflow-live, loading: overflow-loading,
|
|
809
|
+
search-field: overflow-search-field, surface: overflow-surface,
|
|
808
810
|
title: overflow-title" label=${this.moreLabel} placement="top-end"
|
|
809
811
|
@toggle=${(event) => this.heardMenu('overflow', event)}>
|
|
810
812
|
<button
|
|
@@ -38,6 +38,7 @@ import '../../menu/index.js';
|
|
|
38
38
|
* @csspart menu-header - The bar above a submenu, holding the back button and the submenu's name.
|
|
39
39
|
* @csspart menu-live - The inner menu's `live` part, the region screen readers announce its empty and loading messages from.
|
|
40
40
|
* @csspart menu-loading - Shown while the items are loading.
|
|
41
|
+
* @csspart menu-search-field - The inner menu's filter field, drawn with `filterable`.
|
|
41
42
|
* @csspart menu-surface - The floating panel the menu is drawn on.
|
|
42
43
|
* @csspart menu-title - The name of the submenu that is shown.
|
|
43
44
|
* @csspart poster - An entry's poster image, if it has one.
|
|
@@ -84,7 +84,7 @@ let HelixHeadlessVideoPlaylistMenu = class HelixHeadlessVideoPlaylistMenu extend
|
|
|
84
84
|
renderMenu() {
|
|
85
85
|
return html `<${this.tag('menu')} part="menu"
|
|
86
86
|
exportparts="back: menu-back, back-mark: menu-back-mark, empty: menu-empty, header: menu-header,
|
|
87
|
-
live: menu-live, loading: menu-loading, surface: menu-surface, title: menu-title" label=${this.label} placement=${this.placement}
|
|
87
|
+
live: menu-live, loading: menu-loading, search-field: menu-search-field, surface: menu-surface, title: menu-title" label=${this.label} placement=${this.placement}
|
|
88
88
|
@toggle=${this.heardToggle}>
|
|
89
89
|
<slot name="trigger" slot="trigger">
|
|
90
90
|
<button
|
|
@@ -47,8 +47,16 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
|
|
|
47
47
|
if (event.defaultPrevented) {
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
+
const value = event.detail.value;
|
|
51
|
+
const player = this.player;
|
|
52
|
+
if (value === this.active) {
|
|
53
|
+
if (player && player.paused !== false) {
|
|
54
|
+
void Promise.resolve(player.play()).catch(() => { });
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
50
58
|
this.chased = true;
|
|
51
|
-
this.requestActive(
|
|
59
|
+
this.requestActive(value, 'press');
|
|
52
60
|
};
|
|
53
61
|
this.handleFocusIn = (event) => {
|
|
54
62
|
const entry = event.target;
|
|
@@ -259,6 +267,12 @@ let HelixHeadlessVideoPlaylist = class HelixHeadlessVideoPlaylist extends MixinC
|
|
|
259
267
|
if (changed.has('shuffle') && !this.shuffle) {
|
|
260
268
|
this.dealt = undefined;
|
|
261
269
|
}
|
|
270
|
+
if (this.active === '' && this.childrenKnown) {
|
|
271
|
+
const opening = this.resolveActive(this.items);
|
|
272
|
+
if (opening) {
|
|
273
|
+
this.active = opening.value;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
262
276
|
}
|
|
263
277
|
updated(changed) {
|
|
264
278
|
super.updated?.(changed);
|
|
@@ -8,6 +8,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
8
8
|
* `const row: VirtualListRenderer<Person> = (person) => person.name`.
|
|
9
9
|
*/
|
|
10
10
|
export type VirtualListRenderer<Item = unknown> = (item: Item, index: number) => unknown;
|
|
11
|
+
/**
|
|
12
|
+
* Draws one section's header, for a list given `sectionField`, given the
|
|
13
|
+
* section's name and its place among the sections. Returns anything Lit
|
|
14
|
+
* renders.
|
|
15
|
+
*/
|
|
16
|
+
export type VirtualListSectionRenderer = (name: string, index: number) => unknown;
|
|
11
17
|
/** Where `scrollToIndex` puts an item, and how it gets there. */
|
|
12
18
|
export interface VirtualListScrollOptions {
|
|
13
19
|
/**
|
|
@@ -55,12 +61,26 @@ export interface VirtualListRange {
|
|
|
55
61
|
* smallest item size, and follows the box as it resizes. `gap` spaces the rows, and the columns
|
|
56
62
|
* of a grid, by a number of pixels the list counts in each row's place.
|
|
57
63
|
*
|
|
64
|
+
* **Across.** `orientation="horizontal"` lays the items out along a line and
|
|
65
|
+
* scrolls it sideways, as a strip of cards does, windowed the same way:
|
|
66
|
+
* `estimate` is then an item's width, and the box needs an inline size.
|
|
67
|
+
*
|
|
68
|
+
* **Sections.** `section-field` names the field of each item that says which
|
|
69
|
+
* section it is in, and the list draws a header before each section's items,
|
|
70
|
+
* as React Aria's list box sections do. Each section is an element with the
|
|
71
|
+
* `group` role, named by its header, which is its first child and carries
|
|
72
|
+
* `data-header`, so your stylesheet styles it, such as with `position:
|
|
73
|
+
* sticky`. A section starts wherever the field changes, so give the items in
|
|
74
|
+
* order. The window counts the headers with the items, and a section the
|
|
75
|
+
* window starts inside is drawn with its header. `renderSection` draws a
|
|
76
|
+
* header of your own.
|
|
77
|
+
*
|
|
58
78
|
* **Keyboard.** The list is one tab stop.
|
|
59
79
|
*
|
|
60
80
|
* | Key | What it does |
|
|
61
81
|
* |---|---|
|
|
62
82
|
* | Down, Up | Moves to the next or the previous item, or in a grid to the item in the same column on the next or the previous line. |
|
|
63
|
-
* | Right, Left | In a grid, moves to the next or the previous item along the line, the other way round on a right-to-left page. |
|
|
83
|
+
* | Right, Left | In a grid, moves to the next or the previous item along the line, the other way round on a right-to-left page. In a list that runs across, moves to the next or the previous item the same way, and Down and Up are left to the row. |
|
|
64
84
|
* | Home, End | Moves to the first or the last item. |
|
|
65
85
|
* | Page Down, Page Up | Moves by the number of items, or of lines in a grid, on screen. |
|
|
66
86
|
* | A letter or a word | Moves to the next item whose text starts with what you typed. |
|
|
@@ -153,6 +173,7 @@ export interface VirtualListRange {
|
|
|
153
173
|
* @csspart viewport - The scrolling box. On this tier, it needs only a block size.
|
|
154
174
|
*
|
|
155
175
|
* @cssstate empty - While the list has read its items and holds none.
|
|
176
|
+
* @cssstate horizontal - While the list runs across, with `orientation="horizontal"`.
|
|
156
177
|
* @cssstate loading - While `loading` is set.
|
|
157
178
|
*/
|
|
158
179
|
export declare class HelixHeadlessVirtualList extends HelixElement {
|
|
@@ -247,6 +268,13 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
|
|
|
247
268
|
* follows the box as it resizes, which `columns` then does not decide.
|
|
248
269
|
*/
|
|
249
270
|
minColumnWidth: number;
|
|
271
|
+
/**
|
|
272
|
+
* Which way the list runs. `horizontal` lays the items out along a line
|
|
273
|
+
* and scrolls it sideways: Left and Right move between items, the other
|
|
274
|
+
* way round on a right-to-left page, and `estimate` is an item's width. A
|
|
275
|
+
* grid always runs down.
|
|
276
|
+
*/
|
|
277
|
+
orientation: 'horizontal' | 'vertical';
|
|
250
278
|
/**
|
|
251
279
|
* Draws one item into its row, given the item and its index. Set it as a
|
|
252
280
|
* property.
|
|
@@ -256,6 +284,20 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
|
|
|
256
284
|
* and place in the set. Where it is unset, the row `<template>` is used.
|
|
257
285
|
*/
|
|
258
286
|
renderItem: VirtualListRenderer<any> | undefined;
|
|
287
|
+
/**
|
|
288
|
+
* Draws each section's header, given its name and its place among the
|
|
289
|
+
* sections, for a list given `section-field`. Set it as a property. Where
|
|
290
|
+
* it is unset, the header is the name as text.
|
|
291
|
+
*/
|
|
292
|
+
renderSection: VirtualListSectionRenderer | undefined;
|
|
293
|
+
/**
|
|
294
|
+
* The field of each item that names its section, as a key or a path of
|
|
295
|
+
* keys. Set, the items are drawn in sections: a header, then the items, in
|
|
296
|
+
* an element with the `group` role that the header names. A section starts
|
|
297
|
+
* wherever the field changes, so give the items in order. A list of one
|
|
298
|
+
* column only.
|
|
299
|
+
*/
|
|
300
|
+
sectionField: string;
|
|
259
301
|
/**
|
|
260
302
|
* What a press does to the items already chosen: `toggle` adds or takes out
|
|
261
303
|
* the item, and `replace` selects it alone and lets the arrow keys carry the
|
|
@@ -358,6 +400,14 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
|
|
|
358
400
|
private watchWidth;
|
|
359
401
|
/** The items a span of lines holds. */
|
|
360
402
|
private itemsOn;
|
|
403
|
+
/** The line item `at` is on: its entry in a list with sections. */
|
|
404
|
+
private lineOf;
|
|
405
|
+
/** Whether the list runs across, which a grid never does. */
|
|
406
|
+
private get horizontal();
|
|
407
|
+
/** The sections and the items they were read from, kept until either changes. */
|
|
408
|
+
private sectionsRead;
|
|
409
|
+
/** The sections `section-field` divides the items into, in a list of one column. */
|
|
410
|
+
private get sections();
|
|
361
411
|
/**
|
|
362
412
|
* Scrolls the list to show the item at `index`.
|
|
363
413
|
*
|
|
@@ -443,6 +493,8 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
|
|
|
443
493
|
private isDisabled;
|
|
444
494
|
/** The item a row element drew, for an element that is one of the rows. */
|
|
445
495
|
private indexOf;
|
|
496
|
+
/** Whether a node is one of the rows: a child, or a child of a section. */
|
|
497
|
+
private isRow;
|
|
446
498
|
/** The row that holds the tab stop for item `index`, where it is drawn. */
|
|
447
499
|
private rowFor;
|
|
448
500
|
/**
|
|
@@ -541,7 +593,23 @@ export declare class HelixHeadlessVirtualList extends HelixElement {
|
|
|
541
593
|
* buttons are in the tab order, and those of every other row wait.
|
|
542
594
|
*/
|
|
543
595
|
private place;
|
|
544
|
-
/** The
|
|
596
|
+
/** The sections the rows were last placed in. */
|
|
597
|
+
private placedSections;
|
|
598
|
+
/** The function the drawn headers were drawn by, so a new one draws them again. */
|
|
599
|
+
private headedBy;
|
|
600
|
+
/** Ties each header's id to this list. */
|
|
601
|
+
private readonly sectionId;
|
|
602
|
+
/**
|
|
603
|
+
* The section elements a pass draws rows into, by section, for the
|
|
604
|
+
* sections of the wanted items: each a `group` named by its header, which
|
|
605
|
+
* is its first child. The ones no wanted item is in leave, and all of them
|
|
606
|
+
* do when the sections change.
|
|
607
|
+
*/
|
|
608
|
+
private groups;
|
|
609
|
+
/**
|
|
610
|
+
* The spacers' sizes: the window's, with the line kept for focus taking its
|
|
611
|
+
* own room, and so do the headers drawn with their sections from outside it.
|
|
612
|
+
*/
|
|
545
613
|
private spacerSizes;
|
|
546
614
|
protected render(): TemplateResult;
|
|
547
615
|
}
|
|
@@ -11,6 +11,13 @@ import { holdControls, releaseControls } from '../controls.js';
|
|
|
11
11
|
import { fillHoles, keyOf, textsOf } from '../fill.js';
|
|
12
12
|
import { skipDisabled, stepIndex } from '../keys.js';
|
|
13
13
|
import { drawPlan, itemsOf, stopAfter } from '../plan.js';
|
|
14
|
+
import { entryOf, headOf, itemsIn, sectionAt, sectionsOf } from '../sections.js';
|
|
15
|
+
let sectionCount = 0;
|
|
16
|
+
const nextSectionId = () => {
|
|
17
|
+
sectionCount += 1;
|
|
18
|
+
return `virtual-list-${sectionCount}`;
|
|
19
|
+
};
|
|
20
|
+
const ROWS = ':scope > [data-index], :scope > [data-section] > [data-index]';
|
|
14
21
|
let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixElement {
|
|
15
22
|
constructor() {
|
|
16
23
|
super(...arguments);
|
|
@@ -26,6 +33,8 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
26
33
|
this.loadingLabel = 'Loading more';
|
|
27
34
|
this.measured = false;
|
|
28
35
|
this.minColumnWidth = 0;
|
|
36
|
+
this.orientation = 'vertical';
|
|
37
|
+
this.sectionField = '';
|
|
29
38
|
this.selectionBehavior = 'toggle';
|
|
30
39
|
this.textField = '';
|
|
31
40
|
this.value = '';
|
|
@@ -41,9 +50,12 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
41
50
|
});
|
|
42
51
|
this.virtualize = new VirtualizeController(this, {
|
|
43
52
|
container: () => this.partNamed('viewport'),
|
|
44
|
-
count: () =>
|
|
53
|
+
count: () => this.sections
|
|
54
|
+
? this.data.length + this.sections.starts.length
|
|
55
|
+
: Math.ceil(this.data.length / this.across),
|
|
45
56
|
estimate: () => this.estimate,
|
|
46
57
|
gap: () => this.space,
|
|
58
|
+
horizontal: () => this.horizontal,
|
|
47
59
|
measured: () => this.measured,
|
|
48
60
|
rows: () => this.lines(),
|
|
49
61
|
});
|
|
@@ -94,13 +106,23 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
94
106
|
}
|
|
95
107
|
const count = this.data.length;
|
|
96
108
|
const { first, last } = this.virtualize.visible();
|
|
97
|
-
const
|
|
109
|
+
const forward = isRtl(this) ? 'ArrowLeft' : 'ArrowRight';
|
|
110
|
+
const key = !this.horizontal
|
|
111
|
+
? event.key
|
|
112
|
+
: event.key === forward
|
|
113
|
+
? 'ArrowDown'
|
|
114
|
+
: event.key === 'ArrowLeft' || event.key === 'ArrowRight'
|
|
115
|
+
? 'ArrowUp'
|
|
116
|
+
: event.key === 'ArrowDown' || event.key === 'ArrowUp'
|
|
117
|
+
? ''
|
|
118
|
+
: event.key;
|
|
119
|
+
const stepped = stepIndex(key, at, count, last - first - 1, {
|
|
98
120
|
across: this.across,
|
|
99
|
-
forward
|
|
121
|
+
forward,
|
|
100
122
|
});
|
|
101
123
|
const next = stepped === undefined
|
|
102
124
|
? undefined
|
|
103
|
-
: skipDisabled(
|
|
125
|
+
: skipDisabled(key, at, stepped, count, (one) => this.isDisabled(one), this.across);
|
|
104
126
|
if (next === undefined) {
|
|
105
127
|
if (stepped !== undefined) {
|
|
106
128
|
event.preventDefault();
|
|
@@ -122,7 +144,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
122
144
|
return;
|
|
123
145
|
}
|
|
124
146
|
const path = event.composedPath();
|
|
125
|
-
const row = path.find((node) => node
|
|
147
|
+
const row = path.find((node) => this.isRow(node));
|
|
126
148
|
const at = this.indexOf(row ?? null);
|
|
127
149
|
if (at === undefined) {
|
|
128
150
|
return;
|
|
@@ -143,7 +165,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
143
165
|
};
|
|
144
166
|
this.handleFocusIn = (event) => {
|
|
145
167
|
let node = event.target;
|
|
146
|
-
while (node && node
|
|
168
|
+
while (node && node !== this && !this.isRow(node)) {
|
|
147
169
|
node = node.parentNode;
|
|
148
170
|
}
|
|
149
171
|
const index = this.indexOf(node);
|
|
@@ -152,6 +174,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
152
174
|
}
|
|
153
175
|
this.requestUpdate();
|
|
154
176
|
};
|
|
177
|
+
this.sectionId = nextSectionId();
|
|
155
178
|
}
|
|
156
179
|
static { this.words = 'helix-virtual-list'; }
|
|
157
180
|
get range() {
|
|
@@ -186,7 +209,32 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
186
209
|
this.widthWatch.observe(viewport);
|
|
187
210
|
}
|
|
188
211
|
itemsOn(lines) {
|
|
189
|
-
|
|
212
|
+
const sections = this.sections;
|
|
213
|
+
return sections
|
|
214
|
+
? itemsIn(sections, lines, this.data.length)
|
|
215
|
+
: itemsOf(lines, this.across, this.data.length);
|
|
216
|
+
}
|
|
217
|
+
lineOf(at) {
|
|
218
|
+
const sections = this.sections;
|
|
219
|
+
return sections ? entryOf(sections, at) : Math.floor(at / this.across);
|
|
220
|
+
}
|
|
221
|
+
get horizontal() {
|
|
222
|
+
return this.orientation === 'horizontal' && this.across === 1;
|
|
223
|
+
}
|
|
224
|
+
get sections() {
|
|
225
|
+
const data = this.data;
|
|
226
|
+
if (this.sectionField === '' || data.length === 0 || this.across !== 1) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
const read = this.sectionsRead;
|
|
230
|
+
if (read?.data !== data || read.field !== this.sectionField) {
|
|
231
|
+
this.sectionsRead = {
|
|
232
|
+
data,
|
|
233
|
+
field: this.sectionField,
|
|
234
|
+
sections: sectionsOf(data, this.sectionField),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return this.sectionsRead?.sections;
|
|
190
238
|
}
|
|
191
239
|
scrollToIndex(index, options = {}) {
|
|
192
240
|
const count = this.data.length;
|
|
@@ -194,14 +242,14 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
194
242
|
return;
|
|
195
243
|
}
|
|
196
244
|
const at = Math.max(0, Math.min(Math.trunc(index), count - 1));
|
|
197
|
-
this.virtualize.reveal(
|
|
245
|
+
this.virtualize.reveal(this.lineOf(at), 0, options);
|
|
198
246
|
this.virtualize.measure();
|
|
199
247
|
}
|
|
200
248
|
get data() {
|
|
201
249
|
return this.items ?? this.rows;
|
|
202
250
|
}
|
|
203
251
|
drawn() {
|
|
204
|
-
return [...this.querySelectorAll(
|
|
252
|
+
return [...this.querySelectorAll(ROWS)].map((element) => ({
|
|
205
253
|
element,
|
|
206
254
|
index: Number(element.dataset.index),
|
|
207
255
|
}));
|
|
@@ -209,6 +257,13 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
209
257
|
lines() {
|
|
210
258
|
const across = this.across;
|
|
211
259
|
const drawn = this.drawn();
|
|
260
|
+
const sections = this.sections;
|
|
261
|
+
if (sections) {
|
|
262
|
+
return [
|
|
263
|
+
...drawn.map(({ element, index }) => ({ element, index: entryOf(sections, index) })),
|
|
264
|
+
...[...this.querySelectorAll(':scope > [data-section] > [data-entry]')].map((element) => ({ element, index: Number(element.dataset.entry) })),
|
|
265
|
+
];
|
|
266
|
+
}
|
|
212
267
|
return across === 1
|
|
213
268
|
? drawn
|
|
214
269
|
: drawn
|
|
@@ -226,6 +281,9 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
226
281
|
this.measuredAcross = across;
|
|
227
282
|
this.virtualize.forget();
|
|
228
283
|
}
|
|
284
|
+
if (this.hasUpdated && (changed.has('orientation') || changed.has('sectionField'))) {
|
|
285
|
+
this.virtualize.forget();
|
|
286
|
+
}
|
|
229
287
|
if (this.hasUpdated) {
|
|
230
288
|
if (changed.has('items')) {
|
|
231
289
|
this.keepPlace(changed.get('items'));
|
|
@@ -245,10 +303,13 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
245
303
|
if (!same(by, 0) || !same(now.length - 1, was.length - 1)) {
|
|
246
304
|
return;
|
|
247
305
|
}
|
|
248
|
-
if (
|
|
306
|
+
if (this.sections) {
|
|
307
|
+
this.virtualize.shift(this.lineOf(by) - 1);
|
|
308
|
+
}
|
|
309
|
+
else if (by % this.across === 0) {
|
|
249
310
|
this.virtualize.shift(by / this.across);
|
|
250
311
|
}
|
|
251
|
-
for (const row of this.querySelectorAll(
|
|
312
|
+
for (const row of this.querySelectorAll(ROWS)) {
|
|
252
313
|
row.dataset.index = String(Number(row.dataset.index) + by);
|
|
253
314
|
}
|
|
254
315
|
this.active += by;
|
|
@@ -324,6 +385,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
324
385
|
super.updated?.(changed);
|
|
325
386
|
this.toggleState('empty', this.readFor !== undefined && this.data.length === 0);
|
|
326
387
|
this.toggleState('loading', this.loading);
|
|
388
|
+
this.toggleState('horizontal', this.horizontal);
|
|
327
389
|
this.watchWidth();
|
|
328
390
|
const was = this.virtualize.window;
|
|
329
391
|
this.virtualize.measure();
|
|
@@ -376,14 +438,18 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
376
438
|
return this.disabledKeys !== '' && this.disabled.has(this.keyAt(at));
|
|
377
439
|
}
|
|
378
440
|
indexOf(node) {
|
|
379
|
-
if (!(node
|
|
441
|
+
if (!this.isRow(node)) {
|
|
380
442
|
return undefined;
|
|
381
443
|
}
|
|
382
444
|
const index = node.dataset.index;
|
|
383
445
|
return index === undefined ? undefined : Number(index);
|
|
384
446
|
}
|
|
447
|
+
isRow(node) {
|
|
448
|
+
const parent = node instanceof HTMLElement ? node.parentElement : null;
|
|
449
|
+
return (parent === this || (parent?.parentElement === this && parent.hasAttribute('data-section')));
|
|
450
|
+
}
|
|
385
451
|
rowFor(index) {
|
|
386
|
-
return this.
|
|
452
|
+
return ([...this.querySelectorAll(ROWS)].find((row) => row.dataset.index === String(index)) ?? null);
|
|
387
453
|
}
|
|
388
454
|
keyAt(at) {
|
|
389
455
|
return keyOf(this.data[at], at, this.keyField);
|
|
@@ -437,7 +503,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
437
503
|
async focusIndex(index) {
|
|
438
504
|
this.active = index;
|
|
439
505
|
this.focusing = index;
|
|
440
|
-
this.virtualize.reveal(
|
|
506
|
+
this.virtualize.reveal(this.lineOf(index));
|
|
441
507
|
this.virtualize.measure();
|
|
442
508
|
this.requestUpdate();
|
|
443
509
|
try {
|
|
@@ -451,7 +517,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
451
517
|
focusedIndex() {
|
|
452
518
|
const root = this.getRootNode();
|
|
453
519
|
let node = root.activeElement;
|
|
454
|
-
while (node && node
|
|
520
|
+
while (node && node !== this && !this.isRow(node)) {
|
|
455
521
|
node = node.parentElement;
|
|
456
522
|
}
|
|
457
523
|
const index = node instanceof HTMLElement ? node.dataset.index : undefined;
|
|
@@ -518,10 +584,20 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
518
584
|
const onScreen = this.range.first;
|
|
519
585
|
if (this.hasUpdated) {
|
|
520
586
|
const spacers = this.spacerSizes();
|
|
521
|
-
this.
|
|
522
|
-
this.partNamed('
|
|
523
|
-
|
|
524
|
-
|
|
587
|
+
const size = this.horizontal ? 'inline-size' : 'block-size';
|
|
588
|
+
this.partNamed('before')?.style.setProperty(size, `${spacers.before}px`);
|
|
589
|
+
this.partNamed('after')?.style.setProperty(size, `${spacers.after}px`);
|
|
590
|
+
}
|
|
591
|
+
const sections = this.sections;
|
|
592
|
+
const regroup = sections !== this.placedSections;
|
|
593
|
+
this.placedSections = sections;
|
|
594
|
+
const children = [
|
|
595
|
+
...[...this.children].filter((child) => child !== template &&
|
|
596
|
+
child instanceof HTMLElement &&
|
|
597
|
+
!child.hasAttribute('slot') &&
|
|
598
|
+
!child.hasAttribute('data-section')),
|
|
599
|
+
...this.querySelectorAll(':scope > [data-section] > [data-index]'),
|
|
600
|
+
];
|
|
525
601
|
const plan = drawPlan({
|
|
526
602
|
across: this.across,
|
|
527
603
|
count,
|
|
@@ -529,7 +605,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
529
605
|
first,
|
|
530
606
|
kept: this.keptIndex(),
|
|
531
607
|
last,
|
|
532
|
-
redraw: (this.filledBy === undefined) !== (this.renderItem === undefined),
|
|
608
|
+
redraw: regroup || (this.filledBy === undefined) !== (this.renderItem === undefined),
|
|
533
609
|
});
|
|
534
610
|
const drawn = new Map();
|
|
535
611
|
for (const child of children) {
|
|
@@ -541,26 +617,34 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
541
617
|
}
|
|
542
618
|
drawn.set(index, [...(drawn.get(index) ?? []), child]);
|
|
543
619
|
}
|
|
544
|
-
const refill = this.filledFrom !== this.data ||
|
|
620
|
+
const refill = this.filledFrom !== this.data ||
|
|
621
|
+
this.filledBy !== this.renderItem ||
|
|
622
|
+
this.headedBy !== this.renderSection;
|
|
545
623
|
this.filledFrom = this.data;
|
|
546
624
|
this.filledBy = this.renderItem;
|
|
625
|
+
this.headedBy = this.renderSection;
|
|
626
|
+
const groups = this.groups(sections, plan.wanted, regroup, refill);
|
|
547
627
|
const fresh = new Set();
|
|
548
|
-
|
|
628
|
+
const anchors = new Map();
|
|
549
629
|
for (const at of plan.wanted) {
|
|
630
|
+
const parent = (sections && groups.get(sectionAt(sections, at))) || this;
|
|
631
|
+
if (!anchors.has(parent)) {
|
|
632
|
+
anchors.set(parent, parent.querySelector(':scope > [data-index]'));
|
|
633
|
+
}
|
|
550
634
|
const group = drawn.get(at);
|
|
551
635
|
if (group) {
|
|
552
636
|
if (refill) {
|
|
553
637
|
this.fill(group, at);
|
|
554
638
|
fresh.add(at);
|
|
555
639
|
}
|
|
556
|
-
|
|
640
|
+
anchors.set(parent, group.at(-1).nextElementSibling);
|
|
557
641
|
continue;
|
|
558
642
|
}
|
|
559
643
|
const fragment = this.draw(template, at);
|
|
560
644
|
drawn.set(at, [...fragment.children]);
|
|
561
645
|
this.watchSize([...fragment.children]);
|
|
562
646
|
fresh.add(at);
|
|
563
|
-
|
|
647
|
+
parent.insertBefore(fragment, anchors.get(parent) ?? null);
|
|
564
648
|
}
|
|
565
649
|
this.active = stopAfter(this.active, plan.wanted, {
|
|
566
650
|
count,
|
|
@@ -594,13 +678,100 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
594
678
|
}, 0);
|
|
595
679
|
}
|
|
596
680
|
}
|
|
681
|
+
groups(sections, wanted, regroup, refill) {
|
|
682
|
+
const keep = new Set(sections ? wanted.map((at) => sectionAt(sections, at)) : []);
|
|
683
|
+
const groups = new Map();
|
|
684
|
+
for (const group of this.querySelectorAll(':scope > [data-section]')) {
|
|
685
|
+
const at = Number(group.dataset.section);
|
|
686
|
+
if (regroup || !keep.has(at)) {
|
|
687
|
+
this.sizes?.unobserve(group.firstElementChild);
|
|
688
|
+
group.remove();
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
groups.set(at, group);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (!sections) {
|
|
695
|
+
return groups;
|
|
696
|
+
}
|
|
697
|
+
for (const at of [...keep].sort((a, b) => a - b)) {
|
|
698
|
+
let group = groups.get(at);
|
|
699
|
+
const fresh = group === undefined;
|
|
700
|
+
if (!group) {
|
|
701
|
+
group = document.createElement('div');
|
|
702
|
+
const header = document.createElement('div');
|
|
703
|
+
header.id = `${this.sectionId}-${at}`;
|
|
704
|
+
header.dataset.entry = String(headOf(sections, at));
|
|
705
|
+
header.setAttribute('data-header', '');
|
|
706
|
+
group.dataset.section = String(at);
|
|
707
|
+
group.setAttribute('role', 'group');
|
|
708
|
+
group.setAttribute('aria-labelledby', header.id);
|
|
709
|
+
group.append(header);
|
|
710
|
+
const after = [...groups].find(([other]) => other > at)?.[1] ?? null;
|
|
711
|
+
this.insertBefore(group, after);
|
|
712
|
+
groups.set(at, group);
|
|
713
|
+
this.watchSize([header]);
|
|
714
|
+
}
|
|
715
|
+
const header = group.firstElementChild;
|
|
716
|
+
if (fresh || refill) {
|
|
717
|
+
const name = sections.names[at] ?? '';
|
|
718
|
+
if (this.renderSection) {
|
|
719
|
+
render(this.renderSection(name, at), header);
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
header.textContent = name;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (this.selectionMode) {
|
|
726
|
+
header.setAttribute('role', 'presentation');
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
header.removeAttribute('role');
|
|
730
|
+
}
|
|
731
|
+
const gap = this.space;
|
|
732
|
+
group.style.cssText = this.horizontal
|
|
733
|
+
? `display: flex; column-gap: ${gap}px`
|
|
734
|
+
: gap === 0
|
|
735
|
+
? ''
|
|
736
|
+
: `display: flex; flex-direction: column; row-gap: ${gap}px`;
|
|
737
|
+
}
|
|
738
|
+
return groups;
|
|
739
|
+
}
|
|
597
740
|
spacerSizes() {
|
|
598
741
|
const kept = this.hasUpdated ? this.keptIndex() : undefined;
|
|
599
|
-
|
|
742
|
+
const room = this.virtualize.spacers(kept === undefined ? undefined : this.lineOf(kept));
|
|
743
|
+
const sections = this.sections;
|
|
744
|
+
if (!sections || !this.hasUpdated) {
|
|
745
|
+
return room;
|
|
746
|
+
}
|
|
747
|
+
const { first, last } = this.virtualize.window;
|
|
748
|
+
const items = this.itemsOn(this.virtualize.window);
|
|
749
|
+
const drawnIn = new Set();
|
|
750
|
+
if (items.last > items.first) {
|
|
751
|
+
drawnIn.add(sectionAt(sections, items.first));
|
|
752
|
+
}
|
|
753
|
+
if (kept !== undefined) {
|
|
754
|
+
drawnIn.add(sectionAt(sections, kept));
|
|
755
|
+
}
|
|
756
|
+
let { after, before } = room;
|
|
757
|
+
for (const section of drawnIn) {
|
|
758
|
+
const head = headOf(sections, section);
|
|
759
|
+
if (head < first) {
|
|
760
|
+
before = Math.max(0, before - this.virtualize.sizeOf(head));
|
|
761
|
+
}
|
|
762
|
+
else if (head >= last) {
|
|
763
|
+
after = Math.max(0, after - this.virtualize.sizeOf(head));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return { after, before };
|
|
600
767
|
}
|
|
601
768
|
render() {
|
|
602
769
|
const { after, before } = this.spacerSizes();
|
|
603
770
|
const across = this.across;
|
|
771
|
+
const horizontal = this.horizontal;
|
|
772
|
+
const size = horizontal ? 'inline-size' : 'block-size';
|
|
773
|
+
const flex = horizontal ? '; flex: none' : '';
|
|
774
|
+
const gap = this.space;
|
|
604
775
|
const empty = this.readFor !== undefined &&
|
|
605
776
|
!this.loading &&
|
|
606
777
|
this.data.length === 0 &&
|
|
@@ -612,20 +783,24 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
|
|
|
612
783
|
aria-multiselectable=${this.selectionMode === 'multiple' ? 'true' : nothing}
|
|
613
784
|
aria-busy=${this.loading ? 'true' : nothing}
|
|
614
785
|
aria-label=${this.label}
|
|
786
|
+
aria-orientation=${this.selectionMode && horizontal ? 'horizontal' : nothing}
|
|
787
|
+
style=${horizontal ? 'display: flex' : nothing}
|
|
615
788
|
@scroll=${() => {
|
|
616
789
|
this.virtualize.measure();
|
|
617
790
|
this.report();
|
|
618
791
|
}}
|
|
619
792
|
>
|
|
620
|
-
<div part="before" style="
|
|
793
|
+
<div part="before" style="${size}: ${before}px${flex}"></div>
|
|
621
794
|
<slot
|
|
622
|
-
style=${
|
|
623
|
-
?
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
795
|
+
style=${horizontal
|
|
796
|
+
? `display: flex; flex: none${gap === 0 ? '' : `; column-gap: ${gap}px; padding-inline-end: ${gap}px`}`
|
|
797
|
+
: across === 1
|
|
798
|
+
? gap === 0
|
|
799
|
+
? nothing
|
|
800
|
+
: `display: flex; flex-direction: column; row-gap: ${gap}px; padding-block-end: ${gap}px`
|
|
801
|
+
: `display: grid; grid-template-columns: repeat(${across}, minmax(0, 1fr))${gap === 0 ? '' : `; gap: ${gap}px; padding-block-end: ${gap}px`}`}
|
|
627
802
|
></slot>
|
|
628
|
-
<div part="after" style="
|
|
803
|
+
<div part="after" style="${size}: ${after}px${flex}"></div>
|
|
629
804
|
${this.loading
|
|
630
805
|
? html `<div part="loading" role=${this.selectionMode ? 'option' : 'listitem'}>
|
|
631
806
|
<slot name="loading">${this.loadingLabel}</slot>
|
|
@@ -679,9 +854,18 @@ __decorate([
|
|
|
679
854
|
__decorate([
|
|
680
855
|
property({ attribute: 'min-column-width', type: Number })
|
|
681
856
|
], HelixHeadlessVirtualList.prototype, "minColumnWidth", void 0);
|
|
857
|
+
__decorate([
|
|
858
|
+
property()
|
|
859
|
+
], HelixHeadlessVirtualList.prototype, "orientation", void 0);
|
|
682
860
|
__decorate([
|
|
683
861
|
property({ attribute: false })
|
|
684
862
|
], HelixHeadlessVirtualList.prototype, "renderItem", void 0);
|
|
863
|
+
__decorate([
|
|
864
|
+
property({ attribute: false })
|
|
865
|
+
], HelixHeadlessVirtualList.prototype, "renderSection", void 0);
|
|
866
|
+
__decorate([
|
|
867
|
+
property({ attribute: 'section-field' })
|
|
868
|
+
], HelixHeadlessVirtualList.prototype, "sectionField", void 0);
|
|
685
869
|
__decorate([
|
|
686
870
|
property({ attribute: 'selection-behavior' })
|
|
687
871
|
], HelixHeadlessVirtualList.prototype, "selectionBehavior", void 0);
|