@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.
package/react.js CHANGED
@@ -1484,6 +1484,7 @@ function finishScrollTo(ctx) {
1484
1484
  const scrollingTo = state.scrollingTo;
1485
1485
  state.scrollHistory.length = 0;
1486
1486
  state.scrollingTo = void 0;
1487
+ state.scrollTargetPinnedRange = void 0;
1487
1488
  if (state.pendingTotalSize !== void 0) {
1488
1489
  addTotalSize(ctx, null, state.pendingTotalSize);
1489
1490
  }
@@ -1977,6 +1978,8 @@ function prepareMVCP(ctx, dataChanged) {
1977
1978
  }
1978
1979
 
1979
1980
  // src/utils/getScrollVelocity.ts
1981
+ var MAX_SCROLL_VELOCITY_WINDOW_MS = 1e3;
1982
+ var SCROLL_VELOCITY_HALF_LIFE_MS = 200;
1980
1983
  var getScrollVelocity = (state) => {
1981
1984
  const { scrollHistory } = state;
1982
1985
  const newestIndex = scrollHistory.length - 1;
@@ -1984,35 +1987,37 @@ var getScrollVelocity = (state) => {
1984
1987
  return 0;
1985
1988
  }
1986
1989
  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) {
1990
+ if (Date.now() - newest.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1997
1991
  return 0;
1998
1992
  }
1999
- let oldest = newest;
2000
- for (let i = newestIndex - 1; i >= 0; i--) {
1993
+ let direction = 0;
1994
+ let weightedVelocity = 0;
1995
+ let totalWeight = 0;
1996
+ for (let i = newestIndex; i > 0; i--) {
2001
1997
  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;
1998
+ const previous = scrollHistory[i - 1];
1999
+ const scrollDiff = current.scroll - previous.scroll;
2000
+ const timeDiff = current.time - previous.time;
2001
+ const deltaSign = Math.sign(scrollDiff);
2002
+ if (deltaSign !== 0) {
2003
+ if (direction === 0) {
2004
+ direction = deltaSign;
2005
+ } else if (deltaSign !== direction) {
2006
+ break;
2007
+ }
2007
2008
  }
2008
- if (now - current.time > 1e3) {
2009
+ if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
2009
2010
  break;
2010
2011
  }
2011
- oldest = current;
2012
+ if (scrollDiff === 0 || timeDiff <= 0) {
2013
+ continue;
2014
+ }
2015
+ const age = newest.time - current.time;
2016
+ const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
2017
+ weightedVelocity += scrollDiff / timeDiff * weight;
2018
+ totalWeight += weight;
2012
2019
  }
2013
- const scrollDiff = newest.scroll - oldest.scroll;
2014
- const timeDiff = newest.time - oldest.time;
2015
- return timeDiff > 0 ? scrollDiff / timeDiff : 0;
2020
+ return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
2016
2021
  };
2017
2022
 
2018
2023
  // src/core/updateScroll.ts
@@ -2129,8 +2134,93 @@ function syncInitialScrollNativeWatchdog(state, options) {
2129
2134
  initialScrollWatchdog.clear(state);
2130
2135
  }
2131
2136
  }
2132
- function scrollTo(ctx, params) {
2137
+ function findPositionIndexAtOrBeforeOffset(ctx, offset) {
2138
+ const state = ctx.state;
2139
+ const dataLength = state.props.data.length;
2140
+ let low = 0;
2141
+ let high = dataLength - 1;
2142
+ let match;
2143
+ while (low <= high) {
2144
+ const mid = Math.floor((low + high) / 2);
2145
+ const top = state.positions[mid];
2146
+ if (top === void 0) {
2147
+ high = mid - 1;
2148
+ } else {
2149
+ if (top <= offset) {
2150
+ match = mid;
2151
+ low = mid + 1;
2152
+ } else {
2153
+ high = mid - 1;
2154
+ }
2155
+ }
2156
+ }
2157
+ return match;
2158
+ }
2159
+ function getItemBottom(ctx, index) {
2133
2160
  var _a3;
2161
+ const top = ctx.state.positions[index];
2162
+ if (top === void 0) {
2163
+ return void 0;
2164
+ }
2165
+ const itemSize = (_a3 = getItemSizeAtIndex(ctx, index)) != null ? _a3 : 0;
2166
+ return top + (Number.isFinite(itemSize) ? itemSize : 0);
2167
+ }
2168
+ function getTargetViewportRenderRange(ctx, targetOffset, targetIndex) {
2169
+ const state = ctx.state;
2170
+ const dataLength = state.props.data.length;
2171
+ if (dataLength === 0) {
2172
+ return void 0;
2173
+ }
2174
+ const viewportStart = Math.max(0, targetOffset);
2175
+ const viewportEnd = Math.max(viewportStart, targetOffset + state.scrollLength);
2176
+ let start = targetIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, targetIndex)) : findPositionIndexAtOrBeforeOffset(ctx, viewportStart);
2177
+ if (start === void 0) {
2178
+ return void 0;
2179
+ }
2180
+ if (targetIndex !== void 0 && state.positions[start] === void 0) {
2181
+ return { end: start, start };
2182
+ }
2183
+ if (targetIndex === void 0) {
2184
+ const startBottom = getItemBottom(ctx, start);
2185
+ if (startBottom === void 0 || startBottom <= viewportStart) {
2186
+ return void 0;
2187
+ }
2188
+ }
2189
+ while (start > 0) {
2190
+ const top = state.positions[start];
2191
+ if (top === void 0 || top <= viewportStart || state.positions[start - 1] === void 0) {
2192
+ break;
2193
+ }
2194
+ start--;
2195
+ }
2196
+ while (start > 0) {
2197
+ const previousBottom = getItemBottom(ctx, start - 1);
2198
+ if (previousBottom === void 0 || previousBottom <= viewportStart) {
2199
+ break;
2200
+ }
2201
+ start--;
2202
+ }
2203
+ let end = start;
2204
+ while (end + 1 < dataLength) {
2205
+ const nextTop = state.positions[end + 1];
2206
+ if (nextTop === void 0 || nextTop > viewportEnd) {
2207
+ break;
2208
+ }
2209
+ end++;
2210
+ }
2211
+ return { end, start };
2212
+ }
2213
+ function pinScrollTargetRenderRange(ctx, targetOffset, targetIndex) {
2214
+ const range = getTargetViewportRenderRange(ctx, targetOffset, targetIndex);
2215
+ if (range) {
2216
+ ctx.state.scrollTargetPinnedRange = range;
2217
+ ctx.state.scrollForNextCalculateItemsInView = void 0;
2218
+ } else {
2219
+ ctx.state.scrollTargetPinnedRange = void 0;
2220
+ }
2221
+ }
2222
+ function scrollTo(ctx, params) {
2223
+ var _a3, _b;
2134
2224
  const state = ctx.state;
2135
2225
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2136
2226
  const {
@@ -2165,11 +2255,20 @@ function scrollTo(ctx, params) {
2165
2255
  targetOffset,
2166
2256
  waitForInitialScrollCompletionFrame
2167
2257
  };
2258
+ if (!isInitialScroll) {
2259
+ pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2260
+ }
2168
2261
  }
2169
2262
  state.scrollPending = targetOffset;
2170
2263
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2171
- if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2172
- updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
2264
+ if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2265
+ if (animated) {
2266
+ if (state.scrollTargetPinnedRange) {
2267
+ (_b = state.triggerCalculateItemsInView) == null ? void 0 : _b.call(state);
2268
+ }
2269
+ } else {
2270
+ updateScroll(ctx, targetOffset, true, { markHasScrolled: false });
2271
+ }
2173
2272
  }
