dockview-react
Version:
Zero dependency layout manager supporting tabs, grids and splitviews
1,282 lines (1,273 loc) • 494 kB
JavaScript
/**
* dockview-react
* @version 4.2.3
* @link https://github.com/mathuo/dockview
* @license MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["dockview-react"] = {}, global.React, global.ReactDOM));
})(this, (function (exports, React, ReactDOM) { 'use strict';
class TransferObject {
}
class PanelTransfer extends TransferObject {
constructor(viewId, groupId, panelId) {
super();
this.viewId = viewId;
this.groupId = groupId;
this.panelId = panelId;
}
}
class PaneTransfer extends TransferObject {
constructor(viewId, paneId) {
super();
this.viewId = viewId;
this.paneId = paneId;
}
}
/**
* A singleton to store transfer data during drag & drop operations that are only valid within the application.
*/
class LocalSelectionTransfer {
constructor() {
// protect against external instantiation
}
static getInstance() {
return LocalSelectionTransfer.INSTANCE;
}
hasData(proto) {
return proto && proto === this.proto;
}
clearData(proto) {
if (this.hasData(proto)) {
this.proto = undefined;
this.data = undefined;
}
}
getData(proto) {
if (this.hasData(proto)) {
return this.data;
}
return undefined;
}
setData(data, proto) {
if (proto) {
this.data = data;
this.proto = proto;
}
}
}
LocalSelectionTransfer.INSTANCE = new LocalSelectionTransfer();
function getPanelData() {
const panelTransfer = LocalSelectionTransfer.getInstance();
const isPanelEvent = panelTransfer.hasData(PanelTransfer.prototype);
if (!isPanelEvent) {
return undefined;
}
return panelTransfer.getData(PanelTransfer.prototype)[0];
}
function getPaneData() {
const paneTransfer = LocalSelectionTransfer.getInstance();
const isPanelEvent = paneTransfer.hasData(PaneTransfer.prototype);
if (!isPanelEvent) {
return undefined;
}
return paneTransfer.getData(PaneTransfer.prototype)[0];
}
exports.DockviewEvent = void 0;
(function (Event) {
Event.any = (...children) => {
return (listener) => {
const disposables = children.map((child) => child(listener));
return {
dispose: () => {
disposables.forEach((d) => {
d.dispose();
});
},
};
};
};
})(exports.DockviewEvent || (exports.DockviewEvent = {}));
class DockviewEvent {
constructor() {
this._defaultPrevented = false;
}
get defaultPrevented() {
return this._defaultPrevented;
}
preventDefault() {
this._defaultPrevented = true;
}
}
class AcceptableEvent {
constructor() {
this._isAccepted = false;
}
get isAccepted() {
return this._isAccepted;
}
accept() {
this._isAccepted = true;
}
}
class LeakageMonitor {
constructor() {
this.events = new Map();
}
get size() {
return this.events.size;
}
add(event, stacktrace) {
this.events.set(event, stacktrace);
}
delete(event) {
this.events.delete(event);
}
clear() {
this.events.clear();
}
}
class Stacktrace {
static create() {
var _a;
return new Stacktrace((_a = new Error().stack) !== null && _a !== void 0 ? _a : '');
}
constructor(value) {
this.value = value;
}
print() {
console.warn('dockview: stacktrace', this.value);
}
}
class Listener {
constructor(callback, stacktrace) {
this.callback = callback;
this.stacktrace = stacktrace;
}
}
// relatively simple event emitter taken from https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts
class Emitter {
static setLeakageMonitorEnabled(isEnabled) {
if (isEnabled !== Emitter.ENABLE_TRACKING) {
Emitter.MEMORY_LEAK_WATCHER.clear();
}
Emitter.ENABLE_TRACKING = isEnabled;
}
get value() {
return this._last;
}
constructor(options) {
this.options = options;
this._listeners = [];
this._disposed = false;
}
get event() {
if (!this._event) {
this._event = (callback) => {
var _a;
if (((_a = this.options) === null || _a === void 0 ? void 0 : _a.replay) && this._last !== undefined) {
callback(this._last);
}
const listener = new Listener(callback, Emitter.ENABLE_TRACKING ? Stacktrace.create() : undefined);
this._listeners.push(listener);
return {
dispose: () => {
const index = this._listeners.indexOf(listener);
if (index > -1) {
this._listeners.splice(index, 1);
}
else if (Emitter.ENABLE_TRACKING) ;
},
};
};
if (Emitter.ENABLE_TRACKING) {
Emitter.MEMORY_LEAK_WATCHER.add(this._event, Stacktrace.create());
}
}
return this._event;
}
fire(e) {
this._last = e;
for (const listener of this._listeners) {
listener.callback(e);
}
}
dispose() {
if (!this._disposed) {
this._disposed = true;
if (this._listeners.length > 0) {
if (Emitter.ENABLE_TRACKING) {
queueMicrotask(() => {
var _a;
// don't check until stack of execution is completed to allow for out-of-order disposals within the same execution block
for (const listener of this._listeners) {
console.warn('dockview: stacktrace', (_a = listener.stacktrace) === null || _a === void 0 ? void 0 : _a.print());
}
});
}
this._listeners = [];
}
if (Emitter.ENABLE_TRACKING && this._event) {
Emitter.MEMORY_LEAK_WATCHER.delete(this._event);
}
}
}
}
Emitter.ENABLE_TRACKING = false;
Emitter.MEMORY_LEAK_WATCHER = new LeakageMonitor();
function addDisposableListener(element, type, listener, options) {
element.addEventListener(type, listener, options);
return {
dispose: () => {
element.removeEventListener(type, listener, options);
},
};
}
/**
*
* Event Emitter that fires events from a Microtask callback, only one event will fire per event-loop cycle.
*
* It's kind of like using an `asapScheduler` in RxJs with additional logic to only fire once per event-loop cycle.
* This implementation exists to avoid external dependencies.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask
* @see https://rxjs.dev/api/index/const/asapScheduler
*/
class AsapEvent {
constructor() {
this._onFired = new Emitter();
this._currentFireCount = 0;
this._queued = false;
this.onEvent = (e) => {
/**
* when the event is first subscribed to take note of the current fire count
*/
const fireCountAtTimeOfEventSubscription = this._currentFireCount;
return this._onFired.event(() => {
/**
* if the current fire count is greater than the fire count at event subscription
* then the event has been fired since we subscribed and it's ok to "on_next" the event.
*
* if the count is not greater then what we are recieving is an event from the microtask
* queue that was triggered before we actually subscribed and therfore we should ignore it.
*/
if (this._currentFireCount > fireCountAtTimeOfEventSubscription) {
e();
}
});
};
}
fire() {
this._currentFireCount++;
if (this._queued) {
return;
}
this._queued = true;
queueMicrotask(() => {
this._queued = false;
this._onFired.fire();
});
}
dispose() {
this._onFired.dispose();
}
}
exports.DockviewDisposable = void 0;
(function (Disposable) {
Disposable.NONE = {
dispose: () => {
// noop
},
};
function from(func) {
return {
dispose: () => {
func();
},
};
}
Disposable.from = from;
})(exports.DockviewDisposable || (exports.DockviewDisposable = {}));
class CompositeDisposable {
get isDisposed() {
return this._isDisposed;
}
constructor(...args) {
this._isDisposed = false;
this._disposables = args;
}
addDisposables(...args) {
args.forEach((arg) => this._disposables.push(arg));
}
dispose() {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._disposables.forEach((arg) => arg.dispose());
this._disposables = [];
}
}
class MutableDisposable {
constructor() {
this._disposable = exports.DockviewDisposable.NONE;
}
set value(disposable) {
if (this._disposable) {
this._disposable.dispose();
}
this._disposable = disposable;
}
dispose() {
if (this._disposable) {
this._disposable.dispose();
this._disposable = exports.DockviewDisposable.NONE;
}
}
}
class OverflowObserver extends CompositeDisposable {
constructor(el) {
super();
this._onDidChange = new Emitter();
this.onDidChange = this._onDidChange.event;
this._value = null;
this.addDisposables(this._onDidChange, watchElementResize(el, (entry) => {
const hasScrollX = entry.target.scrollWidth > entry.target.clientWidth;
const hasScrollY = entry.target.scrollHeight > entry.target.clientHeight;
this._value = { hasScrollX, hasScrollY };
this._onDidChange.fire(this._value);
}));
}
}
function watchElementResize(element, cb) {
const observer = new ResizeObserver((entires) => {
/**
* Fast browser window resize produces Error: ResizeObserver loop limit exceeded.
* The error isn't visible in browser console, doesn't affect functionality, but degrades performance.
* See https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded/58701523#58701523
*/
requestAnimationFrame(() => {
const firstEntry = entires[0];
cb(firstEntry);
});
});
observer.observe(element);
return {
dispose: () => {
observer.unobserve(element);
observer.disconnect();
},
};
}
const removeClasses = (element, ...classes) => {
for (const classname of classes) {
if (element.classList.contains(classname)) {
element.classList.remove(classname);
}
}
};
const addClasses = (element, ...classes) => {
for (const classname of classes) {
if (!element.classList.contains(classname)) {
element.classList.add(classname);
}
}
};
const toggleClass = (element, className, isToggled) => {
const hasClass = element.classList.contains(className);
if (isToggled && !hasClass) {
element.classList.add(className);
}
if (!isToggled && hasClass) {
element.classList.remove(className);
}
};
function isAncestor(testChild, testAncestor) {
while (testChild) {
if (testChild === testAncestor) {
return true;
}
testChild = testChild.parentNode;
}
return false;
}
function trackFocus(element) {
return new FocusTracker(element);
}
/**
* Track focus on an element. Ensure tabIndex is set when an HTMLElement is not focusable by default
*/
class FocusTracker extends CompositeDisposable {
constructor(element) {
super();
this._onDidFocus = new Emitter();
this.onDidFocus = this._onDidFocus.event;
this._onDidBlur = new Emitter();
this.onDidBlur = this._onDidBlur.event;
this.addDisposables(this._onDidFocus, this._onDidBlur);
let hasFocus = isAncestor(document.activeElement, element);
let loosingFocus = false;
const onFocus = () => {
loosingFocus = false;
if (!hasFocus) {
hasFocus = true;
this._onDidFocus.fire();
}
};
const onBlur = () => {
if (hasFocus) {
loosingFocus = true;
window.setTimeout(() => {
if (loosingFocus) {
loosingFocus = false;
hasFocus = false;
this._onDidBlur.fire();
}
}, 0);
}
};
this._refreshStateHandler = () => {
const currentNodeHasFocus = isAncestor(document.activeElement, element);
if (currentNodeHasFocus !== hasFocus) {
if (hasFocus) {
onBlur();
}
else {
onFocus();
}
}
};
this.addDisposables(addDisposableListener(element, 'focus', onFocus, true));
this.addDisposables(addDisposableListener(element, 'blur', onBlur, true));
}
refreshState() {
this._refreshStateHandler();
}
}
// quasi: apparently, but not really; seemingly
const QUASI_PREVENT_DEFAULT_KEY = 'dv-quasiPreventDefault';
// mark an event directly for other listeners to check
function quasiPreventDefault(event) {
event[QUASI_PREVENT_DEFAULT_KEY] = true;
}
// check if this event has been marked
function quasiDefaultPrevented(event) {
return event[QUASI_PREVENT_DEFAULT_KEY];
}
function addStyles(document, styleSheetList) {
const styleSheets = Array.from(styleSheetList);
for (const styleSheet of styleSheets) {
if (styleSheet.href) {
const link = document.createElement('link');
link.href = styleSheet.href;
link.type = styleSheet.type;
link.rel = 'stylesheet';
document.head.appendChild(link);
}
let cssTexts = [];
try {
if (styleSheet.cssRules) {
cssTexts = Array.from(styleSheet.cssRules).map((rule) => rule.cssText);
}
}
catch (err) {
// security errors (lack of permissions), ignore
}
for (const rule of cssTexts) {
const style = document.createElement('style');
style.appendChild(document.createTextNode(rule));
document.head.appendChild(style);
}
}
}
function getDomNodePagePosition(domNode) {
const { left, top, width, height } = domNode.getBoundingClientRect();
return {
left: left + window.scrollX,
top: top + window.scrollY,
width: width,
height: height,
};
}
/**
* Check whether an element is in the DOM (including the Shadow DOM)
* @see https://terodox.tech/how-to-tell-if-an-element-is-in-the-dom-including-the-shadow-dom/
*/
function isInDocument(element) {
let currentElement = element;
while (currentElement === null || currentElement === void 0 ? void 0 : currentElement.parentNode) {
if (currentElement.parentNode === document) {
return true;
}
else if (currentElement.parentNode instanceof DocumentFragment) {
// handle shadow DOMs
currentElement = currentElement.parentNode.host;
}
else {
currentElement = currentElement.parentNode;
}
}
return false;
}
function addTestId(element, id) {
element.setAttribute('data-testid', id);
}
/**
* Should be more efficient than element.querySelectorAll("*") since there
* is no need to store every element in-memory using this approach
*/
function allTagsNamesInclusiveOfShadowDoms(tagNames) {
const iframes = [];
function findIframesInNode(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (tagNames.includes(node.tagName)) {
iframes.push(node);
}
if (node.shadowRoot) {
findIframesInNode(node.shadowRoot);
}
for (const child of node.children) {
findIframesInNode(child);
}
}
}
findIframesInNode(document.documentElement);
return iframes;
}
function disableIframePointEvents(rootNode = document) {
const iframes = allTagsNamesInclusiveOfShadowDoms(['IFRAME', 'WEBVIEW']);
const original = new WeakMap(); // don't hold onto HTMLElement references longer than required
for (const iframe of iframes) {
original.set(iframe, iframe.style.pointerEvents);
iframe.style.pointerEvents = 'none';
}
return {
release: () => {
var _a;
for (const iframe of iframes) {
iframe.style.pointerEvents = (_a = original.get(iframe)) !== null && _a !== void 0 ? _a : 'auto';
}
iframes.splice(0, iframes.length); // don't hold onto HTMLElement references longer than required
},
};
}
function getDockviewTheme(element) {
function toClassList(element) {
const list = [];
for (let i = 0; i < element.classList.length; i++) {
list.push(element.classList.item(i));
}
return list;
}
let theme = undefined;
let parent = element;
while (parent !== null) {
theme = toClassList(parent).find((cls) => cls.startsWith('dockview-theme-'));
if (typeof theme === 'string') {
break;
}
parent = parent.parentElement;
}
return theme;
}
class Classnames {
constructor(element) {
this.element = element;
this._classNames = [];
}
setClassNames(classNames) {
for (const className of this._classNames) {
toggleClass(this.element, className, false);
}
this._classNames = classNames
.split(' ')
.filter((v) => v.trim().length > 0);
for (const className of this._classNames) {
toggleClass(this.element, className, true);
}
}
}
const DEBOUCE_DELAY = 100;
function isChildEntirelyVisibleWithinParent(child, parent) {
//
const childPosition = getDomNodePagePosition(child);
const parentPosition = getDomNodePagePosition(parent);
if (childPosition.left < parentPosition.left) {
return false;
}
if (childPosition.left + childPosition.width >
parentPosition.left + parentPosition.width) {
return false;
}
return true;
}
function onDidWindowMoveEnd(window) {
const emitter = new Emitter();
let previousScreenX = window.screenX;
let previousScreenY = window.screenY;
let timeout;
const checkMovement = () => {
if (window.closed) {
return;
}
const currentScreenX = window.screenX;
const currentScreenY = window.screenY;
if (currentScreenX !== previousScreenX ||
currentScreenY !== previousScreenY) {
clearTimeout(timeout);
timeout = setTimeout(() => {
emitter.fire();
}, DEBOUCE_DELAY);
previousScreenX = currentScreenX;
previousScreenY = currentScreenY;
}
requestAnimationFrame(checkMovement);
};
checkMovement();
return emitter;
}
function onDidWindowResizeEnd(element, cb) {
let resizeTimeout;
const disposable = new CompositeDisposable(addDisposableListener(element, 'resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
cb();
}, DEBOUCE_DELAY);
}));
return disposable;
}
function tail(arr) {
if (arr.length === 0) {
throw new Error('Invalid tail call');
}
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
}
function sequenceEquals(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
/**
* Pushes an element to the start of the array, if found.
*/
function pushToStart(arr, value) {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
arr.unshift(value);
}
}
/**
* Pushes an element to the end of the array, if found.
*/
function pushToEnd(arr, value) {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
arr.push(value);
}
}
function firstIndex(array, fn) {
for (let i = 0; i < array.length; i++) {
const element = array[i];
if (fn(element)) {
return i;
}
}
return -1;
}
function remove(array, value) {
const index = array.findIndex((t) => t === value);
if (index > -1) {
array.splice(index, 1);
return true;
}
return false;
}
const clamp = (value, min, max) => {
if (min > max) {
/**
* caveat: an error should be thrown here if this was a proper `clamp` function but we need to handle
* cases where `min` > `max` and in those cases return `min`.
*/
return min;
}
return Math.min(max, Math.max(value, min));
};
const sequentialNumberGenerator = () => {
let value = 1;
return { next: () => (value++).toString() };
};
const range = (from, to) => {
const result = [];
if (typeof to !== 'number') {
to = from;
from = 0;
}
if (from <= to) {
for (let i = from; i < to; i++) {
result.push(i);
}
}
else {
for (let i = from; i > to; i--) {
result.push(i);
}
}
return result;
};
class ViewItem {
set size(size) {
this._size = size;
}
get size() {
return this._size;
}
get cachedVisibleSize() {
return this._cachedVisibleSize;
}
get visible() {
return typeof this._cachedVisibleSize === 'undefined';
}
get minimumSize() {
return this.visible ? this.view.minimumSize : 0;
}
get viewMinimumSize() {
return this.view.minimumSize;
}
get maximumSize() {
return this.visible ? this.view.maximumSize : 0;
}
get viewMaximumSize() {
return this.view.maximumSize;
}
get priority() {
return this.view.priority;
}
get snap() {
return !!this.view.snap;
}
set enabled(enabled) {
this.container.style.pointerEvents = enabled ? '' : 'none';
}
constructor(container, view, size, disposable) {
this.container = container;
this.view = view;
this.disposable = disposable;
this._cachedVisibleSize = undefined;
if (typeof size === 'number') {
this._size = size;
this._cachedVisibleSize = undefined;
container.classList.add('visible');
}
else {
this._size = 0;
this._cachedVisibleSize = size.cachedVisibleSize;
}
}
setVisible(visible, size) {
var _a;
if (visible === this.visible) {
return;
}
if (visible) {
this.size = clamp((_a = this._cachedVisibleSize) !== null && _a !== void 0 ? _a : 0, this.viewMinimumSize, this.viewMaximumSize);
this._cachedVisibleSize = undefined;
}
else {
this._cachedVisibleSize =
typeof size === 'number' ? size : this.size;
this.size = 0;
}
this.container.classList.toggle('visible', visible);
if (this.view.setVisible) {
this.view.setVisible(visible);
}
}
dispose() {
this.disposable.dispose();
return this.view;
}
}
/*---------------------------------------------------------------------------------------------
* Accreditation: This file is largly based upon the MIT licenced VSCode sourcecode found at:
* https://github.com/microsoft/vscode/tree/main/src/vs/base/browser/ui/splitview
*--------------------------------------------------------------------------------------------*/
exports.Orientation = void 0;
(function (Orientation) {
Orientation["HORIZONTAL"] = "HORIZONTAL";
Orientation["VERTICAL"] = "VERTICAL";
})(exports.Orientation || (exports.Orientation = {}));
exports.SashState = void 0;
(function (SashState) {
SashState[SashState["MAXIMUM"] = 0] = "MAXIMUM";
SashState[SashState["MINIMUM"] = 1] = "MINIMUM";
SashState[SashState["DISABLED"] = 2] = "DISABLED";
SashState[SashState["ENABLED"] = 3] = "ENABLED";
})(exports.SashState || (exports.SashState = {}));
exports.LayoutPriority = void 0;
(function (LayoutPriority) {
LayoutPriority["Low"] = "low";
LayoutPriority["High"] = "high";
LayoutPriority["Normal"] = "normal";
})(exports.LayoutPriority || (exports.LayoutPriority = {}));
exports.Sizing = void 0;
(function (Sizing) {
Sizing.Distribute = { type: 'distribute' };
function Split(index) {
return { type: 'split', index };
}
Sizing.Split = Split;
function Invisible(cachedVisibleSize) {
return { type: 'invisible', cachedVisibleSize };
}
Sizing.Invisible = Invisible;
})(exports.Sizing || (exports.Sizing = {}));
class Splitview {
get contentSize() {
return this._contentSize;
}
get size() {
return this._size;
}
set size(value) {
this._size = value;
}
get orthogonalSize() {
return this._orthogonalSize;
}
set orthogonalSize(value) {
this._orthogonalSize = value;
}
get length() {
return this.viewItems.length;
}
get proportions() {
return this._proportions ? [...this._proportions] : undefined;
}
get orientation() {
return this._orientation;
}
set orientation(value) {
this._orientation = value;
const tmp = this.size;
this.size = this.orthogonalSize;
this.orthogonalSize = tmp;
removeClasses(this.element, 'dv-horizontal', 'dv-vertical');
this.element.classList.add(this.orientation == exports.Orientation.HORIZONTAL
? 'dv-horizontal'
: 'dv-vertical');
}
get minimumSize() {
return this.viewItems.reduce((r, item) => r + item.minimumSize, 0);
}
get maximumSize() {
return this.length === 0
? Number.POSITIVE_INFINITY
: this.viewItems.reduce((r, item) => r + item.maximumSize, 0);
}
get startSnappingEnabled() {
return this._startSnappingEnabled;
}
set startSnappingEnabled(startSnappingEnabled) {
if (this._startSnappingEnabled === startSnappingEnabled) {
return;
}
this._startSnappingEnabled = startSnappingEnabled;
this.updateSashEnablement();
}
get endSnappingEnabled() {
return this._endSnappingEnabled;
}
set endSnappingEnabled(endSnappingEnabled) {
if (this._endSnappingEnabled === endSnappingEnabled) {
return;
}
this._endSnappingEnabled = endSnappingEnabled;
this.updateSashEnablement();
}
get disabled() {
return this._disabled;
}
set disabled(value) {
this._disabled = value;
toggleClass(this.element, 'dv-splitview-disabled', value);
}
get margin() {
return this._margin;
}
set margin(value) {
this._margin = value;
toggleClass(this.element, 'dv-splitview-has-margin', value !== 0);
}
constructor(container, options) {
var _a, _b;
this.container = container;
this.viewItems = [];
this.sashes = [];
this._size = 0;
this._orthogonalSize = 0;
this._contentSize = 0;
this._proportions = undefined;
this._startSnappingEnabled = true;
this._endSnappingEnabled = true;
this._disabled = false;
this._margin = 0;
this._onDidSashEnd = new Emitter();
this.onDidSashEnd = this._onDidSashEnd.event;
this._onDidAddView = new Emitter();
this.onDidAddView = this._onDidAddView.event;
this._onDidRemoveView = new Emitter();
this.onDidRemoveView = this._onDidRemoveView.event;
this.resize = (index, delta, sizes = this.viewItems.map((x) => x.size), lowPriorityIndexes, highPriorityIndexes, overloadMinDelta = Number.NEGATIVE_INFINITY, overloadMaxDelta = Number.POSITIVE_INFINITY, snapBefore, snapAfter) => {
if (index < 0 || index > this.viewItems.length) {
return 0;
}
const upIndexes = range(index, -1);
const downIndexes = range(index + 1, this.viewItems.length);
//
if (highPriorityIndexes) {
for (const i of highPriorityIndexes) {
pushToStart(upIndexes, i);
pushToStart(downIndexes, i);
}
}
if (lowPriorityIndexes) {
for (const i of lowPriorityIndexes) {
pushToEnd(upIndexes, i);
pushToEnd(downIndexes, i);
}
}
//
const upItems = upIndexes.map((i) => this.viewItems[i]);
const upSizes = upIndexes.map((i) => sizes[i]);
//
const downItems = downIndexes.map((i) => this.viewItems[i]);
const downSizes = downIndexes.map((i) => sizes[i]);
//
const minDeltaUp = upIndexes.reduce((_, i) => _ + this.viewItems[i].minimumSize - sizes[i], 0);
const maxDeltaUp = upIndexes.reduce((_, i) => _ + this.viewItems[i].maximumSize - sizes[i], 0);
//
const maxDeltaDown = downIndexes.length === 0
? Number.POSITIVE_INFINITY
: downIndexes.reduce((_, i) => _ + sizes[i] - this.viewItems[i].minimumSize, 0);
const minDeltaDown = downIndexes.length === 0
? Number.NEGATIVE_INFINITY
: downIndexes.reduce((_, i) => _ + sizes[i] - this.viewItems[i].maximumSize, 0);
//
const minDelta = Math.max(minDeltaUp, minDeltaDown);
const maxDelta = Math.min(maxDeltaDown, maxDeltaUp);
//
let snapped = false;
if (snapBefore) {
const snapView = this.viewItems[snapBefore.index];
const visible = delta >= snapBefore.limitDelta;
snapped = visible !== snapView.visible;
snapView.setVisible(visible, snapBefore.size);
}
if (!snapped && snapAfter) {
const snapView = this.viewItems[snapAfter.index];
const visible = delta < snapAfter.limitDelta;
snapped = visible !== snapView.visible;
snapView.setVisible(visible, snapAfter.size);
}
if (snapped) {
return this.resize(index, delta, sizes, lowPriorityIndexes, highPriorityIndexes, overloadMinDelta, overloadMaxDelta);
}
//
const tentativeDelta = clamp(delta, minDelta, maxDelta);
let actualDelta = 0;
//
let deltaUp = tentativeDelta;
for (let i = 0; i < upItems.length; i++) {
const item = upItems[i];
const size = clamp(upSizes[i] + deltaUp, item.minimumSize, item.maximumSize);
const viewDelta = size - upSizes[i];
actualDelta += viewDelta;
deltaUp -= viewDelta;
item.size = size;
}
//
let deltaDown = actualDelta;
for (let i = 0; i < downItems.length; i++) {
const item = downItems[i];
const size = clamp(downSizes[i] - deltaDown, item.minimumSize, item.maximumSize);
const viewDelta = size - downSizes[i];
deltaDown += viewDelta;
item.size = size;
}
//
return delta;
};
this._orientation = (_a = options.orientation) !== null && _a !== void 0 ? _a : exports.Orientation.VERTICAL;
this.element = this.createContainer();
this.margin = (_b = options.margin) !== null && _b !== void 0 ? _b : 0;
this.proportionalLayout =
options.proportionalLayout === undefined
? true
: !!options.proportionalLayout;
this.viewContainer = this.createViewContainer();
this.sashContainer = this.createSashContainer();
this.element.appendChild(this.sashContainer);
this.element.appendChild(this.viewContainer);
this.container.appendChild(this.element);
this.style(options.styles);
// We have an existing set of view, add them now
if (options.descriptor) {
this._size = options.descriptor.size;
options.descriptor.views.forEach((viewDescriptor, index) => {
const sizing = viewDescriptor.visible === undefined ||
viewDescriptor.visible
? viewDescriptor.size
: {
type: 'invisible',
cachedVisibleSize: viewDescriptor.size,
};
const view = viewDescriptor.view;
this.addView(view, sizing, index, true
// true skip layout
);
});
// Initialize content size and proportions for first layout
this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.saveProportions();
}
}
style(styles) {
if ((styles === null || styles === void 0 ? void 0 : styles.separatorBorder) === 'transparent') {
removeClasses(this.element, 'dv-separator-border');
this.element.style.removeProperty('--dv-separator-border');
}
else {
addClasses(this.element, 'dv-separator-border');
if (styles === null || styles === void 0 ? void 0 : styles.separatorBorder) {
this.element.style.setProperty('--dv-separator-border', styles.separatorBorder);
}
}
}
isViewVisible(index) {
if (index < 0 || index >= this.viewItems.length) {
throw new Error('Index out of bounds');
}
const viewItem = this.viewItems[index];
return viewItem.visible;
}
setViewVisible(index, visible) {
if (index < 0 || index >= this.viewItems.length) {
throw new Error('Index out of bounds');
}
const viewItem = this.viewItems[index];
viewItem.setVisible(visible, viewItem.size);
this.distributeEmptySpace(index);
this.layoutViews();
this.saveProportions();
}
getViewSize(index) {
if (index < 0 || index >= this.viewItems.length) {
return -1;
}
return this.viewItems[index].size;
}
resizeView(index, size) {
if (index < 0 || index >= this.viewItems.length) {
return;
}
const indexes = range(this.viewItems.length).filter((i) => i !== index);
const lowPriorityIndexes = [
...indexes.filter((i) => this.viewItems[i].priority === exports.LayoutPriority.Low),
index,
];
const highPriorityIndexes = indexes.filter((i) => this.viewItems[i].priority === exports.LayoutPriority.High);
const item = this.viewItems[index];
size = Math.round(size);
size = clamp(size, item.minimumSize, Math.min(item.maximumSize, this._size));
item.size = size;
this.relayout(lowPriorityIndexes, highPriorityIndexes);
}
getViews() {
return this.viewItems.map((x) => x.view);
}
onDidChange(item, size) {
const index = this.viewItems.indexOf(item);
if (index < 0 || index >= this.viewItems.length) {
return;
}
size = typeof size === 'number' ? size : item.size;
size = clamp(size, item.minimumSize, item.maximumSize);
item.size = size;
const indexes = range(this.viewItems.length).filter((i) => i !== index);
const lowPriorityIndexes = [
...indexes.filter((i) => this.viewItems[i].priority === exports.LayoutPriority.Low),
index,
];
const highPriorityIndexes = indexes.filter((i) => this.viewItems[i].priority === exports.LayoutPriority.High);
/**
* add this view we are changing to the low-index list since we have determined the size
* here and don't want it changed
*/
this.relayout([...lowPriorityIndexes, index], highPriorityIndexes);
}
addView(view, size = { type: 'distribute' }, index = this.viewItems.length, skipLayout) {
const container = document.createElement('div');
container.className = 'dv-view';
container.appendChild(view.element);
let viewSize;
if (typeof size === 'number') {
viewSize = size;
}
else if (size.type === 'split') {
viewSize = this.getViewSize(size.index) / 2;
}
else if (size.type === 'invisible') {
viewSize = { cachedVisibleSize: size.cachedVisibleSize };
}
else {
viewSize = view.minimumSize;
}
const disposable = view.onDidChange((newSize) => this.onDidChange(viewItem, newSize.size));
const viewItem = new ViewItem(container, view, viewSize, {
dispose: () => {
disposable.dispose();
this.viewContainer.removeChild(container);
},
});
if (index === this.viewItems.length) {
this.viewContainer.appendChild(container);
}
else {
this.viewContainer.insertBefore(container, this.viewContainer.children.item(index));
}
this.viewItems.splice(index, 0, viewItem);
if (this.viewItems.length > 1) {
//add sash
const sash = document.createElement('div');
sash.className = 'dv-sash';
const onPointerStart = (event) => {
for (const item of this.viewItems) {
item.enabled = false;
}
const iframes = disableIframePointEvents();
const start = this._orientation === exports.Orientation.HORIZONTAL
? event.clientX
: event.clientY;
const sashIndex = firstIndex(this.sashes, (s) => s.container === sash);
//
const sizes = this.viewItems.map((x) => x.size);
//
let snapBefore;
let snapAfter;
const upIndexes = range(sashIndex, -1);
const downIndexes = range(sashIndex + 1, this.viewItems.length);
const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].minimumSize - sizes[i]), 0);
const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].viewMaximumSize - sizes[i]), 0);
const maxDeltaDown = downIndexes.length === 0
? Number.POSITIVE_INFINITY
: downIndexes.reduce((r, i) => r +
(sizes[i] - this.viewItems[i].minimumSize), 0);
const minDeltaDown = downIndexes.length === 0
? Number.NEGATIVE_INFINITY
: downIndexes.reduce((r, i) => r +
(sizes[i] -
this.viewItems[i].viewMaximumSize), 0);
const minDelta = Math.max(minDeltaUp, minDeltaDown);
const maxDelta = Math.min(maxDeltaDown, maxDeltaUp);
const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
if (typeof snapBeforeIndex === 'number') {
const snappedViewItem = this.viewItems[snapBeforeIndex];
const halfSize = Math.floor(snappedViewItem.viewMinimumSize / 2);
snapBefore = {
index: snapBeforeIndex,
limitDelta: snappedViewItem.visible
? minDelta - halfSize
: minDelta + halfSize,
size: snappedViewItem.size,
};
}
if (typeof snapAfterIndex === 'number') {
const snappedViewItem = this.viewItems[snapAfterIndex];
const halfSize = Math.floor(snappedViewItem.viewMinimumSize / 2);
snapAfter = {
index: snapAfterIndex,
limitDelta: snappedViewItem.visible
? maxDelta + halfSize
: maxDelta - halfSize,
size: snappedViewItem.size,
};
}
const onPointerMove = (event) => {
const current = this._orientation === exports.Orientation.HORIZONTAL
? event.clientX
: event.clientY;
const delta = current - start;
this.resize(sashIndex, delta, sizes, undefined, undefined, minDelta, maxDelta, snapBefore, snapAfter);
this.distributeEmptySpace();
this.layoutViews();
};
const end = () => {
for (const item of this.viewItems) {
item.enabled = true;
}
iframes.release();
this.saveProportions();
document.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('pointerup', end);
document.removeEventListener('pointercancel', end);
this._onDidSashEnd.fire(undefined);
};
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', end);
document.addEventListener('pointercancel', end);
};
sash.addEventListener('pointerdown', onPointerStart);
const sashItem = {
container: sash,
disposable: () => {
sash.removeEventListener('pointerdown', onPointerStart);
this.sashContainer.removeChild(sash);
},
};
this.sashContainer.appendChild(sash);
this.sashes.push(sashItem);
}
if (!skipLayout) {
this.relayout([index]);
}
if (!skipLayout &&
typeof size !== 'number' &&
size.type === 'distribute') {
this.distributeViewSizes();
}
this._onDidAddView.fire(view);
}
distributeViewSizes() {
const flexibleViewItems = [];
let flexibleSize = 0;
for (const item of this.viewItems) {
if (item.maximumSize - item.minimumSize > 0) {
flexibleViewItems.push(item);
flexibleSize += item.size;
}
}
const size = Math.floor(flexibleSize / flexibleViewItems.length);
for (const item of flexibleViewItems) {
item.size = clamp(size, item.minimumSize, item.maximumSize);
}
const indexes = range(this.viewItems.length);
const lowPriorityIndexes = indexes.filter((i) => this.viewItems[i].priority === exports.LayoutPriority.Low);
const highPriorityIndexes = indexes.filter((i) => this.viewItems[i].priority === exports.LayoutPriority.High);
this.relayout(lowPriorityIndexes, highPriorityIndexes);
}
removeView(index, sizing, skipLayout = false) {
// Remove view
const viewItem = this.viewItems.splice(index, 1)[0];
viewItem.dispose();
// Remove sash