@deephaven/golden-layout
Version:
A multi-screen javascript Layout manager
302 lines (281 loc) • 11.5 kB
JavaScript
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