@legendapp/list 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -92,6 +92,9 @@ const LegendListExample = () => {
92
92
  keyExtractor={(item) => item.id}
93
93
  recycleItems={true}
94
94
 
95
+ // Recommended if data can change
96
+ maintainVisibleContentPosition
97
+
95
98
  ref={listRef}
96
99
  />
97
100
  )
package/index.d.mts CHANGED
@@ -57,7 +57,7 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
57
57
  getEstimatedItemSize?: (index: number, item: ItemT) => number;
58
58
  /**
59
59
  * Ratio of initial container pool size to data length (e.g., 0.5 for half).
60
- * @default 1
60
+ * @default 2
61
61
  */
62
62
  initialContainerPoolRatio?: number | undefined;
63
63
  /**
@@ -276,7 +276,7 @@ interface InternalState {
276
276
  queuedCalculateItemsInView: number | undefined;
277
277
  lastBatchingAction: number;
278
278
  ignoreScrollFromCalcTotal?: boolean;
279
- disableAveragesForScrolls?: number;
279
+ disableScrollJumpsFrom?: number;
280
280
  scrollingToOffset?: number | undefined;
281
281
  averageSizes: Record<string, {
282
282
  num: number;
package/index.d.ts CHANGED
@@ -57,7 +57,7 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
57
57
  getEstimatedItemSize?: (index: number, item: ItemT) => number;
58
58
  /**
59
59
  * Ratio of initial container pool size to data length (e.g., 0.5 for half).
60
- * @default 1
60
+ * @default 2
61
61
  */
62
62
  initialContainerPoolRatio?: number | undefined;
