UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

1,397 lines 55.8 kB
"use strict";
// *****************************************************************************
// Copyright (C) 2018 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
var TreeWidget_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TreeWidget = exports.defaultTreeProps = exports.TreeProps = exports.SCROLL_BOTTOM_THRESHOLD = exports.TREE_NODE_INDENT_GUIDE_CLASS = exports.TREE_NODE_CAPTION_CLASS = exports.COMPOSITE_TREE_NODE_CLASS = exports.EXPANDABLE_TREE_NODE_CLASS = exports.TREE_NODE_SEGMENT_GROW_CLASS = exports.TREE_NODE_SEGMENT_CLASS = exports.TREE_NODE_TAIL_CLASS = exports.TREE_NODE_INFO_CLASS = exports.TREE_NODE_CONTENT_CLASS = exports.TREE_NODE_CLASS = exports.TREE_CONTAINER_CLASS = exports.TREE_CLASS = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const common_1 = require("../../common");
const keys_1 = require("../keyboard/keys");
const context_menu_renderer_1 = require("../context-menu-renderer");
const widgets_1 = require("../widgets");
const tree_1 = require("./tree");
const tree_model_1 = require("./tree-model");
const tree_expansion_1 = require("./tree-expansion");
const tree_selection_1 = require("./tree-selection");
const tree_decorator_1 = require("./tree-decorator");
const objects_1 = require("../../common/objects");
const os_1 = require("../../common/os");
const react_widget_1 = require("../widgets/react-widget");
const React = require("react");
const react_virtuoso_1 = require("react-virtuoso");
const tree_iterator_1 = require("./tree-iterator");
const search_box_1 = require("./search-box");
const tree_search_1 = require("./tree-search");
const domutils_1 = require("@lumino/domutils");
const tree_widget_selection_1 = require("./tree-widget-selection");
const label_provider_1 = require("../label-provider");
const core_preferences_1 = require("../../common/core-preferences");
const tree_focus_service_1 = require("./tree-focus-service");
const react_1 = require("react");
const tree_preference_1 = require("../../common/tree-preference");
const preferences_1 = require("../../common/preferences");
const debounce = require('lodash.debounce');
exports.TREE_CLASS = 'theia-Tree';
exports.TREE_CONTAINER_CLASS = 'theia-TreeContainer';
exports.TREE_NODE_CLASS = 'theia-TreeNode';
exports.TREE_NODE_CONTENT_CLASS = 'theia-TreeNodeContent';
exports.TREE_NODE_INFO_CLASS = 'theia-TreeNodeInfo';
exports.TREE_NODE_TAIL_CLASS = 'theia-TreeNodeTail';
exports.TREE_NODE_SEGMENT_CLASS = 'theia-TreeNodeSegment';
exports.TREE_NODE_SEGMENT_GROW_CLASS = 'theia-TreeNodeSegmentGrow';
exports.EXPANDABLE_TREE_NODE_CLASS = 'theia-ExpandableTreeNode';
exports.COMPOSITE_TREE_NODE_CLASS = 'theia-CompositeTreeNode';
exports.TREE_NODE_CAPTION_CLASS = 'theia-TreeNodeCaption';
exports.TREE_NODE_INDENT_GUIDE_CLASS = 'theia-tree-node-indent';
/**
 * Threshold in pixels to consider the view as being scrolled to the bottom.
 */
exports.SCROLL_BOTTOM_THRESHOLD = 30;
exports.TreeProps = Symbol('TreeProps');
/**
 * The default tree properties.
 */
