UNPKG

dockview-core

Version:

Zero dependency layout manager supporting tabs, grids and splitviews

1,289 lines (1,280 loc) 459 kB
/** * dockview-core * @version 4.2.3 * @link https://github.com/mathuo/dockview * @license MIT */ define(['exports'], (function (exports) { '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 if (this.viewItems.length >= 1) { const sashIndex = Math.max(index - 1, 0); const sashItem = this.sashes.splice(sashIndex, 1)[0]; sashItem.disposable(); } if (!skipLayout) { this.relayout(); } if (sizing && sizing.type === 'distribute') { this.distributeViewSizes(); }