63
63
  /**
@@ -276,7 +276,7 @@ interface InternalState {
276
276
  queuedCalculateItemsInView: number | undefined;
277
277
  lastBatchingAction: number;
278
278
  ignoreScrollFromCalcTotal?: boolean;
279
- disableAveragesForScrolls?: number;
279
+ disableScrollJumpsFrom?: number;
280
280
  scrollingToOffset?: number | undefined;
281
281
  averageSizes: Record<string, {
282
282
  num: number;
package/index.js CHANGED
@@ -159,6 +159,9 @@ function warnDevOnce(id, text) {
159
159
  function roundSize(size) {
160
160
  return Math.floor(size * 8) / 8;
161
161
  }
162
+ function isNullOrUndefined(value) {
163
+ return value === null || value === void 0;
164
+ }
162
165
  var symbolFirst = Symbol();
163
166
  function useInit(cb) {
164
167
  const refValue = React2.useRef(symbolFirst);
@@ -335,20 +338,28 @@ var Container = ({
335
338
  forceLayoutRender((v) => v + 1);
336
339
  }, []);
337
340
  const onLayout = (event) => {
338
- if (itemKey !== void 0) {
341
+ var _a, _b;
342
+ if (!isNullOrUndefined(itemKey)) {
339
343
  const layout = event.nativeEvent.layout;
340
- const size = roundSize(layout[horizontal ? "width" : "height"]);
341
- if (!IsNewArchitecture && size === 0 && layout.x === 0 && layout.y === 0) {
342
- return;
344
+ let size = roundSize(layout[horizontal ? "width" : "height"]);
345
+ const doUpdate = () => {
346
+ refLastSize.current = size;
347
+ updateItemSize(itemKey, size);
348
+ };
349
+ if (IsNewArchitecture || size > 0) {
350
+ doUpdate();
351
+ } else {
352
+ (_b = (_a = ref.current) == null ? void 0 : _a.measure) == null ? void 0 : _b.call(_a, (x, y, width, height) => {
353
+ size = roundSize(horizontal ? width : height);
354
+ doUpdate();
355
+ });
343
356
  }
344
- refLastSize.current = size;
345
- updateItemSize(itemKey, size);
346
357
  }
347
358
  };
348
359
  if (IsNewArchitecture) {
349
360
  React2.useLayoutEffect(() => {
350
361
  var _a, _b;
351
- if (itemKey !== void 0) {
362
+ if (!isNullOrUndefined(itemKey)) {
352
363
  const measured = (_b = (_a = ref.current) == null ? void 0 : _a.unstable_getBoundingClientRect) == null ? void 0 : _b.call(_a);
353
364
  if (measured) {
354
365
  const size = Math.floor(measured[horizontal ? "width" : "height"] * 8) / 8;
@@ -360,7 +371,7 @@ var Container = ({
360
371
  }, [itemKey, layoutRenderCount]);
361
372
  } else {
362
373
  React2.useEffect(() => {
363
- if (itemKey) {
374
+ if (!isNullOrUndefined(itemKey)) {
364
375
  const timeout = setTimeout(() => {
365
376
  if (refLastSize.current) {
366
377
  updateItemSize(itemKey, refLastSize.current);
@@ -1099,6 +1110,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1099
1110
  }) => {
1100
1111
  var _a;
1101
1112
  const state = refState.current;
1113
+ if (index >= state.data.length) {
1114
+ index = state.data.length - 1;
1115
+ } else if (index < 0) {
1116
+ index = 0;
1117
+ }
1102
1118
  const firstIndexOffset = calculateOffsetForIndex(index);
1103
1119
  let firstIndexScrollPostion = firstIndexOffset - viewOffset;
1104
1120
  const diff = Math.abs(state.scroll - firstIndexScrollPostion);
@@ -1117,15 +1133,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1117
1133
  if (viewPosition) {
1118
1134
  firstIndexScrollPostion -= viewPosition * (state.scrollLength - getItemSize(getId(index), index, state.data[index]));
1119
1135
  }
1120
- state.scrollAdjustHandler.setDisableAdjust(true);
1121
- state.scrollingToOffset = firstIndexScrollPostion;
1122
1136
  scrollTo(firstIndexScrollPostion, animated);
1123
- if (!animated) {
1124
- requestAnimationFrame(() => {
1125
- state.scrollingToOffset = void 0;
1126
- state.scrollAdjustHandler.setDisableAdjust(false);
1127
- });
1128
- }
1129
1137
  };
1130
1138
  const setDidLayout = () => {
1131
1139
  refState.current.queuedInitialLayout = true;
@@ -1227,6 +1235,15 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1227
1235
  }
1228
1236
  return map;
1229
1237
  };
1238
+ const disableScrollJumps = (timeout) => {
1239
+ const state = refState.current;
1240
+ if (state.scrollingToOffset === void 0) {
1241
+ state.disableScrollJumpsFrom = state.scroll - state.scrollAdjustHandler.getAppliedAdjust();
1242
+ setTimeout(() => {
1243
+ state.disableScrollJumpsFrom = void 0;
1244
+ }, timeout);
1245
+ }
1246
+ };
1230
1247
  const getElementPositionBelowAchor = (id) => {
1231
1248
  var _a;
1232
1249
  const state = refState.current;
@@ -1305,6 +1322,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1305
1322
  }
1306
1323
  }
1307
1324
  }, []);
1325
+ const checkAllSizesKnown = React2.useCallback(() => {
1326
+ const { startBuffered, endBuffered, sizesKnown } = refState.current;
1327
+ if (endBuffered !== null) {
1328
+ let areAllKnown = true;
1329
+ for (let i = startBuffered; areAllKnown && i <= endBuffered; i++) {
1330
+ const key = getId(i);
1331
+ areAllKnown && (areAllKnown = sizesKnown.has(key));
1332
+ }
1333
+ return areAllKnown;
1334
+ }
1335
+ return false;
1336
+ }, []);
1308
1337
  const calculateItemsInView = React2.useCallback(() => {
1309
1338
  var _a;
1310
1339
  const state = refState.current;
@@ -1315,8 +1344,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1315
1344
  positions,
1316
1345
  columns,
1317
1346
  scrollAdjustHandler,
1318
- scrollVelocity: speed,
1319
- disableAveragesForScrolls
1347
+ scrollVelocity: speed
1320
1348
  } = state;
1321
1349
  if (!data || scrollLength === 0) {
1322
1350
  return;
@@ -1327,7 +1355,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1327
1355
  const previousScrollAdjust = scrollAdjustHandler.getAppliedAdjust();
1328
1356
  const scrollExtra = Math.max(-16, Math.min(16, speed)) * 16;
1329
1357
  let scrollState = state.scroll;
1330
- const useAverageSize = !disableAveragesForScrolls;
1358
+ const useAverageSize = !state.disableScrollJumpsFrom;
1331
1359
  if (!state.queuedInitialLayout && initialScrollIndex) {
1332
1360
  const updatedOffset = calculateOffsetForIndex(initialScrollIndex);
1333
1361
  scrollState = updatedOffset;
@@ -1357,6 +1385,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1357
1385
  }
1358
1386
  }
1359
1387
  const scrollBottom = scroll + scrollLength;
1388
+ const prevStartBuffered = state.startBuffered;
1360
1389
  const prevEndBuffered = state.endBuffered;
1361
1390
  let startNoBuffer = null;
1362
1391
  let startBuffered = null;
@@ -1466,6 +1495,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1466
1495
  const prevNumContainers = ctx.values.get("numContainers");
1467
1496
  let numContainers = prevNumContainers;
1468
1497
  let didWarnMoreContainers = false;
1498
+ const allocatedContainers = /* @__PURE__ */ new Set();
1469
1499
  for (let i = startBuffered; i <= endBuffered; i++) {
1470
1500
  let isContained = false;
1471
1501
  const id = getId(i);
@@ -1481,38 +1511,44 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1481
1511
  let furthestIndex = -1;
1482
1512
  let furthestDistance = 0;
1483
1513
  for (let u = 0; u < numContainers; u++) {
1514
+ if (allocatedContainers.has(u)) {
1515
+ continue;
1516
+ }
1484
1517
  const key = peek$(ctx, `containerItemKey${u}`);
1485
1518
  if (key === void 0) {
1486
1519
  furthestIndex = u;
1487
1520
  break;
1488
1521
  }
1489
- const index = state.indexByKey.get(key);
1522
+ const index2 = state.indexByKey.get(key);
1490
1523
  const pos = peek$(ctx, `containerPosition${u}`).top;
1491
- if (index < startBuffered || index > endBuffered) {
1524
+ if (isNullOrUndefined(index2) || pos === POSITION_OUT_OF_VIEW) {
1525
+ furthestIndex = u;
1526
+ break;
1527
+ }
1528
+ if (index2 < startBuffered || index2 > endBuffered) {
1492
1529
  const distance = Math.abs(pos - top2);
1493
- if (index < 0 || distance > furthestDistance) {
1530
+ if (index2 < 0 || pos === POSITION_OUT_OF_VIEW || distance > furthestDistance) {
1494
1531
  furthestDistance = distance;
1495
1532
  furthestIndex = u;
1496
1533
  }
1497
1534
  }
1498
1535
  }
1499
- if (furthestIndex >= 0) {
1500
- set$(ctx, `containerItemKey${furthestIndex}`, id);
1501
- const index = state.indexByKey.get(id);
1502
- set$(ctx, `containerItemData${furthestIndex}`, data[index]);
1503
- } else {
1504
- const containerId = numContainers;
1536
+ const containerId = furthestIndex >= 0 ? furthestIndex : numContainers;
1537
+ set$(ctx, `containerItemKey${containerId}`, id);
1538
+ const index = state.indexByKey.get(id);
1539
+ set$(ctx, `containerItemData${containerId}`, data[index]);
1540
+ allocatedContainers.add(containerId);
1541
+ if (furthestIndex === -1) {
1505
1542
  numContainers++;
1506
1543
  set$(ctx, `containerItemKey${containerId}`, id);
1507
- const index = state.indexByKey.get(id);
1508
- set$(ctx, `containerItemData${containerId}`, data[index]);
1544
+ const index2 = state.indexByKey.get(id);
1545
+ set$(ctx, `containerItemData${containerId}`, data[index2]);
1509
1546
  set$(ctx, `containerPosition${containerId}`, ANCHORED_POSITION_OUT_OF_VIEW);
1510
1547
  set$(ctx, `containerColumn${containerId}`, -1);
1511
1548
  if (__DEV__ && !didWarnMoreContainers && numContainers > peek$(ctx, "numContainersPooled")) {
1512
1549
  didWarnMoreContainers = true;
1513
1550
  console.warn(
1514
- "[legend-list] No container to recycle, so creating one on demand. This can be a minor performance issue and is likely caused by the estimatedItemSize being too large. Consider decreasing estimatedItemSize. numContainers:",
1515
- numContainers
1551
+ "[legend-list] No container to recycle, so creating one on demand. This can be a minor performance issue and is likely caused by the estimatedItemSize being too large. Consider decreasing estimatedItemSize or increasing initialContainerPoolRatio."
1516
1552
  );
1517
1553
  }
1518
1554
  }
@@ -1534,7 +1570,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1534
1570
  const prevPos = peek$(ctx, `containerPosition${i}`);
1535
1571
  const pos = positions.get(id) || 0;
1536
1572
  const size = getItemSize(id, itemIndex, data[i]);
1537
- if (pos + size >= scroll && pos <= scrollBottom || prevPos + size >= scroll && prevPos <= scrollBottom || endBuffered < prevEndBuffered) {
1573
+ if (pos + size >= scroll && pos <= scrollBottom || prevPos + size >= scroll && prevPos <= scrollBottom || endBuffered < prevEndBuffered || startBuffered > prevStartBuffered) {
1538
1574
  set$(ctx, `containerPosition${i}`, ANCHORED_POSITION_OUT_OF_VIEW);
1539
1575
  }
1540
1576
  } else {
@@ -1569,12 +1605,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1569
1605
  }
1570
1606
  }
1571
1607
  if (!state.queuedInitialLayout && endBuffered !== null) {
1572
- let areAllKnown = true;
1573
- for (let i = startBuffered; areAllKnown && i <= endBuffered; i++) {
1574
- const key = getId(i);
1575
- areAllKnown && (areAllKnown = state.sizesKnown.has(key));
1576
- }
1577
- if (areAllKnown) {
1608
+ if (checkAllSizesKnown()) {
1578
1609
  setDidLayout();
1579
1610
  }
1580
1611
  }
@@ -1622,13 +1653,27 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1622
1653
  setPaddingTop({ alignItemsPaddingTop: paddingTop });
1623
1654
  }
1624
1655
  };
1656
+ const finishScrollTo = () => {
1657
+ const state = refState.current;
1658
+ if (state) {
1659
+ state.scrollingToOffset = void 0;
1660
+ state.scrollAdjustHandler.setDisableAdjust(false);
1661
+ calculateItemsInView();
1662
+ }
1663
+ };
1625
1664
  const scrollTo = (offset, animated) => {
1626
1665
  var _a;
1666
+ const state = refState.current;
1667
+ state.scrollAdjustHandler.setDisableAdjust(true);
1668
+ state.scrollingToOffset = offset;
1627
1669
  (_a = refScroller.current) == null ? void 0 : _a.scrollTo({
1628
1670
  x: horizontal ? offset : 0,
1629
1671
  y: horizontal ? 0 : offset,
1630
1672
  animated: !!animated
1631
1673
  });
1674
+ if (!animated) {
1675
+ requestAnimationFrame(finishScrollTo);
1676
+ }
1632
1677
  };
1633
1678
  const doMaintainScrollAtEnd = (animated) => {
1634
1679
  const state = refState.current;
@@ -1726,7 +1771,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1726
1771
  if (state) {
1727
1772
  state.data = dataProp;
1728
1773
  if (!isFirst2) {
1729
- state.disableAveragesForScrolls = 2;
1774
+ disableScrollJumps(2e3);
1730
1775
  refState.current.scrollForNextCalculateItemsInView = void 0;
1731
1776
  const numContainers = peek$(ctx, "numContainers");
1732
1777
  for (let i = 0; i < numContainers; i++) {
@@ -1881,7 +1926,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1881
1926
  isFirst
1882
1927
  );
1883
1928
  }
1884
- }, [isFirst, dataProp, numColumnsProp]);
1929
+ }, [dataProp, numColumnsProp]);
1885
1930
  React2.useEffect(() => {
1886
1931
  set$(ctx, "extraData", extraData);
1887
1932
  }, [extraData]);
