virtua 0.52.9 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +286 -193
  2. package/lib/angular/VList.d.ts +3 -1
  3. package/lib/angular/VMasonry.d.ts +114 -0
  4. package/lib/angular/Virtualizer.d.ts +3 -1
  5. package/lib/angular/WindowVirtualizer.d.ts +3 -1
  6. package/lib/angular/index.d.ts +1 -0
  7. package/lib/angular/index.js +812 -429
  8. package/lib/angular/index.js.map +1 -1
  9. package/lib/core/index.cjs +215 -137
  10. package/lib/core/index.cjs.map +1 -1
  11. package/lib/core/index.d.ts +1 -0
  12. package/lib/core/index.js +282 -204
  13. package/lib/core/index.js.map +1 -1
  14. package/lib/core/layouts/masonry.d.ts +1 -0
  15. package/lib/index.cjs +595 -423
  16. package/lib/index.cjs.map +1 -1
  17. package/lib/index.js +594 -424
  18. package/lib/index.js.map +1 -1
  19. package/lib/react/VMasonry.d.ts +105 -0
  20. package/lib/react/Virtualizer.d.ts +3 -1
  21. package/lib/react/WindowVirtualizer.d.ts +3 -1
  22. package/lib/react/index.d.ts +2 -0
  23. package/lib/react/types.d.ts +1 -1
  24. package/lib/solid/VMasonry.d.ts +107 -0
  25. package/lib/solid/Virtualizer.d.ts +3 -1
  26. package/lib/solid/WindowVirtualizer.d.ts +3 -1
  27. package/lib/solid/index.cjs +749 -541
  28. package/lib/solid/index.cjs.map +1 -1
  29. package/lib/solid/index.d.ts +2 -0
  30. package/lib/solid/index.js +704 -505
  31. package/lib/solid/index.js.map +1 -1
  32. package/lib/solid/index.jsx +345 -8
  33. package/lib/solid/index.jsx.map +1 -1
  34. package/lib/solid/types.d.ts +1 -1
  35. package/lib/svelte/MasonryItem.svelte +49 -0
  36. package/lib/svelte/VGrid.svelte +1 -1
  37. package/lib/svelte/VList.svelte +1 -1
  38. package/lib/svelte/VMasonry.svelte +182 -0
  39. package/lib/svelte/VMasonry.type.d.ts +104 -0
  40. package/lib/svelte/Virtualizer.svelte +1 -1
  41. package/lib/svelte/Virtualizer.type.d.ts +3 -1
  42. package/lib/svelte/WindowVirtualizer.svelte +1 -1
  43. package/lib/svelte/WindowVirtualizer.type.d.ts +3 -1
  44. package/lib/svelte/index.d.ts +2 -0
  45. package/lib/svelte/index.js +2 -1
  46. package/lib/svelte/types.d.ts +2 -2
  47. package/lib/vue/VMasonry.d.ts +110 -0
  48. package/lib/vue/Virtualizer.d.ts +3 -1
  49. package/lib/vue/WindowVirtualizer.d.ts +3 -1
  50. package/lib/vue/index.cjs +666 -442
  51. package/lib/vue/index.cjs.map +1 -1
  52. package/lib/vue/index.d.ts +2 -0
  53. package/lib/vue/index.js +695 -476
  54. package/lib/vue/index.js.map +1 -1
  55. package/package.json +6 -16
@@ -903,6 +903,167 @@ var getBoxStyle = (from, to, inset, edge, zIndex) => {
903
903
  return style;
904
904
  };
905
905
 
