UNPKG

dockview-core

Version:

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

804 lines (803 loc) 37.7 kB
"use strict"; 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.TabGroupManager = void 0; var dom_1 = require("../../../dom"); var events_1 = require("../../../events"); var dataTransfer_1 = require("../../../dnd/dataTransfer"); var backend_1 = require("../../../dnd/backend"); var longPress_1 = require("../../../dnd/pointer/longPress"); var lifecycle_1 = require("../../../lifecycle"); var dndCapabilities_1 = require("../../dndCapabilities"); var tabGroupAccent_1 = require("../../tabGroupAccent"); var tabGroupChip_1 = require("./tabGroupChip"); var droptarget_1 = require("../../../dnd/droptarget"); var dataTransfer_2 = require("../../../dnd/dataTransfer"); var tabGroupIndicator_1 = require("./tabGroupIndicator"); var EMPTY_MAP = new Map(); var TabGroupManager = /** @class */ (function () { function TabGroupManager(_ctx, _callbacks) { this._ctx = _ctx; this._callbacks = _callbacks; this._chipRenderers = new Map(); this._indicator = null; this._skipNextCollapseAnimation = false; this._pendingTransitionCleanups = new Map(); } Object.defineProperty(TabGroupManager.prototype, "chipRenderers", { get: function () { return this._chipRenderers; }, enumerable: false, configurable: true }); Object.defineProperty(TabGroupManager.prototype, "groupUnderlines", { get: function () { var _a, _b; return (_b = (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.underlines) !== null && _b !== void 0 ? _b : EMPTY_MAP; }, enumerable: false, configurable: true }); Object.defineProperty(TabGroupManager.prototype, "skipNextCollapseAnimation", { get: function () { return this._skipNextCollapseAnimation; }, set: function (value) { this._skipNextCollapseAnimation = value; }, enumerable: false, configurable: true }); /** * Synchronize chip elements and CSS classes for all tab groups * in the parent group model. Call after any tab group mutation. */ TabGroupManager.prototype.update = function () { var e_1, _a, e_2, _b; var model = this._ctx.group.model; var tabGroups = model.getTabGroups(); // Track which group IDs are still active var activeGroupIds = new Set(); try { for (var tabGroups_1 = __values(tabGroups), tabGroups_1_1 = tabGroups_1.next(); !tabGroups_1_1.done; tabGroups_1_1 = tabGroups_1.next()) { var tabGroup = tabGroups_1_1.value; activeGroupIds.add(tabGroup.id); this._ensureChipForGroup(tabGroup); this._positionChipForGroup(tabGroup); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (tabGroups_1_1 && !tabGroups_1_1.done && (_a = tabGroups_1.return)) _a.call(tabGroups_1); } finally { if (e_1) throw e_1.error; } } try { // Remove chips for dissolved/destroyed groups for (var _c = __values(this._chipRenderers), _d = _c.next(); !_d.done; _d = _c.next()) { var _e = __read(_d.value, 2), groupId = _e[0], entry = _e[1]; if (!activeGroupIds.has(groupId)) { entry.chip.element.remove(); entry.chip.dispose(); entry.disposable.dispose(); this._chipRenderers.delete(groupId); } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_d && !_d.done && (_b = _c.return)) _b.call(_c); } finally { if (e_2) throw e_2.error; } } // Update CSS classes on all tabs this._updateTabGroupClasses(); }; /** * Re-read the active palette and re-apply colors to chips, tabs and * the indicator. Called when `tabGroupColors` / `tabGroupAccent` * options change at runtime. */ TabGroupManager.prototype.refreshAccents = function () { var e_3, _a; var _b, _c; try { for (var _d = __values(this._ctx.group.model.getTabGroups()), _e = _d.next(); !_e.done; _e = _d.next()) { var tabGroup = _e.value; var entry = this._chipRenderers.get(tabGroup.id); (_c = entry === null || entry === void 0 ? void 0 : (_b = entry.chip).update) === null || _c === void 0 ? void 0 : _c.call(_b, { tabGroup: tabGroup }); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_e && !_e.done && (_a = _d.return)) _a.call(_d); } finally { if (e_3) throw e_3.error; } } this._updateTabGroupClasses(); }; TabGroupManager.prototype.positionAllChips = function () { var e_4, _a; if (this._chipRenderers.size === 0) { return; } try { for (var _b = __values(this._ctx.group.model.getTabGroups()), _c = _b.next(); !_c.done; _c = _b.next()) { var tabGroup = _c.value; this._positionChipForGroup(tabGroup); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_4) throw e_4.error; } } }; TabGroupManager.prototype.updateDirection = function () { var e_5, _a; var isVertical = this._ctx.getDirection() === 'vertical'; try { for (var _b = __values(this._chipRenderers), _c = _b.next(); !_c.done; _c = _b.next()) { var _d = __read(_c.value, 2), entry = _d[1]; entry.dropTarget.setTargetZones(isVertical ? ['top'] : ['left']); } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_5) throw e_5.error; } } }; TabGroupManager.prototype.snapshotChipWidths = function () { var e_6, _a; var widths = new Map(); try { for (var _b = __values(this._chipRenderers), _c = _b.next(); !_c.done; _c = _b.next()) { var _d = __read(_c.value, 2), groupId = _d[0], entry = _d[1]; widths.set(groupId, entry.chip.element.getBoundingClientRect().width); } } 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; } } return widths; }; TabGroupManager.prototype.positionUnderlines = function () { var _a; (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.positionUnderlines(); }; TabGroupManager.prototype.trackUnderlines = function () { var _a; (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.trackUnderlines(); }; TabGroupManager.prototype.setGroupDragImage = function (event, tabGroup, chipEl) { if (!event.dataTransfer) { return; } var isVertical = this._ctx.getDirection() === 'vertical'; // Clone the entire tabs list so cloned nodes inherit all // theme styles, CSS variables and class-based rules. var clone = this._ctx.tabsList.cloneNode(true); if (isVertical) { // Force horizontal orientation for the drag ghost by // removing vertical CSS classes and overriding writing-mode. clone.classList.remove('dv-tabs-container-vertical', 'dv-vertical'); clone.classList.add('dv-horizontal'); clone.style.writingMode = 'horizontal-tb'; clone.style.height = "".concat(this._ctx.tabsList.offsetWidth, "px"); } else { clone.style.height = "".concat(this._ctx.tabsList.offsetHeight, "px"); } clone.style.width = 'auto'; clone.style.overflow = 'visible'; clone.style.pointerEvents = 'none'; // Remove all elements except the chip so the drag ghost // shows only the chip regardless of the group's expanded state. var children = Array.from(clone.children); var realChildren = Array.from(this._ctx.tabsList.children); for (var i = children.length - 1; i >= 0; i--) { var real = realChildren[i]; if (real === chipEl) { continue; // keep the chip only } children[i].remove(); } // Wrap the clone in a minimal ancestor chain so that CSS // selectors like `.dv-groupview.dv-active-group > .dv-tabs-and-actions-container .dv-tabs-container > .dv-tab` // match the cloned tabs and apply correct color/background. var wrapper = document.createElement('div'); wrapper.className = 'dv-groupview dv-active-group'; wrapper.style.position = 'fixed'; wrapper.style.top = '-10000px'; wrapper.style.left = '0px'; wrapper.style.height = 'auto'; wrapper.style.width = 'auto'; wrapper.style.pointerEvents = 'none'; var actionsWrapper = document.createElement('div'); actionsWrapper.className = 'dv-tabs-and-actions-container'; actionsWrapper.style.height = 'auto'; actionsWrapper.style.width = 'auto'; wrapper.appendChild(actionsWrapper); actionsWrapper.appendChild(clone); // Append inside the dockview root so CSS variables are inherited this._ctx.accessor.element.appendChild(wrapper); // Compute cursor offset relative to the wrapper element. // The cloned chip is the first .dv-tab-group-chip in the clone. var clonedChip = clone.querySelector('.dv-tab-group-chip'); var chipRect = chipEl.getBoundingClientRect(); var cursorInChipX = event.clientX - chipRect.left; var cursorInChipY = event.clientY - chipRect.top; if (clonedChip) { var clonedChipRect = clonedChip.getBoundingClientRect(); var wrapperRect = wrapper.getBoundingClientRect(); var offsetX = clonedChipRect.left - wrapperRect.left + cursorInChipX; var offsetY = clonedChipRect.top - wrapperRect.top + cursorInChipY; event.dataTransfer.setDragImage(wrapper, offsetX, offsetY); } else { event.dataTransfer.setDragImage(wrapper, cursorInChipX, cursorInChipY); } // Clean up after the browser captures the image requestAnimationFrame(function () { wrapper.remove(); }); }; TabGroupManager.prototype.cleanupTransition = function (panelId) { var _a; (_a = this._pendingTransitionCleanups.get(panelId)) === null || _a === void 0 ? void 0 : _a(); this._pendingTransitionCleanups.delete(panelId); }; TabGroupManager.prototype.updateDragAndDropState = function () { var e_7, _a; var caps = (0, dndCapabilities_1.resolveDndCapabilities)(this._ctx.accessor.options); try { for (var _b = __values(this._chipRenderers.values()), _c = _b.next(); !_c.done; _c = _b.next()) { var entry = _c.value; entry.chip.element.draggable = caps.html5; entry.html5DragSource.setDisabled(!caps.html5); entry.pointerDragSource.setDisabled(!caps.pointer); entry.pointerDragSource.setTouchOnly(!caps.pointerHandlesMouse); } } catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_7) throw e_7.error; } } }; /** * Synchronously dispose the chip drag sources for an in-flight chip * drag. Called from `_commitGroupMove` so the transfer payload + * iframe shield are released BEFORE the cross-group move detaches * the chip (chip dispose is scheduled on a microtask via * `_scheduleTabGroupUpdate`, which is too late for callers that read * `getPanelData()` synchronously after the move). Idempotent — the * subsequent `update()` will also dispose the sources. */ TabGroupManager.prototype.disposeChipDrag = function (tabGroupId) { var _a, _b; var entry = this._chipRenderers.get(tabGroupId); if (!entry) { return; } // Optional-chained because tests may inject minimal entries // that skip the manager's normal `_ensureChipForGroup` flow. (_a = entry.html5DragSource) === null || _a === void 0 ? void 0 : _a.dispose(); (_b = entry.pointerDragSource) === null || _b === void 0 ? void 0 : _b.dispose(); }; /** Cloned chip rect used as the pointer follow-finger ghost. */ TabGroupManager.prototype._buildChipGhostElement = function (chipEl) { var style = getComputedStyle(chipEl); var clone = chipEl.cloneNode(true); Array.from(style).forEach(function (key) { clone.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key)); }); clone.style.position = 'absolute'; return clone; }; TabGroupManager.prototype.disposeAll = function () { var e_8, _a, e_9, _b; var _c; (_c = this._indicator) === null || _c === void 0 ? void 0 : _c.dispose(); this._indicator = null; try { for (var _d = __values(this._pendingTransitionCleanups), _e = _d.next(); !_e.done; _e = _d.next()) { var _f = __read(_e.value, 2), cleanup = _f[1]; cleanup(); } } catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (_e && !_e.done && (_a = _d.return)) _a.call(_d); } finally { if (e_8) throw e_8.error; } } this._pendingTransitionCleanups.clear(); try { for (var _g = __values(this._chipRenderers), _h = _g.next(); !_h.done; _h = _g.next()) { var _j = __read(_h.value, 2), entry = _j[1]; entry.chip.element.remove(); entry.chip.dispose(); entry.disposable.dispose(); } } 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; } } this._chipRenderers.clear(); }; TabGroupManager.prototype._ensureIndicator = function () { var _this = this; var _a, _b; var mode = (_b = (_a = this._ctx.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabGroupIndicator) !== null && _b !== void 0 ? _b : 'wrap'; var Ctor = mode === 'none' ? tabGroupIndicator_1.NoneTabGroupIndicator : tabGroupIndicator_1.WrapTabGroupIndicator; // Re-create if the indicator type changed (e.g. theme switch) if (this._indicator && !(this._indicator instanceof Ctor)) { this._indicator.dispose(); this._indicator = null; } if (!this._indicator) { this._indicator = new Ctor({ tabsList: this._ctx.tabsList, getTabGroups: function () { return _this._ctx.group.model.getTabGroups(); }, getActivePanelId: function () { var _a; return (_a = _this._ctx.group.activePanel) === null || _a === void 0 ? void 0 : _a.id; }, getTabMap: function () { return _this._ctx.getTabMap(); }, getChipElement: function (id) { var _a; return (_a = _this._chipRenderers.get(id)) === null || _a === void 0 ? void 0 : _a.chip.element; }, getDirection: function () { return _this._ctx.getDirection(); }, getColorPalette: function () { return _this._ctx.accessor.tabGroupColorPalette; }, }); } }; TabGroupManager.prototype._ensureChipForGroup = function (tabGroup) { var _this = this; if (this._chipRenderers.has(tabGroup.id)) { return; } var createChip = this._ctx.accessor.options.createTabGroupChipComponent; var chip = createChip ? createChip(tabGroup) : new tabGroupChip_1.TabGroupChip(this._ctx.accessor.tabGroupColorPalette); chip.init({ tabGroup: tabGroup, api: this._ctx.accessor.api }); var caps = (0, dndCapabilities_1.resolveDndCapabilities)(this._ctx.accessor.options); chip.element.draggable = caps.html5; var panelTransfer = dataTransfer_1.LocalSelectionTransfer.getInstance(); // Shared `getData` for both backends. Sets a group-level // PanelTransfer (panelId=null, tabGroupId identifies the group). // The returned disposer clears it on drag end. var getData = function () { panelTransfer.setData([ new dataTransfer_1.PanelTransfer(_this._ctx.accessor.id, _this._ctx.group.id, null, tabGroup.id), ], dataTransfer_1.PanelTransfer.prototype); return { dispose: function () { panelTransfer.clearData(dataTransfer_1.PanelTransfer.prototype); }, }; }; // The chip's HTML5 drag image is the cloned tabs list (chip only), // mounted inside the dockview root for CSS-variable inheritance and // positioned against the chip's in-place rect. Layout-dependent // offset means we set the drag image directly in `onDragStart` // (inside the dragstart handler) rather than via the generic // `createGhost` factory, which only knows about ghost specs that // can be appended to `document.body`. var html5DragSource = backend_1.html5Backend.createDragSource(chip.element, { getData: getData, disabled: !caps.html5, isCancelled: function () { return !(0, dndCapabilities_1.resolveDndCapabilities)(_this._ctx.accessor.options).html5; }, onDragStart: function (event) { // Type guard via `dataTransfer` — `instanceof DragEvent` // would throw in jsdom which doesn't ship a DragEvent // constructor. if ('dataTransfer' in event && event.dataTransfer) { _this.setGroupDragImage(event, tabGroup, chip.element); } _this._callbacks.onChipDragStart(tabGroup, chip, event); }, onDragEnd: function (event) { var _a, _b; (_b = (_a = _this._callbacks).onChipDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, tabGroup, chip, event); }, }); // Synchronous panelTransfer cleanup directly on the chip element. // `Html5DragSource`'s dragend defers data disposal via `setTimeout(0)` // so drop handlers can read the payload — but a chip drag that // ends via `moveGroupOrPanel` (no actual drop event) needs the // singleton cleared immediately, otherwise a synchronous // `getPanelData()` after the move still sees the stale chip // payload. Attached directly (not via `addDisposableListener`) so // the listener survives chip disposal in the detach-then-dragend // cross-group path; `once: true` auto-removes after the single // dragend that we care about. (#1254) chip.element.addEventListener('dragend', function () { panelTransfer.clearData(dataTransfer_1.PanelTransfer.prototype); }, { once: true }); var pointerDragSource = backend_1.pointerBackend.createDragSource(chip.element, { getData: getData, disabled: !caps.pointer, touchOnly: !caps.pointerHandlesMouse, isCancelled: function () { return !(0, dndCapabilities_1.resolveDndCapabilities)(_this._ctx.accessor.options).pointer; }, createGhost: function () { return ({ element: _this._buildChipGhostElement(chip.element), offsetX: 8, offsetY: 8, }); }, onDragStart: function (event) { _this._callbacks.onChipDragStart(tabGroup, chip, event); }, }); var disposables = [ tabGroup.onDidChange(function () { var _a; (_a = chip.update) === null || _a === void 0 ? void 0 : _a.call(chip, { tabGroup: tabGroup }); _this._updateTabGroupClasses(); }), tabGroup.onDidPanelChange(function () { _this._positionChipForGroup(tabGroup); _this._updateTabGroupClasses(); }), tabGroup.onDidCollapseChange(function () { _this._updateTabGroupClasses(); }), html5DragSource, pointerDragSource, ]; // Context menu: built-in TabGroupChip already aggregates right-click // + touch long-press into `onContextMenu`. Custom chip renderers // don't, so attach a long-press detector and contextmenu listener // directly on their element. var onContextMenu = function (event) { // A long-press on a chip should preempt the in-flight pointer // drag and open the menu instead. pointerDragSource.cancelPending(); _this._callbacks.onChipContextMenu(tabGroup, event); }; if (chip instanceof tabGroupChip_1.TabGroupChip) { disposables.push(chip.onContextMenu(onContextMenu)); } else { disposables.push(new longPress_1.LongPressDetector(chip.element, { onLongPress: onContextMenu, }), (0, events_1.addDisposableListener)(chip.element, 'contextmenu', onContextMenu)); } // The chip sits before its group's first tab in the DOM, so it // covers the "drop before the group" position. Without a drop // target here, dropping a tab over the chip is a dead zone — // particularly visible when the group is first in the tabs list // and there's no preceding tab whose right zone covers position 0. // The smooth animation path already shifts the chip's margin to // open a gap, so suppress the overlay in that mode. var isVertical = this._ctx.getDirection() === 'vertical'; var dropTarget = new droptarget_1.Droptarget(chip.element, { acceptedTargetZones: isVertical ? ['top'] : ['left'], overlayModel: { activationSize: { value: 100, type: 'percentage' }, }, canDisplayOverlay: function (event, position) { var _a; if (_this._ctx.group.locked) { return false; } if (_this._ctx.accessor.options.disableDnd) { return false; } var data = (0, dataTransfer_2.getPanelData)(); if (data && _this._ctx.accessor.id === data.viewId) { if (((_a = _this._ctx.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') { return false; } return true; } return _this._ctx.group.model.canDisplayOverlay(event, position, 'tab'); }, }); disposables.push(dropTarget, dropTarget.onDrop(function (event) { _this._callbacks.onChipDrop(tabGroup, event); })); var disposable = new (lifecycle_1.CompositeDisposable.bind.apply(lifecycle_1.CompositeDisposable, __spreadArray([void 0], __read(disposables), false)))(); this._chipRenderers.set(tabGroup.id, { chip: chip, html5DragSource: html5DragSource, pointerDragSource: pointerDragSource, disposable: disposable, dropTarget: dropTarget, }); // Group is born collapsed (cross-group drop, layout restore, etc.): // its tabs are about to be added without the collapsed class. Skip // the animation in the upcoming _updateTabGroupClasses call so they // apply the class instantly instead of transitioning from expanded. if (tabGroup.collapsed) { this._skipNextCollapseAnimation = true; } }; TabGroupManager.prototype._positionChipForGroup = function (tabGroup) { var entry = this._chipRenderers.get(tabGroup.id); if (!entry) { return; } var chipEl = entry.chip.element; var panelIds = tabGroup.panelIds; if (panelIds.length === 0) { chipEl.remove(); return; } // Find the first tab element of this group var firstPanelId = panelIds[0]; var firstTabEntry = this._ctx.getTabMap().get(firstPanelId); if (!firstTabEntry) { chipEl.remove(); return; } // Insert chip before the first tab of the group var firstTabEl = firstTabEntry.value.element; if (chipEl.nextSibling !== firstTabEl) { this._ctx.tabsList.insertBefore(chipEl, firstTabEl); } }; TabGroupManager.prototype._updateTabGroupClasses = function () { var e_10, _a, e_11, _b, e_12, _c, e_13, _d, e_14, _e, e_15, _f, e_16, _g; var _this = this; var _h; var model = this._ctx.group.model; var tabGroups = model.getTabGroups(); var tabs = this._ctx.getTabs(); var tabMap = this._ctx.getTabMap(); var hasAnimation = false; // Build a lookup: panelId → tabGroup var panelGroupMap = new Map(); try { for (var tabGroups_2 = __values(tabGroups), tabGroups_2_1 = tabGroups_2.next(); !tabGroups_2_1.done; tabGroups_2_1 = tabGroups_2.next()) { var tg = tabGroups_2_1.value; try { for (var _j = (e_11 = void 0, __values(tg.panelIds)), _k = _j.next(); !_k.done; _k = _j.next()) { var pid = _k.value; panelGroupMap.set(pid, tg); } } catch (e_11_1) { e_11 = { error: e_11_1 }; } finally { try { if (_k && !_k.done && (_b = _j.return)) _b.call(_j); } finally { if (e_11) throw e_11.error; } } } } catch (e_10_1) { e_10 = { error: e_10_1 }; } finally { try { if (tabGroups_2_1 && !tabGroups_2_1.done && (_a = tabGroups_2.return)) _a.call(tabGroups_2); } finally { if (e_10) throw e_10.error; } } var _loop_1 = function (tabEntry) { var tab = tabEntry.value; var panelId = tab.panel.id; var tg = panelGroupMap.get(panelId); var isGrouped = !!tg; (0, dom_1.toggleClass)(tab.element, 'dv-tab--grouped', isGrouped); if (tg) { var ids = tg.panelIds; var isFirst = ids[0] === panelId; var isLast = ids[ids.length - 1] === panelId; (0, dom_1.toggleClass)(tab.element, 'dv-tab--group-first', isFirst); (0, dom_1.toggleClass)(tab.element, 'dv-tab--group-last', isLast); // Expose the resolved group color as a CSS custom property // so pure-CSS themes can use it for borders, backgrounds, etc. (0, tabGroupAccent_1.applyTabGroupAccent)(tab.element, tg.color, this_1._ctx.accessor.tabGroupColorPalette); // Collapse / expand with animation var isCollapsed = tab.element.classList.contains('dv-tab--group-collapsed'); if (!tg.collapsed && isCollapsed) { // Collapsed → expanding: animate back hasAnimation = true; tab.element.classList.remove('dv-tab--group-collapsed'); tab.element.classList.add('dv-tab--group-expanding'); // Clean up any previous transitionend listener // from a rapid collapse/expand cycle (_h = this_1._pendingTransitionCleanups.get(panelId)) === null || _h === void 0 ? void 0 : _h(); var onEnd_1 = function () { tab.element.classList.remove('dv-tab--group-expanding'); tab.element.style.removeProperty('width'); tab.element.removeEventListener('transitionend', onEnd_1); clearTimeout(fallbackTimer_1); _this._pendingTransitionCleanups.delete(panelId); }; // Fallback in case transitionend never fires // (e.g. element removed from DOM mid-transition) var fallbackTimer_1 = setTimeout(onEnd_1, 300); this_1._pendingTransitionCleanups.set(panelId, onEnd_1); tab.element.addEventListener('transitionend', onEnd_1); } } else { (0, dom_1.toggleClass)(tab.element, 'dv-tab--group-first', false); (0, dom_1.toggleClass)(tab.element, 'dv-tab--group-last', false); tab.element.classList.remove('dv-tab--group-collapsed', 'dv-tab--group-expanding'); tab.element.style.removeProperty('width'); tab.element.style.removeProperty('--dv-tab-group-color'); } }; var this_1 = this; try { for (var tabs_1 = __values(tabs), tabs_1_1 = tabs_1.next(); !tabs_1_1.done; tabs_1_1 = tabs_1.next()) { var tabEntry = tabs_1_1.value; _loop_1(tabEntry); } } catch (e_12_1) { e_12 = { error: e_12_1 }; } finally { try { if (tabs_1_1 && !tabs_1_1.done && (_c = tabs_1.return)) _c.call(tabs_1); } finally { if (e_12) throw e_12.error; } } // Track active group IDs for underline/collapse handling var activeGroupIds = new Set(); try { // Handle collapse animation per group for (var tabGroups_3 = __values(tabGroups), tabGroups_3_1 = tabGroups_3.next(); !tabGroups_3_1.done; tabGroups_3_1 = tabGroups_3.next()) { var tg = tabGroups_3_1.value; activeGroupIds.add(tg.id); // Collapse animation var hasNewCollapse = tg.collapsed && tg.panelIds.some(function (pid) { var te = tabMap.get(pid); return (te && !te.value.element.classList.contains('dv-tab--group-collapsed')); }); if (hasNewCollapse) { if (this._skipNextCollapseAnimation) { // Apply collapsed state instantly (no animation). // Disable transitions so the CSS transition on // dv-tab--group-collapsed doesn't fire. var affected = []; try { for (var _l = (e_14 = void 0, __values(tg.panelIds)), _m = _l.next(); !_m.done; _m = _l.next()) { var pid = _m.value; var te = tabMap.get(pid); if (te) { te.value.element.style.transition = 'none'; te.value.element.classList.add('dv-tab--group-collapsed'); affected.push(te.value.element); } } } catch (e_14_1) { e_14 = { error: e_14_1 }; } finally { try { if (_m && !_m.done && (_e = _l.return)) _e.call(_l); } finally { if (e_14) throw e_14.error; } } if (affected.length > 0) { void affected[0].offsetHeight; // single reflow try { for (var affected_1 = (e_15 = void 0, __values(affected)), affected_1_1 = affected_1.next(); !affected_1_1.done; affected_1_1 = affected_1.next()) { var el = affected_1_1.value; el.style.removeProperty('transition'); } } catch (e_15_1) { e_15 = { error: e_15_1 }; } finally { try { if (affected_1_1 && !affected_1_1.done && (_f = affected_1.return)) _f.call(affected_1); } finally { if (e_15) throw e_15.error; } } } } else { hasAnimation = true; var isVert = this._ctx.getDirection() === 'vertical'; try { for (var _o = (e_16 = void 0, __values(tg.panelIds)), _p = _o.next(); !_p.done; _p = _o.next()) { var pid = _p.value; var te = tabMap.get(pid); if (te && !te.value.element.classList.contains('dv-tab--group-collapsed')) { var rect = te.value.element.getBoundingClientRect(); if (isVert) { te.value.element.style.height = "".concat(rect.height, "px"); } else { te.value.element.style.width = "".concat(rect.width, "px"); } void te.value.element.offsetHeight; // force reflow te.value.element.classList.add('dv-tab--group-collapsed'); } } } catch (e_16_1) { e_16 = { error: e_16_1 }; } finally { try { if (_p && !_p.done && (_g = _o.return)) _g.call(_o); } finally { if (e_16) throw e_16.error; } } } } } } catch (e_13_1) { e_13 = { error: e_13_1 }; } finally { try { if (tabGroups_3_1 && !tabGroups_3_1.done && (_d = tabGroups_3.return)) _d.call(tabGroups_3); } finally { if (e_13) throw e_13.error; } } this._skipNextCollapseAnimation = false; // Sync indicator underlines and position them this._ensureIndicator(); if (this._indicator) { this._indicator.syncUnderlineElements(activeGroupIds); if (hasAnimation) { this._indicator.trackUnderlines(); } else { this._indicator.positionUnderlines(); } } }; return TabGroupManager; }()); exports.TabGroupManager = TabGroupManager;