@@ -1943,7 +1988,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1943
1988
  });
1944
1989
  const updateItemSize = React2.useCallback((itemKey, size, fromFixGaps) => {
1945
1990
  const state = refState.current;
1946
- const { sizes, indexByKey, sizesKnown, data, rowHeights, startBuffered, endBuffered, averageSizes } = state;
1991
+ const {
1992
+ sizes,
1993
+ indexByKey,
1994
+ sizesKnown,
1995
+ data,
1996
+ rowHeights,
1997
+ startBuffered,
1998
+ endBuffered,
1999
+ averageSizes,
2000
+ queuedInitialLayout
2001
+ } = state;
1947
2002
  if (!data) {
1948
2003
  return;
1949
2004
  }
@@ -1952,7 +2007,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1952
2007
  state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, index) : index;
1953
2008
  const prevSize = getItemSize(itemKey, index, data);
1954
2009
  let needsCalculate = false;
1955
- const needsUpdateContainersDidLayout = !peek$(ctx, "containersDidLayout");
2010
+ let needsUpdateContainersDidLayout = false;
1956
2011
  sizesKnown.set(itemKey, size);
1957
2012
  const itemType = "";
1958
2013
  let averages = averageSizes[itemType];
@@ -1991,7 +2046,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1991
2046
  );
