dockview-core
Version: 
Zero dependency layout manager supporting tabs, grids and splitviews
1,293 lines (1,284 loc) • 476 kB
JavaScript
/**
 * dockview-core
 * @version 4.9.0
 * @link https://github.com/mathuo/dockview
 * @license MIT
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["dockview-core"] = {}));
})(this, (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) {
            var _a;
            if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.replay) {
                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 shiftAbsoluteElementIntoView(element, root, options = { buffer: 10 }) {
        const buffer = options.buffer;
        const rect = element.getBoundingClientRect();
        const rootRect = root.getBoundingClientRect();
        let translateX = 0;
        let translateY = 0;
        const left = rect.left - rootRect.left;
        const top = rect.top - rootRect.top;
        const bottom = rect.bottom - rootRect.bottom;
        const right = rect.right - rootRect.right;
        // Check horizontal overflow
        if (left < buffer) {
            translateX = buffer - left;
        }
        else if (right > buffer) {
            translateX = -buffer - right;
        }
        // Check vertical overflow
        if (top < buffer) {
            translateY = buffer - top;
        }
        else if (bottom > buffer) {
            translateY = -bottom - buffer;
        }
        // Apply the translation if needed
        if (translateX !== 0 || translateY !== 0) {
            element.style.transform = `translate(${translateX}px, ${translateY}px)`;
        }
    }
    function findRelativeZIndexParent(el) {
        let tmp = el;
        while (tmp && (tmp.style.zIndex === 'auto' || tmp.style.zIndex === '')) {
            tmp = tmp.parentElement;
        }
        return tmp;
    }
    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' &&