dockview-core
Version:
Zero dependency layout manager supporting tabs, groups, grids and splitviews for vanilla TypeScript
254 lines (253 loc) • 12.4 kB
JavaScript
;
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 __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tab = void 0;
var events_1 = require("../../../events");
var lifecycle_1 = require("../../../lifecycle");
var dataTransfer_1 = require("../../../dnd/dataTransfer");
var dom_1 = require("../../../dom");
var backend_1 = require("../../../dnd/backend");
var longPress_1 = require("../../../dnd/pointer/longPress");
var dndCapabilities_1 = require("../../dndCapabilities");
var Tab = /** @class */ (function (_super) {
__extends(Tab, _super);
function Tab(panel, accessor, group) {
var _this = _super.call(this) || this;
_this.panel = panel;
_this.accessor = accessor;
_this.group = group;
_this.content = undefined;
_this.panelTransfer = dataTransfer_1.LocalSelectionTransfer.getInstance();
_this._direction = 'horizontal';
_this._onPointDown = new events_1.Emitter();
_this.onPointerDown = _this._onPointDown.event;
_this._onTabClick = new events_1.Emitter();
_this.onTabClick = _this._onTabClick.event;
_this._onDropped = new events_1.Emitter();
_this.onDrop = _this._onDropped.event;
_this._onDragStart = new events_1.Emitter();
_this.onDragStart = _this._onDragStart.event;
_this._onDragEnd = new events_1.Emitter();
_this.onDragEnd = _this._onDragEnd.event;
var caps = (0, dndCapabilities_1.resolveDndCapabilities)(_this.accessor.options);
_this._element = document.createElement('div');
_this._element.className = 'dv-tab';
_this._element.tabIndex = 0;
_this._element.draggable = caps.html5;
(0, dom_1.toggleClass)(_this.element, 'dv-inactive-tab', true);
var canDisplayOverlay = function (event, position) {
var _a;
if (_this.group.locked) {
return false;
}
var data = (0, dataTransfer_1.getPanelData)();
if (data && _this.accessor.id === data.viewId) {
// Smooth-reorder takes over the in-flight visual when active,
// so individual tab overlays are suppressed for internal drags.
if (((_a = _this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
return false;
}
return true;
}
return _this.group.model.canDisplayOverlay(event, position, 'tab');
};
_this.dropTarget = backend_1.html5Backend.createDropTarget(_this._element, {
acceptedTargetZones: ['left', 'right'],
overlayModel: _this._buildOverlayModel(),
canDisplayOverlay: canDisplayOverlay,
getOverrideTarget: function () { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
});
_this.pointerDropTarget = backend_1.pointerBackend.createDropTarget(_this._element, {
acceptedTargetZones: ['left', 'right'],
overlayModel: _this._buildOverlayModel(),
canDisplayOverlay: canDisplayOverlay,
getOverrideTarget: function () { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
});
var sharedDragOptions = {
getData: function () {
_this.panelTransfer.setData([
new dataTransfer_1.PanelTransfer(_this.accessor.id, _this.group.id, _this.panel.id),
], dataTransfer_1.PanelTransfer.prototype);
return {
dispose: function () {
_this.panelTransfer.clearData(dataTransfer_1.PanelTransfer.prototype);
},
};
},
// 30/-10 matches the HTML5 setDragImage offset that has been
// shipped for years; pointer backend wraps in PointerGhost,
// HTML5 backend feeds into setDragImage.
createGhost: function () { return ({
element: _this._buildGhostElement(),
offsetX: 30,
offsetY: -10,
}); },
onDragStart: function (event) {
var _a;
_this._onDragStart.fire(event);
if (!(event instanceof PointerEvent) &&
((_a = _this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
// Delay collapse to next frame so the browser
// captures the full drag image first.
requestAnimationFrame(function () {
(0, dom_1.toggleClass)(_this.element, 'dv-tab--dragging', true);
});
}
},
onDragEnd: function (event) {
_this._onDragEnd.fire(event);
},
};
_this.html5DragSource = backend_1.html5Backend.createDragSource(_this._element, __assign(__assign({}, sharedDragOptions), { disabled: !caps.html5 }));
_this.pointerDragSource = backend_1.pointerBackend.createDragSource(_this._element, __assign(__assign({}, sharedDragOptions), { disabled: !caps.pointer, touchOnly: !caps.pointerHandlesMouse, isCancelled: function () {
return !(0, dndCapabilities_1.resolveDndCapabilities)(_this.accessor.options).pointer;
} }));
// Both droptargets feed the same downstream stream; consumers don't
// need to know which path produced the overlay.
_this.onWillShowOverlay = events_1.Event.any(_this.dropTarget.onWillShowOverlay, _this.pointerDropTarget.onWillShowOverlay);
_this.addDisposables(_this._onPointDown, _this._onTabClick, _this._onDropped, _this._onDragStart, _this._onDragEnd, _this.accessor.onDidOptionsChange(function () {
var model = _this._buildOverlayModel();
_this.dropTarget.setOverlayModel(model);
_this.pointerDropTarget.setOverlayModel(model);
}), (0, events_1.addDisposableListener)(_this._element, 'dragend', function () {
// The shared onDragEnd handler already fires _onDragEnd via
// the HTML5 backend; just strip the dragging class here.
(0, dom_1.toggleClass)(_this.element, 'dv-tab--dragging', false);
}), _this.html5DragSource, (0, events_1.addDisposableListener)(_this._element, 'pointerdown', function (event) {
_this._onPointDown.fire(event);
}), (0, events_1.addDisposableListener)(_this._element, 'click', function (event) {
_this._onTabClick.fire(event);
}), (0, events_1.addDisposableListener)(_this._element, 'contextmenu', function (event) {
_this.accessor.contextMenuController.show(_this.panel, _this.group, event);
}), new longPress_1.LongPressDetector(_this._element, {
onLongPress: function (event) {
// Don't let a subsequent finger move arm a drag on top
// of the just-opened menu.
_this.pointerDragSource.cancelPending();
_this.accessor.contextMenuController.show(_this.panel, _this.group, event);
},
}), _this.dropTarget.onDrop(function (event) {
_this._onDropped.fire(event);
}), _this.pointerDropTarget.onDrop(function (event) {
_this._onDropped.fire(event);
}), _this.dropTarget, _this.pointerDropTarget, _this.pointerDragSource);
return _this;
}
Object.defineProperty(Tab.prototype, "element", {
get: function () {
return this._element;
},
enumerable: false,
configurable: true
});
Tab.prototype.setActive = function (isActive) {
(0, dom_1.toggleClass)(this.element, 'dv-active-tab', isActive);
(0, dom_1.toggleClass)(this.element, 'dv-inactive-tab', !isActive);
};
Tab.prototype.setContent = function (part) {
if (this.content) {
this._element.removeChild(this.content.element);
}
this.content = part;
this._element.appendChild(this.content.element);
};
Tab.prototype._buildOverlayModel = function () {
var _a;
// 'line' themes render a 4px insertion strip at the tab edge via the
// anchor container's small-boundary path. 'fill' themes render a
// half-width highlighted area, so we disable the small-boundary path
// entirely (boundary = 0 ⟹ isSmall always false).
var smallBoundary = ((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.dndTabIndicator) === 'line'
? Number.POSITIVE_INFINITY
: 0;
return {
activationSize: { value: 50, type: 'percentage' },
smallWidthBoundary: smallBoundary,
smallHeightBoundary: smallBoundary,
};
};
Tab.prototype.setDirection = function (direction) {
this._direction = direction;
var zones = direction === 'vertical' ? ['top', 'bottom'] : ['left', 'right'];
this.dropTarget.setTargetZones(zones);
this.pointerDropTarget.setTargetZones(zones);
};
Tab.prototype.updateDragAndDropState = function () {
var caps = (0, dndCapabilities_1.resolveDndCapabilities)(this.accessor.options);
this._element.draggable = caps.html5;
this.html5DragSource.setDisabled(!caps.html5);
this.pointerDragSource.setDisabled(!caps.pointer);
this.pointerDragSource.setTouchOnly(!caps.pointerHandlesMouse);
};
/**
* Vertical tabs are flipped to horizontal so the ghost stays readable
* during the drag rather than appearing sideways-rotated.
*/
Tab.prototype._buildGhostElement = function () {
var style = getComputedStyle(this.element);
var newNode = this.element.cloneNode(true);
var isVertical = this._direction === 'vertical';
var verticalSkip = new Set([
'writing-mode',
'inline-size',
'block-size',
'min-inline-size',
'min-block-size',
'max-inline-size',
'max-block-size',
'margin-inline',
'margin-inline-start',
'margin-inline-end',
'margin-block',
'margin-block-start',
'margin-block-end',
'padding-inline',
'padding-inline-start',
'padding-inline-end',
'padding-block',
'padding-block-start',
'padding-block-end',
]);
Array.from(style).forEach(function (key) {
if (isVertical && verticalSkip.has(key)) {
return;
}
newNode.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key));
});
if (isVertical) {
newNode.style.setProperty('writing-mode', 'horizontal-tb');
newNode.style.setProperty('width', style.height);
newNode.style.setProperty('height', style.width);
}
newNode.style.position = 'absolute';
newNode.classList.add('dv-tab-ghost-drag');
return newNode;
};
return Tab;
}(lifecycle_1.CompositeDisposable));
exports.Tab = Tab;