UNPKG

dockview-core

Version:

Zero dependency layout manager supporting tabs, groups, grids and splitviews for vanilla TypeScript

1,101 lines 88.8 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Tabs = void 0; var dataTransfer_1 = require("../../../dnd/dataTransfer"); var dom_1 = require("../../../dom"); var events_1 = require("../../../events"); var lifecycle_1 = require("../../../lifecycle"); var scrollbar_1 = require("../../../scrollbar"); var pointerDragController_1 = require("../../../dnd/pointer/pointerDragController"); var events_2 = require("../../events"); var tab_1 = require("../tab/tab"); var tabGroups_1 = require("./tabGroups"); var Tabs = /** @class */ (function (_super) { __extends(Tabs, _super); function Tabs(group, accessor, options) { var _this = _super.call(this) || this; _this.group = group; _this.accessor = accessor; _this._observerDisposable = new lifecycle_1.MutableDisposable(); _this._scrollbar = null; _this._tabs = []; _this._tabMap = new Map(); _this.selectedIndex = -1; _this._showTabsOverflowControl = false; _this._direction = 'horizontal'; _this._animState = null; _this._pendingMarginCleanups = new Map(); _this._pendingCollapse = false; _this._flipTransitionCleanup = null; _this._voidContainer = null; _this._voidContainerListeners = null; _this._extendedDropZone = null; _this._pointerInsideTabsList = false; _this._onTabDragStart = new events_1.Emitter(); _this.onTabDragStart = _this._onTabDragStart.event; _this._onDrop = new events_1.Emitter(); _this.onDrop = _this._onDrop.event; _this._onWillShowOverlay = new events_1.Emitter(); _this.onWillShowOverlay = _this._onWillShowOverlay.event; _this._onOverflowTabsChange = new events_1.Emitter(); _this.onOverflowTabsChange = _this._onOverflowTabsChange.event; _this._tabsList = document.createElement('div'); _this._tabsList.className = 'dv-tabs-container'; _this.showTabsOverflowControl = options.showTabsOverflowControl; if (accessor.options.scrollbars === 'native') { _this._element = _this._tabsList; } else { _this._scrollbar = new scrollbar_1.Scrollbar(_this._tabsList); _this._scrollbar.orientation = _this.direction; _this._element = _this._scrollbar.element; _this.addDisposables(_this._scrollbar); } _this._tabGroupManager = new tabGroups_1.TabGroupManager({ group: _this.group, accessor: _this.accessor, tabsList: _this._tabsList, getTabs: function () { return _this._tabs; }, getTabMap: function () { return _this._tabMap; }, getDirection: function () { return _this._direction; }, }, { onChipContextMenu: function (tabGroup, event) { _this.accessor.contextMenuController.showForChip(tabGroup, _this.group, event); }, onChipDragStart: function (tabGroup, chip, event) { _this._handleChipDragStart(tabGroup, chip, event); }, onChipDragEnd: function () { // HTML5 chip dragend (incl. cancels). The Html5DragSource // owns the listener on the chip element, so this fires // even if the chip was detached cross-group — the // element keeps its listeners until the source is // disposed. resetDragAnimation is a no-op after a // successful drop (anim state already null) thanks to // the gating inside it. _this.resetDragAnimation(); }, onChipDrop: function (tabGroup, event) { _this._handleChipDrop(tabGroup, event); }, }); _this.addDisposables(_this._onOverflowTabsChange, _this._observerDisposable, _this._onWillShowOverlay, _this._onDrop, _this._onTabDragStart, { dispose: function () { var _a; (_a = _this._flipTransitionCleanup) === null || _a === void 0 ? void 0 : _a.call(_this); }, }, // Pointer-side cleanup: when any pointer drag ends, tear // down smooth-reorder anim state the dragover bridge may // have installed. The chip's pointer drag source handles // its own transfer payload + iframe-shield cleanup. pointerDragController_1.PointerDragController.getInstance().onDragEnd(function () { _this._pointerInsideTabsList = false; _this.resetDragAnimation(); }), // Pointer-event mirror of the HTML5 dragover / dragleave handlers // below. Drives smooth-reorder for `dndStrategy: 'pointer'` and // for touch drags in `'auto'`. pointerDragController_1.PointerDragController.getInstance().onDragMove(function (e) { _this._handlePointerDragMove(e.clientX, e.clientY); }), (0, events_1.addDisposableListener)(_this.element, 'pointerdown', function (event) { if (event.defaultPrevented) { return; } var isLeftClick = event.button === 0; if (isLeftClick) { _this.accessor.doSetGroupActive(_this.group); } }), // Trackpad / wheel forwarding. The strip scrolls along its own // axis (x for horizontal headers, y for vertical), so deltaY // from a plain mouse wheel maps onto the strip's axis too — // this gives the VS Code-style "scroll over tab bar to page // through tabs" feel. We only consume the event when the strip // is actually overflowing in the direction the user wheeled in, // so a wheel at the edge of a non-overflowing strip still // bubbles up and scrolls the page. `{ passive: false }` is // required because we call preventDefault(). (0, events_1.addDisposableListener)(_this._tabsList, 'wheel', function (event) { var isVertical = _this._direction === 'vertical'; var primary = isVertical ? event.deltaY || event.deltaX : event.deltaX || event.deltaY; if (primary === 0) { return; } var max = isVertical ? _this._tabsList.scrollHeight - _this._tabsList.clientHeight : _this._tabsList.scrollWidth - _this._tabsList.clientWidth; if (max <= 0) { return; } var current = isVertical ? _this._tabsList.scrollTop : _this._tabsList.scrollLeft; // At the edge in the wheel direction: let the page // scroll instead of trapping the gesture. if ((primary < 0 && current <= 0) || (primary > 0 && current >= max)) { return; } event.preventDefault(); // Custom-scrollbar mode wraps the tabs list and installs // its own wheel listener that rewrites scrollLeft from a // deltaY-only tracker. Without stopPropagation that // handler would clobber our deltaX-aware update. event.stopPropagation(); if (isVertical) { _this._tabsList.scrollTop = current + primary; } else { _this._tabsList.scrollLeft = current + primary; } }, { passive: false }), (0, events_1.addDisposableListener)(_this._tabsList, 'dragover', function (event) { if (_this._processDragOver(event.clientX)) { // Allow `drop` to fire on the tabs list container. event.preventDefault(); } }, true), (0, events_1.addDisposableListener)(_this._tabsList, 'dragleave', function (event) { _this._processDragLeave(event.relatedTarget); }, true), (0, events_1.addDisposableListener)(_this._tabsList, 'dragend', function () { _this.resetDragAnimation(); }), (0, events_1.addDisposableListener)(_this._tabsList, 'drop', function (event) { var _a, _b, _c; if (!_this._animState || _this._animState.currentInsertionIndex === null) { return; } // In non-smooth mode only handle group drags here; // individual tab drops are handled by tab Droptargets. if (((_a = _this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) !== 'smooth' && !_this._animState.sourceTabGroupId) { return; } event.stopPropagation(); event.preventDefault(); // The capturing stopPropagation above prevents the // individual tab's Droptarget.onDrop from firing, so // the anchor overlay won't be cleared by that path. // Clear it explicitly here before processing the drop. (_c = (_b = _this.group.model.dropTargetContainer) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.clear(); var animState = _this._animState; _this._animState = null; _this._pendingCollapse = false; // Handle group drag (entire group repositioned) if (animState.sourceTabGroupId) { _this._commitGroupMove(animState.sourceTabGroupId, animState.currentInsertionIndex); return; } var insertionIndex = animState.currentInsertionIndex; var sourceIndex = animState.sourceIndex; var adjustedIndex = insertionIndex - (sourceIndex !== -1 && sourceIndex < insertionIndex ? 1 : 0); var sourceCurrentGroup = _this.group.model.getTabGroupForPanel(animState.sourceTabId); if (adjustedIndex === sourceIndex && !animState.targetTabGroupId && !sourceCurrentGroup) { _this._uncollapsSourceTab(animState.sourceTabId); _this.resetTabTransforms(); return; } _this._uncollapsSourceTab(animState.sourceTabId); var firstPositions = _this.snapshotTabPositions(); _this.resetTabTransforms(); _this._onDrop.fire({ event: event, index: adjustedIndex, targetTabGroupId: animState.targetTabGroupId, }); _this.runFlipAnimation(firstPositions, animState.sourceTabId, animState.sourceIndex === -1, { from: Math.min(sourceIndex, adjustedIndex), to: Math.max(sourceIndex, adjustedIndex), }); }, true), lifecycle_1.Disposable.from(function () { var e_1, _a; var _b; (_b = _this._voidContainerListeners) === null || _b === void 0 ? void 0 : _b.dispose(); _this.resetDragAnimation(); _this._tabGroupManager.disposeAll(); try { for (var _c = __values(_this._tabs), _d = _c.next(); !_d.done; _d = _c.next()) { var _e = _d.value, value = _e.value, disposable = _e.disposable; disposable.dispose(); value.dispose(); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_1) throw e_1.error; } } _this._tabs = []; _this._tabMap.clear(); })); return _this; } Object.defineProperty(Tabs.prototype, "showTabsOverflowControl", { get: function () { return this._showTabsOverflowControl; }, set: function (value) { var _this = this; if (this._showTabsOverflowControl == value) { return; } this._showTabsOverflowControl = value; if (value) { var observer = new dom_1.OverflowObserver(this._tabsList); this._observerDisposable.value = new lifecycle_1.CompositeDisposable(observer, observer.onDidChange(function (event) { var hasOverflow = event.hasScrollX || event.hasScrollY; _this.toggleDropdown({ reset: !hasOverflow }); if (_this._tabGroupManager.groupUnderlines.size > 0) { _this._tabGroupManager.positionUnderlines(); } }), (0, events_1.addDisposableListener)(this._tabsList, 'scroll', function () { _this.toggleDropdown({ reset: false }); if (_this._tabGroupManager.groupUnderlines.size > 0) { _this._tabGroupManager.positionUnderlines(); } })); } }, enumerable: false, configurable: true }); Object.defineProperty(Tabs.prototype, "element", { get: function () { return this._element; }, enumerable: false, configurable: true }); Object.defineProperty(Tabs.prototype, "voidContainer", { set: function (el) { var _this = this; var _a; (_a = this._voidContainerListeners) === null || _a === void 0 ? void 0 : _a.dispose(); this._voidContainerListeners = null; this._voidContainer = el; if (el) { this._voidContainerListeners = new lifecycle_1.CompositeDisposable((0, events_1.addDisposableListener)(el, 'dragover', function (event) { if (_this._animState) { event.preventDefault(); } }), (0, events_1.addDisposableListener)(el, 'drop', function (event) { var _a; if (((_a = _this._animState) === null || _a === void 0 ? void 0 : _a.sourceTabGroupId) && _this._animState.currentInsertionIndex !== null) { event.preventDefault(); event.stopPropagation(); _this.handleVoidDrop(); } })); } }, enumerable: false, configurable: true }); /** * Handle a drop that occurred on the void container (empty header * space to the right of the tabs). Returns `true` if the drop was * consumed by an active group drag, `false` otherwise. */ Tabs.prototype.handleVoidDrop = function () { var _a, _b; if (!((_a = this._animState) === null || _a === void 0 ? void 0 : _a.sourceTabGroupId)) { return false; } var sourceTabGroupId = this._animState.sourceTabGroupId; var insertionIndex = (_b = this._animState.currentInsertionIndex) !== null && _b !== void 0 ? _b : this._tabs.length; this._animState = null; this._commitGroupMove(sourceTabGroupId, insertionIndex); return true; }; Object.defineProperty(Tabs.prototype, "panels", { get: function () { return this._tabs.map(function (_) { return _.value.panel.id; }); }, enumerable: false, configurable: true }); Object.defineProperty(Tabs.prototype, "size", { get: function () { return this._tabs.length; }, enumerable: false, configurable: true }); Object.defineProperty(Tabs.prototype, "tabs", { get: function () { return this._tabs.map(function (_) { return _.value; }); }, enumerable: false, configurable: true }); Object.defineProperty(Tabs.prototype, "direction", { get: function () { return this._direction; }, set: function (value) { var e_2, _a; if (this._direction === value) { return; } this._direction = value; if (this._scrollbar) { this._scrollbar.orientation = value; } (0, dom_1.removeClasses)(this._tabsList, 'dv-horizontal', 'dv-vertical'); if (value === 'vertical') { (0, dom_1.addClasses)(this._tabsList, 'dv-tabs-container-vertical', 'dv-vertical'); } else { (0, dom_1.removeClasses)(this._tabsList, 'dv-tabs-container-vertical'); (0, dom_1.addClasses)(this._tabsList, 'dv-horizontal'); } try { for (var _b = __values(this._tabs), _c = _b.next(); !_c.done; _c = _b.next()) { var tab = _c.value; tab.value.setDirection(value); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_2) throw e_2.error; } } this._tabGroupManager.updateDirection(); }, enumerable: false, configurable: true }); Tabs.prototype.indexOf = function (id) { return this._tabs.findIndex(function (tab) { return tab.value.panel.id === id; }); }; Tabs.prototype.isActive = function (tab) { return (this.selectedIndex > -1 && this._tabs[this.selectedIndex].value === tab); }; Tabs.prototype.setActivePanel = function (panel) { var e_3, _a; var isVertical = this._direction === 'vertical'; var running = 0; try { for (var _b = __values(this._tabs), _c = _b.next(); !_c.done; _c = _b.next()) { var tab = _c.value; var isActivePanel = panel.id === tab.value.panel.id; tab.value.setActive(isActivePanel); if (isActivePanel) { var element = tab.value.element; var parentElement = element.parentElement; if (isVertical) { if (running < parentElement.scrollTop || running + element.clientHeight > parentElement.scrollTop + parentElement.clientHeight) { parentElement.scrollTop = running; } } else { if (running < parentElement.scrollLeft || running + element.clientWidth > parentElement.scrollLeft + parentElement.clientWidth) { parentElement.scrollLeft = running; } } } running += isVertical ? tab.value.element.clientHeight : tab.value.element.clientWidth; } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_3) throw e_3.error; } } // Reposition underlines so the wrap-around follows the new active tab if (this._tabGroupManager.groupUnderlines.size > 0) { this._tabGroupManager.positionUnderlines(); } }; Tabs.prototype.openPanel = function (panel, index) { var _this = this; if (index === void 0) { index = this._tabs.length; } if (this._tabMap.has(panel.id)) { return; } var tab = new tab_1.Tab(panel, this.accessor, this.group); tab.setContent(panel.view.tab); if (this._direction !== 'horizontal') { tab.setDirection(this._direction); } var disposable = new lifecycle_1.CompositeDisposable(tab.onDragStart(function (event) { var _a; _this._onTabDragStart.fire({ nativeEvent: event, panel: panel }); // Both HTML5 and pointer drags initialize _animState. Cleanup // is wired in both paths: HTML5 via dragend/drop on _tabsList, // pointer via PointerDragController.onDragEnd subscriptions. if (((_a = _this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') { var tabWidth = tab.element.getBoundingClientRect().width; var sourceIndex_1 = _this._tabs.findIndex(function (x) { return x.value === tab; }); _this._animState = { sourceTabId: panel.id, sourceIndex: sourceIndex_1, tabPositions: _this.snapshotTabPositions(), chipPositions: _this._tabGroupManager.snapshotChipWidths(), currentInsertionIndex: null, targetTabGroupId: null, sourceTabGroupId: null, sourceGroupPanelIds: null, sourceChipWidth: 0, cursorOffsetFromDragLeft: tabWidth / 2, sourceGapWidth: tabWidth, containerLeft: _this._tabsList.getBoundingClientRect().left, }; // Collapse the source tab after the browser captures the // drag image, then open the gap at the source position in // the same paint frame — no visual jump. // Both collapse and gap must be instant (no transition). _this._pendingCollapse = true; requestAnimationFrame(function () { var _a; var _b; _this._pendingCollapse = false; if (!_this._animState) { return; } // Collapse source tab instantly (no transition) tab.element.style.transition = 'none'; (0, dom_1.toggleClass)(tab.element, 'dv-tab--dragging', true); void tab.element.offsetHeight; // force reflow (_a = (_b = _this._animState).currentInsertionIndex) !== null && _a !== void 0 ? _a : (_b.currentInsertionIndex = sourceIndex_1); // Apply gap with transitions disabled on the target _this.applyDragOverTransforms(true); // Re-enable transitions for subsequent moves tab.element.style.removeProperty('transition'); }); } }), tab.onTabClick(function (event) { if (event.defaultPrevented) { return; } if (_this.group.api.location.type !== 'edge') { return; } if (_this.group.activePanel === panel) { // Clicking the active tab toggles expansion if (_this.group.api.isCollapsed()) { _this.group.api.expand(); } else { _this.group.api.collapse(); } } else { // Clicking a non-active tab switches the active tab. // If the group is collapsed, also expand it. _this.group.model.openPanel(panel); if (_this.group.api.isCollapsed()) { _this.group.api.expand(); } } }), tab.onPointerDown(function (event) { if (event.defaultPrevented) { return; } var isFloatingGroupsEnabled = !_this.accessor.options.disableFloatingGroups; var isFloatingWithOnePanel = _this.group.api.location.type === 'floating' && _this.size === 1; if (isFloatingGroupsEnabled && !isFloatingWithOnePanel && event.shiftKey) { event.preventDefault(); var panel_1 = _this.accessor.getGroupPanel(tab.panel.id); var _a = tab.element.getBoundingClientRect(), top_1 = _a.top, left = _a.left; var _b = _this.accessor.element.getBoundingClientRect(), rootTop = _b.top, rootLeft = _b.left; _this.accessor.addFloatingGroup(panel_1, { x: left - rootLeft, y: top_1 - rootTop, inDragMode: true, }); return; } switch (event.button) { case 0: if (_this.group.api.location.type === 'edge') { // All tab interaction for edge groups is handled by // onTabClick to avoid race conditions with active panel state } else { if (_this.group.activePanel !== panel) { _this.group.model.openPanel(panel); } } break; } }), tab.onDrop(function (event) { var _a, _b, _c, _d; var animState = _this._animState; _this._animState = null; _this._pendingCollapse = false; var tabIndex = _this._tabs.findIndex(function (x) { return x.value === tab; }); if (animState) { var dropIndex = event.position === 'right' ? tabIndex + 1 : tabIndex; if (animState.sourceTabGroupId) { _this._commitGroupMove(animState.sourceTabGroupId, (_a = animState.currentInsertionIndex) !== null && _a !== void 0 ? _a : dropIndex); return; } _this._uncollapsSourceTab(animState.sourceTabId); var firstPositions = _this.snapshotTabPositions(); _this.resetTabTransforms(); _this._onDrop.fire({ event: event.nativeEvent, index: dropIndex, targetTabGroupId: animState.targetTabGroupId, }); if (((_b = _this.accessor.options.theme) === null || _b === void 0 ? void 0 : _b.tabAnimation) === 'smooth') { _this.runFlipAnimation(firstPositions, animState.sourceTabId, animState.sourceIndex === -1, animState.sourceIndex !== -1 ? { from: Math.min(animState.sourceIndex, dropIndex), to: Math.max(animState.sourceIndex, dropIndex), } : undefined); } } else { // Compute insertion index based on which half of the tab // the pointer is over, then adjust for same-group removal: // when the source tab sits before the insertion point, // removing it shifts all subsequent indices down by one. var afterPosition = _this._direction === 'vertical' ? 'bottom' : 'right'; var insertionIndex = event.position === afterPosition ? tabIndex + 1 : tabIndex; var data_1 = (0, dataTransfer_1.getPanelData)(); var sourceIndex = data_1 ? _this._tabs.findIndex(function (x) { return x.value.panel.id === data_1.panelId; }) : -1; var adjustedIndex = insertionIndex - (sourceIndex !== -1 && sourceIndex < insertionIndex ? 1 : 0); var targetTabGroupId = (_d = (_c = _this.group.model.getTabGroupForPanel(tab.panel.id)) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : null; _this._onDrop.fire({ event: event.nativeEvent, index: adjustedIndex, targetTabGroupId: targetTabGroupId, }); } }), tab.onWillShowOverlay(function (event) { _this._onWillShowOverlay.fire(new events_2.DockviewWillShowOverlayLocationEvent(event, { kind: 'tab', panel: _this.group.activePanel, api: _this.accessor.api, group: _this.group, getData: dataTransfer_1.getPanelData, })); })); var value = { value: tab, disposable: disposable }; this.addTab(value, index); // A new tab may have been inserted between a chip and its // group's first tab — reposition all chips to stay correct. this._tabGroupManager.positionAllChips(); // If a tab was added during active drag, refresh positions if (this._animState) { this._animState.tabPositions = this.snapshotTabPositions(); this._animState.chipPositions = this._tabGroupManager.snapshotChipWidths(); this.applyDragOverTransforms(); } }; Tabs.prototype.delete = function (id) { var _a; if (((_a = this._animState) === null || _a === void 0 ? void 0 : _a.sourceTabId) === id) { this.resetTabTransforms(); this._animState = null; } // Force-clean any pending transitionend listener this._tabGroupManager.cleanupTransition(id); var index = this.indexOf(id); var tabToRemove = this._tabs.splice(index, 1)[0]; this._tabMap.delete(id); if (tabToRemove) { var value = tabToRemove.value, disposable = tabToRemove.disposable; disposable.dispose(); value.dispose(); value.element.remove(); } // If a non-source tab was removed during active drag, refresh positions if (this._animState) { this._animState.tabPositions = this.snapshotTabPositions(); this._animState.chipPositions = this._tabGroupManager.snapshotChipWidths(); this.applyDragOverTransforms(); } }; Tabs.prototype.addTab = function (tab, index) { if (index === void 0) { index = this._tabs.length; } if (index < 0 || index > this._tabs.length) { throw new Error('invalid location'); } // Use the tab element at `index` as the reference node rather than // `children[index]`, because `_tabsList` may contain non-tab children // (e.g. group chips, underlines) that shift the DOM indices. var refNode = index < this._tabs.length ? this._tabs[index].value.element : null; this._tabsList.insertBefore(tab.value.element, refNode); this._tabs = __spreadArray(__spreadArray(__spreadArray([], __read(this._tabs.slice(0, index)), false), [ tab ], false), __read(this._tabs.slice(index)), false); this._tabMap.set(tab.value.panel.id, tab); if (this.selectedIndex < 0) { this.selectedIndex = index; } }; Tabs.prototype.toggleDropdown = function (options) { var e_4, _a, e_5, _b; var _this = this; if (options.reset) { this._onOverflowTabsChange.fire({ tabs: [], tabGroups: [], reset: true, }); return; } var tabs = this._tabs .filter(function (tab) { return !(0, dom_1.isChildEntirelyVisibleWithinParent)(tab.value.element, _this._tabsList); }) .map(function (x) { return x.value.panel.id; }); // Detect tab groups whose chip is clipped or whose tabs are all // in the overflow set (e.g. collapsed groups scrolled out of view). var overflowTabSet = new Set(tabs); var tabGroups = []; try { for (var _c = __values(this.group.model.getTabGroups()), _d = _c.next(); !_d.done; _d = _c.next()) { var tg = _d.value; var chipEntry = this._tabGroupManager.chipRenderers.get(tg.id); var chipClipped = chipEntry && !(0, dom_1.isChildEntirelyVisibleWithinParent)(chipEntry.chip.element, this._tabsList); // A group is in overflow if its chip is clipped OR all its // visible tabs are in the overflow set. var allTabsOverflow = tg.panelIds.length > 0 && tg.panelIds.every(function (pid) { return overflowTabSet.has(pid); }); if (chipClipped || allTabsOverflow) { tabGroups.push(tg.id); // For collapsed groups whose chip is clipped, ensure all // member tabs are included in the overflow list so they // appear in the dropdown. if (tg.collapsed) { try { for (var _e = (e_5 = void 0, __values(tg.panelIds)), _f = _e.next(); !_f.done; _f = _e.next()) { var pid = _f.value; if (!overflowTabSet.has(pid)) { overflowTabSet.add(pid); tabs.push(pid); } } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } finally { if (e_5) throw e_5.error; } } } } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_4) throw e_4.error; } } this._onOverflowTabsChange.fire({ tabs: tabs, tabGroups: tabGroups, reset: false }); }; Tabs.prototype.updateDragAndDropState = function () { var e_6, _a; try { for (var _b = __values(this._tabs), _c = _b.next(); !_c.done; _c = _b.next()) { var tab = _c.value; tab.value.updateDragAndDropState(); } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_6) throw e_6.error; } } this._tabGroupManager.updateDragAndDropState(); }; /** * Synchronize chip elements and CSS classes for all tab groups * in the parent group model. Call after any tab group mutation. */ Tabs.prototype.updateTabGroups = function () { this._tabGroupManager.update(); }; Tabs.prototype.refreshTabGroupAccent = function () { this._tabGroupManager.refreshAccents(); }; /** * Tabs-list-specific side effects of a chip drag start. The chip's * drag sources (constructed by `TabGroupManager`) own the transfer * payload, iframe shielding, dataTransfer setup, and the HTML5 drag * image. This method just sets up the smooth-reorder anim state and * collapses the source-group tabs in the tabs list. */ Tabs.prototype._handleChipDragStart = function (tabGroup, chip, event) { var e_7, _a; var _this = this; var _b; var firstPanelId = tabGroup.panelIds[0]; var firstIdx = firstPanelId ? this._tabs.findIndex(function (t) { return t.value.panel.id === firstPanelId; }) : -1; var chipRect = chip.element.getBoundingClientRect(); // Compute total group width (chip + all tabs) var groupGapWidth = chipRect.width; try { for (var _c = __values(tabGroup.panelIds), _d = _c.next(); !_d.done; _d = _c.next()) { var pid = _d.value; var tabEntry = this._tabMap.get(pid); if (tabEntry) { groupGapWidth += tabEntry.value.element.getBoundingClientRect().width; } } } catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_7) throw e_7.error; } } this._animState = { sourceTabId: '', sourceIndex: firstIdx, tabPositions: this.snapshotTabPositions(), chipPositions: this._tabGroupManager.snapshotChipWidths(), currentInsertionIndex: null, targetTabGroupId: null, sourceTabGroupId: tabGroup.id, sourceGroupPanelIds: new Set(tabGroup.panelIds), sourceChipWidth: chipRect.width, cursorOffsetFromDragLeft: event.clientX - chipRect.left, sourceGapWidth: groupGapWidth, containerLeft: this._tabsList.getBoundingClientRect().left, }; if (((_b = this.accessor.options.theme) === null || _b === void 0 ? void 0 : _b.tabAnimation) !== 'smooth') { return; } // Collapse group tabs + chip after the browser captures the drag // image, then open the gap at the source position — all instant // (no transitions). var groupPanelIds = new Set(tabGroup.panelIds); this._pendingCollapse = true; requestAnimationFrame(function () { var e_8, _a, e_9, _b; var _c; var _d; _this._pendingCollapse = false; if (!_this._animState) { return; } try { // Collapse all group tabs instantly for (var _e = __values(_this._tabs), _f = _e.next(); !_f.done; _f = _e.next()) { var t = _f.value; if (groupPanelIds.has(t.value.panel.id)) { t.value.element.style.transition = 'none'; (0, dom_1.toggleClass)(t.value.element, 'dv-tab--dragging', true); } } } catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (_f && !_f.done && (_a = _e.return)) _a.call(_e); } finally { if (e_8) throw e_8.error; } } // Collapse the group chip instantly var chipEntry = _this._tabGroupManager.chipRenderers.get(tabGroup.id); if (chipEntry) { chipEntry.chip.element.style.transition = 'none'; (0, dom_1.toggleClass)(chipEntry.chip.element, 'dv-tab-group-chip--dragging', true); } // Single reflow for the entire batch void _this._tabsList.offsetHeight; var underline = _this._tabGroupManager.groupUnderlines.get(tabGroup.id); if (underline) { underline.style.display = 'none'; } (_c = (_d = _this._animState).currentInsertionIndex) !== null && _c !== void 0 ? _c : (_d.currentInsertionIndex = firstIdx); _this.applyDragOverTransforms(true); try { for (var _g = __values(_this._tabs), _h = _g.next(); !_h.done; _h = _g.next()) { var t = _h.value; if (groupPanelIds.has(t.value.panel.id)) { t.value.element.style.removeProperty('transition'); } } } catch (e_9_1) { e_9 = { error: e_9_1 }; } finally { try { if (_h && !_h.done && (_b = _g.return)) _b.call(_g); } finally { if (e_9) throw e_9.error; } } if (chipEntry) { chipEntry.chip.element.style.removeProperty('transition'); } }); }; /** * A drop on a tab group chip means "insert before this group". Resolve to * the index of the group's first tab, adjusting for same-group removal * (when the source tab is currently to the left of the target slot, its * removal shifts the insertion index down by one). Always clears * `targetTabGroupId` so the dropped tab lands outside the group. */ Tabs.prototype._handleChipDrop = function (tabGroup, event) { var firstPanelId = tabGroup.panelIds[0]; if (!firstPanelId) { return; } var insertionIndex = this._tabs.findIndex(function (x) { return x.value.panel.id === firstPanelId; }); if (insertionIndex === -1) { return; } var data = (0, dataTransfer_1.getPanelData)(); var sourceIndex = data && data.groupId === this.group.id && data.panelId ? this._tabs.findIndex(function (x) { return x.value.panel.id === data.panelId; }) : -1; var adjustedIndex = insertionIndex - (sourceIndex !== -1 && sourceIndex < insertionIndex ? 1 : 0); this._onDrop.fire({ event: event.nativeEvent, index: adjustedIndex, targetTabGroupId: null, }); }; /** * Sets the broader container that is part of the same logical drop surface * as this tab list (e.g. the full header element). When a dragleave from * the tabs list lands inside this container, `_animState` is preserved so * that external dragover listeners can continue the animation. */ Tabs.prototype.setExtendedDropZone = function (el) { this._extendedDropZone = el; }; /** * Allows external elements (e.g. void container, left actions) to push an * insertion index into the animation while the cursor is outside the tabs * list itself. Pass `null` to clear the indicator. */ Tabs.prototype.setExternalInsertionIndex = function (index) { if (!this._animState) { return; } if (index === this._animState.currentInsertionIndex) { return; } this._animState.currentInsertionIndex = index; this.applyDragOverTransforms(); }; /** * Called when the drag cursor leaves the entire header area (not just the * tabs list). Clears animation state for cross-group drags, which never * receive a `dragend` event on this tab list. */ Tabs.prototype.clearExternalAnimState = function () { if (!this._animState) { return; } this.resetTabTransforms(); if (this._animState.sourceIndex === -1) { this._animState = null; } else { this._animState.currentInsertionIndex = null; } }; Tabs.prototype.snapshotTabPositions = function () { var e_10, _a; var positions = new Map(); try { for (var _b = __values(this._tabs), _c = _b.next(); !_c.done; _c = _b.next()) { var tab = _c.value; positions.set(tab.value.panel.id, tab.value.element.getBoundingClientRect()); } } catch (e_10_1) { e_10 = { error: e_10_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_10) throw e_10.error; } } return positions; }; Tabs.prototype.getAverageTabWidth = function () { var e_11, _a; if (this._tabs.length === 0) { return 0; } var isVertical = this._direction === 'vertical'; var total = 0; try { for (var _b = __values(this._tabs), _c = _b.next(); !_c.done; _c = _b.next()) { var tab = _c.value; var rect = tab.value.element.getBoundingClientRect(); total += isVertical ? rect.height : rect.width; } } catch (e_11_1) { e_11 = { error: e_11_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_11) throw e_11.error; } } return total / this._tabs.length; }; /** * Pointer-event entry point. The HTML5 path enters via the per-element * `dragover` listener; this one hit-tests the global pointer-drag * position against the tabs list and routes through the same shared * `_processDragOver` / `_processDragLeave` helpers. */ Tabs.prototype._handlePointerDragMove = function (clientX, clientY) { var _a; var sourceDoc = (_a = this._tabsList.ownerDocument) !== null && _a !== void 0 ? _a : document; var elAtPoint = sourceDoc.elementFromPoint(clientX, clientY); var inside = !!elAtPoint && (this._tabsList.contains(elAtPoint) || (!!this._extendedDropZone && this._extendedDropZone.contains(elAtPoint))); if (!inside) { if (this._pointerInsideTabsList) { this._pointerInsideTabsList = false; this._processDragLeave(elAtPoint); } return; } this._pointerInsideTabsList = true; this._processDragOver(clientX); }; /** * Shared body of the dragover entry point. Refreshes stale anim state * for a changed drag identity, initializes anim state for incoming * cross-group drags, and dispatches to the gap-following math in * `handleDragOver`. Returns true when this tabs list has taken * ownership of the drag — HTML5 callers use this to gate * `event.preventDefault()`. */ Tabs.prototype._processDragOver = function (clientX) { var _a, _b, _c, _d; if (this.accessor.options.disableDnd) { return false; } // Stale-state guard: if a previous drag's anim state is still here // but the current drag is a different identity, drop the stale one // so the new drag starts from a clean slate. if (this._animState) { var data = (0, dataTransfer_1.getPanelData)(); if ((data === null || data === void 0 ? void 0 : data.tabGroupId) && data.groupId !== this.group.id && this._animState.sourceTabGroupId !== data.tabGroupId) { this._animState = null; } } if (!this._animState) { var data_2 = (0, dataTransfer_1.getPanelData)(); // In default animation mode, individual tab drops are handled // by per-tab Droptargets; only chip drags need tabs-list-level // handling so drops on void space still work. if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'default' && !(data_2 === null || data_2 === void 0 ? void 0 : data_2.tabGroupId)) { return false; } if (data_2 && (data_2.panelId || data_2.tabGroupId) && data_2.groupId !== this.group.id) { var avgWidth = this.getAverageTabWidth(); if (data_2.tabGroupId) { // External group drag — look up the source group to // size the gap. var sourceGroup = this.accessor.getPanel(data_2.groupId); var sourceTg = sourceGroup === null || sourceGroup === void 0 ? void 0 : sourceGroup.model.getTabGroups().find(function (tg) { return tg.id === data_2.tabGroupId; }); var panelCount = (_b = sourceTg === null || sourceTg === void 0 ? void 0 : so