UNPKG

@jupyterlab/notebook

Version:
3,210 lines 120 kB
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/* eslint-disable @typescript-eslint/no-explicit-any */
import { DOMUtils } from '@jupyterlab/apputils';
import { Cell, CodeCell, MarkdownCell, RawCell } from '@jupyterlab/cells';
import { IEditorMimeTypeService } from '@jupyterlab/codeeditor';
import { TableOfContentsUtils } from '@jupyterlab/toc';
import { nullTranslator } from '@jupyterlab/translation';
import { WindowedList } from '@jupyterlab/ui-components';
import { ArrayExt, findIndex } from '@lumino/algorithm';
import { JSONExt, MimeData } from '@lumino/coreutils';
import { ElementExt } from '@lumino/domutils';
import { Drag } from '@lumino/dragdrop';
import { AttachedProperty } from '@lumino/properties';
import { Signal } from '@lumino/signaling';
import { h, VirtualDOM } from '@lumino/virtualdom';
import { PanelLayout, Widget } from '@lumino/widgets';
import { NotebookActions } from './actions';
import { DROP_SOURCE_CLASS, DROP_TARGET_CLASS } from './constants';
import { NotebookViewModel, NotebookWindowedLayout } from './windowing';
import { NotebookFooter } from './notebookfooter';
/**
 * The data attribute added to a widget that has an active kernel.
 */
const KERNEL_USER = 'jpKernelUser';
/**
 * The data attribute added to a widget that can run code.
 */
const CODE_RUNNER = 'jpCodeRunner';
/**
 * The data attribute added to a widget that can undo.
 */
const UNDOER = 'jpUndoer';
/**
 * The class name added to notebook widgets.
 */
const NB_CLASS = 'jp-Notebook';
/**
 * The class name added to notebook widget cells.
 */
const NB_CELL_CLASS = 'jp-Notebook-cell';
/**
 * The class name added to a notebook in edit mode.
 */
const EDIT_CLASS = 'jp-mod-editMode';
/**
 * The class name added to a notebook in command mode.
 */
const COMMAND_CLASS = 'jp-mod-commandMode';
/**
 * The class name added to the active cell.
 */
const ACTIVE_CLASS = 'jp-mod-active';
/**
 * The class name added to selected cells.
 */
const SELECTED_CLASS = 'jp-mod-selected';
/**
 * The class name added to the cell when dirty.
 */
const DIRTY_CLASS = 'jp-mod-dirty';
/**
 * The class name added to an active cell when there are other selected cells.
 */
const OTHER_SELECTED_CLASS = 'jp-mod-multiSelected';
/**
 * The class name added to unconfined images.
 */
const UNCONFINED_CLASS = 'jp-mod-unconfined';
/**
 * The class name added to the notebook when an element within it is focused
 * and takes keyboard input, such as focused <input> or <div contenteditable>.
 *
 * This class is also effective when the focused element is in shadow DOM.
 */
const READ_WRITE_CLASS = 'jp-mod-readWrite';
/**
 * The class name added to drag images.
 */
const DRAG_IMAGE_CLASS = 'jp-dragImage';
/**
 * The class name added to singular drag images
 */
const SINGLE_DRAG_IMAGE_CLASS = 'jp-dragImage-singlePrompt';
/**
 * The class name added to the drag image cell content.
 */
const CELL_DRAG_CONTENT_CLASS = 'jp-dragImage-content';
/**
 * The class name added to the drag image cell content.
 */
const CELL_DRAG_PROMPT_CLASS = 'jp-dragImage-prompt';
/**
 * The class name added to the drag image cell content.
 */
const CELL_DRAG_MULTIPLE_BACK = 'jp-dragImage-multipleBack';
/**
 * The mimetype used for Jupyter cell data.
 */
const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
/**
 * The threshold in pixels to start a drag event.
 */
const DRAG_THRESHOLD = 5;
/**
 * Maximal remaining time for idle callback
 *
 * Ref: https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API#getting_the_most_out_of_idle_callbacks
 */
const MAXIMUM_TIME_REMAINING = 50;
/**
 * The class attached to the heading collapser button
 */
const HEADING_COLLAPSER_CLASS = 'jp-collapseHeadingButton';
/**
 * The class that controls the visibility of "heading collapser" and "show hidden cells" buttons.
 */
const HEADING_COLLAPSER_VISBILITY_CONTROL_CLASS = 'jp-mod-showHiddenCellsButton';
const SIDE_BY_SIDE_CLASS = 'jp-mod-sideBySide';
if (window.requestIdleCallback === undefined) {
    // On Safari, requestIdleCallback is not available, so we use replacement functions for `idleCallbacks`
    // See: https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API#falling_back_to_settimeout
    window.requestIdleCallback = function (handler) {
        let startTime = Date.now();
        return setTimeout(function () {
            handler({
                didTimeout: false,
                timeRemaining: function () {
                    return Math.max(0, 50.0 - (Date.now() - startTime));
                }
            });
        }, 1);
    };
    window.cancelIdleCallback = function (id) {
        clearTimeout(id);
    };
}
/**
 * A widget which renders static non-interactive notebooks.
 *
 * #### Notes
 * The widget model must be set separately and can be changed
 * at any time.  Consumers of the widget must account for a
 * `null` model, and may want to listen to the `modelChanged`
 * signal.
 */
