UNPKG

dockview-core

Version:

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

242 lines (241 loc) 10.9 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."); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PointerDragController = void 0; var dom_1 = require("../../dom"); var events_1 = require("../../events"); var lifecycle_1 = require("../../lifecycle"); /** * Singleton — only one pointer-driven drag active at a time. * * State is shared across every Dockview instance on the page. Targets * from instance B receive hit-tests from drags originating in instance A; * that's intentional for cross-instance drops since `LocalSelectionTransfer` * is also process-wide. The corollary is that every Tabs subscriber to * `onDragMove` fires for every pointer drag globally — each subscriber * hit-tests against its own DOM, so this is O(N) per pointermove where N * is the number of registered listeners across all instances. */ var PointerDragController = /** @class */ (function (_super) { __extends(PointerDragController, _super); function PointerDragController() { var _this = _super.call(this) || this; _this._targets = new Set(); /** Kept in sync with `_targets` so hit-testing is allocation-free. */ _this._targetByElement = new Map(); _this._onDragStart = new events_1.Emitter(); _this.onDragStart = _this._onDragStart.event; _this._onDragMove = new events_1.Emitter(); _this.onDragMove = _this._onDragMove.event; _this._onDragEnd = new events_1.Emitter(); _this.onDragEnd = _this._onDragEnd.event; _this.addDisposables(_this._onDragStart, _this._onDragMove, _this._onDragEnd); return _this; } PointerDragController.getInstance = function () { if (!PointerDragController._instance) { PointerDragController._instance = new PointerDragController(); } return PointerDragController._instance; }; Object.defineProperty(PointerDragController.prototype, "active", { get: function () { return this._active; }, enumerable: false, configurable: true }); PointerDragController.prototype.registerTarget = function (target) { var _this = this; this._targets.add(target); this._targetByElement.set(target.element, target); return { dispose: function () { _this._targets.delete(target); if (_this._targetByElement.get(target.element) === target) { _this._targetByElement.delete(target.element); } if (_this._currentTarget === target) { _this._currentTarget = undefined; } }, }; }; PointerDragController.prototype.beginDrag = function (args) { var _this = this; var _a, _b, _c; if (this._active) { this.cancel(); } var pointerEvent = args.pointerEvent, source = args.source; // Call `getData()` before mutating controller state — a throw // here would otherwise leave `_active` populated with no window // listeners installed, blocking every subsequent drag. var dataDisposable = args.getData(); this._active = { pointerId: pointerEvent.pointerId, startX: pointerEvent.clientX, startY: pointerEvent.clientY, source: source, }; this._onDragMoveCallback = args.onDragMove; this._onDragEndCallback = args.onDragEnd; this._dataDisposable = dataDisposable; this._ghost = args.ghost; // Iframes capture pointermove once the cursor crosses into them, // which would freeze the drag from the parent window's POV. this._iframeShield = (0, dom_1.disableIframePointEvents)((_a = source.ownerDocument) !== null && _a !== void 0 ? _a : document); var startEvent = { clientX: pointerEvent.clientX, clientY: pointerEvent.clientY, pointerEvent: pointerEvent, }; this._onDragStart.fire(startEvent); // Source's owning window — popout drags fire on their own window, // not the main one. var targetWindow = (_c = (_b = source.ownerDocument) === null || _b === void 0 ? void 0 : _b.defaultView) !== null && _c !== void 0 ? _c : window; this._moveListener = (0, events_1.addDisposableListener)(targetWindow, 'pointermove', function (e) { if (!_this._active || e.pointerId !== _this._active.pointerId) { return; } _this._handleMove(e); }); this._upListener = (0, events_1.addDisposableListener)(targetWindow, 'pointerup', function (e) { if (!_this._active || e.pointerId !== _this._active.pointerId) { return; } _this._handleEnd(e, true); }); this._cancelListener = (0, events_1.addDisposableListener)(targetWindow, 'pointercancel', function (e) { if (!_this._active || e.pointerId !== _this._active.pointerId) { return; } _this._handleEnd(e, false); }); }; PointerDragController.prototype.cancel = function () { var _a, _b; if (!this._active) { return; } (_a = this._currentTarget) === null || _a === void 0 ? void 0 : _a.handleDragLeave(); this._teardown(); (_b = this._dataDisposable) === null || _b === void 0 ? void 0 : _b.dispose(); this._dataDisposable = undefined; }; PointerDragController.prototype._findTargetUnder = function (x, y) { var e_1, _a; var _b, _c; // `elementsFromPoint` is topmost-first; walk up to find the closest // registered ancestor (so a tab beats the layout-root that contains it). // Use the source's owning document so popout drags hit their own targets. var sourceDoc = (_c = (_b = this._active) === null || _b === void 0 ? void 0 : _b.source.ownerDocument) !== null && _c !== void 0 ? _c : document; var elements = sourceDoc.elementsFromPoint(x, y); try { for (var elements_1 = __values(elements), elements_1_1 = elements_1.next(); !elements_1_1.done; elements_1_1 = elements_1.next()) { var el = elements_1_1.value; var current = el; while (current) { var target = this._targetByElement.get(current); if (target) { return target; } current = current.parentElement; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (elements_1_1 && !elements_1_1.done && (_a = elements_1.return)) _a.call(elements_1); } finally { if (e_1) throw e_1.error; } } return undefined; }; PointerDragController.prototype._handleMove = function (e) { var _a, _b, _c; (_a = this._ghost) === null || _a === void 0 ? void 0 : _a.update(e.clientX, e.clientY); var dragEvent = { clientX: e.clientX, clientY: e.clientY, pointerEvent: e, }; var newTarget = this._findTargetUnder(e.clientX, e.clientY); if (newTarget !== this._currentTarget) { (_b = this._currentTarget) === null || _b === void 0 ? void 0 : _b.handleDragLeave(); this._currentTarget = newTarget; } if (newTarget) { newTarget.handleDragOver(dragEvent); } (_c = this._onDragMoveCallback) === null || _c === void 0 ? void 0 : _c.call(this, dragEvent); this._onDragMove.fire(dragEvent); }; PointerDragController.prototype._handleEnd = function (e, dropped) { var _a; var dragEvent = { clientX: e.clientX, clientY: e.clientY, pointerEvent: e, }; if (dropped && this._currentTarget) { this._currentTarget.handleDrop(dragEvent); } else { (_a = this._currentTarget) === null || _a === void 0 ? void 0 : _a.handleDragLeave(); } var onEnd = this._onDragEndCallback; var dataDisposable = this._dataDisposable; this._teardown(); this._dataDisposable = undefined; // Defer disposal so drop handlers can still read the transfer data. setTimeout(function () { return dataDisposable === null || dataDisposable === void 0 ? void 0 : dataDisposable.dispose(); }, 0); onEnd === null || onEnd === void 0 ? void 0 : onEnd(dragEvent, dropped); this._onDragEnd.fire(dragEvent); }; PointerDragController.prototype._teardown = function () { var _a, _b, _c, _d, _e; this._currentTarget = undefined; this._active = undefined; this._onDragMoveCallback = undefined; this._onDragEndCallback = undefined; (_a = this._ghost) === null || _a === void 0 ? void 0 : _a.dispose(); this._ghost = undefined; (_b = this._iframeShield) === null || _b === void 0 ? void 0 : _b.release(); this._iframeShield = undefined; (_c = this._moveListener) === null || _c === void 0 ? void 0 : _c.dispose(); (_d = this._upListener) === null || _d === void 0 ? void 0 : _d.dispose(); (_e = this._cancelListener) === null || _e === void 0 ? void 0 : _e.dispose(); this._moveListener = undefined; this._upListener = undefined; this._cancelListener = undefined; }; return PointerDragController; }(lifecycle_1.CompositeDisposable)); exports.PointerDragController = PointerDragController;