@legendapp/list 3.3.7 → 3.3.8

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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 3.3.8
2
+
3
+ - 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.
4
+
1
5
  ## 3.3.7
2
6
 
3
7
  - 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.8",
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
@@ -4596,15 +4596,19 @@ function calculateItemsInView(ctx, params = {}) {
4596
4596
  if (oldKey && oldKey !== id) {
4597
4597
  containerItemKeys.delete(oldKey);
4598
4598
  }
4599
+ state.containerItemMetadata.set(
4600
+ containerIndex,
4601
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4602
+ );
4599
4603
  if (oldKey !== id) {
4600
4604
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4601
4605
  changedContainerIds.add(containerIndex);
4602
4606
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4607
+ if (state.props.hideItemsUntilMeasured) {
4608
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4609
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4610
+ }
4603
4611
  }
4604
- state.containerItemMetadata.set(
4605
- containerIndex,
4606
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4607
- );
4608
4612
  set$(ctx, `containerItemKey${containerIndex}`, id);
4609
4613
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4610
4614
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -5036,24 +5040,39 @@ function resolveSkippedAnchorReset(ctx, itemKey) {
5036
5040
  state.userScrollAnchorReset = void 0;
5037
5041
  }
5038
5042
  }
5043
+ function revealMeasuredGeneration(ctx, containerId, itemKey, generation) {
5044
+ var _a3;
5045
+ if (!ctx.state.props.hideItemsUntilMeasured) {
5046
+ return;
5047
+ }
5048
+ const isCurrentGeneration = ((_a3 = ctx.state.containerItemGenerations[containerId]) != null ? _a3 : 0) === generation;
5049
+ const isCurrentAssignment = peek$(ctx, `containerItemKey${containerId}`) === itemKey;
5050
+ if (isCurrentGeneration && isCurrentAssignment) {
5051
+ set$(ctx, `containerLayoutReady${containerId}`, true);
5052
+ }
5053
+ }
5039
5054
  function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5040
- var _a3, _b, _c;
5055
+ var _a3, _b;
5041
5056
  const state = ctx.state;
5042
5057
  const measurements = [];
5058
+ const measuredGenerations = state.props.hideItemsUntilMeasured ? [] : void 0;
5043
5059
  let isCollectingSynchronousMeasurements = true;
5044
5060
  const containerIds = targetContainerIds != null ? targetContainerIds : ctx.viewRefs.keys();
5045
5061
  for (const containerId of containerIds) {
5046
- const viewRef = ctx.viewRefs.get(containerId);
5062
+ const view = (_a3 = ctx.viewRefs.get(containerId)) == null ? void 0 : _a3.current;
5047
5063
  const itemKey = peek$(ctx, `containerItemKey${containerId}`);
5048
5064
  if (itemKey !== void 0) {
5049
- const generation = ((_a3 = state.containerItemGenerations[containerId]) != null ? _a3 : 0) + 1;
5065
+ const generation = ((_b = state.containerItemGenerations[containerId]) != null ? _b : 0) + 1;
5050
5066
  state.containerItemGenerations[containerId] = generation;
5051
5067
  const fixedItemSize = resolveFixedItemSize(ctx, containerId, itemKey);
5052
5068
  const canSkipMeasurement = !state.needsOtherAxisSize && fixedItemSize !== void 0 && state.sizesKnown.get(itemKey) === fixedItemSize + ctx.scrollAxisGap;
5053
5069
  if (canSkipMeasurement) {
5054
5070
  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) => {
5071
+ if (measuredGenerations) {
5072
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5073
+ }
5074
+ } else if (view == null ? void 0 : view.measure) {
5075
+ view.measure((_x, _y, width, height) => {
5057
5076
  var _a4;
5058
5077
  const isCurrentGeneration = ((_a4 = ctx.state.containerItemGenerations[containerId]) != null ? _a4 : 0) === generation;
5059
5078
  if (isCurrentGeneration) {
@@ -5064,17 +5083,28 @@ function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5064
5083
  };
5065
5084
  if (isCollectingSynchronousMeasurements) {
5066
5085
  measurements.push(measurement);
5086
+ measuredGenerations == null ? void 0 : measuredGenerations.push({ containerId, generation, itemKey });
5067
5087
  } else {
5068
5088
  updateItemSizes(ctx, measurement);
5089
+ if (measuredGenerations) {
5090
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5091
+ }
5069
5092
  }
5070
5093
  }
5071
5094
  });
5095
+ } else if (measuredGenerations) {
5096
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5072
5097
  }
5073
5098
  }
