@legendapp/list 3.3.0 → 3.3.1

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.
@@ -1463,6 +1463,7 @@ function finishScrollTo(ctx) {
1463
1463
  const scrollingTo = state.scrollingTo;
1464
1464
  state.scrollHistory.length = 0;
1465
1465
  state.scrollingTo = void 0;
1466
+ state.scrollTargetPinnedRange = void 0;
1466
1467
  if (state.pendingTotalSize !== void 0) {
1467
1468
  addTotalSize(ctx, null, state.pendingTotalSize);
1468
1469
  }
@@ -1956,6 +1957,8 @@ function prepareMVCP(ctx, dataChanged) {
1956
1957
  }
1957
1958
 
1958
1959
  // src/utils/getScrollVelocity.ts
1960
+ var MAX_SCROLL_VELOCITY_WINDOW_MS = 1e3;
1961
+ var SCROLL_VELOCITY_HALF_LIFE_MS = 200;
1959
1962
  var getScrollVelocity = (state) => {
1960
1963
  const { scrollHistory } = state;
1961
1964
  const newestIndex = scrollHistory.length - 1;
@@ -1963,35 +1966,37 @@ var getScrollVelocity = (state) => {
1963
1966
  return 0;
1964
1967
  }
1965
1968
  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) {
1969
+ if (Date.now() - newest.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1976
1970
  return 0;
1977
1971
  }
1978
- let oldest = newest;
1979
- for (let i = newestIndex - 1; i >= 0; i--) {
1972
+ let direction = 0;
1973
+ let weightedVelocity = 0;
1974
+ let totalWeight = 0;
1975
+ for (let i = newestIndex; i > 0; i--) {
1980
1976
  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;
1977
+ const previous = scrollHistory[i - 1];
1978
+ const scrollDiff = current.scroll - previous.scroll;
1979
+ const timeDiff = current.time - previous.time;
1980
+ const deltaSign = Math.sign(scrollDiff);
1981
+ if (deltaSign !== 0) {
1982
+ if (direction === 0) {
1983
+ direction = deltaSign;
1984
+ } else if (deltaSign !== direction) {
1985
+ break;
1986
+ }
1986
1987
  }
1987
- if (now - current.time > 1e3) {
1988
+ if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1988
1989
  break;
1989
1990
  }
1990
- oldest = current;
1991
+ if (scrollDiff === 0 || timeDiff <= 0) {
1992
+ continue;
1993
+ }
1994
+ const age = newest.time - current.time;
1995
+ const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
1996
+ weightedVelocity += scrollDiff / timeDiff * weight;
1997
+ totalWeight += weight;
1991
1998
  }
1992
- const scrollDiff = newest.scroll - oldest.scroll;
1993
- const timeDiff = newest.time - oldest.time;
1994
- return timeDiff > 0 ? scrollDiff / timeDiff : 0;
1999
+ return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
1995
2000
  };
1996
2001
 
1997
2002
  // src/core/updateScroll.ts
@@ -2108,8 +2113,93 @@ function syncInitialScrollNativeWatchdog(state, options) {
2108
2113
  initialScrollWatchdog.clear(state);
2109
2114
  }
2110
2115
  }
2111
- function scrollTo(ctx, params) {
2116
+ function findPositionIndexAtOrBeforeOffset(ctx, offset) {
2117
+ const state = ctx.state;
2118
+ const dataLength = state.props.data.length;
2119
+ let low = 0;
2120
+ let high = dataLength - 1;
2121
+ let match;
2122
+ while (low <= high) {
2123
+ const mid = Math.floor((low + high) / 2);
2124
+ const top = state.positions[mid];
2125
+ if (top === void 0) {
2126
+ high = mid - 1;
2127
+ } else {
2128
+ if (top <= offset) {
2129
+ match = mid;
2130
+ low = mid + 1;
2131
+ } else {
2132
+ high = mid - 1;
2133
+ }
2134
+ }
2135
+ }
2136
+ return match;
2137
+ }
2138
+ function getItemBottom(ctx, index) {
2112
2139
  var _a3;
2140
+ const top = ctx.state.positions[index];
2141
+ if (top === void 0) {
2142
+ return void 0;
2143
+ }
2144
+ const itemSize = (_a3 = getItemSizeAtIndex(ctx, index)) != null ? _a3 : 0;
2145
+ return top + (Number.isFinite(itemSize) ? itemSize : 0);
2146
+ }
2147
+ function getTargetViewportRenderRange(ctx, targetOffset, targetIndex) {
2148
+ const state = ctx.state;
2149
+ const dataLength = state.props.data.length;
2150
+ if (dataLength === 0) {
2151
+ return void 0;
2152
+ }
2153
+ const viewportStart = Math.max(0, targetOffset);
2154
+ const viewportEnd = Math.max(viewportStart, targetOffset + state.scrollLength);
2155
+ let start = targetIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, targetIndex)) : findPositionIndexAtOrBeforeOffset(ctx, viewportStart);
2156
+ if (start === void 0) {
2157
+ return void 0;
2158
+ }
2159
+ if (targetIndex !== void 0 && state.positions[start] === void 0) {
2160
+ return { end: start, start };
2161
+ }
2162
+ if (targetIndex === void 0) {
2163
+ const startBottom = getItemBottom(ctx, start);
2164
+ if (startBottom === void 0 || startBottom <= viewportStart) {
2165
+ return void 0;
2166
+ }
2167
+ }
2168
+ while (start > 0) {
2169
+ const top = state.positions[start];
2170
+ if (top === void 0 || top <= viewportStart || state.positions[start - 1] === void 0) {
2171
+ break;
2172
+ }
2173
+ start--;
2174
+ }
2175
+ while (start > 0) {
2176
+ const previousBottom = getItemBottom(ctx, start - 1);
2177
+ if (previousBottom === void 0 || previousBottom <= viewportStart) {
2178
+ break;
2179
+ }
2180
+ start--;
2181
+ }
2182
+ let end = start;
2183
+ while (end + 1 < dataLength) {
2184
+ const nextTop = state.positions[end + 1];
2185
+ if (nextTop === void 0 || nextTop > viewportEnd) {
2186
+ break;
2187
+ }
2188
+ end++;
2189
+ }
2190
+ return { end, start };
2191
+ }
2192
+ function pinScrollTargetRenderRange(ctx, targetOffset, targetIndex) {
2193
+ const range = getTargetViewportRenderRange(ctx, targetOffset, targetIndex);
2194
+ if (range) {
2195
+ ctx.state.scrollTargetPinnedRange = range;
2196
+ ctx.state.scrollForNextCalculateItemsInView = void 0;
2197
+ } else {
2198
+ ctx.state.scrollTargetPinnedRange = void 0;
2199
+ }
2200
+ }
2201
+ function scrollTo(ctx, params) {
2202
+ var _a3, _b;
2113
2203
  const state = ctx.state;
2114
2204
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2115
2205
  const {
@@ -2144,11 +2234,20 @@ function scrollTo(ctx, params) {
2144
2234
  targetOffset,
2145
2235
  waitForInitialScrollCompletionFrame
2146
2236
  };
2237
+ if (!isInitialScroll) {
2238
+ pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2239
+ }
2147
2240
  }
2148
2241
  state.scrollPending = targetOffset;
2149
2242
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2150
- if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2151
- updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
2243
+ if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2244
+ if (animated) {
2245
+ if (state.scrollTargetPinnedRange) {
2246
+ (_b = state.triggerCalculateItemsInView) == null ? void 0 : _b.call(state);
2247
+ }
2248
+ } else {
2249
+ updateScroll(ctx, targetOffset, true, { markHasScrolled: false });
2250
+ }
2152
2251
  }
