@legendapp/list 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
+ ## 3.1.1
2
+
3
+ - Fix: `maintainScrollAtEnd` now stays pinned when a `ListFooterComponent` appears, disappears, or changes size, so chat typing indicators and other dynamic footers do not leave the list slightly above the end. If you use an explicit `maintainScrollAtEnd.on` config, add `footerLayout` to opt into footer size changes.
4
+
1
5
  ## 3.1.0
2
6
 
3
- - Feat: Add `experimental_adaptiveRender` with `useAdaptiveRender` and `useAdaptiveRenderChange`, so item components can render a lighter version while the list is moving quickly and return to normal after scrolling slows.
7
+ - Feat: Add `experimental_adaptiveRender` prop, with `useAdaptiveRender` and `useAdaptiveRenderChange` hooks, so item components can render a lighter version while the list is moving quickly and return to normal after scrolling slows.
4
8
  - Feat: Add `setItemSize` to the list ref for updating a known item's measured size directly when its content changes outside normal layout measurement.
5
9
  - Fix: Web maintainVisibleContentPosition keeps the intended anchor when headers change, browser scroll anchoring runs, or an animated `scrollToEnd` is already in progress. #468 #463
6
10
  - Fix: `initialScrollIndex` and `initialScrollAtEnd` use the latest initial scroll props when data starts empty and loads later, so lists do not scroll to an old startup target.
package/animated.d.ts CHANGED
@@ -460,6 +460,7 @@ interface AlwaysRenderConfig {
460
460
  }
461
461
  interface MaintainScrollAtEndOnOptions {
462
462
  dataChange?: boolean;
463
+ footerLayout?: boolean;
463
464
  itemLayout?: boolean;
464
465
  layout?: boolean;
465
466
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.",
5
5
  "sideEffects": false,
6
6
  "private": false,
package/react-native.d.ts CHANGED
@@ -460,6 +460,7 @@ interface AlwaysRenderConfig {
460
460
  }
461
461
  interface MaintainScrollAtEndOnOptions {
462
462
  dataChange?: boolean;
463
+ footerLayout?: boolean;
463
464
  itemLayout?: boolean;
464
465
  layout?: boolean;
465
466
  }
package/react-native.js CHANGED
@@ -1217,6 +1217,67 @@ function WebAnchoredEndSpace({ horizontal }) {
1217
1217
  return /* @__PURE__ */ React2__namespace.createElement("div", { style }, null);
1218
1218
  }
1219
1219
 
1220
+ // src/core/doMaintainScrollAtEnd.ts
1221
+ function doMaintainScrollAtEnd(ctx) {
1222
+ const state = ctx.state;
1223
+ const {
1224
+ didContainersLayout,
1225
+ pendingNativeMVCPAdjust,
1226
+ refScroller,
1227
+ props: { maintainScrollAtEnd }
1228
+ } = state;
1229
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1230
+ const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1231
+ if (pendingNativeMVCPAdjust) {
1232
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1233
+ return false;
1234
+ }
1235
+ state.pendingMaintainScrollAtEnd = false;
1236
+ if (shouldMaintainScrollAtEnd) {
1237
+ const contentSize = getContentSize(ctx);
1238
+ if (contentSize < state.scrollLength) {
1239
+ state.scroll = 0;
1240
+ }
1241
+ if (!state.maintainingScrollAtEnd) {
1242
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1243
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1244
+ state.maintainingScrollAtEnd = pendingState;
1245
+ requestAnimationFrame(() => {
1246
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
1247
+ state.maintainingScrollAtEnd = activeState;
1248
+ const scroller = refScroller.current;
1249
+ if (state.props.horizontal && isHorizontalRTL(state)) {
1250
+ const currentContentSize = getContentSize(ctx);
1251
+ const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1252
+ const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1253
+ scroller == null ? void 0 : scroller.scrollTo({
1254
+ animated: maintainScrollAtEnd.animated,
1255
+ x: nativeOffset,
1256
+ y: 0
1257
+ });
1258
+ } else {
1259
+ scroller == null ? void 0 : scroller.scrollToEnd({
1260
+ animated: maintainScrollAtEnd.animated
1261
+ });
1262
+ }
1263
+ setTimeout(
1264
+ () => {
1265
+ if (state.maintainingScrollAtEnd === activeState) {
1266
+ state.maintainingScrollAtEnd = void 0;
1267
+ }
1268
+ },
1269
+ maintainScrollAtEnd.animated ? 500 : 0
1270
+ );
1271
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1272
+ state.maintainingScrollAtEnd = void 0;
1273
+ }
1274
+ });
1275
+ }
1276
+ return true;
1277
+ }
1278
+ return false;
1279
+ }
1280
+
1220
1281
  // src/core/calculateOffsetForIndex.ts
