(
targetElement: Element | null,
opts: ScrollIntoViewportOpts = {}
)
| 140 | * the body itself. |
| 141 | */ |
| 142 | export function scrollIntoViewport( |
| 143 | targetElement: Element | null, |
| 144 | opts: ScrollIntoViewportOpts = {} |
| 145 | ): void { |
| 146 | let {containingElement} = opts; |
| 147 | if (targetElement && targetElement.isConnected) { |
| 148 | let root = document.scrollingElement || document.documentElement; |
| 149 | let isScrollPrevented = window.getComputedStyle(root).overflow === 'hidden'; |
| 150 | if (!isScrollPrevented) { |
| 151 | let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect(); |
| 152 | |
| 153 | // use scrollIntoView({block: 'nearest'}) instead of .focus to check if the element is fully in view or not since .focus() |
| 154 | // 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 |
| 155 | targetElement?.scrollIntoView?.({block: 'nearest'}); |
| 156 | let {left: newLeft, top: newTop} = targetElement.getBoundingClientRect(); |
| 157 | // Account for sub pixel differences from rounding |
| 158 | if (Math.abs(originalLeft - newLeft) > 1 || Math.abs(originalTop - newTop) > 1) { |
| 159 | containingElement?.scrollIntoView?.({block: 'center', inline: 'center'}); |
| 160 | targetElement.scrollIntoView?.({block: 'nearest'}); |
| 161 | } |
| 162 | } else { |
| 163 | let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect(); |
| 164 | |
| 165 | // 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. |
| 166 | let scrollParents = getScrollParents(targetElement, true); |
| 167 | for (let scrollParent of scrollParents) { |
| 168 | scrollIntoView(scrollParent as HTMLElement, targetElement as HTMLElement); |
| 169 | } |
| 170 | let {left: newLeft, top: newTop} = targetElement.getBoundingClientRect(); |
| 171 | // Account for sub pixel differences from rounding |
| 172 | if (Math.abs(originalLeft - newLeft) > 1 || Math.abs(originalTop - newTop) > 1) { |
| 173 | scrollParents = containingElement ? getScrollParents(containingElement, true) : []; |
| 174 | // scroll containing element into view first, then rescroll target element into view like the non chrome flow above |
| 175 | for (let scrollParent of scrollParents) { |
| 176 | scrollIntoView(scrollParent as HTMLElement, containingElement as HTMLElement, { |
| 177 | block: 'center', |
| 178 | inline: 'center' |
| 179 | }); |
| 180 | } |
| 181 | for (let scrollParent of getScrollParents(targetElement, true)) { |
| 182 | scrollIntoView(scrollParent as HTMLElement, targetElement as HTMLElement); |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | } |
| 187 | } |
no test coverage detected