@gridkitjs/dom 0.0.0-dev-20261108230616 → 0.0.0-dev-20261108231420
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 +43 -18
- package/dist/index.js +173 -82
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
502
|
-
* `isLoadingMore`
|
|
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
|
|
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
|
|
523
|
-
*
|
|
529
|
+
* `onLoadMore` calls, no more than one per approach while no load is in
|
|
530
|
+
* flight.
|
|
524
531
|
*
|
|
525
|
-
*
|
|
526
|
-
* observer's last report),
|
|
527
|
-
*
|
|
528
|
-
*
|
|
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,
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
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
|
|
782
|
+
if (state.kind !== "idle" || !intersecting || !latest.hasMore) {
|
|
746
783
|
return;
|
|
747
784
|
}
|
|
748
|
-
|
|
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
|
|
771
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
if (wasLoading && !loading) {
|
|
787
|
-
fired = false;
|
|
788
|
-
maybeLoadMore();
|
|
826
|
+
if (state.kind === "detached") {
|
|
827
|
+
return;
|
|
789
828
|
}
|
|
790
|
-
|
|
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
|
|
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
|
|
831
|
-
let
|
|
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
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
-
|
|
985
|
+
type(event.key, all, from);
|
|
904
986
|
} else {
|
|
905
|
-
return;
|
|
987
|
+
return false;
|
|
906
988
|
}
|
|
907
|
-
|
|
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 ===
|
|
1011
|
+
if (hover.kind !== "away" && hover.item === item) {
|
|
930
1012
|
return;
|
|
931
1013
|
}
|
|
932
|
-
|
|
933
|
-
window.clearTimeout(hoverTimer);
|
|
1014
|
+
endHover();
|
|
934
1015
|
if (item !== null) {
|
|
935
|
-
|
|
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
|
-
|
|
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
|
-
|
|
965
|
-
|
|
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 (
|
|
1260
|
-
|
|
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
|
|
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 (
|
|
1358
|
-
element.scrollIntoView(
|
|
1359
|
-
|
|
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
|
-
|
|
1456
|
+
phase = { kind: "awaiting-row", index: clamped, options: scrollOptions };
|
|
1377
1457
|
scrollElement.scrollTop = estimateOffsetBefore(clamped);
|
|
1378
1458
|
},
|
|
1379
1459
|
destroy() {
|
|
1380
|
-
|
|
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
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
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
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
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) {
|