@legendapp/list 2.0.9 → 2.0.11

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/index.js CHANGED
@@ -182,11 +182,6 @@ function useInterval(callback, delay) {
182
182
  return () => clearInterval(interval);
183
183
  }, [delay]);
184
184
  }
185
- var LeanViewComponent = React2__namespace.forwardRef((props, ref) => {
186
- return React2__namespace.createElement("RCTView", { ...props, ref });
187
- });
188
- LeanViewComponent.displayName = "RCTView";
189
- var LeanView = reactNative.Platform.OS === "android" || reactNative.Platform.OS === "ios" ? LeanViewComponent : reactNative.View;
190
185
 
191
186
  // src/constants.ts
192
187
  var POSITION_OUT_OF_VIEW = -1e7;
@@ -248,7 +243,7 @@ var PositionViewState = typedMemo(function PositionView({
248
243
  }) {
249
244
  const [position = POSITION_OUT_OF_VIEW] = useArr$([`containerPosition${id}`]);
250
245
  return /* @__PURE__ */ React2__namespace.createElement(
251
- LeanView,
246
+ reactNative.View,
252
247
  {
253
248
  ref: refView,
254
249
  style: [
@@ -289,7 +284,7 @@ var PositionViewSticky = typedMemo(function PositionViewSticky2({
289
284
  }) {
290
285
  const [position = POSITION_OUT_OF_VIEW, headerSize] = useArr$([`containerPosition${id}`, "headerSize"]);
291
286
  const transform = React2__namespace.useMemo(() => {
292
- if (animatedScrollY && stickyOffset) {
287
+ if (animatedScrollY && stickyOffset !== void 0) {
293
288
  const stickyPosition = animatedScrollY.interpolate({
294
289
  extrapolate: "clamp",
295
290
  inputRange: [position + headerSize, position + 5e3 + headerSize],
@@ -454,6 +449,29 @@ function Separator({ ItemSeparatorComponent, leadingItem }) {
454
449
  const isLastItem = useIsLastItem();
455
450
  return isLastItem ? null : /* @__PURE__ */ React2__namespace.createElement(ItemSeparatorComponent, { leadingItem });
456
451
  }
452
+ function useOnLayoutSync({
453
+ ref,
454
+ onLayoutProp,
455
+ onLayoutChange
456
+ }, deps = []) {
457
+ const onLayout = React2.useCallback(
458
+ (event) => {
459
+ onLayoutChange(event.nativeEvent.layout, false);
460
+ onLayoutProp == null ? void 0 : onLayoutProp(event);
461
+ },
462
+ [onLayoutChange]
463
+ );
464
+ if (IsNewArchitecture) {
465
+ React2.useLayoutEffect(() => {
466
+ if (ref.current) {
467
+ ref.current.measure((x, y, width, height) => {
468
+ onLayoutChange({ height, width, x, y }, true);
469
+ });
470
+ }
471
+ }, deps);
472
+ }
473
+ return { onLayout };
474
+ }
457
475
 
458
476
  // src/components/Container.tsx
459
477
  var Container = typedMemo(function Container2({
@@ -480,7 +498,7 @@ var Container = typedMemo(function Container2({
480
498
  const [layoutRenderCount, forceLayoutRender] = React2.useState(0);
481
499
  const otherAxisPos = numColumns > 1 ? `${(column - 1) / numColumns * 100}%` : 0;
482
500
  const otherAxisSize = numColumns > 1 ? `${1 / numColumns * 100}%` : void 0;
483
- let didLayout = false;
501
+ const didLayoutRef = React2.useRef(false);
484
502
  const style = React2.useMemo(() => {
485
503
  let paddingStyles;
486
504
  if (columnWrapperStyle) {
@@ -530,15 +548,16 @@ var Container = typedMemo(function Container2({
530
548
  value: data
531
549
  };
532
550
  }, [id, itemKey, index, data]);
533
- const onLayout = (event) => {
551
+ const onLayoutChange = (rectangle) => {
534
552
  var _a, _b;
535
553
  if (!isNullOrUndefined(itemKey)) {
536
- didLayout = true;
537
- let layout = event.nativeEvent.layout;
538
- const size = layout[horizontal ? "width" : "height"];
554
+ didLayoutRef.current = true;
555
+ let layout = rectangle;
556
+ const size = Math.floor(rectangle[horizontal ? "width" : "height"] * 8) / 8;
539
557
  const doUpdate = () => {
540
558
  refLastSize.current = { height: layout.height, width: layout.width };
541
559
  updateItemSize2(itemKey, layout);
560
+ didLayoutRef.current = true;
542
561
  };
543
562
  if (IsNewArchitecture || size > 0) {
544
563
  doUpdate();
@@ -550,25 +569,20 @@ var Container = typedMemo(function Container2({
550
569
  }
551
570
  }
552
571
  };
553
- if (IsNewArchitecture) {
554
- React2.useLayoutEffect(() => {
555
- var _a, _b;
556
- if (!isNullOrUndefined(itemKey)) {
557
- const measured = (_b = (_a = ref.current) == null ? void 0 : _a.unstable_getBoundingClientRect) == null ? void 0 : _b.call(_a);
558
- if (measured) {
559
- const size = Math.floor(measured[horizontal ? "width" : "height"] * 8) / 8;
560
- if (size) {
561
- updateItemSize2(itemKey, measured);
562
- }
563
- }
564
- }
565
- }, [itemKey, layoutRenderCount]);
566
- } else {
572
+ const { onLayout } = useOnLayoutSync(
573
+ {
574
+ onLayoutChange,
575
+ ref
576
+ },
577
+ [itemKey, layoutRenderCount]
578
+ );
579
+ if (!IsNewArchitecture) {
567
580
  React2.useEffect(() => {
568
581
  if (!isNullOrUndefined(itemKey)) {
569
582
  const timeout = setTimeout(() => {
570
- if (!didLayout && refLastSize.current) {
583
+ if (!didLayoutRef.current && refLastSize.current) {
571
584
  updateItemSize2(itemKey, refLastSize.current);
585
+ didLayoutRef.current = true;
572
586
  }
573
587
  }, 16);
574
588
  return () => {
@@ -657,6 +671,11 @@ var Containers = typedMemo(function Containers2({
657
671
  }
658
672
  return /* @__PURE__ */ React2__namespace.createElement(reactNative.Animated.View, { style }, containers);
659
673
  });
674
+ var LayoutView = ({ onLayoutChange, refView, ...rest }) => {
675
+ const ref = refView != null ? refView : React2.useRef();
676
+ const { onLayout } = useOnLayoutSync({ onLayoutChange, ref });
677
+ return /* @__PURE__ */ React2__namespace.createElement(reactNative.View, { ...rest, onLayout, ref });
678
+ };
660
679
  function ScrollAdjust() {
661
680
  const bias = 1e7;
662
681
  const [scrollAdjust, scrollAdjustUserOffset] = useArr$(["scrollAdjust", "scrollAdjustUserOffset"]);
@@ -678,68 +697,6 @@ function SnapWrapper({ ScrollComponent, ...props }) {
678
697
  const [snapToOffsets] = useArr$(["snapToOffsets"]);
679
698
  return /* @__PURE__ */ React2__namespace.default.createElement(ScrollComponent, { ...props, snapToOffsets });
680
699
  }
681
- function useThrottleDebounce(mode) {
682
- const timeoutRef = React2.useRef(null);
683
- const lastCallTimeRef = React2.useRef(0);
684
- const lastArgsRef = React2.useRef(null);
685
- const clearTimeoutRef = () => {
686
- if (timeoutRef.current) {
687
- clearTimeout(timeoutRef.current);
688
- timeoutRef.current = null;
689
- }
690
- };
691
- const execute = React2.useCallback(
692
- (callback, delay, ...args) => {
693
- {
694
- const now = Date.now();
695
- lastArgsRef.current = args;
696
- if (now - lastCallTimeRef.current >= delay) {
697
- lastCallTimeRef.current = now;
698
- callback(...args);
699
- clearTimeoutRef();
700
- } else {
701
- clearTimeoutRef();
702
- timeoutRef.current = setTimeout(
703
- () => {
704
- if (lastArgsRef.current) {
705
- lastCallTimeRef.current = Date.now();
706
- callback(...lastArgsRef.current);
707
- timeoutRef.current = null;
708
- lastArgsRef.current = null;
709
- }
710
- },
711
- delay - (now - lastCallTimeRef.current)
712
- );
713
- }
714
- }
715
- },
716
- [mode]
717
- );
718
- return execute;
719
- }
720
-
721
- // src/hooks/useSyncLayout.tsx
722
- function useSyncLayout2({
723
- onChange
724
- }) {
725
- const ref = React2.useRef(null);
726
- const onLayout = React2.useCallback(
727
- (event) => {
728
- onChange(event.nativeEvent.layout, false);
729
- },
730
- [onChange]
731
- );
732
- if (IsNewArchitecture) {
733
- React2.useLayoutEffect(() => {
734
- if (ref.current) {
735
- ref.current.measure((x, y, width, height) => {
736
- onChange({ height, width, x, y }, true);
737
- });
738
- }
739
- }, []);
740
- }
741
- return { onLayout, ref };
742
- }
743
700
 
744
701
  // src/components/ListComponent.tsx
745
702
  var getComponent = (Component) => {
@@ -800,9 +757,6 @@ var ListComponent = typedMemo(function ListComponent2({
800
757
  ...rest
801
758
  }) {
802
759
  const ctx = useStateContext();
803
- const { onLayout: onLayoutHeaderSync, ref: refHeader } = useSyncLayout2({
804
- onChange: onLayoutHeader
805
- });
806
760
  const ScrollComponent = renderScrollComponent ? React2.useMemo(
807
761
  () => React2__namespace.forwardRef((props, ref) => renderScrollComponent({ ...props, ref })),
808
762
  [renderScrollComponent]
@@ -836,7 +790,7 @@ var ListComponent = typedMemo(function ListComponent2({
836
790
  },
837
791
  maintainVisibleContentPosition && /* @__PURE__ */ React2__namespace.createElement(ScrollAdjust, null),
838
792
  ENABLE_DEVMODE ? /* @__PURE__ */ React2__namespace.createElement(PaddingDevMode, null) : /* @__PURE__ */ React2__namespace.createElement(Padding, null),
839
- ListHeaderComponent && /* @__PURE__ */ React2__namespace.createElement(reactNative.View, { onLayout: onLayoutHeaderSync, ref: refHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
793
+ ListHeaderComponent && /* @__PURE__ */ React2__namespace.createElement(LayoutView, { onLayoutChange: onLayoutHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
840
794
  ListEmptyComponent && getComponent(ListEmptyComponent),
841
795
  canRender && !ListEmptyComponent && /* @__PURE__ */ React2__namespace.createElement(
842
796
  Containers,
@@ -850,10 +804,10 @@ var ListComponent = typedMemo(function ListComponent2({
850
804
  }
851
805
  ),
852
806
  ListFooterComponent && /* @__PURE__ */ React2__namespace.createElement(
853
- reactNative.View,
807
+ LayoutView,
854
808
  {
855
- onLayout: (event) => {
856
- const size = event.nativeEvent.layout[horizontal ? "width" : "height"];
809
+ onLayoutChange: (layout) => {
810
+ const size = layout[horizontal ? "width" : "height"];
857
811
  set$(ctx, "footerSize", size);
858
812
  },
859
813
  style: ListFooterComponentStyle
@@ -888,7 +842,7 @@ function getId(state, index) {
888
842
  }
889
843
  const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
890
844
  const id = ret;
891
- state.idCache.set(index, id);
845
+ state.idCache[index] = id;
892
846
  return id;
893
847
  }
894
848
 
@@ -1112,6 +1066,68 @@ function prepareMVCP(ctx, state, dataChanged) {
1112
1066
  };
1113
1067
  }
1114
1068
 
1069
+ // src/core/prepareColumnStartState.ts
1070
+ function prepareColumnStartState(ctx, state, startIndex, useAverageSize) {
1071
+ var _a;
1072
+ const numColumns = peek$(ctx, "numColumns");
1073
+ let rowStartIndex = startIndex;
1074
+ const columnAtStart = state.columns.get(state.idCache[startIndex]);
1075
+ if (columnAtStart !== 1) {
1076
+ rowStartIndex = findRowStartIndex(state, numColumns, startIndex);
1077
+ }
1078
+ let currentRowTop = 0;
1079
+ const curId = state.idCache[rowStartIndex];
1080
+ const column = state.columns.get(curId);
1081
+ if (rowStartIndex > 0) {
1082
+ const prevIndex = rowStartIndex - 1;
1083
+ const prevId = state.idCache[prevIndex];
1084
+ const prevPosition = (_a = state.positions.get(prevId)) != null ? _a : 0;
1085
+ const prevRowStart = findRowStartIndex(state, numColumns, prevIndex);
1086
+ const prevRowHeight = calculateRowMaxSize(state, prevRowStart, prevIndex, useAverageSize);
1087
+ currentRowTop = prevPosition + prevRowHeight;
1088
+ }
1089
+ return {
1090
+ column,
1091
+ currentRowTop,
1092
+ startIndex: rowStartIndex
1093
+ };
1094
+ }
1095
+ function findRowStartIndex(state, numColumns, index) {
1096
+ if (numColumns <= 1) {
1097
+ return Math.max(0, index);
1098
+ }
1099
+ let rowStart = Math.max(0, index);
1100
+ while (rowStart > 0) {
1101
+ const columnForIndex = state.columns.get(state.idCache[rowStart]);
1102
+ if (columnForIndex === 1) {
1103
+ break;
1104
+ }
1105
+ rowStart--;
1106
+ }
1107
+ return rowStart;
1108
+ }
1109
+ function calculateRowMaxSize(state, startIndex, endIndex, useAverageSize) {
1110
+ if (endIndex < startIndex) {
1111
+ return 0;
1112
+ }
1113
+ const { data } = state.props;
1114
+ if (!data) {
1115
+ return 0;
1116
+ }
1117
+ let maxSize = 0;
1118
+ for (let i = startIndex; i <= endIndex; i++) {
1119
+ if (i < 0 || i >= data.length) {
1120
+ continue;
1121
+ }
1122
+ const id = state.idCache[i];
1123
+ const size = getItemSize(state, id, i, data[i], useAverageSize);
1124
+ if (size > maxSize) {
1125
+ maxSize = size;
1126
+ }
1127
+ }
1128
+ return maxSize;
1129
+ }
1130
+
1115
1131
  // src/utils/setPaddingTop.ts
1116
1132
  function setPaddingTop(ctx, state, { stylePaddingTop, alignItemsPaddingTop }) {
1117
1133
  if (stylePaddingTop !== void 0) {
@@ -1206,7 +1222,7 @@ function updateSnapToOffsets(ctx, state) {
1206
1222
 
1207
1223
  // src/core/updateItemPositions.ts
1208
1224
  function updateItemPositions(ctx, state, dataChanged, { startIndex, scrollBottomBuffered } = { scrollBottomBuffered: -1, startIndex: 0 }) {
1209
- var _a, _b, _c, _d, _e, _f;
1225
+ var _a, _b, _c, _d;
1210
1226
  const {
1211
1227
  columns,
1212
1228
  indexByKey,
@@ -1216,41 +1232,47 @@ function updateItemPositions(ctx, state, dataChanged, { startIndex, scrollBottom
1216
1232
  props: { getEstimatedItemSize, snapToIndices, enableAverages }
1217
1233
  } = state;
1218
1234
  const data = state.props.data;
1235
+ const dataLength = data.length;
1219
1236
  const numColumns = peek$(ctx, "numColumns");
1237
+ const hasColumns = numColumns > 1;
1220
1238
  const indexByKeyForChecking = __DEV__ ? /* @__PURE__ */ new Map() : void 0;
1221
1239
  const maxVisibleArea = scrollBottomBuffered + 1e3;
1222
1240
  const useAverageSize = enableAverages && !getEstimatedItemSize;
1223
1241
  let currentRowTop = 0;
1224
1242
  let column = 1;
1225
1243
  let maxSizeInRow = 0;
1226
- const hasColumns = numColumns > 1;
1227
1244
  if (startIndex > 0) {
1228
- const prevIndex = startIndex - 1;
1229
- const prevId = (_a = idCache.get(prevIndex)) != null ? _a : getId(state, prevIndex);
1230
- const prevPosition = (_b = positions.get(prevId)) != null ? _b : 0;
1231
1245
  if (hasColumns) {
1232
- const prevColumn = (_c = columns.get(prevId)) != null ? _c : 1;
1233
- currentRowTop = prevPosition;
1234
- column = prevColumn % numColumns + 1;
1235
- } else {
1236
- const prevSize = (_d = sizesKnown.get(prevId)) != null ? _d : getItemSize(state, prevId, prevIndex, data[prevIndex], useAverageSize);
1246
+ const { startIndex: processedStartIndex, currentRowTop: initialRowTop } = prepareColumnStartState(
1247
+ ctx,
1248
+ state,
1249
+ startIndex,
1250
+ useAverageSize
1251
+ );
1252
+ startIndex = processedStartIndex;
1253
+ currentRowTop = initialRowTop;
1254
+ } else if (startIndex < dataLength) {
1255
+ const prevIndex = startIndex - 1;
1256
+ const prevId = getId(state, prevIndex);
1257
+ const prevPosition = (_a = positions.get(prevId)) != null ? _a : 0;
1258
+ const prevSize = (_b = sizesKnown.get(prevId)) != null ? _b : getItemSize(state, prevId, prevIndex, data[prevIndex], useAverageSize);
1237
1259
  currentRowTop = prevPosition + prevSize;
1238
1260
  }
1239
1261
  }
1240
1262
  const needsIndexByKey = dataChanged || indexByKey.size === 0;
1241
1263
  let didBreakEarly = false;
1242
1264
  let breakAt;
1243
- const dataLength = data.length;
1244
1265
  for (let i = startIndex; i < dataLength; i++) {
1245
1266
  if (breakAt && i > breakAt) {
1246
1267
  didBreakEarly = true;
1247
1268
  break;
1248
1269
  }
1249
- if (!dataChanged && currentRowTop > maxVisibleArea) {
1250
- breakAt = i + 10;
1270
+ if (breakAt === void 0 && !dataChanged && currentRowTop > maxVisibleArea) {
1271
+ const itemsPerRow = hasColumns ? numColumns : 1;
1272
+ breakAt = i + itemsPerRow + 10;
1251
1273
  }
1252
- const id = (_e = idCache.get(i)) != null ? _e : getId(state, i);
1253
- const size = (_f = sizesKnown.get(id)) != null ? _f : getItemSize(state, id, i, data[i], useAverageSize);
1274
+ const id = (_c = idCache[i]) != null ? _c : getId(state, i);
1275
+ const size = (_d = sizesKnown.get(id)) != null ? _d : getItemSize(state, id, i, data[i], useAverageSize);
1254
1276
  if (__DEV__ && needsIndexByKey) {
1255
1277
  if (indexByKeyForChecking.has(id)) {
1256
1278
  console.error(
@@ -1487,9 +1509,12 @@ function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
1487
1509
  var batchedUpdates = reactNative.unstable_batchedUpdates || ((callback) => callback());
1488
1510
 
1489
1511
  // src/utils/checkAllSizesKnown.ts
1512
+ function isNullOrUndefined2(value) {
1513
+ return value === null || value === void 0;
1514
+ }
1490
1515
  function checkAllSizesKnown(state) {
1491
1516
  const { startBuffered, endBuffered, sizesKnown } = state;
1492
- if (endBuffered !== null) {
1517
+ if (!isNullOrUndefined2(endBuffered) && !isNullOrUndefined2(startBuffered) && startBuffered >= 0 && endBuffered >= 0) {
1493
1518
  let areAllKnown = true;
1494
1519
  for (let i = startBuffered; areAllKnown && i <= endBuffered; i++) {
1495
1520
  const key = getId(state, i);
@@ -1506,6 +1531,8 @@ function findAvailableContainers(ctx, state, numNeeded, startBuffered, endBuffer
1506
1531
  const { stickyContainerPool, containerItemTypes } = state;
1507
1532
  const result = [];
1508
1533
  const availableContainers = [];
1534
+ const pendingRemovalSet = new Set(pendingRemoval);
1535
+ let pendingRemovalChanged = false;
1509
1536
  const stickyIndicesSet = state.props.stickyIndicesSet;
1510
1537
  const stickyItemIndices = (needNewContainers == null ? void 0 : needNewContainers.filter((index) => stickyIndicesSet.has(index))) || [];
1511
1538
  const canReuseContainer = (containerIndex, requiredType) => {
@@ -1521,12 +1548,11 @@ function findAvailableContainers(ctx, state, numNeeded, startBuffered, endBuffer
1521
1548
  let foundContainer = false;
1522
1549
  for (const containerIndex of stickyContainerPool) {
1523
1550
  const key = peek$(ctx, `containerItemKey${containerIndex}`);
1524
- const isPendingRemoval = pendingRemoval.includes(containerIndex);
1551
+ const isPendingRemoval = pendingRemovalSet.has(containerIndex);
1525
1552
  if ((key === void 0 || isPendingRemoval) && canReuseContainer(containerIndex, requiredType)) {
1526
1553
  result.push(containerIndex);
1527
- if (isPendingRemoval) {
1528
- const index = pendingRemoval.indexOf(containerIndex);
1529
- pendingRemoval.splice(index, 1);
1554
+ if (isPendingRemoval && pendingRemovalSet.delete(containerIndex)) {
1555
+ pendingRemovalChanged = true;
1530
1556
  }
1531
1557
  foundContainer = true;
1532
1558
  if (requiredItemTypes) typeIndex++;
@@ -1546,13 +1572,11 @@ function findAvailableContainers(ctx, state, numNeeded, startBuffered, endBuffer
1546
1572
  }
1547
1573
  const key = peek$(ctx, `containerItemKey${u}`);
1548
1574
  let isOk = key === void 0;
1549
- if (!isOk) {
1550
- const index = pendingRemoval.indexOf(u);
1551
- if (index !== -1) {
1552
- pendingRemoval.splice(index, 1);
1553
- const requiredType = neededTypes[typeIndex];
1554
- isOk = canReuseContainer(u, requiredType);
1555
- }
1575
+ if (!isOk && pendingRemovalSet.has(u)) {
1576
+ pendingRemovalSet.delete(u);
1577
+ pendingRemovalChanged = true;
1578
+ const requiredType = neededTypes[typeIndex];
1579
+ isOk = canReuseContainer(u, requiredType);
1556
1580
  }
1557
1581
  if (isOk) {
1558
1582
  result.push(u);
@@ -1610,6 +1634,12 @@ function findAvailableContainers(ctx, state, numNeeded, startBuffered, endBuffer
1610
1634
  }
1611
1635
  }
1612
1636
  }
1637
+ if (pendingRemovalChanged) {
1638
+ pendingRemoval.length = 0;
1639
+ for (const value of pendingRemovalSet) {
1640
+ pendingRemoval.push(value);
1641
+ }
1642
+ }
1613
1643
  return result.sort(comparatorDefault);
1614
1644
  }
1615
1645
  function comparatorByDistance(a, b) {
@@ -1769,7 +1799,8 @@ function findCurrentStickyIndex(stickyArray, scroll, state) {
1769
1799
  const idCache = state.idCache;
1770
1800
  const positions = state.positions;
1771
1801
  for (let i = stickyArray.length - 1; i >= 0; i--) {
1772
- const stickyId = (_a = idCache.get(stickyArray[i])) != null ? _a : getId(state, stickyArray[i]);
1802
+ const stickyIndex = stickyArray[i];
1803
+ const stickyId = (_a = idCache[stickyIndex]) != null ? _a : getId(state, stickyIndex);
1773
1804
  const stickyPos = stickyId ? positions.get(stickyId) : void 0;
1774
1805
  if (stickyPos !== void 0 && scroll >= stickyPos) {
1775
1806
  return i;
@@ -1782,40 +1813,43 @@ function getActiveStickyIndices(ctx, state, stickyIndices) {
1782
1813
  Array.from(state.stickyContainerPool).map((i) => peek$(ctx, `containerItemKey${i}`)).map((key) => key ? state.indexByKey.get(key) : void 0).filter((idx) => idx !== void 0 && stickyIndices.has(idx))
1783
1814
  );
1784
1815
  }
1785
- function handleStickyActivation(ctx, state, stickyIndices, stickyArray, scroll, needNewContainers, startBuffered, endBuffered) {
1816
+ function handleStickyActivation(ctx, state, stickyIndices, stickyArray, currentStickyIdx, needNewContainers, startBuffered, endBuffered) {
1786
1817
  var _a;
1787
1818
  const activeIndices = getActiveStickyIndices(ctx, state, stickyIndices);
1788
- const currentStickyIdx = findCurrentStickyIndex(stickyArray, scroll, state);
1789
1819
  state.activeStickyIndex = currentStickyIdx >= 0 ? stickyArray[currentStickyIdx] : void 0;
1790
1820
  for (let offset = 0; offset <= 1; offset++) {
1791
1821
  const idx = currentStickyIdx - offset;
1792
1822
  if (idx < 0 || activeIndices.has(stickyArray[idx])) continue;
1793
1823
  const stickyIndex = stickyArray[idx];
1794
- const stickyId = (_a = state.idCache.get(stickyIndex)) != null ? _a : getId(state, stickyIndex);
1824
+ const stickyId = (_a = state.idCache[stickyIndex]) != null ? _a : getId(state, stickyIndex);
1795
1825
  if (stickyId && !state.containerItemKeys.has(stickyId) && (stickyIndex < startBuffered || stickyIndex > endBuffered)) {
1796
1826
  needNewContainers.push(stickyIndex);
1797
1827
  }
1798
1828
  }
1799
1829
  }
1800
- function handleStickyRecycling(ctx, state, stickyArray, scroll, scrollBuffer, pendingRemoval) {
1830
+ function handleStickyRecycling(ctx, state, stickyArray, scroll, scrollBuffer, currentStickyIdx, pendingRemoval) {
1801
1831
  var _a, _b, _c;
1802
- const currentStickyIdx = findCurrentStickyIndex(stickyArray, scroll, state);
1803
1832
  for (const containerIndex of state.stickyContainerPool) {
1804
1833
  const itemKey = peek$(ctx, `containerItemKey${containerIndex}`);
1805
1834
  const itemIndex = itemKey ? state.indexByKey.get(itemKey) : void 0;
1806
1835
  if (itemIndex === void 0) continue;
1807
1836
  const arrayIdx = stickyArray.indexOf(itemIndex);
1808
- if (arrayIdx === -1) continue;
1837
+ if (arrayIdx === -1) {
1838
+ state.stickyContainerPool.delete(containerIndex);
1839
+ set$(ctx, `containerSticky${containerIndex}`, false);
1840
+ set$(ctx, `containerStickyOffset${containerIndex}`, void 0);
1841
+ continue;
1842
+ }
1809
1843
  const isRecentSticky = arrayIdx >= currentStickyIdx - 1 && arrayIdx <= currentStickyIdx + 1;
1810
1844
  if (isRecentSticky) continue;
1811
1845
  const nextIndex = stickyArray[arrayIdx + 1];
1812
1846
  let shouldRecycle = false;
1813
1847
  if (nextIndex) {
1814
- const nextId = (_a = state.idCache.get(nextIndex)) != null ? _a : getId(state, nextIndex);
1848
+ const nextId = (_a = state.idCache[nextIndex]) != null ? _a : getId(state, nextIndex);
1815
1849
  const nextPos = nextId ? state.positions.get(nextId) : void 0;
1816
1850
  shouldRecycle = nextPos !== void 0 && scroll > nextPos + scrollBuffer * 2;
1817
1851
  } else {
1818
- const currentId = (_b = state.idCache.get(itemIndex)) != null ? _b : getId(state, itemIndex);
1852
+ const currentId = (_b = state.idCache[itemIndex]) != null ? _b : getId(state, itemIndex);
1819
1853
  if (currentId) {
1820
1854
  const currentPos = state.positions.get(currentId);
1821
1855
  const currentSize = (_c = state.sizes.get(currentId)) != null ? _c : getItemSize(state, currentId, itemIndex, state.props.data[itemIndex]);
@@ -1843,7 +1877,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1843
1877
  sizes,
1844
1878
  startBufferedId: startBufferedIdOrig,
1845
1879
  viewabilityConfigCallbackPairs,
1846
- props: { getItemType, initialScroll, itemsAreEqual, keyExtractor, scrollBuffer }
1880
+ props: { getItemType, initialScroll, itemsAreEqual, keyExtractor, onStickyHeaderChange, scrollBuffer }
1847
1881
  } = state;
1848
1882
  const { data } = state.props;
1849
1883
  const stickyIndicesArr = state.props.stickyIndicesArr || [];
@@ -1878,6 +1912,10 @@ function calculateItemsInView(ctx, state, params = {}) {
1878
1912
  set$(ctx, "debugRawScroll", scrollState);
1879
1913
  set$(ctx, "debugComputedScroll", scroll);
1880
1914
  }
1915
+ const previousStickyIndex = state.activeStickyIndex;
1916
+ const currentStickyIdx = stickyIndicesArr.length > 0 ? findCurrentStickyIndex(stickyIndicesArr, scroll, state) : -1;
1917
+ const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyIndicesArr[currentStickyIdx] : void 0;
1918
+ state.activeStickyIndex = nextActiveStickyIndex;
1881
1919
  let scrollBufferTop = scrollBuffer;
1882
1920
  let scrollBufferBottom = scrollBuffer;
1883
1921
  if (speed > 0 || speed === 0 && scroll < Math.max(50, scrollBuffer)) {
@@ -1899,7 +1937,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1899
1937
  const checkMVCP = doMVCP ? prepareMVCP(ctx, state, dataChanged) : void 0;
1900
1938
  if (dataChanged) {
1901
1939
  indexByKey.clear();
1902
- idCache.clear();
1940
+ idCache.length = 0;
1903
1941
  positions.clear();
1904
1942
  }
1905
1943
  const startIndex = dataChanged ? 0 : (_a = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _a : 0;
@@ -1915,7 +1953,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1915
1953
  let endBuffered = null;
1916
1954
  let loopStart = !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
1917
1955
  for (let i = loopStart; i >= 0; i--) {
1918
- const id = (_b = idCache.get(i)) != null ? _b : getId(state, i);
1956
+ const id = (_b = idCache[i]) != null ? _b : getId(state, i);
1919
1957
  const top = positions.get(id);
1920
1958
  const size = (_c = sizes.get(id)) != null ? _c : getItemSize(state, id, i, data[i]);
1921
1959
  const bottom = top + size;
@@ -1943,7 +1981,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1943
1981
  let firstFullyOnScreenIndex;
1944
1982
  const dataLength = data.length;
1945
1983
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
1946
- const id = (_d = idCache.get(i)) != null ? _d : getId(state, i);
1984
+ const id = (_d = idCache[i]) != null ? _d : getId(state, i);
1947
1985
  const size = (_e = sizes.get(id)) != null ? _e : getItemSize(state, id, i, data[i]);
1948
1986
  const top = positions.get(id);
1949
1987
  if (!foundEnd) {
@@ -1973,7 +2011,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1973
2011
  }
1974
2012
  const idsInView = [];
1975
2013
  for (let i = firstFullyOnScreenIndex; i <= endNoBuffer; i++) {
1976
- const id = (_f = idCache.get(i)) != null ? _f : getId(state, i);
2014
+ const id = (_f = idCache[i]) != null ? _f : getId(state, i);
1977
2015
  idsInView.push(id);
1978
2016
  }
1979
2017
  Object.assign(state, {
@@ -2005,7 +2043,7 @@ function calculateItemsInView(ctx, state, params = {}) {
2005
2043
  let numContainers2 = prevNumContainers;
2006
2044
  const needNewContainers = [];
2007
2045
  for (let i = startBuffered; i <= endBuffered; i++) {
2008
- const id = (_g = idCache.get(i)) != null ? _g : getId(state, i);
2046
+ const id = (_g = idCache[i]) != null ? _g : getId(state, i);
2009
2047
  if (!containerItemKeys.has(id)) {
2010
2048
  needNewContainers.push(i);
2011
2049
  }
@@ -2016,7 +2054,7 @@ function calculateItemsInView(ctx, state, params = {}) {
2016
2054
  state,
2017
2055
  stickyIndicesSet,
2018
2056
  stickyIndicesArr,
2019
- scroll,
2057
+ currentStickyIdx,
2020
2058
  needNewContainers,
2021
2059
  startBuffered,
2022
2060
  endBuffered
@@ -2042,7 +2080,7 @@ function calculateItemsInView(ctx, state, params = {}) {
2042
2080
  for (let idx = 0; idx < needNewContainers.length; idx++) {
2043
2081
  const i = needNewContainers[idx];
2044
2082
  const containerIndex = availableContainers[idx];
2045
- const id = (_h = idCache.get(i)) != null ? _h : getId(state, i);
2083
+ const id = (_h = idCache[i]) != null ? _h : getId(state, i);
2046
2084
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
2047
2085
  if (oldKey && oldKey !== id) {
2048
2086
  containerItemKeys.delete(oldKey);
@@ -2056,7 +2094,7 @@ function calculateItemsInView(ctx, state, params = {}) {
2056
2094
  if (stickyIndicesSet.has(i)) {
2057
2095
  set$(ctx, `containerSticky${containerIndex}`, true);
2058
2096
  const topPadding = (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
2059
- set$(ctx, `containerStickyOffset${containerIndex}`, new reactNative.Animated.Value(topPadding));
2097
+ set$(ctx, `containerStickyOffset${containerIndex}`, topPadding);
2060
2098
  state.stickyContainerPool.add(containerIndex);
2061
2099
  } else {
2062
2100
  set$(ctx, `containerSticky${containerIndex}`, false);
@@ -2075,7 +2113,7 @@ function calculateItemsInView(ctx, state, params = {}) {
2075
2113
  }
2076
2114
  }
2077
2115
  if (stickyIndicesArr.length > 0) {
2078
- handleStickyRecycling(ctx, state, stickyIndicesArr, scroll, scrollBuffer, pendingRemoval);
2116
+ handleStickyRecycling(ctx, state, stickyIndicesArr, scroll, scrollBuffer, currentStickyIdx, pendingRemoval);
2079
2117
  }
2080
2118
  for (let i = 0; i < numContainers; i++) {
2081
2119
  const itemKey = peek$(ctx, `containerItemKey${i}`);
@@ -2097,7 +2135,7 @@ function calculateItemsInView(ctx, state, params = {}) {
2097
2135
  const itemIndex = indexByKey.get(itemKey);
2098
2136
  const item = data[itemIndex];
2099
2137
  if (item !== void 0) {
2100
- const id = (_i = idCache.get(itemIndex)) != null ? _i : getId(state, itemIndex);
2138
+ const id = (_i = idCache[itemIndex]) != null ? _i : getId(state, itemIndex);
2101
2139
  const position = positions.get(id);
2102
2140
  if (position === void 0) {
2103
2141
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
@@ -2127,6 +2165,12 @@ function calculateItemsInView(ctx, state, params = {}) {
2127
2165
  if (viewabilityConfigCallbackPairs) {
2128
2166
  updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, scrollLength, startNoBuffer, endNoBuffer);
2129
2167
  }
2168
+ if (onStickyHeaderChange && stickyIndicesArr.length > 0 && nextActiveStickyIndex !== void 0 && nextActiveStickyIndex !== previousStickyIndex) {
2169
+ const item = data[nextActiveStickyIndex];
2170
+ if (item !== void 0) {
2171
+ onStickyHeaderChange({ index: nextActiveStickyIndex, item });
2172
+ }
2173
+ }
2130
2174
  });
2131
2175
  }
2132
2176
 
@@ -2285,7 +2329,9 @@ function doInitialAllocateContainers(ctx, state) {
2285
2329
  let totalSize = 0;
2286
2330
  const num = Math.min(20, data.length);
2287
2331
  for (let i = 0; i < num; i++) {
2288
- totalSize += fn(0, data[0], getItemType ? (_a = getItemType(data[0], 0)) != null ? _a : "" : "");
2332
+ const item = data[i];
2333
+ const itemType = getItemType ? (_a = getItemType(item, i)) != null ? _a : "" : "";
2334
+ totalSize += fn(i, item, itemType);
2289
2335
  }
2290
2336
  averageItemSize = totalSize / num;
2291
2337
  } else {
@@ -2314,7 +2360,9 @@ function doInitialAllocateContainers(ctx, state) {
2314
2360
  // src/core/handleLayout.ts
2315
2361
  function handleLayout(ctx, state, layout, setCanRender) {
2316
2362
  const { maintainScrollAtEnd } = state.props;
2317
- const scrollLength = layout[state.props.horizontal ? "width" : "height"];
2363
+ const measuredLength = layout[state.props.horizontal ? "width" : "height"];
2364
+ const previousLength = state.scrollLength;
2365
+ const scrollLength = measuredLength > 0 ? measuredLength : previousLength;
2318
2366
  const otherAxisSize = layout[state.props.horizontal ? "height" : "width"];
2319
2367
  const needsCalculate = !state.lastLayout || scrollLength > state.scrollLength || state.lastLayout.x !== layout.x || state.lastLayout.y !== layout.y;
2320
2368
  state.lastLayout = layout;
@@ -2325,7 +2373,9 @@ function handleLayout(ctx, state, layout, setCanRender) {
2325
2373
  state.otherAxisSize = otherAxisSize;
2326
2374
  state.lastBatchingAction = Date.now();
2327
2375
  state.scrollForNextCalculateItemsInView = void 0;
2328
- doInitialAllocateContainers(ctx, state);
2376
+ if (scrollLength > 0) {
2377
+ doInitialAllocateContainers(ctx, state);
2378
+ }
2329
2379
  if (needsCalculate) {
2330
2380
  calculateItemsInView(ctx, state, { doMVCP: true });
2331
2381
  }
@@ -2341,14 +2391,14 @@ function handleLayout(ctx, state, layout, setCanRender) {
2341
2391
  if (state) {
2342
2392
  state.needsOtherAxisSize = otherAxisSize - (state.props.stylePaddingTop || 0) < 10;
2343
2393
  }
2344
- if (__DEV__ && scrollLength === 0) {
2394
+ if (__DEV__ && measuredLength === 0) {
2345
2395
  warnDevOnce(
2346
2396
  "height0",
2347
2397
  `List ${state.props.horizontal ? "width" : "height"} is 0. You may need to set a style or \`flex: \` for the list, because children are absolutely positioned.`
2348
2398
  );
2349
2399
  }
2350
- setCanRender(true);
2351
2400
  }
2401
+ setCanRender(true);
2352
2402
  }
2353
2403
 
2354
2404
  // src/core/onScroll.ts
@@ -2598,18 +2648,59 @@ function getRenderedItem(ctx, state, key) {
2598
2648
  return null;
2599
2649
  }
2600
2650
  let renderedItem = null;
2601
- if (renderItem && data[index]) {
2651
+ const extraData = peek$(ctx, "extraData");
2652
+ const item = data[index];
2653
+ if (renderItem && !isNullOrUndefined(item)) {
2602
2654
  const itemProps = {
2603
2655
  data,
2604
- extraData: peek$(ctx, "extraData"),
2656
+ extraData,
2605
2657
  index,
2606
- item: data[index],
2607
- type: getItemType ? (_a = getItemType(data[index], index)) != null ? _a : "" : ""
2658
+ item,
2659
+ type: getItemType ? (_a = getItemType(item, index)) != null ? _a : "" : ""
2608
2660
  };
2609
2661
  renderedItem = isFunction(renderItem) ? renderItem(itemProps) : React2__namespace.default.createElement(renderItem, itemProps);
2610
2662
  }
2611
2663
  return { index, item: data[index], renderedItem };
2612
2664
  }
2665
+ function useThrottleDebounce(mode) {
2666
+ const timeoutRef = React2.useRef(null);
2667
+ const lastCallTimeRef = React2.useRef(0);
2668
+ const lastArgsRef = React2.useRef(null);
2669
+ const clearTimeoutRef = () => {
2670
+ if (timeoutRef.current) {
2671
+ clearTimeout(timeoutRef.current);
2672
+ timeoutRef.current = null;
2673
+ }
2674
+ };
2675
+ const execute = React2.useCallback(
2676
+ (callback, delay, ...args) => {
2677
+ {
2678
+ const now = Date.now();
2679
+ lastArgsRef.current = args;
2680
+ if (now - lastCallTimeRef.current >= delay) {
2681
+ lastCallTimeRef.current = now;
2682
+ callback(...args);
2683
+ clearTimeoutRef();
2684
+ } else {
2685
+ clearTimeoutRef();
2686
+ timeoutRef.current = setTimeout(
2687
+ () => {
2688
+ if (lastArgsRef.current) {
2689
+ lastCallTimeRef.current = Date.now();
2690
+ callback(...lastArgsRef.current);
2691
+ timeoutRef.current = null;
2692
+ lastArgsRef.current = null;
2693
+ }
2694
+ },
2695
+ delay - (now - lastCallTimeRef.current)
2696
+ );
2697
+ }
2698
+ }
2699
+ },
2700
+ [mode]
2701
+ );
2702
+ return execute;
2703
+ }
2613
2704
 
2614
2705
  // src/utils/throttledOnScroll.ts
2615
2706
  function useThrottledOnScroll(originalHandler, scrollEventThrottle) {
@@ -2673,6 +2764,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2673
2764
  onScroll: onScrollProp,
2674
2765
  onStartReached,
2675
2766
  onStartReachedThreshold = 0.5,
2767
+ onStickyHeaderChange,
2676
2768
  onViewableItemsChanged,
2677
2769
  progressViewOffset,
2678
2770
  recycleItems = false,
@@ -2720,7 +2812,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2720
2812
  endNoBuffer: -1,
2721
2813
  endReachedBlockedByTimer: false,
2722
2814
  firstFullyOnScreenIndex: -1,
2723
- idCache: /* @__PURE__ */ new Map(),
2815
+ idCache: [],
2724
2816
  idsInView: [],
2725
2817
  indexByKey: /* @__PURE__ */ new Map(),
2726
2818
  initialScroll,
@@ -2795,6 +2887,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2795
2887
  onScroll: throttleScrollFn,
2796
2888
  onStartReached,
2797
2889
  onStartReachedThreshold,
2890
+ onStickyHeaderChange,
2798
2891
  recycleItems: !!recycleItems,
2799
2892
  renderItem,
2800
2893
  scrollBuffer,
@@ -2889,7 +2982,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2889
2982
  }
2890
2983
  }, [snapToIndices]);
2891
2984
  React2.useLayoutEffect(() => {
2892
- const didAllocateContainers = dataProp.length > 0 && doInitialAllocateContainersCallback();
2985
+ const didAllocateContainers = dataProp.length > 0 && doInitialAllocateContainers(ctx, state);
2893
2986
  if (!didAllocateContainers) {
2894
2987
  checkResetContainers(
2895
2988
  ctx,
@@ -2903,30 +2996,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2903
2996
  React2.useLayoutEffect(() => {
2904
2997
  set$(ctx, "extraData", extraData);
2905
2998
  }, [extraData]);
2906
- React2.useLayoutEffect(() => {
2907
- var _a2;
2908
- if (IsNewArchitecture) {
2909
- let measured;
2910
- (_a2 = refScroller.current) == null ? void 0 : _a2.measure((x, y, width, height) => {
2911
- measured = { height, width, x, y };
2912
- });
2913
- if (measured) {
2914
- const size = Math.floor(measured[horizontal ? "width" : "height"] * 8) / 8;
2915
- if (size) {
2916
- handleLayout(ctx, state, measured, setCanRender);
2917
- }
2918
- }
2919
- }
2920
- }, []);
2921
2999
  React2.useLayoutEffect(initializeStateVars, [
2922
3000
  memoizedLastItemKeys.join(","),
2923
3001
  numColumnsProp,
2924
3002
  stylePaddingTopState,
2925
3003
  stylePaddingBottomState
2926
3004
  ]);
2927
- const doInitialAllocateContainersCallback = () => {
2928
- return doInitialAllocateContainers(ctx, state);
2929
- };
2930
3005
  React2.useEffect(() => {
2931
3006
  const viewability = setupViewability({
2932
3007
  onViewableItemsChanged,
@@ -2938,16 +3013,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2938
3013
  }, [viewabilityConfig, viewabilityConfigCallbackPairs, onViewableItemsChanged]);
2939
3014
  if (!IsNewArchitecture) {
2940
3015
  useInit(() => {
2941
- doInitialAllocateContainersCallback();
3016
+ doInitialAllocateContainers(ctx, state);
2942
3017
  });
2943
3018
  }
2944
- const onLayout = React2.useCallback((event) => {
2945
- const layout = event.nativeEvent.layout;
3019
+ const onLayoutChange = React2.useCallback((layout) => {
2946
3020
  handleLayout(ctx, state, layout, setCanRender);
2947
- if (onLayoutProp) {
2948
- onLayoutProp(event);
2949
- }
2950
3021
  }, []);
3022
+ const { onLayout } = useOnLayoutSync({
3023
+ onLayoutChange,
3024
+ onLayoutProp,
3025
+ ref: refScroller
3026
+ // the type of ScrollView doesn't include measure?
3027
+ });
2951
3028
  React2.useImperativeHandle(forwardedRef, () => {
2952
3029
  const scrollIndexIntoView = (options) => {
2953
3030
  const state2 = refState.current;