@fusion.dev/helix-headless 0.160.0 → 0.161.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.
Files changed (39) hide show
  1. package/custom-elements.json +1729 -50
  2. package/dist/canvas/root/canvas-root.d.ts +59 -2
  3. package/dist/canvas/root/canvas-root.js +123 -8
  4. package/dist/chat/message/chat-message.d.ts +8 -0
  5. package/dist/chat/message/chat-message.js +11 -0
  6. package/dist/chat/root/chat-root.d.ts +47 -1
  7. package/dist/chat/root/chat-root.js +130 -4
  8. package/dist/code-block/lines.d.ts +12 -0
  9. package/dist/code-block/lines.js +41 -0
  10. package/dist/code-block/root/code-block-root.d.ts +88 -5
  11. package/dist/code-block/root/code-block-root.js +154 -10
  12. package/dist/element-events.generated.js +4 -0
  13. package/dist/splitter/root/splitter-root.d.ts +15 -0
  14. package/dist/splitter/root/splitter-root.js +16 -2
  15. package/dist/table/grid-controller.d.ts +7 -1
  16. package/dist/table/grid-controller.js +9 -1
  17. package/dist/table/root/table-root.d.ts +122 -0
  18. package/dist/table/root/table-root.js +293 -17
  19. package/dist/table/row/table-row.d.ts +41 -0
  20. package/dist/table/row/table-row.js +56 -2
  21. package/dist/timeline/item/timeline-item.d.ts +47 -10
  22. package/dist/timeline/item/timeline-item.js +36 -4
  23. package/dist/video-playlist/item/video-playlist-item.d.ts +11 -0
  24. package/dist/video-playlist/item/video-playlist-item.js +5 -0
  25. package/dist/video-playlist/menu/video-playlist-menu.d.ts +6 -0
  26. package/dist/video-playlist/menu/video-playlist-menu.js +11 -0
  27. package/dist/video-playlist/root/video-playlist-root.d.ts +59 -5
  28. package/dist/video-playlist/root/video-playlist-root.js +82 -10
  29. package/dist/virtual-list/fill.d.ts +6 -1
  30. package/dist/virtual-list/fill.js +1 -1
  31. package/dist/virtual-list/keys.d.ts +8 -0
  32. package/dist/virtual-list/keys.js +20 -0
  33. package/dist/virtual-list/root/virtual-list-root.d.ts +98 -13
  34. package/dist/virtual-list/root/virtual-list-root.js +195 -20
  35. package/lint.json +1 -1
  36. package/package.json +5 -5
  37. package/types/jsx.d.ts +936 -68
  38. package/types/vue.d.ts +226 -12
  39. package/types/words.d.ts +79 -0
@@ -9,20 +9,24 @@ import { html, nothing, render } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { holdControls, releaseControls } from '../controls.js';
11
11
  import { fillHoles, keyOf, textsOf } from '../fill.js';
12
- import { stepIndex } from '../keys.js';
12
+ import { skipDisabled, stepIndex } from '../keys.js';
13
13
  import { drawPlan, itemsOf, stopAfter } from '../plan.js';
14
14
  let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixElement {
15
15
  constructor() {
16
16
  super(...arguments);
17
17
  this.columns = 1;
18
+ this.disabledKeys = '';
18
19
  this.emptyLabel = '';
19
20
  this.estimate = 32;
20
21
  this.for = '';
22
+ this.gap = 0;
21
23
  this.keyField = '';
22
24
  this.label = '';
23
25
  this.loading = false;
24
26
  this.loadingLabel = 'Loading more';
25
27
  this.measured = false;
28
+ this.minColumnWidth = 0;
29
+ this.selectionBehavior = 'toggle';
26
30
  this.textField = '';
27
31
  this.value = '';
28
32
  this.rows = [];
@@ -39,9 +43,12 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
39
43
  container: () => this.partNamed('viewport'),
40
44
  count: () => Math.ceil(this.data.length / this.across),
41
45
  estimate: () => this.estimate,
46
+ gap: () => this.space,
42
47
  measured: () => this.measured,
43
48
  rows: () => this.lines(),
44
49
  });
