Skip to content

Conversation

@nwidynski
Copy link
Contributor

This PR refactors scrollIntoView and scrollIntoViewport to support scrollMargin, while working around the regressions mentioned in #8689 (comment). Additionally, a new inline & block option for alignment of a container element has been added.

By leveraging bounding rectangles, the internal calculations are now streamlined with DOMLayoutDelegate and significantly simplified.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

Comment on lines +145 to +152
let {left: newLeft, top: newTop} = targetElement.getBoundingClientRect();
// Account for sub pixel differences from rounding
if ((Math.abs(originalLeft - newLeft) > 1) || (Math.abs(originalTop - newTop) > 1)) {
scrollParents = containingElement ? getScrollParents(containingElement, true) : [];
for (let scrollParent of scrollParents) {
scrollIntoView(scrollParent as HTMLElement, containingElement as HTMLElement, {block: 'center', inline: 'center'});
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we added the bypass for chrome, this now also supports alternative alignments, to center the containingElement. This was missing in the previous PR also 👍

if (!isScrollPrevented) {
scrollParents.push(root);
}
let scrollParents = getScrollParents(targetElement, true);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noting that this does not behave like the native equivalent, since it skips overflow:hidden scroll parents. This potential issue also existed before this PR, so I don't think it's a blocker.

Comment on lines +80 to +84
if (shouldScrollBlock && block === 'start') {
y += scrollAreaLeft - scrollPortLeft;
} else if (shouldScrollBlock && block === 'center') {
y += (scrollAreaTop + scrollAreaBottom) / 2 - (scrollPortTop + scrollPortBottom) / 2;
} else if (shouldScrollBlock && block === 'end') {
Copy link
Contributor Author

@nwidynski nwidynski Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native scrollIntoView only checks for shouldScroll with the nearest alignment option. Should we also do that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep this as is, IMO this utility doesn't need to mirror the native behavior and I think we'd prefer more of a scrollIntoViewIfNeeded behavior anyways. Any concerns or thoughts from your end?

Copy link
Contributor Author

@nwidynski nwidynski Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'm not sure whether this is a public utility or internal. If it's public I think we should aim for closer parity, but provide an option to opt-in or out of IfNeeded behavior. One other issue where this is also not pairing the native version is that intermediate scrollParents below the scrollView also do not get scrolled. I think it would make sense to push the scrollParents loop into scrollIntoView to also align there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

honestly I'd classify this more of an internal utility (and thus can be more opinionated) since we never documented it for external usage, but it is a bit of a gray area. Making it a public utility and adding an option for IfNeeded would be nice, but it comes with the extra baggage of committing it to being public API and possibly increasing scope/maintenance depending on how much we'd want it to behave like native (as well as maintain behavior with native if that changes). Happy for the rest of the team to weigh in.

Copy link
Member

@LFDanLu LFDanLu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some preliminary testing and the behavior looks good to me, thanks for the fix! I still need to sit down and go through all the math still haha, but I'll see about getting this into our upcoming test session so we can put it through its paces. Just to share timelines, the team is limited on bandwidth due to some pushes on some end of year docs work so realistically we may not get this in till the new year (which might be when the next release is anyways). Thanks once again for the through fix!

@nwidynski
Copy link
Contributor Author

nwidynski commented Nov 5, 2025

@LFDanLu No problem, i figure that implies the same for the pending review of the ScrollView refactor? At least I won't have much to maintain as long as focus largely remains on docs, haha. You would do me much more of a favor with preliminary feedback to the Carousel RFC, so I could come strong out the gate for the new year with a draft PR ready :)

Carousel's implementation is really quite a lot and I would love to avoid losing most of the detailed implementation insights i currently still got in short term memory 😅

@LFDanLu
Copy link
Member

LFDanLu commented Nov 5, 2025

@nwidynski That's right, the team is currently focused on new docs + getting S2 out of pre-release so we don't have many spare cycles. Thank you for letting me know the Carousel RFC is a higher priority, I'll see if I can get at least a preliminary review by the team squeezed in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants