UNPKG

dockview-core

Version:

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

472 lines (471 loc) 21.3 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; }; 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;