906
+ //#endregion
907
+ //#region src/core/layouts/masonry.ts
908
+ var createMasonryLayout = (length, lanesOpt, gap = 0, itemSize, snapshot) => {
909
+ let lanes = max(lanesOpt, 1);
910
+ let nextLanes = lanes;
911
+ let nextGap = gap;
912
+ let defaultItemSize = snapshot && snapshot[1] || itemSize || 40;
913
+ let computedIndex = -1;
914
+ let estimatingIndexes = itemSize ? undefined : new Set();
915
+ let isEstimating = !itemSize;
916
+ const restoredSizes = snapshot && snapshot[0];
917
+ const sizes = restoredSizes ? fill(restoredSizes.slice(0, min(length, restoredSizes.length)), max(0, length - restoredSizes.length)) : fill([], length);
918
+ const offsets = [];
919
+ const laneIndexes = [];
920
+ const highs = [];
921
+ const getSize = (index) => {
922
+ const size = sizes[index];
923
+ return size === -1 ? defaultItemSize : size;
924
+ };
925
+ const compute = (index) => {
926
+ index = min(index, length - 1);
927
+ if (computedIndex >= index) return;
928
+ const bottoms = [];
929
+ const lastIndexes = [];
930
+ for (let l = 0; l < lanes; l++) {
931
+ bottoms.push(0);
932
+ lastIndexes.push(-1);
933
+ }
934
+ for (let j = computedIndex, remaining = lanes; j >= 0 && remaining; j--) {
935
+ const l = laneIndexes[j];
936
+ if (lastIndexes[l] === -1) {
937
+ lastIndexes[l] = j;
938
+ bottoms[l] = offsets[j] + getSize(j);
939
+ remaining--;
940
+ }
941
+ }
942
+ while (computedIndex < index) {
943
+ const i = ++computedIndex;
944
+ let lane = 0;
945
+ let offset = Infinity;
946
+ let lastIndex = Infinity;
947
+ for (let l = 0; l < lanes; l++) {
948
+ const candidate = bottoms[l] + (lastIndexes[l] >= 0 ? gap : 0);
949
+ if (candidate < offset || candidate === offset && lastIndexes[l] < lastIndex) {
950
+ lane = l;
951
+ offset = candidate;
952
+ lastIndex = lastIndexes[l];
953
+ }
954
+ }
955
+ offsets[i] = offset;
956
+ laneIndexes[i] = lane;
957
+ bottoms[lane] = offset + getSize(i);
958
+ lastIndexes[lane] = i;
959
+ highs[i] = max(i ? highs[i - 1] : 0, bottoms[lane]);
960
+ }
961
+ };
962
+ const getOffset = (index) => {
963
+ if (index >= length) return getTotalSize();
964
+ compute(index);
965
+ return offsets[index];
966
+ };
967
+ const getHigh = (index) => {
968
+ compute(index);
969
+ return highs[index];
970
+ };
971
+ const getTotalSize = () => length ? getHigh(length - 1) : 0;
972
+ const findStart = (offset) => {
973
+ if (!length) return 0;
974
+ const index = findIndex(getHigh, length, offset);
975
+ return getHigh(index) <= offset ? index + 1 : index;
976
+ };
977
+ return {
978
+ $getRange: (startOffset, endOffset) => {
979
+ const end = findIndex(getOffset, length, endOffset);
980
+ return [min(findStart(startOffset), end), end];
981
+ },
982
+ $findIndex: (offset) => findIndex(getOffset, length, offset),
983
+ $getItemOffset: getOffset,
984
+ $getItemSize: getSize,
985
+ $isSizeEqual: (index, size = -1) => sizes[index] === size,
986
+ $resize: (resizes, shouldKeep, scrollOffset, viewportSize) => {
987
+ let anchorIndex = findStart(scrollOffset);
988
+ while (anchorIndex > 0 && !shouldKeep(anchorIndex - 1)) {
989
+ anchorIndex--;
990
+ }
991
+ while (anchorIndex < length && shouldKeep(anchorIndex)) {
992
+ anchorIndex++;
993
+ }
994
+ const prevAnchorOffset = getOffset(anchorIndex);
995
+ for (const [index, size] of resizes) {
996
+ sizes[index] = size;
997
+ estimatingIndexes && estimatingIndexes.add(index);
998
+ computedIndex = min(index - 1, computedIndex);
999
+ }
1000
+ let jump = getOffset(anchorIndex) - prevAnchorOffset;
1001
+ if (estimatingIndexes && viewportSize) {
1002
+ const measuredSizes = [];
1003
+ let totalMeasuredSize = 0;
1004
+ estimatingIndexes.forEach((index) => {
1005
+ const size = sizes[index];
1006
+ if (size > 0) {
1007
+ measuredSizes.push(size);
1008
+ totalMeasuredSize += size + gap;
1009
+ }
1010
+ });
1011
+ if (totalMeasuredSize > viewportSize * lanes) {
1012
+ sort(measuredSizes);
1013
+ const len = measuredSizes.length;
1014
+ const mid = len / 2 | 0;
1015
+ const median = len % 2 === 0 ? (measuredSizes[mid - 1] + measuredSizes[mid]) / 2 : measuredSizes[mid];
1016
+ const startIndex = findStart(scrollOffset + jump);
1017
+ const prevStartOffset = getOffset(startIndex);
1018
+ defaultItemSize = median;
1019
+ computedIndex = -1;
1020
+ jump += getOffset(startIndex) - prevStartOffset;
1021
+ estimatingIndexes = undefined;
1022
+ isEstimating = false;
1023
+ }
1024
+ }
1025
+ return jump;
1026
+ },
1027
+ $getTotalSize: getTotalSize,
1028
+ $getLength: () => length,
1029
+ $setLength: (nextLength) => {
1030
+ const diff = nextLength - length;
1031
+ computedIndex = min(nextLength - 1, computedIndex);
1032
+ length = nextLength;
1033
+ if (diff > 0) {
1034
+ fill(sizes, diff);
1035
+ } else {
1036
+ sizes.splice(diff);
1037
+ }
1038
+ return 0;
1039
+ },
1040
+ $relayout: (_, scrollOffset) => {
1041
+ if (nextLanes === lanes && nextGap === gap) {
1042
+ return;
1043
+ }
1044
+ const startIndex = findStart(scrollOffset);
1045
+ const prevStartOffset = getOffset(startIndex);
1046
+ lanes = nextLanes;
1047
+ gap = nextGap;
1048
+ computedIndex = -1;
1049
+ estimatingIndexes = itemSize ? undefined : new Set();
1050
+ return getOffset(startIndex) - prevStartOffset;
1051
+ },
1052
+ $setOptions: (lanes, gap = 0) => {
1053
+ nextLanes = max(lanes, 1);
1054
+ nextGap = gap;
1055
+ },
1056
+ $isEstimating: () => isEstimating,
1057
+ $snapshot: () => [sizes.slice(), defaultItemSize],
1058
+ $getLanes: () => lanes,
1059
+ $getGap: () => gap,
1060
+ $getItemLane: (index) => {
1061
+ compute(index);
1062
+ return laneIndexes[index];
1063
+ }
1064
+ };
1065
+ };
1066
+
906
1067
  //#endregion
