|
11 | 11 | */ |
12 | 12 |
|
13 | 13 | import {getScrollParents} from './getScrollParents'; |
| 14 | +import {isChrome} from './platform'; |
14 | 15 |
|
15 | 16 | interface ScrollIntoViewportOpts { |
16 | 17 | /** The optional containing element of the target to be centered in the viewport. */ |
@@ -40,32 +41,64 @@ export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement): v |
40 | 41 | scrollPaddingLeft |
41 | 42 | } = getComputedStyle(scrollView); |
42 | 43 |
|
| 44 | + // Account for scroll margin of the element |
| 45 | + let { |
| 46 | + scrollMarginTop, |
| 47 | + scrollMarginRight, |
| 48 | + scrollMarginBottom, |
| 49 | + scrollMarginLeft |
| 50 | + } = getComputedStyle(element); |
| 51 | + |
43 | 52 | let borderAdjustedX = x + parseInt(borderLeftWidth, 10); |
44 | 53 | let borderAdjustedY = y + parseInt(borderTopWidth, 10); |
45 | 54 | // Ignore end/bottom border via clientHeight/Width instead of offsetHeight/Width |
46 | 55 | let maxX = borderAdjustedX + scrollView.clientWidth; |
47 | 56 | let maxY = borderAdjustedY + scrollView.clientHeight; |
48 | 57 |
|
49 | | - // Get scroll padding values as pixels - defaults to 0 if no scroll padding |
| 58 | + // Get scroll padding / margin values as pixels - defaults to 0 if no scroll padding / margin |
50 | 59 | // is used. |
51 | 60 | let scrollPaddingTopNumber = parseInt(scrollPaddingTop, 10) || 0; |
52 | 61 | let scrollPaddingBottomNumber = parseInt(scrollPaddingBottom, 10) || 0; |
53 | 62 | let scrollPaddingRightNumber = parseInt(scrollPaddingRight, 10) || 0; |
54 | 63 | let scrollPaddingLeftNumber = parseInt(scrollPaddingLeft, 10) || 0; |
| 64 | + let scrollMarginTopNumber = parseInt(scrollMarginTop, 10) || 0; |
| 65 | + let scrollMarginBottomNumber = parseInt(scrollMarginBottom, 10) || 0; |
| 66 | + let scrollMarginRightNumber = parseInt(scrollMarginRight, 10) || 0; |
| 67 | + let scrollMarginLeftNumber = parseInt(scrollMarginLeft, 10) || 0; |
| 68 | + |
| 69 | + let targetLeft = offsetX - scrollMarginLeftNumber; |
| 70 | + let targetRight = offsetX + width + scrollMarginRightNumber; |
| 71 | + let targetTop = offsetY - scrollMarginTopNumber; |
| 72 | + let targetBottom = offsetY + height + scrollMarginBottomNumber; |
55 | 73 |
|
56 | | - if (offsetX <= x + scrollPaddingLeftNumber) { |
57 | | - x = offsetX - parseInt(borderLeftWidth, 10) - scrollPaddingLeftNumber; |
58 | | - } else if (offsetX + width > maxX - scrollPaddingRightNumber) { |
59 | | - x += offsetX + width - maxX + scrollPaddingRightNumber; |
| 74 | + let scrollPortLeft = x + parseInt(borderLeftWidth, 10) + scrollPaddingLeftNumber; |
| 75 | + let scrollPortRight = maxX - scrollPaddingRightNumber; |
| 76 | + let scrollPortTop = y + parseInt(borderTopWidth, 10) + scrollPaddingTopNumber; |
| 77 | + let scrollPortBottom = maxY - scrollPaddingBottomNumber; |
| 78 | + |
| 79 | + if (targetLeft > scrollPortLeft || targetRight < scrollPortRight) { |
| 80 | + if (targetLeft <= x + scrollPaddingLeftNumber) { |
| 81 | + x = targetLeft - parseInt(borderLeftWidth, 10) - scrollPaddingLeftNumber; |
| 82 | + } else if (targetRight > maxX - scrollPaddingRightNumber) { |
| 83 | + x += targetRight - maxX + scrollPaddingRightNumber; |
| 84 | + } |
60 | 85 | } |
61 | | - if (offsetY <= borderAdjustedY + scrollPaddingTopNumber) { |
62 | | - y = offsetY - parseInt(borderTopWidth, 10) - scrollPaddingTopNumber; |
63 | | - } else if (offsetY + height > maxY - scrollPaddingBottomNumber) { |
64 | | - y += offsetY + height - maxY + scrollPaddingBottomNumber; |
| 86 | + |
| 87 | + if (targetTop > scrollPortTop || targetBottom < scrollPortBottom) { |
| 88 | + if (targetTop <= borderAdjustedY + scrollPaddingTopNumber) { |
| 89 | + y = targetTop - parseInt(borderTopWidth, 10) - scrollPaddingTopNumber; |
| 90 | + } else if (targetBottom > maxY - scrollPaddingBottomNumber) { |
| 91 | + y += targetBottom - maxY + scrollPaddingBottomNumber; |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + if (process.env.NODE_ENV === 'test') { |
| 96 | + scrollView.scrollLeft = x; |
| 97 | + scrollView.scrollTop = y; |
| 98 | + return; |
65 | 99 | } |
66 | 100 |
|
67 | | - scrollView.scrollLeft = x; |
68 | | - scrollView.scrollTop = y; |
| 101 | + scrollView.scrollTo({left: x, top: y}); |
69 | 102 | } |
70 | 103 |
|
71 | 104 | /** |
@@ -101,8 +134,9 @@ export function scrollIntoViewport(targetElement: Element | null, opts?: ScrollI |
101 | 134 | if (targetElement && document.contains(targetElement)) { |
102 | 135 | let root = document.scrollingElement || document.documentElement; |
103 | 136 | let isScrollPrevented = window.getComputedStyle(root).overflow === 'hidden'; |
104 | | - // 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 |
105 | | - if (!isScrollPrevented) { |
| 137 | + // 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 |
| 138 | + // Also ignore in chrome because of this bug: https://issues.chromium.org/issues/40074749 |
| 139 | + if (!isScrollPrevented && !isChrome()) { |
106 | 140 | let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect(); |
107 | 141 |
|
108 | 142 | // use scrollIntoView({block: 'nearest'}) instead of .focus to check if the element is fully in view or not since .focus() |
|
0 commit comments