export class StaticNotebook extends WindowedList {
    /**
     * Construct a notebook widget.
     */
    constructor(options) {
        var _a, _b, _c, _d, _e, _f;
        const cells = new Array();
        const windowingActive = ((_b = (_a = options.notebookConfig) === null || _a === void 0 ? void 0 : _a.windowingMode) !== null && _b !== void 0 ? _b : StaticNotebook.defaultNotebookConfig.windowingMode) === 'full';
        super({
            model: new NotebookViewModel(cells, {
                overscanCount: (_d = (_c = options.notebookConfig) === null || _c === void 0 ? void 0 : _c.overscanCount) !== null && _d !== void 0 ? _d : StaticNotebook.defaultNotebookConfig.overscanCount,
                windowingActive
            }),
            layout: new NotebookWindowedLayout(),
            renderer: (_e = options.renderer) !== null && _e !== void 0 ? _e : WindowedList.defaultRenderer,
            scrollbar: false
        });
        this._cellCollapsed = new Signal(this);
        this._cellInViewportChanged = new Signal(this);
        this._renderingLayoutChanged = new Signal(this);
        this._contentVisibilityObserver = null;
        this.addClass(NB_CLASS);
        this.cellsArray = cells;
        this._idleCallBack = null;
        this._editorConfig = StaticNotebook.defaultEditorConfig;
        this._notebookConfig = StaticNotebook.defaultNotebookConfig;
        this._mimetype = IEditorMimeTypeService.defaultMimeType;
        this._notebookModel = null;
        this._modelChanged = new Signal(this);
        this._modelContentChanged = new Signal(this);
        this.node.dataset[KERNEL_USER] = 'true';
        this.node.dataset[UNDOER] = 'true';
        this.node.dataset[CODE_RUNNER] = 'true';
        this.rendermime = options.rendermime;
        this.translator = options.translator || nullTranslator;
        this.contentFactory = options.contentFactory;
        this.editorConfig =
            options.editorConfig || StaticNotebook.defaultEditorConfig;
        this.notebookConfig =
            options.notebookConfig || StaticNotebook.defaultNotebookConfig;
        this._updateNotebookConfig();
        this._mimetypeService = options.mimeTypeService;
        this.renderingLayout = (_f = options.notebookConfig) === null || _f === void 0 ? void 0 : _f.renderingLayout;
        this.kernelHistory = options.kernelHistory;
    }
    get cellCollapsed() {
        return this._cellCollapsed;
    }
    get cellInViewportChanged() {
        return this._cellInViewportChanged;
    }
    /**
     * A signal emitted when the model of the notebook changes.
     */
    get modelChanged() {
        return this._modelChanged;
    }
    /**
     * A signal emitted when the model content changes.
     *
     * #### Notes
     * This is a convenience signal that follows the current model.
     */
    get modelContentChanged() {
        return this._modelContentChanged;
    }
    /**
     * A signal emitted when the rendering layout of the notebook changes.
     */
    get renderingLayoutChanged() {
        return this._renderingLayoutChanged;
    }
    /**
     * The model for the widget.
     */
    get model() {
        return this._notebookModel;
    }
    set model(newValue) {
        var _a;
        newValue = newValue || null;
        if (this._notebookModel === newValue) {
            return;
        }
        const oldValue = this._notebookModel;
        this._notebookModel = newValue;
        // Trigger private, protected, and public changes.
        this._onModelChanged(oldValue, newValue);
        this.onModelChanged(oldValue, newValue);
        this._modelChanged.emit(void 0);
        // Trigger state change
        this.viewModel.itemsList = (_a = newValue === null || newValue === void 0 ? void 0 : newValue.cells) !== null && _a !== void 0 ? _a : null;
    }
    /**
     * Get the mimetype for code cells.
     */
    get codeMimetype() {
        return this._mimetype;
    }
    /**
     * A read-only sequence of the widgets in the notebook.
     */
    get widgets() {
        return this.cellsArray;
    }
    /**
     * A configuration object for cell editor settings.
     */
    get editorConfig() {
        return this._editorConfig;
    }
    set editorConfig(value) {
        this._editorConfig = value;
        this._updateEditorConfig();
    }
    /**
     * A configuration object for notebook settings.
     */
    get notebookConfig() {
        return this._notebookConfig;
    }
    set notebookConfig(value) {
        this._notebookConfig = value;
        this._updateNotebookConfig();
    }
    get renderingLayout() {
        return this._renderingLayout;
    }
    set renderingLayout(value) {
        var _a;
        this._renderingLayout = value;
        if (this._renderingLayout === 'side-by-side') {
            this.node.classList.add(SIDE_BY_SIDE_CLASS);
        }
        else {
            this.node.classList.remove(SIDE_BY_SIDE_CLASS);
        }
        this._renderingLayoutChanged.emit((_a = this._renderingLayout) !== null && _a !== void 0 ? _a : 'default');
    }
    /**
     * Dispose of the resources held by the widget.
     */
    dispose() {
        var _a;
        // Do nothing if already disposed.
        if (this.isDisposed) {
            return;
        }
        this._notebookModel = null;
        (_a = this.layout.header) === null || _a === void 0 ? void 0 : _a.dispose();
        //  Disconnect the content-visibility observer
        if (this._contentVisibilityObserver) {
            this._contentVisibilityObserver.disconnect();
            this._contentVisibilityObserver = null;
        }
        super.dispose();
    }
    onBeforeDetach(msg) {
        //  Disconnect the content-visibility observer
        if (this._contentVisibilityObserver) {
            this._contentVisibilityObserver.disconnect();
            this._contentVisibilityObserver = null;
        }
        super.onBeforeDetach(msg);
    }
    /**
     * Move cells preserving widget view state.
     *
     * #### Notes
     * This is required because at the model level a move is a deletion
     * followed by an insertion. Hence the view state is not preserved.
     *
     * @param from The index of the cell to move
     * @param to The new index of the cell
     * @param n Number of cells to move
     */
    moveCell(from, to, n = 1) {
        if (!this.model) {
            return;
        }
        const boundedTo = Math.min(this.model.cells.length - 1, Math.max(0, to));
        if (boundedTo === from) {
            return;
        }
        const viewModel = new Array(n);
        let dirtyState = new Array(n);
        for (let i = 0; i < n; i++) {
            viewModel[i] = {};
            const oldCell = this.widgets[from + i];
            if (oldCell.model.type === 'markdown') {
                for (const k of ['rendered', 'headingCollapsed']) {
                    // @ts-expect-error Cell has no index signature
                    viewModel[i][k] = oldCell[k];
                }
            }
            else if (oldCell.model.type === 'code') {
                const oldCodeCell = oldCell.model;
                dirtyState[i] = oldCodeCell.isDirty;
            }
        }
        this.model.sharedModel.moveCells(from, boundedTo, n);
        for (let i = 0; i < n; i++) {
            const newIndex = from > boundedTo ? boundedTo + i : boundedTo - n + 1 + i;
            const newCell = this.widgets[newIndex];
            const view = viewModel[i];
            for (const state in view) {
                // @ts-expect-error Cell has no index signature
                newCell[state] = view[state];
            }
            if (from > boundedTo) {
                if (this.widgets[boundedTo + i].model.type === 'code') {
                    this.widgets[boundedTo + i].model.isDirty =
                        dirtyState[i];
                }
            }
            else {
                if (this.widgets[boundedTo + i - n + 1].model.type === 'code') {
                    this.widgets[boundedTo + i - n + 1].model.isDirty =
                        dirtyState[i];
                }
            }
        }
        this._refreshCollapsedHeadingVisibility();
    }
    /**
     * Force rendering the cell outputs of a given cell if it is still a placeholder.
     *
     * #### Notes
     * The goal of this method is to allow search on cell outputs (that is based
     * on DOM tree introspection).
     *
     * @param index The cell index
     */
    renderCellOutputs(index) {
        const cell = this.viewModel.widgetRenderer(index);
        if (cell instanceof CodeCell && cell.isPlaceholder()) {
            cell.dataset.windowedListIndex = `${index}`;
            this.layout.insertWidget(index, cell);
            if (this.notebookConfig.windowingMode === 'full') {
                // We need to delay slightly the removal to let codemirror properly initialize
                requestAnimationFrame(() => {
                    this.layout.removeWidget(cell);
                });
            }
        }
    }
    /**
     * Adds a message to the notebook as a header.
     */
    addHeader() {
        const trans = this.translator.load('jupyterlab');
        const info = new Widget();
        info.node.textContent = trans.__('The notebook is empty. Click the + button on the toolbar to add a new cell.');
        this.layout.header = info;
    }
    /**
     * Removes the header.
     */
    removeHeader() {
        var _a;
        (_a = this.layout.header) === null || _a === void 0 ? void 0 : _a.dispose();
        this.layout.header = null;
    }
    /**
     * Handle a new model.
     *
     * #### Notes
     * This method is called after the model change has been handled
     * internally and before the `modelChanged` signal is emitted.
     * The default implementation is a no-op.
     */
    onModelChanged(oldValue, newValue) {
        // No-op.
    }
    /**
     * Handle changes to the notebook model content.
     *
     * #### Notes
     * The default implementation emits the `modelContentChanged` signal.
     */
    onModelContentChanged(model, args) {
        this._modelContentChanged.emit(void 0);
    }
    /**
     * Handle changes to the notebook model metadata.
     *
     * #### Notes
     * The default implementation updates the mimetypes of the code cells
     * when the `language_info` metadata changes.
     */
    onMetadataChanged(sender, args) {
        switch (args.key) {
            case 'language_info':
                this._updateMimetype();
                break;
            default:
                break;
        }
    }
    /**
     * Handle a cell being inserted.
     *
     * The default implementation is a no-op
     */
    onCellInserted(index, cell) {
        // This is a no-op.
    }
    /**
     * Handle a cell being removed.
     *
     * The default implementation is a no-op
     */
    onCellRemoved(index, cell) {
        // This is a no-op.
    }
    /**
     * A message handler invoked on an `'update-request'` message.
     *
     * In defer-like mode, the default logic for inserting cells
     * is skipped, enabling the notebook to render cells in small
     * batches, without blocking the UI.
     */
    onUpdateRequest(msg) {
        if (['defer', 'contentVisibility'].includes(this.notebookConfig.windowingMode)) {
            void this._runOnIdleTime();
        }
        else {
            super.onUpdateRequest(msg);
        }
    }
    /**
     * Handle a new model on the widget.
     */
    _onModelChanged(oldValue, newValue) {
        var _a;
        if (oldValue) {
            oldValue.contentChanged.disconnect(this.onModelContentChanged, this);
            oldValue.metadataChanged.disconnect(this.onMetadataChanged, this);
            oldValue.cells.changed.disconnect(this._onCellsChanged, this);
            while (this.cellsArray.length) {
                this._removeCell(0);
            }
        }
        if (!newValue) {
            this._mimetype = IEditorMimeTypeService.defaultMimeType;
            return;
        }
        this._updateMimetype();
        const cells = newValue.cells;
        const collab = (_a = newValue.collaborative) !== null && _a !== void 0 ? _a : false;
        if (!collab && !cells.length) {
            newValue.sharedModel.insertCell(0, {
                cell_type: this.notebookConfig.defaultCell,
                metadata: this.notebookConfig.defaultCell === 'code'
                    ? {
                        // This is an empty cell created in empty notebook, thus is trusted
                        trusted: true
                    }
                    : {}
            });
        }
        let index = -1;
        for (const cell of cells) {
            this._insertCell(++index, cell);
        }
        this._syncMarkdownCellTrust();
        newValue.cells.changed.connect(this._onCellsChanged, this);
        newValue.metadataChanged.connect(this.onMetadataChanged, this);
        newValue.contentChanged.connect(this.onModelContentChanged, this);
    }
    /**
     * Handle a change cells event.
     */
    _onCellsChanged(sender, args) {
        this.removeHeader();
        // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
        switch (args.type) {
            case 'add': {
                let index = 0;
                index = args.newIndex;
                for (const value of args.newValues) {
                    this._insertCell(index++, value);
                }
                this._updateDataWindowedListIndex(args.newIndex, args.newValues.length);
                break;
            }
            case 'remove':
                for (let length = args.oldValues.length; length > 0; length--) {
                    this._removeCell(args.oldIndex);
                }
                this._updateDataWindowedListIndex(args.oldIndex, -1 * args.oldValues.length);
                // Add default cell if there are no cells remaining.
                if (!sender.length) {
                    const model = this.model;
                    // Add the cell in a new context to avoid triggering another
                    // cell changed event during the handling of this signal.
                    requestAnimationFrame(() => {
                        if (model && !model.isDisposed && !model.sharedModel.cells.length) {
                            model.sharedModel.insertCell(0, {
                                cell_type: this.notebookConfig.defaultCell,
                                metadata: this.notebookConfig.defaultCell === 'code'
                                    ? {
                                        // This is an empty cell created in empty notebook, thus is trusted
                                        trusted: true
                                    }
                                    : {}
                            });
                        }
                    });
                }
                break;
            default:
                return;
        }
        if (!this.model.sharedModel.cells.length) {
            this.addHeader();
        }
        this._syncMarkdownCellTrust();
        this.update();
    }
    /**
     * Create a cell widget and insert into the notebook.
     */
    _insertCell(index, cell) {
        let widget;
        switch (cell.type) {
            case 'code':
                widget = this._createCodeCell(cell);
                widget.model.mimeType = this._mimetype;
                break;
            case 'markdown':
                widget = this._createMarkdownCell(cell);
                if (cell.sharedModel.getSource() === '') {
                    widget.rendered = false;
                }
                break;
            default:
                widget = this._createRawCell(cell);
        }
        cell.stateChanged.connect(this._onCellStateChanged, this);
        widget.inViewportChanged.connect(this._onCellInViewportChanged, this);
        widget.addClass(NB_CELL_CLASS);
        ArrayExt.insert(this.cellsArray, index, widget);
        this._syncMarkdownCellTrust(widget);
        this.onCellInserted(index, widget);
        this._scheduleCellRenderOnIdle();
    }
    /**
     * Create a code cell widget from a code cell model.
     */
    _createCodeCell(model) {
        const rendermime = this.rendermime;
        const contentFactory = this.contentFactory;
        const editorConfig = this.editorConfig.code;
        const options = {
            contentFactory,
            editorConfig,
            inputHistoryScope: this.notebookConfig.inputHistoryScope,
            showInputPlaceholder: this.notebookConfig.showInputPlaceholder,
            maxNumberOutputs: this.notebookConfig.maxNumberOutputs,
            model,
            placeholder: this._notebookConfig.windowingMode !== 'none',
            rendermime,
            translator: this.translator
        };
        const cell = this.contentFactory.createCodeCell(options);
        if (cell.syncCollapse === undefined) {
            cell.syncCollapse = true;
        }
        if (cell.syncEditable === undefined) {
            cell.syncEditable = true;
        }
        if (cell.syncScrolled === undefined) {
            cell.syncScrolled = true;
        }
        cell.outputArea.inputRequested.connect((_, stdin) => {
            this._onInputRequested(cell).catch(reason => {
                console.error('Failed to scroll to cell requesting input.', reason);
            });
            stdin.disposed.connect(() => {
                // The input field is removed from the DOM after the user presses Enter.
                // This causes focus to be lost if we don't explicitly re-focus
                // somewhere else.
                cell.node.focus();
            });
        });
        return cell;
    }
    /**
     * Create a markdown cell widget from a markdown cell model.
     */
    _createMarkdownCell(model) {
        const rendermime = this.rendermime;
        const contentFactory = this.contentFactory;
        const editorConfig = this.editorConfig.markdown;
        const options = {
            contentFactory,
            editorConfig,
            model,
            placeholder: this._notebookConfig.windowingMode !== 'none',
            rendermime,
            showEditorForReadOnlyMarkdown: this._notebookConfig.showEditorForReadOnlyMarkdown
        };
        const cell = this.contentFactory.createMarkdownCell(options);
        if (cell.syncCollapse === undefined) {
            cell.syncCollapse = true;
        }
        if (cell.syncEditable === undefined) {
            cell.syncEditable = true;
        }
        // Connect collapsed signal for each markdown cell widget
        cell.headingCollapsedChanged.connect(this._onCellCollapsed, this);
        return cell;
    }
    /**
     * Create a raw cell widget from a raw cell model.
     */
    _createRawCell(model) {
        const contentFactory = this.contentFactory;
        const editorConfig = this.editorConfig.raw;
        const options = {
            editorConfig,
            model,
            contentFactory,
            placeholder: this._notebookConfig.windowingMode !== 'none'
        };
        const cell = this.contentFactory.createRawCell(options);
        if (cell.syncCollapse === undefined) {
            cell.syncCollapse = true;
        }
        if (cell.syncEditable === undefined) {
            cell.syncEditable = true;
        }
        return cell;
    }
    /**
     * Remove a cell widget.
     */
    _removeCell(index) {
        const widget = this.cellsArray[index];
        widget.model.stateChanged.disconnect(this._onCellStateChanged, this);
        widget.parent = null;
        ArrayExt.removeAt(this.cellsArray, index);
        this.onCellRemoved(index, widget);
        widget.dispose();
    }
    _shouldTrustMarkdown() {
        // Note: this returns false in a notebook without trsuted code cells;
        // This is intended since only Code cells carry trust status on disk.
        if (!this._notebookModel) {
            return false;
        }
        let hasCodeCell = false;
        for (const cell of this._notebookModel.cells) {
            if (cell.type === 'code') {
                hasCodeCell = true;
                if (!cell.trusted) {
                    return false;
                }
            }
        }
        return hasCodeCell;
    }
    _syncMarkdownCellTrust(cell) {
        const trusted = this._shouldTrustMarkdown();
        const trustHandler = this.rendermime.trustHandler;
        if (!trustHandler) {
            return;
        }
        const cells = cell ? [cell] : this.widgets;
        for (const widget of cells) {
            if (!(widget instanceof MarkdownCell)) {
                continue;
            }
            if (trusted) {
                trustHandler.markTrusted(widget.node);
            }
            else {
                trustHandler.unmarkTrusted(widget.node);
            }
        }
    }
    _onCellStateChanged(model, args) {
        if (args.name === 'trusted') {
            this._syncMarkdownCellTrust();
        }
    }
    /**
     * Update the mimetype of the notebook.
     */
    _updateMimetype() {
        var _a;
        const info = (_a = this._notebookModel) === null || _a === void 0 ? void 0 : _a.getMetadata('language_info');
        if (!info) {
            return;
        }
        this._mimetype = this._mimetypeService.getMimeTypeByLanguage(info);
        for (const widget of this.widgets) {
            if (widget.model.type === 'code') {
                widget.model.mimeType = this._mimetype;
            }
        }
    }
    /**
     * Callback when a cell collapsed status changes.
     *
     * @param cell Cell changed
     * @param collapsed New collapsed status
     */
    _onCellCollapsed(cell, collapsed) {
        cell
            .getHeadings()
            .then(() => {
            // Heading parsing is async; ignore stale callbacks that no longer match
            // the current collapsed state.
            if (cell.isDisposed ||
                (cell instanceof MarkdownCell && cell.headingCollapsed !== collapsed)) {
                return;
            }
            NotebookActions.setHeadingCollapse(cell, collapsed, this);
            this._cellCollapsed.emit(cell);
        })
            .catch(error => {
            console.warn('Failed to resolve headings: ', error);
        });
    }
    /**
     * Recompute hidden cells from the current collapsed heading structure.
     */
    _refreshCollapsedHeadingVisibility() {
        for (const cell of this.widgets) {
            cell.setHidden(false);
        }
        for (const cell of this.widgets) {
            if (!(cell instanceof MarkdownCell) || !cell.headingCollapsed) {
                continue;
            }
            if (cell.headingsResolved) {
                NotebookActions.setHeadingCollapse(cell, true, this);
                continue;
            }
            cell
                .getHeadings()
                .then(() => {
                if (!cell.isDisposed && cell.headingCollapsed) {
                    this._refreshCollapsedHeadingVisibility();
                }
            })
                .catch(error => {
                console.warn('Failed to resolve headings: ', error);
            });
        }
    }
    /**
     * Callback when a cell viewport status changes.
     *
     * @param cell Cell changed
     */
    _onCellInViewportChanged(cell) {
        this._cellInViewportChanged.emit(cell);
    }
    /**
     * Ensure to load in the DOM a cell requesting an user input
     *
     * @param cell Cell requesting an input
     */
    async _onInputRequested(cell) {
        if (!cell.inViewport) {
            const cellIndex = this.widgets.findIndex(c => c === cell);
            if (cellIndex >= 0) {
                await this.scrollToItem(cellIndex);
                const inputEl = cell.node.querySelector('.jp-Stdin');
                if (inputEl) {
                    ElementExt.scrollIntoViewIfNeeded(this.node, inputEl);
                    inputEl.focus();
                }
            }
        }
    }
    _scheduleCellRenderOnIdle() {
        if (this.notebookConfig.windowingMode !== 'none' && !this.isDisposed) {
            if (!this._idleCallBack) {
                this._idleCallBack = requestIdleCallback((deadline) => {
                    this._idleCallBack = null;
                    // In case of timeout, render for some time even if it means freezing the UI
                    // This avoids the cells to never be loaded.
                    void this._runOnIdleTime(deadline.didTimeout
                        ? MAXIMUM_TIME_REMAINING
                        : deadline.timeRemaining());
                }, { timeout: 3000 });
            }
        }
    }
    _updateDataWindowedListIndex(start, delta) {
        for (const cell of this.cellsArray) {
            const globalIndex = parseInt(cell.dataset.windowedListIndex, 10);
            if (globalIndex >= start) {
                cell.node.dataset.windowedListIndex = `${globalIndex + delta}`;
            }
        }
    }
    /**
     * Update editor settings for notebook cells.
     */
    _updateEditorConfig() {
        for (let i = 0; i < this.widgets.length; i++) {
            const cell = this.widgets[i];
            let config = {};
            switch (cell.model.type) {
                case 'code':
                    config = this._editorConfig.code;
                    break;
                case 'markdown':
                    config = this._editorConfig.markdown;
                    break;
                default:
                    config = this._editorConfig.raw;
                    break;
            }
            cell.updateEditorConfig({ ...config });
        }
    }
    async _runOnIdleTime(remainingTime = MAXIMUM_TIME_REMAINING) {
        const startTime = Date.now();
        let cellIdx = 0;
        while (Date.now() - startTime < remainingTime &&
            cellIdx < this.cellsArray.length) {
            const cell = this.cellsArray[cellIdx];
            if (cell.isPlaceholder()) {
                if (['defer', 'full', 'contentVisibility'].includes(this.notebookConfig.windowingMode)) {
                    await this._updateForDeferMode(cell, cellIdx);
                    if (this.notebookConfig.windowingMode === 'full') {
                        // We need to delay slightly the removal to let codemirror properly initialize
                        requestAnimationFrame(() => {
                            this.viewModel.setEstimatedWidgetSize(cell.model.id, cell.node.getBoundingClientRect().height);
                            this.layout.removeWidget(cell);
                        });
                    }
                    else if (this.notebookConfig.windowingMode === 'contentVisibility') {
                        // Update height estimate in the view model
                        const height = cell.node.getBoundingClientRect().height;
                        this.viewModel.setEstimatedWidgetSize(cell.model.id, height);
                        cell.node.style.containIntrinsicSize = `auto ${height}px`;
                    }
                }
            }
            cellIdx++;
        }
        // If the notebook is not fully rendered
        if (cellIdx < this.cellsArray.length) {
            // If we are deferring the cell rendering and the rendered cells do
            // not fill the viewport yet
            if (this.notebookConfig.windowingMode === 'defer' &&
                this.viewportNode.clientHeight < this.node.clientHeight) {
                // Spend more time rendering cells to fill the viewport
                await this._runOnIdleTime();
            }
            else {
                this._scheduleCellRenderOnIdle();
            }
        }
        else {
            if (this._idleCallBack) {
                window.cancelIdleCallback(this._idleCallBack);
                this._idleCallBack = null;
            }
        }
    }
    async _updateForDeferMode(cell, cellIdx) {
        cell.dataset.windowedListIndex = `${cellIdx}`;
        this.layout.insertWidget(cellIdx, cell);
        await cell.ready;
    }
    /**
     * Apply updated notebook settings.
     */
    _updateNotebookConfig() {
        // Apply scrollPastEnd setting.
        this.toggleClass('jp-mod-scrollPastEnd', this._notebookConfig.scrollPastEnd);
        // Control visibility of heading collapser UI
        this.toggleClass(HEADING_COLLAPSER_VISBILITY_CONTROL_CLASS, this._notebookConfig.showHiddenCellsButton);
        // Control editor visibility for read-only Markdown cells
        const showEditorForReadOnlyMarkdown = this._notebookConfig.showEditorForReadOnlyMarkdown;
        if (showEditorForReadOnlyMarkdown !== undefined) {
            for (const cell of this.cellsArray) {
                if (cell.model.type === 'markdown') {
                    cell.showEditorForReadOnly =
                        showEditorForReadOnlyMarkdown;
                }
            }
        }
        this.viewModel.windowingActive =
            this._notebookConfig.windowingMode === 'full';
        // Apply content visibility when notebook settings update (without reload)
        const isContentVisibility = this._notebookConfig.windowingMode === 'contentVisibility';
        this.viewportNode.classList.toggle('jp-content-visibility-mode', isContentVisibility);
        if (isContentVisibility) {
            requestAnimationFrame(() => {
                this.cellsArray.forEach((cell, i) => {
                    const estHeight = this._viewModel.estimateWidgetSize(i);
                    cell.node.style.containIntrinsicSize = `auto ${estHeight}px`;
                });
            });
            this._setupContentVisibilityObserver();
        }
        else {
            // Remove intrinsic size styling when disabling content visibility
            this.cellsArray.forEach(cell => {
                cell.node.style.removeProperty('contain-intrinsic-size');
                cell.node.style.removeProperty('content-visibility');
                cell.node.style.removeProperty('contain');
            });
            // Disconnect observer if it exists
            if (this._contentVisibilityObserver) {
                this._contentVisibilityObserver.disconnect();
                this._contentVisibilityObserver = null;
            }
        }
    }
    onAfterAttach(msg) {
        super.onAfterAttach(msg);
        // Apply content visibility when notebook widget is attached to the DOM
        if (this._notebookConfig.windowingMode === 'contentVisibility') {
            this.viewportNode.classList.toggle('jp-content-visibility-mode', true);
            // Update intrinsic sizes for all cells initially
            requestAnimationFrame(() => {
                this.cellsArray.forEach((cell, i) => {
                    const estHeight = this._viewModel.estimateWidgetSize(i);
                    cell.node.style.containIntrinsicSize = `auto ${estHeight}px`;
                    cell.node.style.contentVisibility = 'auto';
                    cell.node.style.contain = 'layout style paint';
                });
            });
            // Use the real browser viewport for visibility detection
            this._setupContentVisibilityObserver();
        }
    }
    _setupContentVisibilityObserver() {
        var _a;
        if (!this._contentVisibilityObserver) {
            // Create observer only once
            this._contentVisibilityObserver = new IntersectionObserver(entries => {
                for (const entry of entries) {
                    const cell = entry.target;
                    if (entry.isIntersecting) {
                        cell.style.contentVisibility = 'visible';
                        cell.style.contain = 'style';
                    }
                    else {
                        cell.style.contentVisibility = 'auto';
                        cell.style.contain = 'layout style paint';
                    }
                }
            }, { root: null, threshold: 0.1 });
        }
        // Observe all existing cells
        this.cellsArray.forEach(cell => this._contentVisibilityObserver.observe(cell.node));
        // Watch for newly added cells and set intrinsic size for them too
        (_a = this.model) === null || _a === void 0 ? void 0 : _a.cells.changed.connect(() => {
            requestAnimationFrame(() => {
                this.cellsArray.forEach((cell, i) => {
                    const estHeight = this._viewModel.estimateWidgetSize(i);
                    cell.node.style.containIntrinsicSize = `auto ${estHeight}px`;
                    this._contentVisibilityObserver.observe(cell.node);
                });
            });
        });
    }
}
/**
 * The namespace for the `StaticNotebook` class statics.
 */