5074
5099
  }
5075
5100
  isCollectingSynchronousMeasurements = false;
5076
5101
  if (measurements.length > 0) {
5077
5102
  updateItemSizesBatch(ctx, measurements);
5103
+ if (measuredGenerations) {
5104
+ for (const { containerId, generation, itemKey } of measuredGenerations) {
5105
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5106
+ }
5107
+ }
5078
5108
  }
5079
5109
  }
5080
5110
  var typedForwardRef = React2__namespace.forwardRef;
@@ -5162,8 +5192,20 @@ var PositionViewState = typedMemo(function PositionViewState2({
5162
5192
  refView,
5163
5193
  ...rest
5164
5194
  }) {
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 });
5195
+ const [position = POSITION_OUT_OF_VIEW, _itemKey, layoutReady = true] = useArr$([
5196
+ `containerPosition${id}`,
5197
+ `containerItemKey${id}`,
5198
+ `containerLayoutReady${id}`
5199
+ ]);
5200
+ return /* @__PURE__ */ React2__namespace.createElement(
5201
+ ReactNative.View,
5202
+ {
5203
+ ref: refView,
5204
+ style: [style, horizontal ? { left: position } : { top: position }, !layoutReady && { opacity: 0 }],
5205
+ ...rest,
5206
+ pointerEvents: layoutReady ? void 0 : "none"
5207
+ }
5208
+ );
5167
5209
  });
