@legendapp/list 3.0.0-beta.44 → 3.0.0-beta.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.native.mjs CHANGED
@@ -8,37 +8,6 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim';
8
8
  Animated.View;
9
9
  var View = View$1;
10
10
  var Text = Text$1;
11
-
12
- // src/state/getContentInsetEnd.ts
13
- function getContentInsetEnd(state) {
14
- var _a3;
15
- const { props } = state;
16
- const horizontal = props.horizontal;
17
- const contentInset = props.contentInset;
18
- const baseInset = contentInset != null ? contentInset : state.nativeContentInset;
19
- const overrideInset = (_a3 = state.contentInsetOverride) != null ? _a3 : void 0;
20
- if (overrideInset) {
21
- const mergedInset = { bottom: 0, right: 0, ...baseInset, ...overrideInset };
22
- return (horizontal ? mergedInset.right : mergedInset.bottom) || 0;
23
- }
24
- if (baseInset) {
25
- return (horizontal ? baseInset.right : baseInset.bottom) || 0;
26
- }
27
- return 0;
28
- }
29
-
30
- // src/state/getContentSize.ts
31
- function getContentSize(ctx) {
32
- var _a3;
33
- const { values, state } = ctx;
34
- const stylePaddingTop = values.get("stylePaddingTop") || 0;
35
- const stylePaddingBottom = state.props.stylePaddingBottom || 0;
36
- const headerSize = values.get("headerSize") || 0;
37
- const footerSize = values.get("footerSize") || 0;
38
- const contentInsetBottom = getContentInsetEnd(state);
39
- const totalSize = (_a3 = state.pendingTotalSize) != null ? _a3 : values.get("totalSize");
40
- return headerSize + footerSize + totalSize + stylePaddingTop + stylePaddingBottom + (contentInsetBottom || 0);
41
- }
42
11
  var createAnimatedValue = (value) => new Animated.Value(value);
43
12
 
44
13
  // src/state/state.tsx
@@ -62,6 +31,11 @@ function StateProvider({ children }) {
62
31
  ["headerSize", 0],
63
32
  ["numContainers", 0],
64
33
  ["activeStickyIndex", -1],
34
+ ["isAtEnd", false],
35
+ ["isAtStart", false],
36
+ ["isNearEnd", false],
37
+ ["isNearStart", false],
38
+ ["isWithinMaintainScrollAtEndThreshold", false],
65
39
  ["totalSize", 0],
66
40
  ["scrollAdjustPending", 0]
67
41
  ]),
@@ -153,29 +127,71 @@ function notifyPosition$(ctx, key, value) {
153
127
  function useArr$(signalNames) {
154
128
  const ctx = React2.useContext(ContextState);
155
129
  const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
156
- const value = useSyncExternalStore(subscribe, get);
130
+ const value = useSyncExternalStore(subscribe, get, get);
157
131
  return value;
158
132
  }
159
133
  function useSelector$(signalName, selector) {
160
134
  const ctx = React2.useContext(ContextState);
161
135
  const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, [signalName]), [ctx, signalName]);
162
- const value = useSyncExternalStore(subscribe, () => selector(get()[0]));
136
+ const getSelectedValue = React2.useCallback(() => selector(get()[0]), [get, selector]);
137
+ const value = useSyncExternalStore(subscribe, getSelectedValue, getSelectedValue);
163
138
  return value;
164
139
  }
165
140
 
141
+ // src/state/getContentInsetEnd.ts
142
+ function getContentInsetEnd(ctx) {
143
+ var _a3, _b;
144
+ const state = ctx.state;
145
+ const { props } = state;
146
+ const horizontal = props.horizontal;
147
+ const contentInset = props.contentInset;
148
+ const baseInset = contentInset != null ? contentInset : state.nativeContentInset;
149
+ const baseEndInset = (horizontal ? baseInset == null ? void 0 : baseInset.right : baseInset == null ? void 0 : baseInset.bottom) || 0;
150
+ const anchoredEndSpaceSize = peek$(ctx, "anchoredEndSpaceSize");
151
+ const anchoredEndInset = ((_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.includeInEndInset) && anchoredEndSpaceSize ? anchoredEndSpaceSize : 0;
152
+ const overrideInset = (_b = state.contentInsetOverride) != null ? _b : void 0;
153
+ if (overrideInset) {
154
+ const mergedInset = { bottom: 0, right: 0, ...baseInset, ...overrideInset };
155
+ return Math.max((horizontal ? mergedInset.right : mergedInset.bottom) || 0, anchoredEndInset);
156
+ }
157
+ return Math.max(baseEndInset, anchoredEndInset);
158
+ }
159
+
160
+ // src/state/getContentSize.ts
161
+ function getContentSize(ctx) {
162
+ var _a3;
163
+ const { values, state } = ctx;
164
+ const stylePaddingTop = values.get("stylePaddingTop") || 0;
165
+ const stylePaddingBottom = state.props.stylePaddingBottom || 0;
166
+ const headerSize = values.get("headerSize") || 0;
167
+ const footerSize = values.get("footerSize") || 0;
168
+ const contentInsetBottom = getContentInsetEnd(ctx);
169
+ const totalSize = (_a3 = state.pendingTotalSize) != null ? _a3 : values.get("totalSize");
170
+ return headerSize + footerSize + totalSize + stylePaddingTop + stylePaddingBottom + (contentInsetBottom || 0);
171
+ }
172
+
166
173
  // src/components/DebugView.tsx
167
174
  var DebugRow = ({ children }) => {
168
175
  return /* @__PURE__ */ React2.createElement(View, { style: { alignItems: "center", flexDirection: "row", justifyContent: "space-between" } }, children);
169
176
  };
170
- React2.memo(function DebugView2({ state }) {
177
+ React2.memo(function DebugView2() {
171
178
  const ctx = useStateContext();
172
- const [totalSize = 0, scrollAdjust = 0, rawScroll = 0, scroll = 0, _numContainers = 0, _numContainersPooled = 0] = useArr$([
179
+ const [
180
+ totalSize = 0,
181
+ scrollAdjust = 0,
182
+ rawScroll = 0,
183
+ scroll = 0,
184
+ _numContainers = 0,
185
+ _numContainersPooled = 0,
186
+ isAtEnd = false
187
+ ] = useArr$([
173
188
  "totalSize",
174
189
  "scrollAdjust",
175
190
  "debugRawScroll",
176
191
  "debugComputedScroll",
177
192
  "numContainers",
178
- "numContainersPooled"
193
+ "numContainersPooled",
194
+ "isAtEnd"
179
195
  ]);
180
196
  const contentSize = getContentSize(ctx);
181
197
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
@@ -200,7 +216,7 @@ React2.memo(function DebugView2({ state }) {
200
216
  },
201
217
  /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "TotalSize:"), /* @__PURE__ */ React2.createElement(Text, null, totalSize.toFixed(2))),
202
218
  /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "ContentSize:"), /* @__PURE__ */ React2.createElement(Text, null, contentSize.toFixed(2))),
203
- /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "At end:"), /* @__PURE__ */ React2.createElement(Text, null, String(state.isAtEnd))),
219
+ /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "At end:"), /* @__PURE__ */ React2.createElement(Text, null, String(isAtEnd))),
204
220
  /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "ScrollAdjust:"), /* @__PURE__ */ React2.createElement(Text, null, scrollAdjust.toFixed(2))),
205
221
  /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "RawScroll: "), /* @__PURE__ */ React2.createElement(Text, null, rawScroll.toFixed(2))),
206
222
  /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "ComputedScroll: "), /* @__PURE__ */ React2.createElement(Text, null, scroll.toFixed(2)))
@@ -213,6 +229,30 @@ function useInterval(callback, delay) {
213
229
  }, [delay]);
214
230
  }
215
231
 
232
+ // src/components/stickyPositionUtils.ts
233
+ function getStickyPushLimit(state, index, itemKey) {
234
+ if (!itemKey) {
235
+ return void 0;
236
+ }
237
+ const currentSize = state.sizes.get(itemKey);
238
+ if (!(currentSize && currentSize > 0)) {
239
+ return void 0;
240
+ }
241
+ const stickyIndexInArray = state.props.stickyIndicesArr.indexOf(index);
242
+ if (stickyIndexInArray === -1) {
243
+ return void 0;
244
+ }
245
+ const nextStickyIndex = state.props.stickyIndicesArr[stickyIndexInArray + 1];
246
+ if (nextStickyIndex === void 0) {
247
+ return void 0;
248
+ }
249
+ const nextStickyPosition = state.positions[nextStickyIndex];
250
+ if (nextStickyPosition === void 0) {
251
+ return void 0;
252
+ }
253
+ return nextStickyPosition - currentSize;
254
+ }
255
+
216
256
  // src/utils/devEnvironment.ts
217
257
  var metroDev = typeof __DEV__ !== "undefined" ? __DEV__ : void 0;
218
258
  var _a;
@@ -223,6 +263,7 @@ var IS_DEV = (_a2 = processDev != null ? processDev : metroDev) != null ? _a2 :
223
263
 
224
264
  // src/constants.ts
225
265
  var POSITION_OUT_OF_VIEW = -1e7;
266
+ var EDGE_POSITION_EPSILON = 1;
226
267
  var ENABLE_DEVMODE = IS_DEV && false;
227
268
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
228
269
 
@@ -234,93 +275,26 @@ var useAnimatedValue = (initialValue) => {
234
275
  return animAnimatedValue;
235
276
  };
236
277
 
237
- // src/utils/helpers.ts
238
- function isFunction(obj) {
239
- return typeof obj === "function";
240
- }
241
- function isArray(obj) {
242
- return Array.isArray(obj);
243
- }
244
- var warned = /* @__PURE__ */ new Set();
245
- function warnDevOnce(id, text) {
246
- if (IS_DEV && !warned.has(id)) {
247
- warned.add(id);
248
- console.warn(`[legend-list] ${text}`);
249
- }
250
- }
251
- function roundSize(size) {
252
- return Math.floor(size * 8) / 8;
253
- }
254
- function isNullOrUndefined(value) {
255
- return value === null || value === void 0;
256
- }
257
- function comparatorDefault(a, b) {
258
- return a - b;
259
- }
260
- function getPadding(s, type) {
261
- var _a3, _b, _c;
262
- return (_c = (_b = (_a3 = s[`padding${type}`]) != null ? _a3 : s.paddingVertical) != null ? _b : s.padding) != null ? _c : 0;
263
- }
264
- function extractPadding(style, contentContainerStyle, type) {
265
- return getPadding(style, type) + getPadding(contentContainerStyle, type);
266
- }
267
- function findContainerId(ctx, key) {
268
- var _a3, _b;
269
- const directMatch = (_b = (_a3 = ctx.state) == null ? void 0 : _a3.containerItemKeys) == null ? void 0 : _b.get(key);
270
- if (directMatch !== void 0) {
271
- return directMatch;
272
- }
273
- const numContainers = peek$(ctx, "numContainers");
274
- for (let i = 0; i < numContainers; i++) {
275
- const itemKey = peek$(ctx, `containerItemKey${i}`);
276
- if (itemKey === key) {
277
- return i;
278
- }
279
- }
280
- return -1;
281
- }
282
-
283
278
  // src/hooks/useValue$.ts
284
279
  function useValue$(key, params) {
285
- const { getValue, delay } = params || {};
280
+ const { getValue } = params || {};
286
281
  const ctx = useStateContext();
287
282
  const getNewValue = () => {
288
283
  var _a3;
289
284
  return (_a3 = getValue ? getValue(peek$(ctx, key)) : peek$(ctx, key)) != null ? _a3 : 0;
290
285
  };
291
286
  const animValue = useAnimatedValue(getNewValue());
292
- useMemo(() => {
293
- let prevValue;
294
- let didQueueTask = false;
295
- listen$(ctx, key, () => {
296
- const newValue = getNewValue();
297
- if (delay !== void 0) {
298
- const fn = () => {
299
- didQueueTask = false;
300
- const latestValue = getNewValue();
301
- if (latestValue !== void 0) {
302
- animValue.setValue(latestValue);
303
- }
304
- };
305
- const delayValue = isFunction(delay) ? delay(newValue, prevValue) : delay;
306
- prevValue = newValue;
307
- if (!didQueueTask) {
308
- didQueueTask = true;
309
- if (delayValue === void 0) {
310
- fn();
311
- } else if (delayValue === 0) {
312
- queueMicrotask(fn);
313
- } else {
314
- setTimeout(fn, delayValue);
315
- }
316
- }
317
- } else {
318
- animValue.setValue(newValue);
319
- }
320
- });
321
- }, []);
287
+ useLayoutEffect(() => {
288
+ const syncCurrentValue = () => {
289
+ animValue.setValue(getNewValue());
290
+ };
291
+ const unsubscribe = listen$(ctx, key, syncCurrentValue);
292
+ syncCurrentValue();
293
+ return unsubscribe;
294
+ }, [animValue, ctx, key]);
322
295
  return animValue;
323
296
  }
297
+ var typedForwardRef = React2.forwardRef;
324
298
  var typedMemo = React2.memo;