(function (StaticNotebook) {
    /**
     * Default configuration options for cell editors.
     */
    StaticNotebook.defaultEditorConfig = {
        code: {
            lineNumbers: false,
            lineWrap: false,
            matchBrackets: true,
            tabFocusable: false
        },
        markdown: {
            lineNumbers: false,
            lineWrap: true,
            matchBrackets: false,
            tabFocusable: false
        },
        raw: {
            lineNumbers: false,
            lineWrap: true,
            matchBrackets: false,
            tabFocusable: false
        }
    };
    /**
     * Default configuration options for notebooks.
     */
    StaticNotebook.defaultNotebookConfig = {
        enableKernelInitNotification: false,
        showHiddenCellsButton: true,
        scrollPastEnd: true,
        defaultCell: 'code',
        recordTiming: false,
        inputHistoryScope: 'global',
        maxNumberOutputs: 50,
        showEditorForReadOnlyMarkdown: true,
        disableDocumentWideUndoRedo: true,
        autoRenderMarkdownCells: false,
        renderingLayout: 'default',
        sideBySideLeftMarginOverride: '10px',
        sideBySideRightMarginOverride: '10px',
        sideBySideOutputRatio: 1,
        overscanCount: 1,
        windowingMode: 'contentVisibility',
        accessKernelHistory: false,
        showInputPlaceholder: true,
        showMinimap: false
    };
    /**
     * The default implementation of an `IContentFactory`.
     */
    class ContentFactory extends Cell.ContentFactory {
        /**
         * Create a new code cell widget.
         *
         * #### Notes
         * If no cell content factory is passed in with the options, the one on the
         * notebook content factory is used.
         */
        createCodeCell(options) {
            return new CodeCell(options).initializeState();
        }
        /**
         * Create a new markdown cell widget.
         *
         * #### Notes
         * If no cell content factory is passed in with the options, the one on the
         * notebook content factory is used.
         */
        createMarkdownCell(options) {
            return new MarkdownCell(options).initializeState();
        }
        /**
         * Create a new raw cell widget.
         *
         * #### Notes
         * If no cell content factory is passed in with the options, the one on the
         * notebook content factory is used.
         */
        createRawCell(options) {
            return new RawCell(options).initializeState();
        }
    }
    StaticNotebook.ContentFactory = ContentFactory;
})(StaticNotebook || (StaticNotebook = {}));
/**
 * A virtual scrollbar item representing a notebook cell.
 */