exports.defaultTreeProps = {
    leftPadding: 8,
    expansionTogglePadding: 22
};
let TreeWidget = TreeWidget_1 = class TreeWidget extends react_widget_1.ReactWidget {
    constructor(props, model, contextMenuRenderer) {
        super();
        this.props = props;
        this.model = model;
        this.contextMenuRenderer = contextMenuRenderer;
        this.onAtBottomStateChangeEmitter = new common_1.Emitter();
        this.onAtBottomStateChange = this.onAtBottomStateChangeEmitter.event;
        this.decorations = new Map();
        this.shouldScrollToRow = true;
        this.treeIndent = 8;
        this.rows = new Map();
        this.updateRows = debounce(() => this.doUpdateRows(), 10);
        this.scheduleUpdateScrollToRow = debounce(this.updateScrollToRow);
        /**
         * Update tree decorations.
         * - Updating decorations are debounced in order to limit the number of expensive updates.
         */
        this.updateDecorations = debounce(() => this.doUpdateDecorations(), 150);
        this.ScrollingRowRenderer = ({ rows }) => {
            (0, react_1.useEffect)(() => this.scrollToSelected());
            return React.createElement(React.Fragment, null, rows.map(row => React.createElement("div", { key: row.index }, this.renderNodeRow(row))));
        };
        this.scrollArea = this.node;
        /**
         * Render the node row.
         */
        this.renderNodeRow = (row) => this.doRenderNodeRow(row);
        /**
         * Toggle the node.
         */
        this.toggle = (event) => this.doToggle(event);
        /**
         * Handle the double-click mouse event on the expansion toggle.
         */
        this.handleExpansionToggleDblClickEvent = (event) => this.doHandleExpansionToggleDblClickEvent(event);
        this.scrollOptions = {
            suppressScrollX: true,
            minScrollbarLength: 35
        };
        this.addClass(exports.TREE_CLASS);
        this.node.tabIndex = 0;
    }
    init() {
        this.treeIndent = this.preferenceService.get(tree_preference_1.PREFERENCE_NAME_TREE_INDENT, this.treeIndent);
        if (this.props.search) {
            this.searchBox = this.searchBoxFactory({ ...search_box_1.SearchBoxProps.DEFAULT, showButtons: true, showFilter: true });
            this.searchBox.node.addEventListener('focus', () => {
                this.node.focus();
            });
            this.toDispose.pushAll([
                this.searchBox,
                this.searchBox.onTextChange(async (data) => {
                    await this.treeSearch.filter(data);
                    this.searchHighlights = this.treeSearch.getHighlights();
                    this.searchBox.updateHighlightInfo({
                        filterText: data,
                        total: this.rows.size,
                        matched: this.searchHighlights.size
                    });
                    this.update();
                }),
                this.searchBox.onClose(data => this.treeSearch.filter(undefined)),
                this.searchBox.onNext(() => {
                    // Enable next selection if there are currently highlights.
                    if (this.searchHighlights.size > 1) {
                        this.model.selectNextNode();
                    }
                }),
                this.searchBox.onPrevious(() => {
                    // Enable previous selection if there are currently highlights.
                    if (this.searchHighlights.size > 1) {
                        this.model.selectPrevNode();
                    }
                }),
                this.searchBox.onFilterToggled(e => {
                    this.updateRows();
                }),
                this.treeSearch,
                this.treeSearch.onFilteredNodesChanged(nodes => {
                    if (this.searchBox.isFiltering) {
                        this.updateRows();
                    }
                    const node = nodes.find(tree_selection_1.SelectableTreeNode.is);
                    if (node) {
                        this.model.selectNode(node);
                    }
                }),
            ]);
        }
        this.node.addEventListener('mousedown', this.handleMiddleClickEvent.bind(this));
        this.node.addEventListener('mouseup', this.handleMiddleClickEvent.bind(this));
        this.node.addEventListener('auxclick', this.handleMiddleClickEvent.bind(this));
        this.toDispose.pushAll([
            this.onAtBottomStateChangeEmitter,
            this.model,
            this.model.onChanged(() => this.updateRows()),
            this.model.onSelectionChanged(() => this.scheduleUpdateScrollToRow({ resize: false })),
            this.focusService.onDidChangeFocus(() => this.scheduleUpdateScrollToRow({ resize: false })),
            this.model.onDidChangeBusy(() => this.update()),
            this.model.onDidUpdate(() => this.update()),
            this.model.onNodeRefreshed(() => this.updateDecorations()),
            this.model.onExpansionChanged(() => this.updateDecorations()),
            this.decoratorService,
            this.decoratorService.onDidChangeDecorations(() => this.updateDecorations()),
            this.labelProvider.onDidChange(e => {
                for (const row of this.rows.values()) {
                    if (e.affects(row)) {
                        this.update();
                        return;
                    }
                }
            }),
            this.preferenceService.onPreferenceChanged((event) => {
                if (event.preferenceName === tree_preference_1.PREFERENCE_NAME_TREE_INDENT) {
                    this.treeIndent = this.preferenceService.get(tree_preference_1.PREFERENCE_NAME_TREE_INDENT, 8);
                    this.update();
                }
            })
        ]);
        setTimeout(() => {
            this.updateRows();
            this.updateDecorations();
        });
        if (this.props.globalSelection) {
            this.registerGlobalSelectionHandlers();
        }
        this.toDispose.push(this.corePreferences.onPreferenceChanged(preference => {
            if (preference.preferenceName === 'workbench.tree.renderIndentGuides') {
                this.update();
            }
        }));
    }
    registerGlobalSelectionHandlers() {
        this.model.onSelectionChanged(this.handleGlobalSelectionOnModelSelectionChange, this, this.toDispose);
        this.focusService.onDidChangeFocus(this.handleGlobalSelectionOnFocusServiceFocusChange, this, this.toDispose);
        this.toDispose.push((0, widgets_1.addEventListener)(this.node, 'focusin', this.handleGlobalSelectionOnFocusIn.bind(this)));
        this.toDispose.push(common_1.Disposable.create(this.handleGlobalSelectionOnDisposal.bind(this)));
    }
    handleGlobalSelectionOnModelSelectionChange() {
        if (this.shouldUpdateGlobalSelection()) {
            this.updateGlobalSelection();
        }
    }
    handleGlobalSelectionOnFocusServiceFocusChange(focus) {
        if (focus && this.shouldUpdateGlobalSelection() && this.model.selectedNodes[0] !== focus && this.model.selectedNodes.includes(focus)) {
            this.updateGlobalSelection();
        }
    }
    handleGlobalSelectionOnFocusIn() {
        if (this.model.selectedNodes.length && (!this.selectionService.selection || !tree_widget_selection_1.TreeWidgetSelection.isSource(this.selectionService.selection, this))) {
            this.updateGlobalSelection();
        }
    }
    handleGlobalSelectionOnDisposal() {
        if (tree_widget_selection_1.TreeWidgetSelection.isSource(this.selectionService.selection, this)) {
            this.selectionService.selection = undefined;
        }
    }
    shouldUpdateGlobalSelection() {
        return this.node.contains(document.activeElement) || tree_widget_selection_1.TreeWidgetSelection.isSource(this.selectionService.selection, this);
    }
    /**
     * Update the global selection for the tree.
     */
    updateGlobalSelection() {
        this.selectionService.selection = tree_widget_selection_1.TreeWidgetSelection.create(this);
    }
    doUpdateRows() {
        const root = this.model.root;
        const rowsToUpdate = [];
        if (root) {
            const depths = new Map();
            let index = 0;
            for (const node of new tree_iterator_1.TopDownTreeIterator(root, {
                pruneCollapsed: true,
                pruneSiblings: true
            })) {
                if (this.shouldDisplayNode(node)) {
                    const depth = this.getDepthForNode(node, depths);
                    if (tree_1.CompositeTreeNode.is(node)) {
                        depths.set(node, depth);
                    }
                    rowsToUpdate.push([node.id, this.toNodeRow(node, index++, depth)]);
                }
            }
        }
        this.rows = new Map(rowsToUpdate);
        this.update();
        this.scheduleUpdateScrollToRow();
    }
    getDepthForNode(node, depths) {
        const parentDepth = depths.get(node.parent);
        return parentDepth === undefined ? 0 : tree_1.TreeNode.isVisible(node.parent) ? parentDepth + 1 : parentDepth;
    }
    toNodeRow(node, index, depth) {
        return { node, index, depth };
    }
    shouldDisplayNode(node) {
        return tree_1.TreeNode.isVisible(node) && (!this.searchBox?.isFiltering || this.treeSearch.passesFilters(node));
    }
    /**
     * Update the `scrollToRow`.
     * @param updateOptions the tree widget force update options.
     */
    updateScrollToRow() {
        this.scrollToRow = this.getScrollToRow();
        this.update();
    }
    /**
     * Get the `scrollToRow`.
     *
     * @returns the `scrollToRow` if available.
     */
    getScrollToRow() {
        if (!this.shouldScrollToRow) {
            return undefined;
        }
        const { focusedNode } = this.focusService;
        return focusedNode && this.rows.get(focusedNode.id)?.index;
    }
    async doUpdateDecorations() {
        this.decorations = await this.decoratorService.getDecorations(this.model);
        this.update();
    }
    onActivateRequest(msg) {
        super.onActivateRequest(msg);
        this.node.focus({ preventScroll: true });
    }
    /**
     * Actually focus the tree node.
     */
    doFocus() {
        if (!this.model.selectedNodes.length) {
            const node = this.getNodeToFocus();
            if (tree_selection_1.SelectableTreeNode.is(node)) {
                this.model.selectNode(node);
            }
        }
    }
    /**
     * Get the tree node to focus.
     *
     * @returns the node to focus if available.
     */
    getNodeToFocus() {
        const { focusedNode } = this.focusService;
        if (focusedNode) {
            return focusedNode;
        }
        const { root } = this.model;
        if (tree_selection_1.SelectableTreeNode.isVisible(root)) {
            return root;
        }
        return this.model.getNextSelectableNode(root);
    }
    onUpdateRequest(msg) {
        if (!this.isAttached || !this.isVisible) {
            return;
        }
        super.onUpdateRequest(msg);
    }
    handleVisiblityChanged(isNowVisible) {
        super.handleVisiblityChanged(isNowVisible);
        if (isNowVisible) {
            this.update();
        }
    }
    onResize(msg) {
        super.onResize(msg);
        this.update();
    }
    render() {
        return React.createElement('div', this.createContainerAttributes(), this.renderTree(this.model));
    }
    /**
     * Create the container attributes for the widget.
     */
    createContainerAttributes() {
        const classNames = [exports.TREE_CONTAINER_CLASS];
        if (!this.rows.size) {
            classNames.push('empty');
        }
        if (this.model.selectedNodes.length === 0 && !this.focusService.focusedNode) {
            classNames.push('focused');
        }
        return {
            className: classNames.join(' '),
            onContextMenu: event => this.handleContextMenuEvent(this.getContainerTreeNode(), event)
        };
    }
    /**
     * Get the container tree node.
     *
     * @returns the tree node for the container if available.
     */
    getContainerTreeNode() {
        return this.model.root;
    }
    /**
     * Render the tree widget.
     * @param model the tree model.
     */
    renderTree(model) {
        if (model.root) {
            const rows = Array.from(this.rows.values());
            if (this.props.virtualized === false) {
                return React.createElement(this.ScrollingRowRenderer, { rows: rows });
            }
            return React.createElement(TreeWidget_1.View, { ref: view => { this.view = (view || undefined); }, width: this.node.offsetWidth, height: this.node.offsetHeight, rows: rows, renderNodeRow: this.renderNodeRow, scrollToRow: this.scrollToRow, onAtBottomStateChangeEmitter: this.onAtBottomStateChangeEmitter, ...this.props.viewProps });
        }
        // eslint-disable-next-line no-null/no-null
        return null;
    }
    /**
     * Scroll to the selected tree node.
     */
    scrollToSelected() {
        if (this.props.scrollIfActive === true && !this.node.contains(document.activeElement)) {
            return;
        }
        const focus = this.node.getElementsByClassName(widgets_1.FOCUS_CLASS)[0];
        if (focus) {
            domutils_1.ElementExt.scrollIntoViewIfNeeded(this.scrollArea, focus);
        }
        else {
            const selected = this.node.getElementsByClassName(widgets_1.SELECTED_CLASS)[0];
            if (selected) {
                domutils_1.ElementExt.scrollIntoViewIfNeeded(this.scrollArea, selected);
            }
        }
    }
    /**
     * Actually render the node row.
     */
    doRenderNodeRow({ node, depth }) {
        return React.createElement(React.Fragment, null,
            this.renderIndent(node, { depth }),
            this.renderNode(node, { depth }));
    }
    /**
     * Render the tree node given the node properties.
     * @param node the tree node.
     * @param props the node properties.
     */
    renderIcon(node, props) {
        // eslint-disable-next-line no-null/no-null
        return null;
    }
    /**
     * Actually toggle the tree node.
     * @param event the mouse click event.
     */
    doToggle(event) {
        const nodeId = event.currentTarget.getAttribute('data-node-id');
        if (nodeId) {
            const node = this.model.getNode(nodeId);
            if (node && this.props.expandOnlyOnExpansionToggleClick) {
                if (this.isExpandable(node) && !this.hasShiftMask(event) && !this.hasCtrlCmdMask(event)) {
                    this.model.toggleNodeExpansion(node);
                }
            }
            else {
                this.handleClickEvent(node, event);
            }
        }
        event.stopPropagation();
    }
    /**
     * Render the node expansion toggle.
     * @param node the tree node.
     * @param props the node properties.
     */
    renderExpansionToggle(node, props) {
        if (!this.isExpandable(node)) {
            // eslint-disable-next-line no-null/no-null
            return null;
        }
        const classes = [exports.TREE_NODE_SEGMENT_CLASS, widgets_1.EXPANSION_TOGGLE_CLASS];
        if (!node.expanded) {
            classes.push(widgets_1.COLLAPSED_CLASS);
        }
        if (node.busy) {
            classes.push(widgets_1.BUSY_CLASS, ...widgets_1.CODICON_LOADING_CLASSES);
        }
        else {
            classes.push(...widgets_1.CODICON_TREE_ITEM_CLASSES);
        }
        const className = classes.join(' ');
        return React.createElement("div", { "data-node-id": node.id, className: className, onClick: this.toggle, onDoubleClick: this.handleExpansionToggleDblClickEvent });
    }
    /**
     * Render the node expansion toggle.
     * @param node the tree node.
     * @param props the node properties.
     */
    renderCheckbox(node, props) {
        if (node.checkboxInfo === undefined) {
            // eslint-disable-next-line no-null/no-null
            return null;
        }
        return React.createElement("input", { "data-node-id": node.id, readOnly: true, type: 'checkbox', checked: !!node.checkboxInfo.checked, title: node.checkboxInfo.tooltip, "aria-label": node.checkboxInfo.accessibilityInformation?.label, role: node.checkboxInfo.accessibilityInformation?.role, className: 'theia-input', onClick: event => this.toggleChecked(event), onDoubleClick: event => event.stopPropagation() });
    }
    toggleChecked(event) {
        const nodeId = event.currentTarget.getAttribute('data-node-id');
        if (nodeId) {
            const node = this.model.getNode(nodeId);
            if (node) {
                this.model.markAsChecked(node, !node.checkboxInfo.checked);
            }
            else {
                this.handleClickEvent(node, event);
            }
        }
        event.preventDefault();
        event.stopPropagation();
    }
    /**
     * Render the tree node caption given the node properties.
     * @param node the tree node.
     * @param props the node properties.
     */
    renderCaption(node, props) {
        const attrs = this.getCaptionAttributes(node, props);
        const children = this.getCaptionChildren(node, props);
        return React.createElement('div', attrs, children);
    }
    getCaptionAttributes(node, props) {
        const tooltip = this.getDecorationData(node, 'tooltip').filter(objects_1.notEmpty).join(' • ');
        const classes = [exports.TREE_NODE_SEGMENT_CLASS];
        if (!this.hasTrailingSuffixes(node)) {
            classes.push(exports.TREE_NODE_SEGMENT_GROW_CLASS);
        }
        const className = classes.join(' ');
        let attrs = this.decorateCaption(node, {
            className, id: node.id
        });
        if (tooltip.length > 0) {
            attrs = {
                ...attrs,
                title: tooltip
            };
        }
        return attrs;
    }
    getCaptionChildren(node, props) {
        const children = [];
        const caption = this.toNodeName(node);
        const highlight = this.getDecorationData(node, 'highlight')[0];
        if (highlight) {
            children.push(this.toReactNode(caption, highlight));
        }
        const searchHighlight = this.searchHighlights ? this.searchHighlights.get(node.id) : undefined;
        if (searchHighlight) {
            children.push(...this.toReactNode(caption, searchHighlight));
        }
        else if (!highlight) {
            children.push(caption);
        }
        return children;
    }
    /**
     * Update the node given the caption and highlight.
     * @param caption the caption.
     * @param highlight the tree decoration caption highlight.
     */
    toReactNode(caption, highlight) {
        let style = {};
        if (highlight.color) {
            style = {
                ...style,
                color: highlight.color
            };
        }
        if (highlight.backgroundColor) {
            style = {
                ...style,
                backgroundColor: highlight.backgroundColor
            };
        }
        const createChildren = (fragment, index) => {
            const { data } = fragment;
            if (fragment.highlight) {
                return React.createElement("mark", { className: tree_decorator_1.TreeDecoration.Styles.CAPTION_HIGHLIGHT_CLASS, style: style, key: index }, data);
            }
            else {
                return data;
            }
        };
        return tree_decorator_1.TreeDecoration.CaptionHighlight.split(caption, highlight).map(createChildren);
    }
    /**
     * Decorate the tree caption.
     * @param node the tree node.
     * @param attrs the additional attributes.
     */
    decorateCaption(node, attrs) {
        const style = this.getDecorationData(node, 'fontData')
            .filter(objects_1.notEmpty)
            .reverse()
            .map(fontData => this.applyFontStyles({}, fontData))
            .reduce((acc, current) => ({
            ...acc,
            ...current
        }), {});
        return {
            ...attrs,
            style
        };
    }
    /**
     * Determine if the tree node contains trailing suffixes.
     * @param node the tree node.
     *
     * @returns `true` if the tree node contains trailing suffices.
     */
    hasTrailingSuffixes(node) {
        return this.getDecorationData(node, 'captionSuffixes').filter(objects_1.notEmpty).reduce((acc, current) => acc.concat(current), []).length > 0;
    }
    /**
     * Apply font styles to the tree.
     * @param original the original css properties.
     * @param fontData the optional `fontData`.
     */
    applyFontStyles(original, fontData) {
        if (fontData === undefined) {
            return original;
        }
        const modified = { ...original }; // make a copy to mutate
        const { color, style } = fontData;
        if (color) {
            modified.color = color;
        }
        if (style) {
            (Array.isArray(style) ? style : [style]).forEach(s => {
                switch (s) {
                    case 'bold':
                        modified.fontWeight = s;
                        break;
                    case 'normal':
                    case 'oblique':
                    case 'italic':
                        modified.fontStyle = s;
                        break;
                    case 'underline':
                    case 'line-through':
                        modified.textDecoration = s;
                        break;
                    default:
                        throw new Error(`Unexpected font style: "${s}".`);
                }
            });
        }
        return modified;
    }
    /**
     * Render caption affixes for the given tree node.
     * @param node the tree node.
     * @param props the node properties.
     * @param affixKey the affix key.
     */
    renderCaptionAffixes(node, props, affixKey) {
        const suffix = affixKey === 'captionSuffixes';
        const affixClass = suffix ? tree_decorator_1.TreeDecoration.Styles.CAPTION_SUFFIX_CLASS : tree_decorator_1.TreeDecoration.Styles.CAPTION_PREFIX_CLASS;
        const classes = [exports.TREE_NODE_SEGMENT_CLASS, affixClass];
        const affixes = this.getDecorationData(node, affixKey).filter(objects_1.notEmpty).reduce((acc, current) => acc.concat(current), []);
        const children = [];
        for (let i = 0; i < affixes.length; i++) {
            const affix = affixes[i];
            if (suffix && i === affixes.length - 1) {
                classes.push(exports.TREE_NODE_SEGMENT_GROW_CLASS);
            }
            const style = this.applyFontStyles({}, affix.fontData);
            const className = classes.join(' ');
            const key = node.id + '_' + i;
            const attrs = {
                className,
                style,
                key
            };
            children.push(React.createElement('div', attrs, affix.data));
        }
        return React.createElement(React.Fragment, null, children);
    }
    /**
     * Decorate the tree node icon.
     * @param node the tree node.
     * @param icon the icon.
     */
    decorateIcon(node, icon) {
        if (!icon) {
            return;
        }
        const overlayIcons = [];
        // if multiple overlays have the same overlay.position attribute, we'll de-duplicate those and only process the first one from the decoration array
        const seenPositions = new Set();
        const overlays = this.getDecorationData(node, 'iconOverlay').filter(objects_1.notEmpty);
        for (const overlay of overlays) {
            if (!seenPositions.has(overlay.position)) {
                seenPositions.add(overlay.position);
                const iconClasses = [tree_decorator_1.TreeDecoration.Styles.DECORATOR_SIZE_CLASS, tree_decorator_1.TreeDecoration.IconOverlayPosition.getStyle(overlay.position)];
                const style = (color) => color === undefined ? {} : { color };
                if (overlay.background) {
                    overlayIcons.push(React.createElement("span", { key: node.id + 'bg', className: this.getIconClass(overlay.background.shape, iconClasses), style: style(overlay.background.color) }));
                }
                const overlayIcon = 'icon' in overlay ? overlay.icon : overlay.iconClass;
                overlayIcons.push(React.createElement("span", { key: node.id, className: this.getIconClass(overlayIcon, iconClasses), style: style(overlay.color) }));
            }
        }
        if (overlayIcons.length > 0) {
            return React.createElement("div", { className: tree_decorator_1.TreeDecoration.Styles.ICON_WRAPPER_CLASS },
                icon,
                overlayIcons);
        }
        return icon;
    }
    /**
     * Render the tree node tail decorations.
     * @param node the tree node.
     * @param props the node properties.
     */
    renderTailDecorations(node, props) {
        const tailDecorations = this.getDecorationData(node, 'tailDecorations').reduce((acc, current) => acc.concat(current), []);
        if (tailDecorations.length === 0) {
            return;
        }
        return this.renderTailDecorationsForNode(node, props, tailDecorations);
    }
    renderTailDecorationsForNode(node, props, tailDecorations) {
        let dotDecoration;
        const otherDecorations = [];
        tailDecorations.reverse().forEach(decoration => {
            if (tree_decorator_1.TreeDecoration.TailDecoration.isDotDecoration(decoration)) {
                dotDecoration ||= decoration;
            }
            else if (decoration.data || decoration.icon || decoration.iconClass) {
                otherDecorations.push(decoration);
            }
        });
        const decorationsToRender = dotDecoration ? [dotDecoration, ...otherDecorations] : otherDecorations;
        return React.createElement(React.Fragment, null, decorationsToRender.map((decoration, index) => {
            const { tooltip, data, fontData, color, icon, iconClass } = decoration;
            const iconToRender = icon ?? iconClass;
            const className = [exports.TREE_NODE_SEGMENT_CLASS, exports.TREE_NODE_TAIL_CLASS, 'flex'].join(' ');
            const style = fontData ? this.applyFontStyles({}, fontData) : color ? { color } : undefined;
            const content = data ? data : iconToRender
                ? React.createElement("span", { key: node.id + 'icon' + index, className: this.getIconClass(iconToRender, iconToRender === 'circle' ? [tree_decorator_1.TreeDecoration.Styles.DECORATOR_SIZE_CLASS] : []) })
                : '';
            return React.createElement("div", { key: node.id + className + index, className: className, style: style, title: tooltip },
                content,
                index !== decorationsToRender.length - 1 ? ',' : '');
        }));
    }
    /**
     * Determine the classes to use for an icon
     * - Assumes a Font Awesome name when passed a single string, otherwise uses the passed string array
     * @param iconName the icon name or list of icon names.
     * @param additionalClasses additional CSS classes.
     *
     * @returns the icon class name.
     */
    getIconClass(iconName, additionalClasses = []) {
        const iconClass = (typeof iconName === 'string') ? ['a', 'fa', `fa-${iconName}`] : ['a'].concat(iconName);
        return iconClass.concat(additionalClasses).join(' ');
    }
    /**
     * Render indent for the file tree based on the depth
     * @param node the tree node.
     * @param depth the depth of the tree node.
     */
    renderIndent(node, props) {
        const renderIndentGuides = this.corePreferences['workbench.tree.renderIndentGuides'];
        if (renderIndentGuides === 'none') {
            return undefined;
        }
        const indentDivs = [];
        let current = node;
        let depth = props.depth;
        while (current && depth) {
            if (this.shouldRenderIndent(current)) {
                const classNames = [exports.TREE_NODE_INDENT_GUIDE_CLASS];
                if (this.needsActiveIndentGuideline(current)) {
                    classNames.push('active');
                }
                else {
                    classNames.push(renderIndentGuides === 'onHover' ? 'hover' : 'always');
                }
                const paddingLeft = this.getDepthPadding(depth);
                indentDivs.unshift(React.createElement("div", { key: depth, className: classNames.join(' '), style: {
                        paddingLeft: `${paddingLeft}px`
                    } }));
                depth--;
            }
            current = current.parent;
        }
        return indentDivs;
    }
    /**
     * Determines whether an indentation div should be rendered for the specified tree node.
     * If there are multiple tree nodes inside of a single rendered row,
     * this method should only return true for the first node.
     */
    shouldRenderIndent(node) {
        return true;
    }
    needsActiveIndentGuideline(node) {
        const parent = node.parent;
        if (!parent || !this.isExpandable(parent)) {
            return false;
        }
        if (tree_selection_1.SelectableTreeNode.isSelected(parent)) {
            return true;
        }
        if (parent.expanded) {
            for (const sibling of parent.children) {
                if (tree_selection_1.SelectableTreeNode.isSelected(sibling) && !(this.isExpandable(sibling) && sibling.expanded)) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Render the node given the tree node and node properties.
     * @param node the tree node.
     * @param props the node properties.
     */
    renderNode(node, props) {
        if (!tree_1.TreeNode.isVisible(node)) {
            return undefined;
        }
        const attributes = this.createNodeAttributes(node, props);
        const content = React.createElement("div", { className: exports.TREE_NODE_CONTENT_CLASS },
            this.renderExpansionToggle(node, props),
            this.renderCheckbox(node, props),
            this.decorateIcon(node, this.renderIcon(node, props)),
            this.renderCaptionAffixes(node, props, 'captionPrefixes'),
            this.renderCaption(node, props),
            this.renderCaptionAffixes(node, props, 'captionSuffixes'),
            this.renderTailDecorations(node, props));
        return React.createElement('div', attributes, content);
    }
    /**
     * Create node attributes for the tree node given the node properties.
     * @param node the tree node.
     * @param props the node properties.
     */
    createNodeAttributes(node, props) {
        const className = this.createNodeClassNames(node, props).join(' ');
        const style = this.createNodeStyle(node, props);
        return {
            className,
            style,
            onClick: event => this.handleClickEvent(node, event),
            onDoubleClick: event => this.handleDblClickEvent(node, event),
            onAuxClick: event => this.handleAuxClickEvent(node, event),
            onContextMenu: event => this.handleContextMenuEvent(node, event),
        };
    }
    /**
     * Create the node class names.
     * @param node the tree node.
     * @param props the node properties.
     *
     * @returns the list of tree node class names.
     */
    createNodeClassNames(node, props) {
        const classNames = [exports.TREE_NODE_CLASS];
        if (tree_1.CompositeTreeNode.is(node)) {
            classNames.push(exports.COMPOSITE_TREE_NODE_CLASS);
        }
        if (this.isExpandable(node)) {
            classNames.push(exports.EXPANDABLE_TREE_NODE_CLASS);
        }
        if (this.rowIsSelected(node, props)) {
            classNames.push(widgets_1.SELECTED_CLASS);
        }
        if (this.focusService.hasFocus(node)) {
            classNames.push(widgets_1.FOCUS_CLASS);
        }
        return classNames;
    }
    rowIsSelected(node, props) {
        return tree_selection_1.SelectableTreeNode.isSelected(node);
    }
    /**
     * Get the default node style.
     * @param node the tree node.
     * @param props the node properties.
     *
     * @returns the CSS properties if available.
     */
    getDefaultNodeStyle(node, props) {
        const paddingLeft = this.getPaddingLeft(node, props) + 'px';
        return { paddingLeft };
    }
    getPaddingLeft(node, props) {
        return this.getDepthPadding(props.depth) + (this.needsExpansionTogglePadding(node) ? this.props.expansionTogglePadding : 0);
    }
    /**
     * If the node is a composite, a toggle will be rendered.
     * Otherwise we need to add the width and the left, right padding => 18px
     */
    needsExpansionTogglePadding(node) {
        return !this.isExpandable(node);
    }
    /**
     * Create the tree node style.
     * @param node the tree node.
     * @param props the node properties.
     */
    createNodeStyle(node, props) {
        return this.decorateNodeStyle(node, this.getDefaultNodeStyle(node, props));
    }
    /**
     * Decorate the node style.
     * @param node the tree node.
     * @param style the optional CSS properties.
     *
     * @returns the CSS styles if available.
     */
    decorateNodeStyle(node, style) {
        const backgroundColor = this.getDecorationData(node, 'backgroundColor').filter(objects_1.notEmpty).shift();
        if (backgroundColor) {
            style = {
                ...(style || {}),
                backgroundColor
            };
        }
        return style;
    }
    /**
     * Determine if the tree node is expandable.
     * @param node the tree node.
     *
     * @returns `true` if the tree node is expandable.
     */
    isExpandable(node) {
        return tree_expansion_1.ExpandableTreeNode.is(node);
    }
    /**
     * Get the tree node decorations.
     * @param node the tree node.
     *
     * @returns the list of tree decoration data.
     */
    getDecorations(node) {
        const decorations = [];
        if (tree_decorator_1.DecoratedTreeNode.is(node)) {
            decorations.push(node.decorationData);
        }
        if (this.decorations.has(node.id)) {
            decorations.push(...this.decorations.get(node.id));
        }
        return decorations.sort(tree_decorator_1.TreeDecoration.Data.comparePriority);
    }
    /**
     * Get the tree decoration data for the given key.
     * @param node the tree node.
     * @param key the tree decoration data key.
     *
     * @returns the tree decoration data at the given key.
     */
    getDecorationData(node, key) {
        return this.getDecorations(node).filter(data => data[key] !== undefined).map(data => data[key]);
    }
    /**
     * Get the scroll container.
     */
    getScrollContainer() {
        this.toDisposeOnDetach.push(common_1.Disposable.create(() => {
            const { scrollTop, scrollLeft } = this.node;
            this.lastScrollState = { scrollTop, scrollLeft };
        }));
        if (this.lastScrollState) {
            const { scrollTop, scrollLeft } = this.lastScrollState;
            this.node.scrollTop = scrollTop;
            this.node.scrollLeft = scrollLeft;
        }
        return this.node;
    }
    onAfterAttach(msg) {
        const up = [
            keys_1.Key.ARROW_UP,
            keys_1.KeyCode.createKeyCode({ first: keys_1.Key.ARROW_UP, modifiers: [keys_1.KeyModifier.Shift] })
        ];
        const down = [
            keys_1.Key.ARROW_DOWN,
            keys_1.KeyCode.createKeyCode({ first: keys_1.Key.ARROW_DOWN, modifiers: [keys_1.KeyModifier.Shift] })
        ];
        if (this.props.search) {
            if (this.searchBox.isAttached) {
                widgets_1.Widget.detach(this.searchBox);
            }
            widgets_1.UnsafeWidgetUtilities.attach(this.searchBox, this.node.parentElement);
            this.addKeyListener(this.node, this.searchBox.keyCodePredicate.bind(this.searchBox), this.searchBox.handle.bind(this.searchBox));
            this.toDisposeOnDetach.push(common_1.Disposable.create(() => {
                widgets_1.Widget.detach(this.searchBox);
            }));
        }
        super.onAfterAttach(msg);
        this.addKeyListener(this.node, keys_1.Key.ARROW_LEFT, event => this.handleLeft(event));
        this.addKeyListener(this.node, keys_1.Key.ARROW_RIGHT, event => this.handleRight(event));
        this.addKeyListener(this.node, up, event => this.handleUp(event));
        this.addKeyListener(this.node, down, event => this.handleDown(event));
        this.addKeyListener(this.node, keys_1.Key.ENTER, event => this.handleEnter(event));
        this.addKeyListener(this.node, keys_1.Key.SPACE, event => this.handleSpace(event));
        this.addKeyListener(this.node, keys_1.Key.ESCAPE, event => this.handleEscape(event));
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.addEventListener(this.node, 'ps-scroll-y', (e) => {
            if (this.view && this.view.list) {
                const { scrollTop } = e.target;
                this.view.list.scrollTo({
                    top: scrollTop
                });
            }
        });
    }
    /**
     * Handle the `left arrow` keyboard event.
     * @param event the `left arrow` keyboard event.
     */
    async handleLeft(event) {
        if (!!this.props.multiSelect && (this.hasCtrlCmdMask(event) || this.hasShiftMask(event))) {
            return;
        }
        if (!await this.model.collapseNode()) {
            this.model.selectParent();
        }
    }
    /**
     * Handle the `right arrow` keyboard event.
     * @param event the `right arrow` keyboard event.
     */
    async handleRight(event) {
        if (!!this.props.multiSelect && (this.hasCtrlCmdMask(event) || this.hasShiftMask(event))) {
            return;
        }
        if (!await this.model.expandNode()) {
            this.model.selectNextNode();
        }
    }
    /**
     * Handle the `up arrow` keyboard event.
     * @param event the `up arrow` keyboard event.
     */
    handleUp(event) {
        if (!!this.props.multiSelect && this.hasShiftMask(event)) {
            this.model.selectPrevNode(tree_selection_1.TreeSelection.SelectionType.RANGE);
        }
        else {
            this.model.selectPrevNode();
        }
        this.node.focus();
    }
    /**
     * Handle the `down arrow` keyboard event.
     * @param event the `down arrow` keyboard event.
     */
    handleDown(event) {
        if (!!this.props.multiSelect && this.hasShiftMask(event)) {
            this.model.selectNextNode(tree_selection_1.TreeSelection.SelectionType.RANGE);
        }
        else {
            this.model.selectNextNode();
        }
        this.node.focus();
    }
    /**
     * Handle the `enter key` keyboard event.
     * - `enter` opens the tree node.
     * @param event the `enter key` keyboard event.
     */
    handleEnter(event) {
        this.model.openNode();
    }
    /**
     * Handle the `space key` keyboard event.
     * - If the element has a checkbox, it will be toggled.
     * - Otherwise, it should be similar to a single-click action.
     * @param event the `space key` keyboard event.
     */
    handleSpace(event) {
        const { focusedNode } = this.focusService;
        if (focusedNode && focusedNode.checkboxInfo) {
            this.model.markAsChecked(focusedNode, !focusedNode.checkboxInfo.checked);
        }
        else if (!this.props.multiSelect || (!event.ctrlKey && !event.metaKey && !event.shiftKey)) {
            this.tapNode(focusedNode);
        }
    }
    handleEscape(event) {
        if (this.model.selectedNodes.length <= 1) {
            this.focusService.setFocus(undefined);
            this.node.focus();
        }
        this.model.clearSelection();
    }
    /**
     * Handle the single-click mouse event.
     * @param node the tree node if available.
     * @param event the mouse single-click event.
     */
    handleClickEvent(node, event) {
        if (node) {
            event.stopPropagation();
            const shiftMask = this.hasShiftMask(event);
            const ctrlCmdMask = this.hasCtrlCmdMask(event);
            if (this.props.multiSelect && (shiftMask || ctrlCmdMask) && tree_selection_1.SelectableTreeNode.is(node)) {
                if (shiftMask) {
                    this.model.selectRange(node);
                }
                else if (ctrlCmdMask) {
                    this.model.toggleNode(node);
                }
            }
            else {
                this.tapNode(node);
            }
        }
    }
    /**
     * The effective handler of an unmodified single-click event.
     */
    tapNode(node) {
        if (tree_selection_1.SelectableTreeNode.is(node)) {
            this.model.selectNode(node);
        }
        if (node && !this.props.expandOnlyOnExpansionToggleClick && this.isExpandable(node)) {
            this.model.toggleNodeExpansion(node);
        }
    }
    /**
     * Handle the double-click mouse event.
     * @param node the tree node if available.
     * @param event the double-click mouse event.
     */
    handleDblClickEvent(node, event) {
        this.model.openNode(node);
        event.stopPropagation();
    }
    /**
     * Handle the middle-click mouse event.
     * @param node the tree node if available.
     * @param event the middle-click mouse event.
     */
    handleAuxClickEvent(node, event) {
        if (event.button === 1) {
            this.model.openNode(node);
            if (tree_selection_1.SelectableTreeNode.is(node)) {
                this.model.selectNode(node);
            }
        }
        event.stopPropagation();
    }
    /**
     * Handle the middle-click mouse event.
     * @param event the middle-click mouse event.
     */
    handleMiddleClickEvent(event) {
        // Prevents auto-scrolling behavior when middle-clicking.
        if (event.button === 1) {
            event.preventDefault();
        }
    }
    /**
     * Handle the context menu click event.
     * - The context menu click event is triggered by the right-click.
     * @param node the tree node if available.
     * @param event the right-click mouse event.
     */
    handleContextMenuEvent(node, event) {
        if (tree_selection_1.SelectableTreeNode.is(node)) {
            // Keep the selection for the context menu, if the widget support multi-selection and the right click happens on an already selected node.
            if (!this.props.multiSelect || !node.selected) {
                const type = !!this.props.multiSelect && this.hasCtrlCmdMask(event) ? tree_selection_1.TreeSelection.SelectionType.TOGGLE : tree_selection_1.TreeSelection.SelectionType.DEFAULT;
                this.model.addSelection({ node, type });
            }
            this.focusService.setFocus(node);
            const contextMenuPath = this.props.contextMenuPath;
            if (contextMenuPath) {
                const { x, y } = event.nativeEvent;
                const args = this.toContextMenuArgs(node);
                const target = event.currentTarget;
                setTimeout(() => this.contextMenuRenderer.render({
                    menuPath: contextMenuPath,
                    context: target,
                    anchor: { x, y },
                    args
                }), 10);
            }
        }
        event.stopPropagation();
        event.preventDefault();
    }
    /**
     * Actually handle the double-click mouse event on the expansion toggle.
     * @param event the double-click mouse event.
     */
    doHandleExpansionToggleDblClickEvent(event) {
        if (this.props.expandOnlyOnExpansionToggleClick) {
            // Ignore the double-click event.
            event.stopPropagation();
        }
    }
    /**
     * Convert the tree node to context menu arguments.
     * @param node the selectable tree node.
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    toContextMenuArgs(node) {
        return undefined;
    }
    /**
     * Determine if the tree modifier aware event has a `ctrlcmd` mask.
     * @param event the tree modifier aware event.
     *
     * @returns `true` if the tree modifier aware event contains the `ctrlcmd` mask.
     */
    hasCtrlCmdMask(event) {
        return os_1.isOSX ? event.metaKey : event.ctrlKey;
    }
    /**
     * Determine if the tree modifier aware event has a `shift` mask.
     * @param event the tree modifier aware event.
     *
     * @returns `true` if the tree modifier aware event contains the `shift` mask.
     */
    hasShiftMask(event) {
        // Ctrl/Cmd mask overrules the Shift mask.
        if (this.hasCtrlCmdMask(event)) {
            return false;
        }
        return event.shiftKey;
    }
    /**
     * Deflate the tree node for storage.
     * @param node the tree node.
     */
    deflateForStorage(node) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const copy = Object.assign({}, node);
        if (copy.parent) {
            delete copy.parent;
        }
        if ('previousSibling' in copy) {
            delete copy.previousSibling;
        }
        if ('nextSibling' in copy) {
            delete copy.nextSibling;
        }
        if ('busy' in copy) {
            delete copy.busy;
        }
        if (tree_1.CompositeTreeNode.is(node)) {
            copy.children = [];
            for (const child of node.children) {
                copy.children.push(this.deflateForStorage(child));
            }
        }
        return copy;
    }
    /**
     * Inflate the tree node from storage.
     * @param node the tree node.
     * @param parent the optional tree node.
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    inflateFromStorage(node, parent) {
        if (node.selected) {
            node.selected = false;
        }
        if (parent) {
            node.parent = parent;
        }
        if (Array.isArray(node.children)) {
            for (const child of node.children) {
                this.inflateFromStorage(child, node);
            }
        }
        return node;
    }
    /**
     * Store the tree state.
     */
    storeState() {
        const decorations = this.decoratorService.deflateDecorators(this.decorations);
        let state = {
            decorations
        };
        if (this.model.root) {
            state = {
                ...state,
                root: this.deflateForStorage(this.model.root),
                model: this.model.storeState(),
                focusedNodeId: this.focusService.focusedNode?.id
            };
        }
        return state;
    }
    /**
     * Restore the state.
     * @param oldState the old state object.
     */
    restoreState(oldState) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const { root, decorations, model, focusedNodeId } = oldState;
        if (root) {
            this.model.root = this.inflateFromStorage(root);
        }
        if (decorations) {
            this.decorations = this.decoratorService.inflateDecorators(decorations);
        }
        if (model) {
            this.model.restoreState(model);
        }
        if (focusedNodeId) {
            const candidate = this.model.getNode(focusedNodeId);
            if (tree_selection_1.SelectableTreeNode.is(candidate)) {
                this.focusService.setFocus(candidate);
            }
        }
    }
    toNodeIcon(node) {
        return this.labelProvider.getIcon(node);
    }
    toNodeName(node) {
        return this.labelProvider.getName(node);
    }
    toNodeDescription(node) {
        return this.labelProvider.getLongName(node);
    }
    getDepthPadding(depth) {
        if (depth === 1) {
            return this.props.leftPadding;
        }
        return depth * this.treeIndent;
    }
};
exports.TreeWidget = TreeWidget;
tslib_1.__decorate([
    (0, inversify_1.inject)(tree_decorator_1.TreeDecoratorService),
    tslib_1.__metadata("design:type", Object)
], TreeWidget.prototype, "decoratorService", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(tree_search_1.TreeSearch),
    tslib_1.__metadata("design:type", tree_search_1.TreeSearch)
], TreeWidget.prototype, "treeSearch", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(search_box_1.SearchBoxFactory),
    tslib_1.__metadata("design:type", Function)
], TreeWidget.prototype, "searchBoxFactory", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(tree_focus_service_1.TreeFocusService),
    tslib_1.__metadata("design:type", Object)
], TreeWidget.prototype, "focusService", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(common_1.SelectionService),
    tslib_1.__metadata("design:type", common_1.SelectionService)
], TreeWidget.prototype, "selectionService", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(preferences_1.PreferenceService),
    tslib_1.__metadata("design:type", Object)
], TreeWidget.prototype, "preferenceService", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(label_provider_1.LabelProvider),
    tslib_1.__metadata("design:type", label_provider_1.LabelProvider)
], TreeWidget.prototype, "labelProvider", void 0);
tslib_1.__decorate([
    (0, inversify_1.inject)(core_preferences_1.CorePreferences),
    tslib_1.__metadata("design:type", Object)
], TreeWidget.prototype, "corePreferences", void 0);
tslib_1.__decorate([
    (0, inversify_1.postConstruct)(),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", []),
    tslib_1.__metadata("design:returntype", void 0)
], TreeWidget.prototype, "init", null);
exports.TreeWidget = TreeWidget = TreeWidget_1 = tslib_1.__decorate([
    (0, inversify_1.injectable)(),
    tslib_1.__param(0, (0, inversify_1.inject)(exports.TreeProps)),
    tslib_1.__param(1, (0, inversify_1.inject)(tree_model_1.TreeModel)),
    tslib_1.__param(2, (0, inversify_1.inject)(context_menu_renderer_1.ContextMenuRenderer)),
    tslib_1.__metadata("design:paramtypes", [Object, Object, context_menu_renderer_1.ContextMenuRenderer])
], TreeWidget);
(function (TreeWidget) {
    class View extends React.Component {
        constructor() {
            super(...arguments);
            /**
             * Ensure the selected row is scrolled into view when virtualization finishes updating.
             */
            this.scrollIntoViewIfNeeded = () => {
                const { scrollToRow } = this.props;
                if (this.list && scrollToRow !== undefined) {
                    this.list.scrollIntoView({
                        index: scrollToRow,
                        align: 'center'
                    });
                }
            };
        }
        componentDidMount() {
            this.scrollIntoViewIfNeeded();
        }
        componentDidUpdate(prevProps) {
            if (this.props.scrollToRow !== prevProps.scrollToRow) {
                this.scrollIntoViewIfNeeded();
            }
        }
        render() {
            const { rows, width, height, scrollToRow, renderNodeRow, onAtBottomStateChangeEmitter, ...other } = this.props;
            return React.createElement(react_virtuoso_1.Virtuoso, { ref: (list) => {
                    this.list = (list || undefined);
                    this.scrollIntoViewIfNeeded();
                }, atBottomStateChange: (atBottom) => {
                    onAtBottomStateChangeEmitter?.fire(atBottom);
                }, atBottomThreshold: exports.SCROLL_BOTTOM_THRESHOLD, totalCount: rows.length, itemContent: index => renderNodeRow(rows[index]), width: width, height: height, 
                // This is a pixel value that determines how many pixels to render outside the visible area
                // Higher value provides smoother scrolling experience especially during inference, but uses more memory
                overscan: 800, ...other });
        }
    }
    TreeWidget.View = View;
})(TreeWidget || (exports.TreeWidget = TreeWidget = {}));
//# sourceMappingURL=tree-widget.js.map