UNPKG

dockview-core

Version:

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

314 lines (313 loc) 12.3 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.ContextMenuController = void 0; var dom_1 = require("../dom"); function popoverZIndexFor(target) { if (!(target instanceof HTMLElement)) { return undefined; } // Floating overlays live in the shell as siblings of the popover anchor // and the AriaLevelTracker sets their inline z-index. Without this, a // popover opened from inside a floating group would render behind it // because they share the shell stacking context. var relativeParent = (0, dom_1.findRelativeZIndexParent)(target); return (relativeParent === null || relativeParent === void 0 ? void 0 : relativeParent.style.zIndex) ? "calc(".concat(relativeParent.style.zIndex, " * 2)") : undefined; } var _nextId = 0; var nextContextMenuItemId = function () { return "dv-ctx-menu-item-".concat(_nextId++); }; function isItemConfig(item) { return typeof item === 'object'; } function buildItem(label, close, action, disabled) { var el = document.createElement('div'); el.className = 'dv-context-menu-item'; el.setAttribute('role', 'menuitem'); if (disabled) { el.classList.add('dv-context-menu-item--disabled'); el.setAttribute('aria-disabled', 'true'); } el.textContent = label; if (!disabled) { el.addEventListener('click', function () { action(); close(); }); } return el; } function buildSeparator() { var el = document.createElement('div'); el.className = 'dv-context-menu-separator'; el.setAttribute('role', 'separator'); return el; } function isCoarsePrimaryInput() { if (typeof window === 'undefined' || !window.matchMedia) { return false; } var coarse = window.matchMedia('(pointer: coarse)').matches; var fine = window.matchMedia('(pointer: fine)').matches; return coarse && !fine; } function buildRenameInput(tabGroup) { var wrapper = document.createElement('div'); wrapper.className = 'dv-context-menu-rename'; var input = document.createElement('input'); input.className = 'dv-context-menu-rename-input'; input.type = 'text'; input.placeholder = 'Name This Group'; input.value = tabGroup.label; input.addEventListener('input', function () { tabGroup.setLabel(input.value); }); input.addEventListener('keydown', function (e) { if (e.key !== 'Escape' && e.key !== 'Enter') { e.stopPropagation(); } }); input.addEventListener('click', function (e) { e.stopPropagation(); }); wrapper.appendChild(input); // Skip auto-focus on touch-primary devices: focusing the input pops the // on-screen keyboard, which fires `window resize`, which `PopupService` // listens to and uses to dismiss the popover — so the menu opens, the // keyboard appears, and the menu immediately closes before the user can // type. The user can still tap the input to focus it intentionally. if (!isCoarsePrimaryInput()) { requestAnimationFrame(function () { input.focus(); input.select(); }); } return wrapper; } function buildColorPicker(tabGroup, palette) { var e_1, _a; var wrapper = document.createElement('div'); wrapper.className = 'dv-context-menu-color-picker'; if (!palette.enabled) { // Opt-out: render no swatches. Returning a wrapper rather than null // keeps the call site simple; the wrapper is empty and visually inert. return wrapper; } var _loop_1 = function (entry) { var swatch = document.createElement('div'); swatch.className = 'dv-context-menu-color-swatch'; // Use a CSS custom property rather than setting `backgroundColor` // directly: the IDL setter validates the value against a color // grammar and rejects `var(...)` references in some environments // (notably jsdom; some browsers have historically had similar // quirks). The matching SCSS rule reads the var at use time. swatch.style.setProperty('--dv-tab-group-color', entry.value); if (entry.label) { swatch.title = entry.label; } if (tabGroup.color === entry.id) { swatch.classList.add('dv-context-menu-color-swatch--selected'); } swatch.addEventListener('click', function () { tabGroup.setColor(entry.id); }); wrapper.appendChild(swatch); }; try { for (var _b = __values(palette.entries()), _c = _b.next(); !_c.done; _c = _b.next()) { var entry = _c.value; _loop_1(entry); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } return wrapper; } var ContextMenuController = /** @class */ (function () { function ContextMenuController(accessor) { this.accessor = accessor; } ContextMenuController.prototype.show = function (panel, group, event) { var e_2, _a; var _b, _c; if (!this.accessor.options.getTabContextMenuItems) { return; } var items = this.accessor.options.getTabContextMenuItems({ panel: panel, group: group, api: this.accessor.api, event: event, }); if (items.length === 0) { return; } event.preventDefault(); var popupService = this.accessor.getPopupServiceForGroup(group); var close = function () { return popupService.close(); }; var menuEl = document.createElement('div'); menuEl.className = 'dv-context-menu'; menuEl.setAttribute('role', 'menu'); var _loop_2 = function (item) { if (item === 'separator') { menuEl.appendChild(buildSeparator()); } else if (item === 'close') { menuEl.appendChild(buildItem('Close', close, function () { return panel.api.close(); })); } else if (item === 'closeOthers') { menuEl.appendChild(buildItem('Close Others', close, function () { group.panels .filter(function (p) { return p !== panel; }) .forEach(function (p) { return p.api.close(); }); })); } else if (item === 'closeAll') { menuEl.appendChild(buildItem('Close All', close, function () { __spreadArray([], __read(group.panels), false).forEach(function (p) { return p.api.close(); }); })); } else if (isItemConfig(item) && item.element) { menuEl.appendChild(item.element); } else if (isItemConfig(item) && item.component) { var renderer = (_c = (_b = this_1.accessor.options).createContextMenuItemComponent) === null || _c === void 0 ? void 0 : _c.call(_b, { id: nextContextMenuItemId(), component: item.component, }); if (renderer) { renderer.init({ panel: panel, group: group, api: this_1.accessor.api, close: close, componentProps: item.componentProps, }); menuEl.appendChild(renderer.element); } } else if (isItemConfig(item) && item.label) { menuEl.appendChild(buildItem(item.label, close, function () { var _a; return (_a = item.action) === null || _a === void 0 ? void 0 : _a.call(item); }, item.disabled)); } }; var this_1 = this; try { for (var items_1 = __values(items), items_1_1 = items_1.next(); !items_1_1.done; items_1_1 = items_1.next()) { var item = items_1_1.value; _loop_2(item); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (items_1_1 && !items_1_1.done && (_a = items_1.return)) _a.call(items_1); } finally { if (e_2) throw e_2.error; } } popupService.openPopover(menuEl, { x: event.clientX, y: event.clientY, zIndex: popoverZIndexFor(event.target), }); }; ContextMenuController.prototype.showForChip = function (tabGroup, group, event) { var e_3, _a; if (!this.accessor.options.getTabGroupChipContextMenuItems) { return; } var items = this.accessor.options.getTabGroupChipContextMenuItems({ tabGroup: tabGroup, group: group, api: this.accessor.api, event: event, }); if (items.length === 0) { return; } event.preventDefault(); var popupService = this.accessor.getPopupServiceForGroup(group); var close = function () { return popupService.close(); }; var menuEl = document.createElement('div'); menuEl.className = 'dv-context-menu'; menuEl.setAttribute('role', 'menu'); var _loop_3 = function (item) { if (item === 'separator') { menuEl.appendChild(buildSeparator()); } else if (item === 'rename') { menuEl.appendChild(buildRenameInput(tabGroup)); } else if (item === 'colorPicker') { menuEl.appendChild(buildColorPicker(tabGroup, this_2.accessor.tabGroupColorPalette)); } else if (isItemConfig(item) && item.element) { menuEl.appendChild(item.element); } else if (isItemConfig(item) && item.label) { menuEl.appendChild(buildItem(item.label, close, function () { var _a; return (_a = item.action) === null || _a === void 0 ? void 0 : _a.call(item); }, item.disabled)); } }; var this_2 = this; try { for (var items_2 = __values(items), items_2_1 = items_2.next(); !items_2_1.done; items_2_1 = items_2.next()) { var item = items_2_1.value; _loop_3(item); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (items_2_1 && !items_2_1.done && (_a = items_2.return)) _a.call(items_2); } finally { if (e_3) throw e_3.error; } } popupService.openPopover(menuEl, { x: event.clientX, y: event.clientY, zIndex: popoverZIndexFor(event.target), }); }; return ContextMenuController; }()); exports.ContextMenuController = ContextMenuController;