1992
2047
  }, 1e3);
1993
2048
  }
1994
- refState.current.scrollForNextCalculateItemsInView = void 0;
2049
+ state.scrollForNextCalculateItemsInView = void 0;
1995
2050
  addTotalSize(itemKey, diff, 0);
1996
2051
  doMaintainScrollAtEnd(false);
1997
2052
  if (onItemSizeChanged) {
@@ -2004,11 +2059,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2004
2059
  });
2005
2060
  }
2006
2061
  }
2062
+ if (!queuedInitialLayout && checkAllSizesKnown()) {
2063
+ needsUpdateContainersDidLayout = true;
2064
+ }
2007
2065
  const isInView = index >= startBuffered && index <= endBuffered;
2008
- if (needsUpdateContainersDidLayout || !fromFixGaps && needsCalculate && isInView) {
2066
+ if (needsUpdateContainersDidLayout || !fromFixGaps && needsCalculate && (isInView || !queuedInitialLayout)) {
2009
2067
  const scrollVelocity = state.scrollVelocity;
2010
2068
  let didCalculate = false;
2011
- if ((Number.isNaN(scrollVelocity) || Math.abs(scrollVelocity) < 1) && (!waitForInitialLayout || needsUpdateContainersDidLayout)) {
2069
+ if ((Number.isNaN(scrollVelocity) || Math.abs(scrollVelocity) < 1 || state.scrollingToOffset !== void 0) && (!waitForInitialLayout || needsUpdateContainersDidLayout || queuedInitialLayout)) {
2012
2070
  if (Date.now() - state.lastBatchingAction < 500) {
2013
2071
  if (!state.queuedCalculateItemsInView) {
2014
2072
  state.queuedCalculateItemsInView = requestAnimationFrame(() => {
@@ -2066,6 +2124,16 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2066
2124
  if (state.ignoreScrollFromCalcTotal && newScroll !== 0) {
2067
2125
  return;
2068
2126
  }
2127
+ if (state.scrollingToOffset !== void 0 && Math.abs(newScroll - state.scrollingToOffset) < 10) {
2128
+ finishScrollTo();
2129
+ }
2130
+ if (state.disableScrollJumpsFrom !== void 0) {
2131
+ const scrollMinusAdjust = newScroll - state.scrollAdjustHandler.getAppliedAdjust();
2132
+ if (Math.abs(scrollMinusAdjust - state.disableScrollJumpsFrom) > 200) {
2133
+ return;
2134
+ }
2135
+ state.disableScrollJumpsFrom = void 0;
2136
+ }
2069
2137
  state.hasScrolled = true;
2070
2138
  state.lastBatchingAction = Date.now();
2071
2139
  const currentTime = performance.now();
@@ -2100,9 +2168,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2100
2168
  if (!fromSelf) {
2101
2169
  (_d = state.onScroll) == null ? void 0 : _d.call(state, event);
2102
2170
  }
2103
- if (state.disableAveragesForScrolls) {
2104
- state.disableAveragesForScrolls--;
2105
- }
2106
2171
  },
2107
2172
  []
2108
2173
  );
package/index.mjs CHANGED
@@ -138,6 +138,9 @@ function warnDevOnce(id, text) {
138
138
  function roundSize(size) {
139
139
  return Math.floor(size * 8) / 8;
140
140
  }
141
+ function isNullOrUndefined(value) {
142
+ return value === null || value === void 0;
143
+ }
141
144
  var symbolFirst = Symbol();
142
145
  function useInit(cb) {
143
146
  const refValue = useRef(symbolFirst);
@@ -314,20 +317,28 @@ var Container = ({
314
317
  forceLayoutRender((v) => v + 1);
315
318
  }, []);
316
319
  const onLayout = (event) => {
317
- if (itemKey !== void 0) {
320
+ var _a, _b;
321
+ if (!isNullOrUndefined(itemKey)) {
318
322
  const layout = event.nativeEvent.layout;
319
- const size = roundSize(layout[horizontal ? "width" : "height"]);
320
- if (!IsNewArchitecture && size === 0 && layout.x === 0 && layout.y === 0) {
321
- return;
323
+ let size = roundSize(layout[horizontal ? "width" : "height"]);
324
+ const doUpdate = () => {
325
+ refLastSize.current = size;
326
+ updateItemSize(itemKey, size);
327
+ };
328
+ if (IsNewArchitecture || size > 0) {
329
+ doUpdate();
330
+ } else {
331
+ (_b = (_a = ref.current) == null ? void 0 : _a.measure) == null ? void 0 : _b.call(_a, (x, y, width, height) => {
332
+ size = roundSize(horizontal ? width : height);
333
+ doUpdate();
334
+ });
322
335
  }
323
- refLastSize.current = size;
324
- updateItemSize(itemKey, size);
325
336
  }
326
337
  };
327
338
  if (IsNewArchitecture) {
328
339
  useLayoutEffect(() => {
329
340
  var _a, _b;
330
- if (itemKey !== void 0) {
341
+ if (!isNullOrUndefined(itemKey)) {
331
342
  const measured = (_b = (_a = ref.current) == null ? void 0 : _a.unstable_getBoundingClientRect) == null ? void 0 : _b.call(_a);
332
343
  if (measured) {
333
344
  const size = Math.floor(measured[horizontal ? "width" : "height"] * 8) / 8;
@@ -339,7 +350,7 @@ var Container = ({
339
350
  }, [itemKey, layoutRenderCount]);
340
351
  } else {
341
352
  useEffect(() => {
342
- if (itemKey) {
353
+ if (!isNullOrUndefined(itemKey)) {
343
354
  const timeout = setTimeout(() => {
344
355
  if (refLastSize.current) {
345
356
  updateItemSize(itemKey, refLastSize.current);
@@ -1078,6 +1089,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1078
1089
  }) => {
1079
1090
  var _a;
1080
1091
  const state = refState.current;
1092
+ if (index >= state.data.length) {
1093
+ index = state.data.length - 1;
1094
+ } else if (index < 0) {
1095
+ index = 0;
1096
+ }
1081
1097
  const firstIndexOffset = calculateOffsetForIndex(index);
1082
1098
  let firstIndexScrollPostion = firstIndexOffset - viewOffset;
1083
1099
  const diff = Math.abs(state.scroll - firstIndexScrollPostion);
@@ -1096,15 +1112,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1096
1112
  if (viewPosition) {
1097
1113
  firstIndexScrollPostion -= viewPosition * (state.scrollLength - getItemSize(getId(index), index, state.data[index]));
1098
1114
  }
1099
- state.scrollAdjustHandler.setDisableAdjust(true);
1100
- state.scrollingToOffset = firstIndexScrollPostion;
1101
1115
  scrollTo(firstIndexScrollPostion, animated);
1102
- if (!animated) {
1103
- requestAnimationFrame(() => {
1104
- state.scrollingToOffset = void 0;
1105
- state.scrollAdjustHandler.setDisableAdjust(false);
1106
- });
1107
- }
1108
1116
  };
1109
1117
  const setDidLayout = () => {
1110
1118
  refState.current.queuedInitialLayout = true;
@@ -1206,6 +1214,15 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1206
1214
  }
1207
1215
  return map;
1208
1216
  };
1217
+ const disableScrollJumps = (timeout) => {
1218
+ const state = refState.current;
1219
+ if (state.scrollingToOffset === void 0) {
1220
+ state.disableScrollJumpsFrom = state.scroll - state.scrollAdjustHandler.getAppliedAdjust();
1221
+ setTimeout(() => {
1222
+ state.disableScrollJumpsFrom = void 0;
1223
+ }, timeout);
1224
+ }
1225
+ };
1209
1226
  const getElementPositionBelowAchor = (id) => {
1210
1227
  var _a;
1211
1228
  const state = refState.current;
@@ -1284,6 +1301,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1284
1301
  }
1285
1302
  }
1286
1303
  }, []);
1304
+ const checkAllSizesKnown = useCallback(() => {
1305
+ const { startBuffered, endBuffered, sizesKnown } = refState.current;
1306
+ if (endBuffered !== null) {
1307
+ let areAllKnown = true;
1308
+ for (let i = startBuffered; areAllKnown && i <= endBuffered; i++) {
1309
+ const key = getId(i);
1310
+ areAllKnown && (areAllKnown = sizesKnown.has(key));
1311
+ }
1312
+ return areAllKnown;
1313
+ }
1314
+ return false;
1315
+ }, []);
1287
1316
  const calculateItemsInView = useCallback(() => {
1288
1317
  var _a;
1289
1318
  const state = refState.current;
@@ -1294,8 +1323,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1294
1323
  positions,
1295
1324
  columns,
1296
1325
  scrollAdjustHandler,
1297
- scrollVelocity: speed,
1298
- disableAveragesForScrolls
1326
+ scrollVelocity: speed
1299
1327
  } = state;
1300
1328
  if (!data || scrollLength === 0) {
1301
1329
  return;
@@ -1306,7 +1334,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1306
1334
  const previousScrollAdjust = scrollAdjustHandler.getAppliedAdjust();
1307
1335
  const scrollExtra = Math.max(-16, Math.min(16, speed)) * 16;
1308
1336
  let scrollState = state.scroll;
1309
- const useAverageSize = !disableAveragesForScrolls;
1337
+ const useAverageSize = !state.disableScrollJumpsFrom;
1310
1338
  if (!state.queuedInitialLayout && initialScrollIndex) {
1311
1339
  const updatedOffset = calculateOffsetForIndex(initialScrollIndex);
1312
1340
  scrollState = updatedOffset;
@@ -1336,6 +1364,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1336
1364
  }
1337
1365
  }
1338
1366
  const scrollBottom = scroll + scrollLength;
1367
+ const prevStartBuffered = state.startBuffered;
1339
1368
  const prevEndBuffered = state.endBuffered;
1340
1369
  let startNoBuffer = null;
1341
1370
  let startBuffered = null;
@@ -1445,6 +1474,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1445
1474
  const prevNumContainers = ctx.values.get("numContainers");
1446
1475
  let numContainers = prevNumContainers;
1447
1476
  let didWarnMoreContainers = false;
1477
+ const allocatedContainers = /* @__PURE__ */ new Set();
1448
1478
  for (let i = startBuffered; i <= endBuffered; i++) {
1449
1479
  let isContained = false;
1450
1480
  const id = getId(i);
@@ -1460,38 +1490,44 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1460
1490
  let furthestIndex = -1;
1461
1491
  let furthestDistance = 0;
1462
1492
  for (let u = 0; u < numContainers; u++) {
1493
+ if (allocatedContainers.has(u)) {
1494
+ continue;
1495
+ }
1463
1496
  const key = peek$(ctx, `containerItemKey${u}`);
1464
1497
  if (key === void 0) {
1465
1498
  furthestIndex = u;
1466
1499
  break;
1467
1500
  }
1468
- const index = state.indexByKey.get(key);
1501
+ const index2 = state.indexByKey.get(key);
1469
1502
  const pos = peek$(ctx, `containerPosition${u}`).top;
1470
- if (index < startBuffered || index > endBuffered) {
1503
+ if (isNullOrUndefined(index2) || pos === POSITION_OUT_OF_VIEW) {
1504
+ furthestIndex = u;
1505
+ break;
1506
+ }
1507
+ if (index2 < startBuffered || index2 > endBuffered) {
1471
1508
  const distance = Math.abs(pos - top2);
1472
- if (index < 0 || distance > furthestDistance) {
1509
+ if (index2 < 0 || pos === POSITION_OUT_OF_VIEW || distance > furthestDistance) {
1473
1510
  furthestDistance = distance;
1474
1511
  furthestIndex = u;
1475
1512
  }
1476
1513
  }
1477
1514
  }
1478
- if (furthestIndex >= 0) {
1479
- set$(ctx, `containerItemKey${furthestIndex}`, id);
1480
- const index = state.indexByKey.get(id);
1481
- set$(ctx, `containerItemData${furthestIndex}`, data[index]);
1482
- } else {
1483
- const containerId = numContainers;
1515
+ const containerId = furthestIndex >= 0 ? furthestIndex : numContainers;
1516
+ set$(ctx, `containerItemKey${containerId}`, id);
1517
+ const index = state.indexByKey.get(id);
1518
+ set$(ctx, `containerItemData${containerId}`, data[index]);
1519
+ allocatedContainers.add(containerId);
1520
+ if (furthestIndex === -1) {
1484
1521
  numContainers++;
1485
1522
  set$(ctx, `containerItemKey${containerId}`, id);
1486
- const index = state.indexByKey.get(id);
1487
- set$(ctx, `containerItemData${containerId}`, data[index]);
1523
+ const index2 = state.indexByKey.get(id);
1524
+ set$(ctx, `containerItemData${containerId}`, data[index2]);
1488
1525
  set$(ctx, `containerPosition${containerId}`, ANCHORED_POSITION_OUT_OF_VIEW);
1489
1526
  set$(ctx, `containerColumn${containerId}`, -1);
1490
1527
  if (__DEV__ && !didWarnMoreContainers && numContainers > peek$(ctx, "numContainersPooled")) {
1491
1528
  didWarnMoreContainers = true;
1492
1529
  console.warn(
1493
- "[legend-list] No container to recycle, so creating one on demand. This can be a minor performance issue and is likely caused by the estimatedItemSize being too large. Consider decreasing estimatedItemSize. numContainers:",
1494
- numContainers
1530
+ "[legend-list] No container to recycle, so creating one on demand. This can be a minor performance issue and is likely caused by the estimatedItemSize being too large. Consider decreasing estimatedItemSize or increasing initialContainerPoolRatio."
1495
1531
  );
1496
1532
  }
1497
1533
  }
@@ -1513,7 +1549,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1513
1549
  const prevPos = peek$(ctx, `containerPosition${i}`);
1514
1550
  const pos = positions.get(id) || 0;
1515
1551
  const size = getItemSize(id, itemIndex, data[i]);
1516
- if (pos + size >= scroll && pos <= scrollBottom || prevPos + size >= scroll && prevPos <= scrollBottom || endBuffered < prevEndBuffered) {
1552
+ if (pos + size >= scroll && pos <= scrollBottom || prevPos + size >= scroll && prevPos <= scrollBottom || endBuffered < prevEndBuffered || startBuffered > prevStartBuffered) {
1517
1553
  set$(ctx, `containerPosition${i}`, ANCHORED_POSITION_OUT_OF_VIEW);
1518
1554
  }
1519
1555
  } else {
@@ -1548,12 +1584,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1548
1584
  }
1549
1585
  }
1550
1586
  if (!state.queuedInitialLayout && endBuffered !== null) {
1551
- let areAllKnown = true;
1552
- for (let i = startBuffered; areAllKnown && i <= endBuffered; i++) {
1553
- const key = getId(i);
1554
- areAllKnown && (areAllKnown = state.sizesKnown.has(key));
1555
- }
1556
- if (areAllKnown) {
1587
+ if (checkAllSizesKnown()) {
1557
1588
  setDidLayout();
1558
1589
  }
1559
1590
  }
@@ -1601,13 +1632,27 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1601
1632
  setPaddingTop({ alignItemsPaddingTop: paddingTop });
1602
1633
  }
1603
1634
  };
1635
+ const finishScrollTo = () => {
1636
+ const state = refState.current;
1637
+ if (state) {
1638
+ state.scrollingToOffset = void 0;
1639
+ state.scrollAdjustHandler.setDisableAdjust(false);
1640
+ calculateItemsInView();
1641
+ }
1642
+ };
1604
1643
  const scrollTo = (offset, animated) => {
1605
1644
  var _a;
1645
+ const state = refState.current;
1646
+ state.scrollAdjustHandler.setDisableAdjust(true);
1647
+ state.scrollingToOffset = offset;
1606
1648
  (_a = refScroller.current) == null ? void 0 : _a.scrollTo({
1607
1649
  x: horizontal ? offset : 0,
1608
1650
  y: horizontal ? 0 : offset,
1609
1651
  animated: !!animated
1610
1652
  });
1653
+ if (!animated) {
1654
+ requestAnimationFrame(finishScrollTo);
1655
+ }
1611
1656
  };
1612
1657
  const doMaintainScrollAtEnd = (animated) => {
1613
1658
  const state = refState.current;
@@ -1705,7 +1750,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1705
1750
  if (state) {
1706
1751
  state.data = dataProp;
1707
1752
  if (!isFirst2) {
1708
- state.disableAveragesForScrolls = 2;
1753
+ disableScrollJumps(2e3);
1709
1754
  refState.current.scrollForNextCalculateItemsInView = void 0;
1710
1755
  const numContainers = peek$(ctx, "numContainers");
1711
1756
  for (let i = 0; i < numContainers; i++) {
@@ -1860,7 +1905,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1860
1905
  isFirst
1861
1906
  );
1862
1907
  }
1863
- }, [isFirst, dataProp, numColumnsProp]);
1908
+ }, [dataProp, numColumnsProp]);
1864
1909
  useEffect(() => {
1865
1910
  set$(ctx, "extraData", extraData);
1866
1911
  }, [extraData]);
@@ -1922,7 +1967,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1922
1967
  });
1923
1968
  const updateItemSize = useCallback((itemKey, size, fromFixGaps) => {
1924
1969
  const state = refState.current;
1925
- const { sizes, indexByKey, sizesKnown, data, rowHeights, startBuffered, endBuffered, averageSizes } = state;
1970
+ const {
1971
+ sizes,
1972
+ indexByKey,
1973
+ sizesKnown,
1974
+ data,
1975
+ rowHeights,
1976
+ startBuffered,
1977
+ endBuffered,
1978
+ averageSizes,
1979
+ queuedInitialLayout
1980
+ } = state;
1926
1981
  if (!data) {
1927
1982
  return;
1928
1983
  }
@@ -1931,7 +1986,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1931
1986
  state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, index) : index;
1932
1987
  const prevSize = getItemSize(itemKey, index, data);
1933
1988
  let needsCalculate = false;
1934
- const needsUpdateContainersDidLayout = !peek$(ctx, "containersDidLayout");
1989
+ let needsUpdateContainersDidLayout = false;
1935
1990
  sizesKnown.set(itemKey, size);
1936
1991
  const itemType = "";
1937
1992
  let averages = averageSizes[itemType];
@@ -1970,7 +2025,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1970
2025
  );
1971
2026
  }, 1e3);
1972
2027
  }
1973
- refState.current.scrollForNextCalculateItemsInView = void 0;
2028
+ state.scrollForNextCalculateItemsInView = void 0;
1974
2029
  addTotalSize(itemKey, diff, 0);
1975
2030
  doMaintainScrollAtEnd(false);
1976
2031
  if (onItemSizeChanged) {
@@ -1983,11 +2038,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1983
2038
  });
1984
2039
  }
1985
2040
  }
2041
+ if (!queuedInitialLayout && checkAllSizesKnown()) {
2042
+ needsUpdateContainersDidLayout = true;
2043
+ }
1986
2044
  const isInView = index >= startBuffered && index <= endBuffered;
1987
- if (needsUpdateContainersDidLayout || !fromFixGaps && needsCalculate && isInView) {
2045
+ if (needsUpdateContainersDidLayout || !fromFixGaps && needsCalculate && (isInView || !queuedInitialLayout)) {
1988
2046
  const scrollVelocity = state.scrollVelocity;
1989
2047
  let didCalculate = false;
1990
- if ((Number.isNaN(scrollVelocity) || Math.abs(scrollVelocity) < 1) && (!waitForInitialLayout || needsUpdateContainersDidLayout)) {
2048
+ if ((Number.isNaN(scrollVelocity) || Math.abs(scrollVelocity) < 1 || state.scrollingToOffset !== void 0) && (!waitForInitialLayout || needsUpdateContainersDidLayout || queuedInitialLayout)) {
1991
2049
  if (Date.now() - state.lastBatchingAction < 500) {
1992
2050
  if (!state.queuedCalculateItemsInView) {
1993
2051
  state.queuedCalculateItemsInView = requestAnimationFrame(() => {
@@ -2045,6 +2103,16 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2045
2103
  if (state.ignoreScrollFromCalcTotal && newScroll !== 0) {
2046
2104
  return;
2047
2105
  }
2106
+ if (state.scrollingToOffset !== void 0 && Math.abs(newScroll - state.scrollingToOffset) < 10) {
2107
+ finishScrollTo();
2108
+ }
2109
+ if (state.disableScrollJumpsFrom !== void 0) {
2110
+ const scrollMinusAdjust = newScroll - state.scrollAdjustHandler.getAppliedAdjust();
2111
+ if (Math.abs(scrollMinusAdjust - state.disableScrollJumpsFrom) > 200) {
2112
+ return;
2113
+ }
2114
+ state.disableScrollJumpsFrom = void 0;
2115
+ }
2048
2116
  state.hasScrolled = true;
2049
2117
  state.lastBatchingAction = Date.now();
2050
2118
  const currentTime = performance.now();
@@ -2079,9 +2147,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2079
2147
  if (!fromSelf) {
2080
2148
  (_d = state.onScroll) == null ? void 0 : _d.call(state, event);
2081
2149
  }
2082
- if (state.disableAveragesForScrolls) {
2083
- state.disableAveragesForScrolls--;
2084
- }
2085
2150
  },
2086
2151
  []
2087
2152
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.",
5
5
  "sideEffects": false,
6
6
  "private": false,