@legendapp/list 3.0.0-beta.28 → 3.0.0-beta.29

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.d.mts CHANGED
@@ -5,7 +5,7 @@ import Reanimated from 'react-native-reanimated';
5
5
 
6
6
  type AnimatedValue = number;
7
7
 
8
- type ListenerType = "activeStickyIndex" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | `containerColumn${number}` | `containerItemData${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}` | `containerStickyOffset${number}`;
8
+ type ListenerType = "activeStickyIndex" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
9
9
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
10
10
  type ListenerTypeValueMap = {
11
11
  activeStickyIndex: number;
@@ -42,9 +42,9 @@ type ListenerTypeValueMap = {
42
42
  } & {
43
43
  [K in ListenerType as K extends `containerColumn${number}` ? K : never]: number;
44
44
  } & {
45
- [K in ListenerType as K extends `containerSticky${number}` ? K : never]: boolean;
45
+ [K in ListenerType as K extends `containerSpan${number}` ? K : never]: number;
46
46
  } & {
47
- [K in ListenerType as K extends `containerStickyOffset${number}` ? K : never]: number;
47
+ [K in ListenerType as K extends `containerSticky${number}` ? K : never]: boolean;
48
48
  };
49
49
  interface StateContext {
50
50
  animatedScrollY: AnimatedValue;
@@ -160,6 +160,13 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
160
160
  * Use instead of FlatList's getItemLayout or FlashList overrideItemLayout if you want to have accurate initialScrollOffset, you should provide this function
161
161
  */
162
162
  getEstimatedItemSize?: (item: ItemT, index: number, type: TItemType) => number;
163
+ /**
164
+ * Customize layout for multi-column lists, such as allowing items to span multiple columns.
165
+ * Similar to FlashList's overrideItemLayout.
166
+ */
167
+ overrideItemLayout?: (layout: {
168
+ span?: number;
169
+ }, item: ItemT, index: number, maxColumns: number, extraData?: any) => void;
163
170
  /**
164
171
  * Ratio of initial container pool size to data length (e.g., 0.5 for half).
165
172
  * @default 2
@@ -429,6 +436,7 @@ interface InternalState {
429
436
  avg: number;
430
437
  }>;
431
438
  columns: Map<string, number>;
439
+ columnSpans: Map<string, number>;
432
440
  containerItemKeys: Map<string, number>;
433
441
  containerItemTypes: Map<number, string>;
434
442
  dataChangeNeedsScrollUpdate: boolean;
@@ -542,6 +550,7 @@ interface InternalState {
542
550
  onStartReached: LegendListProps["onStartReached"];
543
551
  onStartReachedThreshold: number | null | undefined;
544
552
  onStickyHeaderChange: LegendListProps["onStickyHeaderChange"];
553
+ overrideItemLayout: LegendListProps["overrideItemLayout"];
545
554
  recycleItems: boolean;
546
555
  renderItem: LegendListProps["renderItem"];
547
556
  scrollBuffer: number;
package/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import Reanimated from 'react-native-reanimated';
5
5
 
6
6
  type AnimatedValue = number;
7
7
 
8
- type ListenerType = "activeStickyIndex" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | `containerColumn${number}` | `containerItemData${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}` | `containerStickyOffset${number}`;
8
+ type ListenerType = "activeStickyIndex" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
9
9
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
10
10
  type ListenerTypeValueMap = {
11
11
  activeStickyIndex: number;
@@ -42,9 +42,9 @@ type ListenerTypeValueMap = {
42
42
  } & {
43
43
  [K in ListenerType as K extends `containerColumn${number}` ? K : never]: number;
44
44
  } & {
45
- [K in ListenerType as K extends `containerSticky${number}` ? K : never]: boolean;
45
+ [K in ListenerType as K extends `containerSpan${number}` ? K : never]: number;
46
46
  } & {
47
- [K in ListenerType as K extends `containerStickyOffset${number}` ? K : never]: number;
47
+ [K in ListenerType as K extends `containerSticky${number}` ? K : never]: boolean;
48
48
  };
49
49
  interface StateContext {
50
50
  animatedScrollY: AnimatedValue;
@@ -160,6 +160,13 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
160
160
  * Use instead of FlatList's getItemLayout or FlashList overrideItemLayout if you want to have accurate initialScrollOffset, you should provide this function
161
161
  */
162
162
  getEstimatedItemSize?: (item: ItemT, index: number, type: TItemType) => number;
163
+ /**
164
+ * Customize layout for multi-column lists, such as allowing items to span multiple columns.
165
+ * Similar to FlashList's overrideItemLayout.
166
+ */
167
+ overrideItemLayout?: (layout: {
168
+ span?: number;
169
+ }, item: ItemT, index: number, maxColumns: number, extraData?: any) => void;
163
170
  /**
164
171
  * Ratio of initial container pool size to data length (e.g., 0.5 for half).
165
172
  * @default 2
@@ -429,6 +436,7 @@ interface InternalState {
429
436
  avg: number;
430
437
  }>;
431
438
  columns: Map<string, number>;
439
+ columnSpans: Map<string, number>;
432
440
  containerItemKeys: Map<string, number>;
433
441
  containerItemTypes: Map<number, string>;
434
442
  dataChangeNeedsScrollUpdate: boolean;
@@ -542,6 +550,7 @@ interface InternalState {
542
550
  onStartReached: LegendListProps["onStartReached"];
543
551
  onStartReachedThreshold: number | null | undefined;
544
552
  onStickyHeaderChange: LegendListProps["onStickyHeaderChange"];
553
+ overrideItemLayout: LegendListProps["overrideItemLayout"];
545
554
  recycleItems: boolean;
546
555
  renderItem: LegendListProps["renderItem"];
547
556
  scrollBuffer: number;
package/index.js CHANGED
@@ -314,7 +314,7 @@ var PositionViewState = typedMemo(function PositionViewState2({
314
314
  };
315
315
  const composed = isArray(style) ? Object.assign({}, ...style) : style;
316
316
  const combinedStyle = horizontal ? { ...base, ...composed, left: position } : { ...base, ...composed, top: position };
317
- const { animatedScrollY, stickyOffset, onLayout, index, ...webProps } = props;
317
+ const { animatedScrollY, onLayout, index, ...webProps } = props;
318
318
  return /* @__PURE__ */ React3__namespace.createElement("div", { ref: refView, ...webProps, style: combinedStyle });
319
319
  });
