@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.
@@ -148,6 +148,7 @@ var createAnimatedValue = (value) => value;
148
148
 
149
149
  // src/state/state.tsx
150
150
  var ContextState = React3__namespace.createContext(null);
151
+ var SIGNAL_NAMES_SEPARATOR = "\0";
151
152
  var contextNum = 0;
152
153
  function StateProvider({ children }) {
153
154
  const [value] = React3__namespace.useState(() => ({
@@ -219,6 +220,12 @@ function createSelectorFunctionsArr(ctx, signalNames) {
219
220
  }
220
221
  };
221
222
  }
223
+ function getSignalNamesKey(signalNames) {
224
+ return signalNames.length === 1 ? signalNames[0] : signalNames.join(SIGNAL_NAMES_SEPARATOR);
225
+ }
226
+ function getSignalNamesFromKey(signalNamesKey) {
227
+ return signalNamesKey.split(SIGNAL_NAMES_SEPARATOR);
228
+ }
222
229
  function listen$(ctx, signalName, cb) {
223
230
  const { listeners } = ctx;
224
231
  let setListeners = listeners.get(signalName);
@@ -266,7 +273,11 @@ function notifyPosition$(ctx, key, value) {
266
273
  }
267
274
  function useArr$(signalNames) {
268
275
  const ctx = React3__namespace.useContext(ContextState);
269
- const { subscribe, get } = React3__namespace.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
276
+ const signalNamesKey = getSignalNamesKey(signalNames);
277
+ const { subscribe, get } = React3__namespace.useMemo(
278
+ () => createSelectorFunctionsArr(ctx, getSignalNamesFromKey(signalNamesKey)),
279
+ [ctx, signalNamesKey]
280
+ );
270
281
  const value = shim.useSyncExternalStore(subscribe, get, get);
271
282
  return value;
272
283
  }
@@ -553,9 +564,22 @@ function useInit(cb) {
553
564
 
554
565
  // src/state/ContextContainer.ts
555
566
  var ContextContainer = React3.createContext(null);
567
+ var NO_CONTAINER_ID = -1;
556
568
  function useContextContainer() {
557
569
  return React3.useContext(ContextContainer);
558
570
  }
571
+ function useContainerItemInfo(containerContext) {
572
+ var _a3;
573
+ const containerId = (_a3 = containerContext == null ? void 0 : containerContext.containerId) != null ? _a3 : NO_CONTAINER_ID;
574
+ const [itemInfo] = useArr$([`containerItemInfo${containerId}`]);
575
+ return containerContext ? itemInfo : void 0;
576
+ }
577
+ function useContainerItemKey(containerContext) {
578
+ var _a3;
579
+ const containerId = (_a3 = containerContext == null ? void 0 : containerContext.containerId) != null ? _a3 : NO_CONTAINER_ID;
580
+ const [itemKey] = useArr$([`containerItemKey${containerId}`]);
581
+ return containerContext ? itemKey : void 0;
582
+ }
559
583
  function useAdaptiveRender() {
560
584
  const [mode] = useArr$(["adaptiveRender"]);
561
585
  return mode;
@@ -626,40 +650,35 @@ function useViewabilityAmount(callback) {
626
650
  }
627
651
  function useRecyclingEffect(effect) {
628
652
  const containerContext = useContextContainer();
629
- const prevValues = React3.useRef({
630
- prevIndex: void 0,
631
- prevItem: void 0
632
- });
653
+ const itemInfo = useContainerItemInfo(containerContext);
654
+ const prevInfo = React3.useRef(void 0);
633
655
  React3.useEffect(() => {
634
- if (!containerContext) {
656
+ if (!itemInfo) {
635
657
  return;
636
658
  }
637
- const { index, value } = containerContext;
638
659
  let ret;
639
- if (prevValues.current.prevIndex !== void 0 && prevValues.current.prevItem !== void 0) {
660
+ if (prevInfo.current) {
640
661
  ret = effect({
641
- index,
642
- item: value,
643
- prevIndex: prevValues.current.prevIndex,
644
- prevItem: prevValues.current.prevItem
662
+ index: itemInfo.index,
663
+ item: itemInfo.value,
664
+ prevIndex: prevInfo.current.index,
665
+ prevItem: prevInfo.current.value
645
666
  });
646
667
  }
647
- prevValues.current = {
648
- prevIndex: index,
649
- prevItem: value
650
- };
668
+ prevInfo.current = itemInfo;
651
669
  return ret;
652
- }, [effect, containerContext]);
670
+ }, [effect, itemInfo]);
653
671
  }
654
672
  function useRecyclingState(valueOrFun) {
655
- var _a3, _b;
673
+ var _a3;
656
674
  const containerContext = useContextContainer();
657
- const computeValue = (ctx) => {
675
+ const itemInfo = useContainerItemInfo(containerContext);
676
+ const computeValue = (info) => {
658
677
  if (isFunction(valueOrFun)) {
659
678
  const initializer = valueOrFun;
660
- return ctx ? initializer({
661
- index: ctx.index,
662
- item: ctx.value,
679
+ return info ? initializer({
680
+ index: info.index,
681
+ item: info.value,
663
682
  prevIndex: void 0,
664
683
  prevItem: void 0
665
684
  }) : initializer();
@@ -667,13 +686,12 @@ function useRecyclingState(valueOrFun) {
667
686
  return valueOrFun;
668
687
  };
669
688
  const [stateValue, setStateValue] = React3.useState(() => {
670
- return computeValue(containerContext);
689
+ return computeValue(itemInfo);
671
690
  });
672
- const prevItemKeyRef = React3.useRef((_a3 = containerContext == null ? void 0 : containerContext.itemKey) != null ? _a3 : null);
673
- const currentItemKey = (_b = containerContext == null ? void 0 : containerContext.itemKey) != null ? _b : null;
674
- if (currentItemKey !== null && prevItemKeyRef.current !== currentItemKey) {
675
- prevItemKeyRef.current = currentItemKey;
676
- setStateValue(computeValue(containerContext));
691
+ const prevItemKeyRef = React3.useRef((_a3 = itemInfo == null ? void 0 : itemInfo.itemKey) != null ? _a3 : null);
692
+ if (itemInfo && prevItemKeyRef.current !== itemInfo.itemKey) {
693
+ prevItemKeyRef.current = itemInfo.itemKey;
694
+ setStateValue(computeValue(itemInfo));
677
695
  }
678
696
  const triggerLayout = containerContext == null ? void 0 : containerContext.triggerLayout;
679
697
  const setState = React3.useCallback(
@@ -692,12 +710,10 @@ function useRecyclingState(valueOrFun) {
692
710
  }
693
711
  function useIsLastItem() {
694
712
  const containerContext = useContextContainer();
713
+ const itemKey = useContainerItemKey(containerContext);
695
714
  const isLast = useSelector$("lastItemKeys", (lastItemKeys) => {
696
- if (containerContext) {
697
- const { itemKey } = containerContext;
698
- if (!isNullOrUndefined(itemKey)) {
699
- return (lastItemKeys == null ? void 0 : lastItemKeys.includes(itemKey)) || false;
700
- }
715
+ if (containerContext && !isNullOrUndefined(itemKey)) {
716
+ return (lastItemKeys == null ? void 0 : lastItemKeys.includes(itemKey)) || false;
701
717
  }
702
718
  return false;
703
719
  });
@@ -711,12 +727,7 @@ var noop = () => {
711
727
  };
712
728
  function useSyncLayout() {
713
729
  const containerContext = useContextContainer();
714
- if (containerContext) {
715
- const { triggerLayout: syncLayout } = containerContext;
716
- return syncLayout;
717
- } else {
718
- return noop;
719
- }
730
+ return containerContext ? containerContext.triggerLayout : noop;
720
731
  }
721
732
 
722
733
  // src/components/Separator.tsx
@@ -1484,6 +1495,7 @@ function finishScrollTo(ctx) {
1484
1495
  const scrollingTo = state.scrollingTo;
1485
1496
  state.scrollHistory.length = 0;
1486
1497
  state.scrollingTo = void 0;
1498
+ state.scrollTargetPinnedRange = void 0;
1487
1499
  if (state.pendingTotalSize !== void 0) {
1488
1500
  addTotalSize(ctx, null, state.pendingTotalSize);
1489
1501
  }
@@ -1977,6 +1989,8 @@ function prepareMVCP(ctx, dataChanged) {
1977
1989
  }
1978
1990
 
1979
1991
  // src/utils/getScrollVelocity.ts
1992
+ var MAX_SCROLL_VELOCITY_WINDOW_MS = 1e3;
1993
+ var SCROLL_VELOCITY_HALF_LIFE_MS = 200;
1980
1994
  var getScrollVelocity = (state) => {
1981
1995
  const { scrollHistory } = state;
1982
1996
  const newestIndex = scrollHistory.length - 1;
@@ -1984,35 +1998,37 @@ var getScrollVelocity = (state) => {
1984
1998
  return 0;
1985
1999
  }
1986
2000
  const newest = scrollHistory[newestIndex];
1987
- const now = Date.now();
1988
- let direction = 0;
1989
- for (let i = newestIndex; i > 0; i--) {
1990
- const delta = scrollHistory[i].scroll - scrollHistory[i - 1].scroll;
1991
- if (delta !== 0) {
1992
- direction = Math.sign(delta);
1993
- break;
1994
- }
1995
- }
1996
- if (direction === 0) {
2001
+ if (Date.now() - newest.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1997
2002
  return 0;
1998
2003
  }
1999
- let oldest = newest;
2000
- for (let i = newestIndex - 1; i >= 0; i--) {
2004
+ let direction = 0;
2005
+ let weightedVelocity = 0;
2006
+ let totalWeight = 0;
2007
+ for (let i = newestIndex; i > 0; i--) {
2001
2008
  const current = scrollHistory[i];
2002
- const next = scrollHistory[i + 1];
2003
- const delta = next.scroll - current.scroll;
2004
- const deltaSign = Math.sign(delta);
2005
- if (deltaSign !== 0 && deltaSign !== direction) {
2006
- break;
2009
+ const previous = scrollHistory[i - 1];
2010
+ const scrollDiff = current.scroll - previous.scroll;
2011
+ const timeDiff = current.time - previous.time;
2012
+ const deltaSign = Math.sign(scrollDiff);
2013
+ if (deltaSign !== 0) {
2014
+ if (direction === 0) {
2015
+ direction = deltaSign;
2016
+ } else if (deltaSign !== direction) {
2017
+ break;
2018
+ }
2007
2019
  }
2008
- if (now - current.time > 1e3) {
2020
+ if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
2009
2021
  break;
2010
2022
  }
2011
- oldest = current;
2023
+ if (scrollDiff === 0 || timeDiff <= 0) {
2024
+ continue;
2025
+ }
2026
+ const age = newest.time - current.time;
2027
+ const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
2028
+ weightedVelocity += scrollDiff / timeDiff * weight;
2029
+ totalWeight += weight;
2012
2030
  }
2013
- const scrollDiff = newest.scroll - oldest.scroll;
2014
- const timeDiff = newest.time - oldest.time;
2015
- return timeDiff > 0 ? scrollDiff / timeDiff : 0;
2031
+ return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
2016
2032
  };
2017
2033
 
2018
2034
  // src/core/updateScroll.ts
@@ -2129,8 +2145,93 @@ function syncInitialScrollNativeWatchdog(state, options) {
2129
2145
  initialScrollWatchdog.clear(state);
2130
2146
  }
2131
2147
  }
2132
- function scrollTo(ctx, params) {
2148
+ function findPositionIndexAtOrBeforeOffset(ctx, offset) {
2149
+ const state = ctx.state;
2150
+ const dataLength = state.props.data.length;
2151
+ let low = 0;
2152
+ let high = dataLength - 1;
2153
+ let match;
2154
+ while (low <= high) {
2155
+ const mid = Math.floor((low + high) / 2);
2156
+ const top = state.positions[mid];
2157
+ if (top === void 0) {
2158
+ high = mid - 1;
2159
+ } else {
2160
+ if (top <= offset) {
2161
+ match = mid;
2162
+ low = mid + 1;
2163
+ } else {
2164
+ high = mid - 1;
2165
+ }
2166
+ }
2167
+ }
2168
+ return match;
2169
+ }
2170
+ function getItemBottom(ctx, index) {
2133
2171
  var _a3;
2172
+ const top = ctx.state.positions[index];
2173
+ if (top === void 0) {
2174
+ return void 0;
2175
+ }
2176
+ const itemSize = (_a3 = getItemSizeAtIndex(ctx, index)) != null ? _a3 : 0;
2177
+ return top + (Number.isFinite(itemSize) ? itemSize : 0);
2178
+ }
2179
+ function getTargetViewportRenderRange(ctx, targetOffset, targetIndex) {
2180
+ const state = ctx.state;
2181
+ const dataLength = state.props.data.length;
2182
+ if (dataLength === 0) {
2183
+ return void 0;
2184
+ }
2185
+ const viewportStart = Math.max(0, targetOffset);
2186
+ const viewportEnd = Math.max(viewportStart, targetOffset + state.scrollLength);
2187
+ let start = targetIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, targetIndex)) : findPositionIndexAtOrBeforeOffset(ctx, viewportStart);
2188
+ if (start === void 0) {
2189
+ return void 0;
2190
+ }
2191
+ if (targetIndex !== void 0 && state.positions[start] === void 0) {
2192
+ return { end: start, start };
2193
+ }
2194
+ if (targetIndex === void 0) {
2195
+ const startBottom = getItemBottom(ctx, start);
2196
+ if (startBottom === void 0 || startBottom <= viewportStart) {
2197
+ return void 0;
2198
+ }
2199
+ }
2200
+ while (start > 0) {
2201
+ const top = state.positions[start];
2202
+ if (top === void 0 || top <= viewportStart || state.positions[start - 1] === void 0) {
2203
+ break;
2204
+ }
2205
+ start--;
2206
+ }
2207
+ while (start > 0) {
2208
+ const previousBottom = getItemBottom(ctx, start - 1);
2209
+ if (previousBottom === void 0 || previousBottom <= viewportStart) {
2210
+ break;
2211
+ }
2212
+ start--;
2213
+ }
2214
+ let end = start;
2215
+ while (end + 1 < dataLength) {
2216
+ const nextTop = state.positions[end + 1];
2217
+ if (nextTop === void 0 || nextTop > viewportEnd) {
2218
+ break;
2219
+ }
2220
+ end++;
2221
+ }
2222
+ return { end, start };
2223
+ }
2224
+ function pinScrollTargetRenderRange(ctx, targetOffset, targetIndex) {
2225
+ const range = getTargetViewportRenderRange(ctx, targetOffset, targetIndex);
2226
+ if (range) {
2227
+ ctx.state.scrollTargetPinnedRange = range;
2228
+ ctx.state.scrollForNextCalculateItemsInView = void 0;
2229
+ } else {
2230
+ ctx.state.scrollTargetPinnedRange = void 0;
2231
+ }
2232
+ }
2233
+ function scrollTo(ctx, params) {
2234
+ var _a3, _b;
2134
2235
  const state = ctx.state;
2135
2236
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2136
2237
  const {
@@ -2165,11 +2266,20 @@ function scrollTo(ctx, params) {
2165
2266
  targetOffset,
2166
2267
  waitForInitialScrollCompletionFrame
2167
2268
  };
2269
+ if (!isInitialScroll) {
2270
+ pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2271
+ }
2168
2272
  }
2169
2273
  state.scrollPending = targetOffset;
2170
2274
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2171
- if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2172
- updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
2275
+ if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2276
+ if (animated) {
2277
+ if (state.scrollTargetPinnedRange) {
2278
+ (_b = state.triggerCalculateItemsInView) == null ? void 0 : _b.call(state);
2279
+ }
2280
+ } else {
2281
+ updateScroll(ctx, targetOffset, true, { markHasScrolled: false });
2282
+ }
2173
2283
  }
2174
2284
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2175
2285
  doScrollTo(ctx, { animated, horizontal, offset });
@@ -3257,22 +3367,28 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3257
3367
  set$(ctx, `containerSpan${containerIndex}`, span);
3258
3368
  }
3259
3369
  }
3370
+ const prevItemInfo = peek$(ctx, `containerItemInfo${containerIndex}`);
3260
3371
  const prevData = peek$(ctx, `containerItemData${containerIndex}`);
3372
+ let itemInfoValue = prevData;
3373
+ let didChangeItemInfo = (prevItemInfo == null ? void 0 : prevItemInfo.itemKey) !== itemKey || (prevItemInfo == null ? void 0 : prevItemInfo.index) !== itemIndex || (prevItemInfo == null ? void 0 : prevItemInfo.value) !== prevData;
3374
+ const updateData = () => {
3375
+ set$(ctx, `containerItemData${containerIndex}`, item);
3376
+ itemInfoValue = item;
3377
+ didChangeItemInfo = true;
3378
+ didRefreshData = true;
3379
+ };
3261
3380
  if (prevData !== item) {
3262
3381
  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;
3263
3382
  const cachedComparison = (_g = pendingDataComparison == null ? void 0 : pendingDataComparison.byIndex[itemIndex]) != null ? _g : 0;
3264
3383
  if (cachedComparison === 2) {
3265
- set$(ctx, `containerItemData${containerIndex}`, item);
3266
- didRefreshData = true;
3384
+ updateData();
3267
3385
  } else if (cachedComparison !== 1) {
3268
3386
  const nextItemKey = (_h = peek$(ctx, `containerItemKey${containerIndex}`)) != null ? _h : itemKey;
3269
3387
  const prevKey = keyExtractor == null ? void 0 : keyExtractor(prevData, itemIndex);
3270
3388
  if (prevData === void 0 || !keyExtractor || prevKey !== nextItemKey) {
3271
- set$(ctx, `containerItemData${containerIndex}`, item);
3272
- didRefreshData = true;
3389
+ updateData();
3273
3390
  } else if (!itemsAreEqual) {
3274
- set$(ctx, `containerItemData${containerIndex}`, item);
3275
- didRefreshData = true;
3391
+ updateData();
3276
3392
  } else {
3277
3393
  const isEqual = itemsAreEqual(prevData, item, itemIndex, data);
3278
3394
  if (!state.pendingDataComparison || state.pendingDataComparison.previousData !== state.previousData || state.pendingDataComparison.nextData !== data) {
@@ -3288,12 +3404,18 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3288
3404
  state.pendingDataComparison.byIndex[itemIndex] = isEqual ? 1 : 2;
3289
3405
  }
3290
3406
  if (!isEqual) {
3291
- set$(ctx, `containerItemData${containerIndex}`, item);
3292
- didRefreshData = true;
3407
+ updateData();
3293
3408
  }
3294
3409
  }
3295
3410
  }
3296
3411
  }
3412
+ if (didChangeItemInfo) {
3413
+ set$(ctx, `containerItemInfo${containerIndex}`, {
3414
+ index: itemIndex,
3415
+ itemKey,
3416
+ value: itemInfoValue
3417
+ });
3418
+ }
3297
3419
  return { didChangePosition, didRefreshData };
3298
3420
  }
3299
3421
 
@@ -3986,6 +4108,32 @@ function setDidLayout(ctx) {
3986
4108
  }
3987
4109
 
3988
4110
  // src/core/calculateItemsInView.ts
4111
+ var RENDER_RANGE_PROJECTION_FULL_VELOCITY = 4;
4112
+ var RENDER_RANGE_PROJECTION_SETTLE_DELAY = 100;
4113
+ function getProjectedBufferAdjustment(scrollVelocity, trailingBuffer) {
4114
+ if (trailingBuffer <= 0) {
4115
+ return 0;
4116
+ }
4117
+ const velocityProgress = Math.min(1, Math.abs(scrollVelocity) / RENDER_RANGE_PROJECTION_FULL_VELOCITY);
4118
+ return Math.sign(scrollVelocity) * trailingBuffer * velocityProgress;
4119
+ }
4120
+ function scheduleRenderRangeProjectionSettle(ctx) {
4121
+ const state = ctx.state;
4122
+ const previousTimeout = state.timeoutRenderRangeProjectionSettle;
4123
+ if (previousTimeout !== void 0) {
4124
+ clearTimeout(previousTimeout);
4125
+ state.timeouts.delete(previousTimeout);
4126
+ }
4127
+ const timeout = setTimeout(() => {
4128
+ var _a3;
4129
+ state.timeoutRenderRangeProjectionSettle = void 0;
4130
+ state.timeouts.delete(timeout);
4131
+ state.scrollHistory.length = 0;
4132
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
4133
+ }, RENDER_RANGE_PROJECTION_SETTLE_DELAY);
4134
+ state.timeoutRenderRangeProjectionSettle = timeout;
4135
+ state.timeouts.add(timeout);
4136
+ }
3989
4137
  function findCurrentStickyIndex(stickyArray, scroll, state) {
3990
4138
  const positions = state.positions;
3991
4139
  for (let i = stickyArray.length - 1; i >= 0; i--) {
@@ -4026,14 +4174,14 @@ function handleStickyActivation(ctx, stickyArray, currentStickyIdx, needNewConta
4026
4174
  }
4027
4175
  }
4028
4176
  }
4029
- function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, alwaysRenderIndicesSet) {
4177
+ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, isPinnedRenderIndex) {
4030
4178
  var _a3, _b;
4031
4179
  const state = ctx.state;
4032
4180
  for (const containerIndex of state.stickyContainerPool) {
4033
4181
  const itemKey = peek$(ctx, `containerItemKey${containerIndex}`);
4034
4182
  const itemIndex = itemKey ? state.indexByKey.get(itemKey) : void 0;
4035
4183
  if (itemIndex === void 0) continue;
4036
- if (alwaysRenderIndicesSet.has(itemIndex)) continue;
4184
+ if (isPinnedRenderIndex(itemIndex)) continue;
4037
4185
  const arrayIdx = stickyArray.indexOf(itemIndex);
4038
4186
  if (arrayIdx === -1) {
4039
4187
  state.stickyContainerPool.delete(containerIndex);
@@ -4197,8 +4345,6 @@ function calculateItemsInView(ctx, params = {}) {
4197
4345
  const { data } = state.props;
4198
4346
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4199
4347
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4200
- const alwaysRenderArr = alwaysRenderIndicesArr || [];
4201
- const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
4202
4348
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4203
4349
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
4204
4350
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
@@ -4265,14 +4411,19 @@ function calculateItemsInView(ctx, params = {}) {
4265
4411
  scrollBufferTop = drawDistance * 1.5;
4266
4412
  scrollBufferBottom = drawDistance * 0.5;
4267
4413
  }
4414
+ const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4415
+ const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4268
4416
  const updateScrollRange = () => {
4269
4417
  const scrollStart = Math.max(0, scroll);
4270
4418
  const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4271
- scrollTopBuffered = scrollStart - scrollBufferTop;
4272
4419
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4273
- scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4420
+ scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4421
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
4274
4422
  };
4275
4423
  updateScrollRange();
4424
+ if (projectedBufferAdjustment !== 0) {
4425
+ scheduleRenderRangeProjectionSettle(ctx);
4426
+ }
4276
4427
  if (enableScrollForNextCalculateItemsInView && !suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4277
4428
  const { top, bottom } = scrollForNextCalculateItemsInView;
4278
4429
  if (top === null && bottom === null) {
@@ -4430,9 +4581,34 @@ function calculateItemsInView(ctx, params = {}) {
4430
4581
  }
4431
4582
  }
4432
4583
  }
4584
+ const scrollTargetPinnedRange = state.scrollTargetPinnedRange;
4585
+ let scrollTargetPinnedStart = 0;
4586
+ let scrollTargetPinnedEnd = -1;
4587
+ if (scrollTargetPinnedRange) {
4588
+ scrollTargetPinnedStart = Math.max(0, Math.min(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end));
4589
+ scrollTargetPinnedEnd = Math.min(
4590
+ dataLength - 1,
4591
+ Math.max(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end)
4592
+ );
4593
+ }
4594
+ const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4595
+ const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4433
4596
  if (startBuffered !== null && endBuffered !== null) {
4434
4597
  const needNewContainers = [];
4435
4598
  const needNewContainersSet = /* @__PURE__ */ new Set();
4599
+ const addPinnedIndex = (index) => {
4600
+ var _a4;
4601
+ if (index >= 0 && index < dataLength) {
4602
+ const id = (_a4 = idCache[index]) != null ? _a4 : getId(state, index);
4603
+ const containerIndex = containerItemKeys.get(id);
4604
+ if (containerIndex !== void 0) {
4605
+ state.stickyContainerPool.add(containerIndex);
4606
+ } else if (!isNullOrUndefined(id) && !needNewContainersSet.has(index)) {
4607
+ needNewContainersSet.add(index);
4608
+ needNewContainers.push(index);
4609
+ }
4610
+ }
4611
+ };
4436
4612
  for (let i = startBuffered; i <= endBuffered; i++) {
4437
4613
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4438
4614
  if (!containerItemKeys.has(id)) {
@@ -4440,21 +4616,19 @@ function calculateItemsInView(ctx, params = {}) {
4440
4616
  needNewContainers.push(i);
4441
4617
  }
4442
4618
  }
4443
- if (alwaysRenderArr.length > 0) {
4444
- for (const index of alwaysRenderArr) {
4445
- if (index < 0 || index >= dataLength) continue;
4446
- const id = (_n = idCache[index]) != null ? _n : getId(state, index);
4447
- if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
4448
- needNewContainersSet.add(index);
4449
- needNewContainers.push(index);
4450
- }
4619
+ for (const index of alwaysRenderIndicesArr) {
4620
+ addPinnedIndex(index);
4621
+ }
4622
+ if (hasScrollTargetPinnedRange) {
4623
+ for (let index = scrollTargetPinnedStart; index <= scrollTargetPinnedEnd; index++) {
4624
+ addPinnedIndex(index);
4451
4625
  }
4452
4626
  }
4453
4627
  if (stickyHeaderIndicesArr.length > 0) {
4454
4628
  handleStickyActivation(
4455
4629
  ctx,
4456
4630
  stickyHeaderIndicesArr,
4457
- (_o = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _o : -1,
4631
+ (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4458
4632
  needNewContainers,
4459
4633
  needNewContainersSet,
4460
4634
  startBuffered,
@@ -4480,21 +4654,30 @@ function calculateItemsInView(ctx, params = {}) {
4480
4654
  for (const allocation of availableContainerAllocations) {
4481
4655
  const i = allocation.itemIndex;
4482
4656
  const containerIndex = allocation.containerIndex;
4483
- const id = (_p = idCache[i]) != null ? _p : getId(state, i);
4657
+ const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4484
4658
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4485
4659
  if (oldKey && oldKey !== id) {
4486
4660
  containerItemKeys.delete(oldKey);
4487
4661
  }
4488
4662
  set$(ctx, `containerItemKey${containerIndex}`, id);
4489
4663
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
4664
+ set$(ctx, `containerItemInfo${containerIndex}`, {
4665
+ index: i,
4666
+ itemKey: id,
4667
+ value: data[i]
4668
+ });
4490
4669
  if (allocation.itemType !== void 0) {
4491
4670
  state.containerItemTypes.set(containerIndex, allocation.itemType);
4492
4671
  }
4493
4672
  containerItemKeys.set(id, containerIndex);
4494
- (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4673
+ (_p = state.userScrollAnchorReset) == null ? void 0 : _p.keys.add(id);
4674
+ {
4675
+ (_q = state.pendingLayoutEffectMeasurements) != null ? _q : state.pendingLayoutEffectMeasurements = /* @__PURE__ */ new Set();
4676
+ state.pendingLayoutEffectMeasurements.add(id);
4677
+ }
4495
4678
  const containerSticky = `containerSticky${containerIndex}`;
4496
4679
  const isSticky = stickyHeaderIndicesSet.has(i);
4497
- const isAlwaysRender = alwaysRenderSet.has(i);
4680
+ const isPinnedRender = isPinnedRenderIndex(i);
4498
4681
  if (isSticky) {
4499
4682
  set$(ctx, containerSticky, true);
4500
4683
  state.stickyContainerPool.add(containerIndex);
@@ -4502,9 +4685,9 @@ function calculateItemsInView(ctx, params = {}) {
4502
4685
  if (peek$(ctx, containerSticky)) {
4503
4686
  set$(ctx, containerSticky, false);
4504
4687
  }
4505
- if (isAlwaysRender) {
4688
+ if (isPinnedRender) {
4506
4689
  state.stickyContainerPool.add(containerIndex);
4507
- } else if (state.stickyContainerPool.has(containerIndex)) {
4690
+ } else {
4508
4691
  state.stickyContainerPool.delete(containerIndex);
4509
4692
  }
4510
4693
  }
@@ -4519,20 +4702,8 @@ function calculateItemsInView(ctx, params = {}) {
4519
4702
  }
4520
4703
  }
4521
4704
  }
4522
- if (state.userScrollAnchorReset) {
4523
- if (state.userScrollAnchorReset.keys.size === 0) {
4524
- state.userScrollAnchorReset = void 0;
4525
- }
4526
- }
4527
- if (alwaysRenderArr.length > 0) {
4528
- for (const index of alwaysRenderArr) {
4529
- if (index < 0 || index >= dataLength) continue;
4530
- const id = (_r = idCache[index]) != null ? _r : getId(state, index);
4531
- const containerIndex = containerItemKeys.get(id);
4532
- if (containerIndex !== void 0) {
4533
- state.stickyContainerPool.add(containerIndex);
4534
- }
4535
- }
4705
+ if (((_r = state.userScrollAnchorReset) == null ? void 0 : _r.keys.size) === 0) {
4706
+ state.userScrollAnchorReset = void 0;
4536
4707
  }
4537
4708
  }
4538
4709
  if (state.stickyContainerPool.size > 0) {
@@ -4543,7 +4714,7 @@ function calculateItemsInView(ctx, params = {}) {
4543
4714
  drawDistance,
4544
4715
  (_s = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _s : -1,
4545
4716
  pendingRemoval,
4546
- alwaysRenderSet
4717
+ isPinnedRenderIndex
4547
4718
  );
4548
4719
  }
4549
4720
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
@@ -4561,6 +4732,7 @@ function calculateItemsInView(ctx, params = {}) {
4561
4732
  }
4562
4733
  set$(ctx, `containerItemKey${i}`, void 0);
4563
4734
  set$(ctx, `containerItemData${i}`, void 0);
4735
+ set$(ctx, `containerItemInfo${i}`, void 0);
4564
4736
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
4565
4737
  set$(ctx, `containerColumn${i}`, -1);
4566
4738
  set$(ctx, `containerSpan${i}`, 1);
@@ -4702,6 +4874,36 @@ function mergeItemSizeUpdateResult(result, next) {
4702
4874
  result.needsRecalculate || (result.needsRecalculate = next.needsRecalculate);
4703
4875
  result.shouldMaintainScrollAtEnd || (result.shouldMaintainScrollAtEnd = next.shouldMaintainScrollAtEnd);
4704
4876
  }
4877
+ var batchedItemSizeRecalculates = /* @__PURE__ */ new WeakMap();
4878
+ function flushBatchedItemSizeRecalculate(ctx, didFallback = false, expectedResult) {
4879
+ const result = batchedItemSizeRecalculates.get(ctx);
4880
+ if (!result || expectedResult && result !== expectedResult) {
4881
+ return;
4882
+ }
4883
+ batchedItemSizeRecalculates.delete(ctx);
4884
+ if (didFallback) {
4885
+ ctx.state.pendingLayoutEffectMeasurements = void 0;
4886
+ }
4887
+ flushItemSizeUpdates(ctx, result);
4888
+ }
4889
+ function queueItemSizeRecalculate(ctx, result) {
4890
+ var _a3, _b;
4891
+ const batch = batchedItemSizeRecalculates.get(ctx);
4892
+ if (batch) {
4893
+ mergeItemSizeUpdateResult(batch, result);
4894
+ } else {
4895
+ const nextBatch = { ...result };
4896
+ batchedItemSizeRecalculates.set(ctx, nextBatch);
4897
+ if ((_a3 = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _a3.size) {
4898
+ requestAnimationFrame(() => {
4899
+ flushBatchedItemSizeRecalculate(ctx, true, nextBatch);
4900
+ });
4901
+ }
4902
+ }
4903
+ if (!((_b = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _b.size)) {
4904
+ flushBatchedItemSizeRecalculate(ctx);
4905
+ }
4906
+ }
4705
4907
  function flushItemSizeUpdates(ctx, result) {
4706
4908
  var _a3;
4707
4909
  const state = ctx.state;
@@ -4718,12 +4920,22 @@ function flushItemSizeUpdates(ctx, result) {
4718
4920
  function updateItemSizes(ctx, measurement) {
4719
4921
  var _a3, _b, _c, _d;
4720
4922
  const state = ctx.state;
4923
+ let didDrainLayoutEffectMeasurements = false;
4924
+ if (measurement.fromLayoutEffect) {
4925
+ const pendingLayoutEffectMeasurements = state.pendingLayoutEffectMeasurements;
4926
+ if ((pendingLayoutEffectMeasurements == null ? void 0 : pendingLayoutEffectMeasurements.delete(measurement.itemKey)) && pendingLayoutEffectMeasurements.size === 0) {
4927
+ state.pendingLayoutEffectMeasurements = void 0;
4928
+ didDrainLayoutEffectMeasurements = true;
4929
+ }
4930
+ }
4721
4931
  const pendingKeys = (_a3 = state.userScrollAnchorReset) == null ? void 0 : _a3.keys;
4722
4932
  const shouldBatchPendingMeasurements = measurement.fromLayoutEffect && !!(pendingKeys == null ? void 0 : pendingKeys.size);
4933
+ const shouldQueueRecalculate = !!measurement.fromLayoutEffect;
4934
+ let result;
4723
4935
  if (!shouldBatchPendingMeasurements) {
4724
- flushItemSizeUpdates(ctx, applyItemSize(ctx, measurement.itemKey, measurement.size));
4936
+ result = applyItemSize(ctx, measurement.itemKey, measurement.size);
4725
4937
  } else {
4726
- const result = {};
4938
+ result = {};
4727
4939
  const updateContainerItemSize = (itemKey, containerId, size) => {
4728
4940
  if (containerId !== void 0 && peek$(ctx, `containerItemKey${containerId}`) === itemKey) {
4729
4941
  mergeItemSizeUpdateResult(result, applyItemSize(ctx, itemKey, size));
@@ -4741,8 +4953,15 @@ function updateItemSizes(ctx, measurement) {
4741
4953
  });
4742
4954
  }
4743
4955
  }
4956
+ }
4957
+ if (shouldQueueRecalculate && result.needsRecalculate) {
4958
+ queueItemSizeRecalculate(ctx, result);
4959
+ } else {
4744
4960
  flushItemSizeUpdates(ctx, result);
4745
4961
  }
4962
+ if (didDrainLayoutEffectMeasurements) {
4963
+ flushBatchedItemSizeRecalculate(ctx);
4964
+ }
4746
4965
  }
4747
4966
  function applyItemSize(ctx, itemKey, sizeObj) {
4748
4967
  var _a3;
@@ -5134,12 +5353,9 @@ var Container = typedMemo(function Container2({
5134
5353
  ctx.viewRefs.set(id, ref);
5135
5354
  return {
5136
5355
  containerId: id,
5137
- index,
5138
- itemKey,
5139
- triggerLayout,
5140
- value: data
5356
+ triggerLayout
5141
5357
  };
5142
- }, [id, itemKey, index, data, triggerLayout]);
5358
+ }, [id, triggerLayout]);
5143
5359
  React3.useLayoutEffect(() => {
5144
5360
  ctx.containerLayoutTriggers.set(id, triggerLayout);
5145
5361
  return () => {