1221
1282
  function calculateOffsetForIndex(ctx, index) {
1222
1283
  const state = ctx.state;
@@ -2184,10 +2245,12 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
2184
2245
  // src/core/updateContentMetrics.ts
2185
2246
  var SCROLL_ADJUST_EPSILON = 0.1;
2186
2247
  function setContentLengthSignal(ctx, signalName, size) {
2187
- if (peek$(ctx, signalName) !== size) {
2248
+ const didChange = peek$(ctx, signalName) !== size;
2249
+ if (didChange) {
2188
2250
  set$(ctx, signalName, size);
2189
2251
  updateContentMetricsState(ctx);
2190
2252
  }
2253
+ return didChange;
2191
2254
  }
2192
2255
  function shouldAdjustForHeaderSizeChange(ctx, previousHeaderSize, nextHeaderSize) {
2193
2256
  const { didContainersLayout, didFinishInitialScroll, props, scroll, scrollingTo } = ctx.state;
@@ -2211,7 +2274,7 @@ function setHeaderSize(ctx, size) {
2211
2274
  state.didMeasureHeader = true;
2212
2275
  }
2213
2276
  function setFooterSize(ctx, size) {
2214
- setContentLengthSignal(ctx, "footerSize", size);
2277
+ return setContentLengthSignal(ctx, "footerSize", size);
2215
2278
  }
2216
2279
  function areInsetsEqual(left, right) {
2217
2280
  var _a3, _b, _c, _d, _e, _f, _g, _h;
@@ -2312,14 +2375,25 @@ var ListComponent = typedMemo(function ListComponent2({
2312
2375
  );
2313
2376
  const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
2314
2377
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
2378
+ const updateFooterSize = React2.useCallback(
2379
+ (size, afterSizeUpdate) => {
2380
+ var _a3;
2381
+ const didFooterSizeChange = setFooterSize(ctx, size);
2382
+ afterSizeUpdate == null ? void 0 : afterSizeUpdate();
2383
+ if (didFooterSizeChange && ((_a3 = ctx.state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onFooterLayout)) {
2384
+ doMaintainScrollAtEnd(ctx);
2385
+ }
2386
+ },
2387
+ [ctx]
2388
+ );
2315
2389
  React2.useLayoutEffect(() => {
2316
2390
  if (!ListHeaderComponent) {
2317
2391
  setHeaderSize(ctx, 0);
2318
2392
  }
2319
2393
  if (!ListFooterComponent) {
2320
- setFooterSize(ctx, 0);
2394
+ updateFooterSize(0);
2321
2395
  }
2322
- }, [ListHeaderComponent, ListFooterComponent, ctx]);
2396
+ }, [ListHeaderComponent, ListFooterComponent, ctx, updateFooterSize]);
2323
2397
  const onLayoutHeader = React2.useCallback(
2324
2398
  (rect) => {
2325
2399
  const size = rect[horizontal ? "width" : "height"];
@@ -2330,10 +2404,11 @@ var ListComponent = typedMemo(function ListComponent2({
2330
2404
  const onLayoutFooterInternal = React2.useCallback(
2331
2405
  (rect, fromLayoutEffect) => {
2332
2406
  const size = rect[horizontal ? "width" : "height"];
2333
- setFooterSize(ctx, size);
2334
- onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
2407
+ updateFooterSize(size, () => {
2408
+ onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
2409
+ });
2335
2410
  },
2336
- [ctx, horizontal, onLayoutFooter]
2411
+ [horizontal, onLayoutFooter, updateFooterSize]
2337
2412
  );
2338
2413
  return /* @__PURE__ */ React2__namespace.createElement(
2339
2414
  SnapOrScroll,
@@ -2438,67 +2513,6 @@ function useDevChecksNoop(_props) {
2438
2513
  }
2439
2514
  var useDevChecks = IS_DEV ? useDevChecksImpl : useDevChecksNoop;
2440
2515
 
2441
- // src/core/doMaintainScrollAtEnd.ts
2442
- function doMaintainScrollAtEnd(ctx) {
2443
- const state = ctx.state;
2444
- const {
2445
- didContainersLayout,
2446
- pendingNativeMVCPAdjust,
2447
- refScroller,
2448
- props: { maintainScrollAtEnd }
2449
- } = state;
2450
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
2451
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
2452
- if (pendingNativeMVCPAdjust) {
2453
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
2454
- return false;
2455
- }
2456
- state.pendingMaintainScrollAtEnd = false;
2457
- if (shouldMaintainScrollAtEnd) {
2458
- const contentSize = getContentSize(ctx);
2459
- if (contentSize < state.scrollLength) {
2460
- state.scroll = 0;
2461
- }
2462
- if (!state.maintainingScrollAtEnd) {
2463
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
2464
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
2465
- state.maintainingScrollAtEnd = pendingState;
2466
- requestAnimationFrame(() => {
2467
- if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
2468
- state.maintainingScrollAtEnd = activeState;
2469
- const scroller = refScroller.current;
2470
- if (state.props.horizontal && isHorizontalRTL(state)) {
2471
- const currentContentSize = getContentSize(ctx);
2472
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
2473
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
2474
- scroller == null ? void 0 : scroller.scrollTo({
2475
- animated: maintainScrollAtEnd.animated,
2476
- x: nativeOffset,
2477
- y: 0
2478
- });
2479
- } else {
2480
- scroller == null ? void 0 : scroller.scrollToEnd({
2481
- animated: maintainScrollAtEnd.animated
2482
- });
2483
- }
2484
- setTimeout(
2485
- () => {
2486
- if (state.maintainingScrollAtEnd === activeState) {
2487
- state.maintainingScrollAtEnd = void 0;
2488
- }
2489
- },
2490
- maintainScrollAtEnd.animated ? 500 : 0
2491
- );
2492
- } else if (state.maintainingScrollAtEnd === pendingState) {
2493
- state.maintainingScrollAtEnd = void 0;
2494
- }
2495
- });
2496
- }
2497
- return true;
2498
- }
2499
- return false;
2500
- }
2501
-
2502
2516
  // src/core/mvcp.ts
2503
2517
  var MVCP_POSITION_EPSILON = 0.1;
2504
2518
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -6210,12 +6224,13 @@ function getRenderedItem(ctx, key) {
6210
6224
 
6211
6225
  // src/utils/normalizeMaintainScrollAtEnd.ts
6212
6226
  function normalizeMaintainScrollAtEndOn(on, hasExplicitOn) {
6213
- var _a3, _b, _c;
6227
+ var _a3, _b, _c, _d;
6214
6228
  return {
6215
6229
  animated: false,
6216
6230
  onDataChange: hasExplicitOn ? (_a3 = on == null ? void 0 : on.dataChange) != null ? _a3 : false : true,
6217
- onItemLayout: hasExplicitOn ? (_b = on == null ? void 0 : on.itemLayout) != null ? _b : false : true,
6218
- onLayout: hasExplicitOn ? (_c = on == null ? void 0 : on.layout) != null ? _c : false : true
6231
+ onFooterLayout: hasExplicitOn ? (_b = on == null ? void 0 : on.footerLayout) != null ? _b : false : true,
6232
+ onItemLayout: hasExplicitOn ? (_c = on == null ? void 0 : on.itemLayout) != null ? _c : false : true,
6233
+ onLayout: hasExplicitOn ? (_d = on == null ? void 0 : on.layout) != null ? _d : false : true
6219
6234
  };
6220
6235
  }
6221
6236
  function normalizeMaintainScrollAtEnd(value) {
package/react-native.mjs CHANGED
@@ -1196,6 +1196,67 @@ function WebAnchoredEndSpace({ horizontal }) {
1196
1196
  return /* @__PURE__ */ React2.createElement("div", { style }, null);
1197
1197
  }
1198
1198
 
1199
+ // src/core/doMaintainScrollAtEnd.ts
1200
+ function doMaintainScrollAtEnd(ctx) {
1201
+ const state = ctx.state;
1202
+ const {
1203
+ didContainersLayout,
1204
+ pendingNativeMVCPAdjust,
1205
+ refScroller,
1206
+ props: { maintainScrollAtEnd }
1207
+ } = state;
1208
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1209
+ const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1210
+ if (pendingNativeMVCPAdjust) {
1211
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1212
+ return false;
1213
+ }
1214
+ state.pendingMaintainScrollAtEnd = false;
1215
+ if (shouldMaintainScrollAtEnd) {
1216
+ const contentSize = getContentSize(ctx);
1217
+ if (contentSize < state.scrollLength) {
1218
+ state.scroll = 0;
1219
+ }
1220
+ if (!state.maintainingScrollAtEnd) {
1221
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1222
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1223
+ state.maintainingScrollAtEnd = pendingState;
1224
+ requestAnimationFrame(() => {
1225
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
1226
+ state.maintainingScrollAtEnd = activeState;
1227
+ const scroller = refScroller.current;
1228
+ if (state.props.horizontal && isHorizontalRTL(state)) {
1229
+ const currentContentSize = getContentSize(ctx);
1230
+ const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1231
+ const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1232
+ scroller == null ? void 0 : scroller.scrollTo({
1233
+ animated: maintainScrollAtEnd.animated,
1234
+ x: nativeOffset,
1235
+ y: 0
1236
+ });
1237
+ } else {
1238
+ scroller == null ? void 0 : scroller.scrollToEnd({
1239
+ animated: maintainScrollAtEnd.animated
1240
+ });
1241
+ }
1242
+ setTimeout(
1243
+ () => {
1244
+ if (state.maintainingScrollAtEnd === activeState) {
1245
+ state.maintainingScrollAtEnd = void 0;
1246
+ }
1247
+ },
1248
+ maintainScrollAtEnd.animated ? 500 : 0
1249
+ );
1250
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1251
+ state.maintainingScrollAtEnd = void 0;
1252
+ }
1253
+ });
1254
+ }
1255
+ return true;
1256
+ }
1257
+ return false;
1258
+ }
1259
+
1199
1260
  // src/core/calculateOffsetForIndex.ts
1200
1261
  function calculateOffsetForIndex(ctx, index) {
1201
1262
  const state = ctx.state;
@@ -2163,10 +2224,12 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
2163
2224
  // src/core/updateContentMetrics.ts
2164
2225
  var SCROLL_ADJUST_EPSILON = 0.1;
2165
2226
  function setContentLengthSignal(ctx, signalName, size) {
2166
- if (peek$(ctx, signalName) !== size) {
2227
+ const didChange = peek$(ctx, signalName) !== size;
2228
+ if (didChange) {
2167
2229
  set$(ctx, signalName, size);
2168
2230
  updateContentMetricsState(ctx);
2169
2231
  }
2232
+ return didChange;
2170
2233
  }
2171
2234
  function shouldAdjustForHeaderSizeChange(ctx, previousHeaderSize, nextHeaderSize) {
2172
2235
  const { didContainersLayout, didFinishInitialScroll, props, scroll, scrollingTo } = ctx.state;
@@ -2190,7 +2253,7 @@ function setHeaderSize(ctx, size) {
2190
2253
  state.didMeasureHeader = true;
2191
2254
  }
2192
2255
  function setFooterSize(ctx, size) {
2193
- setContentLengthSignal(ctx, "footerSize", size);
2256
+ return setContentLengthSignal(ctx, "footerSize", size);
2194
2257
  }
2195
2258
  function areInsetsEqual(left, right) {
2196
2259
  var _a3, _b, _c, _d, _e, _f, _g, _h;
@@ -2291,14 +2354,25 @@ var ListComponent = typedMemo(function ListComponent2({
2291
2354
  );
2292
2355
  const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
2293
2356
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
2357
+ const updateFooterSize = useCallback(
2358
+ (size, afterSizeUpdate) => {
2359
+ var _a3;
2360
+ const didFooterSizeChange = setFooterSize(ctx, size);
2361
+ afterSizeUpdate == null ? void 0 : afterSizeUpdate();
2362
+ if (didFooterSizeChange && ((_a3 = ctx.state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onFooterLayout)) {
2363
+ doMaintainScrollAtEnd(ctx);
2364
+ }
2365
+ },
2366
+ [ctx]
2367
+ );
2294
2368
  useLayoutEffect(() => {
2295
2369
  if (!ListHeaderComponent) {
2296
2370
  setHeaderSize(ctx, 0);
2297
2371
  }
2298
2372
  if (!ListFooterComponent) {
2299
- setFooterSize(ctx, 0);
2373
+ updateFooterSize(0);
2300
2374
  }
2301
- }, [ListHeaderComponent, ListFooterComponent, ctx]);
2375
+ }, [ListHeaderComponent, ListFooterComponent, ctx, updateFooterSize]);
2302
2376
  const onLayoutHeader = useCallback(
2303
2377
  (rect) => {
2304
2378
  const size = rect[horizontal ? "width" : "height"];
@@ -2309,10 +2383,11 @@ var ListComponent = typedMemo(function ListComponent2({
2309
2383
  const onLayoutFooterInternal = useCallback(
2310
2384
  (rect, fromLayoutEffect) => {
2311
2385
  const size = rect[horizontal ? "width" : "height"];
2312
- setFooterSize(ctx, size);
2313
- onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
2386
+ updateFooterSize(size, () => {
2387
+ onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
2388
+ });
2314
2389
  },
2315
- [ctx, horizontal, onLayoutFooter]
2390
+ [horizontal, onLayoutFooter, updateFooterSize]
2316
2391
  );
2317
2392
  return /* @__PURE__ */ React2.createElement(
2318
2393
  SnapOrScroll,
@@ -2417,67 +2492,6 @@ function useDevChecksNoop(_props) {
2417
2492
  }
2418
2493
  var useDevChecks = IS_DEV ? useDevChecksImpl : useDevChecksNoop;
2419
2494
 
2420
- // src/core/doMaintainScrollAtEnd.ts
2421
- function doMaintainScrollAtEnd(ctx) {
2422
- const state = ctx.state;
2423
- const {
2424
- didContainersLayout,
2425
- pendingNativeMVCPAdjust,
2426
- refScroller,
2427
- props: { maintainScrollAtEnd }
2428
- } = state;
2429
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
2430
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
2431
- if (pendingNativeMVCPAdjust) {
2432
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
2433
- return false;
2434
- }
2435
- state.pendingMaintainScrollAtEnd = false;
2436
- if (shouldMaintainScrollAtEnd) {
2437
- const contentSize = getContentSize(ctx);
2438
- if (contentSize < state.scrollLength) {
2439
- state.scroll = 0;
2440
- }
2441
- if (!state.maintainingScrollAtEnd) {
2442
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
2443
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
2444
- state.maintainingScrollAtEnd = pendingState;
2445
- requestAnimationFrame(() => {
2446
- if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
2447
- state.maintainingScrollAtEnd = activeState;
2448
- const scroller = refScroller.current;
2449
- if (state.props.horizontal && isHorizontalRTL(state)) {
2450
- const currentContentSize = getContentSize(ctx);
2451
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
2452
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
2453
- scroller == null ? void 0 : scroller.scrollTo({
2454
- animated: maintainScrollAtEnd.animated,
2455
- x: nativeOffset,
2456
- y: 0
2457
- });
2458
- } else {
2459
- scroller == null ? void 0 : scroller.scrollToEnd({
2460
- animated: maintainScrollAtEnd.animated
2461
- });
2462
- }
2463
- setTimeout(
2464
- () => {
2465
- if (state.maintainingScrollAtEnd === activeState) {
2466
- state.maintainingScrollAtEnd = void 0;
2467
- }
2468
- },
2469
- maintainScrollAtEnd.animated ? 500 : 0
2470
- );
2471
- } else if (state.maintainingScrollAtEnd === pendingState) {
2472
- state.maintainingScrollAtEnd = void 0;
2473
- }
2474
- });
2475
- }
2476
- return true;
2477
- }
2478
- return false;
2479
- }
2480
-
2481
2495
  // src/core/mvcp.ts
2482
2496
  var MVCP_POSITION_EPSILON = 0.1;
2483
2497
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -6189,12 +6203,13 @@ function getRenderedItem(ctx, key) {
6189
6203
 
6190
6204
  // src/utils/normalizeMaintainScrollAtEnd.ts
6191
6205
  function normalizeMaintainScrollAtEndOn(on, hasExplicitOn) {
6192
- var _a3, _b, _c;
6206
+ var _a3, _b, _c, _d;
6193
6207
  return {
6194
6208
  animated: false,
6195
6209
  onDataChange: hasExplicitOn ? (_a3 = on == null ? void 0 : on.dataChange) != null ? _a3 : false : true,
6196
- onItemLayout: hasExplicitOn ? (_b = on == null ? void 0 : on.itemLayout) != null ? _b : false : true,
6197
- onLayout: hasExplicitOn ? (_c = on == null ? void 0 : on.layout) != null ? _c : false : true
6210
+ onFooterLayout: hasExplicitOn ? (_b = on == null ? void 0 : on.footerLayout) != null ? _b : false : true,
6211
+ onItemLayout: hasExplicitOn ? (_c = on == null ? void 0 : on.itemLayout) != null ? _c : false : true,
6212
+ onLayout: hasExplicitOn ? (_d = on == null ? void 0 : on.layout) != null ? _d : false : true
6198
6213
  };
6199
6214
  }
6200
6215
  function normalizeMaintainScrollAtEnd(value) {
@@ -483,6 +483,7 @@ interface AlwaysRenderConfig {
483
483
  }
484
484
  interface MaintainScrollAtEndOnOptions {
485
485
  dataChange?: boolean;
486
+ footerLayout?: boolean;
486
487
  itemLayout?: boolean;
487
488
  layout?: boolean;
488
489
  }