320
320
  var PositionViewSticky = typedMemo(function PositionViewSticky2({
@@ -323,16 +323,12 @@ var PositionViewSticky = typedMemo(function PositionViewSticky2({
323
323
  style,
324
324
  refView,
325
325
  index,
326
- stickyOffset,
327
326
  animatedScrollY: _animatedScrollY,
327
+ stickyHeaderConfig,
328
328
  children,
329
329
  ...rest
330
330
  }) {
331
- const [position = POSITION_OUT_OF_VIEW, headerSize = 0, activeStickyIndex] = useArr$([
332
- `containerPosition${id}`,
333
- "headerSize",
334
- "activeStickyIndex"
335
- ]);
331
+ const [position = POSITION_OUT_OF_VIEW, activeStickyIndex] = useArr$([`containerPosition${id}`, "activeStickyIndex"]);
336
332
  const base = {
337
333
  contain: "paint layout style"
338
334
  };
@@ -347,7 +343,8 @@ var PositionViewSticky = typedMemo(function PositionViewSticky2({
347
343
  var _a3;
348
344
  const styleBase = { ...base, ...composed };
349
345
  delete styleBase.transform;
350
- const offset = (_a3 = stickyOffset != null ? stickyOffset : headerSize) != null ? _a3 : 0;
346
+ const stickyConfigOffset = (_a3 = stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset) != null ? _a3 : 0;
347
+ const offset = stickyConfigOffset != null ? stickyConfigOffset : 0;
351
348
  const isActive = activeStickyIndex === index;
352
349
  styleBase.position = isActive ? "sticky" : "absolute";
353
350
  styleBase.zIndex = index + 1e3;
@@ -357,7 +354,7 @@ var PositionViewSticky = typedMemo(function PositionViewSticky2({
357
354
  styleBase.top = isActive ? offset : position;
358
355
  }
359
356
  return styleBase;
360
- }, [composed, horizontal, position, index, stickyOffset, headerSize, activeStickyIndex]);
357
+ }, [composed, horizontal, position, index, activeStickyIndex, stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset]);
361
358
  return /* @__PURE__ */ React3__namespace.createElement("div", { ref: refView, style: viewStyle, ...rest }, children);
362
359
  });
363
360
  var PositionView = PositionViewState;
@@ -630,14 +627,14 @@ var Container = typedMemo(function Container2({
630
627
  }) {
631
628
  const ctx = useStateContext();
632
629
  const { columnWrapperStyle, animatedScrollY } = ctx;
633
- const [column = 0, data, itemKey, numColumns, extraData, isSticky, stickyOffset] = useArr$([
630
+ const [column = 0, span = 1, data, itemKey, numColumns = 1, extraData, isSticky] = useArr$([
634
631
  `containerColumn${id}`,
632
+ `containerSpan${id}`,
635
633
  `containerItemData${id}`,
636
634
  `containerItemKey${id}`,
637
635
  "numColumns",
638
636
  "extraData",
639
- `containerSticky${id}`,
640
- `containerStickyOffset${id}`
637
+ `containerSticky${id}`
641
638
  ]);
642
639
  const itemLayoutRef = React3.useRef({
643
640
  horizontal,
@@ -649,8 +646,10 @@ var Container = typedMemo(function Container2({
649
646
  itemLayoutRef.current.updateItemSize = updateItemSize2;
650
647
  const ref = React3.useRef(null);
651
648
  const [layoutRenderCount, forceLayoutRender] = React3.useState(0);
652
- const otherAxisPos = numColumns > 1 ? `${(column - 1) / numColumns * 100}%` : 0;
653
- const otherAxisSize = numColumns > 1 ? `${1 / numColumns * 100}%` : void 0;
649
+ const resolvedColumn = column > 0 ? column : 1;
650
+ const resolvedSpan = Math.min(Math.max(span || 1, 1), numColumns);
651
+ const otherAxisPos = numColumns > 1 ? `${(resolvedColumn - 1) / numColumns * 100}%` : 0;
652
+ const otherAxisSize = numColumns > 1 ? `${resolvedSpan / numColumns * 100}%` : void 0;
654
653
  const didLayoutRef = React3.useRef(false);
655
654
  const style = React3.useMemo(() => {
656
655
  let paddingStyles;
@@ -741,7 +740,6 @@ var Container = typedMemo(function Container2({
741
740
  onLayout,
742
741
  refView: ref,
743
742
  stickyHeaderConfig,
744
- stickyOffset: isSticky ? stickyOffset : void 0,
745
743
  style
746
744
  },
747
745
  /* @__PURE__ */ React3__namespace.createElement(ContextContainer.Provider, { value: contextValue }, renderedItem, renderedItemInfo && ItemSeparatorComponent && /* @__PURE__ */ React3__namespace.createElement(Separator, { ItemSeparatorComponent, leadingItem: renderedItemInfo.item }))
@@ -1206,6 +1204,14 @@ var ListComponent = typedMemo(function ListComponent2({
1206
1204
  [renderScrollComponent]
1207
1205
  ) : ListComponentScrollView;
1208
1206
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1207
+ React3.useLayoutEffect(() => {
1208
+ if (!ListHeaderComponent) {
1209
+ set$(ctx, "headerSize", 0);
1210
+ }
1211
+ if (!ListFooterComponent) {
1212
+ set$(ctx, "footerSize", 0);
1213
+ }
1214
+ }, [ListHeaderComponent, ListFooterComponent, ctx]);
1209
1215
  return /* @__PURE__ */ React3__namespace.createElement(
1210
1216
  SnapOrScroll,
1211
1217
  {
@@ -1591,7 +1597,8 @@ function checkAtBottom(ctx) {
1591
1597
  } = state;
1592
1598
  const contentSize = getContentSize(ctx);
1593
1599
  if (contentSize > 0 && queuedInitialLayout && !maintainingScrollAtEnd) {
1594
- const distanceFromEnd = contentSize - scroll - scrollLength;
1600
+ const insetEnd = getContentInsetEnd(state);
1601
+ const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
1595
1602
  const isContentLess = contentSize < scrollLength;
1596
1603
  state.isAtEnd = isContentLess || distanceFromEnd < scrollLength * maintainScrollAtEndThreshold;
1597
1604
  state.isEndReached = checkThreshold(
@@ -1887,11 +1894,13 @@ function calculateRowMaxSize(ctx, startIndex, endIndex, useAverageSize) {
1887
1894
 
1888
1895
  // src/core/updateTotalSize.ts
1889
1896
  function updateTotalSize(ctx) {
1897
+ var _a3, _b, _c;
1890
1898
  const state = ctx.state;
1891
1899
  const {
1892
1900
  positions,
1893
1901
  props: { data }
1894
1902
  } = state;
1903
+ const numColumns = (_a3 = peek$(ctx, "numColumns")) != null ? _a3 : 1;
1895
1904
  if (data.length === 0) {
1896
1905
  addTotalSize(ctx, null, 0);
1897
1906
  } else {
@@ -1899,10 +1908,31 @@ function updateTotalSize(ctx) {
1899
1908
  if (lastId !== void 0) {
1900
1909
  const lastPosition = positions.get(lastId);
1901
1910
  if (lastPosition !== void 0) {
1902
- const lastSize = getItemSize(ctx, lastId, data.length - 1, data[data.length - 1]);
1903
- if (lastSize !== void 0) {
1904
- const totalSize = lastPosition + lastSize;
1905
- addTotalSize(ctx, null, totalSize);
1911
+ if (numColumns > 1) {
1912
+ let rowStart = data.length - 1;
1913
+ while (rowStart > 0) {
1914
+ const rowId = (_b = state.idCache[rowStart]) != null ? _b : getId(state, rowStart);
1915
+ const column = state.columns.get(rowId);
1916
+ if (column === 1 || column === void 0) {
1917
+ break;
1918
+ }
1919
+ rowStart -= 1;
1920
+ }
1921
+ let maxSize = 0;
1922
+ for (let i = rowStart; i < data.length; i++) {
1923
+ const rowId = (_c = state.idCache[i]) != null ? _c : getId(state, i);
1924
+ const size = getItemSize(ctx, rowId, i, data[i]);
1925
+ if (size > maxSize) {
1926
+ maxSize = size;
1927
+ }
1928
+ }
1929
+ addTotalSize(ctx, null, lastPosition + maxSize);
1930
+ } else {
1931
+ const lastSize = getItemSize(ctx, lastId, data.length - 1, data[data.length - 1]);
1932
+ if (lastSize !== void 0) {
1933
+ const totalSize = lastPosition + lastSize;
1934
+ addTotalSize(ctx, null, totalSize);
1935
+ }
1906
1936
  }
1907
1937
  }
1908
1938
  }
@@ -1971,30 +2001,36 @@ function updateItemPositions(ctx, dataChanged, { startIndex, scrollBottomBuffere
1971
2001
  scrollBottomBuffered: -1,
1972
2002
  startIndex: 0
1973
2003
  }) {
1974
- var _a3, _b, _c, _d, _e;
2004
+ var _a3, _b, _c, _d, _e, _f;
1975
2005
  const state = ctx.state;
1976
2006
  const {
1977
2007
  columns,
2008
+ columnSpans,
1978
2009
  indexByKey,
1979
2010
  positions,
1980
2011
  idCache,
1981
2012
  sizesKnown,
1982
- props: { data, getEstimatedItemSize, snapToIndices },
2013
+ props: { data, getEstimatedItemSize, overrideItemLayout, snapToIndices },
1983
2014
  scrollingTo
1984
2015
  } = state;
1985
2016
  const dataLength = data.length;
1986
- const numColumns = peek$(ctx, "numColumns");
2017
+ const numColumns = (_a3 = peek$(ctx, "numColumns")) != null ? _a3 : 1;
1987
2018
  const hasColumns = numColumns > 1;
1988
2019
  const indexByKeyForChecking = IS_DEV ? /* @__PURE__ */ new Map() : void 0;
2020
+ const extraData = peek$(ctx, "extraData");
2021
+ const layoutConfig = overrideItemLayout ? { span: 1 } : void 0;
1989
2022
  const lastScrollDelta = state.lastScrollDelta;
1990
2023
  const velocity = getScrollVelocity(state);
1991
2024
  const shouldOptimize = !forceFullUpdate && !dataChanged && (Math.abs(velocity) > 0 || state.scrollLength > 0 && lastScrollDelta > state.scrollLength);
1992
2025
  const maxVisibleArea = scrollBottomBuffered + 1e3;
1993
2026
  const useAverageSize = !getEstimatedItemSize;
1994
- const preferCachedSize = !doMVCP || dataChanged || state.scrollAdjustHandler.getAdjust() !== 0 || ((_a3 = peek$(ctx, "scrollAdjustPending")) != null ? _a3 : 0) !== 0;
2027
+ const preferCachedSize = !doMVCP || dataChanged || state.scrollAdjustHandler.getAdjust() !== 0 || ((_b = peek$(ctx, "scrollAdjustPending")) != null ? _b : 0) !== 0;
1995
2028
  let currentRowTop = 0;
1996
2029
  let column = 1;
1997
2030
  let maxSizeInRow = 0;
2031
+ if (dataChanged) {
2032
+ columnSpans.clear();
2033
+ }
1998
2034
  if (startIndex > 0) {
1999
2035
  if (hasColumns) {
2000
2036
  const { startIndex: processedStartIndex, currentRowTop: initialRowTop } = prepareColumnStartState(
@@ -2007,8 +2043,8 @@ function updateItemPositions(ctx, dataChanged, { startIndex, scrollBottomBuffere
2007
2043
  } else if (startIndex < dataLength) {
2008
2044
  const prevIndex = startIndex - 1;
2009
2045
  const prevId = getId(state, prevIndex);
2010
- const prevPosition = (_b = positions.get(prevId)) != null ? _b : 0;
2011
- const prevSize = (_c = sizesKnown.get(prevId)) != null ? _c : getItemSize(ctx, prevId, prevIndex, data[prevIndex], useAverageSize, preferCachedSize);
2046
+ const prevPosition = (_c = positions.get(prevId)) != null ? _c : 0;
2047
+ const prevSize = (_d = sizesKnown.get(prevId)) != null ? _d : getItemSize(ctx, prevId, prevIndex, data[prevIndex], useAverageSize, preferCachedSize);
2012
2048
  currentRowTop = prevPosition + prevSize;
2013
2049
  }
2014
2050
  }
@@ -2024,8 +2060,22 @@ function updateItemPositions(ctx, dataChanged, { startIndex, scrollBottomBuffere
2024
2060
  const itemsPerRow = hasColumns ? numColumns : 1;
2025
2061
  breakAt = i + itemsPerRow + 10;
2026
2062
  }
2027
- const id = (_d = idCache[i]) != null ? _d : getId(state, i);
2028
- const size = (_e = sizesKnown.get(id)) != null ? _e : getItemSize(ctx, id, i, data[i], useAverageSize, preferCachedSize);
2063
+ const id = (_e = idCache[i]) != null ? _e : getId(state, i);
2064
+ let span = 1;
2065
+ if (hasColumns && overrideItemLayout && layoutConfig) {
2066
+ layoutConfig.span = 1;
2067
+ overrideItemLayout(layoutConfig, data[i], i, numColumns, extraData);
2068
+ const requestedSpan = layoutConfig.span;
2069
+ if (requestedSpan !== void 0 && Number.isFinite(requestedSpan)) {
2070
+ span = Math.max(1, Math.min(numColumns, Math.round(requestedSpan)));
2071
+ }
2072
+ }
2073
+ if (hasColumns && column + span - 1 > numColumns) {
2074
+ currentRowTop += maxSizeInRow;
2075
+ column = 1;
2076
+ maxSizeInRow = 0;
2077
+ }
2078
+ const size = (_f = sizesKnown.get(id)) != null ? _f : getItemSize(ctx, id, i, data[i], useAverageSize, preferCachedSize);
2029
2079
  if (IS_DEV && needsIndexByKey) {
2030
2080
  if (indexByKeyForChecking.has(id)) {
2031
2081
  console.error(
@@ -2042,11 +2092,12 @@ function updateItemPositions(ctx, dataChanged, { startIndex, scrollBottomBuffere
2042
2092
  indexByKey.set(id, i);
2043
2093
  }
2044
2094
  columns.set(id, column);
2095
+ columnSpans.set(id, span);
2045
2096
  if (hasColumns) {
2046
2097
  if (size > maxSizeInRow) {
2047
2098
  maxSizeInRow = size;
2048
2099
  }
2049
- column++;
2100
+ column += span;
2050
2101
  if (column > numColumns) {
2051
2102
  currentRowTop += maxSizeInRow;
2052
2103
  column = 1;
@@ -2496,7 +2547,6 @@ function handleStickyRecycling(ctx, stickyArray, scroll, scrollBuffer, currentSt
2496
2547
  if (arrayIdx === -1) {
2497
2548
  state.stickyContainerPool.delete(containerIndex);
2498
2549
  set$(ctx, `containerSticky${containerIndex}`, false);
2499
- set$(ctx, `containerStickyOffset${containerIndex}`, void 0);
2500
2550
  continue;
2501
2551
  }
2502
2552
  const isRecentSticky = arrayIdx >= currentStickyIdx - 1 && arrayIdx <= currentStickyIdx + 1;
@@ -2523,9 +2573,10 @@ function handleStickyRecycling(ctx, stickyArray, scroll, scrollBuffer, currentSt
2523
2573
  function calculateItemsInView(ctx, params = {}) {
2524
2574
  const state = ctx.state;
2525
2575
  reactDom.unstable_batchedUpdates(() => {
2526
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
2576
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2527
2577
  const {
2528
2578
  columns,
2579
+ columnSpans,
2529
2580
  containerItemKeys,
2530
2581
  enableScrollForNextCalculateItemsInView,
2531
2582
  idCache,
@@ -2643,9 +2694,15 @@ function calculateItemsInView(ctx, params = {}) {
2643
2694
  break;
2644
2695
  }
2645
2696
  }
2646
- const loopStartMod = loopStart % numColumns;
2647
- if (loopStartMod > 0) {
2648
- loopStart -= loopStartMod;
2697
+ if (numColumns > 1) {
2698
+ while (loopStart > 0) {
2699
+ const loopId = (_e = idCache[loopStart]) != null ? _e : getId(state, loopStart);
2700
+ const loopColumn = columns.get(loopId);
2701
+ if (loopColumn === 1 || loopColumn === void 0) {
2702
+ break;
2703
+ }
2704
+ loopStart -= 1;
2705
+ }
2649
2706
  }
2650
2707
  let foundEnd = false;
2651
2708
  let nextTop;
@@ -2661,8 +2718,8 @@ function calculateItemsInView(ctx, params = {}) {
2661
2718
  let firstFullyOnScreenIndex;
2662
2719
  const dataLength = data.length;
2663
2720
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
2664
- const id = (_e = idCache[i]) != null ? _e : getId(state, i);
2665
- const size = (_f = sizes.get(id)) != null ? _f : getItemSize(ctx, id, i, data[i]);
2721
+ const id = (_f = idCache[i]) != null ? _f : getId(state, i);
2722
+ const size = (_g = sizes.get(id)) != null ? _g : getItemSize(ctx, id, i, data[i]);
2666
2723
  const top = positions.get(id);
2667
2724
  if (!foundEnd) {
2668
2725
  if (startNoBuffer === null && top + size > scroll) {
@@ -2699,7 +2756,7 @@ function calculateItemsInView(ctx, params = {}) {
2699
2756
  }
2700
2757
  const idsInView = [];
2701
2758
  for (let i = firstFullyOnScreenIndex; i <= endNoBuffer; i++) {
2702
- const id = (_g = idCache[i]) != null ? _g : getId(state, i);
2759
+ const id = (_h = idCache[i]) != null ? _h : getId(state, i);
2703
2760
  idsInView.push(id);
2704
2761
  }
2705
2762
  Object.assign(state, {
@@ -2731,7 +2788,7 @@ function calculateItemsInView(ctx, params = {}) {
2731
2788
  const needNewContainers = [];
2732
2789
  const needNewContainersSet = /* @__PURE__ */ new Set();
2733
2790
  for (let i = startBuffered; i <= endBuffered; i++) {
2734
- const id = (_h = idCache[i]) != null ? _h : getId(state, i);
2791
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
2735
2792
  if (!containerItemKeys.has(id)) {
2736
2793
  needNewContainersSet.add(i);
2737
2794
  needNewContainers.push(i);
@@ -2740,7 +2797,7 @@ function calculateItemsInView(ctx, params = {}) {
2740
2797
  if (alwaysRenderArr.length > 0) {
2741
2798
  for (const index of alwaysRenderArr) {
2742
2799
  if (index < 0 || index >= dataLength) continue;
2743
- const id = (_i = idCache[index]) != null ? _i : getId(state, index);
2800
+ const id = (_j = idCache[index]) != null ? _j : getId(state, index);
2744
2801
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
2745
2802
  needNewContainersSet.add(index);
2746
2803
  needNewContainers.push(index);
@@ -2778,7 +2835,7 @@ function calculateItemsInView(ctx, params = {}) {
2778
2835
  for (let idx = 0; idx < needNewContainers.length; idx++) {
2779
2836
  const i = needNewContainers[idx];
2780
2837
  const containerIndex = availableContainers[idx];
2781
- const id = (_j = idCache[i]) != null ? _j : getId(state, i);
2838
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
2782
2839
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
2783
2840
  if (oldKey && oldKey !== id) {
2784
2841
  containerItemKeys.delete(oldKey);
@@ -2794,13 +2851,10 @@ function calculateItemsInView(ctx, params = {}) {
2794
2851
  const isAlwaysRender = alwaysRenderSet.has(i);
2795
2852
  if (isSticky) {
2796
2853
  set$(ctx, containerSticky, true);
2797
- const topPadding = (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
2798
- set$(ctx, `containerStickyOffset${containerIndex}`, topPadding);
2799
2854
  state.stickyContainerPool.add(containerIndex);
2800
2855
  } else {
2801
2856
  if (peek$(ctx, containerSticky)) {
2802
2857
  set$(ctx, containerSticky, false);
2803
- set$(ctx, `containerStickyOffset${containerIndex}`, void 0);
2804
2858
  }
2805
2859
  if (isAlwaysRender) {
2806
2860
  state.stickyContainerPool.add(containerIndex);
@@ -2822,7 +2876,7 @@ function calculateItemsInView(ctx, params = {}) {
2822
2876
  if (alwaysRenderArr.length > 0) {
2823
2877
  for (const index of alwaysRenderArr) {
2824
2878
  if (index < 0 || index >= dataLength) continue;
2825
- const id = (_k = idCache[index]) != null ? _k : getId(state, index);
2879
+ const id = (_l = idCache[index]) != null ? _l : getId(state, index);
2826
2880
  const containerIndex = containerItemKeys.get(id);
2827
2881
  if (containerIndex !== void 0) {
2828
2882
  state.stickyContainerPool.add(containerIndex);
@@ -2851,26 +2905,28 @@ function calculateItemsInView(ctx, params = {}) {
2851
2905
  state.containerItemTypes.delete(i);
2852
2906
  if (state.stickyContainerPool.has(i)) {
2853
2907
  set$(ctx, `containerSticky${i}`, false);
2854
- set$(ctx, `containerStickyOffset${i}`, void 0);
2855
2908
  state.stickyContainerPool.delete(i);
2856
2909
  }
2857
2910
  set$(ctx, `containerItemKey${i}`, void 0);
2858
2911
  set$(ctx, `containerItemData${i}`, void 0);
2859
2912
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
2860
2913
  set$(ctx, `containerColumn${i}`, -1);
2914
+ set$(ctx, `containerSpan${i}`, 1);
2861
2915
  } else {
2862
2916
  const itemIndex = indexByKey.get(itemKey);
2863
2917
  const item = data[itemIndex];
2864
2918
  if (item !== void 0) {
2865
- const id = (_l = idCache[itemIndex]) != null ? _l : getId(state, itemIndex);
2919
+ const id = (_m = idCache[itemIndex]) != null ? _m : getId(state, itemIndex);
2866
2920
  const positionValue = positions.get(id);
2867
2921
  if (positionValue === void 0) {
2868
2922
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
2869
2923
  } else {
2870
2924
  const position = (positionValue || 0) - scrollAdjustPending;
2871
2925
  const column = columns.get(id) || 1;
2926
+ const span = columnSpans.get(id) || 1;
2872
2927
  const prevPos = peek$(ctx, `containerPosition${i}`);
2873
2928
  const prevColumn = peek$(ctx, `containerColumn${i}`);
2929
+ const prevSpan = peek$(ctx, `containerSpan${i}`);
2874
2930
  const prevData = peek$(ctx, `containerItemData${i}`);
2875
2931
  if (position > POSITION_OUT_OF_VIEW && position !== prevPos) {
2876
2932
  set$(ctx, `containerPosition${i}`, position);
@@ -2879,6 +2935,9 @@ function calculateItemsInView(ctx, params = {}) {
2879
2935
  if (column >= 0 && column !== prevColumn) {
2880
2936
  set$(ctx, `containerColumn${i}`, column);
2881
2937
  }
2938
+ if (span !== prevSpan) {
2939
+ set$(ctx, `containerSpan${i}`, span);
2940
+ }
2882
2941
  if (prevData !== item && (itemsAreEqual ? !itemsAreEqual(prevData, item, itemIndex, data) : true)) {
2883
2942
  set$(ctx, `containerItemData${i}`, item);
2884
2943
  }
@@ -3116,6 +3175,7 @@ function doInitialAllocateContainers(ctx) {
3116
3175
  for (let i = 0; i < numContainers; i++) {
3117
3176
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
3118
3177
  set$(ctx, `containerColumn${i}`, -1);
3178
+ set$(ctx, `containerSpan${i}`, 1);
3119
3179
  }
3120
3180
  set$(ctx, "numContainers", numContainers);
3121
3181
  set$(ctx, "numContainersPooled", numContainers * state.props.initialContainerPoolRatio);
@@ -3752,6 +3812,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3752
3812
  maintainScrollAtEndThreshold = 0.1,
3753
3813
  maintainVisibleContentPosition: maintainVisibleContentPositionProp,
3754
3814
  numColumns: numColumnsProp = 1,
3815
+ overrideItemLayout,
3755
3816
  onEndReached,
3756
3817
  onEndReachedThreshold = 0.5,
3757
3818
  onItemSizeChanged,
@@ -3781,7 +3842,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3781
3842
  viewabilityConfig,
3782
3843
  viewabilityConfigCallbackPairs,
3783
3844
  waitForInitialLayout = true,
3784
- stickyHeaderConfig,
3785
3845
  ...rest
3786
3846
  } = props;
3787
3847
  const animatedPropsInternal = props.animatedPropsInternal;
@@ -3840,6 +3900,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3840
3900
  );
3841
3901
  }
3842
3902
  const refState = React3.useRef();
3903
+ const hasOverrideItemLayout = !!overrideItemLayout;
3904
+ const prevHasOverrideItemLayout = React3.useRef(hasOverrideItemLayout);
3843
3905
  if (!refState.current) {
3844
3906
  if (!ctx.state) {
3845
3907
  const initialScrollLength = (estimatedListSize != null ? estimatedListSize : { height: 0, width: 0 } )[horizontal ? "width" : "height"];
@@ -3847,6 +3909,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3847
3909
  activeStickyIndex: -1,
3848
3910
  averageSizes: {},
3849
3911
  columns: /* @__PURE__ */ new Map(),
3912
+ columnSpans: /* @__PURE__ */ new Map(),
3850
3913
  containerItemKeys: /* @__PURE__ */ new Map(),
3851
3914
  containerItemTypes: /* @__PURE__ */ new Map(),
3852
3915
  contentInsetOverride: void 0,
@@ -3953,6 +4016,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3953
4016
  onStartReached,
3954
4017
  onStartReachedThreshold,
3955
4018
  onStickyHeaderChange,
4019
+ overrideItemLayout,
3956
4020
  recycleItems: !!recycleItems,
3957
4021
  renderItem,
3958
4022
  scrollBuffer,
@@ -3971,14 +4035,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3971
4035
  (_, i) => getId(state, dataProp.length - 1 - i)
3972
4036
  );
3973
4037
  }, [dataProp, dataVersion, numColumnsProp]);
3974
- const initializeStateVars = () => {
4038
+ const initializeStateVars = (shouldAdjustPadding) => {
3975
4039
  set$(ctx, "lastItemKeys", memoizedLastItemKeys);
3976
4040
  set$(ctx, "numColumns", numColumnsProp);
3977
4041
  const prevPaddingTop = peek$(ctx, "stylePaddingTop");
3978
4042
  setPaddingTop(ctx, { stylePaddingTop: stylePaddingTopState });
3979
4043
  refState.current.props.stylePaddingBottom = stylePaddingBottomState;
3980
4044
  let paddingDiff = stylePaddingTopState - prevPaddingTop;
3981
- if (maintainVisibleContentPositionConfig.size && paddingDiff && prevPaddingTop !== void 0 && Platform.OS === "ios") {
4045
+ if (shouldAdjustPadding && maintainVisibleContentPositionConfig.size && paddingDiff && prevPaddingTop !== void 0 && Platform.OS === "ios") {
3982
4046
  if (state.scroll < 0) {
3983
4047
  paddingDiff += state.scroll;
3984
4048
  }
@@ -3986,7 +4050,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3986
4050
  }
3987
4051
  };
3988
4052
  if (isFirstLocal) {
3989
- initializeStateVars();
4053
+ initializeStateVars(false);
3990
4054
  updateItemPositions(
3991
4055
  ctx,
3992
4056
  /*dataChanged*/
@@ -4085,15 +4149,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4085
4149
  state.isFirst = false;
4086
4150
  }, [dataProp, dataVersion, numColumnsProp]);
4087
4151
  React3.useLayoutEffect(() => {
4152
+ var _a4;
4088
4153
  set$(ctx, "extraData", extraData);
4089
- }, [extraData]);
4090
- React3.useLayoutEffect(initializeStateVars, [
4091
- dataVersion,
4092
- memoizedLastItemKeys.join(","),
4093
- numColumnsProp,
4094
- stylePaddingBottomState,
4095
- stylePaddingTopState
4096
- ]);
4154
+ const didToggleOverride = prevHasOverrideItemLayout.current !== hasOverrideItemLayout;
4155
+ prevHasOverrideItemLayout.current = hasOverrideItemLayout;
4156
+ if ((hasOverrideItemLayout || didToggleOverride) && numColumnsProp > 1) {
4157
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { forceFullItemPositions: true });
4158
+ }
4159
+ }, [extraData, hasOverrideItemLayout, numColumnsProp]);
4160
+ React3.useLayoutEffect(
4161
+ () => initializeStateVars(true),
4162
+ [dataVersion, memoizedLastItemKeys.join(","), numColumnsProp, stylePaddingBottomState, stylePaddingTopState]
4163
+ );
4097
4164
  React3.useEffect(() => {
4098
4165
  if (!onMetricsChange) {
4099
4166
  return;
@@ -4177,7 +4244,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4177
4244
  scrollAdjustHandler: (_d = refState.current) == null ? void 0 : _d.scrollAdjustHandler,
4178
4245
  scrollEventThrottle: 0,
4179
4246
  snapToIndices,
4180
- stickyHeaderConfig,
4181
4247
  stickyHeaderIndices,
4182
4248
  style,
4183
4249
  updateItemSize: fns.updateItemSize,