dockview-core
Version:
Zero dependency layout manager supporting tabs, groups, grids and splitviews for vanilla TypeScript
804 lines (803 loc) • 37.7 kB
JavaScript
"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;