@fusion.dev/helix-headless 0.166.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 +2658 -481
- 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/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 +2 -0
- 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/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 +13 -1
- package/dist/select/root/select-root.js +1 -1
- 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/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 +454 -76
- package/types/vue.d.ts +89 -7
- package/types/words.d.ts +27 -0
|
@@ -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);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Where each section starts in the items, and what it is called. */
|
|
2
|
+
export interface Sections {
|
|
3
|
+
/** The section names, in order. */
|
|
4
|
+
names: string[];
|
|
5
|
+
/** The index of each section's first item, in order. */
|
|
6
|
+
starts: number[];
|
|
7
|
+
}
|
|
8
|
+
/** The sections `field` divides the items into. */
|
|
9
|
+
export declare const sectionsOf: (items: readonly unknown[], field: string) => Sections;
|
|
10
|
+
/** The section item `item` is in. */
|
|
11
|
+
export declare const sectionAt: ({ starts }: Sections, item: number) => number;
|
|
12
|
+
/** The entry item `item` is. */
|
|
13
|
+
export declare const entryOf: (sections: Sections, item: number) => number;
|
|
14
|
+
/** The entry of section `section`'s header. */
|
|
15
|
+
export declare const headOf: ({ starts }: Sections, section: number) => number;
|
|
16
|
+
/**
|
|
17
|
+
* The items a span of entries holds: what a window of entries draws, or what
|
|
18
|
+
* a reader sees. A header at either end holds no item, so the span starts at
|
|
19
|
+
* its section's first item.
|
|
20
|
+
*/
|
|
21
|
+
export declare const itemsIn: (sections: Sections, entries: {
|
|
22
|
+
first: number;
|
|
23
|
+
last: number;
|
|
24
|
+
}, count: number) => {
|
|
25
|
+
first: number;
|
|
26
|
+
last: number;
|
|
27
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { valueAt } from '@fusion.dev/helix-core';
|
|
2
|
+
import { textOf } from './fill.js';
|
|
3
|
+
export const sectionsOf = (items, field) => {
|
|
4
|
+
const names = [];
|
|
5
|
+
const starts = [];
|
|
6
|
+
let last;
|
|
7
|
+
for (const [at, item] of items.entries()) {
|
|
8
|
+
const name = textOf(valueAt(item, field));
|
|
9
|
+
if (at === 0 || name !== last) {
|
|
10
|
+
names.push(name);
|
|
11
|
+
starts.push(at);
|
|
12
|
+
last = name;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return { names, starts };
|
|
16
|
+
};
|
|
17
|
+
const lastUpTo = (length, at, target) => {
|
|
18
|
+
let low = 0;
|
|
19
|
+
let high = length - 1;
|
|
20
|
+
while (low < high) {
|
|
21
|
+
const middle = Math.ceil((low + high) / 2);
|
|
22
|
+
if (at(middle) <= target) {
|
|
23
|
+
low = middle;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
high = middle - 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return low;
|
|
30
|
+
};
|
|
31
|
+
export const sectionAt = ({ starts }, item) => lastUpTo(starts.length, (index) => starts[index] ?? 0, item);
|
|
32
|
+
export const entryOf = (sections, item) => item + sectionAt(sections, item) + 1;
|
|
33
|
+
export const headOf = ({ starts }, section) => (starts[section] ?? 0) + section;
|
|
34
|
+
export const itemsIn = (sections, entries, count) => {
|
|
35
|
+
const item = (entry) => {
|
|
36
|
+
if (entry >= count + sections.starts.length) {
|
|
37
|
+
return count;
|
|
38
|
+
}
|
|
39
|
+
const section = lastUpTo(sections.starts.length, (index) => headOf(sections, index), entry);
|
|
40
|
+
return Math.max(sections.starts[section] ?? 0, entry - section - 1);
|
|
41
|
+
};
|
|
42
|
+
return { first: item(entries.first), last: Math.max(item(entries.first), item(entries.last)) };
|
|
43
|
+
};
|