@legendapp/list 3.3.6 → 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,11 @@
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
+
5
+ ## 3.3.7
6
+
7
+ - Fix: Recycled rows no longer briefly show content from the wrong item when data is prepended.
8
+
1
9
  ## 3.3.6
2
10
 
3
11
  - Fix: Animated `maintainScrollAtEnd` follows rapid and first-load content growth, including short `alignItemsAtEnd` lists, and stops when the user scrolls away.
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.6",
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
@@ -401,6 +401,7 @@ var IsNewArchitecture = f !== void 0 && f != null;
401
401
  // src/core/containerItemMetadata.ts
402
402
  function createContainerItemMetadata(state, itemIndex, itemData, itemType) {
403
403
  return {
404
+ data: state.props.data,
404
405
  dataChangeEpoch: state.dataChangeEpoch,
405
406
  getFixedItemSize: state.props.getFixedItemSize,
406
407
  getItemType: state.props.getItemType,
@@ -409,25 +410,29 @@ function createContainerItemMetadata(state, itemIndex, itemData, itemType) {
409
410
  itemType
410
411
  };
411
412
  }
412
- function resolveContainerItemMetadata(state, containerId, itemIndex, itemData) {
413
- var _a3, _b;
414
- const { getFixedItemSize, getItemType } = state.props;
413
+ function updateContainerItemMetadata(state, containerId, itemIndex, itemData, itemType) {
414
+ var _a3;
415
+ const { getItemType } = state.props;
415
416
  const previousMetadata = state.containerItemMetadata.get(containerId);
416
- let metadata;
417
417
  if ((previousMetadata == null ? void 0 : previousMetadata.dataChangeEpoch) === state.dataChangeEpoch && previousMetadata.getItemType === getItemType && previousMetadata.itemData === itemData && previousMetadata.itemIndex === itemIndex) {
418
- metadata = previousMetadata;
419
- } else {
420
- const itemType = getItemType ? (_a3 = getItemType(itemData, itemIndex)) != null ? _a3 : "" : void 0;
421
- metadata = createContainerItemMetadata(state, itemIndex, itemData, itemType);
422
- state.containerItemMetadata.set(containerId, metadata);
418
+ return previousMetadata;
423
419
  }
420
+ const resolvedItemType = getItemType ? (_a3 = getItemType(itemData, itemIndex)) != null ? _a3 : "" : void 0;
421
+ const metadata = createContainerItemMetadata(state, itemIndex, itemData, resolvedItemType);
422
+ state.containerItemMetadata.set(containerId, metadata);
423
+ return metadata;
424
+ }
425
+ function resolveContainerItemMetadata(state, containerId, itemIndex, itemData) {
426
+ var _a3;
427
+ const { getFixedItemSize } = state.props;
428
+ const metadata = updateContainerItemMetadata(state, containerId, itemIndex, itemData);
424
429
  if (metadata.getFixedItemSize !== getFixedItemSize) {
425
430
  metadata.didResolveFixedItemSize = false;
426
431
  metadata.fixedItemSize = void 0;
427
432
  metadata.getFixedItemSize = getFixedItemSize;
428
433
  }
429
434
  if (getFixedItemSize && !metadata.didResolveFixedItemSize) {
430
- metadata.fixedItemSize = getFixedItemSize(itemData, itemIndex, (_b = metadata.itemType) != null ? _b : "");
435
+ metadata.fixedItemSize = getFixedItemSize(itemData, itemIndex, (_a3 = metadata.itemType) != null ? _a3 : "");
431
436
  metadata.didResolveFixedItemSize = true;
432
437
  }
433
438
  return metadata;
@@ -3294,6 +3299,7 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3294
3299
  return { didChangePosition: false, didRefreshData: false };
3295
3300
  }
3296
3301
  const itemKey = (_a3 = state.idCache[itemIndex]) != null ? _a3 : getId(state, itemIndex);
3302
+ const metadata = updateContainerItemMetadata(state, containerIndex, itemIndex, item);
3297
3303
  const updateLayout = (_b = options == null ? void 0 : options.updateLayout) != null ? _b : true;
3298
3304
  let didChangePosition = false;
3299
3305
  let didRefreshData = false;
@@ -3304,7 +3310,7 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3304
3310
  return { didChangePosition: false, didRefreshData: false };
3305
3311
  }
3306
3312
  const logicalPosition = (positionValue || 0) - ((_c = options == null ? void 0 : options.scrollAdjustPending) != null ? _c : 0);
3307
- const itemSize = (_d = state.sizes.get(itemKey)) != null ? _d : getItemSize(ctx, itemKey, itemIndex, item);
3313
+ const itemSize = (_d = state.sizes.get(itemKey)) != null ? _d : getItemSize(ctx, itemKey, itemIndex, item, void 0, void 0, void 0, metadata);
3308
3314
  const position = toPhysicalHorizontalItemPosition(state, logicalPosition, itemSize, peek$(ctx, "totalSize"));
3309
3315
  const column = columns[itemIndex] || 1;
3310
3316
  const span = columnSpans[itemIndex] || 1;
@@ -4590,18 +4596,22 @@ function calculateItemsInView(ctx, params = {}) {
4590
4596
  if (oldKey && oldKey !== id) {
4591
4597
  containerItemKeys.delete(oldKey);
4592
4598
  }
4599
+ state.containerItemMetadata.set(
4600
+ containerIndex,
4601
+ createContainerItemMetadata(state, i, data[i], allocation.itemType)
4602
+ );
4593
4603
  if (oldKey !== id) {
4594
4604
  changedContainerIds != null ? changedContainerIds : changedContainerIds = /* @__PURE__ */ new Set();
4595
4605
  changedContainerIds.add(containerIndex);
4596
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
+ }
4597
4611
  }
4598
4612
  set$(ctx, `containerItemKey${containerIndex}`, id);
4599
4613
  set$(ctx, `containerItemIndex${containerIndex}`, i);
4600
4614
  set$(ctx, `containerItemData${containerIndex}`, data[i]);
4601
- state.containerItemMetadata.set(
4602
- containerIndex,
4603
- createContainerItemMetadata(state, i, data[i], allocation.itemType)
4604
- );
4605
4615
  containerItemKeys.set(id, containerIndex);
4606
4616
  (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4607
4617
  const containerSticky = `containerSticky${containerIndex}`;
@@ -5030,24 +5040,39 @@ function resolveSkippedAnchorReset(ctx, itemKey) {
5030
5040
  state.userScrollAnchorReset = void 0;
5031
5041
  }
5032
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
+ }
5033
5054
  function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5034
- var _a3, _b, _c;
5055
+ var _a3, _b;
5035
5056
  const state = ctx.state;
5036
5057
  const measurements = [];
5058
+ const measuredGenerations = state.props.hideItemsUntilMeasured ? [] : void 0;
5037
5059
  let isCollectingSynchronousMeasurements = true;
5038
5060
  const containerIds = targetContainerIds != null ? targetContainerIds : ctx.viewRefs.keys();
5039
5061
  for (const containerId of containerIds) {
5040
- const viewRef = ctx.viewRefs.get(containerId);
5062
+ const view = (_a3 = ctx.viewRefs.get(containerId)) == null ? void 0 : _a3.current;
5041
5063
  const itemKey = peek$(ctx, `containerItemKey${containerId}`);
5042
5064
  if (itemKey !== void 0) {
5043
- const generation = ((_a3 = state.containerItemGenerations[containerId]) != null ? _a3 : 0) + 1;
5065
+ const generation = ((_b = state.containerItemGenerations[containerId]) != null ? _b : 0) + 1;
5044
5066
  state.containerItemGenerations[containerId] = generation;
5045
5067
  const fixedItemSize = resolveFixedItemSize(ctx, containerId, itemKey);
5046
5068
  const canSkipMeasurement = !state.needsOtherAxisSize && fixedItemSize !== void 0 && state.sizesKnown.get(itemKey) === fixedItemSize + ctx.scrollAxisGap;
5047
5069
  if (canSkipMeasurement) {
5048
5070
  resolveSkippedAnchorReset(ctx, itemKey);
5049
- } else if (viewRef) {
5050
- (_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) => {
5051
5076
  var _a4;
5052
5077
  const isCurrentGeneration = ((_a4 = ctx.state.containerItemGenerations[containerId]) != null ? _a4 : 0) === generation;
5053
5078
  if (isCurrentGeneration) {
@@ -5058,17 +5083,28 @@ function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
5058
5083
  };
5059
5084
  if (isCollectingSynchronousMeasurements) {
5060
5085
  measurements.push(measurement);
5086
+ measuredGenerations == null ? void 0 : measuredGenerations.push({ containerId, generation, itemKey });
5061
5087
  } else {
5062
5088
  updateItemSizes(ctx, measurement);
5089
+ if (measuredGenerations) {
5090
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5091
+ }
5063
5092
  }
5064
5093
  }
5065
5094
  });
5095
+ } else if (measuredGenerations) {
5096
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5066
5097
  }
5067
5098
  }
