2016-08-17 20:13:54 +03:00
|
|
|
/* 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";
|
|
|
|
|
2017-03-09 18:20:07 +03:00
|
|
|
// Make this available to both AMD and CJS environments
|
2023-05-20 15:26:49 +03:00
|
|
|
define(function (require, exports, module) {
|
2017-03-09 18:20:07 +03:00
|
|
|
/**
|
|
|
|
* 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.
|
2018-08-22 11:35:40 +03:00
|
|
|
* @param {Boolean} smooth
|
|
|
|
* true if you want the scroll to happen smoothly, instead of instantly.
|
|
|
|
* It is false by default.
|
2017-03-09 18:20:07 +03:00
|
|
|
*/
|
2018-08-22 11:35:40 +03:00
|
|
|
function scrollIntoViewIfNeeded(elem, centered = true, smooth = false) {
|
2018-06-01 13:36:09 +03:00
|
|
|
const win = elem.ownerDocument.defaultView;
|
|
|
|
const clientRect = elem.getBoundingClientRect();
|
2016-08-17 20:13:54 +03:00
|
|
|
|
2017-03-09 18:20:07 +03:00
|
|
|
// 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.
|
2016-08-17 20:13:54 +03:00
|
|
|
|
2018-06-01 13:36:09 +03:00
|
|
|
const topToBottom = clientRect.bottom;
|
|
|
|
const bottomToTop = clientRect.top - win.innerHeight;
|
2017-03-09 18:20:07 +03:00
|
|
|
// We allow one translation on the y axis.
|
|
|
|
let yAllowed = true;
|
2016-08-17 20:13:54 +03:00
|
|
|
|
2018-08-22 11:35:40 +03:00
|
|
|
// 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" };
|
|
|
|
|
2017-03-09 18:20:07 +03:00
|
|
|
// Whatever `centered` is, the behavior is the same if the box is
|
|
|
|
// (even partially) visible.
|
|
|
|
if ((topToBottom > 0 || !centered) && topToBottom <= elem.offsetHeight) {
|
2018-08-22 11:35:40 +03:00
|
|
|
win.scrollBy(
|
|
|
|
Object.assign(
|
|
|
|
{ left: 0, top: topToBottom - elem.offsetHeight },
|
|
|
|
options
|
2019-07-05 12:24:38 +03:00
|
|
|
)
|
2018-08-22 11:35:40 +03:00
|
|
|
);
|
2017-03-09 18:20:07 +03:00
|
|
|
yAllowed = false;
|
|
|
|
} else if (
|
|
|
|
(bottomToTop < 0 || !centered) &&
|
|
|
|
bottomToTop >= -elem.offsetHeight
|
|
|
|
) {
|
2018-08-22 11:35:40 +03:00
|
|
|
win.scrollBy(
|
|
|
|
Object.assign(
|
|
|
|
{ left: 0, top: bottomToTop + elem.offsetHeight },
|
|
|
|
options
|
2019-07-05 12:24:38 +03:00
|
|
|
)
|
2018-08-22 11:35:40 +03:00
|
|
|
);
|
|
|
|
|
2017-03-09 18:20:07 +03:00
|
|
|
yAllowed = false;
|
|
|
|
}
|
2016-08-17 20:13:54 +03:00
|
|
|
|
2017-03-09 18:20:07 +03:00
|
|
|
// If we want it centered, and the box is completely hidden,
|
|
|
|
// then we center it explicitly.
|
|
|
|
if (centered) {
|
|
|
|
if (yAllowed && (topToBottom <= 0 || bottomToTop >= 0)) {
|
2018-08-22 11:35:40 +03:00
|
|
|
const x = win.scrollX;
|
|
|
|
const y =
|
|
|
|
win.scrollY +
|
|
|
|
clientRect.top -
|
|
|
|
(win.innerHeight - elem.offsetHeight) / 2;
|
|
|
|
win.scroll(Object.assign({ left: x, top: y }, options));
|
2017-03-09 18:20:07 +03:00
|
|
|
}
|
2016-08-17 20:13:54 +03:00
|
|
|
}
|
|
|
|
}
|
2019-02-12 22:39:46 +03:00
|
|
|
|
|
|
|
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.
|
2019-05-03 20:46:58 +03:00
|
|
|
* - center: Indicate if we should scroll the element to the middle of the
|
|
|
|
* scrollable container when the element is off canvas.
|
2019-02-12 22:39:46 +03:00
|
|
|
*/
|
|
|
|
function scrollIntoView(element, options = {}) {
|
|
|
|
if (!element) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-03 20:46:58 +03:00
|
|
|
const { alignTo, center, container } = options;
|
2019-02-12 22:39:46 +03:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-05-03 20:46:58 +03:00
|
|
|
if (center) {
|
|
|
|
element.scrollIntoView({ block: "center" });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-12 22:39:46 +03:00
|
|
|
const scrollToTop = alignTo
|
|
|
|
? alignTo === "top"
|
|
|
|
: !scrolledParentRect || top < scrolledParentRect.top;
|
|
|
|
element.scrollIntoView(scrollToTop);
|
|
|
|
}
|
|
|
|
|
2017-03-09 18:20:07 +03:00
|
|
|
// Exports from this module
|
|
|
|
module.exports.scrollIntoViewIfNeeded = scrollIntoViewIfNeeded;
|
2019-02-12 22:39:46 +03:00
|
|
|
module.exports.scrollIntoView = scrollIntoView;
|
2017-03-09 18:20:07 +03:00
|
|
|
});
|