2174
2273
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2175
2274
  doScrollTo(ctx, { animated, horizontal, offset });
@@ -3986,6 +4085,32 @@ function setDidLayout(ctx) {
3986
4085
  }
3987
4086
 
3988
4087
  // src/core/calculateItemsInView.ts
4088
+ var RENDER_RANGE_PROJECTION_FULL_VELOCITY = 4;
4089
+ var RENDER_RANGE_PROJECTION_SETTLE_DELAY = 100;
4090
+ function getProjectedBufferAdjustment(scrollVelocity, trailingBuffer) {
4091
+ if (trailingBuffer <= 0) {
4092
+ return 0;
4093
+ }
4094
+ const velocityProgress = Math.min(1, Math.abs(scrollVelocity) / RENDER_RANGE_PROJECTION_FULL_VELOCITY);
4095
+ return Math.sign(scrollVelocity) * trailingBuffer * velocityProgress;
4096
+ }
4097
+ function scheduleRenderRangeProjectionSettle(ctx) {
4098
+ const state = ctx.state;
4099
+ const previousTimeout = state.timeoutRenderRangeProjectionSettle;
4100
+ if (previousTimeout !== void 0) {
4101
+ clearTimeout(previousTimeout);
4102
+ state.timeouts.delete(previousTimeout);
4103
+ }
4104
+ const timeout = setTimeout(() => {
4105
+ var _a3;
4106
+ state.timeoutRenderRangeProjectionSettle = void 0;
4107
+ state.timeouts.delete(timeout);
4108
+ state.scrollHistory.length = 0;
4109
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
4110
+ }, RENDER_RANGE_PROJECTION_SETTLE_DELAY);
4111
+ state.timeoutRenderRangeProjectionSettle = timeout;
4112
+ state.timeouts.add(timeout);
4113
+ }
3989
4114
  function findCurrentStickyIndex(stickyArray, scroll, state) {
3990
4115
  const positions = state.positions;
3991
4116
  for (let i = stickyArray.length - 1; i >= 0; i--) {
@@ -4026,14 +4151,14 @@ function handleStickyActivation(ctx, stickyArray, currentStickyIdx, needNewConta
4026
4151
  }
4027
4152
  }
4028
4153
  }
