virtua 0.52.10 → 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 +733 -352
  8. package/lib/angular/index.js.map +1 -1
  9. package/lib/core/index.cjs +180 -104
  10. package/lib/core/index.cjs.map +1 -1
  11. package/lib/core/index.d.ts +1 -0
  12. package/lib/core/index.js +257 -181
  13. package/lib/core/index.js.map +1 -1
  14. package/lib/core/layouts/masonry.d.ts +1 -0
  15. package/lib/index.cjs +555 -385
  16. package/lib/index.cjs.map +1 -1
  17. package/lib/index.js +572 -404
  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 +720 -514
  28. package/lib/solid/index.cjs.map +1 -1
  29. package/lib/solid/index.d.ts +2 -0
  30. package/lib/solid/index.js +691 -494
  31. package/lib/solid/index.js.map +1 -1
  32. package/lib/solid/index.jsx +329 -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 +625 -403
  51. package/lib/vue/index.cjs.map +1 -1
  52. package/lib/vue/index.d.ts +2 -0
  53. package/lib/vue/index.js +672 -455
  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) => {
@@ -1071,10 +1232,8 @@ var createScrollObserver = (store, viewport, scroller, isHorizontal, isRtl, onMo
1071
1232
  };
1072
1233
  if (smooth) {
1073
1234
  store.$update(8, getTargetOffset());
1074
- microtask(start);
1075
- } else {
1076
- start();
1077
1235
  }
1236
+ start();
1078
1237
  }
1079
1238
  };
1080
1239
  };
@@ -1279,9 +1438,8 @@ var createContainerGridDriver = (rowStore, colStore) => {
1279
1438
  (isHorizontal ? colScrollObserver : rowScrollObserver)._scroll(getTargetOffset);
1280
1439
  }
1281
1440
  },
1282
- $observe(containerElement) {
1283
- const viewport = viewportElement = containerElement.parentElement;
1284
- resizeObserver._observe(viewport);
1441
+ $observe(containerElement, viewport = containerElement.parentElement) {
1442
+ resizeObserver._observe(viewportElement = viewport);
1285
1443
  const observe = (store, isHorizontal) => createScrollObserver(store, viewport, viewport, isHorizontal, isHorizontal && getComputedStyle(viewport).direction === "rtl", () => {
1286
1444
  const overflowKey = isHorizontal ? "overflowX" : "overflowY";
1287
1445
  const style = viewport.style;
@@ -1602,7 +1760,7 @@ var VList = (props) => {
1602
1760
  "style"
1603
1761
  ]);
1604
1762
  return <div {...others} style={{
1605
- display: local.horizontal ? "inline-block" : "block",
1763
+ display: "block",
1606
1764
  [local.horizontal ? "overflow-x" : "overflow-y"]: "auto",
1607
1765
  contain: "strict",
1608
1766
  width: "100%",
@@ -1918,5 +2076,168 @@ var VGrid = (props) => {
1918
2076
  };
1919
2077
 
1920
2078
  //#endregion
1921
- 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 };
1922
2243
  //# sourceMappingURL=index.jsx.map