2153
2252
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2154
2253
  doScrollTo(ctx, { animated, horizontal, offset });
@@ -3965,6 +4064,32 @@ function setDidLayout(ctx) {
3965
4064
  }
3966
4065
 
3967
4066
  // src/core/calculateItemsInView.ts
4067
+ var RENDER_RANGE_PROJECTION_FULL_VELOCITY = 4;
4068
+ var RENDER_RANGE_PROJECTION_SETTLE_DELAY = 100;
4069
+ function getProjectedBufferAdjustment(scrollVelocity, trailingBuffer) {
4070
+ if (trailingBuffer <= 0) {
4071
+ return 0;
4072
+ }
4073
+ const velocityProgress = Math.min(1, Math.abs(scrollVelocity) / RENDER_RANGE_PROJECTION_FULL_VELOCITY);
4074
+ return Math.sign(scrollVelocity) * trailingBuffer * velocityProgress;
4075
+ }
4076
+ function scheduleRenderRangeProjectionSettle(ctx) {
4077
+ const state = ctx.state;
4078
+ const previousTimeout = state.timeoutRenderRangeProjectionSettle;
4079
+ if (previousTimeout !== void 0) {
4080
+ clearTimeout(previousTimeout);
4081
+ state.timeouts.delete(previousTimeout);
4082
+ }
4083
+ const timeout = setTimeout(() => {
4084
+ var _a3;
4085
+ state.timeoutRenderRangeProjectionSettle = void 0;
4086
+ state.timeouts.delete(timeout);
4087
+ state.scrollHistory.length = 0;
4088
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
4089
+ }, RENDER_RANGE_PROJECTION_SETTLE_DELAY);
4090
+ state.timeoutRenderRangeProjectionSettle = timeout;
4091
+ state.timeouts.add(timeout);
4092
+ }
3968
4093
  function findCurrentStickyIndex(stickyArray, scroll, state) {
3969
4094
  const positions = state.positions;
3970
4095
  for (let i = stickyArray.length - 1; i >= 0; i--) {
@@ -4005,14 +4130,14 @@ function handleStickyActivation(ctx, stickyArray, currentStickyIdx, needNewConta
4005
4130
  }
4006
4131
  }
4007
4132
  }