325
299
  var getComponent = (Component) => {
326
300
  if (React2.isValidElement(Component)) {
@@ -340,18 +314,8 @@ var PositionViewState = typedMemo(function PositionViewState2({
340
314
  refView,
341
315
  ...rest
342
316
  }) {
343
- const [position = POSITION_OUT_OF_VIEW] = useArr$([`containerPosition${id}`]);
344
- return /* @__PURE__ */ React2.createElement(
345
- View$1,
346
- {
347
- ref: refView,
348
- style: [
349
- style,
350
- horizontal ? { transform: [{ translateX: position }] } : { transform: [{ translateY: position }] }
351
- ],
352
- ...rest
353
- }
354
- );
317
+ const [position = POSITION_OUT_OF_VIEW, _itemKey] = useArr$([`containerPosition${id}`, `containerItemKey${id}`]);
318
+ return /* @__PURE__ */ React2.createElement(View$1, { ref: refView, style: [style, horizontal ? { left: position } : { top: position }], ...rest });
355
319
  });
356
320
  var PositionViewAnimated = typedMemo(function PositionViewAnimated2({
357
321
  id,
@@ -382,25 +346,46 @@ var PositionViewSticky = typedMemo(function PositionViewSticky2({
382
346
  children,
383
347
  ...rest
384
348
  }) {
385
- const [position = POSITION_OUT_OF_VIEW, headerSize = 0, stylePaddingTop = 0] = useArr$([
349
+ const ctx = useStateContext();
350
+ const [position = POSITION_OUT_OF_VIEW, headerSize = 0, stylePaddingTop = 0, itemKey, _totalSize = 0] = useArr$([
386
351
  `containerPosition${id}`,
387
352
  "headerSize",
388
- "stylePaddingTop"
353
+ "stylePaddingTop",
354
+ `containerItemKey${id}`,
355
+ "totalSize"
389
356
  ]);
357
+ const pushLimit = React2.useMemo(
358
+ () => getStickyPushLimit(ctx.state, index, itemKey),
359
+ [ctx.state, index, itemKey, _totalSize]
360
+ );
390
361
  const transform = React2.useMemo(() => {
391
362
  var _a3;
392
363
  if (animatedScrollY) {
393
364
  const stickyConfigOffset = (_a3 = stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset) != null ? _a3 : 0;
394
365
  const stickyStart = position + headerSize + stylePaddingTop - stickyConfigOffset;
395
- const stickyPosition = animatedScrollY.interpolate({
396
- extrapolateLeft: "clamp",
397
- extrapolateRight: "extend",
398
- inputRange: [stickyStart, stickyStart + 5e3],
399
- outputRange: [position, position + 5e3]
400
- });
366
+ let stickyPosition;
367
+ if (pushLimit !== void 0) {
368
+ if (pushLimit <= position) {
369
+ stickyPosition = pushLimit;
370
+ } else {
371
+ stickyPosition = animatedScrollY.interpolate({
372
+ extrapolateLeft: "clamp",
373
+ extrapolateRight: "clamp",
374
+ inputRange: [stickyStart, stickyStart + (pushLimit - position)],
375
+ outputRange: [position, pushLimit]
376
+ });
377
+ }
378
+ } else {
379
+ stickyPosition = animatedScrollY.interpolate({
380
+ extrapolateLeft: "clamp",
381
+ extrapolateRight: "extend",
382
+ inputRange: [stickyStart, stickyStart + 5e3],
383
+ outputRange: [position, position + 5e3]
384
+ });
385
+ }
401
386
  return horizontal ? [{ translateX: stickyPosition }] : [{ translateY: stickyPosition }];
402
387
  }
403
- }, [animatedScrollY, headerSize, horizontal, position, stylePaddingTop, stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset]);
388
+ }, [animatedScrollY, headerSize, horizontal, position, pushLimit, stylePaddingTop, stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset]);
404
389
  const viewStyle = React2.useMemo(() => [style, { zIndex: index + 1e3 }, { transform }], [style, transform]);
405
390
  const renderStickyHeaderBackdrop = React2.useMemo(() => {
406
391
  if (!(stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.backdropComponent)) {
@@ -425,6 +410,52 @@ function useInit(cb) {
425
410
  useState(() => cb());
426
411
  }
427
412
 
413
+ // src/utils/helpers.ts
414
+ function isFunction(obj) {
415
+ return typeof obj === "function";
416
+ }
417
+ function isArray(obj) {
418
+ return Array.isArray(obj);
419
+ }
420
+ var warned = /* @__PURE__ */ new Set();
421
+ function warnDevOnce(id, text) {
422
+ if (IS_DEV && !warned.has(id)) {
423
+ warned.add(id);
424
+ console.warn(`[legend-list] ${text}`);
425
+ }
426
+ }
427
+ function roundSize(size) {
428
+ return Math.floor(size * 8) / 8;
429
+ }
430
+ function isNullOrUndefined(value) {
431
+ return value === null || value === void 0;
432
+ }
433
+ function comparatorDefault(a, b) {
434
+ return a - b;
435
+ }
436
+ function getPadding(s, type) {
437
+ var _a3, _b, _c;
438
+ return (_c = (_b = (_a3 = s[`padding${type}`]) != null ? _a3 : s.paddingVertical) != null ? _b : s.padding) != null ? _c : 0;
439
+ }
440
+ function extractPadding(style, contentContainerStyle, type) {
441
+ return getPadding(style, type) + getPadding(contentContainerStyle, type);
442
+ }
443
+ function findContainerId(ctx, key) {
444
+ var _a3, _b;
445
+ const directMatch = (_b = (_a3 = ctx.state) == null ? void 0 : _a3.containerItemKeys) == null ? void 0 : _b.get(key);
446
+ if (directMatch !== void 0) {
447
+ return directMatch;
448
+ }
449
+ const numContainers = peek$(ctx, "numContainers");
450
+ for (let i = 0; i < numContainers; i++) {
451
+ const itemKey = peek$(ctx, `containerItemKey${i}`);
452
+ if (itemKey === key) {
453
+ return i;
454
+ }
455
+ }
456
+ return -1;
457
+ }
458
+
428
459
  // src/state/ContextContainer.ts
429
460
  var ContextContainer = createContext(null);
430
461
  function useContextContainer() {
@@ -592,11 +623,11 @@ function useOnLayoutSync({
592
623
  const { layout } = event.nativeEvent;
593
624
  if (layout.height !== ((_a3 = lastLayoutRef.current) == null ? void 0 : _a3.height) || layout.width !== ((_b = lastLayoutRef.current) == null ? void 0 : _b.width)) {
594
625
  onLayoutChange(layout, false);
595
- onLayoutProp == null ? void 0 : onLayoutProp(event);
596
626
  lastLayoutRef.current = layout;
597
627
  }
628
+ onLayoutProp == null ? void 0 : onLayoutProp(event);
598
629
  },
599
- [onLayoutChange]
630
+ [onLayoutChange, onLayoutProp]
600
631
  );
601
632
  if (IsNewArchitecture) {
602
633
  useLayoutEffect(() => {
@@ -613,8 +644,6 @@ function useOnLayoutSync({
613
644
  }
614
645
  var Platform2 = Platform;
615
646
  var PlatformAdjustBreaksScroll = Platform2.OS === "android";
616
- var typedForwardRef = React2.forwardRef;
617
- var typedMemo2 = React2.memo;
618
647
 
619
648
  // src/utils/isInMVCPActiveMode.native.ts
620
649
  function isInMVCPActiveMode(state) {
@@ -622,7 +651,7 @@ function isInMVCPActiveMode(state) {
622
651
  }
623
652
 
624
653
  // src/components/Container.tsx
625
- var Container = typedMemo2(function Container2({
654
+ var Container = typedMemo(function Container2({
626
655
  id,
627
656
  recycleItems,
628
657
  horizontal,
@@ -666,17 +695,20 @@ var Container = typedMemo2(function Container2({
666
695
  const { columnGap, rowGap, gap } = columnWrapperStyle;
667
696
  if (horizontal) {
668
697
  paddingStyles = {
698
+ paddingBottom: numColumns > 1 ? (rowGap || gap || 0) / 2 : void 0,
669
699
  paddingRight: columnGap || gap || void 0,
670
- paddingVertical: numColumns > 1 ? (rowGap || gap || 0) / 2 : void 0
700
+ paddingTop: numColumns > 1 ? (rowGap || gap || 0) / 2 : void 0
671
701
  };
672
702
  } else {
673
703
  paddingStyles = {
674
704
  paddingBottom: rowGap || gap || void 0,
675
- paddingHorizontal: numColumns > 1 ? (columnGap || gap || 0) / 2 : void 0
705
+ paddingLeft: numColumns > 1 ? (columnGap || gap || 0) / 2 : void 0,
706
+ paddingRight: numColumns > 1 ? (columnGap || gap || 0) / 2 : void 0
676
707
  };
677
708
  }
678
709
  }
679
710
  return horizontal ? {
711
+ boxSizing: paddingStyles ? "border-box" : void 0,
680
712
  flexDirection: ItemSeparatorComponent ? "row" : void 0,
681
713
  height: otherAxisSize,
682
714
  left: 0,
@@ -684,6 +716,7 @@ var Container = typedMemo2(function Container2({
684
716
  top: otherAxisPos,
685
717
  ...paddingStyles || {}
686
718
  } : {
719
+ boxSizing: paddingStyles ? "border-box" : void 0,
687
720
  left: otherAxisPos,
688
721
  position: "absolute",
689
722
  right: numColumns > 1 ? null : 0,
@@ -817,16 +850,9 @@ var Containers = typedMemo(function Containers2({
817
850
  const ctx = useStateContext();
818
851
  const columnWrapperStyle = ctx.columnWrapperStyle;
819
852
  const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
820
- const animSize = useValue$("totalSize", {
821
- // Use a microtask if increasing the size significantly, otherwise use a timeout
822
- // If this is the initial scroll, we don't want to delay because we want to update the size immediately
823
- delay: (value, prevValue) => {
824
- var _a3;
825
- return !((_a3 = ctx.state) == null ? void 0 : _a3.initialScroll) ? !prevValue || value - prevValue > 20 ? 0 : 200 : void 0;
826
- }
827
- });
853
+ const animSize = useValue$("totalSize");
854
+ const otherAxisSize = useValue$("otherAxisSize");
828
855
  const animOpacity = useValue$("readyToRender", { getValue: (value) => value ? 1 : 0 });
829
- const otherAxisSize = useValue$("otherAxisSize", { delay: 0 });
830
856
  const containers = [];
831
857
  for (let i = 0; i < numContainers; i++) {
832
858
  containers.push(
@@ -870,17 +896,20 @@ var Containers = typedMemo(function Containers2({
870
896
  });
871
897
  var ListComponentScrollView = Animated.ScrollView;
872
898
  function ScrollAdjust() {
899
+ var _a3;
900
+ const ctx = useStateContext();
873
901
  const bias = 1e7;
874
902
  const [scrollAdjust, scrollAdjustUserOffset] = useArr$(["scrollAdjust", "scrollAdjustUserOffset"]);
875
903
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0) + bias;
904
+ const horizontal = !!((_a3 = ctx.state) == null ? void 0 : _a3.props.horizontal);
876
905
  return /* @__PURE__ */ React2.createElement(
877
906
  View$1,
878
907
  {
879
908
  style: {
880
909
  height: 0,
881
- left: 0,
910
+ left: horizontal ? scrollOffset : 0,
882
911
  position: "absolute",
883
- top: scrollOffset,
912
+ top: horizontal ? 0 : scrollOffset,
884
913
  width: 0
885
914
  }
886
915
  }
@@ -890,14 +919,25 @@ function SnapWrapper({ ScrollComponent, ...props }) {
890
919
  const [snapToOffsets] = useArr$(["snapToOffsets"]);
891
920
  return /* @__PURE__ */ React2.createElement(ScrollComponent, { ...props, snapToOffsets });
892
921
  }
922
+ function WebAnchoredEndSpace({ horizontal }) {
923
+ const ctx = useStateContext();
924
+ const [anchoredEndSpaceSize] = useArr$(["anchoredEndSpaceSize"]);
925
+ const shouldRenderAnchoredEndSpace = !!ctx.state.props.anchoredEndSpace && (anchoredEndSpaceSize || 0) > 0;
926
+ if (!shouldRenderAnchoredEndSpace) {
927
+ return null;
928
+ }
929
+ const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
930
+ return /* @__PURE__ */ React2.createElement("div", { style }, null);
931
+ }
893
932
  var LayoutView = ({ onLayoutChange, refView, ...rest }) => {
894
- const ref = refView != null ? refView : useRef(null);
933
+ const localRef = useRef(null);
934
+ const ref = refView != null ? refView : localRef;
895
935
  const { onLayout } = useOnLayoutSync({ onLayoutChange, ref });
896
936
  return /* @__PURE__ */ React2.createElement(View$1, { ...rest, onLayout, ref });
897
937
  };
898
938
 
899
939
  // src/components/ListComponent.tsx
900
- var ListComponent = typedMemo2(function ListComponent2({
940
+ var ListComponent = typedMemo(function ListComponent2({
901
941
  canRender,
902
942
  style,
903
943
  contentContainerStyle,
@@ -927,12 +967,14 @@ var ListComponent = typedMemo2(function ListComponent2({
927
967
  }) {
928
968
  const ctx = useStateContext();
929
969
  const maintainVisibleContentPosition = ctx.state.props.maintainVisibleContentPosition;
930
- const ScrollComponent = renderScrollComponent ? useMemo(
931
- () => React2.forwardRef(
970
+ const ScrollComponent = useMemo(() => {
971
+ if (!renderScrollComponent) {
972
+ return ListComponentScrollView;
973
+ }
974
+ return React2.forwardRef(
932
975
  (props, ref) => renderScrollComponent({ ...props, ref })
933
- ),
934
- [renderScrollComponent]
935
- ) : ListComponentScrollView;
976
+ );
977
+ }, [renderScrollComponent]);
936
978
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
937
979
  useLayoutEffect(() => {
938
980
  if (!ListHeaderComponent) {
@@ -992,183 +1034,90 @@ var ListComponent = typedMemo2(function ListComponent2({
992
1034
  }
993
1035
  ),
994
1036
  ListFooterComponent && /* @__PURE__ */ React2.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)),
1037
+ Platform2.OS === "web" && /* @__PURE__ */ React2.createElement(WebAnchoredEndSpace, { horizontal }),
995
1038
  IS_DEV && ENABLE_DEVMODE
996
1039
  );
997
1040
  });
998
-
999
- // src/core/calculateOffsetForIndex.ts
1000
- function calculateOffsetForIndex(ctx, index) {
1001
- const state = ctx.state;
1002
- return index !== void 0 ? state.positions[index] || 0 : 0;
1041
+ var WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH = 100;
1042
+ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
1043
+ var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
1044
+ function useDevChecksImpl(props) {
1045
+ const ctx = useStateContext();
1046
+ const { childrenMode, keyExtractor, renderScrollComponent, stickyHeaderIndices, stickyIndices, useWindowScroll } = props;
1047
+ useEffect(() => {
1048
+ if (stickyIndices && !stickyHeaderIndices) {
1049
+ warnDevOnce(
1050
+ "stickyIndices",
1051
+ "stickyIndices has been renamed to stickyHeaderIndices. Please update your props to use stickyHeaderIndices."
1052
+ );
1053
+ }
1054
+ }, [stickyHeaderIndices, stickyIndices]);
1055
+ useEffect(() => {
1056
+ if (useWindowScroll && renderScrollComponent) {
1057
+ warnDevOnce(
1058
+ "useWindowScrollRenderScrollComponent",
1059
+ "useWindowScroll is not supported when renderScrollComponent is provided."
1060
+ );
1061
+ }
1062
+ }, [renderScrollComponent, useWindowScroll]);
1063
+ useEffect(() => {
1064
+ if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
1065
+ warnDevOnce(
1066
+ "keyExtractor",
1067
+ "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior."
1068
+ );
1069
+ }
1070
+ }, [childrenMode, ctx, keyExtractor]);
1071
+ useEffect(() => {
1072
+ const state = ctx.state;
1073
+ const dataLength = state.props.data.length;
1074
+ const useWindowScrollResolved = state.props.useWindowScroll;
1075
+ if (Platform2.OS !== "web" || useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
1076
+ return;
1077
+ }
1078
+ const warnIfUnboundedOuterSize = () => {
1079
+ const readyToRender = peek$(ctx, "readyToRender");
1080
+ const numContainers = peek$(ctx, "numContainers") || 0;
1081
+ const totalSize = peek$(ctx, "totalSize") || 0;
1082
+ const scrollLength = ctx.state.scrollLength || 0;
1083
+ if (!readyToRender || totalSize <= 0 || scrollLength <= 0) {
1084
+ return;
1085
+ }
1086
+ const rendersAlmostEverything = numContainers >= Math.ceil(dataLength * WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO);
1087
+ const viewportMatchesContent = scrollLength >= totalSize * WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO;
1088
+ if (rendersAlmostEverything && viewportMatchesContent) {
1089
+ warnDevOnce(
1090
+ "webUnboundedOuterSize",
1091
+ "LegendList appears to have an unbounded outer height on web, so virtualization is effectively disabled. Set a bounded height or flex: 1 on the list container, or use useWindowScroll."
1092
+ );
1093
+ }
1094
+ };
1095
+ warnIfUnboundedOuterSize();
1096
+ const unsubscribe = [
1097
+ listen$(ctx, "numContainers", warnIfUnboundedOuterSize),
1098
+ listen$(ctx, "readyToRender", warnIfUnboundedOuterSize),
1099
+ listen$(ctx, "totalSize", warnIfUnboundedOuterSize)
1100
+ ];
1101
+ return () => {
1102
+ for (const unsub of unsubscribe) {
1103
+ unsub();
1104
+ }
1105
+ };
1106
+ }, [ctx]);
1003
1107
  }
1004
-
1005
- // src/core/getTopOffsetAdjustment.ts
1006
- function getTopOffsetAdjustment(ctx) {
1007
- return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
1108
+ function useDevChecksNoop(_props) {
1008
1109
  }
1110
+ var useDevChecks = IS_DEV ? useDevChecksImpl : useDevChecksNoop;
1009
1111
 
1010
- // src/utils/getId.ts
1011
- function getId(state, index) {
1012
- const { data, keyExtractor } = state.props;
1013
- if (!data) {
1014
- return "";
1015
- }
1016
- const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
1017
- const id = ret;
1018
- state.idCache[index] = id;
1019
- return id;
1020
- }
1021
-
1022
- // src/core/addTotalSize.ts
1023
- function addTotalSize(ctx, key, add) {
1024
- const state = ctx.state;
1025
- const prevTotalSize = state.totalSize;
1026
- let totalSize = state.totalSize;
1027
- if (key === null) {
1028
- totalSize = add;
1029
- if (state.timeoutSetPaddingTop) {
1030
- clearTimeout(state.timeoutSetPaddingTop);
1031
- state.timeoutSetPaddingTop = void 0;
1032
- }
1033
- } else {
1034
- totalSize += add;
1035
- }
1036
- if (prevTotalSize !== totalSize) {
1037
- if (!IsNewArchitecture && state.initialScroll && totalSize < prevTotalSize) {
1038
- state.pendingTotalSize = totalSize;
1039
- } else {
1040
- state.pendingTotalSize = void 0;
1041
- state.totalSize = totalSize;
1042
- set$(ctx, "totalSize", totalSize);
1043
- }
1044
- }
1045
- }
1046
-
1047
- // src/core/setSize.ts
1048
- function setSize(ctx, itemKey, size) {
1049
- const state = ctx.state;
1050
- const { sizes } = state;
1051
- const previousSize = sizes.get(itemKey);
1052
- const diff = previousSize !== void 0 ? size - previousSize : size;
1053
- if (diff !== 0) {
1054
- addTotalSize(ctx, itemKey, diff);
1055
- }
1056
- sizes.set(itemKey, size);
1057
- }
1058
-
1059
- // src/utils/getItemSize.ts
1060
- function getItemSize(ctx, key, index, data, useAverageSize, preferCachedSize) {
1061
- var _a3, _b;
1062
- const state = ctx.state;
1063
- const {
1064
- sizesKnown,
1065
- sizes,
1066
- averageSizes,
1067
- props: { estimatedItemSize, getEstimatedItemSize, getFixedItemSize, getItemType },
1068
- scrollingTo
1069
- } = state;
1070
- const sizeKnown = sizesKnown.get(key);
1071
- if (sizeKnown !== void 0) {
1072
- return sizeKnown;
1073
- }
1074
- let size;
1075
- if (preferCachedSize) {
1076
- const cachedSize = sizes.get(key);
1077
- if (cachedSize !== void 0) {
1078
- return cachedSize;
1079
- }
1080
- }
1081
- const itemType = getItemType ? (_a3 = getItemType(data, index)) != null ? _a3 : "" : "";
1082
- if (getFixedItemSize) {
1083
- size = getFixedItemSize(data, index, itemType);
1084
- if (size !== void 0) {
1085
- sizesKnown.set(key, size);
1086
- }
1087
- }
1088
- if (size === void 0 && useAverageSize && sizeKnown === void 0 && !scrollingTo) {
1089
- const averageSizeForType = (_b = averageSizes[itemType]) == null ? void 0 : _b.avg;
1090
- if (averageSizeForType !== void 0) {
1091
- size = roundSize(averageSizeForType);
1092
- }
1093
- }
1094
- if (size === void 0) {
1095
- size = sizes.get(key);
1096
- if (size !== void 0) {
1097
- return size;
1098
- }
1099
- }
1100
- if (size === void 0) {
1101
- size = getEstimatedItemSize ? getEstimatedItemSize(data, index, itemType) : estimatedItemSize;
1102
- }
1103
- setSize(ctx, key, size);
1104
- return size;
1105
- }
1106
- function getItemSizeAtIndex(ctx, index) {
1107
- if (index === void 0 || index < 0) {
1108
- return void 0;
1109
- }
1110
- const targetId = getId(ctx.state, index);
1111
- return getItemSize(ctx, targetId, index, ctx.state.props.data[index]);
1112
- }
1113
-
1114
- // src/core/calculateOffsetWithOffsetPosition.ts
1115
- function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1116
- var _a3;
1117
- const state = ctx.state;
1118
- const { index, viewOffset, viewPosition } = params;
1119
- let offset = offsetParam;
1120
- if (viewOffset) {
1121
- offset -= viewOffset;
1122
- }
1123
- if (index !== void 0) {
1124
- const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1125
- if (topOffsetAdjustment) {
1126
- offset += topOffsetAdjustment;
1127
- }
1128
- }
1129
- if (viewPosition !== void 0 && index !== void 0) {
1130
- const dataLength = state.props.data.length;
1131
- if (dataLength === 0) {
1132
- return offset;
1133
- }
1134
- const isOutOfBounds = index < 0 || index >= dataLength;
1135
- const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
1136
- const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1137
- const trailingInset = getContentInsetEnd(state);
1138
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1139
- if (!isOutOfBounds && index === state.props.data.length - 1) {
1140
- const footerSize = peek$(ctx, "footerSize") || 0;
1141
- offset += footerSize;
1142
- }
1143
- }
1144
- return offset;
1145
- }
1146
-
1147
- // src/core/clampScrollOffset.ts
1148
- function clampScrollOffset(ctx, offset, scrollTarget) {
1149
- const state = ctx.state;
1150
- const contentSize = getContentSize(ctx);
1151
- let clampedOffset = offset;
1152
- if (Number.isFinite(contentSize) && Number.isFinite(state.scrollLength) && (Platform2.OS !== "android" || state.lastLayout)) {
1153
- const baseMaxOffset = Math.max(0, contentSize - state.scrollLength);
1154
- const viewOffset = scrollTarget == null ? void 0 : scrollTarget.viewOffset;
1155
- const extraEndOffset = typeof viewOffset === "number" && viewOffset < 0 ? -viewOffset : 0;
1156
- const maxOffset = baseMaxOffset + extraEndOffset;
1157
- clampedOffset = Math.min(offset, maxOffset);
1158
- }
1159
- clampedOffset = Math.max(0, clampedOffset);
1160
- return clampedOffset;
1161
- }
1162
-
1163
- // src/core/deferredPublicOnScroll.ts
1164
- function withResolvedContentOffset(state, event, resolvedOffset) {
1165
- return {
1166
- ...event,
1167
- nativeEvent: {
1168
- ...event.nativeEvent,
1169
- contentOffset: state.props.horizontal ? { x: resolvedOffset, y: 0 } : { x: 0, y: resolvedOffset }
1170
- }
1171
- };
1112
+ // src/core/deferredPublicOnScroll.ts
1113
+ function withResolvedContentOffset(state, event, resolvedOffset) {
1114
+ return {
1115
+ ...event,
1116
+ nativeEvent: {
1117
+ ...event.nativeEvent,
1118
+ contentOffset: state.props.horizontal ? { x: resolvedOffset, y: 0 } : { x: 0, y: resolvedOffset }
1119
+ }
1120
+ };
1172
1121
  }
1173
1122
  function releaseDeferredPublicOnScroll(ctx, resolvedOffset) {
1174
1123
  var _a3, _b, _c, _d;
@@ -1247,63 +1196,489 @@ var initialScrollCompletion = {
1247
1196
  if (!state.initialScrollSession) {
1248
1197
  return;
1249
1198
  }
1250
- const completion = ensureInitialScrollSessionCompletion(state, state.initialScrollSession.kind);
1251
- completion.didDispatchNativeScroll = void 0;
1252
- completion.didRetrySilentInitialScroll = void 0;
1199
+ const completion = ensureInitialScrollSessionCompletion(state, state.initialScrollSession.kind);
1200
+ completion.didDispatchNativeScroll = void 0;
1201
+ completion.didRetrySilentInitialScroll = void 0;
1202
+ }
1203
+ };
1204
+ var initialScrollWatchdog = {
1205
+ clear(state) {
1206
+ initialScrollWatchdog.set(state, void 0);
1207
+ },
1208
+ didObserveProgress(newScroll, watchdog) {
1209
+ const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
1210
+ const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
1211
+ return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET || nextDistance + INITIAL_SCROLL_MIN_TARGET_OFFSET < previousDistance;
1212
+ },
1213
+ get(state) {
1214
+ var _a3, _b;
1215
+ return (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.completion) == null ? void 0 : _b.watchdog;
1216
+ },
1217
+ hasNonZeroTargetOffset(targetOffset) {
1218
+ return targetOffset !== void 0 && targetOffset > INITIAL_SCROLL_MIN_TARGET_OFFSET;
1219
+ },
1220
+ isAtZeroTargetOffset(targetOffset) {
1221
+ return targetOffset <= INITIAL_SCROLL_MIN_TARGET_OFFSET;
1222
+ },
1223
+ set(state, watchdog) {
1224
+ var _a3, _b;
1225
+ if (!watchdog && !((_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.completion) == null ? void 0 : _b.watchdog)) {
1226
+ return;
1227
+ }
1228
+ const completion = ensureInitialScrollSessionCompletion(state);
1229
+ completion.watchdog = watchdog ? {
1230
+ startScroll: watchdog.startScroll,
1231
+ targetOffset: watchdog.targetOffset
1232
+ } : void 0;
1233
+ }
1234
+ };
1235
+ function setInitialScrollSession(state, options = {}) {
1236
+ var _a3, _b, _c;
1237
+ const existingSession = state.initialScrollSession;
1238
+ const kind = (_a3 = options.kind) != null ? _a3 : existingSession == null ? void 0 : existingSession.kind;
1239
+ const completion = existingSession == null ? void 0 : existingSession.completion;
1240
+ const hasBootstrapOverride = Object.hasOwn(options, "bootstrap");
1241
+ const bootstrap = kind === "bootstrap" ? hasBootstrapOverride ? options.bootstrap : (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0 : void 0;
1242
+ if (!kind) {
1243
+ return clearInitialScrollSession(state);
1244
+ }
1245
+ if (!state.initialScroll && !bootstrap && !hasInitialScrollSessionCompletion(completion)) {
1246
+ return clearInitialScrollSession(state);
1247
+ }
1248
+ const previousDataLength = (_c = (_b = options.previousDataLength) != null ? _b : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _c : 0;
1249
+ state.initialScrollSession = createInitialScrollSession({
1250
+ bootstrap,
1251
+ completion,
1252
+ kind,
1253
+ previousDataLength
1254
+ });
1255
+ return state.initialScrollSession;
1256
+ }
1257
+
1258
+ // src/utils/checkThreshold.ts
1259
+ var HYSTERESIS_MULTIPLIER = 1.3;
1260
+ var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, context, onReached, setSnapshot, allowReentryOnChange) => {
1261
+ const absDistance = Math.abs(distance);
1262
+ const within = atThreshold || threshold > 0 && absDistance <= threshold;
1263
+ const updateSnapshot = () => {
1264
+ setSnapshot({
1265
+ atThreshold,
1266
+ contentSize: context.contentSize,
1267
+ dataLength: context.dataLength,
1268
+ scrollPosition: context.scrollPosition
1269
+ });
1270
+ };
1271
+ if (!wasReached) {
1272
+ if (!within) {
1273
+ return false;
1274
+ }
1275
+ onReached(distance);
1276
+ updateSnapshot();
1277
+ return true;
1278
+ }
1279
+ const reset = !atThreshold && threshold > 0 && absDistance >= threshold * HYSTERESIS_MULTIPLIER || !atThreshold && threshold <= 0 && absDistance > 0;
1280
+ if (reset) {
1281
+ setSnapshot(void 0);
1282
+ return false;
1283
+ }
1284
+ if (within) {
1285
+ const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
1286
+ if (changed) {
1287
+ if (allowReentryOnChange) {
1288
+ onReached(distance);
1289
+ }
1290
+ updateSnapshot();
1291
+ }
1292
+ }
1293
+ return true;
1294
+ };
1295
+
1296
+ // src/utils/hasActiveInitialScroll.ts
1297
+ function hasActiveInitialScroll(state) {
1298
+ return !!(state == null ? void 0 : state.initialScroll) && !state.didFinishInitialScroll;
1299
+ }
1300
+
1301
+ // src/utils/checkAtBottom.ts
1302
+ function checkAtBottom(ctx) {
1303
+ var _a3;
1304
+ const state = ctx.state;
1305
+ if (!state) {
1306
+ return;
1307
+ }
1308
+ const {
1309
+ queuedInitialLayout,
1310
+ scrollLength,
1311
+ scroll,
1312
+ maintainingScrollAtEnd,
1313
+ props: { maintainScrollAtEndThreshold, onEndReachedThreshold }
1314
+ } = state;
1315
+ const contentSize = getContentSize(ctx);
1316
+ if (contentSize > 0 && queuedInitialLayout) {
1317
+ const insetEnd = getContentInsetEnd(ctx);
1318
+ const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
1319
+ const isContentLess = contentSize < scrollLength;
1320
+ set$(ctx, "isAtEnd", isContentLess || distanceFromEnd <= EDGE_POSITION_EPSILON);
1321
+ set$(ctx, "isNearEnd", isContentLess || distanceFromEnd <= onEndReachedThreshold * scrollLength);
1322
+ set$(
1323
+ ctx,
1324
+ "isWithinMaintainScrollAtEndThreshold",
1325
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
1326
+ );
1327
+ const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
1328
+ if (!shouldSkipThresholdChecks) {
1329
+ state.isEndReached = checkThreshold(
1330
+ distanceFromEnd,
1331
+ isContentLess,
1332
+ onEndReachedThreshold * scrollLength,
1333
+ state.isEndReached,
1334
+ state.endReachedSnapshot,
1335
+ {
1336
+ contentSize,
1337
+ dataLength: (_a3 = state.props.data) == null ? void 0 : _a3.length,
1338
+ scrollPosition: scroll
1339
+ },
1340
+ (distance) => {
1341
+ var _a4, _b;
1342
+ return (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
1343
+ },
1344
+ (snapshot) => {
1345
+ state.endReachedSnapshot = snapshot;
1346
+ },
1347
+ true
1348
+ );
1349
+ }
1350
+ }
1351
+ }
1352
+
1353
+ // src/utils/checkAtTop.ts
1354
+ function checkAtTop(ctx) {
1355
+ const state = ctx == null ? void 0 : ctx.state;
1356
+ if (!state) {
1357
+ return;
1358
+ }
1359
+ const {
1360
+ dataChangeEpoch,
1361
+ isStartReached,
1362
+ props: { data, onStartReachedThreshold },
1363
+ scroll,
1364
+ scrollLength,
1365
+ startReachedSnapshot,
1366
+ startReachedSnapshotDataChangeEpoch,
1367
+ totalSize
1368
+ } = state;
1369
+ const dataLength = data.length;
1370
+ const threshold = onStartReachedThreshold * scrollLength;
1371
+ const dataChanged = startReachedSnapshotDataChangeEpoch !== dataChangeEpoch;
1372
+ const withinThreshold = threshold > 0 && Math.abs(scroll) <= threshold;
1373
+ const allowReentryOnDataChange = !!isStartReached && withinThreshold && !!dataChanged && !isInMVCPActiveMode(state);
1374
+ if (isStartReached && threshold > 0 && scroll > threshold && startReachedSnapshot && (dataChanged || startReachedSnapshot.contentSize !== totalSize || startReachedSnapshot.dataLength !== dataLength)) {
1375
+ state.isStartReached = false;
1376
+ state.startReachedSnapshot = void 0;
1377
+ state.startReachedSnapshotDataChangeEpoch = void 0;
1378
+ }
1379
+ set$(ctx, "isAtStart", scroll <= EDGE_POSITION_EPSILON);
1380
+ set$(ctx, "isNearStart", scroll <= threshold);
1381
+ const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || !!state.scrollingTo;
1382
+ const shouldDeferDataChangeRefire = isStartReached && withinThreshold && dataChanged && !allowReentryOnDataChange;
1383
+ if (!shouldSkipThresholdChecks && !shouldDeferDataChangeRefire) {
1384
+ state.isStartReached = checkThreshold(
1385
+ scroll,
1386
+ false,
1387
+ threshold,
1388
+ state.isStartReached,
1389
+ allowReentryOnDataChange ? void 0 : startReachedSnapshot,
1390
+ {
1391
+ contentSize: totalSize,
1392
+ dataLength,
1393
+ scrollPosition: scroll
1394
+ },
1395
+ (distance) => {
1396
+ var _a3, _b;
1397
+ return (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
1398
+ },
1399
+ (snapshot) => {
1400
+ state.startReachedSnapshot = snapshot;
1401
+ state.startReachedSnapshotDataChangeEpoch = snapshot ? dataChangeEpoch : void 0;
1402
+ },
1403
+ allowReentryOnDataChange
1404
+ );
1405
+ }
1406
+ }
1407
+
1408
+ // src/utils/checkThresholds.ts
1409
+ function checkThresholds(ctx) {
1410
+ checkAtBottom(ctx);
1411
+ checkAtTop(ctx);
1412
+ }
1413
+
1414
+ // src/core/recalculateSettledScroll.ts
1415
+ function recalculateSettledScroll(ctx) {
1416
+ var _a3, _b;
1417
+ const state = ctx.state;
1418
+ if ((_a3 = state.props) == null ? void 0 : _a3.data) {
1419
+ (_b = state.triggerCalculateItemsInView) == null ? void 0 : _b.call(state, { forceFullItemPositions: true });
1420
+ }
1421
+ checkThresholds(ctx);
1422
+ }
1423
+
1424
+ // src/utils/setInitialRenderState.ts
1425
+ function setInitialRenderState(ctx, {
1426
+ didLayout,
1427
+ didInitialScroll
1428
+ }) {
1429
+ const { state } = ctx;
1430
+ const {
1431
+ loadStartTime,
1432
+ props: { onLoad }
1433
+ } = state;
1434
+ if (didLayout) {
1435
+ state.didContainersLayout = true;
1436
+ }
1437
+ if (didInitialScroll) {
1438
+ state.didFinishInitialScroll = true;
1439
+ }
1440
+ const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll);
1441
+ if (isReadyToRender && !peek$(ctx, "readyToRender")) {
1442
+ set$(ctx, "readyToRender", true);
1443
+ if (onLoad) {
1444
+ onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
1445
+ }
1446
+ }
1447
+ }
1448
+
1449
+ // src/core/finishInitialScroll.ts
1450
+ var PRESERVED_INITIAL_SCROLL_FALLBACK_CLEAR_DELAY_MS = 2e3;
1451
+ function syncInitialScrollOffset(state, offset) {
1452
+ state.scroll = offset;
1453
+ state.scrollPending = offset;
1454
+ state.scrollPrev = offset;
1455
+ }
1456
+ function clearPreservedInitialScrollTargetTimeout(state) {
1457
+ if (state.timeoutPreservedInitialScrollClear !== void 0) {
1458
+ clearTimeout(state.timeoutPreservedInitialScrollClear);
1459
+ state.timeoutPreservedInitialScrollClear = void 0;
1460
+ }
1461
+ }
1462
+ function clearPreservedInitialScrollTarget(state) {
1463
+ clearPreservedInitialScrollTargetTimeout(state);
1464
+ state.clearPreservedInitialScrollOnNextFinish = void 0;
1465
+ state.initialScroll = void 0;
1466
+ setInitialScrollSession(state);
1467
+ }
1468
+ function finishInitialScroll(ctx, options) {
1469
+ var _a3, _b, _c;
1470
+ const state = ctx.state;
1471
+ if ((options == null ? void 0 : options.resolvedOffset) !== void 0) {
1472
+ syncInitialScrollOffset(state, options.resolvedOffset);
1473
+ } else if ((options == null ? void 0 : options.syncObservedOffset) && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
1474
+ const observedOffset = (_c = (_b = state.refScroller.current) == null ? void 0 : _b.getCurrentScrollOffset) == null ? void 0 : _c.call(_b);
1475
+ if (typeof observedOffset === "number" && Number.isFinite(observedOffset)) {
1476
+ syncInitialScrollOffset(state, observedOffset);
1477
+ }
1478
+ }
1479
+ const complete = () => {
1480
+ var _a4, _b2, _c2, _d, _e;
1481
+ const shouldReleaseDeferredPublicOnScroll = Platform2.OS === "web" && ((_a4 = state.initialScrollSession) == null ? void 0 : _a4.kind) === "bootstrap";
1482
+ const finalScrollOffset = (_d = (_c2 = (_b2 = options == null ? void 0 : options.resolvedOffset) != null ? _b2 : state.scrollPending) != null ? _c2 : state.scroll) != null ? _d : 0;
1483
+ initialScrollWatchdog.clear(state);
1484
+ if ((options == null ? void 0 : options.preserveTarget) && state.initialScroll) {
1485
+ state.clearPreservedInitialScrollOnNextFinish = void 0;
1486
+ setInitialScrollSession(state);
1487
+ clearPreservedInitialScrollTargetTimeout(state);
1488
+ if (options == null ? void 0 : options.schedulePreservedTargetClear) {
1489
+ state.timeoutPreservedInitialScrollClear = setTimeout(() => {
1490
+ var _a5;
1491
+ state.timeoutPreservedInitialScrollClear = void 0;
1492
+ if (!state.didFinishInitialScroll || ((_a5 = state.scrollingTo) == null ? void 0 : _a5.isInitialScroll) || !state.initialScroll) {
1493
+ return;
1494
+ }
1495
+ clearPreservedInitialScrollTarget(state);
1496
+ }, PRESERVED_INITIAL_SCROLL_FALLBACK_CLEAR_DELAY_MS);
1497
+ }
1498
+ } else {
1499
+ clearPreservedInitialScrollTarget(state);
1500
+ }
1501
+ if (options == null ? void 0 : options.recalculateItems) {
1502
+ recalculateSettledScroll(ctx);
1503
+ }
1504
+ setInitialRenderState(ctx, { didInitialScroll: true });
1505
+ if (shouldReleaseDeferredPublicOnScroll) {
1506
+ releaseDeferredPublicOnScroll(ctx, finalScrollOffset);
1507
+ }
1508
+ (_e = options == null ? void 0 : options.onFinished) == null ? void 0 : _e.call(options);
1509
+ };
1510
+ if (options == null ? void 0 : options.waitForCompletionFrame) {
1511
+ requestAnimationFrame(complete);
1512
+ return;
1513
+ }
1514
+ complete();
1515
+ }
1516
+
1517
+ // src/core/calculateOffsetForIndex.ts
1518
+ function calculateOffsetForIndex(ctx, index) {
1519
+ const state = ctx.state;
1520
+ return index !== void 0 ? state.positions[index] || 0 : 0;
1521
+ }
1522
+
1523
+ // src/core/getTopOffsetAdjustment.ts
1524
+ function getTopOffsetAdjustment(ctx) {
1525
+ return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
1526
+ }
1527
+
1528
+ // src/utils/getId.ts
1529
+ function getId(state, index) {
1530
+ const { data, keyExtractor } = state.props;
1531
+ if (!data) {
1532
+ return "";
1533
+ }
1534
+ const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
1535
+ const id = ret;
1536
+ state.idCache[index] = id;
1537
+ return id;
1538
+ }
1539
+
1540
+ // src/core/addTotalSize.ts
1541
+ function addTotalSize(ctx, key, add) {
1542
+ const state = ctx.state;
1543
+ const prevTotalSize = state.totalSize;
1544
+ let totalSize = state.totalSize;
1545
+ if (key === null) {
1546
+ totalSize = add;
1547
+ if (state.timeoutSetPaddingTop) {
1548
+ clearTimeout(state.timeoutSetPaddingTop);
1549
+ state.timeoutSetPaddingTop = void 0;
1550
+ }
1551
+ } else {
1552
+ totalSize += add;
1553
+ }
1554
+ if (prevTotalSize !== totalSize) {
1555
+ if (!IsNewArchitecture && state.initialScroll && totalSize < prevTotalSize) {
1556
+ state.pendingTotalSize = totalSize;
1557
+ } else {
1558
+ state.pendingTotalSize = void 0;
1559
+ state.totalSize = totalSize;
1560
+ set$(ctx, "totalSize", totalSize);
1561
+ }
1562
+ }
1563
+ }
1564
+
1565
+ // src/core/setSize.ts
1566
+ function setSize(ctx, itemKey, size) {
1567
+ const state = ctx.state;
1568
+ const { sizes } = state;
1569
+ const previousSize = sizes.get(itemKey);
1570
+ const diff = previousSize !== void 0 ? size - previousSize : size;
1571
+ if (diff !== 0) {
1572
+ addTotalSize(ctx, itemKey, diff);
1573
+ }
1574
+ sizes.set(itemKey, size);
1575
+ }
1576
+
1577
+ // src/utils/getItemSize.ts
1578
+ function getItemSize(ctx, key, index, data, useAverageSize, preferCachedSize) {
1579
+ var _a3, _b, _c;
1580
+ const state = ctx.state;
1581
+ const {
1582
+ sizesKnown,
1583
+ sizes,
1584
+ averageSizes,
1585
+ props: { estimatedItemSize, getEstimatedItemSize, getFixedItemSize, getItemType },
1586
+ scrollingTo
1587
+ } = state;
1588
+ const sizeKnown = sizesKnown.get(key);
1589
+ if (sizeKnown !== void 0) {
1590
+ return sizeKnown;
1591
+ }
1592
+ let size;
1593
+ const renderedSize = sizes.get(key);
1594
+ if (preferCachedSize) {
1595
+ if (renderedSize !== void 0) {
1596
+ return renderedSize;
1597
+ }
1598
+ }
1599
+ const itemType = getItemType ? (_a3 = getItemType(data, index)) != null ? _a3 : "" : "";
1600
+ if (getFixedItemSize) {
1601
+ size = getFixedItemSize(data, index, itemType);
1602
+ if (size !== void 0) {
1603
+ sizesKnown.set(key, size);
1604
+ }
1605
+ }
1606
+ if (size === void 0 && useAverageSize && sizeKnown === void 0 && !scrollingTo) {
1607
+ const averageSizeForType = (_b = averageSizes[itemType]) == null ? void 0 : _b.avg;
1608
+ if (averageSizeForType !== void 0) {
1609
+ size = roundSize(averageSizeForType);
1610
+ }
1253
1611
  }
1254
- };
1255
- var initialScrollWatchdog = {
1256
- clear(state) {
1257
- initialScrollWatchdog.set(state, void 0);
1258
- },
1259
- didObserveProgress(newScroll, watchdog) {
1260
- const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
1261
- const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
1262
- return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET || nextDistance + INITIAL_SCROLL_MIN_TARGET_OFFSET < previousDistance;
1263
- },
1264
- get(state) {
1265
- var _a3, _b;
1266
- return (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.completion) == null ? void 0 : _b.watchdog;
1267
- },
1268
- hasNonZeroTargetOffset(targetOffset) {
1269
- return targetOffset !== void 0 && targetOffset > INITIAL_SCROLL_MIN_TARGET_OFFSET;
1270
- },
1271
- isAtZeroTargetOffset(targetOffset) {
1272
- return targetOffset <= INITIAL_SCROLL_MIN_TARGET_OFFSET;
1273
- },
1274
- set(state, watchdog) {
1275
- var _a3, _b;
1276
- if (!watchdog && !((_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.completion) == null ? void 0 : _b.watchdog)) {
1277
- return;
1612
+ if (size === void 0 && renderedSize !== void 0) {
1613
+ return renderedSize;
1614
+ }
1615
+ if (size === void 0 && useAverageSize && sizeKnown === void 0 && scrollingTo) {
1616
+ const averageSizeForType = (_c = scrollingTo.averageSizeSnapshot) == null ? void 0 : _c[itemType];
1617
+ if (averageSizeForType !== void 0) {
1618
+ size = roundSize(averageSizeForType);
1278
1619
  }
1279
- const completion = ensureInitialScrollSessionCompletion(state);
1280
- completion.watchdog = watchdog ? {
1281
- startScroll: watchdog.startScroll,
1282
- targetOffset: watchdog.targetOffset
1283
- } : void 0;
1284
1620
  }
1285
- };
1286
- function setInitialScrollSession(state, options = {}) {
1287
- var _a3, _b, _c;
1288
- const existingSession = state.initialScrollSession;
1289
- const kind = (_a3 = options.kind) != null ? _a3 : existingSession == null ? void 0 : existingSession.kind;
1290
- const completion = existingSession == null ? void 0 : existingSession.completion;
1291
- const hasBootstrapOverride = Object.hasOwn(options, "bootstrap");
1292
- const bootstrap = kind === "bootstrap" ? hasBootstrapOverride ? options.bootstrap : (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0 : void 0;
1293
- if (!kind) {
1294
- return clearInitialScrollSession(state);
1621
+ if (size === void 0) {
1622
+ size = getEstimatedItemSize ? getEstimatedItemSize(data, index, itemType) : estimatedItemSize;
1295
1623
  }
1296
- if (!state.initialScroll && !bootstrap && !hasInitialScrollSessionCompletion(completion)) {
1297
- return clearInitialScrollSession(state);
1624
+ setSize(ctx, key, size);
1625
+ return size;
1626
+ }
1627
+ function getItemSizeAtIndex(ctx, index) {
1628
+ if (index === void 0 || index < 0) {
1629
+ return void 0;
1298
1630
  }
1299
- const previousDataLength = (_c = (_b = options.previousDataLength) != null ? _b : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _c : 0;
1300
- state.initialScrollSession = createInitialScrollSession({
1301
- bootstrap,
1302
- completion,
1303
- kind,
1304
- previousDataLength
1305
- });
1306
- return state.initialScrollSession;
1631
+ const targetId = getId(ctx.state, index);
1632
+ return getItemSize(ctx, targetId, index, ctx.state.props.data[index]);
1633
+ }
1634
+
1635
+ // src/core/calculateOffsetWithOffsetPosition.ts
1636
+ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1637
+ var _a3;
1638
+ const state = ctx.state;
1639
+ const { index, viewOffset, viewPosition } = params;
1640
+ let offset = offsetParam;
1641
+ if (viewOffset) {
1642
+ offset -= viewOffset;
1643
+ }
1644
+ if (index !== void 0) {
1645
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1646
+ if (topOffsetAdjustment) {
1647
+ offset += topOffsetAdjustment;
1648
+ }
1649
+ }
1650
+ if (viewPosition !== void 0 && index !== void 0) {
1651
+ const dataLength = state.props.data.length;
1652
+ if (dataLength === 0) {
1653
+ return offset;
1654
+ }
1655
+ const isOutOfBounds = index < 0 || index >= dataLength;
1656
+ const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
1657
+ const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1658
+ const trailingInset = getContentInsetEnd(ctx);
1659
+ offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1660
+ if (!isOutOfBounds && index === state.props.data.length - 1) {
1661
+ const footerSize = peek$(ctx, "footerSize") || 0;
1662
+ offset += footerSize;
1663
+ }
1664
+ }
1665
+ return offset;
1666
+ }
1667
+
1668
+ // src/core/clampScrollOffset.ts
1669
+ function clampScrollOffset(ctx, offset, scrollTarget) {
1670
+ const state = ctx.state;
1671
+ const contentSize = getContentSize(ctx);
1672
+ let clampedOffset = offset;
1673
+ if (Number.isFinite(contentSize) && Number.isFinite(state.scrollLength) && (Platform2.OS !== "android" || state.lastLayout)) {
1674
+ const baseMaxOffset = Math.max(0, contentSize - state.scrollLength);
1675
+ const viewOffset = scrollTarget == null ? void 0 : scrollTarget.viewOffset;
1676
+ const extraEndOffset = typeof viewOffset === "number" && viewOffset < 0 ? -viewOffset : 0;
1677
+ const maxOffset = baseMaxOffset + extraEndOffset;
1678
+ clampedOffset = Math.min(offset, maxOffset);
1679
+ }
1680
+ clampedOffset = Math.max(0, clampedOffset);
1681
+ return clampedOffset;
1307
1682
  }
1308
1683
 
1309
1684
  // src/core/finishScrollTo.ts
@@ -1324,15 +1699,20 @@ function finishScrollTo(ctx) {
1324
1699
  }
1325
1700
  if (scrollingTo.isInitialScroll || state.initialScroll) {
1326
1701
  const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
1702
+ const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
1327
1703
  finishInitialScroll(ctx, {
1328
- onFinished: resolvePendingScroll,
1329
- preserveTarget: isOffsetSession && state.props.data.length === 0 || !!scrollingTo.isInitialScroll && !!((_b = state.initialScroll) == null ? void 0 : _b.preserveForFooterLayout),
1704
+ onFinished: () => {
1705
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1706
+ },
1707
+ preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1330
1708
  recalculateItems: true,
1709
+ schedulePreservedTargetClear: shouldPreserveResizeTarget,
1331
1710
  syncObservedOffset: isOffsetSession,
1332
1711
  waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1333
1712
  });
1334
1713
  return;
1335
1714
  }
1715
+ recalculateSettledScroll(ctx);
1336
1716
  resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1337
1717
  }
1338
1718
  }
@@ -1507,6 +1887,17 @@ function doScrollTo(ctx, params) {
1507
1887
  }
1508
1888
 
1509
1889
  // src/core/scrollTo.ts
1890
+ function getAverageSizeSnapshot(state) {
1891
+ if (Object.keys(state.averageSizes).length === 0) {
1892
+ return void 0;
1893
+ }
1894
+ const snapshot = {};
1895
+ for (const itemType in state.averageSizes) {
1896
+ const averages = state.averageSizes[itemType];
1897
+ snapshot[itemType] = averages.avg;
1898
+ }
1899
+ return snapshot;
1900
+ }
1510
1901
  function syncInitialScrollNativeWatchdog(state, options) {
1511
1902
  var _a3;
1512
1903
  const { isInitialScroll, requestedOffset, targetOffset } = options;
@@ -1554,8 +1945,10 @@ function scrollTo(ctx, params) {
1554
1945
  if (isInitialScroll) {
1555
1946
  initialScrollCompletion.resetFlags(state);
1556
1947
  }
1948
+ const averageSizeSnapshot = getAverageSizeSnapshot(state);
1557
1949
  state.scrollingTo = {
1558
1950
  ...scrollTarget,
1951
+ ...averageSizeSnapshot ? { averageSizeSnapshot } : {},
1559
1952
  targetOffset,
1560
1953
  waitForInitialScrollCompletionFrame
1561
1954
  };
@@ -1609,183 +2002,10 @@ function scrollToIndex(ctx, {
1609
2002
  offset: firstIndexOffset,
1610
2003
  viewOffset,
1611
2004
  viewPosition: viewPosition != null ? viewPosition : 0
1612
- });
1613
- }
1614
-
1615
- // src/utils/checkThreshold.ts
1616
- var HYSTERESIS_MULTIPLIER = 1.3;
1617
- var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, context, onReached, setSnapshot, allowReentryOnChange) => {
1618
- const absDistance = Math.abs(distance);
1619
- const within = atThreshold || threshold > 0 && absDistance <= threshold;
1620
- const updateSnapshot = () => {
1621
- setSnapshot({
1622
- atThreshold,
1623
- contentSize: context.contentSize,
1624
- dataLength: context.dataLength,
1625
- scrollPosition: context.scrollPosition
1626
- });
1627
- };
1628
- if (!wasReached) {
1629
- if (!within) {
1630
- return false;
1631
- }
1632
- onReached(distance);
1633
- updateSnapshot();
1634
- return true;
1635
- }
1636
- const reset = !atThreshold && threshold > 0 && absDistance >= threshold * HYSTERESIS_MULTIPLIER || !atThreshold && threshold <= 0 && absDistance > 0;
1637
- if (reset) {
1638
- setSnapshot(void 0);
1639
- return false;
1640
- }
1641
- if (within) {
1642
- const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
1643
- if (changed) {
1644
- if (allowReentryOnChange) {
1645
- onReached(distance);
1646
- }
1647
- updateSnapshot();
1648
- }
1649
- }
1650
- return true;
1651
- };
1652
-
1653
- // src/utils/checkAtBottom.ts
1654
- function checkAtBottom(ctx) {
1655
- var _a3;
1656
- const state = ctx.state;
1657
- if (!state || state.initialScroll) {
1658
- return;
1659
- }
1660
- const {
1661
- queuedInitialLayout,
1662
- scrollLength,
1663
- scroll,
1664
- maintainingScrollAtEnd,
1665
- props: { maintainScrollAtEndThreshold, onEndReachedThreshold }
1666
- } = state;
1667
- if (state.initialScroll) {
1668
- return;
1669
- }
1670
- const contentSize = getContentSize(ctx);
1671
- if (contentSize > 0 && queuedInitialLayout && !maintainingScrollAtEnd) {
1672
- const insetEnd = getContentInsetEnd(state);
1673
- const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
1674
- const isContentLess = contentSize < scrollLength;
1675
- state.isAtEnd = isContentLess || distanceFromEnd < scrollLength * maintainScrollAtEndThreshold;
1676
- state.isEndReached = checkThreshold(
1677
- distanceFromEnd,
1678
- isContentLess,
1679
- onEndReachedThreshold * scrollLength,
1680
- state.isEndReached,
1681
- state.endReachedSnapshot,
1682
- {
1683
- contentSize,
1684
- dataLength: (_a3 = state.props.data) == null ? void 0 : _a3.length,
1685
- scrollPosition: scroll
1686
- },
1687
- (distance) => {
1688
- var _a4, _b;
1689
- return (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
1690
- },
1691
- (snapshot) => {
1692
- state.endReachedSnapshot = snapshot;
1693
- },
1694
- true
1695
- );
1696
- }
1697
- }
1698
-
1699
- // src/utils/checkAtTop.ts
1700
- function checkAtTop(ctx) {
1701
- const state = ctx == null ? void 0 : ctx.state;
1702
- if (!state || state.initialScroll || state.scrollingTo) {
1703
- return;
1704
- }
1705
- const {
1706
- dataChangeEpoch,
1707
- isStartReached,
1708
- props: { data, onStartReachedThreshold },
1709
- scroll,
1710
- scrollLength,
1711
- startReachedSnapshot,
1712
- startReachedSnapshotDataChangeEpoch,
1713
- totalSize
1714
- } = state;
1715
- const dataLength = data.length;
1716
- const threshold = onStartReachedThreshold * scrollLength;
1717
- const dataChanged = startReachedSnapshotDataChangeEpoch !== dataChangeEpoch;
1718
- const withinThreshold = threshold > 0 && Math.abs(scroll) <= threshold;
1719
- const allowReentryOnDataChange = !!isStartReached && withinThreshold && !!dataChanged && !isInMVCPActiveMode(state);
1720
- if (isStartReached && threshold > 0 && scroll > threshold && startReachedSnapshot && (dataChanged || startReachedSnapshot.contentSize !== totalSize || startReachedSnapshot.dataLength !== dataLength)) {
1721
- state.isStartReached = false;
1722
- state.startReachedSnapshot = void 0;
1723
- state.startReachedSnapshotDataChangeEpoch = void 0;
1724
- }
1725
- state.isAtStart = scroll <= 0;
1726
- if (isStartReached && withinThreshold && dataChanged && !allowReentryOnDataChange) {
1727
- return;
1728
- }
1729
- state.isStartReached = checkThreshold(
1730
- scroll,
1731
- false,
1732
- threshold,
1733
- state.isStartReached,
1734
- allowReentryOnDataChange ? void 0 : startReachedSnapshot,
1735
- {
1736
- contentSize: totalSize,
1737
- dataLength,
1738
- scrollPosition: scroll
1739
- },
1740
- (distance) => {
1741
- var _a3, _b;
1742
- return (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
1743
- },
1744
- (snapshot) => {
1745
- state.startReachedSnapshot = snapshot;
1746
- state.startReachedSnapshotDataChangeEpoch = snapshot ? dataChangeEpoch : void 0;
1747
- },
1748
- allowReentryOnDataChange
1749
- );
1750
- }
1751
-
1752
- // src/utils/checkThresholds.ts
1753
- function checkThresholds(ctx) {
1754
- checkAtBottom(ctx);
1755
- checkAtTop(ctx);
1756
- }
1757
-
1758
- // src/utils/setInitialRenderState.ts
1759
- function setInitialRenderState(ctx, {
1760
- didLayout,
1761
- didInitialScroll
1762
- }) {
1763
- const { state } = ctx;
1764
- const {
1765
- loadStartTime,
1766
- props: { onLoad }
1767
- } = state;
1768
- if (didLayout) {
1769
- state.didContainersLayout = true;
1770
- }
1771
- if (didInitialScroll) {
1772
- state.didFinishInitialScroll = true;
1773
- }
1774
- const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll);
1775
- if (isReadyToRender && !peek$(ctx, "readyToRender")) {
1776
- set$(ctx, "readyToRender", true);
1777
- if (onLoad) {
1778
- onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
1779
- }
1780
- }
2005
+ });
1781
2006
  }
1782
2007
 
1783
2008
  // src/core/initialScroll.ts
1784
- function syncInitialScrollOffset(state, offset) {
1785
- state.scroll = offset;
1786
- state.scrollPending = offset;
1787
- state.scrollPrev = offset;
1788
- }
1789
2009
  function dispatchInitialScroll(ctx, params) {
1790
2010
  const { forceScroll, resolvedOffset, target, waitForCompletionFrame } = params;
1791
2011
  const requestedIndex = target.index;
@@ -1806,6 +2026,11 @@ function dispatchInitialScroll(ctx, params) {
1806
2026
  }
1807
2027
  function setInitialScrollTarget(state, target, options) {
1808
2028
  var _a3;
2029
+ state.clearPreservedInitialScrollOnNextFinish = void 0;
2030
+ if (state.timeoutPreservedInitialScrollClear !== void 0) {
2031
+ clearTimeout(state.timeoutPreservedInitialScrollClear);
2032
+ state.timeoutPreservedInitialScrollClear = void 0;
2033
+ }
1809
2034
  state.initialScroll = target;
1810
2035
  if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
1811
2036
  state.didFinishInitialScroll = false;
@@ -1814,44 +2039,6 @@ function setInitialScrollTarget(state, target, options) {
1814
2039
  kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
1815
2040
  });
1816
2041
  }
1817
- function finishInitialScroll(ctx, options) {
1818
- var _a3, _b, _c;
1819
- const state = ctx.state;
1820
- if ((options == null ? void 0 : options.resolvedOffset) !== void 0) {
1821
- syncInitialScrollOffset(state, options.resolvedOffset);
1822
- } else if ((options == null ? void 0 : options.syncObservedOffset) && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
1823
- const observedOffset = (_c = (_b = state.refScroller.current) == null ? void 0 : _b.getCurrentScrollOffset) == null ? void 0 : _c.call(_b);
1824
- if (typeof observedOffset === "number" && Number.isFinite(observedOffset)) {
1825
- syncInitialScrollOffset(state, observedOffset);
1826
- }
1827
- }
1828
- const complete = () => {
1829
- var _a4, _b2, _c2, _d, _e, _f, _g;
1830
- const shouldReleaseDeferredPublicOnScroll = Platform2.OS === "web" && ((_a4 = state.initialScrollSession) == null ? void 0 : _a4.kind) === "bootstrap";
1831
- const finalScrollOffset = (_d = (_c2 = (_b2 = options == null ? void 0 : options.resolvedOffset) != null ? _b2 : state.scrollPending) != null ? _c2 : state.scroll) != null ? _d : 0;
1832
- initialScrollWatchdog.clear(state);
1833
- if (!(options == null ? void 0 : options.preserveTarget)) {
1834
- state.initialScroll = void 0;
1835
- }
1836
- setInitialScrollSession(state);
1837
- if ((options == null ? void 0 : options.recalculateItems) && ((_e = state.props) == null ? void 0 : _e.data)) {
1838
- (_f = state.triggerCalculateItemsInView) == null ? void 0 : _f.call(state, { forceFullItemPositions: true });
1839
- }
1840
- if (options == null ? void 0 : options.recalculateItems) {
1841
- checkThresholds(ctx);
1842
- }
1843
- setInitialRenderState(ctx, { didInitialScroll: true });
1844
- if (shouldReleaseDeferredPublicOnScroll) {
1845
- releaseDeferredPublicOnScroll(ctx, finalScrollOffset);
1846
- }
1847
- (_g = options == null ? void 0 : options.onFinished) == null ? void 0 : _g.call(options);
1848
- };
1849
- if (options == null ? void 0 : options.waitForCompletionFrame) {
1850
- requestAnimationFrame(complete);
1851
- return;
1852
- }
1853
- complete();
1854
- }
1855
2042
  function resolveInitialScrollOffset(ctx, initialScroll) {
1856
2043
  var _a3, _b;
1857
2044
  const state = ctx.state;
@@ -1945,13 +2132,18 @@ function advanceCurrentInitialScrollSession(ctx, options) {
1945
2132
  function isNullOrUndefined2(value) {
1946
2133
  return value === null || value === void 0;
1947
2134
  }
1948
- function getMountedBufferedIndices(state) {
1949
- const { startBuffered, endBuffered } = state;
1950
- if (!isNullOrUndefined2(endBuffered) && !isNullOrUndefined2(startBuffered) && startBuffered >= 0 && endBuffered >= 0) {
1951
- return Array.from(state.containerItemKeys.keys()).map((key) => state.indexByKey.get(key)).filter((index) => index !== void 0 && index >= startBuffered && index <= endBuffered).sort((a, b) => a - b);
2135
+ function getMountedIndicesInRange(state, start, end) {
2136
+ if (!isNullOrUndefined2(end) && !isNullOrUndefined2(start) && start >= 0 && end >= 0) {
2137
+ return Array.from(state.containerItemKeys.keys()).map((key) => state.indexByKey.get(key)).filter((index) => index !== void 0 && index >= start && index <= end).sort((a, b) => a - b);
1952
2138
  }
1953
2139
  return [];
1954
2140
  }
2141
+ function getMountedBufferedIndices(state) {
2142
+ return getMountedIndicesInRange(state, state.startBuffered, state.endBuffered);
2143
+ }
2144
+ function getMountedNoBufferIndices(state) {
2145
+ return getMountedIndicesInRange(state, state.startNoBuffer, state.endNoBuffer);
2146
+ }
1955
2147
  function checkAllSizesKnown(state, indices = getMountedBufferedIndices(state)) {
1956
2148
  return indices.length > 0 && indices.every((index) => {
1957
2149
  const key = getId(state, index);
@@ -2170,16 +2362,22 @@ function createRetargetedBottomAlignedInitialScroll(options) {
2170
2362
  function areEquivalentBootstrapInitialScrollTargets(current, next) {
2171
2363
  return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
2172
2364
  }
2173
- function clearPendingInitialScrollFooterLayout(state, target) {
2365
+ function clearPendingInitialScrollFooterLayout(ctx, options) {
2366
+ const { dataLength, stylePaddingBottom, target } = options;
2367
+ const state = ctx.state;
2174
2368
  if (!shouldPreserveInitialScrollForFooterLayout(target)) {
2175
2369
  return;
2176
2370
  }
2177
- if (state.didFinishInitialScroll && !getBootstrapInitialScrollSession(state)) {
2178
- state.initialScroll = void 0;
2179
- setInitialScrollSession(state);
2180
- return;
2181
- }
2182
- setInitialScrollTarget(state, { ...target, preserveForFooterLayout: void 0 });
2371
+ const clearedFooterTarget = createInitialScrollAtEndTarget({
2372
+ dataLength,
2373
+ footerSize: 0,
2374
+ preserveForFooterLayout: void 0,
2375
+ stylePaddingBottom
2376
+ });
2377
+ setInitialScrollTarget(state, clearedFooterTarget);
2378
+ }
2379
+ function clearFinishedViewportRetargetableInitialScroll(state) {
2380
+ clearPreservedInitialScrollTarget(state);
2183
2381
  }
2184
2382
  function didFinishedInitialScrollMoveAwayFromTarget(ctx, target, epsilon = DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2185
2383
  const state = ctx.state;
@@ -2194,6 +2392,25 @@ function getObservedBootstrapInitialScrollOffset(state) {
2194
2392
  const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
2195
2393
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2196
2394
  }
2395
+ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
2396
+ var _a3, _b;
2397
+ const state = ctx.state;
2398
+ const initialScroll = state.initialScroll;
2399
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1) {
2400
+ return;
2401
+ }
2402
+ if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2403
+ if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
2404
+ clearPendingInitialScrollFooterLayout(ctx, {
2405
+ dataLength: state.props.data.length,
2406
+ stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
2407
+ target: initialScroll
2408
+ });
2409
+ return;
2410
+ }
2411
+ clearFinishedViewportRetargetableInitialScroll(state);
2412
+ }
2413
+ }
2197
2414
  function startBootstrapInitialScrollOnMount(ctx, options) {
2198
2415
  var _a3, _b, _c;
2199
2416
  const { initialScrollAtEnd, target } = options;
@@ -2230,15 +2447,16 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2230
2447
  }
2231
2448
  const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
2232
2449
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2450
+ const shouldClearFinishedResizePreservation = didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
2451
+ if (shouldClearFinishedResizePreservation) {
2452
+ clearPreservedInitialScrollTarget(state);
2453
+ return;
2454
+ }
2233
2455
  const shouldRetargetBottomAligned = dataLength > 0 && (initialScrollAtEnd || isRetargetableBottomAlignedInitialScrollTarget(initialScroll));
2234
2456
  if (!didDataChange && !shouldResetDidFinish && !shouldRetargetBottomAligned) {
2235
2457
  return;
2236
2458
  }
2237
2459
  if (shouldRetargetBottomAligned) {
2238
- if (!shouldResetDidFinish && didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2239
- clearPendingInitialScrollFooterLayout(state, initialScroll);
2240
- return;
2241
- }
2242
2460
  const updatedInitialScroll = initialScrollAtEnd ? createInitialScrollAtEndTarget({
2243
2461
  dataLength,
2244
2462
  footerSize: peek$(ctx, "footerSize") || 0,
@@ -2251,6 +2469,14 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2251
2469
  stylePaddingBottom,
2252
2470
  target: initialScroll
2253
2471
  });
2472
+ if (!shouldResetDidFinish && didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2473
+ clearPendingInitialScrollFooterLayout(ctx, {
2474
+ dataLength,
2475
+ stylePaddingBottom,
2476
+ target: initialScroll
2477
+ });
2478
+ return;
2479
+ }
2254
2480
  if (!areEquivalentBootstrapInitialScrollTargets(initialScroll, updatedInitialScroll) || !!bootstrapInitialScroll || shouldResetDidFinish || didDataChange) {
2255
2481
  setInitialScrollTarget(state, updatedInitialScroll, {
2256
2482
  resetDidFinish: shouldResetDidFinish
@@ -2292,7 +2518,11 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2292
2518
  return;
2293
2519
  }
2294
2520
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2295
- clearPendingInitialScrollFooterLayout(state, initialScroll);
2521
+ clearPendingInitialScrollFooterLayout(ctx, {
2522
+ dataLength,
2523
+ stylePaddingBottom,
2524
+ target: initialScroll
2525
+ });
2296
2526
  } else {
2297
2527
  const updatedInitialScroll = createInitialScrollAtEndTarget({
2298
2528
  dataLength,
@@ -2302,10 +2532,15 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2302
2532
  });
2303
2533
  const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2304
2534
  if (!didTargetChange) {
2305
- clearPendingInitialScrollFooterLayout(state, initialScroll);
2535
+ clearPendingInitialScrollFooterLayout(ctx, {
2536
+ dataLength,
2537
+ stylePaddingBottom,
2538
+ target: initialScroll
2539
+ });
2306
2540
  } else {
2541
+ const didFinishInitialScroll = !!state.didFinishInitialScroll;
2307
2542
  setInitialScrollTarget(state, updatedInitialScroll, {
2308
- resetDidFinish: !!state.didFinishInitialScroll
2543
+ resetDidFinish: didFinishInitialScroll
2309
2544
  });
2310
2545
  rearmBootstrapInitialScroll(ctx, {
2311
2546
  scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
@@ -2314,6 +2549,29 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2314
2549
  }
2315
2550
  }
2316
2551
  }
2552
+ function handleBootstrapInitialScrollLayoutChange(ctx) {
2553
+ const state = ctx.state;
2554
+ const initialScroll = state.initialScroll;
2555
+ if (isOffsetInitialScrollSession(state) || state.props.data.length === 0 || !initialScroll) {
2556
+ return;
2557
+ }
2558
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2559
+ if (!bootstrapInitialScroll && initialScroll.viewPosition !== 1) {
2560
+ return;
2561
+ }
2562
+ const didFinishInitialScroll = state.didFinishInitialScroll;
2563
+ if (didFinishInitialScroll) {
2564
+ setInitialScrollTarget(state, initialScroll, {
2565
+ resetDidFinish: true
2566
+ });
2567
+ state.clearPreservedInitialScrollOnNextFinish = true;
2568
+ }
2569
+ rearmBootstrapInitialScroll(ctx, {
2570
+ scroll: resolveInitialScrollOffset(ctx, initialScroll),
2571
+ seedContentOffset: didFinishInitialScroll && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
2572
+ targetIndexSeed: initialScroll.index
2573
+ });
2574
+ }
2317
2575
  function evaluateBootstrapInitialScroll(ctx) {
2318
2576
  var _a3, _b;
2319
2577
  const state = ctx.state;
@@ -2371,7 +2629,7 @@ function evaluateBootstrapInitialScroll(ctx) {
2371
2629
  queueBootstrapInitialScrollReevaluation(state);
2372
2630
  return;
2373
2631
  }
2374
- if (Platform2.OS !== "web" && Platform2.OS !== "android" && Math.abs(bootstrapInitialScroll.seedContentOffset - resolvedOffset) <= 1) {
2632
+ if (Platform2.OS !== "web" && Platform2.OS !== "android" && Math.abs(bootstrapInitialScroll.seedContentOffset - resolvedOffset) <= 1 && Math.abs(getObservedBootstrapInitialScrollOffset(state) - resolvedOffset) <= 1) {
2375
2633
  finishBootstrapInitialScrollWithoutScroll(ctx, resolvedOffset);
2376
2634
  } else {
2377
2635
  clearBootstrapInitialScrollSession(state);
@@ -2384,12 +2642,15 @@ function evaluateBootstrapInitialScroll(ctx) {
2384
2642
  }
2385
2643
  }
2386
2644
  function finishBootstrapInitialScrollWithoutScroll(ctx, resolvedOffset) {
2645
+ var _a3;
2387
2646
  const state = ctx.state;
2388
2647
  clearBootstrapInitialScrollSession(state);
2648
+ const shouldPreserveResizeTarget = !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_a3 = state.initialScroll) == null ? void 0 : _a3.viewPosition) === 1;
2389
2649
  finishInitialScroll(ctx, {
2390
- preserveTarget: shouldPreserveInitialScrollForFooterLayout(state.initialScroll),
2650
+ preserveTarget: shouldPreserveResizeTarget,
2391
2651
  recalculateItems: true,
2392
- resolvedOffset
2652
+ resolvedOffset,
2653
+ schedulePreservedTargetClear: shouldPreserveResizeTarget
2393
2654
  });
2394
2655
  }
2395
2656
  function abortBootstrapInitialScroll(ctx) {
@@ -2666,7 +2927,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
2666
2927
  settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2667
2928
  return true;
2668
2929
  }
2669
- if (state.pendingMaintainScrollAtEnd && state.isAtEnd && progressTowardAmount > MVCP_POSITION_EPSILON) {
2930
+ if (state.pendingMaintainScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold") && progressTowardAmount > MVCP_POSITION_EPSILON) {
2670
2931
  settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2671
2932
  return true;
2672
2933
  }
@@ -2831,6 +3092,86 @@ function prepareMVCP(ctx, dataChanged) {
2831
3092
  }
2832
3093
  }
2833
3094
 
3095
+ // src/core/syncMountedContainer.ts
3096
+ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3097
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
3098
+ const state = ctx.state;
3099
+ const {
3100
+ columns,
3101
+ columnSpans,
3102
+ positions,
3103
+ props: { data, itemsAreEqual, keyExtractor }
3104
+ } = state;
3105
+ const item = data[itemIndex];
3106
+ if (item === void 0) {
3107
+ return { didChangePosition: false, didRefreshData: false };
3108
+ }
3109
+ const updateLayout = (_a3 = options == null ? void 0 : options.updateLayout) != null ? _a3 : true;
3110
+ let didChangePosition = false;
3111
+ let didRefreshData = false;
3112
+ if (updateLayout) {
3113
+ const positionValue = positions[itemIndex];
3114
+ if (positionValue === void 0) {
3115
+ set$(ctx, `containerPosition${containerIndex}`, POSITION_OUT_OF_VIEW);
3116
+ return { didChangePosition: false, didRefreshData: false };
3117
+ }
3118
+ const position = (positionValue || 0) - ((_b = options == null ? void 0 : options.scrollAdjustPending) != null ? _b : 0);
3119
+ const column = columns[itemIndex] || 1;
3120
+ const span = columnSpans[itemIndex] || 1;
3121
+ const prevPos = peek$(ctx, `containerPosition${containerIndex}`);
3122
+ const prevColumn = peek$(ctx, `containerColumn${containerIndex}`);
3123
+ const prevSpan = peek$(ctx, `containerSpan${containerIndex}`);
3124
+ if (position > POSITION_OUT_OF_VIEW && position !== prevPos) {
3125
+ set$(ctx, `containerPosition${containerIndex}`, position);
3126
+ didChangePosition = true;
3127
+ }
3128
+ if (column >= 0 && column !== prevColumn) {
3129
+ set$(ctx, `containerColumn${containerIndex}`, column);
3130
+ }
3131
+ if (span !== prevSpan) {
3132
+ set$(ctx, `containerSpan${containerIndex}`, span);
3133
+ }
3134
+ }
3135
+ const prevData = peek$(ctx, `containerItemData${containerIndex}`);
3136
+ if (prevData !== item) {
3137
+ const pendingDataComparison = ((_c = state.pendingDataComparison) == null ? void 0 : _c.previousData) === state.previousData && ((_d = state.pendingDataComparison) == null ? void 0 : _d.nextData) === data ? state.pendingDataComparison : void 0;
3138
+ const cachedComparison = (_e = pendingDataComparison == null ? void 0 : pendingDataComparison.byIndex[itemIndex]) != null ? _e : 0;
3139
+ if (cachedComparison === 2) {
3140
+ set$(ctx, `containerItemData${containerIndex}`, item);
3141
+ didRefreshData = true;
3142
+ } else if (cachedComparison !== 1) {
3143
+ const itemKey = (_g = (_f = peek$(ctx, `containerItemKey${containerIndex}`)) != null ? _f : state.idCache[itemIndex]) != null ? _g : getId(state, itemIndex);
3144
+ const prevKey = keyExtractor == null ? void 0 : keyExtractor(prevData, itemIndex);
3145
+ if (prevData === void 0 || !keyExtractor || prevKey !== itemKey) {
3146
+ set$(ctx, `containerItemData${containerIndex}`, item);
3147
+ didRefreshData = true;
3148
+ } else if (!itemsAreEqual) {
3149
+ set$(ctx, `containerItemData${containerIndex}`, item);
3150
+ didRefreshData = true;
3151
+ } else {
3152
+ const isEqual = itemsAreEqual(prevData, item, itemIndex, data);
3153
+ if (!state.pendingDataComparison || state.pendingDataComparison.previousData !== state.previousData || state.pendingDataComparison.nextData !== data) {
3154
+ if (state.previousData) {
3155
+ state.pendingDataComparison = {
3156
+ byIndex: [],
3157
+ nextData: data,
3158
+ previousData: state.previousData
3159
+ };
3160
+ }
3161
+ }
3162
+ if ((_h = state.pendingDataComparison) == null ? void 0 : _h.byIndex) {
3163
+ state.pendingDataComparison.byIndex[itemIndex] = isEqual ? 1 : 2;
3164
+ }
3165
+ if (!isEqual) {
3166
+ set$(ctx, `containerItemData${containerIndex}`, item);
3167
+ didRefreshData = true;
3168
+ }
3169
+ }
3170
+ }
3171
+ }
3172
+ return { didChangePosition, didRefreshData };
3173
+ }
3174
+
2834
3175
  // src/core/prepareColumnStartState.ts
2835
3176
  function prepareColumnStartState(ctx, startIndex, useAverageSize) {
2836
3177
  var _a3;
@@ -2993,9 +3334,10 @@ function updateSnapToOffsets(ctx) {
2993
3334
  }
2994
3335
 
2995
3336
  // src/core/updateItemPositions.ts
2996
- function updateItemPositions(ctx, dataChanged, { startIndex, scrollBottomBuffered, forceFullUpdate = false, doMVCP } = {
3337
+ function updateItemPositions(ctx, dataChanged, { startIndex, scrollBottomBuffered, forceFullUpdate = false, doMVCP, optimizeForVisibleWindow = false } = {
2997
3338
  doMVCP: false,
2998
3339
  forceFullUpdate: false,
3340
+ optimizeForVisibleWindow: false,
2999
3341
  scrollBottomBuffered: -1,
3000
3342
  startIndex: 0
3001
3343
  }) {
@@ -3020,7 +3362,7 @@ function updateItemPositions(ctx, dataChanged, { startIndex, scrollBottomBuffere
3020
3362
  const layoutConfig = overrideItemLayout ? { span: 1 } : void 0;
3021
3363
  const lastScrollDelta = state.lastScrollDelta;
3022
3364
  const velocity = getScrollVelocity(state);
3023
- const shouldOptimize = !forceFullUpdate && !dataChanged && (Math.abs(velocity) > 0 || Platform2.OS === "web" && state.scrollLength > 0 && lastScrollDelta > state.scrollLength);
3365
+ const shouldOptimize = !forceFullUpdate && !dataChanged && (optimizeForVisibleWindow || Math.abs(velocity) > 0 || Platform2.OS === "web" && state.scrollLength > 0 && lastScrollDelta > state.scrollLength);
3024
3366
  const maxVisibleArea = scrollBottomBuffered + 1e3;
3025
3367
  const useAverageSize = !getEstimatedItemSize;
3026
3368
  const preferCachedSize = !doMVCP || dataChanged || state.scrollAdjustHandler.getAdjust() !== 0 || ((_b = peek$(ctx, "scrollAdjustPending")) != null ? _b : 0) !== 0;
@@ -3135,7 +3477,15 @@ function ensureViewabilityState(ctx, configId) {
3135
3477
  }
3136
3478
  let state = map.get(configId);
3137
3479
  if (!state) {
3138
- state = { end: -1, previousEnd: -1, previousStart: -1, start: -1, viewableItems: [] };
3480
+ state = {
3481
+ end: -1,
3482
+ endBuffered: -1,
3483
+ previousEnd: -1,
3484
+ previousStart: -1,
3485
+ start: -1,
3486
+ startBuffered: -1,
3487
+ viewableItems: []
3488
+ };
3139
3489
  map.set(configId, state);
3140
3490
  }
3141
3491
  return state;
@@ -3155,7 +3505,7 @@ function setupViewability(props) {
3155
3505
  }
3156
3506
  return viewabilityConfigCallbackPairs;
3157
3507
  }
3158
- function updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, scrollSize, start, end) {
3508
+ function updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, scrollSize, start, end, startBuffered = start, endBuffered = end) {
3159
3509
  const {
3160
3510
  timeouts,
3161
3511
  props: { data }
@@ -3164,6 +3514,8 @@ function updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, scrollS
3164
3514
  const viewabilityState = ensureViewabilityState(ctx, viewabilityConfigCallbackPair.viewabilityConfig.id);
3165
3515
  viewabilityState.start = start;
3166
3516
  viewabilityState.end = end;
3517
+ viewabilityState.startBuffered = startBuffered;
3518
+ viewabilityState.endBuffered = endBuffered;
3167
3519
  if (viewabilityConfigCallbackPair.viewabilityConfig.minimumViewTime) {
3168
3520
  const timer = setTimeout(() => {
3169
3521
  timeouts.delete(timer);
@@ -3179,7 +3531,7 @@ function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, stat
3179
3531
  const { viewabilityConfig, onViewableItemsChanged } = viewabilityConfigCallbackPair;
3180
3532
  const configId = viewabilityConfig.id;
3181
3533
  const viewabilityState = ensureViewabilityState(ctx, configId);
3182
- const { viewableItems: previousViewableItems, start, end } = viewabilityState;
3534
+ const { viewableItems: previousViewableItems, start, end, startBuffered, endBuffered } = viewabilityState;
3183
3535
  const viewabilityTokens = /* @__PURE__ */ new Map();
3184
3536
  for (const [containerId, value] of ctx.mapViewabilityAmountValues) {
3185
3537
  viewabilityTokens.set(
@@ -3248,7 +3600,7 @@ function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, stat
3248
3600
  maybeUpdateViewabilityCallback(ctx, configId, change.containerId, change);
3249
3601
  }
3250
3602
  if (onViewableItemsChanged) {
3251
- onViewableItemsChanged({ changed, viewableItems });
3603
+ onViewableItemsChanged({ changed, end, endBuffered, start, startBuffered, viewableItems });
3252
3604
  }
3253
3605
  }
3254
3606
  for (const [containerId, value] of ctx.mapViewabilityAmountValues) {
@@ -3560,7 +3912,6 @@ function calculateItemsInView(ctx, params = {}) {
3560
3912
  alwaysRenderIndicesSet,
3561
3913
  drawDistance,
3562
3914
  getItemType,
3563
- itemsAreEqual,
3564
3915
  keyExtractor,
3565
3916
  onStickyHeaderChange
3566
3917
  },
@@ -3587,11 +3938,11 @@ function calculateItemsInView(ctx, params = {}) {
3587
3938
  const numColumns = peek$(ctx, "numColumns");
3588
3939
  const speed = getScrollVelocity(state);
3589
3940
  const scrollExtra = 0;
3590
- const { queuedInitialLayout } = state;
3591
- const scrollState = suppressInitialScrollSideEffects ? (_b = bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.scroll) != null ? _b : state.scroll : !queuedInitialLayout && state.initialScroll ? (
3941
+ const { initialScroll, queuedInitialLayout } = state;
3942
+ const scrollState = suppressInitialScrollSideEffects ? (_b = bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.scroll) != null ? _b : state.scroll : !queuedInitialLayout && hasActiveInitialScroll(state) && initialScroll ? (
3592
3943
  // Before the initial layout settles, keep viewport math anchored to the
3593
3944
  // current initial-scroll target instead of transient native adjustments.
3594
- resolveInitialScrollOffset(ctx, state.initialScroll)
3945
+ resolveInitialScrollOffset(ctx, initialScroll)
3595
3946
  ) : state.scroll;
3596
3947
  const scrollAdjustPending = (_c = peek$(ctx, "scrollAdjustPending")) != null ? _c : 0;
3597
3948
  const scrollAdjustPad = scrollAdjustPending - topPad;
@@ -3636,9 +3987,11 @@ function calculateItemsInView(ctx, params = {}) {
3636
3987
  columnSpans.length = 0;
3637
3988
  }
3638
3989
  const startIndex = forceFullItemPositions || dataChanged ? 0 : (_d = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _d : 0;
3990
+ const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
3639
3991
  updateItemPositions(ctx, dataChanged, {
3640
3992
  doMVCP,
3641
3993
  forceFullUpdate: !!forceFullItemPositions,
3994
+ optimizeForVisibleWindow,
3642
3995
  scrollBottomBuffered,
3643
3996
  startIndex
3644
3997
  });
@@ -3886,33 +4239,11 @@ function calculateItemsInView(ctx, params = {}) {
3886
4239
  set$(ctx, `containerSpan${i}`, 1);
3887
4240
  } else {
3888
4241
  const itemIndex = indexByKey.get(itemKey);
3889
- const item = data[itemIndex];
3890
- if (item !== void 0) {
3891
- const positionValue = positions[itemIndex];
3892
- if (positionValue === void 0) {
3893
- set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
3894
- } else {
3895
- const position = (positionValue || 0) - scrollAdjustPending;
3896
- const column = columns[itemIndex] || 1;
3897
- const span = columnSpans[itemIndex] || 1;
3898
- const prevPos = peek$(ctx, `containerPosition${i}`);
3899
- const prevColumn = peek$(ctx, `containerColumn${i}`);
3900
- const prevSpan = peek$(ctx, `containerSpan${i}`);
3901
- const prevData = peek$(ctx, `containerItemData${i}`);
3902
- if (position > POSITION_OUT_OF_VIEW && position !== prevPos) {
3903
- set$(ctx, `containerPosition${i}`, position);
3904
- didChangePositions = true;
3905
- }
3906
- if (column >= 0 && column !== prevColumn) {
3907
- set$(ctx, `containerColumn${i}`, column);
3908
- }
3909
- if (span !== prevSpan) {
3910
- set$(ctx, `containerSpan${i}`, span);
3911
- }
3912
- if (prevData !== item && (itemsAreEqual ? !itemsAreEqual(prevData, item, itemIndex, data) : true)) {
3913
- set$(ctx, `containerItemData${i}`, item);
3914
- }
3915
- }
4242
+ if (itemIndex !== void 0) {
4243
+ didChangePositions = syncMountedContainer(ctx, i, itemIndex, {
4244
+ scrollAdjustPending,
4245
+ updateLayout: true
4246
+ }).didChangePosition || didChangePositions;
3916
4247
  }
3917
4248
  }
3918
4249
  }
@@ -3923,7 +4254,10 @@ function calculateItemsInView(ctx, params = {}) {
3923
4254
  evaluateBootstrapInitialScroll(ctx);
3924
4255
  return;
3925
4256
  }
3926
- if (!queuedInitialLayout && endBuffered !== null && checkAllSizesKnown(state)) {
4257
+ const mountedBufferedIndices = getMountedBufferedIndices(state);
4258
+ const mountedNoBufferIndices = getMountedNoBufferIndices(state);
4259
+ const readinessIndices = hasActiveInitialScroll(state) ? mountedBufferedIndices : mountedNoBufferIndices.length > 0 ? mountedNoBufferIndices : mountedBufferedIndices;
4260
+ if (!queuedInitialLayout && readinessIndices.length > 0 && checkAllSizesKnown(state, readinessIndices)) {
3927
4261
  setDidLayout(ctx);
3928
4262
  handleInitialScrollLayoutReady(ctx);
3929
4263
  }
@@ -3934,7 +4268,9 @@ function calculateItemsInView(ctx, params = {}) {
3934
4268
  viewabilityConfigCallbackPairs,
3935
4269
  scrollLength,
3936
4270
  startNoBuffer,
3937
- endNoBuffer
4271
+ endNoBuffer,
4272
+ startBuffered != null ? startBuffered : startNoBuffer,
4273
+ endBuffered != null ? endBuffered : endNoBuffer
3938
4274
  );
3939
4275
  }
3940
4276
  if (onStickyHeaderChange && stickyIndicesArr.length > 0 && nextActiveStickyIndex !== void 0 && nextActiveStickyIndex !== previousStickyIndex) {
@@ -3946,37 +4282,17 @@ function calculateItemsInView(ctx, params = {}) {
3946
4282
  });
3947
4283
  }
3948
4284
 
3949
- // src/core/checkActualChange.ts
3950
- function checkActualChange(state, dataProp, previousData) {
3951
- if (!previousData || !dataProp || dataProp.length !== previousData.length) {
3952
- return true;
3953
- }
3954
- const {
3955
- idCache,
3956
- props: { keyExtractor }
3957
- } = state;
3958
- for (let i = 0; i < dataProp.length; i++) {
3959
- if (dataProp[i] !== previousData[i]) {
3960
- return true;
3961
- }
3962
- if (keyExtractor ? idCache[i] !== keyExtractor(previousData[i], i) : dataProp[i] !== previousData[i]) {
3963
- return true;
3964
- }
3965
- }
3966
- return false;
3967
- }
3968
-
3969
4285
  // src/core/doMaintainScrollAtEnd.ts
3970
4286
  function doMaintainScrollAtEnd(ctx) {
3971
4287
  const state = ctx.state;
3972
4288
  const {
3973
4289
  didContainersLayout,
3974
- isAtEnd,
3975
4290
  pendingNativeMVCPAdjust,
3976
4291
  refScroller,
3977
4292
  props: { maintainScrollAtEnd }
3978
4293
  } = state;
3979
- const shouldMaintainScrollAtEnd = !!(isAtEnd && maintainScrollAtEnd && didContainersLayout);
4294
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
4295
+ const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
3980
4296
  if (pendingNativeMVCPAdjust) {
3981
4297
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
3982
4298
  return false;
@@ -3989,7 +4305,7 @@ function doMaintainScrollAtEnd(ctx) {
3989
4305
  }
3990
4306
  requestAnimationFrame(() => {
3991
4307
  var _a3;
3992
- if (state.isAtEnd) {
4308
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
3993
4309
  state.maintainingScrollAtEnd = true;
3994
4310
  (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
3995
4311
  animated: maintainScrollAtEnd.animated
@@ -4007,68 +4323,22 @@ function doMaintainScrollAtEnd(ctx) {
4007
4323
  return false;
4008
4324
  }
4009
4325
 
4010
- // src/utils/updateAveragesOnDataChange.ts
4011
- function updateAveragesOnDataChange(state, oldData, newData) {
4012
- var _a3;
4013
- const {
4014
- averageSizes,
4015
- sizesKnown,
4016
- indexByKey,
4017
- props: { itemsAreEqual, getItemType, keyExtractor }
4018
- } = state;
4019
- if (!itemsAreEqual || !oldData.length || !newData.length) {
4020
- for (const key in averageSizes) {
4021
- delete averageSizes[key];
4022
- }
4023
- return;
4024
- }
4025
- const itemTypesToPreserve = {};
4026
- const newDataLength = newData.length;
4027
- const oldDataLength = oldData.length;
4028
- for (let newIndex = 0; newIndex < newDataLength; newIndex++) {
4029
- const newItem = newData[newIndex];
4030
- const id = keyExtractor ? keyExtractor(newItem, newIndex) : String(newIndex);
4031
- const oldIndex = indexByKey.get(id);
4032
- if (oldIndex !== void 0 && oldIndex < oldDataLength) {
4033
- const knownSize = sizesKnown.get(id);
4034
- if (knownSize === void 0) continue;
4035
- const oldItem = oldData[oldIndex];
4036
- const areEqual = itemsAreEqual(oldItem, newItem, newIndex, newData);
4037
- if (areEqual) {
4038
- const itemType = getItemType ? (_a3 = getItemType(newItem, newIndex)) != null ? _a3 : "" : "";
4039
- let typeData = itemTypesToPreserve[itemType];
4040
- if (!typeData) {
4041
- typeData = itemTypesToPreserve[itemType] = { count: 0, totalSize: 0 };
4042
- }
4043
- typeData.totalSize += knownSize;
4044
- typeData.count++;
4045
- }
4046
- }
4047
- }
4048
- for (const key in averageSizes) {
4049
- delete averageSizes[key];
4050
- }
4051
- for (const itemType in itemTypesToPreserve) {
4052
- const { totalSize, count } = itemTypesToPreserve[itemType];
4053
- if (count > 0) {
4054
- averageSizes[itemType] = {
4055
- avg: totalSize / count,
4056
- num: count
4057
- };
4058
- }
4059
- }
4060
- }
4061
-
4062
4326
  // src/core/checkResetContainers.ts
4063
- function checkResetContainers(ctx, dataProp) {
4327
+ function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {}) {
4064
4328
  const state = ctx.state;
4065
4329
  const { previousData } = state;
4066
- if (previousData) {
4067
- updateAveragesOnDataChange(state, previousData, dataProp);
4068
- }
4069
4330
  const { maintainScrollAtEnd } = state.props;
4331
+ if (didColumnsChange) {
4332
+ state.sizes.clear();
4333
+ state.sizesKnown.clear();
4334
+ for (const key in state.averageSizes) {
4335
+ delete state.averageSizes[key];
4336
+ }
4337
+ state.minIndexSizeChanged = 0;
4338
+ state.scrollForNextCalculateItemsInView = void 0;
4339
+ }
4070
4340
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
4071
- const shouldMaintainScrollAtEnd = maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onDataChange;
4341
+ const shouldMaintainScrollAtEnd = !didColumnsChange && (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onDataChange);
4072
4342
  const didMaintainScrollAtEnd = shouldMaintainScrollAtEnd && doMaintainScrollAtEnd(ctx);
4073
4343
  if (!didMaintainScrollAtEnd && previousData && dataProp.length > previousData.length) {
4074
4344
  state.isEndReached = false;
@@ -4079,6 +4349,53 @@ function checkResetContainers(ctx, dataProp) {
4079
4349
  delete state.previousData;
4080
4350
  }
4081
4351
 
4352
+ // src/core/checkStructuralDataChange.ts
4353
+ function checkStructuralDataChange(state, dataProp, previousData) {
4354
+ var _a3;
4355
+ state.pendingDataComparison = void 0;
4356
+ if (!previousData || !dataProp || dataProp.length !== previousData.length) {
4357
+ return true;
4358
+ }
4359
+ const {
4360
+ idCache,
4361
+ props: { itemsAreEqual, keyExtractor }
4362
+ } = state;
4363
+ let byIndex;
4364
+ for (let i = 0; i < dataProp.length; i++) {
4365
+ if (dataProp[i] === previousData[i]) {
4366
+ continue;
4367
+ }
4368
+ if (!keyExtractor) {
4369
+ if (byIndex) {
4370
+ state.pendingDataComparison = { byIndex, nextData: dataProp, previousData };
4371
+ }
4372
+ return true;
4373
+ }
4374
+ const previousKey = (_a3 = idCache[i]) != null ? _a3 : keyExtractor(previousData[i], i);
4375
+ const nextKey = keyExtractor(dataProp[i], i);
4376
+ if (previousKey !== nextKey) {
4377
+ if (byIndex) {
4378
+ state.pendingDataComparison = { byIndex, nextData: dataProp, previousData };
4379
+ }
4380
+ return true;
4381
+ }
4382
+ if (!itemsAreEqual) {
4383
+ if (byIndex) {
4384
+ state.pendingDataComparison = { byIndex, nextData: dataProp, previousData };
4385
+ }
4386
+ return true;
4387
+ }
4388
+ const isEqual = itemsAreEqual(previousData[i], dataProp[i], i, dataProp);
4389
+ byIndex != null ? byIndex : byIndex = [];
4390
+ byIndex[i] = isEqual ? 1 : 2;
4391
+ if (!isEqual) {
4392
+ state.pendingDataComparison = { byIndex, nextData: dataProp, previousData };
4393
+ return true;
4394
+ }
4395
+ }
4396
+ return false;
4397
+ }
4398
+
4082
4399
  // src/core/doInitialAllocateContainers.ts
4083
4400
  function doInitialAllocateContainers(ctx) {
4084
4401
  var _a3, _b, _c;
@@ -4323,6 +4640,7 @@ function onScroll(ctx, event) {
4323
4640
  state.scrollPending = newScroll;
4324
4641
  updateScroll(ctx, newScroll, insetChanged);
4325
4642
  trackInitialScrollNativeProgress(state, newScroll);
4643
+ clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx);
4326
4644
  if (state.scrollingTo) {
4327
4645
  checkFinishedScroll(ctx);
4328
4646
  }
@@ -4386,6 +4704,43 @@ var ScrollAdjustHandler = class {
4386
4704
  }
4387
4705
  };
4388
4706
 
4707
+ // src/core/updateAnchoredEndSpace.ts
4708
+ function maybeUpdateAnchoredEndSpace(ctx) {
4709
+ var _a3;
4710
+ const state = ctx.state;
4711
+ const anchoredEndSpace = state.props.anchoredEndSpace;
4712
+ const previousSize = peek$(ctx, "anchoredEndSpaceSize");
4713
+ let nextSize = 0;
4714
+ if (anchoredEndSpace) {
4715
+ const { anchorIndex, anchorMaxSize, anchorOffset = 0 } = anchoredEndSpace;
4716
+ const { data } = state.props;
4717
+ if (anchorIndex >= 0 && anchorIndex < data.length && state.scrollLength > 0) {
4718
+ let contentBelowAnchor = 0;
4719
+ const footerSize = ctx.values.get("footerSize") || 0;
4720
+ const stylePaddingBottom = state.props.stylePaddingBottom || 0;
4721
+ for (let index = anchorIndex; index < data.length; index++) {
4722
+ const itemKey = getId(state, index);
4723
+ const size = itemKey ? state.sizesKnown.get(itemKey) : void 0;
4724
+ const effectiveSize = index === anchorIndex && anchorMaxSize !== void 0 ? Math.min(size || 0, Math.max(0, anchorMaxSize)) : size;
4725
+ if (effectiveSize !== null && effectiveSize !== void 0 && effectiveSize > 0) {
4726
+ contentBelowAnchor += effectiveSize;
4727
+ }
4728
+ }
4729
+ contentBelowAnchor += footerSize + stylePaddingBottom;
4730
+ nextSize = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4731
+ }
4732
+ }
4733
+ if (previousSize === nextSize) {
4734
+ return nextSize;
4735
+ }
4736
+ set$(ctx, "anchoredEndSpaceSize", nextSize);
4737
+ (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4738
+ if (anchoredEndSpace == null ? void 0 : anchoredEndSpace.includeInEndInset) {
4739
+ updateScroll(ctx, state.scroll, true);
4740
+ }
4741
+ return nextSize;
4742
+ }
4743
+
4389
4744
  // src/core/updateItemSize.ts
4390
4745
  function runOrScheduleMVCPRecalculate(ctx) {
4391
4746
  const state = ctx.state;
@@ -4469,6 +4824,7 @@ function updateItemSize(ctx, itemKey, sizeObj) {
4469
4824
  previous: size - diff,
4470
4825
  size
4471
4826
  });
4827
+ maybeUpdateAnchoredEndSpace(ctx);
4472
4828
  }
4473
4829
  if (minIndexSizeChanged !== void 0) {
4474
4830
  state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
@@ -4599,26 +4955,13 @@ function createColumnWrapperStyle(contentContainerStyle) {
4599
4955
  }
4600
4956
  }
4601
4957
 
4602
- // src/utils/hasActiveMVCPAnchorLock.ts
4603
- function hasActiveMVCPAnchorLock(state) {
4604
- const lock = state.mvcpAnchorLock;
4605
- if (!lock) {
4606
- return false;
4607
- }
4608
- if (Date.now() > lock.expiresAt) {
4609
- state.mvcpAnchorLock = void 0;
4610
- return false;
4611
- }
4612
- return true;
4613
- }
4614
-
4615
4958
  // src/utils/createImperativeHandle.ts
4616
4959
  function createImperativeHandle(ctx) {
4617
4960
  const state = ctx.state;
4618
4961
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
4619
4962
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
4620
4963
  let imperativeScrollToken = 0;
4621
- const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.queuedMVCPRecalculate !== void 0 || state.ignoreScrollFromMVCP !== void 0 || hasActiveMVCPAnchorLock(state);
4964
+ const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.queuedMVCPRecalculate !== void 0 || state.ignoreScrollFromMVCP !== void 0;
4622
4965
  const runWhenSettled = (token, run) => {
4623
4966
  const startedAt = Date.now();
4624
4967
  let stableFrames = 0;
@@ -4640,9 +4983,10 @@ function createImperativeHandle(ctx) {
4640
4983
  };
4641
4984
  requestAnimationFrame(check);
4642
4985
  };
4643
- const runScrollWithPromise = (run) => new Promise((resolve) => {
4986
+ const runScrollWithPromise = (run, options) => new Promise((resolve) => {
4644
4987
  var _a3;
4645
4988
  const token = ++imperativeScrollToken;
4989
+ const shouldWaitOneFrame = !!(options == null ? void 0 : options.shouldWaitOneFrame);
4646
4990
  (_a3 = state.pendingScrollResolve) == null ? void 0 : _a3.call(state);
4647
4991
  state.pendingScrollResolve = resolve;
4648
4992
  const runNow = () => {
@@ -4657,11 +5001,12 @@ function createImperativeHandle(ctx) {
4657
5001
  resolve();
4658
5002
  }
4659
5003
  };
5004
+ const execute = shouldWaitOneFrame ? () => requestAnimationFrame(runNow) : runNow;
4660
5005
  if (isSettlingAfterDataChange()) {
4661
- runWhenSettled(token, runNow);
4662
- return;
5006
+ runWhenSettled(token, execute);
5007
+ } else {
5008
+ execute();
4663
5009
  }
4664
- runNow();
4665
5010
  });
4666
5011
  const scrollIndexIntoView = (options) => {
4667
5012
  if (state) {
@@ -4718,10 +5063,13 @@ function createImperativeHandle(ctx) {
4718
5063
  },
4719
5064
  end: state.endNoBuffer,
4720
5065
  endBuffered: state.endBuffered,
4721
- isAtEnd: state.isAtEnd,
4722
- isAtStart: state.isAtStart,
5066
+ isAtEnd: peek$(ctx, "isAtEnd"),
5067
+ isAtStart: peek$(ctx, "isAtStart"),
4723
5068
  isEndReached: state.isEndReached,
5069
+ isNearEnd: peek$(ctx, "isNearEnd"),
5070
+ isNearStart: peek$(ctx, "isNearStart"),
4724
5071
  isStartReached: state.isStartReached,
5072
+ isWithinMaintainScrollAtEndThreshold: peek$(ctx, "isWithinMaintainScrollAtEndThreshold"),
4725
5073
  listen: (signalName, cb) => listen$(ctx, signalName, cb),
4726
5074
  listenToPosition: (key, cb) => listenPosition$(ctx, key, cb),
4727
5075
  positionAtIndex: (index) => state.positions[index],
@@ -4768,10 +5116,15 @@ function createImperativeHandle(ctx) {
4768
5116
  }
4769
5117
  return false;
4770
5118
  }),
4771
- scrollToIndex: (params) => runScrollWithPromise(() => {
4772
- scrollToIndex(ctx, params);
4773
- return true;
4774
- }),
5119
+ scrollToIndex: (params) => runScrollWithPromise(
5120
+ () => {
5121
+ scrollToIndex(ctx, params);
5122
+ return true;
5123
+ },
5124
+ {
5125
+ shouldWaitOneFrame: params.index >= 0 && params.index >= state.props.data.length
5126
+ }
5127
+ ),
4775
5128
  scrollToItem: ({ item, ...props }) => runScrollWithPromise(() => {
4776
5129
  const data = state.props.data;
4777
5130
  const index = data.indexOf(item);
@@ -4867,7 +5220,7 @@ function getRenderedItem(ctx, key) {
4867
5220
  item,
4868
5221
  type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
4869
5222
  };
4870
- renderedItem = isFunction(renderItem) ? renderItem(itemProps) : React2__default.createElement(renderItem, itemProps);
5223
+ renderedItem = React2__default.createElement(renderItem, itemProps);
4871
5224
  }
4872
5225
  return { index, item: data[index], renderedItem };
4873
5226
  }
@@ -4982,7 +5335,7 @@ function useThrottledOnScroll(originalHandler, scrollEventThrottle) {
4982
5335
  }
4983
5336
 
4984
5337
  // src/components/LegendList.tsx
4985
- var LegendList = typedMemo2(
5338
+ var LegendList = typedMemo(
4986
5339
  // biome-ignore lint/nursery/noShadow: const function name shadowing is intentional
4987
5340
  typedForwardRef(function LegendList2(props, forwardedRef) {
4988
5341
  const { children, data: dataProp, renderItem: renderItemProp, ...restProps } = props;
@@ -5001,9 +5354,18 @@ var LegendList = typedMemo2(
5001
5354
  })
5002
5355
  );
5003
5356
  var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
5004
- var _a3, _b, _c, _d, _e, _f, _g;
5357
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
5358
+ const noopOnScroll = useCallback((_event) => {
5359
+ }, []);
5360
+ if (props.recycleItems === void 0) {
5361
+ warnDevOnce(
5362
+ "recycleItems-omitted",
5363
+ "recycleItems was not provided, so it defaults to false. Set recycleItems explicitly to true for better performance with recycling-aware rows, or false to preserve remount-on-reuse behavior."
5364
+ );
5365
+ }
5005
5366
  const {
5006
5367
  alignItemsAtEnd = false,
5368
+ anchoredEndSpace,
5007
5369
  alwaysRender,
5008
5370
  columnWrapperStyle,
5009
5371
  contentContainerStyle: contentContainerStyleProp,
@@ -5069,7 +5431,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5069
5431
  const positionComponentInternal = props.positionComponentInternal;
5070
5432
  const stickyPositionComponentInternal = props.stickyPositionComponentInternal;
5071
5433
  const {
5072
- childrenMode,
5073
5434
  positionComponentInternal: _positionComponentInternal,
5074
5435
  stickyPositionComponentInternal: _stickyPositionComponentInternal,
5075
5436
  ...restProps
@@ -5133,18 +5494,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5133
5494
  dataVersion,
5134
5495
  keyExtractor
5135
5496
  ]);
5136
- if (IS_DEV && stickyIndicesDeprecated && !stickyHeaderIndicesProp) {
5137
- warnDevOnce(
5138
- "stickyIndices",
5139
- "stickyIndices has been renamed to stickyHeaderIndices. Please update your props to use stickyHeaderIndices."
5140
- );
5141
- }
5142
- if (IS_DEV && useWindowScroll && renderScrollComponent) {
5143
- warnDevOnce(
5144
- "useWindowScrollRenderScrollComponent",
5145
- "useWindowScroll is not supported when renderScrollComponent is provided."
5146
- );
5147
- }
5148
5497
  const useWindowScrollResolved = Platform2.OS === "web" && !!useWindowScroll && !renderScrollComponent;
5149
5498
  const refState = useRef(void 0);
5150
5499
  const hasOverrideItemLayout = !!overrideItemLayout;
@@ -5153,7 +5502,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5153
5502
  if (!ctx.state) {
5154
5503
  const initialScrollLength = (estimatedListSize != null ? estimatedListSize : IsNewArchitecture ? { height: 0, width: 0 } : getWindowSize())[horizontal ? "width" : "height"];
5155
5504
  ctx.state = {
5156
- activeStickyIndex: -1,
5157
5505
  averageSizes: {},
5158
5506
  columnSpans: [],
5159
5507
  columns: [],
@@ -5177,8 +5525,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5177
5525
  kind: initialScrollUsesOffsetOnly ? "offset" : "bootstrap",
5178
5526
  previousDataLength: dataProp.length
5179
5527
  } : void 0,
5180
- isAtEnd: false,
5181
- isAtStart: false,
5182
5528
  isEndReached: null,
5183
5529
  isFirst: true,
5184
5530
  isStartReached: null,
@@ -5189,6 +5535,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5189
5535
  minIndexSizeChanged: 0,
5190
5536
  nativeContentInset: void 0,
5191
5537
  nativeMarginTop: 0,
5538
+ pendingDataComparison: void 0,
5192
5539
  pendingNativeMVCPAdjust: void 0,
5193
5540
  positions: [],
5194
5541
  props: {},
@@ -5227,22 +5574,29 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5227
5574
  }
5228
5575
  const state = refState.current;
5229
5576
  const isFirstLocal = state.isFirst;
5230
- state.didColumnsChange = numColumnsProp !== state.props.numColumns;
5577
+ const previousNumColumnsProp = state.props.numColumns;
5578
+ state.didColumnsChange = numColumnsProp !== previousNumColumnsProp;
5231
5579
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
5232
5580
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
5233
- const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkActualChange(state, dataProp, state.props.data);
5581
+ const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
5582
+ if (didDataChangeLocal && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
5583
+ clearPreservedInitialScrollTarget(state);
5584
+ }
5234
5585
  if (didDataChangeLocal) {
5235
5586
  state.dataChangeEpoch += 1;
5236
5587
  state.dataChangeNeedsScrollUpdate = true;
5237
5588
  state.didDataChange = true;
5238
5589
  state.previousData = state.props.data;
5239
5590
  }
5240
- const throttleScrollFn = scrollEventThrottle && onScrollProp ? useThrottledOnScroll(onScrollProp, scrollEventThrottle) : onScrollProp;
5591
+ const throttledOnScroll = useThrottledOnScroll(onScrollProp != null ? onScrollProp : noopOnScroll, scrollEventThrottle != null ? scrollEventThrottle : 0);
5592
+ const throttleScrollFn = scrollEventThrottle && onScrollProp ? throttledOnScroll : onScrollProp;
5593
+ const anchoredEndSpaceResolved = Platform2.OS === "web" && anchoredEndSpace ? { ...anchoredEndSpace, includeInEndInset: true } : anchoredEndSpace;
5241
5594
  state.props = {
5242
5595
  alignItemsAtEnd,
5243
5596
  alwaysRender,
5244
5597
  alwaysRenderIndicesArr: alwaysRenderIndices.arr,
5245
5598
  alwaysRenderIndicesSet: alwaysRenderIndices.set,
5599
+ anchoredEndSpace: anchoredEndSpaceResolved,
5246
5600
  animatedProps: animatedPropsInternal,
5247
5601
  contentInset,
5248
5602
  data: dataProp,
@@ -5332,16 +5686,15 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5332
5686
  if (isFirstLocal || didDataChangeLocal || numColumnsProp !== peek$(ctx, "numColumns")) {
5333
5687
  refState.current.lastBatchingAction = Date.now();
5334
5688
  if (!keyExtractorProp && !isFirstLocal && didDataChangeLocal) {
5335
- IS_DEV && !childrenMode && warnDevOnce(
5336
- "keyExtractor",
5337
- "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior."
5338
- );
5339
5689
  refState.current.sizes.clear();
5340
5690
  refState.current.positions.length = 0;
5341
5691
  refState.current.totalSize = 0;
5342
5692
  set$(ctx, "totalSize", 0);
5343
5693
  }
5344
5694
  }
5695
+ if (IS_DEV) {
5696
+ useDevChecks(props);
5697
+ }
5345
5698
  useLayoutEffect(() => {
5346
5699
  handleInitialScrollDataChange(ctx, {
5347
5700
  dataLength: dataProp.length,
@@ -5351,6 +5704,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5351
5704
  useBootstrapInitialScroll: usesBootstrapInitialScroll
5352
5705
  });
5353
5706
  }, [dataProp.length, didDataChangeLocal, initialScrollAtEnd, stylePaddingBottomState, usesBootstrapInitialScroll]);
5707
+ useLayoutEffect(() => {
5708
+ maybeUpdateAnchoredEndSpace(ctx);
5709
+ }, [
5710
+ ctx,
5711
+ dataProp,
5712
+ dataVersion,
5713
+ anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex,
5714
+ anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorMaxSize,
5715
+ anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorOffset,
5716
+ numColumnsProp
5717
+ ]);
5354
5718
  const onLayoutFooter = useCallback(
5355
5719
  (layout) => {
5356
5720
  if (!usesBootstrapInitialScroll) {
@@ -5366,14 +5730,21 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5366
5730
  [dataProp.length, initialScrollAtEnd, horizontal, stylePaddingBottomState, usesBootstrapInitialScroll]
5367
5731
  );
5368
5732
  const onLayoutChange = useCallback(
5369
- (layout) => {
5733
+ (layout, fromLayoutEffect) => {
5734
+ const previousScrollLength = state.scrollLength;
5735
+ const previousOtherAxisSize = state.otherAxisSize;
5370
5736
  handleLayout(ctx, layout, setCanRender);
5737
+ maybeUpdateAnchoredEndSpace(ctx);
5738
+ const didLayoutAffectBootstrapTarget = previousScrollLength !== state.scrollLength || previousOtherAxisSize !== state.otherAxisSize;
5739
+ if (usesBootstrapInitialScroll && !fromLayoutEffect && didLayoutAffectBootstrapTarget) {
5740
+ handleBootstrapInitialScrollLayoutChange(ctx);
5741
+ }
5371
5742
  if (usesBootstrapInitialScroll) {
5372
5743
  return;
5373
5744
  }
5374
5745
  advanceCurrentInitialScrollSession(ctx);
5375
5746
  },
5376
- [usesBootstrapInitialScroll]
5747
+ [dataProp.length, initialScrollAtEnd, stylePaddingBottomState, usesBootstrapInitialScroll]
5377
5748
  );
5378
5749
  const { onLayout } = useOnLayoutSync({
5379
5750
  onLayoutChange,
@@ -5386,6 +5757,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5386
5757
  updateSnapToOffsets(ctx);
5387
5758
  }
5388
5759
  }, [snapToIndices]);
5760
+ useLayoutEffect(
5761
+ () => initializeStateVars(true),
5762
+ [dataVersion, memoizedLastItemKeys.join(","), numColumnsProp, stylePaddingBottomState, stylePaddingTopState]
5763
+ );
5389
5764
  useLayoutEffect(() => {
5390
5765
  const {
5391
5766
  didColumnsChange,
@@ -5395,7 +5770,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5395
5770
  } = state;
5396
5771
  const didAllocateContainers = data.length > 0 && doInitialAllocateContainers(ctx);
5397
5772
  if (!didAllocateContainers && !isFirst && (didDataChange || didColumnsChange)) {
5398
- checkResetContainers(ctx, data);
5773
+ checkResetContainers(ctx, data, { didColumnsChange });
5774
+ }
5775
+ if (didDataChange) {
5776
+ state.pendingDataComparison = void 0;
5399
5777
  }
5400
5778
  state.didColumnsChange = false;
5401
5779
  state.didDataChange = false;
@@ -5410,10 +5788,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5410
5788
  (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { forceFullItemPositions: true });
5411
5789
  }
5412
5790
  }, [extraData, hasOverrideItemLayout, numColumnsProp]);
5413
- useLayoutEffect(
5414
- () => initializeStateVars(true),
5415
- [dataVersion, memoizedLastItemKeys.join(","), numColumnsProp, stylePaddingBottomState, stylePaddingTopState]
5416
- );
5417
5791
  useEffect(() => {
5418
5792
  if (!onMetricsChange) {
5419
5793
  return;
@@ -5446,20 +5820,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5446
5820
  state.viewabilityConfigCallbackPairs = viewability;
5447
5821
  state.enableScrollForNextCalculateItemsInView = !viewability;
5448
5822
  }, [viewabilityConfig, viewabilityConfigCallbackPairs, onViewableItemsChanged]);
5449
- if (!IsNewArchitecture) {
5450
- useInit(() => {
5823
+ useInit(() => {
5824
+ if (!IsNewArchitecture) {
5451
5825
  doInitialAllocateContainers(ctx);
5452
- });
5453
- }
5826
+ }
5827
+ });
5454
5828
  useImperativeHandle(forwardedRef, () => createImperativeHandle(ctx), []);
5455
- if (Platform2.OS === "web") {
5456
- useEffect(() => {
5457
- if (usesBootstrapInitialScroll) {
5458
- return;
5459
- }
5460
- advanceCurrentInitialScrollSession(ctx);
5461
- }, [usesBootstrapInitialScroll]);
5462
- }
5829
+ useEffect(() => {
5830
+ if (Platform2.OS !== "web" || usesBootstrapInitialScroll) {
5831
+ return;
5832
+ }
5833
+ advanceCurrentInitialScrollSession(ctx);
5834
+ }, [ctx, usesBootstrapInitialScroll]);
5463
5835
  const fns = useMemo(
5464
5836
  () => ({
5465
5837
  getRenderedItem: (key) => getRenderedItem(ctx, key),
@@ -5497,7 +5869,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5497
5869
  onScroll: onScrollHandler,
5498
5870
  recycleItems,
5499
5871
  refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React2.cloneElement(refreshControlElement, {
5500
- progressViewOffset: ((_f = refreshControlElement.props.progressViewOffset) != null ? _f : 0) + stylePaddingTopState
5872
+ progressViewOffset: ((_g = refreshControlElement.props.progressViewOffset) != null ? _g : 0) + stylePaddingTopState
5501
5873
  }) : refreshControlElement : onRefresh && /* @__PURE__ */ React2.createElement(
5502
5874
  RefreshControl,
5503
5875
  {
@@ -5508,7 +5880,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
5508
5880
  ),
5509
5881
  refScrollView: combinedRef,
5510
5882
  renderScrollComponent,
5511
- scrollAdjustHandler: (_g = refState.current) == null ? void 0 : _g.scrollAdjustHandler,
5883
+ scrollAdjustHandler: (_h = refState.current) == null ? void 0 : _h.scrollAdjustHandler,
5512
5884
  scrollEventThrottle: 0,
5513
5885
  snapToIndices,
5514
5886
  stickyHeaderIndices,
@@ -5527,4 +5899,4 @@ if (IS_DEV) {
5527
5899
  );
5528
5900
  }
5529
5901
 
5530
- export { LegendList3 as LegendList, typedForwardRef, typedMemo2 as typedMemo, useIsLastItem, useListScrollSize, useRecyclingEffect, useRecyclingState, useSyncLayout, useViewability, useViewabilityAmount };
5902
+ export { LegendList3 as LegendList, typedForwardRef, typedMemo, useIsLastItem, useListScrollSize, useRecyclingEffect, useRecyclingState, useSyncLayout, useViewability, useViewabilityAmount };