@legendapp/list 3.3.7 → 3.3.9

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/.DS_Store ADDED
Binary file
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 3.3.9
2
+
3
+ - Fix: Rows after an item that changes size stay in the correct positions while scrolling
4
+
5
+ ## 3.3.8
6
+
7
+ - Feat: Add `experimental_hideItemsUntilMeasured` to make items never display at provisional layouts and wait for correct sizing, at the cost of an extra render per recycled row. This fixes cases where slow item renders could display with gaps/overlaps when scrolling up quickly on slow phones.
8
+
1
9
  ## 3.3.7
2
10
 
3
11
  - Fix: Recycled rows no longer briefly show content from the wrong item when data is prepended.
package/animated.d.ts CHANGED
@@ -8,7 +8,7 @@ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
8
8
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
9
9
  }
10
10
 
11
- type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
11
+ type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerLayoutReady${number}` | `containerPosition${number}` | `containerSticky${number}`;
12
12
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
13
13
  type ListenerTypeValueMap = {
14
14
  activeStickyIndex: number;
@@ -47,6 +47,8 @@ type ListenerTypeValueMap = {
47
47
  adaptiveRender: "normal" | "light";
48
48
  } & {
49
49
  [K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
50
+ } & {
51
+ [K in ListenerType as K extends `containerLayoutReady${number}` ? K : never]: boolean;
50
52
  } & {
51
53
  [K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
52
54
  } & {
@@ -348,6 +350,20 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
348
350
  * Configures the adaptive render signal. Items can use this to render a lighter version while scrolling quickly.
349
351
  */
350
352
  experimental_adaptiveRender?: AdaptiveRenderConfig;
353
+ /**
354
+ * Keeps a newly recycled item laid out but invisible until its size has been measured, so it is never painted at
355
+ * the estimated position it was recycled to. Only applies on the new architecture.
356
+ *
357
+ * This trades a possible flash at the wrong position for an extra render and native commit per recycled row, so it
358
+ * is worth enabling only for lists with dynamically sized items whose estimates are visibly wrong. Items with a
359
+ * size from `getFixedItemSize`, or with an already measured size, are exact and render immediately either way.
360
+ *
361
+ * Sticky headers are intentionally excluded. They are painted from a scroll-driven interpolation rather than the
362
+ * committed position, so hiding them would blink a header out mid-scroll without preventing a flash.
363
+ *
364
+ * @default false
365
+ */
366
+ experimental_hideItemsUntilMeasured?: boolean;
351
367
  /**
352
368
  * Function to call when the user pulls to refresh.
353
369
  */
@@ -9,7 +9,7 @@ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
9
9
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
10
10
  }
11
11
 
12
- type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
12
+ type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerLayoutReady${number}` | `containerPosition${number}` | `containerSticky${number}`;
13
13
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
14
14
  type ListenerTypeValueMap = {
15
15
  activeStickyIndex: number;
@@ -48,6 +48,8 @@ type ListenerTypeValueMap = {
48
48
  adaptiveRender: "normal" | "light";
49
49
  } & {
50
50
  [K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
51
+ } & {
52
+ [K in ListenerType as K extends `containerLayoutReady${number}` ? K : never]: boolean;
51
53
  } & {
52
54
  [K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
53
55
  } & {
package/keyboard.d.ts CHANGED
@@ -10,7 +10,7 @@ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
10
10
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
11
11
  }
12
12
 
13
- type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
13
+ type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerLayoutReady${number}` | `containerPosition${number}` | `containerSticky${number}`;
14
14
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
15
15
  type ListenerTypeValueMap = {
16
16
  activeStickyIndex: number;
@@ -49,6 +49,8 @@ type ListenerTypeValueMap = {
49
49
  adaptiveRender: "normal" | "light";
50
50
  } & {
51
51
  [K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
52
+ } & {
53
+ [K in ListenerType as K extends `containerLayoutReady${number}` ? K : never]: boolean;
52
54
  } & {
53
55
  [K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
54
56
  } & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "3.3.7",
3
+ "version": "3.3.9",
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,
package/react-native.d.ts CHANGED
@@ -8,7 +8,7 @@ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
8
8
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
9
9
  }
10
10
 
11
- type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
11
+ type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerLayoutReady${number}` | `containerPosition${number}` | `containerSticky${number}`;
12
12
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
13
13
  type ListenerTypeValueMap = {
14
14
  activeStickyIndex: number;
@@ -47,6 +47,8 @@ type ListenerTypeValueMap = {
47
47
  adaptiveRender: "normal" | "light";
48
48
  } & {
49
49
  [K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
50
+ } & {
51
+ [K in ListenerType as K extends `containerLayoutReady${number}` ? K : never]: boolean;
50
52
  } & {
51
53
  [K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
52
54
  } & {
@@ -348,6 +350,20 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
348
350
  * Configures the adaptive render signal. Items can use this to render a lighter version while scrolling quickly.
349
351
  */
350
352
  experimental_adaptiveRender?: AdaptiveRenderConfig;
353
+ /**
354
+ * Keeps a newly recycled item laid out but invisible until its size has been measured, so it is never painted at
355
+ * the estimated position it was recycled to. Only applies on the new architecture.
356
+ *
357
+ * This trades a possible flash at the wrong position for an extra render and native commit per recycled row, so it
358
+ * is worth enabling only for lists with dynamically sized items whose estimates are visibly wrong. Items with a
359
+ * size from `getFixedItemSize`, or with an already measured size, are exact and render immediately either way.
360
+ *
361
+ * Sticky headers are intentionally excluded. They are painted from a scroll-driven interpolation rather than the
362
+ * committed position, so hiding them would blink a header out mid-scroll without preventing a flash.
363
+ *
364
+ * @default false
365
+ */
366
+ experimental_hideItemsUntilMeasured?: boolean;
351
367
  /**
352
368
  * Function to call when the user pulls to refresh.
353
369
  */
package/react-native.js CHANGED
@@ -4391,7 +4391,9 @@ function calculateItemsInView(ctx, params = {}) {
4391
4391
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4392
4392
  updateItemPositions(ctx, dataChanged, {
4393
4393
  doMVCP,
4394
- forceFullUpdate: !!forceFullItemPositions,
4394
+ // A changed size shifts every following position. The scrolling early-exit
4395
+ // would leave the untouched suffix in the old coordinate space.
4396
+ forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4395
4397
  optimizeForVisibleWindow,
4396
4398
  scrollBottomBuffered,
4397
4399
  scrollVelocity: speed,
@@ -4596,15 +4598,19 @@ function calculateItemsInView(ctx, params = {}) {
4596
4598
  if (oldKey && oldKey !== id) {
4597
4599
  containerItemKeys.delete(oldKey);
4598
4600
  }
4601
+ state.containerItemMetadata.set(
4602
+ containerIndex,
4603
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4604
+ );
4599
4605
  if (oldKey !== id) {
4600
4606
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4601
4607
  changedContainerIds.add(containerIndex);
4602
4608
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4609
+ if (state.props.hideItemsUntilMeasured) {
4610
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4611
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4612
+ }
4603
4613
  }
4604
- state.containerItemMetadata.set(
4605
- containerIndex,
4606
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4607
- );
4608
4614
  set$(ctx, `containerItemKey${containerIndex}`, id);
4609
4615
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4610
4616
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -5036,24 +5042,39 @@ function resolveSkippedAnchorReset(ctx, itemKey) {
5036
5042
  state.userScrollAnchorReset = void 0;
5037
5043
  }
5038
5044
  }
5045
+ function revealMeasuredGeneration(ctx, containerId, itemKey, generation) {
5046
+ var _a3;
5047
+ if (!ctx.state.props.hideItemsUntilMeasured) {
5048
+ return;
5049
+ }
5050
+ const isCurrentGeneration = ((_a3 = ctx.state.containerItemGenerations[containerId]) != null ? _a3 : 0) === generation;
5051
+ const isCurrentAssignment = peek$(ctx, `containerItemKey${containerId}`) === itemKey;
5052
+ if (isCurrentGeneration && isCurrentAssignment) {
5053
+ set$(ctx, `containerLayoutReady${containerId}`, true);
5054
+ }
5055
+ }
5039
5056
  function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5040
- var _a3, _b, _c;
5057
+ var _a3, _b;
5041
5058
  const state = ctx.state;
5042
5059
  const measurements = [];
5060
+ const measuredGenerations = state.props.hideItemsUntilMeasured ? [] : void 0;
5043
5061
  let isCollectingSynchronousMeasurements = true;
5044
5062
  const containerIds = targetContainerIds != null ? targetContainerIds : ctx.viewRefs.keys();
5045
5063
  for (const containerId of containerIds) {
5046
- const viewRef = ctx.viewRefs.get(containerId);
5064
+ const view = (_a3 = ctx.viewRefs.get(containerId)) == null ? void 0 : _a3.current;
5047
5065
  const itemKey = peek$(ctx, `containerItemKey${containerId}`);
5048
5066
  if (itemKey !== void 0) {
5049
- const generation = ((_a3 = state.containerItemGenerations[containerId]) != null ? _a3 : 0) + 1;
5067
+ const generation = ((_b = state.containerItemGenerations[containerId]) != null ? _b : 0) + 1;
5050
5068
  state.containerItemGenerations[containerId] = generation;
5051
5069
  const fixedItemSize = resolveFixedItemSize(ctx, containerId, itemKey);
5052
5070
  const canSkipMeasurement = !state.needsOtherAxisSize && fixedItemSize !== void 0 && state.sizesKnown.get(itemKey) === fixedItemSize + ctx.scrollAxisGap;
5053
5071
  if (canSkipMeasurement) {
5054
5072
  resolveSkippedAnchorReset(ctx, itemKey);
5055
- } else if (viewRef) {
5056
- (_c = (_b = viewRef.current) == null ? void 0 : _b.measure) == null ? void 0 : _c.call(_b, (_x, _y, width, height) => {
5073
+ if (measuredGenerations) {
5074
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5075
+ }
5076
+ } else if (view == null ? void 0 : view.measure) {
5077
+ view.measure((_x, _y, width, height) => {
5057
5078
  var _a4;
5058
5079
  const isCurrentGeneration = ((_a4 = ctx.state.containerItemGenerations[containerId]) != null ? _a4 : 0) === generation;
5059
5080
  if (isCurrentGeneration) {
@@ -5064,17 +5085,28 @@ function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5064
5085
  };
5065
5086
  if (isCollectingSynchronousMeasurements) {
5066
5087
  measurements.push(measurement);
5088
+ measuredGenerations == null ? void 0 : measuredGenerations.push({ containerId, generation, itemKey });
5067
5089
  } else {
5068
5090
  updateItemSizes(ctx, measurement);
5091
+ if (measuredGenerations) {
5092
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5093
+ }
5069
5094
  }
5070
5095
  }
5071
5096
  });
5097
+ } else if (measuredGenerations) {
5098
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5072
5099
  }
5073
5100
  }
5074
5101
  }
5075
5102
  isCollectingSynchronousMeasurements = false;
5076
5103
  if (measurements.length > 0) {
5077
5104
  updateItemSizesBatch(ctx, measurements);
5105
+ if (measuredGenerations) {
5106
+ for (const { containerId, generation, itemKey } of measuredGenerations) {
5107
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5108
+ }
5109
+ }
5078
5110
  }
5079
5111
  }
5080
5112
  var typedForwardRef = React2__namespace.forwardRef;
@@ -5162,8 +5194,20 @@ var PositionViewState = typedMemo(function PositionViewState2({
5162
5194
  refView,
5163
5195
  ...rest
5164
5196
  }) {
5165
- const [position = POSITION_OUT_OF_VIEW, _itemKey] = useArr$([`containerPosition${id}`, `containerItemKey${id}`]);
5166
- return /* @__PURE__ */ React2__namespace.createElement(ReactNative.View, { ref: refView, style: [style, horizontal ? { left: position } : { top: position }], ...rest });
5197
+ const [position = POSITION_OUT_OF_VIEW, _itemKey, layoutReady = true] = useArr$([
5198
+ `containerPosition${id}`,
5199
+ `containerItemKey${id}`,
5200
+ `containerLayoutReady${id}`
5201
+ ]);
5202
+ return /* @__PURE__ */ React2__namespace.createElement(
5203
+ ReactNative.View,
5204
+ {
5205
+ ref: refView,
5206
+ style: [style, horizontal ? { left: position } : { top: position }, !layoutReady && { opacity: 0 }],
5207
+ ...rest,
5208
+ pointerEvents: layoutReady ? void 0 : "none"
5209
+ }
5210
+ );
5167
5211
  });
5168
5212
  var PositionViewAnimated = typedMemo(function PositionViewAnimated2({
5169
5213
  id,
@@ -6291,6 +6335,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6291
6335
  return false;
6292
6336
  }
6293
6337
 
6338
+ // src/core/containerLayoutReady.ts
6339
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6340
+ function resetContainerLayoutReady(ctx) {
6341
+ for (const signalName of ctx.values.keys()) {
6342
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6343
+ set$(ctx, signalName, void 0);
6344
+ }
6345
+ }
6346
+ }
6347
+
6294
6348
  // src/core/doInitialAllocateContainers.ts
6295
6349
  function doInitialAllocateContainers(ctx) {
6296
6350
  var _a3, _b;
@@ -7197,6 +7251,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7197
7251
  initialScrollIndex: initialScrollIndexProp,
7198
7252
  initialScrollOffset: initialScrollOffsetProp,
7199
7253
  experimental_adaptiveRender,
7254
+ experimental_hideItemsUntilMeasured,
7200
7255
  itemsAreEqual,
7201
7256
  keyExtractor: keyExtractorProp,
7202
7257
  ListEmptyComponent,
@@ -7417,6 +7472,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7417
7472
  const state = refState.current;
7418
7473
  const isFirstLocal = state.isFirst;
7419
7474
  const previousAdaptiveRender = state.props.adaptiveRender;
7475
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
7420
7476
  const previousNumColumnsProp = state.props.numColumns;
7421
7477
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
7422
7478
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -7462,6 +7518,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7462
7518
  estimatedItemSize,
7463
7519
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
7464
7520
  getItemType: useWrapIfItem(getItemType),
7521
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
7465
7522
  horizontal: !!horizontal,
7466
7523
  itemsAreEqual,
7467
7524
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -7499,6 +7556,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7499
7556
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
7500
7557
  resetAdaptiveRender(ctx);
7501
7558
  }
7559
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
7560
+ resetContainerLayoutReady(ctx);
7561
+ }
7502
7562
  const memoizedLastItemKeys = React2.useMemo(() => {
7503
7563
  if (!dataProp.length) return [];
7504
7564
  return Array.from(
package/react-native.mjs CHANGED
@@ -4370,7 +4370,9 @@ function calculateItemsInView(ctx, params = {}) {
4370
4370
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4371
4371
  updateItemPositions(ctx, dataChanged, {
4372
4372
  doMVCP,
4373
- forceFullUpdate: !!forceFullItemPositions,
4373
+ // A changed size shifts every following position. The scrolling early-exit
4374
+ // would leave the untouched suffix in the old coordinate space.
4375
+ forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4374
4376
  optimizeForVisibleWindow,
4375
4377
  scrollBottomBuffered,
4376
4378
  scrollVelocity: speed,
@@ -4575,15 +4577,19 @@ function calculateItemsInView(ctx, params = {}) {
4575
4577
  if (oldKey && oldKey !== id) {
4576
4578
  containerItemKeys.delete(oldKey);
4577
4579
  }
4580
+ state.containerItemMetadata.set(
4581
+ containerIndex,
4582
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4583
+ );
4578
4584
  if (oldKey !== id) {
4579
4585
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4580
4586
  changedContainerIds.add(containerIndex);
4581
4587
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4588
+ if (state.props.hideItemsUntilMeasured) {
4589
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4590
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4591
+ }
4582
4592
  }
4583
- state.containerItemMetadata.set(
4584
- containerIndex,
4585
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4586
- );
4587
4593
  set$(ctx, `containerItemKey${containerIndex}`, id);
4588
4594
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4589
4595
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -5015,24 +5021,39 @@ function resolveSkippedAnchorReset(ctx, itemKey) {
5015
5021
  state.userScrollAnchorReset = void 0;
5016
5022
  }
5017
5023
  }
5024
+ function revealMeasuredGeneration(ctx, containerId, itemKey, generation) {
5025
+ var _a3;
5026
+ if (!ctx.state.props.hideItemsUntilMeasured) {
5027
+ return;
5028
+ }
5029
+ const isCurrentGeneration = ((_a3 = ctx.state.containerItemGenerations[containerId]) != null ? _a3 : 0) === generation;
5030
+ const isCurrentAssignment = peek$(ctx, `containerItemKey${containerId}`) === itemKey;
5031
+ if (isCurrentGeneration && isCurrentAssignment) {
5032
+ set$(ctx, `containerLayoutReady${containerId}`, true);
5033
+ }
5034
+ }
5018
5035
  function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5019
- var _a3, _b, _c;
5036
+ var _a3, _b;
5020
5037
  const state = ctx.state;
5021
5038
  const measurements = [];
5039
+ const measuredGenerations = state.props.hideItemsUntilMeasured ? [] : void 0;
5022
5040
  let isCollectingSynchronousMeasurements = true;
5023
5041
  const containerIds = targetContainerIds != null ? targetContainerIds : ctx.viewRefs.keys();
5024
5042
  for (const containerId of containerIds) {
5025
- const viewRef = ctx.viewRefs.get(containerId);
5043
+ const view = (_a3 = ctx.viewRefs.get(containerId)) == null ? void 0 : _a3.current;
5026
5044
  const itemKey = peek$(ctx, `containerItemKey${containerId}`);
5027
5045
  if (itemKey !== void 0) {
5028
- const generation = ((_a3 = state.containerItemGenerations[containerId]) != null ? _a3 : 0) + 1;
5046
+ const generation = ((_b = state.containerItemGenerations[containerId]) != null ? _b : 0) + 1;
5029
5047
  state.containerItemGenerations[containerId] = generation;
5030
5048
  const fixedItemSize = resolveFixedItemSize(ctx, containerId, itemKey);
5031
5049
  const canSkipMeasurement = !state.needsOtherAxisSize && fixedItemSize !== void 0 && state.sizesKnown.get(itemKey) === fixedItemSize + ctx.scrollAxisGap;
5032
5050
  if (canSkipMeasurement) {
5033
5051
  resolveSkippedAnchorReset(ctx, itemKey);
5034
- } else if (viewRef) {
5035
- (_c = (_b = viewRef.current) == null ? void 0 : _b.measure) == null ? void 0 : _c.call(_b, (_x, _y, width, height) => {
5052
+ if (measuredGenerations) {
5053
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5054
+ }
5055
+ } else if (view == null ? void 0 : view.measure) {
5056
+ view.measure((_x, _y, width, height) => {
5036
5057
  var _a4;
5037
5058
  const isCurrentGeneration = ((_a4 = ctx.state.containerItemGenerations[containerId]) != null ? _a4 : 0) === generation;
5038
5059
  if (isCurrentGeneration) {
@@ -5043,17 +5064,28 @@ function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5043
5064
  };
5044
5065
  if (isCollectingSynchronousMeasurements) {
5045
5066
  measurements.push(measurement);
5067
+ measuredGenerations == null ? void 0 : measuredGenerations.push({ containerId, generation, itemKey });
5046
5068
  } else {
5047
5069
  updateItemSizes(ctx, measurement);
5070
+ if (measuredGenerations) {
5071
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5072
+ }
5048
5073
  }
5049
5074
  }
5050
5075
  });
5076
+ } else if (measuredGenerations) {
5077
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5051
5078
  }
5052
5079
  }
5053
5080
  }
5054
5081
  isCollectingSynchronousMeasurements = false;
5055
5082
  if (measurements.length > 0) {
5056
5083
  updateItemSizesBatch(ctx, measurements);
5084
+ if (measuredGenerations) {
5085
+ for (const { containerId, generation, itemKey } of measuredGenerations) {
5086
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5087
+ }
5088
+ }
5057
5089
  }
5058
5090
  }
5059
5091
  var typedForwardRef = React2.forwardRef;
@@ -5141,8 +5173,20 @@ var PositionViewState = typedMemo(function PositionViewState2({
5141
5173
  refView,
5142
5174
  ...rest
5143
5175
  }) {
5144
- const [position = POSITION_OUT_OF_VIEW, _itemKey] = useArr$([`containerPosition${id}`, `containerItemKey${id}`]);
5145
- return /* @__PURE__ */ React2.createElement(View$1, { ref: refView, style: [style, horizontal ? { left: position } : { top: position }], ...rest });
5176
+ const [position = POSITION_OUT_OF_VIEW, _itemKey, layoutReady = true] = useArr$([
5177
+ `containerPosition${id}`,
5178
+ `containerItemKey${id}`,
5179
+ `containerLayoutReady${id}`
5180
+ ]);
5181
+ return /* @__PURE__ */ React2.createElement(
5182
+ View$1,
5183
+ {
5184
+ ref: refView,
5185
+ style: [style, horizontal ? { left: position } : { top: position }, !layoutReady && { opacity: 0 }],
5186
+ ...rest,
5187
+ pointerEvents: layoutReady ? void 0 : "none"
5188
+ }
5189
+ );
5146
5190
  });
5147
5191
  var PositionViewAnimated = typedMemo(function PositionViewAnimated2({
5148
5192
  id,
@@ -6270,6 +6314,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6270
6314
  return false;
6271
6315
  }
6272
6316
 
6317
+ // src/core/containerLayoutReady.ts
6318
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6319
+ function resetContainerLayoutReady(ctx) {
6320
+ for (const signalName of ctx.values.keys()) {
6321
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6322
+ set$(ctx, signalName, void 0);
6323
+ }
6324
+ }
6325
+ }
6326
+
6273
6327
  // src/core/doInitialAllocateContainers.ts
6274
6328
  function doInitialAllocateContainers(ctx) {
6275
6329
  var _a3, _b;
@@ -7176,6 +7230,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7176
7230
  initialScrollIndex: initialScrollIndexProp,
7177
7231
  initialScrollOffset: initialScrollOffsetProp,
7178
7232
  experimental_adaptiveRender,
7233
+ experimental_hideItemsUntilMeasured,
7179
7234
  itemsAreEqual,
7180
7235
  keyExtractor: keyExtractorProp,
7181
7236
  ListEmptyComponent,
@@ -7396,6 +7451,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7396
7451
  const state = refState.current;
7397
7452
  const isFirstLocal = state.isFirst;
7398
7453
  const previousAdaptiveRender = state.props.adaptiveRender;
7454
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
7399
7455
  const previousNumColumnsProp = state.props.numColumns;
7400
7456
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
7401
7457
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -7441,6 +7497,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7441
7497
  estimatedItemSize,
7442
7498
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
7443
7499
  getItemType: useWrapIfItem(getItemType),
7500
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
7444
7501
  horizontal: !!horizontal,
7445
7502
  itemsAreEqual,
7446
7503
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -7478,6 +7535,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7478
7535
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
7479
7536
  resetAdaptiveRender(ctx);
7480
7537
  }
7538
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
7539
+ resetContainerLayoutReady(ctx);
7540
+ }
7481
7541
  const memoizedLastItemKeys = useMemo(() => {
7482
7542
  if (!dataProp.length) return [];
7483
7543
  return Array.from(
@@ -31,7 +31,7 @@ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
31
31
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
32
32
  }
33
33
 
34
- type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
34
+ type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerLayoutReady${number}` | `containerPosition${number}` | `containerSticky${number}`;
35
35
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
36
36
  type ListenerTypeValueMap = {
37
37
  activeStickyIndex: number;
@@ -70,6 +70,8 @@ type ListenerTypeValueMap = {
70
70
  adaptiveRender: "normal" | "light";
71
71
  } & {
72
72
  [K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
73
+ } & {
74
+ [K in ListenerType as K extends `containerLayoutReady${number}` ? K : never]: boolean;
73
75
  } & {
74
76
  [K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
75
77
  } & {
@@ -371,6 +373,20 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
371
373
  * Configures the adaptive render signal. Items can use this to render a lighter version while scrolling quickly.
372
374
  */
373
375
  experimental_adaptiveRender?: AdaptiveRenderConfig;
376
+ /**
377
+ * Keeps a newly recycled item laid out but invisible until its size has been measured, so it is never painted at
378
+ * the estimated position it was recycled to. Only applies on the new architecture.
379
+ *
380
+ * This trades a possible flash at the wrong position for an extra render and native commit per recycled row, so it
381
+ * is worth enabling only for lists with dynamically sized items whose estimates are visibly wrong. Items with a
382
+ * size from `getFixedItemSize`, or with an already measured size, are exact and render immediately either way.
383
+ *
384
+ * Sticky headers are intentionally excluded. They are painted from a scroll-driven interpolation rather than the
385
+ * committed position, so hiding them would blink a header out mid-scroll without preventing a flash.
386
+ *
387
+ * @default false
388
+ */
389
+ experimental_hideItemsUntilMeasured?: boolean;
374
390
  /**
375
391
  * Function to call when the user pulls to refresh.
376
392
  */
@@ -4420,7 +4420,9 @@ function calculateItemsInView(ctx, params = {}) {
4420
4420
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4421
4421
  updateItemPositions(ctx, dataChanged, {
4422
4422
  doMVCP,
4423
- forceFullUpdate: !!forceFullItemPositions,
4423
+ // A changed size shifts every following position. The scrolling early-exit
4424
+ // would leave the untouched suffix in the old coordinate space.
4425
+ forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4424
4426
  optimizeForVisibleWindow,
4425
4427
  scrollBottomBuffered,
4426
4428
  scrollVelocity: speed,
@@ -4625,15 +4627,19 @@ function calculateItemsInView(ctx, params = {}) {
4625
4627
  if (oldKey && oldKey !== id) {
4626
4628
  containerItemKeys.delete(oldKey);
4627
4629
  }
4630
+ state.containerItemMetadata.set(
4631
+ containerIndex,
4632
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4633
+ );
4628
4634
  if (oldKey !== id) {
4629
4635
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4630
4636
  changedContainerIds.add(containerIndex);
4631
4637
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4638
+ if (state.props.hideItemsUntilMeasured) {
4639
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4640
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4641
+ }
4632
4642
  }
4633
- state.containerItemMetadata.set(
4634
- containerIndex,
4635
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4636
- );
4637
4643
  set$(ctx, `containerItemKey${containerIndex}`, id);
4638
4644
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4639
4645
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -6950,6 +6956,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6950
6956
  return false;
6951
6957
  }
6952
6958
 
6959
+ // src/core/containerLayoutReady.ts
6960
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6961
+ function resetContainerLayoutReady(ctx) {
6962
+ for (const signalName of ctx.values.keys()) {
6963
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6964
+ set$(ctx, signalName, void 0);
6965
+ }
6966
+ }
6967
+ }
6968
+
6953
6969
  // src/core/doInitialAllocateContainers.ts
6954
6970
  function doInitialAllocateContainers(ctx) {
6955
6971
  var _a3, _b;
@@ -7840,6 +7856,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7840
7856
  initialScrollIndex: initialScrollIndexProp,
7841
7857
  initialScrollOffset: initialScrollOffsetProp,
7842
7858
  experimental_adaptiveRender,
7859
+ experimental_hideItemsUntilMeasured,
7843
7860
  itemsAreEqual,
7844
7861
  keyExtractor: keyExtractorProp,
7845
7862
  ListEmptyComponent,
@@ -8060,6 +8077,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8060
8077
  const state = refState.current;
8061
8078
  const isFirstLocal = state.isFirst;
8062
8079
  const previousAdaptiveRender = state.props.adaptiveRender;
8080
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
8063
8081
  const previousNumColumnsProp = state.props.numColumns;
8064
8082
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
8065
8083
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -8105,6 +8123,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8105
8123
  estimatedItemSize,
8106
8124
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8107
8125
  getItemType: useWrapIfItem(getItemType),
8126
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8108
8127
  horizontal: !!horizontal,
8109
8128
  itemsAreEqual,
8110
8129
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -8142,6 +8161,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8142
8161
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
8143
8162
  resetAdaptiveRender(ctx);
8144
8163
  }
8164
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
8165
+ resetContainerLayoutReady(ctx);
8166
+ }
8145
8167
  const memoizedLastItemKeys = React3.useMemo(() => {
8146
8168
  if (!dataProp.length) return [];
8147
8169
  return Array.from(
@@ -4399,7 +4399,9 @@ function calculateItemsInView(ctx, params = {}) {
4399
4399
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4400
4400
  updateItemPositions(ctx, dataChanged, {
4401
4401
  doMVCP,
4402
- forceFullUpdate: !!forceFullItemPositions,
4402
+ // A changed size shifts every following position. The scrolling early-exit
4403
+ // would leave the untouched suffix in the old coordinate space.
4404
+ forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4403
4405
  optimizeForVisibleWindow,
4404
4406
  scrollBottomBuffered,
4405
4407
  scrollVelocity: speed,
@@ -4604,15 +4606,19 @@ function calculateItemsInView(ctx, params = {}) {
4604
4606
  if (oldKey && oldKey !== id) {
4605
4607
  containerItemKeys.delete(oldKey);
4606
4608
  }
4609
+ state.containerItemMetadata.set(
4610
+ containerIndex,
4611
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4612
+ );
4607
4613
  if (oldKey !== id) {
4608
4614
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4609
4615
  changedContainerIds.add(containerIndex);
4610
4616
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4617
+ if (state.props.hideItemsUntilMeasured) {
4618
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4619
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4620
+ }
4611
4621
  }
4612
- state.containerItemMetadata.set(
4613
- containerIndex,
4614
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4615
- );
4616
4622
  set$(ctx, `containerItemKey${containerIndex}`, id);
4617
4623
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4618
4624
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -6929,6 +6935,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6929
6935
  return false;
6930
6936
  }
6931
6937
 
6938
+ // src/core/containerLayoutReady.ts
6939
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6940
+ function resetContainerLayoutReady(ctx) {
6941
+ for (const signalName of ctx.values.keys()) {
6942
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6943
+ set$(ctx, signalName, void 0);
6944
+ }
6945
+ }
6946
+ }
6947
+
6932
6948
  // src/core/doInitialAllocateContainers.ts
6933
6949
  function doInitialAllocateContainers(ctx) {
6934
6950
  var _a3, _b;
@@ -7819,6 +7835,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7819
7835
  initialScrollIndex: initialScrollIndexProp,
7820
7836
  initialScrollOffset: initialScrollOffsetProp,
7821
7837
  experimental_adaptiveRender,
7838
+ experimental_hideItemsUntilMeasured,
7822
7839
  itemsAreEqual,
7823
7840
  keyExtractor: keyExtractorProp,
7824
7841
  ListEmptyComponent,
@@ -8039,6 +8056,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8039
8056
  const state = refState.current;
8040
8057
  const isFirstLocal = state.isFirst;
8041
8058
  const previousAdaptiveRender = state.props.adaptiveRender;
8059
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
8042
8060
  const previousNumColumnsProp = state.props.numColumns;
8043
8061
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
8044
8062
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -8084,6 +8102,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8084
8102
  estimatedItemSize,
8085
8103
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8086
8104
  getItemType: useWrapIfItem(getItemType),
8105
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8087
8106
  horizontal: !!horizontal,
8088
8107
  itemsAreEqual,
8089
8108
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -8121,6 +8140,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8121
8140
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
8122
8141
  resetAdaptiveRender(ctx);
8123
8142
  }
8143
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
8144
+ resetContainerLayoutReady(ctx);
8145
+ }
8124
8146
  const memoizedLastItemKeys = useMemo(() => {
8125
8147
  if (!dataProp.length) return [];
8126
8148
  return Array.from(
package/react.d.ts CHANGED
@@ -31,7 +31,7 @@ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
31
31
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
32
32
  }
33
33
 
34
- type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
34
+ type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerLayoutReady${number}` | `containerPosition${number}` | `containerSticky${number}`;
35
35
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
36
36
  type ListenerTypeValueMap = {
37
37
  activeStickyIndex: number;
@@ -70,6 +70,8 @@ type ListenerTypeValueMap = {
70
70
  adaptiveRender: "normal" | "light";
71
71
  } & {
72
72
  [K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
73
+ } & {
74
+ [K in ListenerType as K extends `containerLayoutReady${number}` ? K : never]: boolean;
73
75
  } & {
74
76
  [K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
75
77
  } & {
@@ -371,6 +373,20 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
371
373
  * Configures the adaptive render signal. Items can use this to render a lighter version while scrolling quickly.
372
374
  */
373
375
  experimental_adaptiveRender?: AdaptiveRenderConfig;
376
+ /**
377
+ * Keeps a newly recycled item laid out but invisible until its size has been measured, so it is never painted at
378
+ * the estimated position it was recycled to. Only applies on the new architecture.
379
+ *
380
+ * This trades a possible flash at the wrong position for an extra render and native commit per recycled row, so it
381
+ * is worth enabling only for lists with dynamically sized items whose estimates are visibly wrong. Items with a
382
+ * size from `getFixedItemSize`, or with an already measured size, are exact and render immediately either way.
383
+ *
384
+ * Sticky headers are intentionally excluded. They are painted from a scroll-driven interpolation rather than the
385
+ * committed position, so hiding them would blink a header out mid-scroll without preventing a flash.
386
+ *
387
+ * @default false
388
+ */
389
+ experimental_hideItemsUntilMeasured?: boolean;
374
390
  /**
375
391
  * Function to call when the user pulls to refresh.
376
392
  */
package/react.js CHANGED
@@ -4420,7 +4420,9 @@ function calculateItemsInView(ctx, params = {}) {
4420
4420
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4421
4421
  updateItemPositions(ctx, dataChanged, {
4422
4422
  doMVCP,
4423
- forceFullUpdate: !!forceFullItemPositions,
4423
+ // A changed size shifts every following position. The scrolling early-exit
4424
+ // would leave the untouched suffix in the old coordinate space.
4425
+ forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4424
4426
  optimizeForVisibleWindow,
4425
4427
  scrollBottomBuffered,
4426
4428
  scrollVelocity: speed,
@@ -4625,15 +4627,19 @@ function calculateItemsInView(ctx, params = {}) {
4625
4627
  if (oldKey && oldKey !== id) {
4626
4628
  containerItemKeys.delete(oldKey);
4627
4629
  }
4630
+ state.containerItemMetadata.set(
4631
+ containerIndex,
4632
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4633
+ );
4628
4634
  if (oldKey !== id) {
4629
4635
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4630
4636
  changedContainerIds.add(containerIndex);
4631
4637
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4638
+ if (state.props.hideItemsUntilMeasured) {
4639
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4640
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4641
+ }
4632
4642
  }
4633
- state.containerItemMetadata.set(
4634
- containerIndex,
4635
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4636
- );
4637
4643
  set$(ctx, `containerItemKey${containerIndex}`, id);
4638
4644
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4639
4645
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -6950,6 +6956,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6950
6956
  return false;
6951
6957
  }
6952
6958
 
6959
+ // src/core/containerLayoutReady.ts
6960
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6961
+ function resetContainerLayoutReady(ctx) {
6962
+ for (const signalName of ctx.values.keys()) {
6963
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6964
+ set$(ctx, signalName, void 0);
6965
+ }
6966
+ }
6967
+ }
6968
+
6953
6969
  // src/core/doInitialAllocateContainers.ts
6954
6970
  function doInitialAllocateContainers(ctx) {
6955
6971
  var _a3, _b;
@@ -7840,6 +7856,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7840
7856
  initialScrollIndex: initialScrollIndexProp,
7841
7857
  initialScrollOffset: initialScrollOffsetProp,
7842
7858
  experimental_adaptiveRender,
7859
+ experimental_hideItemsUntilMeasured,
7843
7860
  itemsAreEqual,
7844
7861
  keyExtractor: keyExtractorProp,
7845
7862
  ListEmptyComponent,
@@ -8060,6 +8077,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8060
8077
  const state = refState.current;
8061
8078
  const isFirstLocal = state.isFirst;
8062
8079
  const previousAdaptiveRender = state.props.adaptiveRender;
8080
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
8063
8081
  const previousNumColumnsProp = state.props.numColumns;
8064
8082
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
8065
8083
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -8105,6 +8123,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8105
8123
  estimatedItemSize,
8106
8124
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8107
8125
  getItemType: useWrapIfItem(getItemType),
8126
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8108
8127
  horizontal: !!horizontal,
8109
8128
  itemsAreEqual,
8110
8129
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -8142,6 +8161,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8142
8161
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
8143
8162
  resetAdaptiveRender(ctx);
8144
8163
  }
8164
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
8165
+ resetContainerLayoutReady(ctx);
8166
+ }
8145
8167
  const memoizedLastItemKeys = React3.useMemo(() => {
8146
8168
  if (!dataProp.length) return [];
8147
8169
  return Array.from(
package/react.mjs CHANGED
@@ -4399,7 +4399,9 @@ function calculateItemsInView(ctx, params = {}) {
4399
4399
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4400
4400
  updateItemPositions(ctx, dataChanged, {
4401
4401
  doMVCP,
4402
- forceFullUpdate: !!forceFullItemPositions,
4402
+ // A changed size shifts every following position. The scrolling early-exit
4403
+ // would leave the untouched suffix in the old coordinate space.
4404
+ forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4403
4405
  optimizeForVisibleWindow,
4404
4406
  scrollBottomBuffered,
4405
4407
  scrollVelocity: speed,
@@ -4604,15 +4606,19 @@ function calculateItemsInView(ctx, params = {}) {
4604
4606
  if (oldKey && oldKey !== id) {
4605
4607
  containerItemKeys.delete(oldKey);
4606
4608
  }
4609
+ state.containerItemMetadata.set(
4610
+ containerIndex,
4611
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4612
+ );
4607
4613
  if (oldKey !== id) {
4608
4614
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4609
4615
  changedContainerIds.add(containerIndex);
4610
4616
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4617
+ if (state.props.hideItemsUntilMeasured) {
4618
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4619
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4620
+ }
4611
4621
  }
4612
- state.containerItemMetadata.set(
4613
- containerIndex,
4614
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4615
- );
4616
4622
  set$(ctx, `containerItemKey${containerIndex}`, id);
4617
4623
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4618
4624
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -6929,6 +6935,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6929
6935
  return false;
6930
6936
  }
6931
6937
 
6938
+ // src/core/containerLayoutReady.ts
6939
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6940
+ function resetContainerLayoutReady(ctx) {
6941
+ for (const signalName of ctx.values.keys()) {
6942
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6943
+ set$(ctx, signalName, void 0);
6944
+ }
6945
+ }
6946
+ }
6947
+
6932
6948
  // src/core/doInitialAllocateContainers.ts
6933
6949
  function doInitialAllocateContainers(ctx) {
6934
6950
  var _a3, _b;
@@ -7819,6 +7835,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7819
7835
  initialScrollIndex: initialScrollIndexProp,
7820
7836
  initialScrollOffset: initialScrollOffsetProp,
7821
7837
  experimental_adaptiveRender,
7838
+ experimental_hideItemsUntilMeasured,
7822
7839
  itemsAreEqual,
7823
7840
  keyExtractor: keyExtractorProp,
7824
7841
  ListEmptyComponent,
@@ -8039,6 +8056,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8039
8056
  const state = refState.current;
8040
8057
  const isFirstLocal = state.isFirst;
8041
8058
  const previousAdaptiveRender = state.props.adaptiveRender;
8059
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
8042
8060
  const previousNumColumnsProp = state.props.numColumns;
8043
8061
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
8044
8062
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -8084,6 +8102,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8084
8102
  estimatedItemSize,
8085
8103
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8086
8104
  getItemType: useWrapIfItem(getItemType),
8105
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8087
8106
  horizontal: !!horizontal,
8088
8107
  itemsAreEqual,
8089
8108
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -8121,6 +8140,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8121
8140
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
8122
8141
  resetAdaptiveRender(ctx);
8123
8142
  }
8143
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
8144
+ resetContainerLayoutReady(ctx);
8145
+ }
8124
8146
  const memoizedLastItemKeys = useMemo(() => {
8125
8147
  if (!dataProp.length) return [];
8126
8148
  return Array.from(
package/reanimated.d.ts CHANGED
@@ -9,7 +9,7 @@ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
9
9
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
10
10
  }
11
11
 
12
- type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
12
+ type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerLayoutReady${number}` | `containerPosition${number}` | `containerSticky${number}`;
13
13
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
14
14
  type ListenerTypeValueMap = {
15
15
  activeStickyIndex: number;
@@ -48,6 +48,8 @@ type ListenerTypeValueMap = {
48
48
  adaptiveRender: "normal" | "light";
49
49
  } & {
50
50
  [K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
51
+ } & {
52
+ [K in ListenerType as K extends `containerLayoutReady${number}` ? K : never]: boolean;
51
53
  } & {
52
54
  [K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
53
55
  } & {
@@ -349,6 +351,20 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
349
351
  * Configures the adaptive render signal. Items can use this to render a lighter version while scrolling quickly.
350
352
  */
351
353
  experimental_adaptiveRender?: AdaptiveRenderConfig;
354
+ /**
355
+ * Keeps a newly recycled item laid out but invisible until its size has been measured, so it is never painted at
356
+ * the estimated position it was recycled to. Only applies on the new architecture.
357
+ *
358
+ * This trades a possible flash at the wrong position for an extra render and native commit per recycled row, so it
359
+ * is worth enabling only for lists with dynamically sized items whose estimates are visibly wrong. Items with a
360
+ * size from `getFixedItemSize`, or with an already measured size, are exact and render immediately either way.
361
+ *
362
+ * Sticky headers are intentionally excluded. They are painted from a scroll-driven interpolation rather than the
363
+ * committed position, so hiding them would blink a header out mid-scroll without preventing a flash.
364
+ *
365
+ * @default false
366
+ */
367
+ experimental_hideItemsUntilMeasured?: boolean;
352
368
  /**
353
369
  * Function to call when the user pulls to refresh.
354
370
  */
package/reanimated.js CHANGED
@@ -113,7 +113,10 @@ var ReanimatedPositionViewSticky = typedMemo(function ReanimatedPositionViewStic
113
113
  var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent(props) {
114
114
  const ctx = useStateContext();
115
115
  const { id, horizontal, style, refView, children, recycleItems, layoutTransition, ...rest } = props;
116
- const [positionValue = POSITION_OUT_OF_VIEW] = useArr$([`containerPosition${id}`]);
116
+ const [positionValue = POSITION_OUT_OF_VIEW, layoutReady = true] = useArr$([
117
+ `containerPosition${id}`,
118
+ `containerLayoutReady${id}`
119
+ ]);
117
120
  const prevItemKeyRef = React__namespace.useRef(void 0);
118
121
  let shouldSkipTransitionForRecycleReuse = false;
119
122
  if (recycleItems && layoutTransition) {
@@ -127,8 +130,8 @@ var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent(
127
130
  prevItemKeyRef.current = void 0;
128
131
  }
129
132
  const viewStyle = React__namespace.useMemo(
130
- () => [style, horizontal ? { left: positionValue } : { top: positionValue }],
131
- [horizontal, positionValue, style]
133
+ () => [style, horizontal ? { left: positionValue } : { top: positionValue }, !layoutReady && { opacity: 0 }],
134
+ [horizontal, layoutReady, positionValue, style]
132
135
  );
133
136
  return /* @__PURE__ */ React__namespace.createElement(
134
137
  Reanimated__default.default.View,
@@ -136,7 +139,8 @@ var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent(
136
139
  layout: shouldSkipTransitionForRecycleReuse ? void 0 : layoutTransition,
137
140
  ref: refView,
138
141
  style: viewStyle,
139
- ...rest
142
+ ...rest,
143
+ pointerEvents: layoutReady ? void 0 : "none"
140
144
  },
141
145
  children
142
146
  );
package/reanimated.mjs CHANGED
@@ -89,7 +89,10 @@ var ReanimatedPositionViewSticky = typedMemo(function ReanimatedPositionViewStic
89
89
  var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent(props) {
90
90
  const ctx = useStateContext();
91
91
  const { id, horizontal, style, refView, children, recycleItems, layoutTransition, ...rest } = props;
92
- const [positionValue = POSITION_OUT_OF_VIEW] = useArr$([`containerPosition${id}`]);
92
+ const [positionValue = POSITION_OUT_OF_VIEW, layoutReady = true] = useArr$([
93
+ `containerPosition${id}`,
94
+ `containerLayoutReady${id}`
95
+ ]);
93
96
  const prevItemKeyRef = React.useRef(void 0);
94
97
  let shouldSkipTransitionForRecycleReuse = false;
95
98
  if (recycleItems && layoutTransition) {
@@ -103,8 +106,8 @@ var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent(
103
106
  prevItemKeyRef.current = void 0;
104
107
  }
105
108
  const viewStyle = React.useMemo(
106
- () => [style, horizontal ? { left: positionValue } : { top: positionValue }],
107
- [horizontal, positionValue, style]
109
+ () => [style, horizontal ? { left: positionValue } : { top: positionValue }, !layoutReady && { opacity: 0 }],
110
+ [horizontal, layoutReady, positionValue, style]
108
111
  );
109
112
  return /* @__PURE__ */ React.createElement(
110
113
  Reanimated.View,
@@ -112,7 +115,8 @@ var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent(
112
115
  layout: shouldSkipTransitionForRecycleReuse ? void 0 : layoutTransition,
113
116
  ref: refView,
114
117
  style: viewStyle,
115
- ...rest
118
+ ...rest,
119
+ pointerEvents: layoutReady ? void 0 : "none"
116
120
  },
117
121
  children
118
122
  );
package/section-list.d.ts CHANGED
@@ -65,7 +65,7 @@ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
65
65
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
66
66
  }
67
67
 
68
- type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
68
+ type ListenerType = "activeStickyIndex" | "alignItemsAtEndPadding" | "anchoredEndSpaceSize" | "containerLayoutEpoch" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemIndex${number}` | `containerItemKey${number}` | `containerLayoutReady${number}` | `containerPosition${number}` | `containerSticky${number}`;
69
69
  type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "adaptiveRender" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
70
70
  type ListenerTypeValueMap = {
71
71
  activeStickyIndex: number;
@@ -104,6 +104,8 @@ type ListenerTypeValueMap = {
104
104
  adaptiveRender: "normal" | "light";
105
105
  } & {
106
106
  [K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
107
+ } & {
108
+ [K in ListenerType as K extends `containerLayoutReady${number}` ? K : never]: boolean;
107
109
  } & {
108
110
  [K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
109
111
  } & {
@@ -405,6 +407,20 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
405
407
  * Configures the adaptive render signal. Items can use this to render a lighter version while scrolling quickly.
406
408
  */
407
409
  experimental_adaptiveRender?: AdaptiveRenderConfig;
410
+ /**
411
+ * Keeps a newly recycled item laid out but invisible until its size has been measured, so it is never painted at
412
+ * the estimated position it was recycled to. Only applies on the new architecture.
413
+ *
414
+ * This trades a possible flash at the wrong position for an extra render and native commit per recycled row, so it
415
+ * is worth enabling only for lists with dynamically sized items whose estimates are visibly wrong. Items with a
416
+ * size from `getFixedItemSize`, or with an already measured size, are exact and render immediately either way.
417
+ *
418
+ * Sticky headers are intentionally excluded. They are painted from a scroll-driven interpolation rather than the
419
+ * committed position, so hiding them would blink a header out mid-scroll without preventing a flash.
420
+ *
421
+ * @default false
422
+ */
423
+ experimental_hideItemsUntilMeasured?: boolean;
408
424
  /**
409
425
  * Function to call when the user pulls to refresh.
410
426
  */