4008
- function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, alwaysRenderIndicesSet) {
4133
+ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, isPinnedRenderIndex) {
4009
4134
  var _a3, _b;
4010
4135
  const state = ctx.state;
4011
4136
  for (const containerIndex of state.stickyContainerPool) {
4012
4137
  const itemKey = peek$(ctx, `containerItemKey${containerIndex}`);
4013
4138
  const itemIndex = itemKey ? state.indexByKey.get(itemKey) : void 0;
4014
4139
  if (itemIndex === void 0) continue;
4015
- if (alwaysRenderIndicesSet.has(itemIndex)) continue;
4140
+ if (isPinnedRenderIndex(itemIndex)) continue;
4016
4141
  const arrayIdx = stickyArray.indexOf(itemIndex);
4017
4142
  if (arrayIdx === -1) {
4018
4143
  state.stickyContainerPool.delete(containerIndex);
@@ -4176,8 +4301,6 @@ function calculateItemsInView(ctx, params = {}) {
4176
4301
  const { data } = state.props;
4177
4302
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4178
4303
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4179
- const alwaysRenderArr = alwaysRenderIndicesArr || [];
4180
- const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
4181
4304
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4182
4305
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
4183
4306
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
@@ -4244,14 +4367,19 @@ function calculateItemsInView(ctx, params = {}) {
4244
4367
  scrollBufferTop = drawDistance * 1.5;
4245
4368
  scrollBufferBottom = drawDistance * 0.5;
4246
4369
  }
4370
+ const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4371
+ const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4247
4372
  const updateScrollRange = () => {
4248
4373
  const scrollStart = Math.max(0, scroll);
4249
4374
  const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4250
- scrollTopBuffered = scrollStart - scrollBufferTop;
4251
4375
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4252
- scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4376
+ scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4377
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
4253
4378
  };
4254
4379
  updateScrollRange();
4380
+ if (projectedBufferAdjustment !== 0) {
4381
+ scheduleRenderRangeProjectionSettle(ctx);
4382
+ }
4255
4383
  if (enableScrollForNextCalculateItemsInView && !suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4256
4384
  const { top, bottom } = scrollForNextCalculateItemsInView;
4257
4385
  if (top === null && bottom === null) {
@@ -4409,9 +4537,34 @@ function calculateItemsInView(ctx, params = {}) {
4409
4537
  }
4410
4538
  }
4411
4539
  }
4540
+ const scrollTargetPinnedRange = state.scrollTargetPinnedRange;
4541
+ let scrollTargetPinnedStart = 0;
4542
+ let scrollTargetPinnedEnd = -1;
4543
+ if (scrollTargetPinnedRange) {
4544
+ scrollTargetPinnedStart = Math.max(0, Math.min(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end));
4545
+ scrollTargetPinnedEnd = Math.min(
4546
+ dataLength - 1,
4547
+ Math.max(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end)
4548
+ );
4549
+ }
4550
+ const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4551
+ const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4412
4552
  if (startBuffered !== null && endBuffered !== null) {
4413
4553
  const needNewContainers = [];
4414
4554
  const needNewContainersSet = /* @__PURE__ */ new Set();
4555
+ const addPinnedIndex = (index) => {
4556
+ var _a4;
4557
+ if (index >= 0 && index < dataLength) {
4558
+ const id = (_a4 = idCache[index]) != null ? _a4 : getId(state, index);
4559
+ const containerIndex = containerItemKeys.get(id);
4560
+ if (containerIndex !== void 0) {
4561
+ state.stickyContainerPool.add(containerIndex);
4562
+ } else if (!isNullOrUndefined(id) && !needNewContainersSet.has(index)) {
4563
+ needNewContainersSet.add(index);
4564
+ needNewContainers.push(index);
4565
+ }
4566
+ }
4567
+ };
4415
4568
  for (let i = startBuffered; i <= endBuffered; i++) {
4416
4569
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4417
4570
  if (!containerItemKeys.has(id)) {
@@ -4419,21 +4572,19 @@ function calculateItemsInView(ctx, params = {}) {
4419
4572
  needNewContainers.push(i);
4420
4573
  }
4421
4574
  }
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
- }
4575
+ for (const index of alwaysRenderIndicesArr) {
4576
+ addPinnedIndex(index);
4577
+ }
4578
+ if (hasScrollTargetPinnedRange) {
4579
+ for (let index = scrollTargetPinnedStart; index <= scrollTargetPinnedEnd; index++) {
4580
+ addPinnedIndex(index);
4430
4581
  }
4431
4582
  }
4432
4583
  if (stickyHeaderIndicesArr.length > 0) {
4433
4584
  handleStickyActivation(
4434
4585
  ctx,
4435
4586
  stickyHeaderIndicesArr,
4436
- (_o = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _o : -1,
4587
+ (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4437
4588
  needNewContainers,
4438
4589
  needNewContainersSet,
4439
4590
  startBuffered,
@@ -4459,7 +4610,7 @@ function calculateItemsInView(ctx, params = {}) {
4459
4610
  for (const allocation of availableContainerAllocations) {
4460
4611
  const i = allocation.itemIndex;
4461
4612
  const containerIndex = allocation.containerIndex;
4462
- const id = (_p = idCache[i]) != null ? _p : getId(state, i);
4613
+ const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4463
4614
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4464
4615
  if (oldKey && oldKey !== id) {
4465
4616
  containerItemKeys.delete(oldKey);
@@ -4470,10 +4621,14 @@ function calculateItemsInView(ctx, params = {}) {
4470
4621
  state.containerItemTypes.set(containerIndex, allocation.itemType);
4471
4622
  }
4472
4623
  containerItemKeys.set(id, containerIndex);
4473
- (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4624
+ (_p = state.userScrollAnchorReset) == null ? void 0 : _p.keys.add(id);
4625
+ {
4626
+ (_q = state.pendingLayoutEffectMeasurements) != null ? _q : state.pendingLayoutEffectMeasurements = /* @__PURE__ */ new Set();
4627
+ state.pendingLayoutEffectMeasurements.add(id);
4628
+ }
4474
4629
  const containerSticky = `containerSticky${containerIndex}`;
4475
4630
  const isSticky = stickyHeaderIndicesSet.has(i);
4476
- const isAlwaysRender = alwaysRenderSet.has(i);
4631
+ const isPinnedRender = isPinnedRenderIndex(i);
4477
4632
  if (isSticky) {
4478
4633
  set$(ctx, containerSticky, true);
4479
4634
  state.stickyContainerPool.add(containerIndex);
@@ -4481,9 +4636,9 @@ function calculateItemsInView(ctx, params = {}) {
4481
4636
  if (peek$(ctx, containerSticky)) {
4482
4637
  set$(ctx, containerSticky, false);
4483
4638
  }
4484
- if (isAlwaysRender) {
4639
+ if (isPinnedRender) {
4485
4640
  state.stickyContainerPool.add(containerIndex);
4486
- } else if (state.stickyContainerPool.has(containerIndex)) {
4641
+ } else {
4487
4642
  state.stickyContainerPool.delete(containerIndex);
4488
4643
  }
4489
4644
  }
@@ -4498,20 +4653,8 @@ function calculateItemsInView(ctx, params = {}) {
4498
4653
  }
4499
4654
  }
4500
4655
  }
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
- }
4656
+ if (((_r = state.userScrollAnchorReset) == null ? void 0 : _r.keys.size) === 0) {
4657
+ state.userScrollAnchorReset = void 0;
4515
4658
  }
4516
4659
  }
4517
4660
  if (state.stickyContainerPool.size > 0) {
@@ -4522,7 +4665,7 @@ function calculateItemsInView(ctx, params = {}) {
4522
4665
  drawDistance,
4523
4666
  (_s = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _s : -1,
4524
4667
  pendingRemoval,
4525
- alwaysRenderSet
4668
+ isPinnedRenderIndex
4526
4669
  );
4527
4670
  }
4528
4671
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
@@ -4681,6 +4824,36 @@ function mergeItemSizeUpdateResult(result, next) {
4681
4824
  result.needsRecalculate || (result.needsRecalculate = next.needsRecalculate);
4682
4825
  result.shouldMaintainScrollAtEnd || (result.shouldMaintainScrollAtEnd = next.shouldMaintainScrollAtEnd);
4683
4826
  }
4827
+ var batchedItemSizeRecalculates = /* @__PURE__ */ new WeakMap();
4828
+ function flushBatchedItemSizeRecalculate(ctx, didFallback = false, expectedResult) {
4829
+ const result = batchedItemSizeRecalculates.get(ctx);
4830
+ if (!result || expectedResult && result !== expectedResult) {
4831
+ return;
4832
+ }
4833
+ batchedItemSizeRecalculates.delete(ctx);
4834
+ if (didFallback) {
4835
+ ctx.state.pendingLayoutEffectMeasurements = void 0;
4836
+ }
4837
+ flushItemSizeUpdates(ctx, result);
4838
+ }
4839
+ function queueItemSizeRecalculate(ctx, result) {
4840
+ var _a3, _b;
4841
+ const batch = batchedItemSizeRecalculates.get(ctx);
4842
+ if (batch) {
4843
+ mergeItemSizeUpdateResult(batch, result);
4844
+ } else {
4845
+ const nextBatch = { ...result };
4846
+ batchedItemSizeRecalculates.set(ctx, nextBatch);
4847
+ if ((_a3 = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _a3.size) {
4848
+ requestAnimationFrame(() => {
4849
+ flushBatchedItemSizeRecalculate(ctx, true, nextBatch);
4850
+ });
4851
+ }
4852
+ }
4853
+ if (!((_b = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _b.size)) {
4854
+ flushBatchedItemSizeRecalculate(ctx);
4855
+ }
4856
+ }
4684
4857
  function flushItemSizeUpdates(ctx, result) {
4685
4858
  var _a3;
4686
4859
  const state = ctx.state;
@@ -4697,12 +4870,22 @@ function flushItemSizeUpdates(ctx, result) {
4697
4870
  function updateItemSizes(ctx, measurement) {
4698
4871
  var _a3, _b, _c, _d;
4699
4872
  const state = ctx.state;
4873
+ let didDrainLayoutEffectMeasurements = false;
4874
+ if (measurement.fromLayoutEffect) {
4875
+ const pendingLayoutEffectMeasurements = state.pendingLayoutEffectMeasurements;
4876
+ if ((pendingLayoutEffectMeasurements == null ? void 0 : pendingLayoutEffectMeasurements.delete(measurement.itemKey)) && pendingLayoutEffectMeasurements.size === 0) {
4877
+ state.pendingLayoutEffectMeasurements = void 0;
4878
+ didDrainLayoutEffectMeasurements = true;
4879
+ }
4880
+ }
4700
4881
  const pendingKeys = (_a3 = state.userScrollAnchorReset) == null ? void 0 : _a3.keys;
4701
4882
  const shouldBatchPendingMeasurements = measurement.fromLayoutEffect && !!(pendingKeys == null ? void 0 : pendingKeys.size);
4883
+ const shouldQueueRecalculate = !!measurement.fromLayoutEffect;
4884
+ let result;
4702
4885
  if (!shouldBatchPendingMeasurements) {
4703
- flushItemSizeUpdates(ctx, applyItemSize(ctx, measurement.itemKey, measurement.size));
4886
+ result = applyItemSize(ctx, measurement.itemKey, measurement.size);
4704
4887
  } else {
4705
- const result = {};
4888
+ result = {};
4706
4889
  const updateContainerItemSize = (itemKey, containerId, size) => {
4707
4890
  if (containerId !== void 0 && peek$(ctx, `containerItemKey${containerId}`) === itemKey) {
4708
4891
  mergeItemSizeUpdateResult(result, applyItemSize(ctx, itemKey, size));
@@ -4720,8 +4903,15 @@ function updateItemSizes(ctx, measurement) {
4720
4903
  });
4721
4904
  }
4722
4905
  }
4906
+ }
4907
+ if (shouldQueueRecalculate && result.needsRecalculate) {
4908
+ queueItemSizeRecalculate(ctx, result);
4909
+ } else {
4723
4910
  flushItemSizeUpdates(ctx, result);
4724
4911
  }
4912
+ if (didDrainLayoutEffectMeasurements) {
4913
+ flushBatchedItemSizeRecalculate(ctx);
4914
+ }
4725
4915
  }
4726
4916
  function applyItemSize(ctx, itemKey, sizeObj) {
4727
4917
  var _a3;