907
1068
  //#region src/core/observer.ts
908
1069
  var createResizeObserver = (cb) => {
@@ -988,6 +1149,7 @@ var createScrollObserver = (store, viewport, scroller, isHorizontal, isRtl, onMo
988
1149
  viewport.addEventListener("touchstart", onTouchStart, { passive: true });
989
1150
  viewport.addEventListener("touchend", onTouchEnd, { passive: true });
990
1151
  return {
1152
+ _sync: onScroll,
991
1153
  _dispose: () => {
992
1154
  viewport.removeEventListener("scroll", onScroll);
993
1155
  viewport.removeEventListener("wheel", onWheel);
@@ -1070,10 +1232,8 @@ var createScrollObserver = (store, viewport, scroller, isHorizontal, isRtl, onMo
1070
1232
  };
1071
1233
  if (smooth) {
1072
1234
  store.$update(8, getTargetOffset());
1073
- microtask(start);
1074
- } else {
1075
- start();
1076
1235
  }
1236
+ start();
1077
1237
  }
1078
1238
  };
1079
1239
  };
@@ -1084,13 +1244,20 @@ var createContainerDriver = (store, isHorizontal) => {
1084
1244
  let viewportElement;
1085
1245
  let scrollObserver;
1086
1246
  let initialized = createPromise();
1247
+ let isViewportDisplayed = true;
1087
1248
  const mountedIndexes = new WeakMap();
1088
1249
  const resizeObserver = createResizeObserver((entries) => {
1089
1250
  const resizes = [];
1090
1251
  for (const { target, contentRect: { width, height } } of entries) {
1091
1252
  if (target === viewportElement) {
1092
1253
  if (width || height) {
1254
+ if (!isViewportDisplayed) {
1255
+ scrollObserver._sync();
1256
+ }
1257
+ isViewportDisplayed = true;
1093
1258
  store.$update(4, isHorizontal ? width : height);
1259
+ } else {
1260
+ isViewportDisplayed = false;
1094
1261
  }
1095
1262
  } else if (target.offsetParent) {
1096
1263
  const index = mountedIndexes.get(target);
@@ -1228,6 +1395,7 @@ var createContainerGridDriver = (rowStore, colStore) => {
1228
1395
  let rowScrollObserver;
1229
1396
  let colScrollObserver;
1230
1397
  let initialized = createPromise();
1398
+ let isViewportDisplayed = true;
1231
1399
  const mountedRowIndexes = new WeakMap();
1232
1400
  const mountedColIndexes = new WeakMap();
1233
1401
  const resizeObserver = createResizeObserver((entries) => {
@@ -1236,8 +1404,15 @@ var createContainerGridDriver = (rowStore, colStore) => {
1236
1404
  for (const { target, contentRect: { width, height } } of entries) {
1237
1405
  if (target === viewportElement) {
1238
1406
  if (width || height) {
1407
+ if (!isViewportDisplayed) {
1408
+ rowScrollObserver._sync();
1409
+ colScrollObserver._sync();
1410
+ }
1411
+ isViewportDisplayed = true;
1239
1412
  rowStore.$update(4, height);
1240
1413
  colStore.$update(4, width);
1414
+ } else {
1415
+ isViewportDisplayed = false;
1241
1416
  }
1242
1417
  } else if (target.offsetParent) {
1243
1418
  const rowIndex = mountedRowIndexes.get(target);
@@ -1263,9 +1438,8 @@ var createContainerGridDriver = (rowStore, colStore) => {
1263
1438
  (isHorizontal ? colScrollObserver : rowScrollObserver)._scroll(getTargetOffset);
1264
1439
  }
1265
1440
  },
1266
- $observe(containerElement) {
1267
- const viewport = viewportElement = containerElement.parentElement;
1268
- resizeObserver._observe(viewport);
1441
+ $observe(containerElement, viewport = containerElement.parentElement) {
1442
+ resizeObserver._observe(viewportElement = viewport);
1269
1443
  const observe = (store, isHorizontal) => createScrollObserver(store, viewport, viewport, isHorizontal, isHorizontal && getComputedStyle(viewport).direction === "rtl", () => {
1270
1444
  const overflowKey = isHorizontal ? "overflowX" : "overflowY";
1271
1445
  const style = viewport.style;
@@ -1586,7 +1760,7 @@ var VList = (props) => {
1586
1760
  "style"
1587
1761
  ]);
1588
1762
  return <div {...others} style={{
1589
- display: local.horizontal ? "inline-block" : "block",
1763
+ display: "block",
1590
1764
  [local.horizontal ? "overflow-x" : "overflow-y"]: "auto",
1591
1765
  contain: "strict",
1592
1766
  width: "100%",
@@ -1902,5 +2076,168 @@ var VGrid = (props) => {
1902
2076
  };
1903
2077
 
1904
2078
  //#endregion
1905
- export { VGrid, VList, Virtualizer, WindowVirtualizer };
2079
+ //#region src/solid/VMasonry.tsx
2080
+ var toCrossValue = (fraction, px) => px ? `calc(${fraction * 100}% + ${px}px)` : fraction * 100 + "%";
2081
+ var MasonryItem = (props) => {
2082
+ let elementRef;
2083
+ createEffect(() => {
2084
+ if (!elementRef) return;
2085
+ onCleanup(props._resizer(elementRef, props._index));
2086
+ });
2087
+ const style = createMemo(() => ({
2088
+ contain: "layout style",
2089
+ position: "absolute",
2090
+ top: props._offset + "px",
2091
+ "inset-inline-start": props._crossOffset,
2092
+ width: props._crossSize,
2093
+ visibility: props._hide ? "hidden" : undefined
2094
+ }));
2095
+ return <div ref={elementRef} style={style()}>
2096
+ {props._children}
2097
+ </div>;
2098
+ };
2099
+ var VMasonry = (props) => {
2100
+ let containerRef;
2101
+ const { itemSize, cache } = props;
2102
+ const [, others] = splitProps(props, [
2103
+ "ref",
2104
+ "data",
2105
+ "children",
2106
+ "lanes",
2107
+ "gap",
2108
+ "itemSize",
2109
+ "bufferSize",
2110
+ "cache",
2111
+ "onScroll",
2112
+ "onScrollEnd",
2113
+ "style"
2114
+ ]);
2115
+ const layout = createMasonryLayout(props.data.length, props.lanes, props.gap, itemSize, cache);
2116
+ const store = createVirtualStore(layout);
2117
+ const driver = createContainerDriver(store, false);
2118
+ const [stateVersion, setRerender] = createSignal(store.$getStateVersion());
2119
+ store.$subscribe(1, () => {
2120
+ setRerender(store.$getStateVersion());
2121
+ });
2122
+ store.$subscribe(4, () => {
2123
+ props.onScroll?.(store.$getScrollOffset());
2124
+ });
2125
+ store.$subscribe(8, () => {
2126
+ props.onScrollEnd?.();
2127
+ });
2128
+ createComputed(() => {
2129
+ layout.$setOptions(props.lanes, props.gap);
2130
+ store.$update(9, undefined);
2131
+ });
2132
+ const range = createMemo((prev) => {
2133
+ stateVersion();
2134
+ const next = store.$getRange(props.bufferSize);
2135
+ if (prev && isSameRange(prev, next)) {
2136
+ return prev;
2137
+ }
2138
+ return next;
2139
+ });
2140
+ const isScrolling = createMemo(() => stateVersion() && store.$isScrolling());
2141
+ const totalSize = createMemo(() => stateVersion() && store.$getTotalSize());
2142
+ const lanes = createMemo(() => stateVersion() && layout.$getLanes());
2143
+ const gap = createMemo(() => stateVersion() && layout.$getGap());
2144
+ const crossSize = createMemo(() => toCrossValue(1 / lanes(), gap() / lanes() - gap()));
2145
+ const ref = props.ref;
2146
+ if (!isServer && ref) {
2147
+ ref({
2148
+ get cache() {
2149
+ return layout.$snapshot();
2150
+ },
2151
+ get scrollOffset() {
2152
+ return store.$getScrollOffset();
2153
+ },
2154
+ get scrollSize() {
2155
+ return getScrollSize(store);
2156
+ },
2157
+ get viewportSize() {
2158
+ return store.$getViewportSize();
2159
+ },
2160
+ getItemOffset: store.$getItemOffset,
2161
+ getItemSize: store.$getItemSize,
2162
+ scrollToIndex: (index, opts) => scrollToIndex(driver, store, index, opts),
2163
+ scrollTo: (offset) => scrollTo(driver, offset),
2164
+ scrollBy: (offset) => scrollBy(driver, store, offset)
2165
+ });
2166
+ onCleanup(() => ref());
2167
+ }
2168
+ onMount(() => {
2169
+ driver.$observe(containerRef);
2170
+ onCleanup(() => {
2171
+ store.$dispose();
2172
+ driver.$dispose();
2173
+ });
2174
+ });
2175
+ createEffect(on(stateVersion, () => {
2176
+ driver.$effect();
2177
+ }));
2178
+ const dataSlice = createMemo(() => {
2179
+ const count = props.data.length;
2180
+ untrack(() => {
2181
+ if (count !== store.$getItemsLength()) {
2182
+ store.$update(5, [count]);
2183
+ }
2184
+ });
2185
+ const items = [];
2186
+ const indexes = [];
2187
+ for (let [i, j] = range(); i <= j; i++) {
2188
+ items.push(props.data[i]);
2189
+ indexes.push(i);
2190
+ }
2191
+ return {
2192
+ _items: items,
2193
+ _indexes: indexes
2194
+ };
2195
+ });
2196
+ const renderItem = (data, index) => {
2197
+ const offset = createMemo(() => {
2198
+ stateVersion();
2199
+ return store.$getItemOffset(index());
2200
+ });
2201
+ const lane = createMemo(() => {
2202
+ stateVersion();
2203
+ return layout.$getItemLane(index());
2204
+ });
2205
+ const hide = createMemo(() => {
2206
+ stateVersion();
2207
+ return store.$isUnmeasuredItem(index());
2208
+ });
2209
+ const children = createMemo(() => {
2210
+ return untrack(() => props.children(data, index));
2211
+ });
2212
+ return <MasonryItem _index={index()} _resizer={driver.$observeItem} _offset={offset()} _crossOffset={toCrossValue(lane() / lanes(), lane() * gap() / lanes())} _crossSize={crossSize()} _hide={hide()} _children={children()} />;
2213
+ };
2214
+ return <div {...others} style={{
2215
+ display: "block",
2216
+ "overflow-y": "auto",
2217
+ contain: "strict",
2218
+ width: "100%",
2219
+ height: "100%",
2220
+ ...props.style
2221
+ }}>
2222
+ <div ref={containerRef} style={{
2223
+ contain: "size style",
2224
+ "overflow-anchor": "none",
2225
+ flex: "none",
2226
+ position: "relative",
2227
+ width: "100%",
2228
+ height: totalSize() + "px",
2229
+ "pointer-events": isScrolling() ? "none" : undefined
2230
+ }}>
2231
+ <For each={dataSlice()._items}>
2232
+ {(data, index) => {
2233
+ const itemIndex = createMemo(() => dataSlice()._indexes[index()]);
2234
+ return renderItem(data, itemIndex);
2235
+ }}
2236
+ </For>
2237
+ </div>
2238
+ </div>;
2239
+ };
2240
+
2241
+ //#endregion
2242
+ export { VGrid, VList, VMasonry, Virtualizer, WindowVirtualizer };
1906
2243
  //# sourceMappingURL=index.jsx.map