@gridkitjs/dom 0.3.3 → 0.5.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/dist/index.js CHANGED
@@ -69,13 +69,15 @@ function bodyRowFrom(target) {
69
69
  // src/checklist/attachChecklist.ts
70
70
  var ITEM = "[data-gridkit-checklist-item]";
71
71
  var INDEX = "data-gridkit-checklist-index";
72
+ var SETTLED = { kind: "settled" };
73
+ var DETACHED = { kind: "detached" };
72
74
  function indexOf(item) {
73
75
  return Number(item.getAttribute(INDEX));
74
76
  }
75
77
  function attachChecklist(list, options) {
76
78
  let count = 0;
77
79
  let active = 0;
78
- let pending = null;
80
+ let state = SETTLED;
79
81
  function items() {
80
82
  return Array.from(list.querySelectorAll(ITEM));
81
83
  }
@@ -102,7 +104,7 @@ function attachChecklist(list, options) {
102
104
  }
103
105
  }
104
106
  function moveTo(index) {
105
- if (count === 0) {
107
+ if (state.kind === "detached" || count === 0) {
106
108
  return;
107
109
  }
108
110
  const target = Math.max(0, Math.min(index, count - 1));
@@ -111,9 +113,9 @@ function attachChecklist(list, options) {
111
113
  options.scrollToIndex(target);
112
114
  const item = itemAt(target);
113
115
  if (item === null) {
114
- pending = target;
116
+ state = { kind: "awaiting-item", index: target };
115
117
  } else {
116
- pending = null;
118
+ state = SETTLED;
117
119
  item.focus({ preventScroll: true });
118
120
  }
119
121
  }
@@ -163,7 +165,7 @@ function attachChecklist(list, options) {
163
165
  function onFocusIn(event) {
164
166
  const item = itemOf(event.target);
165
167
  if (item !== null) {
166
- pending = null;
168
+ state = SETTLED;
167
169
  makeActive(indexOf(item));
168
170
  setTabStop();
169
171
  }
@@ -175,17 +177,20 @@ function attachChecklist(list, options) {
175
177
  item.focus({ preventScroll: true });
176
178
  }
177
179
  }
178
- const observer = new MutationObserver(() => {
179
- setTabStop();
180
- const item = pending === null ? null : itemAt(pending);
180
+ function focusAwaitedItemIfMounted() {
181
+ const item = state.kind === "awaiting-item" ? itemAt(state.index) : null;
181
182
  if (item === null) {
182
183
  return;
183
184
  }
184
- pending = null;
185
+ state = SETTLED;
185
186
  const focused = document.activeElement;
186
187
  if (focused === null || focused === document.body || list.contains(focused)) {
187
188
  item.focus({ preventScroll: true });
188
189
  }
190
+ }
191
+ const observer = new MutationObserver(() => {
192
+ setTabStop();
193
+ focusAwaitedItemIfMounted();
189
194
  });
190
195
  observer.observe(list, {
191
196
  childList: true,
@@ -198,13 +203,23 @@ function attachChecklist(list, options) {
198
203
  list.addEventListener("click", onClick);
199
204
  return {
200
205
  setCount(next) {
206
+ if (state.kind === "detached") {
207
+ return;
208
+ }
201
209
  count = next;
202
210
  active = Math.max(0, Math.min(active, count - 1));
211
+ if (state.kind === "awaiting-item") {
212
+ state = count === 0 ? SETTLED : {
213
+ kind: "awaiting-item",
214
+ index: Math.min(state.index, count - 1)
215
+ };
216
+ }
203
217
  setTabStop();
218
+ focusAwaitedItemIfMounted();
204
219
  },
205
220
  focusItem: moveTo,
206
221
  detach() {
207
- pending = null;
222
+ state = DETACHED;
208
223
  observer.disconnect();
209
224
  list.removeEventListener("keydown", onKeyDown2);
210
225
  list.removeEventListener("focusin", onFocusIn);
@@ -322,61 +337,79 @@ function attachContextMenuTrigger(root, options) {
322
337
  import { applyColumnResize } from "@gridkitjs/core";
323
338
 
324
339
  // src/pointerGesture.ts
325
- function isPrimaryPress(press) {
326
- return press.button === 0 && press.isPrimary;
340
+ function mayStartGesture(press, signal) {
341
+ return press.button === 0 && press.isPrimary && signal?.aborted !== true;
327
342
  }
328
- function startPointerGesture(element, pointerId, handlers) {
343
+ function startPointerGesture(element, pointerId, handlers, signal) {
329
344
  element.setPointerCapture(pointerId);
330
345
  function stop() {
331
346
  element.removeEventListener("pointermove", move);
332
347
  element.removeEventListener("pointerup", end);
333
348
  element.removeEventListener("pointercancel", cancel);
334
349
  window.removeEventListener("keydown", onKeyDown2);
350
+ signal?.removeEventListener("abort", abandon);
335
351
  if (element.hasPointerCapture(pointerId)) {
336
352
  element.releasePointerCapture(pointerId);
337
353
  }
338
354
  }
339
355
  function move(event) {
340
- handlers.onMove(event);
356
+ if (event.pointerId === pointerId) {
357
+ handlers.onMove(event);
358
+ }
341
359
  }
342
- function end() {
343
- stop();
344
- handlers.onEnd();
360
+ function end(event) {
361
+ if (event.pointerId === pointerId) {
362
+ stop();
363
+ handlers.onEnd();
364
+ }
365
+ }
366
+ function cancel(event) {
367
+ if (event.pointerId === pointerId) {
368
+ abandon();
369
+ }
345
370
  }
346
- function cancel() {
371
+ function abandon() {
347
372
  stop();
348
373
  handlers.onCancel();
349
374
  }
350
375
  function onKeyDown2(event) {
351
376
  if (event.key === "Escape") {
352
- cancel();
377
+ abandon();
353
378
  }
354
379
  }
355
380
  element.addEventListener("pointermove", move);
356
381
  element.addEventListener("pointerup", end);
357
382
  element.addEventListener("pointercancel", cancel);
358
383
  window.addEventListener("keydown", onKeyDown2);
384
+ signal?.addEventListener("abort", abandon);
359
385
  }
360
386
 
361
387
  // src/columnResize.ts
362
388
  function startColumnResizeGesture(handle, press, session, handlers) {
363
- if (!isPrimaryPress(press)) {
389
+ if (!mayStartGesture(press, handlers.signal)) {
364
390
  return false;
365
391
  }
366
392
  press.preventDefault();
367
393
  let width = session.startWidth;
368
- startPointerGesture(handle, press.pointerId, {
369
- onMove(event) {
370
- width = applyColumnResize(session, event.clientX);
371
- handlers.onMove(width);
372
- },
373
- onEnd() {
374
- handlers.onEnd(width === session.startWidth ? null : width);
394
+ let resized = false;
395
+ startPointerGesture(
396
+ handle,
397
+ press.pointerId,
398
+ {
399
+ onMove(event) {
400
+ width = applyColumnResize(session, event.clientX);
401
+ resized ||= width !== session.startWidth;
402
+ handlers.onMove(width);
403
+ },
404
+ onEnd() {
405
+ handlers.onEnd(resized ? width : null);
406
+ },
407
+ onCancel() {
408
+ handlers.onCancel();
409
+ }
375
410
  },
376
- onCancel() {
377
- handlers.onCancel();
378
- }
379
- });
411
+ handlers.signal
412
+ );
380
413
  return true;
381
414
  }
382
415
  function measureColumnContent(table, columnId) {
@@ -497,47 +530,46 @@ function downloadBlob(blob, fileName) {
497
530
 
498
531
  // src/dragReorder.ts
499
532
  var DRAG_THRESHOLD = 4;
500
- var NO_DRAG = {
501
- draggedId: null,
502
- dropTarget: null,
503
- ghostTransform: null
504
- };
533
+ var NO_DRAG = { kind: "idle" };
505
534
  function startDragReorder(id, grabElement, press, handlers) {
506
- if (!isPrimaryPress(press)) {
535
+ if (!mayStartGesture(press, handlers.signal)) {
507
536
  return false;
508
537
  }
509
- const { pointerId, clientX: startX } = press;
538
+ const { pointerId, clientX: startX, clientY: startY } = press;
510
539
  const grabX = startX - grabElement.getBoundingClientRect().left;
511
- let dragging = false;
512
- let target = null;
513
- startPointerGesture(grabElement, pointerId, {
514
- onMove(moveEvent) {
515
- const { clientX, clientY } = moveEvent;
516
- if (!dragging) {
517
- if (Math.abs(clientX - startX) < DRAG_THRESHOLD) {
540
+ let phase = { kind: "pressed" };
541
+ startPointerGesture(
542
+ grabElement,
543
+ pointerId,
544
+ {
545
+ onMove(moveEvent) {
546
+ const { clientX, clientY } = moveEvent;
547
+ if (phase.kind === "pressed" && Math.hypot(clientX - startX, clientY - startY) < DRAG_THRESHOLD) {
518
548
  return;
519
549
  }
520
- dragging = true;
521
- }
522
- target = handlers.resolveTarget(clientX, clientY, id);
523
- handlers.onState({
524
- draggedId: id,
525
- dropTarget: target,
526
- ghostTransform: `translate(${String(clientX - grabX)}px, ${String(clientY)}px)`
527
- });
528
- },
529
- onEnd() {
530
- const dropped = target;
531
- handlers.onState(NO_DRAG);
532
- if (dragging && dropped !== null) {
533
- handlers.onDrop(dropped, id);
550
+ phase = {
551
+ kind: "dragging",
552
+ target: handlers.resolveTarget(clientX, clientY, id)
553
+ };
554
+ handlers.onState({
555
+ kind: "dragging",
556
+ draggedId: id,
557
+ dropTarget: phase.target,
558
+ ghostTransform: `translate(${String(clientX - grabX)}px, ${String(clientY)}px)`
559
+ });
560
+ },
561
+ onEnd() {
562
+ handlers.onState(NO_DRAG);
563
+ if (phase.kind === "dragging" && phase.target !== null) {
564
+ handlers.onDrop(phase.target, id);
565
+ }
566
+ },
567
+ onCancel() {
568
+ handlers.onState(NO_DRAG);
534
569
  }
535
570
  },
536
- onCancel() {
537
- target = null;
538
- handlers.onState(NO_DRAG);
539
- }
540
- });
571
+ handlers.signal
572
+ );
541
573
  return true;
542
574
  }
543
575
 
@@ -594,7 +626,7 @@ function observeElementWidth(element, onWidth) {
594
626
  // src/gridFocus.ts
595
627
  import {
596
628
  clampFocus,
597
- HEADER_ROW,
629
+ headerFocus,
598
630
  nextFocusForKey
599
631
  } from "@gridkitjs/core";
600
632
  var FALLBACK_PAGE = 10;
@@ -604,6 +636,12 @@ function cellIn(row, columnIndex) {
604
636
  }
605
637
  return row.cells[Math.min(columnIndex, row.cells.length - 1)] ?? null;
606
638
  }
639
+ function sameCell(a, b) {
640
+ if (a.area === "body" && b.area === "body") {
641
+ return a.rowIndex === b.rowIndex && a.columnIndex === b.columnIndex;
642
+ }
643
+ return a.area === b.area && a.columnIndex === b.columnIndex;
644
+ }
607
645
  function findBodyRow(bodyTable, rowIndex) {
608
646
  return bodyTable?.tBodies[0]?.querySelector(
609
647
  `:scope > tr[data-gridkit-row-index="${String(rowIndex)}"]`
@@ -621,7 +659,7 @@ function createGridFocus(initialOptions) {
621
659
  let options = initialOptions;
622
660
  let rows = null;
623
661
  const listeners = /* @__PURE__ */ new Set();
624
- let stored = { rowIndex: HEADER_ROW, columnIndex: 0 };
662
+ let stored = headerFocus(0);
625
663
  let snapshot = {
626
664
  stored,
627
665
  rowCount: options.rowCount,
@@ -640,14 +678,28 @@ function createGridFocus(initialOptions) {
640
678
  }
641
679
  return snapshot.focus;
642
680
  }
643
- function focusCell(rowIndex, columnIndex) {
644
- const next = clampFocus(
645
- { rowIndex, columnIndex },
646
- options.rowCount,
647
- options.columnCount
648
- );
649
- const current = getFocus();
650
- if (next.rowIndex === current.rowIndex && next.columnIndex === current.columnIndex) {
681
+ function dropPendingMove() {
682
+ pendingMove = null;
683
+ }
684
+ function listenForTakeover(root) {
685
+ function onFocusOut(event) {
686
+ const to = event.relatedTarget;
687
+ if (to instanceof Node && !root.contains(to)) {
688
+ dropPendingMove();
689
+ }
690
+ }
691
+ root.addEventListener("focusout", onFocusOut);
692
+ root.addEventListener("wheel", dropPendingMove, { passive: true });
693
+ root.addEventListener("pointerdown", dropPendingMove);
694
+ return () => {
695
+ root.removeEventListener("focusout", onFocusOut);
696
+ root.removeEventListener("wheel", dropPendingMove);
697
+ root.removeEventListener("pointerdown", dropPendingMove);
698
+ };
699
+ }
700
+ function focusCell(focus) {
701
+ const next = clampFocus(focus, options.rowCount, options.columnCount);
702
+ if (sameCell(next, getFocus())) {
651
703
  return;
652
704
  }
653
705
  pendingMove = next;
@@ -681,12 +733,14 @@ function createGridFocus(initialOptions) {
681
733
  if (next === null) {
682
734
  return false;
683
735
  }
684
- focusCell(next.rowIndex, next.columnIndex);
736
+ focusCell(next);
685
737
  return true;
686
738
  },
687
- attach(next) {
739
+ attach(next, root) {
688
740
  rows = next;
741
+ const stopListening = root == null ? void 0 : listenForTakeover(root);
689
742
  return () => {
743
+ stopListening?.();
690
744
  if (rows === next) {
691
745
  rows = null;
692
746
  }
@@ -697,7 +751,7 @@ function createGridFocus(initialOptions) {
697
751
  return;
698
752
  }
699
753
  const cell = cellIn(
700
- drawn.rowIndex === HEADER_ROW ? rows.getHeaderRow() : rows.getBodyRow(drawn.rowIndex),
754
+ drawn.area === "header" ? rows.getHeaderRow() : rows.getBodyRow(drawn.rowIndex),
701
755
  drawn.columnIndex
702
756
  );
703
757
  if (cell !== null) {
@@ -706,7 +760,7 @@ function createGridFocus(initialOptions) {
706
760
  return;
707
761
  }
708
762
  const range = options.virtualRange;
709
- if (range != null && drawn.rowIndex !== HEADER_ROW && (drawn.rowIndex < range.startIndex || drawn.rowIndex > range.endIndex)) {
763
+ if (range != null && drawn.area === "body" && (drawn.rowIndex < range.startIndex || drawn.rowIndex > range.endIndex)) {
710
764
  range.scrollToIndex(drawn.rowIndex);
711
765
  return;
712
766
  }
@@ -716,20 +770,48 @@ function createGridFocus(initialOptions) {
716
770
  }
717
771
 
718
772
  // src/infiniteScroll.ts
773
+ var IDLE = { kind: "idle" };
774
+ var LOADING = { kind: "loading" };
775
+ var DETACHED2 = { kind: "detached" };
776
+ function afterUpdate(state, loading, hasMoreChanged) {
777
+ if (loading) {
778
+ return LOADING;
779
+ }
780
+ return state.kind === "loading" || hasMoreChanged ? IDLE : state;
781
+ }
782
+ function contentOffset(root, sentinel) {
783
+ return sentinel.getBoundingClientRect().top - root.getBoundingClientRect().top + root.scrollTop;
784
+ }
719
785
  function attachInfiniteScroll(root, options) {
720
786
  let latest = options;
721
787
  let intersecting = false;
722
- let fired = false;
723
- let wasLoading = options.isLoadingMore ?? false;
788
+ let state = options.isLoadingMore ?? false ? LOADING : IDLE;
724
789
  let observer = null;
725
790
  let observed = null;
726
791
  function maybeLoadMore() {
727
- if (!intersecting || !latest.hasMore || (latest.isLoadingMore ?? false) || fired) {
792
+ if (state.kind !== "idle" || !intersecting || observed === null || !latest.hasMore) {
728
793
  return;
729
794
  }
730
- fired = true;
795
+ state = {
796
+ kind: "requested",
797
+ sentinelOffset: contentOffset(root, observed.sentinel)
798
+ };
731
799
  latest.onLoadMore?.();
732
800
  }
801
+ function requestAnswered() {
802
+ if (state.kind !== "requested" || observed === null) {
803
+ return false;
804
+ }
805
+ const moved = contentOffset(root, observed.sentinel) - state.sentinelOffset;
806
+ return Math.abs(moved) >= 1;
807
+ }
808
+ function report(isIntersecting) {
809
+ intersecting = isIntersecting;
810
+ if (!intersecting && state.kind === "requested") {
811
+ state = IDLE;
812
+ }
813
+ maybeLoadMore();
814
+ }
733
815
  function disconnect() {
734
816
  observer?.disconnect();
735
817
  observer = null;
@@ -749,11 +831,9 @@ function attachInfiniteScroll(root, options) {
749
831
  observer = new IntersectionObserver(
750
832
  (entries) => {
751
833
  const entry = entries[entries.length - 1];
752
- if (entry === void 0) {
753
- return;
834
+ if (entry !== void 0) {
835
+ report(entry.isIntersecting);
754
836
  }
755
- intersecting = entry.isIntersecting;
756
- maybeLoadMore();
757
837
  },
758
838
  { root, rootMargin }
759
839
  );
@@ -763,16 +843,24 @@ function attachInfiniteScroll(root, options) {
763
843
  observe();
764
844
  return {
765
845
  update(next) {
766
- latest = next;
767
- const loading = next.isLoadingMore ?? false;
768
- if (wasLoading && !loading) {
769
- fired = false;
770
- maybeLoadMore();
846
+ if (state.kind === "detached") {
847
+ return;
771
848
  }
772
- wasLoading = loading;
849
+ const hasMoreChanged = next.hasMore !== latest.hasMore;
850
+ latest = next;
851
+ state = afterUpdate(state, next.isLoadingMore ?? false, hasMoreChanged);
773
852
  observe();
853
+ if (requestAnswered()) {
854
+ state = IDLE;
855
+ disconnect();
856
+ observe();
857
+ }
858
+ maybeLoadMore();
774
859
  },
775
- detach: disconnect
860
+ detach() {
861
+ disconnect();
862
+ state = DETACHED2;
863
+ }
776
864
  };
777
865
  }
778
866
 
@@ -792,6 +880,8 @@ var MOVES = {
792
880
  Home: "first",
793
881
  End: "last"
794
882
  };
883
+ var IDLE2 = { kind: "idle" };
884
+ var AWAY = { kind: "away" };
795
885
  function idOf(item) {
796
886
  return item.getAttribute("data-gridkit-menu-item") ?? "";
797
887
  }
@@ -809,10 +899,8 @@ function isTypeahead(event) {
809
899
  return /^\S$/u.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
810
900
  }
811
901
  function attachMenu(panel, options) {
812
- let typed = "";
813
- let typeaheadTimer;
814
- let hovered = null;
815
- let hoverTimer;
902
+ let typeahead = IDLE2;
903
+ let hover = AWAY;
816
904
  function items() {
817
905
  return Array.from(panel.querySelectorAll(ITEM2));
818
906
  }
@@ -841,21 +929,58 @@ function attachMenu(panel, options) {
841
929
  options.onActivate(idOf(item));
842
930
  }
843
931
  }
844
- function typeahead(key, all, from) {
845
- typed += key;
846
- window.clearTimeout(typeaheadTimer);
847
- typeaheadTimer = window.setTimeout(() => {
848
- typed = "";
849
- }, TYPEAHEAD_TIMEOUT);
932
+ function endTyping() {
933
+ if (typeahead.kind === "typing") {
934
+ window.clearTimeout(typeahead.timer);
935
+ }
936
+ typeahead = IDLE2;
937
+ }
938
+ function endHover() {
939
+ if (hover.kind === "resting") {
940
+ window.clearTimeout(hover.timer);
941
+ }
942
+ hover = AWAY;
943
+ }
944
+ function type(key, all, from) {
945
+ const typed = (typeahead.kind === "typing" ? typeahead.typed : "") + key;
946
+ endTyping();
947
+ typeahead = {
948
+ kind: "typing",
949
+ typed,
950
+ timer: window.setTimeout(() => {
951
+ typeahead = IDLE2;
952
+ }, TYPEAHEAD_TIMEOUT)
953
+ };
850
954
  const match = all[typeaheadIndex(all.map(labelOf), from, typed)];
851
955
  if (match !== void 0) {
852
956
  focus(match);
853
957
  }
854
958
  }
959
+ function rest(item) {
960
+ hover = {
961
+ kind: "resting",
962
+ item,
963
+ timer: window.setTimeout(() => {
964
+ hover = { kind: "rested", item };
965
+ if (opensSubmenu(item) && !isDisabled(item)) {
966
+ openSubmenu(item, "none");
967
+ } else {
968
+ options.onCollapseSubmenu?.();
969
+ }
970
+ }, HOVER_DELAY)
971
+ };
972
+ }
855
973
  function onKeyDown2(event) {
856
- if (event.isComposing) {
974
+ if (event.isComposing || !actOnKey(event)) {
857
975
  return;
858
976
  }
977
+ event.preventDefault();
978
+ endHover();
979
+ if (!isTypeahead(event)) {
980
+ endTyping();
981
+ }
982
+ }
983
+ function actOnKey(event) {
859
984
  const all = items();
860
985
  const item = itemOf(event.target);
861
986
  const from = item === null ? -1 : all.indexOf(item);
@@ -882,11 +1007,11 @@ function attachMenu(panel, options) {
882
1007
  } else if (event.key === "Tab") {
883
1008
  options.onClose("tab");
884
1009
  } else if (isTypeahead(event)) {
885
- typeahead(event.key, all, from);
1010
+ type(event.key, all, from);
886
1011
  } else {
887
- return;
1012
+ return false;
888
1013
  }
889
- event.preventDefault();
1014
+ return true;
890
1015
  }
891
1016
  function onFocusIn(event) {
892
1017
  const item = itemOf(event.target);
@@ -908,24 +1033,16 @@ function attachMenu(panel, options) {
908
1033
  if (item !== null && document.activeElement !== item) {
909
1034
  focus(item);
910
1035
  }
911
- if (item === hovered) {
1036
+ if (hover.kind !== "away" && hover.item === item) {
912
1037
  return;
913
1038
  }
914
- hovered = item;
915
- window.clearTimeout(hoverTimer);
1039
+ endHover();
916
1040
  if (item !== null) {
917
- hoverTimer = window.setTimeout(() => {
918
- if (opensSubmenu(item) && !isDisabled(item)) {
919
- openSubmenu(item, "none");
920
- } else {
921
- options.onCollapseSubmenu?.();
922
- }
923
- }, HOVER_DELAY);
1041
+ rest(item);
924
1042
  }
925
1043
  }
926
1044
  function onPointerLeave() {
927
- hovered = null;
928
- window.clearTimeout(hoverTimer);
1045
+ endHover();
929
1046
  }
930
1047
  const focused = itemOf(document.activeElement);
931
1048
  setTabStop(focused ?? items()[0]);
@@ -943,8 +1060,8 @@ function attachMenu(panel, options) {
943
1060
  }
944
1061
  },
945
1062
  detach() {
946
- window.clearTimeout(typeaheadTimer);
947
- window.clearTimeout(hoverTimer);
1063
+ endTyping();
1064
+ endHover();
948
1065
  panel.removeEventListener("keydown", onKeyDown2);
949
1066
  panel.removeEventListener("focusin", onFocusIn);
950
1067
  panel.removeEventListener("click", onClick);
@@ -1238,15 +1355,18 @@ function attachOverlay(anchor, panel, options) {
1238
1355
  if (settlingFrame !== null) {
1239
1356
  cancelAnimationFrame(settlingFrame);
1240
1357
  }
1241
- if (returnFocusTo !== null && focusInside) {
1242
- returnFocusTo.focus({ preventScroll: true });
1358
+ if (!focusInside) {
1359
+ return;
1243
1360
  }
1361
+ const target = returnFocusTo?.isConnected === true ? returnFocusTo : options.returnFocusFallback?.();
1362
+ target?.focus({ preventScroll: true });
1244
1363
  }
1245
1364
  return detach;
1246
1365
  }
1247
1366
 
1248
1367
  // src/rowVirtualizer.ts
1249
1368
  import { visibleRange } from "@gridkitjs/core";
1369
+ var READY = { kind: "ready" };
1250
1370
  function sameRange(a, b) {
1251
1371
  return a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.offsetBefore === b.offsetBefore && a.offsetAfter === b.offsetAfter;
1252
1372
  }
@@ -1256,13 +1376,12 @@ function createRowVirtualizer(scrollElement, options) {
1256
1376
  let scrollTop = scrollElement.scrollTop;
1257
1377
  let viewportHeight = scrollElement.clientHeight;
1258
1378
  let range = null;
1259
- let destroyed = false;
1260
- let pendingScroll;
1379
+ let phase = READY;
1261
1380
  const elements = /* @__PURE__ */ new Map();
1262
1381
  const indexByElement = /* @__PURE__ */ new Map();
1263
1382
  const refCallbacks = /* @__PURE__ */ new Map();
1264
1383
  function recompute() {
1265
- if (destroyed) {
1384
+ if (phase.kind === "destroyed") {
1266
1385
  return;
1267
1386
  }
1268
1387
  const next = visibleRange(
@@ -1313,10 +1432,16 @@ function createRowVirtualizer(scrollElement, options) {
1313
1432
  recompute();
1314
1433
  return {
1315
1434
  setRowCount(count) {
1435
+ if (phase.kind === "awaiting-row" && phase.index >= count) {
1436
+ phase = READY;
1437
+ }
1316
1438
  heights = Array.from({ length: count }, (_, index) => heights[index]);
1317
1439
  recompute();
1318
1440
  },
1319
1441
  resetHeights() {
1442
+ if (phase.kind === "awaiting-row") {
1443
+ phase = READY;
1444
+ }
1320
1445
  heights = new Array(heights.length).fill(void 0);
1321
1446
  recompute();
1322
1447
  },
@@ -1330,15 +1455,15 @@ function createRowVirtualizer(scrollElement, options) {
1330
1455
  indexByElement.delete(previous);
1331
1456
  elements.delete(index);
1332
1457
  }
1333
- if (element === null || destroyed) {
1458
+ if (element === null || phase.kind === "destroyed") {
1334
1459
  return;
1335
1460
  }
1336
1461
  elements.set(index, element);
1337
1462
  indexByElement.set(element, index);
1338
1463
  rowObserver.observe(element);
1339
- if (pendingScroll?.index === index) {
1340
- element.scrollIntoView(pendingScroll.options);
1341
- pendingScroll = void 0;
1464
+ if (phase.kind === "awaiting-row" && phase.index === index) {
1465
+ element.scrollIntoView(phase.options);
1466
+ phase = READY;
1342
1467
  }
1343
1468
  };
1344
1469
  refCallbacks.set(index, callback);
@@ -1346,21 +1471,21 @@ function createRowVirtualizer(scrollElement, options) {
1346
1471
  return callback;
1347
1472
  },
1348
1473
  scrollToIndex(index, scrollOptions) {
1349
- if (heights.length === 0) {
1474
+ if (phase.kind === "destroyed" || heights.length === 0) {
1350
1475
  return;
1351
1476
  }
1352
1477
  const clamped = Math.max(0, Math.min(index, heights.length - 1));
1353
1478
  const mounted = elements.get(clamped);
1354
1479
  if (mounted !== void 0) {
1480
+ phase = READY;
1355
1481
  mounted.scrollIntoView(scrollOptions);
1356
1482
  return;
1357
1483
  }
1358
- pendingScroll = { index: clamped, options: scrollOptions };
1484
+ phase = { kind: "awaiting-row", index: clamped, options: scrollOptions };
1359
1485
  scrollElement.scrollTop = estimateOffsetBefore(clamped);
1360
1486
  },
1361
1487
  destroy() {
1362
- destroyed = true;
1363
- pendingScroll = void 0;
1488
+ phase = { kind: "destroyed" };
1364
1489
  scrollElement.removeEventListener("scroll", handleScroll);
1365
1490
  viewportObserver.disconnect();
1366
1491
  rowObserver.disconnect();
@@ -1573,6 +1698,7 @@ function observeToolbarOverflow(toolbar, options) {
1573
1698
 
1574
1699
  // src/print/printDocument.ts
1575
1700
  var RETURN_EVENTS = ["pointerdown", "keydown"];
1701
+ var FONTS_TIMEOUT_MS = 3e3;
1576
1702
  var printing = false;
1577
1703
  function createFrame(bodyHtml) {
1578
1704
  const frame = document.createElement("iframe");
@@ -1589,17 +1715,26 @@ function createFrame(bodyHtml) {
1589
1715
  }
1590
1716
  function load(frame) {
1591
1717
  return new Promise((resolve, reject) => {
1592
- frame.addEventListener(
1593
- "load",
1594
- () => {
1595
- if (frame.contentWindow === null) {
1596
- reject(new Error("The print frame has no window."));
1597
- } else {
1598
- resolve(frame.contentWindow);
1599
- }
1600
- },
1601
- { once: true }
1602
- );
1718
+ const removal = new MutationObserver(() => {
1719
+ if (!frame.isConnected) {
1720
+ settle();
1721
+ reject(new Error("The print frame was removed before it loaded."));
1722
+ }
1723
+ });
1724
+ function loaded() {
1725
+ settle();
1726
+ if (frame.contentWindow === null) {
1727
+ reject(new Error("The print frame has no window."));
1728
+ } else {
1729
+ resolve(frame.contentWindow);
1730
+ }
1731
+ }
1732
+ function settle() {
1733
+ removal.disconnect();
1734
+ frame.removeEventListener("load", loaded);
1735
+ }
1736
+ frame.addEventListener("load", loaded);
1737
+ removal.observe(document.body, { childList: true });
1603
1738
  document.body.append(frame);
1604
1739
  });
1605
1740
  }
@@ -1610,7 +1745,18 @@ function fill(view, { title, styles }) {
1610
1745
  style.textContent = styles;
1611
1746
  frameDocument.head.append(style);
1612
1747
  }
1613
- function print(view) {
1748
+ async function fontsLoaded(view) {
1749
+ let timer = 0;
1750
+ const timeout = new Promise((resolve) => {
1751
+ timer = window.setTimeout(resolve, FONTS_TIMEOUT_MS);
1752
+ });
1753
+ try {
1754
+ await Promise.race([view.document.fonts.ready, timeout]);
1755
+ } finally {
1756
+ window.clearTimeout(timer);
1757
+ }
1758
+ }
1759
+ async function print(view) {
1614
1760
  const listeners = [
1615
1761
  [view, "afterprint"],
1616
1762
  ...RETURN_EVENTS.flatMap((type) => [
@@ -1618,19 +1764,22 @@ function print(view) {
1618
1764
  [window, type]
1619
1765
  ])
1620
1766
  ];
1621
- return new Promise((resolve) => {
1622
- function done() {
1623
- for (const [target, type] of listeners) {
1624
- target.removeEventListener(type, done, { capture: true });
1625
- }
1626
- resolve();
1627
- }
1628
- for (const [target, type] of listeners) {
1629
- target.addEventListener(type, done, { capture: true });
1630
- }
1767
+ let done = () => void 0;
1768
+ const closed = new Promise((resolve) => {
1769
+ done = resolve;
1770
+ });
1771
+ for (const [target, type] of listeners) {
1772
+ target.addEventListener(type, done, { capture: true });
1773
+ }
1774
+ try {
1631
1775
  view.focus();
1632
1776
  view.print();
1633
- });
1777
+ await closed;
1778
+ } finally {
1779
+ for (const [target, type] of listeners) {
1780
+ target.removeEventListener(type, done, { capture: true });
1781
+ }
1782
+ }
1634
1783
  }
1635
1784
  async function printDocument(options) {
1636
1785
  if (printing) {
@@ -1642,7 +1791,7 @@ async function printDocument(options) {
1642
1791
  try {
1643
1792
  const view = await load(frame);
1644
1793
  fill(view, options);
1645
- await view.document.fonts.ready;
1794
+ await fontsLoaded(view);
1646
1795
  await print(view);
1647
1796
  } finally {
1648
1797
  frame.remove();