| ... | ... | @@ -241,7 +241,7 @@ index ce1fe00001c9e5aee6c6ea8bb2d4757d4586d002..3ccf6f16067152dfcb0c143371e2ec6a |
| 241 | 241 | * Number of columns to render items in. |
| 242 | 242 | * @default 1 |
| 243 | 243 | diff --git a/react-native.js b/react-native.js |
| 244 | | index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac0047a8ead3 100644 |
| 244 | index b3c5a306b293f797a8b338adfca3060c0f6db22b..5ac6bbe8fe40bf252fefa6b885c2196d0677d75f 100644 |
| 245 | 245 | --- a/react-native.js |
| 246 | 246 | +++ b/react-native.js |
| 247 | 247 | @@ -717,6 +717,15 @@ function hasActiveInitialScroll(state) { |
| ... | ... | @@ -269,16 +269,25 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 269 | 269 | set$(ctx, "isNearEnd", isContentLess || distanceFromEnd <= onEndReachedThreshold * scrollLength); |
| 270 | 270 | set$( |
| 271 | 271 | ctx, |
| 272 | | @@ -954,7 +963,7 @@ function setInitialRenderState(ctx, { |
| 272 | @@ -954,7 +963,16 @@ function setInitialRenderState(ctx, { |
| 273 | 273 | if (didInitialScroll) { |
| 274 | 274 | state.didFinishInitialScroll = true; |
| 275 | 275 | } |
| 276 | 276 | - const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll); |
| 277 | + // Native contentOffset can seed an end target without dispatchInitialScroll. |
| 278 | + // Both overflow completion paths must wait for the native end landing. |
| 279 | + // Underflow needs no scroll: preserve UIKit's untouched resting position. |
| 280 | + if (state.didContainersLayout && state.didFinishInitialScroll && state.initialScroll && state.initialScroll.viewPosition === 1 && state.initialScroll.index === state.props.data.length - 1 && state.props.data.length > 0) { |
| 281 | + const insetStartAdjustment = getContentInsetStartAdjustment(ctx); |
| 282 | + if (insetStartAdjustment > 0 && getContentSize(ctx) > state.scrollLength - insetStartAdjustment + 1) { |
| 283 | + startInsetEndSettleWatchdog(ctx); |
| 284 | + } |
| 285 | + } |
| 277 | 286 | + const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll && !state.insetEndRevealHold); |
| 278 | 287 | if (isReadyToRender && !peek$(ctx, "readyToRender")) { |
| 279 | 288 | set$(ctx, "readyToRender", true); |
| 280 | 289 | setAdaptiveRender(ctx, "normal", "ready"); |
| 281 | | @@ -1090,7 +1099,7 @@ function getRawContentLength(ctx) { |
| 290 | @@ -1090,7 +1108,7 @@ function getRawContentLength(ctx) { |
| 282 | 291 | function getAlignItemsAtEndPadding(ctx) { |
| 283 | 292 | const { state } = ctx; |
| 284 | 293 | const shouldPad = !!state.props.alignItemsAtEndPaddingEnabled && !state.props.horizontal && state.props.data.length > 0 && state.scrollLength > 0; |
| ... | ... | @@ -287,7 +296,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 287 | 296 | } |
| 288 | 297 | function updateContentMetricsState(ctx) { |
| 289 | 298 | const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0; |
| 290 | | @@ -1115,6 +1124,10 @@ function addTotalSize(ctx, key, add, notifyTotalSize = true) { |
| 299 | @@ -1115,6 +1133,10 @@ function addTotalSize(ctx, key, add, notifyTotalSize = true) { |
| 291 | 300 | totalSize += add; |
| 292 | 301 | } |
| 293 | 302 | if (prevTotalSize !== totalSize) { |
| ... | ... | @@ -298,7 +307,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 298 | 307 | if (!IsNewArchitecture && state.initialScroll && totalSize < prevTotalSize) { |
| 299 | 308 | state.pendingTotalSize = totalSize; |
| 300 | 309 | } else { |
| 301 | | @@ -1304,18 +1317,23 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) { |
| 310 | @@ -1304,18 +1326,23 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) { |
| 302 | 311 | } |
| 303 | 312 | |
| 304 | 313 | // src/core/clampScrollOffset.ts |
| ... | ... | @@ -324,7 +333,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 324 | 333 | return clampedOffset; |
| 325 | 334 | } |
| 326 | 335 | |
| 327 | | @@ -1451,10 +1469,10 @@ function checkFinishedScrollFrame(ctx) { |
| 336 | @@ -1451,10 +1478,10 @@ function checkFinishedScrollFrame(ctx) { |
| 328 | 337 | finishScrollTo(ctx); |
| 329 | 338 | } |
| 330 | 339 | } |
| ... | ... | @@ -337,7 +346,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 337 | 346 | x: ctx.state.props.horizontal ? offset : 0, |
| 338 | 347 | y: ctx.state.props.horizontal ? 0 : offset |
| 339 | 348 | }); |
| 340 | | @@ -1503,7 +1521,10 @@ function checkFinishedScrollFallback(ctx) { |
| 349 | @@ -1503,7 +1530,10 @@ function checkFinishedScrollFallback(ctx) { |
| 341 | 350 | ); |
| 342 | 351 | scheduleFallbackCheck(SILENT_INITIAL_SCROLL_RETRY_DELAY_MS); |
| 343 | 352 | } else if (shouldRetryUnalignedEndScroll) { |
| ... | ... | @@ -349,7 +358,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 349 | 358 | scheduleFallbackCheck(100); |
| 350 | 359 | } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) { |
| 351 | 360 | finishScrollTo(ctx); |
| 352 | | @@ -1560,15 +1581,28 @@ function doMaintainScrollAtEnd(ctx) { |
| 361 | @@ -1560,15 +1590,28 @@ function doMaintainScrollAtEnd(ctx) { |
| 353 | 362 | } = state; |
| 354 | 363 | const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold"); |
| 355 | 364 | const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout); |
| ... | ... | @@ -379,7 +388,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 379 | 388 | } |
| 380 | 389 | if (!state.maintainingScrollAtEnd) { |
| 381 | 390 | const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant"; |
| 382 | | @@ -1591,9 +1625,18 @@ function doMaintainScrollAtEnd(ctx) { |
| 391 | @@ -1591,9 +1634,18 @@ function doMaintainScrollAtEnd(ctx) { |
| 383 | 392 | y: 0 |
| 384 | 393 | }); |
| 385 | 394 | } else { |
| ... | ... | @@ -401,7 +410,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 401 | 410 | } |
| 402 | 411 | setTimeout( |
| 403 | 412 | () => { |
| 404 | | @@ -1624,6 +1667,10 @@ function doMaintainScrollAtEnd(ctx) { |
| 413 | @@ -1624,6 +1676,10 @@ function doMaintainScrollAtEnd(ctx) { |
| 405 | 414 | function requestAdjust(ctx, positionDiff, dataChanged) { |
| 406 | 415 | const state = ctx.state; |
| 407 | 416 | if (Math.abs(positionDiff) > 0.1) { |
| ... | ... | @@ -412,7 +421,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 412 | 421 | const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff; |
| 413 | 422 | const doit = () => { |
| 414 | 423 | if (needsScrollWorkaround) { |
| 415 | | @@ -1728,7 +1775,9 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) { |
| 424 | @@ -1728,7 +1784,9 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) { |
| 416 | 425 | if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) { |
| 417 | 426 | return 0; |
| 418 | 427 | } |
| ... | ... | @@ -423,7 +432,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 423 | 432 | const clampDelta = maxScroll - state.scroll; |
| 424 | 433 | if (unresolvedAmount < 0) { |
| 425 | 434 | return Math.max(unresolvedAmount, Math.min(0, clampDelta)); |
| 426 | | @@ -1790,7 +1839,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { |
| 435 | @@ -1790,7 +1848,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { |
| 427 | 436 | settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta); |
| 428 | 437 | return true; |
| 429 | 438 | } |
| ... | ... | @@ -432,7 +441,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 432 | 441 | const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll); |
| 433 | 442 | const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON; |
| 434 | 443 | if (isAtExpectedNativeClamp) { |
| 435 | | @@ -1923,7 +1972,7 @@ function prepareMVCP(ctx, dataChanged) { |
| 444 | @@ -1923,7 +1981,7 @@ function prepareMVCP(ctx, dataChanged) { |
| 436 | 445 | if (diff > 0) { |
| 437 | 446 | diff = Math.max(0, totalSize - state.scroll - state.scrollLength); |
| 438 | 447 | } else { |
| ... | ... | @@ -441,7 +450,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 441 | 450 | state.scroll = maxScroll; |
| 442 | 451 | state.scrollPending = maxScroll; |
| 443 | 452 | diff = 0; |
| 444 | | @@ -2320,8 +2369,121 @@ function scrollToIndex(ctx, { |
| 453 | @@ -2320,8 +2378,121 @@ function scrollToIndex(ctx, { |
| 445 | 454 | } |
| 446 | 455 | |
| 447 | 456 | // src/core/initialScroll.ts |
| ... | ... | @@ -451,11 +460,11 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 451 | 460 | +var INSET_END_REVEAL_MAX_HOLD_FRAMES = 40; |
| 452 | 461 | +function startInsetEndSettleWatchdog(ctx) { |
| 453 | 462 | + const state = ctx.state; |
| 454 | | + if (state.insetEndSettleWatchdogActive) { |
| 463 | + if (state.insetEndSettleWatchdogStarted || state.didLoad) { |
| 455 | 464 | + return; |
| 456 | 465 | + } |
| 466 | + state.insetEndSettleWatchdogStarted = true; |
| 457 | 467 | + state.insetEndSettleWatchdogActive = true; |
| 458 | | + state.didUserDrag = false; |
| 459 | 468 | + // Hold the readyToRender opacity gate until the end landing is stable, so |
| 460 | 469 | + // the estimated-to-measured settle chase happens before first VISIBLE |
| 461 | 470 | + // paint instead of in front of the user. Capped so slow measurement can |
| ... | ... | @@ -464,6 +473,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 464 | 473 | + let frames = 0; |
| 465 | 474 | + let settledFrames = 0; |
| 466 | 475 | + let revealStableFrames = 0; |
| 476 | + let previousEndOffset; |
| 467 | 477 | + const releaseRevealHold = () => { |
| 468 | 478 | + if (state.insetEndRevealHold) { |
| 469 | 479 | + state.insetEndRevealHold = false; |
| ... | ... | @@ -503,33 +513,32 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 503 | 513 | + const insetStartAdjustment = getContentInsetStartAdjustment(ctx); |
| 504 | 514 | + const contentSize = getContentSize(ctx); |
| 505 | 515 | + const scrollLength = state.scrollLength; |
| 506 | | + if (insetStartAdjustment > 0 && scrollLength > 0 && Number.isFinite(contentSize) && contentSize > scrollLength && !state.scrollingTo && !state.maintainingScrollAtEnd) { |
| 507 | | + const endOffset = Math.max(-insetStartAdjustment, contentSize - scrollLength); |
| 508 | | + const distance = endOffset - state.scroll; |
| 509 | | + // Estimated row sizes converging to measured ones can strand the initial |
| 510 | | + // end landing when the library's own end-anchor bookkeeping gives up. |
| 511 | | + // While still near the end (never fighting a user who scrolled away), |
| 512 | | + // re-pin to the current true end until sizes stop changing. |
| 513 | | + if (Math.abs(distance) > 2 && Math.abs(distance) <= scrollLength * 0.5) { |
| 514 | | + settledFrames = 0; |
| 515 | | + revealStableFrames = 0; |
| 516 | + const endOffset = Math.max(-insetStartAdjustment, contentSize - scrollLength); |
| 517 | + // state.scroll is optimistic: non-animated scrollTo writes it before UIKit |
| 518 | + // moves. Only a native scroll event proves that the requested offset landed. |
| 519 | + const nativeOffset = state.lastNativeScroll; |
| 520 | + const hasNativeOffset = typeof nativeOffset === "number" && Number.isFinite(nativeOffset); |
| 521 | + const distance = hasNativeOffset ? endOffset - nativeOffset : Infinity; |
| 522 | + const isIdle = !state.scrollingTo && !state.maintainingScrollAtEnd; |
| 523 | + const hasViewport = insetStartAdjustment > 0 && scrollLength > 0 && Number.isFinite(contentSize); |
| 524 | + const isStable = hasViewport && isIdle && Math.abs(distance) <= 2 && previousEndOffset !== void 0 && Math.abs(endOffset - previousEndOffset) <= 1; |
| 525 | + previousEndOffset = endOffset; |
| 526 | + if (isStable) { |
| 527 | + settledFrames++; |
| 528 | + revealStableFrames++; |
| 529 | + if (revealStableFrames >= INSET_END_REVEAL_STABLE_FRAMES) { |
| 530 | + onRevealStability(); |
| 531 | + } |
| 532 | + } else { |
| 533 | + settledFrames = 0; |
| 534 | + revealStableFrames = 0; |
| 535 | + // Keep the existing near-end correction limit. In-flight scrolls and |
| 536 | + // unknown native offsets wait; neither counts as a settled frame. |
| 537 | + if (hasViewport && isIdle && Math.abs(distance) > 2 && Math.abs(distance) <= scrollLength * 0.5) { |
| 516 | 538 | + const scroller = state.refScroller.current; |
| 517 | 539 | + if (scroller) { |
| 518 | 540 | + scroller.scrollTo({ animated: false, x: 0, y: endOffset }); |
| 519 | 541 | + } |
| 520 | | + } else { |
| 521 | | + settledFrames++; |
| 522 | | + revealStableFrames++; |
| 523 | | + if (revealStableFrames >= INSET_END_REVEAL_STABLE_FRAMES) { |
| 524 | | + onRevealStability(); |
| 525 | | + } |
| 526 | | + } |
| 527 | | + } else { |
| 528 | | + // Conditions that make re-pinning unnecessary (underflow, in-flight |
| 529 | | + // programmatic scroll) count toward stability for the reveal. |
| 530 | | + revealStableFrames++; |
| 531 | | + if (revealStableFrames >= INSET_END_REVEAL_STABLE_FRAMES) { |
| 532 | | + onRevealStability(); |
| 533 | 542 | + } |
| 534 | 543 | + } |
| 535 | 544 | + requestAnimationFrame(tick); |
| ... | ... | @@ -563,7 +572,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 563 | 572 | const requestedIndex = target.index; |
| 564 | 573 | const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0; |
| 565 | 574 | const itemSize = getItemSizeAtIndex(ctx, index); |
| 566 | | @@ -2747,7 +2909,9 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) { |
| 575 | @@ -2747,7 +2918,9 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) { |
| 567 | 576 | return; |
| 568 | 577 | } |
| 569 | 578 | if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) { |
| ... | ... | @@ -574,7 +583,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 574 | 583 | if (!shouldKeepEndTargetAlive) { |
| 575 | 584 | if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) { |
| 576 | 585 | clearPendingInitialScrollFooterLayout(ctx, { |
| 577 | | @@ -4672,7 +4836,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { |
| 586 | @@ -4672,7 +4845,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { |
| 578 | 587 | contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap); |
| 579 | 588 | contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props); |
| 580 | 589 | isReady = !hasUnknownTailSize; |
| ... | ... | @@ -584,7 +593,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 584 | 593 | } else if (anchorIndex >= 0) { |
| 585 | 594 | isReady = false; |
| 586 | 595 | } |
| 587 | | @@ -4692,6 +4857,12 @@ function maybeUpdateAnchoredEndSpace(ctx) { |
| 596 | @@ -4692,6 +4866,12 @@ function maybeUpdateAnchoredEndSpace(ctx) { |
| 588 | 597 | updateScroll(ctx, state.scroll, true, { markHasScrolled: false }); |
| 589 | 598 | } |
| 590 | 599 | (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize }); |
| ... | ... | @@ -597,7 +606,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 597 | 606 | } |
| 598 | 607 | return nextSize; |
| 599 | 608 | } |
| 600 | | @@ -5715,6 +5886,7 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 609 | @@ -5715,6 +5895,7 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 601 | 610 | horizontal |
| 602 | 611 | }) { |
| 603 | 612 | const ctx = useStateContext(); |
| ... | ... | @@ -605,7 +614,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 605 | 614 | const columnWrapperStyle = ctx.columnWrapperStyle; |
| 606 | 615 | const animSize = useValue$("totalSize"); |
| 607 | 616 | const [readyToRender, numColumns, otherAxisSize = 0] = useArr$(["readyToRender", "numColumns", "otherAxisSize"]); |
| 608 | | @@ -5725,6 +5897,13 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 617 | @@ -5725,6 +5906,13 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 609 | 618 | opacity: isVisible ? 1 : 0, |
| 610 | 619 | width: animSize |
| 611 | 620 | } : { height: animSize, minWidth: otherAxisSize, opacity: isVisible ? 1 : 0 }; |
| ... | ... | @@ -619,7 +628,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 619 | 628 | if (columnWrapperStyle) { |
| 620 | 629 | const { columnGap, rowGap, gap } = columnWrapperStyle; |
| 621 | 630 | const gapX = columnGap || gap || 0; |
| 622 | | @@ -5745,7 +5924,8 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 631 | @@ -5745,7 +5933,8 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 623 | 632 | } |
| 624 | 633 | } |
| 625 | 634 | } |
| ... | ... | @@ -629,7 +638,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 629 | 638 | }); |
| 630 | 639 | var Containers = typedMemo(function Containers2({ |
| 631 | 640 | freshDataTransitionEpoch, |
| 632 | | @@ -5896,7 +6076,12 @@ var StyleSheet = ReactNative.StyleSheet; |
| 641 | @@ -5896,7 +6085,12 @@ var StyleSheet = ReactNative.StyleSheet; |
| 633 | 642 | |
| 634 | 643 | // src/components/ListComponent.tsx |
| 635 | 644 | var AlignItemsAtEndSpacer = typedMemo(function AlignItemsAtEndSpacer2({ horizontal }) { |
| ... | ... | @@ -642,7 +651,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 642 | 651 | if (alignItemsAtEndPadding <= 0) { |
| 643 | 652 | return null; |
| 644 | 653 | } |
| 645 | | @@ -5929,8 +6114,12 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 654 | @@ -5929,8 +6123,12 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 646 | 655 | refScrollView, |
| 647 | 656 | renderScrollComponent, |
| 648 | 657 | onLayoutFooter, |
| ... | ... | @@ -655,7 +664,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 655 | 664 | scrollAdjustHandler, |
| 656 | 665 | snapToIndices, |
| 657 | 666 | stickyHeaderConfig, |
| 658 | | @@ -6001,7 +6190,17 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 667 | @@ -6001,7 +6199,17 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 659 | 668 | SnapOrScroll, |
| 660 | 669 | { |
| 661 | 670 | ...rest, |
| ... | ... | @@ -674,7 +683,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 674 | 683 | contentContainerStyle: [ |
| 675 | 684 | horizontal ? { height: "100%" } : {}, |
| 676 | 685 | contentContainerStyle, |
| 677 | | @@ -6010,7 +6209,10 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 686 | @@ -6010,7 +6218,10 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 678 | 687 | ], |
| 679 | 688 | contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0, |
| 680 | 689 | horizontal, |
| ... | ... | @@ -686,7 +695,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 686 | 695 | onLayout, |
| 687 | 696 | onScroll: onScroll2, |
| 688 | 697 | ref: refScrollView, |
| 689 | | @@ -6751,7 +6953,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) { |
| 698 | @@ -6751,7 +6962,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) { |
| 690 | 699 | endBuffered: state.endBuffered, |
| 691 | 700 | getAverageItemSizes: () => getAverageItemSizes(state), |
| 692 | 701 | indexByKey: (key) => state.indexByKey.get(key), |
| ... | ... | @@ -695,7 +704,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 695 | 704 | isAtStart: peek$(ctx, "isAtStart"), |
| 696 | 705 | isEndReached: state.isEndReached, |
| 697 | 706 | isNearEnd: peek$(ctx, "isNearEnd"), |
| 698 | | @@ -7075,6 +7277,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 707 | @@ -7075,6 +7286,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 699 | 708 | dataVersion, |
| 700 | 709 | drawDistance = 250, |
| 701 | 710 | contentInsetEndAdjustment, |
| ... | ... | @@ -703,7 +712,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 703 | 712 | estimatedItemSize = 100, |
| 704 | 713 | estimatedListSize, |
| 705 | 714 | extraData, |
| 706 | | @@ -7132,10 +7335,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 715 | @@ -7132,10 +7344,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 707 | 716 | const animatedPropsInternal = props.animatedPropsInternal; |
| 708 | 717 | const anchoredEndSpaceOwner = (_a3 = props.anchoredEndSpaceOwnerInternal) != null ? _a3 : "list"; |
| 709 | 718 | const positionComponentInternal = props.positionComponentInternal; |
| ... | ... | @@ -716,7 +725,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 716 | 725 | stickyPositionComponentInternal: _stickyPositionComponentInternal, |
| 717 | 726 | ...restProps |
| 718 | 727 | } = rest; |
| 719 | | @@ -7200,7 +7405,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 728 | @@ -7200,7 +7414,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 720 | 729 | const combinedRef = useCombinedRef(refScroller, refScrollView); |
| 721 | 730 | const keyExtractor = keyExtractorProp != null ? keyExtractorProp : ((_item, index) => index.toString()); |
| 722 | 731 | const stickyHeaderIndices = stickyHeaderIndicesProp; |
| ... | ... | @@ -725,7 +734,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 725 | 734 | const previousContentInsetEndAdjustmentRef = React2.useRef(contentInsetEndAdjustmentResolved); |
| 726 | 735 | const alwaysRenderIndices = React2.useMemo(() => { |
| 727 | 736 | const indices = getAlwaysRenderIndices(alwaysRender, dataProp, keyExtractor, anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex); |
| 728 | | @@ -7341,6 +7546,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 737 | @@ -7341,6 +7555,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 729 | 738 | contentContainerAlignItems: contentContainerStyle.alignItems, |
| 730 | 739 | contentInset, |
| 731 | 740 | contentInsetEndAdjustment: contentInsetEndAdjustmentResolved, |
| ... | ... | @@ -733,7 +742,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 733 | 742 | data: dataProp, |
| 734 | 743 | dataKey, |
| 735 | 744 | dataVersion, |
| 736 | | @@ -7372,6 +7578,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 745 | @@ -7372,6 +7587,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 737 | 746 | renderItem, |
| 738 | 747 | rtl, |
| 739 | 748 | snapToIndices, |
| ... | ... | @@ -741,7 +750,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 741 | 750 | stickyHeaderIndicesArr: stickyHeaderIndices != null ? stickyHeaderIndices : [], |
| 742 | 751 | stickyHeaderIndicesSet: React2.useMemo(() => new Set(stickyHeaderIndices != null ? stickyHeaderIndices : []), [stickyHeaderIndices == null ? void 0 : stickyHeaderIndices.join(",")]), |
| 743 | 752 | stickyPositionComponentInternal, |
| 744 | | @@ -7423,6 +7630,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 753 | @@ -7423,6 +7639,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 745 | 754 | return void 0; |
| 746 | 755 | } |
| 747 | 756 | const resolvedOffset = (_a4 = initialScroll.contentOffset) != null ? _a4 : resolveInitialScrollOffset(ctx, initialScroll); |
| ... | ... | @@ -755,7 +764,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 755 | 764 | return usesBootstrapInitialScroll && ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap" && Platform.OS === "web" ? void 0 : resolvedOffset; |
| 756 | 765 | }, [usesBootstrapInitialScroll]); |
| 757 | 766 | React2.useLayoutEffect(() => { |
| 758 | | @@ -7547,6 +7761,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 767 | @@ -7547,6 +7770,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 759 | 768 | [ |
| 760 | 769 | dataKey, |
| 761 | 770 | dataVersion, |
| ... | ... | @@ -763,7 +772,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 763 | 772 | memoizedLastItemKeys.join(","), |
| 764 | 773 | numColumnsProp, |
| 765 | 774 | nextScrollAxisGap, |
| 766 | | @@ -7643,6 +7858,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 775 | @@ -7643,6 +7867,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 767 | 776 | () => ({ |
| 768 | 777 | getRenderedItem: (key) => getRenderedItem(ctx, key), |
| 769 | 778 | onMomentumScrollEnd: (event) => { |
| ... | ... | @@ -771,7 +780,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 771 | 780 | checkFinishedScrollFallback(ctx); |
| 772 | 781 | if (state.props.onMomentumScrollEnd) { |
| 773 | 782 | state.props.onMomentumScrollEnd(event); |
| 774 | | @@ -7651,6 +7867,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 783 | @@ -7651,6 +7876,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 775 | 784 | onScroll: (event) => onScroll(ctx, event), |
| 776 | 785 | onScrollBeginDrag: (event) => { |
| 777 | 786 | var _a4, _b2; |
| ... | ... | @@ -780,7 +789,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 780 | 789 | prepareReachedEdgeForNextUserScroll(ctx); |
| 781 | 790 | (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event); |
| 782 | 791 | }, |
| 783 | | @@ -7676,11 +7894,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 792 | @@ -7676,11 +7903,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 784 | 793 | ListFooterComponent, |
| 785 | 794 | ListFooterComponentStyle, |
| 786 | 795 | ListHeaderComponent, |
| ... | ... | @@ -800,7 +809,7 @@ index b3c5a306b293f797a8b338adfca3060c0f6db22b..ddca4bcd8a5dc5863cb65e03b6cfac00 |
| 800 | 809 | recycleItems, |
| 801 | 810 | refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React2__namespace.cloneElement(refreshControlElement, { |
| 802 | 811 | diff --git a/react-native.mjs b/react-native.mjs |
| 803 | | index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97440bc623 100644 |
| 812 | index 40e87cda8c9bc79a889e5542f29af429a24b24d4..93aac741d2cf77ce35996362439108176f19da7a 100644 |
| 804 | 813 | --- a/react-native.mjs |
| 805 | 814 | +++ b/react-native.mjs |
| 806 | 815 | @@ -696,6 +696,15 @@ function hasActiveInitialScroll(state) { |
| ... | ... | @@ -828,16 +837,25 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 828 | 837 | set$(ctx, "isNearEnd", isContentLess || distanceFromEnd <= onEndReachedThreshold * scrollLength); |
| 829 | 838 | set$( |
| 830 | 839 | ctx, |
| 831 | | @@ -933,7 +942,7 @@ function setInitialRenderState(ctx, { |
| 840 | @@ -933,7 +942,16 @@ function setInitialRenderState(ctx, { |
| 832 | 841 | if (didInitialScroll) { |
| 833 | 842 | state.didFinishInitialScroll = true; |
| 834 | 843 | } |
| 835 | 844 | - const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll); |
| 845 | + // Native contentOffset can seed an end target without dispatchInitialScroll. |
| 846 | + // Both overflow completion paths must wait for the native end landing. |
| 847 | + // Underflow needs no scroll: preserve UIKit's untouched resting position. |
| 848 | + if (state.didContainersLayout && state.didFinishInitialScroll && state.initialScroll && state.initialScroll.viewPosition === 1 && state.initialScroll.index === state.props.data.length - 1 && state.props.data.length > 0) { |
| 849 | + const insetStartAdjustment = getContentInsetStartAdjustment(ctx); |
| 850 | + if (insetStartAdjustment > 0 && getContentSize(ctx) > state.scrollLength - insetStartAdjustment + 1) { |
| 851 | + startInsetEndSettleWatchdog(ctx); |
| 852 | + } |
| 853 | + } |
| 836 | 854 | + const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll && !state.insetEndRevealHold); |
| 837 | 855 | if (isReadyToRender && !peek$(ctx, "readyToRender")) { |
| 838 | 856 | set$(ctx, "readyToRender", true); |
| 839 | 857 | setAdaptiveRender(ctx, "normal", "ready"); |
| 840 | | @@ -1069,7 +1078,7 @@ function getRawContentLength(ctx) { |
| 858 | @@ -1069,7 +1087,7 @@ function getRawContentLength(ctx) { |
| 841 | 859 | function getAlignItemsAtEndPadding(ctx) { |
| 842 | 860 | const { state } = ctx; |
| 843 | 861 | const shouldPad = !!state.props.alignItemsAtEndPaddingEnabled && !state.props.horizontal && state.props.data.length > 0 && state.scrollLength > 0; |
| ... | ... | @@ -846,7 +864,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 846 | 864 | } |
| 847 | 865 | function updateContentMetricsState(ctx) { |
| 848 | 866 | const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0; |
| 849 | | @@ -1094,6 +1103,10 @@ function addTotalSize(ctx, key, add, notifyTotalSize = true) { |
| 867 | @@ -1094,6 +1112,10 @@ function addTotalSize(ctx, key, add, notifyTotalSize = true) { |
| 850 | 868 | totalSize += add; |
| 851 | 869 | } |
| 852 | 870 | if (prevTotalSize !== totalSize) { |
| ... | ... | @@ -857,7 +875,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 857 | 875 | if (!IsNewArchitecture && state.initialScroll && totalSize < prevTotalSize) { |
| 858 | 876 | state.pendingTotalSize = totalSize; |
| 859 | 877 | } else { |
| 860 | | @@ -1283,18 +1296,23 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) { |
| 878 | @@ -1283,18 +1305,23 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) { |
| 861 | 879 | } |
| 862 | 880 | |
| 863 | 881 | // src/core/clampScrollOffset.ts |
| ... | ... | @@ -883,7 +901,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 883 | 901 | return clampedOffset; |
| 884 | 902 | } |
| 885 | 903 | |
| 886 | | @@ -1430,10 +1448,10 @@ function checkFinishedScrollFrame(ctx) { |
| 904 | @@ -1430,10 +1457,10 @@ function checkFinishedScrollFrame(ctx) { |
| 887 | 905 | finishScrollTo(ctx); |
| 888 | 906 | } |
| 889 | 907 | } |
| ... | ... | @@ -896,7 +914,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 896 | 914 | x: ctx.state.props.horizontal ? offset : 0, |
| 897 | 915 | y: ctx.state.props.horizontal ? 0 : offset |
| 898 | 916 | }); |
| 899 | | @@ -1482,7 +1500,10 @@ function checkFinishedScrollFallback(ctx) { |
| 917 | @@ -1482,7 +1509,10 @@ function checkFinishedScrollFallback(ctx) { |
| 900 | 918 | ); |
| 901 | 919 | scheduleFallbackCheck(SILENT_INITIAL_SCROLL_RETRY_DELAY_MS); |
| 902 | 920 | } else if (shouldRetryUnalignedEndScroll) { |
| ... | ... | @@ -908,7 +926,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 908 | 926 | scheduleFallbackCheck(100); |
| 909 | 927 | } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) { |
| 910 | 928 | finishScrollTo(ctx); |
| 911 | | @@ -1539,15 +1560,28 @@ function doMaintainScrollAtEnd(ctx) { |
| 929 | @@ -1539,15 +1569,28 @@ function doMaintainScrollAtEnd(ctx) { |
| 912 | 930 | } = state; |
| 913 | 931 | const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold"); |
| 914 | 932 | const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout); |
| ... | ... | @@ -938,7 +956,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 938 | 956 | } |
| 939 | 957 | if (!state.maintainingScrollAtEnd) { |
| 940 | 958 | const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant"; |
| 941 | | @@ -1570,9 +1604,18 @@ function doMaintainScrollAtEnd(ctx) { |
| 959 | @@ -1570,9 +1613,18 @@ function doMaintainScrollAtEnd(ctx) { |
| 942 | 960 | y: 0 |
| 943 | 961 | }); |
| 944 | 962 | } else { |
| ... | ... | @@ -960,7 +978,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 960 | 978 | } |
| 961 | 979 | setTimeout( |
| 962 | 980 | () => { |
| 963 | | @@ -1603,6 +1646,10 @@ function doMaintainScrollAtEnd(ctx) { |
| 981 | @@ -1603,6 +1655,10 @@ function doMaintainScrollAtEnd(ctx) { |
| 964 | 982 | function requestAdjust(ctx, positionDiff, dataChanged) { |
| 965 | 983 | const state = ctx.state; |
| 966 | 984 | if (Math.abs(positionDiff) > 0.1) { |
| ... | ... | @@ -971,7 +989,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 971 | 989 | const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff; |
| 972 | 990 | const doit = () => { |
| 973 | 991 | if (needsScrollWorkaround) { |
| 974 | | @@ -1707,7 +1754,9 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) { |
| 992 | @@ -1707,7 +1763,9 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) { |
| 975 | 993 | if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) { |
| 976 | 994 | return 0; |
| 977 | 995 | } |
| ... | ... | @@ -982,7 +1000,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 982 | 1000 | const clampDelta = maxScroll - state.scroll; |
| 983 | 1001 | if (unresolvedAmount < 0) { |
| 984 | 1002 | return Math.max(unresolvedAmount, Math.min(0, clampDelta)); |
| 985 | | @@ -1769,7 +1818,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { |
| 1003 | @@ -1769,7 +1827,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { |
| 986 | 1004 | settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta); |
| 987 | 1005 | return true; |
| 988 | 1006 | } |
| ... | ... | @@ -991,7 +1009,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 991 | 1009 | const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll); |
| 992 | 1010 | const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON; |
| 993 | 1011 | if (isAtExpectedNativeClamp) { |
| 994 | | @@ -1902,7 +1951,7 @@ function prepareMVCP(ctx, dataChanged) { |
| 1012 | @@ -1902,7 +1960,7 @@ function prepareMVCP(ctx, dataChanged) { |
| 995 | 1013 | if (diff > 0) { |
| 996 | 1014 | diff = Math.max(0, totalSize - state.scroll - state.scrollLength); |
| 997 | 1015 | } else { |
| ... | ... | @@ -1000,7 +1018,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1000 | 1018 | state.scroll = maxScroll; |
| 1001 | 1019 | state.scrollPending = maxScroll; |
| 1002 | 1020 | diff = 0; |
| 1003 | | @@ -2299,8 +2348,121 @@ function scrollToIndex(ctx, { |
| 1021 | @@ -2299,8 +2357,121 @@ function scrollToIndex(ctx, { |
| 1004 | 1022 | } |
| 1005 | 1023 | |
| 1006 | 1024 | // src/core/initialScroll.ts |
| ... | ... | @@ -1010,11 +1028,11 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1010 | 1028 | +var INSET_END_REVEAL_MAX_HOLD_FRAMES = 40; |
| 1011 | 1029 | +function startInsetEndSettleWatchdog(ctx) { |
| 1012 | 1030 | + const state = ctx.state; |
| 1013 | | + if (state.insetEndSettleWatchdogActive) { |
| 1031 | + if (state.insetEndSettleWatchdogStarted || state.didLoad) { |
| 1014 | 1032 | + return; |
| 1015 | 1033 | + } |
| 1034 | + state.insetEndSettleWatchdogStarted = true; |
| 1016 | 1035 | + state.insetEndSettleWatchdogActive = true; |
| 1017 | | + state.didUserDrag = false; |
| 1018 | 1036 | + // Hold the readyToRender opacity gate until the end landing is stable, so |
| 1019 | 1037 | + // the estimated-to-measured settle chase happens before first VISIBLE |
| 1020 | 1038 | + // paint instead of in front of the user. Capped so slow measurement can |
| ... | ... | @@ -1023,6 +1041,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1023 | 1041 | + let frames = 0; |
| 1024 | 1042 | + let settledFrames = 0; |
| 1025 | 1043 | + let revealStableFrames = 0; |
| 1044 | + let previousEndOffset; |
| 1026 | 1045 | + const releaseRevealHold = () => { |
| 1027 | 1046 | + if (state.insetEndRevealHold) { |
| 1028 | 1047 | + state.insetEndRevealHold = false; |
| ... | ... | @@ -1062,33 +1081,32 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1062 | 1081 | + const insetStartAdjustment = getContentInsetStartAdjustment(ctx); |
| 1063 | 1082 | + const contentSize = getContentSize(ctx); |
| 1064 | 1083 | + const scrollLength = state.scrollLength; |
| 1065 | | + if (insetStartAdjustment > 0 && scrollLength > 0 && Number.isFinite(contentSize) && contentSize > scrollLength && !state.scrollingTo && !state.maintainingScrollAtEnd) { |
| 1066 | | + const endOffset = Math.max(-insetStartAdjustment, contentSize - scrollLength); |
| 1067 | | + const distance = endOffset - state.scroll; |
| 1068 | | + // Estimated row sizes converging to measured ones can strand the initial |
| 1069 | | + // end landing when the library's own end-anchor bookkeeping gives up. |
| 1070 | | + // While still near the end (never fighting a user who scrolled away), |
| 1071 | | + // re-pin to the current true end until sizes stop changing. |
| 1072 | | + if (Math.abs(distance) > 2 && Math.abs(distance) <= scrollLength * 0.5) { |
| 1073 | | + settledFrames = 0; |
| 1074 | | + revealStableFrames = 0; |
| 1084 | + const endOffset = Math.max(-insetStartAdjustment, contentSize - scrollLength); |
| 1085 | + // state.scroll is optimistic: non-animated scrollTo writes it before UIKit |
| 1086 | + // moves. Only a native scroll event proves that the requested offset landed. |
| 1087 | + const nativeOffset = state.lastNativeScroll; |
| 1088 | + const hasNativeOffset = typeof nativeOffset === "number" && Number.isFinite(nativeOffset); |
| 1089 | + const distance = hasNativeOffset ? endOffset - nativeOffset : Infinity; |
| 1090 | + const isIdle = !state.scrollingTo && !state.maintainingScrollAtEnd; |
| 1091 | + const hasViewport = insetStartAdjustment > 0 && scrollLength > 0 && Number.isFinite(contentSize); |
| 1092 | + const isStable = hasViewport && isIdle && Math.abs(distance) <= 2 && previousEndOffset !== void 0 && Math.abs(endOffset - previousEndOffset) <= 1; |
| 1093 | + previousEndOffset = endOffset; |
| 1094 | + if (isStable) { |
| 1095 | + settledFrames++; |
| 1096 | + revealStableFrames++; |
| 1097 | + if (revealStableFrames >= INSET_END_REVEAL_STABLE_FRAMES) { |
| 1098 | + onRevealStability(); |
| 1099 | + } |
| 1100 | + } else { |
| 1101 | + settledFrames = 0; |
| 1102 | + revealStableFrames = 0; |
| 1103 | + // Keep the existing near-end correction limit. In-flight scrolls and |
| 1104 | + // unknown native offsets wait; neither counts as a settled frame. |
| 1105 | + if (hasViewport && isIdle && Math.abs(distance) > 2 && Math.abs(distance) <= scrollLength * 0.5) { |
| 1075 | 1106 | + const scroller = state.refScroller.current; |
| 1076 | 1107 | + if (scroller) { |
| 1077 | 1108 | + scroller.scrollTo({ animated: false, x: 0, y: endOffset }); |
| 1078 | 1109 | + } |
| 1079 | | + } else { |
| 1080 | | + settledFrames++; |
| 1081 | | + revealStableFrames++; |
| 1082 | | + if (revealStableFrames >= INSET_END_REVEAL_STABLE_FRAMES) { |
| 1083 | | + onRevealStability(); |
| 1084 | | + } |
| 1085 | | + } |
| 1086 | | + } else { |
| 1087 | | + // Conditions that make re-pinning unnecessary (underflow, in-flight |
| 1088 | | + // programmatic scroll) count toward stability for the reveal. |
| 1089 | | + revealStableFrames++; |
| 1090 | | + if (revealStableFrames >= INSET_END_REVEAL_STABLE_FRAMES) { |
| 1091 | | + onRevealStability(); |
| 1092 | 1110 | + } |
| 1093 | 1111 | + } |
| 1094 | 1112 | + requestAnimationFrame(tick); |
| ... | ... | @@ -1122,7 +1140,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1122 | 1140 | const requestedIndex = target.index; |
| 1123 | 1141 | const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0; |
| 1124 | 1142 | const itemSize = getItemSizeAtIndex(ctx, index); |
| 1125 | | @@ -2726,7 +2888,9 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) { |
| 1143 | @@ -2726,7 +2897,9 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) { |
| 1126 | 1144 | return; |
| 1127 | 1145 | } |
| 1128 | 1146 | if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) { |
| ... | ... | @@ -1133,7 +1151,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1133 | 1151 | if (!shouldKeepEndTargetAlive) { |
| 1134 | 1152 | if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) { |
| 1135 | 1153 | clearPendingInitialScrollFooterLayout(ctx, { |
| 1136 | | @@ -4651,7 +4815,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { |
| 1154 | @@ -4651,7 +4824,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { |
| 1137 | 1155 | contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap); |
| 1138 | 1156 | contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props); |
| 1139 | 1157 | isReady = !hasUnknownTailSize; |
| ... | ... | @@ -1143,7 +1161,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1143 | 1161 | } else if (anchorIndex >= 0) { |
| 1144 | 1162 | isReady = false; |
| 1145 | 1163 | } |
| 1146 | | @@ -4671,6 +4836,12 @@ function maybeUpdateAnchoredEndSpace(ctx) { |
| 1164 | @@ -4671,6 +4845,12 @@ function maybeUpdateAnchoredEndSpace(ctx) { |
| 1147 | 1165 | updateScroll(ctx, state.scroll, true, { markHasScrolled: false }); |
| 1148 | 1166 | } |
| 1149 | 1167 | (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize }); |
| ... | ... | @@ -1156,7 +1174,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1156 | 1174 | } |
| 1157 | 1175 | return nextSize; |
| 1158 | 1176 | } |
| 1159 | | @@ -5694,6 +5865,7 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 1177 | @@ -5694,6 +5874,7 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 1160 | 1178 | horizontal |
| 1161 | 1179 | }) { |
| 1162 | 1180 | const ctx = useStateContext(); |
| ... | ... | @@ -1164,7 +1182,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1164 | 1182 | const columnWrapperStyle = ctx.columnWrapperStyle; |
| 1165 | 1183 | const animSize = useValue$("totalSize"); |
| 1166 | 1184 | const [readyToRender, numColumns, otherAxisSize = 0] = useArr$(["readyToRender", "numColumns", "otherAxisSize"]); |
| 1167 | | @@ -5704,6 +5876,13 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 1185 | @@ -5704,6 +5885,13 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 1168 | 1186 | opacity: isVisible ? 1 : 0, |
| 1169 | 1187 | width: animSize |
| 1170 | 1188 | } : { height: animSize, minWidth: otherAxisSize, opacity: isVisible ? 1 : 0 }; |
| ... | ... | @@ -1178,7 +1196,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1178 | 1196 | if (columnWrapperStyle) { |
| 1179 | 1197 | const { columnGap, rowGap, gap } = columnWrapperStyle; |
| 1180 | 1198 | const gapX = columnGap || gap || 0; |
| 1181 | | @@ -5724,7 +5903,8 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 1199 | @@ -5724,7 +5912,8 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ |
| 1182 | 1200 | } |
| 1183 | 1201 | } |
| 1184 | 1202 | } |
| ... | ... | @@ -1188,7 +1206,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1188 | 1206 | }); |
| 1189 | 1207 | var Containers = typedMemo(function Containers2({ |
| 1190 | 1208 | freshDataTransitionEpoch, |
| 1191 | | @@ -5875,7 +6055,12 @@ var StyleSheet = StyleSheet$1; |
| 1209 | @@ -5875,7 +6064,12 @@ var StyleSheet = StyleSheet$1; |
| 1192 | 1210 | |
| 1193 | 1211 | // src/components/ListComponent.tsx |
| 1194 | 1212 | var AlignItemsAtEndSpacer = typedMemo(function AlignItemsAtEndSpacer2({ horizontal }) { |
| ... | ... | @@ -1201,7 +1219,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1201 | 1219 | if (alignItemsAtEndPadding <= 0) { |
| 1202 | 1220 | return null; |
| 1203 | 1221 | } |
| 1204 | | @@ -5908,8 +6093,12 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 1222 | @@ -5908,8 +6102,12 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 1205 | 1223 | refScrollView, |
| 1206 | 1224 | renderScrollComponent, |
| 1207 | 1225 | onLayoutFooter, |
| ... | ... | @@ -1214,7 +1232,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1214 | 1232 | scrollAdjustHandler, |
| 1215 | 1233 | snapToIndices, |
| 1216 | 1234 | stickyHeaderConfig, |
| 1217 | | @@ -5980,7 +6169,17 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 1235 | @@ -5980,7 +6178,17 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 1218 | 1236 | SnapOrScroll, |
| 1219 | 1237 | { |
| 1220 | 1238 | ...rest, |
| ... | ... | @@ -1233,7 +1251,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1233 | 1251 | contentContainerStyle: [ |
| 1234 | 1252 | horizontal ? { height: "100%" } : {}, |
| 1235 | 1253 | contentContainerStyle, |
| 1236 | | @@ -5989,7 +6188,10 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 1254 | @@ -5989,7 +6197,10 @@ var ListComponent = typedMemo(function ListComponent2({ |
| 1237 | 1255 | ], |
| 1238 | 1256 | contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0, |
| 1239 | 1257 | horizontal, |
| ... | ... | @@ -1245,7 +1263,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1245 | 1263 | onLayout, |
| 1246 | 1264 | onScroll: onScroll2, |
| 1247 | 1265 | ref: refScrollView, |
| 1248 | | @@ -6730,7 +6932,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) { |
| 1266 | @@ -6730,7 +6941,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) { |
| 1249 | 1267 | endBuffered: state.endBuffered, |
| 1250 | 1268 | getAverageItemSizes: () => getAverageItemSizes(state), |
| 1251 | 1269 | indexByKey: (key) => state.indexByKey.get(key), |
| ... | ... | @@ -1254,7 +1272,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1254 | 1272 | isAtStart: peek$(ctx, "isAtStart"), |
| 1255 | 1273 | isEndReached: state.isEndReached, |
| 1256 | 1274 | isNearEnd: peek$(ctx, "isNearEnd"), |
| 1257 | | @@ -7054,6 +7256,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1275 | @@ -7054,6 +7265,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1258 | 1276 | dataVersion, |
| 1259 | 1277 | drawDistance = 250, |
| 1260 | 1278 | contentInsetEndAdjustment, |
| ... | ... | @@ -1262,7 +1280,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1262 | 1280 | estimatedItemSize = 100, |
| 1263 | 1281 | estimatedListSize, |
| 1264 | 1282 | extraData, |
| 1265 | | @@ -7111,10 +7314,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1283 | @@ -7111,10 +7323,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1266 | 1284 | const animatedPropsInternal = props.animatedPropsInternal; |
| 1267 | 1285 | const anchoredEndSpaceOwner = (_a3 = props.anchoredEndSpaceOwnerInternal) != null ? _a3 : "list"; |
| 1268 | 1286 | const positionComponentInternal = props.positionComponentInternal; |
| ... | ... | @@ -1275,7 +1293,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1275 | 1293 | stickyPositionComponentInternal: _stickyPositionComponentInternal, |
| 1276 | 1294 | ...restProps |
| 1277 | 1295 | } = rest; |
| 1278 | | @@ -7179,7 +7384,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1296 | @@ -7179,7 +7393,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1279 | 1297 | const combinedRef = useCombinedRef(refScroller, refScrollView); |
| 1280 | 1298 | const keyExtractor = keyExtractorProp != null ? keyExtractorProp : ((_item, index) => index.toString()); |
| 1281 | 1299 | const stickyHeaderIndices = stickyHeaderIndicesProp; |
| ... | ... | @@ -1284,7 +1302,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1284 | 1302 | const previousContentInsetEndAdjustmentRef = useRef(contentInsetEndAdjustmentResolved); |
| 1285 | 1303 | const alwaysRenderIndices = useMemo(() => { |
| 1286 | 1304 | const indices = getAlwaysRenderIndices(alwaysRender, dataProp, keyExtractor, anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex); |
| 1287 | | @@ -7320,6 +7525,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1305 | @@ -7320,6 +7534,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1288 | 1306 | contentContainerAlignItems: contentContainerStyle.alignItems, |
| 1289 | 1307 | contentInset, |
| 1290 | 1308 | contentInsetEndAdjustment: contentInsetEndAdjustmentResolved, |
| ... | ... | @@ -1292,7 +1310,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1292 | 1310 | data: dataProp, |
| 1293 | 1311 | dataKey, |
| 1294 | 1312 | dataVersion, |
| 1295 | | @@ -7351,6 +7557,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1313 | @@ -7351,6 +7566,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1296 | 1314 | renderItem, |
| 1297 | 1315 | rtl, |
| 1298 | 1316 | snapToIndices, |
| ... | ... | @@ -1300,7 +1318,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1300 | 1318 | stickyHeaderIndicesArr: stickyHeaderIndices != null ? stickyHeaderIndices : [], |
| 1301 | 1319 | stickyHeaderIndicesSet: useMemo(() => new Set(stickyHeaderIndices != null ? stickyHeaderIndices : []), [stickyHeaderIndices == null ? void 0 : stickyHeaderIndices.join(",")]), |
| 1302 | 1320 | stickyPositionComponentInternal, |
| 1303 | | @@ -7402,6 +7609,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1321 | @@ -7402,6 +7618,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1304 | 1322 | return void 0; |
| 1305 | 1323 | } |
| 1306 | 1324 | const resolvedOffset = (_a4 = initialScroll.contentOffset) != null ? _a4 : resolveInitialScrollOffset(ctx, initialScroll); |
| ... | ... | @@ -1314,7 +1332,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1314 | 1332 | return usesBootstrapInitialScroll && ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap" && Platform.OS === "web" ? void 0 : resolvedOffset; |
| 1315 | 1333 | }, [usesBootstrapInitialScroll]); |
| 1316 | 1334 | useLayoutEffect(() => { |
| 1317 | | @@ -7526,6 +7740,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1335 | @@ -7526,6 +7749,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1318 | 1336 | [ |
| 1319 | 1337 | dataKey, |
| 1320 | 1338 | dataVersion, |
| ... | ... | @@ -1322,7 +1340,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1322 | 1340 | memoizedLastItemKeys.join(","), |
| 1323 | 1341 | numColumnsProp, |
| 1324 | 1342 | nextScrollAxisGap, |
| 1325 | | @@ -7622,6 +7837,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1343 | @@ -7622,6 +7846,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1326 | 1344 | () => ({ |
| 1327 | 1345 | getRenderedItem: (key) => getRenderedItem(ctx, key), |
| 1328 | 1346 | onMomentumScrollEnd: (event) => { |
| ... | ... | @@ -1330,7 +1348,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1330 | 1348 | checkFinishedScrollFallback(ctx); |
| 1331 | 1349 | if (state.props.onMomentumScrollEnd) { |
| 1332 | 1350 | state.props.onMomentumScrollEnd(event); |
| 1333 | | @@ -7630,6 +7846,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1351 | @@ -7630,6 +7855,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1334 | 1352 | onScroll: (event) => onScroll(ctx, event), |
| 1335 | 1353 | onScrollBeginDrag: (event) => { |
| 1336 | 1354 | var _a4, _b2; |
| ... | ... | @@ -1339,7 +1357,7 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..b8c6faf0be51cbda3e8af01f290dbe97 |
| 1339 | 1357 | prepareReachedEdgeForNextUserScroll(ctx); |
| 1340 | 1358 | (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event); |
| 1341 | 1359 | }, |
| 1342 | | @@ -7655,11 +7873,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1360 | @@ -7655,11 +7882,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded |
| 1343 | 1361 | ListFooterComponent, |
| 1344 | 1362 | ListFooterComponentStyle, |
| 1345 | 1363 | ListHeaderComponent, |