class ScrollbarItem {
    /**
     * Construct a scrollbar item.
     */
    constructor(options) {
        /**
         * Render the scrollbar item as an HTML element.
         */
        this.render = (props) => {
            if (!this._element) {
                this._element = this._createElement();
                this._notebook.activeCellChanged.connect(this._updateActive);
                this._notebook.selectionChanged.connect(this._updateSelection);
                if (this._model.type === 'code') {
                    const model = this._model;
                    model.outputs.changed.connect(this._updatePrompt);
                    model.stateChanged.connect(this._updateState);
                }
            }
            // Add cell type (code/markdown/raw)
            if (this._model.type != this._element.dataset.type) {
                this._element.dataset.type = this._model.type;
            }
            const source = this._model.sharedModel.source;
            const trimmedSource = source.length > 10000 ? source.substring(0, 10000) : source;
            if (trimmedSource !== this._source.textContent) {
                this._source.textContent = trimmedSource;
            }
            this._updateActive();
            this._updateSelection();
            this._updatePrompt();
            this._updateDirty();
            return this._element;
        };
        /**
         * Dispose of the resources held by the item.
         */
        this.dispose = () => {
            this._isDisposed = true;
            this._notebook.activeCellChanged.disconnect(this._updateActive);
            this._notebook.selectionChanged.disconnect(this._updateSelection);
            if (this._model.type === 'code') {
                const model = this._model;
                if (model.outputs) {
                    model.outputs.changed.disconnect(this._updatePrompt);
                    model.stateChanged.disconnect(this._updateState);
                }
            }
        };
        this._updateState = (_, change) => {
            // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
            switch (change.name) {
                case 'executionCount':
                case 'executionState':
                    this._updatePrompt();
                    break;
                case 'isDirty': {
                    this._updateDirty();
                    break;
                }
            }
        };
        this._updatePrompt = () => {
            if (this._model.type !== 'code') {
                return;
            }
            const model = this._model;
            let hasError = false;
            for (let i = 0; i < model.outputs.length; i++) {
                const output = model.outputs.get(i);
                if (output.type === 'error') {
                    hasError = true;
                    break;
                }
            }
            let content;
            let state = '';
            if (hasError) {
                content = '[!]';
                state = 'error';
            }
            else if (model.executionState == 'running') {
                content = '[*]';
            }
            else if (model.executionCount) {
                content = `[${model.executionCount}]`;
            }
            else {
                content = '[ ]';
            }
            if (this._executionIndicator.textContent !== content) {
                this._executionIndicator.textContent = content;
            }
            if (this._element.dataset.output !== state) {
                this._element.dataset.output = state;
            }
        };
        this._updateActive = () => {
            var _a;
            if (!this._element) {
                this._element = this._createElement();
            }
            const li = this._element;
            const wasActive = li.classList.contains(ACTIVE_CLASS);
            if (((_a = this._notebook.activeCell) === null || _a === void 0 ? void 0 : _a.model) === this._model) {
                if (!wasActive) {
                    li.classList.add(ACTIVE_CLASS);
                }
            }
            else if (wasActive) {
                li.classList.remove(ACTIVE_CLASS);
                // Needed due to order in which selection and active changed signals fire
                li.classList.remove(SELECTED_CLASS);
            }
        };
        this._updateSelection = () => {
            if (!this._element) {
                this._element = this._createElement();
            }
            const li = this._element;
            const wasSelected = li.classList.contains(SELECTED_CLASS);
            if (this._notebook.selectedCells.some(cell => this._model === cell.model)) {
                if (!wasSelected) {
                    li.classList.add(SELECTED_CLASS);
                }
            }
            else if (wasSelected) {
                li.classList.remove(SELECTED_CLASS);
            }
        };
        this._isDisposed = false;
        this._element = null;
        // Note: there should be no DOM operations in the constructor
        this._model = options.model;
        this._notebook = options.notebook;
    }
    /**
     * Unique item key used for caching.
     */
    get key() {
        return this._model.id;
    }
    /**
     * Test whether the item has been disposed.
     */
    get isDisposed() {
        // Ensure the state is up-to-date in case if the model was disposed
        // (the model can be disposed when cells are moved/recreated).
        if (!this._isDisposed && this._model.isDisposed) {
            this.dispose();
        }
        return this._isDisposed;
    }
    _updateDirty() {
        if (this._model.type !== 'code' || !this._element) {
            return;
        }
        const model = this._model;
        const wasDirty = this._element.classList.contains(DIRTY_CLASS);
        if (wasDirty !== model.isDirty) {
            if (model.isDirty) {
                this._element.classList.add(DIRTY_CLASS);
            }
            else {
                this._element.classList.remove(DIRTY_CLASS);
            }
        }
    }
    _createElement() {
        const li = document.createElement('li');
        const executionIndicator = (this._executionIndicator =
            document.createElement('div'));
        executionIndicator.className = 'jp-scrollbarItem-executionIndicator';
        const source = (this._source = document.createElement('div'));
        source.className = 'jp-scrollbarItem-source';
        li.append(executionIndicator);
        li.append(source);
        return li;
    }
}
/**
 * A notebook widget that supports interactivity.
 */
