UNPKG

@deephaven/golden-layout

Version:

A multi-screen javascript Layout manager

302 lines (281 loc) • 11.5 kB
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } import $ from 'jquery'; import { isComponent } from "../items/index.js"; import { DragListener } from "../utils/index.js"; import DragProxy from "./DragProxy.js"; /** * Represents an individual tab within a Stack's header * * @param header * @param contentItem */ export default class Tab { constructor(header, contentItem) { var _this$_layoutManager$; _defineProperty(this, "header", void 0); _defineProperty(this, "contentItem", void 0); _defineProperty(this, "element", $(Tab._template)); _defineProperty(this, "titleElement", void 0); _defineProperty(this, "closeElement", void 0); _defineProperty(this, "isActive", false); _defineProperty(this, "_layoutManager", void 0); _defineProperty(this, "_dragListener", void 0); this.header = header; this.contentItem = contentItem; this.titleElement = this.element.find('.lm_title'); this.closeElement = this.element.find('.lm_close_tab'); this.closeElement[contentItem.config.isClosable ? 'show' : 'hide'](); this.setTitle(contentItem.config.title); this.contentItem.on('titleChanged', this.setTitle, this); this._layoutManager = this.contentItem.layoutManager; if ((_this$_layoutManager$ = this._layoutManager.config.settings) !== null && _this$_layoutManager$ !== void 0 && _this$_layoutManager$.reorderEnabled && contentItem.config.reorderEnabled) { this._dragListener = new DragListener(this.element, this._layoutManager.root); this._dragListener.on('dragStart', this._onDragStart, this); this.contentItem.on('destroy', this._dragListener.destroy, this._dragListener); } this._onTabClick = this._onTabClick.bind(this); this._onCloseClick = this._onCloseClick.bind(this); this._onTabContentFocusIn = this._onTabContentFocusIn.bind(this); this._onTabContentFocusOut = this._onTabContentFocusOut.bind(this); this.element.on('click', this._onTabClick); this.element.on('auxclick', this._onTabClick); this.element.on('mouseup', this._onMouseUp); if (this.contentItem.config.isClosable) { this.closeElement.on('click', this._onCloseClick); this.closeElement.on('mousedown', this._onCloseMousedown); } else { this.closeElement.remove(); } this.contentItem.tab = this; this.contentItem.emit('tab', this); this.contentItem.layoutManager.emit('tabCreated', this); if (isComponent(this.contentItem)) { // add focus class to tab when content this.contentItem.container._contentElement.on('focusin', this._onTabContentFocusIn).on('focusout', this._onTabContentFocusOut); this.contentItem.container.tab = this; this.contentItem.container.emit('tab', this); } } /** * Sets the tab's title to the provided string and sets * its title attribute to a pure text representation (without * html tags) of the same string. * @param title can contain html */ setTitle() { var title = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; // Disabling for illumon project, we want to manage our own tooltips // this.element.attr( 'title', lm.utils.stripTags( title ) ); this.titleElement.text(title); } /** * Updates the content item this tab is associated with. * Properly transfers event listeners from the old content item to the new one. * @param newContentItem The new content item */ setContentItem(newContentItem) { var oldContentItem = this.contentItem; // Transfer the 'destroy' listener if dragListener exists if (this._dragListener) { oldContentItem.off('destroy', this._dragListener.destroy, this._dragListener); newContentItem.on('destroy', this._dragListener.destroy, this._dragListener); } // Transfer 'titleChanged' listener oldContentItem.off('titleChanged', this.setTitle, this); newContentItem.on('titleChanged', this.setTitle, this); // Update the reference this.contentItem = newContentItem; newContentItem.tab = this; // Update title this.setTitle(newContentItem.config.title); } /** * Sets this tab's active state. To programmatically * switch tabs, use header.setActiveContentItem( item ) instead. * @param isActive */ setActive(isActive) { if (isActive === this.isActive) { return; } this.isActive = isActive; if (isActive) { this.element.addClass('lm_active'); } else { this.element.removeClass('lm_active'); } } /** * Destroys the tab * * @private * @returns {void} */ _$destroy() { this.element.off('click', this._onTabClick); this.element.off('auxclick', this._onTabClick); this.element.off('mouseup', this._onMouseUp); this.closeElement.off('click', this._onCloseClick); if (isComponent(this.contentItem)) { this.contentItem.container._contentElement.off(); } if (this._dragListener) { this.contentItem.off('destroy', this._dragListener.destroy, this._dragListener); this._dragListener.off('dragStart', this._onDragStart); this._dragListener = undefined; } this.element.remove(); } /** * Callback for the DragListener * * @param x The tabs absolute x position * @param y The tabs absolute y position */ _onDragStart(x, y) { var _this$contentItem$par; if ((_this$contentItem$par = this.contentItem.parent) !== null && _this$contentItem$par !== void 0 && _this$contentItem$par.isMaximised) { this.contentItem.parent.toggleMaximise(); } if (!this._dragListener) { return; } new DragProxy(x, y, this._dragListener, this._layoutManager, this.contentItem, this.header.parent); } /** * Callback when the contentItem is focused in */ _onTabContentFocusIn(event) { // If the focus originated from inside a nested LayoutManager (a // dashboard within a dashboard), let that nested layout's tab own // the focus indicator instead of this outer tab. The focusin event // bubbles, so without this check the outer tab would clear the // nested tab's "lm_focusin" class and claim focus for itself. if (isComponent(this.contentItem) && (event === null || event === void 0 ? void 0 : event.target) instanceof Element && this._isInNestedLayout(event.target)) { return; } // Ensure only one tab is marked as having focus at a time. // In Firefox, if the focused element is removed from the DOM, // the focusout event won't trigger. This can result in two tabs // being erroneously marked as focused if the user's DOM element // is removed and they click another tab. To prevent this, we // remove existing "lm_focusin" classes first. $('.lm_focusin', this._layoutManager.root.element).removeClass('lm_focusin'); this.element.addClass('lm_focusin'); } /** * Callback when the contentItem is focused out * * @param {jQuery DOM event} event * * @private * @returns {void} */ _onTabContentFocusOut() { if (isComponent(this.contentItem) && !this.contentItem.container._contentElement[0].contains(document.activeElement)) { this.element.removeClass('lm_focusin'); } } /** * Callback when the tab is clicked * * @param event */ _onTabClick(event) { // left mouse button or tap if (!event || event.button === 0) { var _this$element$get; var activeContentItem = this.header.parent.getActiveContentItem(); if (this.contentItem !== activeContentItem && isComponent(this.contentItem)) { this.header.parent.setActiveContentItem(this.contentItem); } else if (isComponent(this.contentItem) && !this._isFocusDirectlyInContainer()) { // if no focus inside put focus onto the container // so focusin always fires for tabclicks. Focus inside a nested // LayoutManager is not considered "inside" this container so // that clicking the outer tab transfers focus (and the // lm_focusin indicator) to it. this.contentItem.container._contentElement.focus(); } if (isComponent(this.contentItem)) { this.contentItem.container.emit('tabClicked', event); } // might have been called from the dropdown this.header._hideAdditionalTabsDropdown(); // makes sure clicked tabs scrollintoview (either those partially offscreen or in dropdown) (_this$element$get = this.element.get(0)) === null || _this$element$get === void 0 || _this$element$get.scrollIntoView({ inline: 'nearest' // behaviour smooth is not possible here, as when a tab becomes active it may attempt to take focus // which interupts any scroll behaviour from completeting }); // middle mouse button } else if (event.button === 1 && this.contentItem.config.isClosable) { this._onCloseClick(event); } } /** * Callback when the tab's close button is * clicked * * @param event */ _onCloseClick(event) { event.stopPropagation(); if (isComponent(this.contentItem)) { this.contentItem.container.close(); } else { this.header.parent.removeChild(this.contentItem); } } /** * Callback to prevent paste into active input on Linux * when closing a tab via middle click. * @param event */ _onMouseUp(event) { if (event.button === 1) { event.preventDefault(); // This seems to prevent the paste event from firing event.stopPropagation(); // Stop propagation just in case } } /** * Callback to capture tab close button mousedown * to prevent tab from activating. * * @param event */ _onCloseMousedown(event) { event.stopPropagation(); } /** * Returns true if the given element is inside this content item's * container but within a nested LayoutManager (i.e. a dashboard * embedded inside this tab's component). Such focus belongs to the * nested layout's tab, not this outer one. */ _isInNestedLayout(element) { var nestedLayoutRoot = $(element).closest('.lm_goldenlayout')[0]; var ownLayoutRoot = this._layoutManager.root.element[0]; return nestedLayoutRoot != null && nestedLayoutRoot !== ownLayoutRoot; } /** * Returns true if the document's active element is inside this tab's * container directly (not inside a nested LayoutManager). */ _isFocusDirectlyInContainer() { if (!isComponent(this.contentItem)) { return false; } var active = document.activeElement; if (active == null || !this.contentItem.container._contentElement[0].contains(active)) { return false; } return !this._isInNestedLayout(active); } } /** * The tab's html template */ _defineProperty(Tab, "_template", ['<li class="lm_tab">', '<span class="lm_title_before"></span>', '<span class="lm_title"></span>', '<div class="lm_close_tab" aria-label="Close tab"></div>', '</li>'].join('')); //# sourceMappingURL=Tab.js.map