summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/scroll.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/scroll.js')
-rw-r--r--devtools/client/shared/scroll.js144
1 files changed, 144 insertions, 0 deletions
diff --git a/devtools/client/shared/scroll.js b/devtools/client/shared/scroll.js
new file mode 100644
index 0000000000..e740e5c8f6
--- /dev/null
+++ b/devtools/client/shared/scroll.js
@@ -0,0 +1,144 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function (require, exports, module) {
+ /**
+ * Scroll the document so that the element "elem" appears in the viewport.
+ *
+ * @param {DOMNode} elem
+ * The element that needs to appear in the viewport.
+ * @param {Boolean} centered
+ * true if you want it centered, false if you want it to appear on the
+ * top of the viewport. It is true by default, and that is usually what
+ * you want.
+ * @param {Boolean} smooth
+ * true if you want the scroll to happen smoothly, instead of instantly.
+ * It is false by default.
+ */
+ function scrollIntoViewIfNeeded(elem, centered = true, smooth = false) {
+ const win = elem.ownerDocument.defaultView;
+ const clientRect = elem.getBoundingClientRect();
+
+ // The following are always from the {top, bottom}
+ // of the viewport, to the {top, …} of the box.
+ // Think of them as geometrical vectors, it helps.
+ // The origin is at the top left.
+
+ const topToBottom = clientRect.bottom;
+ const bottomToTop = clientRect.top - win.innerHeight;
+ // We allow one translation on the y axis.
+ let yAllowed = true;
+
+ // disable smooth scrolling when the user prefers reduced motion
+ const reducedMotion = win.matchMedia("(prefers-reduced-motion)").matches;
+ smooth = smooth && !reducedMotion;
+
+ const options = { behavior: smooth ? "smooth" : "auto" };
+
+ // Whatever `centered` is, the behavior is the same if the box is
+ // (even partially) visible.
+ if ((topToBottom > 0 || !centered) && topToBottom <= elem.offsetHeight) {
+ win.scrollBy(
+ Object.assign(
+ { left: 0, top: topToBottom - elem.offsetHeight },
+ options
+ )
+ );
+ yAllowed = false;
+ } else if (
+ (bottomToTop < 0 || !centered) &&
+ bottomToTop >= -elem.offsetHeight
+ ) {
+ win.scrollBy(
+ Object.assign(
+ { left: 0, top: bottomToTop + elem.offsetHeight },
+ options
+ )
+ );
+
+ yAllowed = false;
+ }
+
+ // If we want it centered, and the box is completely hidden,
+ // then we center it explicitly.
+ if (centered) {
+ if (yAllowed && (topToBottom <= 0 || bottomToTop >= 0)) {
+ const x = win.scrollX;
+ const y =
+ win.scrollY +
+ clientRect.top -
+ (win.innerHeight - elem.offsetHeight) / 2;
+ win.scroll(Object.assign({ left: x, top: y }, options));
+ }
+ }
+ }
+
+ function closestScrolledParent(node) {
+ if (node == null) {
+ return null;
+ }
+
+ if (node.scrollHeight > node.clientHeight) {
+ return node;
+ }
+
+ return closestScrolledParent(node.parentNode);
+ }
+
+ /**
+ * Scrolls the element into view if it is not visible.
+ *
+ * @param {DOMNode|undefined} element
+ * The item to be scrolled to.
+ *
+ * @param {Object|undefined} options
+ * An options object which can contain:
+ * - container: possible scrollable container. If it is not scrollable, we will
+ * look it up.
+ * - alignTo: "top" or "bottom" to indicate if we should scroll the element
+ * to the top or the bottom of the scrollable container when the
+ * element is off canvas.
+ * - center: Indicate if we should scroll the element to the middle of the
+ * scrollable container when the element is off canvas.
+ */
+ function scrollIntoView(element, options = {}) {
+ if (!element) {
+ return;
+ }
+
+ const { alignTo, center, container } = options;
+
+ const { top, bottom } = element.getBoundingClientRect();
+ const scrolledParent = closestScrolledParent(
+ container || element.parentNode
+ );
+ const scrolledParentRect = scrolledParent
+ ? scrolledParent.getBoundingClientRect()
+ : null;
+ const isVisible =
+ !scrolledParent ||
+ (top >= scrolledParentRect.top && bottom <= scrolledParentRect.bottom);
+
+ if (isVisible) {
+ return;
+ }
+
+ if (center) {
+ element.scrollIntoView({ block: "center" });
+ return;
+ }
+
+ const scrollToTop = alignTo
+ ? alignTo === "top"
+ : !scrolledParentRect || top < scrolledParentRect.top;
+ element.scrollIntoView(scrollToTop);
+ }
+
+ // Exports from this module
+ module.exports.scrollIntoViewIfNeeded = scrollIntoViewIfNeeded;
+ module.exports.scrollIntoView = scrollIntoView;
+});