chromium/chrome/browser/resources/chromeos/sys_internals/line_chart/scrollbar.js

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {createElementWithClassName} from 'chrome://resources/ash/common/util.js';

/**
 * Create by |LineChart.LineChart|.
 * Create a dummy scrollbar to show the position of the line chart and to scroll
 * the line chart, so we can draw the visible part of the line chart only
 * instead of drawing the whole chart.
 * @const
 */
export class Scrollbar {
  constructor(/** function(): undefined */ callback) {
    /** @const {function(): undefined} - Handle the scrolling event. */
    this.callback_ = callback;

    /** @type {number} - The range the scrollbar can scroll. */
    this.range_ = 0;

    /** @type {number} - The current position of the scrollbar. */
    this.position_ = 0;

    /** @type {number} - The real width of this scrollbar, in pixels. */
    this.width_ = 0;

    /** @type {Element} - The outer div to show the scrollbar. */
    this.outerDiv_ =
        createElementWithClassName('div', 'horizontal-scrollbar-outer');
    this.outerDiv_.addEventListener('scroll', this.onScroll_.bind(this));

    /** @type {Element} - The inner div to make outer div scrollable. */
    this.innerDiv_ =
        createElementWithClassName('div', 'horizontal-scrollbar-inner');
    this.outerDiv_.appendChild(this.innerDiv_);
  }

  /**
   * Scrolling event handler.
   */
  onScroll_() {
    const /** number */ newPosition = this.outerDiv_.scrollLeft;
    if (newPosition === this.position_) {
      return;
    }
    this.position_ = newPosition;
    this.callback_();
  }

  /** @return {Element} */
  getRootDiv() {
    return this.outerDiv_;
  }

  /**
   * Return the height of scrollbar element.
   * @return {number}
   */
  getHeight() {
    return this.outerDiv_.offsetHeight;
  }

  /** @return {number} */
  getRange() {
    return this.range_;
  }

  /**
   * Position may be float point number because |document.scrollLeft| may be
   * float point number.
   * @return {number}
   */
  getPosition() {
    return Math.round(this.position_);
  }

  /**
   * Change the size of the outer div and update the scrollbar position.
   * @param {number} width
   */
  resize(width) {
    if (this.width_ === width) {
      return;
    }
    this.width_ = width;
    this.updateOuterDivWidth_();
  }

  updateOuterDivWidth_() {
    this.constructor.setNodeWidth(this.outerDiv_, this.width_);
  }

  /**
   * Set the scrollable range to |range|. Use the inner div's width to control
   * the scrollable range. If position go out of range after range update, set
   * it to the boundary value.
   * @param {number} range
   */
  setRange(range) {
    this.range_ = range;
    this.updateInnerDivWidth_();
    if (range < this.position_) {
      this.position_ = range;
      this.updateScrollbarPosition_();
    }
  }

  updateInnerDivWidth_() {
    const width = this.outerDiv_.clientWidth;
    this.constructor.setNodeWidth(this.innerDiv_, width + this.range_);
  }

  /**
   * @param {Element} node
   * @param {number} width
   */
  static setNodeWidth(node, width) {
    node.style.width = width + 'px';
  }

  /**
   * Set the scrollbar position to |position|. If the new position go out of
   * range, set it to the boundary value.
   * @param {number} position
   */
  setPosition(position) {
    const /** number */ newPosition =
        Math.max(0, Math.min(position, this.range_));
    this.position_ = newPosition;
    this.updateScrollbarPosition_();
  }

  /**
   * Update the scrollbar position via Javascript scrollbar api. Position may
   * not be the same value as what we assigned even if the value is in the
   * range. See crbug.com/760425.
   */
  updateScrollbarPosition_() {
    if (this.outerDiv_.scrollLeft === this.position_) {
      return;
    }
    this.outerDiv_.scrollLeft = this.position_;
  }

  /**
   * Return true if scrollbar is at the right edge of the chart.
   * @return {boolean}
   */
  isScrolledToRightEdge() {
    /* |scrollLeft| may become a float point number even if we set it to some
     * integer value. If the distance to the right edge less than 2 pixels, we
     * consider that it is scrolled to the right edge.
     */
    const scrollLeftErrorAmount = 2;
    return this.position_ + scrollLeftErrorAmount > this.range_;
  }

  /**
   * Scroll the scrollbar to the right edge.
   */
  scrollToRightEdge() {
    this.setPosition(this.range_);
  }
}