chore: Revert "Revert "fix: scrollIntoView should respect scroll-margin (#8715)"" #9146
chore: Revert "Revert "fix: scrollIntoView should respect scroll-margin (#8715)"" #9146LFDanLu merged 5 commits intoadobe:mainfrom
Conversation
| 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'}); | ||
| } | ||
| } |
There was a problem hiding this comment.
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 👍
There was a problem hiding this comment.
@nwidynski still in the middle of looking at the code, but I tested against the story that https://github.com/adobe/react-spectrum/pull/7717/changes#diff-b741e806d7e03bb7f42427dbe388c23128044ae2c96c612a9bceb9852ec408ab added and it seems to cause some strange scroll behaviors when attempting to use ArrowDown) to scroll an out of view item into view (for instance, Item 20 in that story). It seems to be hitting the last bit of logic in https://github.com/nwidynski/react-spectrum/blob/fc0f5a0fb1c2e15fd9d2d82a67b39feb0d350a89/packages/%40react-aria/utils/src/scrollIntoView.ts#L150, and attempts to scroll the item with respect to the gridlist itself which isn't actually scrollable itself in this story example. Rather, the wrapping div around the gridlist itself is scrollable here so theoretically we should've skipped the gridlist in favor of scrolling of scrolling the item against the wrapping div.
There was a problem hiding this comment.
hmm, I suppose in that particular story, the scrollRef of the gridlist is inaccurate because of this wrapping div configuration (should be the wrapping div), perhaps we should allow configuring that...
There was a problem hiding this comment.
thanks for finding those issues. Its a bit unfortunate that without being able to set the scrollRef that this PR's change breaks the above use case, even though it had only worked previously by happenstance (aka we only called scrollIntoView once rather than accounting for the sub-pixel rounding).
| if (!isScrollPrevented) { | ||
| scrollParents.push(root); | ||
| } | ||
| let scrollParents = getScrollParents(targetElement, true); |
There was a problem hiding this comment.
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.
| if (shouldScrollBlock && block === 'start') { | ||
| y += scrollAreaLeft - scrollPortLeft; | ||
| } else if (shouldScrollBlock && block === 'center') { | ||
| y += (scrollAreaTop + scrollAreaBottom) / 2 - (scrollPortTop + scrollPortBottom) / 2; | ||
| } else if (shouldScrollBlock && block === 'end') { |
There was a problem hiding this comment.
Native scrollIntoView only checks for shouldScroll with the nearest alignment option. Should we also do that?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
LFDanLu
left a comment
There was a problem hiding this comment.
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!
|
@LFDanLu No problem, i figure that implies the same for the pending review of the 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 😅 |
|
@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. |
| let viewTop = scrollView === root ? 0 : view.top; | ||
| let viewBottom = scrollView === root ? scrollView.clientHeight : view.bottom; | ||
| let viewLeft = scrollView === root ? 0 : view.left; | ||
| let viewRight = scrollView === root ? scrollView.clientWidth : view.right; |
There was a problem hiding this comment.
@snowystinger Related to https://github.com/adobe/react-spectrum/pull/7717/files#r1999951343, let me know if we need support for pinch-zoom here.
|
Just wanted to give this a bump so it doesn't get lost. Excuse me if you guys are not back to being fully staffed yet 😅 |
|
I'll see about picking this back up soon hopefully, we ran into something similar via #7717 |
| while (node && node !== document.documentElement) { | ||
| do { |
There was a problem hiding this comment.
noticed that with this change that if checkForOverflow is false, we'd return the root element as one of the scroll parentElements, even if the node provided is the root element. I don't think that case happens right now of in the code since I think we always check for overflow, but was curious what prompted this
There was a problem hiding this comment.
I'm not sure I can follow why that is just the case for checkForOverflow=false, since the intention, iirc, was for it to always be included. We pass true in scrollIntoView solely as a runtime optimization to skip unscrollable parent elements, not out of necessity.
Basically all of what this changeset aims to do, is to move the scrollParents.push(root) of scrollIntoView into this helper method. I felt like this is the more appropriate place, in case somebody starts using this helper elsewhere - same goes for isScrollable which previously didn't work for the root element.
There was a problem hiding this comment.
My initial hang up on was that it might be unexpected if someone was previously using this util and didn't expect root elements to be included (though to be fair we never documented this util and I would consider it as an internal util). From what I can tell, I think the original utility and scrollIntoView logic actually didn't want to include the root since it was only handling the overlay case where we had prevented scrolling from happening on the body anyways but with the changes in the PR that assumption no longer holds up so this is probably fine on second thought.
| 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'}); | ||
| } | ||
| } |
There was a problem hiding this comment.
@nwidynski still in the middle of looking at the code, but I tested against the story that https://github.com/adobe/react-spectrum/pull/7717/changes#diff-b741e806d7e03bb7f42427dbe388c23128044ae2c96c612a9bceb9852ec408ab added and it seems to cause some strange scroll behaviors when attempting to use ArrowDown) to scroll an out of view item into view (for instance, Item 20 in that story). It seems to be hitting the last bit of logic in https://github.com/nwidynski/react-spectrum/blob/fc0f5a0fb1c2e15fd9d2d82a67b39feb0d350a89/packages/%40react-aria/utils/src/scrollIntoView.ts#L150, and attempts to scroll the item with respect to the gridlist itself which isn't actually scrollable itself in this story example. Rather, the wrapping div around the gridlist itself is scrollable here so theoretically we should've skipped the gridlist in favor of scrolling of scrolling the item against the wrapping div.
There was a problem hiding this comment.
out of curiosity were these calculations based off any existing work? (math is hard, and this kind of code always has small pitfalls/non-obvious considerations that reminds me of the grueling overlay positioning code where I always have to start drawing diagrams and what not)
There was a problem hiding this comment.
Most of it was honestly based on first principle thinking, which was done within the exploration and prep work for the Carousel RFC. Since ScrollDelegate operates within the constraints of the LayoutDelegate interface, and especially with my work on #8696, I already had to come up with the math anyways - and I was already drawing diagrams too 😅
This is why I suggested in that RFC review of yours, that deeper integration might be able to replace scrollIntoView. Looking at the library, which Rob linked in https://github.com/adobe/react-spectrum/pull/7717/changes#r1999951343, I think the calculations largely appear very similar.
LFDanLu
left a comment
There was a problem hiding this comment.
will have to discuss https://github.com/adobe/react-spectrum/pull/9146/changes#r2766507693 with the team since although technically not supported, the scrollable wrapping div case did happen to "work" previously. Otherwise the logic looks good, will see if we can do some more testing in next weeks testing sesion
Co-authored-by: Daniel Lu <danilu@adobe.com>
LFDanLu
left a comment
There was a problem hiding this comment.
Applied a fix for something we noticed in testing (didn't account for scrollbars) but otherwise looks good! Sorry about the delay
This PR refactors
scrollIntoViewandscrollIntoViewportto supportscrollMargin, while working around the regressions mentioned in #8689 (comment). Additionally, a newinline&blockoption for alignment of a container element has been added.By leveraging bounding rectangles, the internal calculations are now streamlined with
DOMLayoutDelegateand significantly simplified.✅ Pull Request Checklist:
📝 Test Instructions:
🧢 Your Project: