dockview-core
Version:
Zero dependency layout manager supporting tabs, groups, grids and splitviews for vanilla TypeScript
472 lines (471 loc) • 21.3 kB
JavaScript
"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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NoneTabGroupIndicator = exports.WrapTabGroupIndicator = void 0;
var tabGroupAccent_1 = require("../../tabGroupAccent");
/**
* Shared positioning logic for tab group indicators.
* Subclasses implement `applyShape` to control the visual output.
*/
var BaseTabGroupIndicator = /** @class */ (function () {
function BaseTabGroupIndicator(_ctx) {
this._ctx = _ctx;
this._underlines = new Map();
this._rafId = null;
}
Object.defineProperty(BaseTabGroupIndicator.prototype, "underlines", {
get: function () {
return this._underlines;
},
enumerable: false,
configurable: true
});
BaseTabGroupIndicator.prototype.positionUnderlines = function () {
var _this = this;
requestAnimationFrame(function () {
_this._positionUnderlinesSync();
});
};
/**
* Continuously reposition underlines every frame for the duration
* of a tab transition (~200ms), so the underline tracks tab sizes.
*/
BaseTabGroupIndicator.prototype.trackUnderlines = function () {
var _this = this;
if (this._rafId !== null) {
cancelAnimationFrame(this._rafId);
}
var start = performance.now();
var duration = 250; // slightly longer than transition to ensure we catch the end
var tick = function () {
_this._positionUnderlinesSync();
if (performance.now() - start < duration) {
_this._rafId = requestAnimationFrame(tick);
}
else {
_this._rafId = null;
}
};
this._rafId = requestAnimationFrame(tick);
};
BaseTabGroupIndicator.prototype.syncUnderlineElements = function (activeGroupIds) {
var e_1, _a, e_2, _b;
try {
// Ensure underline elements exist for active groups
for (var activeGroupIds_1 = __values(activeGroupIds), activeGroupIds_1_1 = activeGroupIds_1.next(); !activeGroupIds_1_1.done; activeGroupIds_1_1 = activeGroupIds_1.next()) {
var groupId = activeGroupIds_1_1.value;
if (!this._underlines.has(groupId)) {
var underline = document.createElement('div');
underline.className = 'dv-tab-group-underline';
this._ctx.tabsList.appendChild(underline);
this._underlines.set(groupId, underline);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (activeGroupIds_1_1 && !activeGroupIds_1_1.done && (_a = activeGroupIds_1.return)) _a.call(activeGroupIds_1);
}
finally { if (e_1) throw e_1.error; }
}
try {
// Remove underlines for dissolved groups
for (var _c = __values(this._underlines), _d = _c.next(); !_d.done; _d = _c.next()) {
var _e = __read(_d.value, 2), groupId = _e[0], el = _e[1];
if (!activeGroupIds.has(groupId)) {
el.remove();
this._underlines.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; }
}
};
BaseTabGroupIndicator.prototype.getUnderline = function (groupId) {
return this._underlines.get(groupId);
};
BaseTabGroupIndicator.prototype.dispose = function () {
var e_3, _a;
if (this._rafId !== null) {
cancelAnimationFrame(this._rafId);
this._rafId = null;
}
try {
for (var _b = __values(this._underlines), _c = _b.next(); !_c.done; _c = _b.next()) {
var _d = __read(_c.value, 2), el = _d[1];
el.remove();
}
}
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; }
}
this._underlines.clear();
};
BaseTabGroupIndicator.prototype._positionUnderlinesSync = function () {
var e_4, _a, e_5, _b;
var containerRect = this._ctx.tabsList.getBoundingClientRect();
var tabGroups = this._ctx.getTabGroups();
var isVertical = this._ctx.getDirection() === 'vertical';
var containerCrossSize = isVertical
? containerRect.width
: containerRect.height;
var activePanelId = this._ctx.getActivePanelId();
var tabMap = this._ctx.getTabMap();
try {
for (var tabGroups_1 = __values(tabGroups), tabGroups_1_1 = tabGroups_1.next(); !tabGroups_1_1.done; tabGroups_1_1 = tabGroups_1.next()) {
var tg = tabGroups_1_1.value;
var underline = this._underlines.get(tg.id);
if (!underline) {
continue;
}
var panelIds = tg.panelIds;
if (panelIds.length === 0) {
underline.style.display = 'none';
continue;
}
underline.style.display = '';
var chipEl = this._ctx.getChipElement(tg.id);
// In vertical mode, compute top/bottom edges; in horizontal, left/right.
var startEdge = void 0;
if (chipEl) {
var chipRect = chipEl.getBoundingClientRect();
var chipStyle = getComputedStyle(chipEl);
var leadingMargin = isVertical
? Number.parseFloat(chipStyle.marginTop) || 0
: Number.parseFloat(chipStyle.marginLeft) || 0;
startEdge = isVertical
? chipRect.top - containerRect.top - leadingMargin
: chipRect.left - containerRect.left - leadingMargin;
}
else {
var firstPanelId = panelIds[0];
var firstTabEntry = tabMap.get(firstPanelId);
if (firstTabEntry) {
var firstRect = firstTabEntry.value.element.getBoundingClientRect();
startEdge = isVertical
? firstRect.top - containerRect.top
: firstRect.left - containerRect.left;
}
else {
startEdge = 0;
}
}
// Measure the actual last tab position (follows CSS transitions in real-time)
var lastPanelId = panelIds[panelIds.length - 1];
var lastTabEntry = tabMap.get(lastPanelId);
if (!lastTabEntry) {
if (isVertical) {
underline.style.top = "".concat(startEdge, "px");
underline.style.height = '0px';
underline.style.left = '';
underline.style.width = '';
}
else {
underline.style.left = "".concat(startEdge, "px");
underline.style.width = '0px';
underline.style.top = '';
underline.style.height = '';
}
continue;
}
var lastTabRect = lastTabEntry.value.element.getBoundingClientRect();
var endEdge = isVertical
? lastTabRect.bottom - containerRect.top
: lastTabRect.right - containerRect.left;
var span = endEdge - startEdge;
// During collapse or expand: converge both edges toward chip center
var isAnimating = tg.collapsed ||
tg.panelIds.some(function (pid) {
var te = tabMap.get(pid);
return (te &&
te.value.element.classList.contains('dv-tab--group-expanding'));
});
if (isAnimating && chipEl) {
var chipRect = chipEl.getBoundingClientRect();
var chipCenter = isVertical
? chipRect.top + chipRect.height / 2 - containerRect.top
: chipRect.left + chipRect.width / 2 - containerRect.left;
// Sum of current visible tab sizes (shrinking or growing)
var currentTabSize = 0;
var fullTabSize = 0;
try {
for (var _c = (e_5 = void 0, __values(tg.panelIds)), _d = _c.next(); !_d.done; _d = _c.next()) {
var pid = _d.value;
var te = tabMap.get(pid);
if (!te)
continue;
var el = te.value.element;
if (isVertical) {
currentTabSize += el.getBoundingClientRect().height;
fullTabSize += el.scrollHeight;
}
else {
currentTabSize += el.getBoundingClientRect().width;
fullTabSize += el.scrollWidth;
}
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
}
finally { if (e_5) throw e_5.error; }
}
// progress: 0 when tabs at 0 size, 1 when fully open
var progress = fullTabSize > 0
? Math.min(1, currentTabSize / fullTabSize)
: 0;
// Interpolate start and end edges toward chip center
startEdge = chipCenter + (startEdge - chipCenter) * progress;
endEdge = chipCenter + (endEdge - chipCenter) * progress;
span = Math.max(0, endEdge - startEdge);
}
if (isVertical) {
underline.style.top = "".concat(startEdge, "px");
underline.style.height = "".concat(Math.max(0, span), "px");
// Clear horizontal properties
underline.style.left = '';
underline.style.width = '';
}
else {
underline.style.left = "".concat(startEdge, "px");
underline.style.width = "".concat(Math.max(0, span), "px");
// Clear vertical properties
underline.style.top = '';
underline.style.height = '';
}
this.applyShape(underline, tg, startEdge, span, containerCrossSize, activePanelId, containerRect, isVertical);
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (tabGroups_1_1 && !tabGroups_1_1.done && (_a = tabGroups_1.return)) _a.call(tabGroups_1);
}
finally { if (e_4) throw e_4.error; }
}
};
return BaseTabGroupIndicator;
}());
/**
* Chrome-style wrap-around indicator using SVG paths.
*/
var WrapTabGroupIndicator = /** @class */ (function (_super) {
__extends(WrapTabGroupIndicator, _super);
function WrapTabGroupIndicator() {
return _super !== null && _super.apply(this, arguments) || this;
}
WrapTabGroupIndicator.prototype._applyStraightLine = function (svg, path, underline, t, mainSize, isVertical) {
if (isVertical) {
svg.setAttribute('width', String(t));
svg.setAttribute('height', String(mainSize));
underline.style.width = "".concat(t, "px");
underline.style.height = "".concat(mainSize, "px");
path.setAttribute('d', "M ".concat(t / 2, ",0 L ").concat(t / 2, ",").concat(mainSize));
}
else {
svg.setAttribute('width', String(mainSize));
svg.setAttribute('height', String(t));
underline.style.width = "".concat(mainSize, "px");
underline.style.height = "".concat(t, "px");
path.setAttribute('d', "M 0,".concat(t / 2, " L ").concat(mainSize, ",").concat(t / 2));
}
};
/**
* Chrome-style wrap-around underline: a stroked SVG path that runs
* along the bottom (or left edge in vertical mode), curving up and
* over the active tab with rounded corners.
*
* The SVG and path elements are created once per underline and reused;
* only the `d`, `stroke`, and viewport attributes are updated each frame.
*/
WrapTabGroupIndicator.prototype.applyShape = function (underline, tg, groupStart, groupSpan, containerCrossSize, activePanelId, containerRect, isVertical) {
var t = 2; // line thickness in px
var crossSize = containerCrossSize;
var mainSize = groupSpan;
var color = (0, tabGroupAccent_1.resolveTabGroupAccent)(tg.color, this._ctx.getColorPalette());
if (mainSize <= 0 || crossSize <= 0 || color === undefined) {
underline.style.display = 'none';
return;
}
underline.style.display = '';
// Find the active tab within this group
var activeTabEntry;
if (activePanelId && tg.panelIds.includes(activePanelId)) {
activeTabEntry = this._ctx.getTabMap().get(activePanelId);
}
// Ensure SVG + path child exists (created once, reused)
var svg = underline.firstElementChild;
var path;
if (!svg || svg.tagName !== 'svg') {
underline.replaceChildren();
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.display = 'block';
path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('fill', 'none');
svg.appendChild(path);
underline.appendChild(svg);
}
else {
path = svg.firstElementChild;
}
path.setAttribute('stroke', color);
path.setAttribute('stroke-width', String(t));
if (!activeTabEntry) {
this._applyStraightLine(svg, path, underline, t, mainSize, isVertical);
return;
}
var activeRect = activeTabEntry.value.element.getBoundingClientRect();
// Compute active tab start/end relative to the group start
var aStart;
var aEnd;
if (isVertical) {
aStart = Math.max(0, activeRect.top - containerRect.top - groupStart);
aEnd = Math.min(mainSize, activeRect.bottom - containerRect.top - groupStart);
}
else {
aStart = Math.max(0, activeRect.left - containerRect.left - groupStart);
aEnd = Math.min(mainSize, activeRect.right - containerRect.left - groupStart);
}
if (aEnd <= aStart) {
this._applyStraightLine(svg, path, underline, t, mainSize, isVertical);
return;
}
var r = 6; // corner radius
var half = t / 2;
if (isVertical) {
var svgW = crossSize;
var svgH = mainSize;
svg.setAttribute('width', String(svgW));
svg.setAttribute('height', String(svgH));
underline.style.width = "".concat(svgW, "px");
underline.style.height = "".concat(svgH, "px");
var xLeft = half;
var xRight = svgW - half;
var d = [
"M ".concat(xLeft, ",0"),
"L ".concat(xLeft, ",").concat(aStart - r),
"Q ".concat(xLeft, ",").concat(aStart, " ").concat(xLeft + r, ",").concat(aStart),
"L ".concat(xRight - r, ",").concat(aStart),
"Q ".concat(xRight, ",").concat(aStart, " ").concat(xRight, ",").concat(aStart + r),
"L ".concat(xRight, ",").concat(aEnd - r),
"Q ".concat(xRight, ",").concat(aEnd, " ").concat(xRight - r, ",").concat(aEnd),
"L ".concat(xLeft + r, ",").concat(aEnd),
"Q ".concat(xLeft, ",").concat(aEnd, " ").concat(xLeft, ",").concat(aEnd + r),
"L ".concat(xLeft, ",").concat(svgH),
].join(' ');
path.setAttribute('d', d);
}
else {
var svgW = mainSize;
var svgH = crossSize;
svg.setAttribute('width', String(svgW));
svg.setAttribute('height', String(svgH));
underline.style.width = "".concat(svgW, "px");
underline.style.height = "".concat(svgH, "px");
var yBot = svgH - half;
var yTop = half;
var d = [
"M 0,".concat(yBot),
"L ".concat(aStart - r, ",").concat(yBot),
"Q ".concat(aStart, ",").concat(yBot, " ").concat(aStart, ",").concat(yBot - r),
"L ".concat(aStart, ",").concat(yTop + r),
"Q ".concat(aStart, ",").concat(yTop, " ").concat(aStart + r, ",").concat(yTop),
"L ".concat(aEnd - r, ",").concat(yTop),
"Q ".concat(aEnd, ",").concat(yTop, " ").concat(aEnd, ",").concat(yTop + r),
"L ".concat(aEnd, ",").concat(yBot - r),
"Q ".concat(aEnd, ",").concat(yBot, " ").concat(aEnd + r, ",").concat(yBot),
"L ".concat(svgW, ",").concat(yBot),
].join(' ');
path.setAttribute('d', d);
}
};
return WrapTabGroupIndicator;
}(BaseTabGroupIndicator));
exports.WrapTabGroupIndicator = WrapTabGroupIndicator;
/**
* Flat continuous bar indicator — no wrap-around, just a colored line
* spanning the full tab group width.
*/
var NoneTabGroupIndicator = /** @class */ (function (_super) {
__extends(NoneTabGroupIndicator, _super);
function NoneTabGroupIndicator() {
return _super !== null && _super.apply(this, arguments) || this;
}
NoneTabGroupIndicator.prototype.applyShape = function (underline, tg, _startEdge, span, _containerCrossSize, _activePanelId, _containerRect, isVertical) {
var t = 2; // line thickness in px
var color = (0, tabGroupAccent_1.resolveTabGroupAccent)(tg.color, this._ctx.getColorPalette());
if (span <= 0 || color === undefined) {
underline.style.display = 'none';
return;
}
underline.style.display = '';
// Clear any SVG content left over from a mode switch
if (underline.firstElementChild) {
underline.replaceChildren();
}
underline.style.backgroundColor = color;
if (isVertical) {
underline.style.width = "".concat(t, "px");
underline.style.height = "".concat(span, "px");
}
else {
underline.style.width = "".concat(span, "px");
underline.style.height = "".concat(t, "px");
}
};
return NoneTabGroupIndicator;
}(BaseTabGroupIndicator));
exports.NoneTabGroupIndicator = NoneTabGroupIndicator;