@legendapp/list 3.3.0 → 3.3.2

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.
@@ -127,6 +127,7 @@ var createAnimatedValue = (value) => value;
127
127
 
128
128
  // src/state/state.tsx
129
129
  var ContextState = React3.createContext(null);
130
+ var SIGNAL_NAMES_SEPARATOR = "\0";
130
131
  var contextNum = 0;
131
132
  function StateProvider({ children }) {
132
133
  const [value] = React3.useState(() => ({
@@ -198,6 +199,12 @@ function createSelectorFunctionsArr(ctx, signalNames) {
198
199
  }
199
200
  };
200
201
  }
202
+ function getSignalNamesKey(signalNames) {
203
+ return signalNames.length === 1 ? signalNames[0] : signalNames.join(SIGNAL_NAMES_SEPARATOR);
204
+ }
205
+ function getSignalNamesFromKey(signalNamesKey) {
206
+ return signalNamesKey.split(SIGNAL_NAMES_SEPARATOR);
207
+ }
201
208
  function listen$(ctx, signalName, cb) {
202
209
  const { listeners } = ctx;
203
210
  let setListeners = listeners.get(signalName);
@@ -245,7 +252,11 @@ function notifyPosition$(ctx, key, value) {
245
252
  }
246
253
  function useArr$(signalNames) {
247
254
  const ctx = React3.useContext(ContextState);
248
- const { subscribe, get } = React3.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
255
+ const signalNamesKey = getSignalNamesKey(signalNames);
256
+ const { subscribe, get } = React3.useMemo(
257
+ () => createSelectorFunctionsArr(ctx, getSignalNamesFromKey(signalNamesKey)),
258
+ [ctx, signalNamesKey]
259
+ );
249
260
  const value = useSyncExternalStore(subscribe, get, get);
250
261
  return value;
251
262
  }
@@ -532,9 +543,22 @@ function useInit(cb) {
532
543
 
533
544
  // src/state/ContextContainer.ts
534
545
  var ContextContainer = createContext(null);
546
+ var NO_CONTAINER_ID = -1;
535
547
  function useContextContainer() {
536
548
  return useContext(ContextContainer);
537
549
  }
550
+ function useContainerItemInfo(containerContext) {
551
+ var _a3;
552
+ const containerId = (_a3 = containerContext == null ? void 0 : containerContext.containerId) != null ? _a3 : NO_CONTAINER_ID;
553
+ const [itemInfo] = useArr$([`containerItemInfo${containerId}`]);
554
+ return containerContext ? itemInfo : void 0;
555
+ }
556
+ function useContainerItemKey(containerContext) {
557
+ var _a3;
558
+ const containerId = (_a3 = containerContext == null ? void 0 : containerContext.containerId) != null ? _a3 : NO_CONTAINER_ID;
559
+ const [itemKey] = useArr$([`containerItemKey${containerId}`]);
560
+ return containerContext ? itemKey : void 0;
561
+ }
538
562
  function useAdaptiveRender() {
539
563
  const [mode] = useArr$(["adaptiveRender"]);
540
564
  return mode;
@@ -605,40 +629,35 @@ function useViewabilityAmount(callback) {
605
629
  }
606
630
  function useRecyclingEffect(effect) {
607
631
  const containerContext = useContextContainer();
608
- const prevValues = useRef({
609
- prevIndex: void 0,
610
- prevItem: void 0
611
- });
632
+ const itemInfo = useContainerItemInfo(containerContext);
633
+ const prevInfo = useRef(void 0);
612
634
  useEffect(() => {
613
- if (!containerContext) {
635
+ if (!itemInfo) {
614
636
  return;
615
637
  }
616
- const { index, value } = containerContext;
617
638
  let ret;
618
- if (prevValues.current.prevIndex !== void 0 && prevValues.current.prevItem !== void 0) {
639
+ if (prevInfo.current) {
619
640
  ret = effect({
620
- index,
621
- item: value,
622
- prevIndex: prevValues.current.prevIndex,
623
- prevItem: prevValues.current.prevItem
641
+ index: itemInfo.index,
642
+ item: itemInfo.value,
643
+ prevIndex: prevInfo.current.index,
644
+ prevItem: prevInfo.current.value
624
645
  });
625
646
  }
626
- prevValues.current = {
627
- prevIndex: index,
628
- prevItem: value
629
- };
647
+ prevInfo.current = itemInfo;
630
648
  return ret;
631
- }, [effect, containerContext]);
649
+ }, [effect, itemInfo]);
632
650
  }
633
651
  function useRecyclingState(valueOrFun) {
634
- var _a3, _b;
652
+ var _a3;
635
653
  const containerContext = useContextContainer();
636
- const computeValue = (ctx) => {
654
+ const itemInfo = useContainerItemInfo(containerContext);
655
+ const computeValue = (info) => {
637
656
  if (isFunction(valueOrFun)) {
638
657
  const initializer = valueOrFun;
639
- return ctx ? initializer({
640
- index: ctx.index,
641
- item: ctx.value,
658
+ return info ? initializer({
659
+ index: info.index,
660
+ item: info.value,
642
661
  prevIndex: void 0,
643
662
  prevItem: void 0
644
663
  }) : initializer();
@@ -646,13 +665,12 @@ function useRecyclingState(valueOrFun) {
646
665
  return valueOrFun;
647
666
  };
648
667
  const [stateValue, setStateValue] = useState(() => {
649
- return computeValue(containerContext);
668
+ return computeValue(itemInfo);
650
669
  });
651
- const prevItemKeyRef = useRef((_a3 = containerContext == null ? void 0 : containerContext.itemKey) != null ? _a3 : null);
652
- const currentItemKey = (_b = containerContext == null ? void 0 : containerContext.itemKey) != null ? _b : null;
653
- if (currentItemKey !== null && prevItemKeyRef.current !== currentItemKey) {
654
- prevItemKeyRef.current = currentItemKey;
655
- setStateValue(computeValue(containerContext));
670
+ const prevItemKeyRef = useRef((_a3 = itemInfo == null ? void 0 : itemInfo.itemKey) != null ? _a3 : null);
671
+ if (itemInfo && prevItemKeyRef.current !== itemInfo.itemKey) {
672
+ prevItemKeyRef.current = itemInfo.itemKey;
673
+ setStateValue(computeValue(itemInfo));
656
674
  }
657
675
  const triggerLayout = containerContext == null ? void 0 : containerContext.triggerLayout;
658
676
  const setState = useCallback(
@@ -671,12 +689,10 @@ function useRecyclingState(valueOrFun) {
671
689
  }
672
690
  function useIsLastItem() {
673
691
  const containerContext = useContextContainer();
692
+ const itemKey = useContainerItemKey(containerContext);
674
693
  const isLast = useSelector$("lastItemKeys", (lastItemKeys) => {
675
- if (containerContext) {
676
- const { itemKey } = containerContext;
677
- if (!isNullOrUndefined(itemKey)) {
678
- return (lastItemKeys == null ? void 0 : lastItemKeys.includes(itemKey)) || false;
679
- }
694
+ if (containerContext && !isNullOrUndefined(itemKey)) {
695
+ return (lastItemKeys == null ? void 0 : lastItemKeys.includes(itemKey)) || false;
680
696
  }
681
697
  return false;
682
698
  });
@@ -690,12 +706,7 @@ var noop = () => {
690
706
  };
691
707
  function useSyncLayout() {
692
708
  const containerContext = useContextContainer();
693
- if (containerContext) {
694
- const { triggerLayout: syncLayout } = containerContext;
695
- return syncLayout;
696
- } else {
697
- return noop;
698
- }
709
+ return containerContext ? containerContext.triggerLayout : noop;
699
710
  }
700
711
 
701
712
  // src/components/Separator.tsx
@@ -1463,6 +1474,7 @@ function finishScrollTo(ctx) {
1463
1474
  const scrollingTo = state.scrollingTo;
1464
1475
  state.scrollHistory.length = 0;
1465
1476
  state.scrollingTo = void 0;
1477
+ state.scrollTargetPinnedRange = void 0;
1466
1478
  if (state.pendingTotalSize !== void 0) {
1467
1479
  addTotalSize(ctx, null, state.pendingTotalSize);
1468
1480
  }
@@ -1956,6 +1968,8 @@ function prepareMVCP(ctx, dataChanged) {
1956
1968
  }
1957
1969
 
1958
1970
  // src/utils/getScrollVelocity.ts
1971
+ var MAX_SCROLL_VELOCITY_WINDOW_MS = 1e3;
1972
+ var SCROLL_VELOCITY_HALF_LIFE_MS = 200;
1959
1973
  var getScrollVelocity = (state) => {
1960
1974
  const { scrollHistory } = state;
1961
1975
  const newestIndex = scrollHistory.length - 1;
@@ -1963,35 +1977,37 @@ var getScrollVelocity = (state) => {
1963
1977
  return 0;
1964
1978
  }
1965
1979
  const newest = scrollHistory[newestIndex];
1966
- const now = Date.now();
1967
- let direction = 0;
1968
- for (let i = newestIndex; i > 0; i--) {
1969
- const delta = scrollHistory[i].scroll - scrollHistory[i - 1].scroll;
1970
- if (delta !== 0) {
1971
- direction = Math.sign(delta);
1972
- break;
1973
- }
1974
- }
1975
- if (direction === 0) {
1980
+ if (Date.now() - newest.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1976
1981
  return 0;
1977
1982
  }
1978
- let oldest = newest;
1979
- for (let i = newestIndex - 1; i >= 0; i--) {
1983
+ let direction = 0;
1984
+ let weightedVelocity = 0;
1985
+ let totalWeight = 0;
1986
+ for (let i = newestIndex; i > 0; i--) {
1980
1987
  const current = scrollHistory[i];
1981
- const next = scrollHistory[i + 1];
1982
- const delta = next.scroll - current.scroll;
1983
- const deltaSign = Math.sign(delta);
1984
- if (deltaSign !== 0 && deltaSign !== direction) {
1985
- break;
1988
+ const previous = scrollHistory[i - 1];
1989
+ const scrollDiff = current.scroll - previous.scroll;
1990
+ const timeDiff = current.time - previous.time;
1991
+ const deltaSign = Math.sign(scrollDiff);
1992
+ if (deltaSign !== 0) {
1993
+ if (direction === 0) {
1994
+ direction = deltaSign;
1995
+ } else if (deltaSign !== direction) {
1996
+ break;
1997
+ }
1986
1998
  }
1987
- if (now - current.time > 1e3) {
1999
+ if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1988
2000
  break;
1989
2001
  }
1990
- oldest = current;
2002
+ if (scrollDiff === 0 || timeDiff <= 0) {
2003
+ continue;
2004
+ }
2005
+ const age = newest.time - current.time;
2006
+ const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
2007
+ weightedVelocity += scrollDiff / timeDiff * weight;
2008
+ totalWeight += weight;
1991
2009
  }
1992
- const scrollDiff = newest.scroll - oldest.scroll;
1993
- const timeDiff = newest.time - oldest.time;
1994
- return timeDiff > 0 ? scrollDiff / timeDiff : 0;
2010
+ return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
1995
2011
  };
1996
2012
 
1997
2013
  // src/core/updateScroll.ts
@@ -2108,8 +2124,93 @@ function syncInitialScrollNativeWatchdog(state, options) {
2108
2124
  initialScrollWatchdog.clear(state);
2109
2125
  }
2110
2126
  }
2111
- function scrollTo(ctx, params) {
2127
+ function findPositionIndexAtOrBeforeOffset(ctx, offset) {
2128
+ const state = ctx.state;
2129
+ const dataLength = state.props.data.length;
2130
+ let low = 0;
2131
+ let high = dataLength - 1;
2132
+ let match;
2133
+ while (low <= high) {
2134
+ const mid = Math.floor((low + high) / 2);
2135
+ const top = state.positions[mid];
2136
+ if (top === void 0) {
2137
+ high = mid - 1;
2138
+ } else {
2139
+ if (top <= offset) {
2140
+ match = mid;
2141
+ low = mid + 1;
2142
+ } else {
2143
+ high = mid - 1;
2144
+ }
2145
+ }
2146
+ }
2147
+ return match;
2148
+ }
2149
+ function getItemBottom(ctx, index) {
2112
2150
  var _a3;
2151
+ const top = ctx.state.positions[index];
2152
+ if (top === void 0) {
2153
+ return void 0;
2154
+ }
2155
+ const itemSize = (_a3 = getItemSizeAtIndex(ctx, index)) != null ? _a3 : 0;
2156
+ return top + (Number.isFinite(itemSize) ? itemSize : 0);
2157
+ }
2158
+ function getTargetViewportRenderRange(ctx, targetOffset, targetIndex) {
2159
+ const state = ctx.state;
2160
+ const dataLength = state.props.data.length;
2161
+ if (dataLength === 0) {
2162
+ return void 0;
2163
+ }
2164
+ const viewportStart = Math.max(0, targetOffset);
2165
+ const viewportEnd = Math.max(viewportStart, targetOffset + state.scrollLength);
2166
+ let start = targetIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, targetIndex)) : findPositionIndexAtOrBeforeOffset(ctx, viewportStart);
2167
+ if (start === void 0) {
2168
+ return void 0;
2169
+ }
2170
+ if (targetIndex !== void 0 && state.positions[start] === void 0) {
2171
+ return { end: start, start };
2172
+ }
2173
+ if (targetIndex === void 0) {
2174
+ const startBottom = getItemBottom(ctx, start);
2175
+ if (startBottom === void 0 || startBottom <= viewportStart) {
2176
+ return void 0;
2177
+ }
2178
+ }
2179
+ while (start > 0) {
2180
+ const top = state.positions[start];
2181
+ if (top === void 0 || top <= viewportStart || state.positions[start - 1] === void 0) {
2182
+ break;
2183
+ }
2184
+ start--;
2185
+ }
2186
+ while (start > 0) {
2187
+ const previousBottom = getItemBottom(ctx, start - 1);
2188
+ if (previousBottom === void 0 || previousBottom <= viewportStart) {
2189
+ break;
2190
+ }
2191
+ start--;
2192
+ }
2193
+ let end = start;
2194
+ while (end + 1 < dataLength) {
2195
+ const nextTop = state.positions[end + 1];
2196
+ if (nextTop === void 0 || nextTop > viewportEnd) {
2197
+ break;
2198
+ }
2199
+ end++;
2200
+ }
2201
+ return { end, start };
2202
+ }
2203
+ function pinScrollTargetRenderRange(ctx, targetOffset, targetIndex) {
2204
+ const range = getTargetViewportRenderRange(ctx, targetOffset, targetIndex);
2205
+ if (range) {
2206
+ ctx.state.scrollTargetPinnedRange = range;
2207
+ ctx.state.scrollForNextCalculateItemsInView = void 0;
2208
+ } else {
2209
+ ctx.state.scrollTargetPinnedRange = void 0;
2210
+ }
2211
+ }
2212
+ function scrollTo(ctx, params) {
2213
+ var _a3, _b;
2113
2214
  const state = ctx.state;
2114
2215
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2115
2216
  const {
@@ -2144,11 +2245,20 @@ function scrollTo(ctx, params) {
2144
2245
  targetOffset,
2145
2246
  waitForInitialScrollCompletionFrame
2146
2247
  };
2248
+ if (!isInitialScroll) {
2249
+ pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2250
+ }
2147
2251
  }
2148
2252
  state.scrollPending = targetOffset;
2149
2253
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2150
- if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2151
- updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
2254
+ if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2255
+ if (animated) {
2256
+ if (state.scrollTargetPinnedRange) {
2257
+ (_b = state.triggerCalculateItemsInView) == null ? void 0 : _b.call(state);
2258
+ }
2259
+ } else {
2260
+ updateScroll(ctx, targetOffset, true, { markHasScrolled: false });
2261
+ }
2152
2262
  }
2153
2263
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2154
2264
  doScrollTo(ctx, { animated, horizontal, offset });
@@ -3236,22 +3346,28 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3236
3346
  set$(ctx, `containerSpan${containerIndex}`, span);
3237
3347
  }
3238
3348
  }
3349
+ const prevItemInfo = peek$(ctx, `containerItemInfo${containerIndex}`);
3239
3350
  const prevData = peek$(ctx, `containerItemData${containerIndex}`);
3351
+ let itemInfoValue = prevData;
3352
+ let didChangeItemInfo = (prevItemInfo == null ? void 0 : prevItemInfo.itemKey) !== itemKey || (prevItemInfo == null ? void 0 : prevItemInfo.index) !== itemIndex || (prevItemInfo == null ? void 0 : prevItemInfo.value) !== prevData;
3353
+ const updateData = () => {
3354
+ set$(ctx, `containerItemData${containerIndex}`, item);
3355
+ itemInfoValue = item;
3356
+ didChangeItemInfo = true;
3357
+ didRefreshData = true;
3358
+ };
3240
3359
  if (prevData !== item) {
3241
3360
  const pendingDataComparison = ((_e = state.pendingDataComparison) == null ? void 0 : _e.previousData) === state.previousData && ((_f = state.pendingDataComparison) == null ? void 0 : _f.nextData) === data ? state.pendingDataComparison : void 0;
3242
3361
  const cachedComparison = (_g = pendingDataComparison == null ? void 0 : pendingDataComparison.byIndex[itemIndex]) != null ? _g : 0;
3243
3362
  if (cachedComparison === 2) {
3244
- set$(ctx, `containerItemData${containerIndex}`, item);
3245
- didRefreshData = true;
3363
+ updateData();
3246
3364
  } else if (cachedComparison !== 1) {
3247
3365
  const nextItemKey = (_h = peek$(ctx, `containerItemKey${containerIndex}`)) != null ? _h : itemKey;
3248
3366
  const prevKey = keyExtractor == null ? void 0 : keyExtractor(prevData, itemIndex);
3249
3367
  if (prevData === void 0 || !keyExtractor || prevKey !== nextItemKey) {
3250
- set$(ctx, `containerItemData${containerIndex}`, item);
3251
- didRefreshData = true;
3368
+ updateData();
3252
3369
  } else if (!itemsAreEqual) {
3253
- set$(ctx, `containerItemData${containerIndex}`, item);
3254
- didRefreshData = true;
3370
+ updateData();
3255
3371
  } else {
3256
3372
  const isEqual = itemsAreEqual(prevData, item, itemIndex, data);
3257
3373
  if (!state.pendingDataComparison || state.pendingDataComparison.previousData !== state.previousData || state.pendingDataComparison.nextData !== data) {
@@ -3267,12 +3383,18 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3267
3383
  state.pendingDataComparison.byIndex[itemIndex] = isEqual ? 1 : 2;
3268
3384
  }
3269
3385
  if (!isEqual) {
3270
- set$(ctx, `containerItemData${containerIndex}`, item);
3271
- didRefreshData = true;
3386
+ updateData();
3272
3387
  }
3273
3388
  }
3274
3389
  }
3275
3390
  }
3391
+ if (didChangeItemInfo) {
3392
+ set$(ctx, `containerItemInfo${containerIndex}`, {
3393
+ index: itemIndex,
3394
+ itemKey,
3395
+ value: itemInfoValue
3396
+ });
3397
+ }
3276
3398
  return { didChangePosition, didRefreshData };
3277
3399
  }
3278
3400
 
@@ -3965,6 +4087,32 @@ function setDidLayout(ctx) {
3965
4087
  }
3966
4088
 
3967
4089
  // src/core/calculateItemsInView.ts
4090
+ var RENDER_RANGE_PROJECTION_FULL_VELOCITY = 4;
4091
+ var RENDER_RANGE_PROJECTION_SETTLE_DELAY = 100;
4092
+ function getProjectedBufferAdjustment(scrollVelocity, trailingBuffer) {
4093
+ if (trailingBuffer <= 0) {
4094
+ return 0;
4095
+ }
4096
+ const velocityProgress = Math.min(1, Math.abs(scrollVelocity) / RENDER_RANGE_PROJECTION_FULL_VELOCITY);
4097
+ return Math.sign(scrollVelocity) * trailingBuffer * velocityProgress;
4098
+ }
4099
+ function scheduleRenderRangeProjectionSettle(ctx) {
4100
+ const state = ctx.state;
4101
+ const previousTimeout = state.timeoutRenderRangeProjectionSettle;
4102
+ if (previousTimeout !== void 0) {
4103
+ clearTimeout(previousTimeout);
4104
+ state.timeouts.delete(previousTimeout);
4105
+ }
4106
+ const timeout = setTimeout(() => {
4107
+ var _a3;
4108
+ state.timeoutRenderRangeProjectionSettle = void 0;
4109
+ state.timeouts.delete(timeout);
4110
+ state.scrollHistory.length = 0;
4111
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
4112
+ }, RENDER_RANGE_PROJECTION_SETTLE_DELAY);
4113
+ state.timeoutRenderRangeProjectionSettle = timeout;
4114
+ state.timeouts.add(timeout);
4115
+ }
3968
4116
  function findCurrentStickyIndex(stickyArray, scroll, state) {
3969
4117
  const positions = state.positions;
3970
4118
  for (let i = stickyArray.length - 1; i >= 0; i--) {
@@ -4005,14 +4153,14 @@ function handleStickyActivation(ctx, stickyArray, currentStickyIdx, needNewConta
4005
4153
  }
4006
4154
  }
4007
4155
  }
4008
- function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, alwaysRenderIndicesSet) {
4156
+ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, isPinnedRenderIndex) {
4009
4157
  var _a3, _b;
4010
4158
  const state = ctx.state;
4011
4159
  for (const containerIndex of state.stickyContainerPool) {
4012
4160
  const itemKey = peek$(ctx, `containerItemKey${containerIndex}`);
4013
4161
  const itemIndex = itemKey ? state.indexByKey.get(itemKey) : void 0;
4014
4162
  if (itemIndex === void 0) continue;
4015
- if (alwaysRenderIndicesSet.has(itemIndex)) continue;
4163
+ if (isPinnedRenderIndex(itemIndex)) continue;
4016
4164
  const arrayIdx = stickyArray.indexOf(itemIndex);
4017
4165
  if (arrayIdx === -1) {
4018
4166
  state.stickyContainerPool.delete(containerIndex);
@@ -4176,8 +4324,6 @@ function calculateItemsInView(ctx, params = {}) {
4176
4324
  const { data } = state.props;
4177
4325
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4178
4326
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4179
- const alwaysRenderArr = alwaysRenderIndicesArr || [];
4180
- const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
4181
4327
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4182
4328
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
4183
4329
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
@@ -4244,14 +4390,19 @@ function calculateItemsInView(ctx, params = {}) {
4244
4390
  scrollBufferTop = drawDistance * 1.5;
4245
4391
  scrollBufferBottom = drawDistance * 0.5;
4246
4392
  }
4393
+ const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4394
+ const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4247
4395
  const updateScrollRange = () => {
4248
4396
  const scrollStart = Math.max(0, scroll);
4249
4397
  const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4250
- scrollTopBuffered = scrollStart - scrollBufferTop;
4251
4398
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4252
- scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4399
+ scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4400
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
4253
4401
  };
4254
4402
  updateScrollRange();
4403
+ if (projectedBufferAdjustment !== 0) {
4404
+ scheduleRenderRangeProjectionSettle(ctx);
4405
+ }
4255
4406
  if (enableScrollForNextCalculateItemsInView && !suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4256
4407
  const { top, bottom } = scrollForNextCalculateItemsInView;
4257
4408
  if (top === null && bottom === null) {
@@ -4409,9 +4560,34 @@ function calculateItemsInView(ctx, params = {}) {
4409
4560
  }
4410
4561
  }
4411
4562
  }
4563
+ const scrollTargetPinnedRange = state.scrollTargetPinnedRange;
4564
+ let scrollTargetPinnedStart = 0;
4565
+ let scrollTargetPinnedEnd = -1;
4566
+ if (scrollTargetPinnedRange) {
4567
+ scrollTargetPinnedStart = Math.max(0, Math.min(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end));
4568
+ scrollTargetPinnedEnd = Math.min(
4569
+ dataLength - 1,
4570
+ Math.max(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end)
4571
+ );
4572
+ }
4573
+ const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4574
+ const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4412
4575
  if (startBuffered !== null && endBuffered !== null) {
4413
4576
  const needNewContainers = [];
4414
4577
  const needNewContainersSet = /* @__PURE__ */ new Set();
4578
+ const addPinnedIndex = (index) => {
4579
+ var _a4;
4580
+ if (index >= 0 && index < dataLength) {
4581
+ const id = (_a4 = idCache[index]) != null ? _a4 : getId(state, index);
4582
+ const containerIndex = containerItemKeys.get(id);
4583
+ if (containerIndex !== void 0) {
4584
+ state.stickyContainerPool.add(containerIndex);
4585
+ } else if (!isNullOrUndefined(id) && !needNewContainersSet.has(index)) {
4586
+ needNewContainersSet.add(index);
4587
+ needNewContainers.push(index);
4588
+ }
4589
+ }
4590
+ };
4415
4591
  for (let i = startBuffered; i <= endBuffered; i++) {
4416
4592
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4417
4593
  if (!containerItemKeys.has(id)) {
@@ -4419,21 +4595,19 @@ function calculateItemsInView(ctx, params = {}) {
4419
4595
  needNewContainers.push(i);
4420
4596
  }
4421
4597
  }
4422
- if (alwaysRenderArr.length > 0) {
4423
- for (const index of alwaysRenderArr) {
4424
- if (index < 0 || index >= dataLength) continue;
4425
- const id = (_n = idCache[index]) != null ? _n : getId(state, index);
4426
- if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
4427
- needNewContainersSet.add(index);
4428
- needNewContainers.push(index);
4429
- }
4598
+ for (const index of alwaysRenderIndicesArr) {
4599
+ addPinnedIndex(index);
4600
+ }
4601
+ if (hasScrollTargetPinnedRange) {
4602
+ for (let index = scrollTargetPinnedStart; index <= scrollTargetPinnedEnd; index++) {
4603
+ addPinnedIndex(index);
4430
4604
  }
4431
4605
  }
4432
4606
  if (stickyHeaderIndicesArr.length > 0) {
4433
4607
  handleStickyActivation(
4434
4608
  ctx,
4435
4609
  stickyHeaderIndicesArr,
4436
- (_o = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _o : -1,
4610
+ (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4437
4611
  needNewContainers,
4438
4612
  needNewContainersSet,
4439
4613
  startBuffered,
@@ -4459,21 +4633,30 @@ function calculateItemsInView(ctx, params = {}) {
4459
4633
  for (const allocation of availableContainerAllocations) {
4460
4634
  const i = allocation.itemIndex;
4461
4635
  const containerIndex = allocation.containerIndex;
4462
- const id = (_p = idCache[i]) != null ? _p : getId(state, i);
4636
+ const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4463
4637
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4464
4638
  if (oldKey && oldKey !== id) {
4465
4639
  containerItemKeys.delete(oldKey);
4466
4640
  }
4467
4641
  set$(ctx, `containerItemKey${containerIndex}`, id);
4468
4642
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
4643
+ set$(ctx, `containerItemInfo${containerIndex}`, {
4644
+ index: i,
4645
+ itemKey: id,
4646
+ value: data[i]
4647
+ });
4469
4648
  if (allocation.itemType !== void 0) {
4470
4649
  state.containerItemTypes.set(containerIndex, allocation.itemType);
4471
4650
  }
4472
4651
  containerItemKeys.set(id, containerIndex);
4473
- (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4652
+ (_p = state.userScrollAnchorReset) == null ? void 0 : _p.keys.add(id);
4653
+ {
4654
+ (_q = state.pendingLayoutEffectMeasurements) != null ? _q : state.pendingLayoutEffectMeasurements = /* @__PURE__ */ new Set();
4655
+ state.pendingLayoutEffectMeasurements.add(id);
4656
+ }
4474
4657
  const containerSticky = `containerSticky${containerIndex}`;
4475
4658
  const isSticky = stickyHeaderIndicesSet.has(i);
4476
- const isAlwaysRender = alwaysRenderSet.has(i);
4659
+ const isPinnedRender = isPinnedRenderIndex(i);
4477
4660
  if (isSticky) {
4478
4661
  set$(ctx, containerSticky, true);
4479
4662
  state.stickyContainerPool.add(containerIndex);
@@ -4481,9 +4664,9 @@ function calculateItemsInView(ctx, params = {}) {
4481
4664
  if (peek$(ctx, containerSticky)) {
4482
4665
  set$(ctx, containerSticky, false);
4483
4666
  }
4484
- if (isAlwaysRender) {
4667
+ if (isPinnedRender) {
4485
4668
  state.stickyContainerPool.add(containerIndex);
4486
- } else if (state.stickyContainerPool.has(containerIndex)) {
4669
+ } else {
4487
4670
  state.stickyContainerPool.delete(containerIndex);
4488
4671
  }
4489
4672
  }
@@ -4498,20 +4681,8 @@ function calculateItemsInView(ctx, params = {}) {
4498
4681
  }
4499
4682
  }
4500
4683
  }
4501
- if (state.userScrollAnchorReset) {
4502
- if (state.userScrollAnchorReset.keys.size === 0) {
4503
- state.userScrollAnchorReset = void 0;
4504
- }
4505
- }
4506
- if (alwaysRenderArr.length > 0) {
4507
- for (const index of alwaysRenderArr) {
4508
- if (index < 0 || index >= dataLength) continue;
4509
- const id = (_r = idCache[index]) != null ? _r : getId(state, index);
4510
- const containerIndex = containerItemKeys.get(id);
4511
- if (containerIndex !== void 0) {
4512
- state.stickyContainerPool.add(containerIndex);
4513
- }
4514
- }
4684
+ if (((_r = state.userScrollAnchorReset) == null ? void 0 : _r.keys.size) === 0) {
4685
+ state.userScrollAnchorReset = void 0;
4515
4686
  }
4516
4687
  }
4517
4688
  if (state.stickyContainerPool.size > 0) {
@@ -4522,7 +4693,7 @@ function calculateItemsInView(ctx, params = {}) {
4522
4693
  drawDistance,
4523
4694
  (_s = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _s : -1,
4524
4695
  pendingRemoval,
4525
- alwaysRenderSet
4696
+ isPinnedRenderIndex
4526
4697
  );
4527
4698
  }
4528
4699
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
@@ -4540,6 +4711,7 @@ function calculateItemsInView(ctx, params = {}) {
4540
4711
  }
4541
4712
  set$(ctx, `containerItemKey${i}`, void 0);
4542
4713
  set$(ctx, `containerItemData${i}`, void 0);
4714
+ set$(ctx, `containerItemInfo${i}`, void 0);
4543
4715
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
4544
4716
  set$(ctx, `containerColumn${i}`, -1);
4545
4717
  set$(ctx, `containerSpan${i}`, 1);
@@ -4681,6 +4853,36 @@ function mergeItemSizeUpdateResult(result, next) {
4681
4853
  result.needsRecalculate || (result.needsRecalculate = next.needsRecalculate);
4682
4854
  result.shouldMaintainScrollAtEnd || (result.shouldMaintainScrollAtEnd = next.shouldMaintainScrollAtEnd);
4683
4855
  }
4856
+ var batchedItemSizeRecalculates = /* @__PURE__ */ new WeakMap();
4857
+ function flushBatchedItemSizeRecalculate(ctx, didFallback = false, expectedResult) {
4858
+ const result = batchedItemSizeRecalculates.get(ctx);
4859
+ if (!result || expectedResult && result !== expectedResult) {
4860
+ return;
4861
+ }
4862
+ batchedItemSizeRecalculates.delete(ctx);
4863
+ if (didFallback) {
4864
+ ctx.state.pendingLayoutEffectMeasurements = void 0;
4865
+ }
4866
+ flushItemSizeUpdates(ctx, result);
4867
+ }
4868
+ function queueItemSizeRecalculate(ctx, result) {
4869
+ var _a3, _b;
4870
+ const batch = batchedItemSizeRecalculates.get(ctx);
4871
+ if (batch) {
4872
+ mergeItemSizeUpdateResult(batch, result);
4873
+ } else {
4874
+ const nextBatch = { ...result };
4875
+ batchedItemSizeRecalculates.set(ctx, nextBatch);
4876
+ if ((_a3 = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _a3.size) {
4877
+ requestAnimationFrame(() => {
4878
+ flushBatchedItemSizeRecalculate(ctx, true, nextBatch);
4879
+ });
4880
+ }
4881
+ }
4882
+ if (!((_b = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _b.size)) {
4883
+ flushBatchedItemSizeRecalculate(ctx);
4884
+ }
4885
+ }
4684
4886
  function flushItemSizeUpdates(ctx, result) {
4685
4887
  var _a3;
4686
4888
  const state = ctx.state;
@@ -4697,12 +4899,22 @@ function flushItemSizeUpdates(ctx, result) {
4697
4899
  function updateItemSizes(ctx, measurement) {
4698
4900
  var _a3, _b, _c, _d;
4699
4901
  const state = ctx.state;
4902
+ let didDrainLayoutEffectMeasurements = false;
4903
+ if (measurement.fromLayoutEffect) {
4904
+ const pendingLayoutEffectMeasurements = state.pendingLayoutEffectMeasurements;
4905
+ if ((pendingLayoutEffectMeasurements == null ? void 0 : pendingLayoutEffectMeasurements.delete(measurement.itemKey)) && pendingLayoutEffectMeasurements.size === 0) {
4906
+ state.pendingLayoutEffectMeasurements = void 0;
4907
+ didDrainLayoutEffectMeasurements = true;
4908
+ }
4909
+ }
4700
4910
  const pendingKeys = (_a3 = state.userScrollAnchorReset) == null ? void 0 : _a3.keys;
4701
4911
  const shouldBatchPendingMeasurements = measurement.fromLayoutEffect && !!(pendingKeys == null ? void 0 : pendingKeys.size);
4912
+ const shouldQueueRecalculate = !!measurement.fromLayoutEffect;
4913
+ let result;
4702
4914
  if (!shouldBatchPendingMeasurements) {
4703
- flushItemSizeUpdates(ctx, applyItemSize(ctx, measurement.itemKey, measurement.size));
4915
+ result = applyItemSize(ctx, measurement.itemKey, measurement.size);
4704
4916
  } else {
4705
- const result = {};
4917
+ result = {};
4706
4918
  const updateContainerItemSize = (itemKey, containerId, size) => {
4707
4919
  if (containerId !== void 0 && peek$(ctx, `containerItemKey${containerId}`) === itemKey) {
4708
4920
  mergeItemSizeUpdateResult(result, applyItemSize(ctx, itemKey, size));
@@ -4720,8 +4932,15 @@ function updateItemSizes(ctx, measurement) {
4720
4932
  });
4721
4933
  }
4722
4934
  }
4935
+ }
4936
+ if (shouldQueueRecalculate && result.needsRecalculate) {
4937
+ queueItemSizeRecalculate(ctx, result);
4938
+ } else {
4723
4939
  flushItemSizeUpdates(ctx, result);
4724
4940
  }
4941
+ if (didDrainLayoutEffectMeasurements) {
4942
+ flushBatchedItemSizeRecalculate(ctx);
4943
+ }
4725
4944
  }
4726
4945
  function applyItemSize(ctx, itemKey, sizeObj) {
4727
4946
  var _a3;
@@ -5113,12 +5332,9 @@ var Container = typedMemo(function Container2({
5113
5332
  ctx.viewRefs.set(id, ref);
5114
5333
  return {
5115
5334
  containerId: id,
5116
- index,
5117
- itemKey,
5118
- triggerLayout,
5119
- value: data
5335
+ triggerLayout
5120
5336
  };
5121
- }, [id, itemKey, index, data, triggerLayout]);
5337
+ }, [id, triggerLayout]);
5122
5338
  useLayoutEffect(() => {
5123
5339
  ctx.containerLayoutTriggers.set(id, triggerLayout);
5124
5340
  return () => {