5168
5210
  var PositionViewAnimated = typedMemo(function PositionViewAnimated2({
5169
5211
  id,
@@ -6291,6 +6333,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6291
6333
  return false;
6292
6334
  }
6293
6335
 
6336
+ // src/core/containerLayoutReady.ts
6337
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6338
+ function resetContainerLayoutReady(ctx) {
6339
+ for (const signalName of ctx.values.keys()) {
6340
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6341
+ set$(ctx, signalName, void 0);
6342
+ }
6343
+ }
6344
+ }
6345
+
6294
6346
  // src/core/doInitialAllocateContainers.ts
6295
6347
  function doInitialAllocateContainers(ctx) {
6296
6348
  var _a3, _b;
@@ -7197,6 +7249,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7197
7249
  initialScrollIndex: initialScrollIndexProp,
7198
7250
  initialScrollOffset: initialScrollOffsetProp,
7199
7251
  experimental_adaptiveRender,
7252
+ experimental_hideItemsUntilMeasured,
7200
7253
  itemsAreEqual,
7201
7254
  keyExtractor: keyExtractorProp,
7202
7255
  ListEmptyComponent,
@@ -7417,6 +7470,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7417
7470
  const state = refState.current;
7418
7471
  const isFirstLocal = state.isFirst;
7419
7472
  const previousAdaptiveRender = state.props.adaptiveRender;
7473
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
7420
7474
  const previousNumColumnsProp = state.props.numColumns;
7421
7475
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
7422
7476
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -7462,6 +7516,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7462
7516
  estimatedItemSize,
7463
7517
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
7464
7518
  getItemType: useWrapIfItem(getItemType),
7519
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
7465
7520
  horizontal: !!horizontal,
7466
7521
  itemsAreEqual,
7467
7522
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -7499,6 +7554,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7499
7554
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
7500
7555
  resetAdaptiveRender(ctx);
7501
7556
  }
7557
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
7558
+ resetContainerLayoutReady(ctx);
7559
+ }
7502
7560
  const memoizedLastItemKeys = React2.useMemo(() => {
7503
7561
  if (!dataProp.length) return [];
7504
7562
  return Array.from(
package/react-native.mjs CHANGED
@@ -4575,15 +4575,19 @@ function calculateItemsInView(ctx, params = {}) {
4575
4575
  if (oldKey && oldKey !== id) {
4576
4576
  containerItemKeys.delete(oldKey);
4577
4577
  }
4578
+ state.containerItemMetadata.set(
4579
+ containerIndex,
4580
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4581
+ );
4578
4582
  if (oldKey !== id) {
4579
4583
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4580
4584
  changedContainerIds.add(containerIndex);
4581
4585
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4586
+ if (state.props.hideItemsUntilMeasured) {
4587
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4588
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4589
+ }
4582
4590
  }
4583
- state.containerItemMetadata.set(
4584
- containerIndex,
4585
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4586
- );
4587
4591
  set$(ctx, `containerItemKey${containerIndex}`, id);
4588
4592
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4589
4593
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -5015,24 +5019,39 @@ function resolveSkippedAnchorReset(ctx, itemKey) {
5015
5019
  state.userScrollAnchorReset = void 0;
5016
5020
  }
5017
5021
  }
5022
+ function revealMeasuredGeneration(ctx, containerId, itemKey, generation) {
5023
+ var _a3;
5024
+ if (!ctx.state.props.hideItemsUntilMeasured) {
5025
+ return;
5026
+ }
5027
+ const isCurrentGeneration = ((_a3 = ctx.state.containerItemGenerations[containerId]) != null ? _a3 : 0) === generation;
5028
+ const isCurrentAssignment = peek$(ctx, `containerItemKey${containerId}`) === itemKey;
5029
+ if (isCurrentGeneration && isCurrentAssignment) {
5030
+ set$(ctx, `containerLayoutReady${containerId}`, true);
5031
+ }
5032
+ }
5018
5033
  function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5019
- var _a3, _b, _c;
5034
+ var _a3, _b;
5020
5035
  const state = ctx.state;
5021
5036
  const measurements = [];
5037
+ const measuredGenerations = state.props.hideItemsUntilMeasured ? [] : void 0;
5022
5038
  let isCollectingSynchronousMeasurements = true;
5023
5039
  const containerIds = targetContainerIds != null ? targetContainerIds : ctx.viewRefs.keys();
5024
5040
  for (const containerId of containerIds) {
5025
- const viewRef = ctx.viewRefs.get(containerId);
5041
+ const view = (_a3 = ctx.viewRefs.get(containerId)) == null ? void 0 : _a3.current;
5026
5042
  const itemKey = peek$(ctx, `containerItemKey${containerId}`);
5027
5043
  if (itemKey !== void 0) {
5028
- const generation = ((_a3 = state.containerItemGenerations[containerId]) != null ? _a3 : 0) + 1;
5044
+ const generation = ((_b = state.containerItemGenerations[containerId]) != null ? _b : 0) + 1;
5029
5045
  state.containerItemGenerations[containerId] = generation;
5030
5046
  const fixedItemSize = resolveFixedItemSize(ctx, containerId, itemKey);
5031
5047
  const canSkipMeasurement = !state.needsOtherAxisSize && fixedItemSize !== void 0 && state.sizesKnown.get(itemKey) === fixedItemSize + ctx.scrollAxisGap;
5032
5048
  if (canSkipMeasurement) {
5033
5049
  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) => {
5050
+ if (measuredGenerations) {
5051
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5052
+ }
5053
+ } else if (view == null ? void 0 : view.measure) {
5054
+ view.measure((_x, _y, width, height) => {
5036
5055
  var _a4;
5037
5056
  const isCurrentGeneration = ((_a4 = ctx.state.containerItemGenerations[containerId]) != null ? _a4 : 0) === generation;
5038
5057
  if (isCurrentGeneration) {
@@ -5043,17 +5062,28 @@ function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5043
5062
  };
5044
5063
  if (isCollectingSynchronousMeasurements) {
5045
5064
  measurements.push(measurement);
5065
+ measuredGenerations == null ? void 0 : measuredGenerations.push({ containerId, generation, itemKey });
5046
5066
  } else {
5047
5067
  updateItemSizes(ctx, measurement);
5068
+ if (measuredGenerations) {
5069
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5070
+ }
5048
5071
  }
5049
5072
  }
5050
5073
  });
5074
+ } else if (measuredGenerations) {
5075
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5051
5076
  }
5052
5077
  }
5053
5078
  }
5054
5079
  isCollectingSynchronousMeasurements = false;
5055
5080
  if (measurements.length > 0) {
5056
5081
  updateItemSizesBatch(ctx, measurements);
5082
+ if (measuredGenerations) {
5083
+ for (const { containerId, generation, itemKey } of measuredGenerations) {
5084
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5085
+ }
5086
+ }
5057
5087
  }
5058
5088
  }
5059
5089
  var typedForwardRef = React2.forwardRef;
@@ -5141,8 +5171,20 @@ var PositionViewState = typedMemo(function PositionViewState2({
5141
5171
  refView,
5142
5172
  ...rest
5143
5173
  }) {
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 });
5174
+ const [position = POSITION_OUT_OF_VIEW, _itemKey, layoutReady = true] = useArr$([
5175
+ `containerPosition${id}`,
5176
+ `containerItemKey${id}`,
5177
+ `containerLayoutReady${id}`
5178
+ ]);
5179
+ return /* @__PURE__ */ React2.createElement(
5180
+ View$1,
5181
+ {
5182
+ ref: refView,
5183
+ style: [style, horizontal ? { left: position } : { top: position }, !layoutReady && { opacity: 0 }],
5184
+ ...rest,
5185
+ pointerEvents: layoutReady ? void 0 : "none"
5186
+ }
5187
+ );
5146
5188
  });
