UNPKG

@lumino/widgets

Version:
15,977 lines • 546 kB
import { ArrayExt, StringExt, empty, find, max } from '@lumino/algorithm';
import { UUID, JSONExt, MimeData } from '@lumino/coreutils';
import { ElementExt, Selector, Platform } from '@lumino/domutils';
import { Message, ConflatableMessage, MessageLoop } from '@lumino/messaging';
import { AttachedProperty } from '@lumino/properties';
import { Signal } from '@lumino/signaling';
import { Drag } from '@lumino/dragdrop';
import { CommandRegistry } from '@lumino/commands';
import { VirtualDOM, h } from '@lumino/virtualdom';
import { DisposableDelegate } from '@lumino/disposable';
import { getKeyboardLayout } from '@lumino/keyboard';

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A sizer object for use with the box engine layout functions.
 *
 * #### Notes
 * A box sizer holds the geometry information for an object along an
 * arbitrary layout orientation.
 *
 * For best performance, this class should be treated as a raw data
 * struct. It should not typically be subclassed.
 */
class BoxSizer {
    constructor() {
        /**
         * The preferred size for the sizer.
         *
         * #### Notes
         * The sizer will be given this initial size subject to its size
         * bounds. The sizer will not deviate from this size unless such
         * deviation is required to fit into the available layout space.
         *
         * There is no limit to this value, but it will be clamped to the
         * bounds defined by {@link minSize} and {@link maxSize}.
         *
         * The default value is `0`.
         */
        this.sizeHint = 0;
        /**
         * The minimum size of the sizer.
         *
         * #### Notes
         * The sizer will never be sized less than this value, even if
         * it means the sizer will overflow the available layout space.
         *
         * It is assumed that this value lies in the range `[0, Infinity)`
         * and that it is `<=` to {@link maxSize}. Failure to adhere to this
         * constraint will yield undefined results.
         *
         * The default value is `0`.
         */
        this.minSize = 0;
        /**
         * The maximum size of the sizer.
         *
         * #### Notes
         * The sizer will never be sized greater than this value, even if
         * it means the sizer will underflow the available layout space.
         *
         * It is assumed that this value lies in the range `[0, Infinity]`
         * and that it is `>=` to {@link minSize}. Failure to adhere to this
         * constraint will yield undefined results.
         *
         * The default value is `Infinity`.
         */
        this.maxSize = Infinity;
        /**
         * The stretch factor for the sizer.
         *
         * #### Notes
         * This controls how much the sizer stretches relative to its sibling
         * sizers when layout space is distributed. A stretch factor of zero
         * is special and will cause the sizer to only be resized after all
         * other sizers with a stretch factor greater than zero have been
         * resized to their limits.
         *
         * It is assumed that this value is an integer that lies in the range
         * `[0, Infinity)`. Failure to adhere to this constraint will yield
         * undefined results.
         *
         * The default value is `1`.
         */
        this.stretch = 1;
        /**
         * The computed size of the sizer.
         *
         * #### Notes
         * This value is the output of a call to {@link BoxEngine.calc}. It represents
         * the computed size for the object along the layout orientation,
         * and will always lie in the range `[minSize, maxSize]`.
         *
         * This value is output only.
         *
         * Changing this value will have no effect.
         */
        this.size = 0;
        /**
         * An internal storage property for the layout algorithm.
         *
         * #### Notes
         * This value is used as temporary storage by the layout algorithm.
         *
         * Changing this value will have no effect.
         */
        this.done = false;
    }
}
/**
 * The namespace for the box engine layout functions.
 */
var BoxEngine;
(function (BoxEngine) {
    /**
     * Calculate the optimal layout sizes for a sequence of box sizers.
     *
     * This distributes the available layout space among the box sizers
     * according to the following algorithm:
     *
     * 1. Initialize the sizers's size to its size hint and compute the
     *    sums for each of size hint, min size, and max size.
     *
     * 2. If the total size hint equals the available space, return.
     *
     * 3. If the available space is less than the total min size, set all
     *    sizers to their min size and return.
     *
     * 4. If the available space is greater than the total max size, set
     *    all sizers to their max size and return.
     *
     * 5. If the layout space is less than the total size hint, distribute
     *    the negative delta as follows:
     *
     *    a. Shrink each sizer with a stretch factor greater than zero by
     *       an amount proportional to the negative space and the sum of
     *       stretch factors. If the sizer reaches its min size, remove
     *       it and its stretch factor from the computation.
     *
     *    b. If after adjusting all stretch sizers there remains negative
     *       space, distribute the space equally among the sizers with a
     *       stretch factor of zero. If a sizer reaches its min size,
     *       remove it from the computation.
     *
     * 6. If the layout space is greater than the total size hint,
     *    distribute the positive delta as follows:
     *
     *    a. Expand each sizer with a stretch factor greater than zero by
     *       an amount proportional to the postive space and the sum of
     *       stretch factors. If the sizer reaches its max size, remove
     *       it and its stretch factor from the computation.
     *
     *    b. If after adjusting all stretch sizers there remains positive
     *       space, distribute the space equally among the sizers with a
     *       stretch factor of zero. If a sizer reaches its max size,
     *       remove it from the computation.
     *
     * 7. return
     *
     * @param sizers - The sizers for a particular layout line.
     *
     * @param space - The available layout space for the sizers.
     *
     * @returns The delta between the provided available space and the
     *   actual consumed space. This value will be zero if the sizers
     *   can be adjusted to fit, negative if the available space is too
     *   small, and positive if the available space is too large.
     *
     * #### Notes
     * The {@link BoxSizer.size} of each sizer is updated with the computed size.
     *
     * This function can be called at any time to recompute the layout for
     * an existing sequence of sizers. The previously computed results will
     * have no effect on the new output. It is therefore not necessary to
     * create new sizer objects on each resize event.
     */
    function calc(sizers, space) {
        // Bail early if there is nothing to do.
        let count = sizers.length;
        if (count === 0) {
            return space;
        }
        // Setup the size and stretch counters.
        let totalMin = 0;
        let totalMax = 0;
        let totalSize = 0;
        let totalStretch = 0;
        let stretchCount = 0;
        // Setup the sizers and compute the totals.
        for (let i = 0; i < count; ++i) {
            let sizer = sizers[i];
            let min = sizer.minSize;
            let max = sizer.maxSize;
            let hint = sizer.sizeHint;
            sizer.done = false;
            sizer.size = Math.max(min, Math.min(hint, max));
            totalSize += sizer.size;
            totalMin += min;
            totalMax += max;
            if (sizer.stretch > 0) {
                totalStretch += sizer.stretch;
                stretchCount++;
            }
        }
        // If the space is equal to the total size, return early.
        if (space === totalSize) {
            return 0;
        }
        // If the space is less than the total min, minimize each sizer.
        if (space <= totalMin) {
            for (let i = 0; i < count; ++i) {
                let sizer = sizers[i];
                sizer.size = sizer.minSize;
            }
            return space - totalMin;
        }
        // If the space is greater than the total max, maximize each sizer.
        if (space >= totalMax) {
            for (let i = 0; i < count; ++i) {
                let sizer = sizers[i];
                sizer.size = sizer.maxSize;
            }
            return space - totalMax;
        }
        // The loops below perform sub-pixel precision sizing. A near zero
        // value is used for compares instead of zero to ensure that the
        // loop terminates when the subdivided space is reasonably small.
        let nearZero = 0.01;
        // A counter which is decremented each time a sizer is resized to
        // its limit. This ensures the loops terminate even if there is
        // space remaining to distribute.
        let notDoneCount = count;
        // Distribute negative delta space.
        if (space < totalSize) {
            // Shrink each stretchable sizer by an amount proportional to its
            // stretch factor. If a sizer reaches its min size it's marked as
            // done. The loop progresses in phases where each sizer is given
            // a chance to consume its fair share for the pass, regardless of
            // whether a sizer before it reached its limit. This continues
            // until the stretchable sizers or the free space is exhausted.
            let freeSpace = totalSize - space;
            while (stretchCount > 0 && freeSpace > nearZero) {
                let distSpace = freeSpace;
                let distStretch = totalStretch;
                for (let i = 0; i < count; ++i) {
                    let sizer = sizers[i];
                    if (sizer.done || sizer.stretch === 0) {
                        continue;
                    }
                    let amt = (sizer.stretch * distSpace) / distStretch;
                    if (sizer.size - amt <= sizer.minSize) {
                        freeSpace -= sizer.size - sizer.minSize;
                        totalStretch -= sizer.stretch;
                        sizer.size = sizer.minSize;
                        sizer.done = true;
                        notDoneCount--;
                        stretchCount--;
                    }
                    else {
                        freeSpace -= amt;
                        sizer.size -= amt;
                    }
                }
            }
            // Distribute any remaining space evenly among the non-stretchable
            // sizers. This progresses in phases in the same manner as above.
            while (notDoneCount > 0 && freeSpace > nearZero) {
                let amt = freeSpace / notDoneCount;
                for (let i = 0; i < count; ++i) {
                    let sizer = sizers[i];
                    if (sizer.done) {
                        continue;
                    }
                    if (sizer.size - amt <= sizer.minSize) {
                        freeSpace -= sizer.size - sizer.minSize;
                        sizer.size = sizer.minSize;
                        sizer.done = true;
                        notDoneCount--;
                    }
                    else {
                        freeSpace -= amt;
                        sizer.size -= amt;
                    }
                }
            }
        }
        // Distribute positive delta space.
        else {
            // Expand each stretchable sizer by an amount proportional to its
            // stretch factor. If a sizer reaches its max size it's marked as
            // done. The loop progresses in phases where each sizer is given
            // a chance to consume its fair share for the pass, regardless of
            // whether a sizer before it reached its limit. This continues
            // until the stretchable sizers or the free space is exhausted.
            let freeSpace = space - totalSize;
            while (stretchCount > 0 && freeSpace > nearZero) {
                let distSpace = freeSpace;
                let distStretch = totalStretch;
                for (let i = 0; i < count; ++i) {
                    let sizer = sizers[i];
                    if (sizer.done || sizer.stretch === 0) {
                        continue;
                    }
                    let amt = (sizer.stretch * distSpace) / distStretch;
                    if (sizer.size + amt >= sizer.maxSize) {
                        freeSpace -= sizer.maxSize - sizer.size;
                        totalStretch -= sizer.stretch;
                        sizer.size = sizer.maxSize;
                        sizer.done = true;
                        notDoneCount--;
                        stretchCount--;
                    }
                    else {
                        freeSpace -= amt;
                        sizer.size += amt;
                    }
                }
            }
            // Distribute any remaining space evenly among the non-stretchable
            // sizers. This progresses in phases in the same manner as above.
            while (notDoneCount > 0 && freeSpace > nearZero) {
                let amt = freeSpace / notDoneCount;
                for (let i = 0; i < count; ++i) {
                    let sizer = sizers[i];
                    if (sizer.done) {
                        continue;
                    }
                    if (sizer.size + amt >= sizer.maxSize) {
                        freeSpace -= sizer.maxSize - sizer.size;
                        sizer.size = sizer.maxSize;
                        sizer.done = true;
                        notDoneCount--;
                    }
                    else {
                        freeSpace -= amt;
                        sizer.size += amt;
                    }
                }
            }
        }
        // Indicate that the consumed space equals the available space.
        return 0;
    }
    BoxEngine.calc = calc;
    /**
     * Adjust a sizer by a delta and update its neighbors accordingly.
     *
     * @param sizers - The sizers which should be adjusted.
     *
     * @param index - The index of the sizer to grow.
     *
     * @param delta - The amount to adjust the sizer, positive or negative.
     *
     * #### Notes
     * This will adjust the indicated sizer by the specified amount, along
     * with the sizes of the appropriate neighbors, subject to the limits
     * specified by each of the sizers.
     *
     * This is useful when implementing box layouts where the boundaries
     * between the sizers are interactively adjustable by the user.
     */
    function adjust(sizers, index, delta) {
        // Bail early when there is nothing to do.
        if (sizers.length === 0 || delta === 0) {
            return;
        }
        // Dispatch to the proper implementation.
        if (delta > 0) {
            growSizer(sizers, index, delta);
        }
        else {
            shrinkSizer(sizers, index, -delta);
        }
    }
    BoxEngine.adjust = adjust;
    /**
     * Grow a sizer by a positive delta and adjust neighbors.
     */
    function growSizer(sizers, index, delta) {
        // Compute how much the items to the left can expand.
        let growLimit = 0;
        for (let i = 0; i <= index; ++i) {
            let sizer = sizers[i];
            growLimit += sizer.maxSize - sizer.size;
        }
        // Compute how much the items to the right can shrink.
        let shrinkLimit = 0;
        for (let i = index + 1, n = sizers.length; i < n; ++i) {
            let sizer = sizers[i];
            shrinkLimit += sizer.size - sizer.minSize;
        }
        // Clamp the delta adjustment to the limits.
        delta = Math.min(delta, growLimit, shrinkLimit);
        // Grow the sizers to the left by the delta.
        let grow = delta;
        for (let i = index; i >= 0 && grow > 0; --i) {
            let sizer = sizers[i];
            let limit = sizer.maxSize - sizer.size;
            if (limit >= grow) {
                sizer.sizeHint = sizer.size + grow;
                grow = 0;
            }
            else {
                sizer.sizeHint = sizer.size + limit;
                grow -= limit;
            }
        }
        // Shrink the sizers to the right by the delta.
        let shrink = delta;
        for (let i = index + 1, n = sizers.length; i < n && shrink > 0; ++i) {
            let sizer = sizers[i];
            let limit = sizer.size - sizer.minSize;
            if (limit >= shrink) {
                sizer.sizeHint = sizer.size - shrink;
                shrink = 0;
            }
            else {
                sizer.sizeHint = sizer.size - limit;
                shrink -= limit;
            }
        }
    }
    /**
     * Shrink a sizer by a positive delta and adjust neighbors.
     */
    function shrinkSizer(sizers, index, delta) {
        // Compute how much the items to the right can expand.
        let growLimit = 0;
        for (let i = index + 1, n = sizers.length; i < n; ++i) {
            let sizer = sizers[i];
            growLimit += sizer.maxSize - sizer.size;
        }
        // Compute how much the items to the left can shrink.
        let shrinkLimit = 0;
        for (let i = 0; i <= index; ++i) {
            let sizer = sizers[i];
            shrinkLimit += sizer.size - sizer.minSize;
        }
        // Clamp the delta adjustment to the limits.
        delta = Math.min(delta, growLimit, shrinkLimit);
        // Grow the sizers to the right by the delta.
        let grow = delta;
        for (let i = index + 1, n = sizers.length; i < n && grow > 0; ++i) {
            let sizer = sizers[i];
            let limit = sizer.maxSize - sizer.size;
            if (limit >= grow) {
                sizer.sizeHint = sizer.size + grow;
                grow = 0;
            }
            else {
                sizer.sizeHint = sizer.size + limit;
                grow -= limit;
            }
        }
        // Shrink the sizers to the left by the delta.
        let shrink = delta;
        for (let i = index; i >= 0 && shrink > 0; --i) {
            let sizer = sizers[i];
            let limit = sizer.size - sizer.minSize;
            if (limit >= shrink) {
                sizer.sizeHint = sizer.size - shrink;
                shrink = 0;
            }
            else {
                sizer.sizeHint = sizer.size - limit;
                shrink -= limit;
            }
        }
    }
})(BoxEngine || (BoxEngine = {}));

/**
 * An object which holds data related to an object's title.
 *
 * #### Notes
 * A title object is intended to hold the data necessary to display a
 * header for a particular object. A common example is the `TabPanel`,
 * which uses the widget title to populate the tab for a child widget.
 *
 * It is the responsibility of the owner to call the title disposal.
 */
class Title {
    /**
     * Construct a new title.
     *
     * @param options - The options for initializing the title.
     */
    constructor(options) {
        this._label = '';
        this._caption = '';
        this._mnemonic = -1;
        this._icon = undefined;
        this._iconClass = '';
        this._iconLabel = '';
        this._className = '';
        this._closable = false;
        this._changed = new Signal(this);
        this._isDisposed = false;
        this.owner = options.owner;
        if (options.label !== undefined) {
            this._label = options.label;
        }
        if (options.mnemonic !== undefined) {
            this._mnemonic = options.mnemonic;
        }
        if (options.icon !== undefined) {
            this._icon = options.icon;
        }
        if (options.iconClass !== undefined) {
            this._iconClass = options.iconClass;
        }
        if (options.iconLabel !== undefined) {
            this._iconLabel = options.iconLabel;
        }
        if (options.caption !== undefined) {
            this._caption = options.caption;
        }
        if (options.className !== undefined) {
            this._className = options.className;
        }
        if (options.closable !== undefined) {
            this._closable = options.closable;
        }
        this._dataset = options.dataset || {};
    }
    /**
     * A signal emitted when the state of the title changes.
     */
    get changed() {
        return this._changed;
    }
    /**
     * Get the label for the title.
     *
     * #### Notes
     * The default value is an empty string.
     */
    get label() {
        return this._label;
    }
    /**
     * Set the label for the title.
     */
    set label(value) {
        if (this._label === value) {
            return;
        }
        this._label = value;
        this._changed.emit(undefined);
    }
    /**
     * Get the mnemonic index for the title.
     *
     * #### Notes
     * The default value is `-1`.
     */
    get mnemonic() {
        return this._mnemonic;
    }
    /**
     * Set the mnemonic index for the title.
     */
    set mnemonic(value) {
        if (this._mnemonic === value) {
            return;
        }
        this._mnemonic = value;
        this._changed.emit(undefined);
    }
    /**
     * Get the icon renderer for the title.
     *
     * #### Notes
     * The default value is undefined.
     */
    get icon() {
        return this._icon;
    }
    /**
     * Set the icon renderer for the title.
     *
     * #### Notes
     * A renderer is an object that supplies a render and unrender function.
     */
    set icon(value) {
        if (this._icon === value) {
            return;
        }
        this._icon = value;
        this._changed.emit(undefined);
    }
    /**
     * Get the icon class name for the title.
     *
     * #### Notes
     * The default value is an empty string.
     */
    get iconClass() {
        return this._iconClass;
    }
    /**
     * Set the icon class name for the title.
     *
     * #### Notes
     * Multiple class names can be separated with whitespace.
     */
    set iconClass(value) {
        if (this._iconClass === value) {
            return;
        }
        this._iconClass = value;
        this._changed.emit(undefined);
    }
    /**
     * Get the icon label for the title.
     *
     * #### Notes
     * The default value is an empty string.
     */
    get iconLabel() {
        return this._iconLabel;
    }
    /**
     * Set the icon label for the title.
     *
     * #### Notes
     * Multiple class names can be separated with whitespace.
     */
    set iconLabel(value) {
        if (this._iconLabel === value) {
            return;
        }
        this._iconLabel = value;
        this._changed.emit(undefined);
    }
    /**
     * Get the caption for the title.
     *
     * #### Notes
     * The default value is an empty string.
     */
    get caption() {
        return this._caption;
    }
    /**
     * Set the caption for the title.
     */
    set caption(value) {
        if (this._caption === value) {
            return;
        }
        this._caption = value;
        this._changed.emit(undefined);
    }
    /**
     * Get the extra class name for the title.
     *
     * #### Notes
     * The default value is an empty string.
     */
    get className() {
        return this._className;
    }
    /**
     * Set the extra class name for the title.
     *
     * #### Notes
     * Multiple class names can be separated with whitespace.
     */
    set className(value) {
        if (this._className === value) {
            return;
        }
        this._className = value;
        this._changed.emit(undefined);
    }
    /**
     * Get the closable state for the title.
     *
     * #### Notes
     * The default value is `false`.
     */
    get closable() {
        return this._closable;
    }
    /**
     * Set the closable state for the title.
     *
     * #### Notes
     * This controls the presence of a close icon when applicable.
     */
    set closable(value) {
        if (this._closable === value) {
            return;
        }
        this._closable = value;
        this._changed.emit(undefined);
    }
    /**
     * Get the dataset for the title.
     *
     * #### Notes
     * The default value is an empty dataset.
     */
    get dataset() {
        return this._dataset;
    }
    /**
     * Set the dataset for the title.
     *
     * #### Notes
     * This controls the data attributes when applicable.
     */
    set dataset(value) {
        if (this._dataset === value) {
            return;
        }
        this._dataset = value;
        this._changed.emit(undefined);
    }
    /**
     * Test whether the title has been disposed.
     */
    get isDisposed() {
        return this._isDisposed;
    }
    /**
     * Dispose of the resources held by the title.
     *
     * #### Notes
     * It is the responsibility of the owner to call the title disposal.
     */
    dispose() {
        if (this.isDisposed) {
            return;
        }
        this._isDisposed = true;
        Signal.clearData(this);
    }
}

/**
 * The base class of the lumino widget hierarchy.
 *
 * #### Notes
 * This class will typically be subclassed in order to create a useful
 * widget. However, it can be used directly to host externally created
 * content.
 */
class Widget {
    /**
     * Construct a new widget.
     *
     * @param options - The options for initializing the widget.
     */
    constructor(options = {}) {
        this._flags = 0;
        this._layout = null;
        this._parent = null;
        this._disposed = new Signal(this);
        this._hiddenMode = Widget.HiddenMode.Display;
        this.node = Private$j.createNode(options);
        this.addClass('lm-Widget');
    }
    /**
     * Dispose of the widget and its descendant widgets.
     *
     * #### Notes
     * It is unsafe to use the widget after it has been disposed.
     *
     * All calls made to this method after the first are a no-op.
     */
    dispose() {
        // Do nothing if the widget is already disposed.
        if (this.isDisposed) {
            return;
        }
        // Set the disposed flag and emit the disposed signal.
        this.setFlag(Widget.Flag.IsDisposed);
        this._disposed.emit(undefined);
        // Remove or detach the widget if necessary.
        if (this.parent) {
            this.parent = null;
        }
        else if (this.isAttached) {
            Widget.detach(this);
        }
        // Dispose of the widget layout.
        if (this._layout) {
            this._layout.dispose();
            this._layout = null;
        }
        // Dispose the title
        this.title.dispose();
        // Clear the extra data associated with the widget.
        Signal.clearData(this);
        MessageLoop.clearData(this);
        AttachedProperty.clearData(this);
    }
    /**
     * A signal emitted when the widget is disposed.
     */
    get disposed() {
        return this._disposed;
    }
    /**
     * Test whether the widget has been disposed.
     */
    get isDisposed() {
        return this.testFlag(Widget.Flag.IsDisposed);
    }
    /**
     * Test whether the widget's node is attached to the DOM.
     */
    get isAttached() {
        return this.testFlag(Widget.Flag.IsAttached);
    }
    /**
     * Test whether the widget is explicitly hidden.
     *
     * #### Notes
     * You should prefer `!{@link isVisible}` over `{@link isHidden}` if you want to know if the
     * widget is hidden as this does not test if the widget is hidden because one of its ancestors is hidden.
     */
    get isHidden() {
        return this.testFlag(Widget.Flag.IsHidden);
    }
    /**
     * Test whether the widget is visible.
     *
     * #### Notes
     * A widget is visible when it is attached to the DOM, is not
     * explicitly hidden, and has no explicitly hidden ancestors.
     *
     * Since 2.7.0, this does not rely on the {@link Widget.Flag.IsVisible} flag.
     * It recursively checks the visibility of all parent widgets.
     */
    get isVisible() {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        let parent = this;
        do {
            if (parent.isHidden || !parent.isAttached) {
                return false;
            }
            parent = parent.parent;
        } while (parent != null);
        return true;
    }
    /**
     * The title object for the widget.
     *
     * #### Notes
     * The title object is used by some container widgets when displaying
     * the widget alongside some title, such as a tab panel or side bar.
     *
     * Since not all widgets will use the title, it is created on demand.
     *
     * The `owner` property of the title is set to this widget.
     */
    get title() {
        return Private$j.titleProperty.get(this);
    }
    /**
     * Get the id of the widget's DOM node.
     */
    get id() {
        return this.node.id;
    }
    /**
     * Set the id of the widget's DOM node.
     */
    set id(value) {
        this.node.id = value;
    }
    /**
     * The dataset for the widget's DOM node.
     */
    get dataset() {
        return this.node.dataset;
    }
    /**
     * Get the method for hiding the widget.
     */
    get hiddenMode() {
        return this._hiddenMode;
    }
    /**
     * Set the method for hiding the widget.
     */
    set hiddenMode(value) {
        if (this._hiddenMode === value) {
            return;
        }
        if (this.isHidden) {
            // Reset styles set by previous mode.
            this._toggleHidden(false);
        }
        if (value == Widget.HiddenMode.Scale) {
            this.node.style.willChange = 'transform';
        }
        else {
            this.node.style.willChange = 'auto';
        }
        this._hiddenMode = value;
        if (this.isHidden) {
            // Set styles for new mode.
            this._toggleHidden(true);
        }
    }
    /**
     * Get the parent of the widget.
     */
    get parent() {
        return this._parent;
    }
    /**
     * Set the parent of the widget.
     *
     * #### Notes
     * Children are typically added to a widget by using a layout, which
     * means user code will not normally set the parent widget directly.
     *
     * The widget will be automatically removed from its old parent.
     *
     * This is a no-op if there is no effective parent change.
     */
    set parent(value) {
        if (this._parent === value) {
            return;
        }
        if (value && this.contains(value)) {
            throw new Error('Invalid parent widget.');
        }
        if (this._parent && !this._parent.isDisposed) {
            let msg = new Widget.ChildMessage('child-removed', this);
            MessageLoop.sendMessage(this._parent, msg);
        }
        this._parent = value;
        if (this._parent && !this._parent.isDisposed) {
            let msg = new Widget.ChildMessage('child-added', this);
            MessageLoop.sendMessage(this._parent, msg);
        }
        if (!this.isDisposed) {
            MessageLoop.sendMessage(this, Widget.Msg.ParentChanged);
        }
    }
    /**
     * Get the layout for the widget.
     */
    get layout() {
        return this._layout;
    }
    /**
     * Set the layout for the widget.
     *
     * #### Notes
     * The layout is single-use only. It cannot be changed after the
     * first assignment.
     *
     * The layout is disposed automatically when the widget is disposed.
     */
    set layout(value) {
        if (this._layout === value) {
            return;
        }
        if (this.testFlag(Widget.Flag.DisallowLayout)) {
            throw new Error('Cannot set widget layout.');
        }
        if (this._layout) {
            throw new Error('Cannot change widget layout.');
        }
        if (value.parent) {
            throw new Error('Cannot change layout parent.');
        }
        this._layout = value;
        value.parent = this;
    }
    /**
     * Create an iterator over the widget's children.
     *
     * @returns A new iterator over the children of the widget.
     *
     * #### Notes
     * The widget must have a populated layout in order to have children.
     *
     * If a layout is not installed, the returned iterator will be empty.
     */
    *children() {
        if (this._layout) {
            yield* this._layout;
        }
    }
    /**
     * Test whether a widget is a descendant of this widget.
     *
     * @param widget - The descendant widget of interest.
     *
     * @returns `true` if the widget is a descendant, `false` otherwise.
     */
    contains(widget) {
        for (let value = widget; value; value = value._parent) {
            if (value === this) {
                return true;
            }
        }
        return false;
    }
    /**
     * Test whether the widget's DOM node has the given class name.
     *
     * @param name - The class name of interest.
     *
     * @returns `true` if the node has the class, `false` otherwise.
     */
    hasClass(name) {
        return this.node.classList.contains(name);
    }
    /**
     * Add a class name to the widget's DOM node.
     *
     * @param name - The class name to add to the node.
     *
     * #### Notes
     * If the class name is already added to the node, this is a no-op.
     *
     * The class name must not contain whitespace.
     */
    addClass(name) {
        this.node.classList.add(name);
    }
    /**
     * Remove a class name from the widget's DOM node.
     *
     * @param name - The class name to remove from the node.
     *
     * #### Notes
     * If the class name is not yet added to the node, this is a no-op.
     *
     * The class name must not contain whitespace.
     */
    removeClass(name) {
        this.node.classList.remove(name);
    }
    /**
     * Toggle a class name on the widget's DOM node.
     *
     * @param name - The class name to toggle on the node.
     *
     * @param force - Whether to force add the class (`true`) or force
     *   remove the class (`false`). If not provided, the presence of
     *   the class will be toggled from its current state.
     *
     * @returns `true` if the class is now present, `false` otherwise.
     *
     * #### Notes
     * The class name must not contain whitespace.
     */
    toggleClass(name, force) {
        if (force === true) {
            this.node.classList.add(name);
            return true;
        }
        if (force === false) {
            this.node.classList.remove(name);
            return false;
        }
        return this.node.classList.toggle(name);
    }
    /**
     * Post an `'update-request'` message to the widget.
     *
     * #### Notes
     * This is a simple convenience method for posting the message.
     */
    update() {
        MessageLoop.postMessage(this, Widget.Msg.UpdateRequest);
    }
    /**
     * Post a `'fit-request'` message to the widget.
     *
     * #### Notes
     * This is a simple convenience method for posting the message.
     */
    fit() {
        MessageLoop.postMessage(this, Widget.Msg.FitRequest);
    }
    /**
     * Post an `'activate-request'` message to the widget.
     *
     * #### Notes
     * This is a simple convenience method for posting the message.
     */
    activate() {
        MessageLoop.postMessage(this, Widget.Msg.ActivateRequest);
    }
    /**
     * Send a `'close-request'` message to the widget.
     *
     * #### Notes
     * This is a simple convenience method for sending the message.
     */
    close() {
        MessageLoop.sendMessage(this, Widget.Msg.CloseRequest);
    }
    /**
     * Show the widget and make it visible to its parent widget.
     *
     * #### Notes
     * This causes the {@link isHidden} property to be `false`.
     *
     * If the widget is not explicitly hidden, this is a no-op.
     */
    show() {
        if (!this.testFlag(Widget.Flag.IsHidden)) {
            return;
        }
        if (this.isAttached && (!this.parent || this.parent.isVisible)) {
            MessageLoop.sendMessage(this, Widget.Msg.BeforeShow);
        }
        this.clearFlag(Widget.Flag.IsHidden);
        this._toggleHidden(false);
        if (this.isAttached && (!this.parent || this.parent.isVisible)) {
            MessageLoop.sendMessage(this, Widget.Msg.AfterShow);
        }
        if (this.parent) {
            let msg = new Widget.ChildMessage('child-shown', this);
            MessageLoop.sendMessage(this.parent, msg);
        }
    }
    /**
     * Hide the widget and make it hidden to its parent widget.
     *
     * #### Notes
     * This causes the {@link isHidden} property to be `true`.
     *
     * If the widget is explicitly hidden, this is a no-op.
     */
    hide() {
        if (this.testFlag(Widget.Flag.IsHidden)) {
            return;
        }
        if (this.isAttached && (!this.parent || this.parent.isVisible)) {
            MessageLoop.sendMessage(this, Widget.Msg.BeforeHide);
        }
        this.setFlag(Widget.Flag.IsHidden);
        this._toggleHidden(true);
        if (this.isAttached && (!this.parent || this.parent.isVisible)) {
            MessageLoop.sendMessage(this, Widget.Msg.AfterHide);
        }
        if (this.parent) {
            let msg = new Widget.ChildMessage('child-hidden', this);
            MessageLoop.sendMessage(this.parent, msg);
        }
    }
    /**
     * Show or hide the widget according to a boolean value.
     *
     * @param hidden - `true` to hide the widget, or `false` to show it.
     *
     * #### Notes
     * This is a convenience method for `hide()` and `show()`.
     */
    setHidden(hidden) {
        if (hidden) {
            this.hide();
        }
        else {
            this.show();
        }
    }
    /**
     * Test whether the given widget flag is set.
     *
     * #### Notes
     * This will not typically be called directly by user code.
     *
     * Since 2.7.0, {@link Widget.Flag.IsVisible} is deprecated.
     * It will be removed in a future version.
     */
    testFlag(flag) {
        return (this._flags & flag) !== 0;
    }
    /**
     * Set the given widget flag.
     *
     * #### Notes
     * This will not typically be called directly by user code.
     *
     * Since 2.7.0, {@link Widget.Flag.IsVisible} is deprecated.
     * It will be removed in a future version.
     */
    setFlag(flag) {
        this._flags |= flag;
    }
    /**
     * Clear the given widget flag.
     *
     * #### Notes
     * This will not typically be called directly by user code.
     *
     * Since 2.7.0, {@link Widget.Flag.IsVisible} is deprecated.
     * It will be removed in a future version.
     */
    clearFlag(flag) {
        this._flags &= ~flag;
    }
    /**
     * Process a message sent to the widget.
     *
     * @param msg - The message sent to the widget.
     *
     * #### Notes
     * Subclasses may reimplement this method as needed.
     */
    processMessage(msg) {
        switch (msg.type) {
            case 'resize':
                this.notifyLayout(msg);
                this.onResize(msg);
                break;
            case 'update-request':
                this.notifyLayout(msg);
                this.onUpdateRequest(msg);
                break;
            case 'fit-request':
                this.notifyLayout(msg);
                this.onFitRequest(msg);
                break;
            case 'before-show':
                this.notifyLayout(msg);
                this.onBeforeShow(msg);
                break;
            case 'after-show':
                this.setFlag(Widget.Flag.IsVisible);
                this.notifyLayout(msg);
                this.onAfterShow(msg);
                break;
            case 'before-hide':
                this.notifyLayout(msg);
                this.onBeforeHide(msg);
                break;
            case 'after-hide':
                this.clearFlag(Widget.Flag.IsVisible);
                this.notifyLayout(msg);
                this.onAfterHide(msg);
                break;
            case 'before-attach':
                this.notifyLayout(msg);
                this.onBeforeAttach(msg);
                break;
            case 'after-attach':
                if (!this.isHidden && (!this.parent || this.parent.isVisible)) {
                    this.setFlag(Widget.Flag.IsVisible);
                }
                this.setFlag(Widget.Flag.IsAttached);
                this.notifyLayout(msg);
                this.onAfterAttach(msg);
                break;
            case 'before-detach':
                this.notifyLayout(msg);
                this.onBeforeDetach(msg);
                break;
            case 'after-detach':
                this.clearFlag(Widget.Flag.IsVisible);
                this.clearFlag(Widget.Flag.IsAttached);
                this.notifyLayout(msg);
                this.onAfterDetach(msg);
                break;
            case 'activate-request':
                this.notifyLayout(msg);
                this.onActivateRequest(msg);
                break;
            case 'close-request':
                this.notifyLayout(msg);
                this.onCloseRequest(msg);
                break;
            case 'child-added':
                this.notifyLayout(msg);
                this.onChildAdded(msg);
                break;
            case 'child-removed':
                this.notifyLayout(msg);
                this.onChildRemoved(msg);
                break;
            default:
                this.notifyLayout(msg);
                break;
        }
    }
    /**
     * Invoke the message processing routine of the widget's layout.
     *
     * @param msg - The message to dispatch to the layout.
     *
     * #### Notes
     * This is a no-op if the widget does not have a layout.
     *
     * This will not typically be called directly by user code.
     */
    notifyLayout(msg) {
        if (this._layout) {
            this._layout.processParentMessage(msg);
        }
    }
    /**
     * A message handler invoked on a `'close-request'` message.
     *
     * #### Notes
     * The default implementation unparents or detaches the widget.
     */
    onCloseRequest(msg) {
        if (this.parent) {
            this.parent = null;
        }
        else if (this.isAttached) {
            Widget.detach(this);
        }
    }
    /**
     * A message handler invoked on a `'resize'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onResize(msg) { }
    /**
     * A message handler invoked on an `'update-request'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onUpdateRequest(msg) { }
    /**
     * A message handler invoked on a `'fit-request'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onFitRequest(msg) { }
    /**
     * A message handler invoked on an `'activate-request'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onActivateRequest(msg) { }
    /**
     * A message handler invoked on a `'before-show'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onBeforeShow(msg) { }
    /**
     * A message handler invoked on an `'after-show'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onAfterShow(msg) { }
    /**
     * A message handler invoked on a `'before-hide'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onBeforeHide(msg) { }
    /**
     * A message handler invoked on an `'after-hide'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onAfterHide(msg) { }
    /**
     * A message handler invoked on a `'before-attach'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onBeforeAttach(msg) { }
    /**
     * A message handler invoked on an `'after-attach'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onAfterAttach(msg) { }
    /**
     * A message handler invoked on a `'before-detach'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onBeforeDetach(msg) { }
    /**
     * A message handler invoked on an `'after-detach'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onAfterDetach(msg) { }
    /**
     * A message handler invoked on a `'child-added'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onChildAdded(msg) { }
    /**
     * A message handler invoked on a `'child-removed'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onChildRemoved(msg) { }
    _toggleHidden(hidden) {
        if (hidden) {
            switch (this._hiddenMode) {
                case Widget.HiddenMode.Display:
                    this.addClass('lm-mod-hidden');
                    break;
                case Widget.HiddenMode.Scale:
                    this.node.style.transform = 'scale(0)';
                    this.node.setAttribute('aria-hidden', 'true');
                    break;
                case Widget.HiddenMode.ContentVisibility:
                    // @ts-expect-error content-visibility unknown by DOM lib types
                    this.node.style.contentVisibility = 'hidden';
                    this.node.style.zIndex = '-1';
                    // Set opacity to 0 so that the widget's box (e.g. borders or
                    // background) does not shine through despite the content being
                    // skipped by `content-visibility`.
                    this.node.style.opacity = '0';
                    break;
            }
        }
        else {
            switch (this._hiddenMode) {
                case Widget.HiddenMode.Display:
                    this.removeClass('lm-mod-hidden');
                    break;
                case Widget.HiddenMode.Scale:
                    this.node.style.transform = '';
                    this.node.removeAttribute('aria-hidden');
                    break;
                case Widget.HiddenMode.ContentVisibility:
                    // @ts-expect-error content-visibility unknown by DOM lib types
                    this.node.style.contentVisibility = '';
                    this.node.style.zIndex = '';
                    this.node.style.opacity = '';
                    break;
            }
        }
    }
}
/**
 * The namespace for the `Widget` class statics.
 */
(function (Widget) {
    (function (HiddenMode) {
        /**
         * Set a `lm-mod-hidden` CSS class to hide the widget using `display:none`
         * CSS from the standard Lumino CSS.
         */
        HiddenMode[HiddenMode["Display"] = 0] = "Display";
        /**
         * Hide the widget by setting the `transform` to `'scale(0)'`.
         */
        HiddenMode[HiddenMode["Scale"] = 1] = "Scale";
        /**
         *Hide the widget by setting the `content-visibility` to `'hidden'`.
         */
        HiddenMode[HiddenMode["ContentVisibility"] = 2] = "ContentVisibility";
    })(Widget.HiddenMode || (Widget.HiddenMode = {}));
    (function (Flag) {
        /**
         * The widget has been disposed.
         */
        Flag[Flag["IsDisposed"] = 1] = "IsDisposed";
        /**
         * The widget is attached to the DOM.
         */
        Flag[Flag["IsAttached"] = 2] = "IsAttached";
        /**
         * The widget is hidden.
         */
        Flag[Flag["IsHidden"] = 4] = "IsHidden";
        /**
         * The widget is visible.
         *
         * @deprecated since 2.7.0, apply that flag consistently was not reliable
         * so it was dropped in favor of a recursive check of the visibility of all parents.
         */
        Flag[Flag["IsVisible"] = 8] = "IsVisible";
        /**
         * A layout cannot be set on the widget.
         */
        Flag[Flag["DisallowLayout"] = 16] = "DisallowLayout";
    })(Widget.Flag || (Widget.Flag = {}));
    (function (Msg) {
        /**
         * A singleton `'before-show'` message.
         *
         * #### Notes
         * This message is sent to a widget before it becomes visible.
         *
         * This message is **not** sent when the widget is being attached.
         */
        Msg.BeforeShow = new Message('before-show');
        /**
         * A singleton `'after-show'` message.
         *
         * #### Notes
         * This message is sent to a widget after it becomes visible.
         *
         * This message is **not** sent when the widget is being attached.
         */
        Msg.AfterShow = new Message('after-show');
        /**
         * A singleton `'before-hide'` message.
         *
         * #### Notes
         * This message is sent to a widget before it becomes not-visible.
         *
         * This message is **not** sent when the widget is being detached.
         */
        Msg.BeforeHide = new Message('before-hide');
        /**
         * A singleton `'after-hide'` message.
         *
         * #### Notes
         * This message is sent to a widget after it becomes not-visible.
         *
         * This message is **not** sent when the widget is being detached.
         */
        Msg.AfterHide = new Message('after-hide');
        /**
         * A singleton `'before-attach'` message.
         *
         * #### Notes
         * This message is sent to a widget before it is attached.
         */
        Msg.BeforeAttach = new Message('before-attach');
        /**
         * A singleton `'after-attach'` message.
         *
         * #### Notes
         * This message is sent to a widget after it is attached.
         */
        Msg.AfterAttach = new Message('after-attach');
        /**
         * A singleton `'before-detach'` message.
         *
         * #### Notes
         * This message is sent to a widget before it is detached.
         */
        Msg.BeforeDetach = new Message('before-detach');
        /**
         * A singleton `'after-detach'` message.
         *
         * #### Notes
         * This message is sent to a widget after it is detached.
         */
        Msg.AfterDetach = new Message('after-detach');
        /**
         * A singleton `'parent-changed'` message.
         *
         * #### Notes
         * This message is sent to a widget when its parent has changed.
         */
        Msg.ParentChanged = new Message('parent-changed');
        /**
         * A singleton conflatable `'update-request'` message.
         *
         * #### Notes
         * This message can be dispatched to supporting widgets in order to
         * update their content based on the current widget state. Not all
         * widgets will respond to messages of this type.
         *
         * For widgets with a layout, this message will inform the layout to
         * update the position and size of its child widgets.
         */
        Msg.UpdateRequest = new ConflatableMessage('update-request');
        /**
         * A singleton conflatable `'fit-request'` message.
         *
         * #### Notes
         * For widgets with a layout, this message will inform the layout to
         * recalculate its size constraints to fit the space requirements of
         * its child widgets, and to update their position and size. Not all
         * layouts will respond to messages of this type.
         */
        Msg.FitRequest = new ConflatableMessage('fit-request');
        /**
         * A singleton conflatable `'activate-request'` message.
         *
         * #### Notes
         * This message should be dispatched to a widget when it should
         * perform the actions necessary to activate the widget, which
         * may include focusing its node or descendant node.
         */
        Msg.ActivateRequest = new ConflatableMessage('activate-request');
        /**
         * A singleton conflatable `'close-request'` message.
         *
         * #### Notes
         * This message should be dispatched to a widget when it should close
         * and remove itself from the widget hierarchy.
         */
        Msg.CloseRequest = new ConflatableMessage('close-request');
    })(Widget.Msg || (Widget.Msg = {}));
    /**
     * A message class for child related messages.
     */
    class ChildMessage extends Message {
        /**
         * Construct a new child message.
         *
         * @param type - The message type.
         *
         * @param child - The child widget for the message.
         */
        constructor(type, child) {
            super(type);
            this.child = child;
        }
    }
    Widget.ChildMessage = ChildMessage;
    /**
     * A message class for `'resize'` messages.
     */
    class ResizeMessage extends Message {
        /**
         * Construct a new resize message.
         *
         * @param width - The **offset width** of the widget, or `-1` if
         *   the width is not known.
         *
         * @param height - The **offset height** of the widget, or `-1` if
         *   the height is not known.
         */
        constructor(width, height) {
            super('resize');
            this.width = width;
            this.height = height;
        }
    }
    Widget.ResizeMessage = ResizeMessage;
    /**
     * The namespace for the `ResizeMessage` class statics.
     */
    (function (ResizeMessage) {
        /**
         * A singleton `'resize'` message with an unknown size.
         */
        ResizeMessage.UnknownSize = new ResizeMessage(-1, -1);
    })(ResizeMessage = Widget.ResizeMessage || (Widget.ResizeMessage = {}));
    /**
     * Attach a widget to a host DOM node.
     *
     * @param widget - The widget of interest.
     *
     * @param host - The DOM node to use as the widget's host.
     *
     * @param ref - The child of `host` to use as the reference element.
     *   If this is provided, the widget will be inserted before this
     *   node in the host. The default is `null`, which will cause the
     *   widget to be added as the last child of the host.
     *
     * #### Notes
     * This will throw an error if the widget is not a root widget, if
     * the widget is already attached, or if the host is not attached
     * to the DOM.
     */
    function attach(widget, host, ref = null) {
        if (widget.parent) {
            throw new Error('Cannot attach a child widget.');
        }
        if (widget.isAttached || widget.node.isConnected) {
            throw new Error('Widget is already attached.');
        }
        if (!host.isConnected) {
            throw new Error('Host is not attached.');
        }
        MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        host.insertBefore(widget.node, ref);
        MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
    }
    Widget.attach = attach;
    /**
     * Detach the widget from its host DOM node.
     *
     * @param widget - The widget of interest.
     *
     * #### Notes
     * This will throw an error if the widget is not a root widget,
     * or if the widget is not attached to the DOM.
     */
    function detach(widget) {
        if (widget.parent) {
            throw new Error('Cannot detach a child widget.');
        }
        if (!widget.isAttached || !widget.node.isConnected) {
            throw new Error('Widget is not attached.');
        }
        MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        widget.node.parentNode.removeChild(widget.node);
        MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
    }
    Widget.detach = detach;
})(Widget || (Widget = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$j;
(function (Private) {
    /**
     * An attached property for the widget title object.
     */
    Private.titleProperty = new AttachedProperty({
        name: 'title',
        create: owner => new Title({ owner })
    });
    /**
     * Create a DOM node for the given widget options.
     */
    function createNode(options) {
        return options.node || document.createElement(options.tag || 'div');
    }
    Private.createNode = createNode;
})(Private$j || (Private$j = {}));

/**
 * An abstract base class for creating lumino layouts.
 *
 * #### Notes
 * A layout is used to add widgets to a parent and to arrange those
 * widgets within the parent's DOM node.
 *
 * This class implements the base functionality which is required of
 * nearly all layouts. It must be subclassed in order to be useful.
 *
 * Notably, this class does not define a uniform interface for adding
 * widgets to the layout. A subclass should define that API in a way
 * which is meaningful for its intended use.
 */
class Layout {
    /**
     * Construct a new layout.
     *
     * @param options - The options for initializing the layout.
     */
    constructor(options = {}) {
        this._disposed = false;
        this._parent = null;
        this._fitPolicy = options.fitPolicy || 'set-min-size';
    }
    /**
     * Dispose of the resources held by the layout.
     *
     * #### Notes
     * This should be reimplemented to clear and dispose of the widgets.
     *
     * All reimplementations should call the superclass method.
     *
     * This method is called automatically when the parent is disposed.
     */
    dispose() {
        this._parent = null;
        this._disposed = true;
        Signal.clearData(this);
        AttachedProperty.clearData(this);
    }
    /**
     * Test whether the layout is disposed.
     */
    get isDisposed() {
        return this._disposed;
    }
    /**
     * Get the parent widget of the layout.
     */
    get parent() {
        return this._parent;
    }
    /**
     * Set the parent widget of the layout.
     *
     * #### Notes
     * This is set automatically when installing the layout on the parent
     * widget. The parent widget should not be set directly by user code.
     */
    set parent(value) {
        if (this._parent === value) {
            return;
        }
        if (this._parent) {
            throw new Error('Cannot change parent widget.');
        }
        if (value.layout !== this) {
            throw new Error('Invalid parent widget.');
        }
        this._parent = value;
        this.init();
    }
    /**
     * Get the fit policy for the layout.
     *
     * #### Notes
     * The fit policy controls the computed size constraints which are
     * applied to the parent widget by the layout.
     *
     * Some layout implementations may ignore the fit policy.
     */
    get fitPolicy() {
        return this._fitPolicy;
    }
    /**
     * Set the fit policy for the layout.
     *
     * #### Notes
     * The fit policy controls the computed size constraints which are
     * applied to the parent widget by the layout.
     *
     * Some layout implementations may ignore the fit policy.
     *
     * Changing the fit policy will clear the current size constraint
     * for the parent widget and then re-fit the parent.
     */
    set fitPolicy(value) {
        // Bail if the policy does not change
        if (this._fitPolicy === value) {
            return;
        }
        // Update the internal policy.
        this._fitPolicy = value;
        // Clear the size constraints and schedule a fit of the parent.
        if (this._parent) {
            let style = this._parent.node.style;
            style.minWidth = '';
            style.minHeight = '';
            style.maxWidth = '';
            style.maxHeight = '';
            this._parent.fit();
        }
    }
    /**
     * Process a message sent to the parent widget.
     *
     * @param msg - The message sent to the parent widget.
     *
     * #### Notes
     * This method is called by the parent widget to process a message.
     *
     * Subclasses may reimplement this method as needed.
     */
    processParentMessage(msg) {
        switch (msg.type) {
            case 'resize':
                this.onResize(msg);
                break;
            case 'update-request':
                this.onUpdateRequest(msg);
                break;
            case 'fit-request':
                this.onFitRequest(msg);
                break;
            case 'before-show':
                this.onBeforeShow(msg);
                break;
            case 'after-show':
                this.onAfterShow(msg);
                break;
            case 'before-hide':
                this.onBeforeHide(msg);
                break;
            case 'after-hide':
                this.onAfterHide(msg);
                break;
            case 'before-attach':
                this.onBeforeAttach(msg);
                break;
            case 'after-attach':
                this.onAfterAttach(msg);
                break;
            case 'before-detach':
                this.onBeforeDetach(msg);
                break;
            case 'after-detach':
                this.onAfterDetach(msg);
                break;
            case 'child-removed':
                this.onChildRemoved(msg);
                break;
            case 'child-shown':
                this.onChildShown(msg);
                break;
            case 'child-hidden':
                this.onChildHidden(msg);
                break;
        }
    }
    /**
     * Perform layout initialization which requires the parent widget.
     *
     * #### Notes
     * This method is invoked immediately after the layout is installed
     * on the parent widget.
     *
     * The default implementation reparents all of the widgets to the
     * layout parent widget.
     *
     * Subclasses should reimplement this method and attach the child
     * widget nodes to the parent widget's node.
     */
    init() {
        for (const widget of this) {
            widget.parent = this.parent;
        }
    }
    /**
     * A message handler invoked on a `'resize'` message.
     *
     * #### Notes
     * The layout should ensure that its widgets are resized according
     * to the specified layout space, and that they are sent a `'resize'`
     * message if appropriate.
     *
     * The default implementation of this method sends an `UnknownSize`
     * resize message to all widgets.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onResize(msg) {
        for (const widget of this) {
            MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
        }
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     *
     * #### Notes
     * The layout should ensure that its widgets are resized according
     * to the available layout space, and that they are sent a `'resize'`
     * message if appropriate.
     *
     * The default implementation of this method sends an `UnknownSize`
     * resize message to all widgets.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onUpdateRequest(msg) {
        for (const widget of this) {
            MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
        }
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     *
     * #### Notes
     * The default implementation of this method forwards the message
     * to all widgets. It assumes all widget nodes are attached to the
     * parent widget node.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onBeforeAttach(msg) {
        for (const widget of this) {
            MessageLoop.sendMessage(widget, msg);
        }
    }
    /**
     * A message handler invoked on an `'after-attach'` message.
     *
     * #### Notes
     * The default implementation of this method forwards the message
     * to all widgets. It assumes all widget nodes are attached to the
     * parent widget node.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onAfterAttach(msg) {
        for (const widget of this) {
            MessageLoop.sendMessage(widget, msg);
        }
    }
    /**
     * A message handler invoked on a `'before-detach'` message.
     *
     * #### Notes
     * The default implementation of this method forwards the message
     * to all widgets. It assumes all widget nodes are attached to the
     * parent widget node.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onBeforeDetach(msg) {
        for (const widget of this) {
            MessageLoop.sendMessage(widget, msg);
        }
    }
    /**
     * A message handler invoked on an `'after-detach'` message.
     *
     * #### Notes
     * The default implementation of this method forwards the message
     * to all widgets. It assumes all widget nodes are attached to the
     * parent widget node.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onAfterDetach(msg) {
        for (const widget of this) {
            MessageLoop.sendMessage(widget, msg);
        }
    }
    /**
     * A message handler invoked on a `'before-show'` message.
     *
     * #### Notes
     * The default implementation of this method forwards the message to
     * all non-hidden widgets. It assumes all widget nodes are attached
     * to the parent widget node.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onBeforeShow(msg) {
        for (const widget of this) {
            if (!widget.isHidden) {
                MessageLoop.sendMessage(widget, msg);
            }
        }
    }
    /**
     * A message handler invoked on an `'after-show'` message.
     *
     * #### Notes
     * The default implementation of this method forwards the message to
     * all non-hidden widgets. It assumes all widget nodes are attached
     * to the parent widget node.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onAfterShow(msg) {
        for (const widget of this) {
            if (!widget.isHidden) {
                MessageLoop.sendMessage(widget, msg);
            }
        }
    }
    /**
     * A message handler invoked on a `'before-hide'` message.
     *
     * #### Notes
     * The default implementation of this method forwards the message to
     * all non-hidden widgets. It assumes all widget nodes are attached
     * to the parent widget node.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onBeforeHide(msg) {
        for (const widget of this) {
            if (!widget.isHidden) {
                MessageLoop.sendMessage(widget, msg);
            }
        }
    }
    /**
     * A message handler invoked on an `'after-hide'` message.
     *
     * #### Notes
     * The default implementation of this method forwards the message to
     * all non-hidden widgets. It assumes all widget nodes are attached
     * to the parent widget node.
     *
     * This may be reimplemented by subclasses as needed.
     */
    onAfterHide(msg) {
        for (const widget of this) {
            if (!widget.isHidden) {
                MessageLoop.sendMessage(widget, msg);
            }
        }
    }
    /**
     * A message handler invoked on a `'child-removed'` message.
     *
     * #### Notes
     * This will remove the child widget from the layout.
     *
     * Subclasses should **not** typically reimplement this method.
     */
    onChildRemoved(msg) {
        this.removeWidget(msg.child);
    }
    /**
     * A message handler invoked on a `'fit-request'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onFitRequest(msg) { }
    /**
     * A message handler invoked on a `'child-shown'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onChildShown(msg) { }
    /**
     * A message handler invoked on a `'child-hidden'` message.
     *
     * #### Notes
     * The default implementation of this handler is a no-op.
     */
    onChildHidden(msg) { }
}
/**
 * The namespace for the `Layout` class statics.
 */
(function (Layout) {
    /**
     * Get the horizontal alignment for a widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The horizontal alignment for the widget.
     *
     * #### Notes
     * If the layout width allocated to a widget is larger than its max
     * width, the horizontal alignment controls how the widget is placed
     * within the extra horizontal space.
     *
     * If the allocated width is less than the widget's max width, the
     * horizontal alignment has no effect.
     *
     * Some layout implementations may ignore horizontal alignment.
     */
    function getHorizontalAlignment(widget) {
        return Private$i.horizontalAlignmentProperty.get(widget);
    }
    Layout.getHorizontalAlignment = getHorizontalAlignment;
    /**
     * Set the horizontal alignment for a widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the horizontal alignment.
     *
     * #### Notes
     * If the layout width allocated to a widget is larger than its max
     * width, the horizontal alignment controls how the widget is placed
     * within the extra horizontal space.
     *
     * If the allocated width is less than the widget's max width, the
     * horizontal alignment has no effect.
     *
     * Some layout implementations may ignore horizontal alignment.
     *
     * Changing the horizontal alignment will post an `update-request`
     * message to widget's parent, provided the parent has a layout
     * installed.
     */
    function setHorizontalAlignment(widget, value) {
        Private$i.horizontalAlignmentProperty.set(widget, value);
    }
    Layout.setHorizontalAlignment = setHorizontalAlignment;
    /**
     * Get the vertical alignment for a widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The vertical alignment for the widget.
     *
     * #### Notes
     * If the layout height allocated to a widget is larger than its max
     * height, the vertical alignment controls how the widget is placed
     * within the extra vertical space.
     *
     * If the allocated height is less than the widget's max height, the
     * vertical alignment has no effect.
     *
     * Some layout implementations may ignore vertical alignment.
     */
    function getVerticalAlignment(widget) {
        return Private$i.verticalAlignmentProperty.get(widget);
    }
    Layout.getVerticalAlignment = getVerticalAlignment;
    /**
     * Set the vertical alignment for a widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the vertical alignment.
     *
     * #### Notes
     * If the layout height allocated to a widget is larger than its max
     * height, the vertical alignment controls how the widget is placed
     * within the extra vertical space.
     *
     * If the allocated height is less than the widget's max height, the
     * vertical alignment has no effect.
     *
     * Some layout implementations may ignore vertical alignment.
     *
     * Changing the horizontal alignment will post an `update-request`
     * message to widget's parent, provided the parent has a layout
     * installed.
     */
    function setVerticalAlignment(widget, value) {
        Private$i.verticalAlignmentProperty.set(widget, value);
    }
    Layout.setVerticalAlignment = setVerticalAlignment;
})(Layout || (Layout = {}));
/**
 * An object which assists in the absolute layout of widgets.
 *
 * #### Notes
 * This class is useful when implementing a layout which arranges its
 * widgets using absolute positioning.
 *
 * This class is used by nearly all of the built-in lumino layouts.
 */
class LayoutItem {
    /**
     * Construct a new layout item.
     *
     * @param widget - The widget to be managed by the item.
     *
     * #### Notes
     * The widget will be set to absolute positioning.
     * The widget will use strict CSS containment.
     */
    constructor(widget) {
        this._top = NaN;
        this._left = NaN;
        this._width = NaN;
        this._height = NaN;
        this._minWidth = 0;
        this._minHeight = 0;
        this._maxWidth = Infinity;
        this._maxHeight = Infinity;
        this._disposed = false;
        this.widget = widget;
        this.widget.node.style.position = 'absolute';
        this.widget.node.style.contain = 'strict';
    }
    /**
     * Dispose of the the layout item.
     *
     * #### Notes
     * This will reset the positioning of the widget.
     */
    dispose() {
        // Do nothing if the item is already disposed.
        if (this._disposed) {
            return;
        }
        // Mark the item as disposed.
        this._disposed = true;
        // Reset the widget style.
        let style = this.widget.node.style;
        style.position = '';
        style.top = '';
        style.left = '';
        style.width = '';
        style.height = '';
        style.contain = '';
    }
    /**
     * The computed minimum width of the widget.
     *
     * #### Notes
     * This value can be updated by calling the `fit` method.
     */
    get minWidth() {
        return this._minWidth;
    }
    /**
     * The computed minimum height of the widget.
     *
     * #### Notes
     * This value can be updated by calling the `fit` method.
     */
    get minHeight() {
        return this._minHeight;
    }
    /**
     * The computed maximum width of the widget.
     *
     * #### Notes
     * This value can be updated by calling the `fit` method.
     */
    get maxWidth() {
        return this._maxWidth;
    }
    /**
     * The computed maximum height of the widget.
     *
     * #### Notes
     * This value can be updated by calling the `fit` method.
     */
    get maxHeight() {
        return this._maxHeight;
    }
    /**
     * Whether the layout item is disposed.
     */
    get isDisposed() {
        return this._disposed;
    }
    /**
     * Whether the managed widget is hidden.
     */
    get isHidden() {
        return this.widget.isHidden;
    }
    /**
     * Whether the managed widget is visible.
     */
    get isVisible() {
        return this.widget.isVisible;
    }
    /**
     * Whether the managed widget is attached.
     */
    get isAttached() {
        return this.widget.isAttached;
    }
    /**
     * Update the computed size limits of the managed widget.
     */
    fit() {
        let limits = ElementExt.sizeLimits(this.widget.node);
        this._minWidth = limits.minWidth;
        this._minHeight = limits.minHeight;
        this._maxWidth = limits.maxWidth;
        this._maxHeight = limits.maxHeight;
    }
    /**
     * Update the position and size of the managed widget.
     *
     * @param left - The left edge position of the layout box.
     *
     * @param top - The top edge position of the layout box.
     *
     * @param width - The width of the layout box.
     *
     * @param height - The height of the layout box.
     */
    update(left, top, width, height) {
        // Clamp the size to the computed size limits.
        let clampW = Math.max(this._minWidth, Math.min(width, this._maxWidth));
        let clampH = Math.max(this._minHeight, Math.min(height, this._maxHeight));
        // Adjust the left edge for the horizontal alignment, if needed.
        if (clampW < width) {
            switch (Layout.getHorizontalAlignment(this.widget)) {
                case 'left':
                    break;
                case 'center':
                    left += (width - clampW) / 2;
                    break;
                case 'right':
                    left += width - clampW;
                    break;
                default:
                    throw 'unreachable';
            }
        }
        // Adjust the top edge for the vertical alignment, if needed.
        if (clampH < height) {
            switch (Layout.getVerticalAlignment(this.widget)) {
                case 'top':
                    break;
                case 'center':
                    top += (height - clampH) / 2;
                    break;
                case 'bottom':
                    top += height - clampH;
                    break;
                default:
                    throw 'unreachable';
            }
        }
        // Set up the resize variables.
        let resized = false;
        let style = this.widget.node.style;
        // Update the top edge of the widget if needed.
        if (this._top !== top) {
            this._top = top;
            style.top = `${top}px`;
        }
        // Update the left edge of the widget if needed.
        if (this._left !== left) {
            this._left = left;
            style.left = `${left}px`;
        }
        // Update the width of the widget if needed.
        if (this._width !== clampW) {
            resized = true;
            this._width = clampW;
            style.width = `${clampW}px`;
        }
        // Update the height of the widget if needed.
        if (this._height !== clampH) {
            resized = true;
            this._height = clampH;
            style.height = `${clampH}px`;
        }
        // Send a resize message to the widget if needed.
        if (resized) {
            let msg = new Widget.ResizeMessage(clampW, clampH);
            MessageLoop.sendMessage(this.widget, msg);
        }
    }
}
/**
 * The namespace for the module implementation details.
 */
var Private$i;
(function (Private) {
    /**
     * The attached property for a widget horizontal alignment.
     */
    Private.horizontalAlignmentProperty = new AttachedProperty({
        name: 'horizontalAlignment',
        create: () => 'center',
        changed: onAlignmentChanged
    });
    /**
     * The attached property for a widget vertical alignment.
     */
    Private.verticalAlignmentProperty = new AttachedProperty({
        name: 'verticalAlignment',
        create: () => 'top',
        changed: onAlignmentChanged
    });
    /**
     * The change handler for the attached alignment properties.
     */
    function onAlignmentChanged(child) {
        if (child.parent && child.parent.layout) {
            child.parent.update();
        }
    }
})(Private$i || (Private$i = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A concrete layout implementation suitable for many use cases.
 *
 * #### Notes
 * This class is suitable as a base class for implementing a variety of
 * layouts, but can also be used directly with standard CSS to layout a
 * collection of widgets.
 */
class PanelLayout extends Layout {
    constructor() {
        super(...arguments);
        this._widgets = [];
    }
    /**
     * Dispose of the resources held by the layout.
     *
     * #### Notes
     * This will clear and dispose all widgets in the layout.
     *
     * All reimplementations should call the superclass method.
     *
     * This method is called automatically when the parent is disposed.
     */
    dispose() {
        while (this._widgets.length > 0) {
            this._widgets.pop().dispose();
        }
        super.dispose();
    }
    /**
     * A read-only array of the widgets in the layout.
     */
    get widgets() {
        return this._widgets;
    }
    /**
     * Create an iterator over the widgets in the layout.
     *
     * @returns A new iterator over the widgets in the layout.
     */
    *[Symbol.iterator]() {
        yield* this._widgets;
    }
    /**
     * Add a widget to the end of the layout.
     *
     * @param widget - The widget to add to the layout.
     *
     * #### Notes
     * If the widget is already contained in the layout, it will be moved.
     */
    addWidget(widget) {
        this.insertWidget(this._widgets.length, widget);
    }
    /**
     * Insert a widget into the layout at the specified index.
     *
     * @param index - The index at which to insert the widget.
     *
     * @param widget - The widget to insert into the layout.
     *
     * #### Notes
     * The index will be clamped to the bounds of the widgets.
     *
     * If the widget is already added to the layout, it will be moved.
     *
     * #### Undefined Behavior
     * An `index` which is non-integral.
     */
    insertWidget(index, widget) {
        // Remove the widget from its current parent. This is a no-op
        // if the widget's parent is already the layout parent widget.
        widget.parent = this.parent;
        // Look up the current index of the widget.
        let i = this._widgets.indexOf(widget);
        // Clamp the insert index to the array bounds.
        let j = Math.max(0, Math.min(index, this._widgets.length));
        // If the widget is not in the array, insert it.
        if (i === -1) {
            // Insert the widget into the array.
            ArrayExt.insert(this._widgets, j, widget);
            // If the layout is parented, attach the widget to the DOM.
            if (this.parent) {
                this.attachWidget(j, widget);
            }
            // There is nothing more to do.
            return;
        }
        // Otherwise, the widget exists in the array and should be moved.
        // Adjust the index if the location is at the end of the array.
        if (j === this._widgets.length) {
            j--;
        }
        // Bail if there is no effective move.
        if (i === j) {
            return;
        }
        // Move the widget to the new location.
        ArrayExt.move(this._widgets, i, j);
        // If the layout is parented, move the widget in the DOM.
        if (this.parent) {
            this.moveWidget(i, j, widget);
        }
    }
    /**
     * Remove a widget from the layout.
     *
     * @param widget - The widget to remove from the layout.
     *
     * #### Notes
     * A widget is automatically removed from the layout when its `parent`
     * is set to `null`. This method should only be invoked directly when
     * removing a widget from a layout which has yet to be installed on a
     * parent widget.
     *
     * This method does *not* modify the widget's `parent`.
     */
    removeWidget(widget) {
        this.removeWidgetAt(this._widgets.indexOf(widget));
    }
    /**
     * Remove the widget at a given index from the layout.
     *
     * @param index - The index of the widget to remove.
     *
     * #### Notes
     * A widget is automatically removed from the layout when its `parent`
     * is set to `null`. This method should only be invoked directly when
     * removing a widget from a layout which has yet to be installed on a
     * parent widget.
     *
     * This method does *not* modify the widget's `parent`.
     *
     * #### Undefined Behavior
     * An `index` which is non-integral.
     */
    removeWidgetAt(index) {
        // Remove the widget from the array.
        let widget = ArrayExt.removeAt(this._widgets, index);
        // If the layout is parented, detach the widget from the DOM.
        if (widget && this.parent) {
            this.detachWidget(index, widget);
        }
    }
    /**
     * Perform layout initialization which requires the parent widget.
     */
    init() {
        super.init();
        let index = 0;
        for (const widget of this) {
            this.attachWidget(index++, widget);
        }
    }
    /**
     * Attach a widget to the parent's DOM node.
     *
     * @param index - The current index of the widget in the layout.
     *
     * @param widget - The widget to attach to the parent.
     *
     * #### Notes
     * This method is called automatically by the panel layout at the
     * appropriate time. It should not be called directly by user code.
     *
     * The default implementation adds the widgets's node to the parent's
     * node at the proper location, and sends the appropriate attach
     * messages to the widget if the parent is attached to the DOM.
     *
     * Subclasses may reimplement this method to control how the widget's
     * node is added to the parent's node.
     */
    attachWidget(index, widget) {
        // Look up the next sibling reference node.
        let ref = this.parent.node.children[index];
        // Send a `'before-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        }
        // Insert the widget's node before the sibling.
        this.parent.node.insertBefore(widget.node, ref);
        // Send an `'after-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
        }
    }
    /**
     * Move a widget in the parent's DOM node.
     *
     * @param fromIndex - The previous index of the widget in the layout.
     *
     * @param toIndex - The current index of the widget in the layout.
     *
     * @param widget - The widget to move in the parent.
     *
     * #### Notes
     * This method is called automatically by the panel layout at the
     * appropriate time. It should not be called directly by user code.
     *
     * The default implementation moves the widget's node to the proper
     * location in the parent's node and sends the appropriate attach and
     * detach messages to the widget if the parent is attached to the DOM.
     *
     * Subclasses may reimplement this method to control how the widget's
     * node is moved in the parent's node.
     */
    moveWidget(fromIndex, toIndex, widget) {
        // Send a `'before-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        }
        // Remove the widget's node from the parent.
        this.parent.node.removeChild(widget.node);
        // Send an `'after-detach'` and  message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
        }
        // Look up the next sibling reference node.
        let ref = this.parent.node.children[toIndex];
        // Send a `'before-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        }
        // Insert the widget's node before the sibling.
        this.parent.node.insertBefore(widget.node, ref);
        // Send an `'after-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
        }
    }
    /**
     * Detach a widget from the parent's DOM node.
     *
     * @param index - The previous index of the widget in the layout.
     *
     * @param widget - The widget to detach from the parent.
     *
     * #### Notes
     * This method is called automatically by the panel layout at the
     * appropriate time. It should not be called directly by user code.
     *
     * The default implementation removes the widget's node from the
     * parent's node, and sends the appropriate detach messages to the
     * widget if the parent is attached to the DOM.
     *
     * Subclasses may reimplement this method to control how the widget's
     * node is removed from the parent's node.
     */
    detachWidget(index, widget) {
        // Send a `'before-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        }
        // Remove the widget's node from the parent.
        this.parent.node.removeChild(widget.node);
        // Send an `'after-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
        }
    }
}

/*
 * Copyright (c) Jupyter Development Team.
 * Distributed under the terms of the Modified BSD License.
 */
var Utils;
(function (Utils) {
    /**
     * Clamp a dimension value to an integer >= 0.
     */
    function clampDimension(value) {
        return Math.max(0, Math.floor(value));
    }
    Utils.clampDimension = clampDimension;
    /**
     * Whether a pointer event came from touch input.
     */
    function isTouchEvent(event) {
        return 'pointerType' in event && event.pointerType === 'touch';
    }
    Utils.isTouchEvent = isTouchEvent;
})(Utils || (Utils = {}));
var Utils$1 = Utils;

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A layout which arranges its widgets into resizable sections.
 */
class SplitLayout extends PanelLayout {
    /**
     * Construct a new split layout.
     *
     * @param options - The options for initializing the layout.
     */
    constructor(options) {
        super();
        this.widgetOffset = 0;
        this._fixed = 0;
        this._spacing = 4;
        this._dirty = false;
        this._hasNormedSizes = false;
        this._sizers = [];
        this._items = [];
        this._handles = [];
        this._box = null;
        this._alignment = 'start';
        this._orientation = 'horizontal';
        this.renderer = options.renderer;
        if (options.orientation !== undefined) {
            this._orientation = options.orientation;
        }
        if (options.alignment !== undefined) {
            this._alignment = options.alignment;
        }
        if (options.spacing !== undefined) {
            this._spacing = Utils.clampDimension(options.spacing);
        }
    }
    /**
     * Dispose of the resources held by the layout.
     */
    dispose() {
        // Dispose of the layout items.
        for (const item of this._items) {
            item.dispose();
        }
        // Clear the layout state.
        this._box = null;
        this._items.length = 0;
        this._sizers.length = 0;
        this._handles.length = 0;
        // Dispose of the rest of the layout.
        super.dispose();
    }
    /**
     * Get the layout orientation for the split layout.
     */
    get orientation() {
        return this._orientation;
    }
    /**
     * Set the layout orientation for the split layout.
     */
    set orientation(value) {
        if (this._orientation === value) {
            return;
        }
        this._orientation = value;
        if (!this.parent) {
            return;
        }
        this.parent.dataset['orientation'] = value;
        this.parent.fit();
    }
    /**
     * Get the content alignment for the split layout.
     *
     * #### Notes
     * This is the alignment of the widgets in the layout direction.
     *
     * The alignment has no effect if the widgets can expand  to fill the
     * entire split layout.
     */
    get alignment() {
        return this._alignment;
    }
    /**
     * Set the content alignment for the split layout.
     *
     * #### Notes
     * This is the alignment of the widgets in the layout direction.
     *
     * The alignment has no effect if the widgets can expand  to fill the
     * entire split layout.
     */
    set alignment(value) {
        if (this._alignment === value) {
            return;
        }
        this._alignment = value;
        if (!this.parent) {
            return;
        }
        this.parent.dataset['alignment'] = value;
        this.parent.update();
    }
    /**
     * Get the inter-element spacing for the split layout.
     */
    get spacing() {
        return this._spacing;
    }
    /**
     * Set the inter-element spacing for the split layout.
     */
    set spacing(value) {
        value = Utils.clampDimension(value);
        if (this._spacing === value) {
            return;
        }
        this._spacing = value;
        if (!this.parent) {
            return;
        }
        this.parent.fit();
    }
    /**
     * A read-only array of the split handles in the layout.
     */
    get handles() {
        return this._handles;
    }
    /**
     * Get the absolute sizes of the widgets in the layout.
     *
     * @returns A new array of the absolute sizes of the widgets.
     *
     * This method **does not** measure the DOM nodes.
     */
    absoluteSizes() {
        return this._sizers.map(sizer => sizer.size);
    }
    /**
     * Get the relative sizes of the widgets in the layout.
     *
     * @returns A new array of the relative sizes of the widgets.
     *
     * #### Notes
     * The returned sizes reflect the sizes of the widgets normalized
     * relative to their siblings.
     *
     * This method **does not** measure the DOM nodes.
     */
    relativeSizes() {
        return Private$h.normalize(this._sizers.map(sizer => sizer.size));
    }
    /**
     * Set the relative sizes for the widgets in the layout.
     *
     * @param sizes - The relative sizes for the widgets in the panel.
     * @param update - Update the layout after setting relative sizes.
     * Default is True.
     *
     * #### Notes
     * Extra values are ignored, too few will yield an undefined layout.
     *
     * The actual geometry of the DOM nodes is updated asynchronously.
     */
    setRelativeSizes(sizes, update = true) {
        // Copy the sizes and pad with zeros as needed.
        let n = this._sizers.length;
        let temp = sizes.slice(0, n);
        while (temp.length < n) {
            temp.push(0);
        }
        // Normalize the padded sizes.
        let normed = Private$h.normalize(temp);
        // Apply the normalized sizes to the sizers.
        for (let i = 0; i < n; ++i) {
            let sizer = this._sizers[i];
            sizer.sizeHint = normed[i];
            sizer.size = normed[i];
        }
        // Set the flag indicating the sizes are normalized.
        this._hasNormedSizes = true;
        // Trigger an update of the parent widget.
        if (update && this.parent) {
            this.parent.update();
        }
    }
    /**
     * Move the offset position of a split handle.
     *
     * @param index - The index of the handle of the interest.
     *
     * @param position - The desired offset position of the handle.
     *
     * #### Notes
     * The position is relative to the offset parent.
     *
     * This will move the handle as close as possible to the desired
     * position. The sibling widgets will be adjusted as necessary.
     */
    moveHandle(index, position) {
        // Bail if the index is invalid or the handle is hidden.
        let handle = this._handles[index];
        if (!handle || handle.classList.contains('lm-mod-hidden')) {
            return;
        }
        // Compute the desired delta movement for the handle.
        let delta;
        if (this._orientation === 'horizontal') {
            delta = position - handle.offsetLeft;
        }
        else {
            delta = position - handle.offsetTop;
        }
        // Bail if there is no handle movement.
        if (delta === 0) {
            return;
        }
        // Prevent widget resizing unless needed.
        for (let sizer of this._sizers) {
            if (sizer.size > 0) {
                sizer.sizeHint = sizer.size;
            }
        }
        // Adjust the sizers to reflect the handle movement.
        BoxEngine.adjust(this._sizers, index, delta);
        // Update the layout of the widgets.
        if (this.parent) {
            this.parent.update();
        }
    }
    /**
     * Perform layout initialization which requires the parent widget.
     */
    init() {
        this.parent.dataset['orientation'] = this.orientation;
        this.parent.dataset['alignment'] = this.alignment;
        super.init();
    }
    /**
     * Attach a widget to the parent's DOM node.
     *
     * @param index - The current index of the widget in the layout.
     *
     * @param widget - The widget to attach to the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    attachWidget(index, widget) {
        // Create the item, handle, and sizer for the new widget.
        let item = new LayoutItem(widget);
        let handle = Private$h.createHandle(this.renderer);
        let average = Private$h.averageSize(this._sizers);
        let sizer = Private$h.createSizer(average);
        // Insert the item, handle, and sizer into the internal arrays.
        ArrayExt.insert(this._items, index, item);
        ArrayExt.insert(this._sizers, index, sizer);
        ArrayExt.insert(this._handles, index, handle);
        // Send a `'before-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        }
        // Add the widget and handle nodes to the parent.
        this.parent.node.appendChild(widget.node);
        this.parent.node.appendChild(handle);
        // Send an `'after-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
        }
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * Move a widget in the parent's DOM node.
     *
     * @param fromIndex - The previous index of the widget in the layout.
     *
     * @param toIndex - The current index of the widget in the layout.
     *
     * @param widget - The widget to move in the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    moveWidget(fromIndex, toIndex, widget) {
        // Move the item, sizer, and handle for the widget.
        ArrayExt.move(this._items, fromIndex, toIndex);
        ArrayExt.move(this._sizers, fromIndex, toIndex);
        ArrayExt.move(this._handles, fromIndex, toIndex);
        // Post a fit request to the parent to show/hide last handle.
        this.parent.fit();
    }
    /**
     * Detach a widget from the parent's DOM node.
     *
     * @param index - The previous index of the widget in the layout.
     *
     * @param widget - The widget to detach from the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    detachWidget(index, widget) {
        // Remove the item, handle, and sizer for the widget.
        let item = ArrayExt.removeAt(this._items, index);
        let handle = ArrayExt.removeAt(this._handles, index);
        ArrayExt.removeAt(this._sizers, index);
        // Send a `'before-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        }
        // Remove the widget and handle nodes from the parent.
        this.parent.node.removeChild(widget.node);
        this.parent.node.removeChild(handle);
        // Send an `'after-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
        }
        // Dispose of the layout item.
        item.dispose();
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'before-show'` message.
     */
    onBeforeShow(msg) {
        super.onBeforeShow(msg);
        this.parent.update();
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        super.onBeforeAttach(msg);
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-shown'` message.
     */
    onChildShown(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-hidden'` message.
     */
    onChildHidden(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'resize'` message.
     */
    onResize(msg) {
        if (this.parent.isVisible) {
            this._update(msg.width, msg.height);
        }
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        if (this.parent.isVisible) {
            this._update(-1, -1);
        }
    }
    /**
     * A message handler invoked on a `'fit-request'` message.
     */
    onFitRequest(msg) {
        if (this.parent.isAttached) {
            this._fit();
        }
    }
    /**
     * Update the item position.
     *
     * @param i Item index
     * @param isHorizontal Whether the layout is horizontal or not
     * @param left Left position in pixels
     * @param top Top position in pixels
     * @param height Item height
     * @param width Item width
     * @param size Item size
     */
    updateItemPosition(i, isHorizontal, left, top, height, width, size) {
        const item = this._items[i];
        if (item.isHidden) {
            return;
        }
        // Fetch the style for the handle.
        let handleStyle = this._handles[i].style;
        // Update the widget and handle, and advance the relevant edge.
        if (isHorizontal) {
            left += this.widgetOffset;
            item.update(left, top, size, height);
            left += size;
            handleStyle.top = `${top}px`;
            handleStyle.left = `${left}px`;
            handleStyle.width = `${this._spacing}px`;
            handleStyle.height = `${height}px`;
        }
        else {
            top += this.widgetOffset;
            item.update(left, top, width, size);
            top += size;
            handleStyle.top = `${top}px`;
            handleStyle.left = `${left}px`;
            handleStyle.width = `${width}px`;
            handleStyle.height = `${this._spacing}px`;
        }
    }
    /**
     * Fit the layout to the total size required by the widgets.
     */
    _fit() {
        // Update the handles and track the visible widget count.
        let nVisible = 0;
        let lastHandleIndex = -1;
        for (let i = 0, n = this._items.length; i < n; ++i) {
            if (this._items[i].isHidden) {
                this._handles[i].classList.add('lm-mod-hidden');
            }
            else {
                this._handles[i].classList.remove('lm-mod-hidden');
                lastHandleIndex = i;
                nVisible++;
            }
        }
        // Hide the handle for the last visible widget.
        if (lastHandleIndex !== -1) {
            this._handles[lastHandleIndex].classList.add('lm-mod-hidden');
        }
        // Update the fixed space for the visible items.
        this._fixed =
            this._spacing * Math.max(0, nVisible - 1) +
                this.widgetOffset * this._items.length;
        // Setup the computed minimum size.
        let horz = this._orientation === 'horizontal';
        let minW = horz ? this._fixed : 0;
        let minH = horz ? 0 : this._fixed;
        // Update the sizers and computed size limits.
        for (let i = 0, n = this._items.length; i < n; ++i) {
            // Fetch the item and corresponding box sizer.
            let item = this._items[i];
            let sizer = this._sizers[i];
            // Prevent resizing unless necessary.
            if (sizer.size > 0) {
                sizer.sizeHint = sizer.size;
            }
            // If the item is hidden, it should consume zero size.
            if (item.isHidden) {
                sizer.minSize = 0;
                sizer.maxSize = 0;
                continue;
            }
            // Update the size limits for the item.
            item.fit();
            // Update the stretch factor.
            sizer.stretch = SplitLayout.getStretch(item.widget);
            // Update the sizer limits and computed min size.
            if (horz) {
                sizer.minSize = item.minWidth;
                sizer.maxSize = item.maxWidth;
                minW += item.minWidth;
                minH = Math.max(minH, item.minHeight);
            }
            else {
                sizer.minSize = item.minHeight;
                sizer.maxSize = item.maxHeight;
                minH += item.minHeight;
                minW = Math.max(minW, item.minWidth);
            }
        }
        // Update the box sizing and add it to the computed min size.
        let box = (this._box = ElementExt.boxSizing(this.parent.node));
        minW += box.horizontalSum;
        minH += box.verticalSum;
        // Update the parent's min size constraints.
        let style = this.parent.node.style;
        style.minWidth = `${minW}px`;
        style.minHeight = `${minH}px`;
        // Set the dirty flag to ensure only a single update occurs.
        this._dirty = true;
        // Notify the ancestor that it should fit immediately. This may
        // cause a resize of the parent, fulfilling the required update.
        if (this.parent.parent) {
            MessageLoop.sendMessage(this.parent.parent, Widget.Msg.FitRequest);
        }
        // If the dirty flag is still set, the parent was not resized.
        // Trigger the required update on the parent widget immediately.
        if (this._dirty) {
            MessageLoop.sendMessage(this.parent, Widget.Msg.UpdateRequest);
        }
    }
    /**
     * Update the layout position and size of the widgets.
     *
     * The parent offset dimensions should be `-1` if unknown.
     */
    _update(offsetWidth, offsetHeight) {
        // Clear the dirty flag to indicate the update occurred.
        this._dirty = false;
        // Compute the visible item count.
        let nVisible = 0;
        for (let i = 0, n = this._items.length; i < n; ++i) {
            nVisible += +!this._items[i].isHidden;
        }
        // Bail early if there are no visible items to layout.
        if (nVisible === 0 && this.widgetOffset === 0) {
            return;
        }
        // Measure the parent if the offset dimensions are unknown.
        if (offsetWidth < 0) {
            offsetWidth = this.parent.node.offsetWidth;
        }
        if (offsetHeight < 0) {
            offsetHeight = this.parent.node.offsetHeight;
        }
        // Ensure the parent box sizing data is computed.
        if (!this._box) {
            this._box = ElementExt.boxSizing(this.parent.node);
        }
        // Compute the actual layout bounds adjusted for border and padding.
        let top = this._box.paddingTop;
        let left = this._box.paddingLeft;
        let width = offsetWidth - this._box.horizontalSum;
        let height = offsetHeight - this._box.verticalSum;
        // Set up the variables for justification and alignment offset.
        let extra = 0;
        let offset = 0;
        let horz = this._orientation === 'horizontal';
        if (nVisible > 0) {
            // Compute the adjusted layout space.
            let space;
            if (horz) {
                // left += this.widgetOffset;
                space = Math.max(0, width - this._fixed);
            }
            else {
                // top += this.widgetOffset;
                space = Math.max(0, height - this._fixed);
            }
            // Scale the size hints if they are normalized.
            if (this._hasNormedSizes) {
                for (let sizer of this._sizers) {
                    sizer.sizeHint *= space;
                }
                this._hasNormedSizes = false;
            }
            // Distribute the layout space to the box sizers.
            let delta = BoxEngine.calc(this._sizers, space);
            // Account for alignment if there is extra layout space.
            if (delta > 0) {
                switch (this._alignment) {
                    case 'start':
                        break;
                    case 'center':
                        extra = 0;
                        offset = delta / 2;
                        break;
                    case 'end':
                        extra = 0;
                        offset = delta;
                        break;
                    case 'justify':
                        extra = delta / nVisible;
                        offset = 0;
                        break;
                    default:
                        throw 'unreachable';
                }
            }
        }
        // Layout the items using the computed box sizes.
        for (let i = 0, n = this._items.length; i < n; ++i) {
            // Fetch the item.
            const item = this._items[i];
            // Fetch the computed size for the widget.
            const size = item.isHidden ? 0 : this._sizers[i].size + extra;
            this.updateItemPosition(i, horz, horz ? left + offset : left, horz ? top : top + offset, height, width, size);
            const fullOffset = this.widgetOffset +
                (this._handles[i].classList.contains('lm-mod-hidden')
                    ? 0
                    : this._spacing);
            if (horz) {
                left += size + fullOffset;
            }
            else {
                top += size + fullOffset;
            }
        }
    }
}
/**
 * The namespace for the `SplitLayout` class statics.
 */
(function (SplitLayout) {
    /**
     * Get the split layout stretch factor for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The split layout stretch factor for the widget.
     */
    function getStretch(widget) {
        return Private$h.stretchProperty.get(widget);
    }
    SplitLayout.getStretch = getStretch;
    /**
     * Set the split layout stretch factor for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the stretch factor.
     */
    function setStretch(widget, value) {
        Private$h.stretchProperty.set(widget, value);
    }
    SplitLayout.setStretch = setStretch;
})(SplitLayout || (SplitLayout = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$h;
(function (Private) {
    /**
     * The property descriptor for a widget stretch factor.
     */
    Private.stretchProperty = new AttachedProperty({
        name: 'stretch',
        create: () => 0,
        coerce: (owner, value) => Math.max(0, Math.floor(value)),
        changed: onChildSizingChanged
    });
    /**
     * Create a new box sizer with the given size hint.
     */
    function createSizer(size) {
        let sizer = new BoxSizer();
        sizer.sizeHint = Math.floor(size);
        return sizer;
    }
    Private.createSizer = createSizer;
    /**
     * Create a new split handle node using the given renderer.
     */
    function createHandle(renderer) {
        let handle = renderer.createHandle();
        handle.style.position = 'absolute';
        // Do not use size containment to allow the handle to fill the available space
        handle.style.contain = 'style';
        return handle;
    }
    Private.createHandle = createHandle;
    /**
     * Compute the average size of an array of box sizers.
     */
    function averageSize(sizers) {
        return sizers.reduce((v, s) => v + s.size, 0) / sizers.length || 0;
    }
    Private.averageSize = averageSize;
    /**
     * Normalize an array of values.
     */
    function normalize(values) {
        let n = values.length;
        if (n === 0) {
            return [];
        }
        let sum = values.reduce((a, b) => a + Math.abs(b), 0);
        return sum === 0 ? values.map(v => 1 / n) : values.map(v => v / sum);
    }
    Private.normalize = normalize;
    /**
     * The change handler for the attached sizing properties.
     */
    function onChildSizingChanged(child) {
        if (child.parent && child.parent.layout instanceof SplitLayout) {
            child.parent.fit();
        }
    }
})(Private$h || (Private$h = {}));

/*
 * Copyright (c) Jupyter Development Team.
 * Distributed under the terms of the Modified BSD License.
 */
/**
 * A layout which arranges its widgets into collapsible resizable sections.
 */
class AccordionLayout extends SplitLayout {
    /**
     * Construct a new accordion layout.
     *
     * @param options - The options for initializing the layout.
     *
     * #### Notes
     * The default orientation will be vertical.
     *
     * Titles must be rotated for horizontal accordion panel using CSS: see accordionpanel.css
     */
    constructor(options) {
        super({ ...options, orientation: options.orientation || 'vertical' });
        this._titles = [];
        this.titleSpace = options.titleSpace || 22;
    }
    /**
     * The section title height or width depending on the orientation.
     */
    get titleSpace() {
        return this.widgetOffset;
    }
    set titleSpace(value) {
        value = Utils$1.clampDimension(value);
        if (this.widgetOffset === value) {
            return;
        }
        this.widgetOffset = value;
        if (!this.parent) {
            return;
        }
        this.parent.fit();
    }
    /**
     * A read-only array of the section titles in the panel.
     */
    get titles() {
        return this._titles;
    }
    /**
     * Dispose of the resources held by the layout.
     */
    dispose() {
        if (this.isDisposed) {
            return;
        }
        // Clear the layout state.
        this._titles.length = 0;
        // Dispose of the rest of the layout.
        super.dispose();
    }
    updateTitle(index, widget) {
        const oldTitle = this._titles[index];
        const expanded = oldTitle.classList.contains('lm-mod-expanded');
        const newTitle = Private$g.createTitle(this.renderer, widget.title, expanded);
        this._titles[index] = newTitle;
        // Add the title node to the parent before the widget.
        this.parent.node.replaceChild(newTitle, oldTitle);
    }
    /**
     * Insert a widget into the layout at the specified index.
     *
     * @param index - The index at which to insert the widget.
     *
     * @param widget - The widget to insert into the layout.
     *
     * #### Notes
     * The index will be clamped to the bounds of the widgets.
     *
     * If the widget is already added to the layout, it will be moved.
     *
     * #### Undefined Behavior
     * An `index` which is non-integral.
     */
    insertWidget(index, widget) {
        if (!widget.id) {
            widget.id = `id-${UUID.uuid4()}`;
        }
        super.insertWidget(index, widget);
    }
    /**
     * Attach a widget to the parent's DOM node.
     *
     * @param index - The current index of the widget in the layout.
     *
     * @param widget - The widget to attach to the parent.
     */
    attachWidget(index, widget) {
        const title = Private$g.createTitle(this.renderer, widget.title, !widget.isHidden);
        ArrayExt.insert(this._titles, index, title);
        // Add the title node to the parent before the widget.
        this.parent.node.appendChild(title);
        widget.node.setAttribute('role', 'region');
        widget.node.setAttribute('aria-labelledby', title.id);
        super.attachWidget(index, widget);
    }
    /**
     * Move a widget in the parent's DOM node.
     *
     * @param fromIndex - The previous index of the widget in the layout.
     *
     * @param toIndex - The current index of the widget in the layout.
     *
     * @param widget - The widget to move in the parent.
     */
    moveWidget(fromIndex, toIndex, widget) {
        ArrayExt.move(this._titles, fromIndex, toIndex);
        super.moveWidget(fromIndex, toIndex, widget);
    }
    /**
     * Detach a widget from the parent's DOM node.
     *
     * @param index - The previous index of the widget in the layout.
     *
     * @param widget - The widget to detach from the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    detachWidget(index, widget) {
        const title = ArrayExt.removeAt(this._titles, index);
        this.parent.node.removeChild(title);
        super.detachWidget(index, widget);
    }
    /**
     * Update the item position.
     *
     * @param i Item index
     * @param isHorizontal Whether the layout is horizontal or not
     * @param left Left position in pixels
     * @param top Top position in pixels
     * @param height Item height
     * @param width Item width
     * @param size Item size
     */
    updateItemPosition(i, isHorizontal, left, top, height, width, size) {
        const titleStyle = this._titles[i].style;
        // Titles must be rotated for horizontal accordion panel using CSS: see accordionpanel.css
        titleStyle.top = `${top}px`;
        titleStyle.left = `${left}px`;
        titleStyle.height = `${this.widgetOffset}px`;
        if (isHorizontal) {
            titleStyle.width = `${height}px`;
        }
        else {
            titleStyle.width = `${width}px`;
        }
        super.updateItemPosition(i, isHorizontal, left, top, height, width, size);
    }
}
var Private$g;
(function (Private) {
    /**
     * Create the title HTML element.
     *
     * @param renderer Accordion renderer
     * @param data Widget title
     * @returns Title HTML element
     */
    function createTitle(renderer, data, expanded = true) {
        const title = renderer.createSectionTitle(data);
        title.style.position = 'absolute';
        title.style.contain = 'strict';
        title.setAttribute('aria-label', `${data.label} Section`);
        title.setAttribute('aria-expanded', expanded ? 'true' : 'false');
        title.setAttribute('aria-controls', data.owner.id);
        if (expanded) {
            title.classList.add('lm-mod-expanded');
        }
        return title;
    }
    Private.createTitle = createTitle;
})(Private$g || (Private$g = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A simple and convenient panel widget class.
 *
 * #### Notes
 * This class is suitable as a base class for implementing a variety of
 * convenience panel widgets, but can also be used directly with CSS to
 * arrange a collection of widgets.
 *
 * This class provides a convenience wrapper around a {@link PanelLayout}.
 */
class Panel extends Widget {
    /**
     * Construct a new panel.
     *
     * @param options - The options for initializing the panel.
     */
    constructor(options = {}) {
        super();
        this.addClass('lm-Panel');
        this.layout = Private$f.createLayout(options);
    }
    /**
     * A read-only array of the widgets in the panel.
     */
    get widgets() {
        return this.layout.widgets;
    }
    /**
     * Add a widget to the end of the panel.
     *
     * @param widget - The widget to add to the panel.
     *
     * #### Notes
     * If the widget is already contained in the panel, it will be moved.
     */
    addWidget(widget) {
        this.layout.addWidget(widget);
    }
    /**
     * Insert a widget at the specified index.
     *
     * @param index - The index at which to insert the widget.
     *
     * @param widget - The widget to insert into to the panel.
     *
     * #### Notes
     * If the widget is already contained in the panel, it will be moved.
     */
    insertWidget(index, widget) {
        this.layout.insertWidget(index, widget);
    }
}
/**
 * The namespace for the module implementation details.
 */
var Private$f;
(function (Private) {
    /**
     * Create a panel layout for the given panel options.
     */
    function createLayout(options) {
        return options.layout || new PanelLayout();
    }
    Private.createLayout = createLayout;
})(Private$f || (Private$f = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A panel which arranges its widgets into resizable sections.
 *
 * #### Notes
 * This class provides a convenience wrapper around a {@link SplitLayout}.
 */
class SplitPanel extends Panel {
    /**
     * Construct a new split panel.
     *
     * @param options - The options for initializing the split panel.
     */
    constructor(options = {}) {
        super({ layout: Private$e.createLayout(options) });
        this._handleMoved = new Signal(this);
        this._pressData = null;
        this.addClass('lm-SplitPanel');
    }
    /**
     * Dispose of the resources held by the panel.
     */
    dispose() {
        this._releaseMouse();
        super.dispose();
    }
    /**
     * Get the layout orientation for the split panel.
     */
    get orientation() {
        return this.layout.orientation;
    }
    /**
     * Set the layout orientation for the split panel.
     */
    set orientation(value) {
        this.layout.orientation = value;
    }
    /**
     * Get the content alignment for the split panel.
     *
     * #### Notes
     * This is the alignment of the widgets in the layout direction.
     *
     * The alignment has no effect if the widgets can expand to fill the
     * entire split panel.
     */
    get alignment() {
        return this.layout.alignment;
    }
    /**
     * Set the content alignment for the split panel.
     *
     * #### Notes
     * This is the alignment of the widgets in the layout direction.
     *
     * The alignment has no effect if the widgets can expand to fill the
     * entire split panel.
     */
    set alignment(value) {
        this.layout.alignment = value;
    }
    /**
     * Get the inter-element spacing for the split panel.
     */
    get spacing() {
        return this.layout.spacing;
    }
    /**
     * Set the inter-element spacing for the split panel.
     */
    set spacing(value) {
        this.layout.spacing = value;
    }
    /**
     * The renderer used by the split panel.
     */
    get renderer() {
        return this.layout.renderer;
    }
    /**
     * A signal emitted when a split handle has moved.
     */
    get handleMoved() {
        return this._handleMoved;
    }
    /**
     * A read-only array of the split handles in the panel.
     */
    get handles() {
        return this.layout.handles;
    }
    /**
     * Get the relative sizes of the widgets in the panel.
     *
     * @returns A new array of the relative sizes of the widgets.
     *
     * #### Notes
     * The returned sizes reflect the sizes of the widgets normalized
     * relative to their siblings.
     *
     * This method **does not** measure the DOM nodes.
     */
    relativeSizes() {
        return this.layout.relativeSizes();
    }
    /**
     * Set the relative sizes for the widgets in the panel.
     *
     * @param sizes - The relative sizes for the widgets in the panel.
     * @param update - Update the layout after setting relative sizes.
     * Default is True.
     *
     * #### Notes
     * Extra values are ignored, too few will yield an undefined layout.
     *
     * The actual geometry of the DOM nodes is updated asynchronously.
     */
    setRelativeSizes(sizes, update = true) {
        this.layout.setRelativeSizes(sizes, update);
    }
    /**
     * Handle the DOM events for the split panel.
     *
     * @param event - The DOM event sent to the panel.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the panel's DOM node. It should
     * not be called directly by user code.
     */
    handleEvent(event) {
        switch (event.type) {
            case 'pointerdown':
                this._evtPointerDown(event);
                break;
            case 'pointermove':
                this._evtPointerMove(event);
                break;
            case 'pointerup':
                this._evtPointerUp(event);
                break;
            case 'keydown':
                this._evtKeyDown(event);
                break;
            case 'contextmenu':
                event.preventDefault();
                event.stopPropagation();
                break;
        }
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        this.node.addEventListener('pointerdown', this);
    }
    /**
     * A message handler invoked on an `'after-detach'` message.
     */
    onAfterDetach(msg) {
        this.node.removeEventListener('pointerdown', this);
        this._releaseMouse();
    }
    /**
     * A message handler invoked on a `'child-added'` message.
     */
    onChildAdded(msg) {
        msg.child.addClass('lm-SplitPanel-child');
        this._releaseMouse();
    }
    /**
     * A message handler invoked on a `'child-removed'` message.
     */
    onChildRemoved(msg) {
        msg.child.removeClass('lm-SplitPanel-child');
        this._releaseMouse();
    }
    /**
     * Handle the `'keydown'` event for the split panel.
     */
    _evtKeyDown(event) {
        // Stop input events during drag.
        if (this._pressData) {
            event.preventDefault();
            event.stopPropagation();
        }
        // Release the mouse if `Escape` is pressed.
        if (event.keyCode === 27) {
            this._releaseMouse();
        }
    }
    /**
     * Handle the `'pointerdown'` event for the split panel.
     */
    _evtPointerDown(event) {
        // Do nothing if the primary button is not pressed.
        if (event.button !== 0) {
            return;
        }
        // Find the handle which contains the target, if any.
        let layout = this.layout;
        let index = ArrayExt.findFirstIndex(layout.handles, handle => {
            return handle.contains(event.target);
        });
        // Bail early if the mouse press was not on a handle.
        if (index === -1) {
            return;
        }
        // Stop the event when a split handle is pressed.
        event.preventDefault();
        event.stopPropagation();
        // Add the extra document listeners.
        document.addEventListener('pointerup', this, true);
        document.addEventListener('pointermove', this, true);
        document.addEventListener('keydown', this, true);
        document.addEventListener('contextmenu', this, true);
        // Compute the offset delta for the handle press.
        let delta;
        let handle = layout.handles[index];
        let rect = handle.getBoundingClientRect();
        if (layout.orientation === 'horizontal') {
            delta = event.clientX - rect.left;
        }
        else {
            delta = event.clientY - rect.top;
        }
        // Override the cursor and store the press data.
        let style = window.getComputedStyle(handle);
        let override = Drag.overrideCursor(style.cursor);
        this._pressData = { index, delta, override };
    }
    /**
     * Handle the `'pointermove'` event for the split panel.
     */
    _evtPointerMove(event) {
        // Stop the event when dragging a split handle.
        event.preventDefault();
        event.stopPropagation();
        // Compute the desired offset position for the handle.
        let pos;
        let layout = this.layout;
        let rect = this.node.getBoundingClientRect();
        if (layout.orientation === 'horizontal') {
            pos = event.clientX - rect.left - this._pressData.delta;
        }
        else {
            pos = event.clientY - rect.top - this._pressData.delta;
        }
        // Move the handle as close to the desired position as possible.
        layout.moveHandle(this._pressData.index, pos);
    }
    /**
     * Handle the `'pointerup'` event for the split panel.
     */
    _evtPointerUp(event) {
        // Do nothing if the primary button is not released.
        if (event.button !== 0) {
            return;
        }
        // Stop the event when releasing a handle.
        event.preventDefault();
        event.stopPropagation();
        // Finalize the mouse release.
        this._releaseMouse();
    }
    /**
     * Release the mouse grab for the split panel.
     */
    _releaseMouse() {
        // Bail early if no drag is in progress.
        if (!this._pressData) {
            return;
        }
        // Clear the override cursor.
        this._pressData.override.dispose();
        this._pressData = null;
        // Emit the handle moved signal.
        this._handleMoved.emit();
        // Remove the extra document listeners.
        document.removeEventListener('keydown', this, true);
        document.removeEventListener('pointerup', this, true);
        document.removeEventListener('pointermove', this, true);
        document.removeEventListener('contextmenu', this, true);
    }
}
/**
 * The namespace for the `SplitPanel` class statics.
 */
(function (SplitPanel) {
    /**
     * The default implementation of `IRenderer`.
     */
    class Renderer {
        /**
         * Create a new handle for use with a split panel.
         *
         * @returns A new handle element for a split panel.
         */
        createHandle() {
            let handle = document.createElement('div');
            handle.className = 'lm-SplitPanel-handle';
            return handle;
        }
    }
    SplitPanel.Renderer = Renderer;
    /**
     * The default `Renderer` instance.
     */
    SplitPanel.defaultRenderer = new Renderer();
    /**
     * Get the split panel stretch factor for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The split panel stretch factor for the widget.
     */
    function getStretch(widget) {
        return SplitLayout.getStretch(widget);
    }
    SplitPanel.getStretch = getStretch;
    /**
     * Set the split panel stretch factor for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the stretch factor.
     */
    function setStretch(widget, value) {
        SplitLayout.setStretch(widget, value);
    }
    SplitPanel.setStretch = setStretch;
})(SplitPanel || (SplitPanel = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$e;
(function (Private) {
    /**
     * Create a split layout for the given panel options.
     */
    function createLayout(options) {
        return (options.layout ||
            new SplitLayout({
                renderer: options.renderer || SplitPanel.defaultRenderer,
                orientation: options.orientation,
                alignment: options.alignment,
                spacing: options.spacing
            }));
    }
    Private.createLayout = createLayout;
})(Private$e || (Private$e = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/**
 * A panel which arranges its widgets into resizable sections separated by a title widget.
 *
 * #### Notes
 * This class provides a convenience wrapper around {@link AccordionLayout}.
 *
 * See also the related [example](../../examples/accordionpanel/index.html) and
 * its [source](https://github.com/jupyterlab/lumino/tree/main/examples/example-accordionpanel).
 */
class AccordionPanel extends SplitPanel {
    /**
     * Construct a new accordion panel.
     *
     * @param options - The options for initializing the accordion panel.
     *
     */
    constructor(options = {}) {
        super({ ...options, layout: Private$d.createLayout(options) });
        this._widgetSizesCache = new WeakMap();
        this._expansionToggled = new Signal(this);
        this.addClass('lm-AccordionPanel');
    }
    /**
     * The renderer used by the accordion panel.
     */
    get renderer() {
        return this.layout.renderer;
    }
    /**
     * The section title space.
     *
     * This is the height if the panel is vertical and the width if it is
     * horizontal.
     */
    get titleSpace() {
        return this.layout.titleSpace;
    }
    set titleSpace(value) {
        this.layout.titleSpace = value;
    }
    /**
     * A read-only array of the section titles in the panel.
     */
    get titles() {
        return this.layout.titles;
    }
    /**
     * A signal emitted when a widget of the AccordionPanel is collapsed or expanded.
     */
    get expansionToggled() {
        return this._expansionToggled;
    }
    /**
     * Add a widget to the end of the panel.
     *
     * @param widget - The widget to add to the panel.
     *
     * #### Notes
     * If the widget is already contained in the panel, it will be moved.
     */
    addWidget(widget) {
        super.addWidget(widget);
        widget.title.changed.connect(this._onTitleChanged, this);
    }
    /**
     * Collapse the widget at position `index`.
     *
     * #### Notes
     * If no widget is found for `index`, this will bail.
     *
     * @param index Widget index
     */
    collapse(index) {
        const widget = this.layout.widgets[index];
        if (widget && !widget.isHidden) {
            this._toggleExpansion(index);
        }
    }
    /**
     * Expand the widget at position `index`.
     *
     * #### Notes
     * If no widget is found for `index`, this will bail.
     *
     * @param index Widget index
     */
    expand(index) {
        const widget = this.layout.widgets[index];
        if (widget && widget.isHidden) {
            this._toggleExpansion(index);
        }
    }
    /**
     * Insert a widget at the specified index.
     *
     * @param index - The index at which to insert the widget.
     *
     * @param widget - The widget to insert into to the panel.
     *
     * #### Notes
     * If the widget is already contained in the panel, it will be moved.
     */
    insertWidget(index, widget) {
        super.insertWidget(index, widget);
        widget.title.changed.connect(this._onTitleChanged, this);
    }
    /**
     * Handle the DOM events for the accordion panel.
     *
     * @param event - The DOM event sent to the panel.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the panel's DOM node. It should
     * not be called directly by user code.
     */
    handleEvent(event) {
        super.handleEvent(event);
        switch (event.type) {
            case 'click':
                this._evtClick(event);
                break;
            case 'keydown':
                this._eventKeyDown(event);
                break;
        }
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        this.node.addEventListener('click', this);
        this.node.addEventListener('keydown', this);
        super.onBeforeAttach(msg);
    }
    /**
     * A message handler invoked on an `'after-detach'` message.
     */
    onAfterDetach(msg) {
        super.onAfterDetach(msg);
        this.node.removeEventListener('click', this);
        this.node.removeEventListener('keydown', this);
    }
    /**
     * Handle the `changed` signal of a title object.
     */
    _onTitleChanged(sender) {
        const index = ArrayExt.findFirstIndex(this.widgets, widget => {
            return widget.contains(sender.owner);
        });
        if (index >= 0) {
            this.layout.updateTitle(index, sender.owner);
            this.update();
        }
    }
    /**
     * Compute the size of widgets in this panel on the title click event.
     * On closing, the size of the widget is cached and we will try to expand
     * the last opened widget.
     * On opening, we will use the cached size if it is available to restore the
     * widget.
     * In both cases, if we can not compute the size of widgets, we will let
     * `SplitLayout` decide.
     *
     * @param index - The index of widget to be opened of closed
     *
     * @returns Relative size of widgets in this panel, if this size can
     * not be computed, return `undefined`
     */
    _computeWidgetSize(index) {
        const layout = this.layout;
        const widget = layout.widgets[index];
        if (!widget) {
            return undefined;
        }
        const isHidden = widget.isHidden;
        const widgetSizes = layout.absoluteSizes();
        const delta = (isHidden ? -1 : 1) * this.spacing;
        const totalSize = widgetSizes.reduce((prev, curr) => prev + curr);
        let newSize = [...widgetSizes];
        if (!isHidden) {
            // Hide the widget
            const currentSize = widgetSizes[index];
            this._widgetSizesCache.set(widget, currentSize);
            newSize[index] = 0;
            const widgetToCollapse = newSize.map(sz => sz > 0).lastIndexOf(true);
            if (widgetToCollapse === -1) {
                // All widget are closed, let the `SplitLayout` compute widget sizes.
                return undefined;
            }
            newSize[widgetToCollapse] =
                widgetSizes[widgetToCollapse] + currentSize + delta;
        }
        else {
            // Show the widget
            const previousSize = this._widgetSizesCache.get(widget);
            if (!previousSize) {
                // Previous size is unavailable, let the `SplitLayout` compute widget sizes.
                return undefined;
            }
            newSize[index] += previousSize;
            const widgetToCollapse = newSize
                .map(sz => sz - previousSize > 0)
                .lastIndexOf(true);
            if (widgetToCollapse === -1) {
                // Can not reduce the size of one widget, reduce all opened widgets
                // proportionally with its size.
                newSize.forEach((_, idx) => {
                    if (idx !== index) {
                        newSize[idx] -=
                            (widgetSizes[idx] / totalSize) * (previousSize - delta);
                    }
                });
            }
            else {
                newSize[widgetToCollapse] -= previousSize - delta;
            }
        }
        return newSize.map(sz => sz / (totalSize + delta));
    }
    /**
     * Handle the `'click'` event for the accordion panel
     */
    _evtClick(event) {
        const target = event.target;
        if (target) {
            const index = ArrayExt.findFirstIndex(this.titles, title => {
                return title.contains(target);
            });
            if (index >= 0) {
                event.preventDefault();
                event.stopPropagation();
                this._toggleExpansion(index);
            }
        }
    }
    /**
     * Handle the `'keydown'` event for the accordion panel.
     */
    _eventKeyDown(event) {
        if (event.defaultPrevented) {
            return;
        }
        const target = event.target;
        let handled = false;
        if (target) {
            const index = ArrayExt.findFirstIndex(this.titles, title => {
                return title.contains(target);
            });
            if (index >= 0) {
                const keyCode = event.keyCode.toString();
                // If Space or Enter is pressed on title, emulate click event
                if (event.key.match(/Space|Enter/) || keyCode.match(/13|32/)) {
                    target.click();
                    handled = true;
                }
                else if (this.orientation === 'horizontal'
                    ? event.key.match(/ArrowLeft|ArrowRight/) || keyCode.match(/37|39/)
                    : event.key.match(/ArrowUp|ArrowDown/) || keyCode.match(/38|40/)) {
                    // If Up or Down (for vertical) / Left or Right (for horizontal) is pressed on title, loop on titles
                    const direction = event.key.match(/ArrowLeft|ArrowUp/) || keyCode.match(/37|38/)
                        ? -1
                        : 1;
                    const length = this.titles.length;
                    const newIndex = (index + length + direction) % length;
                    this.titles[newIndex].focus();
                    handled = true;
                }
                else if (event.key === 'End' || keyCode === '35') {
                    // If End is pressed on title, focus on the last title
                    this.titles[this.titles.length - 1].focus();
                    handled = true;
                }
                else if (event.key === 'Home' || keyCode === '36') {
                    // If Home is pressed on title, focus on the first title
                    this.titles[0].focus();
                    handled = true;
                }
            }
            if (handled) {
                event.preventDefault();
            }
        }
    }
    _toggleExpansion(index) {
        const title = this.titles[index];
        const widget = this.layout.widgets[index];
        const newSize = this._computeWidgetSize(index);
        if (newSize) {
            this.setRelativeSizes(newSize, false);
        }
        if (widget.isHidden) {
            title.classList.add('lm-mod-expanded');
            title.setAttribute('aria-expanded', 'true');
            widget.show();
        }
        else {
            title.classList.remove('lm-mod-expanded');
            title.setAttribute('aria-expanded', 'false');
            widget.hide();
        }
        // Emit the expansion state signal.
        this._expansionToggled.emit(index);
    }
}
/**
 * The namespace for the `AccordionPanel` class statics.
 */
(function (AccordionPanel) {
    /**
     * The default implementation of `IRenderer`.
     */
    class Renderer extends SplitPanel.Renderer {
        constructor() {
            super();
            /**
             * A selector which matches any title node in the accordion.
             */
            this.titleClassName = 'lm-AccordionPanel-title';
            this._titleID = 0;
            this._titleKeys = new WeakMap();
            this._uuid = ++Renderer._nInstance;
        }
        /**
         * Render the collapse indicator for a section title.
         *
         * @param data - The data to use for rendering the section title.
         *
         * @returns A element representing the collapse indicator.
         */
        createCollapseIcon(data) {
            return document.createElement('span');
        }
        /**
         * Render the element for a section title.
         *
         * @param data - The data to use for rendering the section title.
         *
         * @returns A element representing the section title.
         */
        createSectionTitle(data) {
            const handle = document.createElement('h3');
            handle.setAttribute('tabindex', '0');
            handle.id = this.createTitleKey(data);
            handle.className = this.titleClassName;
            for (const aData in data.dataset) {
                handle.dataset[aData] = data.dataset[aData];
            }
            const collapser = handle.appendChild(this.createCollapseIcon(data));
            collapser.className = 'lm-AccordionPanel-titleCollapser';
            const label = handle.appendChild(document.createElement('span'));
            label.className = 'lm-AccordionPanel-titleLabel';
            label.textContent = data.label;
            label.title = data.caption || data.label;
            return handle;
        }
        /**
         * Create a unique render key for the title.
         *
         * @param data - The data to use for the title.
         *
         * @returns The unique render key for the title.
         *
         * #### Notes
         * This method caches the key against the section title the first time
         * the key is generated.
         */
        createTitleKey(data) {
            let key = this._titleKeys.get(data);
            if (key === undefined) {
                key = `title-key-${this._uuid}-${this._titleID++}`;
                this._titleKeys.set(data, key);
            }
            return key;
        }
    }
    Renderer._nInstance = 0;
    AccordionPanel.Renderer = Renderer;
    /**
     * The default `Renderer` instance.
     */
    AccordionPanel.defaultRenderer = new Renderer();
})(AccordionPanel || (AccordionPanel = {}));
var Private$d;
(function (Private) {
    /**
     * Create an accordion layout for the given panel options.
     *
     * @param options Panel options
     * @returns Panel layout
     */
    function createLayout(options) {
        return (options.layout ||
            new AccordionLayout({
                renderer: options.renderer || AccordionPanel.defaultRenderer,
                orientation: options.orientation,
                alignment: options.alignment,
                spacing: options.spacing,
                titleSpace: options.titleSpace
            }));
    }
    Private.createLayout = createLayout;
})(Private$d || (Private$d = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A layout which arranges its widgets in a single row or column.
 */
class BoxLayout extends PanelLayout {
    /**
     * Construct a new box layout.
     *
     * @param options - The options for initializing the layout.
     */
    constructor(options = {}) {
        super();
        this._fixed = 0;
        this._spacing = 4;
        this._dirty = false;
        this._sizers = [];
        this._items = [];
        this._box = null;
        this._alignment = 'start';
        this._direction = 'top-to-bottom';
        if (options.direction !== undefined) {
            this._direction = options.direction;
        }
        if (options.alignment !== undefined) {
            this._alignment = options.alignment;
        }
        if (options.spacing !== undefined) {
            this._spacing = Utils$1.clampDimension(options.spacing);
        }
    }
    /**
     * Dispose of the resources held by the layout.
     */
    dispose() {
        // Dispose of the layout items.
        for (const item of this._items) {
            item.dispose();
        }
        // Clear the layout state.
        this._box = null;
        this._items.length = 0;
        this._sizers.length = 0;
        // Dispose of the rest of the layout.
        super.dispose();
    }
    /**
     * Get the layout direction for the box layout.
     */
    get direction() {
        return this._direction;
    }
    /**
     * Set the layout direction for the box layout.
     */
    set direction(value) {
        if (this._direction === value) {
            return;
        }
        this._direction = value;
        if (!this.parent) {
            return;
        }
        this.parent.dataset['direction'] = value;
        this.parent.fit();
    }
    /**
     * Get the content alignment for the box layout.
     *
     * #### Notes
     * This is the alignment of the widgets in the layout direction.
     *
     * The alignment has no effect if the widgets can expand to fill the
     * entire box layout.
     */
    get alignment() {
        return this._alignment;
    }
    /**
     * Set the content alignment for the box layout.
     *
     * #### Notes
     * This is the alignment of the widgets in the layout direction.
     *
     * The alignment has no effect if the widgets can expand to fill the
     * entire box layout.
     */
    set alignment(value) {
        if (this._alignment === value) {
            return;
        }
        this._alignment = value;
        if (!this.parent) {
            return;
        }
        this.parent.dataset['alignment'] = value;
        this.parent.update();
    }
    /**
     * Get the inter-element spacing for the box layout.
     */
    get spacing() {
        return this._spacing;
    }
    /**
     * Set the inter-element spacing for the box layout.
     */
    set spacing(value) {
        value = Utils$1.clampDimension(value);
        if (this._spacing === value) {
            return;
        }
        this._spacing = value;
        if (!this.parent) {
            return;
        }
        this.parent.fit();
    }
    /**
     * Perform layout initialization which requires the parent widget.
     */
    init() {
        this.parent.dataset['direction'] = this.direction;
        this.parent.dataset['alignment'] = this.alignment;
        super.init();
    }
    /**
     * Attach a widget to the parent's DOM node.
     *
     * @param index - The current index of the widget in the layout.
     *
     * @param widget - The widget to attach to the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    attachWidget(index, widget) {
        // Create and add a new layout item for the widget.
        ArrayExt.insert(this._items, index, new LayoutItem(widget));
        // Create and add a new sizer for the widget.
        ArrayExt.insert(this._sizers, index, new BoxSizer());
        // Send a `'before-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        }
        // Add the widget's node to the parent.
        this.parent.node.appendChild(widget.node);
        // Send an `'after-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
        }
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * Move a widget in the parent's DOM node.
     *
     * @param fromIndex - The previous index of the widget in the layout.
     *
     * @param toIndex - The current index of the widget in the layout.
     *
     * @param widget - The widget to move in the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    moveWidget(fromIndex, toIndex, widget) {
        // Move the layout item for the widget.
        ArrayExt.move(this._items, fromIndex, toIndex);
        // Move the sizer for the widget.
        ArrayExt.move(this._sizers, fromIndex, toIndex);
        // Post an update request for the parent widget.
        this.parent.update();
    }
    /**
     * Detach a widget from the parent's DOM node.
     *
     * @param index - The previous index of the widget in the layout.
     *
     * @param widget - The widget to detach from the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    detachWidget(index, widget) {
        // Remove the layout item for the widget.
        let item = ArrayExt.removeAt(this._items, index);
        // Remove the sizer for the widget.
        ArrayExt.removeAt(this._sizers, index);
        // Send a `'before-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        }
        // Remove the widget's node from the parent.
        this.parent.node.removeChild(widget.node);
        // Send an `'after-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
        }
        // Dispose of the layout item.
        item.dispose();
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'before-show'` message.
     */
    onBeforeShow(msg) {
        super.onBeforeShow(msg);
        this.parent.update();
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        super.onBeforeAttach(msg);
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-shown'` message.
     */
    onChildShown(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-hidden'` message.
     */
    onChildHidden(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'resize'` message.
     */
    onResize(msg) {
        if (this.parent.isVisible) {
            this._update(msg.width, msg.height);
        }
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        if (this.parent.isVisible) {
            this._update(-1, -1);
        }
    }
    /**
     * A message handler invoked on a `'fit-request'` message.
     */
    onFitRequest(msg) {
        if (this.parent.isAttached) {
            this._fit();
        }
    }
    /**
     * Fit the layout to the total size required by the widgets.
     */
    _fit() {
        // Compute the visible item count.
        let nVisible = 0;
        for (let i = 0, n = this._items.length; i < n; ++i) {
            nVisible += +!this._items[i].isHidden;
        }
        // Update the fixed space for the visible items.
        this._fixed = this._spacing * Math.max(0, nVisible - 1);
        // Setup the computed minimum size.
        let horz = Private$c.isHorizontal(this._direction);
        let minW = horz ? this._fixed : 0;
        let minH = horz ? 0 : this._fixed;
        // Update the sizers and computed minimum size.
        for (let i = 0, n = this._items.length; i < n; ++i) {
            // Fetch the item and corresponding box sizer.
            let item = this._items[i];
            let sizer = this._sizers[i];
            // If the item is hidden, it should consume zero size.
            if (item.isHidden) {
                sizer.minSize = 0;
                sizer.maxSize = 0;
                continue;
            }
            // Update the size limits for the item.
            item.fit();
            // Update the size basis and stretch factor.
            sizer.sizeHint = BoxLayout.getSizeBasis(item.widget);
            sizer.stretch = BoxLayout.getStretch(item.widget);
            // Update the sizer limits and computed min size.
            if (horz) {
                sizer.minSize = item.minWidth;
                sizer.maxSize = item.maxWidth;
                minW += item.minWidth;
                minH = Math.max(minH, item.minHeight);
            }
            else {
                sizer.minSize = item.minHeight;
                sizer.maxSize = item.maxHeight;
                minH += item.minHeight;
                minW = Math.max(minW, item.minWidth);
            }
        }
        // Update the box sizing and add it to the computed min size.
        let box = (this._box = ElementExt.boxSizing(this.parent.node));
        minW += box.horizontalSum;
        minH += box.verticalSum;
        // Update the parent's min size constraints.
        let style = this.parent.node.style;
        style.minWidth = `${minW}px`;
        style.minHeight = `${minH}px`;
        // Set the dirty flag to ensure only a single update occurs.
        this._dirty = true;
        // Notify the ancestor that it should fit immediately. This may
        // cause a resize of the parent, fulfilling the required update.
        if (this.parent.parent) {
            MessageLoop.sendMessage(this.parent.parent, Widget.Msg.FitRequest);
        }
        // If the dirty flag is still set, the parent was not resized.
        // Trigger the required update on the parent widget immediately.
        if (this._dirty) {
            MessageLoop.sendMessage(this.parent, Widget.Msg.UpdateRequest);
        }
    }
    /**
     * Update the layout position and size of the widgets.
     *
     * The parent offset dimensions should be `-1` if unknown.
     */
    _update(offsetWidth, offsetHeight) {
        // Clear the dirty flag to indicate the update occurred.
        this._dirty = false;
        // Compute the visible item count.
        let nVisible = 0;
        for (let i = 0, n = this._items.length; i < n; ++i) {
            nVisible += +!this._items[i].isHidden;
        }
        // Bail early if there are no visible items to layout.
        if (nVisible === 0) {
            return;
        }
        // Measure the parent if the offset dimensions are unknown.
        if (offsetWidth < 0) {
            offsetWidth = this.parent.node.offsetWidth;
        }
        if (offsetHeight < 0) {
            offsetHeight = this.parent.node.offsetHeight;
        }
        // Ensure the parent box sizing data is computed.
        if (!this._box) {
            this._box = ElementExt.boxSizing(this.parent.node);
        }
        // Compute the layout area adjusted for border and padding.
        let top = this._box.paddingTop;
        let left = this._box.paddingLeft;
        let width = offsetWidth - this._box.horizontalSum;
        let height = offsetHeight - this._box.verticalSum;
        // Distribute the layout space and adjust the start position.
        let delta;
        switch (this._direction) {
            case 'left-to-right':
                delta = BoxEngine.calc(this._sizers, Math.max(0, width - this._fixed));
                break;
            case 'top-to-bottom':
                delta = BoxEngine.calc(this._sizers, Math.max(0, height - this._fixed));
                break;
            case 'right-to-left':
                delta = BoxEngine.calc(this._sizers, Math.max(0, width - this._fixed));
                left += width;
                break;
            case 'bottom-to-top':
                delta = BoxEngine.calc(this._sizers, Math.max(0, height - this._fixed));
                top += height;
                break;
            default:
                throw 'unreachable';
        }
        // Setup the variables for justification and alignment offset.
        let extra = 0;
        let offset = 0;
        // Account for alignment if there is extra layout space.
        if (delta > 0) {
            switch (this._alignment) {
                case 'start':
                    break;
                case 'center':
                    extra = 0;
                    offset = delta / 2;
                    break;
                case 'end':
                    extra = 0;
                    offset = delta;
                    break;
                case 'justify':
                    extra = delta / nVisible;
                    offset = 0;
                    break;
                default:
                    throw 'unreachable';
            }
        }
        // Layout the items using the computed box sizes.
        for (let i = 0, n = this._items.length; i < n; ++i) {
            // Fetch the item.
            let item = this._items[i];
            // Ignore hidden items.
            if (item.isHidden) {
                continue;
            }
            // Fetch the computed size for the widget.
            let size = this._sizers[i].size;
            // Update the widget geometry and advance the relevant edge.
            switch (this._direction) {
                case 'left-to-right':
                    item.update(left + offset, top, size + extra, height);
                    left += size + extra + this._spacing;
                    break;
                case 'top-to-bottom':
                    item.update(left, top + offset, width, size + extra);
                    top += size + extra + this._spacing;
                    break;
                case 'right-to-left':
                    item.update(left - offset - size - extra, top, size + extra, height);
                    left -= size + extra + this._spacing;
                    break;
                case 'bottom-to-top':
                    item.update(left, top - offset - size - extra, width, size + extra);
                    top -= size + extra + this._spacing;
                    break;
                default:
                    throw 'unreachable';
            }
        }
    }
}
/**
 * The namespace for the `BoxLayout` class statics.
 */
(function (BoxLayout) {
    /**
     * Get the box layout stretch factor for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The box layout stretch factor for the widget.
     */
    function getStretch(widget) {
        return Private$c.stretchProperty.get(widget);
    }
    BoxLayout.getStretch = getStretch;
    /**
     * Set the box layout stretch factor for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the stretch factor.
     */
    function setStretch(widget, value) {
        Private$c.stretchProperty.set(widget, value);
    }
    BoxLayout.setStretch = setStretch;
    /**
     * Get the box layout size basis for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The box layout size basis for the widget.
     */
    function getSizeBasis(widget) {
        return Private$c.sizeBasisProperty.get(widget);
    }
    BoxLayout.getSizeBasis = getSizeBasis;
    /**
     * Set the box layout size basis for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the size basis.
     */
    function setSizeBasis(widget, value) {
        Private$c.sizeBasisProperty.set(widget, value);
    }
    BoxLayout.setSizeBasis = setSizeBasis;
})(BoxLayout || (BoxLayout = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$c;
(function (Private) {
    /**
     * The property descriptor for a widget stretch factor.
     */
    Private.stretchProperty = new AttachedProperty({
        name: 'stretch',
        create: () => 0,
        coerce: (owner, value) => Math.max(0, Math.floor(value)),
        changed: onChildSizingChanged
    });
    /**
     * The property descriptor for a widget size basis.
     */
    Private.sizeBasisProperty = new AttachedProperty({
        name: 'sizeBasis',
        create: () => 0,
        coerce: (owner, value) => Math.max(0, Math.floor(value)),
        changed: onChildSizingChanged
    });
    /**
     * Test whether a direction has horizontal orientation.
     */
    function isHorizontal(dir) {
        return dir === 'left-to-right' || dir === 'right-to-left';
    }
    Private.isHorizontal = isHorizontal;
    /**
     * Clamp a spacing value to an integer >= 0.
     */
    function clampSpacing(value) {
        return Math.max(0, Math.floor(value));
    }
    Private.clampSpacing = clampSpacing;
    /**
     * The change handler for the attached sizing properties.
     */
    function onChildSizingChanged(child) {
        if (child.parent && child.parent.layout instanceof BoxLayout) {
            child.parent.fit();
        }
    }
})(Private$c || (Private$c = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A panel which arranges its widgets in a single row or column.
 *
 * #### Notes
 * This class provides a convenience wrapper around a {@link BoxLayout}.
 */
class BoxPanel extends Panel {
    /**
     * Construct a new box panel.
     *
     * @param options - The options for initializing the box panel.
     */
    constructor(options = {}) {
        super({ layout: Private$b.createLayout(options) });
        this.addClass('lm-BoxPanel');
    }
    /**
     * Get the layout direction for the box panel.
     */
    get direction() {
        return this.layout.direction;
    }
    /**
     * Set the layout direction for the box panel.
     */
    set direction(value) {
        this.layout.direction = value;
    }
    /**
     * Get the content alignment for the box panel.
     *
     * #### Notes
     * This is the alignment of the widgets in the layout direction.
     *
     * The alignment has no effect if the widgets can expand to fill the
     * entire box layout.
     */
    get alignment() {
        return this.layout.alignment;
    }
    /**
     * Set the content alignment for the box panel.
     *
     * #### Notes
     * This is the alignment of the widgets in the layout direction.
     *
     * The alignment has no effect if the widgets can expand to fill the
     * entire box layout.
     */
    set alignment(value) {
        this.layout.alignment = value;
    }
    /**
     * Get the inter-element spacing for the box panel.
     */
    get spacing() {
        return this.layout.spacing;
    }
    /**
     * Set the inter-element spacing for the box panel.
     */
    set spacing(value) {
        this.layout.spacing = value;
    }
    /**
     * A message handler invoked on a `'child-added'` message.
     */
    onChildAdded(msg) {
        msg.child.addClass('lm-BoxPanel-child');
    }
    /**
     * A message handler invoked on a `'child-removed'` message.
     */
    onChildRemoved(msg) {
        msg.child.removeClass('lm-BoxPanel-child');
    }
}
/**
 * The namespace for the `BoxPanel` class statics.
 */
(function (BoxPanel) {
    /**
     * Get the box panel stretch factor for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The box panel stretch factor for the widget.
     */
    function getStretch(widget) {
        return BoxLayout.getStretch(widget);
    }
    BoxPanel.getStretch = getStretch;
    /**
     * Set the box panel stretch factor for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the stretch factor.
     */
    function setStretch(widget, value) {
        BoxLayout.setStretch(widget, value);
    }
    BoxPanel.setStretch = setStretch;
    /**
     * Get the box panel size basis for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The box panel size basis for the widget.
     */
    function getSizeBasis(widget) {
        return BoxLayout.getSizeBasis(widget);
    }
    BoxPanel.getSizeBasis = getSizeBasis;
    /**
     * Set the box panel size basis for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the size basis.
     */
    function setSizeBasis(widget, value) {
        BoxLayout.setSizeBasis(widget, value);
    }
    BoxPanel.setSizeBasis = setSizeBasis;
})(BoxPanel || (BoxPanel = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$b;
(function (Private) {
    /**
     * Create a box layout for the given panel options.
     */
    function createLayout(options) {
        return options.layout || new BoxLayout(options);
    }
    Private.createLayout = createLayout;
})(Private$b || (Private$b = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A widget which displays command items as a searchable palette.
 */
class CommandPalette extends Widget {
    /**
     * Construct a new command palette.
     *
     * @param options - The options for initializing the palette.
     */
    constructor(options) {
        super({ node: Private$a.createNode() });
        this._activeIndex = -1;
        this._itemTriggered = new Signal(this);
        this._items = [];
        this._results = null;
        this.addClass('lm-CommandPalette');
        this.setFlag(Widget.Flag.DisallowLayout);
        this.commands = options.commands;
        this.renderer = options.renderer || CommandPalette.defaultRenderer;
        this.commands.commandChanged.connect(this._onGenericChange, this);
        this.commands.keyBindingChanged.connect(this._onGenericChange, this);
    }
    /**
     * Dispose of the resources held by the widget.
     */
    dispose() {
        this._items.length = 0;
        this._results = null;
        super.dispose();
    }
    /**
     * A signal emitted when the user triggers a command item.
     *
     * #### Notes
     * This signal is emitted when an enabled item is clicked, or when
     * `Enter` is pressed while an enabled item is active. It is not
     * emitted for a header, or for a command which is executed by other
     * means, such as a key binding or a menu.
     *
     * The signal is emitted before the command is executed and before
     * the query text is cleared.
     */
    get itemTriggered() {
        return this._itemTriggered;
    }
    /**
     * The command palette search node.
     *
     * #### Notes
     * This is the node which contains the search-related elements.
     */
    get searchNode() {
        return this.node.getElementsByClassName('lm-CommandPalette-search')[0];
    }
    /**
     * The command palette input node.
     *
     * #### Notes
     * This is the actual input node for the search area.
     */
    get inputNode() {
        return this.node.getElementsByClassName('lm-CommandPalette-input')[0];
    }
    /**
     * The command palette content node.
     *
     * #### Notes
     * This is the node which holds the command item nodes.
     *
     * Modifying this node directly can lead to undefined behavior.
     */
    get contentNode() {
        return this.node.getElementsByClassName('lm-CommandPalette-content')[0];
    }
    /**
     * A read-only array of the command items in the palette.
     */
    get items() {
        return this._items;
    }
    /**
     * Add a command item to the command palette.
     *
     * @param options - The options for creating the command item.
     *
     * @returns The command item added to the palette.
     */
    addItem(options) {
        // Create a new command item for the options.
        let item = Private$a.createItem(this.commands, options);
        // Add the item to the array.
        this._items.push(item);
        // Refresh the search results.
        this.refresh();
        // Return the item added to the palette.
        return item;
    }
    /**
     * Adds command items to the command palette.
     *
     * @param items - An array of options for creating each command item.
     *
     * @returns The command items added to the palette.
     */
    addItems(items) {
        const newItems = items.map(item => Private$a.createItem(this.commands, item));
        newItems.forEach(item => this._items.push(item));
        this.refresh();
        return newItems;
    }
    /**
     * Remove an item from the command palette.
     *
     * @param item - The item to remove from the palette.
     *
     * #### Notes
     * This is a no-op if the item is not in the palette.
     */
    removeItem(item) {
        this.removeItemAt(this._items.indexOf(item));
    }
    /**
     * Remove the item at a given index from the command palette.
     *
     * @param index - The index of the item to remove.
     *
     * #### Notes
     * This is a no-op if the index is out of range.
     */
    removeItemAt(index) {
        // Remove the item from the array.
        let item = ArrayExt.removeAt(this._items, index);
        // Bail if the index is out of range.
        if (!item) {
            return;
        }
        // Refresh the search results.
        this.refresh();
    }
    /**
     * Remove all items from the command palette.
     */
    clearItems() {
        // Bail if there is nothing to remove.
        if (this._items.length === 0) {
            return;
        }
        // Clear the array of items.
        this._items.length = 0;
        // Refresh the search results.
        this.refresh();
    }
    /**
     * Clear the search results and schedule an update.
     *
     * #### Notes
     * This should be called whenever the search results of the palette
     * should be updated.
     *
     * This is typically called automatically by the palette as needed,
     * but can be called manually if the input text is programatically
     * changed.
     *
     * The rendered results are updated asynchronously.
     */
    refresh() {
        this._results = null;
        if (this.inputNode.value !== '') {
            let clear = this.node.getElementsByClassName('lm-close-icon')[0];
            clear.style.display = 'inherit';
        }
        else {
            let clear = this.node.getElementsByClassName('lm-close-icon')[0];
            clear.style.display = 'none';
        }
        this.update();
    }
    /**
     * Handle the DOM events for the command palette.
     *
     * @param event - The DOM event sent to the command palette.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the command palette's DOM node.
     * It should not be called directly by user code.
     */
    handleEvent(event) {
        switch (event.type) {
            case 'click':
                this._evtClick(event);
                break;
            case 'keydown':
                this._evtKeyDown(event);
                break;
            case 'input':
                this.refresh();
                break;
            case 'focus':
            case 'blur':
                this._toggleFocused();
                break;
        }
    }
    /**
     * Create the search results for a query.
     *
     * @param query - The raw text of the search input.
     *
     * @returns The search results, in display order.
     *
     * #### Notes
     * The default implementation of this method returns the results of
     * `CommandPalette.search()` for the palette items.
     *
     * The palette renders every returned result, so the results should
     * not include items which are not visible. Triggering a header result
     * replaces the query with the header category.
     *
     * The results are cached until `refresh()` is called. A subclass
     * which uses external state should call `refresh()` when that state
     * changes.
     *
     * A subclass may reimplement this method as needed.
     */
    search(query) {
        return CommandPalette.search(this.items, query);
    }
    /**
     * Get the index of the result to activate for new search results.
     *
     * @param query - The raw text of the search input.
     *
     * @param results - The search results returned by `search()`.
     *
     * @returns The index of the result to activate, or `-1` for none.
     *
     * #### Notes
     * The default implementation of this method returns the index of the
     * first enabled item when the query is not empty, and `-1` otherwise.
     *
     * The returned index should refer to an enabled item result.
     *
     * A subclass may reimplement this method as needed.
     */
    initialActiveIndex(query, results) {
        return query ? ArrayExt.findFirstIndex(results, Private$a.canActivate) : -1;
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        this.node.addEventListener('click', this);
        this.node.addEventListener('keydown', this);
        this.node.addEventListener('input', this);
        this.node.addEventListener('focus', this, true);
        this.node.addEventListener('blur', this, true);
    }
    /**
     * A message handler invoked on an `'after-detach'` message.
     */
    onAfterDetach(msg) {
        this.node.removeEventListener('click', this);
        this.node.removeEventListener('keydown', this);
        this.node.removeEventListener('input', this);
        this.node.removeEventListener('focus', this, true);
        this.node.removeEventListener('blur', this, true);
    }
    /**
     * A message handler invoked on an `'after-show'` message.
     */
    onAfterShow(msg) {
        this.update();
        super.onAfterShow(msg);
    }
    /**
     * A message handler invoked on an `'activate-request'` message.
     */
    onActivateRequest(msg) {
        if (this.isAttached) {
            let input = this.inputNode;
            input.focus();
            input.select();
        }
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        if (!this.isVisible) {
            // Ensure to clear the content if the widget is hidden
            VirtualDOM.render(null, this.contentNode);
            return;
        }
        // Fetch the current query text and content node.
        let query = this.inputNode.value;
        let contentNode = this.contentNode;
        // Ensure the search results are generated.
        let results = this._results;
        if (!results) {
            // Generate and store the new search results.
            results = this._results = this.search(query);
            // Reset the active index.
            this._activeIndex = this.initialActiveIndex(query, results);
        }
        // If there is no query and no results, clear the content.
        if (!query && results.length === 0) {
            VirtualDOM.render(null, contentNode);
            return;
        }
        // If the is a query but no results, render the empty message.
        if (query && results.length === 0) {
            let content = this.renderer.renderEmptyMessage({ query });
            VirtualDOM.render(content, contentNode);
            return;
        }
        // Create the render content for the search results.
        let renderer = this.renderer;
        let activeIndex = this._activeIndex;
        let content = new Array(results.length);
        for (let i = 0, n = results.length; i < n; ++i) {
            let result = results[i];
            if (result.type === 'header') {
                let indices = result.indices;
                let category = result.category;
                content[i] = renderer.renderHeader({ category, indices });
            }
            else {
                let item = result.item;
                let indices = result.indices;
                let active = i === activeIndex;
                content[i] = renderer.renderItem({ item, indices, active });
            }
        }
        // Render the search result content.
        VirtualDOM.render(content, contentNode);
        // Adjust the scroll position as needed.
        if (activeIndex < 0 || activeIndex >= results.length) {
            contentNode.scrollTop = 0;
        }
        else {
            let element = contentNode.children[activeIndex];
            ElementExt.scrollIntoViewIfNeeded(contentNode, element);
        }
    }
    /**
     * Handle the `'click'` event for the command palette.
     */
    _evtClick(event) {
        // Bail if the click is not the left button.
        if (event.button !== 0) {
            return;
        }
        // Clear input if the target is clear button
        if (event.target.classList.contains('lm-close-icon')) {
            this.inputNode.value = '';
            this.refresh();
            return;
        }
        // Find the index of the item which was clicked.
        let index = ArrayExt.findFirstIndex(this.contentNode.children, node => {
            return node.contains(event.target);
        });
        // Bail if the click was not on an item.
        if (index === -1) {
            return;
        }
        // Kill the event when a content item is clicked.
        event.preventDefault();
        event.stopPropagation();
        // Execute the item if possible.
        this._execute(index);
    }
    /**
     * Handle the `'keydown'` event for the command palette.
     */
    _evtKeyDown(event) {
        if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
            return;
        }
        switch (event.keyCode) {
            case 13: // Enter
                event.preventDefault();
                event.stopPropagation();
                this._execute(this._activeIndex);
                break;
            case 38: // Up Arrow
                event.preventDefault();
                event.stopPropagation();
                this._activatePreviousItem();
                break;
            case 40: // Down Arrow
                event.preventDefault();
                event.stopPropagation();
                this._activateNextItem();
                break;
        }
    }
    /**
     * Activate the next enabled command item.
     */
    _activateNextItem() {
        // Bail if there are no search results.
        if (!this._results || this._results.length === 0) {
            return;
        }
        // Find the next enabled item index.
        let ai = this._activeIndex;
        let n = this._results.length;
        let start = ai < n - 1 ? ai + 1 : 0;
        let stop = start === 0 ? n - 1 : start - 1;
        this._activeIndex = ArrayExt.findFirstIndex(this._results, Private$a.canActivate, start, stop);
        // Schedule an update of the items.
        this.update();
    }
    /**
     * Activate the previous enabled command item.
     */
    _activatePreviousItem() {
        // Bail if there are no search results.
        if (!this._results || this._results.length === 0) {
            return;
        }
        // Find the previous enabled item index.
        let ai = this._activeIndex;
        let n = this._results.length;
        let start = ai <= 0 ? n - 1 : ai - 1;
        let stop = start === n - 1 ? 0 : start + 1;
        this._activeIndex = ArrayExt.findLastIndex(this._results, Private$a.canActivate, start, stop);
        // Schedule an update of the items.
        this.update();
    }
    /**
     * Execute the command item at the given index, if possible.
     */
    _execute(index) {
        // Bail if there are no search results.
        if (!this._results) {
            return;
        }
        // Bail if the index is out of range.
        let part = this._results[index];
        if (!part) {
            return;
        }
        // Update the search text if the item is a header.
        if (part.type === 'header') {
            let input = this.inputNode;
            input.value = `${part.category.toLowerCase()} `;
            input.focus();
            this.refresh();
            return;
        }
        // Bail if item is not enabled.
        if (!part.item.isEnabled) {
            return;
        }
        // Emit the item triggered signal before the execution.
        this._itemTriggered.emit(part.item);
        // Execute the item.
        this.commands.execute(part.item.command, part.item.args);
        // Clear the query text.
        this.inputNode.value = '';
        // Refresh the search results.
        this.refresh();
    }
    /**
     * Toggle the focused modifier based on the input node focus state.
     */
    _toggleFocused() {
        let focused = document.activeElement === this.inputNode;
        this.toggleClass('lm-mod-focused', focused);
    }
    /**
     * A signal handler for generic command changes.
     */
    _onGenericChange() {
        this.refresh();
    }
}
/**
 * The namespace for the `CommandPalette` class statics.
 */
(function (CommandPalette) {
    /**
     * The default implementation of `IRenderer`.
     */
    class Renderer {
        /**
         * Render the virtual element for a command palette header.
         *
         * @param data - The data to use for rendering the header.
         *
         * @returns A virtual element representing the header.
         */
        renderHeader(data) {
            let content = this.formatHeader(data);
            return h.li({ className: 'lm-CommandPalette-header' }, content);
        }
        /**
         * Render the virtual element for a command palette item.
         *
         * @param data - The data to use for rendering the item.
         *
         * @returns A virtual element representing the item.
         */
        renderItem(data) {
            let className = this.createItemClass(data);
            let dataset = this.createItemDataset(data);
            if (data.item.isToggleable) {
                return h.li({
                    className,
                    dataset,
                    role: 'menuitemcheckbox',
                    'aria-checked': `${data.item.isToggled}`
                }, this.renderItemIcon(data), this.renderItemContent(data), this.renderItemShortcut(data));
            }
            return h.li({
                className,
                dataset,
                role: 'menuitem'
            }, this.renderItemIcon(data), this.renderItemContent(data), this.renderItemShortcut(data));
        }
        /**
         * Render the empty results message for a command palette.
         *
         * @param data - The data to use for rendering the message.
         *
         * @returns A virtual element representing the message.
         */
        renderEmptyMessage(data) {
            let content = this.formatEmptyMessage(data);
            return h.li({ className: 'lm-CommandPalette-emptyMessage' }, content);
        }
        /**
         * Render the icon for a command palette item.
         *
         * @param data - The data to use for rendering the icon.
         *
         * @returns A virtual element representing the icon.
         */
        renderItemIcon(data) {
            let className = this.createIconClass(data);
            // If data.item.icon is undefined, it will be ignored.
            return h.div({ className }, data.item.icon, data.item.iconLabel);
        }
        /**
         * Render the content for a command palette item.
         *
         * @param data - The data to use for rendering the content.
         *
         * @returns A virtual element representing the content.
         */
        renderItemContent(data) {
            return h.div({ className: 'lm-CommandPalette-itemContent' }, this.renderItemLabel(data), this.renderItemCaption(data));
        }
        /**
         * Render the label for a command palette item.
         *
         * @param data - The data to use for rendering the label.
         *
         * @returns A virtual element representing the label.
         */
        renderItemLabel(data) {
            let content = this.formatItemLabel(data);
            return h.div({ className: 'lm-CommandPalette-itemLabel' }, content);
        }
        /**
         * Render the caption for a command palette item.
         *
         * @param data - The data to use for rendering the caption.
         *
         * @returns A virtual element representing the caption.
         */
        renderItemCaption(data) {
            let content = this.formatItemCaption(data);
            return h.div({ className: 'lm-CommandPalette-itemCaption' }, content);
        }
        /**
         * Render the shortcut for a command palette item.
         *
         * @param data - The data to use for rendering the shortcut.
         *
         * @returns A virtual element representing the shortcut.
         */
        renderItemShortcut(data) {
            let content = this.formatItemShortcut(data);
            return h.div({ className: 'lm-CommandPalette-itemShortcut' }, content);
        }
        /**
         * Create the class name for the command palette item.
         *
         * @param data - The data to use for the class name.
         *
         * @returns The full class name for the command palette item.
         */
        createItemClass(data) {
            // Set up the initial class name.
            let name = 'lm-CommandPalette-item';
            // Add the boolean state classes.
            if (!data.item.isEnabled) {
                name += ' lm-mod-disabled';
            }
            if (data.item.isToggled) {
                name += ' lm-mod-toggled';
            }
            if (data.active) {
                name += ' lm-mod-active';
            }
            // Add the extra class.
            let extra = data.item.className;
            if (extra) {
                name += ` ${extra}`;
            }
            // Return the complete class name.
            return name;
        }
        /**
         * Create the dataset for the command palette item.
         *
         * @param data - The data to use for creating the dataset.
         *
         * @returns The dataset for the command palette item.
         */
        createItemDataset(data) {
            return { ...data.item.dataset, command: data.item.command };
        }
        /**
         * Create the class name for the command item icon.
         *
         * @param data - The data to use for the class name.
         *
         * @returns The full class name for the item icon.
         */
        createIconClass(data) {
            let name = 'lm-CommandPalette-itemIcon';
            let extra = data.item.iconClass;
            return extra ? `${name} ${extra}` : name;
        }
        /**
         * Create the render content for the header node.
         *
         * @param data - The data to use for the header content.
         *
         * @returns The content to add to the header node.
         */
        formatHeader(data) {
            if (!data.indices || data.indices.length === 0) {
                return data.category;
            }
            return StringExt.highlight(data.category, data.indices, h.mark);
        }
        /**
         * Create the render content for the empty message node.
         *
         * @param data - The data to use for the empty message content.
         *
         * @returns The content to add to the empty message node.
         */
        formatEmptyMessage(data) {
            return `No commands found that match '${data.query}'`;
        }
        /**
         * Create the render content for the item shortcut node.
         *
         * @param data - The data to use for the shortcut content.
         *
         * @returns The content to add to the shortcut node.
         */
        formatItemShortcut(data) {
            let kb = data.item.keyBinding;
            return kb ? CommandRegistry.formatKeystroke(kb.keys) : null;
        }
        /**
         * Create the render content for the item label node.
         *
         * @param data - The data to use for the label content.
         *
         * @returns The content to add to the label node.
         */
        formatItemLabel(data) {
            if (!data.indices || data.indices.length === 0) {
                return data.item.label;
            }
            return StringExt.highlight(data.item.label, data.indices, h.mark);
        }
        /**
         * Create the render content for the item caption node.
         *
         * @param data - The data to use for the caption content.
         *
         * @returns The content to add to the caption node.
         */
        formatItemCaption(data) {
            return data.item.caption;
        }
    }
    CommandPalette.Renderer = Renderer;
    /**
     * The default `Renderer` instance.
     */
    CommandPalette.defaultRenderer = new Renderer();
    /**
     * Search an array of command items for fuzzy matches.
     *
     * @param items - The command items to search.
     *
     * @param query - The query text to match against the items.
     *
     * @returns The search results for the query.
     *
     * #### Notes
     * Items which are not visible are excluded. Whitespace in the query
     * is ignored. An empty query matches all items, ordered by category,
     * rank, and label. Otherwise, the matched items are ordered by match
     * quality.
     *
     * Each run of items which share a category is preceded by a header
     * result for that category.
     *
     * This function is used by the default implementation of the protected
     * `search()` method of a command palette. A subclass can use it to
     * compose the default results with custom results.
     */
    function search(items, query) {
        return Private$a.search(items, query);
    }
    CommandPalette.search = search;
})(CommandPalette || (CommandPalette = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$a;
(function (Private) {
    /**
     * Create the DOM node for a command palette.
     */
    function createNode() {
        let node = document.createElement('div');
        let search = document.createElement('div');
        let wrapper = document.createElement('div');
        let input = document.createElement('input');
        let content = document.createElement('ul');
        let clear = document.createElement('button');
        search.className = 'lm-CommandPalette-search';
        wrapper.className = 'lm-CommandPalette-wrapper';
        input.className = 'lm-CommandPalette-input';
        clear.className = 'lm-close-icon';
        content.className = 'lm-CommandPalette-content';
        content.setAttribute('role', 'menu');
        input.spellcheck = false;
        wrapper.appendChild(input);
        wrapper.appendChild(clear);
        search.appendChild(wrapper);
        node.appendChild(search);
        node.appendChild(content);
        return node;
    }
    Private.createNode = createNode;
    /**
     * Create a new command item from a command registry and options.
     */
    function createItem(commands, options) {
        return new CommandItem(commands, options);
    }
    Private.createItem = createItem;
    /**
     * Search an array of command items for fuzzy matches.
     */
    function search(items, query) {
        // Fuzzy match the items for the query.
        let scores = matchItems(items, query);
        // Sort the items based on their score.
        scores.sort(scoreCmp);
        // Create the results for the search.
        return createResults(scores);
    }
    Private.search = search;
    /**
     * Test whether a result item can be activated.
     */
    function canActivate(result) {
        return result.type === 'item' && result.item.isEnabled;
    }
    Private.canActivate = canActivate;
    /**
     * Normalize a category for a command item.
     */
    function normalizeCategory(category) {
        return category.trim().replace(/\s+/g, ' ');
    }
    /**
     * Normalize the query text for a fuzzy search.
     */
    function normalizeQuery(text) {
        return text.replace(/\s+/g, '').toLowerCase();
    }
    /**
     * Perform a fuzzy match on an array of command items.
     */
    function matchItems(items, query) {
        // Normalize the query text to lower case with no whitespace.
        query = normalizeQuery(query);
        // Create the array to hold the scores.
        let scores = [];
        // Iterate over the items and match against the query.
        for (let i = 0, n = items.length; i < n; ++i) {
            // Ignore items which are not visible.
            let item = items[i];
            if (!item.isVisible) {
                continue;
            }
            // If the query is empty, all items are matched by default.
            if (!query) {
                scores.push({
                    matchType: 3 /* MatchType.Default */,
                    categoryIndices: null,
                    labelIndices: null,
                    score: 0,
                    item
                });
                continue;
            }
            // Run the fuzzy search for the item and query.
            let score = fuzzySearch(item, query);
            // Ignore the item if it is not a match.
            if (!score) {
                continue;
            }
            // Penalize disabled items.
            // TODO - push disabled items all the way down in sort cmp?
            if (!item.isEnabled) {
                score.score += 1000;
            }
            // Add the score to the results.
            scores.push(score);
        }
        // Return the final array of scores.
        return scores;
    }
    /**
     * Perform a fuzzy search on a single command item.
     */
    function fuzzySearch(item, query) {
        // Create the source text to be searched.
        let category = item.category.toLowerCase();
        let label = item.label.toLowerCase();
        let source = `${category} ${label}`;
        // Set up the match score and indices array.
        let score = Infinity;
        let indices = null;
        // The regex for search word boundaries
        let rgx = /\b\w/g;
        // Search the source by word boundary.
        // eslint-disable-next-line no-constant-condition
        while (true) {
            // Find the next word boundary in the source.
            let rgxMatch = rgx.exec(source);
            // Break if there is no more source context.
            if (!rgxMatch) {
                break;
            }
            // Run the string match on the relevant substring.
            let match = StringExt.matchSumOfDeltas(source, query, rgxMatch.index);
            // Break if there is no match.
            if (!match) {
                break;
            }
            // Update the match if the score is better.
            if (match.score <= score) {
                score = match.score;
                indices = match.indices;
            }
        }
        // Bail if there was no match.
        if (!indices || score === Infinity) {
            return null;
        }
        // Compute the pivot index between category and label text.
        let pivot = category.length + 1;
        // Find the slice index to separate matched indices.
        let j = ArrayExt.lowerBound(indices, pivot, (a, b) => a - b);
        // Extract the matched category and label indices.
        let categoryIndices = indices.slice(0, j);
        let labelIndices = indices.slice(j);
        // Adjust the label indices for the pivot offset.
        for (let i = 0, n = labelIndices.length; i < n; ++i) {
            labelIndices[i] -= pivot;
        }
        // Handle a pure label match.
        if (categoryIndices.length === 0) {
            return {
                matchType: 0 /* MatchType.Label */,
                categoryIndices: null,
                labelIndices,
                score,
                item
            };
        }
        // Handle a pure category match.
        if (labelIndices.length === 0) {
            return {
                matchType: 1 /* MatchType.Category */,
                categoryIndices,
                labelIndices: null,
                score,
                item
            };
        }
        // Handle a split match.
        return {
            matchType: 2 /* MatchType.Split */,
            categoryIndices,
            labelIndices,
            score,
            item
        };
    }
    /**
     * A sort comparison function for a match score.
     */
    function scoreCmp(a, b) {
        // First compare based on the match type
        let m1 = a.matchType - b.matchType;
        if (m1 !== 0) {
            return m1;
        }
        // Otherwise, compare based on the match score.
        let d1 = a.score - b.score;
        if (d1 !== 0) {
            return d1;
        }
        // Find the match index based on the match type.
        let i1 = 0;
        let i2 = 0;
        switch (a.matchType) {
            case 0 /* MatchType.Label */:
                i1 = a.labelIndices[0];
                i2 = b.labelIndices[0];
                break;
            case 1 /* MatchType.Category */:
            case 2 /* MatchType.Split */:
                i1 = a.categoryIndices[0];
                i2 = b.categoryIndices[0];
                break;
        }
        // Compare based on the match index.
        if (i1 !== i2) {
            return i1 - i2;
        }
        // Otherwise, compare by category.
        let d2 = a.item.category.localeCompare(b.item.category);
        if (d2 !== 0) {
            return d2;
        }
        // Otherwise, compare by rank.
        let r1 = a.item.rank;
        let r2 = b.item.rank;
        if (r1 !== r2) {
            return r1 < r2 ? -1 : 1; // Infinity safe
        }
        // Finally, compare by label.
        return a.item.label.localeCompare(b.item.label);
    }
    /**
     * Create the results from an array of sorted scores.
     */
    function createResults(scores) {
        // Set up the search results array.
        let results = [];
        // Iterate over each score in the array.
        for (let i = 0, n = scores.length; i < n; ++i) {
            // Extract the current item and indices.
            let { item, categoryIndices, labelIndices } = scores[i];
            // Extract the category for the current item.
            let category = item.category;
            // Is this the same category as the preceding result?
            if (i === 0 || category !== scores[i - 1].item.category) {
                // Add the header result for the category.
                results.push({ type: 'header', category, indices: categoryIndices });
            }
            // Create the item result for the score.
            results.push({ type: 'item', item, indices: labelIndices });
        }
        // Return the final results.
        return results;
    }
    /**
     * A concrete implementation of `CommandPalette.IItem`.
     */
    class CommandItem {
        /**
         * Construct a new command item.
         */
        constructor(commands, options) {
            this._commands = commands;
            this.category = normalizeCategory(options.category);
            this.command = options.command;
            this.args = options.args || JSONExt.emptyObject;
            this.rank = options.rank !== undefined ? options.rank : Infinity;
        }
        /**
         * The display label for the command item.
         */
        get label() {
            return this._commands.label(this.command, this.args);
        }
        /**
         * The icon renderer for the command item.
         */
        get icon() {
            return this._commands.icon(this.command, this.args);
        }
        /**
         * The icon class for the command item.
         */
        get iconClass() {
            return this._commands.iconClass(this.command, this.args);
        }
        /**
         * The icon label for the command item.
         */
        get iconLabel() {
            return this._commands.iconLabel(this.command, this.args);
        }
        /**
         * The display caption for the command item.
         */
        get caption() {
            return this._commands.caption(this.command, this.args);
        }
        /**
         * The extra class name for the command item.
         */
        get className() {
            return this._commands.className(this.command, this.args);
        }
        /**
         * The dataset for the command item.
         */
        get dataset() {
            return this._commands.dataset(this.command, this.args);
        }
        /**
         * Whether the command item is enabled.
         */
        get isEnabled() {
            return this._commands.isEnabled(this.command, this.args);
        }
        /**
         * Whether the command item is toggled.
         */
        get isToggled() {
            return this._commands.isToggled(this.command, this.args);
        }
        /**
         * Whether the command item is toggleable.
         */
        get isToggleable() {
            return this._commands.isToggleable(this.command, this.args);
        }
        /**
         * Whether the command item is visible.
         */
        get isVisible() {
            return this._commands.isVisible(this.command, this.args);
        }
        /**
         * The key binding for the command item.
         */
        get keyBinding() {
            let { command, args } = this;
            return (ArrayExt.findLastValue(this._commands.keyBindings, kb => {
                return kb.command === command && JSONExt.deepEqual(kb.args, args);
            }) || null);
        }
    }
})(Private$a || (Private$a = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A widget which displays items as a canonical menu.
 */
class Menu extends Widget {
    /**
     * Construct a new menu.
     *
     * @param options - The options for initializing the menu.
     */
    constructor(options) {
        super({ node: Private$9.createNode() });
        this._childIndex = -1;
        this._activeIndex = -1;
        this._openTimerID = 0;
        this._closeTimerID = 0;
        this._pendingTouchActivation = false;
        this._items = [];
        this._childMenu = null;
        this._parentMenu = null;
        this._aboutToClose = new Signal(this);
        this._menuRequested = new Signal(this);
        this.addClass('lm-Menu');
        this.setFlag(Widget.Flag.DisallowLayout);
        this.commands = options.commands;
        this.renderer = options.renderer || Menu.defaultRenderer;
    }
    /**
     * Dispose of the resources held by the menu.
     */
    dispose() {
        this.close();
        this._items.length = 0;
        super.dispose();
    }
    /**
     * A signal emitted just before the menu is closed.
     *
     * #### Notes
     * This signal is emitted when the menu receives a `'close-request'`
     * message, just before it removes itself from the DOM.
     *
     * This signal is not emitted if the menu is already detached from
     * the DOM when it receives the `'close-request'` message.
     */
    get aboutToClose() {
        return this._aboutToClose;
    }
    /**
     * A signal emitted when a new menu is requested by the user.
     *
     * #### Notes
     * This signal is emitted whenever the user presses the right or left
     * arrow keys, and a submenu cannot be opened or closed in response.
     *
     * This signal is useful when implementing menu bars in order to open
     * the next or previous menu in response to a user key press.
     *
     * This signal is only emitted for the root menu in a hierarchy.
     */
    get menuRequested() {
        return this._menuRequested;
    }
    /**
     * The parent menu of the menu.
     *
     * #### Notes
     * This is `null` unless the menu is an open submenu.
     */
    get parentMenu() {
        return this._parentMenu;
    }
    /**
     * The child menu of the menu.
     *
     * #### Notes
     * This is `null` unless the menu has an open submenu.
     */
    get childMenu() {
        return this._childMenu;
    }
    /**
     * The root menu of the menu hierarchy.
     */
    get rootMenu() {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        let menu = this;
        while (menu._parentMenu) {
            menu = menu._parentMenu;
        }
        return menu;
    }
    /**
     * The leaf menu of the menu hierarchy.
     */
    get leafMenu() {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        let menu = this;
        while (menu._childMenu) {
            menu = menu._childMenu;
        }
        return menu;
    }
    /**
     * The menu content node.
     *
     * #### Notes
     * This is the node which holds the menu item nodes.
     *
     * Modifying this node directly can lead to undefined behavior.
     */
    get contentNode() {
        return this.node.getElementsByClassName('lm-Menu-content')[0];
    }
    /**
     * Get the currently active menu item.
     */
    get activeItem() {
        return this._items[this._activeIndex] || null;
    }
    /**
     * Set the currently active menu item.
     *
     * #### Notes
     * If the item cannot be activated, the item will be set to `null`.
     */
    set activeItem(value) {
        this.activeIndex = value ? this._items.indexOf(value) : -1;
    }
    /**
     * Get the index of the currently active menu item.
     *
     * #### Notes
     * This will be `-1` if no menu item is active.
     */
    get activeIndex() {
        return this._activeIndex;
    }
    /**
     * Set the index of the currently active menu item.
     *
     * #### Notes
     * If the item cannot be activated, the index will be set to `-1`.
     */
    set activeIndex(value) {
        // Adjust the value for an out of range index.
        if (value < 0 || value >= this._items.length) {
            value = -1;
        }
        // Ensure the item can be activated.
        if (value !== -1 && !Private$9.canActivate(this._items[value])) {
            value = -1;
        }
        // Bail if the index will not change.
        if (this._activeIndex === value) {
            return;
        }
        // Update the active index.
        this._activeIndex = value;
        // Make active element in focus
        if (this._activeIndex >= 0 &&
            this.contentNode.childNodes[this._activeIndex]) {
            this.contentNode.childNodes[this._activeIndex].focus();
        }
        // schedule an update of the items.
        this.update();
    }
    /**
     * A read-only array of the menu items in the menu.
     */
    get items() {
        return this._items;
    }
    /**
     * Activate the next selectable item in the menu.
     *
     * #### Notes
     * If no item is selectable, the index will be set to `-1`.
     */
    activateNextItem() {
        let n = this._items.length;
        let ai = this._activeIndex;
        let start = ai < n - 1 ? ai + 1 : 0;
        let stop = start === 0 ? n - 1 : start - 1;
        this.activeIndex = ArrayExt.findFirstIndex(this._items, Private$9.canActivate, start, stop);
    }
    /**
     * Activate the previous selectable item in the menu.
     *
     * #### Notes
     * If no item is selectable, the index will be set to `-1`.
     */
    activatePreviousItem() {
        let n = this._items.length;
        let ai = this._activeIndex;
        let start = ai <= 0 ? n - 1 : ai - 1;
        let stop = start === n - 1 ? 0 : start + 1;
        this.activeIndex = ArrayExt.findLastIndex(this._items, Private$9.canActivate, start, stop);
    }
    /**
     * Trigger the active menu item.
     *
     * #### Notes
     * If the active item is a submenu, it will be opened and the first
     * item will be activated.
     *
     * If the active item is a command, the command will be executed.
     *
     * If the menu is not attached, this is a no-op.
     *
     * If there is no active item, this is a no-op.
     */
    triggerActiveItem() {
        // Bail if the menu is not attached.
        if (!this.isAttached) {
            return;
        }
        // Bail if there is no active item.
        let item = this.activeItem;
        if (!item) {
            return;
        }
        // Cancel the pending timers.
        this._cancelOpenTimer();
        this._cancelCloseTimer();
        // If the item is a submenu, open it.
        if (item.type === 'submenu') {
            this._openChildMenu(true);
            return;
        }
        // Close the root menu before executing the command.
        this.rootMenu.close();
        // Execute the command for the item.
        let { command, args } = item;
        if (this.commands.isEnabled(command, args)) {
            this.commands.execute(command, args);
        }
        else {
            console.log(`Command '${command}' is disabled.`);
        }
    }
    /**
     * Add a menu item to the end of the menu.
     *
     * @param options - The options for creating the menu item.
     *
     * @returns The menu item added to the menu.
     */
    addItem(options) {
        return this.insertItem(this._items.length, options);
    }
    /**
     * Insert a menu item into the menu at the specified index.
     *
     * @param index - The index at which to insert the item.
     *
     * @param options - The options for creating the menu item.
     *
     * @returns The menu item added to the menu.
     *
     * #### Notes
     * The index will be clamped to the bounds of the items.
     */
    insertItem(index, options) {
        // Close the menu if it's attached.
        if (this.isAttached) {
            this.close();
        }
        // Reset the active index.
        this.activeIndex = -1;
        // Clamp the insert index to the array bounds.
        let i = Math.max(0, Math.min(index, this._items.length));
        // Create the item for the options.
        let item = Private$9.createItem(this, options);
        // Insert the item into the array.
        ArrayExt.insert(this._items, i, item);
        // Schedule an update of the items.
        this.update();
        // Return the item added to the menu.
        return item;
    }
    /**
     * Remove an item from the menu.
     *
     * @param item - The item to remove from the menu.
     *
     * #### Notes
     * This is a no-op if the item is not in the menu.
     */
    removeItem(item) {
        let index = this._items.indexOf(item);
        if (index === -1) {
            return;
        }
        this.removeItemAt(index);
    }
    /**
     * Remove the item at a given index from the menu.
     *
     * @param index - The index of the item to remove.
     *
     * #### Notes
     * This is a no-op if the index is out of range.
     */
    removeItemAt(index) {
        // Close the menu if it's attached.
        if (this.isAttached) {
            this.close();
        }
        // Reset the active index.
        this.activeIndex = -1;
        // Remove the item from the array.
        let item = ArrayExt.removeAt(this._items, index);
        // Bail if the index is out of range.
        if (!item) {
            return;
        }
        // Schedule an update of the items.
        this.update();
    }
    /**
     * Remove all menu items from the menu.
     */
    clearItems() {
        // Close the menu if it's attached.
        if (this.isAttached) {
            this.close();
        }
        // Reset the active index.
        this.activeIndex = -1;
        // Bail if there is nothing to remove.
        if (this._items.length === 0) {
            return;
        }
        // Clear the items.
        this._items.length = 0;
        // Schedule an update of the items.
        this.update();
    }
    /**
     * Open the menu at the specified location.
     *
     * @param x - The client X coordinate of the menu location.
     *
     * @param y - The client Y coordinate of the menu location.
     *
     * @param options - The additional options for opening the menu.
     *
     * #### Notes
     * The menu will be opened at the given location unless it will not
     * fully fit on the screen. If it will not fit, it will be adjusted
     * to fit naturally on the screen.
     *
     * The menu will be attached under the `host` element in the DOM
     * (or `document.body` if `host` is `null`) and before the `ref`
     * element (or as the last child of `host` if `ref` is `null`).
     * The menu may be displayed outside of the `host` element
     * following the rules of CSS absolute positioning.
     *
     * This is a no-op if the menu is already attached to the DOM.
     */
    open(x, y, options = {}) {
        var _a, _b, _c;
        // Bail early if the menu is already attached.
        if (this.isAttached) {
            return;
        }
        // Extract the menu options.
        let forceX = options.forceX || false;
        let forceY = options.forceY || false;
        const host = (_a = options.host) !== null && _a !== void 0 ? _a : null;
        const ref = (_b = options.ref) !== null && _b !== void 0 ? _b : null;
        const horizontalAlignment = (_c = options.horizontalAlignment) !== null && _c !== void 0 ? _c : (document.documentElement.dir === 'rtl' ? 'right' : 'left');
        // Open the menu as a root menu.
        Private$9.openRootMenu(this, x, y, forceX, forceY, horizontalAlignment, host, ref);
        // Activate the menu to accept keyboard input.
        this.activate();
    }
    /**
     * Handle the DOM events for the menu.
     *
     * @param event - The DOM event sent to the menu.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the menu's DOM nodes. It should
     * not be called directly by user code.
     */
    handleEvent(event) {
        switch (event.type) {
            case 'keydown':
                this._evtKeyDown(event);
                break;
            case 'click':
                this._evtClick(event);
                break;
            case 'pointerup':
                this._evtPointerUp(event);
                break;
            case 'pointermove':
                this._evtPointerMove(event);
                break;
            case 'pointerenter':
                this._evtPointerEnter(event);
                break;
            case 'pointerleave':
                this._evtPointerLeave(event);
                break;
            case 'pointerdown':
                this._evtPointerDown(event);
                break;
            case 'contextmenu':
                event.preventDefault();
                event.stopPropagation();
                break;
        }
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        this.node.addEventListener('keydown', this);
        this.node.addEventListener('click', this);
        this.node.addEventListener('pointerup', this);
        this.node.addEventListener('pointermove', this);
        this.node.addEventListener('pointerenter', this);
        this.node.addEventListener('pointerleave', this);
        this.node.addEventListener('contextmenu', this);
        document.addEventListener('pointerdown', this, true);
    }
    /**
     * A message handler invoked on an `'after-detach'` message.
     */
    onAfterDetach(msg) {
        this.node.removeEventListener('keydown', this);
        this.node.removeEventListener('click', this);
        this.node.removeEventListener('pointerup', this);
        this.node.removeEventListener('pointermove', this);
        this.node.removeEventListener('pointerenter', this);
        this.node.removeEventListener('pointerleave', this);
        this.node.removeEventListener('contextmenu', this);
        document.removeEventListener('pointerdown', this, true);
        this._pendingTouchActivation = false;
    }
    /**
     * A message handler invoked on an `'activate-request'` message.
     */
    onActivateRequest(msg) {
        if (this.isAttached) {
            this.node.focus();
        }
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        let items = this._items;
        let renderer = this.renderer;
        let activeIndex = this._activeIndex;
        let collapsedFlags = Private$9.computeCollapsed(items);
        let content = new Array(items.length);
        for (let i = 0, n = items.length; i < n; ++i) {
            let item = items[i];
            let active = i === activeIndex;
            let collapsed = collapsedFlags[i];
            content[i] = renderer.renderItem({
                item,
                active,
                collapsed,
                onfocus: () => {
                    this.activeIndex = i;
                }
            });
        }
        VirtualDOM.render(content, this.contentNode);
    }
    /**
     * A message handler invoked on a `'close-request'` message.
     */
    onCloseRequest(msg) {
        // Cancel the pending timers.
        this._cancelOpenTimer();
        this._cancelCloseTimer();
        // Reset the active index.
        this.activeIndex = -1;
        this._pendingTouchActivation = false;
        // Close any open child menu.
        let childMenu = this._childMenu;
        if (childMenu) {
            this._childIndex = -1;
            this._childMenu = null;
            childMenu._parentMenu = null;
            childMenu.close();
        }
        // Remove this menu from its parent and activate the parent.
        let parentMenu = this._parentMenu;
        if (parentMenu) {
            this._parentMenu = null;
            parentMenu._childIndex = -1;
            parentMenu._childMenu = null;
            parentMenu.activate();
        }
        // Emit the `aboutToClose` signal if the menu is attached.
        if (this.isAttached) {
            this._aboutToClose.emit(undefined);
        }
        // Finish closing the menu.
        super.onCloseRequest(msg);
    }
    /**
     * Handle the `'keydown'` event for the menu.
     *
     * #### Notes
     * This listener is attached to the menu node.
     */
    _evtKeyDown(event) {
        // A menu handles all keydown events.
        event.preventDefault();
        event.stopPropagation();
        // Fetch the key code for the event.
        let kc = event.keyCode;
        // Enter
        if (kc === 13) {
            this.triggerActiveItem();
            return;
        }
        // Escape
        if (kc === 27) {
            this.close();
            return;
        }
        // Left Arrow
        if (kc === 37) {
            if (this._parentMenu) {
                this.close();
            }
            else {
                this._menuRequested.emit('previous');
            }
            return;
        }
        // Up Arrow
        if (kc === 38) {
            this.activatePreviousItem();
            return;
        }
        // Right Arrow
        if (kc === 39) {
            let item = this.activeItem;
            if (item && item.type === 'submenu') {
                this.triggerActiveItem();
            }
            else {
                this.rootMenu._menuRequested.emit('next');
            }
            return;
        }
        // Down Arrow
        if (kc === 40) {
            this.activateNextItem();
            return;
        }
        // Get the pressed key character.
        let key = getKeyboardLayout().keyForKeydownEvent(event);
        // Bail if the key is not valid.
        if (!key) {
            return;
        }
        // Search for the next best matching mnemonic item.
        let start = this._activeIndex + 1;
        let result = Private$9.findMnemonic(this._items, key, start);
        // Handle the requested mnemonic based on the search results.
        // If exactly one mnemonic is matched, that item is triggered.
        // Otherwise, the next mnemonic is activated if available,
        // followed by the auto mnemonic if available.
        if (result.index !== -1 && !result.multiple) {
            this.activeIndex = result.index;
            this.triggerActiveItem();
        }
        else if (result.index !== -1) {
            this.activeIndex = result.index;
        }
        else if (result.auto !== -1) {
            this.activeIndex = result.auto;
        }
    }
    /**
     * Handle the `'pointerup'` event for the menu.
     *
     * #### Notes
     * This listener is attached to the menu node.
     */
    _evtPointerUp(event) {
        if (event.button !== 0) {
            return;
        }
        if (Utils$1.isTouchEvent(event)) {
            this._pendingTouchActivation = this._activateItemFromEvent(event);
            event.stopPropagation();
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        this._activateItemFromEvent(event);
        this.triggerActiveItem();
    }
    /**
     * Handle the `'click'` event for the menu.
     *
     * #### Notes
     * This listener is attached to the menu node.
     */
    _evtClick(event) {
        if (!this._pendingTouchActivation) {
            return;
        }
        this._pendingTouchActivation = false;
        event.preventDefault();
        event.stopPropagation();
        if (!this._activateItemFromEvent(event)) {
            return;
        }
        this.triggerActiveItem();
    }
    /**
     * Handle the `'pointermove'` event for the menu.
     *
     * #### Notes
     * This listener is attached to the menu node.
     */
    _evtPointerMove(event) {
        if (Utils$1.isTouchEvent(event)) {
            return;
        }
        // Hit test the item nodes for the item under the mouse.
        let index = ArrayExt.findFirstIndex(this.contentNode.children, node => {
            return ElementExt.hitTest(node, event.clientX, event.clientY);
        });
        // Bail early if the mouse is already over the active index.
        if (index === this._activeIndex) {
            return;
        }
        // Update and coerce the active index.
        this.activeIndex = index;
        index = this.activeIndex;
        // If the index is the current child index, cancel the timers.
        if (index === this._childIndex) {
            this._cancelOpenTimer();
            this._cancelCloseTimer();
            return;
        }
        // If a child menu is currently open, start the close timer.
        if (this._childIndex !== -1) {
            this._startCloseTimer();
        }
        // Cancel the open timer to give a full delay for opening.
        this._cancelOpenTimer();
        // Bail if the active item is not a valid submenu item.
        let item = this.activeItem;
        if (!item || item.type !== 'submenu' || !item.submenu) {
            return;
        }
        // Start the open timer to open the active item submenu.
        this._startOpenTimer();
    }
    /**
     * Handle the `'pointerenter'` event for the menu.
     *
     * #### Notes
     * This listener is attached to the menu node.
     */
    _evtPointerEnter(event) {
        if (Utils$1.isTouchEvent(event)) {
            return;
        }
        // Synchronize the active ancestor items.
        for (let menu = this._parentMenu; menu; menu = menu._parentMenu) {
            menu._cancelOpenTimer();
            menu._cancelCloseTimer();
            menu.activeIndex = menu._childIndex;
        }
    }
    /**
     * Handle the `'pointerleave'` event for the menu.
     *
     * #### Notes
     * This listener is attached to the menu node.
     */
    _evtPointerLeave(event) {
        if (Utils$1.isTouchEvent(event)) {
            return;
        }
        // Cancel any pending submenu opening.
        this._cancelOpenTimer();
        // If there is no open child menu, just reset the active index.
        if (!this._childMenu) {
            this.activeIndex = -1;
            return;
        }
        // If the mouse is over the child menu, cancel the close timer.
        let { clientX, clientY } = event;
        if (ElementExt.hitTest(this._childMenu.node, clientX, clientY)) {
            this._cancelCloseTimer();
            return;
        }
        // Otherwise, reset the active index and start the close timer.
        this.activeIndex = -1;
        this._startCloseTimer();
    }
    /**
     * Handle the `'pointerdown'` event for the menu.
     *
     * #### Notes
     * This listener is attached to the document node.
     */
    _evtPointerDown(event) {
        this._pendingTouchActivation = false;
        // Bail if the menu is not a root menu.
        if (this._parentMenu) {
            return;
        }
        // The mouse button which is pressed is irrelevant. If the press
        // is not on a menu, the entire hierarchy is closed and the event
        // is allowed to propagate. This allows other code to act on the
        // event, such as focusing the clicked element.
        if (Private$9.hitTestMenus(this, event.clientX, event.clientY)) {
            if (!Utils$1.isTouchEvent(event)) {
                event.preventDefault();
            }
            event.stopPropagation();
        }
        else {
            this.close();
        }
    }
    /**
     * Open the child menu at the active index immediately.
     *
     * If a different child menu is already open, it will be closed,
     * even if the active item is not a valid submenu.
     */
    _openChildMenu(activateFirst = false) {
        // If the item is not a valid submenu, close the child menu.
        let item = this.activeItem;
        if (!item || item.type !== 'submenu' || !item.submenu) {
            this._closeChildMenu();
            return;
        }
        // Do nothing if the child menu will not change.
        let submenu = item.submenu;
        if (submenu === this._childMenu) {
            return;
        }
        // Prior to any DOM modifications save window data
        Menu.saveWindowData();
        // Ensure the current child menu is closed.
        this._closeChildMenu();
        // Update the private child state.
        this._childMenu = submenu;
        this._childIndex = this._activeIndex;
        // Set the parent menu reference for the child.
        submenu._parentMenu = this;
        // Ensure the menu is updated and lookup the item node.
        MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest);
        let itemNode = this.contentNode.children[this._activeIndex];
        // Open the submenu at the active node.
        Private$9.openSubmenu(submenu, itemNode);
        // Activate the first item if desired.
        if (activateFirst) {
            submenu.activeIndex = -1;
            submenu.activateNextItem();
        }
        // Activate the child menu.
        submenu.activate();
    }
    /**
     * Activate the menu item under an event.
     */
    _activateItemFromEvent(event) {
        let index = ArrayExt.findFirstIndex(this.contentNode.children, node => {
            return ElementExt.hitTest(node, event.clientX, event.clientY);
        });
        if (index === -1) {
            return false;
        }
        this.activeIndex = index;
        return true;
    }
    /**
     * Close the child menu immediately.
     *
     * This is a no-op if a child menu is not open.
     */
    _closeChildMenu() {
        if (this._childMenu) {
            this._childMenu.close();
        }
    }
    /**
     * Start the open timer, unless it is already pending.
     */
    _startOpenTimer() {
        if (this._openTimerID === 0) {
            this._openTimerID = window.setTimeout(() => {
                this._openTimerID = 0;
                this._openChildMenu();
            }, Private$9.TIMER_DELAY);
        }
    }
    /**
     * Start the close timer, unless it is already pending.
     */
    _startCloseTimer() {
        if (this._closeTimerID === 0) {
            this._closeTimerID = window.setTimeout(() => {
                this._closeTimerID = 0;
                this._closeChildMenu();
            }, Private$9.TIMER_DELAY);
        }
    }
    /**
     * Cancel the open timer, if the timer is pending.
     */
    _cancelOpenTimer() {
        if (this._openTimerID !== 0) {
            clearTimeout(this._openTimerID);
            this._openTimerID = 0;
        }
    }
    /**
     * Cancel the close timer, if the timer is pending.
     */
    _cancelCloseTimer() {
        if (this._closeTimerID !== 0) {
            clearTimeout(this._closeTimerID);
            this._closeTimerID = 0;
        }
    }
    /**
     * Save window data used for menu positioning in transient cache.
     *
     * In order to avoid layout trashing it is recommended to invoke this
     * method immediately prior to opening the menu and any DOM modifications
     * (like closing previously visible menu, or adding a class to menu widget).
     *
     * The transient cache will be released upon `open()` call.
     */
    static saveWindowData() {
        Private$9.saveWindowData();
    }
}
/**
 * The namespace for the `Menu` class statics.
 */
(function (Menu) {
    /**
     * The default implementation of `IRenderer`.
     *
     * #### Notes
     * Subclasses are free to reimplement rendering methods as needed.
     */
    class Renderer {
        /**
         * Render the virtual element for a menu item.
         *
         * @param data - The data to use for rendering the item.
         *
         * @returns A virtual element representing the item.
         */
        renderItem(data) {
            let className = this.createItemClass(data);
            let dataset = this.createItemDataset(data);
            let aria = this.createItemARIA(data);
            return h.li({
                className,
                dataset,
                tabindex: '0',
                onfocus: data.onfocus,
                ...aria
            }, this.renderIcon(data), this.renderLabel(data), this.renderShortcut(data), this.renderSubmenu(data));
        }
        /**
         * Render the icon element for a menu item.
         *
         * @param data - The data to use for rendering the icon.
         *
         * @returns A virtual element representing the item icon.
         */
        renderIcon(data) {
            let className = this.createIconClass(data);
            // If data.item.icon is undefined, it will be ignored.
            return h.div({ className }, data.item.icon, data.item.iconLabel);
        }
        /**
         * Render the label element for a menu item.
         *
         * @param data - The data to use for rendering the label.
         *
         * @returns A virtual element representing the item label.
         */
        renderLabel(data) {
            let content = this.formatLabel(data);
            return h.div({ className: 'lm-Menu-itemLabel' }, content);
        }
        /**
         * Render the shortcut element for a menu item.
         *
         * @param data - The data to use for rendering the shortcut.
         *
         * @returns A virtual element representing the item shortcut.
         */
        renderShortcut(data) {
            let content = this.formatShortcut(data);
            return h.div({ className: 'lm-Menu-itemShortcut' }, content);
        }
        /**
         * Render the submenu icon element for a menu item.
         *
         * @param data - The data to use for rendering the submenu icon.
         *
         * @returns A virtual element representing the submenu icon.
         */
        renderSubmenu(data) {
            return h.div({ className: 'lm-Menu-itemSubmenuIcon' });
        }
        /**
         * Create the class name for the menu item.
         *
         * @param data - The data to use for the class name.
         *
         * @returns The full class name for the menu item.
         */
        createItemClass(data) {
            // Setup the initial class name.
            let name = 'lm-Menu-item';
            // Add the boolean state classes.
            if (!data.item.isEnabled) {
                name += ' lm-mod-disabled';
            }
            if (data.item.isToggled) {
                name += ' lm-mod-toggled';
            }
            if (!data.item.isVisible) {
                name += ' lm-mod-hidden';
            }
            if (data.active) {
                name += ' lm-mod-active';
            }
            if (data.collapsed) {
                name += ' lm-mod-collapsed';
            }
            // Add the extra class.
            let extra = data.item.className;
            if (extra) {
                name += ` ${extra}`;
            }
            // Return the complete class name.
            return name;
        }
        /**
         * Create the dataset for the menu item.
         *
         * @param data - The data to use for creating the dataset.
         *
         * @returns The dataset for the menu item.
         */
        createItemDataset(data) {
            let result;
            let { type, command, dataset } = data.item;
            if (type === 'command') {
                result = { ...dataset, type, command };
            }
            else {
                result = { ...dataset, type };
            }
            return result;
        }
        /**
         * Create the class name for the menu item icon.
         *
         * @param data - The data to use for the class name.
         *
         * @returns The full class name for the item icon.
         */
        createIconClass(data) {
            let name = 'lm-Menu-itemIcon';
            let extra = data.item.iconClass;
            return extra ? `${name} ${extra}` : name;
        }
        /**
         * Create the aria attributes for menu item.
         *
         * @param data - The data to use for the aria attributes.
         *
         * @returns The aria attributes object for the item.
         */
        createItemARIA(data) {
            let aria = {};
            switch (data.item.type) {
                case 'separator':
                    aria.role = 'presentation';
                    break;
                case 'submenu':
                    aria['aria-haspopup'] = 'true';
                    if (!data.item.isEnabled) {
                        aria['aria-disabled'] = 'true';
                    }
                    break;
                default:
                    if (!data.item.isEnabled) {
                        aria['aria-disabled'] = 'true';
                    }
                    if (data.item.isToggleable || data.item.isToggled) {
                        aria.role = 'menuitemcheckbox';
                        aria['aria-checked'] = `${data.item.isToggled}`;
                    }
                    else {
                        aria.role = 'menuitem';
                    }
            }
            return aria;
        }
        /**
         * Create the render content for the label node.
         *
         * @param data - The data to use for the label content.
         *
         * @returns The content to add to the label node.
         */
        formatLabel(data) {
            // Fetch the label text and mnemonic index.
            let { label, mnemonic } = data.item;
            // If the index is out of range, do not modify the label.
            if (mnemonic < 0 || mnemonic >= label.length) {
                return label;
            }
            // Split the label into parts.
            let prefix = label.slice(0, mnemonic);
            let suffix = label.slice(mnemonic + 1);
            let char = label[mnemonic];
            // Wrap the mnemonic character in a span.
            let span = h.span({ className: 'lm-Menu-itemMnemonic' }, char);
            // Return the content parts.
            return [prefix, span, suffix];
        }
        /**
         * Create the render content for the shortcut node.
         *
         * @param data - The data to use for the shortcut content.
         *
         * @returns The content to add to the shortcut node.
         */
        formatShortcut(data) {
            let kb = data.item.keyBinding;
            return kb ? CommandRegistry.formatKeystroke(kb.keys) : null;
        }
    }
    Menu.Renderer = Renderer;
    /**
     * The default `Renderer` instance.
     */
    Menu.defaultRenderer = new Renderer();
})(Menu || (Menu = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$9;
(function (Private) {
    /**
     * The ms delay for opening and closing a submenu.
     */
    Private.TIMER_DELAY = 300;
    /**
     * The horizontal pixel overlap for an open submenu.
     */
    Private.SUBMENU_OVERLAP = 3;
    let transientWindowDataCache = null;
    let transientCacheCounter = 0;
    function getWindowData() {
        // if transient cache is in use, take one from it
        if (transientCacheCounter > 0) {
            transientCacheCounter--;
            return transientWindowDataCache;
        }
        return _getWindowData();
    }
    /**
     * Store window data in transient cache.
     *
     * The transient cache will be released upon `getWindowData()` call.
     * If this function is called multiple times, the cache will be
     * retained until as many calls to `getWindowData()` were made.
     *
     * Note: should be called before any DOM modifications.
     */
    function saveWindowData() {
        transientWindowDataCache = _getWindowData();
        transientCacheCounter++;
    }
    Private.saveWindowData = saveWindowData;
    /**
     * Create the DOM node for a menu.
     */
    function createNode() {
        let node = document.createElement('div');
        let content = document.createElement('ul');
        content.className = 'lm-Menu-content';
        node.appendChild(content);
        content.setAttribute('role', 'menu');
        node.tabIndex = 0;
        return node;
    }
    Private.createNode = createNode;
    /**
     * Test whether a menu item can be activated.
     */
    function canActivate(item) {
        return item.type !== 'separator' && item.isEnabled && item.isVisible;
    }
    Private.canActivate = canActivate;
    /**
     * Create a new menu item for an owner menu.
     */
    function createItem(owner, options) {
        return new MenuItem(owner.commands, options);
    }
    Private.createItem = createItem;
    /**
     * Hit test a menu hierarchy starting at the given root.
     */
    function hitTestMenus(menu, x, y) {
        for (let temp = menu; temp; temp = temp.childMenu) {
            if (ElementExt.hitTest(temp.node, x, y)) {
                return true;
            }
        }
        return false;
    }
    Private.hitTestMenus = hitTestMenus;
    /**
     * Compute which extra separator items should be collapsed.
     */
    function computeCollapsed(items) {
        // Allocate the return array and fill it with `false`.
        let result = new Array(items.length);
        ArrayExt.fill(result, false);
        // Collapse the leading separators.
        let k1 = 0;
        let n = items.length;
        for (; k1 < n; ++k1) {
            let item = items[k1];
            if (!item.isVisible) {
                continue;
            }
            if (item.type !== 'separator') {
                break;
            }
            result[k1] = true;
        }
        // Hide the trailing separators.
        let k2 = n - 1;
        for (; k2 >= 0; --k2) {
            let item = items[k2];
            if (!item.isVisible) {
                continue;
            }
            if (item.type !== 'separator') {
                break;
            }
            result[k2] = true;
        }
        // Hide the remaining consecutive separators.
        let hide = false;
        while (++k1 < k2) {
            let item = items[k1];
            if (!item.isVisible) {
                continue;
            }
            if (item.type !== 'separator') {
                hide = false;
            }
            else if (hide) {
                result[k1] = true;
            }
            else {
                hide = true;
            }
        }
        // Return the resulting flags.
        return result;
    }
    Private.computeCollapsed = computeCollapsed;
    function _getWindowData() {
        return {
            pageXOffset: window.pageXOffset,
            pageYOffset: window.pageYOffset,
            clientWidth: document.documentElement.clientWidth,
            clientHeight: document.documentElement.clientHeight
        };
    }
    /**
     * Open a menu as a root menu at the target location.
     */
    function openRootMenu(menu, x, y, forceX, forceY, horizontalAlignment, host, ref) {
        // Get the current position and size of the main viewport.
        const windowData = getWindowData();
        let px = windowData.pageXOffset;
        let py = windowData.pageYOffset;
        let cw = windowData.clientWidth;
        let ch = windowData.clientHeight;
        // Ensure the menu is updated before attaching and measuring.
        MessageLoop.sendMessage(menu, Widget.Msg.UpdateRequest);
        // Compute the maximum allowed height for the menu.
        let maxHeight = ch - (forceY ? y : 0);
        // Fetch common variables.
        let node = menu.node;
        let style = node.style;
        // Clear the menu geometry and prepare it for measuring.
        style.opacity = '0';
        style.maxHeight = `${maxHeight}px`;
        // Attach the menu to the document.
        Widget.attach(menu, host || document.body, ref);
        // Measure the size of the menu.
        let { width, height } = node.getBoundingClientRect();
        // align the menu to the right of the target if requested or language is RTL
        if (horizontalAlignment === 'right') {
            x -= width;
        }
        // Adjust the X position of the menu to fit on-screen.
        if (!forceX && x + width > px + cw) {
            x = px + cw - width;
        }
        // Adjust the Y position of the menu to fit on-screen.
        if (!forceY && y + height > py + ch) {
            if (y > py + ch) {
                y = py + ch - height;
            }
            else {
                y = y - height;
            }
        }
        // Update the position of the menu to the computed position.
        style.transform = `translate(${Math.max(0, x)}px, ${Math.max(0, y)}px`;
        // Finally, make the menu visible on the screen.
        style.opacity = '1';
    }
    Private.openRootMenu = openRootMenu;
    /**
     * Open a menu as a submenu using an item node for positioning.
     */
    function openSubmenu(submenu, itemNode) {
        // Get the current position and size of the main viewport.
        const windowData = getWindowData();
        let px = windowData.pageXOffset;
        let py = windowData.pageYOffset;
        let cw = windowData.clientWidth;
        let ch = windowData.clientHeight;
        // Ensure the menu is updated before opening.
        MessageLoop.sendMessage(submenu, Widget.Msg.UpdateRequest);
        // Compute the maximum allowed height for the menu.
        let maxHeight = ch;
        // Fetch common variables.
        let node = submenu.node;
        let style = node.style;
        // Clear the menu geometry and prepare it for measuring.
        style.opacity = '0';
        style.maxHeight = `${maxHeight}px`;
        // Attach the menu to the document.
        Widget.attach(submenu, document.body);
        // Measure the size of the menu.
        let { width, height } = node.getBoundingClientRect();
        // Compute the box sizing for the menu.
        let box = ElementExt.boxSizing(submenu.node);
        // Get the bounding rect for the target item node.
        let itemRect = itemNode.getBoundingClientRect();
        // Compute the target X position.
        let x = itemRect.right - Private.SUBMENU_OVERLAP;
        // Adjust the X position to fit on the screen.
        if (x + width > px + cw) {
            x = itemRect.left + Private.SUBMENU_OVERLAP - width;
        }
        // Compute the target Y position.
        let y = itemRect.top - box.borderTop - box.paddingTop;
        // Adjust the Y position to fit on the screen.
        if (y + height > py + ch) {
            y = itemRect.bottom + box.borderBottom + box.paddingBottom - height;
        }
        // Update the position of the menu to the computed position.
        style.transform = `translate(${Math.max(0, x)}px, ${Math.max(0, y)}px`;
        // Finally, make the menu visible on the screen.
        style.opacity = '1';
    }
    Private.openSubmenu = openSubmenu;
    /**
     * Find the best matching mnemonic item.
     *
     * The search starts at the given index and wraps around.
     */
    function findMnemonic(items, key, start) {
        // Setup the result variables.
        let index = -1;
        let auto = -1;
        let multiple = false;
        // Normalize the key to upper case.
        let upperKey = key.toUpperCase();
        // Search the items from the given start index.
        for (let i = 0, n = items.length; i < n; ++i) {
            // Compute the wrapped index.
            let k = (i + start) % n;
            // Lookup the item
            let item = items[k];
            // Ignore items which cannot be activated.
            if (!canActivate(item)) {
                continue;
            }
            // Ignore items with an empty label.
            let label = item.label;
            if (label.length === 0) {
                continue;
            }
            // Lookup the mnemonic index for the label.
            let mn = item.mnemonic;
            // Handle a valid mnemonic index.
            if (mn >= 0 && mn < label.length) {
                if (label[mn].toUpperCase() === upperKey) {
                    if (index === -1) {
                        index = k;
                    }
                    else {
                        multiple = true;
                    }
                }
                continue;
            }
            // Finally, handle the auto index if possible.
            if (auto === -1 && label[0].toUpperCase() === upperKey) {
                auto = k;
            }
        }
        // Return the search results.
        return { index, multiple, auto };
    }
    Private.findMnemonic = findMnemonic;
    /**
     * A concrete implementation of `Menu.IItem`.
     */
    class MenuItem {
        /**
         * Construct a new menu item.
         */
        constructor(commands, options) {
            this._commands = commands;
            this.type = options.type || 'command';
            this.command = options.command || '';
            this.args = options.args || JSONExt.emptyObject;
            this.submenu = options.submenu || null;
        }
        /**
         * The display label for the menu item.
         */
        get label() {
            if (this.type === 'command') {
                return this._commands.label(this.command, this.args);
            }
            if (this.type === 'submenu' && this.submenu) {
                return this.submenu.title.label;
            }
            return '';
        }
        /**
         * The mnemonic index for the menu item.
         */
        get mnemonic() {
            if (this.type === 'command') {
                return this._commands.mnemonic(this.command, this.args);
            }
            if (this.type === 'submenu' && this.submenu) {
                return this.submenu.title.mnemonic;
            }
            return -1;
        }
        /**
         * The icon renderer for the menu item.
         */
        get icon() {
            if (this.type === 'command') {
                return this._commands.icon(this.command, this.args);
            }
            if (this.type === 'submenu' && this.submenu) {
                return this.submenu.title.icon;
            }
            return undefined;
        }
        /**
         * The icon class for the menu item.
         */
        get iconClass() {
            if (this.type === 'command') {
                return this._commands.iconClass(this.command, this.args);
            }
            if (this.type === 'submenu' && this.submenu) {
                return this.submenu.title.iconClass;
            }
            return '';
        }
        /**
         * The icon label for the menu item.
         */
        get iconLabel() {
            if (this.type === 'command') {
                return this._commands.iconLabel(this.command, this.args);
            }
            if (this.type === 'submenu' && this.submenu) {
                return this.submenu.title.iconLabel;
            }
            return '';
        }
        /**
         * The display caption for the menu item.
         */
        get caption() {
            if (this.type === 'command') {
                return this._commands.caption(this.command, this.args);
            }
            if (this.type === 'submenu' && this.submenu) {
                return this.submenu.title.caption;
            }
            return '';
        }
        /**
         * The extra class name for the menu item.
         */
        get className() {
            if (this.type === 'command') {
                return this._commands.className(this.command, this.args);
            }
            if (this.type === 'submenu' && this.submenu) {
                return this.submenu.title.className;
            }
            return '';
        }
        /**
         * The dataset for the menu item.
         */
        get dataset() {
            if (this.type === 'command') {
                return this._commands.dataset(this.command, this.args);
            }
            if (this.type === 'submenu' && this.submenu) {
                return this.submenu.title.dataset;
            }
            return {};
        }
        /**
         * Whether the menu item is enabled.
         */
        get isEnabled() {
            if (this.type === 'command') {
                return this._commands.isEnabled(this.command, this.args);
            }
            if (this.type === 'submenu') {
                return this.submenu !== null;
            }
            return true;
        }
        /**
         * Whether the menu item is toggled.
         */
        get isToggled() {
            if (this.type === 'command') {
                return this._commands.isToggled(this.command, this.args);
            }
            return false;
        }
        /**
         * Whether the menu item is toggleable.
         */
        get isToggleable() {
            if (this.type === 'command') {
                return this._commands.isToggleable(this.command, this.args);
            }
            return false;
        }
        /**
         * Whether the menu item is visible.
         */
        get isVisible() {
            if (this.type === 'command') {
                return this._commands.isVisible(this.command, this.args);
            }
            if (this.type === 'submenu') {
                return this.submenu !== null;
            }
            return true;
        }
        /**
         * The key binding for the menu item.
         */
        get keyBinding() {
            if (this.type === 'command') {
                let { command, args } = this;
                return (ArrayExt.findLastValue(this._commands.keyBindings, kb => {
                    return kb.command === command && JSONExt.deepEqual(kb.args, args);
                }) || null);
            }
            return null;
        }
    }
})(Private$9 || (Private$9 = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * An object which implements a universal context menu.
 *
 * #### Notes
 * The items shown in the context menu are determined by CSS selector
 * matching against the DOM hierarchy at the site of the mouse click.
 * This is similar in concept to how keyboard shortcuts are matched
 * in the command registry.
 */
class ContextMenu {
    /**
     * Construct a new context menu.
     *
     * @param options - The options for initializing the menu.
     */
    constructor(options) {
        this._groupByTarget = true;
        this._idTick = 0;
        this._items = [];
        this._sortBySelector = true;
        const { groupByTarget, sortBySelector, ...others } = options;
        this.menu = new Menu(others);
        this._groupByTarget = groupByTarget !== false;
        this._sortBySelector = sortBySelector !== false;
    }
    /**
     * Add an item to the context menu.
     *
     * @param options - The options for creating the item.
     *
     * @returns A disposable which will remove the item from the menu.
     */
    addItem(options) {
        // Create an item from the given options.
        let item = Private$8.createItem(options, this._idTick++);
        // Add the item to the internal array.
        this._items.push(item);
        // Return a disposable which will remove the item.
        return new DisposableDelegate(() => {
            ArrayExt.removeFirstOf(this._items, item);
        });
    }
    /**
     * Open the context menu in response to a `'contextmenu'` event.
     *
     * @param event - The `'contextmenu'` event of interest.
     *
     * @returns `true` if the menu was opened, or `false` if no items
     *   matched the event and the menu was not opened.
     *
     * #### Notes
     * This method will populate the context menu with items which match
     * the propagation path of the event, then open the menu at the mouse
     * position indicated by the event.
     */
    open(event) {
        // Prior to any DOM modifications update the window data.
        Menu.saveWindowData();
        // Clear the current contents of the context menu.
        this.menu.clearItems();
        // Bail early if there are no items to match.
        if (this._items.length === 0) {
            return false;
        }
        // Find the matching items for the event.
        let items = Private$8.matchItems(this._items, event, this._groupByTarget, this._sortBySelector);
        // Bail if there are no matching items.
        if (!items || items.length === 0) {
            return false;
        }
        // Add the filtered items to the menu.
        for (const item of items) {
            this.menu.addItem(item);
        }
        // Open the context menu at the current mouse position.
        this.menu.open(event.clientX, event.clientY);
        // Indicate success.
        return true;
    }
}
/**
 * The namespace for the module implementation details.
 */
var Private$8;
(function (Private) {
    /**
     * Create a normalized context menu item from an options object.
     */
    function createItem(options, id) {
        let selector = validateSelector(options.selector);
        let rank = options.rank !== undefined ? options.rank : Infinity;
        return { ...options, selector, rank, id };
    }
    Private.createItem = createItem;
    /**
     * Find the items which match a context menu event.
     *
     * The results are sorted by DOM level, specificity, and rank.
     */
    function matchItems(items, event, groupByTarget, sortBySelector) {
        // Look up the target of the event.
        let target = event.target;
        // Bail if there is no target.
        if (!target) {
            return null;
        }
        // Look up the current target of the event.
        let currentTarget = event.currentTarget;
        // Bail if there is no current target.
        if (!currentTarget) {
            return null;
        }
        // There are some third party libraries that cause the `target` to
        // be detached from the DOM before lumino can process the event.
        // If that happens, search for a new target node by point. If that
        // node is still dangling, bail.
        if (!currentTarget.contains(target)) {
            target = document.elementFromPoint(event.clientX, event.clientY);
            if (!target || !currentTarget.contains(target)) {
                return null;
            }
        }
        // Set up the result array.
        let result = [];
        // Copy the items array to allow in-place modification.
        let availableItems = items.slice();
        // Walk up the DOM hierarchy searching for matches.
        while (target !== null) {
            // Set up the match array for this DOM level.
            let matches = [];
            // Search the remaining items for matches.
            for (let i = 0, n = availableItems.length; i < n; ++i) {
                // Fetch the item.
                let item = availableItems[i];
                // Skip items which are already consumed.
                if (!item) {
                    continue;
                }
                // Skip items which do not match the element.
                if (!Selector.matches(target, item.selector)) {
                    continue;
                }
                // Add the matched item to the result for this DOM level.
                matches.push(item);
                // Mark the item as consumed.
                availableItems[i] = null;
            }
            // Sort the matches for this level and add them to the results.
            if (matches.length !== 0) {
                if (groupByTarget) {
                    matches.sort(sortBySelector ? itemCmp : itemCmpRank);
                }
                result.push(...matches);
            }
            // Stop searching at the limits of the DOM range.
            if (target === currentTarget) {
                break;
            }
            // Step to the parent DOM level.
            target = target.parentElement;
        }
        if (!groupByTarget) {
            result.sort(sortBySelector ? itemCmp : itemCmpRank);
        }
        // Return the matched and sorted results.
        return result;
    }
    Private.matchItems = matchItems;
    /**
     * Validate the selector for a menu item.
     *
     * This returns the validated selector, or throws if the selector is
     * invalid or contains commas.
     */
    function validateSelector(selector) {
        if (selector.indexOf(',') !== -1) {
            throw new Error(`Selector cannot contain commas: ${selector}`);
        }
        if (!Selector.isValid(selector)) {
            throw new Error(`Invalid selector: ${selector}`);
        }
        return selector;
    }
    /**
     * A sort comparison function for a context menu item by ranks.
     */
    function itemCmpRank(a, b) {
        // Sort based on rank.
        let r1 = a.rank;
        let r2 = b.rank;
        if (r1 !== r2) {
            return r1 < r2 ? -1 : 1; // Infinity-safe
        }
        // When all else fails, sort by item id.
        return a.id - b.id;
    }
    /**
     * A sort comparison function for a context menu item by selectors and ranks.
     */
    function itemCmp(a, b) {
        // Sort first based on selector specificity.
        let s1 = Selector.calculateSpecificity(a.selector);
        let s2 = Selector.calculateSpecificity(b.selector);
        if (s1 !== s2) {
            return s2 - s1;
        }
        // If specificities are equal
        return itemCmpRank(a, b);
    }
})(Private$8 || (Private$8 = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
const ARROW_KEYS = [
    'ArrowLeft',
    'ArrowUp',
    'ArrowRight',
    'ArrowDown',
    'Home',
    'End'
];
/**
 * A widget which displays titles as a single row or column of tabs.
 *
 * #### Notes
 * If CSS transforms are used to rotate nodes for vertically oriented
 * text, then tab dragging will not work correctly. The `tabsMovable`
 * property should be set to `false` when rotating nodes from CSS.
 */
class TabBar extends Widget {
    /**
     * Construct a new tab bar.
     *
     * @param options - The options for initializing the tab bar.
     */
    constructor(options = {}) {
        super({ node: Private$7.createNode() });
        this._currentIndex = -1;
        this._titles = [];
        this._titlesEditable = false;
        this._previousTitle = null;
        this._dragData = null;
        this._tabSizeFrozen = false;
        this._frozenTabWidths = null;
        this._unfreezeRunID = 0;
        this._unfreezeFallbackTimerID = 0;
        this._unfreezeTransitionListener = null;
        this._pendingTouchCloseRequest = null;
        this._addButtonEnabled = false;
        this._tabMoved = new Signal(this);
        this._currentChanged = new Signal(this);
        this._addRequested = new Signal(this);
        this._tabCloseRequested = new Signal(this);
        this._tabDetachRequested = new Signal(this);
        this._tabActivateRequested = new Signal(this);
        this.addClass('lm-TabBar');
        this.contentNode.setAttribute('role', 'tablist');
        this.setFlag(Widget.Flag.DisallowLayout);
        this._document = options.document || document;
        this.tabsMovable = options.tabsMovable || false;
        this.titlesEditable = options.titlesEditable || false;
        this.allowDeselect = options.allowDeselect || false;
        this.addButtonEnabled = options.addButtonEnabled || false;
        this.insertBehavior = options.insertBehavior || 'select-tab-if-needed';
        this.name = options.name || '';
        this.orientation = options.orientation || 'horizontal';
        this.removeBehavior = options.removeBehavior || 'select-tab-after';
        this.renderer = options.renderer || TabBar.defaultRenderer;
    }
    /**
     * Dispose of the resources held by the widget.
     */
    dispose() {
        this._releaseMouse();
        this._titles.length = 0;
        this._previousTitle = null;
        super.dispose();
    }
    /**
     * A signal emitted when the current tab is changed.
     *
     * #### Notes
     * This signal is emitted when the currently selected tab is changed
     * either through user or programmatic interaction.
     *
     * Notably, this signal is not emitted when the index of the current
     * tab changes due to tabs being inserted, removed, or moved. It is
     * only emitted when the actual current tab node is changed.
     */
    get currentChanged() {
        return this._currentChanged;
    }
    /**
     * A signal emitted when a tab is moved by the user.
     *
     * #### Notes
     * This signal is emitted when a tab is moved by user interaction.
     *
     * This signal is not emitted when a tab is moved programmatically.
     */
    get tabMoved() {
        return this._tabMoved;
    }
    /**
     * A signal emitted when a tab is clicked by the user.
     *
     * #### Notes
     * If the clicked tab is not the current tab, the clicked tab will be
     * made current and the `currentChanged` signal will be emitted first.
     *
     * This signal is emitted even if the clicked tab is the current tab.
     */
    get tabActivateRequested() {
        return this._tabActivateRequested;
    }
    /**
     * A signal emitted when the tab bar add button is clicked.
     */
    get addRequested() {
        return this._addRequested;
    }
    /**
     * A signal emitted when a tab close icon is clicked.
     *
     * #### Notes
     * This signal is not emitted unless the tab title is `closable`.
     */
    get tabCloseRequested() {
        return this._tabCloseRequested;
    }
    /**
     * A signal emitted when a tab is dragged beyond the detach threshold.
     *
     * #### Notes
     * This signal is emitted when the user drags a tab with the mouse,
     * and mouse is dragged beyond the detach threshold.
     *
     * The consumer of the signal should call `releaseMouse` and remove
     * the tab in order to complete the detach.
     *
     * This signal is only emitted once per drag cycle.
     */
    get tabDetachRequested() {
        return this._tabDetachRequested;
    }
    /**
     * The document to use with the tab bar.
     *
     * The default is the global `document` instance.
     */
    get document() {
        return this._document;
    }
    /**
     * Whether the titles can be user-edited.
     *
     */
    get titlesEditable() {
        return this._titlesEditable;
    }
    /**
     * Set whether titles can be user edited.
     *
     */
    set titlesEditable(value) {
        this._titlesEditable = value;
    }
    /**
     * Get the currently selected title.
     *
     * #### Notes
     * This will be `null` if no tab is selected.
     */
    get currentTitle() {
        return this._titles[this._currentIndex] || null;
    }
    /**
     * Set the currently selected title.
     *
     * #### Notes
     * If the title does not exist, the title will be set to `null`.
     */
    set currentTitle(value) {
        this.currentIndex = value ? this._titles.indexOf(value) : -1;
    }
    /**
     * Get the index of the currently selected tab.
     *
     * #### Notes
     * This will be `-1` if no tab is selected.
     */
    get currentIndex() {
        return this._currentIndex;
    }
    /**
     * Set the index of the currently selected tab.
     *
     * #### Notes
     * If the value is out of range, the index will be set to `-1`.
     */
    set currentIndex(value) {
        // Adjust for an out of range index.
        if (value < 0 || value >= this._titles.length) {
            value = -1;
        }
        // Bail early if the index will not change.
        if (this._currentIndex === value) {
            return;
        }
        // Look up the previous index and title.
        let pi = this._currentIndex;
        let pt = this._titles[pi] || null;
        // Look up the current index and title.
        let ci = value;
        let ct = this._titles[ci] || null;
        // Update the current index and previous title.
        this._currentIndex = ci;
        this._previousTitle = pt;
        // Schedule an update of the tabs.
        this.update();
        // Emit the current changed signal.
        this._currentChanged.emit({
            previousIndex: pi,
            previousTitle: pt,
            currentIndex: ci,
            currentTitle: ct
        });
    }
    /**
     * Get the name of the tab bar.
     */
    get name() {
        return this._name;
    }
    /**
     * Set the name of the tab bar.
     */
    set name(value) {
        this._name = value;
        if (value) {
            this.contentNode.setAttribute('aria-label', value);
        }
        else {
            this.contentNode.removeAttribute('aria-label');
        }
    }
    /**
     * Get the orientation of the tab bar.
     *
     * #### Notes
     * This controls whether the tabs are arranged in a row or column.
     */
    get orientation() {
        return this._orientation;
    }
    /**
     * Set the orientation of the tab bar.
     *
     * #### Notes
     * This controls whether the tabs are arranged in a row or column.
     */
    set orientation(value) {
        // Do nothing if the orientation does not change.
        if (this._orientation === value) {
            return;
        }
        // Release the mouse before making any changes.
        this._releaseMouse();
        // Toggle the orientation values.
        this._orientation = value;
        this.dataset['orientation'] = value;
        this.contentNode.setAttribute('aria-orientation', value);
    }
    /**
     * Whether the add button is enabled.
     */
    get addButtonEnabled() {
        return this._addButtonEnabled;
    }
    /**
     * Set whether the add button is enabled.
     */
    set addButtonEnabled(value) {
        // Do nothing if the value does not change.
        if (this._addButtonEnabled === value) {
            return;
        }
        this._addButtonEnabled = value;
        if (value) {
            this.addButtonNode.classList.remove('lm-mod-hidden');
        }
        else {
            this.addButtonNode.classList.add('lm-mod-hidden');
        }
    }
    /**
     * A read-only array of the titles in the tab bar.
     */
    get titles() {
        return this._titles;
    }
    /**
     * The tab bar content node.
     *
     * #### Notes
     * This is the node which holds the tab nodes.
     *
     * Modifying this node directly can lead to undefined behavior.
     */
    get contentNode() {
        return this.node.getElementsByClassName('lm-TabBar-content')[0];
    }
    /**
     * The tab bar add button node.
     *
     * #### Notes
     * This is the node which holds the add button.
     *
     * Modifying this node directly can lead to undefined behavior.
     */
    get addButtonNode() {
        return this.node.getElementsByClassName('lm-TabBar-addButton')[0];
    }
    /**
     * Add a tab to the end of the tab bar.
     *
     * @param value - The title which holds the data for the tab,
     *   or an options object to convert to a title.
     *
     * @returns The title object added to the tab bar.
     *
     * #### Notes
     * If the title is already added to the tab bar, it will be moved.
     */
    addTab(value) {
        return this.insertTab(this._titles.length, value);
    }
    /**
     * Insert a tab into the tab bar at the specified index.
     *
     * @param index - The index at which to insert the tab.
     *
     * @param value - The title which holds the data for the tab,
     *   or an options object to convert to a title.
     *
     * @returns The title object added to the tab bar.
     *
     * #### Notes
     * The index will be clamped to the bounds of the tabs.
     *
     * If the title is already added to the tab bar, it will be moved.
     */
    insertTab(index, value) {
        // Release the mouse before making any changes.
        this._releaseMouse();
        // Coerce the value to a title.
        let title = Private$7.asTitle(value);
        // Look up the index of the title.
        let i = this._titles.indexOf(title);
        // Clamp the insert index to the array bounds.
        let j = Math.max(0, Math.min(index, this._titles.length));
        // If the title is not in the array, insert it.
        if (i === -1) {
            // Insert the title into the array.
            ArrayExt.insert(this._titles, j, title);
            // Connect to the title changed signal.
            title.changed.connect(this._onTitleChanged, this);
            // Schedule an update of the tabs.
            this.update();
            // Adjust the current index for the insert.
            this._adjustCurrentForInsert(j, title);
            // Return the title added to the tab bar.
            return title;
        }
        // Otherwise, the title exists in the array and should be moved.
        // Adjust the index if the location is at the end of the array.
        if (j === this._titles.length) {
            j--;
        }
        // Bail if there is no effective move.
        if (i === j) {
            return title;
        }
        // Move the title to the new location.
        ArrayExt.move(this._titles, i, j);
        // Schedule an update of the tabs.
        this.update();
        // Adjust the current index for the move.
        this._adjustCurrentForMove(i, j);
        // Return the title added to the tab bar.
        return title;
    }
    /**
     * Remove a tab from the tab bar.
     *
     * @param title - The title for the tab to remove.
     *
     * #### Notes
     * This is a no-op if the title is not in the tab bar.
     */
    removeTab(title) {
        this.removeTabAt(this._titles.indexOf(title));
    }
    /**
     * Remove the tab at a given index from the tab bar.
     *
     * @param index - The index of the tab to remove.
     *
     * #### Notes
     * This is a no-op if the index is out of range.
     */
    removeTabAt(index) {
        let indexIsValid = index >= 0 && index < this._titles.length;
        if (this._tabSizeFrozen) {
            if (indexIsValid) {
                // Capture the widths of all tabs that will remain.
                let tabs = this.contentNode.children;
                this._frozenTabWidths = [];
                for (let i = 0, n = tabs.length; i < n; ++i) {
                    if (i !== index) {
                        this._frozenTabWidths.push(tabs[i].offsetWidth);
                    }
                }
            }
            else {
                this._frozenTabWidths = null;
            }
        }
        // Release the mouse before making any changes.
        this._releaseMouse();
        // Remove the title from the array.
        let title = ArrayExt.removeAt(this._titles, index);
        // Bail if the index is out of range.
        if (!title) {
            return;
        }
        // Disconnect from the title changed signal.
        title.changed.disconnect(this._onTitleChanged, this);
        // Clear the previous title if it's being removed.
        if (title === this._previousTitle) {
            this._previousTitle = null;
        }
        // Schedule an update of the tabs.
        this.update();
        // Adjust the current index for the remove.
        this._adjustCurrentForRemove(index, title);
    }
    /**
     * Remove all tabs from the tab bar.
     */
    clearTabs() {
        // Bail if there is nothing to remove.
        if (this._titles.length === 0) {
            return;
        }
        // Release the mouse before making any changes.
        this._releaseMouse();
        // Disconnect from the title changed signals.
        for (let title of this._titles) {
            title.changed.disconnect(this._onTitleChanged, this);
        }
        // Get the current index and title.
        let pi = this.currentIndex;
        let pt = this.currentTitle;
        // Reset the current index and previous title.
        this._currentIndex = -1;
        this._previousTitle = null;
        // Clear the title array.
        this._titles.length = 0;
        // Schedule an update of the tabs.
        this.update();
        // If no tab was selected, there's nothing else to do.
        if (pi === -1) {
            return;
        }
        // Emit the current changed signal.
        this._currentChanged.emit({
            previousIndex: pi,
            previousTitle: pt,
            currentIndex: -1,
            currentTitle: null
        });
    }
    /**
     * Release the mouse and restore the non-dragged tab positions.
     *
     * #### Notes
     * This will cause the tab bar to stop handling mouse events and to
     * restore the tabs to their non-dragged positions.
     */
    releaseMouse() {
        this._releaseMouse();
    }
    /**
     * Handle the DOM events for the tab bar.
     *
     * @param event - The DOM event sent to the tab bar.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the tab bar's DOM node.
     *
     * This should not be called directly by user code.
     */
    handleEvent(event) {
        switch (event.type) {
            case 'pointerdown':
                this._evtPointerDown(event);
                break;
            case 'pointermove':
                this._evtPointerMove(event);
                break;
            case 'pointerup':
                this._evtPointerUp(event);
                break;
            case 'pointerleave':
                this._evtPointerLeave(event);
                break;
            case 'click':
                this._evtClick(event);
                break;
            case 'dblclick':
                this._evtDblClick(event);
                break;
            case 'keydown':
                event.eventPhase === Event.CAPTURING_PHASE
                    ? this._evtKeyDownCapturing(event)
                    : this._evtKeyDown(event);
                break;
            case 'contextmenu':
                event.preventDefault();
                event.stopPropagation();
                break;
        }
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        this.node.addEventListener('pointerdown', this);
        this.node.addEventListener('pointerleave', this);
        this.node.addEventListener('click', this);
        this.node.addEventListener('dblclick', this);
        this.node.addEventListener('keydown', this);
    }
    /**
     * A message handler invoked on an `'after-detach'` message.
     */
    onAfterDetach(msg) {
        this.node.removeEventListener('pointerdown', this);
        this.node.removeEventListener('pointerleave', this);
        this.node.removeEventListener('click', this);
        this.node.removeEventListener('dblclick', this);
        this.node.removeEventListener('keydown', this);
        this._clearUnfreezeTransitionState();
        this._clearTabSizeFreezeState();
        this._pendingTouchCloseRequest = null;
        this._releaseMouse();
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        var _a;
        let titles = this._titles;
        let renderer = this.renderer;
        let currentTitle = this.currentTitle;
        let content = new Array(titles.length);
        // Keep the tabindex="0" attribute to the tab which handled it before the update.
        // If the add button handles it, no need to do anything. If no element of the tab
        // bar handles it, set it on the current or the first tab to ensure one element
        // handles it after update.
        const tabHandlingTabindex = (_a = this._getCurrentTabindex()) !== null && _a !== void 0 ? _a : (this._currentIndex > -1 ? this._currentIndex : 0);
        for (let i = 0, n = titles.length; i < n; ++i) {
            let title = titles[i];
            let current = title === currentTitle;
            let zIndex = current ? n : n - i - 1;
            let tabIndex = tabHandlingTabindex === i ? 0 : -1;
            content[i] = renderer.renderTab({ title, current, zIndex, tabIndex });
        }
        VirtualDOM.render(content, this.contentNode);
        if (this._tabSizeFrozen) {
            let tabs = this.contentNode.children;
            if (this._frozenTabWidths &&
                this._frozenTabWidths.length === tabs.length) {
                for (let i = 0, n = tabs.length; i < n; ++i) {
                    this._setFrozenTabSize(tabs[i], this._frozenTabWidths[i]);
                }
                this._frozenTabWidths = null;
            }
            else {
                this._frozenTabWidths = null;
                for (let i = 0, n = tabs.length; i < n; ++i) {
                    let tab = tabs[i];
                    this._setFrozenTabSize(tab, tab.offsetWidth);
                }
            }
        }
    }
    /**
     * Get the index of the tab which handles tabindex="0".
     * If the add button handles tabindex="0", -1 is returned.
     * If none of the previous handles tabindex="0", null is returned.
     */
    _getCurrentTabindex() {
        let index = null;
        const elemTabindex = this.contentNode.querySelector('li[tabindex="0"]');
        if (elemTabindex) {
            index = [...this.contentNode.children].indexOf(elemTabindex);
        }
        else if (this._addButtonEnabled &&
            this.addButtonNode.getAttribute('tabindex') === '0') {
            index = -1;
        }
        return index;
    }
    /**
     * Handle the `'dblclick'` event for the tab bar.
     */
    _evtDblClick(event) {
        // Do nothing if titles are not editable
        if (!this.titlesEditable) {
            return;
        }
        let tabs = this.contentNode.children;
        // Find the index of the targeted tab.
        let index = ArrayExt.findFirstIndex(tabs, tab => {
            return ElementExt.hitTest(tab, event.clientX, event.clientY);
        });
        // Do nothing if the press is not on a tab.
        if (index === -1) {
            return;
        }
        let title = this.titles[index];
        let label = tabs[index].querySelector('.lm-TabBar-tabLabel');
        if (label && label.contains(event.target)) {
            let value = title.label || '';
            // Clear the label element
            let oldValue = label.innerHTML;
            label.innerHTML = '';
            let input = document.createElement('input');
            input.classList.add('lm-TabBar-tabInput');
            input.value = value;
            label.appendChild(input);
            let onblur = () => {
                input.removeEventListener('blur', onblur);
                label.innerHTML = oldValue;
                this.node.addEventListener('keydown', this);
            };
            input.addEventListener('dblclick', (event) => event.stopPropagation());
            input.addEventListener('blur', onblur);
            input.addEventListener('keydown', (event) => {
                if (event.key === 'Enter') {
                    if (input.value !== '') {
                        title.label = title.caption = input.value;
                    }
                    onblur();
                }
                else if (event.key === 'Escape') {
                    onblur();
                }
            });
            this.node.removeEventListener('keydown', this);
            input.select();
            input.focus();
            if (label.children.length > 0) {
                label.children[0].focus();
            }
        }
    }
    /**
     * Handle the `'keydown'` event for the tab bar at capturing phase.
     */
    _evtKeyDownCapturing(event) {
        if (event.eventPhase !== Event.CAPTURING_PHASE) {
            return;
        }
        // Stop all input events during drag.
        event.preventDefault();
        event.stopPropagation();
        // Release the mouse if `Escape` is pressed.
        if (event.key === 'Escape') {
            this._releaseMouse();
        }
    }
    /**
     * Handle the `'keydown'` event for the tab bar at target phase.
     */
    _evtKeyDown(event) {
        var _a, _b, _c;
        // Allow for navigation using tab key
        if (event.key === 'Tab' || event.eventPhase === Event.CAPTURING_PHASE) {
            return;
        }
        // Check if Enter or Spacebar key has been pressed and open that tab
        if (event.key === 'Enter' ||
            event.key === 'Spacebar' ||
            event.key === ' ') {
            // Get focus element that is in focus by the tab key
            const focusedElement = document.activeElement;
            // Test first if the focus is on the add button node
            if (this.addButtonEnabled &&
                this.addButtonNode.contains(focusedElement)) {
                event.preventDefault();
                event.stopPropagation();
                this._addRequested.emit();
            }
            else {
                const index = ArrayExt.findFirstIndex(this.contentNode.children, tab => tab.contains(focusedElement));
                if (index >= 0) {
                    event.preventDefault();
                    event.stopPropagation();
                    this.currentIndex = index;
                }
            }
            // Handle the arrow keys to switch tabs.
        }
        else if (ARROW_KEYS.includes(event.key)) {
            // Create a list of all focusable elements in the tab bar.
            const focusable = [...this.contentNode.children];
            if (this.addButtonEnabled) {
                focusable.push(this.addButtonNode);
            }
            // If the tab bar contains only one element, nothing to do.
            if (focusable.length <= 1) {
                return;
            }
            event.preventDefault();
            event.stopPropagation();
            // Get the current focused element.
            let focusedIndex = focusable.indexOf(document.activeElement);
            if (focusedIndex === -1) {
                focusedIndex = this._currentIndex;
            }
            // Find the next element to focus on.
            let nextFocused;
            if ((event.key === 'ArrowRight' && this._orientation === 'horizontal') ||
                (event.key === 'ArrowDown' && this._orientation === 'vertical')) {
                nextFocused = (_a = focusable[focusedIndex + 1]) !== null && _a !== void 0 ? _a : focusable[0];
            }
            else if ((event.key === 'ArrowLeft' && this._orientation === 'horizontal') ||
                (event.key === 'ArrowUp' && this._orientation === 'vertical')) {
                nextFocused =
                    (_b = focusable[focusedIndex - 1]) !== null && _b !== void 0 ? _b : focusable[focusable.length - 1];
            }
            else if (event.key === 'Home') {
                nextFocused = focusable[0];
            }
            else if (event.key === 'End') {
                nextFocused = focusable[focusable.length - 1];
            }
            // Change the focused element and the tabindex value.
            if (nextFocused) {
                (_c = focusable[focusedIndex]) === null || _c === void 0 ? void 0 : _c.setAttribute('tabindex', '-1');
                nextFocused === null || nextFocused === void 0 ? void 0 : nextFocused.setAttribute('tabindex', '0');
                nextFocused.focus();
            }
        }
    }
    /**
     * Handle the `'pointerdown'` event for the tab bar.
     */
    _evtPointerDown(event) {
        this._pendingTouchCloseRequest = null;
        // Do nothing if it's not a left or middle mouse press.
        if (event.button !== 0 && event.button !== 1) {
            return;
        }
        // Do nothing if a drag is in progress.
        if (this._dragData) {
            return;
        }
        // Do nothing if a title editable input was clicked.
        if (event.target.classList.contains('lm-TabBar-tabInput')) {
            return;
        }
        // Check if the add button was clicked.
        let addButtonClicked = this.addButtonEnabled &&
            this.addButtonNode.contains(event.target);
        // Lookup the tab nodes.
        let tabs = this.contentNode.children;
        // Find the index of the pressed tab.
        let index = ArrayExt.findFirstIndex(tabs, tab => {
            return ElementExt.hitTest(tab, event.clientX, event.clientY);
        });
        // Do nothing if the press is not on a tab or the add button.
        if (index === -1 && !addButtonClicked) {
            return;
        }
        // Check if the close icon was pressed.
        let icon = index !== -1
            ? tabs[index].querySelector(this.renderer.closeIconSelector)
            : null;
        let closeIconPressed = !!icon && icon.contains(event.target);
        // Pressing on a tab stops the event propagation.
        if (!closeIconPressed || !Utils$1.isTouchEvent(event)) {
            event.preventDefault();
        }
        event.stopPropagation();
        // Initialize the non-measured parts of the drag data.
        this._dragData = {
            tab: tabs[index],
            index: index,
            pressX: event.clientX,
            pressY: event.clientY,
            tabPos: -1,
            tabSize: -1,
            tabPressPos: -1,
            targetIndex: -1,
            tabLayout: null,
            contentRect: null,
            override: null,
            dragActive: false,
            dragAborted: false,
            detachRequested: false
        };
        // Add the document pointer up listener.
        this.document.addEventListener('pointerup', this, true);
        // Do nothing else if the middle button or add button is clicked.
        if (event.button === 1 || addButtonClicked) {
            return;
        }
        // Do nothing else if the close icon is clicked.
        if (closeIconPressed) {
            return;
        }
        // Add the extra listeners if the tabs are movable.
        if (this.tabsMovable) {
            this.document.addEventListener('pointermove', this, true);
            this.document.addEventListener('keydown', this, true);
            this.document.addEventListener('contextmenu', this, true);
        }
        // Update the current index as appropriate.
        if (this.allowDeselect && this.currentIndex === index) {
            this.currentIndex = -1;
        }
        else {
            this.currentIndex = index;
        }
        // Do nothing else if there is no current tab.
        if (this.currentIndex === -1) {
            return;
        }
        // Emit the tab activate request signal.
        this._tabActivateRequested.emit({
            index: this.currentIndex,
            title: this.currentTitle
        });
    }
    /**
     * Handle the `'pointermove'` event for the tab bar.
     */
    _evtPointerMove(event) {
        // Do nothing if no drag is in progress.
        let data = this._dragData;
        if (!data) {
            return;
        }
        // Suppress the event during a drag.
        event.preventDefault();
        event.stopPropagation();
        // Lookup the tab nodes.
        let tabs = this.contentNode.children;
        // Bail early if the drag threshold has not been met.
        if (!data.dragActive && !Private$7.dragExceeded(data, event)) {
            return;
        }
        // Activate the drag if necessary.
        if (!data.dragActive) {
            // Fill in the rest of the drag data measurements.
            let tabRect = data.tab.getBoundingClientRect();
            if (this._orientation === 'horizontal') {
                data.tabPos = data.tab.offsetLeft;
                data.tabSize = tabRect.width;
                data.tabPressPos = data.pressX - tabRect.left;
            }
            else {
                data.tabPos = data.tab.offsetTop;
                data.tabSize = tabRect.height;
                data.tabPressPos = data.pressY - tabRect.top;
            }
            data.tabPressOffset = {
                x: data.pressX - tabRect.left,
                y: data.pressY - tabRect.top
            };
            data.tabLayout = Private$7.snapTabLayout(tabs, this._orientation);
            data.contentRect = this.contentNode.getBoundingClientRect();
            data.override = Drag.overrideCursor('default');
            // Add the dragging style classes.
            data.tab.classList.add('lm-mod-dragging');
            this.addClass('lm-mod-dragging');
            // Mark the drag as active.
            data.dragActive = true;
        }
        // Emit the detach requested signal if the threshold is exceeded.
        if (!data.detachRequested && Private$7.detachExceeded(data, event)) {
            // Only emit the signal once per drag cycle.
            data.detachRequested = true;
            // Setup the arguments for the signal.
            let index = data.index;
            let clientX = event.clientX;
            let clientY = event.clientY;
            let tab = tabs[index];
            let title = this._titles[index];
            // Emit the tab detach requested signal.
            this._tabDetachRequested.emit({
                index,
                title,
                tab,
                clientX,
                clientY,
                offset: data.tabPressOffset
            });
            // Bail if the signal handler aborted the drag.
            if (data.dragAborted) {
                return;
            }
        }
        // Update the positions of the tabs.
        Private$7.layoutTabs(tabs, data, event, this._orientation);
    }
    /**
     * Handle the `'pointerup'` event for the document.
     */
    _evtPointerUp(event) {
        // Do nothing if it's not a left or middle mouse release.
        if (event.button !== 0 && event.button !== 1) {
            return;
        }
        // Do nothing if no drag is in progress.
        const data = this._dragData;
        if (!data) {
            return;
        }
        const isTouch = Utils$1.isTouchEvent(event);
        // Stop the event propagation.
        if (!isTouch) {
            event.preventDefault();
        }
        event.stopPropagation();
        // Remove the extra mouse event listeners.
        this.document.removeEventListener('pointermove', this, true);
        this.document.removeEventListener('pointerup', this, true);
        this.document.removeEventListener('keydown', this, true);
        this.document.removeEventListener('contextmenu', this, true);
        // Handle a release when the drag is not active.
        if (!data.dragActive) {
            // Clear the drag data.
            this._dragData = null;
            // Handle clicking the add button.
            let addButtonClicked = this.addButtonEnabled &&
                this.addButtonNode.contains(event.target);
            if (addButtonClicked) {
                this._addRequested.emit(undefined);
                return;
            }
            // Lookup the tab nodes.
            let tabs = this.contentNode.children;
            // Find the index of the released tab.
            let index = ArrayExt.findFirstIndex(tabs, tab => {
                return ElementExt.hitTest(tab, event.clientX, event.clientY);
            });
            // Do nothing if the release is not on the original pressed tab.
            if (index !== data.index) {
                return;
            }
            // Ignore the release if the title is not closable.
            let title = this._titles[index];
            if (!title.closable) {
                return;
            }
            // Emit the close requested signal if the middle button is released.
            if (event.button === 1) {
                this._tabCloseRequested.emit({ index, title });
                return;
            }
            // Emit the close requested signal if the close icon was released.
            let icon = tabs[index].querySelector(this.renderer.closeIconSelector);
            if (icon && icon.contains(event.target)) {
                this._tabSizeFrozen = true;
                if (isTouch) {
                    this._pendingTouchCloseRequest = { index, title };
                    return;
                }
                this._tabCloseRequested.emit({ index, title });
                return;
            }
            // Otherwise, there is nothing left to do.
            return;
        }
        // Do nothing if the left button is not released.
        if (event.button !== 0) {
            return;
        }
        // Position the tab at its final resting position.
        Private$7.finalizeTabPosition(data, this._orientation);
        // Remove the dragging class from the tab so it can be transitioned.
        data.tab.classList.remove('lm-mod-dragging');
        // Parse the transition duration for releasing the tab.
        let duration = Private$7.parseTransitionDuration(data.tab);
        // Complete the release on a timer to allow the tab to transition.
        setTimeout(() => {
            // Do nothing if the drag has been aborted.
            if (data.dragAborted) {
                return;
            }
            // Clear the drag data reference.
            this._dragData = null;
            // Reset the positions of the tabs.
            Private$7.resetTabPositions(this.contentNode.children, this._orientation);
            // Clear the cursor grab.
            data.override.dispose();
            // Remove the remaining dragging style.
            this.removeClass('lm-mod-dragging');
            // If the tab was not moved, there is nothing else to do.
            let i = data.index;
            let j = data.targetIndex;
            if (j === -1 || i === j) {
                return;
            }
            // Move the title to the new locations.
            ArrayExt.move(this._titles, i, j);
            // Adjust the current index for the move.
            this._adjustCurrentForMove(i, j);
            // Emit the tab moved signal.
            this._tabMoved.emit({
                fromIndex: i,
                toIndex: j,
                title: this._titles[j]
            });
            // Update the tabs immediately to prevent flicker.
            MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest);
        }, duration);
    }
    /**
     * Release the mouse and restore the non-dragged tab positions.
     */
    _releaseMouse() {
        // Do nothing if no drag is in progress.
        let data = this._dragData;
        if (!data) {
            return;
        }
        // Clear the drag data reference.
        this._dragData = null;
        // Remove the extra document event listeners.
        this.document.removeEventListener('pointermove', this, true);
        this.document.removeEventListener('pointerup', this, true);
        this.document.removeEventListener('keydown', this, true);
        this.document.removeEventListener('contextmenu', this, true);
        // Indicate the drag has been aborted. This allows the mouse
        // event handlers to return early when the drag is canceled.
        data.dragAborted = true;
        // If the drag is not active, there's nothing more to do.
        if (!data.dragActive) {
            return;
        }
        // Reset the tabs to their non-dragged positions.
        Private$7.resetTabPositions(this.contentNode.children, this._orientation);
        // Clear the cursor override.
        data.override.dispose();
        // Clear the dragging style classes.
        data.tab.classList.remove('lm-mod-dragging');
        this.removeClass('lm-mod-dragging');
    }
    /**
     * Adjust the current index for a tab insert operation.
     *
     * This method accounts for the tab bar's insertion behavior when
     * adjusting the current index and emitting the changed signal.
     */
    _adjustCurrentForInsert(i, title) {
        // Lookup commonly used variables.
        let ct = this.currentTitle;
        let ci = this._currentIndex;
        let bh = this.insertBehavior;
        // TODO: do we need to do an update to update the aria-selected attribute?
        // Handle the behavior where the new tab is always selected,
        // or the behavior where the new tab is selected if needed.
        if (bh === 'select-tab' || (bh === 'select-tab-if-needed' && ci === -1)) {
            this._currentIndex = i;
            this._previousTitle = ct;
            this._currentChanged.emit({
                previousIndex: ci,
                previousTitle: ct,
                currentIndex: i,
                currentTitle: title
            });
            return;
        }
        // Otherwise, silently adjust the current index if needed.
        if (ci >= i) {
            this._currentIndex++;
        }
    }
    /**
     * Handle the `'click'` event for the tab bar.
     */
    _evtClick(event) {
        const request = this._pendingTouchCloseRequest;
        if (!request) {
            return;
        }
        this._pendingTouchCloseRequest = null;
        event.preventDefault();
        event.stopPropagation();
        const tab = this.contentNode.children[request.index];
        const icon = tab === null || tab === void 0 ? void 0 : tab.querySelector(this.renderer.closeIconSelector);
        if (this._titles[request.index] !== request.title ||
            !icon ||
            !icon.contains(event.target)) {
            this._clearTabSizeFreezeState();
            return;
        }
        this._tabCloseRequested.emit(request);
    }
    /**
     * Adjust the current index for a tab move operation.
     *
     * This method will not cause the actual current tab to change.
     * It silently adjusts the index to account for the given move.
     */
    _adjustCurrentForMove(i, j) {
        if (this._currentIndex === i) {
            this._currentIndex = j;
        }
        else if (this._currentIndex < i && this._currentIndex >= j) {
            this._currentIndex++;
        }
        else if (this._currentIndex > i && this._currentIndex <= j) {
            this._currentIndex--;
        }
    }
    /**
     * Adjust the current index for a tab remove operation.
     *
     * This method accounts for the tab bar's remove behavior when
     * adjusting the current index and emitting the changed signal.
     */
    _adjustCurrentForRemove(i, title) {
        // Lookup commonly used variables.
        let ci = this._currentIndex;
        let bh = this.removeBehavior;
        // Silently adjust the index if the current tab is not removed.
        if (ci !== i) {
            if (ci > i) {
                this._currentIndex--;
            }
            return;
        }
        // TODO: do we need to do an update to adjust the aria-selected value?
        // No tab gets selected if the tab bar is empty.
        if (this._titles.length === 0) {
            this._currentIndex = -1;
            this._currentChanged.emit({
                previousIndex: i,
                previousTitle: title,
                currentIndex: -1,
                currentTitle: null
            });
            return;
        }
        // Handle behavior where the next sibling tab is selected.
        if (bh === 'select-tab-after') {
            this._currentIndex = Math.min(i, this._titles.length - 1);
            this._currentChanged.emit({
                previousIndex: i,
                previousTitle: title,
                currentIndex: this._currentIndex,
                currentTitle: this.currentTitle
            });
            return;
        }
        // Handle behavior where the previous sibling tab is selected.
        if (bh === 'select-tab-before') {
            this._currentIndex = Math.max(0, i - 1);
            this._currentChanged.emit({
                previousIndex: i,
                previousTitle: title,
                currentIndex: this._currentIndex,
                currentTitle: this.currentTitle
            });
            return;
        }
        // Handle behavior where the previous history tab is selected.
        if (bh === 'select-previous-tab') {
            if (this._previousTitle) {
                this._currentIndex = this._titles.indexOf(this._previousTitle);
                this._previousTitle = null;
            }
            else {
                this._currentIndex = Math.min(i, this._titles.length - 1);
            }
            this._currentChanged.emit({
                previousIndex: i,
                previousTitle: title,
                currentIndex: this._currentIndex,
                currentTitle: this.currentTitle
            });
            return;
        }
        // Otherwise, no tab gets selected.
        this._currentIndex = -1;
        this._currentChanged.emit({
            previousIndex: i,
            previousTitle: title,
            currentIndex: -1,
            currentTitle: null
        });
    }
    /**
     * Handle the `changed` signal of a title object.
     */
    _onTitleChanged(sender) {
        this.update();
    }
    /**
     * Handle the `'pointerleave'` event for the tab bar.
     *
     * This restores the natural tab sizing after tabs were frozen
     * due to a close-icon click.
     */
    _evtPointerLeave(event) {
        // Do nothing if tab sizes are not currently frozen.
        if (!this._tabSizeFrozen) {
            return;
        }
        // Unfreeze tab sizes.
        this._tabSizeFrozen = false;
        this._frozenTabWidths = null;
        // Start a new unfreeze run and clear stale listeners/timers.
        this._unfreezeRunID++;
        let runID = this._unfreezeRunID;
        this._clearUnfreezeTransitionState();
        // Add the unfreezing class to enable a smooth width transition.
        this.addClass('lm-mod-unfreezing');
        // Clear the inline width on all tabs, triggering the CSS transition.
        let tabs = this.contentNode.children;
        for (let i = 0, n = tabs.length; i < n; ++i) {
            let tab = tabs[i];
            tab.style.width = '';
            tab.style.flexBasis = '';
        }
        if (tabs.length === 0) {
            this.removeClass('lm-mod-unfreezing');
        }
        else {
            let onTransitionDone = (event) => {
                let propertyName = event.propertyName;
                if (propertyName === 'width' || propertyName === 'flex-basis') {
                    this._finalizeUnfreezeRun(runID);
                }
            };
            this._unfreezeTransitionListener = onTransitionDone;
            this.node.addEventListener('transitionend', onTransitionDone);
            this.node.addEventListener('transitioncancel', onTransitionDone);
            this._unfreezeFallbackTimerID = window.setTimeout(() => {
                this._finalizeUnfreezeRun(runID);
            }, 2000);
        }
        // Schedule an update to re-render the tabs at natural size.
        this.update();
    }
    /**
     * Apply a frozen size to a tab.
     *
     * The width is mirrored to `flex-basis` so frozen sizing works even when
     * themes define tab sizing through flex rules.
     */
    _setFrozenTabSize(tab, width) {
        let size = `${width}px`;
        tab.style.width = size;
        tab.style.flexBasis = size;
    }
    /**
     * Finish an in-flight unfreeze transition run.
     */
    _finalizeUnfreezeRun(runID) {
        if (runID !== this._unfreezeRunID) {
            return;
        }
        this._clearUnfreezeTransitionState();
        this.removeClass('lm-mod-unfreezing');
    }
    /**
     * Remove unfreeze listeners and timers.
     */
    _clearUnfreezeTransitionState() {
        if (this._unfreezeTransitionListener) {
            this.node.removeEventListener('transitionend', this._unfreezeTransitionListener);
            this.node.removeEventListener('transitioncancel', this._unfreezeTransitionListener);
            this._unfreezeTransitionListener = null;
        }
        if (this._unfreezeFallbackTimerID !== 0) {
            clearTimeout(this._unfreezeFallbackTimerID);
            this._unfreezeFallbackTimerID = 0;
        }
    }
    /**
     * Clear transient tab-size freeze state and styles.
     */
    _clearTabSizeFreezeState() {
        this._tabSizeFrozen = false;
        this._frozenTabWidths = null;
        this.removeClass('lm-mod-unfreezing');
        let tabs = this.contentNode.children;
        for (let i = 0, n = tabs.length; i < n; ++i) {
            let tab = tabs[i];
            tab.style.width = '';
            tab.style.flexBasis = '';
        }
    }
}
/**
 * The namespace for the `TabBar` class statics.
 */
(function (TabBar) {
    /**
     * The default implementation of `IRenderer`.
     *
     * #### Notes
     * Subclasses are free to reimplement rendering methods as needed.
     */
    class Renderer {
        constructor() {
            /**
             * A selector which matches the close icon node in a tab.
             */
            this.closeIconSelector = '.lm-TabBar-tabCloseIcon';
            this._tabID = 0;
            this._tabKeys = new WeakMap();
            this._uuid = ++Renderer._nInstance;
        }
        /**
         * Render the virtual element for a tab.
         *
         * @param data - The data to use for rendering the tab.
         *
         * @returns A virtual element representing the tab.
         */
        renderTab(data) {
            let title = data.title.caption;
            let key = this.createTabKey(data);
            let id = key;
            let style = this.createTabStyle(data);
            let className = this.createTabClass(data);
            let dataset = this.createTabDataset(data);
            let aria = this.createTabARIA(data);
            if (data.title.closable) {
                return h.li({ id, key, className, title, style, dataset, ...aria }, this.renderIcon(data), this.renderLabel(data), this.renderCloseIcon(data));
            }
            else {
                return h.li({ id, key, className, title, style, dataset, ...aria }, this.renderIcon(data), this.renderLabel(data));
            }
        }
        /**
         * Render the icon element for a tab.
         *
         * @param data - The data to use for rendering the tab.
         *
         * @returns A virtual element representing the tab icon.
         */
        renderIcon(data) {
            const { title } = data;
            let className = this.createIconClass(data);
            // If title.icon is undefined, it will be ignored.
            return h.div({ className }, title.icon, title.iconLabel);
        }
        /**
         * Render the label element for a tab.
         *
         * @param data - The data to use for rendering the tab.
         *
         * @returns A virtual element representing the tab label.
         */
        renderLabel(data) {
            return h.div({ className: 'lm-TabBar-tabLabel' }, data.title.label);
        }
        /**
         * Render the close icon element for a tab.
         *
         * @param data - The data to use for rendering the tab.
         *
         * @returns A virtual element representing the tab close icon.
         */
        renderCloseIcon(data) {
            return h.div({ className: 'lm-TabBar-tabCloseIcon' });
        }
        /**
         * Create a unique render key for the tab.
         *
         * @param data - The data to use for the tab.
         *
         * @returns The unique render key for the tab.
         *
         * #### Notes
         * This method caches the key against the tab title the first time
         * the key is generated. This enables efficient rendering of moved
         * tabs and avoids subtle hover style artifacts.
         */
        createTabKey(data) {
            let key = this._tabKeys.get(data.title);
            if (key === undefined) {
                key = `tab-key-${this._uuid}-${this._tabID++}`;
                this._tabKeys.set(data.title, key);
            }
            return key;
        }
        /**
         * Create the inline style object for a tab.
         *
         * @param data - The data to use for the tab.
         *
         * @returns The inline style data for the tab.
         */
        createTabStyle(data) {
            return { zIndex: `${data.zIndex}` };
        }
        /**
         * Create the class name for the tab.
         *
         * @param data - The data to use for the tab.
         *
         * @returns The full class name for the tab.
         */
        createTabClass(data) {
            let name = 'lm-TabBar-tab';
            if (data.title.className) {
                name += ` ${data.title.className}`;
            }
            if (data.title.closable) {
                name += ' lm-mod-closable';
            }
            if (data.current) {
                name += ' lm-mod-current';
            }
            return name;
        }
        /**
         * Create the dataset for a tab.
         *
         * @param data - The data to use for the tab.
         *
         * @returns The dataset for the tab.
         */
        createTabDataset(data) {
            return data.title.dataset;
        }
        /**
         * Create the ARIA attributes for a tab.
         *
         * @param data - The data to use for the tab.
         *
         * @returns The ARIA attributes for the tab.
         */
        createTabARIA(data) {
            var _a;
            return {
                role: 'tab',
                'aria-selected': data.current.toString(),
                tabindex: `${(_a = data.tabIndex) !== null && _a !== void 0 ? _a : '-1'}`
            };
        }
        /**
         * Create the class name for the tab icon.
         *
         * @param data - The data to use for the tab.
         *
         * @returns The full class name for the tab icon.
         */
        createIconClass(data) {
            let name = 'lm-TabBar-tabIcon';
            let extra = data.title.iconClass;
            return extra ? `${name} ${extra}` : name;
        }
    }
    Renderer._nInstance = 0;
    TabBar.Renderer = Renderer;
    /**
     * The default `Renderer` instance.
     */
    TabBar.defaultRenderer = new Renderer();
    /**
     * A selector which matches the add button node in the tab bar.
     */
    TabBar.addButtonSelector = '.lm-TabBar-addButton';
})(TabBar || (TabBar = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$7;
(function (Private) {
    /**
     * The start drag distance threshold.
     */
    Private.DRAG_THRESHOLD = 5;
    /**
     * The detach distance threshold.
     */
    Private.DETACH_THRESHOLD = 20;
    /**
     * Create the DOM node for a tab bar.
     */
    function createNode() {
        let node = document.createElement('div');
        let content = document.createElement('ul');
        content.setAttribute('role', 'tablist');
        content.className = 'lm-TabBar-content';
        node.appendChild(content);
        let add = document.createElement('div');
        add.className = 'lm-TabBar-addButton lm-mod-hidden';
        add.setAttribute('tabindex', '-1');
        add.setAttribute('role', 'button');
        node.appendChild(add);
        return node;
    }
    Private.createNode = createNode;
    /**
     * Coerce a title or options into a real title.
     */
    function asTitle(value) {
        return value instanceof Title ? value : new Title(value);
    }
    Private.asTitle = asTitle;
    /**
     * Parse the transition duration for a tab node.
     */
    function parseTransitionDuration(tab) {
        let style = window.getComputedStyle(tab);
        return 1000 * (parseFloat(style.transitionDuration) || 0);
    }
    Private.parseTransitionDuration = parseTransitionDuration;
    /**
     * Get a snapshot of the current tab layout values.
     */
    function snapTabLayout(tabs, orientation) {
        let layout = new Array(tabs.length);
        for (let i = 0, n = tabs.length; i < n; ++i) {
            let node = tabs[i];
            let style = window.getComputedStyle(node);
            if (orientation === 'horizontal') {
                layout[i] = {
                    pos: node.offsetLeft,
                    size: node.offsetWidth,
                    margin: parseFloat(style.marginLeft) || 0
                };
            }
            else {
                layout[i] = {
                    pos: node.offsetTop,
                    size: node.offsetHeight,
                    margin: parseFloat(style.marginTop) || 0
                };
            }
        }
        return layout;
    }
    Private.snapTabLayout = snapTabLayout;
    /**
     * Test if the event exceeds the drag threshold.
     */
    function dragExceeded(data, event) {
        let dx = Math.abs(event.clientX - data.pressX);
        let dy = Math.abs(event.clientY - data.pressY);
        return dx >= Private.DRAG_THRESHOLD || dy >= Private.DRAG_THRESHOLD;
    }
    Private.dragExceeded = dragExceeded;
    /**
     * Test if the event exceeds the drag detach threshold.
     */
    function detachExceeded(data, event) {
        let rect = data.contentRect;
        return (event.clientX < rect.left - Private.DETACH_THRESHOLD ||
            event.clientX >= rect.right + Private.DETACH_THRESHOLD ||
            event.clientY < rect.top - Private.DETACH_THRESHOLD ||
            event.clientY >= rect.bottom + Private.DETACH_THRESHOLD);
    }
    Private.detachExceeded = detachExceeded;
    /**
     * Update the relative tab positions and computed target index.
     */
    function layoutTabs(tabs, data, event, orientation) {
        // Compute the orientation-sensitive values.
        let pressPos;
        let localPos;
        let clientPos;
        let clientSize;
        if (orientation === 'horizontal') {
            pressPos = data.pressX;
            localPos = event.clientX - data.contentRect.left;
            clientPos = event.clientX;
            clientSize = data.contentRect.width;
        }
        else {
            pressPos = data.pressY;
            localPos = event.clientY - data.contentRect.top;
            clientPos = event.clientY;
            clientSize = data.contentRect.height;
        }
        // Compute the target data.
        let targetIndex = data.index;
        let targetPos = localPos - data.tabPressPos;
        let targetEnd = targetPos + data.tabSize;
        // Update the relative tab positions.
        for (let i = 0, n = tabs.length; i < n; ++i) {
            let pxPos;
            let layout = data.tabLayout[i];
            let threshold = layout.pos + (layout.size >> 1);
            if (i < data.index && targetPos < threshold) {
                pxPos = `${data.tabSize + data.tabLayout[i + 1].margin}px`;
                targetIndex = Math.min(targetIndex, i);
            }
            else if (i > data.index && targetEnd > threshold) {
                pxPos = `${-data.tabSize - layout.margin}px`;
                targetIndex = Math.max(targetIndex, i);
            }
            else if (i === data.index) {
                let ideal = clientPos - pressPos;
                let limit = clientSize - (data.tabPos + data.tabSize);
                pxPos = `${Math.max(-data.tabPos, Math.min(ideal, limit))}px`;
            }
            else {
                pxPos = '';
            }
            if (orientation === 'horizontal') {
                tabs[i].style.left = pxPos;
            }
            else {
                tabs[i].style.top = pxPos;
            }
        }
        // Update the computed target index.
        data.targetIndex = targetIndex;
    }
    Private.layoutTabs = layoutTabs;
    /**
     * Position the drag tab at its final resting relative position.
     */
    function finalizeTabPosition(data, orientation) {
        // Compute the orientation-sensitive client size.
        let clientSize;
        if (orientation === 'horizontal') {
            clientSize = data.contentRect.width;
        }
        else {
            clientSize = data.contentRect.height;
        }
        // Compute the ideal final tab position.
        let ideal;
        if (data.targetIndex === data.index) {
            ideal = 0;
        }
        else if (data.targetIndex > data.index) {
            let tgt = data.tabLayout[data.targetIndex];
            ideal = tgt.pos + tgt.size - data.tabSize - data.tabPos;
        }
        else {
            let tgt = data.tabLayout[data.targetIndex];
            ideal = tgt.pos - data.tabPos;
        }
        // Compute the tab position limit.
        let limit = clientSize - (data.tabPos + data.tabSize);
        let final = Math.max(-data.tabPos, Math.min(ideal, limit));
        // Set the final orientation-sensitive position.
        if (orientation === 'horizontal') {
            data.tab.style.left = `${final}px`;
        }
        else {
            data.tab.style.top = `${final}px`;
        }
    }
    Private.finalizeTabPosition = finalizeTabPosition;
    /**
     * Reset the relative positions of the given tabs.
     */
    function resetTabPositions(tabs, orientation) {
        for (const tab of tabs) {
            if (orientation === 'horizontal') {
                tab.style.left = '';
            }
            else {
                tab.style.top = '';
            }
        }
    }
    Private.resetTabPositions = resetTabPositions;
})(Private$7 || (Private$7 = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A layout which provides a flexible docking arrangement.
 *
 * #### Notes
 * The consumer of this layout is responsible for handling all signals
 * from the generated tab bars and managing the visibility of widgets
 * and tab bars as needed.
 */
class DockLayout extends Layout {
    /**
     * Construct a new dock layout.
     *
     * @param options - The options for initializing the layout.
     */
    constructor(options) {
        super();
        this._spacing = 4;
        this._dirty = false;
        this._root = null;
        this._box = null;
        this._items = new Map();
        this.renderer = options.renderer;
        if (options.spacing !== undefined) {
            this._spacing = Utils$1.clampDimension(options.spacing);
        }
        this._document = options.document || document;
        this._hiddenMode =
            options.hiddenMode !== undefined
                ? options.hiddenMode
                : Widget.HiddenMode.Display;
    }
    /**
     * Dispose of the resources held by the layout.
     *
     * #### Notes
     * This will clear and dispose all widgets in the layout.
     */
    dispose() {
        // Get an iterator over the widgets in the layout.
        let widgets = this[Symbol.iterator]();
        // Dispose of the layout items.
        this._items.forEach(item => {
            item.dispose();
        });
        // Clear the layout state before disposing the widgets.
        this._box = null;
        this._root = null;
        this._items.clear();
        // Dispose of the widgets contained in the old layout root.
        for (const widget of widgets) {
            widget.dispose();
        }
        // Dispose of the base class.
        super.dispose();
    }
    /**
     * The method for hiding child widgets.
     *
     * #### Notes
     * If there is only one child widget, `Display` hiding mode will be used
     * regardless of this setting.
     */
    get hiddenMode() {
        return this._hiddenMode;
    }
    set hiddenMode(v) {
        if (this._hiddenMode === v) {
            return;
        }
        this._hiddenMode = v;
        for (const bar of this.tabBars()) {
            if (bar.titles.length > 1) {
                for (const title of bar.titles) {
                    title.owner.hiddenMode = this._hiddenMode;
                }
            }
        }
    }
    /**
     * Get the inter-element spacing for the dock layout.
     */
    get spacing() {
        return this._spacing;
    }
    /**
     * Set the inter-element spacing for the dock layout.
     */
    set spacing(value) {
        value = Utils$1.clampDimension(value);
        if (this._spacing === value) {
            return;
        }
        this._spacing = value;
        if (!this.parent) {
            return;
        }
        this.parent.fit();
    }
    /**
     * Whether the dock layout is empty.
     */
    get isEmpty() {
        return this._root === null;
    }
    /**
     * Create an iterator over all widgets in the layout.
     *
     * @returns A new iterator over the widgets in the layout.
     *
     * #### Notes
     * This iterator includes the generated tab bars.
     */
    [Symbol.iterator]() {
        return this._root ? this._root.iterAllWidgets() : empty();
    }
    /**
     * Create an iterator over the user widgets in the layout.
     *
     * @returns A new iterator over the user widgets in the layout.
     *
     * #### Notes
     * This iterator does not include the generated tab bars.
     */
    widgets() {
        return this._root ? this._root.iterUserWidgets() : empty();
    }
    /**
     * Create an iterator over the selected widgets in the layout.
     *
     * @returns A new iterator over the selected user widgets.
     *
     * #### Notes
     * This iterator yields the widgets corresponding to the current tab
     * of each tab bar in the layout.
     */
    selectedWidgets() {
        return this._root ? this._root.iterSelectedWidgets() : empty();
    }
    /**
     * Create an iterator over the tab bars in the layout.
     *
     * @returns A new iterator over the tab bars in the layout.
     *
     * #### Notes
     * This iterator does not include the user widgets.
     */
    tabBars() {
        return this._root ? this._root.iterTabBars() : empty();
    }
    /**
     * Create an iterator over the handles in the layout.
     *
     * @returns A new iterator over the handles in the layout.
     */
    handles() {
        return this._root ? this._root.iterHandles() : empty();
    }
    /**
     * Move a handle to the given offset position.
     *
     * @param handle - The handle to move.
     *
     * @param offsetX - The desired offset X position of the handle.
     *
     * @param offsetY - The desired offset Y position of the handle.
     *
     * #### Notes
     * If the given handle is not contained in the layout, this is no-op.
     *
     * The handle will be moved as close as possible to the desired
     * position without violating any of the layout constraints.
     *
     * Only one of the coordinates is used depending on the orientation
     * of the handle. This method accepts both coordinates to make it
     * easy to invoke from a mouse move event without needing to know
     * the handle orientation.
     */
    moveHandle(handle, offsetX, offsetY) {
        // Bail early if there is no root or if the handle is hidden.
        let hidden = handle.classList.contains('lm-mod-hidden');
        if (!this._root || hidden) {
            return;
        }
        // Lookup the split node for the handle.
        let data = this._root.findSplitNode(handle);
        if (!data) {
            return;
        }
        // Compute the desired delta movement for the handle.
        let delta;
        if (data.node.orientation === 'horizontal') {
            delta = offsetX - handle.offsetLeft;
        }
        else {
            delta = offsetY - handle.offsetTop;
        }
        // Bail if there is no handle movement.
        if (delta === 0) {
            return;
        }
        // Prevent sibling resizing unless needed.
        data.node.holdSizes();
        // Adjust the sizers to reflect the handle movement.
        BoxEngine.adjust(data.node.sizers, data.index, delta);
        // Update the layout of the widgets.
        if (this.parent) {
            this.parent.update();
        }
    }
    /**
     * Save the current configuration of the dock layout.
     *
     * @returns A new config object for the current layout state.
     *
     * #### Notes
     * The return value can be provided to the `restoreLayout` method
     * in order to restore the layout to its current configuration.
     */
    saveLayout() {
        // Bail early if there is no root.
        if (!this._root) {
            return { main: null };
        }
        // Hold the current sizes in the layout tree.
        this._root.holdAllSizes();
        // Return the layout config.
        return { main: this._root.createConfig() };
    }
    /**
     * Restore the layout to a previously saved configuration.
     *
     * @param config - The layout configuration to restore.
     *
     * #### Notes
     * Widgets which currently belong to the layout but which are not
     * contained in the config will be unparented.
     */
    restoreLayout(config) {
        // Create the widget set for validating the config.
        let widgetSet = new Set();
        // Normalize the main area config and collect the widgets.
        let mainConfig;
        if (config.main) {
            mainConfig = Private$6.normalizeAreaConfig(config.main, widgetSet);
        }
        else {
            mainConfig = null;
        }
        // Create iterators over the old content.
        let oldWidgets = this.widgets();
        let oldTabBars = this.tabBars();
        let oldHandles = this.handles();
        // Clear the root before removing the old content.
        this._root = null;
        // Unparent the old widgets which are not in the new config.
        for (const widget of oldWidgets) {
            if (!widgetSet.has(widget)) {
                widget.parent = null;
            }
        }
        // Dispose of the old tab bars.
        for (const tabBar of oldTabBars) {
            tabBar.dispose();
        }
        // Remove the old handles.
        for (const handle of oldHandles) {
            if (handle.parentNode) {
                handle.parentNode.removeChild(handle);
            }
        }
        // Reparent the new widgets to the current parent.
        for (const widget of widgetSet) {
            widget.parent = this.parent;
        }
        // Create the root node for the new config.
        if (mainConfig) {
            this._root = Private$6.realizeAreaConfig(mainConfig, {
                // Ignoring optional `document` argument as we must reuse `this._document`
                createTabBar: (document) => this._createTabBar(),
                createHandle: () => this._createHandle()
            }, this._document);
        }
        else {
            this._root = null;
        }
        // If there is no parent, there is nothing more to do.
        if (!this.parent) {
            return;
        }
        // Attach the new widgets to the parent.
        widgetSet.forEach(widget => {
            this.attachWidget(widget);
        });
        // Post a fit request to the parent.
        this.parent.fit();
    }
    /**
     * Add a widget to the dock layout.
     *
     * @param widget - The widget to add to the dock layout.
     *
     * @param options - The additional options for adding the widget.
     *
     * #### Notes
     * The widget will be moved if it is already contained in the layout.
     *
     * An error will be thrown if the reference widget is invalid.
     */
    addWidget(widget, options = {}) {
        // Parse the options.
        let ref = options.ref || null;
        let mode = options.mode || 'tab-after';
        // Find the tab node which holds the reference widget.
        let refNode = null;
        if (this._root && ref) {
            refNode = this._root.findTabNode(ref);
        }
        // Throw an error if the reference widget is invalid.
        if (ref && !refNode) {
            throw new Error('Reference widget is not in the layout.');
        }
        // Reparent the widget to the current layout parent.
        widget.parent = this.parent;
        // Insert the widget according to the insert mode.
        switch (mode) {
            case 'tab-after':
                this._insertTab(widget, ref, refNode, true);
                break;
            case 'tab-before':
                this._insertTab(widget, ref, refNode, false);
                break;
            case 'split-top':
                this._insertSplit(widget, ref, refNode, 'vertical', false);
                break;
            case 'split-left':
                this._insertSplit(widget, ref, refNode, 'horizontal', false);
                break;
            case 'split-right':
                this._insertSplit(widget, ref, refNode, 'horizontal', true);
                break;
            case 'split-bottom':
                this._insertSplit(widget, ref, refNode, 'vertical', true);
                break;
            case 'merge-top':
                this._insertSplit(widget, ref, refNode, 'vertical', false, true);
                break;
            case 'merge-left':
                this._insertSplit(widget, ref, refNode, 'horizontal', false, true);
                break;
            case 'merge-right':
                this._insertSplit(widget, ref, refNode, 'horizontal', true, true);
                break;
            case 'merge-bottom':
                this._insertSplit(widget, ref, refNode, 'vertical', true, true);
                break;
        }
        // Do nothing else if there is no parent widget.
        if (!this.parent) {
            return;
        }
        // Ensure the widget is attached to the parent widget.
        this.attachWidget(widget);
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * Remove a widget from the layout.
     *
     * @param widget - The widget to remove from the layout.
     *
     * #### Notes
     * A widget is automatically removed from the layout when its `parent`
     * is set to `null`. This method should only be invoked directly when
     * removing a widget from a layout which has yet to be installed on a
     * parent widget.
     *
     * This method does *not* modify the widget's `parent`.
     */
    removeWidget(widget) {
        // Remove the widget from its current layout location.
        this._removeWidget(widget);
        // Do nothing else if there is no parent widget.
        if (!this.parent) {
            return;
        }
        // Detach the widget from the parent widget.
        this.detachWidget(widget);
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * Find the tab area which contains the given client position.
     *
     * @param clientX - The client X position of interest.
     *
     * @param clientY - The client Y position of interest.
     *
     * @returns The geometry of the tab area at the given position, or
     *   `null` if there is no tab area at the given position.
     */
    hitTestTabAreas(clientX, clientY) {
        // Bail early if hit testing cannot produce valid results.
        if (!this._root || !this.parent || !this.parent.isVisible) {
            return null;
        }
        // Ensure the parent box sizing data is computed.
        if (!this._box) {
            this._box = ElementExt.boxSizing(this.parent.node);
        }
        // Convert from client to local coordinates.
        let rect = this.parent.node.getBoundingClientRect();
        let x = clientX - rect.left - this._box.borderLeft;
        let y = clientY - rect.top - this._box.borderTop;
        // Find the tab layout node at the local position.
        let tabNode = this._root.hitTestTabNodes(x, y);
        // Bail if a tab layout node was not found.
        if (!tabNode) {
            return null;
        }
        // Extract the data from the tab node.
        let { tabBar, top, left, width, height } = tabNode;
        // Compute the right and bottom edges of the tab area.
        let borderWidth = this._box.borderLeft + this._box.borderRight;
        let borderHeight = this._box.borderTop + this._box.borderBottom;
        let right = rect.width - borderWidth - (left + width);
        let bottom = rect.height - borderHeight - (top + height);
        // Return the hit test results.
        return { tabBar, x, y, top, left, right, bottom, width, height };
    }
    /**
     * Perform layout initialization which requires the parent widget.
     */
    init() {
        // Perform superclass initialization.
        super.init();
        // Attach each widget to the parent.
        for (const widget of this) {
            this.attachWidget(widget);
        }
        // Attach each handle to the parent.
        for (const handle of this.handles()) {
            this.parent.node.appendChild(handle);
        }
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * Attach the widget to the layout parent widget.
     *
     * @param widget - The widget to attach to the parent.
     *
     * #### Notes
     * This is a no-op if the widget is already attached.
     */
    attachWidget(widget) {
        // Do nothing if the widget is already attached.
        if (this.parent.node === widget.node.parentNode) {
            return;
        }
        // Create the layout item for the widget.
        this._items.set(widget, new LayoutItem(widget));
        // Send a `'before-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        }
        // Add the widget's node to the parent.
        this.parent.node.appendChild(widget.node);
        // Send an `'after-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
        }
    }
    /**
     * Detach the widget from the layout parent widget.
     *
     * @param widget - The widget to detach from the parent.
     *
     * #### Notes
     * This is a no-op if the widget is not attached.
     */
    detachWidget(widget) {
        // Do nothing if the widget is not attached.
        if (this.parent.node !== widget.node.parentNode) {
            return;
        }
        // Send a `'before-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        }
        // Remove the widget's node from the parent.
        this.parent.node.removeChild(widget.node);
        // Send an `'after-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
        }
        // Delete the layout item for the widget.
        let item = this._items.get(widget);
        if (item) {
            this._items.delete(widget);
            item.dispose();
        }
    }
    /**
     * A message handler invoked on a `'before-show'` message.
     */
    onBeforeShow(msg) {
        super.onBeforeShow(msg);
        this.parent.update();
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        super.onBeforeAttach(msg);
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-shown'` message.
     */
    onChildShown(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-hidden'` message.
     */
    onChildHidden(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'resize'` message.
     */
    onResize(msg) {
        if (this.parent.isVisible) {
            this._update(msg.width, msg.height);
        }
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        if (this.parent.isVisible) {
            this._update(-1, -1);
        }
    }
    /**
     * A message handler invoked on a `'fit-request'` message.
     */
    onFitRequest(msg) {
        if (this.parent.isAttached) {
            this._fit();
        }
    }
    /**
     * Remove the specified widget from the layout structure.
     *
     * #### Notes
     * This is a no-op if the widget is not in the layout tree.
     *
     * This does not detach the widget from the parent node.
     */
    _removeWidget(widget) {
        // Bail early if there is no layout root.
        if (!this._root) {
            return;
        }
        // Find the tab node which contains the given widget.
        let tabNode = this._root.findTabNode(widget);
        // Bail early if the tab node is not found.
        if (!tabNode) {
            return;
        }
        Private$6.removeAria(widget);
        // If there are multiple tabs, just remove the widget's tab.
        if (tabNode.tabBar.titles.length > 1) {
            tabNode.tabBar.removeTab(widget.title);
            if (this._hiddenMode === Widget.HiddenMode.Scale &&
                tabNode.tabBar.titles.length == 1) {
                const existingWidget = tabNode.tabBar.titles[0].owner;
                existingWidget.hiddenMode = Widget.HiddenMode.Display;
            }
            return;
        }
        // Otherwise, the tab node needs to be removed...
        // Dispose the tab bar.
        tabNode.tabBar.dispose();
        // Handle the case where the tab node is the root.
        if (this._root === tabNode) {
            this._root = null;
            return;
        }
        // Otherwise, remove the tab node from its parent...
        // Prevent widget resizing unless needed.
        this._root.holdAllSizes();
        // Clear the parent reference on the tab node.
        let splitNode = tabNode.parent;
        tabNode.parent = null;
        // Remove the tab node from its parent split node.
        let i = ArrayExt.removeFirstOf(splitNode.children, tabNode);
        let handle = ArrayExt.removeAt(splitNode.handles, i);
        ArrayExt.removeAt(splitNode.sizers, i);
        // Remove the handle from its parent DOM node.
        if (handle.parentNode) {
            handle.parentNode.removeChild(handle);
        }
        // If there are multiple children, just update the handles.
        if (splitNode.children.length > 1) {
            splitNode.syncHandles();
            return;
        }
        // Otherwise, the split node also needs to be removed...
        // Clear the parent reference on the split node.
        let maybeParent = splitNode.parent;
        splitNode.parent = null;
        // Lookup the remaining child node and handle.
        let childNode = splitNode.children[0];
        let childHandle = splitNode.handles[0];
        // Clear the split node data.
        splitNode.children.length = 0;
        splitNode.handles.length = 0;
        splitNode.sizers.length = 0;
        // Remove the child handle from its parent node.
        if (childHandle.parentNode) {
            childHandle.parentNode.removeChild(childHandle);
        }
        // Handle the case where the split node is the root.
        if (this._root === splitNode) {
            childNode.parent = null;
            this._root = childNode;
            return;
        }
        // Otherwise, move the child node to the parent node...
        let parentNode = maybeParent;
        // Lookup the index of the split node.
        let j = parentNode.children.indexOf(splitNode);
        // Handle the case where the child node is a tab node.
        if (childNode instanceof Private$6.TabLayoutNode) {
            childNode.parent = parentNode;
            parentNode.children[j] = childNode;
            return;
        }
        // Remove the split data from the parent.
        let splitHandle = ArrayExt.removeAt(parentNode.handles, j);
        ArrayExt.removeAt(parentNode.children, j);
        ArrayExt.removeAt(parentNode.sizers, j);
        // Remove the handle from its parent node.
        if (splitHandle.parentNode) {
            splitHandle.parentNode.removeChild(splitHandle);
        }
        // The child node and the split parent node will have the same
        // orientation. Merge the grand-children with the parent node.
        for (let i = 0, n = childNode.children.length; i < n; ++i) {
            let gChild = childNode.children[i];
            let gHandle = childNode.handles[i];
            let gSizer = childNode.sizers[i];
            ArrayExt.insert(parentNode.children, j + i, gChild);
            ArrayExt.insert(parentNode.handles, j + i, gHandle);
            ArrayExt.insert(parentNode.sizers, j + i, gSizer);
            gChild.parent = parentNode;
        }
        // Clear the child node.
        childNode.children.length = 0;
        childNode.handles.length = 0;
        childNode.sizers.length = 0;
        childNode.parent = null;
        // Sync the handles on the parent node.
        parentNode.syncHandles();
    }
    /**
     * Create the tab layout node to hold the widget.
     */
    _createTabNode(widget) {
        let tabNode = new Private$6.TabLayoutNode(this._createTabBar());
        tabNode.tabBar.addTab(widget.title);
        Private$6.addAria(widget, tabNode.tabBar);
        return tabNode;
    }
    /**
     * Insert a widget next to an existing tab.
     *
     * #### Notes
     * This does not attach the widget to the parent widget.
     */
    _insertTab(widget, ref, refNode, after) {
        // Do nothing if the tab is inserted next to itself.
        if (widget === ref) {
            return;
        }
        // Create the root if it does not exist.
        if (!this._root) {
            let tabNode = new Private$6.TabLayoutNode(this._createTabBar());
            tabNode.tabBar.addTab(widget.title);
            this._root = tabNode;
            Private$6.addAria(widget, tabNode.tabBar);
            return;
        }
        // Use the first tab node as the ref node if needed.
        if (!refNode) {
            refNode = this._root.findFirstTabNode();
        }
        // If the widget is not contained in the ref node, ensure it is
        // removed from the layout and hidden before being added again.
        if (refNode.tabBar.titles.indexOf(widget.title) === -1) {
            this._removeWidget(widget);
            widget.hide();
        }
        // Lookup the target index for inserting the tab.
        let index;
        if (ref) {
            index = refNode.tabBar.titles.indexOf(ref.title);
        }
        else {
            index = refNode.tabBar.currentIndex;
        }
        // Using transform create an additional layer in the pixel pipeline
        // to limit the number of layer, it is set only if there is more than one widget.
        if (this._hiddenMode === Widget.HiddenMode.Scale) {
            if (refNode.tabBar.titles.length === 0) {
                // Singular tab should use display mode to limit number of layers.
                widget.hiddenMode = Widget.HiddenMode.Display;
            }
            else if (refNode.tabBar.titles.length == 1) {
                // If we are adding a second tab, switch the existing tab back to scale.
                const existingWidget = refNode.tabBar.titles[0].owner;
                existingWidget.hiddenMode = Widget.HiddenMode.Scale;
            }
            else {
                // For the third and subsequent tabs no special action is needed.
                widget.hiddenMode = Widget.HiddenMode.Scale;
            }
        }
        else {
            // For all other modes just propagate the current mode.
            widget.hiddenMode = this._hiddenMode;
        }
        // Insert the widget's tab relative to the target index.
        refNode.tabBar.insertTab(index + (after ? 1 : 0), widget.title);
        Private$6.addAria(widget, refNode.tabBar);
    }
    /**
     * Insert a widget as a new split area.
     *
     * #### Notes
     * This does not attach the widget to the parent widget.
     */
    _insertSplit(widget, ref, refNode, orientation, after, merge = false) {
        // Do nothing if there is no effective split.
        if (widget === ref && refNode && refNode.tabBar.titles.length === 1) {
            return;
        }
        // Ensure the widget is removed from the current layout.
        this._removeWidget(widget);
        // Set the root if it does not exist.
        if (!this._root) {
            this._root = this._createTabNode(widget);
            return;
        }
        // If the ref node parent is null, split the root.
        if (!refNode || !refNode.parent) {
            // Ensure the root is split with the correct orientation.
            let root = this._splitRoot(orientation);
            // Determine the insert index for the new tab node.
            let i = after ? root.children.length : 0;
            // Normalize the split node.
            root.normalizeSizes();
            // Create the sizer for new tab node.
            let sizer = Private$6.createSizer(refNode ? 1 : Private$6.GOLDEN_RATIO);
            // Insert the tab node sized to the golden ratio.
            let tabNode = this._createTabNode(widget);
            ArrayExt.insert(root.children, i, tabNode);
            ArrayExt.insert(root.sizers, i, sizer);
            ArrayExt.insert(root.handles, i, this._createHandle());
            tabNode.parent = root;
            // Re-normalize the split node to maintain the ratios.
            root.normalizeSizes();
            // Finally, synchronize the visibility of the handles.
            root.syncHandles();
            return;
        }
        // Lookup the split node for the ref widget.
        let splitNode = refNode.parent;
        // If the split node already had the correct orientation,
        // the widget can be inserted into the split node directly.
        if (splitNode.orientation === orientation) {
            // Find the index of the ref node.
            let i = splitNode.children.indexOf(refNode);
            // Conditionally reuse a tab layout found in the wanted position.
            if (merge) {
                let j = i + (after ? 1 : -1);
                let sibling = splitNode.children[j];
                if (sibling instanceof Private$6.TabLayoutNode) {
                    this._insertTab(widget, null, sibling, true);
                    ++sibling.tabBar.currentIndex;
                    return;
                }
            }
            // Normalize the split node.
            splitNode.normalizeSizes();
            // Consume half the space for the insert location.
            let s = (splitNode.sizers[i].sizeHint /= 2);
            // Insert the tab node sized to the other half.
            let j = i + (after ? 1 : 0);
            let tabNode = this._createTabNode(widget);
            ArrayExt.insert(splitNode.children, j, tabNode);
            ArrayExt.insert(splitNode.sizers, j, Private$6.createSizer(s));
            ArrayExt.insert(splitNode.handles, j, this._createHandle());
            tabNode.parent = splitNode;
            // Finally, synchronize the visibility of the handles.
            splitNode.syncHandles();
            return;
        }
        // Remove the ref node from the split node.
        let i = ArrayExt.removeFirstOf(splitNode.children, refNode);
        // Create a new normalized split node for the children.
        let childNode = new Private$6.SplitLayoutNode(orientation);
        childNode.normalized = true;
        // Add the ref node sized to half the space.
        childNode.children.push(refNode);
        childNode.sizers.push(Private$6.createSizer(0.5));
        childNode.handles.push(this._createHandle());
        refNode.parent = childNode;
        // Add the tab node sized to the other half.
        let j = after ? 1 : 0;
        let tabNode = this._createTabNode(widget);
        ArrayExt.insert(childNode.children, j, tabNode);
        ArrayExt.insert(childNode.sizers, j, Private$6.createSizer(0.5));
        ArrayExt.insert(childNode.handles, j, this._createHandle());
        tabNode.parent = childNode;
        // Synchronize the visibility of the handles.
        childNode.syncHandles();
        // Finally, add the new child node to the original split node.
        ArrayExt.insert(splitNode.children, i, childNode);
        childNode.parent = splitNode;
    }
    /**
     * Ensure the root is a split node with the given orientation.
     */
    _splitRoot(orientation) {
        // Bail early if the root already meets the requirements.
        let oldRoot = this._root;
        if (oldRoot instanceof Private$6.SplitLayoutNode) {
            if (oldRoot.orientation === orientation) {
                return oldRoot;
            }
        }
        // Create a new root node with the specified orientation.
        let newRoot = (this._root = new Private$6.SplitLayoutNode(orientation));
        // Add the old root to the new root.
        if (oldRoot) {
            newRoot.children.push(oldRoot);
            newRoot.sizers.push(Private$6.createSizer(0));
            newRoot.handles.push(this._createHandle());
            oldRoot.parent = newRoot;
        }
        // Return the new root as a convenience.
        return newRoot;
    }
    /**
     * Fit the layout to the total size required by the widgets.
     */
    _fit() {
        // Set up the computed minimum size.
        let minW = 0;
        let minH = 0;
        // Update the size limits for the layout tree.
        if (this._root) {
            let limits = this._root.fit(this._spacing, this._items);
            minW = limits.minWidth;
            minH = limits.minHeight;
        }
        // Update the box sizing and add it to the computed min size.
        let box = (this._box = ElementExt.boxSizing(this.parent.node));
        minW += box.horizontalSum;
        minH += box.verticalSum;
        // Update the parent's min size constraints.
        let style = this.parent.node.style;
        style.minWidth = `${minW}px`;
        style.minHeight = `${minH}px`;
        // Set the dirty flag to ensure only a single update occurs.
        this._dirty = true;
        // Notify the ancestor that it should fit immediately. This may
        // cause a resize of the parent, fulfilling the required update.
        if (this.parent.parent) {
            MessageLoop.sendMessage(this.parent.parent, Widget.Msg.FitRequest);
        }
        // If the dirty flag is still set, the parent was not resized.
        // Trigger the required update on the parent widget immediately.
        if (this._dirty) {
            MessageLoop.sendMessage(this.parent, Widget.Msg.UpdateRequest);
        }
    }
    /**
     * Update the layout position and size of the widgets.
     *
     * The parent offset dimensions should be `-1` if unknown.
     */
    _update(offsetWidth, offsetHeight) {
        // Clear the dirty flag to indicate the update occurred.
        this._dirty = false;
        // Bail early if there is no root layout node.
        if (!this._root) {
            return;
        }
        // Measure the parent if the offset dimensions are unknown.
        if (offsetWidth < 0) {
            offsetWidth = this.parent.node.offsetWidth;
        }
        if (offsetHeight < 0) {
            offsetHeight = this.parent.node.offsetHeight;
        }
        // Ensure the parent box sizing data is computed.
        if (!this._box) {
            this._box = ElementExt.boxSizing(this.parent.node);
        }
        // Compute the actual layout bounds adjusted for border and padding.
        let x = this._box.paddingTop;
        let y = this._box.paddingLeft;
        let width = offsetWidth - this._box.horizontalSum;
        let height = offsetHeight - this._box.verticalSum;
        // Update the geometry of the layout tree.
        this._root.update(x, y, width, height, this._spacing, this._items);
    }
    /**
     * Create a new tab bar for use by the dock layout.
     *
     * #### Notes
     * The tab bar will be attached to the parent if it exists.
     */
    _createTabBar() {
        // Create the tab bar using the renderer.
        let tabBar = this.renderer.createTabBar(this._document);
        // Enforce necessary tab bar behavior.
        tabBar.orientation = 'horizontal';
        // Attach the tab bar to the parent if possible.
        if (this.parent) {
            this.attachWidget(tabBar);
        }
        // Return the initialized tab bar.
        return tabBar;
    }
    /**
     * Create a new handle for the dock layout.
     *
     * #### Notes
     * The handle will be attached to the parent if it exists.
     */
    _createHandle() {
        // Create the handle using the renderer.
        let handle = this.renderer.createHandle();
        // Initialize the handle layout behavior.
        let style = handle.style;
        style.position = 'absolute';
        style.contain = 'strict';
        style.top = '0';
        style.left = '0';
        style.width = '0';
        style.height = '0';
        // Attach the handle to the parent if it exists.
        if (this.parent) {
            this.parent.node.appendChild(handle);
        }
        // Return the initialized handle.
        return handle;
    }
}
/**
 * The namespace for the module implementation details.
 */
var Private$6;
(function (Private) {
    /**
     * A fraction used for sizing root panels; ~= `1 / golden_ratio`.
     */
    Private.GOLDEN_RATIO = 0.618;
    /**
     * Create a box sizer with an initial size hint.
     */
    function createSizer(hint) {
        let sizer = new BoxSizer();
        sizer.sizeHint = hint;
        sizer.size = hint;
        return sizer;
    }
    Private.createSizer = createSizer;
    /**
     * Normalize an area config object and collect the visited widgets.
     */
    function normalizeAreaConfig(config, widgetSet) {
        let result;
        if (config.type === 'tab-area') {
            result = normalizeTabAreaConfig(config, widgetSet);
        }
        else {
            result = normalizeSplitAreaConfig(config, widgetSet);
        }
        return result;
    }
    Private.normalizeAreaConfig = normalizeAreaConfig;
    /**
     * Convert a normalized area config into a layout tree.
     */
    function realizeAreaConfig(config, renderer, document) {
        let node;
        if (config.type === 'tab-area') {
            node = realizeTabAreaConfig(config, renderer, document);
        }
        else {
            node = realizeSplitAreaConfig(config, renderer, document);
        }
        return node;
    }
    Private.realizeAreaConfig = realizeAreaConfig;
    /**
     * A layout node which holds the data for a tabbed area.
     */
    class TabLayoutNode {
        /**
         * Construct a new tab layout node.
         *
         * @param tabBar - The tab bar to use for the layout node.
         */
        constructor(tabBar) {
            /**
             * The parent of the layout node.
             */
            this.parent = null;
            this._top = 0;
            this._left = 0;
            this._width = 0;
            this._height = 0;
            let tabSizer = new BoxSizer();
            let widgetSizer = new BoxSizer();
            tabSizer.stretch = 0;
            widgetSizer.stretch = 1;
            this.tabBar = tabBar;
            this.sizers = [tabSizer, widgetSizer];
        }
        /**
         * The most recent value for the `top` edge of the layout box.
         */
        get top() {
            return this._top;
        }
        /**
         * The most recent value for the `left` edge of the layout box.
         */
        get left() {
            return this._left;
        }
        /**
         * The most recent value for the `width` of the layout box.
         */
        get width() {
            return this._width;
        }
        /**
         * The most recent value for the `height` of the layout box.
         */
        get height() {
            return this._height;
        }
        /**
         * Create an iterator for all widgets in the layout tree.
         */
        *iterAllWidgets() {
            yield this.tabBar;
            yield* this.iterUserWidgets();
        }
        /**
         * Create an iterator for the user widgets in the layout tree.
         */
        *iterUserWidgets() {
            for (const title of this.tabBar.titles) {
                yield title.owner;
            }
        }
        /**
         * Create an iterator for the selected widgets in the layout tree.
         */
        *iterSelectedWidgets() {
            let title = this.tabBar.currentTitle;
            if (title) {
                yield title.owner;
            }
        }
        /**
         * Create an iterator for the tab bars in the layout tree.
         */
        *iterTabBars() {
            yield this.tabBar;
        }
        /**
         * Create an iterator for the handles in the layout tree.
         */
        // eslint-disable-next-line require-yield
        *iterHandles() {
            return;
        }
        /**
         * Find the tab layout node which contains the given widget.
         */
        findTabNode(widget) {
            return this.tabBar.titles.indexOf(widget.title) !== -1 ? this : null;
        }
        /**
         * Find the split layout node which contains the given handle.
         */
        findSplitNode(handle) {
            return null;
        }
        /**
         * Find the first tab layout node in a layout tree.
         */
        findFirstTabNode() {
            return this;
        }
        /**
         * Find the tab layout node which contains the local point.
         */
        hitTestTabNodes(x, y) {
            if (x < this._left || x >= this._left + this._width) {
                return null;
            }
            if (y < this._top || y >= this._top + this._height) {
                return null;
            }
            return this;
        }
        /**
         * Create a configuration object for the layout tree.
         */
        createConfig() {
            let widgets = this.tabBar.titles.map(title => title.owner);
            let currentIndex = this.tabBar.currentIndex;
            return { type: 'tab-area', widgets, currentIndex };
        }
        /**
         * Recursively hold all of the sizes in the layout tree.
         *
         * This ignores the sizers of tab layout nodes.
         */
        holdAllSizes() {
            return;
        }
        /**
         * Fit the layout tree.
         */
        fit(spacing, items) {
            // Set up the limit variables.
            let minWidth = 0;
            let minHeight = 0;
            let maxWidth = Infinity;
            let maxHeight = Infinity;
            // Lookup the tab bar layout item.
            let tabBarItem = items.get(this.tabBar);
            // Lookup the widget layout item.
            let current = this.tabBar.currentTitle;
            let widgetItem = current ? items.get(current.owner) : undefined;
            // Lookup the tab bar and widget sizers.
            let [tabBarSizer, widgetSizer] = this.sizers;
            // Update the tab bar limits.
            if (tabBarItem) {
                tabBarItem.fit();
            }
            // Update the widget limits.
            if (widgetItem) {
                widgetItem.fit();
            }
            // Update the results and sizer for the tab bar.
            if (tabBarItem && !tabBarItem.isHidden) {
                minWidth = Math.max(minWidth, tabBarItem.minWidth);
                minHeight += tabBarItem.minHeight;
                tabBarSizer.minSize = tabBarItem.minHeight;
                tabBarSizer.maxSize = tabBarItem.maxHeight;
            }
            else {
                tabBarSizer.minSize = 0;
                tabBarSizer.maxSize = 0;
            }
            // Update the results and sizer for the current widget.
            if (widgetItem && !widgetItem.isHidden) {
                minWidth = Math.max(minWidth, widgetItem.minWidth);
                minHeight += widgetItem.minHeight;
                widgetSizer.minSize = widgetItem.minHeight;
                widgetSizer.maxSize = Infinity;
            }
            else {
                widgetSizer.minSize = 0;
                widgetSizer.maxSize = Infinity;
            }
            // Return the computed size limits for the layout node.
            return { minWidth, minHeight, maxWidth, maxHeight };
        }
        /**
         * Update the layout tree.
         */
        update(left, top, width, height, spacing, items) {
            // Update the layout box values.
            this._top = top;
            this._left = left;
            this._width = width;
            this._height = height;
            // Lookup the tab bar layout item.
            let tabBarItem = items.get(this.tabBar);
            // Lookup the widget layout item.
            let current = this.tabBar.currentTitle;
            let widgetItem = current ? items.get(current.owner) : undefined;
            // Distribute the layout space to the sizers.
            BoxEngine.calc(this.sizers, height);
            // Update the tab bar item using the computed size.
            if (tabBarItem && !tabBarItem.isHidden) {
                let size = this.sizers[0].size;
                tabBarItem.update(left, top, width, size);
                top += size;
            }
            // Layout the widget using the computed size.
            if (widgetItem && !widgetItem.isHidden) {
                let size = this.sizers[1].size;
                widgetItem.update(left, top, width, size);
            }
        }
    }
    Private.TabLayoutNode = TabLayoutNode;
    /**
     * A layout node which holds the data for a split area.
     */
    class SplitLayoutNode {
        /**
         * Construct a new split layout node.
         *
         * @param orientation - The orientation of the node.
         */
        constructor(orientation) {
            /**
             * The parent of the layout node.
             */
            this.parent = null;
            /**
             * Whether the sizers have been normalized.
             */
            this.normalized = false;
            /**
             * The child nodes for the split node.
             */
            this.children = [];
            /**
             * The box sizers for the layout children.
             */
            this.sizers = [];
            /**
             * The handles for the layout children.
             */
            this.handles = [];
            this.orientation = orientation;
        }
        /**
         * Create an iterator for all widgets in the layout tree.
         */
        *iterAllWidgets() {
            for (const child of this.children) {
                yield* child.iterAllWidgets();
            }
        }
        /**
         * Create an iterator for the user widgets in the layout tree.
         */
        *iterUserWidgets() {
            for (const child of this.children) {
                yield* child.iterUserWidgets();
            }
        }
        /**
         * Create an iterator for the selected widgets in the layout tree.
         */
        *iterSelectedWidgets() {
            for (const child of this.children) {
                yield* child.iterSelectedWidgets();
            }
        }
        /**
         * Create an iterator for the tab bars in the layout tree.
         */
        *iterTabBars() {
            for (const child of this.children) {
                yield* child.iterTabBars();
            }
        }
        /**
         * Create an iterator for the handles in the layout tree.
         */
        *iterHandles() {
            yield* this.handles;
            for (const child of this.children) {
                yield* child.iterHandles();
            }
        }
        /**
         * Find the tab layout node which contains the given widget.
         */
        findTabNode(widget) {
            for (let i = 0, n = this.children.length; i < n; ++i) {
                let result = this.children[i].findTabNode(widget);
                if (result) {
                    return result;
                }
            }
            return null;
        }
        /**
         * Find the split layout node which contains the given handle.
         */
        findSplitNode(handle) {
            let index = this.handles.indexOf(handle);
            if (index !== -1) {
                return { index, node: this };
            }
            for (let i = 0, n = this.children.length; i < n; ++i) {
                let result = this.children[i].findSplitNode(handle);
                if (result) {
                    return result;
                }
            }
            return null;
        }
        /**
         * Find the first tab layout node in a layout tree.
         */
        findFirstTabNode() {
            if (this.children.length === 0) {
                return null;
            }
            return this.children[0].findFirstTabNode();
        }
        /**
         * Find the tab layout node which contains the local point.
         */
        hitTestTabNodes(x, y) {
            for (let i = 0, n = this.children.length; i < n; ++i) {
                let result = this.children[i].hitTestTabNodes(x, y);
                if (result) {
                    return result;
                }
            }
            return null;
        }
        /**
         * Create a configuration object for the layout tree.
         */
        createConfig() {
            let orientation = this.orientation;
            let sizes = this.createNormalizedSizes();
            let children = this.children.map(child => child.createConfig());
            return { type: 'split-area', orientation, children, sizes };
        }
        /**
         * Sync the visibility and orientation of the handles.
         */
        syncHandles() {
            this.handles.forEach((handle, i) => {
                handle.setAttribute('data-orientation', this.orientation);
                if (i === this.handles.length - 1) {
                    handle.classList.add('lm-mod-hidden');
                }
                else {
                    handle.classList.remove('lm-mod-hidden');
                }
            });
        }
        /**
         * Hold the current sizes of the box sizers.
         *
         * This sets the size hint of each sizer to its current size.
         */
        holdSizes() {
            for (const sizer of this.sizers) {
                sizer.sizeHint = sizer.size;
            }
        }
        /**
         * Recursively hold all of the sizes in the layout tree.
         *
         * This ignores the sizers of tab layout nodes.
         */
        holdAllSizes() {
            for (const child of this.children) {
                child.holdAllSizes();
            }
            this.holdSizes();
        }
        /**
         * Normalize the sizes of the split layout node.
         */
        normalizeSizes() {
            // Bail early if the sizers are empty.
            let n = this.sizers.length;
            if (n === 0) {
                return;
            }
            // Hold the current sizes of the sizers.
            this.holdSizes();
            // Compute the sum of the sizes.
            let sum = this.sizers.reduce((v, sizer) => v + sizer.sizeHint, 0);
            // Normalize the sizes based on the sum.
            if (sum === 0) {
                for (const sizer of this.sizers) {
                    sizer.size = sizer.sizeHint = 1 / n;
                }
            }
            else {
                for (const sizer of this.sizers) {
                    sizer.size = sizer.sizeHint /= sum;
                }
            }
            // Mark the sizes as normalized.
            this.normalized = true;
        }
        /**
         * Snap the normalized sizes of the split layout node.
         */
        createNormalizedSizes() {
            // Bail early if the sizers are empty.
            let n = this.sizers.length;
            if (n === 0) {
                return [];
            }
            // Grab the current sizes of the sizers.
            let sizes = this.sizers.map(sizer => sizer.size);
            // Compute the sum of the sizes.
            let sum = sizes.reduce((v, size) => v + size, 0);
            // Normalize the sizes based on the sum.
            if (sum === 0) {
                for (let i = sizes.length - 1; i > -1; i--) {
                    sizes[i] = 1 / n;
                }
            }
            else {
                for (let i = sizes.length - 1; i > -1; i--) {
                    sizes[i] /= sum;
                }
            }
            // Return the normalized sizes.
            return sizes;
        }
        /**
         * Fit the layout tree.
         */
        fit(spacing, items) {
            // Compute the required fixed space.
            let horizontal = this.orientation === 'horizontal';
            let fixed = Math.max(0, this.children.length - 1) * spacing;
            // Set up the limit variables.
            let minWidth = horizontal ? fixed : 0;
            let minHeight = horizontal ? 0 : fixed;
            let maxWidth = Infinity;
            let maxHeight = Infinity;
            // Fit the children and update the limits.
            for (let i = 0, n = this.children.length; i < n; ++i) {
                let limits = this.children[i].fit(spacing, items);
                if (horizontal) {
                    minHeight = Math.max(minHeight, limits.minHeight);
                    minWidth += limits.minWidth;
                    this.sizers[i].minSize = limits.minWidth;
                }
                else {
                    minWidth = Math.max(minWidth, limits.minWidth);
                    minHeight += limits.minHeight;
                    this.sizers[i].minSize = limits.minHeight;
                }
            }
            // Return the computed limits for the layout node.
            return { minWidth, minHeight, maxWidth, maxHeight };
        }
        /**
         * Update the layout tree.
         */
        update(left, top, width, height, spacing, items) {
            // Compute the available layout space.
            let horizontal = this.orientation === 'horizontal';
            let fixed = Math.max(0, this.children.length - 1) * spacing;
            let space = Math.max(0, (horizontal ? width : height) - fixed);
            // De-normalize the sizes if needed.
            if (this.normalized) {
                for (const sizer of this.sizers) {
                    sizer.sizeHint *= space;
                }
                this.normalized = false;
            }
            // Distribute the layout space to the sizers.
            BoxEngine.calc(this.sizers, space);
            // Update the geometry of the child nodes and handles.
            for (let i = 0, n = this.children.length; i < n; ++i) {
                let child = this.children[i];
                let size = this.sizers[i].size;
                let handleStyle = this.handles[i].style;
                if (horizontal) {
                    child.update(left, top, size, height, spacing, items);
                    left += size;
                    handleStyle.top = `${top}px`;
                    handleStyle.left = `${left}px`;
                    handleStyle.width = `${spacing}px`;
                    handleStyle.height = `${height}px`;
                    left += spacing;
                }
                else {
                    child.update(left, top, width, size, spacing, items);
                    top += size;
                    handleStyle.top = `${top}px`;
                    handleStyle.left = `${left}px`;
                    handleStyle.width = `${width}px`;
                    handleStyle.height = `${spacing}px`;
                    top += spacing;
                }
            }
        }
    }
    Private.SplitLayoutNode = SplitLayoutNode;
    function addAria(widget, tabBar) {
        widget.node.setAttribute('role', 'tabpanel');
        let renderer = tabBar.renderer;
        if (renderer instanceof TabBar.Renderer) {
            let tabId = renderer.createTabKey({
                title: widget.title,
                current: false,
                zIndex: 0
            });
            widget.node.setAttribute('aria-labelledby', tabId);
        }
    }
    Private.addAria = addAria;
    function removeAria(widget) {
        widget.node.removeAttribute('role');
        widget.node.removeAttribute('aria-labelledby');
    }
    Private.removeAria = removeAria;
    /**
     * Normalize a tab area config and collect the visited widgets.
     */
    function normalizeTabAreaConfig(config, widgetSet) {
        // Bail early if there is no content.
        if (config.widgets.length === 0) {
            return null;
        }
        // Setup the filtered widgets array.
        let widgets = [];
        // Filter the config for unique widgets.
        for (const widget of config.widgets) {
            if (!widgetSet.has(widget)) {
                widgetSet.add(widget);
                widgets.push(widget);
            }
        }
        // Bail if there are no effective widgets.
        if (widgets.length === 0) {
            return null;
        }
        // Normalize the current index.
        let index = config.currentIndex;
        if (index !== -1 && (index < 0 || index >= widgets.length)) {
            index = 0;
        }
        // Return a normalized config object.
        return { type: 'tab-area', widgets, currentIndex: index };
    }
    /**
     * Normalize a split area config and collect the visited widgets.
     */
    function normalizeSplitAreaConfig(config, widgetSet) {
        // Set up the result variables.
        let orientation = config.orientation;
        let children = [];
        let sizes = [];
        // Normalize the config children.
        for (let i = 0, n = config.children.length; i < n; ++i) {
            // Normalize the child config.
            let child = normalizeAreaConfig(config.children[i], widgetSet);
            // Ignore an empty child.
            if (!child) {
                continue;
            }
            // Add the child or hoist its content as appropriate.
            if (child.type === 'tab-area' || child.orientation !== orientation) {
                children.push(child);
                sizes.push(Math.abs(config.sizes[i] || 0));
            }
            else {
                children.push(...child.children);
                sizes.push(...child.sizes);
            }
        }
        // Bail if there are no effective children.
        if (children.length === 0) {
            return null;
        }
        // If there is only one effective child, return that child.
        if (children.length === 1) {
            return children[0];
        }
        // Return a normalized config object.
        return { type: 'split-area', orientation, children, sizes };
    }
    /**
     * Convert a normalized tab area config into a layout tree.
     */
    function realizeTabAreaConfig(config, renderer, document) {
        // Create the tab bar for the layout node.
        let tabBar = renderer.createTabBar(document);
        // Hide each widget and add it to the tab bar.
        for (const widget of config.widgets) {
            widget.hide();
            tabBar.addTab(widget.title);
            Private.addAria(widget, tabBar);
        }
        // Set the current index of the tab bar.
        tabBar.currentIndex = config.currentIndex;
        // Return the new tab layout node.
        return new TabLayoutNode(tabBar);
    }
    /**
     * Convert a normalized split area config into a layout tree.
     */
    function realizeSplitAreaConfig(config, renderer, document) {
        // Create the split layout node.
        let node = new SplitLayoutNode(config.orientation);
        // Add each child to the layout node.
        config.children.forEach((child, i) => {
            // Create the child data for the layout node.
            let childNode = realizeAreaConfig(child, renderer, document);
            let sizer = createSizer(config.sizes[i]);
            let handle = renderer.createHandle();
            // Add the child data to the layout node.
            node.children.push(childNode);
            node.handles.push(handle);
            node.sizers.push(sizer);
            // Update the parent for the child node.
            childNode.parent = node;
        });
        // Synchronize the handle state for the layout node.
        node.syncHandles();
        // Normalize the sizes for the layout node.
        node.normalizeSizes();
        // Return the new layout node.
        return node;
    }
})(Private$6 || (Private$6 = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A widget which provides a flexible docking area for widgets.
 *
 * #### Notes
 * See also the related [example](../../examples/dockpanel/index.html) and
 * its [source](https://github.com/jupyterlab/lumino/tree/main/examples/example-dockpanel).
 */
class DockPanel extends Widget {
    /**
     * Construct a new dock panel.
     *
     * @param options - The options for initializing the panel.
     */
    constructor(options = {}) {
        super();
        this._drag = null;
        this._tabsMovable = true;
        this._tabsConstrained = false;
        this._addButtonEnabled = false;
        this._pressData = null;
        this._layoutModified = new Signal(this);
        this._addRequested = new Signal(this);
        this.addClass('lm-DockPanel');
        this._document = options.document || document;
        this._mode = options.mode || 'multiple-document';
        this._renderer = options.renderer || DockPanel.defaultRenderer;
        this._edges = options.edges || Private$5.DEFAULT_EDGES;
        if (options.tabsMovable !== undefined) {
            this._tabsMovable = options.tabsMovable;
        }
        if (options.tabsConstrained !== undefined) {
            this._tabsConstrained = options.tabsConstrained;
        }
        if (options.addButtonEnabled !== undefined) {
            this._addButtonEnabled = options.addButtonEnabled;
        }
        // Toggle the CSS mode attribute.
        this.dataset['mode'] = this._mode;
        // Create the delegate renderer for the layout.
        let renderer = {
            createTabBar: () => this._createTabBar(),
            createHandle: () => this._createHandle()
        };
        // Set up the dock layout for the panel.
        this.layout = new DockLayout({
            document: this._document,
            renderer,
            spacing: options.spacing,
            hiddenMode: options.hiddenMode
        });
        // Set up the overlay drop indicator.
        this.overlay = options.overlay || new DockPanel.Overlay();
        this.node.appendChild(this.overlay.node);
    }
    /**
     * Dispose of the resources held by the panel.
     */
    dispose() {
        // Ensure the mouse is released.
        this._releaseMouse();
        // Hide the overlay.
        this.overlay.hide(0);
        // Cancel a drag if one is in progress.
        if (this._drag) {
            this._drag.dispose();
        }
        // Dispose of the base class.
        super.dispose();
    }
    /**
     * The method for hiding widgets.
     */
    get hiddenMode() {
        return this.layout.hiddenMode;
    }
    /**
     * Set the method for hiding widgets.
     */
    set hiddenMode(v) {
        this.layout.hiddenMode = v;
    }
    /**
     * A signal emitted when the layout configuration is modified.
     *
     * #### Notes
     * This signal is emitted whenever the current layout configuration
     * may have changed.
     *
     * This signal is emitted asynchronously in a collapsed fashion, so
     * that multiple synchronous modifications results in only a single
     * emit of the signal.
     */
    get layoutModified() {
        return this._layoutModified;
    }
    /**
     * A signal emitted when the add button on a tab bar is clicked.
     *
     */
    get addRequested() {
        return this._addRequested;
    }
    /**
     * The renderer used by the dock panel.
     */
    get renderer() {
        return this.layout.renderer;
    }
    /**
     * Get the spacing between the widgets.
     */
    get spacing() {
        return this.layout.spacing;
    }
    /**
     * Set the spacing between the widgets.
     */
    set spacing(value) {
        this.layout.spacing = value;
    }
    /**
     * Get the mode for the dock panel.
     */
    get mode() {
        return this._mode;
    }
    /**
     * Set the mode for the dock panel.
     *
     * #### Notes
     * Changing the mode is a destructive operation with respect to the
     * panel's layout configuration. If layout state must be preserved,
     * save the current layout config before changing the mode.
     */
    set mode(value) {
        // Bail early if the mode does not change.
        if (this._mode === value) {
            return;
        }
        // Update the internal mode.
        this._mode = value;
        // Toggle the CSS mode attribute.
        this.dataset['mode'] = value;
        // Get the layout for the panel.
        let layout = this.layout;
        // Configure the layout for the specified mode.
        switch (value) {
            case 'multiple-document':
                for (const tabBar of layout.tabBars()) {
                    tabBar.show();
                }
                break;
            case 'single-document':
                layout.restoreLayout(Private$5.createSingleDocumentConfig(this));
                break;
            default:
                throw 'unreachable';
        }
        // Schedule an emit of the layout modified signal.
        MessageLoop.postMessage(this, Private$5.LayoutModified);
    }
    /**
     * Whether the tabs can be dragged / moved at runtime.
     */
    get tabsMovable() {
        return this._tabsMovable;
    }
    /**
     * Enable / Disable draggable / movable tabs.
     */
    set tabsMovable(value) {
        this._tabsMovable = value;
        for (const tabBar of this.tabBars()) {
            tabBar.tabsMovable = value;
        }
    }
    /**
     * Whether the tabs are constrained to their source dock panel
     */
    get tabsConstrained() {
        return this._tabsConstrained;
    }
    /**
     * Constrain/Allow tabs to be dragged outside of this dock panel
     */
    set tabsConstrained(value) {
        this._tabsConstrained = value;
    }
    /**
     * Whether the add buttons for each tab bar are enabled.
     */
    get addButtonEnabled() {
        return this._addButtonEnabled;
    }
    /**
     * Set whether the add buttons for each tab bar are enabled.
     */
    set addButtonEnabled(value) {
        this._addButtonEnabled = value;
        for (const tabBar of this.tabBars()) {
            tabBar.addButtonEnabled = value;
        }
    }
    /**
     * Whether the dock panel is empty.
     */
    get isEmpty() {
        return this.layout.isEmpty;
    }
    /**
     * Create an iterator over the user widgets in the panel.
     *
     * @returns A new iterator over the user widgets in the panel.
     *
     * #### Notes
     * This iterator does not include the generated tab bars.
     */
    *widgets() {
        yield* this.layout.widgets();
    }
    /**
     * Create an iterator over the selected widgets in the panel.
     *
     * @returns A new iterator over the selected user widgets.
     *
     * #### Notes
     * This iterator yields the widgets corresponding to the current tab
     * of each tab bar in the panel.
     */
    *selectedWidgets() {
        yield* this.layout.selectedWidgets();
    }
    /**
     * Create an iterator over the tab bars in the panel.
     *
     * @returns A new iterator over the tab bars in the panel.
     *
     * #### Notes
     * This iterator does not include the user widgets.
     */
    *tabBars() {
        yield* this.layout.tabBars();
    }
    /**
     * Create an iterator over the handles in the panel.
     *
     * @returns A new iterator over the handles in the panel.
     */
    *handles() {
        yield* this.layout.handles();
    }
    /**
     * Select a specific widget in the dock panel.
     *
     * @param widget - The widget of interest.
     *
     * #### Notes
     * This will make the widget the current widget in its tab area.
     */
    selectWidget(widget) {
        // Find the tab bar which contains the widget.
        let tabBar = find(this.tabBars(), bar => {
            return bar.titles.indexOf(widget.title) !== -1;
        });
        // Throw an error if no tab bar is found.
        if (!tabBar) {
            throw new Error('Widget is not contained in the dock panel.');
        }
        // Ensure the widget is the current widget.
        tabBar.currentTitle = widget.title;
    }
    /**
     * Activate a specified widget in the dock panel.
     *
     * @param widget - The widget of interest.
     *
     * #### Notes
     * This will select and activate the given widget.
     */
    activateWidget(widget) {
        this.selectWidget(widget);
        widget.activate();
    }
    /**
     * Save the current layout configuration of the dock panel.
     *
     * @returns A new config object for the current layout state.
     *
     * #### Notes
     * The return value can be provided to the `restoreLayout` method
     * in order to restore the layout to its current configuration.
     */
    saveLayout() {
        return this.layout.saveLayout();
    }
    /**
     * Restore the layout to a previously saved configuration.
     *
     * @param config - The layout configuration to restore.
     *
     * #### Notes
     * Widgets which currently belong to the layout but which are not
     * contained in the config will be unparented.
     *
     * The dock panel automatically reverts to `'multiple-document'`
     * mode when a layout config is restored.
     */
    restoreLayout(config) {
        // Reset the mode.
        this._mode = 'multiple-document';
        // Restore the layout.
        this.layout.restoreLayout(config);
        // Flush the message loop on IE and Edge to prevent flicker.
        if (Platform.IS_EDGE || Platform.IS_IE) {
            MessageLoop.flush();
        }
        // Schedule an emit of the layout modified signal.
        MessageLoop.postMessage(this, Private$5.LayoutModified);
    }
    /**
     * Add a widget to the dock panel.
     *
     * @param widget - The widget to add to the dock panel.
     *
     * @param options - The additional options for adding the widget.
     *
     * #### Notes
     * If the panel is in single document mode, the options are ignored
     * and the widget is always added as tab in the hidden tab bar.
     */
    addWidget(widget, options = {}) {
        // Add the widget to the layout.
        if (this._mode === 'single-document') {
            this.layout.addWidget(widget);
        }
        else {
            this.layout.addWidget(widget, options);
        }
        // Schedule an emit of the layout modified signal.
        MessageLoop.postMessage(this, Private$5.LayoutModified);
    }
    /**
     * Process a message sent to the widget.
     *
     * @param msg - The message sent to the widget.
     */
    processMessage(msg) {
        if (msg.type === 'layout-modified') {
            this._layoutModified.emit(undefined);
        }
        else {
            super.processMessage(msg);
        }
    }
    /**
     * Handle the DOM events for the dock panel.
     *
     * @param event - The DOM event sent to the panel.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the panel's DOM node. It should
     * not be called directly by user code.
     */
    handleEvent(event) {
        switch (event.type) {
            case 'lm-dragenter':
                this._evtDragEnter(event);
                break;
            case 'lm-dragleave':
                this._evtDragLeave(event);
                break;
            case 'lm-dragover':
                this._evtDragOver(event);
                break;
            case 'lm-drop':
                this._evtDrop(event);
                break;
            case 'pointerdown':
                this._evtPointerDown(event);
                break;
            case 'pointermove':
                this._evtPointerMove(event);
                break;
            case 'pointerup':
                this._evtPointerUp(event);
                break;
            case 'keydown':
                this._evtKeyDown(event);
                break;
            case 'contextmenu':
                event.preventDefault();
                event.stopPropagation();
                break;
        }
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        this.node.addEventListener('lm-dragenter', this);
        this.node.addEventListener('lm-dragleave', this);
        this.node.addEventListener('lm-dragover', this);
        this.node.addEventListener('lm-drop', this);
        this.node.addEventListener('pointerdown', this);
    }
    /**
     * A message handler invoked on an `'after-detach'` message.
     */
    onAfterDetach(msg) {
        this.node.removeEventListener('lm-dragenter', this);
        this.node.removeEventListener('lm-dragleave', this);
        this.node.removeEventListener('lm-dragover', this);
        this.node.removeEventListener('lm-drop', this);
        this.node.removeEventListener('pointerdown', this);
        this._releaseMouse();
    }
    /**
     * A message handler invoked on a `'child-added'` message.
     */
    onChildAdded(msg) {
        // Ignore the generated tab bars.
        if (Private$5.isGeneratedTabBarProperty.get(msg.child)) {
            return;
        }
        // Add the widget class to the child.
        msg.child.addClass('lm-DockPanel-widget');
    }
    /**
     * A message handler invoked on a `'child-removed'` message.
     */
    onChildRemoved(msg) {
        // Ignore the generated tab bars.
        if (Private$5.isGeneratedTabBarProperty.get(msg.child)) {
            return;
        }
        // Remove the widget class from the child.
        msg.child.removeClass('lm-DockPanel-widget');
        // Schedule an emit of the layout modified signal.
        MessageLoop.postMessage(this, Private$5.LayoutModified);
    }
    /**
     * Handle the `'lm-dragenter'` event for the dock panel.
     */
    _evtDragEnter(event) {
        // If the factory mime type is present, mark the event as
        // handled in order to get the rest of the drag events.
        if (event.mimeData.hasData('application/vnd.lumino.widget-factory')) {
            event.preventDefault();
            event.stopPropagation();
        }
    }
    /**
     * Handle the `'lm-dragleave'` event for the dock panel.
     */
    _evtDragLeave(event) {
        // Mark the event as handled.
        event.preventDefault();
        if (this._tabsConstrained && event.source !== this)
            return;
        event.stopPropagation();
        // The new target might be a descendant, so we might still handle the drop.
        // Hide asynchronously so that if a lm-dragover event bubbles up to us, the
        // hide is cancelled by the lm-dragover handler's show overlay logic.
        this.overlay.hide(1);
    }
    /**
     * Handle the `'lm-dragover'` event for the dock panel.
     */
    _evtDragOver(event) {
        // Mark the event as handled.
        event.preventDefault();
        // Show the drop indicator overlay and update the drop
        // action based on the drop target zone under the mouse.
        if ((this._tabsConstrained && event.source !== this) ||
            this._showOverlay(event.clientX, event.clientY) === 'invalid') {
            event.dropAction = 'none';
        }
        else {
            event.stopPropagation();
            event.dropAction = event.proposedAction;
        }
    }
    /**
     * Handle the `'lm-drop'` event for the dock panel.
     */
    _evtDrop(event) {
        // Mark the event as handled.
        event.preventDefault();
        // Hide the drop indicator overlay.
        this.overlay.hide(0);
        // Bail if the proposed action is to do nothing.
        if (event.proposedAction === 'none') {
            event.dropAction = 'none';
            return;
        }
        // Find the drop target under the mouse.
        let { clientX, clientY } = event;
        let { zone, target } = Private$5.findDropTarget(this, clientX, clientY, this._edges);
        // Bail if the drop zone is invalid.
        if ((this._tabsConstrained && event.source !== this) ||
            zone === 'invalid') {
            event.dropAction = 'none';
            return;
        }
        // Bail if the factory mime type has invalid data.
        let mimeData = event.mimeData;
        let factory = mimeData.getData('application/vnd.lumino.widget-factory');
        if (typeof factory !== 'function') {
            event.dropAction = 'none';
            return;
        }
        // Bail if the factory does not produce a widget.
        let widget = factory();
        if (!(widget instanceof Widget)) {
            event.dropAction = 'none';
            return;
        }
        // Bail if the widget is an ancestor of the dock panel.
        if (widget.contains(this)) {
            event.dropAction = 'none';
            return;
        }
        // Find the reference widget for the drop target.
        let ref = target ? Private$5.getDropRef(target.tabBar) : null;
        // Add the widget according to the indicated drop zone.
        switch (zone) {
            case 'root-all':
                this.addWidget(widget);
                break;
            case 'root-top':
                this.addWidget(widget, { mode: 'split-top' });
                break;
            case 'root-left':
                this.addWidget(widget, { mode: 'split-left' });
                break;
            case 'root-right':
                this.addWidget(widget, { mode: 'split-right' });
                break;
            case 'root-bottom':
                this.addWidget(widget, { mode: 'split-bottom' });
                break;
            case 'widget-all':
                this.addWidget(widget, { mode: 'tab-after', ref });
                break;
            case 'widget-top':
                this.addWidget(widget, { mode: 'split-top', ref });
                break;
            case 'widget-left':
                this.addWidget(widget, { mode: 'split-left', ref });
                break;
            case 'widget-right':
                this.addWidget(widget, { mode: 'split-right', ref });
                break;
            case 'widget-bottom':
                this.addWidget(widget, { mode: 'split-bottom', ref });
                break;
            case 'widget-tab':
                this.addWidget(widget, { mode: 'tab-after', ref });
                break;
            default:
                throw 'unreachable';
        }
        // Accept the proposed drop action.
        event.dropAction = event.proposedAction;
        // Stop propagation if we have not bailed so far.
        event.stopPropagation();
        // Activate the dropped widget.
        this.activateWidget(widget);
    }
    /**
     * Handle the `'keydown'` event for the dock panel.
     */
    _evtKeyDown(event) {
        // Stop input events during drag.
        event.preventDefault();
        event.stopPropagation();
        // Release the mouse if `Escape` is pressed.
        if (event.keyCode === 27) {
            // Finalize the mouse release.
            this._releaseMouse();
            // Schedule an emit of the layout modified signal.
            MessageLoop.postMessage(this, Private$5.LayoutModified);
        }
    }
    /**
     * Handle the `'pointerdown'` event for the dock panel.
     */
    _evtPointerDown(event) {
        // Do nothing if the left mouse button is not pressed.
        if (event.button !== 0) {
            return;
        }
        // Find the handle which contains the mouse target, if any.
        let layout = this.layout;
        let target = event.target;
        let handle = find(layout.handles(), handle => handle.contains(target));
        if (!handle) {
            return;
        }
        // Stop the event when a handle is pressed.
        event.preventDefault();
        event.stopPropagation();
        // Add the extra document listeners.
        this._document.addEventListener('keydown', this, true);
        this._document.addEventListener('pointerup', this, true);
        this._document.addEventListener('pointermove', this, true);
        this._document.addEventListener('contextmenu', this, true);
        // Compute the offset deltas for the handle press.
        let rect = handle.getBoundingClientRect();
        let deltaX = event.clientX - rect.left;
        let deltaY = event.clientY - rect.top;
        // Override the cursor and store the press data.
        let style = window.getComputedStyle(handle);
        let override = Drag.overrideCursor(style.cursor, this._document);
        this._pressData = { handle, deltaX, deltaY, override };
    }
    /**
     * Handle the `'pointermove'` event for the dock panel.
     */
    _evtPointerMove(event) {
        // Bail early if no drag is in progress.
        if (!this._pressData) {
            return;
        }
        // Stop the event when dragging a handle.
        event.preventDefault();
        event.stopPropagation();
        // Compute the desired offset position for the handle.
        let rect = this.node.getBoundingClientRect();
        let xPos = event.clientX - rect.left - this._pressData.deltaX;
        let yPos = event.clientY - rect.top - this._pressData.deltaY;
        // Set the handle as close to the desired position as possible.
        let layout = this.layout;
        layout.moveHandle(this._pressData.handle, xPos, yPos);
    }
    /**
     * Handle the `'pointerup'` event for the dock panel.
     */
    _evtPointerUp(event) {
        // Do nothing if the left mouse button is not released.
        if (event.button !== 0) {
            return;
        }
        // Stop the event when releasing a handle.
        event.preventDefault();
        event.stopPropagation();
        // Finalize the mouse release.
        this._releaseMouse();
        // Schedule an emit of the layout modified signal.
        MessageLoop.postMessage(this, Private$5.LayoutModified);
    }
    /**
     * Release the mouse grab for the dock panel.
     */
    _releaseMouse() {
        // Bail early if no drag is in progress.
        if (!this._pressData) {
            return;
        }
        // Clear the override cursor.
        this._pressData.override.dispose();
        this._pressData = null;
        // Remove the extra document listeners.
        this._document.removeEventListener('keydown', this, true);
        this._document.removeEventListener('pointerup', this, true);
        this._document.removeEventListener('pointermove', this, true);
        this._document.removeEventListener('contextmenu', this, true);
    }
    /**
     * Show the overlay indicator at the given client position.
     *
     * Returns the drop zone at the specified client position.
     *
     * #### Notes
     * If the position is not over a valid zone, the overlay is hidden.
     */
    _showOverlay(clientX, clientY) {
        // Find the dock target for the given client position.
        let { zone, target } = Private$5.findDropTarget(this, clientX, clientY, this._edges);
        // If the drop zone is invalid, hide the overlay and bail.
        if (zone === 'invalid') {
            this.overlay.hide(100);
            return zone;
        }
        // Setup the variables needed to compute the overlay geometry.
        let top;
        let left;
        let right;
        let bottom;
        let box = ElementExt.boxSizing(this.node); // TODO cache this?
        let rect = this.node.getBoundingClientRect();
        // Compute the overlay geometry based on the dock zone.
        switch (zone) {
            case 'root-all':
                top = box.paddingTop;
                left = box.paddingLeft;
                right = box.paddingRight;
                bottom = box.paddingBottom;
                break;
            case 'root-top':
                top = box.paddingTop;
                left = box.paddingLeft;
                right = box.paddingRight;
                bottom = rect.height * Private$5.GOLDEN_RATIO;
                break;
            case 'root-left':
                top = box.paddingTop;
                left = box.paddingLeft;
                right = rect.width * Private$5.GOLDEN_RATIO;
                bottom = box.paddingBottom;
                break;
            case 'root-right':
                top = box.paddingTop;
                left = rect.width * Private$5.GOLDEN_RATIO;
                right = box.paddingRight;
                bottom = box.paddingBottom;
                break;
            case 'root-bottom':
                top = rect.height * Private$5.GOLDEN_RATIO;
                left = box.paddingLeft;
                right = box.paddingRight;
                bottom = box.paddingBottom;
                break;
            case 'widget-all':
                top = target.top;
                left = target.left;
                right = target.right;
                bottom = target.bottom;
                break;
            case 'widget-top':
                top = target.top;
                left = target.left;
                right = target.right;
                bottom = target.bottom + target.height / 2;
                break;
            case 'widget-left':
                top = target.top;
                left = target.left;
                right = target.right + target.width / 2;
                bottom = target.bottom;
                break;
            case 'widget-right':
                top = target.top;
                left = target.left + target.width / 2;
                right = target.right;
                bottom = target.bottom;
                break;
            case 'widget-bottom':
                top = target.top + target.height / 2;
                left = target.left;
                right = target.right;
                bottom = target.bottom;
                break;
            case 'widget-tab': {
                const tabHeight = target.tabBar.node.getBoundingClientRect().height;
                top = target.top;
                left = target.left;
                right = target.right;
                bottom = target.bottom + target.height - tabHeight;
                break;
            }
            default:
                throw 'unreachable';
        }
        // Show the overlay with the computed geometry.
        this.overlay.show({ top, left, right, bottom });
        // Finally, return the computed drop zone.
        return zone;
    }
    /**
     * Create a new tab bar for use by the panel.
     */
    _createTabBar() {
        // Create the tab bar.
        let tabBar = this._renderer.createTabBar(this._document);
        // Set the generated tab bar property for the tab bar.
        Private$5.isGeneratedTabBarProperty.set(tabBar, true);
        // Hide the tab bar when in single document mode.
        if (this._mode === 'single-document') {
            tabBar.hide();
        }
        // Enforce necessary tab bar behavior.
        // TODO do we really want to enforce *all* of these?
        tabBar.tabsMovable = this._tabsMovable;
        tabBar.allowDeselect = false;
        tabBar.addButtonEnabled = this._addButtonEnabled;
        tabBar.removeBehavior = 'select-previous-tab';
        tabBar.insertBehavior = 'select-tab-if-needed';
        // Connect the signal handlers for the tab bar.
        tabBar.tabMoved.connect(this._onTabMoved, this);
        tabBar.currentChanged.connect(this._onCurrentChanged, this);
        tabBar.tabCloseRequested.connect(this._onTabCloseRequested, this);
        tabBar.tabDetachRequested.connect(this._onTabDetachRequested, this);
        tabBar.tabActivateRequested.connect(this._onTabActivateRequested, this);
        tabBar.addRequested.connect(this._onTabAddRequested, this);
        // Return the initialized tab bar.
        return tabBar;
    }
    /**
     * Create a new handle for use by the panel.
     */
    _createHandle() {
        return this._renderer.createHandle();
    }
    /**
     * Handle the `tabMoved` signal from a tab bar.
     */
    _onTabMoved() {
        MessageLoop.postMessage(this, Private$5.LayoutModified);
    }
    /**
     * Handle the `currentChanged` signal from a tab bar.
     */
    _onCurrentChanged(sender, args) {
        // Extract the previous and current title from the args.
        let { previousTitle, currentTitle } = args;
        // Hide the previous widget.
        if (previousTitle) {
            previousTitle.owner.hide();
        }
        // Show the current widget.
        if (currentTitle) {
            currentTitle.owner.show();
        }
        // Flush the message loop on IE and Edge to prevent flicker.
        if (Platform.IS_EDGE || Platform.IS_IE) {
            MessageLoop.flush();
        }
        // Schedule an emit of the layout modified signal.
        MessageLoop.postMessage(this, Private$5.LayoutModified);
    }
    /**
     * Handle the `addRequested` signal from a tab bar.
     */
    _onTabAddRequested(sender) {
        this._addRequested.emit(sender);
    }
    /**
     * Handle the `tabActivateRequested` signal from a tab bar.
     */
    _onTabActivateRequested(sender, args) {
        args.title.owner.activate();
    }
    /**
     * Handle the `tabCloseRequested` signal from a tab bar.
     */
    _onTabCloseRequested(sender, args) {
        args.title.owner.close();
    }
    /**
     * Handle the `tabDetachRequested` signal from a tab bar.
     */
    _onTabDetachRequested(sender, args) {
        // Do nothing if a drag is already in progress.
        if (this._drag) {
            return;
        }
        // Release the tab bar's hold on the mouse.
        sender.releaseMouse();
        // Extract the data from the args.
        let { title, tab, clientX, clientY, offset } = args;
        // Setup the mime data for the drag operation.
        let mimeData = new MimeData();
        let factory = () => title.owner;
        mimeData.setData('application/vnd.lumino.widget-factory', factory);
        // Create the drag image for the drag operation.
        let dragImage = tab.cloneNode(true);
        if (offset) {
            dragImage.style.top = `-${offset.y}px`;
            dragImage.style.left = `-${offset.x}px`;
        }
        // Create the drag object to manage the drag-drop operation.
        this._drag = new Drag({
            document: this._document,
            mimeData,
            dragImage,
            proposedAction: 'move',
            supportedActions: 'move',
            source: this
        });
        // Hide the tab node in the original tab.
        tab.classList.add('lm-mod-hidden');
        let cleanup = () => {
            this._drag = null;
            tab.classList.remove('lm-mod-hidden');
        };
        // Start the drag operation and cleanup when done.
        this._drag.start(clientX, clientY).then(cleanup);
    }
}
/**
 * The namespace for the `DockPanel` class statics.
 */
(function (DockPanel) {
    /**
     * A concrete implementation of `IOverlay`.
     *
     * This is the default overlay implementation for a dock panel.
     */
    class Overlay {
        /**
         * Construct a new overlay.
         */
        constructor() {
            this._timer = -1;
            this._hidden = true;
            this.node = document.createElement('div');
            this.node.classList.add('lm-DockPanel-overlay');
            this.node.classList.add('lm-mod-hidden');
            this.node.style.position = 'absolute';
            this.node.style.contain = 'strict';
        }
        /**
         * Show the overlay using the given overlay geometry.
         *
         * @param geo - The desired geometry for the overlay.
         */
        show(geo) {
            // Update the position of the overlay.
            let style = this.node.style;
            style.top = `${geo.top}px`;
            style.left = `${geo.left}px`;
            style.right = `${geo.right}px`;
            style.bottom = `${geo.bottom}px`;
            // Clear any pending hide timer.
            clearTimeout(this._timer);
            this._timer = -1;
            // If the overlay is already visible, we're done.
            if (!this._hidden) {
                return;
            }
            // Clear the hidden flag.
            this._hidden = false;
            // Finally, show the overlay.
            this.node.classList.remove('lm-mod-hidden');
        }
        /**
         * Hide the overlay node.
         *
         * @param delay - The delay (in ms) before hiding the overlay.
         *   A delay value <= 0 will hide the overlay immediately.
         */
        hide(delay) {
            // Do nothing if the overlay is already hidden.
            if (this._hidden) {
                return;
            }
            // Hide immediately if the delay is <= 0.
            if (delay <= 0) {
                clearTimeout(this._timer);
                this._timer = -1;
                this._hidden = true;
                this.node.classList.add('lm-mod-hidden');
                return;
            }
            // Do nothing if a hide is already pending.
            if (this._timer !== -1) {
                return;
            }
            // Otherwise setup the hide timer.
            this._timer = window.setTimeout(() => {
                this._timer = -1;
                this._hidden = true;
                this.node.classList.add('lm-mod-hidden');
            }, delay);
        }
    }
    DockPanel.Overlay = Overlay;
    /**
     * The default implementation of `IRenderer`.
     */
    class Renderer {
        /**
         * Create a new tab bar for use with a dock panel.
         *
         * @returns A new tab bar for a dock panel.
         */
        createTabBar(document) {
            let bar = new TabBar({ document });
            bar.addClass('lm-DockPanel-tabBar');
            return bar;
        }
        /**
         * Create a new handle node for use with a dock panel.
         *
         * @returns A new handle node for a dock panel.
         */
        createHandle() {
            let handle = document.createElement('div');
            handle.className = 'lm-DockPanel-handle';
            return handle;
        }
    }
    DockPanel.Renderer = Renderer;
    /**
     * The default `Renderer` instance.
     */
    DockPanel.defaultRenderer = new Renderer();
})(DockPanel || (DockPanel = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$5;
(function (Private) {
    /**
     * A fraction used for sizing root panels; ~= `1 / golden_ratio`.
     */
    Private.GOLDEN_RATIO = 0.618;
    /**
     * The default sizes for the edge drop zones, in pixels.
     */
    Private.DEFAULT_EDGES = {
        /**
         * The size of the top edge dock zone for the root panel, in pixels.
         * This is different from the others to distinguish between the top
         * tab bar and the top root zone.
         */
        top: 12,
        /**
         * The size of the edge dock zone for the root panel, in pixels.
         */
        right: 40,
        /**
         * The size of the edge dock zone for the root panel, in pixels.
         */
        bottom: 40,
        /**
         * The size of the edge dock zone for the root panel, in pixels.
         */
        left: 40
    };
    /**
     * A singleton `'layout-modified'` conflatable message.
     */
    Private.LayoutModified = new ConflatableMessage('layout-modified');
    /**
     * An attached property used to track generated tab bars.
     */
    Private.isGeneratedTabBarProperty = new AttachedProperty({
        name: 'isGeneratedTabBar',
        create: () => false
    });
    /**
     * Create a single document config for the widgets in a dock panel.
     */
    function createSingleDocumentConfig(panel) {
        // Return an empty config if the panel is empty.
        if (panel.isEmpty) {
            return { main: null };
        }
        // Get a flat array of the widgets in the panel.
        let widgets = Array.from(panel.widgets());
        // Get the first selected widget in the panel.
        let selected = panel.selectedWidgets().next().value;
        // Compute the current index for the new config.
        let currentIndex = selected ? widgets.indexOf(selected) : -1;
        // Return the single document config.
        return { main: { type: 'tab-area', widgets, currentIndex } };
    }
    Private.createSingleDocumentConfig = createSingleDocumentConfig;
    /**
     * Find the drop target at the given client position.
     */
    function findDropTarget(panel, clientX, clientY, edges) {
        // Bail if the mouse is not over the dock panel.
        if (!ElementExt.hitTest(panel.node, clientX, clientY)) {
            return { zone: 'invalid', target: null };
        }
        // Look up the layout for the panel.
        let layout = panel.layout;
        // If the layout is empty, indicate the entire root drop zone.
        if (layout.isEmpty) {
            return { zone: 'root-all', target: null };
        }
        // Test the edge zones when in multiple document mode.
        if (panel.mode === 'multiple-document') {
            // Get the client rect for the dock panel.
            let panelRect = panel.node.getBoundingClientRect();
            // Compute the distance to each edge of the panel.
            let pl = clientX - panelRect.left + 1;
            let pt = clientY - panelRect.top + 1;
            let pr = panelRect.right - clientX;
            let pb = panelRect.bottom - clientY;
            // Find the minimum distance to an edge.
            let pd = Math.min(pt, pr, pb, pl);
            // Return a root zone if the mouse is within an edge.
            switch (pd) {
                case pt:
                    if (pt < edges.top) {
                        return { zone: 'root-top', target: null };
                    }
                    break;
                case pr:
                    if (pr < edges.right) {
                        return { zone: 'root-right', target: null };
                    }
                    break;
                case pb:
                    if (pb < edges.bottom) {
                        return { zone: 'root-bottom', target: null };
                    }
                    break;
                case pl:
                    if (pl < edges.left) {
                        return { zone: 'root-left', target: null };
                    }
                    break;
                default:
                    throw 'unreachable';
            }
        }
        // Hit test the dock layout at the given client position.
        let target = layout.hitTestTabAreas(clientX, clientY);
        // Bail if no target area was found.
        if (!target) {
            return { zone: 'invalid', target: null };
        }
        // Return the whole tab area when in single document mode.
        if (panel.mode === 'single-document') {
            return { zone: 'widget-all', target };
        }
        // Compute the distance to each edge of the tab area.
        let al = target.x - target.left + 1;
        let at = target.y - target.top + 1;
        let ar = target.left + target.width - target.x;
        let ab = target.top + target.height - target.y;
        const tabHeight = target.tabBar.node.getBoundingClientRect().height;
        if (at < tabHeight) {
            return { zone: 'widget-tab', target };
        }
        // Get the X and Y edge sizes for the area.
        let rx = Math.round(target.width / 3);
        let ry = Math.round(target.height / 3);
        // If the mouse is not within an edge, indicate the entire area.
        if (al > rx && ar > rx && at > ry && ab > ry) {
            return { zone: 'widget-all', target };
        }
        // Scale the distances by the slenderness ratio.
        al /= rx;
        at /= ry;
        ar /= rx;
        ab /= ry;
        // Find the minimum distance to the area edge.
        let ad = Math.min(al, at, ar, ab);
        // Find the widget zone for the area edge.
        let zone;
        switch (ad) {
            case al:
                zone = 'widget-left';
                break;
            case at:
                zone = 'widget-top';
                break;
            case ar:
                zone = 'widget-right';
                break;
            case ab:
                zone = 'widget-bottom';
                break;
            default:
                throw 'unreachable';
        }
        // Return the final drop target.
        return { zone, target };
    }
    Private.findDropTarget = findDropTarget;
    /**
     * Get the drop reference widget for a tab bar.
     */
    function getDropRef(tabBar) {
        if (tabBar.titles.length === 0) {
            return null;
        }
        if (tabBar.currentTitle) {
            return tabBar.currentTitle.owner;
        }
        return tabBar.titles[tabBar.titles.length - 1].owner;
    }
    Private.getDropRef = getDropRef;
})(Private$5 || (Private$5 = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A class which tracks focus among a set of widgets.
 *
 * This class is useful when code needs to keep track of the most
 * recently focused widget(s) among a set of related widgets.
 */
class FocusTracker {
    constructor() {
        this._counter = 0;
        this._widgets = [];
        this._activeWidget = null;
        this._currentWidget = null;
        this._numbers = new Map();
        this._nodes = new Map();
        this._activeChanged = new Signal(this);
        this._currentChanged = new Signal(this);
    }
    /**
     * Dispose of the resources held by the tracker.
     */
    dispose() {
        // Do nothing if the tracker is already disposed.
        if (this._counter < 0) {
            return;
        }
        // Mark the tracker as disposed.
        this._counter = -1;
        // Clear the connections for the tracker.
        Signal.clearData(this);
        // Remove all event listeners.
        for (const widget of this._widgets) {
            widget.node.removeEventListener('focus', this, true);
            widget.node.removeEventListener('blur', this, true);
        }
        // Clear the internal data structures.
        this._activeWidget = null;
        this._currentWidget = null;
        this._nodes.clear();
        this._numbers.clear();
        this._widgets.length = 0;
    }
    /**
     * A signal emitted when the current widget has changed.
     */
    get currentChanged() {
        return this._currentChanged;
    }
    /**
     * A signal emitted when the active widget has changed.
     */
    get activeChanged() {
        return this._activeChanged;
    }
    /**
     * A flag indicating whether the tracker is disposed.
     */
    get isDisposed() {
        return this._counter < 0;
    }
    /**
     * The current widget in the tracker.
     *
     * #### Notes
     * The current widget is the widget among the tracked widgets which
     * has the *descendant node* which has most recently been focused.
     *
     * The current widget will not be updated if the node loses focus. It
     * will only be updated when a different tracked widget gains focus.
     *
     * If the current widget is removed from the tracker, the previous
     * current widget will be restored.
     *
     * This behavior is intended to follow a user's conceptual model of
     * a semantically "current" widget, where the "last thing of type X"
     * to be interacted with is the "current instance of X", regardless
     * of whether that instance still has focus.
     */
    get currentWidget() {
        return this._currentWidget;
    }
    /**
     * The active widget in the tracker.
     *
     * #### Notes
     * The active widget is the widget among the tracked widgets which
     * has the *descendant node* which is currently focused.
     */
    get activeWidget() {
        return this._activeWidget;
    }
    /**
     * A read only array of the widgets being tracked.
     */
    get widgets() {
        return this._widgets;
    }
    /**
     * Get the focus number for a particular widget in the tracker.
     *
     * @param widget - The widget of interest.
     *
     * @returns The focus number for the given widget, or `-1` if the
     *   widget has not had focus since being added to the tracker, or
     *   is not contained by the tracker.
     *
     * #### Notes
     * The focus number indicates the relative order in which the widgets
     * have gained focus. A widget with a larger number has gained focus
     * more recently than a widget with a smaller number.
     *
     * The `currentWidget` will always have the largest focus number.
     *
     * All widgets start with a focus number of `-1`, which indicates that
     * the widget has not been focused since being added to the tracker.
     */
    focusNumber(widget) {
        let n = this._numbers.get(widget);
        return n === undefined ? -1 : n;
    }
    /**
     * Test whether the focus tracker contains a given widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns `true` if the widget is tracked, `false` otherwise.
     */
    has(widget) {
        return this._numbers.has(widget);
    }
    /**
     * Add a widget to the focus tracker.
     *
     * @param widget - The widget of interest.
     *
     * #### Notes
     * A widget will be automatically removed from the tracker if it
     * is disposed after being added.
     *
     * If the widget is already tracked, this is a no-op.
     */
    add(widget) {
        // Do nothing if the widget is already tracked.
        if (this._numbers.has(widget)) {
            return;
        }
        // Test whether the widget has focus.
        let focused = widget.node.contains(document.activeElement);
        // Set up the initial focus number.
        let n = focused ? this._counter++ : -1;
        // Add the widget to the internal data structures.
        this._widgets.push(widget);
        this._numbers.set(widget, n);
        this._nodes.set(widget.node, widget);
        // Set up the event listeners. The capturing phase must be used
        // since the 'focus' and 'blur' events don't bubble and Firefox
        // doesn't support the 'focusin' or 'focusout' events.
        widget.node.addEventListener('focus', this, true);
        widget.node.addEventListener('blur', this, true);
        // Connect the disposed signal handler.
        widget.disposed.connect(this._onWidgetDisposed, this);
        // Set the current and active widgets if needed.
        if (focused) {
            this._setWidgets(widget, widget);
        }
    }
    /**
     * Remove a widget from the focus tracker.
     *
     * #### Notes
     * If the widget is the `currentWidget`, the previous current widget
     * will become the new `currentWidget`.
     *
     * A widget will be automatically removed from the tracker if it
     * is disposed after being added.
     *
     * If the widget is not tracked, this is a no-op.
     */
    remove(widget) {
        // Bail early if the widget is not tracked.
        if (!this._numbers.has(widget)) {
            return;
        }
        // Disconnect the disposed signal handler.
        widget.disposed.disconnect(this._onWidgetDisposed, this);
        // Remove the event listeners.
        widget.node.removeEventListener('focus', this, true);
        widget.node.removeEventListener('blur', this, true);
        // Remove the widget from the internal data structures.
        ArrayExt.removeFirstOf(this._widgets, widget);
        this._nodes.delete(widget.node);
        this._numbers.delete(widget);
        // Bail early if the widget is not the current widget.
        if (this._currentWidget !== widget) {
            return;
        }
        // Filter the widgets for those which have had focus.
        let valid = this._widgets.filter(w => this._numbers.get(w) !== -1);
        // Get the valid widget with the max focus number.
        let previous = max(valid, (first, second) => {
            let a = this._numbers.get(first);
            let b = this._numbers.get(second);
            return a - b;
        }) || null;
        // Set the current and active widgets.
        this._setWidgets(previous, null);
    }
    /**
     * Handle the DOM events for the focus tracker.
     *
     * @param event - The DOM event sent to the panel.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the tracked nodes. It should
     * not be called directly by user code.
     */
    handleEvent(event) {
        switch (event.type) {
            case 'focus':
                this._evtFocus(event);
                break;
            case 'blur':
                this._evtBlur(event);
                break;
        }
    }
    /**
     * Set the current and active widgets for the tracker.
     */
    _setWidgets(current, active) {
        // Swap the current widget.
        let oldCurrent = this._currentWidget;
        this._currentWidget = current;
        // Swap the active widget.
        let oldActive = this._activeWidget;
        this._activeWidget = active;
        // Emit the `currentChanged` signal if needed.
        if (oldCurrent !== current) {
            this._currentChanged.emit({ oldValue: oldCurrent, newValue: current });
        }
        // Emit the `activeChanged` signal if needed.
        if (oldActive !== active) {
            this._activeChanged.emit({ oldValue: oldActive, newValue: active });
        }
    }
    /**
     * Handle the `'focus'` event for a tracked widget.
     */
    _evtFocus(event) {
        // Find the widget which gained focus, which is known to exist.
        let widget = this._nodes.get(event.currentTarget);
        // Update the focus number if necessary.
        if (widget !== this._currentWidget) {
            this._numbers.set(widget, this._counter++);
        }
        // Set the current and active widgets.
        this._setWidgets(widget, widget);
    }
    /**
     * Handle the `'blur'` event for a tracked widget.
     */
    _evtBlur(event) {
        // Find the widget which lost focus, which is known to exist.
        let widget = this._nodes.get(event.currentTarget);
        // Get the node which being focused after this blur.
        let focusTarget = event.relatedTarget;
        // If no other node is being focused, clear the active widget.
        if (!focusTarget) {
            this._setWidgets(this._currentWidget, null);
            return;
        }
        // Bail if the focus widget is not changing.
        if (widget.node.contains(focusTarget)) {
            return;
        }
        // If no tracked widget is being focused, clear the active widget.
        if (!find(this._widgets, w => w.node.contains(focusTarget))) {
            this._setWidgets(this._currentWidget, null);
            return;
        }
    }
    /**
     * Handle the `disposed` signal for a tracked widget.
     */
    _onWidgetDisposed(sender) {
        this.remove(sender);
    }
}

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A layout which arranges its widgets in a grid.
 */
class GridLayout extends Layout {
    /**
     * Construct a new grid layout.
     *
     * @param options - The options for initializing the layout.
     */
    constructor(options = {}) {
        super(options);
        this._dirty = false;
        this._rowSpacing = 4;
        this._columnSpacing = 4;
        this._items = [];
        this._rowStarts = [];
        this._columnStarts = [];
        this._rowSizers = [new BoxSizer()];
        this._columnSizers = [new BoxSizer()];
        this._box = null;
        if (options.rowCount !== undefined) {
            Private$4.reallocSizers(this._rowSizers, options.rowCount);
        }
        if (options.columnCount !== undefined) {
            Private$4.reallocSizers(this._columnSizers, options.columnCount);
        }
        if (options.rowSpacing !== undefined) {
            this._rowSpacing = Private$4.clampValue(options.rowSpacing);
        }
        if (options.columnSpacing !== undefined) {
            this._columnSpacing = Private$4.clampValue(options.columnSpacing);
        }
    }
    /**
     * Dispose of the resources held by the layout.
     */
    dispose() {
        // Dispose of the widgets and layout items.
        for (const item of this._items) {
            let widget = item.widget;
            item.dispose();
            widget.dispose();
        }
        // Clear the layout state.
        this._box = null;
        this._items.length = 0;
        this._rowStarts.length = 0;
        this._rowSizers.length = 0;
        this._columnStarts.length = 0;
        this._columnSizers.length = 0;
        // Dispose of the rest of the layout.
        super.dispose();
    }
    /**
     * Get the number of rows in the layout.
     */
    get rowCount() {
        return this._rowSizers.length;
    }
    /**
     * Set the number of rows in the layout.
     *
     * #### Notes
     * The minimum row count is `1`.
     */
    set rowCount(value) {
        // Do nothing if the row count does not change.
        if (value === this.rowCount) {
            return;
        }
        // Reallocate the row sizers.
        Private$4.reallocSizers(this._rowSizers, value);
        // Schedule a fit of the parent.
        if (this.parent) {
            this.parent.fit();
        }
    }
    /**
     * Get the number of columns in the layout.
     */
    get columnCount() {
        return this._columnSizers.length;
    }
    /**
     * Set the number of columns in the layout.
     *
     * #### Notes
     * The minimum column count is `1`.
     */
    set columnCount(value) {
        // Do nothing if the column count does not change.
        if (value === this.columnCount) {
            return;
        }
        // Reallocate the column sizers.
        Private$4.reallocSizers(this._columnSizers, value);
        // Schedule a fit of the parent.
        if (this.parent) {
            this.parent.fit();
        }
    }
    /**
     * Get the row spacing for the layout.
     */
    get rowSpacing() {
        return this._rowSpacing;
    }
    /**
     * Set the row spacing for the layout.
     */
    set rowSpacing(value) {
        // Clamp the spacing to the allowed range.
        value = Private$4.clampValue(value);
        // Bail if the spacing does not change
        if (this._rowSpacing === value) {
            return;
        }
        // Update the internal spacing.
        this._rowSpacing = value;
        // Schedule a fit of the parent.
        if (this.parent) {
            this.parent.fit();
        }
    }
    /**
     * Get the column spacing for the layout.
     */
    get columnSpacing() {
        return this._columnSpacing;
    }
    /**
     * Set the col spacing for the layout.
     */
    set columnSpacing(value) {
        // Clamp the spacing to the allowed range.
        value = Private$4.clampValue(value);
        // Bail if the spacing does not change
        if (this._columnSpacing === value) {
            return;
        }
        // Update the internal spacing.
        this._columnSpacing = value;
        // Schedule a fit of the parent.
        if (this.parent) {
            this.parent.fit();
        }
    }
    /**
     * Get the stretch factor for a specific row.
     *
     * @param index - The row index of interest.
     *
     * @returns The stretch factor for the row.
     *
     * #### Notes
     * This returns `-1` if the index is out of range.
     */
    rowStretch(index) {
        let sizer = this._rowSizers[index];
        return sizer ? sizer.stretch : -1;
    }
    /**
     * Set the stretch factor for a specific row.
     *
     * @param index - The row index of interest.
     *
     * @param value - The stretch factor for the row.
     *
     * #### Notes
     * This is a no-op if the index is out of range.
     */
    setRowStretch(index, value) {
        // Look up the row sizer.
        let sizer = this._rowSizers[index];
        // Bail if the index is out of range.
        if (!sizer) {
            return;
        }
        // Clamp the value to the allowed range.
        value = Private$4.clampValue(value);
        // Bail if the stretch does not change.
        if (sizer.stretch === value) {
            return;
        }
        // Update the sizer stretch.
        sizer.stretch = value;
        // Schedule an update of the parent.
        if (this.parent) {
            this.parent.update();
        }
    }
    /**
     * Get the stretch factor for a specific column.
     *
     * @param index - The column index of interest.
     *
     * @returns The stretch factor for the column.
     *
     * #### Notes
     * This returns `-1` if the index is out of range.
     */
    columnStretch(index) {
        let sizer = this._columnSizers[index];
        return sizer ? sizer.stretch : -1;
    }
    /**
     * Set the stretch factor for a specific column.
     *
     * @param index - The column index of interest.
     *
     * @param value - The stretch factor for the column.
     *
     * #### Notes
     * This is a no-op if the index is out of range.
     */
    setColumnStretch(index, value) {
        // Look up the column sizer.
        let sizer = this._columnSizers[index];
        // Bail if the index is out of range.
        if (!sizer) {
            return;
        }
        // Clamp the value to the allowed range.
        value = Private$4.clampValue(value);
        // Bail if the stretch does not change.
        if (sizer.stretch === value) {
            return;
        }
        // Update the sizer stretch.
        sizer.stretch = value;
        // Schedule an update of the parent.
        if (this.parent) {
            this.parent.update();
        }
    }
    /**
     * Create an iterator over the widgets in the layout.
     *
     * @returns A new iterator over the widgets in the layout.
     */
    *[Symbol.iterator]() {
        for (const item of this._items) {
            yield item.widget;
        }
    }
    /**
     * Add a widget to the grid layout.
     *
     * @param widget - The widget to add to the layout.
     *
     * #### Notes
     * If the widget is already contained in the layout, this is no-op.
     */
    addWidget(widget) {
        // Look up the index for the widget.
        let i = ArrayExt.findFirstIndex(this._items, it => it.widget === widget);
        // Bail if the widget is already in the layout.
        if (i !== -1) {
            return;
        }
        // Add the widget to the layout.
        this._items.push(new LayoutItem(widget));
        // Attach the widget to the parent.
        if (this.parent) {
            this.attachWidget(widget);
        }
    }
    /**
     * Remove a widget from the grid layout.
     *
     * @param widget - The widget to remove from the layout.
     *
     * #### Notes
     * A widget is automatically removed from the layout when its `parent`
     * is set to `null`. This method should only be invoked directly when
     * removing a widget from a layout which has yet to be installed on a
     * parent widget.
     *
     * This method does *not* modify the widget's `parent`.
     */
    removeWidget(widget) {
        // Look up the index for the widget.
        let i = ArrayExt.findFirstIndex(this._items, it => it.widget === widget);
        // Bail if the widget is not in the layout.
        if (i === -1) {
            return;
        }
        // Remove the widget from the layout.
        let item = ArrayExt.removeAt(this._items, i);
        // Detach the widget from the parent.
        if (this.parent) {
            this.detachWidget(widget);
        }
        // Dispose the layout item.
        item.dispose();
    }
    /**
     * Perform layout initialization which requires the parent widget.
     */
    init() {
        super.init();
        for (const widget of this) {
            this.attachWidget(widget);
        }
    }
    /**
     * Attach a widget to the parent's DOM node.
     *
     * @param widget - The widget to attach to the parent.
     */
    attachWidget(widget) {
        // Send a `'before-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        }
        // Add the widget's node to the parent.
        this.parent.node.appendChild(widget.node);
        // Send an `'after-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
        }
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * Detach a widget from the parent's DOM node.
     *
     * @param widget - The widget to detach from the parent.
     */
    detachWidget(widget) {
        // Send a `'before-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        }
        // Remove the widget's node from the parent.
        this.parent.node.removeChild(widget.node);
        // Send an `'after-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
        }
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'before-show'` message.
     */
    onBeforeShow(msg) {
        super.onBeforeShow(msg);
        this.parent.update();
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        super.onBeforeAttach(msg);
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-shown'` message.
     */
    onChildShown(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-hidden'` message.
     */
    onChildHidden(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'resize'` message.
     */
    onResize(msg) {
        if (this.parent.isVisible) {
            this._update(msg.width, msg.height);
        }
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        if (this.parent.isVisible) {
            this._update(-1, -1);
        }
    }
    /**
     * A message handler invoked on a `'fit-request'` message.
     */
    onFitRequest(msg) {
        if (this.parent.isAttached) {
            this._fit();
        }
    }
    /**
     * Fit the layout to the total size required by the widgets.
     */
    _fit() {
        // Reset the min sizes of the sizers.
        for (let i = 0, n = this.rowCount; i < n; ++i) {
            this._rowSizers[i].minSize = 0;
        }
        for (let i = 0, n = this.columnCount; i < n; ++i) {
            this._columnSizers[i].minSize = 0;
        }
        // Filter for the visible layout items.
        let items = this._items.filter(it => !it.isHidden);
        // Fit the layout items.
        for (let i = 0, n = items.length; i < n; ++i) {
            items[i].fit();
        }
        // Get the max row and column index.
        let maxRow = this.rowCount - 1;
        let maxCol = this.columnCount - 1;
        // Sort the items by row span.
        items.sort(Private$4.rowSpanCmp);
        // Update the min sizes of the row sizers.
        for (let i = 0, n = items.length; i < n; ++i) {
            // Fetch the item.
            let item = items[i];
            // Get the row bounds for the item.
            let config = GridLayout.getCellConfig(item.widget);
            let r1 = Math.min(config.row, maxRow);
            let r2 = Math.min(config.row + config.rowSpan - 1, maxRow);
            // Distribute the minimum height to the sizers as needed.
            Private$4.distributeMin(this._rowSizers, r1, r2, item.minHeight);
        }
        // Sort the items by column span.
        items.sort(Private$4.columnSpanCmp);
        // Update the min sizes of the column sizers.
        for (let i = 0, n = items.length; i < n; ++i) {
            // Fetch the item.
            let item = items[i];
            // Get the column bounds for the item.
            let config = GridLayout.getCellConfig(item.widget);
            let c1 = Math.min(config.column, maxCol);
            let c2 = Math.min(config.column + config.columnSpan - 1, maxCol);
            // Distribute the minimum width to the sizers as needed.
            Private$4.distributeMin(this._columnSizers, c1, c2, item.minWidth);
        }
        // If no size constraint is needed, just update the parent.
        if (this.fitPolicy === 'set-no-constraint') {
            MessageLoop.sendMessage(this.parent, Widget.Msg.UpdateRequest);
            return;
        }
        // Set up the computed min size.
        let minH = maxRow * this._rowSpacing;
        let minW = maxCol * this._columnSpacing;
        // Add the sizer minimums to the computed min size.
        for (let i = 0, n = this.rowCount; i < n; ++i) {
            minH += this._rowSizers[i].minSize;
        }
        for (let i = 0, n = this.columnCount; i < n; ++i) {
            minW += this._columnSizers[i].minSize;
        }
        // Update the box sizing and add it to the computed min size.
        let box = (this._box = ElementExt.boxSizing(this.parent.node));
        minW += box.horizontalSum;
        minH += box.verticalSum;
        // Update the parent's min size constraints.
        let style = this.parent.node.style;
        style.minWidth = `${minW}px`;
        style.minHeight = `${minH}px`;
        // Set the dirty flag to ensure only a single update occurs.
        this._dirty = true;
        // Notify the ancestor that it should fit immediately. This may
        // cause a resize of the parent, fulfilling the required update.
        if (this.parent.parent) {
            MessageLoop.sendMessage(this.parent.parent, Widget.Msg.FitRequest);
        }
        // If the dirty flag is still set, the parent was not resized.
        // Trigger the required update on the parent widget immediately.
        if (this._dirty) {
            MessageLoop.sendMessage(this.parent, Widget.Msg.UpdateRequest);
        }
    }
    /**
     * Update the layout position and size of the widgets.
     *
     * The parent offset dimensions should be `-1` if unknown.
     */
    _update(offsetWidth, offsetHeight) {
        // Clear the dirty flag to indicate the update occurred.
        this._dirty = false;
        // Measure the parent if the offset dimensions are unknown.
        if (offsetWidth < 0) {
            offsetWidth = this.parent.node.offsetWidth;
        }
        if (offsetHeight < 0) {
            offsetHeight = this.parent.node.offsetHeight;
        }
        // Ensure the parent box sizing data is computed.
        if (!this._box) {
            this._box = ElementExt.boxSizing(this.parent.node);
        }
        // Compute the layout area adjusted for border and padding.
        let top = this._box.paddingTop;
        let left = this._box.paddingLeft;
        let width = offsetWidth - this._box.horizontalSum;
        let height = offsetHeight - this._box.verticalSum;
        // Get the max row and column index.
        let maxRow = this.rowCount - 1;
        let maxCol = this.columnCount - 1;
        // Compute the total fixed row and column space.
        let fixedRowSpace = maxRow * this._rowSpacing;
        let fixedColSpace = maxCol * this._columnSpacing;
        // Distribute the available space to the box sizers.
        BoxEngine.calc(this._rowSizers, Math.max(0, height - fixedRowSpace));
        BoxEngine.calc(this._columnSizers, Math.max(0, width - fixedColSpace));
        // Update the row start positions.
        for (let i = 0, pos = top, n = this.rowCount; i < n; ++i) {
            this._rowStarts[i] = pos;
            pos += this._rowSizers[i].size + this._rowSpacing;
        }
        // Update the column start positions.
        for (let i = 0, pos = left, n = this.columnCount; i < n; ++i) {
            this._columnStarts[i] = pos;
            pos += this._columnSizers[i].size + this._columnSpacing;
        }
        // Update the geometry of the layout items.
        for (let i = 0, n = this._items.length; i < n; ++i) {
            // Fetch the item.
            let item = this._items[i];
            // Ignore hidden items.
            if (item.isHidden) {
                continue;
            }
            // Fetch the cell bounds for the widget.
            let config = GridLayout.getCellConfig(item.widget);
            let r1 = Math.min(config.row, maxRow);
            let c1 = Math.min(config.column, maxCol);
            let r2 = Math.min(config.row + config.rowSpan - 1, maxRow);
            let c2 = Math.min(config.column + config.columnSpan - 1, maxCol);
            // Compute the cell geometry.
            let x = this._columnStarts[c1];
            let y = this._rowStarts[r1];
            let w = this._columnStarts[c2] + this._columnSizers[c2].size - x;
            let h = this._rowStarts[r2] + this._rowSizers[r2].size - y;
            // Update the geometry of the layout item.
            item.update(x, y, w, h);
        }
    }
}
/**
 * The namespace for the `GridLayout` class statics.
 */
(function (GridLayout) {
    /**
     * Get the cell config for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @returns The cell config for the widget.
     */
    function getCellConfig(widget) {
        return Private$4.cellConfigProperty.get(widget);
    }
    GridLayout.getCellConfig = getCellConfig;
    /**
     * Set the cell config for the given widget.
     *
     * @param widget - The widget of interest.
     *
     * @param value - The value for the cell config.
     */
    function setCellConfig(widget, value) {
        Private$4.cellConfigProperty.set(widget, Private$4.normalizeConfig(value));
    }
    GridLayout.setCellConfig = setCellConfig;
})(GridLayout || (GridLayout = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$4;
(function (Private) {
    /**
     * The property descriptor for the widget cell config.
     */
    Private.cellConfigProperty = new AttachedProperty({
        name: 'cellConfig',
        create: () => ({ row: 0, column: 0, rowSpan: 1, columnSpan: 1 }),
        changed: onChildCellConfigChanged
    });
    /**
     * Normalize a partial cell config object.
     */
    function normalizeConfig(config) {
        let row = Math.max(0, Math.floor(config.row || 0));
        let column = Math.max(0, Math.floor(config.column || 0));
        let rowSpan = Math.max(1, Math.floor(config.rowSpan || 0));
        let columnSpan = Math.max(1, Math.floor(config.columnSpan || 0));
        return { row, column, rowSpan, columnSpan };
    }
    Private.normalizeConfig = normalizeConfig;
    /**
     * Clamp a value to an integer >= 0.
     */
    function clampValue(value) {
        return Math.max(0, Math.floor(value));
    }
    Private.clampValue = clampValue;
    /**
     * A sort comparison function for row spans.
     */
    function rowSpanCmp(a, b) {
        let c1 = Private.cellConfigProperty.get(a.widget);
        let c2 = Private.cellConfigProperty.get(b.widget);
        return c1.rowSpan - c2.rowSpan;
    }
    Private.rowSpanCmp = rowSpanCmp;
    /**
     * A sort comparison function for column spans.
     */
    function columnSpanCmp(a, b) {
        let c1 = Private.cellConfigProperty.get(a.widget);
        let c2 = Private.cellConfigProperty.get(b.widget);
        return c1.columnSpan - c2.columnSpan;
    }
    Private.columnSpanCmp = columnSpanCmp;
    /**
     * Reallocate the box sizers for the given grid dimensions.
     */
    function reallocSizers(sizers, count) {
        // Coerce the count to the valid range.
        count = Math.max(1, Math.floor(count));
        // Add the missing sizers.
        while (sizers.length < count) {
            sizers.push(new BoxSizer());
        }
        // Remove the extra sizers.
        if (sizers.length > count) {
            sizers.length = count;
        }
    }
    Private.reallocSizers = reallocSizers;
    /**
     * Distribute a min size constraint across a range of sizers.
     */
    function distributeMin(sizers, i1, i2, minSize) {
        // Sanity check the indices.
        if (i2 < i1) {
            return;
        }
        // Handle the simple case of no cell span.
        if (i1 === i2) {
            let sizer = sizers[i1];
            sizer.minSize = Math.max(sizer.minSize, minSize);
            return;
        }
        // Compute the total current min size of the span.
        let totalMin = 0;
        for (let i = i1; i <= i2; ++i) {
            totalMin += sizers[i].minSize;
        }
        // Do nothing if the total is greater than the required.
        if (totalMin >= minSize) {
            return;
        }
        // Compute the portion of the space to allocate to each sizer.
        let portion = (minSize - totalMin) / (i2 - i1 + 1);
        // Add the portion to each sizer.
        for (let i = i1; i <= i2; ++i) {
            sizers[i].minSize += portion;
        }
    }
    Private.distributeMin = distributeMin;
    /**
     * The change handler for the child cell config property.
     */
    function onChildCellConfigChanged(child) {
        if (child.parent && child.parent.layout instanceof GridLayout) {
            child.parent.fit();
        }
    }
})(Private$4 || (Private$4 = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A widget which displays menus as a canonical menu bar.
 *
 * #### Notes
 * See also the related [example](../../examples/menubar/index.html) and
 * its [source](https://github.com/jupyterlab/lumino/tree/main/examples/example-menubar).
 */
class MenuBar extends Widget {
    /**
     * Construct a new menu bar.
     *
     * @param options - The options for initializing the menu bar.
     */
    constructor(options = {}) {
        super({ node: Private$3.createNode() });
        // Track the index of the item that is currently focused or hovered. -1 means nothing focused or hovered.
        this._activeIndex = -1;
        // Track which item can be focused using the TAB key. Unlike _activeIndex will
        // always point to a menuitem. Whenever you update this value, it's important
        // to follow it with an "update-request" message so that the `tabindex`
        // attribute on each menubar item gets properly updated.
        this._tabFocusIndex = 0;
        this._menus = [];
        this._childMenu = null;
        this._overflowMenu = null;
        this._menuItemSizes = [];
        this._overflowIndex = -1;
        this.addClass('lm-MenuBar');
        this.setFlag(Widget.Flag.DisallowLayout);
        this.renderer = options.renderer || MenuBar.defaultRenderer;
        this._forceItemsPosition = options.forceItemsPosition || {
            forceX: true,
            forceY: true
        };
        this._overflowMenuOptions = options.overflowMenuOptions || {
            isVisible: true
        };
    }
    /**
     * Dispose of the resources held by the widget.
     */
    dispose() {
        this._closeChildMenu();
        this._menus.length = 0;
        super.dispose();
    }
    /**
     * The child menu of the menu bar.
     *
     * #### Notes
     * This will be `null` if the menu bar does not have an open menu.
     */
    get childMenu() {
        return this._childMenu;
    }
    /**
     * The overflow index of the menu bar.
     */
    get overflowIndex() {
        return this._overflowIndex;
    }
    /**
     * The overflow menu of the menu bar.
     */
    get overflowMenu() {
        return this._overflowMenu;
    }
    /**
     * Get the menu bar content node.
     *
     * #### Notes
     * This is the node which holds the menu title nodes.
     *
     * Modifying this node directly can lead to undefined behavior.
     */
    get contentNode() {
        return this.node.getElementsByClassName('lm-MenuBar-content')[0];
    }
    /**
     * Get the currently active menu.
     */
    get activeMenu() {
        return this._menus[this._activeIndex] || null;
    }
    /**
     * Set the currently active menu.
     *
     * #### Notes
     * If the menu does not exist, the menu will be set to `null`.
     */
    set activeMenu(value) {
        this.activeIndex = value ? this._menus.indexOf(value) : -1;
    }
    /**
     * Get the index of the currently active menu.
     *
     * #### Notes
     * This will be `-1` if no menu is active.
     */
    get activeIndex() {
        return this._activeIndex;
    }
    /**
     * Set the index of the currently active menu.
     *
     * #### Notes
     * If the menu cannot be activated, the index will be set to `-1`.
     */
    set activeIndex(value) {
        // Adjust the value for an out of range index.
        if (value < 0 || value >= this._menus.length) {
            value = -1;
        }
        // An empty menu cannot be active
        if (value > -1 && this._menus[value].items.length === 0) {
            value = -1;
        }
        // Bail early if the index will not change.
        if (this._activeIndex === value) {
            return;
        }
        // Update the active index.
        this._activeIndex = value;
        // Schedule an update of the items.
        this.update();
    }
    /**
     * A read-only array of the menus in the menu bar.
     */
    get menus() {
        return this._menus;
    }
    /**
     * Open the active menu and activate its first menu item.
     *
     * #### Notes
     * If there is no active menu, this is a no-op.
     */
    openActiveMenu() {
        // Bail early if there is no active item.
        if (this._activeIndex === -1) {
            return;
        }
        // Open the child menu.
        this._openChildMenu();
        // Activate the first item in the child menu.
        if (this._childMenu) {
            this._childMenu.activeIndex = -1;
            this._childMenu.activateNextItem();
        }
    }
    /**
     * Add a menu to the end of the menu bar.
     *
     * @param menu - The menu to add to the menu bar.
     *
     * #### Notes
     * If the menu is already added to the menu bar, it will be moved.
     */
    addMenu(menu, update = true) {
        this.insertMenu(this._menus.length, menu, update);
    }
    /**
     * Insert a menu into the menu bar at the specified index.
     *
     * @param index - The index at which to insert the menu.
     *
     * @param menu - The menu to insert into the menu bar.
     *
     * #### Notes
     * The index will be clamped to the bounds of the menus.
     *
     * If the menu is already added to the menu bar, it will be moved.
     */
    insertMenu(index, menu, update = true) {
        // Close the child menu before making changes.
        this._closeChildMenu();
        // Look up the index of the menu.
        let i = this._menus.indexOf(menu);
        // Clamp the insert index to the array bounds.
        let j = Math.max(0, Math.min(index, this._menus.length));
        // If the menu is not in the array, insert it.
        if (i === -1) {
            // Insert the menu into the array.
            ArrayExt.insert(this._menus, j, menu);
            // Add the styling class to the menu.
            menu.addClass('lm-MenuBar-menu');
            // Connect to the menu signals.
            menu.aboutToClose.connect(this._onMenuAboutToClose, this);
            menu.menuRequested.connect(this._onMenuMenuRequested, this);
            menu.title.changed.connect(this._onTitleChanged, this);
            // Schedule an update of the items.
            if (update) {
                this.update();
            }
            // There is nothing more to do.
            return;
        }
        // Otherwise, the menu exists in the array and should be moved.
        // Adjust the index if the location is at the end of the array.
        if (j === this._menus.length) {
            j--;
        }
        // Bail if there is no effective move.
        if (i === j) {
            return;
        }
        // Move the menu to the new locations.
        ArrayExt.move(this._menus, i, j);
        // Schedule an update of the items.
        if (update) {
            this.update();
        }
    }
    /**
     * Remove a menu from the menu bar.
     *
     * @param menu - The menu to remove from the menu bar.
     *
     * #### Notes
     * This is a no-op if the menu is not in the menu bar.
     */
    removeMenu(menu, update = true) {
        this.removeMenuAt(this._menus.indexOf(menu), update);
    }
    /**
     * Remove the menu at a given index from the menu bar.
     *
     * @param index - The index of the menu to remove.
     *
     * #### Notes
     * This is a no-op if the index is out of range.
     */
    removeMenuAt(index, update = true) {
        // Close the child menu before making changes.
        this._closeChildMenu();
        // Remove the menu from the array.
        let menu = ArrayExt.removeAt(this._menus, index);
        // Bail if the index is out of range.
        if (!menu) {
            return;
        }
        // Disconnect from the menu signals.
        menu.aboutToClose.disconnect(this._onMenuAboutToClose, this);
        menu.menuRequested.disconnect(this._onMenuMenuRequested, this);
        menu.title.changed.disconnect(this._onTitleChanged, this);
        // Remove the styling class from the menu.
        menu.removeClass('lm-MenuBar-menu');
        // Schedule an update of the items.
        if (update) {
            this.update();
        }
    }
    /**
     * Remove all menus from the menu bar.
     */
    clearMenus() {
        // Bail if there is nothing to remove.
        if (this._menus.length === 0) {
            return;
        }
        // Close the child menu before making changes.
        this._closeChildMenu();
        // Disconnect from the menu signals and remove the styling class.
        for (let menu of this._menus) {
            menu.aboutToClose.disconnect(this._onMenuAboutToClose, this);
            menu.menuRequested.disconnect(this._onMenuMenuRequested, this);
            menu.title.changed.disconnect(this._onTitleChanged, this);
            menu.removeClass('lm-MenuBar-menu');
        }
        // Clear the menus array.
        this._menus.length = 0;
        // Schedule an update of the items.
        this.update();
    }
    /**
     * Handle the DOM events for the menu bar.
     *
     * @param event - The DOM event sent to the menu bar.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the menu bar's DOM nodes. It
     * should not be called directly by user code.
     */
    handleEvent(event) {
        switch (event.type) {
            case 'keydown':
                this._evtKeyDown(event);
                break;
            case 'pointerdown':
                this._evtPointerDown(event);
                break;
            case 'pointermove':
            case 'pointerleave':
                this._evtPointerMove(event);
                break;
            case 'focusout':
                this._evtFocusOut(event);
                break;
            case 'contextmenu':
                event.preventDefault();
                event.stopPropagation();
                break;
        }
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        this.node.addEventListener('keydown', this);
        this.node.addEventListener('pointerdown', this);
        this.node.addEventListener('pointermove', this);
        this.node.addEventListener('pointerleave', this);
        this.node.addEventListener('focusout', this);
        this.node.addEventListener('contextmenu', this);
    }
    /**
     * A message handler invoked on an `'after-detach'` message.
     */
    onAfterDetach(msg) {
        this.node.removeEventListener('keydown', this);
        this.node.removeEventListener('pointerdown', this);
        this.node.removeEventListener('pointermove', this);
        this.node.removeEventListener('pointerleave', this);
        this.node.removeEventListener('focusout', this);
        this.node.removeEventListener('contextmenu', this);
        this._closeChildMenu();
    }
    /**
     * A message handler invoked on an `'activate-request'` message.
     */
    onActivateRequest(msg) {
        if (this.isAttached) {
            this._focusItemAt(0);
        }
    }
    /**
     * A message handler invoked on a `'resize'` message.
     */
    onResize(msg) {
        this.update();
        super.onResize(msg);
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        var _a;
        let menus = this._menus;
        let renderer = this.renderer;
        let activeIndex = this._activeIndex;
        let tabFocusIndex = this._tabFocusIndex >= 0 && this._tabFocusIndex < menus.length
            ? this._tabFocusIndex
            : 0;
        let length = this._overflowIndex > -1 ? this._overflowIndex : menus.length;
        let totalMenuSize = 0;
        let isVisible = false;
        // Check that the overflow menu doesn't count
        length = this._overflowMenu !== null ? length - 1 : length;
        let content = new Array(length);
        // Render visible menus
        for (let i = 0; i < length; ++i) {
            content[i] = renderer.renderItem({
                title: menus[i].title,
                active: i === activeIndex,
                tabbable: i === tabFocusIndex,
                disabled: menus[i].items.length === 0,
                onfocus: () => {
                    this._tabFocusIndex = i;
                    this.activeIndex = i;
                }
            });
            // Calculate size of current menu
            totalMenuSize += this._menuItemSizes[i];
            // Check if overflow menu is already rendered
            if (menus[i].title.label === this._overflowMenuOptions.title) {
                isVisible = true;
                length--;
            }
        }
        // Render overflow menu if needed and active
        if (this._overflowMenuOptions.isVisible) {
            if (this._overflowIndex > -1 && !isVisible) {
                // Create overflow menu
                if (this._overflowMenu === null) {
                    const overflowMenuTitle = (_a = this._overflowMenuOptions.title) !== null && _a !== void 0 ? _a : '...';
                    this._overflowMenu = new Menu({ commands: new CommandRegistry() });
                    this._overflowMenu.title.label = overflowMenuTitle;
                    this._overflowMenu.title.mnemonic = 0;
                    this.addMenu(this._overflowMenu, false);
                }
                // Move menus to overflow menu
                for (let i = menus.length - 2; i >= length; i--) {
                    const submenu = this.menus[i];
                    submenu.title.mnemonic = 0;
                    this._overflowMenu.insertItem(0, {
                        type: 'submenu',
                        submenu: submenu
                    });
                    this.removeMenu(submenu, false);
                }
                content[length] = renderer.renderItem({
                    title: this._overflowMenu.title,
                    active: length === activeIndex && menus[length].items.length !== 0,
                    tabbable: length === tabFocusIndex,
                    disabled: menus[length].items.length === 0,
                    onfocus: () => {
                        this._tabFocusIndex = length;
                        this.activeIndex = length;
                    }
                });
                length++;
            }
            else if (this._overflowMenu !== null) {
                // Remove submenus from overflow menu
                let overflowMenuItems = this._overflowMenu.items;
                let screenSize = this.node.offsetWidth;
                let n = this._overflowMenu.items.length;
                for (let i = 0; i < n; ++i) {
                    let index = menus.length - 1 - i;
                    if (screenSize - totalMenuSize > this._menuItemSizes[index]) {
                        let menu = overflowMenuItems[0].submenu;
                        this._overflowMenu.removeItemAt(0);
                        this.insertMenu(length, menu, false);
                        content[length] = renderer.renderItem({
                            title: menu.title,
                            active: false,
                            tabbable: length === tabFocusIndex,
                            disabled: menus[length].items.length === 0,
                            onfocus: () => {
                                this._tabFocusIndex = length;
                                this.activeIndex = length;
                            }
                        });
                        length++;
                    }
                }
                if (this._overflowMenu.items.length === 0) {
                    this.removeMenu(this._overflowMenu, false);
                    content.pop();
                    this._overflowMenu = null;
                    this._overflowIndex = -1;
                }
            }
        }
        VirtualDOM.render(content, this.contentNode);
        this._updateOverflowIndex();
    }
    /**
     * Calculate and update the current overflow index.
     */
    _updateOverflowIndex() {
        if (!this._overflowMenuOptions.isVisible) {
            return;
        }
        // Get elements visible in the main menu bar
        const itemMenus = this.contentNode.childNodes;
        let screenSize = this.node.offsetWidth;
        let totalMenuSize = 0;
        let index = -1;
        let n = itemMenus.length;
        if (this._menuItemSizes.length == 0) {
            // Check if it is the first resize and get info about menu items sizes
            for (let i = 0; i < n; i++) {
                let item = itemMenus[i];
                // Add sizes to array
                totalMenuSize += item.offsetWidth;
                this._menuItemSizes.push(item.offsetWidth);
                if (totalMenuSize > screenSize && index === -1) {
                    index = i;
                }
            }
        }
        else {
            // Calculate current menu size
            for (let i = 0; i < this._menuItemSizes.length; i++) {
                totalMenuSize += this._menuItemSizes[i];
                if (totalMenuSize > screenSize) {
                    index = i;
                    break;
                }
            }
        }
        this._overflowIndex = index;
    }
    /**
     * Handle the `'keydown'` event for the menu bar.
     *
     * #### Notes
     * All keys are trapped except the tab key that is ignored.
     */
    _evtKeyDown(event) {
        // Fetch the key code for the event.
        let kc = event.keyCode;
        // Reset the active index on tab, but do not trap the tab key.
        if (kc === 9) {
            this.activeIndex = -1;
            return;
        }
        // A menu bar handles all other keydown events.
        event.preventDefault();
        event.stopPropagation();
        // Enter, Space, Up Arrow, Down Arrow
        if (kc === 13 || kc === 32 || kc === 38 || kc === 40) {
            // The active index may have changed (for example, user hovers over an
            // item with the mouse), so be sure to use the focus index.
            this.activeIndex = this._tabFocusIndex;
            if (this.activeIndex !== this._tabFocusIndex) {
                // Bail if the setter refused to set activeIndex to tabFocusIndex
                // because it means that the item at tabFocusIndex cannot be opened (for
                // example, it has an empty menu)
                return;
            }
            this.openActiveMenu();
            return;
        }
        // Escape
        if (kc === 27) {
            this._closeChildMenu();
            this._focusItemAt(this.activeIndex);
            return;
        }
        // Left or Right Arrow
        if (kc === 37 || kc === 39) {
            let direction = kc === 37 ? -1 : 1;
            let start = this._tabFocusIndex + direction;
            let n = this._menus.length;
            for (let i = 0; i < n; i++) {
                let index = (n + start + direction * i) % n;
                if (this._menus[index].items.length) {
                    this._focusItemAt(index);
                    return;
                }
            }
            return;
        }
        // Get the pressed key character.
        let key = getKeyboardLayout().keyForKeydownEvent(event);
        // Bail if the key is not valid.
        if (!key) {
            return;
        }
        // Search for the next best matching mnemonic item.
        let start = this._activeIndex + 1;
        let result = Private$3.findMnemonic(this._menus, key, start);
        // Handle the requested mnemonic based on the search results.
        // If exactly one mnemonic is matched, that menu is opened.
        // Otherwise, the next mnemonic is activated if available,
        // followed by the auto mnemonic if available.
        if (result.index !== -1 && !result.multiple) {
            this.activeIndex = result.index;
            this.openActiveMenu();
        }
        else if (result.index !== -1) {
            this.activeIndex = result.index;
            this._focusItemAt(this.activeIndex);
        }
        else if (result.auto !== -1) {
            this.activeIndex = result.auto;
            this._focusItemAt(this.activeIndex);
        }
    }
    /**
     * Handle the `'pointerdown'` event for the menu bar.
     */
    _evtPointerDown(event) {
        // Bail if the mouse press was not on the menu bar. This can occur
        // when the document listener is installed for an active menu bar.
        if (!ElementExt.hitTest(this.node, event.clientX, event.clientY)) {
            return;
        }
        // Stop the propagation of the event. Immediate propagation is
        // also stopped so that an open menu does not handle the event.
        event.stopPropagation();
        event.stopImmediatePropagation();
        // Check if the mouse is over one of the menu items.
        let index = ArrayExt.findFirstIndex(this.contentNode.children, node => {
            return ElementExt.hitTest(node, event.clientX, event.clientY);
        });
        // If the press was not on an item, close the child menu.
        if (index === -1) {
            this._closeChildMenu();
            return;
        }
        // If the press was not the left mouse button, do nothing further.
        if (event.button !== 0) {
            return;
        }
        // Otherwise, toggle the open state of the child menu.
        if (this._childMenu) {
            this._closeChildMenu();
            this.activeIndex = index;
        }
        else {
            // If we don't call preventDefault() here, then the item in the menu
            // bar will take focus over the menu that is being opened.
            event.preventDefault();
            const position = this._positionForMenu(index);
            Menu.saveWindowData();
            // Begin DOM modifications.
            this.activeIndex = index;
            this._openChildMenu(position);
        }
    }
    /**
     * Handle the `'pointermove'` event for the menu bar.
     */
    _evtPointerMove(event) {
        if (Utils$1.isTouchEvent(event)) {
            return;
        }
        // Check if the mouse is over one of the menu items.
        let index = ArrayExt.findFirstIndex(this.contentNode.children, node => {
            return ElementExt.hitTest(node, event.clientX, event.clientY);
        });
        // Bail early if the active index will not change.
        if (index === this._activeIndex) {
            return;
        }
        // Bail early if a child menu is open and the mouse is not over
        // an item. This allows the child menu to be kept open when the
        // mouse is over the empty part of the menu bar.
        if (index === -1 && this._childMenu) {
            return;
        }
        // Get position for the new menu >before< updating active index.
        const position = index >= 0 && this._childMenu ? this._positionForMenu(index) : null;
        // Before any modification, update window data.
        Menu.saveWindowData();
        // Begin DOM modifications.
        // Update the active index to the hovered item.
        this.activeIndex = index;
        // Open the new menu if a menu is already open.
        if (position) {
            this._openChildMenu(position);
        }
    }
    /**
     * Find initial position for the menu based on menubar item position.
     *
     * NOTE: this should be called before updating active index to avoid
     * an additional layout and style invalidation as changing active
     * index modifies DOM.
     */
    _positionForMenu(index) {
        let itemNode = this.contentNode.children[index];
        let { left, bottom } = itemNode.getBoundingClientRect();
        return {
            top: bottom,
            left
        };
    }
    /**
     * Handle the `'focusout'` event for the menu bar.
     */
    _evtFocusOut(event) {
        // Reset the active index if there is no open menu and the menubar is losing focus.
        if (!this._childMenu && !this.node.contains(event.relatedTarget)) {
            this.activeIndex = -1;
        }
    }
    /**
     * Focus an item in the menu bar.
     *
     * #### Notes
     * Does not open the associated menu.
     */
    _focusItemAt(index) {
        const itemNode = this.contentNode.childNodes[index];
        if (itemNode) {
            itemNode.focus();
        }
    }
    /**
     * Open the child menu at the active index immediately.
     *
     * If a different child menu is already open, it will be closed,
     * even if there is no active menu.
     */
    _openChildMenu(options = {}) {
        // If there is no active menu, close the current menu.
        let newMenu = this.activeMenu;
        if (!newMenu) {
            this._closeChildMenu();
            return;
        }
        // Bail if there is no effective menu change.
        let oldMenu = this._childMenu;
        if (oldMenu === newMenu) {
            return;
        }
        // Swap the internal menu reference.
        this._childMenu = newMenu;
        // Close the current menu, or setup for the new menu.
        if (oldMenu) {
            oldMenu.close();
        }
        else {
            document.addEventListener('pointerdown', this, true);
        }
        // Update the tab focus index and ensure the menu bar is updated.
        this._tabFocusIndex = this.activeIndex;
        MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest);
        // Get the positioning data for the new menu.
        let { left, top } = options;
        if (typeof left === 'undefined' || typeof top === 'undefined') {
            ({ left, top } = this._positionForMenu(this._activeIndex));
        }
        // Begin DOM modifications
        if (!oldMenu) {
            // Continue setup for new menu
            this.addClass('lm-mod-active');
        }
        // Open the new menu at the computed location.
        if (newMenu.items.length > 0) {
            newMenu.open(left, top, this._forceItemsPosition);
        }
    }
    /**
     * Close the child menu immediately.
     *
     * This is a no-op if a child menu is not open.
     */
    _closeChildMenu() {
        // Bail if no child menu is open.
        if (!this._childMenu) {
            return;
        }
        // Remove the active class from the menu bar.
        this.removeClass('lm-mod-active');
        // Remove the document listeners.
        document.removeEventListener('pointerdown', this, true);
        // Clear the internal menu reference.
        let menu = this._childMenu;
        this._childMenu = null;
        // Close the menu.
        menu.close();
        // Reset the active index.
        this.activeIndex = -1;
    }
    /**
     * Handle the `aboutToClose` signal of a menu.
     */
    _onMenuAboutToClose(sender) {
        // Bail if the sender is not the child menu.
        if (sender !== this._childMenu) {
            return;
        }
        // Remove the active class from the menu bar.
        this.removeClass('lm-mod-active');
        // Remove the document listeners.
        document.removeEventListener('pointerdown', this, true);
        // Clear the internal menu reference.
        this._childMenu = null;
        // Reset the active index.
        this.activeIndex = -1;
    }
    /**
     * Handle the `menuRequested` signal of a child menu.
     */
    _onMenuMenuRequested(sender, args) {
        // Bail if the sender is not the child menu.
        if (sender !== this._childMenu) {
            return;
        }
        // Look up the active index and menu count.
        let i = this._activeIndex;
        let n = this._menus.length;
        // Active the next requested index.
        switch (args) {
            case 'next':
                this.activeIndex = i === n - 1 ? 0 : i + 1;
                break;
            case 'previous':
                this.activeIndex = i === 0 ? n - 1 : i - 1;
                break;
        }
        // Open the active menu.
        this.openActiveMenu();
    }
    /**
     * Handle the `changed` signal of a title object.
     */
    _onTitleChanged() {
        this.update();
    }
}
/**
 * The namespace for the `MenuBar` class statics.
 */
(function (MenuBar) {
    /**
     * The default implementation of `IRenderer`.
     *
     * #### Notes
     * Subclasses are free to reimplement rendering methods as needed.
     */
    class Renderer {
        /**
         * Render the virtual element for a menu bar item.
         *
         * @param data - The data to use for rendering the item.
         *
         * @returns A virtual element representing the item.
         */
        renderItem(data) {
            let className = this.createItemClass(data);
            let dataset = this.createItemDataset(data);
            let aria = this.createItemARIA(data);
            return h.li({
                className,
                dataset,
                ...(data.disabled ? {} : { tabindex: data.tabbable ? '0' : '-1' }),
                onfocus: data.onfocus,
                ...aria
            }, this.renderIcon(data), this.renderLabel(data));
        }
        /**
         * Render the icon element for a menu bar item.
         *
         * @param data - The data to use for rendering the icon.
         *
         * @returns A virtual element representing the item icon.
         */
        renderIcon(data) {
            let className = this.createIconClass(data);
            // If data.title.icon is undefined, it will be ignored.
            return h.div({ className }, data.title.icon, data.title.iconLabel);
        }
        /**
         * Render the label element for a menu item.
         *
         * @param data - The data to use for rendering the label.
         *
         * @returns A virtual element representing the item label.
         */
        renderLabel(data) {
            let content = this.formatLabel(data);
            return h.div({ className: 'lm-MenuBar-itemLabel' }, content);
        }
        /**
         * Create the class name for the menu bar item.
         *
         * @param data - The data to use for the class name.
         *
         * @returns The full class name for the menu item.
         */
        createItemClass(data) {
            let name = 'lm-MenuBar-item';
            if (data.title.className) {
                name += ` ${data.title.className}`;
            }
            if (data.active && !data.disabled) {
                name += ' lm-mod-active';
            }
            return name;
        }
        /**
         * Create the dataset for a menu bar item.
         *
         * @param data - The data to use for the item.
         *
         * @returns The dataset for the menu bar item.
         */
        createItemDataset(data) {
            return data.title.dataset;
        }
        /**
         * Create the aria attributes for menu bar item.
         *
         * @param data - The data to use for the aria attributes.
         *
         * @returns The aria attributes object for the item.
         */
        createItemARIA(data) {
            return {
                role: 'menuitem',
                'aria-haspopup': 'true',
                'aria-disabled': data.disabled ? 'true' : 'false'
            };
        }
        /**
         * Create the class name for the menu bar item icon.
         *
         * @param data - The data to use for the class name.
         *
         * @returns The full class name for the item icon.
         */
        createIconClass(data) {
            let name = 'lm-MenuBar-itemIcon';
            let extra = data.title.iconClass;
            return extra ? `${name} ${extra}` : name;
        }
        /**
         * Create the render content for the label node.
         *
         * @param data - The data to use for the label content.
         *
         * @returns The content to add to the label node.
         */
        formatLabel(data) {
            // Fetch the label text and mnemonic index.
            let { label, mnemonic } = data.title;
            // If the index is out of range, do not modify the label.
            if (mnemonic < 0 || mnemonic >= label.length) {
                return label;
            }
            // Split the label into parts.
            let prefix = label.slice(0, mnemonic);
            let suffix = label.slice(mnemonic + 1);
            let char = label[mnemonic];
            // Wrap the mnemonic character in a span.
            let span = h.span({ className: 'lm-MenuBar-itemMnemonic' }, char);
            // Return the content parts.
            return [prefix, span, suffix];
        }
    }
    MenuBar.Renderer = Renderer;
    /**
     * The default `Renderer` instance.
     */
    MenuBar.defaultRenderer = new Renderer();
})(MenuBar || (MenuBar = {}));
/**
 * The namespace for the module implementation details.
 */
var Private$3;
(function (Private) {
    /**
     * Create the DOM node for a menu bar.
     */
    function createNode() {
        let node = document.createElement('div');
        let content = document.createElement('ul');
        content.className = 'lm-MenuBar-content';
        node.appendChild(content);
        content.setAttribute('role', 'menubar');
        return node;
    }
    Private.createNode = createNode;
    /**
     * Find the best matching mnemonic item.
     *
     * The search starts at the given index and wraps around.
     */
    function findMnemonic(menus, key, start) {
        // Setup the result variables.
        let index = -1;
        let auto = -1;
        let multiple = false;
        // Normalize the key to upper case.
        let upperKey = key.toUpperCase();
        // Search the items from the given start index.
        for (let i = 0, n = menus.length; i < n; ++i) {
            // Compute the wrapped index.
            let k = (i + start) % n;
            // Look up the menu title.
            let title = menus[k].title;
            // Ignore titles with an empty label.
            if (title.label.length === 0) {
                continue;
            }
            // Look up the mnemonic index for the label.
            let mn = title.mnemonic;
            // Handle a valid mnemonic index.
            if (mn >= 0 && mn < title.label.length) {
                if (title.label[mn].toUpperCase() === upperKey) {
                    if (index === -1) {
                        index = k;
                    }
                    else {
                        multiple = true;
                    }
                }
                continue;
            }
            // Finally, handle the auto index if possible.
            if (auto === -1 && title.label[0].toUpperCase() === upperKey) {
                auto = k;
            }
        }
        // Return the search results.
        return { index, multiple, auto };
    }
    Private.findMnemonic = findMnemonic;
})(Private$3 || (Private$3 = {}));

/**
 * A widget which implements a canonical scroll bar.
 */
class ScrollBar extends Widget {
    /**
     * Construct a new scroll bar.
     *
     * @param options - The options for initializing the scroll bar.
     */
    constructor(options = {}) {
        super({ node: Private$2.createNode() });
        /**
         * A timeout callback for repeating the mouse press.
         */
        this._onRepeat = () => {
            // Clear the repeat timer id.
            this._repeatTimer = -1;
            // Bail if the mouse has been released.
            if (!this._pressData) {
                return;
            }
            // Look up the part that was pressed.
            let part = this._pressData.part;
            // Bail if the thumb was pressed.
            if (part === 'thumb') {
                return;
            }
            // Schedule the timer for another repeat.
            this._repeatTimer = window.setTimeout(this._onRepeat, 20);
            // Get the current mouse position.
            let mouseX = this._pressData.mouseX;
            let mouseY = this._pressData.mouseY;
            // Handle a decrement button repeat.
            if (part === 'decrement') {
                // Bail if the mouse is not over the button.
                if (!ElementExt.hitTest(this.decrementNode, mouseX, mouseY)) {
                    return;
                }
                // Emit the step requested signal.
                this._stepRequested.emit('decrement');
                // Finished.
                return;
            }
            // Handle an increment button repeat.
            if (part === 'increment') {
                // Bail if the mouse is not over the button.
                if (!ElementExt.hitTest(this.incrementNode, mouseX, mouseY)) {
                    return;
                }
                // Emit the step requested signal.
                this._stepRequested.emit('increment');
                // Finished.
                return;
            }
            // Handle a track repeat.
            if (part === 'track') {
                // Bail if the mouse is not over the track.
                if (!ElementExt.hitTest(this.trackNode, mouseX, mouseY)) {
                    return;
                }
                // Fetch the thumb node.
                let thumbNode = this.thumbNode;
                // Bail if the mouse is over the thumb.
                if (ElementExt.hitTest(thumbNode, mouseX, mouseY)) {
                    return;
                }
                // Fetch the client rect for the thumb.
                let thumbRect = thumbNode.getBoundingClientRect();
                // Determine the direction for the page request.
                let dir;
                if (this._orientation === 'horizontal') {
                    dir = mouseX < thumbRect.left ? 'decrement' : 'increment';
                }
                else {
                    dir = mouseY < thumbRect.top ? 'decrement' : 'increment';
                }
                // Emit the page requested signal.
                this._pageRequested.emit(dir);
                // Finished.
                return;
            }
        };
        this._value = 0;
        this._page = 10;
        this._maximum = 100;
        this._repeatTimer = -1;
        this._pressData = null;
        this._thumbMoved = new Signal(this);
        this._stepRequested = new Signal(this);
        this._pageRequested = new Signal(this);
        this.addClass('lm-ScrollBar');
        this.setFlag(Widget.Flag.DisallowLayout);
        // Set the orientation.
        this._orientation = options.orientation || 'vertical';
        this.dataset['orientation'] = this._orientation;
        // Parse the rest of the options.
        if (options.maximum !== undefined) {
            this._maximum = Math.max(0, options.maximum);
        }
        if (options.page !== undefined) {
            this._page = Math.max(0, options.page);
        }
        if (options.value !== undefined) {
            this._value = Math.max(0, Math.min(options.value, this._maximum));
        }
    }
    /**
     * A signal emitted when the user moves the scroll thumb.
     *
     * #### Notes
     * The payload is the current value of the scroll bar.
     */
    get thumbMoved() {
        return this._thumbMoved;
    }
    /**
     * A signal emitted when the user clicks a step button.
     *
     * #### Notes
     * The payload is whether a decrease or increase is requested.
     */
    get stepRequested() {
        return this._stepRequested;
    }
    /**
     * A signal emitted when the user clicks the scroll track.
     *
     * #### Notes
     * The payload is whether a decrease or increase is requested.
     */
    get pageRequested() {
        return this._pageRequested;
    }
    /**
     * Get the orientation of the scroll bar.
     */
    get orientation() {
        return this._orientation;
    }
    /**
     * Set the orientation of the scroll bar.
     */
    set orientation(value) {
        // Do nothing if the orientation does not change.
        if (this._orientation === value) {
            return;
        }
        // Release the mouse before making changes.
        this._releaseMouse();
        // Update the internal orientation.
        this._orientation = value;
        this.dataset['orientation'] = value;
        // Schedule an update the scroll bar.
        this.update();
    }
    /**
     * Get the current value of the scroll bar.
     */
    get value() {
        return this._value;
    }
    /**
     * Set the current value of the scroll bar.
     *
     * #### Notes
     * The value will be clamped to the range `[0, maximum]`.
     */
    set value(value) {
        // Clamp the value to the allowable range.
        value = Math.max(0, Math.min(value, this._maximum));
        // Do nothing if the value does not change.
        if (this._value === value) {
            return;
        }
        // Update the internal value.
        this._value = value;
        // Schedule an update the scroll bar.
        this.update();
    }
    /**
     * Get the page size of the scroll bar.
     *
     * #### Notes
     * The page size is the amount of visible content in the scrolled
     * region, expressed in data units. It determines the size of the
     * scroll bar thumb.
     */
    get page() {
        return this._page;
    }
    /**
     * Set the page size of the scroll bar.
     *
     * #### Notes
     * The page size will be clamped to the range `[0, Infinity]`.
     */
    set page(value) {
        // Clamp the page size to the allowable range.
        value = Math.max(0, value);
        // Do nothing if the value does not change.
        if (this._page === value) {
            return;
        }
        // Update the internal page size.
        this._page = value;
        // Schedule an update the scroll bar.
        this.update();
    }
    /**
     * Get the maximum value of the scroll bar.
     */
    get maximum() {
        return this._maximum;
    }
    /**
     * Set the maximum value of the scroll bar.
     *
     * #### Notes
     * The max size will be clamped to the range `[0, Infinity]`.
     */
    set maximum(value) {
        // Clamp the value to the allowable range.
        value = Math.max(0, value);
        // Do nothing if the value does not change.
        if (this._maximum === value) {
            return;
        }
        // Update the internal values.
        this._maximum = value;
        // Clamp the current value to the new range.
        this._value = Math.min(this._value, value);
        // Schedule an update the scroll bar.
        this.update();
    }
    /**
     * The scroll bar decrement button node.
     *
     * #### Notes
     * Modifying this node directly can lead to undefined behavior.
     */
    get decrementNode() {
        return this.node.getElementsByClassName('lm-ScrollBar-button')[0];
    }
    /**
     * The scroll bar increment button node.
     *
     * #### Notes
     * Modifying this node directly can lead to undefined behavior.
     */
    get incrementNode() {
        return this.node.getElementsByClassName('lm-ScrollBar-button')[1];
    }
    /**
     * The scroll bar track node.
     *
     * #### Notes
     * Modifying this node directly can lead to undefined behavior.
     */
    get trackNode() {
        return this.node.getElementsByClassName('lm-ScrollBar-track')[0];
    }
    /**
     * The scroll bar thumb node.
     *
     * #### Notes
     * Modifying this node directly can lead to undefined behavior.
     */
    get thumbNode() {
        return this.node.getElementsByClassName('lm-ScrollBar-thumb')[0];
    }
    /**
     * Handle the DOM events for the scroll bar.
     *
     * @param event - The DOM event sent to the scroll bar.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the scroll bar's DOM node.
     *
     * This should not be called directly by user code.
     */
    handleEvent(event) {
        switch (event.type) {
            case 'mousedown':
                this._evtMouseDown(event);
                break;
            case 'mousemove':
                this._evtMouseMove(event);
                break;
            case 'mouseup':
                this._evtMouseUp(event);
                break;
            case 'keydown':
                this._evtKeyDown(event);
                break;
            case 'contextmenu':
                event.preventDefault();
                event.stopPropagation();
                break;
        }
    }
    /**
     * A method invoked on a 'before-attach' message.
     */
    onBeforeAttach(msg) {
        this.node.addEventListener('mousedown', this);
        this.update();
    }
    /**
     * A method invoked on an 'after-detach' message.
     */
    onAfterDetach(msg) {
        this.node.removeEventListener('mousedown', this);
        this._releaseMouse();
    }
    /**
     * A method invoked on an 'update-request' message.
     */
    onUpdateRequest(msg) {
        // Convert the value and page into percentages.
        let value = (this._value * 100) / this._maximum;
        let page = (this._page * 100) / (this._page + this._maximum);
        // Clamp the value and page to the relevant range.
        value = Math.max(0, Math.min(value, 100));
        page = Math.max(0, Math.min(page, 100));
        // Fetch the thumb style.
        let thumbStyle = this.thumbNode.style;
        // Update the thumb style for the current orientation.
        if (this._orientation === 'horizontal') {
            thumbStyle.top = '';
            thumbStyle.height = '';
            thumbStyle.left = `${value}%`;
            thumbStyle.width = `${page}%`;
            thumbStyle.transform = `translate(${-value}%, 0%)`;
        }
        else {
            thumbStyle.left = '';
            thumbStyle.width = '';
            thumbStyle.top = `${value}%`;
            thumbStyle.height = `${page}%`;
            thumbStyle.transform = `translate(0%, ${-value}%)`;
        }
    }
    /**
     * Handle the `'keydown'` event for the scroll bar.
     */
    _evtKeyDown(event) {
        // Stop all input events during drag.
        event.preventDefault();
        event.stopPropagation();
        // Ignore anything except the `Escape` key.
        if (event.keyCode !== 27) {
            return;
        }
        // Fetch the previous scroll value.
        let value = this._pressData ? this._pressData.value : -1;
        // Release the mouse.
        this._releaseMouse();
        // Restore the old scroll value if possible.
        if (value !== -1) {
            this._moveThumb(value);
        }
    }
    /**
     * Handle the `'mousedown'` event for the scroll bar.
     */
    _evtMouseDown(event) {
        // Do nothing if it's not a left mouse press.
        if (event.button !== 0) {
            return;
        }
        // Send an activate request to the scroll bar. This can be
        // used by message hooks to activate something relevant.
        this.activate();
        // Do nothing if the mouse is already captured.
        if (this._pressData) {
            return;
        }
        // Find the pressed scroll bar part.
        let part = Private$2.findPart(this, event.target);
        // Do nothing if the part is not of interest.
        if (!part) {
            return;
        }
        // Stop the event propagation.
        event.preventDefault();
        event.stopPropagation();
        // Override the mouse cursor.
        let override = Drag.overrideCursor('default');
        // Set up the press data.
        this._pressData = {
            part,
            override,
            delta: -1,
            value: -1,
            mouseX: event.clientX,
            mouseY: event.clientY
        };
        // Add the extra event listeners.
        document.addEventListener('mousemove', this, true);
        document.addEventListener('mouseup', this, true);
        document.addEventListener('keydown', this, true);
        document.addEventListener('contextmenu', this, true);
        // Handle a thumb press.
        if (part === 'thumb') {
            // Fetch the thumb node.
            let thumbNode = this.thumbNode;
            // Fetch the client rect for the thumb.
            let thumbRect = thumbNode.getBoundingClientRect();
            // Update the press data delta for the current orientation.
            if (this._orientation === 'horizontal') {
                this._pressData.delta = event.clientX - thumbRect.left;
            }
            else {
                this._pressData.delta = event.clientY - thumbRect.top;
            }
            // Add the active class to the thumb node.
            thumbNode.classList.add('lm-mod-active');
            // Store the current value in the press data.
            this._pressData.value = this._value;
            // Finished.
            return;
        }
        // Handle a track press.
        if (part === 'track') {
            // Fetch the client rect for the thumb.
            let thumbRect = this.thumbNode.getBoundingClientRect();
            // Determine the direction for the page request.
            let dir;
            if (this._orientation === 'horizontal') {
                dir = event.clientX < thumbRect.left ? 'decrement' : 'increment';
            }
            else {
                dir = event.clientY < thumbRect.top ? 'decrement' : 'increment';
            }
            // Start the repeat timer.
            this._repeatTimer = window.setTimeout(this._onRepeat, 350);
            // Emit the page requested signal.
            this._pageRequested.emit(dir);
            // Finished.
            return;
        }
        // Handle a decrement button press.
        if (part === 'decrement') {
            // Add the active class to the decrement node.
            this.decrementNode.classList.add('lm-mod-active');
            // Start the repeat timer.
            this._repeatTimer = window.setTimeout(this._onRepeat, 350);
            // Emit the step requested signal.
            this._stepRequested.emit('decrement');
            // Finished.
            return;
        }
        // Handle an increment button press.
        if (part === 'increment') {
            // Add the active class to the increment node.
            this.incrementNode.classList.add('lm-mod-active');
            // Start the repeat timer.
            this._repeatTimer = window.setTimeout(this._onRepeat, 350);
            // Emit the step requested signal.
            this._stepRequested.emit('increment');
            // Finished.
            return;
        }
    }
    /**
     * Handle the `'mousemove'` event for the scroll bar.
     */
    _evtMouseMove(event) {
        // Do nothing if no drag is in progress.
        if (!this._pressData) {
            return;
        }
        // Stop the event propagation.
        event.preventDefault();
        event.stopPropagation();
        // Update the mouse position.
        this._pressData.mouseX = event.clientX;
        this._pressData.mouseY = event.clientY;
        // Bail if the thumb is not being dragged.
        if (this._pressData.part !== 'thumb') {
            return;
        }
        // Get the client rect for the thumb and track.
        let thumbRect = this.thumbNode.getBoundingClientRect();
        let trackRect = this.trackNode.getBoundingClientRect();
        // Fetch the scroll geometry based on the orientation.
        let trackPos;
        let trackSpan;
        if (this._orientation === 'horizontal') {
            trackPos = event.clientX - trackRect.left - this._pressData.delta;
            trackSpan = trackRect.width - thumbRect.width;
        }
        else {
            trackPos = event.clientY - trackRect.top - this._pressData.delta;
            trackSpan = trackRect.height - thumbRect.height;
        }
        // Compute the desired value from the scroll geometry.
        let value = trackSpan === 0 ? 0 : (trackPos * this._maximum) / trackSpan;
        // Move the thumb to the computed value.
        this._moveThumb(value);
    }
    /**
     * Handle the `'mouseup'` event for the scroll bar.
     */
    _evtMouseUp(event) {
        // Do nothing if it's not a left mouse release.
        if (event.button !== 0) {
            return;
        }
        // Stop the event propagation.
        event.preventDefault();
        event.stopPropagation();
        // Release the mouse.
        this._releaseMouse();
    }
    /**
     * Release the mouse and restore the node states.
     */
    _releaseMouse() {
        // Bail if there is no press data.
        if (!this._pressData) {
            return;
        }
        // Clear the repeat timer.
        clearTimeout(this._repeatTimer);
        this._repeatTimer = -1;
        // Clear the press data.
        this._pressData.override.dispose();
        this._pressData = null;
        // Remove the extra event listeners.
        document.removeEventListener('mousemove', this, true);
        document.removeEventListener('mouseup', this, true);
        document.removeEventListener('keydown', this, true);
        document.removeEventListener('contextmenu', this, true);
        // Remove the active classes from the nodes.
        this.thumbNode.classList.remove('lm-mod-active');
        this.decrementNode.classList.remove('lm-mod-active');
        this.incrementNode.classList.remove('lm-mod-active');
    }
    /**
     * Move the thumb to the specified position.
     */
    _moveThumb(value) {
        // Clamp the value to the allowed range.
        value = Math.max(0, Math.min(value, this._maximum));
        // Bail if the value does not change.
        if (this._value === value) {
            return;
        }
        // Update the internal value.
        this._value = value;
        // Schedule an update of the scroll bar.
        this.update();
        // Emit the thumb moved signal.
        this._thumbMoved.emit(value);
    }
}
/**
 * The namespace for the module implementation details.
 */
var Private$2;
(function (Private) {
    /**
     * Create the DOM node for a scroll bar.
     */
    function createNode() {
        let node = document.createElement('div');
        let decrement = document.createElement('div');
        let increment = document.createElement('div');
        let track = document.createElement('div');
        let thumb = document.createElement('div');
        decrement.className = 'lm-ScrollBar-button';
        increment.className = 'lm-ScrollBar-button';
        decrement.dataset['action'] = 'decrement';
        increment.dataset['action'] = 'increment';
        track.className = 'lm-ScrollBar-track';
        thumb.className = 'lm-ScrollBar-thumb';
        track.appendChild(thumb);
        node.appendChild(decrement);
        node.appendChild(track);
        node.appendChild(increment);
        return node;
    }
    Private.createNode = createNode;
    /**
     * Find the scroll bar part which contains the given target.
     */
    function findPart(scrollBar, target) {
        // Test the thumb.
        if (scrollBar.thumbNode.contains(target)) {
            return 'thumb';
        }
        // Test the track.
        if (scrollBar.trackNode.contains(target)) {
            return 'track';
        }
        // Test the decrement button.
        if (scrollBar.decrementNode.contains(target)) {
            return 'decrement';
        }
        // Test the increment button.
        if (scrollBar.incrementNode.contains(target)) {
            return 'increment';
        }
        // Indicate no match.
        return null;
    }
    Private.findPart = findPart;
})(Private$2 || (Private$2 = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A concrete layout implementation which holds a single widget.
 *
 * #### Notes
 * This class is useful for creating simple container widgets which
 * hold a single child. The child should be positioned with CSS.
 */
class SingletonLayout extends Layout {
    constructor() {
        super(...arguments);
        this._widget = null;
    }
    /**
     * Dispose of the resources held by the layout.
     */
    dispose() {
        if (this._widget) {
            let widget = this._widget;
            this._widget = null;
            widget.dispose();
        }
        super.dispose();
    }
    /**
     * Get the child widget for the layout.
     */
    get widget() {
        return this._widget;
    }
    /**
     * Set the child widget for the layout.
     *
     * #### Notes
     * Setting the child widget will cause the old child widget to be
     * automatically disposed. If that is not desired, set the parent
     * of the old child to `null` before assigning a new child.
     */
    set widget(widget) {
        // Remove the widget from its current parent. This is a no-op
        // if the widget's parent is already the layout parent widget.
        if (widget) {
            widget.parent = this.parent;
        }
        // Bail early if the widget does not change.
        if (this._widget === widget) {
            return;
        }
        // Dispose of the old child widget.
        if (this._widget) {
            this._widget.dispose();
        }
        // Update the internal widget.
        this._widget = widget;
        // Attach the new child widget if needed.
        if (this.parent && widget) {
            this.attachWidget(widget);
        }
    }
    /**
     * Create an iterator over the widgets in the layout.
     *
     * @returns A new iterator over the widgets in the layout.
     */
    *[Symbol.iterator]() {
        if (this._widget) {
            yield this._widget;
        }
    }
    /**
     * Remove a widget from the layout.
     *
     * @param widget - The widget to remove from the layout.
     *
     * #### Notes
     * A widget is automatically removed from the layout when its `parent`
     * is set to `null`. This method should only be invoked directly when
     * removing a widget from a layout which has yet to be installed on a
     * parent widget.
     *
     * This method does *not* modify the widget's `parent`.
     */
    removeWidget(widget) {
        // Bail early if the widget does not exist in the layout.
        if (this._widget !== widget) {
            return;
        }
        // Clear the internal widget.
        this._widget = null;
        // If the layout is parented, detach the widget from the DOM.
        if (this.parent) {
            this.detachWidget(widget);
        }
    }
    /**
     * Perform layout initialization which requires the parent widget.
     */
    init() {
        super.init();
        for (const widget of this) {
            this.attachWidget(widget);
        }
    }
    /**
     * Attach a widget to the parent's DOM node.
     *
     * @param widget - The widget to attach to the parent.
     *
     * #### Notes
     * This method is called automatically by the single layout at the
     * appropriate time. It should not be called directly by user code.
     *
     * The default implementation adds the widgets's node to the parent's
     * node at the proper location, and sends the appropriate attach
     * messages to the widget if the parent is attached to the DOM.
     *
     * Subclasses may reimplement this method to control how the widget's
     * node is added to the parent's node.
     */
    attachWidget(widget) {
        // Send a `'before-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        }
        // Add the widget's node to the parent.
        this.parent.node.appendChild(widget.node);
        // Send an `'after-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
        }
    }
    /**
     * Detach a widget from the parent's DOM node.
     *
     * @param widget - The widget to detach from the parent.
     *
     * #### Notes
     * This method is called automatically by the single layout at the
     * appropriate time. It should not be called directly by user code.
     *
     * The default implementation removes the widget's node from the
     * parent's node, and sends the appropriate detach messages to the
     * widget if the parent is attached to the DOM.
     *
     * Subclasses may reimplement this method to control how the widget's
     * node is removed from the parent's node.
     */
    detachWidget(widget) {
        // Send a `'before-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        }
        // Remove the widget's node from the parent.
        this.parent.node.removeChild(widget.node);
        // Send an `'after-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
        }
    }
}

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A layout where visible widgets are stacked atop one another.
 *
 * #### Notes
 * The Z-order of the visible widgets follows their layout order.
 */
class StackedLayout extends PanelLayout {
    constructor(options = {}) {
        super(options);
        this._dirty = false;
        this._items = [];
        this._box = null;
        this._hiddenMode =
            options.hiddenMode !== undefined
                ? options.hiddenMode
                : Widget.HiddenMode.Display;
    }
    /**
     * The method for hiding widgets.
     *
     * #### Notes
     * If there is only one child widget, `Display` hiding mode will be used
     * regardless of this setting.
     */
    get hiddenMode() {
        return this._hiddenMode;
    }
    /**
     * Set the method for hiding widgets.
     *
     * #### Notes
     * If there is only one child widget, `Display` hiding mode will be used
     * regardless of this setting.
     */
    set hiddenMode(v) {
        if (this._hiddenMode === v) {
            return;
        }
        this._hiddenMode = v;
        if (this.widgets.length > 1) {
            this.widgets.forEach(w => {
                w.hiddenMode = this._hiddenMode;
            });
        }
    }
    /**
     * Dispose of the resources held by the layout.
     */
    dispose() {
        // Dispose of the layout items.
        for (const item of this._items) {
            item.dispose();
        }
        // Clear the layout state.
        this._box = null;
        this._items.length = 0;
        // Dispose of the rest of the layout.
        super.dispose();
    }
    /**
     * Attach a widget to the parent's DOM node.
     *
     * @param index - The current index of the widget in the layout.
     *
     * @param widget - The widget to attach to the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    attachWidget(index, widget) {
        // Using transform create an additional layer in the pixel pipeline
        // to limit the number of layer, it is set only if there is more than one widget.
        if (this._hiddenMode === Widget.HiddenMode.Scale &&
            this._items.length > 0) {
            if (this._items.length === 1) {
                this.widgets[0].hiddenMode = Widget.HiddenMode.Scale;
            }
            widget.hiddenMode = Widget.HiddenMode.Scale;
        }
        else {
            widget.hiddenMode = Widget.HiddenMode.Display;
        }
        // Create and add a new layout item for the widget.
        ArrayExt.insert(this._items, index, new LayoutItem(widget));
        // Send a `'before-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
        }
        // Add the widget's node to the parent.
        this.parent.node.appendChild(widget.node);
        // Send an `'after-attach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
        }
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * Move a widget in the parent's DOM node.
     *
     * @param fromIndex - The previous index of the widget in the layout.
     *
     * @param toIndex - The current index of the widget in the layout.
     *
     * @param widget - The widget to move in the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    moveWidget(fromIndex, toIndex, widget) {
        // Move the layout item for the widget.
        ArrayExt.move(this._items, fromIndex, toIndex);
        // Post an update request for the parent widget.
        this.parent.update();
    }
    /**
     * Detach a widget from the parent's DOM node.
     *
     * @param index - The previous index of the widget in the layout.
     *
     * @param widget - The widget to detach from the parent.
     *
     * #### Notes
     * This is a reimplementation of the superclass method.
     */
    detachWidget(index, widget) {
        // Remove the layout item for the widget.
        let item = ArrayExt.removeAt(this._items, index);
        // Send a `'before-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
        }
        // Remove the widget's node from the parent.
        this.parent.node.removeChild(widget.node);
        // Send an `'after-detach'` message if the parent is attached.
        if (this.parent.isAttached) {
            MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
        }
        // Reset the z-index for the widget.
        item.widget.node.style.zIndex = '';
        // Reset the hidden mode for the widget.
        if (this._hiddenMode === Widget.HiddenMode.Scale) {
            widget.hiddenMode = Widget.HiddenMode.Display;
            // Reset the hidden mode for the first widget if necessary.
            if (this._items.length === 1) {
                this._items[0].widget.hiddenMode = Widget.HiddenMode.Display;
            }
        }
        // Dispose of the layout item.
        item.dispose();
        // Post a fit request for the parent widget.
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'before-show'` message.
     */
    onBeforeShow(msg) {
        super.onBeforeShow(msg);
        this.parent.update();
    }
    /**
     * A message handler invoked on a `'before-attach'` message.
     */
    onBeforeAttach(msg) {
        super.onBeforeAttach(msg);
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-shown'` message.
     */
    onChildShown(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'child-hidden'` message.
     */
    onChildHidden(msg) {
        this.parent.fit();
    }
    /**
     * A message handler invoked on a `'resize'` message.
     */
    onResize(msg) {
        if (this.parent.isVisible) {
            this._update(msg.width, msg.height);
        }
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     */
    onUpdateRequest(msg) {
        if (this.parent.isVisible) {
            this._update(-1, -1);
        }
    }
    /**
     * A message handler invoked on a `'fit-request'` message.
     */
    onFitRequest(msg) {
        if (this.parent.isAttached) {
            this._fit();
        }
    }
    /**
     * Fit the layout to the total size required by the widgets.
     */
    _fit() {
        // Set up the computed minimum size.
        let minW = 0;
        let minH = 0;
        // Update the computed minimum size.
        for (let i = 0, n = this._items.length; i < n; ++i) {
            // Fetch the item.
            let item = this._items[i];
            // Ignore hidden items.
            if (item.isHidden) {
                continue;
            }
            // Update the size limits for the item.
            item.fit();
            // Update the computed minimum size.
            minW = Math.max(minW, item.minWidth);
            minH = Math.max(minH, item.minHeight);
        }
        // Update the box sizing and add it to the computed min size.
        let box = (this._box = ElementExt.boxSizing(this.parent.node));
        minW += box.horizontalSum;
        minH += box.verticalSum;
        // Update the parent's min size constraints.
        let style = this.parent.node.style;
        style.minWidth = `${minW}px`;
        style.minHeight = `${minH}px`;
        // Set the dirty flag to ensure only a single update occurs.
        this._dirty = true;
        // Notify the ancestor that it should fit immediately. This may
        // cause a resize of the parent, fulfilling the required update.
        if (this.parent.parent) {
            MessageLoop.sendMessage(this.parent.parent, Widget.Msg.FitRequest);
        }
        // If the dirty flag is still set, the parent was not resized.
        // Trigger the required update on the parent widget immediately.
        if (this._dirty) {
            MessageLoop.sendMessage(this.parent, Widget.Msg.UpdateRequest);
        }
    }
    /**
     * Update the layout position and size of the widgets.
     *
     * The parent offset dimensions should be `-1` if unknown.
     */
    _update(offsetWidth, offsetHeight) {
        // Clear the dirty flag to indicate the update occurred.
        this._dirty = false;
        // Compute the visible item count.
        let nVisible = 0;
        for (let i = 0, n = this._items.length; i < n; ++i) {
            nVisible += +!this._items[i].isHidden;
        }
        // Bail early if there are no visible items to layout.
        if (nVisible === 0) {
            return;
        }
        // Measure the parent if the offset dimensions are unknown.
        if (offsetWidth < 0) {
            offsetWidth = this.parent.node.offsetWidth;
        }
        if (offsetHeight < 0) {
            offsetHeight = this.parent.node.offsetHeight;
        }
        // Ensure the parent box sizing data is computed.
        if (!this._box) {
            this._box = ElementExt.boxSizing(this.parent.node);
        }
        // Compute the actual layout bounds adjusted for border and padding.
        let top = this._box.paddingTop;
        let left = this._box.paddingLeft;
        let width = offsetWidth - this._box.horizontalSum;
        let height = offsetHeight - this._box.verticalSum;
        // Update the widget stacking order and layout geometry.
        for (let i = 0, n = this._items.length; i < n; ++i) {
            // Fetch the item.
            let item = this._items[i];
            // Ignore hidden items.
            if (item.isHidden) {
                continue;
            }
            // Set the z-index for the widget.
            item.widget.node.style.zIndex = `${i}`;
            // Update the item geometry.
            item.update(left, top, width, height);
        }
    }
}

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A panel where visible widgets are stacked atop one another.
 *
 * #### Notes
 * This class provides a convenience wrapper around a {@link StackedLayout}.
 */
class StackedPanel extends Panel {
    /**
     * Construct a new stacked panel.
     *
     * @param options - The options for initializing the panel.
     */
    constructor(options = {}) {
        super({ layout: Private$1.createLayout(options) });
        this._widgetRemoved = new Signal(this);
        this.addClass('lm-StackedPanel');
    }
    /**
     * The method for hiding widgets.
     *
     * #### Notes
     * If there is only one child widget, `Display` hiding mode will be used
     * regardless of this setting.
     */
    get hiddenMode() {
        return this.layout.hiddenMode;
    }
    /**
     * Set the method for hiding widgets.
     *
     * #### Notes
     * If there is only one child widget, `Display` hiding mode will be used
     * regardless of this setting.
     */
    set hiddenMode(v) {
        this.layout.hiddenMode = v;
    }
    /**
     * A signal emitted when a widget is removed from a stacked panel.
     */
    get widgetRemoved() {
        return this._widgetRemoved;
    }
    /**
     * A message handler invoked on a `'child-added'` message.
     */
    onChildAdded(msg) {
        msg.child.addClass('lm-StackedPanel-child');
    }
    /**
     * A message handler invoked on a `'child-removed'` message.
     */
    onChildRemoved(msg) {
        msg.child.removeClass('lm-StackedPanel-child');
        this._widgetRemoved.emit(msg.child);
    }
}
/**
 * The namespace for the module implementation details.
 */
var Private$1;
(function (Private) {
    /**
     * Create a stacked layout for the given panel options.
     */
    function createLayout(options) {
        return options.layout || new StackedLayout();
    }
    Private.createLayout = createLayout;
})(Private$1 || (Private$1 = {}));

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
/**
 * A widget which combines a `TabBar` and a `StackedPanel`.
 *
 * #### Notes
 * This is a simple panel which handles the common case of a tab bar
 * placed next to a content area. The selected tab controls the widget
 * which is shown in the content area.
 *
 * For use cases which require more control than is provided by this
 * panel, the `TabBar` widget may be used independently.
 */
class TabPanel extends Widget {
    /**
     * Construct a new tab panel.
     *
     * @param options - The options for initializing the tab panel.
     */
    constructor(options = {}) {
        super();
        this._currentChanged = new Signal(this);
        this._addRequested = new Signal(this);
        this.addClass('lm-TabPanel');
        // Create the tab bar and stacked panel.
        this.tabBar = new TabBar(options);
        this.tabBar.addClass('lm-TabPanel-tabBar');
        this.stackedPanel = new StackedPanel();
        this.stackedPanel.addClass('lm-TabPanel-stackedPanel');
        // Connect the tab bar signal handlers.
        this.tabBar.tabMoved.connect(this._onTabMoved, this);
        this.tabBar.currentChanged.connect(this._onCurrentChanged, this);
        this.tabBar.tabCloseRequested.connect(this._onTabCloseRequested, this);
        this.tabBar.tabActivateRequested.connect(this._onTabActivateRequested, this);
        this.tabBar.addRequested.connect(this._onTabAddRequested, this);
        // Connect the stacked panel signal handlers.
        this.stackedPanel.widgetRemoved.connect(this._onWidgetRemoved, this);
        // Get the data related to the placement.
        this._tabPlacement = options.tabPlacement || 'top';
        let direction = Private.directionFromPlacement(this._tabPlacement);
        let orientation = Private.orientationFromPlacement(this._tabPlacement);
        // Configure the tab bar for the placement.
        this.tabBar.orientation = orientation;
        this.tabBar.dataset['placement'] = this._tabPlacement;
        // Create the box layout.
        let layout = new BoxLayout({ direction, spacing: 0 });
        // Set the stretch factors for the child widgets.
        BoxLayout.setStretch(this.tabBar, 0);
        BoxLayout.setStretch(this.stackedPanel, 1);
        // Add the child widgets to the layout.
        layout.addWidget(this.tabBar);
        layout.addWidget(this.stackedPanel);
        // Install the layout on the tab panel.
        this.layout = layout;
    }
    /**
     * A signal emitted when the current tab is changed.
     *
     * #### Notes
     * This signal is emitted when the currently selected tab is changed
     * either through user or programmatic interaction.
     *
     * Notably, this signal is not emitted when the index of the current
     * tab changes due to tabs being inserted, removed, or moved. It is
     * only emitted when the actual current tab node is changed.
     */
    get currentChanged() {
        return this._currentChanged;
    }
    /**
     * Get the index of the currently selected tab.
     *
     * #### Notes
     * This will be `-1` if no tab is selected.
     */
    get currentIndex() {
        return this.tabBar.currentIndex;
    }
    /**
     * Set the index of the currently selected tab.
     *
     * #### Notes
     * If the index is out of range, it will be set to `-1`.
     */
    set currentIndex(value) {
        this.tabBar.currentIndex = value;
    }
    /**
     * Get the currently selected widget.
     *
     * #### Notes
     * This will be `null` if there is no selected tab.
     */
    get currentWidget() {
        let title = this.tabBar.currentTitle;
        return title ? title.owner : null;
    }
    /**
     * Set the currently selected widget.
     *
     * #### Notes
     * If the widget is not in the panel, it will be set to `null`.
     */
    set currentWidget(value) {
        this.tabBar.currentTitle = value ? value.title : null;
    }
    /**
     * Get the whether the tabs are movable by the user.
     *
     * #### Notes
     * Tabs can always be moved programmatically.
     */
    get tabsMovable() {
        return this.tabBar.tabsMovable;
    }
    /**
     * Set the whether the tabs are movable by the user.
     *
     * #### Notes
     * Tabs can always be moved programmatically.
     */
    set tabsMovable(value) {
        this.tabBar.tabsMovable = value;
    }
    /**
     * Get the whether the add button is enabled.
     *
     */
    get addButtonEnabled() {
        return this.tabBar.addButtonEnabled;
    }
    /**
     * Set the whether the add button is enabled.
     *
     */
    set addButtonEnabled(value) {
        this.tabBar.addButtonEnabled = value;
    }
    /**
     * Get the tab placement for the tab panel.
     *
     * #### Notes
     * This controls the position of the tab bar relative to the content.
     */
    get tabPlacement() {
        return this._tabPlacement;
    }
    /**
     * Set the tab placement for the tab panel.
     *
     * #### Notes
     * This controls the position of the tab bar relative to the content.
     */
    set tabPlacement(value) {
        // Bail if the placement does not change.
        if (this._tabPlacement === value) {
            return;
        }
        // Update the internal value.
        this._tabPlacement = value;
        // Get the values related to the placement.
        let direction = Private.directionFromPlacement(value);
        let orientation = Private.orientationFromPlacement(value);
        // Configure the tab bar for the placement.
        this.tabBar.orientation = orientation;
        this.tabBar.dataset['placement'] = value;
        // Update the layout direction.
        this.layout.direction = direction;
    }
    /**
     * A signal emitted when the add button on a tab bar is clicked.
     *
     */
    get addRequested() {
        return this._addRequested;
    }
    /**
     * A read-only array of the widgets in the panel.
     */
    get widgets() {
        return this.stackedPanel.widgets;
    }
    /**
     * Add a widget to the end of the tab panel.
     *
     * @param widget - The widget to add to the tab panel.
     *
     * #### Notes
     * If the widget is already contained in the panel, it will be moved.
     *
     * The widget's `title` is used to populate the tab.
     */
    addWidget(widget) {
        this.insertWidget(this.widgets.length, widget);
    }
    /**
     * Insert a widget into the tab panel at a specified index.
     *
     * @param index - The index at which to insert the widget.
     *
     * @param widget - The widget to insert into to the tab panel.
     *
     * #### Notes
     * If the widget is already contained in the panel, it will be moved.
     *
     * The widget's `title` is used to populate the tab.
     */
    insertWidget(index, widget) {
        if (widget !== this.currentWidget) {
            widget.hide();
        }
        this.stackedPanel.insertWidget(index, widget);
        this.tabBar.insertTab(index, widget.title);
        widget.node.setAttribute('role', 'tabpanel');
        let renderer = this.tabBar.renderer;
        if (renderer instanceof TabBar.Renderer) {
            let tabId = renderer.createTabKey({
                title: widget.title,
                current: false,
                zIndex: 0
            });
            widget.node.setAttribute('aria-labelledby', tabId);
        }
    }
    /**
     * Handle the `currentChanged` signal from the tab bar.
     */
    _onCurrentChanged(sender, args) {
        // Extract the previous and current title from the args.
        let { previousIndex, previousTitle, currentIndex, currentTitle } = args;
        // Extract the widgets from the titles.
        let previousWidget = previousTitle ? previousTitle.owner : null;
        let currentWidget = currentTitle ? currentTitle.owner : null;
        // Hide the previous widget.
        if (previousWidget) {
            previousWidget.hide();
        }
        // Show the current widget.
        if (currentWidget) {
            currentWidget.show();
        }
        // Emit the `currentChanged` signal for the tab panel.
        this._currentChanged.emit({
            previousIndex,
            previousWidget,
            currentIndex,
            currentWidget
        });
        // Flush the message loop on IE and Edge to prevent flicker.
        if (Platform.IS_EDGE || Platform.IS_IE) {
            MessageLoop.flush();
        }
    }
    /**
     * Handle the `tabAddRequested` signal from the tab bar.
     */
    _onTabAddRequested(sender, args) {
        this._addRequested.emit(sender);
    }
    /**
     * Handle the `tabActivateRequested` signal from the tab bar.
     */
    _onTabActivateRequested(sender, args) {
        args.title.owner.activate();
    }
    /**
     * Handle the `tabCloseRequested` signal from the tab bar.
     */
    _onTabCloseRequested(sender, args) {
        args.title.owner.close();
    }
    /**
     * Handle the `tabMoved` signal from the tab bar.
     */
    _onTabMoved(sender, args) {
        this.stackedPanel.insertWidget(args.toIndex, args.title.owner);
    }
    /**
     * Handle the `widgetRemoved` signal from the stacked panel.
     */
    _onWidgetRemoved(sender, widget) {
        widget.node.removeAttribute('role');
        widget.node.removeAttribute('aria-labelledby');
        this.tabBar.removeTab(widget.title);
    }
}
/**
 * The namespace for the module implementation details.
 */
var Private;
(function (Private) {
    /**
     * Convert a tab placement to tab bar orientation.
     */
    function orientationFromPlacement(plc) {
        return placementToOrientationMap[plc];
    }
    Private.orientationFromPlacement = orientationFromPlacement;
    /**
     * Convert a tab placement to a box layout direction.
     */
    function directionFromPlacement(plc) {
        return placementToDirectionMap[plc];
    }
    Private.directionFromPlacement = directionFromPlacement;
    /**
     * A mapping of tab placement to tab bar orientation.
     */
    const placementToOrientationMap = {
        top: 'horizontal',
        left: 'vertical',
        right: 'vertical',
        bottom: 'horizontal'
    };
    /**
     * A mapping of tab placement to box layout direction.
     */
    const placementToDirectionMap = {
        top: 'top-to-bottom',
        left: 'left-to-right',
        right: 'right-to-left',
        bottom: 'bottom-to-top'
    };
})(Private || (Private = {}));

export { AccordionLayout, AccordionPanel, BoxEngine, BoxLayout, BoxPanel, BoxSizer, CommandPalette, ContextMenu, DockLayout, DockPanel, FocusTracker, GridLayout, Layout, LayoutItem, Menu, MenuBar, Panel, PanelLayout, ScrollBar, SingletonLayout, SplitLayout, SplitPanel, StackedLayout, StackedPanel, TabBar, TabPanel, Title, Widget };
//# sourceMappingURL=index.es6.js.map