@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.
package/react-native.js CHANGED
@@ -138,6 +138,7 @@ var createAnimatedValue = (value) => new ReactNative.Animated.Value(value);
138
138
 
139
139
  // src/state/state.tsx
140
140
  var ContextState = React2__namespace.createContext(null);
141
+ var SIGNAL_NAMES_SEPARATOR = "\0";
141
142
  var contextNum = 0;
142
143
  function StateProvider({ children }) {
143
144
  const [value] = React2__namespace.useState(() => ({
@@ -209,6 +210,12 @@ function createSelectorFunctionsArr(ctx, signalNames) {
209
210
  }
210
211
  };
211
212
  }
213
+ function getSignalNamesKey(signalNames) {
214
+ return signalNames.length === 1 ? signalNames[0] : signalNames.join(SIGNAL_NAMES_SEPARATOR);
215
+ }
216
+ function getSignalNamesFromKey(signalNamesKey) {
217
+ return signalNamesKey.split(SIGNAL_NAMES_SEPARATOR);
218
+ }
212
219
  function listen$(ctx, signalName, cb) {
213
220
  const { listeners } = ctx;
214
221
  let setListeners = listeners.get(signalName);
@@ -256,7 +263,11 @@ function notifyPosition$(ctx, key, value) {
256
263
  }
257
264
  function useArr$(signalNames) {
258
265
  const ctx = React2__namespace.useContext(ContextState);
259
- const { subscribe, get } = React2__namespace.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
266
+ const signalNamesKey = getSignalNamesKey(signalNames);
267
+ const { subscribe, get } = React2__namespace.useMemo(
268
+ () => createSelectorFunctionsArr(ctx, getSignalNamesFromKey(signalNamesKey)),
269
+ [ctx, signalNamesKey]
270
+ );
260
271
  const value = shim.useSyncExternalStore(subscribe, get, get);
261
272
  return value;
262
273
  }
@@ -608,9 +619,22 @@ function findContainerId(ctx, key) {
608
619
 
609
620
  // src/state/ContextContainer.ts
610
621
  var ContextContainer = React2.createContext(null);
622
+ var NO_CONTAINER_ID = -1;
611
623
  function useContextContainer() {
612
624
  return React2.useContext(ContextContainer);
613
625
  }
626
+ function useContainerItemInfo(containerContext) {
627
+ var _a3;
628
+ const containerId = (_a3 = containerContext == null ? void 0 : containerContext.containerId) != null ? _a3 : NO_CONTAINER_ID;
629
+ const [itemInfo] = useArr$([`containerItemInfo${containerId}`]);
630
+ return containerContext ? itemInfo : void 0;
631
+ }
632
+ function useContainerItemKey(containerContext) {
633
+ var _a3;
634
+ const containerId = (_a3 = containerContext == null ? void 0 : containerContext.containerId) != null ? _a3 : NO_CONTAINER_ID;
635
+ const [itemKey] = useArr$([`containerItemKey${containerId}`]);
636
+ return containerContext ? itemKey : void 0;
637
+ }
614
638
  function useAdaptiveRender() {
615
639
  const [mode] = useArr$(["adaptiveRender"]);
616
640
  return mode;
@@ -681,40 +705,35 @@ function useViewabilityAmount(callback) {
681
705
  }
682
706
  function useRecyclingEffect(effect) {
683
707
  const containerContext = useContextContainer();
684
- const prevValues = React2.useRef({
685
- prevIndex: void 0,
686
- prevItem: void 0
687
- });
708
+ const itemInfo = useContainerItemInfo(containerContext);
709
+ const prevInfo = React2.useRef(void 0);
688
710
  React2.useEffect(() => {
689
- if (!containerContext) {
711
+ if (!itemInfo) {
690
712
  return;
691
713
  }
692
- const { index, value } = containerContext;
693
714
  let ret;
694
- if (prevValues.current.prevIndex !== void 0 && prevValues.current.prevItem !== void 0) {
715
+ if (prevInfo.current) {
695
716
  ret = effect({
696
- index,
697
- item: value,
698
- prevIndex: prevValues.current.prevIndex,
699
- prevItem: prevValues.current.prevItem
717
+ index: itemInfo.index,
718
+ item: itemInfo.value,
719
+ prevIndex: prevInfo.current.index,
720
+ prevItem: prevInfo.current.value
700
721
  });
701
722
  }
702
- prevValues.current = {
703
- prevIndex: index,
704
- prevItem: value
705
- };
723
+ prevInfo.current = itemInfo;
706
724
  return ret;
707
- }, [effect, containerContext]);
725
+ }, [effect, itemInfo]);
708
726
  }
709
727
  function useRecyclingState(valueOrFun) {
710
- var _a3, _b;
728
+ var _a3;
711
729
  const containerContext = useContextContainer();
712
- const computeValue = (ctx) => {
730
+ const itemInfo = useContainerItemInfo(containerContext);
731
+ const computeValue = (info) => {
713
732
  if (isFunction(valueOrFun)) {
714
733
  const initializer = valueOrFun;
715
- return ctx ? initializer({
716
- index: ctx.index,
717
- item: ctx.value,
734
+ return info ? initializer({
735
+ index: info.index,
736
+ item: info.value,
718
737
  prevIndex: void 0,
719
738
  prevItem: void 0
720
739
  }) : initializer();
@@ -722,13 +741,12 @@ function useRecyclingState(valueOrFun) {
722
741
  return valueOrFun;
723
742
  };
724
743
  const [stateValue, setStateValue] = React2.useState(() => {
725
- return computeValue(containerContext);
744
+ return computeValue(itemInfo);
726
745
  });
727
- const prevItemKeyRef = React2.useRef((_a3 = containerContext == null ? void 0 : containerContext.itemKey) != null ? _a3 : null);
728
- const currentItemKey = (_b = containerContext == null ? void 0 : containerContext.itemKey) != null ? _b : null;
729
- if (currentItemKey !== null && prevItemKeyRef.current !== currentItemKey) {
730
- prevItemKeyRef.current = currentItemKey;
731
- setStateValue(computeValue(containerContext));
746
+ const prevItemKeyRef = React2.useRef((_a3 = itemInfo == null ? void 0 : itemInfo.itemKey) != null ? _a3 : null);
747
+ if (itemInfo && prevItemKeyRef.current !== itemInfo.itemKey) {
748
+ prevItemKeyRef.current = itemInfo.itemKey;
749
+ setStateValue(computeValue(itemInfo));
732
750
  }
733
751
  const triggerLayout = containerContext == null ? void 0 : containerContext.triggerLayout;
734
752
  const setState = React2.useCallback(
@@ -747,12 +765,10 @@ function useRecyclingState(valueOrFun) {
747
765
  }
748
766
  function useIsLastItem() {
749
767
  const containerContext = useContextContainer();
768
+ const itemKey = useContainerItemKey(containerContext);
750
769
  const isLast = useSelector$("lastItemKeys", (lastItemKeys) => {
751
- if (containerContext) {
752
- const { itemKey } = containerContext;
753
- if (!isNullOrUndefined(itemKey)) {
754
- return (lastItemKeys == null ? void 0 : lastItemKeys.includes(itemKey)) || false;
755
- }
770
+ if (containerContext && !isNullOrUndefined(itemKey)) {
771
+ return (lastItemKeys == null ? void 0 : lastItemKeys.includes(itemKey)) || false;
756
772
  }
757
773
  return false;
758
774
  });
@@ -766,12 +782,7 @@ var noop = () => {
766
782
  };
767
783
  function useSyncLayout() {
768
784
  const containerContext = useContextContainer();
769
- if (IsNewArchitecture && containerContext) {
770
- const { triggerLayout: syncLayout } = containerContext;
771
- return syncLayout;
772
- } else {
773
- return noop;
774
- }
785
+ return IsNewArchitecture && containerContext ? containerContext.triggerLayout : noop;
775
786
  }
776
787
 
777
788
  // src/components/Separator.tsx
@@ -1534,6 +1545,7 @@ function finishScrollTo(ctx) {
1534
1545
  const scrollingTo = state.scrollingTo;
1535
1546
  state.scrollHistory.length = 0;
1536
1547
  state.scrollingTo = void 0;
1548
+ state.scrollTargetPinnedRange = void 0;
1537
1549
  if (state.pendingTotalSize !== void 0) {
1538
1550
  addTotalSize(ctx, null, state.pendingTotalSize);
1539
1551
  }
@@ -2173,6 +2185,8 @@ var flushSync = (fn) => {
2173
2185
  };
2174
2186
 
2175
2187
  // src/utils/getScrollVelocity.ts
2188
+ var MAX_SCROLL_VELOCITY_WINDOW_MS = 1e3;
2189
+ var SCROLL_VELOCITY_HALF_LIFE_MS = 200;
2176
2190
  var getScrollVelocity = (state) => {
2177
2191
  const { scrollHistory } = state;
2178
2192
  const newestIndex = scrollHistory.length - 1;
@@ -2180,35 +2194,37 @@ var getScrollVelocity = (state) => {
2180
2194
  return 0;
2181
2195
  }
2182
2196
  const newest = scrollHistory[newestIndex];
2183
- const now = Date.now();
2184
- let direction = 0;
2185
- for (let i = newestIndex; i > 0; i--) {
2186
- const delta = scrollHistory[i].scroll - scrollHistory[i - 1].scroll;
2187
- if (delta !== 0) {
2188
- direction = Math.sign(delta);
2189
- break;
2190
- }
2191
- }
2192
- if (direction === 0) {
2197
+ if (Date.now() - newest.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
2193
2198
  return 0;
2194
2199
  }
2195
- let oldest = newest;
2196
- for (let i = newestIndex - 1; i >= 0; i--) {
2200
+ let direction = 0;
2201
+ let weightedVelocity = 0;
2202
+ let totalWeight = 0;
2203
+ for (let i = newestIndex; i > 0; i--) {
2197
2204
  const current = scrollHistory[i];
2198
- const next = scrollHistory[i + 1];
2199
- const delta = next.scroll - current.scroll;
2200
- const deltaSign = Math.sign(delta);
2201
- if (deltaSign !== 0 && deltaSign !== direction) {
2202
- break;
2205
+ const previous = scrollHistory[i - 1];
2206
+ const scrollDiff = current.scroll - previous.scroll;
2207
+ const timeDiff = current.time - previous.time;
2208
+ const deltaSign = Math.sign(scrollDiff);
2209
+ if (deltaSign !== 0) {
2210
+ if (direction === 0) {
2211
+ direction = deltaSign;
2212
+ } else if (deltaSign !== direction) {
2213
+ break;
2214
+ }
2203
2215
  }
2204
- if (now - current.time > 1e3) {
2216
+ if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
2205
2217
  break;
2206
2218
  }
2207
- oldest = current;
2219
+ if (scrollDiff === 0 || timeDiff <= 0) {
2220
+ continue;
2221
+ }
2222
+ const age = newest.time - current.time;
2223
+ const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
2224
+ weightedVelocity += scrollDiff / timeDiff * weight;
2225
+ totalWeight += weight;
2208
2226
  }
2209
- const scrollDiff = newest.scroll - oldest.scroll;
2210
- const timeDiff = newest.time - oldest.time;
2211
- return timeDiff > 0 ? scrollDiff / timeDiff : 0;
2227
+ return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
2212
2228
  };
2213
2229
 
2214
2230
  // src/core/updateScroll.ts
@@ -2325,8 +2341,93 @@ function syncInitialScrollNativeWatchdog(state, options) {
2325
2341
  initialScrollWatchdog.clear(state);
2326
2342
  }
2327
2343
  }
2328
- function scrollTo(ctx, params) {
2344
+ function findPositionIndexAtOrBeforeOffset(ctx, offset) {
2345
+ const state = ctx.state;
2346
+ const dataLength = state.props.data.length;
2347
+ let low = 0;
2348
+ let high = dataLength - 1;
2349
+ let match;
2350
+ while (low <= high) {
2351
+ const mid = Math.floor((low + high) / 2);
2352
+ const top = state.positions[mid];
2353
+ if (top === void 0) {
2354
+ high = mid - 1;
2355
+ } else {
2356
+ if (top <= offset) {
2357
+ match = mid;
2358
+ low = mid + 1;
2359
+ } else {
2360
+ high = mid - 1;
2361
+ }
2362
+ }
2363
+ }
2364
+ return match;
2365
+ }
2366
+ function getItemBottom(ctx, index) {
2329
2367
  var _a3;
2368
+ const top = ctx.state.positions[index];
2369
+ if (top === void 0) {
2370
+ return void 0;
2371
+ }
2372
+ const itemSize = (_a3 = getItemSizeAtIndex(ctx, index)) != null ? _a3 : 0;
2373
+ return top + (Number.isFinite(itemSize) ? itemSize : 0);
2374
+ }
2375
+ function getTargetViewportRenderRange(ctx, targetOffset, targetIndex) {
2376
+ const state = ctx.state;
2377
+ const dataLength = state.props.data.length;
2378
+ if (dataLength === 0) {
2379
+ return void 0;
2380
+ }
2381
+ const viewportStart = Math.max(0, targetOffset);
2382
+ const viewportEnd = Math.max(viewportStart, targetOffset + state.scrollLength);
2383
+ let start = targetIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, targetIndex)) : findPositionIndexAtOrBeforeOffset(ctx, viewportStart);
2384
+ if (start === void 0) {
2385
+ return void 0;
2386
+ }
2387
+ if (targetIndex !== void 0 && state.positions[start] === void 0) {
2388
+ return { end: start, start };
2389
+ }
2390
+ if (targetIndex === void 0) {
2391
+ const startBottom = getItemBottom(ctx, start);
2392
+ if (startBottom === void 0 || startBottom <= viewportStart) {
2393
+ return void 0;
2394
+ }
2395
+ }
2396
+ while (start > 0) {
2397
+ const top = state.positions[start];
2398
+ if (top === void 0 || top <= viewportStart || state.positions[start - 1] === void 0) {
2399
+ break;
2400
+ }
2401
+ start--;
2402
+ }
2403
+ while (start > 0) {
2404
+ const previousBottom = getItemBottom(ctx, start - 1);
2405
+ if (previousBottom === void 0 || previousBottom <= viewportStart) {
2406
+ break;
2407
+ }
2408
+ start--;
2409
+ }
2410
+ let end = start;
2411
+ while (end + 1 < dataLength) {
2412
+ const nextTop = state.positions[end + 1];
2413
+ if (nextTop === void 0 || nextTop > viewportEnd) {
2414
+ break;
2415
+ }
2416
+ end++;
2417
+ }
2418
+ return { end, start };
2419
+ }
2420
+ function pinScrollTargetRenderRange(ctx, targetOffset, targetIndex) {
2421
+ const range = getTargetViewportRenderRange(ctx, targetOffset, targetIndex);
2422
+ if (range) {
2423
+ ctx.state.scrollTargetPinnedRange = range;
2424
+ ctx.state.scrollForNextCalculateItemsInView = void 0;
2425
+ } else {
2426
+ ctx.state.scrollTargetPinnedRange = void 0;
2427
+ }
2428
+ }
2429
+ function scrollTo(ctx, params) {
2430
+ var _a3, _b;
2330
2431
  const state = ctx.state;
2331
2432
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2332
2433
  const {
@@ -2361,11 +2462,20 @@ function scrollTo(ctx, params) {
2361
2462
  targetOffset,
2362
2463
  waitForInitialScrollCompletionFrame
2363
2464
  };
2465
+ if (!isInitialScroll) {
2466
+ pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2467
+ }
2364
2468
  }
2365
2469
  state.scrollPending = targetOffset;
2366
2470
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2367
- if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2368
- updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
2471
+ if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2472
+ if (animated) {
2473
+ if (state.scrollTargetPinnedRange) {
2474
+ (_b = state.triggerCalculateItemsInView) == null ? void 0 : _b.call(state);
2475
+ }
2476
+ } else {
2477
+ updateScroll(ctx, targetOffset, true, { markHasScrolled: false });
2478
+ }
2369
2479
  }
2370
2480
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2371
2481
  doScrollTo(ctx, { animated, horizontal, isInitialScroll, offset });
@@ -3308,22 +3418,28 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3308
3418
  set$(ctx, `containerSpan${containerIndex}`, span);
3309
3419
  }
3310
3420
  }
3421
+ const prevItemInfo = peek$(ctx, `containerItemInfo${containerIndex}`);
3311
3422
  const prevData = peek$(ctx, `containerItemData${containerIndex}`);
3423
+ let itemInfoValue = prevData;
3424
+ let didChangeItemInfo = (prevItemInfo == null ? void 0 : prevItemInfo.itemKey) !== itemKey || (prevItemInfo == null ? void 0 : prevItemInfo.index) !== itemIndex || (prevItemInfo == null ? void 0 : prevItemInfo.value) !== prevData;
3425
+ const updateData = () => {
3426
+ set$(ctx, `containerItemData${containerIndex}`, item);
3427
+ itemInfoValue = item;
3428
+ didChangeItemInfo = true;
3429
+ didRefreshData = true;
3430
+ };
3312
3431
  if (prevData !== item) {
3313
3432
  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;
3314
3433
  const cachedComparison = (_g = pendingDataComparison == null ? void 0 : pendingDataComparison.byIndex[itemIndex]) != null ? _g : 0;
3315
3434
  if (cachedComparison === 2) {
3316
- set$(ctx, `containerItemData${containerIndex}`, item);
3317
- didRefreshData = true;
3435
+ updateData();
3318
3436
  } else if (cachedComparison !== 1) {
3319
3437
  const nextItemKey = (_h = peek$(ctx, `containerItemKey${containerIndex}`)) != null ? _h : itemKey;
3320
3438
  const prevKey = keyExtractor == null ? void 0 : keyExtractor(prevData, itemIndex);
3321
3439
  if (prevData === void 0 || !keyExtractor || prevKey !== nextItemKey) {
3322
- set$(ctx, `containerItemData${containerIndex}`, item);
3323
- didRefreshData = true;
3440
+ updateData();
3324
3441
  } else if (!itemsAreEqual) {
3325
- set$(ctx, `containerItemData${containerIndex}`, item);
3326
- didRefreshData = true;
3442
+ updateData();
3327
3443
  } else {
3328
3444
  const isEqual = itemsAreEqual(prevData, item, itemIndex, data);
3329
3445
  if (!state.pendingDataComparison || state.pendingDataComparison.previousData !== state.previousData || state.pendingDataComparison.nextData !== data) {
@@ -3339,12 +3455,18 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3339
3455
  state.pendingDataComparison.byIndex[itemIndex] = isEqual ? 1 : 2;
3340
3456
  }
3341
3457
  if (!isEqual) {
3342
- set$(ctx, `containerItemData${containerIndex}`, item);
3343
- didRefreshData = true;
3458
+ updateData();
3344
3459
  }
3345
3460
  }
3346
3461
  }
3347
3462
  }
3463
+ if (didChangeItemInfo) {
3464
+ set$(ctx, `containerItemInfo${containerIndex}`, {
3465
+ index: itemIndex,
3466
+ itemKey,
3467
+ value: itemInfoValue
3468
+ });
3469
+ }
3348
3470
  return { didChangePosition, didRefreshData };
3349
3471
  }
3350
3472
 
@@ -4037,6 +4159,32 @@ function setDidLayout(ctx) {
4037
4159
  }
4038
4160
 
4039
4161
  // src/core/calculateItemsInView.ts
4162
+ var RENDER_RANGE_PROJECTION_FULL_VELOCITY = 4;
4163
+ var RENDER_RANGE_PROJECTION_SETTLE_DELAY = 100;
4164
+ function getProjectedBufferAdjustment(scrollVelocity, trailingBuffer) {
4165
+ if (trailingBuffer <= 0) {
4166
+ return 0;
4167
+ }
4168
+ const velocityProgress = Math.min(1, Math.abs(scrollVelocity) / RENDER_RANGE_PROJECTION_FULL_VELOCITY);
4169
+ return Math.sign(scrollVelocity) * trailingBuffer * velocityProgress;
4170
+ }
4171
+ function scheduleRenderRangeProjectionSettle(ctx) {
4172
+ const state = ctx.state;
4173
+ const previousTimeout = state.timeoutRenderRangeProjectionSettle;
4174
+ if (previousTimeout !== void 0) {
4175
+ clearTimeout(previousTimeout);
4176
+ state.timeouts.delete(previousTimeout);
4177
+ }
4178
+ const timeout = setTimeout(() => {
4179
+ var _a3;
4180
+ state.timeoutRenderRangeProjectionSettle = void 0;
4181
+ state.timeouts.delete(timeout);
4182
+ state.scrollHistory.length = 0;
4183
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
4184
+ }, RENDER_RANGE_PROJECTION_SETTLE_DELAY);
4185
+ state.timeoutRenderRangeProjectionSettle = timeout;
4186
+ state.timeouts.add(timeout);
4187
+ }
4040
4188
  function findCurrentStickyIndex(stickyArray, scroll, state) {
4041
4189
  const positions = state.positions;
4042
4190
  for (let i = stickyArray.length - 1; i >= 0; i--) {
@@ -4077,14 +4225,14 @@ function handleStickyActivation(ctx, stickyArray, currentStickyIdx, needNewConta
4077
4225
  }
4078
4226
  }
4079
4227
  }
4080
- function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, alwaysRenderIndicesSet) {
4228
+ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, isPinnedRenderIndex) {
4081
4229
  var _a3, _b;
4082
4230
  const state = ctx.state;
4083
4231
  for (const containerIndex of state.stickyContainerPool) {
4084
4232
  const itemKey = peek$(ctx, `containerItemKey${containerIndex}`);
4085
4233
  const itemIndex = itemKey ? state.indexByKey.get(itemKey) : void 0;
4086
4234
  if (itemIndex === void 0) continue;
4087
- if (alwaysRenderIndicesSet.has(itemIndex)) continue;
4235
+ if (isPinnedRenderIndex(itemIndex)) continue;
4088
4236
  const arrayIdx = stickyArray.indexOf(itemIndex);
4089
4237
  if (arrayIdx === -1) {
4090
4238
  state.stickyContainerPool.delete(containerIndex);
@@ -4248,8 +4396,6 @@ function calculateItemsInView(ctx, params = {}) {
4248
4396
  const { data } = state.props;
4249
4397
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4250
4398
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4251
- const alwaysRenderArr = alwaysRenderIndicesArr || [];
4252
- const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
4253
4399
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4254
4400
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
4255
4401
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
@@ -4316,14 +4462,19 @@ function calculateItemsInView(ctx, params = {}) {
4316
4462
  scrollBufferTop = drawDistance * 1.5;
4317
4463
  scrollBufferBottom = drawDistance * 0.5;
4318
4464
  }
4465
+ const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4466
+ const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4319
4467
  const updateScrollRange = () => {
4320
4468
  const scrollStart = Math.max(0, scroll);
4321
4469
  const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4322
- scrollTopBuffered = scrollStart - scrollBufferTop;
4323
4470
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4324
- scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4471
+ scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4472
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
4325
4473
  };
4326
4474
  updateScrollRange();
4475
+ if (projectedBufferAdjustment !== 0) {
4476
+ scheduleRenderRangeProjectionSettle(ctx);
4477
+ }
4327
4478
  if (enableScrollForNextCalculateItemsInView && !suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4328
4479
  const { top, bottom } = scrollForNextCalculateItemsInView;
4329
4480
  if (top === null && bottom === null) {
@@ -4481,9 +4632,34 @@ function calculateItemsInView(ctx, params = {}) {
4481
4632
  }
4482
4633
  }
4483
4634
  }
4635
+ const scrollTargetPinnedRange = state.scrollTargetPinnedRange;
4636
+ let scrollTargetPinnedStart = 0;
4637
+ let scrollTargetPinnedEnd = -1;
4638
+ if (scrollTargetPinnedRange) {
4639
+ scrollTargetPinnedStart = Math.max(0, Math.min(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end));
4640
+ scrollTargetPinnedEnd = Math.min(
4641
+ dataLength - 1,
4642
+ Math.max(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end)
4643
+ );
4644
+ }
4645
+ const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4646
+ const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4484
4647
  if (startBuffered !== null && endBuffered !== null) {
4485
4648
  const needNewContainers = [];
4486
4649
  const needNewContainersSet = /* @__PURE__ */ new Set();
4650
+ const addPinnedIndex = (index) => {
4651
+ var _a4;
4652
+ if (index >= 0 && index < dataLength) {
4653
+ const id = (_a4 = idCache[index]) != null ? _a4 : getId(state, index);
4654
+ const containerIndex = containerItemKeys.get(id);
4655
+ if (containerIndex !== void 0) {
4656
+ state.stickyContainerPool.add(containerIndex);
4657
+ } else if (!isNullOrUndefined(id) && !needNewContainersSet.has(index)) {
4658
+ needNewContainersSet.add(index);
4659
+ needNewContainers.push(index);
4660
+ }
4661
+ }
4662
+ };
4487
4663
  for (let i = startBuffered; i <= endBuffered; i++) {
4488
4664
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4489
4665
  if (!containerItemKeys.has(id)) {
@@ -4491,21 +4667,19 @@ function calculateItemsInView(ctx, params = {}) {
4491
4667
  needNewContainers.push(i);
4492
4668
  }
4493
4669
  }
4494
- if (alwaysRenderArr.length > 0) {
4495
- for (const index of alwaysRenderArr) {
4496
- if (index < 0 || index >= dataLength) continue;
4497
- const id = (_n = idCache[index]) != null ? _n : getId(state, index);
4498
- if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
4499
- needNewContainersSet.add(index);
4500
- needNewContainers.push(index);
4501
- }
4670
+ for (const index of alwaysRenderIndicesArr) {
4671
+ addPinnedIndex(index);
4672
+ }
4673
+ if (hasScrollTargetPinnedRange) {
4674
+ for (let index = scrollTargetPinnedStart; index <= scrollTargetPinnedEnd; index++) {
4675
+ addPinnedIndex(index);
4502
4676
  }
4503
4677
  }
4504
4678
  if (stickyHeaderIndicesArr.length > 0) {
4505
4679
  handleStickyActivation(
4506
4680
  ctx,
4507
4681
  stickyHeaderIndicesArr,
4508
- (_o = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _o : -1,
4682
+ (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4509
4683
  needNewContainers,
4510
4684
  needNewContainersSet,
4511
4685
  startBuffered,
@@ -4531,21 +4705,30 @@ function calculateItemsInView(ctx, params = {}) {
4531
4705
  for (const allocation of availableContainerAllocations) {
4532
4706
  const i = allocation.itemIndex;
4533
4707
  const containerIndex = allocation.containerIndex;
4534
- const id = (_p = idCache[i]) != null ? _p : getId(state, i);
4708
+ const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4535
4709
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4536
4710
  if (oldKey && oldKey !== id) {
4537
4711
  containerItemKeys.delete(oldKey);
4538
4712
  }
4539
4713
  set$(ctx, `containerItemKey${containerIndex}`, id);
4540
4714
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
4715
+ set$(ctx, `containerItemInfo${containerIndex}`, {
4716
+ index: i,
4717
+ itemKey: id,
4718
+ value: data[i]
4719
+ });
4541
4720
  if (allocation.itemType !== void 0) {
4542
4721
  state.containerItemTypes.set(containerIndex, allocation.itemType);
4543
4722
  }
4544
4723
  containerItemKeys.set(id, containerIndex);
4545
- (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4724
+ (_p = state.userScrollAnchorReset) == null ? void 0 : _p.keys.add(id);
4725
+ if (IsNewArchitecture) {
4726
+ (_q = state.pendingLayoutEffectMeasurements) != null ? _q : state.pendingLayoutEffectMeasurements = /* @__PURE__ */ new Set();
4727
+ state.pendingLayoutEffectMeasurements.add(id);
4728
+ }
4546
4729
  const containerSticky = `containerSticky${containerIndex}`;
4547
4730
  const isSticky = stickyHeaderIndicesSet.has(i);
4548
- const isAlwaysRender = alwaysRenderSet.has(i);
4731
+ const isPinnedRender = isPinnedRenderIndex(i);
4549
4732
  if (isSticky) {
4550
4733
  set$(ctx, containerSticky, true);
4551
4734
  state.stickyContainerPool.add(containerIndex);
@@ -4553,9 +4736,9 @@ function calculateItemsInView(ctx, params = {}) {
4553
4736
  if (peek$(ctx, containerSticky)) {
4554
4737
  set$(ctx, containerSticky, false);
4555
4738
  }
4556
- if (isAlwaysRender) {
4739
+ if (isPinnedRender) {
4557
4740
  state.stickyContainerPool.add(containerIndex);
4558
- } else if (state.stickyContainerPool.has(containerIndex)) {
4741
+ } else {
4559
4742
  state.stickyContainerPool.delete(containerIndex);
4560
4743
  }
4561
4744
  }
@@ -4570,20 +4753,8 @@ function calculateItemsInView(ctx, params = {}) {
4570
4753
  }
4571
4754
  }
4572
4755
  }
4573
- if (state.userScrollAnchorReset) {
4574
- if (state.userScrollAnchorReset.keys.size === 0) {
4575
- state.userScrollAnchorReset = void 0;
4576
- }
4577
- }
4578
- if (alwaysRenderArr.length > 0) {
4579
- for (const index of alwaysRenderArr) {
4580
- if (index < 0 || index >= dataLength) continue;
4581
- const id = (_r = idCache[index]) != null ? _r : getId(state, index);
4582
- const containerIndex = containerItemKeys.get(id);
4583
- if (containerIndex !== void 0) {
4584
- state.stickyContainerPool.add(containerIndex);
4585
- }
4586
- }
4756
+ if (((_r = state.userScrollAnchorReset) == null ? void 0 : _r.keys.size) === 0) {
4757
+ state.userScrollAnchorReset = void 0;
4587
4758
  }
4588
4759
  }
4589
4760
  if (state.stickyContainerPool.size > 0) {
@@ -4594,7 +4765,7 @@ function calculateItemsInView(ctx, params = {}) {
4594
4765
  drawDistance,
4595
4766
  (_s = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _s : -1,
4596
4767
  pendingRemoval,
4597
- alwaysRenderSet
4768
+ isPinnedRenderIndex
4598
4769
  );
4599
4770
  }
4600
4771
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
@@ -4612,6 +4783,7 @@ function calculateItemsInView(ctx, params = {}) {
4612
4783
  }
4613
4784
  set$(ctx, `containerItemKey${i}`, void 0);
4614
4785
  set$(ctx, `containerItemData${i}`, void 0);
4786
+ set$(ctx, `containerItemInfo${i}`, void 0);
4615
4787
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
4616
4788
  set$(ctx, `containerColumn${i}`, -1);
4617
4789
  set$(ctx, `containerSpan${i}`, 1);
@@ -4766,6 +4938,36 @@ function mergeItemSizeUpdateResult(result, next) {
4766
4938
  result.needsRecalculate || (result.needsRecalculate = next.needsRecalculate);
4767
4939
  result.shouldMaintainScrollAtEnd || (result.shouldMaintainScrollAtEnd = next.shouldMaintainScrollAtEnd);
4768
4940
  }
4941
+ var batchedItemSizeRecalculates = /* @__PURE__ */ new WeakMap();
4942
+ function flushBatchedItemSizeRecalculate(ctx, didFallback = false, expectedResult) {
4943
+ const result = batchedItemSizeRecalculates.get(ctx);
4944
+ if (!result || expectedResult && result !== expectedResult) {
4945
+ return;
4946
+ }
4947
+ batchedItemSizeRecalculates.delete(ctx);
4948
+ if (didFallback) {
4949
+ ctx.state.pendingLayoutEffectMeasurements = void 0;
4950
+ }
4951
+ flushItemSizeUpdates(ctx, result);
4952
+ }
4953
+ function queueItemSizeRecalculate(ctx, result) {
4954
+ var _a3, _b;
4955
+ const batch = batchedItemSizeRecalculates.get(ctx);
4956
+ if (batch) {
4957
+ mergeItemSizeUpdateResult(batch, result);
4958
+ } else {
4959
+ const nextBatch = { ...result };
4960
+ batchedItemSizeRecalculates.set(ctx, nextBatch);
4961
+ if ((_a3 = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _a3.size) {
4962
+ requestAnimationFrame(() => {
4963
+ flushBatchedItemSizeRecalculate(ctx, true, nextBatch);
4964
+ });
4965
+ }
4966
+ }
4967
+ if (!((_b = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _b.size)) {
4968
+ flushBatchedItemSizeRecalculate(ctx);
4969
+ }
4970
+ }
4769
4971
  function flushItemSizeUpdates(ctx, result) {
4770
4972
  var _a3;
4771
4973
  const state = ctx.state;
@@ -4782,12 +4984,22 @@ function flushItemSizeUpdates(ctx, result) {
4782
4984
  function updateItemSizes(ctx, measurement) {
4783
4985
  var _a3, _b, _c, _d;
4784
4986
  const state = ctx.state;
4987
+ let didDrainLayoutEffectMeasurements = false;
4988
+ if (IsNewArchitecture && measurement.fromLayoutEffect) {
4989
+ const pendingLayoutEffectMeasurements = state.pendingLayoutEffectMeasurements;
4990
+ if ((pendingLayoutEffectMeasurements == null ? void 0 : pendingLayoutEffectMeasurements.delete(measurement.itemKey)) && pendingLayoutEffectMeasurements.size === 0) {
4991
+ state.pendingLayoutEffectMeasurements = void 0;
4992
+ didDrainLayoutEffectMeasurements = true;
4993
+ }
4994
+ }
4785
4995
  const pendingKeys = (_a3 = state.userScrollAnchorReset) == null ? void 0 : _a3.keys;
4786
4996
  const shouldBatchPendingMeasurements = IsNewArchitecture && measurement.fromLayoutEffect && !!(pendingKeys == null ? void 0 : pendingKeys.size);
4997
+ const shouldQueueRecalculate = IsNewArchitecture && !!measurement.fromLayoutEffect;
4998
+ let result;
4787
4999
  if (!shouldBatchPendingMeasurements) {
4788
- flushItemSizeUpdates(ctx, applyItemSize(ctx, measurement.itemKey, measurement.size));
5000
+ result = applyItemSize(ctx, measurement.itemKey, measurement.size);
4789
5001
  } else {
4790
- const result = {};
5002
+ result = {};
4791
5003
  const updateContainerItemSize = (itemKey, containerId, size) => {
4792
5004
  if (containerId !== void 0 && peek$(ctx, `containerItemKey${containerId}`) === itemKey) {
4793
5005
  mergeItemSizeUpdateResult(result, applyItemSize(ctx, itemKey, size));
@@ -4805,8 +5017,15 @@ function updateItemSizes(ctx, measurement) {
4805
5017
  });
4806
5018
  }
4807
5019
  }
5020
+ }
5021
+ if (shouldQueueRecalculate && result.needsRecalculate) {
5022
+ queueItemSizeRecalculate(ctx, result);
5023
+ } else {
4808
5024
  flushItemSizeUpdates(ctx, result);
4809
5025
  }
5026
+ if (didDrainLayoutEffectMeasurements) {
5027
+ flushBatchedItemSizeRecalculate(ctx);
5028
+ }
4810
5029
  }
4811
5030
  function applyItemSize(ctx, itemKey, sizeObj) {
4812
5031
  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
  React2.useLayoutEffect(() => {
5144
5360
  ctx.containerLayoutTriggers.set(id, triggerLayout);
5145
5361
  return () => {