virtua 0.44.2 → 0.45.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/README.md +29 -6
- package/lib/core/index.cjs +1 -1
- package/lib/core/index.cjs.map +1 -1
- package/lib/core/index.js +1 -1
- package/lib/core/index.js.map +1 -1
- package/lib/index.cjs +1 -1
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/react/VList.d.ts +5 -2
- package/lib/react/Virtualizer.d.ts +9 -7
- package/lib/react/WindowVirtualizer.d.ts +9 -7
- package/lib/solid/Virtualizer.d.ts +1 -1
- package/lib/solid/WindowVirtualizer.d.ts +1 -1
- package/lib/solid/index.cjs +1 -1
- package/lib/solid/index.cjs.map +1 -1
- package/lib/solid/index.js +1 -1
- package/lib/solid/index.js.map +1 -1
- package/lib/solid/index.jsx +114 -173
- package/lib/solid/index.jsx.map +1 -1
- package/lib/svelte/Virtualizer.svelte +1 -1
- package/lib/svelte/Virtualizer.type.d.ts +1 -1
- package/lib/svelte/WindowVirtualizer.svelte +1 -1
- package/lib/svelte/WindowVirtualizer.type.d.ts +1 -1
- package/lib/vue/index.cjs +1 -1
- package/lib/vue/index.cjs.map +1 -1
- package/lib/vue/index.js +1 -1
- package/lib/vue/index.js.map +1 -1
- package/package.json +2 -2
package/lib/solid/index.jsx
CHANGED
|
@@ -292,12 +292,6 @@ const UPDATE_SCROLL_END_EVENT = 0b1000;
|
|
|
292
292
|
const getScrollSize = (store) => {
|
|
293
293
|
return max(store.$getTotalSize(), store.$getViewportSize());
|
|
294
294
|
};
|
|
295
|
-
/**
|
|
296
|
-
* @internal
|
|
297
|
-
*/
|
|
298
|
-
const isInitialMeasurementDone = (store) => {
|
|
299
|
-
return !!store.$getViewportSize();
|
|
300
|
-
};
|
|
301
295
|
/**
|
|
302
296
|
* @internal
|
|
303
297
|
*/
|
|
@@ -686,69 +680,81 @@ const createScrollObserver = (store, viewport, isHorizontal, getScrollOffset, up
|
|
|
686
680
|
},
|
|
687
681
|
};
|
|
688
682
|
};
|
|
689
|
-
|
|
690
|
-
* @internal
|
|
691
|
-
*/
|
|
692
|
-
const createScroller = (store, isHorizontal) => {
|
|
693
|
-
let viewportElement;
|
|
694
|
-
let scrollObserver;
|
|
683
|
+
const createScrollScheduler = (store, initialized, scroll) => {
|
|
695
684
|
let cancelScroll;
|
|
696
|
-
let initialized = createPromise();
|
|
697
|
-
const scrollOffsetKey = isHorizontal ? "scrollLeft" : "scrollTop";
|
|
698
|
-
const overflowKey = isHorizontal ? "overflowX" : "overflowY";
|
|
699
685
|
// The given offset will be clamped by browser
|
|
700
686
|
// https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (cancelScroll) {
|
|
709
|
-
// Cancel waiting scrollTo
|
|
710
|
-
cancelScroll();
|
|
711
|
-
}
|
|
712
|
-
const waitForMeasurement = () => {
|
|
713
|
-
// Wait for the scroll destination items to be measured.
|
|
714
|
-
// The measurement will be done asynchronously and the timing is not predictable so we use promise.
|
|
715
|
-
const [promise, resolve] = createPromise();
|
|
716
|
-
cancelScroll = () => {
|
|
717
|
-
resolve(false);
|
|
718
|
-
};
|
|
719
|
-
// Resize event may not happen when the window/tab is not visible, or during browser back in Safari.
|
|
720
|
-
// We have to wait for the initial measurement to avoid failing imperative scroll on mount.
|
|
721
|
-
// https://github.com/inokawa/virtua/issues/450
|
|
722
|
-
if (isInitialMeasurementDone(store)) {
|
|
723
|
-
// Cancel when items around scroll destination completely measured
|
|
724
|
-
timeout(cancelScroll, 150);
|
|
687
|
+
return [
|
|
688
|
+
async (getTargetOffset, smooth) => {
|
|
689
|
+
// Wait for element assign. The element may be undefined if scrollRef prop is used and scroll is scheduled on mount.
|
|
690
|
+
// https://github.com/inokawa/virtua/pull/733
|
|
691
|
+
// https://github.com/inokawa/virtua/pull/750
|
|
692
|
+
if (!(await initialized())) {
|
|
693
|
+
return;
|
|
725
694
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
695
|
+
if (cancelScroll) {
|
|
696
|
+
// Cancel waiting scrollTo
|
|
697
|
+
cancelScroll();
|
|
698
|
+
}
|
|
699
|
+
const waitForMeasurement = () => {
|
|
700
|
+
// Wait for the scroll destination items to be measured.
|
|
701
|
+
// The measurement will be done asynchronously and the timing is not predictable so we use promise.
|
|
702
|
+
const [promise, resolve] = createPromise();
|
|
703
|
+
cancelScroll = () => {
|
|
704
|
+
resolve(false);
|
|
705
|
+
};
|
|
706
|
+
// Resize event may not happen when the window/tab is not visible, or during browser back in Safari.
|
|
707
|
+
// We have to wait for the initial measurement to avoid failing imperative scroll on mount.
|
|
708
|
+
// https://github.com/inokawa/virtua/issues/450
|
|
709
|
+
if (store.$getViewportSize()) {
|
|
710
|
+
// Cancel when items around scroll destination completely measured
|
|
711
|
+
timeout(cancelScroll, 150);
|
|
712
|
+
}
|
|
713
|
+
return [
|
|
714
|
+
promise,
|
|
715
|
+
store.$subscribe(UPDATE_SIZE_EVENT, () => {
|
|
716
|
+
resolve(true);
|
|
717
|
+
}),
|
|
718
|
+
];
|
|
719
|
+
};
|
|
720
|
+
if (smooth && isSmoothScrollSupported()) {
|
|
721
|
+
store.$update(ACTION_BEFORE_MANUAL_SMOOTH_SCROLL, getTargetOffset());
|
|
722
|
+
// https://github.com/inokawa/virtua/issues/590
|
|
723
|
+
microtask(async () => {
|
|
724
|
+
while (true) {
|
|
725
|
+
let done = true;
|
|
726
|
+
for (let [i, end] = store.$getRange(); i <= end; i++) {
|
|
727
|
+
if (store.$isUnmeasuredItem(i)) {
|
|
728
|
+
done = false;
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (done) {
|
|
742
733
|
break;
|
|
743
734
|
}
|
|
735
|
+
const [promise, unsubscribe] = waitForMeasurement();
|
|
736
|
+
try {
|
|
737
|
+
if (!(await promise)) {
|
|
738
|
+
// canceled
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
finally {
|
|
743
|
+
unsubscribe();
|
|
744
|
+
}
|
|
744
745
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
746
|
+
store.$update(ACTION_MANUAL_SCROLL);
|
|
747
|
+
scroll(getTargetOffset(), smooth);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
while (true) {
|
|
748
752
|
const [promise, unsubscribe] = waitForMeasurement();
|
|
749
753
|
try {
|
|
754
|
+
store.$update(ACTION_MANUAL_SCROLL);
|
|
755
|
+
scroll(getTargetOffset());
|
|
750
756
|
if (!(await promise)) {
|
|
751
|
-
// canceled
|
|
757
|
+
// canceled or finished
|
|
752
758
|
return;
|
|
753
759
|
}
|
|
754
760
|
}
|
|
@@ -756,30 +762,34 @@ const createScroller = (store, isHorizontal) => {
|
|
|
756
762
|
unsubscribe();
|
|
757
763
|
}
|
|
758
764
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
() => {
|
|
768
|
+
cancelScroll && cancelScroll();
|
|
769
|
+
},
|
|
770
|
+
];
|
|
771
|
+
};
|
|
772
|
+
/**
|
|
773
|
+
* @internal
|
|
774
|
+
*/
|
|
775
|
+
const createScroller = (store, isHorizontal) => {
|
|
776
|
+
let viewportElement;
|
|
777
|
+
let scrollObserver;
|
|
778
|
+
let initialized = createPromise();
|
|
779
|
+
const scrollOffsetKey = isHorizontal ? "scrollLeft" : "scrollTop";
|
|
780
|
+
const overflowKey = isHorizontal ? "overflowX" : "overflowY";
|
|
781
|
+
const [scheduleScroll, cancelScroll] = createScrollScheduler(store, () => initialized[0], (offset, smooth) => {
|
|
782
|
+
offset = normalizeOffset(offset, isHorizontal);
|
|
783
|
+
if (smooth) {
|
|
784
|
+
viewportElement.scrollTo({
|
|
785
|
+
[isHorizontal ? "left" : "top"]: offset,
|
|
786
|
+
behavior: "smooth",
|
|
764
787
|
});
|
|
765
788
|
}
|
|
766
789
|
else {
|
|
767
|
-
|
|
768
|
-
const [promise, unsubscribe] = waitForMeasurement();
|
|
769
|
-
try {
|
|
770
|
-
store.$update(ACTION_MANUAL_SCROLL);
|
|
771
|
-
viewportElement[scrollOffsetKey] = normalizeOffset(getTargetOffset(), isHorizontal);
|
|
772
|
-
if (!(await promise)) {
|
|
773
|
-
// canceled or finished
|
|
774
|
-
return;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
finally {
|
|
778
|
-
unsubscribe();
|
|
779
|
-
}
|
|
780
|
-
}
|
|
790
|
+
viewportElement[scrollOffsetKey] = offset;
|
|
781
791
|
}
|
|
782
|
-
};
|
|
792
|
+
});
|
|
783
793
|
return {
|
|
784
794
|
$observe(viewport) {
|
|
785
795
|
viewportElement = viewport;
|
|
@@ -801,7 +811,7 @@ const createScroller = (store, isHorizontal) => {
|
|
|
801
811
|
viewport[scrollOffsetKey] = store.$getScrollOffset() + jump;
|
|
802
812
|
if (shift) {
|
|
803
813
|
// https://github.com/inokawa/virtua/issues/357
|
|
804
|
-
cancelScroll
|
|
814
|
+
cancelScroll();
|
|
805
815
|
}
|
|
806
816
|
});
|
|
807
817
|
initialized[1](true);
|
|
@@ -813,11 +823,11 @@ const createScroller = (store, isHorizontal) => {
|
|
|
813
823
|
initialized = createPromise();
|
|
814
824
|
},
|
|
815
825
|
$scrollTo(offset) {
|
|
816
|
-
|
|
826
|
+
scheduleScroll(() => offset);
|
|
817
827
|
},
|
|
818
828
|
$scrollBy(offset) {
|
|
819
829
|
offset += store.$getScrollOffset();
|
|
820
|
-
|
|
830
|
+
scheduleScroll(() => offset);
|
|
821
831
|
},
|
|
822
832
|
$scrollToIndex(index, { align, smooth, offset = 0 } = {}) {
|
|
823
833
|
index = clamp(index, 0, store.$getItemsLength() - 1);
|
|
@@ -836,7 +846,7 @@ const createScroller = (store, isHorizontal) => {
|
|
|
836
846
|
return;
|
|
837
847
|
}
|
|
838
848
|
}
|
|
839
|
-
|
|
849
|
+
scheduleScroll(() => {
|
|
840
850
|
return (offset +
|
|
841
851
|
store.$getStartSpacerSize() +
|
|
842
852
|
store.$getItemOffset(index) +
|
|
@@ -858,8 +868,23 @@ const createScroller = (store, isHorizontal) => {
|
|
|
858
868
|
const createWindowScroller = (store, isHorizontal) => {
|
|
859
869
|
let containerElement;
|
|
860
870
|
let scrollObserver;
|
|
861
|
-
let cancelScroll;
|
|
862
871
|
let initialized = createPromise();
|
|
872
|
+
const scrollToKey = isHorizontal ? "left" : "top";
|
|
873
|
+
const [scheduleScroll] = createScrollScheduler(store, () => initialized[0], (offset, smooth) => {
|
|
874
|
+
offset = normalizeOffset(offset, isHorizontal);
|
|
875
|
+
const window = getCurrentWindow(getCurrentDocument(containerElement));
|
|
876
|
+
if (smooth) {
|
|
877
|
+
window.scroll({
|
|
878
|
+
[scrollToKey]: offset,
|
|
879
|
+
behavior: "smooth",
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
window.scroll({
|
|
884
|
+
[scrollToKey]: offset,
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
});
|
|
863
888
|
const calcOffsetToViewport = (node, viewport, window, isHorizontal, offset = 0) => {
|
|
864
889
|
// TODO calc offset only when it changes (maybe impossible)
|
|
865
890
|
const offsetKey = isHorizontal ? "offsetLeft" : "offsetTop";
|
|
@@ -873,104 +898,20 @@ const createWindowScroller = (store, isHorizontal) => {
|
|
|
873
898
|
}
|
|
874
899
|
return calcOffsetToViewport(parent, viewport, window, isHorizontal, offsetSum);
|
|
875
900
|
};
|
|
876
|
-
const scheduleImperativeScroll = async (getTargetOffset, smooth) => {
|
|
877
|
-
// Wait for element assign. The element may be undefined if scrollRef prop is used and scroll is scheduled on mount.
|
|
878
|
-
// https://github.com/inokawa/virtua/pull/733
|
|
879
|
-
// https://github.com/inokawa/virtua/pull/750
|
|
880
|
-
if (!(await initialized[0])) {
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
if (cancelScroll) {
|
|
884
|
-
cancelScroll();
|
|
885
|
-
}
|
|
886
|
-
const waitForMeasurement = () => {
|
|
887
|
-
// Wait for the scroll destination items to be measured.
|
|
888
|
-
// The measurement will be done asynchronously and the timing is not predictable so we use promise.
|
|
889
|
-
const [promise, resolve] = createPromise();
|
|
890
|
-
cancelScroll = () => {
|
|
891
|
-
resolve(false);
|
|
892
|
-
};
|
|
893
|
-
// Resize event may not happen when the window/tab is not visible, or during browser back in Safari.
|
|
894
|
-
// We have to wait for the initial measurement to avoid failing imperative scroll on mount.
|
|
895
|
-
// https://github.com/inokawa/virtua/issues/450
|
|
896
|
-
if (isInitialMeasurementDone(store)) {
|
|
897
|
-
// Cancel when items around scroll destination completely measured
|
|
898
|
-
timeout(cancelScroll, 150);
|
|
899
|
-
}
|
|
900
|
-
return [
|
|
901
|
-
promise,
|
|
902
|
-
store.$subscribe(UPDATE_SIZE_EVENT, () => {
|
|
903
|
-
resolve(true);
|
|
904
|
-
}),
|
|
905
|
-
];
|
|
906
|
-
};
|
|
907
|
-
const window = getCurrentWindow(getCurrentDocument(containerElement));
|
|
908
|
-
if (smooth && isSmoothScrollSupported()) {
|
|
909
|
-
store.$update(ACTION_BEFORE_MANUAL_SMOOTH_SCROLL, getTargetOffset());
|
|
910
|
-
// https://github.com/inokawa/virtua/issues/590
|
|
911
|
-
microtask(async () => {
|
|
912
|
-
while (true) {
|
|
913
|
-
let measuring = false;
|
|
914
|
-
for (let [i, end] = store.$getRange(); i <= end; i++) {
|
|
915
|
-
if (store.$isUnmeasuredItem(i)) {
|
|
916
|
-
measuring = true;
|
|
917
|
-
break;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
if (!measuring) {
|
|
921
|
-
break;
|
|
922
|
-
}
|
|
923
|
-
const [promise, unsubscribe] = waitForMeasurement();
|
|
924
|
-
try {
|
|
925
|
-
if (!(await promise)) {
|
|
926
|
-
// canceled
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
finally {
|
|
931
|
-
unsubscribe();
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
store.$update(ACTION_MANUAL_SCROLL);
|
|
935
|
-
window.scroll({
|
|
936
|
-
[isHorizontal ? "left" : "top"]: normalizeOffset(getTargetOffset(), isHorizontal),
|
|
937
|
-
behavior: "smooth",
|
|
938
|
-
});
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
else {
|
|
942
|
-
while (true) {
|
|
943
|
-
const [promise, unsubscribe] = waitForMeasurement();
|
|
944
|
-
try {
|
|
945
|
-
store.$update(ACTION_MANUAL_SCROLL);
|
|
946
|
-
window.scroll({
|
|
947
|
-
[isHorizontal ? "left" : "top"]: normalizeOffset(getTargetOffset(), isHorizontal),
|
|
948
|
-
});
|
|
949
|
-
if (!(await promise)) {
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
finally {
|
|
954
|
-
unsubscribe();
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
901
|
return {
|
|
960
902
|
$observe(container) {
|
|
961
903
|
containerElement = container;
|
|
962
904
|
const scrollOffsetKey = isHorizontal ? "scrollX" : "scrollY";
|
|
963
905
|
const document = getCurrentDocument(container);
|
|
964
906
|
const window = getCurrentWindow(document);
|
|
965
|
-
const documentBody = document.body;
|
|
966
907
|
scrollObserver = createScrollObserver(store, window, isHorizontal, () => normalizeOffset(window[scrollOffsetKey], isHorizontal), (jump) => {
|
|
967
908
|
// Use absolute position not to exceed scrollable bounds
|
|
968
909
|
// https://github.com/inokawa/virtua/discussions/475
|
|
969
910
|
// TODO support case two window scrollers exist in the same view
|
|
970
911
|
window.scroll({
|
|
971
|
-
[
|
|
912
|
+
[scrollToKey]: store.$getScrollOffset() + jump,
|
|
972
913
|
});
|
|
973
|
-
}, () => calcOffsetToViewport(container,
|
|
914
|
+
}, () => calcOffsetToViewport(container, document.body, window, isHorizontal));
|
|
974
915
|
initialized[1](true);
|
|
975
916
|
},
|
|
976
917
|
$dispose() {
|
|
@@ -1006,7 +947,7 @@ const createWindowScroller = (store, isHorizontal) => {
|
|
|
1006
947
|
const html = document.documentElement;
|
|
1007
948
|
const getScrollbarSize = () => store.$getViewportSize() -
|
|
1008
949
|
(isHorizontal ? html.clientWidth : html.clientHeight);
|
|
1009
|
-
|
|
950
|
+
scheduleScroll(() => {
|
|
1010
951
|
return (offset +
|
|
1011
952
|
// Calculate target scroll position including container's offset from document
|
|
1012
953
|
calcOffsetToViewport(containerElement, document.body, window, isHorizontal) +
|
|
@@ -1458,7 +1399,7 @@ const Virtualizer = (props) => {
|
|
|
1458
1399
|
return (<ListItem _as={props.item} _index={index()} _resizer={resizer.$observeItem} _offset={offset()} _hide={hide()} _children={children()} _isHorizontal={horizontal}/>);
|
|
1459
1400
|
};
|
|
1460
1401
|
return (<Dynamic component={props.as} ref={containerRef} style={{
|
|
1461
|
-
contain: "
|
|
1402
|
+
contain: "size paint style", // https://github.com/inokawa/virtua/pull/775
|
|
1462
1403
|
"overflow-anchor": "none", // opt out browser's scroll anchoring because it will conflict to scroll anchoring of virtualizer
|
|
1463
1404
|
overflow: "clip", // https://github.com/inokawa/virtua/pull/485 https://github.com/inokawa/virtua/issues/717
|
|
1464
1405
|
flex: "none", // flex style can break layout
|
|
@@ -1590,7 +1531,7 @@ const WindowVirtualizer = (props) => {
|
|
|
1590
1531
|
return items;
|
|
1591
1532
|
});
|
|
1592
1533
|
return (<div ref={containerRef} style={{
|
|
1593
|
-
contain: "
|
|
1534
|
+
contain: "size paint style", // https://github.com/inokawa/virtua/pull/775
|
|
1594
1535
|
"overflow-anchor": "none", // opt out browser's scroll anchoring because it will conflict to scroll anchoring of virtualizer
|
|
1595
1536
|
overflow: "clip", // https://github.com/inokawa/virtua/pull/485 https://github.com/inokawa/virtua/issues/717
|
|
1596
1537
|
flex: "none", // flex style can break layout
|