@gridkitjs/dom 0.0.0-dev-20261108230616 → 0.0.0-dev-20261108231045

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.d.ts CHANGED
@@ -87,7 +87,8 @@ interface AttachedChecklist {
87
87
  * How many items the whole list holds, mounted or not. Starts at 0, so a
88
88
  * renderer calls this straight after attaching, and again whenever the
89
89
  * list changes — a search narrowing it, say. A focused place past the new
90
- * end moves back to the last item.
90
+ * end moves back to the last item, and so does a move still waiting for
91
+ * an item past it.
91
92
  */
92
93
  readonly setCount: (count: number) => void;
93
94
  /**
@@ -445,8 +446,13 @@ interface GridFocusController {
445
446
  /**
446
447
  * Connects the tab stop to the rendered rows, which the browser's focus
447
448
  * then follows it among. Returns the function that disconnects them.
449
+ *
450
+ * `root`, the element holding every row, lets the user take over from a
451
+ * move still waiting for its row to be drawn: focus leaving `root`, or a
452
+ * wheel or a press inside it, drops the move, so a row mounting later
453
+ * neither scrolls back to itself nor pulls focus back into the grid.
448
454
  */
449
- readonly attach: (rows: GridFocusRows) => () => void;
455
+ readonly attach: (rows: GridFocusRows, root?: HTMLElement | null) => () => void;
450
456
  /**
451
457
  * Called by the renderer once it has drawn `drawn` — a value `getFocus`
452
458
  * returned — so the browser's focus follows a move to the cell now in the
@@ -498,8 +504,9 @@ interface InfiniteScrollOptions {
498
504
  readonly hasMore: boolean;
499
505
  /**
500
506
  * Called at most once per approach: when the sentinel comes within
501
- * `rootMargin` of the root's visible area, and not again until
502
- * `isLoadingMore` has gone from `true` back to `false`.
507
+ * `rootMargin` of the root's visible area. Not again until that load has
508
+ * finished (`isLoadingMore` back to `false`), `hasMore` has changed, or
509
+ * the sentinel has left `rootMargin` and come back.
503
510
  */
504
511
  readonly onLoadMore?: (() => void) | undefined;
505
512
  /** Whether a load `onLoadMore` started is still in flight. */
@@ -511,24 +518,24 @@ interface InfiniteScroll {
511
518
  /**
512
519
  * Takes the current options. Cheap when nothing changed, so a renderer may
513
520
  * call it after every render; a new sentinel or `rootMargin` restarts the
514
- * observer, and `isLoadingMore` going back to `false` re-checks at once.
521
+ * observer, and a load finishing or `hasMore` changing re-checks at once.
515
522
  */
516
523
  readonly update: (options: InfiniteScrollOptions) => void;
517
- /** Stops watching for good. */
524
+ /** Stops watching for good: a later `update` does nothing. */
518
525
  readonly detach: () => void;
519
526
  }
520
527
  /**
521
528
  * Turns a sentinel's approach to the bottom of the scrolling `root` into
522
- * `onLoadMore` calls, no more than one per "the sentinel is near and no load
523
- * is in flight since the last one finished".
529
+ * `onLoadMore` calls, no more than one per approach while no load is in
530
+ * flight.
524
531
  *
525
- * Three facts drive it: whether the sentinel is intersecting (as of the
526
- * observer's last report), whether `onLoadMore` has fired since the last
527
- * load finished, and whether a load was in flight at the last update. They
528
- * outlive the sentinel itself, which a renderer mounts and unmounts as
529
- * `hasMore` changes, so they live here rather than on any one observer.
532
+ * Two facts drive it: whether the sentinel is intersecting (as of the
533
+ * observer's last report), and the `LoadState`. Both outlive the sentinel
534
+ * itself, which a renderer mounts and unmounts as `hasMore` changes, so they
535
+ * live here rather than on any one observer.
530
536
  *
531
- * Off means off: with `hasMore` false, or no sentinel, no observer exists.
537
+ * Off means off: with `hasMore` false, no sentinel, or once detached, no
538
+ * observer exists.
532
539
  */
533
540
  declare function attachInfiniteScroll(root: Element, options: InfiniteScrollOptions): InfiniteScroll;
534
541
 
@@ -693,9 +700,19 @@ interface OverlayOptions extends OverlayPlacementOptions {
693
700
  readonly initialFocus?: OverlayInitialFocus | undefined;
694
701
  /**
695
702
  * Where focus goes when the overlay detaches. Defaults to the element that
696
- * had focus when it attached; `null` leaves focus alone.
703
+ * had focus when it attached; `null` leaves focus alone, unless
704
+ * `returnFocusFallback` names somewhere.
697
705
  */
698
706
  readonly returnFocusTo?: HTMLElement | null | undefined;
707
+ /**
708
+ * Where focus goes when there is nothing to return it to as the overlay
709
+ * detaches: `returnFocusTo` is `null`, or has left the page — the button a
710
+ * menu opened from, taken away by what the menu ran. Called then, so it
711
+ * can name what is on the page by that time; returning `null` leaves focus
712
+ * alone. Like the return itself, it applies only while focus is still in
713
+ * the panel, in an overlay above it, or nowhere.
714
+ */
715
+ readonly returnFocusFallback?: (() => HTMLElement | null) | undefined;
699
716
  /**
700
717
  * Whether a scroll outside the panel just after input inside it — an
701
718
  * `input` or `change` event, in the two animation frames that follow it —
@@ -745,6 +762,8 @@ interface OverlayOptions extends OverlayPlacementOptions {
745
762
  * Detaching removes every listener and returns focus, but only when focus is
746
763
  * still in the panel, in an overlay above it, or nowhere — a click that
747
764
  * dismissed it by landing on another control leaves focus on that control.
765
+ * Focus goes to `returnFocusFallback` instead when the element it would
766
+ * return to has left the page.
748
767
  * Any overlay still above it is dismissed with `"parent"` and detached first.
749
768
  */
750
769
  declare function attachOverlay(anchor: OverlayAnchor, panel: HTMLElement, options: OverlayOptions): () => void;
@@ -776,10 +795,15 @@ interface RowVirtualizer {
776
795
  /**
777
796
  * Scrolls so row `index` is in view. A mounted row is scrolled to directly;
778
797
  * one outside the window is jumped to at its estimated offset, then
779
- * scrolled to properly once it mounts and can be measured.
798
+ * scrolled to properly once it mounts and can be measured. A later call
799
+ * replaces one still waiting for its row, and so does a row count that no
800
+ * longer holds that row.
780
801
  */
781
802
  readonly scrollToIndex: (index: number, options?: ScrollIntoViewOptions) => void;
782
- /** Stops listening and measuring, for good. */
803
+ /**
804
+ * Stops listening, measuring and scrolling, for good: every method does
805
+ * nothing after it.
806
+ */
783
807
  readonly destroy: () => void;
784
808
  }
785
809
  /**
@@ -894,7 +918,8 @@ interface PrintDocumentOptions {
894
918
  * which a popup blocker would stop.
895
919
  *
896
920
  * One print at a time: a call while a print is open resolves at once
897
- * without printing.
921
+ * without printing. Rejects, with the frame removed, if the frame is taken
922
+ * out of the page before it loads or the dialog fails to open.
898
923
  */
899
924
  declare function printDocument(options: PrintDocumentOptions): Promise<void>;
900
925
 
package/dist/index.js CHANGED
@@ -175,8 +175,7 @@ function attachChecklist(list, options) {
175
175
  item.focus({ preventScroll: true });
176
176
  }
177
177
  }
178
- const observer = new MutationObserver(() => {
179
- setTabStop();
178
+ function focusPendingIfMounted() {
180
179
  const item = pending === null ? null : itemAt(pending);
181
180
  if (item === null) {
182
181
  return;
@@ -186,6 +185,10 @@ function attachChecklist(list, options) {
186
185
  if (focused === null || focused === document.body || list.contains(focused)) {
187
186
  item.focus({ preventScroll: true });
188
187
  }
188
+ }
189
+ const observer = new MutationObserver(() => {
190
+ setTabStop();
191
+ focusPendingIfMounted();
189
192
  });
190
193
  observer.observe(list, {
191
194
  childList: true,
@@ -200,7 +203,11 @@ function attachChecklist(list, options) {
200
203
  setCount(next) {
201
204
  count = next;
202
205
  active = Math.max(0, Math.min(active, count - 1));
206
+ if (pending !== null) {
207
+ pending = count === 0 ? null : Math.min(pending, count - 1);
208
+ }
203
209
  setTabStop();
210
+ focusPendingIfMounted();
204
211
  },
205
212
  focusItem: moveTo,
206
213
  detach() {
@@ -658,6 +665,25 @@ function createGridFocus(initialOptions) {
658
665
  }
659
666
  return snapshot.focus;
660
667
  }
668
+ function dropPendingMove() {
669
+ pendingMove = null;
670
+ }
671
+ function listenForTakeover(root) {
672
+ function onFocusOut(event) {
673
+ const to = event.relatedTarget;
674
+ if (to instanceof Node && !root.contains(to)) {
675
+ dropPendingMove();
676
+ }
677
+ }
678
+ root.addEventListener("focusout", onFocusOut);
679
+ root.addEventListener("wheel", dropPendingMove, { passive: true });
680
+ root.addEventListener("pointerdown", dropPendingMove);
681
+ return () => {
682
+ root.removeEventListener("focusout", onFocusOut);
683
+ root.removeEventListener("wheel", dropPendingMove);
684
+ root.removeEventListener("pointerdown", dropPendingMove);
685
+ };
686
+ }
661
687
  function focusCell(rowIndex, columnIndex) {
662
688
  const next = clampFocus(
663
689
  { rowIndex, columnIndex },
@@ -702,9 +728,11 @@ function createGridFocus(initialOptions) {
702
728
  focusCell(next.rowIndex, next.columnIndex);
703
729
  return true;
704
730
  },
705
- attach(next) {
731
+ attach(next, root) {
706
732
  rows = next;
733
+ const stopListening = root == null ? void 0 : listenForTakeover(root);
707
734
  return () => {
735
+ stopListening?.();
708
736
  if (rows === next) {
709
737
  rows = null;
710
738
  }
@@ -734,20 +762,36 @@ function createGridFocus(initialOptions) {
734
762
  }
735
763
 
736
764
  // src/infiniteScroll.ts
765
+ var IDLE = { kind: "idle" };
766
+ var REQUESTED = { kind: "requested" };
767
+ var LOADING = { kind: "loading" };
768
+ var DETACHED = { kind: "detached" };
769
+ function afterUpdate(state, loading, hasMoreChanged) {
770
+ if (loading) {
771
+ return LOADING;
772
+ }
773
+ return state.kind === "loading" || hasMoreChanged ? IDLE : state;
774
+ }
737
775
  function attachInfiniteScroll(root, options) {
738
776
  let latest = options;
739
777
  let intersecting = false;
740
- let fired = false;
741
- let wasLoading = options.isLoadingMore ?? false;
778
+ let state = options.isLoadingMore ?? false ? LOADING : IDLE;
742
779
  let observer = null;
743
780
  let observed = null;
744
781
  function maybeLoadMore() {
745
- if (!intersecting || !latest.hasMore || (latest.isLoadingMore ?? false) || fired) {
782
+ if (state.kind !== "idle" || !intersecting || !latest.hasMore) {
746
783
  return;
747
784
  }
748
- fired = true;
785
+ state = REQUESTED;
749
786
  latest.onLoadMore?.();
750
787
  }
788
+ function report(isIntersecting) {
789
+ intersecting = isIntersecting;
790
+ if (!intersecting && state.kind === "requested") {
791
+ state = IDLE;
792
+ }
793
+ maybeLoadMore();
794
+ }
751
795
  function disconnect() {
752
796
  observer?.disconnect();
753
797
  observer = null;
@@ -767,11 +811,9 @@ function attachInfiniteScroll(root, options) {
767
811
  observer = new IntersectionObserver(
768
812
  (entries) => {
769
813
  const entry = entries[entries.length - 1];
770
- if (entry === void 0) {
771
- return;
814
+ if (entry !== void 0) {
815
+ report(entry.isIntersecting);
772
816
  }
773
- intersecting = entry.isIntersecting;
774
- maybeLoadMore();
775
817
  },
776
818
  { root, rootMargin }
777
819
  );
@@ -781,16 +823,19 @@ function attachInfiniteScroll(root, options) {
781
823
  observe();
782
824
  return {
783
825
  update(next) {
784
- latest = next;
785
- const loading = next.isLoadingMore ?? false;
786
- if (wasLoading && !loading) {
787
- fired = false;
788
- maybeLoadMore();
826
+ if (state.kind === "detached") {
827
+ return;
789
828
  }
790
- wasLoading = loading;
829
+ const hasMoreChanged = next.hasMore !== latest.hasMore;
830
+ latest = next;
831
+ state = afterUpdate(state, next.isLoadingMore ?? false, hasMoreChanged);
791
832
  observe();
833
+ maybeLoadMore();
792
834
  },
793
- detach: disconnect
835
+ detach() {
836
+ disconnect();
837
+ state = DETACHED;
838
+ }
794
839
  };
795
840
  }
796
841
 
@@ -810,6 +855,8 @@ var MOVES = {
810
855
  Home: "first",
811
856
  End: "last"
812
857
  };
858
+ var IDLE2 = { kind: "idle" };
859
+ var AWAY = { kind: "away" };
813
860
  function idOf(item) {
814
861
  return item.getAttribute("data-gridkit-menu-item") ?? "";
815
862
  }
@@ -827,10 +874,8 @@ function isTypeahead(event) {
827
874
  return /^\S$/u.test(event.key) && !event.ctrlKey && !event.metaKey && !event.altKey;
828
875
  }
829
876
  function attachMenu(panel, options) {
830
- let typed = "";
831
- let typeaheadTimer;
832
- let hovered = null;
833
- let hoverTimer;
877
+ let typeahead = IDLE2;
878
+ let hover = AWAY;
834
879
  function items() {
835
880
  return Array.from(panel.querySelectorAll(ITEM2));
836
881
  }
@@ -859,21 +904,58 @@ function attachMenu(panel, options) {
859
904
  options.onActivate(idOf(item));
860
905
  }
861
906
  }
862
- function typeahead(key, all, from) {
863
- typed += key;
864
- window.clearTimeout(typeaheadTimer);
865
- typeaheadTimer = window.setTimeout(() => {
866
- typed = "";
867
- }, TYPEAHEAD_TIMEOUT);
907
+ function endTyping() {
908
+ if (typeahead.kind === "typing") {
909
+ window.clearTimeout(typeahead.timer);
910
+ }
911
+ typeahead = IDLE2;
912
+ }
913
+ function endHover() {
914
+ if (hover.kind === "resting") {
915
+ window.clearTimeout(hover.timer);
916
+ }
917
+ hover = AWAY;
918
+ }
919
+ function type(key, all, from) {
920
+ const typed = (typeahead.kind === "typing" ? typeahead.typed : "") + key;
921
+ endTyping();
922
+ typeahead = {
923
+ kind: "typing",
924
+ typed,
925
+ timer: window.setTimeout(() => {
926
+ typeahead = IDLE2;
927
+ }, TYPEAHEAD_TIMEOUT)
928
+ };
868
929
  const match = all[typeaheadIndex(all.map(labelOf), from, typed)];
869
930
  if (match !== void 0) {
870
931
  focus(match);
871
932
  }
872
933
  }
934
+ function rest(item) {
935
+ hover = {
936
+ kind: "resting",
937
+ item,
938
+ timer: window.setTimeout(() => {
939
+ hover = { kind: "rested", item };
940
+ if (opensSubmenu(item) && !isDisabled(item)) {
941
+ openSubmenu(item, "none");
942
+ } else {
943
+ options.onCollapseSubmenu?.();
944
+ }
945
+ }, HOVER_DELAY)
946
+ };
947
+ }
873
948
  function onKeyDown2(event) {
874
- if (event.isComposing) {
949
+ if (event.isComposing || !actOnKey(event)) {
875
950
  return;
876
951
  }
952
+ event.preventDefault();
953
+ endHover();
954
+ if (!isTypeahead(event)) {
955
+ endTyping();
956
+ }
957
+ }
958
+ function actOnKey(event) {
877
959
  const all = items();
878
960
  const item = itemOf(event.target);
879
961
  const from = item === null ? -1 : all.indexOf(item);
@@ -900,11 +982,11 @@ function attachMenu(panel, options) {
900
982
  } else if (event.key === "Tab") {
901
983
  options.onClose("tab");
902
984
  } else if (isTypeahead(event)) {
903
- typeahead(event.key, all, from);
985
+ type(event.key, all, from);
904
986
  } else {
905
- return;
987
+ return false;
906
988
  }
907
- event.preventDefault();
989
+ return true;
908
990
  }
909
991
  function onFocusIn(event) {
910
992
  const item = itemOf(event.target);
@@ -926,24 +1008,16 @@ function attachMenu(panel, options) {
926
1008
  if (item !== null && document.activeElement !== item) {
927
1009
  focus(item);
928
1010
  }
929
- if (item === hovered) {
1011
+ if (hover.kind !== "away" && hover.item === item) {
930
1012
  return;
931
1013
  }
932
- hovered = item;
933
- window.clearTimeout(hoverTimer);
1014
+ endHover();
934
1015
  if (item !== null) {
935
- hoverTimer = window.setTimeout(() => {
936
- if (opensSubmenu(item) && !isDisabled(item)) {
937
- openSubmenu(item, "none");
938
- } else {
939
- options.onCollapseSubmenu?.();
940
- }
941
- }, HOVER_DELAY);
1016
+ rest(item);
942
1017
  }
943
1018
  }
944
1019
  function onPointerLeave() {
945
- hovered = null;
946
- window.clearTimeout(hoverTimer);
1020
+ endHover();
947
1021
  }
948
1022
  const focused = itemOf(document.activeElement);
949
1023
  setTabStop(focused ?? items()[0]);
@@ -961,8 +1035,8 @@ function attachMenu(panel, options) {
961
1035
  }
962
1036
  },
963
1037
  detach() {
964
- window.clearTimeout(typeaheadTimer);
965
- window.clearTimeout(hoverTimer);
1038
+ endTyping();
1039
+ endHover();
966
1040
  panel.removeEventListener("keydown", onKeyDown2);
967
1041
  panel.removeEventListener("focusin", onFocusIn);
968
1042
  panel.removeEventListener("click", onClick);
@@ -1256,15 +1330,18 @@ function attachOverlay(anchor, panel, options) {
1256
1330
  if (settlingFrame !== null) {
1257
1331
  cancelAnimationFrame(settlingFrame);
1258
1332
  }
1259
- if (returnFocusTo !== null && focusInside) {
1260
- returnFocusTo.focus({ preventScroll: true });
1333
+ if (!focusInside) {
1334
+ return;
1261
1335
  }
1336
+ const target = returnFocusTo?.isConnected === true ? returnFocusTo : options.returnFocusFallback?.();
1337
+ target?.focus({ preventScroll: true });
1262
1338
  }
1263
1339
  return detach;
1264
1340
  }
1265
1341
 
1266
1342
  // src/rowVirtualizer.ts
1267
1343
  import { visibleRange } from "@gridkitjs/core";
1344
+ var READY = { kind: "ready" };
1268
1345
  function sameRange(a, b) {
1269
1346
  return a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.offsetBefore === b.offsetBefore && a.offsetAfter === b.offsetAfter;
1270
1347
  }
@@ -1274,13 +1351,12 @@ function createRowVirtualizer(scrollElement, options) {
1274
1351
  let scrollTop = scrollElement.scrollTop;
1275
1352
  let viewportHeight = scrollElement.clientHeight;
1276
1353
  let range = null;
1277
- let destroyed = false;
1278
- let pendingScroll;
1354
+ let phase = READY;
1279
1355
  const elements = /* @__PURE__ */ new Map();
1280
1356
  const indexByElement = /* @__PURE__ */ new Map();
1281
1357
  const refCallbacks = /* @__PURE__ */ new Map();
1282
1358
  function recompute() {
1283
- if (destroyed) {
1359
+ if (phase.kind === "destroyed") {
1284
1360
  return;
1285
1361
  }
1286
1362
  const next = visibleRange(
@@ -1331,6 +1407,9 @@ function createRowVirtualizer(scrollElement, options) {
1331
1407
  recompute();
1332
1408
  return {
1333
1409
  setRowCount(count) {
1410
+ if (phase.kind === "awaiting-row" && phase.index >= count) {
1411
+ phase = READY;
1412
+ }
1334
1413
  heights = Array.from({ length: count }, (_, index) => heights[index]);
1335
1414
  recompute();
1336
1415
  },
@@ -1348,15 +1427,15 @@ function createRowVirtualizer(scrollElement, options) {
1348
1427
  indexByElement.delete(previous);
1349
1428
  elements.delete(index);
1350
1429
  }
1351
- if (element === null || destroyed) {
1430
+ if (element === null || phase.kind === "destroyed") {
1352
1431
  return;
1353
1432
  }
1354
1433
  elements.set(index, element);
1355
1434
  indexByElement.set(element, index);
1356
1435
  rowObserver.observe(element);
1357
- if (pendingScroll?.index === index) {
1358
- element.scrollIntoView(pendingScroll.options);
1359
- pendingScroll = void 0;
1436
+ if (phase.kind === "awaiting-row" && phase.index === index) {
1437
+ element.scrollIntoView(phase.options);
1438
+ phase = READY;
1360
1439
  }
1361
1440
  };
1362
1441
  refCallbacks.set(index, callback);
@@ -1364,21 +1443,21 @@ function createRowVirtualizer(scrollElement, options) {
1364
1443
  return callback;
1365
1444
  },
1366
1445
  scrollToIndex(index, scrollOptions) {
1367
- if (heights.length === 0) {
1446
+ if (phase.kind === "destroyed" || heights.length === 0) {
1368
1447
  return;
1369
1448
  }
1370
1449
  const clamped = Math.max(0, Math.min(index, heights.length - 1));
1371
1450
  const mounted = elements.get(clamped);
1372
1451
  if (mounted !== void 0) {
1452
+ phase = READY;
1373
1453
  mounted.scrollIntoView(scrollOptions);
1374
1454
  return;
1375
1455
  }
1376
- pendingScroll = { index: clamped, options: scrollOptions };
1456
+ phase = { kind: "awaiting-row", index: clamped, options: scrollOptions };
1377
1457
  scrollElement.scrollTop = estimateOffsetBefore(clamped);
1378
1458
  },
1379
1459
  destroy() {
1380
- destroyed = true;
1381
- pendingScroll = void 0;
1460
+ phase = { kind: "destroyed" };
1382
1461
  scrollElement.removeEventListener("scroll", handleScroll);
1383
1462
  viewportObserver.disconnect();
1384
1463
  rowObserver.disconnect();
@@ -1607,17 +1686,26 @@ function createFrame(bodyHtml) {
1607
1686
  }
1608
1687
  function load(frame) {
1609
1688
  return new Promise((resolve, reject) => {
1610
- frame.addEventListener(
1611
- "load",
1612
- () => {
1613
- if (frame.contentWindow === null) {
1614
- reject(new Error("The print frame has no window."));
1615
- } else {
1616
- resolve(frame.contentWindow);
1617
- }
1618
- },
1619
- { once: true }
1620
- );
1689
+ const removal = new MutationObserver(() => {
1690
+ if (!frame.isConnected) {
1691
+ settle();
1692
+ reject(new Error("The print frame was removed before it loaded."));
1693
+ }
1694
+ });
1695
+ function loaded() {
1696
+ settle();
1697
+ if (frame.contentWindow === null) {
1698
+ reject(new Error("The print frame has no window."));
1699
+ } else {
1700
+ resolve(frame.contentWindow);
1701
+ }
1702
+ }
1703
+ function settle() {
1704
+ removal.disconnect();
1705
+ frame.removeEventListener("load", loaded);
1706
+ }
1707
+ frame.addEventListener("load", loaded);
1708
+ removal.observe(document.body, { childList: true });
1621
1709
  document.body.append(frame);
1622
1710
  });
1623
1711
  }
@@ -1628,7 +1716,7 @@ function fill(view, { title, styles }) {
1628
1716
  style.textContent = styles;
1629
1717
  frameDocument.head.append(style);
1630
1718
  }
1631
- function print(view) {
1719
+ async function print(view) {
1632
1720
  const listeners = [
1633
1721
  [view, "afterprint"],
1634
1722
  ...RETURN_EVENTS.flatMap((type) => [
@@ -1636,19 +1724,22 @@ function print(view) {
1636
1724
  [window, type]
1637
1725
  ])
1638
1726
  ];
1639
- return new Promise((resolve) => {
1640
- function done() {
1641
- for (const [target, type] of listeners) {
1642
- target.removeEventListener(type, done, { capture: true });
1643
- }
1644
- resolve();
1645
- }
1646
- for (const [target, type] of listeners) {
1647
- target.addEventListener(type, done, { capture: true });
1648
- }
1727
+ let done = () => void 0;
1728
+ const closed = new Promise((resolve) => {
1729
+ done = resolve;
1730
+ });
1731
+ for (const [target, type] of listeners) {
1732
+ target.addEventListener(type, done, { capture: true });
1733
+ }
1734
+ try {
1649
1735
  view.focus();
1650
1736
  view.print();
1651
- });
1737
+ await closed;
1738
+ } finally {
1739
+ for (const [target, type] of listeners) {
1740
+ target.removeEventListener(type, done, { capture: true });
1741
+ }
1742
+ }
1652
1743
  }
1653
1744
  async function printDocument(options) {
1654
1745
  if (printing) {