SplitPanel.react.js

import PropTypes from 'prop-types';
import DashLuminoComponent from '../component.js'
import { components, props_id } from '../registry.js';

import {
    SplitPanel as l_SplitPanel,
    Panel as l_Panel,
    DockPanel,
    TabPanel,
    BoxPanel,
    Panel
} from '@lumino/widgets';

/**
 * A panel which arranges its widgets into resizable sections.   
 * {@link https://jupyterlab.github.io/lumino/widgets/classes/splitpanel.html}
 * @hideconstructor
 * 
 * @example
 * //Python:
 * import dash
 * import dash_lumino_components as dlc
 * 
 * splitPanel = dlc.SplitPanel([
 *     dlc.TabPanel([], id="tab-panel"),
 *     dlc.DockPanel([], id="dock-panel")
 * ], id="split-panel")
 */
class SplitPanel extends DashLuminoComponent {

    constructor(props) {
        super(props);

        // register a new SplitPanel
        let luminoComponent = super.register(new l_SplitPanel({
            alignment: props.alignment,
            orientation: props.orientation,
            spacing: props.spacing
        }), props.addToDom, false);

        // do a special resize to keep the size of the fixed
        // side panels at the same size
        let that = this;
        window.addEventListener("resize", function () {
            luminoComponent.update();
            that.updateSizes();
        });

        // add the children of the component to the widgets of the panel
        if (this.props.children) {
            super.parseChildrenToArray().forEach(el => {
                super.applyAfterLuminoChildCreation(el, (target, child) => {



                    let tabBar = child.lumino.tabBar;
                    let stackedPanel = child.lumino.stackedPanel;
                    let tabPlacement = child.lumino.tabPlacement;
                    if (tabBar && stackedPanel && tabPlacement &&
                        (tabPlacement === "left" || tabPlacement === "right")) {

                        // add ids to the special items of the TabPanel
                        stackedPanel.id = props_id(child.dash) + "-stackedPanel";
                        tabBar.id = props_id(child.dash) + "-tabBar";

                        // add the content panel directly
                        target.lumino.addWidget(stackedPanel);


                        //create an extra panel for the tabbar
                        const tabBarContainer = new l_Panel();
                        tabBarContainer.addClass('tabBarContainer');
                        tabBarContainer.addWidget(tabBar);
                        tabBar.addClass('lm-SideBar');
                        tabBar.addClass('lm-mod-' + tabPlacement);

                        //store the original component order
                        let oldWidgets = new Array(...target.lumino.parent.widgets);

                        //add the tabbar panel to the parent container
                        target.lumino.parent.addWidget(tabBarContainer);

                        //if the tabpanel is placed on the left, we can enshure this
                        // by adding all old panels again
                        if (tabPlacement === "left") {
                            oldWidgets.forEach(el => {
                                target.lumino.parent.addWidget(el);
                            })
                        }

                    } else {
                        // in all other cases just add the lumino widget to the container
                        target.lumino.addWidget(child.lumino);
                    }
                });
            })
        }

    }


    /**
     * Redistribute the sizes if the total size chanches. Panel sizes of split panes are
     * taken from the width parameter, if they are not hidden.
     * Panels with undefined width are equally spread.
     * @private
     */
    updateSizes() {

        let w = components[this.id].lumino.widgets.map(el => {
            if (el.isHidden) {
                return 0;
            } else {
                return components[el.id.replace("-stackedPanel", "")].dash.props.width;
            }
        })
        let sum_w = sum_w = w.reduce((a, c) => Number.isFinite(c) ? a + c : a, 0);
        let count_undef = w.reduce((a, c) => Number.isFinite(c) ? a : a + 1, 0);
        let replaceSize = (components[this.id].dash.props.orientation === 'horizontal') ? (window.innerWidth - sum_w) / count_undef : (window.innerHeight - sum_w) / count_undef;
        replaceSize = Math.max(replaceSize, 100);
        let sizes = w.map(el => Number.isFinite(el) ? el : replaceSize);

        components[this.id].lumino.setRelativeSizes(sizes);
    }


    render() {
        return super.render();
    }

}

SplitPanel.defaultProps = {
    alignment: 'start',
    orientation: 'horizontal',
    spacing: 0,
    addToDom: false,
};

/**
 * @typedef
 * @enum {}
 */
SplitPanel.propTypes = {
    /**
     * ID of the widget
     * @type {string}
     */
    id: PropTypes.string.isRequired,

    /**
     * the content alignment of the layout ("start" | "center" | "end" | "justify")
     * @type {string}
     */
    alignment: PropTypes.string,

    /**
      * a type alias for a split layout orientation ("horizontal" | "vertical")
      * @type {string}
      */
    orientation: PropTypes.string,

    /**
     * The spacing between items in the layout
     * @type {number}
     */
    spacing: PropTypes.number,

    /**
     * bool if the object has to be added to the dom directly
     * @type {boolean}
     */
    addToDom: PropTypes.bool,

    /**
     * The widgets
     * @type {Array<DockPanel, TabPanel, BoxPanel, Panel>}
     */
    children: PropTypes.node
};


/**
 * @private
 */
export default SplitPanel;