UNPKG

@deephaven/golden-layout

Version:

A multi-screen javascript Layout manager

737 lines (682 loc) • 33.9 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 { EventEmitter } from "../utils/index.js"; import HeaderButton from "./HeaderButton.js"; import Tab from "./Tab.js"; /** * This class represents a header above a Stack ContentItem. * * @param layoutManager * @param parent */ export default class Header extends EventEmitter { // use for scroll repeat // mouse hold timeout to act as hold instead of click // mouse hold acceleration constructor(layoutManager, parent) { var _this$layoutManager$c, _this$layoutManager$c2; super(); _defineProperty(this, "layoutManager", void 0); _defineProperty(this, "element", $(Header._template)); _defineProperty(this, "tabsContainer", void 0); _defineProperty(this, "tabDropdownContainer", void 0); _defineProperty(this, "tabDropdownSearch", void 0); _defineProperty(this, "tabDropdownList", null); _defineProperty(this, "controlsContainer", void 0); _defineProperty(this, "parent", void 0); _defineProperty(this, "tabs", []); _defineProperty(this, "activeContentItem", null); _defineProperty(this, "closeButton", null); _defineProperty(this, "tabDropdownButton", null); _defineProperty(this, "tabNextButton", $(Header._nextButtonTemplate)); _defineProperty(this, "tabPreviousButton", $(Header._previousButtonTemplate)); _defineProperty(this, "holdTimer", null); _defineProperty(this, "rAF", null); _defineProperty(this, "CLICK_TIMEOUT", 500); _defineProperty(this, "START_SPEED", 0.01); _defineProperty(this, "ACCELERATION", 0.0005); _defineProperty(this, "PADDING", 10); _defineProperty(this, "SCROLL_LEFT", 'left'); _defineProperty(this, "SCROLL_RIGHT", 'right'); _defineProperty(this, "isDraggingTab", false); _defineProperty(this, "isOverflowing", false); _defineProperty(this, "isDropdownShown", false); _defineProperty(this, "dropdownKeyIndex", 0); _defineProperty(this, "_lastVisibleTabIndex", -1); _defineProperty(this, "_tabControlOffset", void 0); this.layoutManager = layoutManager; if ((_this$layoutManager$c = this.layoutManager.config.settings) !== null && _this$layoutManager$c !== void 0 && _this$layoutManager$c.selectionEnabled) { this.element.addClass('lm_selectable'); this.element.on('click', this._onHeaderClick.bind(this)); } this.tabsContainer = this.element.find('.lm_tabs'); this.tabDropdownContainer = this.element.find('.lm_tabdropdown_list'); this.tabDropdownContainer.hide(); this.tabDropdownSearch = this.element.find('.lm_tabdropdown_search input'); this._handleFilterInput = this._handleFilterInput.bind(this); this._handleFilterKeydown = this._handleFilterKeydown.bind(this); this.tabDropdownSearch.on('input', this._handleFilterInput); this.tabDropdownSearch.on('keydown', this._handleFilterKeydown); this.controlsContainer = this.element.find('.lm_controls'); this.parent = parent; this.parent.on('resize', this._updateTabSizes, this); this._handleItemPickedUp = this._handleItemPickedUp.bind(this); this._handleItemDropped = this._handleItemDropped.bind(this); this._handleNextMouseEnter = this._handleNextMouseEnter.bind(this); this._handleNextMouseLeave = this._handleNextMouseLeave.bind(this); this._handlePreviousMouseEnter = this._handlePreviousMouseEnter.bind(this); this._handlePreviousMouseLeave = this._handlePreviousMouseLeave.bind(this); this._handleScrollRepeat = this._handleScrollRepeat.bind(this); this._handleNextButtonMouseDown = this._handleNextButtonMouseDown.bind(this); this._handlePreviousButtonMouseDown = this._handlePreviousButtonMouseDown.bind(this); this._handleScrollButtonMouseDown = this._handleScrollButtonMouseDown.bind(this); this._handleScrollButtonMouseUp = this._handleScrollButtonMouseUp.bind(this); this._handleScrollEvent = this._handleScrollEvent.bind(this); this.tabNextButton.on('mousedown', this._handleNextButtonMouseDown); this.tabPreviousButton.on('mousedown', this._handlePreviousButtonMouseDown); this.tabsContainer.on('scroll', this._handleScrollEvent); this.layoutManager.on('itemPickedUp', this._handleItemPickedUp); this.layoutManager.on('itemDropped', this._handleItemDropped); // append previous button template this.tabsContainer.before(this.tabPreviousButton); // change reference to just the li, not the wrapping ul this.tabPreviousButton = this.tabPreviousButton.find('>:first-child'); this.tabPreviousButton.hide(); this._showAdditionalTabsDropdown = this._showAdditionalTabsDropdown.bind(this); this._hideAdditionalTabsDropdown = this._hideAdditionalTabsDropdown.bind(this); this._tabControlOffset = (_this$layoutManager$c2 = this.layoutManager.config.settings) === null || _this$layoutManager$c2 === void 0 ? void 0 : _this$layoutManager$c2.tabControlOffset; this._createControls(); } /** * Creates a new tab and associates it with a contentItem * * @param contentItem * @param index The position of the tab */ createTab(contentItem, index) { //If there's already a tab relating to the //content item, don't do anything for (var i = 0; i < this.tabs.length; i++) { if (this.tabs[i].contentItem === contentItem) { return; } } var tab = new Tab(this, contentItem); if (this.tabs.length === 0) { this.tabs.push(tab); this.tabsContainer.append(tab.element); return; } if (index === undefined) { index = this.tabs.length; } if (index > 0) { this.tabs[index - 1].element.after(tab.element); } else { this.tabs[0].element.before(tab.element); } this.tabs.splice(index, 0, tab); this._updateTabSizes(); } /** * Finds a tab based on the contentItem its associated with and removes it. * * @param contentItem */ removeTab(contentItem) { for (var i = 0; i < this.tabs.length; i++) { if (this.tabs[i].contentItem === contentItem) { this.tabs[i]._$destroy(); this.tabs.splice(i, 1); return; } } throw new Error('contentItem is not controlled by this header'); } /** * The programmatical equivalent of clicking a Tab. * * @param contentItem */ setActiveContentItem(contentItem) { var _this$tabs$element$ge, _this$tabs$element$ge2, _this$parent$config$a; var isActive; for (var i = 0; i < this.tabs.length; i++) { isActive = this.tabs[i].contentItem === contentItem; this.tabs[i].setActive(isActive); if (isActive === true) { this.activeContentItem = contentItem; this.parent.config.activeItemIndex = i; } } // makes sure dropped tabs are scrollintoview, removed any re-ordering (_this$tabs$element$ge = this.tabs[(_this$parent$config$a = this.parent.config.activeItemIndex) !== null && _this$parent$config$a !== void 0 ? _this$parent$config$a : 0].element.get(0)) === null || _this$tabs$element$ge === void 0 ? void 0 : (_this$tabs$element$ge2 = _this$tabs$element$ge.scrollIntoView) === null || _this$tabs$element$ge2 === void 0 ? void 0 : _this$tabs$element$ge2.call(_this$tabs$element$ge, { // Optional chain scrollIntoView call so tests do not error inline: 'nearest' }); this._hideAdditionalTabsDropdown(); this._updateTabSizes(); this.parent.emitBubblingEvent('stateChanged'); } /** * Programmatically operate with header position. * * @param position one of ('top','left','right','bottom') to set or empty to get it. * * @returns previous header position */ position(position) { var previous = this.parent._header.show; if (previous && !this.parent._side) previous = 'top'; if (position !== undefined && this.parent._header.show !== position) { this.parent._header.show = position; this.parent._setupHeaderPosition(); } return previous; } // Manually attaching so wheel can be passive instead of jquery .on // _attachWheelListener is called by parent init _attachWheelListener() { var _this$tabsContainer$g; (_this$tabsContainer$g = this.tabsContainer.get(0)) === null || _this$tabsContainer$g === void 0 ? void 0 : _this$tabsContainer$g.addEventListener('wheel', this._handleWheelEvent, { passive: true }); } // detach called by this.destroy _detachWheelListener() { var _this$tabsContainer$g2; (_this$tabsContainer$g2 = this.tabsContainer.get(0)) === null || _this$tabsContainer$g2 === void 0 ? void 0 : _this$tabsContainer$g2.removeEventListener('wheel', this._handleWheelEvent); } _handleWheelEvent(event) { var target = event.currentTarget; if (!(target instanceof HTMLElement)) { return; } // we only care about the larger of the two deltas var delta = Math.abs(event.deltaY) > Math.abs(event.deltaX) ? event.deltaY : event.deltaX; // jshint /* globals WheelEvent */ if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) { var _this$tabsContainer$i; // Users can set OS to be in deltaMode page // scrolly by page units as pixels delta *= (_this$tabsContainer$i = this.tabsContainer.innerWidth()) !== null && _this$tabsContainer$i !== void 0 ? _this$tabsContainer$i : 1; } else if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) { // chrome goes 100px per 3 lines, and firefox would go 102 per 3 (17 lineheight * 3 lines * 2) delta *= 100 / 3; } target.scrollLeft += Math.round(delta); } // on scroll we need to check if side error might need to be disabled _handleScrollEvent() { this._checkScrollArrows(); } // when and item is picked up, attach mouse enter listeners to next/previous buttons _handleItemPickedUp() { this.isDraggingTab = true; if (this.isDropdownShown) this._hideAdditionalTabsDropdown(); this.controlsContainer.on('mouseenter', this._handleNextMouseEnter); this.tabPreviousButton.on('mouseenter', this._handlePreviousMouseEnter); } // when an item is dropped remove listeners and cancel animation _handleItemDropped() { var _this$rAF; this.isDraggingTab = false; window.cancelAnimationFrame((_this$rAF = this.rAF) !== null && _this$rAF !== void 0 ? _this$rAF : -1); this.rAF = null; this.controlsContainer.off('mouseenter', this._handleNextMouseEnter); this.controlsContainer.off('mouseleave', this._handleNextMouseLeave); this.tabPreviousButton.off('mouseenter', this._handlePreviousMouseEnter); this.tabPreviousButton.off('mouseleave', this._handlePreviousMouseLeave); } // on next/previous enter start scroll repeat animation loop // and attach a listener looking for mouseleave // cancel animation on mouse leave, and remove leave listener _handleNextMouseEnter() { var _this$tabsContainer$s; this.controlsContainer.on('mouseleave', this._handleNextMouseLeave); this._handleScrollRepeat(this.SCROLL_RIGHT, (_this$tabsContainer$s = this.tabsContainer.scrollLeft()) !== null && _this$tabsContainer$s !== void 0 ? _this$tabsContainer$s : 0); } _handlePreviousMouseEnter() { var _this$tabsContainer$s2; this.tabPreviousButton.on('mouseleave', this._handlePreviousMouseLeave); this._handleScrollRepeat(this.SCROLL_LEFT, (_this$tabsContainer$s2 = this.tabsContainer.scrollLeft()) !== null && _this$tabsContainer$s2 !== void 0 ? _this$tabsContainer$s2 : 0); } _handleNextMouseLeave() { var _this$rAF2; window.cancelAnimationFrame((_this$rAF2 = this.rAF) !== null && _this$rAF2 !== void 0 ? _this$rAF2 : -1); this.rAF = null; this.controlsContainer.off('mouseleave', this._handleNextMouseLeave); } _handlePreviousMouseLeave() { var _this$rAF3; window.cancelAnimationFrame((_this$rAF3 = this.rAF) !== null && _this$rAF3 !== void 0 ? _this$rAF3 : -1); this.rAF = null; this.tabPreviousButton.off('mouseleave', this._handlePreviousMouseLeave); } // scroll one tab to the right on mouse down // start scrollRepeat if mouse is held down _handleNextButtonMouseDown() { var rightOffscreenChild; for (var i = 0; i < this.tabs.length; i += 1) { var _this$tabs$i$element$, _this$tabs$i$element$2, _this$tabsContainer$g3, _this$tabsContainer$g4, _this$tabsContainer$s3; if (((_this$tabs$i$element$ = (_this$tabs$i$element$2 = this.tabs[i].element.get(0)) === null || _this$tabs$i$element$2 === void 0 ? void 0 : _this$tabs$i$element$2.offsetLeft) !== null && _this$tabs$i$element$ !== void 0 ? _this$tabs$i$element$ : 0) > ((_this$tabsContainer$g3 = (_this$tabsContainer$g4 = this.tabsContainer.get(0)) === null || _this$tabsContainer$g4 === void 0 ? void 0 : _this$tabsContainer$g4.offsetWidth) !== null && _this$tabsContainer$g3 !== void 0 ? _this$tabsContainer$g3 : 0) + ((_this$tabsContainer$s3 = this.tabsContainer.scrollLeft()) !== null && _this$tabsContainer$s3 !== void 0 ? _this$tabsContainer$s3 : 0)) { rightOffscreenChild = this.tabs[i].element.get(0); break; } } if (rightOffscreenChild) { rightOffscreenChild.scrollIntoView({ behavior: 'smooth', inline: 'nearest' }); this._handleScrollButtonMouseDown(this.SCROLL_RIGHT); } else { var tabsContainer = this.tabsContainer.get(0); if (tabsContainer) { var _this$tabsContainer$g5, _this$tabsContainer$g6; tabsContainer.scrollLeft = (_this$tabsContainer$g5 = (_this$tabsContainer$g6 = this.tabsContainer.get(0)) === null || _this$tabsContainer$g6 === void 0 ? void 0 : _this$tabsContainer$g6.scrollWidth) !== null && _this$tabsContainer$g5 !== void 0 ? _this$tabsContainer$g5 : 0; } } } // scroll left by one tab // start scrollRepeat if mouse is held down _handlePreviousButtonMouseDown() { var leftOffscreenChild; for (var i = this.tabs.length - 1; i >= 0; i -= 1) { var _this$tabs$i$element$3, _this$tabs$i$element$4, _this$tabsContainer$s4; if (((_this$tabs$i$element$3 = (_this$tabs$i$element$4 = this.tabs[i].element.get(0)) === null || _this$tabs$i$element$4 === void 0 ? void 0 : _this$tabs$i$element$4.offsetLeft) !== null && _this$tabs$i$element$3 !== void 0 ? _this$tabs$i$element$3 : 0) < ((_this$tabsContainer$s4 = this.tabsContainer.scrollLeft()) !== null && _this$tabsContainer$s4 !== void 0 ? _this$tabsContainer$s4 : 0)) { leftOffscreenChild = this.tabs[i].element.get(0); break; } } if (leftOffscreenChild) { leftOffscreenChild.scrollIntoView({ behavior: 'smooth', inline: 'start' }); this._handleScrollButtonMouseDown(this.SCROLL_LEFT); } else { var tabsContainer = this.tabsContainer.get(0); if (tabsContainer) { tabsContainer.scrollLeft = 0; } } } // when hold timer is reached start scroll repeat anim loop // cancel it when mouse up happens anywhere _handleScrollButtonMouseDown(direction) { // closure so that scrollLeft is value at end of timer, and not start of timer this.holdTimer = window.setTimeout(() => { var _this$tabsContainer$s5; this._handleScrollRepeat(direction, (_this$tabsContainer$s5 = this.tabsContainer.scrollLeft()) !== null && _this$tabsContainer$s5 !== void 0 ? _this$tabsContainer$s5 : 0, 100); // kickstart deltaX to be faster }, this.CLICK_TIMEOUT); window.addEventListener('mouseup', this._handleScrollButtonMouseUp); } // cancel scroll repeat _handleScrollButtonMouseUp() { var _this$holdTimer, _this$rAF4; clearTimeout((_this$holdTimer = this.holdTimer) !== null && _this$holdTimer !== void 0 ? _this$holdTimer : -1); this.holdTimer = null; cancelAnimationFrame((_this$rAF4 = this.rAF) !== null && _this$rAF4 !== void 0 ? _this$rAF4 : -1); this.rAF = null; window.removeEventListener('mouseup', this._handleScrollButtonMouseUp); } // disables scroll arrow if at edge of scroll area _checkScrollArrows() { var _this$tabsContainer$s6, _this$tabsContainer$i2, _this$tabsContainer$g7; if (this.tabsContainer.scrollLeft() === 0) { this.tabPreviousButton.first().prop('disabled', true); } else if (((_this$tabsContainer$s6 = this.tabsContainer.scrollLeft()) !== null && _this$tabsContainer$s6 !== void 0 ? _this$tabsContainer$s6 : 0) + ((_this$tabsContainer$i2 = this.tabsContainer.innerWidth()) !== null && _this$tabsContainer$i2 !== void 0 ? _this$tabsContainer$i2 : 0) === ((_this$tabsContainer$g7 = this.tabsContainer.get(0)) === null || _this$tabsContainer$g7 === void 0 ? void 0 : _this$tabsContainer$g7.scrollWidth)) { this.tabNextButton.prop('disabled', true); } else { this.tabNextButton.prop('disabled', false); this.tabPreviousButton.first().prop('disabled', false); } } // scrolls the tab header container on drag tab over control buttons // or on press and hold of scroll arrows at either end // called recurisevly in an animation loop until cancelled // or directional end is reached _handleScrollRepeat(direction, startX) { var _this$rAF5; var deltaX = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; var prevTimestamp = arguments.length > 3 ? arguments[3] : undefined; var tabContainer = this.tabsContainer.get(0); if (!tabContainer) { return; } var tabContainerRect = tabContainer.getBoundingClientRect(); if (direction === this.SCROLL_LEFT) { this.tabsContainer.scrollLeft(startX - deltaX); if (this.isDraggingTab) { // update drag placeholder this.parent._highlightHeaderDropZone(tabContainerRect.left - 1); } // stop loop at left edge if (this.tabsContainer.scrollLeft() === 0) { this._checkScrollArrows(); return; } } else if (direction === this.SCROLL_RIGHT) { var _this$tabsContainer$s7, _this$tabsContainer$i3; this.tabsContainer.scrollLeft(startX + deltaX); if (this.isDraggingTab) { // update drag placeholder this.parent._highlightHeaderDropZone(tabContainerRect.right + 1); } // stop loop at right edge if (((_this$tabsContainer$s7 = this.tabsContainer.scrollLeft()) !== null && _this$tabsContainer$s7 !== void 0 ? _this$tabsContainer$s7 : 0) + ((_this$tabsContainer$i3 = this.tabsContainer.innerWidth()) !== null && _this$tabsContainer$i3 !== void 0 ? _this$tabsContainer$i3 : 0) === tabContainer.scrollWidth) { this._checkScrollArrows(); return; } } // setup animation loop, scroll with acceleration window.cancelAnimationFrame((_this$rAF5 = this.rAF) !== null && _this$rAF5 !== void 0 ? _this$rAF5 : -1); this.rAF = window.requestAnimationFrame(timestamp => { var _this$tabsContainer$g8, _this$tabsContainer$g9; var startTime = prevTimestamp || timestamp; var deltaTime = timestamp - startTime; var newDeltaX = this.START_SPEED * deltaTime + 0.5 * this.ACCELERATION * (deltaTime * deltaTime); newDeltaX = Math.min(newDeltaX, (_this$tabsContainer$g8 = (_this$tabsContainer$g9 = this.tabsContainer.get(0)) === null || _this$tabsContainer$g9 === void 0 ? void 0 : _this$tabsContainer$g9.scrollWidth) !== null && _this$tabsContainer$g8 !== void 0 ? _this$tabsContainer$g8 : Infinity); this._handleScrollRepeat(direction, startX, newDeltaX, startTime); }); } /** * Programmatically set closability. * @param isClosable Whether to enable/disable closability. * @returns Whether the action was successful */ _$setClosable(isClosable) { if (this.closeButton && this._isClosable()) { this.closeButton.element[isClosable ? 'show' : 'hide'](); return true; } return false; } /** * Destroys the entire header */ _$destroy() { this.emit('destroy', this); for (var i = 0; i < this.tabs.length; i++) { this.tabs[i]._$destroy(); } this._detachWheelListener(); this._handleItemDropped(); $(document).off('mouseup', this._hideAdditionalTabsDropdown); this.tabDropdownSearch.off('input', this._handleFilterInput); this.tabDropdownSearch.off('keydown', this._handleFilterKeydown); this.element.remove(); } /** * get settings from header * * @returns when exists */ _getHeaderSetting(name) { if (name in this.parent._header) return this.parent._header[name]; } /** * Creates the popout, maximise and close buttons in the header's top right corner */ _createControls() { var closeStack, popout, label, maximise, tabDropdownLabel, tabOverflowNextLabel, tabOverflowPreviousLabel; /** * Dropdown to show additional tabs. */ tabDropdownLabel = this.layoutManager.config.labels.tabDropdown; tabOverflowNextLabel = this.layoutManager.config.labels.tabNextLabel; tabOverflowPreviousLabel = this.layoutManager.config.labels.tabPreviousLabel; this.tabDropdownButton = new HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', this._showAdditionalTabsDropdown); this.tabDropdownButton.element.hide(); this.controlsContainer.prepend(this.tabNextButton); this.tabNextButton.hide(); /** * Popout control to launch component in new window. */ if (this._getHeaderSetting('popout')) { popout = this._onPopoutClick.bind(this); label = this._getHeaderSetting('popout'); new HeaderButton(this, label, 'lm_popout', popout); } /** * Maximise control - set the component to the full size of the layout */ if (this._getHeaderSetting('maximise')) { maximise = this.parent.toggleMaximise.bind(this.parent); var maximiseLabel = this._getHeaderSetting('maximise'); var minimiseLabel = this._getHeaderSetting('minimise'); var maximiseButton = new HeaderButton(this, maximiseLabel, 'lm_maximise', maximise); this.parent.on('maximised', function () { maximiseButton.element.attr('title', minimiseLabel !== null && minimiseLabel !== void 0 ? minimiseLabel : ''); }); this.parent.on('minimised', function () { maximiseButton.element.attr('title', maximiseLabel !== null && maximiseLabel !== void 0 ? maximiseLabel : ''); }); } /** * Close button */ if (this._isClosable()) { closeStack = this.parent.remove.bind(this.parent); label = this._getHeaderSetting('close'); this.closeButton = new HeaderButton(this, label, 'lm_close', closeStack); } } /** * Shows drop down for additional tabs when there are too many to display. * * @returns {void} */ _showAdditionalTabsDropdown() { if (this.isDropdownShown) { this._hideAdditionalTabsDropdown(); return; } // clone tabs in the current list, with event listeners // and add them to the drop down this.tabDropdownList = this.tabsContainer.clone(true).appendTo(this.tabDropdownContainer).children().removeClass('lm_active'); // move it to be absolutely positioned in document root // as header has overflow hidden, and we need to escape that $(document.body).append(this.tabDropdownContainer); // show the dropdown this.tabDropdownContainer.show(); this.isDropdownShown = true; // dropdown is a part of the header z-index context // add class to header when dropdown is open // so we can bump the z-index of the lists parent this.element.addClass('lm_dropdown_open'); // focus the dropdown filter list input this.tabDropdownSearch.val('').focus(); this.dropdownKeyIndex = 0; this.tabDropdownList.eq(this.dropdownKeyIndex).addClass('lm_keyboard_active'); $(document).on('mousedown', this._hideAdditionalTabsDropdown); this._updateAdditionalTabsDropdown(); } // enables synthetic keyboard navigation of the list _handleFilterKeydown(e) { if (e.key === 'Escape') { this._hideAdditionalTabsDropdown(); return; } // a negative dropdownKeyIndex means the list is filtered to an empty state if (this.dropdownKeyIndex === -1) return; if (e.key === 'Enter' || e.key === ' ') { // simulate "click" this._hideAdditionalTabsDropdown(); this.tabs[this.dropdownKeyIndex]._onTabClick(); return; } function getNextDropdownIndex(startIndex, delta, tabDropdownList) { if (tabDropdownList == null || tabDropdownList.length < 2) { return -1; } var i = (startIndex + delta + tabDropdownList.length) % tabDropdownList.length; while (i !== startIndex) { if (tabDropdownList.eq(i).css('display') !== 'none') { return i; } i = (i + delta + tabDropdownList.length) % tabDropdownList.length; } return startIndex; } // allow tab or arrow key navigation of list, prevent tabs default behaviour if (e.key === 'ArrowDown' || e.key === 'Tab' && e.shiftKey === false) { var _this$tabDropdownList, _this$tabDropdownList2; e.preventDefault(); (_this$tabDropdownList = this.tabDropdownList) === null || _this$tabDropdownList === void 0 ? void 0 : _this$tabDropdownList.eq(this.dropdownKeyIndex).removeClass('lm_keyboard_active'); this.dropdownKeyIndex = getNextDropdownIndex(this.dropdownKeyIndex, 1, this.tabDropdownList); (_this$tabDropdownList2 = this.tabDropdownList) === null || _this$tabDropdownList2 === void 0 ? void 0 : _this$tabDropdownList2.eq(this.dropdownKeyIndex).addClass('lm_keyboard_active'); } else if (e.key === 'ArrowUp' || e.key === 'Tab') { var _this$tabDropdownList3, _this$tabDropdownList4; e.preventDefault(); (_this$tabDropdownList3 = this.tabDropdownList) === null || _this$tabDropdownList3 === void 0 ? void 0 : _this$tabDropdownList3.eq(this.dropdownKeyIndex).removeClass('lm_keyboard_active'); this.dropdownKeyIndex = getNextDropdownIndex(this.dropdownKeyIndex, -1, this.tabDropdownList); (_this$tabDropdownList4 = this.tabDropdownList) === null || _this$tabDropdownList4 === void 0 ? void 0 : _this$tabDropdownList4.eq(this.dropdownKeyIndex).addClass('lm_keyboard_active'); } } // filters the list _handleFilterInput(event) { if (this.tabDropdownList == null) { return; } // reset keyboard index this.tabDropdownList.eq(this.dropdownKeyIndex).removeClass('lm_keyboard_active'); this.dropdownKeyIndex = -1; for (var i = 0; i < this.tabDropdownList.length; i++) { var _this$tabs$i$contentI, _this$tabs$i$contentI2; if (((_this$tabs$i$contentI = (_this$tabs$i$contentI2 = this.tabs[i].contentItem.config.title) === null || _this$tabs$i$contentI2 === void 0 ? void 0 : _this$tabs$i$contentI2.toLowerCase().indexOf(event.target.value.toLowerCase())) !== null && _this$tabs$i$contentI !== void 0 ? _this$tabs$i$contentI : -1) !== -1) { this.tabDropdownList.eq(i).css('display', ''); if (this.dropdownKeyIndex === -1) this.dropdownKeyIndex = i; } else { this.tabDropdownList.eq(i).css('display', 'none'); } } if (this.dropdownKeyIndex !== -1) { this.tabDropdownList.eq(this.dropdownKeyIndex).addClass('lm_keyboard_active'); } } /** * Hides drop down for additional tabs when needed. It is called via mousedown * event on document when list is open, or programmatically when drag starts, * or active tab changes etc. */ _hideAdditionalTabsDropdown(event) { var _this$tabDropdownCont, _this$tabDropdownButt; // dropdown already closed, do nothing if (!this.isDropdownShown) return; if (event && (_this$tabDropdownCont = this.tabDropdownContainer.get(0)) !== null && _this$tabDropdownCont !== void 0 && _this$tabDropdownCont.contains(event.target)) { // prevent events occuring inside the list from causing a close return; } else if (event && ((_this$tabDropdownButt = this.tabDropdownButton) === null || _this$tabDropdownButt === void 0 ? void 0 : _this$tabDropdownButt.element.get(0)) === event.target) { // do nothing on the mouse down so that the click event can close, rather then re-open return; } this.element.removeClass('lm_dropdown_open'); this.tabDropdownContainer.hide(); this.isDropdownShown = false; // put it back in the header dom to keep the root tidy this.element.append(this.tabDropdownContainer); // remove the current tab list this.tabDropdownContainer.find('.lm_tabs').remove(); $(document).off('mousedown', this._hideAdditionalTabsDropdown); } /** * Ensures additional tab drop down which is absolutely positioned in the root * doesn't overflow the screen, and instead becomes scrollable. Positions the * floating menu in the correct location relative to the dropdown button. */ _updateAdditionalTabsDropdown() { var _this$tabDropdownCont2, _this$tabDropdownCont3, _$$height, _$$width; if (!this.isDropdownShown) return; if (!this.tabDropdownList || this.tabDropdownList.length == 0) return; var scrollHeight = (_this$tabDropdownCont2 = (_this$tabDropdownCont3 = this.tabDropdownContainer.get(0)) === null || _this$tabDropdownCont3 === void 0 ? void 0 : _this$tabDropdownCont3.scrollHeight) !== null && _this$tabDropdownCont2 !== void 0 ? _this$tabDropdownCont2 : 0; if (scrollHeight === 0) return; // height can be zero if called on a hidden or empty list var list_rect = this.tabDropdownContainer[0].getBoundingClientRect(); var windowHeight = (_$$height = $(window).height()) !== null && _$$height !== void 0 ? _$$height : 0; var windowWidth = (_$$width = $(window).width()) !== null && _$$width !== void 0 ? _$$width : 0; // position relative to the dropdown button if (!this.tabDropdownButton) return; var button_rect = this.tabDropdownButton.element[0].getBoundingClientRect(); // If it fits below button, put it there. If it doesn't and there is more // than 60% space above, then flip to displaying menu above button var hasSpaceBelow = scrollHeight < windowHeight - button_rect.bottom; var shouldFlip = !hasSpaceBelow && button_rect.bottom > windowHeight * 0.6; // 0.6 bias to not flipping rather than strictly 0.5 // keep from going of right edge // when flipped offset to right edge of button var left = Math.min(button_rect.left + (shouldFlip ? button_rect.width : 0), windowWidth - list_rect.width - this.PADDING); // flip, with limit of top of screen var top = shouldFlip ? Math.max(button_rect.top - scrollHeight, this.PADDING) : button_rect.bottom; // if needs scroll to fit, apply a max-height var maxHeight = windowHeight - top - this.PADDING; this.tabDropdownContainer.css({ 'max-height': maxHeight, top, left }); } /** * Checks whether the header is closable based on the parent config and * the global config. * * @returns Whether the header is closable. */ _isClosable() { return Boolean(this.parent.config.isClosable && this.layoutManager.config.settings.showCloseIcon); } _onPopoutClick() { if (this.layoutManager.config.settings.popoutWholeStack === true) { this.parent.popout(); } else { var _this$activeContentIt; (_this$activeContentIt = this.activeContentItem) === null || _this$activeContentIt === void 0 ? void 0 : _this$activeContentIt.popout(); } } /** * Invoked when the header's background is clicked (not it's tabs or controls) * * @param event */ _onHeaderClick(event) { if (event.target === this.element[0]) { this.parent.select(); } } /** * Pushes the tabs to the tab dropdown if the available space is not sufficient */ _updateTabSizes() { if (this.tabs.length === 0) { return; } var tabsContainer = this.tabsContainer.get(0); if (this.isDropdownShown) { // resize the dropdown list too as it is currently open this._updateAdditionalTabsDropdown(); } if (!tabsContainer) { return; } if (!this.isOverflowing && tabsContainer.scrollWidth > tabsContainer.clientWidth) { var _this$tabDropdownButt2; (_this$tabDropdownButt2 = this.tabDropdownButton) === null || _this$tabDropdownButt2 === void 0 ? void 0 : _this$tabDropdownButt2.element.show(); this.tabNextButton.show(); this.tabPreviousButton.show(); this.isOverflowing = true; } else if (this.isOverflowing && tabsContainer.scrollWidth <= tabsContainer.clientWidth) { var _this$tabDropdownButt3; (_this$tabDropdownButt3 = this.tabDropdownButton) === null || _this$tabDropdownButt3 === void 0 ? void 0 : _this$tabDropdownButt3.element.hide(); this.tabNextButton.hide(); this.tabPreviousButton.hide(); this.isOverflowing = false; } if (this.isOverflowing) this._checkScrollArrows(); } } _defineProperty(Header, "_template", ['<div class="lm_header">', '<ul class="lm_tabs"></ul>', '<ul class="lm_controls"></ul>', '<ul class="lm_tabdropdown_list">', '<li class="lm_tabdropdown_search"><input type="text" placeholder="Find tab..."></li>', '</ul>', '</div>'].join('')); _defineProperty(Header, "_previousButtonTemplate", ['<ul class="lm_controls">', '<li class="lm_tabpreviousbutton"></li>', '</ul>'].join('')); _defineProperty(Header, "_nextButtonTemplate", ['<li class="lm_tabnextbutton"></li>'].join('')); //# sourceMappingURL=Header.js.map