@react-aria/utils 3.14.1 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,13 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import {getScrollParent} from './';
14
+
15
+ interface ScrollIntoViewportOpts {
16
+ /** The optional containing element of the target to be centered in the viewport. */
17
+ containingElement?: Element
18
+ }
19
+
13
20
  /**
14
21
  * Scrolls `scrollView` so that `element` is visible.
15
22
  * Similar to `element.scrollIntoView({block: 'nearest'})` (not supported in Edge),
@@ -22,20 +29,25 @@ export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement) {
22
29
  let height = element.offsetHeight;
23
30
  let x = scrollView.scrollLeft;
24
31
  let y = scrollView.scrollTop;
25
- let maxX = x + scrollView.offsetWidth;
26
- let maxY = y + scrollView.offsetHeight;
32
+
33
+ // Account for top/left border offsetting the scroll top/Left
34
+ let {borderTopWidth, borderLeftWidth} = getComputedStyle(scrollView);
35
+ let borderAdjustedX = scrollView.scrollLeft + parseInt(borderLeftWidth, 10);
36
+ let borderAdjustedY = scrollView.scrollTop + parseInt(borderTopWidth, 10);
37
+ // Ignore end/bottom border via clientHeight/Width instead of offsetHeight/Width
38
+ let maxX = borderAdjustedX + scrollView.clientWidth;
39
+ let maxY = borderAdjustedY + scrollView.clientHeight;
27
40
 
28
41
  if (offsetX <= x) {
29
- x = offsetX;
42
+ x = offsetX - parseInt(borderLeftWidth, 10);
30
43
  } else if (offsetX + width > maxX) {
31
44
  x += offsetX + width - maxX;
32
45
  }
33
- if (offsetY <= y) {
34
- y = offsetY;
46
+ if (offsetY <= borderAdjustedY) {
47
+ y = offsetY - parseInt(borderTopWidth, 10);
35
48
  } else if (offsetY + height > maxY) {
36
49
  y += offsetY + height - maxY;
37
50
  }
38
-
39
51
  scrollView.scrollLeft = x;
40
52
  scrollView.scrollTop = y;
41
53
  }
@@ -63,3 +75,37 @@ function relativeOffset(ancestor: HTMLElement, child: HTMLElement, axis: 'left'|
63
75
  }
64
76
  return sum;
65
77
  }
78
+
79
+ /**
80
+ * Scrolls the `targetElement` so it is visible in the viewport. Accepts an optional `opts.containingElement`
81
+ * that will be centered in the viewport prior to scrolling the targetElement into view. If scrolling is prevented on
82
+ * 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.
83
+ */
84
+ export function scrollIntoViewport(targetElement: Element, opts?: ScrollIntoViewportOpts) {
85
+ if (document.contains(targetElement)) {
86
+ let root = document.scrollingElement || document.documentElement;
87
+ let isScrollPrevented = window.getComputedStyle(root).overflow === 'hidden';
88
+ // 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
89
+ if (!isScrollPrevented) {
90
+ let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect();
91
+
92
+ // use scrollIntoView({block: 'nearest'}) instead of .focus to check if the element is fully in view or not since .focus()
93
+ // 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
94
+ targetElement?.scrollIntoView?.({block: 'nearest'});
95
+ let {left: newLeft, top: newTop} = targetElement.getBoundingClientRect();
96
+ // Account for sub pixel differences from rounding
97
+ if ((Math.abs(originalLeft - newLeft) > 1) || (Math.abs(originalTop - newTop) > 1)) {
98
+ opts?.containingElement?.scrollIntoView?.({block: 'center', inline: 'center'});
99
+ targetElement.scrollIntoView?.({block: 'nearest'});
100
+ }
101
+ } else {
102
+ let scrollParent = getScrollParent(targetElement);
103
+ // 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.
104
+ while (targetElement && scrollParent && targetElement !== root && scrollParent !== root) {
105
+ scrollIntoView(scrollParent as HTMLElement, targetElement as HTMLElement);
106
+ targetElement = scrollParent;
107
+ scrollParent = getScrollParent(targetElement);
108
+ }
109
+ }
110
+ }
111
+ }