Repository: adobe/react-spectrum
File: packages/@react-aria/utils/src/scrollIntoView.ts (TypeScript)

Code before:
  let {containingElement} = opts;
  if (targetElement && document.contains(targetElement)) {
    let root = document.scrollingElement || document.documentElement;
    let isScrollPrevented = window.getComputedStyle(root).overflow === 'hidden';
    // If scrolling is not currently prevented then we aren't in a overlay nor is a overlay open, just use element.scrollIntoView to bring the element into view
    // Also ignore in chrome because of this bug: https://issues.chromium.org/issues/40074749
    if (!isScrollPrevented && !isChrome()) {
      let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect();

      // use scrollIntoView({block: 'nearest'}) instead of .focus to check if the element is fully in view or not since .focus()
      // won't cause a scroll if the element is already focused and doesn't behave consistently when an element is partially out of view horizontally vs vertically
      targetElement?.scrollIntoView?.({block: 'nearest'});
      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)) {
        containingElement?.scrollIntoView?.({block: 'center', inline: 'center'});
        targetElement.scrollIntoView?.({block: 'nearest'});
      }
    } else {
      let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect();

      // If scrolling is prevented, we don't want to scroll the body since it might move the overlay partially offscreen and the user can't scroll it back into view.
      let scrollParents = getScrollParents(targetElement, true);
      for (let scrollParent of scrollParents) {
        scrollIntoView(scrollParent as HTMLElement, targetElement as HTMLElement);
      }
      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'});
        }
      }
    }
  }
}


Review comment (bug):
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.

Code after:
 * the body (e.g. targetElement is in a popover), this will only scroll the scroll parents of the targetElement up to but not including the body itself.
 */
export function scrollIntoViewport(targetElement: Element | null, opts: ScrollIntoViewportOpts = {}): void {
  let {containingElement} = opts;
  if (targetElement && targetElement.isConnected) {
    let root = document.scrollingElement || document.documentElement;
    let isScrollPrevented = window.getComputedStyle(root).overflow === 'hidden';
    // If scrolling is not currently prevented then we aren't in a overlay nor is a overlay open, just use element.scrollIntoView to bring the element into view
    // Also ignore in chrome because of this bug: https://issues.chromium.org/issues/40074749
    if (!isScrollPrevented && !isChrome()) {
      let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect();

      // use scrollIntoView({block: 'nearest'}) instead of .focus to check if the element is fully in view or not since .focus()
      // won't cause a scroll if the element is already focused and doesn't behave consistently when an element is partially out of view horizontally vs vertically
      targetElement?.scrollIntoView?.({block: 'nearest'});
      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)) {
        containingElement?.scrollIntoView?.({block: 'center', inline: 'center'});
        targetElement.scrollIntoView?.({block: 'nearest'});
      }
    } else {
      let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect();

      // If scrolling is prevented, we don't want to scroll the body since it might move the overlay partially offscreen and the user can't scroll it back into view.
      let scrollParents = getScrollParents(targetElement, true);
      for (let scrollParent of scrollParents) {
        scrollIntoView(scrollParent as HTMLElement, targetElement as HTMLElement);
      }
      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'});
        }
      }
    }
  }
}