export class Notebook extends StaticNotebook {
    /**
     * Construct a notebook widget.
     */
    constructor(options) {
        const trans = (options.translator || nullTranslator).load('jupyterlab');
        super({
            renderer: {
                createOuter() {
                    return document.createElement('div');
                },
                createViewport() {
                    const el = document.createElement('div');
                    el.setAttribute('role', 'feed');
                    el.setAttribute('aria-label', trans.__('Cells'));
                    return el;
                },
                createScrollbar() {
                    return document.createElement('ol');
                },
                createScrollbarViewportIndicator() {
                    return document.createElement('div');
                },
                createScrollbarItem(notebook, _index, model) {
                    return new ScrollbarItem({
                        notebook,
                        model
                    });
                }
            },
            ...options
        });
        this._lastModifiedCellBackStack = [];
        this._lastModifiedCellForwardStack = [];
        this._activeCellIndex = -1;
        this._activeCell = null;
        this._mode = 'command';
        this._drag = null;
        this._dragData = null;
        this._selectData = null;
        this._mouseMode = null;
        this._activeCellChanged = new Signal(this);
        this._stateChanged = new Signal(this);
        this._selectionChanged = new Signal(this);
        this._cellsPasted = new Signal(this);
        this._localCopy = [];
        this._checkCacheOnNextResize = false;
        this._lastClipboardInteraction = null;
        this._selectedCells = [];
        // Allow the node to scroll while dragging items.
        this.outerNode.setAttribute('data-lm-dragscroll', 'true');
        this.activeCellChanged.connect(this._updateSelectedCells, this);
        this.jumped.connect((_, index) => (this.activeCellIndex = index));
        this.selectionChanged.connect(this._updateSelectedCells, this);
        this.addFooter();
    }
    /**
     * List of selected and active cells
     */
    get selectedCells() {
        return this._selectedCells;
    }
    /**
     * Adds a footer to the notebook.
     */
    addFooter() {
        const info = new NotebookFooter(this);
        this.layout.footer = info;
    }
    /**
     * Handle a change cells event.
     */
    _onCellsChanged(sender, args) {
        var _a, _b;
        const activeCellId = (_a = this.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
        super._onCellsChanged(sender, args);
        if (activeCellId) {
            const newActiveCellIndex = (_b = this.model) === null || _b === void 0 ? void 0 : _b.sharedModel.cells.findIndex(cell => cell.getId() === activeCellId);
            if (newActiveCellIndex != null) {
                this.activeCellIndex = newActiveCellIndex;
            }
        }
        switch (args.type) {
            case 'add':
            case 'set':
                args.oldValues.forEach(model => {
                    model.contentChanged.disconnect(this._onCellContentChanged, this);
                });
                args.newValues.forEach(model => {
                    model.contentChanged.connect(this._onCellContentChanged, this);
                });
                break;
            case 'remove':
            case 'clear':
                args.oldValues.forEach(model => {
                    if (model) {
                        model.contentChanged.disconnect(this._onCellContentChanged, this);
                    }
                });
                break;
            case 'move':
                break;
            default:
                break;
        }
    }
    /**
     * A signal emitted when the active cell changes.
     *
     * #### Notes
     * This can be due to the active index changing or the
     * cell at the active index changing.
     */
    get activeCellChanged() {
        return this._activeCellChanged;
    }
    /**
     * A signal emitted when the state of the notebook changes.
     */
    get stateChanged() {
        return this._stateChanged;
    }
    /**
     * A signal emitted when the selection state of the notebook changes.
     */
    get selectionChanged() {
        return this._selectionChanged;
    }
    /**
     * A signal emitted when cells are pasted to the notebook.
     */
    get cellsPasted() {
        return this._cellsPasted;
    }
    /**
     * The interactivity mode of the notebook.
     */
    get mode() {
        return this._mode;
    }
    set mode(newValue) {
        this.setMode(newValue);
    }
    /**
     * Set the notebook mode.
     *
     * @param newValue Notebook mode
     * @param options Control mode side-effect
     * @param options.focus Whether to ensure focus (default) or not when setting the mode.
     */
    setMode(newValue, options = {}) {
        var _a;
        const setFocus = (_a = options.focus) !== null && _a !== void 0 ? _a : true;
        const activeCell = this.activeCell;
        if (!activeCell) {
            newValue = 'command';
        }
        if (newValue === this._mode) {
            if (setFocus) {
                this._ensureFocus();
            }
            return;
        }
        // Post an update request.
        this.update();
        const oldValue = this._mode;
        this._mode = newValue;
        if (newValue === 'edit') {
            // Edit mode deselects all cells.
            for (const widget of this.widgets) {
                this.deselect(widget);
            }
            // Edit mode unrenders an active markdown widget.
            if (activeCell instanceof MarkdownCell) {
                activeCell.rendered = false;
            }
            activeCell.inputHidden = false;
        }
        else {
            if (setFocus) {
                void NotebookActions.focusActiveCell(this, {
                    // Do not await the active cell because that creates a bug. If the user
                    // is editing a code cell and presses Accel Shift C to open the command
                    // palette, then the command palette opens before
                    // activeCell.node.focus() is called, which closes the command palette.
                    // To the end user, it looks as if all the keyboard shortcut did was
                    // move focus from the cell editor to the cell as a whole.
                    waitUntilReady: false,
                    preventScroll: true
                });
            }
        }
        this._stateChanged.emit({ name: 'mode', oldValue, newValue });
        if (setFocus) {
            this._ensureFocus();
        }
    }
    /**
     * The active cell index of the notebook.
     *
     * #### Notes
     * The index will be clamped to the bounds of the notebook cells.
     */
    get activeCellIndex() {
        if (!this.model) {
            return -1;
        }
        return this.widgets.length ? this._activeCellIndex : -1;
    }
    set activeCellIndex(newValue) {
        var _a, _b;
        const oldValue = this._activeCellIndex;
        if (!this.model || !this.widgets.length) {
            newValue = -1;
        }
        else {
            newValue = Math.max(newValue, 0);
            newValue = Math.min(newValue, this.widgets.length - 1);
        }
        this._activeCellIndex = newValue;
        const oldCell = (_a = this.widgets[oldValue]) !== null && _a !== void 0 ? _a : null;
        const cell = (_b = this.widgets[newValue]) !== null && _b !== void 0 ? _b : null;
        this.layout.activeCell = cell;
        const cellChanged = cell !== this._activeCell;
        if (cellChanged) {
            // Post an update request.
            this.update();
            this._activeCell = cell;
        }
        if (cellChanged || newValue != oldValue) {
            this._activeCellChanged.emit(cell);
        }
        if (this.mode === 'edit') {
            if (cell instanceof MarkdownCell) {
                cell.rendered = false;
            }
            if (this.notebookConfig.autoRenderMarkdownCells &&
                cellChanged &&
                oldCell instanceof MarkdownCell) {
                oldCell.rendered = true;
            }
        }
        this._ensureFocus();
        if (newValue === oldValue) {
            return;
        }
        this._trimSelections();
        this._stateChanged.emit({ name: 'activeCellIndex', oldValue, newValue });
    }
    /**
     * Get the active cell widget.
     *
     * #### Notes
     * This is a cell or `null` if there is no active cell.
     */
    get activeCell() {
        return this._activeCell;
    }
    get lastClipboardInteraction() {
        return this._lastClipboardInteraction;
    }
    set lastClipboardInteraction(newValue) {
        this._lastClipboardInteraction = newValue;
    }
    /**
     * This function should be called when a clipboard interaction involve notebook cells.
     *
     * @param interaction - the clipboard interaction (copy, cut or paste).
     * @param cells - the cells copied, cut or pasted.
     */
    recordCellClipboardInteraction(interaction, cells) {
        const lastInteraction = this._lastClipboardInteraction;
        if (interaction === 'copy' || interaction === 'cut') {
            this._localCopy = cells;
        }
        else if (interaction === 'paste') {
            const pasted = cells;
            // Check if the current clipboard match the previously copied/cut cells.
            const isLocal = ArrayExt.shallowEqual(pasted, this._localCopy, JSONExt.deepEqual);
            // Emit a signal with the last interaction and the number of pasted cells.
            this._cellsPasted.emit({
                previousInteraction: isLocal ? lastInteraction : null,
                cellCount: pasted.length
            });
        }
        // Update the last clipboard interaction.
        this.lastClipboardInteraction = interaction;
    }
    /**
     * Dispose of the resources held by the widget.
     */
    dispose() {
        if (this.isDisposed) {
            return;
        }
        this._activeCell = null;
        super.dispose();
    }
    /**
     * Move cells preserving widget view state.
     *
     * #### Notes
     * This is required because at the model level a move is a deletion
     * followed by an insertion. Hence the view state is not preserved.
     *
     * @param from The index of the cell to move
     * @param to The new index of the cell
     * @param n Number of cells to move
     */
    moveCell(from, to, n = 1) {
        // Save active cell id to be restored
        const newActiveCellIndex = from <= this.activeCellIndex && this.activeCellIndex < from + n
            ? this.activeCellIndex + to - from - (from > to ? 0 : n - 1)
            : -1;
        const isSelected = this.widgets
            .slice(from, from + n)
            .map(w => this.isSelected(w));
        super.moveCell(from, to, n);
        if (newActiveCellIndex >= 0) {
            this.activeCellIndex = newActiveCellIndex;
        }
        if (from > to) {
            isSelected.forEach((selected, idx) => {
                if (selected) {
                    this.select(this.widgets[to + idx]);
                }
            });
        }
        else {
            isSelected.forEach((selected, idx) => {
                if (selected) {
                    this.select(this.widgets[to - n + 1 + idx]);
                }
            });
        }
    }
    /**
     * Select a cell widget.
     *
     * #### Notes
     * It is a no-op if the value does not change.
     * It will emit the `selectionChanged` signal.
     */
    select(widget) {
        if (Private.selectedProperty.get(widget)) {
            return;
        }
        Private.selectedProperty.set(widget, true);
        this._selectionChanged.emit(void 0);
        this.update();
    }
    /**
     * Deselect a cell widget.
     *
     * #### Notes
     * It is a no-op if the value does not change.
     * It will emit the `selectionChanged` signal.
     */
    deselect(widget) {
        if (!Private.selectedProperty.get(widget)) {
            return;
        }
        Private.selectedProperty.set(widget, false);
        this._selectionChanged.emit(void 0);
        this.update();
    }
    /**
     * Whether a cell is selected.
     */
    isSelected(widget) {
        return Private.selectedProperty.get(widget);
    }
    /**
     * Whether a cell is selected or is the active cell.
     */
    isSelectedOrActive(widget) {
        if (widget === this._activeCell) {
            return true;
        }
        return Private.selectedProperty.get(widget);
    }
    /**
     * Deselect all of the cells.
     */
    deselectAll() {
        let changed = false;
        for (const widget of this.widgets) {
            if (Private.selectedProperty.get(widget)) {
                changed = true;
            }
            Private.selectedProperty.set(widget, false);
        }
        if (changed) {
            this._selectionChanged.emit(void 0);
        }
        // Make sure we have a valid active cell.
        // eslint-disable-next-line no-self-assign
        this.activeCellIndex = this.activeCellIndex;
        this.update();
    }
    /**
     * Move the head of an existing contiguous selection to extend the selection.
     *
     * @param index - The new head of the existing selection.
     *
     * #### Notes
     * If there is no existing selection, the active cell is considered an
     * existing one-cell selection.
     *
     * If the new selection is a single cell, that cell becomes the active cell
     * and all cells are deselected.
     *
     * There is no change if there are no cells (i.e., activeCellIndex is -1).
     */
    extendContiguousSelectionTo(index) {
        let { head, anchor } = this.getContiguousSelection();
        let i;
        // Handle the case of no current selection.
        if (anchor === null || head === null) {
            if (index === this.activeCellIndex) {
                // Already collapsed selection, nothing more to do.
                return;
            }
            // We will start a new selection below.
            head = this.activeCellIndex;
            anchor = this.activeCellIndex;
        }
        // Move the active cell. We do this before the collapsing shortcut below.
        this.activeCellIndex = index;
        // Make sure the index is valid, according to the rules for setting and clipping the
        // active cell index. This may change the index.
        index = this.activeCellIndex;
        // Collapse the selection if it is only the active cell.
        if (index === anchor) {
            this.deselectAll();
            return;
        }
        let selectionChanged = false;
        if (head < index) {
            if (head < anchor) {
                Private.selectedProperty.set(this.widgets[head], false);
                selectionChanged = true;
            }
            // Toggle everything strictly between head and index except anchor.
            for (i = head + 1; i < index; i++) {
                if (i !== anchor) {
                    Private.selectedProperty.set(this.widgets[i], !Private.selectedProperty.get(this.widgets[i]));
                    selectionChanged = true;
                }
            }
        }
        else if (index < head) {
            if (anchor < head) {
                Private.selectedProperty.set(this.widgets[head], false);
                selectionChanged = true;
            }
            // Toggle everything strictly between index and head except anchor.
            for (i = index + 1; i < head; i++) {
                if (i !== anchor) {
                    Private.selectedProperty.set(this.widgets[i], !Private.selectedProperty.get(this.widgets[i]));
                    selectionChanged = true;
                }
            }
        }
        // Anchor and index should *always* be selected.
        if (!Private.selectedProperty.get(this.widgets[anchor])) {
            selectionChanged = true;
        }
        Private.selectedProperty.set(this.widgets[anchor], true);
        if (!Private.selectedProperty.get(this.widgets[index])) {
            selectionChanged = true;
        }
        Private.selectedProperty.set(this.widgets[index], true);
        if (selectionChanged) {
            this._selectionChanged.emit(void 0);
        }
    }
    /**
     * Get the head and anchor of a contiguous cell selection.
     *
     * The head of a contiguous selection is always the active cell.
     *
     * If there are no cells selected, `{head: null, anchor: null}` is returned.
     *
     * Throws an error if the currently selected cells do not form a contiguous
     * selection.
     */
    getContiguousSelection() {
        const cells = this.widgets;
        const first = ArrayExt.findFirstIndex(cells, c => this.isSelected(c));
        // Return early if no cells are selected.
        if (first === -1) {
            return { head: null, anchor: null };
        }
        const last = ArrayExt.findLastIndex(cells, c => this.isSelected(c), -1, first);
        // Check that the selection is contiguous.
        for (let i = first; i <= last; i++) {
            if (!this.isSelected(cells[i])) {
                throw new Error('Selection not contiguous');
            }
        }
        // Check that the active cell is one of the endpoints of the selection.
        const activeIndex = this.activeCellIndex;
        if (first !== activeIndex && last !== activeIndex) {
            throw new Error('Active cell not at endpoint of selection');
        }
        // Determine the head and anchor of the selection.
        if (first === activeIndex) {
            return { head: first, anchor: last };
        }
        else {
            return { head: last, anchor: first };
        }
    }
    /**
     * Scroll so that the given cell is in view. Selects and activates cell.
     *
     * @param cell - A cell in the notebook widget.
     * @param align - Type of alignment.
     *
     */
    async scrollToCell(cell, align = 'auto') {
        try {
            await this.scrollToItem(this.widgets.findIndex(c => c === cell), align);
        }
        catch (r) {
            //no-op
        }
        // change selection and active cell:
        this.deselectAll();
        this.select(cell);
        cell.activate();
    }
    _parseFragment(fragment) {
        const cleanedFragment = fragment.slice(1);
        if (!cleanedFragment) {
            // Bail early
            return;
        }
        const parts = cleanedFragment.split('=');
        if (parts.length === 1) {
            // Default to legacy if no prefix is given.
            return {
                kind: 'legacy',
                value: cleanedFragment
            };
        }
        return {
            kind: parts[0],
            value: parts.slice(1).join('=')
        };
    }
    /**
     * Set URI fragment identifier.
     */
    async setFragment(fragment) {
        const parsedFragment = this._parseFragment(fragment);
        if (!parsedFragment) {
            // Bail early
            return;
        }
        let result;
        switch (parsedFragment.kind) {
            case 'heading':
                result = await this._findHeading(parsedFragment.value);
                break;
            case 'cell-id':
                result = this._findCellById(parsedFragment.value);
                break;
            case 'legacy':
                result = await this._findLegacy(parsedFragment.value);
                break;
            default:
                console.warn(`Unknown target type for URI fragment ${fragment}, interpreting as a heading`);
                result = await this._findLegacy(parsedFragment.kind + '=' + parsedFragment.value);
                break;
        }
        if (result == null) {
            return;
        }
        let { cell, element } = result;
        if (!cell.inViewport) {
            await this.scrollToCell(cell, 'center');
        }
        if (element == null) {
            element = cell.node;
        }
        // Scroll notebook to set the target on top.
        element.scrollIntoView();
    }
    /**
     * Handle the DOM events for the widget.
     *
     * @param event - The DOM event sent to the widget.
     *
     * #### Notes
     * This method implements the DOM `EventListener` interface and is
     * called in response to events on the notebook panel's node. It should
     * not be called directly by user code.
     */
    handleEvent(event) {
        if (!this.model) {
            return;
        }
        switch (event.type) {
            case 'contextmenu':
                if (event.eventPhase === Event.CAPTURING_PHASE) {
                    this._evtContextMenuCapture(event);
                }
                break;
            case 'mousedown':
                if (event.eventPhase === Event.CAPTURING_PHASE) {
                    this._evtMouseDownCapture(event);
                }
                else {
                    // Skip processing the event when it resulted from a toolbar button click
                    if (!event.defaultPrevented) {
                        this._evtMouseDown(event);
                    }
                }
                break;
            case 'mouseup':
                if (event.currentTarget === document) {
                    this._evtDocumentMouseup(event);
                }
                break;
            case 'mousemove':
                if (event.currentTarget === document) {
                    this._evtDocumentMousemove(event);
                }
                break;
            case 'keydown':
                // This works because CodeMirror does not stop the event propagation
                this._ensureFocus(true);
                break;
            case 'dblclick':
                this._evtDblClick(event);
                break;
            case 'focusin':
                this._evtFocusIn(event);
                break;
            case 'focusout':
                this._evtFocusOut(event);
                break;
            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;
            default:
                super.handleEvent(event);
                break;
        }
    }
    /**
     * Pop and return the top cell from the back stack, pushing to forward stack.
     * @experimental
     */
    popLastModifiedCell() {
        var _a;
        const activeId = (_a = this.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
        while (this._lastModifiedCellBackStack.length) {
            const id = this._lastModifiedCellBackStack.pop();
            // Skip active cell
            if (id === activeId) {
                this._lastModifiedCellForwardStack.push(id);
                continue;
            }
            const cell = this.widgets.find(c => c.model.id === id);
            if (cell && !cell.isDisposed) {
                this._lastModifiedCellForwardStack.push(id);
                return cell;
            }
        }
        return null;
    }
    /**
     * Pop and return the top cell from the forward stack, pushing to back stack.
     * @experimental
     */
    popNextModifiedCell() {
        var _a;
        const activeId = (_a = this.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
        while (this._lastModifiedCellForwardStack.length) {
            const id = this._lastModifiedCellForwardStack.pop();
            if (id === activeId) {
                this._lastModifiedCellBackStack.push(id);
                continue;
            }
            const cell = this.widgets.find(c => c.model.id === id);
            if (cell && !cell.isDisposed) {
                this._lastModifiedCellBackStack.push(id);
                return cell;
            }
        }
        return null;
    }
    /**
     * Check if there is a navigable cell in the back stack.
     * @experimental
     */
    hasNavigableModifiedCellBack() {
        var _a;
        const activeId = (_a = this.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
        for (let i = this._lastModifiedCellBackStack.length - 1; i >= 0; i--) {
            const id = this._lastModifiedCellBackStack[i];
            if (id !== activeId) {
                const cell = this.widgets.find(c => c.model.id === id);
                if (cell && !cell.isDisposed) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Check if there is a navigable cell in the forward stack.
     * @experimental
     */
    hasNavigableModifiedCellForward() {
        var _a;
        const activeId = (_a = this.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
        for (let i = this._lastModifiedCellForwardStack.length - 1; i >= 0; i--) {
            const id = this._lastModifiedCellForwardStack[i];
            if (id !== activeId) {
                const cell = this.widgets.find(c => c.model.id === id);
                if (cell && !cell.isDisposed) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Handle `after-attach` messages for the widget.
     */
    onAfterAttach(msg) {
        super.onAfterAttach(msg);
        const node = this.node;
        node.addEventListener('contextmenu', this, true);
        node.addEventListener('mousedown', this, true);
        node.addEventListener('mousedown', this);
        node.addEventListener('keydown', this);
        node.addEventListener('dblclick', this);
        node.addEventListener('focusin', this);
        node.addEventListener('focusout', this);
        // Capture drag events for the notebook widget
        // in order to preempt the drag/drop handlers in the
        // code editor widgets, which can take text data.
        node.addEventListener('lm-dragenter', this, true);
        node.addEventListener('lm-dragleave', this, true);
        node.addEventListener('lm-dragover', this, true);
        node.addEventListener('lm-drop', this, true);
    }
    /**
     * Handle `before-detach` messages for the widget.
     */
    onBeforeDetach(msg) {
        const node = this.node;
        node.removeEventListener('contextmenu', this, true);
        node.removeEventListener('mousedown', this, true);
        node.removeEventListener('mousedown', this);
        node.removeEventListener('keydown', this);
        node.removeEventListener('dblclick', this);
        node.removeEventListener('focusin', this);
        node.removeEventListener('focusout', this);
        node.removeEventListener('lm-dragenter', this, true);
        node.removeEventListener('lm-dragleave', this, true);
        node.removeEventListener('lm-dragover', this, true);
        node.removeEventListener('lm-drop', this, true);
        document.removeEventListener('mousemove', this, true);
        document.removeEventListener('mouseup', this, true);
        super.onBeforeDetach(msg);
    }
    /**
     * A message handler invoked on an `'after-show'` message.
     */
    onAfterShow(msg) {
        super.onAfterShow(msg);
        this._checkCacheOnNextResize = true;
    }
    /**
     * A message handler invoked on a `'resize'` message.
     */
    onResize(msg) {
        var _a;
        // TODO
        if (!this._checkCacheOnNextResize) {
            return super.onResize(msg);
        }
        super.onResize(msg);
        this._checkCacheOnNextResize = false;
        const cache = this._cellLayoutStateCache;
        const width = parseInt(this.node.style.width, 10);
        if (cache) {
            if (width === cache.width) {
                // Cache identical, do nothing
                return;
            }
        }
        // Update cache
        this._cellLayoutStateCache = { width };
        // Fallback:
        for (const w of this.widgets) {
            if (w instanceof Cell && w.inViewport) {
                (_a = w.editorWidget) === null || _a === void 0 ? void 0 : _a.update();
            }
        }
    }
    /**
     * A message handler invoked on an `'before-hide'` message.
     */
    onBeforeHide(msg) {
        super.onBeforeHide(msg);
        // Update cache
        const width = parseInt(this.node.style.width, 10);
        this._cellLayoutStateCache = { width };
    }
    /**
     * Handle `'activate-request'` messages.
     */
    onActivateRequest(msg) {
        super.onActivateRequest(msg);
        this._ensureFocus(true);
    }
    /**
     * Handle `update-request` messages sent to the widget.
     */
    onUpdateRequest(msg) {
        super.onUpdateRequest(msg);
        const activeCell = this.activeCell;
        // Set the appropriate classes on the cells.
        if (this.mode === 'edit') {
            this.addClass(EDIT_CLASS);
            this.removeClass(COMMAND_CLASS);
        }
        else {
            this.addClass(COMMAND_CLASS);
            this.removeClass(EDIT_CLASS);
        }
        let count = 0;
        for (const widget of this.widgets) {
            // Set tabIndex to -1 to allow calling .focus() on cell without allowing
            // focus via tab key. This allows focus (document.activeElement) to move
            // up and down the document, cell by cell, when the user presses J/K or
            // ArrowDown/ArrowUp, but (unlike tabIndex = 0) does not add the notebook
            // cells (which could be numerous) to the set of nodes that the user would
            // have to visit when pressing the tab key to move about the UI.
            // NOTE: we need to be very careful to avoid modifying DOM to avoid triggering layout on scroll
            if (widget === activeCell) {
                activeCell.addClass(ACTIVE_CLASS);
                activeCell.addClass(SELECTED_CLASS);
                // Set tab index to 0 on the active cell so that if the user tabs away from
                // the notebook then tabs back, they will return to the cell where they
                // left off.
                activeCell.node.tabIndex = 0;
            }
            else {
                widget.node.tabIndex = -1;
                widget.removeClass(ACTIVE_CLASS);
                widget.removeClass(OTHER_SELECTED_CLASS);
            }
            if (this.isSelectedOrActive(widget)) {
                widget.addClass(SELECTED_CLASS);
                count++;
            }
            else {
                widget.removeClass(SELECTED_CLASS);
            }
        }
        if (activeCell && count > 1) {
            activeCell.addClass(OTHER_SELECTED_CLASS);
        }
    }
    /**
     * Handle a cell being inserted.
     */
    onCellInserted(index, cell) {
        void cell.ready.then(() => {
            if (!cell.isDisposed) {
                cell.editor.edgeRequested.connect(this._onEdgeRequest, this);
            }
        });
        cell.scrollRequested.connect((_emitter, scrollRequest) => {
            if (cell !== this.activeCell) {
                // Do nothing for cells other than the active cell
                // to avoid scroll requests from editor extensions
                // stealing user focus (this may be revisited).
                return;
            }
            if (!scrollRequest.defaultPrevented) {
                // Nothing to do if scroll request was already handled.
                return;
            }
            // Node which allows to scroll the notebook
            const scroller = this.outerNode;
            if (cell.inViewport) {
                // If cell got scrolled to the viewport in the meantime,
                // proceed with scrolling within the cell.
                return scrollRequest.scrollWithinCell({ scroller });
            }
            // If cell is not in the viewport and needs scrolling,
            // first scroll to the cell and then scroll within the cell.
            this.scrollToItem(this.activeCellIndex)
                .then(() => {
                void cell.ready.then(() => {
                    scrollRequest.scrollWithinCell({ scroller });
                });
            })
                .catch(reason => {
                // no-op
            });
        });
        // If the insertion happened above, increment the active cell
        // index, otherwise it stays the same.
        this.activeCellIndex =
            index <= this.activeCellIndex
                ? this.activeCellIndex + 1
                : this.activeCellIndex;
    }
    /**
     * Handle a cell being removed.
     */
    onCellRemoved(index, cell) {
        // If the removal happened above, decrement the active
        // cell index, otherwise it stays the same.
        this.activeCellIndex =
            index <= this.activeCellIndex
                ? this.activeCellIndex - 1
                : this.activeCellIndex;
        if (this.isSelected(cell)) {
            this._selectionChanged.emit(void 0);
        }
    }
    /**
     * Handle a new model.
     */
    onModelChanged(oldValue, newValue) {
        super.onModelChanged(oldValue, newValue);
        // Try to set the active cell index to 0.
        // It will be set to `-1` if there is no new model or the model is empty.
        this.activeCellIndex = 0;
    }
    /**
     * Handle edge request signals from cells.
     */
    _onEdgeRequest(editor, location) {
        // Ensure edit mode but do not focus yet; we ensure the edit mode
        // with focus trigger at the end, as a side-effect of `this.mode = 'edit'`,
        // which could be simplified in the future.
        this.setMode('edit', { focus: false });
        const prev = this.activeCellIndex;
        if (location === 'top') {
            this.activeCellIndex--;
            // Move the cursor to the first position on the last line.
            if (this.activeCellIndex < prev) {
                const editor = this.activeCell.editor;
                if (editor) {
                    const lastLine = editor.lineCount - 1;
                    editor.setCursorPosition({ line: lastLine, column: 0 });
                }
            }
        }
        else if (location === 'bottom') {
            this.activeCellIndex++;
            // Move the cursor to the first character.
            if (this.activeCellIndex > prev) {
                const editor = this.activeCell.editor;
                if (editor) {
                    editor.setCursorPosition({ line: 0, column: 0 });
                }
            }
        }
        // This is potentially no longer needed with the `setMode` call at the top.
        this.mode = 'edit';
    }
    /**
     * Ensure that the notebook has proper focus.
     */
    _ensureFocus(force = false) {
        var _a, _b;
        const activeElement = document.activeElement;
        if (!force && activeElement && !this.node.contains(activeElement)) {
            return;
        }
        // No-op is the footer has the focus.
        const footer = this.layout.footer;
        if (footer && document.activeElement === footer.node) {
            return;
        }
        const activeCell = this.activeCell;
        if (this.mode === 'edit' && activeCell) {
            // Test for !== true to cover hasFocus is false and editor is not yet rendered.
            if (((_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.hasFocus()) !== true) {
                if (activeCell.inViewport) {
                    (_b = activeCell.editor) === null || _b === void 0 ? void 0 : _b.focus();
                }
                else {
                    this.scrollToItem(this.activeCellIndex)
                        .then(() => {
                        void activeCell.ready.then(() => {
                            var _a;
                            (_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.focus();
                        });
                    })
                        .catch(reason => {
                        // no-op
                    });
                }
            }
        }
        if (force &&
            activeCell &&
            !activeCell.node.contains(document.activeElement)) {
            void NotebookActions.focusActiveCell(this, {
                preventScroll: true
            });
        }
    }
    /**
     * Find the cell containing a node, traversing open shadow roots.
     *
     * Returned cell is `null` if the node is not in a cell of this notebook.
     */
    _cellContaining(node) {
        let current = node;
        while (current) {
            const element = current instanceof Element ? current : current.parentElement;
            if (element) {
                const index = this._findCell(element);
                if (index !== -1) {
                    return this.widgets[index];
                }
            }
            const root = (element !== null && element !== void 0 ? element : current).getRootNode();
            current = root instanceof ShadowRoot ? root.host : null;
        }
        return null;
    }
    /**
     * Whether a shift-click on `target` should be left to the browser to handle
     * as an extension of the current text selection, rather than be turned into
     * a cell range selection.
     *
     * This is the case when the click lands in a region of a cell whose text the
     * browser selects - the output area of a code cell, or the rendered input of
     * a rendered markdown cell - and the current selection starts in such a
     * region too; see https://github.com/jupyterlab/jupyterlab/issues/4800. A
     * click on a cell prompt, on cell chrome, or on a cell without such a region
     * (an unrendered markdown cell, a code cell without outputs) always selects
     * cells.
     *
     * #### Notes
     * Only the anchor of the selection is considered, because a shift-click
     * moves the focus and leaves the anchor in place. The anchor may sit in a
     * different cell than the one clicked, which is what allows a selection to
     * be extended across the outputs of several cells; and disregarding the
     * focus keeps this working when a drag overshot the edge of an output and
     * left the focus outside of it.
     *
     * This trade-off between extending the text selection and extending the cell
     * selection can be adjusted in the future. For now the text selection wins to
     * preserve the behaviour from before refactor that users may be accustomed to.
     */
    _isExtendingTextSelection(selection, targetCell, target) {
        if (!selection || selection.isCollapsed) {
            return false;
        }
        if (!Private.isInTextRegion(targetCell, target)) {
            return false;
        }
        const { anchorNode } = selection;
        // The selection usually starts in the very cell which was clicked, which
        // can be checked without having to locate the cell of the anchor.
        if (Private.isInTextRegion(targetCell, anchorNode)) {
            return true;
        }
        const anchorCell = this._cellContaining(anchorNode);
        return !!anchorCell && Private.isInTextRegion(anchorCell, anchorNode);
    }
    /**
     * Find the cell index containing the target html element.
     *
     * #### Notes
     * Returns -1 if the cell is not found.
     */
    _findCell(node) {
        // Trace up the DOM hierarchy to find the root cell node.
        // Then find the corresponding child and select it.
        let n = node;
        while (n && n !== this.node) {
            if (n.classList.contains(NB_CELL_CLASS)) {
                const i = ArrayExt.findFirstIndex(this.widgets, widget => widget.node === n);
                if (i !== -1) {
                    return i;
                }
                break;
            }
            n = n.parentElement;
        }
        return -1;
    }
    /**
     * Find the target of html mouse event and cell index containing this target.
     *
     * #### Notes
     * Returned index is -1 if the cell is not found.
     */
    _findEventTargetAndCell(event) {
        let target = event.target;
        let index = this._findCell(target);
        if (index === -1) {
            // `event.target` sometimes gives an orphaned node in Firefox 57, which
            // can have `null` anywhere in its parent line. If we fail to find a cell
            // using `event.target`, try again using a target reconstructed from the
            // position of the click event.
            target = document.elementFromPoint(event.clientX, event.clientY);
            index = this._findCell(target);
        }
        return [target, index];
    }
    /**
     * Find heading with given ID in any of the cells.
     */
    async _findHeading(queryId) {
        var _a;
        // Loop on cells, get headings and search for first matching id.
        for (let cellIdx = 0; cellIdx < this.widgets.length; cellIdx++) {
            const cell = this.widgets[cellIdx];
            if (cell.model.type === 'raw' ||
                (cell.model.type === 'markdown' && !cell.rendered)) {
                // Bail early
                continue;
            }
            for (const heading of cell.headings) {
                let id = '';
                switch (heading.type) {
                    case Cell.HeadingType.HTML:
                        id = heading.id;
                        break;
                    case Cell.HeadingType.Markdown:
                        {
                            const mdHeading = heading;
                            id = await TableOfContentsUtils.Markdown.getHeadingId(this.rendermime.markdownParser, mdHeading.raw, mdHeading.level, this.rendermime.sanitizer);
                        }
                        break;
                }
                if (id === queryId) {
                    const attribute = ((_a = this.rendermime.sanitizer.allowNamedProperties) !== null && _a !== void 0 ? _a : false)
                        ? 'id'
                        : 'data-jupyter-id';
                    const element = this.node.querySelector(`h${heading.level}[${attribute}="${CSS.escape(id)}"]`);
                    return {
                        cell,
                        element
                    };
                }
            }
        }
        return null;
    }
    /**
     * Find cell by its unique ID.
     */
    _findCellById(queryId) {
        for (let cellIdx = 0; cellIdx < this.widgets.length; cellIdx++) {
            const cell = this.widgets[cellIdx];
            if (cell.model.id === queryId) {
                return {
                    cell
                };
            }
        }
        return null;
    }
    /**
     * Find anchor target by any ID in any of the cell.
     */
    async _findLegacy(queryId) {
        var _a;
        // Loop on cells, search for first matching id.
        for (let cellIdx = 0; cellIdx < this.widgets.length; cellIdx++) {
            const cell = this.widgets[cellIdx];
            if (cell.model.type === 'raw' ||
                (cell.model.type === 'markdown' && !cell.rendered)) {
                // Bail early
                continue;
            }
            const attribute = ((_a = this.rendermime.sanitizer.allowNamedProperties) !== null && _a !== void 0 ? _a : false)
                ? 'id'
                : 'data-jupyter-id';
            const element = cell.node.querySelector(`[${attribute}="${CSS.escape(queryId)}"]`);
            if (element !== null) {
                return {
                    cell,
                    element: element
                };
            }
        }
        return null;
    }
    /**
     * Handle `contextmenu` event.
     */
    _evtContextMenuCapture(event) {
        var _a;
        // Allow the event to propagate un-modified if the user
        // is holding the shift-key (and probably requesting
        // the native context menu).
        if (event.shiftKey) {
            return;
        }
        const [target, index] = this._findEventTargetAndCell(event);
        const widget = this.widgets[index];
        if (widget && ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target))) {
            // Prevent CodeMirror from focusing the editor.
            // TODO: find an editor-agnostic solution.
            if (!target.closest('[data-jp-suppress-context-menu]')) {
                event.preventDefault();
            }
        }
    }
    /**
     * Handle `mousedown` event in the capture phase for the widget.
     */
    _evtMouseDownCapture(event) {
        var _a;
        const { button, shiftKey } = event;
        const [target, index] = this._findEventTargetAndCell(event);
        const widget = this.widgets[index];
        // On OS X, the context menu may be triggered with ctrl-left-click. In
        // Firefox, ctrl-left-click gives an event with button 2, but in Chrome,
        // ctrl-left-click gives an event with button 0 with the ctrl modifier.
        if (button === 2 &&
            !shiftKey &&
            widget &&
            ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target))) {
            this.mode = 'command';
            // Prevent CodeMirror from focusing the editor.
            // TODO: find an editor-agnostic solution.
            event.preventDefault();
        }
    }
    /**
     * Handle `mousedown` events for the widget.
     */
    _evtMouseDown(event) {
        var _a, _b, _c;
        const { button, shiftKey } = event;
        // We only handle main or secondary button actions.
        if (!(button === 0 || button === 2)) {
            return;
        }
        // Shift right-click gives the browser default behavior.
        if (shiftKey && button === 2) {
            return;
        }
        const [target, index] = this._findEventTargetAndCell(event);
        const widget = this.widgets[index];
        let targetArea;
        if (widget) {
            if ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target)) {
                targetArea = 'input';
            }
            else if ((_b = widget.promptNode) === null || _b === void 0 ? void 0 : _b.contains(target)) {
                targetArea = 'prompt';
            }
            else {
                targetArea = 'cell';
            }
        }
        else {
            targetArea = 'notebook';
        }
        // Make sure we go to command mode if the click isn't in the cell editor If
        // we do click in the cell editor, the editor handles the focus event to
        // switch to edit mode.
        if (targetArea !== 'input') {
            this.mode = 'command';
        }
        if (targetArea === 'notebook') {
            this.deselectAll();
        }
        else if (targetArea === 'prompt' || targetArea === 'cell') {
            if (button === 0 &&
                shiftKey &&
                !['INPUT', 'OPTION'].includes(target.tagName) &&
                // We don't want to prevent the default selection behavior when the
                // user is extending a text selection into the clicked region.
                !this._isExtendingTextSelection(window.getSelection(), widget, target)) {
                // Prevent browser selecting text in prompt or output
                event.preventDefault();
                // Preventing the default also stops the browser from collapsing any
                // text selection which is already there; drop it explicitly so that
                // stale highlighted text is not left over the new cell selection.
                (_c = window.getSelection()) === null || _c === void 0 ? void 0 : _c.removeAllRanges();
                // Shift-click - extend selection
                try {
                    this.extendContiguousSelectionTo(index);
                }
                catch (e) {
                    console.error(e);
                    this.deselectAll();
                    return;
                }
                // Enter selecting mode
                this._mouseMode = 'select';
                // We don't want to block the shift-click mouse up handler
                // when the current cell is (and remains) the active cell.
                this._selectData = {
                    startedOnActiveCell: index == this.activeCellIndex,
                    startingCellIndex: this.activeCellIndex
                };
                document.addEventListener('mouseup', this, true);
                document.addEventListener('mousemove', this, true);
            }
            else if (button === 0 && !shiftKey) {
                // Prepare to start a drag if we are on the drag region.
                if (targetArea === 'prompt') {
                    // Prepare for a drag start
                    this._dragData = {
                        pressX: event.clientX,
                        pressY: event.clientY,
                        index: index
                    };
                    // Enter possible drag mode
                    this._mouseMode = 'couldDrag';
                    document.addEventListener('mouseup', this, true);
                    document.addEventListener('mousemove', this, true);
                    event.preventDefault();
                }
                if (!this.isSelectedOrActive(widget)) {
                    this.deselectAll();
                    this.activeCellIndex = index;
                }
            }
            else if (button === 2) {
                if (!this.isSelectedOrActive(widget)) {
                    this.deselectAll();
                    this.activeCellIndex = index;
                }
                event.preventDefault();
            }
        }
        else if (targetArea === 'input') {
            if (button === 2 && !this.isSelectedOrActive(widget)) {
                this.deselectAll();
                this.activeCellIndex = index;
            }
        }
        // If we didn't set focus above, make sure we get focus now.
        this._ensureFocus(true);
    }
    /**
     * Handle the `'mouseup'` event on the document.
     */
    _evtDocumentMouseup(event) {
        const [, index] = this._findEventTargetAndCell(event);
        let shouldPreventDefault = true;
        if (this._mouseMode === 'select' && this._selectData) {
            // User did not move the mouse over to a difference cell, so there was no selection
            const { startedOnActiveCell, startingCellIndex } = this._selectData;
            if (startedOnActiveCell && index === startingCellIndex) {
                shouldPreventDefault = false;
            }
            this._selectData = null;
        }
        if (shouldPreventDefault) {
            event.preventDefault();
            event.stopPropagation();
        }
        // Remove the event listeners we put on the document
        document.removeEventListener('mousemove', this, true);
        document.removeEventListener('mouseup', this, true);
        if (this._mouseMode === 'couldDrag') {
            // We didn't end up dragging if we are here, so treat it as a click event.
            this.deselectAll();
            this.activeCellIndex = index;
            // Focus notebook if active cell changes but does not have focus.
            if (!this.activeCell.node.contains(document.activeElement)) {
                void NotebookActions.focusActiveCell(this);
            }
        }
        this._mouseMode = null;
    }
    /**
     * Handle the `'mousemove'` event for the widget.
     */
    _evtDocumentMousemove(event) {
        event.preventDefault();
        event.stopPropagation();
        // If in select mode, update the selection
        // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
        switch (this._mouseMode) {
            case 'select': {
                const target = event.target;
                const index = this._findCell(target);
                if (index !== -1) {
                    this.extendContiguousSelectionTo(index);
                }
                break;
            }
            case 'couldDrag': {
                // Check for a drag initialization.
                const data = this._dragData;
                const dx = Math.abs(event.clientX - data.pressX);
                const dy = Math.abs(event.clientY - data.pressY);
                if (dx >= DRAG_THRESHOLD || dy >= DRAG_THRESHOLD) {
                    this._mouseMode = null;
                    this._startDrag(data.index, event.clientX, event.clientY);
                }
                break;
            }
            default:
                break;
        }
    }
    /**
     * Handle the `'lm-dragenter'` event for the widget.
     */
    _evtDragEnter(event) {
        if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        const target = event.target;
        const index = this._findCell(target);
        if (index === -1) {
            return;
        }
        const widget = this.cellsArray[index];
        widget.node.classList.add(DROP_TARGET_CLASS);
    }
    /**
     * Handle the `'lm-dragleave'` event for the widget.
     */
    _evtDragLeave(event) {
        if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        const elements = this.node.getElementsByClassName(DROP_TARGET_CLASS);
        if (elements.length) {
            elements[0].classList.remove(DROP_TARGET_CLASS);
        }
    }
    /**
     * Handle the `'lm-dragover'` event for the widget.
     */
    _evtDragOver(event) {
        if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        event.dropAction = event.proposedAction;
        const elements = this.node.getElementsByClassName(DROP_TARGET_CLASS);
        if (elements.length) {
            elements[0].classList.remove(DROP_TARGET_CLASS);
        }
        const target = event.target;
        const index = this._findCell(target);
        if (index === -1) {
            return;
        }
        const widget = this.cellsArray[index];
        widget.node.classList.add(DROP_TARGET_CLASS);
    }
    /**
     * Handle the `'lm-drop'` event for the widget.
     */
    _evtDrop(event) {
        if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        if (event.proposedAction === 'none') {
            event.dropAction = 'none';
            return;
        }
        let target = event.target;
        while (target && target.parentElement) {
            if (target.classList.contains(DROP_TARGET_CLASS)) {
                target.classList.remove(DROP_TARGET_CLASS);
                break;
            }
            target = target.parentElement;
        }
        // Model presence should be checked before calling event handlers
        const model = this.model;
        const source = event.source;
        if (source === this) {
            // Handle the case where we are moving cells within
            // the same notebook.
            event.dropAction = 'move';
            const toMove = event.mimeData.getData('internal:cells');
            // For collapsed markdown headings with hidden "child" cells, move all
            // child cells as well as the markdown heading.
            const cell = toMove[toMove.length - 1];
            if (cell instanceof MarkdownCell && cell.headingCollapsed) {
                const nextParent = NotebookActions.findNextParentHeading(cell, source);
                if (nextParent > 0) {
                    const index = findIndex(source.widgets, (possibleCell) => {
                        return cell.model.id === possibleCell.model.id;
                    });
                    toMove.push(...source.widgets.slice(index + 1, nextParent));
                }
            }
            // Compute the to/from indices for the move.
            let fromIndex = ArrayExt.firstIndexOf(this.widgets, toMove[0]);
            let toIndex = this._findCell(target);
            // This check is needed for consistency with the view.
            if (toIndex !== -1 && toIndex > fromIndex) {
                toIndex -= 1;
            }
            else if (toIndex === -1) {
                // If the drop is within the notebook but not on any cell,
                // most often this means it is past the cell areas, so
                // set it to move the cells to the end of the notebook.
                toIndex = this.widgets.length - 1;
            }
            // Don't move if we are within the block of selected cells.
            if (toIndex >= fromIndex && toIndex < fromIndex + toMove.length) {
                return;
            }
            // Move the selected block of cells, preserving in-flight executions.
            NotebookActions.moveCells(this, fromIndex, toIndex, toMove.length);
        }
        else {
            // Handle the case where we are copying cells between
            // notebooks.
            event.dropAction = 'copy';
            // Find the target cell and insert the copied cells.
            let index = this._findCell(target);
            if (index === -1) {
                index = this.widgets.length;
            }
            const start = index;
            const values = event.mimeData.getData(JUPYTER_CELL_MIME);
            // Insert the copies of the original cells.
            // We preserve trust status of pasted cells by not modifying metadata.
            model.sharedModel.insertCells(index, values);
            // Select the inserted cells.
            this.deselectAll();
            this.activeCellIndex = start;
            this.extendContiguousSelectionTo(index - 1);
        }
        void NotebookActions.focusActiveCell(this);
    }
    /**
     * Start a drag event.
     */
    _startDrag(index, clientX, clientY) {
        var _a;
        const cells = this.model.cells;
        const selected = [];
        const toMove = [];
        let i = -1;
        for (const widget of this.widgets) {
            const cell = cells.get(++i);
            if (this.isSelectedOrActive(widget)) {
                widget.addClass(DROP_SOURCE_CLASS);
                selected.push(cell.toJSON());
                toMove.push(widget);
            }
        }
        const activeCell = this.activeCell;
        let dragImage = null;
        let countString;
        if ((activeCell === null || activeCell === void 0 ? void 0 : activeCell.model.type) === 'code') {
            const executionCount = activeCell.model
                .executionCount;
            countString = ' ';
            if (executionCount) {
                countString = executionCount.toString();
            }
        }
        else {
            countString = '';
        }
        // Create the drag image.
        dragImage = Private.createDragImage(selected.length, countString, (_a = activeCell === null || activeCell === void 0 ? void 0 : activeCell.model.sharedModel.getSource().split('\n')[0].slice(0, 26)) !== null && _a !== void 0 ? _a : '');
        // Set up the drag event.
        this._drag = new Drag({
            mimeData: new MimeData(),
            dragImage,
            supportedActions: 'copy-move',
            proposedAction: 'copy',
            source: this
        });
        this._drag.mimeData.setData(JUPYTER_CELL_MIME, selected);
        // Add mimeData for the fully reified cell widgets, for the
        // case where the target is in the same notebook and we
        // can just move the cells.
        this._drag.mimeData.setData('internal:cells', toMove);
        // Add mimeData for the text content of the selected cells,
        // allowing for drag/drop into plain text fields.
        const textContent = toMove
            .map(cell => cell.model.sharedModel.getSource())
            .join('\n');
        this._drag.mimeData.setData('text/plain', textContent);
        // Remove mousemove and mouseup listeners and start the drag.
        document.removeEventListener('mousemove', this, true);
        document.removeEventListener('mouseup', this, true);
        this._mouseMode = null;
        void this._drag.start(clientX, clientY).then(action => {
            if (this.isDisposed) {
                return;
            }
            this._drag = null;
            for (const widget of toMove) {
                widget.removeClass(DROP_SOURCE_CLASS);
            }
        });
    }
    /**
     * Update the notebook node with class indicating read-write state.
     */
    _updateReadWrite() {
        const inReadWrite = DOMUtils.hasActiveEditableElement(this.node);
        if (this.node.classList.contains(READ_WRITE_CLASS) === inReadWrite) {
            return;
        }
        this.node.classList.toggle(READ_WRITE_CLASS, inReadWrite);
    }
    /**
     * Handle `focus` events for the widget.
     */
    _evtFocusIn(event) {
        var _a, _b;
        // Update read-write class state.
        this._updateReadWrite();
        const target = event.target;
        const index = this._findCell(target);
        if (index !== -1) {
            const widget = this.widgets[index];
            // If the editor itself does not have focus, ensure command mode.
            if (widget.editorWidget && !widget.editorWidget.node.contains(target)) {
                this.setMode('command', { focus: false });
            }
            // Cell index needs to be updated before changing mode,
            // otherwise the previous cell may get un-rendered.
            this.activeCellIndex = index;
            // If the editor has focus, ensure edit mode.
            const node = (_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node;
            if (node === null || node === void 0 ? void 0 : node.contains(target)) {
                this.setMode('edit', { focus: false });
            }
        }
        else {
            // No cell has focus, ensure command mode.
            this.setMode('command', { focus: false });
            // Prevents the parent element to get the focus.
            event.preventDefault();
            // Check if the focus was previously in the active cell to avoid focus looping
            // between the cell and the cell toolbar.
            const source = event.relatedTarget;
            // Focuses on the active cell if the focus did not come from it.
            // Otherwise focus on the footer element (add cell button).
            if (this._activeCell && !this._activeCell.node.contains(source)) {
                this._activeCell.ready
                    .then(() => {
                    var _a;
                    (_a = this._activeCell) === null || _a === void 0 ? void 0 : _a.node.focus({
                        preventScroll: true
                    });
                })
                    .catch(() => {
                    var _a;
                    (_a = this.layout.footer) === null || _a === void 0 ? void 0 : _a.node.focus({
                        preventScroll: true
                    });
                });
            }
            else {
                (_b = this.layout.footer) === null || _b === void 0 ? void 0 : _b.node.focus({
                    preventScroll: true
                });
            }
        }
    }
    /**
     * Handle `focusout` events for the notebook.
     */
    _evtFocusOut(event) {
        var _a;
        const relatedTarget = event.relatedTarget;
        // Bail if the window is losing focus, to preserve edit mode. This test
        // assumes that we explicitly focus things rather than calling blur()
        if (!relatedTarget) {
            // Update read-write class state.
            this._updateReadWrite();
            return;
        }
        // Bail if the item gaining focus is another cell,
        // and we should not be entering command mode.
        const index = this._findCell(relatedTarget);
        if (index !== -1) {
            const widget = this.widgets[index];
            if ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(relatedTarget)) {
                // Do not update read-write state as there is no real loss of focus,
                // instead we are just switching to a different cell
                return;
            }
        }
        // Otherwise enter command mode if not already.
        if (this.mode !== 'command') {
            this.setMode('command', { focus: false });
        }
        // Update read-write class state.
        this._updateReadWrite();
    }
    /**
     * Handle `dblclick` events for the widget.
     */
    _evtDblClick(event) {
        const model = this.model;
        if (!model) {
            return;
        }
        this.deselectAll();
        const [target, index] = this._findEventTargetAndCell(event);
        if (event.target.classList.contains(HEADING_COLLAPSER_CLASS)) {
            return;
        }
        if (index === -1) {
            return;
        }
        this.activeCellIndex = index;
        if (model.cells.get(index).type === 'markdown') {
            const widget = this.widgets[index];
            widget.rendered = false;
        }
        else if (target.localName === 'img') {
            target.classList.toggle(UNCONFINED_CLASS);
        }
    }
    /**
     * Remove selections from inactive cells to avoid
     * spurious cursors.
     */
    _trimSelections() {
        for (let i = 0; i < this.widgets.length; i++) {
            if (i !== this._activeCellIndex) {
                const cell = this.widgets[i];
                if (!cell.model.isDisposed && cell.editor) {
                    cell.model.selections.delete(cell.editor.uuid);
                    // Collapse the editor's visual selection to the cursor position
                    // to avoid multiple cells showing highlighted text.
                    const cursor = cell.editor.getCursorPosition();
                    cell.editor.setCursorPosition(cursor, { scroll: false });
                }
            }
        }
    }
    /**
     * Handle a cell content change.
     */
    _onCellContentChanged(sender) {
        const id = sender.id;
        const backStack = this._lastModifiedCellBackStack;
        // Do nothing if the last modified cell is the same as this one
        if (backStack.length > 0 && backStack[backStack.length - 1] === id) {
            return;
        }
        // Remove if already present elsewhere in the stack to move it to the top
        const idx = backStack.lastIndexOf(id);
        if (idx !== -1) {
            backStack.splice(idx, 1);
        }
        backStack.push(id);
        if (backStack.length > Notebook.MAX_MODIFIED_STACK) {
            backStack.shift();
        }
        // Clear forward stack when a new cell is modified
        this._lastModifiedCellForwardStack = [];
        this._stateChanged.emit({
            name: 'lastModifiedCellStack',
            oldValue: null,
            newValue: null
        });
    }
    _updateSelectedCells() {
        this._selectedCells = this.widgets.filter(cell => this.isSelectedOrActive(cell));
        if (this.kernelHistory) {
            this.kernelHistory.reset();
        }
    }
}
Notebook.MAX_MODIFIED_STACK = 50;
/**
 * The namespace for the `Notebook` class statics.
 */
(function (Notebook) {
    /**
     * The default implementation of a notebook content factory..
     *
     * #### Notes
     * Override methods on this class to customize the default notebook factory
     * methods that create notebook content.
     */
    class ContentFactory extends StaticNotebook.ContentFactory {
    }
    Notebook.ContentFactory = ContentFactory;
})(Notebook || (Notebook = {}));
/**
 * A namespace for private data.
 */
var Private;
(function (Private) {
    /**
     * An attached property for the selected state of a cell.
     */
    Private.selectedProperty = new AttachedProperty({
        name: 'selected',
        create: () => false
    });
    /**
     * A custom panel layout for the notebook.
     */
    class NotebookPanelLayout extends PanelLayout {
        /**
         * A message handler invoked on an `'update-request'` message.
         *
         * #### Notes
         * This is a reimplementation of the base class method,
         * and is a no-op.
         */
        onUpdateRequest(msg) {
            // This is a no-op.
        }
    }
    Private.NotebookPanelLayout = NotebookPanelLayout;
    /**
     * Check whether `node` is `parent` or a descendant of it.
     *
     * Unlike `Node.contains()` this traverses out of open shadow roots, so a
     * node rendered by a widget library which attaches a shadow root (such as
     * Panel or Bokeh) is still recognised as belonging to its host subtree.
     */
    function containsDeep(parent, node) {
        let current = node;
        while (current) {
            if (parent.contains(current)) {
                return true;
            }
            const root = current.getRootNode();
            current = root instanceof ShadowRoot ? root.host : null;
        }
        return false;
    }
    /**
     * Whether a cell exposes an output area.
     *
     * #### Notes
     * This is a structural check rather than `instanceof CodeCell` so that it
     * still holds when a federated extension loads its own copy of
     * `@jupyterlab/cells`.
     */
    function hasOutputArea(cell) {
        return 'outputArea' in cell;
    }
    /**
     * The regions of a cell whose text the browser, rather than the notebook,
     * is responsible for selecting: the output area of a code cell and the
     * rendered input of a cell which renders it, such as a rendered markdown
     * cell.
     */
    function textRegionsOf(cell) {
        var _a;
        const regions = [];
        if (hasOutputArea(cell)) {
            regions.push(cell.outputArea.node);
        }
        const rendered = (_a = cell.inputArea) === null || _a === void 0 ? void 0 : _a.renderedInput;
        if (rendered) {
            regions.push(rendered.node);
        }
        return regions;
    }
    /**
     * Whether a node is in a region of a cell whose text the browser selects.
     */
    function isInTextRegion(cell, node) {
        return textRegionsOf(cell).some(region => containsDeep(region, node));
    }
    Private.isInTextRegion = isInTextRegion;
    /**
     * Create a cell drag image.
     */
    function createDragImage(count, promptNumber, cellContent) {
        if (count > 1) {
            if (promptNumber !== '') {
                return VirtualDOM.realize(h.div(h.div({ className: DRAG_IMAGE_CLASS }, h.span({ className: CELL_DRAG_PROMPT_CLASS }, '[' + promptNumber + ']:'), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)), h.div({ className: CELL_DRAG_MULTIPLE_BACK }, '')));
            }
            else {
                return VirtualDOM.realize(h.div(h.div({ className: DRAG_IMAGE_CLASS }, h.span({ className: CELL_DRAG_PROMPT_CLASS }), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)), h.div({ className: CELL_DRAG_MULTIPLE_BACK }, '')));
            }
        }
        else {
            if (promptNumber !== '') {
                return VirtualDOM.realize(h.div(h.div({ className: `${DRAG_IMAGE_CLASS} ${SINGLE_DRAG_IMAGE_CLASS}` }, h.span({ className: CELL_DRAG_PROMPT_CLASS }, '[' + promptNumber + ']:'), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent))));
            }
            else {
                return VirtualDOM.realize(h.div(h.div({ className: `${DRAG_IMAGE_CLASS} ${SINGLE_DRAG_IMAGE_CLASS}` }, h.span({ className: CELL_DRAG_PROMPT_CLASS }), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent))));
            }
        }
    }
    Private.createDragImage = createDragImage;
})(Private || (Private = {}));
//# sourceMappingURL=widget.js.map