debug-server-next
Version:
Dev server for hippy-core.
1,093 lines (1,092 loc) • 40.7 kB
JavaScript
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as IconButton from '../components/icon_button/icon_button.js';
import * as ARIAUtils from './ARIAUtils.js';
import { ContextMenu } from './ContextMenu.js';
import { Constraints, Size } from './Geometry.js';
import { Icon } from './Icon.js';
import { Toolbar } from './Toolbar.js';
import { Tooltip } from './Tooltip.js';
import { installDragHandle, invokeOnceAfterBatchUpdate } from './UIUtils.js';
import { VBox } from './Widget.js';
import { ZoomManager } from './ZoomManager.js';
const UIStrings = {
/**
*@description The aria label for the button to open more tabs at the right tabbed pane in Elements tools
*/
moreTabs: 'More tabs',
/**
*@description Text in Tabbed Pane
*@example {tab} PH1
*/
closeS: 'Close {PH1}',
/**
*@description Text to close something
*/
close: 'Close',
/**
*@description Text on a menu option to close other drawers when right click on a drawer title
*/
closeOthers: 'Close others',
/**
*@description Text on a menu option to close the drawer to the right when right click on a drawer title
*/
closeTabsToTheRight: 'Close tabs to the right',
/**
*@description Text on a menu option to close all the drawers except Console when right click on a drawer title
*/
closeAll: 'Close all',
};
const str_ = i18n.i18n.registerUIStrings('ui/legacy/TabbedPane.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class TabbedPane extends VBox {
_headerElement;
_headerContentsElement;
_tabSlider;
_tabsElement;
_contentElement;
_tabs;
_tabsHistory;
_tabsById;
_currentTabLocked;
_autoSelectFirstItemOnShow;
_triggerDropDownTimeout;
_dropDownButton;
_currentDevicePixelRatio;
_shrinkableTabs;
_verticalTabLayout;
_closeableTabs;
_delegate;
_currentTab;
_sliderEnabled;
_placeholderElement;
_focusedPlaceholderElement;
_placeholderContainerElement;
_lastSelectedOverflowTab;
_overflowDisabled;
_measuredDropDownButtonWidth;
_leftToolbar;
_rightToolbar;
_allowTabReorder;
_automaticReorder;
constructor() {
super(true);
this.registerRequiredCSS('ui/legacy/tabbedPane.css');
this.element.classList.add('tabbed-pane');
this.contentElement.classList.add('tabbed-pane-shadow');
this.contentElement.tabIndex = -1;
this.setDefaultFocusedElement(this.contentElement);
this._headerElement = this.contentElement.createChild('div', 'tabbed-pane-header');
this._headerContentsElement = this._headerElement.createChild('div', 'tabbed-pane-header-contents');
this._tabSlider = document.createElement('div');
this._tabSlider.classList.add('tabbed-pane-tab-slider');
this._tabsElement = this._headerContentsElement.createChild('div', 'tabbed-pane-header-tabs');
this._tabsElement.setAttribute('role', 'tablist');
this._tabsElement.addEventListener('keydown', this._keyDown.bind(this), false);
this._contentElement = this.contentElement.createChild('div', 'tabbed-pane-content');
this._contentElement.createChild('slot');
this._tabs = [];
this._tabsHistory = [];
this._tabsById = new Map();
this._currentTabLocked = false;
this._autoSelectFirstItemOnShow = true;
this._triggerDropDownTimeout = null;
this._dropDownButton = this._createDropDownButton();
this._currentDevicePixelRatio = window.devicePixelRatio;
ZoomManager.instance().addEventListener("ZoomChanged" /* ZoomChanged */, this._zoomChanged, this);
this.makeTabSlider();
}
setAccessibleName(name) {
ARIAUtils.setAccessibleName(this._tabsElement, name);
}
setCurrentTabLocked(locked) {
this._currentTabLocked = locked;
this._headerElement.classList.toggle('locked', this._currentTabLocked);
}
setAutoSelectFirstItemOnShow(autoSelect) {
this._autoSelectFirstItemOnShow = autoSelect;
}
get visibleView() {
return this._currentTab ? this._currentTab.view : null;
}
tabIds() {
return this._tabs.map(tab => tab._id);
}
tabIndex(tabId) {
return this._tabs.findIndex(tab => tab.id === tabId);
}
tabViews() {
return this._tabs.map(tab => tab.view);
}
tabView(tabId) {
const tab = this._tabsById.get(tabId);
return tab ? tab.view : null;
}
get selectedTabId() {
return this._currentTab ? this._currentTab.id : null;
}
setShrinkableTabs(shrinkableTabs) {
this._shrinkableTabs = shrinkableTabs;
}
makeVerticalTabLayout() {
this._verticalTabLayout = true;
this._setTabSlider(false);
this.contentElement.classList.add('vertical-tab-layout');
this.invalidateConstraints();
}
setCloseableTabs(closeableTabs) {
this._closeableTabs = closeableTabs;
}
focus() {
if (this.visibleView) {
this.visibleView.focus();
}
else {
this.contentElement.focus();
}
}
focusSelectedTabHeader() {
const selectedTab = this._currentTab;
if (selectedTab) {
selectedTab.tabElement.focus();
}
}
headerElement() {
return this._headerElement;
}
isTabCloseable(id) {
const tab = this._tabsById.get(id);
return tab ? tab.isCloseable() : false;
}
setTabDelegate(delegate) {
const tabs = this._tabs.slice();
for (let i = 0; i < tabs.length; ++i) {
tabs[i].setDelegate(delegate);
}
this._delegate = delegate;
}
appendTab(id, tabTitle, view, tabTooltip, userGesture, isCloseable, index) {
const closeable = typeof isCloseable === 'boolean' ? isCloseable : Boolean(this._closeableTabs);
const tab = new TabbedPaneTab(this, id, tabTitle, closeable, view, tabTooltip);
tab.setDelegate(this._delegate);
console.assert(!this._tabsById.has(id), `Tabbed pane already contains a tab with id '${id}'`);
this._tabsById.set(id, tab);
if (index !== undefined) {
this._tabs.splice(index, 0, tab);
}
else {
this._tabs.push(tab);
}
this._tabsHistory.push(tab);
if (this._tabsHistory[0] === tab && this.isShowing()) {
this.selectTab(tab.id, userGesture);
}
this._updateTabElements();
}
closeTab(id, userGesture) {
this.closeTabs([id], userGesture);
}
closeTabs(ids, userGesture) {
const focused = this.hasFocus();
for (let i = 0; i < ids.length; ++i) {
this._innerCloseTab(ids[i], userGesture);
}
this._updateTabElements();
if (this._tabsHistory.length) {
this.selectTab(this._tabsHistory[0].id, false);
}
if (focused) {
this.focus();
}
}
_innerCloseTab(id, userGesture) {
const tab = this._tabsById.get(id);
if (!tab) {
return;
}
if (userGesture && !tab._closeable) {
return;
}
if (this._currentTab && this._currentTab.id === id) {
this._hideCurrentTab();
}
this._tabsById.delete(id);
this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
this._tabs.splice(this._tabs.indexOf(tab), 1);
if (tab._shown) {
this._hideTabElement(tab);
}
const eventData = { prevTabId: undefined, tabId: id, view: tab.view, isUserGesture: userGesture };
this.dispatchEventToListeners(Events.TabClosed, eventData);
return true;
}
hasTab(tabId) {
return this._tabsById.has(tabId);
}
otherTabs(id) {
const result = [];
for (let i = 0; i < this._tabs.length; ++i) {
if (this._tabs[i].id !== id) {
result.push(this._tabs[i].id);
}
}
return result;
}
_tabsToTheRight(id) {
let index = -1;
for (let i = 0; i < this._tabs.length; ++i) {
if (this._tabs[i].id === id) {
index = i;
break;
}
}
if (index === -1) {
return [];
}
return this._tabs.slice(index + 1).map(function (tab) {
return tab.id;
});
}
_viewHasFocus() {
if (this.visibleView && this.visibleView.hasFocus()) {
return true;
}
const root = this.contentElement.getComponentRoot();
return root instanceof Document && this.contentElement === root.activeElement;
}
selectTab(id, userGesture, forceFocus) {
if (this._currentTabLocked) {
return false;
}
const focused = this._viewHasFocus();
const tab = this._tabsById.get(id);
if (!tab) {
return false;
}
const eventData = {
prevTabId: this._currentTab ? this._currentTab.id : undefined,
tabId: id,
view: tab.view,
isUserGesture: userGesture,
};
this.dispatchEventToListeners(Events.TabInvoked, eventData);
if (this._currentTab && this._currentTab.id === id) {
return true;
}
this.suspendInvalidations();
this._hideCurrentTab();
this._showTab(tab);
this.resumeInvalidations();
this._currentTab = tab;
this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
this._tabsHistory.splice(0, 0, tab);
this._updateTabElements();
if (focused || forceFocus) {
this.focus();
}
this.dispatchEventToListeners(Events.TabSelected, eventData);
return true;
}
selectNextTab() {
const index = this._tabs.indexOf(this._currentTab);
const nextIndex = Platform.NumberUtilities.mod(index + 1, this._tabs.length);
this.selectTab(this._tabs[nextIndex].id, true);
}
selectPrevTab() {
const index = this._tabs.indexOf(this._currentTab);
const nextIndex = Platform.NumberUtilities.mod(index - 1, this._tabs.length);
this.selectTab(this._tabs[nextIndex].id, true);
}
lastOpenedTabIds(tabsCount) {
function tabToTabId(tab) {
return tab.id;
}
return this._tabsHistory.slice(0, tabsCount).map(tabToTabId);
}
setTabIcon(id, icon) {
const tab = this._tabsById.get(id);
if (!tab) {
return;
}
tab._setIcon(icon);
this._updateTabElements();
}
setTabEnabled(id, enabled) {
const tab = this._tabsById.get(id);
if (tab) {
tab.tabElement.classList.toggle('disabled', !enabled);
}
}
toggleTabClass(id, className, force) {
const tab = this._tabsById.get(id);
if (tab && tab._toggleClass(className, force)) {
this._updateTabElements();
}
}
_zoomChanged(_event) {
this._clearMeasuredWidths();
if (this.isShowing()) {
this._updateTabElements();
}
}
_clearMeasuredWidths() {
for (let i = 0; i < this._tabs.length; ++i) {
delete this._tabs[i]._measuredWidth;
}
}
changeTabTitle(id, tabTitle, tabTooltip) {
const tab = this._tabsById.get(id);
if (tab && tabTooltip !== undefined) {
tab.tooltip = tabTooltip;
}
if (tab && tab.title !== tabTitle) {
tab.title = tabTitle;
ARIAUtils.setAccessibleName(tab.tabElement, tabTitle);
this._updateTabElements();
}
}
changeTabView(id, view) {
const tab = this._tabsById.get(id);
if (!tab || tab.view === view) {
return;
}
this.suspendInvalidations();
const isSelected = this._currentTab && this._currentTab.id === id;
const shouldFocus = tab.view.hasFocus();
if (isSelected) {
this._hideTab(tab);
}
tab.view = view;
if (isSelected) {
this._showTab(tab);
}
if (shouldFocus) {
tab.view.focus();
}
this.resumeInvalidations();
}
onResize() {
if (this._currentDevicePixelRatio !== window.devicePixelRatio) {
// Force recalculation of all tab widths on a DPI change
this._clearMeasuredWidths();
this._currentDevicePixelRatio = window.devicePixelRatio;
}
this._updateTabElements();
}
headerResized() {
this._updateTabElements();
}
wasShown() {
const effectiveTab = this._currentTab || this._tabsHistory[0];
if (effectiveTab && this._autoSelectFirstItemOnShow) {
this.selectTab(effectiveTab.id);
}
}
makeTabSlider() {
if (this._verticalTabLayout) {
return;
}
this._setTabSlider(true);
}
_setTabSlider(enable) {
this._sliderEnabled = enable;
this._tabSlider.classList.toggle('enabled', enable);
}
calculateConstraints() {
let constraints = super.calculateConstraints();
const minContentConstraints = new Constraints(new Size(0, 0), new Size(50, 50));
constraints = constraints.widthToMax(minContentConstraints).heightToMax(minContentConstraints);
if (this._verticalTabLayout) {
constraints = constraints.addWidth(new Constraints(new Size(120, 0)));
}
else {
constraints = constraints.addHeight(new Constraints(new Size(0, 30)));
}
return constraints;
}
_updateTabElements() {
invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements);
}
setPlaceholderElement(element, focusedElement) {
this._placeholderElement = element;
if (focusedElement) {
this._focusedPlaceholderElement = focusedElement;
}
if (this._placeholderContainerElement) {
this._placeholderContainerElement.removeChildren();
this._placeholderContainerElement.appendChild(element);
}
}
async waitForTabElementUpdate() {
this._innerUpdateTabElements();
}
_innerUpdateTabElements() {
if (!this.isShowing()) {
return;
}
if (!this._tabs.length) {
this._contentElement.classList.add('has-no-tabs');
if (this._placeholderElement && !this._placeholderContainerElement) {
this._placeholderContainerElement = this._contentElement.createChild('div', 'tabbed-pane-placeholder fill');
this._placeholderContainerElement.appendChild(this._placeholderElement);
if (this._focusedPlaceholderElement) {
this.setDefaultFocusedElement(this._focusedPlaceholderElement);
}
}
}
else {
this._contentElement.classList.remove('has-no-tabs');
if (this._placeholderContainerElement) {
this._placeholderContainerElement.remove();
this.setDefaultFocusedElement(this.contentElement);
delete this._placeholderContainerElement;
}
}
this._measureDropDownButton();
this._updateWidths();
this._updateTabsDropDown();
this._updateTabSlider();
}
_showTabElement(index, tab) {
if (index >= this._tabsElement.children.length) {
this._tabsElement.appendChild(tab.tabElement);
}
else {
this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]);
}
tab._shown = true;
}
_hideTabElement(tab) {
this._tabsElement.removeChild(tab.tabElement);
tab._shown = false;
}
_createDropDownButton() {
const dropDownContainer = document.createElement('div');
dropDownContainer.classList.add('tabbed-pane-header-tabs-drop-down-container');
const chevronIcon = Icon.create('largeicon-chevron', 'chevron-icon');
ARIAUtils.markAsMenuButton(dropDownContainer);
ARIAUtils.setAccessibleName(dropDownContainer, i18nString(UIStrings.moreTabs));
dropDownContainer.tabIndex = 0;
dropDownContainer.appendChild(chevronIcon);
dropDownContainer.addEventListener('click', this._dropDownClicked.bind(this));
dropDownContainer.addEventListener('keydown', this._dropDownKeydown.bind(this));
dropDownContainer.addEventListener('mousedown', event => {
if (event.which !== 1 || this._triggerDropDownTimeout) {
return;
}
this._triggerDropDownTimeout = window.setTimeout(this._dropDownClicked.bind(this, event), 200);
});
return dropDownContainer;
}
_dropDownClicked(ev) {
const event = ev;
if (event.which !== 1) {
return;
}
if (this._triggerDropDownTimeout) {
clearTimeout(this._triggerDropDownTimeout);
this._triggerDropDownTimeout = null;
}
const rect = this._dropDownButton.getBoundingClientRect();
const menu = new ContextMenu(event, false, rect.left, rect.bottom);
for (const tab of this._tabs) {
if (tab._shown) {
continue;
}
if (this._numberOfTabsShown() === 0 && this._tabsHistory[0] === tab) {
menu.defaultSection().appendCheckboxItem(tab.title, this._dropDownMenuItemSelected.bind(this, tab), /* checked */ true);
}
else {
menu.defaultSection().appendItem(tab.title, this._dropDownMenuItemSelected.bind(this, tab));
}
}
menu.show();
}
_dropDownKeydown(event) {
if (isEnterOrSpaceKey(event)) {
this._dropDownButton.click();
event.consume(true);
}
}
_dropDownMenuItemSelected(tab) {
this._lastSelectedOverflowTab = tab;
this.selectTab(tab.id, true, true);
}
_totalWidth() {
return this._headerContentsElement.getBoundingClientRect().width;
}
_numberOfTabsShown() {
let numTabsShown = 0;
for (const tab of this._tabs) {
if (tab._shown) {
numTabsShown++;
}
}
return numTabsShown;
}
disableOverflowMenu() {
this._overflowDisabled = true;
}
_updateTabsDropDown() {
const tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDownButtonWidth || 0);
if (this._lastSelectedOverflowTab && this._numberOfTabsShown() !== tabsToShowIndexes.length) {
delete this._lastSelectedOverflowTab;
this._updateTabsDropDown();
return;
}
for (let i = 0; i < this._tabs.length; ++i) {
if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1) {
this._hideTabElement(this._tabs[i]);
}
}
for (let i = 0; i < tabsToShowIndexes.length; ++i) {
const tab = this._tabs[tabsToShowIndexes[i]];
if (!tab._shown) {
this._showTabElement(i, tab);
}
}
if (!this._overflowDisabled) {
this._maybeShowDropDown(tabsToShowIndexes.length !== this._tabs.length);
}
}
_maybeShowDropDown(hasMoreTabs) {
if (hasMoreTabs && !this._dropDownButton.parentElement) {
this._headerContentsElement.appendChild(this._dropDownButton);
}
else if (!hasMoreTabs && this._dropDownButton.parentElement) {
this._headerContentsElement.removeChild(this._dropDownButton);
}
}
_measureDropDownButton() {
if (this._overflowDisabled || this._measuredDropDownButtonWidth) {
return;
}
this._dropDownButton.classList.add('measuring');
this._headerContentsElement.appendChild(this._dropDownButton);
this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRect().width;
this._headerContentsElement.removeChild(this._dropDownButton);
this._dropDownButton.classList.remove('measuring');
}
_updateWidths() {
const measuredWidths = this._measureWidths();
const maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), this._totalWidth()) : Number.MAX_VALUE;
let i = 0;
for (const tab of this._tabs) {
tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWidths[i++]));
}
}
_measureWidths() {
// Add all elements to measure into this._tabsElement
this._tabsElement.style.setProperty('width', '2000px');
const measuringTabElements = new Map();
for (const tab of this._tabs) {
if (typeof tab._measuredWidth === 'number') {
continue;
}
const measuringTabElement = tab._createTabElement(true);
measuringTabElements.set(measuringTabElement, tab);
this._tabsElement.appendChild(measuringTabElement);
}
// Perform measurement
for (const [measuringTabElement, tab] of measuringTabElements) {
const width = measuringTabElement.getBoundingClientRect().width;
tab._measuredWidth = Math.ceil(width);
}
// Nuke elements from the UI
for (const measuringTabElement of measuringTabElements.keys()) {
measuringTabElement.remove();
}
// Combine the results.
const measuredWidths = [];
for (const tab of this._tabs) {
measuredWidths.push(tab._measuredWidth || 0);
}
this._tabsElement.style.removeProperty('width');
return measuredWidths;
}
_calculateMaxWidth(measuredWidths, totalWidth) {
if (!measuredWidths.length) {
return 0;
}
measuredWidths.sort(function (x, y) {
return x - y;
});
let totalMeasuredWidth = 0;
for (let i = 0; i < measuredWidths.length; ++i) {
totalMeasuredWidth += measuredWidths[i];
}
if (totalWidth >= totalMeasuredWidth) {
return measuredWidths[measuredWidths.length - 1];
}
let totalExtraWidth = 0;
for (let i = measuredWidths.length - 1; i > 0; --i) {
const extraWidth = measuredWidths[i] - measuredWidths[i - 1];
totalExtraWidth += (measuredWidths.length - i) * extraWidth;
if (totalWidth + totalExtraWidth >= totalMeasuredWidth) {
return measuredWidths[i - 1] +
(totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i);
}
}
return totalWidth / measuredWidths.length;
}
_tabsToShowIndexes(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth) {
const tabsToShowIndexes = [];
let totalTabsWidth = 0;
const tabCount = tabsOrdered.length;
const tabsToLookAt = tabsOrdered.slice(0);
if (this._currentTab !== undefined) {
tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this._currentTab), 1)[0]);
}
if (this._lastSelectedOverflowTab !== undefined) {
tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this._lastSelectedOverflowTab), 1)[0]);
}
for (let i = 0; i < tabCount; ++i) {
const tab = this._automaticReorder ? tabsHistory[i] : tabsToLookAt[i];
totalTabsWidth += tab.width();
let minimalRequiredWidth = totalTabsWidth;
if (i !== tabCount - 1) {
minimalRequiredWidth += measuredDropDownButtonWidth;
}
if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth) {
break;
}
tabsToShowIndexes.push(tabsOrdered.indexOf(tab));
}
tabsToShowIndexes.sort(function (x, y) {
return x - y;
});
return tabsToShowIndexes;
}
_hideCurrentTab() {
if (!this._currentTab) {
return;
}
this._hideTab(this._currentTab);
delete this._currentTab;
}
_showTab(tab) {
tab.tabElement.tabIndex = 0;
tab.tabElement.classList.add('selected');
ARIAUtils.setSelected(tab.tabElement, true);
tab.view.show(this.element);
this._updateTabSlider();
}
_updateTabSlider() {
if (!this._sliderEnabled) {
return;
}
if (!this._currentTab) {
this._tabSlider.style.width = '0';
return;
}
let left = 0;
for (let i = 0; i < this._tabs.length && this._currentTab !== this._tabs[i]; i++) {
if (this._tabs[i]._shown) {
left += this._tabs[i]._measuredWidth || 0;
}
}
const sliderWidth = this._currentTab._shown ? this._currentTab._measuredWidth : this._dropDownButton.offsetWidth;
const scaleFactor = window.devicePixelRatio >= 1.5 ? ' scaleY(0.75)' : '';
this._tabSlider.style.transform = 'translateX(' + left + 'px)' + scaleFactor;
this._tabSlider.style.width = sliderWidth + 'px';
if (this._tabSlider.parentElement !== this._headerContentsElement) {
this._headerContentsElement.appendChild(this._tabSlider);
}
}
_hideTab(tab) {
tab.tabElement.removeAttribute('tabIndex');
tab.tabElement.classList.remove('selected');
tab.tabElement.setAttribute('aria-selected', 'false');
tab.view.detach();
}
elementsToRestoreScrollPositionsFor() {
return [this._contentElement];
}
_insertBefore(tab, index) {
this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.childNodes[index]);
const oldIndex = this._tabs.indexOf(tab);
this._tabs.splice(oldIndex, 1);
if (oldIndex < index) {
--index;
}
this._tabs.splice(index, 0, tab);
const eventData = { prevTabId: undefined, tabId: tab.id, view: tab.view, isUserGesture: undefined };
this.dispatchEventToListeners(Events.TabOrderChanged, eventData);
}
leftToolbar() {
if (!this._leftToolbar) {
this._leftToolbar = new Toolbar('tabbed-pane-left-toolbar');
this._headerElement.insertBefore(this._leftToolbar.element, this._headerElement.firstChild);
}
return this._leftToolbar;
}
rightToolbar() {
if (!this._rightToolbar) {
this._rightToolbar = new Toolbar('tabbed-pane-right-toolbar');
this._headerElement.appendChild(this._rightToolbar.element);
}
return this._rightToolbar;
}
setAllowTabReorder(allow, automatic) {
this._allowTabReorder = allow;
this._automaticReorder = automatic;
}
_keyDown(ev) {
if (!this._currentTab) {
return;
}
const event = ev;
let nextTabElement = null;
switch (event.key) {
case 'ArrowUp':
case 'ArrowLeft':
nextTabElement = this._currentTab.tabElement.previousElementSibling;
if (!nextTabElement && !this._dropDownButton.parentElement) {
nextTabElement = this._currentTab.tabElement.parentElement ?
this._currentTab.tabElement.parentElement.lastElementChild :
null;
}
break;
case 'ArrowDown':
case 'ArrowRight':
nextTabElement = this._currentTab.tabElement.nextElementSibling;
if (!nextTabElement && !this._dropDownButton.parentElement) {
nextTabElement = this._currentTab.tabElement.parentElement ?
this._currentTab.tabElement.parentElement.firstElementChild :
null;
}
break;
case 'Enter':
case ' ':
this._currentTab.view.focus();
return;
default:
return;
}
if (!nextTabElement) {
this._dropDownButton.click();
return;
}
const tab = this._tabs.find(tab => tab.tabElement === nextTabElement);
if (tab) {
this.selectTab(tab.id, true);
}
nextTabElement.focus();
}
}
// TODO(crbug.com/1167717): Make this a const enum again
// eslint-disable-next-line rulesdir/const_enum
export var Events;
(function (Events) {
Events["TabInvoked"] = "TabInvoked";
Events["TabSelected"] = "TabSelected";
Events["TabClosed"] = "TabClosed";
Events["TabOrderChanged"] = "TabOrderChanged";
})(Events || (Events = {}));
export class TabbedPaneTab {
_closeable;
_tabbedPane;
_id;
_title;
_tooltip;
_view;
_shown;
_measuredWidth;
_tabElement;
_iconContainer;
_icon;
_width;
_delegate;
_titleElement;
_dragStartX;
constructor(tabbedPane, id, title, closeable, view, tooltip) {
this._closeable = closeable;
this._tabbedPane = tabbedPane;
this._id = id;
this._title = title;
this._tooltip = tooltip;
this._view = view;
this._shown = false;
this._iconContainer = null;
}
get id() {
return this._id;
}
get title() {
return this._title;
}
set title(title) {
if (title === this._title) {
return;
}
this._title = title;
if (this._titleElement) {
this._titleElement.textContent = title;
}
delete this._measuredWidth;
}
isCloseable() {
return this._closeable;
}
_setIcon(icon) {
this._icon = icon;
if (this._tabElement && this._titleElement) {
this._createIconElement(this._tabElement, this._titleElement, false);
}
delete this._measuredWidth;
}
_toggleClass(className, force) {
const element = this.tabElement;
const hasClass = element.classList.contains(className);
if (hasClass === force) {
return false;
}
element.classList.toggle(className, force);
delete this._measuredWidth;
return true;
}
get view() {
return this._view;
}
set view(view) {
this._view = view;
}
get tooltip() {
return this._tooltip;
}
set tooltip(tooltip) {
this._tooltip = tooltip;
if (this._titleElement) {
Tooltip.install(this._titleElement, tooltip || '');
}
}
get tabElement() {
if (!this._tabElement) {
this._tabElement = this._createTabElement(false);
}
return this._tabElement;
}
width() {
return this._width || 0;
}
setWidth(width) {
this.tabElement.style.width = width === -1 ? '' : (width + 'px');
this._width = width;
}
setDelegate(delegate) {
this._delegate = delegate;
}
_createIconElement(tabElement, titleElement, measuring) {
const iconElement = tabIcons.get(tabElement);
if (iconElement) {
iconElement.remove();
tabIcons.delete(tabElement);
}
if (!this._icon) {
return;
}
const iconContainer = document.createElement('span');
iconContainer.classList.add('tabbed-pane-header-tab-icon');
const iconNode = measuring ? this._icon.cloneNode(true) : this._icon;
iconContainer.appendChild(iconNode);
tabElement.insertBefore(iconContainer, titleElement);
tabIcons.set(tabElement, iconContainer);
}
_createTabElement(measuring) {
const tabElement = document.createElement('div');
tabElement.classList.add('tabbed-pane-header-tab');
tabElement.id = 'tab-' + this._id;
ARIAUtils.markAsTab(tabElement);
ARIAUtils.setSelected(tabElement, false);
ARIAUtils.setAccessibleName(tabElement, this.title);
const titleElement = tabElement.createChild('span', 'tabbed-pane-header-tab-title');
titleElement.textContent = this.title;
Tooltip.install(titleElement, this.tooltip || '');
this._createIconElement(tabElement, titleElement, measuring);
if (!measuring) {
this._titleElement = titleElement;
}
if (this._closeable) {
const closeIcon = this.createCloseIconButton();
tabElement.appendChild(closeIcon);
tabElement.classList.add('closeable');
}
if (measuring) {
tabElement.classList.add('measuring');
}
else {
tabElement.addEventListener('click', this._tabClicked.bind(this), false);
tabElement.addEventListener('auxclick', this._tabClicked.bind(this), false);
tabElement.addEventListener('mousedown', this._tabMouseDown.bind(this), false);
tabElement.addEventListener('mouseup', this._tabMouseUp.bind(this), false);
tabElement.addEventListener('contextmenu', this._tabContextMenu.bind(this), false);
if (this._tabbedPane._allowTabReorder) {
installDragHandle(tabElement, this._startTabDragging.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), '-webkit-grabbing', 'pointer', 200);
}
}
return tabElement;
}
createCloseIconButton() {
const closeIconContainer = document.createElement('div');
closeIconContainer.classList.add('close-button', 'tabbed-pane-close-button');
const closeIcon = new IconButton.Icon.Icon();
closeIcon.data = {
iconName: 'close-icon',
color: 'var(--tabbed-pane-close-icon-color)',
width: '7px',
};
closeIconContainer.appendChild(closeIcon);
closeIconContainer.setAttribute('role', 'button');
closeIconContainer.setAttribute('title', i18nString(UIStrings.closeS, { PH1: this.title }));
closeIconContainer.setAttribute('aria-label', i18nString(UIStrings.closeS, { PH1: this.title }));
return closeIconContainer;
}
isCloseIconClicked(element) {
return element?.classList.contains('tabbed-pane-close-button') ||
element?.parentElement?.classList.contains('tabbed-pane-close-button') || false;
}
_tabClicked(ev) {
const event = ev;
const middleButton = event.button === 1;
const shouldClose = this._closeable && (middleButton || this.isCloseIconClicked(event.target));
if (!shouldClose) {
this._tabbedPane.focus();
return;
}
this._closeTabs([this.id]);
event.consume(true);
}
_tabMouseDown(ev) {
const event = ev;
if (this.isCloseIconClicked(event.target) || event.button !== 0) {
return;
}
this._tabbedPane.selectTab(this.id, true);
}
_tabMouseUp(ev) {
const event = ev;
// This is needed to prevent middle-click pasting on linux when tabs are clicked.
if (event.button === 1) {
event.consume(true);
}
}
_closeTabs(ids) {
if (this._delegate) {
this._delegate.closeTabs(this._tabbedPane, ids);
return;
}
this._tabbedPane.closeTabs(ids, true);
}
_tabContextMenu(event) {
function close() {
this._closeTabs([this.id]);
}
function closeOthers() {
this._closeTabs(this._tabbedPane.otherTabs(this.id));
}
function closeAll() {
this._closeTabs(this._tabbedPane.tabIds());
}
function closeToTheRight() {
this._closeTabs(this._tabbedPane._tabsToTheRight(this.id));
}
const contextMenu = new ContextMenu(event);
if (this._closeable) {
contextMenu.defaultSection().appendItem(i18nString(UIStrings.close), close.bind(this));
contextMenu.defaultSection().appendItem(i18nString(UIStrings.closeOthers), closeOthers.bind(this));
contextMenu.defaultSection().appendItem(i18nString(UIStrings.closeTabsToTheRight), closeToTheRight.bind(this));
contextMenu.defaultSection().appendItem(i18nString(UIStrings.closeAll), closeAll.bind(this));
}
if (this._delegate) {
this._delegate.onContextMenu(this.id, contextMenu);
}
contextMenu.show();
}
_startTabDragging(ev) {
const event = ev;
if (this.isCloseIconClicked(event.target)) {
return false;
}
this._dragStartX = event.pageX;
if (this._tabElement) {
this._tabElement.classList.add('dragging');
}
this._tabbedPane._tabSlider.remove();
return true;
}
_tabDragging(ev) {
const event = ev;
const tabElements = this._tabbedPane._tabsElement.childNodes;
for (let i = 0; i < tabElements.length; ++i) {
let tabElement = tabElements[i];
if (!this._tabElement || tabElement === this._tabElement) {
continue;
}
const intersects = tabElement.offsetLeft + tabElement.clientWidth > this._tabElement.offsetLeft &&
this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElement.offsetLeft;
if (!intersects) {
continue;
}
const dragStartX = this._dragStartX;
if (Math.abs(event.pageX - dragStartX) < tabElement.clientWidth / 2 + 5) {
break;
}
if (event.pageX - dragStartX > 0) {
tabElement = tabElement.nextSibling;
++i;
}
const oldOffsetLeft = this._tabElement.offsetLeft;
this._tabbedPane._insertBefore(this, i);
this._dragStartX = dragStartX + this._tabElement.offsetLeft - oldOffsetLeft;
break;
}
const dragStartX = this._dragStartX;
const tabElement = this._tabElement;
if (!tabElement.previousSibling && event.pageX - dragStartX < 0) {
tabElement.style.setProperty('left', '0px');
return;
}
if (!tabElement.nextSibling && event.pageX - dragStartX > 0) {
tabElement.style.setProperty('left', '0px');
return;
}
tabElement.style.setProperty('left', (event.pageX - dragStartX) + 'px');
}
_endTabDragging(_event) {
const tabElement = this._tabElement;
tabElement.classList.remove('dragging');
tabElement.style.removeProperty('left');
delete this._dragStartX;
this._tabbedPane._updateTabSlider();
}
}
const tabIcons = new WeakMap();