50
+ this.boxWidth = 0;
51
+ this.measuredAcross = 1;
45
52
  this.handleKeydown = (event) => {
46
53
  if (event.defaultPrevented) {
47
54
  return;
@@ -59,28 +66,45 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
59
66
  return;
60
67
  }
61
68
  const toggle = event.ctrlKey || event.metaKey;
69
+ const replacing = this.selectionMode !== undefined && this.selectionBehavior === 'replace';
62
70
  if (this.selectionMode && !event.altKey) {
63
71
  if (selectsAll(event) && this.selectionMode === 'multiple') {
64
72
  event.preventDefault();
65
- this.choose(this.keys().join(','));
73
+ const off = this.disabled;
74
+ this.choose(this.keys()
75
+ .filter((key) => !off.has(key))
76
+ .join(','));
66
77
  return;
67
78
  }
68
- if (event.key === ' ' && !toggle) {
79
+ if (event.key === ' ' && (!toggle || replacing)) {
69
80
  event.preventDefault();
70
- this.press(at, event.shiftKey);
81
+ this.press(at, event.shiftKey, toggle);
82
+ return;
83
+ }
84
+ if (event.key === 'Escape' && this.value !== '' && !toggle && !event.shiftKey) {
85
+ event.preventDefault();
86
+ event.stopPropagation();
87
+ this.choose('');
71
88
  return;
72
89
  }
73
90
  }
74
91
  const extend = event.shiftKey && this.selectionMode === 'multiple';
75
- if (event.altKey || toggle || (event.shiftKey && !extend)) {
92
+ if (event.altKey || (toggle && !replacing) || (event.shiftKey && !extend)) {
76
93
  return;
77
94
  }
95
+ const count = this.data.length;
78
96
  const { first, last } = this.virtualize.visible();
79
- const next = stepIndex(event.key, at, this.data.length, last - first - 1, {
97
+ const stepped = stepIndex(event.key, at, count, last - first - 1, {
80
98
  across: this.across,
81
99
  forward: isRtl(this) ? 'ArrowLeft' : 'ArrowRight',
82
100
  });
101
+ const next = stepped === undefined
102
+ ? undefined
103
+ : skipDisabled(event.key, at, stepped, count, (one) => this.isDisabled(one), this.across);
83
104
  if (next === undefined) {
105
+ if (stepped !== undefined) {
106
+ event.preventDefault();
107
+ }
84
108
  return;
85
109
  }
86
110
  event.preventDefault();
@@ -89,6 +113,9 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
89
113
  this.anchor ??= this.keyAt(at);
90
114
  this.press(next, true);
91
115
  }
116
+ else if (replacing && !toggle) {
117
+ this.press(next);
118
+ }
92
119
  };
93
120
  this.handleClick = (event) => {
94
121
  if (!this.selectionMode) {
@@ -103,7 +130,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
103
130
  if (pressedOwnControl(event, row, 'a[href], button, input, select, textarea, label')) {
104
131
  return;
105
132
  }
106
- this.press(at, event.shiftKey && this.selectionMode === 'multiple');
133
+ this.press(at, event.shiftKey && this.selectionMode === 'multiple', event.ctrlKey || event.metaKey);
107
134
  };
108
135
  this.handleFocusOut = (event) => {
109
136
  const next = event.relatedTarget;
@@ -130,9 +157,34 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
130
157
  get range() {
131
158
  return this.itemsOn(this.virtualize.visible());
132
159
  }
160
+ get space() {
161
+ return Number.isFinite(this.gap) && this.gap > 0 ? this.gap : 0;
162
+ }
133
163
  get across() {
164
+ if (this.minColumnWidth > 0 && this.boxWidth > 0) {
165
+ const space = this.space;
166
+ return Math.max(1, Math.floor((this.boxWidth + space) / (this.minColumnWidth + space)));
167
+ }
134
168
  return Math.max(1, Math.trunc(this.columns) || 1);
135
169
  }
170
+ watchWidth() {
171
+ const viewport = this.partNamed('viewport');
172
+ if (this.minColumnWidth <= 0 || !viewport || typeof ResizeObserver === 'undefined') {
173
+ this.widthWatch?.disconnect();
174
+ this.widthWatch = undefined;
175
+ return;
176
+ }
177
+ if (this.widthWatch) {
178
+ return;
179
+ }
180
+ this.widthWatch = new ResizeObserver(([entry]) => {
181
+ const width = entry?.contentRect.width ?? 0;
182
+ if (width > 0 && Math.abs(width - this.boxWidth) >= 1) {
183
+ this.boxWidth = width;
184
+ }
185
+ });
186
+ this.widthWatch.observe(viewport);
187
+ }
136
188
  itemsOn(lines) {
137
189
  return itemsOf(lines, this.across, this.data.length);
138
190
  }
@@ -169,6 +221,40 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
169
221
  this.readFor = this.for;
170
222
  this.read();
171
223
  }
224
+ const across = this.across;
225
+ if (across !== this.measuredAcross) {
226
+ this.measuredAcross = across;
227
+ this.virtualize.forget();
228
+ }
229
+ if (this.hasUpdated) {
230
+ if (changed.has('items')) {
231
+ this.keepPlace(changed.get('items'));
232
+ }
233
+ this.place();
234
+ }
235
+ }
236
+ keepPlace(was) {
237
+ const now = this.data;
238
+ if (!was || was.length === 0 || now.length <= was.length) {
239
+ return;
240
+ }
241
+ const by = now.length - was.length;
242
+ const same = (at, then) => now[at] === was[then] ||
243
+ (this.keyField !== '' &&
244
+ keyOf(now[at], at, this.keyField) === keyOf(was[then], then, this.keyField));
245
+ if (!same(by, 0) || !same(now.length - 1, was.length - 1)) {
246
+ return;
247
+ }
248
+ if (by % this.across === 0) {
249
+ this.virtualize.shift(by / this.across);
250
+ }
251
+ for (const row of this.querySelectorAll(':scope > [data-index]')) {
252
+ row.dataset.index = String(Number(row.dataset.index) + by);
253
+ }
254
+ this.active += by;
255
+ if (this.released !== undefined) {
256
+ this.released += by;
257
+ }
172
258
  }
173
259
  connectedCallback() {
174
260
  super.connectedCallback();
@@ -194,6 +280,31 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
194
280
  this.removeEventListener('focusout', this.handleFocusOut);
195
281
  this.watcher?.disconnect();
196
282
  this.watcher = undefined;
283
+ this.sizes?.disconnect();
284
+ this.sizes = undefined;
285
+ this.widthWatch?.disconnect();
286
+ this.widthWatch = undefined;
287
+ if (this.resized !== undefined) {
288
+ cancelAnimationFrame(this.resized);
289
+ this.resized = undefined;
290
+ }
291
+ }
292
+ watchSize(elements) {
293
+ if (typeof ResizeObserver === 'undefined') {
294
+ return;
295
+ }
296
+ this.sizes ??= new ResizeObserver(() => {
297
+ if (this.resized !== undefined) {
298
+ return;
299
+ }
300
+ this.resized = requestAnimationFrame(() => {
301
+ this.resized = undefined;
302
+ this.virtualize.settle();
303
+ });
304
+ });
305
+ for (const element of elements) {
306
+ this.sizes.observe(element);
307
+ }
197
308
  }
198
309
  handleMutations(records) {
199
310
  if (this.for === '' || this.hydrating || this.readFor === undefined) {
@@ -213,8 +324,12 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
213
324
  super.updated?.(changed);
214
325
  this.toggleState('empty', this.readFor !== undefined && this.data.length === 0);
215
326
  this.toggleState('loading', this.loading);
327
+ this.watchWidth();
328
+ const was = this.virtualize.window;
216
329
  this.virtualize.measure();
217
- this.place();
330
+ if (this.virtualize.window === was) {
331
+ this.place();
332
+ }
218
333
  this.virtualize.settle();
219
334
  this.report();
220
335
  }
@@ -250,7 +365,15 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
250
365
  ?.content.querySelector('[data-field]')
251
366
  ?.getAttribute('data-field') ||
252
367
  '';
253
- return textsOf(this.data, field);
368
+ const off = this.disabled;
369
+ const texts = textsOf(this.data, field);
370
+ return off.size === 0 ? texts : texts.map((text, at) => (off.has(this.keyAt(at)) ? '' : text));
371
+ }
372
+ get disabled() {
373
+ return new Set(commaList(this.disabledKeys));
374
+ }
375
+ isDisabled(at) {
376
+ return this.disabledKeys !== '' && this.disabled.has(this.keyAt(at));
254
377
  }
255
378
  indexOf(node) {
256
379
  if (!(node instanceof HTMLElement) || node.parentElement !== this) {
@@ -271,22 +394,29 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
271
394
  get chosen() {
272
395
  return new Set(commaList(this.value));
273
396
  }
274
- press(at, extend = false) {
397
+ press(at, extend = false, toggle = false) {
275
398
  const mode = this.selectionMode;
276
- const key = this.keyAt(at);
277
- if (!mode || at < 0 || at >= this.data.length) {
399
+ if (!mode || at < 0 || at >= this.data.length || this.isDisabled(at)) {
278
400
  return;
279
401
  }
402
+ const key = this.keyAt(at);
280
403
  const now = commaList(this.value);
281
404
  if (extend && mode === 'multiple') {
282
405
  const keys = this.keys();
283
- const range = rangeAdd(now, keys, anchorAt(keys, this.anchor, at).anchor, at);
406
+ const off = this.disabled;
407
+ const range = rangeAdd(now, keys.map((one) => (off.has(one) ? '' : one)), anchorAt(keys, this.anchor, at).anchor, at)?.filter((one) => one !== '');
284
408
  if (range) {
285
409
  this.choose(this.inOrder(range));
286
410
  }
287
411
  return;
288
412
  }
289
413
  this.anchor = key;
414
+ if (this.selectionBehavior === 'replace' && !toggle) {
415
+ if (!(now.length === 1 && now[0] === key)) {
416
+ this.choose(key);
417
+ }
418
+ return;
419
+ }
290
420
  if (mode === 'single') {
291
421
  this.choose(now.length === 1 && now[0] === key ? '' : key);
292
422
  return;
@@ -334,7 +464,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
334
464
  ? index
335
465
  : undefined;
336
466
  }
337
- mark(element, at, chosen) {
467
+ mark(element, at, chosen, off) {
338
468
  element.setAttribute('role', this.selectionMode ? 'option' : 'listitem');
339
469
  if (this.selectionMode) {
340
470
  element.setAttribute('aria-selected', String(chosen.has(this.keyAt(at))));
@@ -342,6 +472,12 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
342
472
  else {
343
473
  element.removeAttribute('aria-selected');
344
474
  }
475
+ if (off.size > 0 && off.has(this.keyAt(at))) {
476
+ element.setAttribute('aria-disabled', 'true');
477
+ }
478
+ else {
479
+ element.removeAttribute('aria-disabled');
480
+ }
345
481
  }
346
482
  stamp(element, at) {
347
483
  element.setAttribute('aria-posinset', String(at + 1));
@@ -378,6 +514,13 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
378
514
  }
379
515
  const count = this.data.length;
380
516
  const { first, last } = this.itemsOn(this.virtualize.window);
517
+ const holding = this.focusedIndex();
518
+ const onScreen = this.range.first;
519
+ if (this.hasUpdated) {
520
+ const spacers = this.spacerSizes();
521
+ this.partNamed('before')?.style.setProperty('block-size', `${spacers.before}px`);
522
+ this.partNamed('after')?.style.setProperty('block-size', `${spacers.after}px`);
523
+ }
381
524
  const children = [...this.children].filter((child) => child !== template && child instanceof HTMLElement && !child.hasAttribute('slot'));
382
525
  const plan = drawPlan({
383
526
  across: this.across,
@@ -392,6 +535,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
392
535
  for (const child of children) {
393
536
  const index = Number(child.dataset.index);
394
537
  if (plan.remove.has(index)) {
538
+ this.sizes?.unobserve(child);
395
539
  child.remove();
396
540
  continue;
397
541
  }
@@ -414,6 +558,7 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
414
558
  }
415
559
  const fragment = this.draw(template, at);
416
560
  drawn.set(at, [...fragment.children]);
561
+ this.watchSize([...fragment.children]);
417
562
  fresh.add(at);
418
563
  this.insertBefore(fragment, anchor);
419
564
  }
@@ -421,12 +566,13 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
421
566
  count,
422
567
  first,
423
568
  last,
424
- onScreen: this.range.first,
569
+ onScreen,
425
570
  });
426
571
  const chosen = this.chosen;
572
+ const off = this.disabled;
427
573
  for (const [at, group] of drawn) {
428
574
  for (const element of group) {
429
- this.mark(element, at, chosen);
575
+ this.mark(element, at, chosen, off);
430
576
  }
431
577
  const stop = at === this.active;
432
578
  group[0]?.setAttribute('tabindex', stop ? '0' : '-1');
@@ -438,10 +584,22 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
438
584
  }
439
585
  }
440
586
  this.released = this.active;
587
+ if (holding !== undefined && plan.remove.has(holding) && count > 0) {
588
+ const next = Math.min(holding, count - 1);
589
+ this.active = next;
590
+ setTimeout(() => {
591
+ if (this.focusedIndex() === undefined && this.isConnected) {
592
+ void this.focusIndex(next);
593
+ }
594
+ }, 0);
595
+ }
441
596
  }
442
- render() {
597
+ spacerSizes() {
443
598
  const kept = this.hasUpdated ? this.keptIndex() : undefined;
444
- const { after, before } = this.virtualize.spacers(kept === undefined ? undefined : Math.floor(kept / this.across));
599
+ return this.virtualize.spacers(kept === undefined ? undefined : Math.floor(kept / this.across));
600
+ }
601
+ render() {
602
+ const { after, before } = this.spacerSizes();
445
603
  const across = this.across;
446
604
  const empty = this.readFor !== undefined &&
447
605
  !this.loading &&
@@ -462,8 +620,10 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
462
620
  <div part="before" style="block-size: ${before}px"></div>
463
621
  <slot
464
622
  style=${across === 1
465
- ? nothing
466
- : `display: grid; grid-template-columns: repeat(${across}, minmax(0, 1fr))`}
623
+ ? this.space === 0
624
+ ? nothing
625
+ : `display: flex; flex-direction: column; row-gap: ${this.space}px; padding-block-end: ${this.space}px`
626
+ : `display: grid; grid-template-columns: repeat(${across}, minmax(0, 1fr))${this.space === 0 ? '' : `; gap: ${this.space}px; padding-block-end: ${this.space}px`}`}
467
627
  ></slot>
468
628
  <div part="after" style="block-size: ${after}px"></div>
469
629
  ${this.loading
@@ -482,6 +642,9 @@ let HelixHeadlessVirtualList = class HelixHeadlessVirtualList extends HelixEleme
482
642
  __decorate([
483
643
  property({ type: Number })
484
644
  ], HelixHeadlessVirtualList.prototype, "columns", void 0);
645
+ __decorate([
646
+ property({ attribute: 'disabled-keys' })
647
+ ], HelixHeadlessVirtualList.prototype, "disabledKeys", void 0);
485
648
  __decorate([
486
649
  property({ attribute: 'empty-label' })
487
650
  ], HelixHeadlessVirtualList.prototype, "emptyLabel", void 0);
@@ -491,6 +654,9 @@ __decorate([
491
654
  __decorate([
492
655
  property()
493
656
  ], HelixHeadlessVirtualList.prototype, "for", void 0);
657
+ __decorate([
658
+ property({ type: Number })
659
+ ], HelixHeadlessVirtualList.prototype, "gap", void 0);
494
660
  __decorate([
495
661
  property({ attribute: false })
496
662
  ], HelixHeadlessVirtualList.prototype, "items", void 0);
@@ -510,9 +676,15 @@ __decorate([
510
676
  __decorate([
511
677
  property({ type: Boolean })
512
678
  ], HelixHeadlessVirtualList.prototype, "measured", void 0);
679
+ __decorate([
680
+ property({ attribute: 'min-column-width', type: Number })
681
+ ], HelixHeadlessVirtualList.prototype, "minColumnWidth", void 0);
513
682
  __decorate([
514
683
  property({ attribute: false })
515
684
  ], HelixHeadlessVirtualList.prototype, "renderItem", void 0);
685
+ __decorate([
686
+ property({ attribute: 'selection-behavior' })
687
+ ], HelixHeadlessVirtualList.prototype, "selectionBehavior", void 0);
516
688
  __decorate([
517
689
  property({ attribute: 'selection-mode' })
518
690
  ], HelixHeadlessVirtualList.prototype, "selectionMode", void 0);
@@ -525,6 +697,9 @@ __decorate([
525
697
  __decorate([
526
698
  state()
527
699
  ], HelixHeadlessVirtualList.prototype, "rows", void 0);
700
+ __decorate([
701
+ state()
702
+ ], HelixHeadlessVirtualList.prototype, "boxWidth", void 0);
528
703
  HelixHeadlessVirtualList = __decorate([
529
704
  customElement('helix-headless-virtual-list')
530
705
  ], HelixHeadlessVirtualList);