5068
5099
  }
5069
5100
  isCollectingSynchronousMeasurements = false;
5070
5101
  if (measurements.length > 0) {
5071
5102
  updateItemSizesBatch(ctx, measurements);
5103
+ if (measuredGenerations) {
5104
+ for (const { containerId, generation, itemKey } of measuredGenerations) {
5105
+ revealMeasuredGeneration(ctx, containerId, itemKey, generation);
5106
+ }
5107
+ }
5072
5108
  }
5073
5109
  }
5074
5110
  var typedForwardRef = React2__namespace.forwardRef;
@@ -5156,8 +5192,20 @@ var PositionViewState = typedMemo(function PositionViewState2({
5156
5192
  refView,
5157
5193
  ...rest
5158
5194
  }) {
5159
- const [position = POSITION_OUT_OF_VIEW, _itemKey] = useArr$([`containerPosition${id}`, `containerItemKey${id}`]);
5160
- 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
+ );
5161
5209
  });
5162
5210
  var PositionViewAnimated = typedMemo(function PositionViewAnimated2({
5163
5211
  id,
@@ -5744,7 +5792,7 @@ var Container = typedMemo(function Container2({
5744
5792
  ]
5745
5793
  );
5746
5794
  const renderedItemInfo = React2.useMemo(
5747
- () => itemKey !== void 0 ? getRenderedItem2(itemKey) : null,
5795
+ () => itemKey !== void 0 ? getRenderedItem2(itemKey, id) : null,
5748
5796
  [itemKey, data, extraData]
5749
5797
  );
5750
5798
  const { renderedItem } = renderedItemInfo || {};
@@ -6285,6 +6333,16 @@ function checkStructuralDataChange(state, dataProp, previousData) {
6285
6333
  return false;
6286
6334
  }
6287
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
+
6288
6346
  // src/core/doInitialAllocateContainers.ts
6289
6347
  function doInitialAllocateContainers(ctx) {
6290
6348
  var _a3, _b;
@@ -6995,34 +7053,37 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
6995
7053
  }
6996
7054
 
6997
7055
  // src/utils/getRenderedItem.ts
6998
- function getRenderedItem(ctx, key) {
6999
- var _a3;
7056
+ function getRenderedItem(ctx, key, containerId) {
7057
+ var _a3, _b, _c;
7000
7058
  const state = ctx.state;
7001
7059
  if (!state) {
7002
7060
  return null;
7003
7061
  }
7062
+ const metadata = state.containerItemMetadata.get(containerId);
7063
+ const useAssignedGeneration = metadata && metadata.dataChangeEpoch !== state.dataChangeEpoch;
7004
7064
  const {
7005
7065
  indexByKey,
7006
- props: { data, getItemType, renderItem }
7066
+ props: { data: currentData, getItemType, renderItem }
7007
7067
  } = state;
7008
- const index = indexByKey.get(key);
7068
+ const data = useAssignedGeneration ? metadata.data : currentData;
7069
+ const index = (_a3 = metadata == null ? void 0 : metadata.itemIndex) != null ? _a3 : indexByKey.get(key);
7009
7070
  if (index === void 0) {
7010
7071
  return null;
7011
7072
  }
7012
7073
  let renderedItem = null;
7013
7074
  const extraData = peek$(ctx, "extraData");
7014
- const item = data[index];
7075
+ const item = useAssignedGeneration ? metadata.itemData : data[index];
7015
7076
  if (renderItem && !isNullOrUndefined(item)) {
7016
7077
  const itemProps = {
7017
7078
  data,
7018
7079
  extraData,
7019
7080
  index,
7020
7081
  item,
7021
- type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
7082
+ type: useAssignedGeneration ? (_b = metadata.itemType) != null ? _b : "" : getItemType ? (_c = getItemType(item, index)) != null ? _c : "" : ""
7022
7083
  };
7023
7084
  renderedItem = renderItem(itemProps);
7024
7085
  }
7025
- return { index, item: data[index], renderedItem };
7086
+ return { index, item, renderedItem };
7026
7087
  }
7027
7088
 
7028
7089
  // src/utils/normalizeMaintainScrollAtEnd.ts
@@ -7188,6 +7249,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7188
7249
  initialScrollIndex: initialScrollIndexProp,
7189
7250
  initialScrollOffset: initialScrollOffsetProp,
7190
7251
  experimental_adaptiveRender,
7252
+ experimental_hideItemsUntilMeasured,
7191
7253
  itemsAreEqual,
7192
7254
  keyExtractor: keyExtractorProp,
7193
7255
  ListEmptyComponent,
@@ -7408,6 +7470,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7408
7470
  const state = refState.current;
7409
7471
  const isFirstLocal = state.isFirst;
7410
7472
  const previousAdaptiveRender = state.props.adaptiveRender;
7473
+ const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
7411
7474
  const previousNumColumnsProp = state.props.numColumns;
7412
7475
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
7413
7476
  ctx.scrollAxisGap = nextScrollAxisGap;
@@ -7453,6 +7516,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7453
7516
  estimatedItemSize,
7454
7517
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
7455
7518
  getItemType: useWrapIfItem(getItemType),
7519
+ hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
7456
7520
  horizontal: !!horizontal,
7457
7521
  itemsAreEqual,
7458
7522
  keyExtractor: useWrapIfItem(keyExtractor),
@@ -7490,6 +7554,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7490
7554
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
7491
7555
  resetAdaptiveRender(ctx);
7492
7556
  }
7557
+ if (!isFirstLocal && previousHideItemsUntilMeasured && !experimental_hideItemsUntilMeasured) {
7558
+ resetContainerLayoutReady(ctx);
7559
+ }
7493
7560
  const memoizedLastItemKeys = React2.useMemo(() => {
7494
7561
  if (!dataProp.length) return [];
7495
7562
  return Array.from(
@@ -7746,7 +7813,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7746
7813
  }, [ctx, usesBootstrapInitialScroll]);
7747
7814
  const fns = React2.useMemo(
7748
7815
  () => ({
7749
- getRenderedItem: (key) => getRenderedItem(ctx, key),
7816
+ getRenderedItem: (key, containerId) => getRenderedItem(ctx, key, containerId),
7750
7817
  onMomentumScrollEnd: (event) => {
7751
7818
  checkFinishedScrollFallback(ctx);
7752
7819
  if (state.props.onMomentumScrollEnd) {