mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D35884 --HG-- extra : source : 60e4496cf9699dc59f2f4738cb60f87cbdb01e67
		
			
				
	
	
		
			144 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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;
 | 
						|
});
 |