blob: c603c7759bdb47ae5c2acc10f64b7a23dcd90ff2 [file] [log] [blame]
/**
* @license
* Copyright 2019-2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
/**
* Class names used by OverflowingTabListController.
* @private @enum {string}
*/
const OverflowingTabListClassName = {
IS_OVERFLOWING: 'is-overflowing',
};
/**
* Allows a list of tabs to overflow into a “more” menu depending on the size
* of the container.
*/
class OverflowingTabListController {
/**
* @param {Element} el
*/
constructor(el) {
if (!el) {
throw new Error('Must provide an element.');
}
/**
* @type {!Element}
* @private
*/
this._el = el;
const selectEl = this._el.querySelector('select');
if (!selectEl) {
throw new Error('Element must contain a <select> element.');
}
/**
* @type {!Element}
* @private
*/
this._selectEl = /** @type {!Element} */ (selectEl);
/**
* An array of tab elements’ rightmost points.
* @type {!Array<number>}
* @private
*/
this._tabRects = [];
if (!window.ResizeObserver) {
this._el.style.overflowX = 'scroll';
return;
}
let initialLeft = 0;
this._tabRects = Array.from(this._el.querySelectorAll(`[role='tab']`)).map((el, i) => {
if (i === 0) {
initialLeft = el.offsetLeft;
}
return el.offsetLeft + el.offsetWidth - initialLeft;
});
const resizeObserver = new ResizeObserver(entries => this.resizeObserverCallback(entries));
resizeObserver.observe(this._el);
this._selectEl.addEventListener('change', e =>
this.handleOverflowSelectChange(/** @type {!Event} */ (e))
);
}
/**
* @param {!Array<ResizeObserverEntry>} entries
* @private
*/
resizeObserverCallback(entries) {
entries.forEach(entry => {
const containerRect = entry.target.getBoundingClientRect();
const hiddenEls = [];
this._el.querySelectorAll(`[role='tab']`).forEach((el, i) => {
const rect = this._tabRects[i];
let overflowPoint = containerRect.width;
const OVERFLOW_MENU_ELEMENT_ADJUSTMENT_PX = 40;
if (this._el.classList.contains(OverflowingTabListClassName.IS_OVERFLOWING)) {
overflowPoint -= OVERFLOW_MENU_ELEMENT_ADJUSTMENT_PX;
}
const hideEl = rect > overflowPoint;
el.setAttribute('aria-hidden', hideEl);
if (hideEl) {
hiddenEls.push(i);
}
});
this.setOverflowMenuHidden(hiddenEls.length === 0);
this._selectEl.querySelectorAll('option').forEach((el, i) => {
el.disabled = !hiddenEls.includes(i);
});
});
}
/**
* @param {boolean} hidden
* @private
*/
setOverflowMenuHidden(hidden) {
this._el.classList.toggle(OverflowingTabListClassName.IS_OVERFLOWING, !hidden);
}
/**
* Handles when the overflow menu select element changes.
* @param {!Event} e
*/
handleOverflowSelectChange(e) {
window.location.href = e.target.value;
}
}