5147
5189
  var PositionViewAnimated = typedMemo(function PositionViewAnimated2({
5148
5190
  id,
@@ -6270,6 +6312,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6270
6312
  return false;
6271
6313
  }
6272
6314
 
6315
+ // src/core/containerLayoutReady.ts
6316
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6317
+ function resetContainerLayoutReady(ctx) {
6318
+ for (const signalName of ctx.values.keys()) {
6319
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6320
+ set$(ctx, signalName, void 0);
6321
+ }
6322
+ }
6323
+ }
6324
+
6273
6325
  // src/core/doInitialAllocateContainers.ts
6274
6326
  function doInitialAllocateContainers(ctx) {
6275
6327
  var _a3, _b;
@@ -7176,6 +7228,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7176
7228
  initialScrollIndex: initialScrollIndexProp,
7177
7229
  initialScrollOffset: initialScrollOffsetProp,
7178
7230
  experimental_adaptiveRender,
7231
+ experimental_hideItemsUntilMeasured,
7179
7232
  itemsAreEqual,
7180
7233
  keyExtractor: keyExtractorProp,
7181
7234
  ListEmptyComponent,
@@ -7396,6 +7449,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7396
7449
  const state = refState.current;
7397
7450
  const isFirstLocal = state.isFirst;
7398
7451
  const previousAdaptiveRender = state.props.adaptiveRender;
7452
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
7399
7453
  const previousNumColumnsProp = state.props.numColumns;
7400
7454
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
7401
7455
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -7441,6 +7495,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7441
7495
  estimatedItemSize,
7442
7496
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
7443
7497
  getItemType: useWrapIfItem(getItemType),
7498
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
7444
7499
  horizontal: !!horizontal,
7445
7500
  itemsAreEqual,
7446
7501
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -7478,6 +7533,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7478
7533
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
7479
7534
  resetAdaptiveRender(ctx);
7480
7535
  }
7536
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
7537
+ resetContainerLayoutReady(ctx);
7538
+ }
7481
7539
  const memoizedLastItemKeys = useMemo(() => {
7482
7540
  if (!dataProp.length) return [];
7483
7541
  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
  */
@@ -4625,15 +4625,19 @@ function calculateItemsInView(ctx, params = {}) {
4625
4625
  if (oldKey && oldKey !== id) {
4626
4626
  containerItemKeys.delete(oldKey);
4627
4627
  }
4628
+ state.containerItemMetadata.set(
4629
+ containerIndex,
4630
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4631
+ );
4628
4632
  if (oldKey !== id) {
4629
4633
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4630
4634
  changedContainerIds.add(containerIndex);
4631
4635
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4636
+ if (state.props.hideItemsUntilMeasured) {
4637
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4638
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4639
+ }
4632
4640
  }
4633
- state.containerItemMetadata.set(
4634
- containerIndex,
4635
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4636
- );
4637
4641
  set$(ctx, `containerItemKey${containerIndex}`, id);
4638
4642
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4639
4643
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -6950,6 +6954,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6950
6954
  return false;
6951
6955
  }
6952
6956
 
6957
+ // src/core/containerLayoutReady.ts
6958
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6959
+ function resetContainerLayoutReady(ctx) {
6960
+ for (const signalName of ctx.values.keys()) {
6961
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6962
+ set$(ctx, signalName, void 0);
6963
+ }
6964
+ }
6965
+ }
6966
+
6953
6967
  // src/core/doInitialAllocateContainers.ts
6954
6968
  function doInitialAllocateContainers(ctx) {
6955
6969
  var _a3, _b;
@@ -7840,6 +7854,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7840
7854
  initialScrollIndex: initialScrollIndexProp,
7841
7855
  initialScrollOffset: initialScrollOffsetProp,
7842
7856
  experimental_adaptiveRender,
7857
+ experimental_hideItemsUntilMeasured,
7843
7858
  itemsAreEqual,
7844
7859
  keyExtractor: keyExtractorProp,
7845
7860
  ListEmptyComponent,
@@ -8060,6 +8075,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8060
8075
  const state = refState.current;
8061
8076
  const isFirstLocal = state.isFirst;
8062
8077
  const previousAdaptiveRender = state.props.adaptiveRender;
8078
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
8063
8079
  const previousNumColumnsProp = state.props.numColumns;
8064
8080
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
8065
8081
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -8105,6 +8121,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8105
8121
  estimatedItemSize,
8106
8122
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8107
8123
  getItemType: useWrapIfItem(getItemType),
8124
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8108
8125
  horizontal: !!horizontal,
8109
8126
  itemsAreEqual,
8110
8127
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -8142,6 +8159,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8142
8159
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
8143
8160
  resetAdaptiveRender(ctx);
8144
8161
  }
8162
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
8163
+ resetContainerLayoutReady(ctx);
8164
+ }
8145
8165
  const memoizedLastItemKeys = React3.useMemo(() => {
8146
8166
  if (!dataProp.length) return [];
8147
8167
  return Array.from(
@@ -4604,15 +4604,19 @@ function calculateItemsInView(ctx, params = {}) {
4604
4604
  if (oldKey && oldKey !== id) {
4605
4605
  containerItemKeys.delete(oldKey);
4606
4606
  }
4607
+ state.containerItemMetadata.set(
4608
+ containerIndex,
4609
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4610
+ );
4607
4611
  if (oldKey !== id) {
4608
4612
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4609
4613
  changedContainerIds.add(containerIndex);
4610
4614
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4615
+ if (state.props.hideItemsUntilMeasured) {
4616
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4617
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4618
+ }
4611
4619
  }
4612
- state.containerItemMetadata.set(
4613
- containerIndex,
4614
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4615
- );
4616
4620
  set$(ctx, `containerItemKey${containerIndex}`, id);
4617
4621
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4618
4622
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -6929,6 +6933,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6929
6933
  return false;
6930
6934
  }
6931
6935
 
6936
+ // src/core/containerLayoutReady.ts
6937
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6938
+ function resetContainerLayoutReady(ctx) {
6939
+ for (const signalName of ctx.values.keys()) {
6940
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6941
+ set$(ctx, signalName, void 0);
6942
+ }
6943
+ }
6944
+ }
6945
+
6932
6946
  // src/core/doInitialAllocateContainers.ts
6933
6947
  function doInitialAllocateContainers(ctx) {
6934
6948
  var _a3, _b;
@@ -7819,6 +7833,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7819
7833
  initialScrollIndex: initialScrollIndexProp,
7820
7834
  initialScrollOffset: initialScrollOffsetProp,
7821
7835
  experimental_adaptiveRender,
7836
+ experimental_hideItemsUntilMeasured,
7822
7837
  itemsAreEqual,
7823
7838
  keyExtractor: keyExtractorProp,
7824
7839
  ListEmptyComponent,
@@ -8039,6 +8054,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8039
8054
  const state = refState.current;
8040
8055
  const isFirstLocal = state.isFirst;
8041
8056
  const previousAdaptiveRender = state.props.adaptiveRender;
8057
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
8042
8058
  const previousNumColumnsProp = state.props.numColumns;
8043
8059
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
8044
8060
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -8084,6 +8100,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8084
8100
  estimatedItemSize,
8085
8101
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8086
8102
  getItemType: useWrapIfItem(getItemType),
8103
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8087
8104
  horizontal: !!horizontal,
8088
8105
  itemsAreEqual,
8089
8106
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -8121,6 +8138,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8121
8138
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
8122
8139
  resetAdaptiveRender(ctx);
8123
8140
  }
8141
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
8142
+ resetContainerLayoutReady(ctx);
8143
+ }
8124
8144
  const memoizedLastItemKeys = useMemo(() => {
8125
8145
  if (!dataProp.length) return [];
8126
8146
  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
@@ -4625,15 +4625,19 @@ function calculateItemsInView(ctx, params = {}) {
4625
4625
  if (oldKey && oldKey !== id) {
4626
4626
  containerItemKeys.delete(oldKey);
4627
4627
  }
4628
+ state.containerItemMetadata.set(
4629
+ containerIndex,
4630
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4631
+ );
4628
4632
  if (oldKey !== id) {
4629
4633
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4630
4634
  changedContainerIds.add(containerIndex);
4631
4635
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4636
+ if (state.props.hideItemsUntilMeasured) {
4637
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4638
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4639
+ }
4632
4640
  }
4633
- state.containerItemMetadata.set(
4634
- containerIndex,
4635
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4636
- );
4637
4641
  set$(ctx, `containerItemKey${containerIndex}`, id);
4638
4642
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4639
4643
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -6950,6 +6954,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6950
6954
  return false;
6951
6955
  }
6952
6956
 
6957
+ // src/core/containerLayoutReady.ts
6958
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6959
+ function resetContainerLayoutReady(ctx) {
6960
+ for (const signalName of ctx.values.keys()) {
6961
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6962
+ set$(ctx, signalName, void 0);
6963
+ }
6964
+ }
6965
+ }
6966
+
6953
6967
  // src/core/doInitialAllocateContainers.ts
6954
6968
  function doInitialAllocateContainers(ctx) {
6955
6969
  var _a3, _b;
@@ -7840,6 +7854,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7840
7854
  initialScrollIndex: initialScrollIndexProp,
7841
7855
  initialScrollOffset: initialScrollOffsetProp,
7842
7856
  experimental_adaptiveRender,
7857
+ experimental_hideItemsUntilMeasured,
7843
7858
  itemsAreEqual,
7844
7859
  keyExtractor: keyExtractorProp,
7845
7860
  ListEmptyComponent,
@@ -8060,6 +8075,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8060
8075
  const state = refState.current;
8061
8076
  const isFirstLocal = state.isFirst;
8062
8077
  const previousAdaptiveRender = state.props.adaptiveRender;
8078
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
8063
8079
  const previousNumColumnsProp = state.props.numColumns;
8064
8080
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
8065
8081
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -8105,6 +8121,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8105
8121
  estimatedItemSize,
8106
8122
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8107
8123
  getItemType: useWrapIfItem(getItemType),
8124
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8108
8125
  horizontal: !!horizontal,
8109
8126
  itemsAreEqual,
8110
8127
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -8142,6 +8159,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8142
8159
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
8143
8160
  resetAdaptiveRender(ctx);
8144
8161
  }
8162
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
8163
+ resetContainerLayoutReady(ctx);
8164
+ }
8145
8165
  const memoizedLastItemKeys = React3.useMemo(() => {
8146
8166
  if (!dataProp.length) return [];
8147
8167
  return Array.from(
package/react.mjs CHANGED
@@ -4604,15 +4604,19 @@ function calculateItemsInView(ctx, params = {}) {
4604
4604
  if (oldKey && oldKey !== id) {
4605
4605
  containerItemKeys.delete(oldKey);
4606
4606
  }
4607
+ state.containerItemMetadata.set(
4608
+ containerIndex,
4609
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4610
+ );
4607
4611
  if (oldKey !== id) {
4608
4612
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4609
4613
  changedContainerIds.add(containerIndex);
4610
4614
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4615
+ if (state.props.hideItemsUntilMeasured) {
4616
+ const isLayoutReady = state.sizesKnown.has(id) || resolveContainerItemMetadata(state, containerIndex, i, data[i]).fixedItemSize !== void 0;
4617
+ set$(ctx, `containerLayoutReady${containerIndex}`, isLayoutReady);
4618
+ }
4611
4619
  }
4612
- state.containerItemMetadata.set(
4613
- containerIndex,
4614
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4615
- );
4616
4620
  set$(ctx, `containerItemKey${containerIndex}`, id);
4617
4621
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4618
4622
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
@@ -6929,6 +6933,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6929
6933
  return false;
6930
6934
  }
6931
6935
 
6936
+ // src/core/containerLayoutReady.ts
6937
+ var CONTAINER_LAYOUT_READY_PREFIX = "containerLayoutReady";
6938
+ function resetContainerLayoutReady(ctx) {
6939
+ for (const signalName of ctx.values.keys()) {
6940
+ if (signalName.startsWith(CONTAINER_LAYOUT_READY_PREFIX)) {
6941
+ set$(ctx, signalName, void 0);
6942
+ }
6943
+ }
6944
+ }
6945
+
6932
6946
  // src/core/doInitialAllocateContainers.ts
6933
6947
  function doInitialAllocateContainers(ctx) {
6934
6948
  var _a3, _b;
@@ -7819,6 +7833,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7819
7833
  initialScrollIndex: initialScrollIndexProp,
7820
7834
  initialScrollOffset: initialScrollOffsetProp,
7821
7835
  experimental_adaptiveRender,
7836
+ experimental_hideItemsUntilMeasured,
7822
7837
  itemsAreEqual,
7823
7838
  keyExtractor: keyExtractorProp,
7824
7839
  ListEmptyComponent,
@@ -8039,6 +8054,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8039
8054
  const state = refState.current;
8040
8055
  const isFirstLocal = state.isFirst;
8041
8056
  const previousAdaptiveRender = state.props.adaptiveRender;
8057
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
8042
8058
  const previousNumColumnsProp = state.props.numColumns;
8043
8059
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
8044
8060
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -8084,6 +8100,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8084
8100
  estimatedItemSize,
8085
8101
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8086
8102
  getItemType: useWrapIfItem(getItemType),
8103
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8087
8104
  horizontal: !!horizontal,
8088
8105
  itemsAreEqual,
8089
8106
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -8121,6 +8138,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8121
8138
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
8122
8139
  resetAdaptiveRender(ctx);
8123
8140
  }
8141
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
8142
+ resetContainerLayoutReady(ctx);
8143
+ }
8124
8144
  const memoizedLastItemKeys = useMemo(() => {
8125
8145
  if (!dataProp.length) return [];
8126
8146
  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
  */