UNPKG

3.07 kBPlain TextView Raw
1/*
2 * Copyright 2020 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13import {FocusableElement} from '@react-types/shared';
14
15// This is a polyfill for element.focus({preventScroll: true});
16// Currently necessary for Safari and old Edge:
17// https://caniuse.com/#feat=mdn-api_htmlelement_focus_preventscroll_option
18// See https://bugs.webkit.org/show_bug.cgi?id=178583
19//
20
21// Original licensing for the following methods can be found in the
22// NOTICE file in the root directory of this source tree.
23// See https://github.com/calvellido/focus-options-polyfill
24
25interface ScrollableElement {
26 element: HTMLElement,
27 scrollTop: number,
28 scrollLeft: number
29}
30
31export function focusWithoutScrolling(element: FocusableElement) {
32 if (supportsPreventScroll()) {
33 element.focus({preventScroll: true});
34 } else {
35 let scrollableElements = getScrollableElements(element);
36 element.focus();
37 restoreScrollPosition(scrollableElements);
38 }
39}
40
41let supportsPreventScrollCached: boolean = null;
42function supportsPreventScroll() {
43 if (supportsPreventScrollCached == null) {
44 supportsPreventScrollCached = false;
45 try {
46 var focusElem = document.createElement('div');
47 focusElem.focus({
48 get preventScroll() {
49 supportsPreventScrollCached = true;
50 return true;
51 }
52 });
53 } catch (e) {
54 // Ignore
55 }
56 }
57
58 return supportsPreventScrollCached;
59}
60
61function getScrollableElements(element: FocusableElement): ScrollableElement[] {
62 var parent = element.parentNode;
63 var scrollableElements: ScrollableElement[] = [];
64 var rootScrollingElement = document.scrollingElement || document.documentElement;
65
66 while (parent instanceof HTMLElement && parent !== rootScrollingElement) {
67 if (
68 parent.offsetHeight < parent.scrollHeight ||
69 parent.offsetWidth < parent.scrollWidth
70 ) {
71 scrollableElements.push({
72 element: parent,
73 scrollTop: parent.scrollTop,
74 scrollLeft: parent.scrollLeft
75 });
76 }
77 parent = parent.parentNode;
78 }
79
80 if (rootScrollingElement instanceof HTMLElement) {
81 scrollableElements.push({
82 element: rootScrollingElement,
83 scrollTop: rootScrollingElement.scrollTop,
84 scrollLeft: rootScrollingElement.scrollLeft
85 });
86 }
87
88 return scrollableElements;
89}
90
91function restoreScrollPosition(scrollableElements: ScrollableElement[]) {
92 for (let {element, scrollTop, scrollLeft} of scrollableElements) {
93 element.scrollTop = scrollTop;
94 element.scrollLeft = scrollLeft;
95 }
96}