4029
- function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, alwaysRenderIndicesSet) {
4154
+ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, isPinnedRenderIndex) {
4030
4155
  var _a3, _b;
4031
4156
  const state = ctx.state;
4032
4157
  for (const containerIndex of state.stickyContainerPool) {
4033
4158
  const itemKey = peek$(ctx, `containerItemKey${containerIndex}`);
4034
4159
  const itemIndex = itemKey ? state.indexByKey.get(itemKey) : void 0;
4035
4160
  if (itemIndex === void 0) continue;
4036
- if (alwaysRenderIndicesSet.has(itemIndex)) continue;
4161
+ if (isPinnedRenderIndex(itemIndex)) continue;
4037
4162
  const arrayIdx = stickyArray.indexOf(itemIndex);
4038
4163
  if (arrayIdx === -1) {
4039
4164
  state.stickyContainerPool.delete(containerIndex);
@@ -4197,8 +4322,6 @@ function calculateItemsInView(ctx, params = {}) {
4197
4322
  const { data } = state.props;
4198
4323
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4199
4324
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4200
- const alwaysRenderArr = alwaysRenderIndicesArr || [];
4201
- const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
4202
4325
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4203
4326
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
4204
4327
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
@@ -4265,14 +4388,19 @@ function calculateItemsInView(ctx, params = {}) {
4265
4388
  scrollBufferTop = drawDistance * 1.5;
4266
4389
  scrollBufferBottom = drawDistance * 0.5;
4267
4390
  }
4391
+ const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4392
+ const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4268
4393
  const updateScrollRange = () => {
4269
4394
  const scrollStart = Math.max(0, scroll);
4270
4395
  const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4271
- scrollTopBuffered = scrollStart - scrollBufferTop;
4272
4396
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4273
- scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4397
+ scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4398
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
4274
4399
  };
4275
4400
  updateScrollRange();
4401
+ if (projectedBufferAdjustment !== 0) {
4402
+ scheduleRenderRangeProjectionSettle(ctx);
4403
+ }
4276
4404
  if (enableScrollForNextCalculateItemsInView && !suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4277
4405
  const { top, bottom } = scrollForNextCalculateItemsInView;
4278
4406
  if (top === null && bottom === null) {
@@ -4430,9 +4558,34 @@ function calculateItemsInView(ctx, params = {}) {
4430
4558
  }
4431
4559
  }
4432
4560
  }
4561
+ const scrollTargetPinnedRange = state.scrollTargetPinnedRange;
4562
+ let scrollTargetPinnedStart = 0;
4563
+ let scrollTargetPinnedEnd = -1;
4564
+ if (scrollTargetPinnedRange) {
4565
+ scrollTargetPinnedStart = Math.max(0, Math.min(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end));
4566
+ scrollTargetPinnedEnd = Math.min(
4567
+ dataLength - 1,
4568
+ Math.max(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end)
4569
+ );
4570
+ }
4571
+ const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4572
+ const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4433
4573
  if (startBuffered !== null && endBuffered !== null) {
4434
4574
  const needNewContainers = [];
4435
4575
  const needNewContainersSet = /* @__PURE__ */ new Set();
4576
+ const addPinnedIndex = (index) => {
4577
+ var _a4;
4578
+ if (index >= 0 && index < dataLength) {
4579
+ const id = (_a4 = idCache[index]) != null ? _a4 : getId(state, index);
4580
+ const containerIndex = containerItemKeys.get(id);
4581
+ if (containerIndex !== void 0) {
4582
+ state.stickyContainerPool.add(containerIndex);
4583
+ } else if (!isNullOrUndefined(id) && !needNewContainersSet.has(index)) {
4584
+ needNewContainersSet.add(index);
4585
+ needNewContainers.push(index);
4586
+ }
4587
+ }
4588
+ };
4436
4589
  for (let i = startBuffered; i <= endBuffered; i++) {
4437
4590
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4438
4591
  if (!containerItemKeys.has(id)) {
@@ -4440,21 +4593,19 @@ function calculateItemsInView(ctx, params = {}) {
4440
4593
  needNewContainers.push(i);
4441
4594
  }
4442
4595
  }
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
- }
4596
+ for (const index of alwaysRenderIndicesArr) {
4597
+ addPinnedIndex(index);
4598
+ }
4599
+ if (hasScrollTargetPinnedRange) {
4600
+ for (let index = scrollTargetPinnedStart; index <= scrollTargetPinnedEnd; index++) {
4601
+ addPinnedIndex(index);
4451
4602
  }
4452
4603
  }
4453
4604
  if (stickyHeaderIndicesArr.length > 0) {
4454
4605
  handleStickyActivation(
4455
4606
  ctx,
4456
4607
  stickyHeaderIndicesArr,
4457
- (_o = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _o : -1,
4608
+ (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4458
4609
  needNewContainers,
4459
4610
  needNewContainersSet,
4460
4611
  startBuffered,
@@ -4480,7 +4631,7 @@ function calculateItemsInView(ctx, params = {}) {
4480
4631
  for (const allocation of availableContainerAllocations) {
4481
4632
  const i = allocation.itemIndex;
4482
4633
  const containerIndex = allocation.containerIndex;
4483
- const id = (_p = idCache[i]) != null ? _p : getId(state, i);
4634
+ const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4484
4635
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4485
4636
  if (oldKey && oldKey !== id) {
4486
4637
  containerItemKeys.delete(oldKey);
@@ -4491,10 +4642,14 @@ function calculateItemsInView(ctx, params = {}) {
4491
4642
  state.containerItemTypes.set(containerIndex, allocation.itemType);
4492
4643
  }
4493
4644
  containerItemKeys.set(id, containerIndex);
4494
- (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4645
+ (_p = state.userScrollAnchorReset) == null ? void 0 : _p.keys.add(id);
4646
+ {
4647
+ (_q = state.pendingLayoutEffectMeasurements) != null ? _q : state.pendingLayoutEffectMeasurements = /* @__PURE__ */ new Set();
4648
+ state.pendingLayoutEffectMeasurements.add(id);
4649
+ }
4495
4650
  const containerSticky = `containerSticky${containerIndex}`;
4496
4651
  const isSticky = stickyHeaderIndicesSet.has(i);
4497
- const isAlwaysRender = alwaysRenderSet.has(i);
4652
+ const isPinnedRender = isPinnedRenderIndex(i);
4498
4653
  if (isSticky) {
4499
4654
  set$(ctx, containerSticky, true);
4500
4655
  state.stickyContainerPool.add(containerIndex);
@@ -4502,9 +4657,9 @@ function calculateItemsInView(ctx, params = {}) {
4502
4657
  if (peek$(ctx, containerSticky)) {
4503
4658
  set$(ctx, containerSticky, false);
4504
4659
  }
4505
- if (isAlwaysRender) {
4660
+ if (isPinnedRender) {
4506
4661
  state.stickyContainerPool.add(containerIndex);
4507
- } else if (state.stickyContainerPool.has(containerIndex)) {
4662
+ } else {
4508
4663
  state.stickyContainerPool.delete(containerIndex);
4509
4664
  }
4510
4665
  }
@@ -4519,20 +4674,8 @@ function calculateItemsInView(ctx, params = {}) {
4519
4674
  }
4520
4675
  }
4521
4676
  }
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
- }
4677
+ if (((_r = state.userScrollAnchorReset) == null ? void 0 : _r.keys.size) === 0) {
4678
+ state.userScrollAnchorReset = void 0;
4536
4679
  }
4537
4680
  }
4538
4681
  if (state.stickyContainerPool.size > 0) {
@@ -4543,7 +4686,7 @@ function calculateItemsInView(ctx, params = {}) {
4543
4686
  drawDistance,
4544
4687
  (_s = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _s : -1,
4545
4688
  pendingRemoval,
4546
- alwaysRenderSet
4689
+ isPinnedRenderIndex
4547
4690
  );
4548
4691
  }
4549
4692
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
@@ -4702,6 +4845,36 @@ function mergeItemSizeUpdateResult(result, next) {
4702
4845
  result.needsRecalculate || (result.needsRecalculate = next.needsRecalculate);
4703
4846
  result.shouldMaintainScrollAtEnd || (result.shouldMaintainScrollAtEnd = next.shouldMaintainScrollAtEnd);
4704
4847
  }
4848
+ var batchedItemSizeRecalculates = /* @__PURE__ */ new WeakMap();
4849
+ function flushBatchedItemSizeRecalculate(ctx, didFallback = false, expectedResult) {
4850
+ const result = batchedItemSizeRecalculates.get(ctx);
4851
+ if (!result || expectedResult && result !== expectedResult) {
4852
+ return;
4853
+ }
4854
+ batchedItemSizeRecalculates.delete(ctx);
4855
+ if (didFallback) {
4856
+ ctx.state.pendingLayoutEffectMeasurements = void 0;
4857
+ }
4858
+ flushItemSizeUpdates(ctx, result);
4859
+ }
4860
+ function queueItemSizeRecalculate(ctx, result) {
4861
+ var _a3, _b;
4862
+ const batch = batchedItemSizeRecalculates.get(ctx);
4863
+ if (batch) {
4864
+ mergeItemSizeUpdateResult(batch, result);
4865
+ } else {
4866
+ const nextBatch = { ...result };
4867
+ batchedItemSizeRecalculates.set(ctx, nextBatch);
4868
+ if ((_a3 = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _a3.size) {
4869
+ requestAnimationFrame(() => {
4870
+ flushBatchedItemSizeRecalculate(ctx, true, nextBatch);
4871
+ });
4872
+ }
4873
+ }
4874
+ if (!((_b = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _b.size)) {
4875
+ flushBatchedItemSizeRecalculate(ctx);
4876
+ }
4877
+ }
4705
4878
  function flushItemSizeUpdates(ctx, result) {
4706
4879
  var _a3;
4707
4880
  const state = ctx.state;
@@ -4718,12 +4891,22 @@ function flushItemSizeUpdates(ctx, result) {
4718
4891
  function updateItemSizes(ctx, measurement) {
4719
4892
  var _a3, _b, _c, _d;
4720
4893
  const state = ctx.state;
4894
+ let didDrainLayoutEffectMeasurements = false;
4895
+ if (measurement.fromLayoutEffect) {
4896
+ const pendingLayoutEffectMeasurements = state.pendingLayoutEffectMeasurements;
4897
+ if ((pendingLayoutEffectMeasurements == null ? void 0 : pendingLayoutEffectMeasurements.delete(measurement.itemKey)) && pendingLayoutEffectMeasurements.size === 0) {
4898
+ state.pendingLayoutEffectMeasurements = void 0;
4899
+ didDrainLayoutEffectMeasurements = true;
4900
+ }
4901
+ }
4721
4902
  const pendingKeys = (_a3 = state.userScrollAnchorReset) == null ? void 0 : _a3.keys;
4722
4903
  const shouldBatchPendingMeasurements = measurement.fromLayoutEffect && !!(pendingKeys == null ? void 0 : pendingKeys.size);
4904
+ const shouldQueueRecalculate = !!measurement.fromLayoutEffect;
4905
+ let result;
4723
4906
  if (!shouldBatchPendingMeasurements) {
4724
- flushItemSizeUpdates(ctx, applyItemSize(ctx, measurement.itemKey, measurement.size));
4907
+ result = applyItemSize(ctx, measurement.itemKey, measurement.size);
4725
4908
  } else {
4726
- const result = {};
4909
+ result = {};
4727
4910
  const updateContainerItemSize = (itemKey, containerId, size) => {
4728
4911
  if (containerId !== void 0 && peek$(ctx, `containerItemKey${containerId}`) === itemKey) {
4729
4912
  mergeItemSizeUpdateResult(result, applyItemSize(ctx, itemKey, size));
@@ -4741,8 +4924,15 @@ function updateItemSizes(ctx, measurement) {
4741
4924
  });
4742
4925
  }
4743
4926
  }
4927
+ }
4928
+ if (shouldQueueRecalculate && result.needsRecalculate) {
4929
+ queueItemSizeRecalculate(ctx, result);
4930
+ } else {
4744
4931
  flushItemSizeUpdates(ctx, result);
4745
4932
  }
4933
+ if (didDrainLayoutEffectMeasurements) {
4934
+ flushBatchedItemSizeRecalculate(ctx);
4935
+ }
4746
4936
  }
4747
4937
  function applyItemSize(ctx, itemKey, sizeObj) {
4748
4938
  var _a3;