Source: node_modules/react-aiot/src/components/ResizeButton.js

/** @module react-aiot/components/ResizeButton */
import React from 'react';
import { touchable, resizeOpposite } from '../util';

/**
 * Resize button with resizer (three vertical dots) and collapse button.
 * 
 * @param {object} props Properties
 * @param {string} props.containerId The container id which should be resized and contains the resize button
 * @param {DOMElement} [props.opposite] The opposite which gets also resized (width: calc() method)
 * @param {int} [props.oppositeOffset] The offset reduced width in px
 * @param {int} [props.restoreWidth] The restored width in px (if you save in localStorage for example)
 * @param {int} props.initialWidth If restoreWidth is not setted this width in px is used
 * @param {int} [props.minWidth] The minimum width in px
 * @param {int} [props.maxWidth] The maximum width in px
 * @param {module:react-aiot/components/ResizeButton#onResizeFinished} [props.onResizeFinished]
 * @extensd React.Component
 */
export default class ResizeButton extends React.Component {
    static stateKeys = 'defaultRestoreWidth,restoreWidth'.split(',');
    
    currentlyResizing = false;
    
    constructor(props) {
        super(props);
        const { initialWidth, minWidth, restoreWidth } = props,  
            defaultRestoreWidth = typeof initialWidth === 'number' ? initialWidth : minWidth;
        this.state = {
            defaultRestoreWidth,
            restoreWidth: restoreWidth || defaultRestoreWidth
        };
    }
    
    /**
     * Avoid rerender of the resizebutton during resizing.
     */
    shouldComponentUpdate(nextProps, nextState) {
        const changedState = ResizeButton.stateKeys.filter(k => this.state[k] !== nextState[k]);
        if (changedState.length === 1 && changedState[0] === 'restoreWidth') {
            return false;
        }
        return true;
    }
    
    componentDidMount() {
        // Add handler to the resize button
        this._getContainer('.aiot-split-resizer').addEventListener(touchable.down, this.handleMouseDown);
        document.addEventListener(touchable.up, this.handleMouseUp);
        
        // Initialize resize width
        const defaultRestoreWidth = this.state.defaultRestoreWidth;
        this.handleResize(defaultRestoreWidth);
        this.props.onResizeFinished && this.props.onResizeFinished(defaultRestoreWidth);
    }
    
    handleDoubleClick = () => {
        const width = this._getContainerWidth() > 0 ? 0 : this.state.restoreWidth;
        this.handleResize(width);
        this.props.onResizeFinished && this.props.onResizeFinished(width);
    }
    
    handleMouseDown = ev => {
        ev.preventDefault();
        document.addEventListener(touchable.move, this.handleResize);
        this.currentlyResizing = true;
    }
    
    handleMouseUp = ev => {
        document.removeEventListener(touchable.move, this.handleResize);
        /**
         * This function is called when the container is resized.
         * 
         * @callback module:react-aiot/components/ResizeButton#onResizeFinished
         * @param {int} width The width in px
         */
        this.currentlyResizing && this.props.onResizeFinished
            && this.props.onResizeFinished(this._getContainerWidth());
        
        this.currentlyResizing = false;
    }
    
    handleOpposite = width => {
        return resizeOpposite(this._container.id, this.props.opposite.id, width);
    }
    
    handleResize = (ev, force) => {
        const { minWidth, maxWidth } = this.props,
            isEvent = !!(ev && ev.pageX);
        let x = (isEvent
                ? ev.pageX - (this._container.getBoundingClientRect().left + document.body.scrollLeft) - 15
                : ev), // Offset
            move = x >= minWidth && x <= maxWidth;
        
        // Prevent default if event
        isEvent && ev.preventDefault();
        
        // Allow collapse
        x < minWidth - 50 && (move = x = 1);
        
        const collapse = move === 1, xOpposite = x + this.props.oppositeOffset;
        window.requestAnimationFrame(() => {
            if ((move || force) && this.handleOpposite(collapse ? x : xOpposite) !== false) {
                this._container.style.width = xOpposite + 'px';
                !collapse && this.setState({ restoreWidth: x });
                /**
                 * This function is called while container gets resized.
                 * 
                 * @callback module:react-aiot/components/ResizeButton#onResize
                 * @param {int} width The width in px
                 * @param {boolean} collapsed If true the sidebar is collapsed
                 */
                this.props.onResize && this.props.onResize(x, collapse);
            }
        });
    }
    
    render() {
        return <span className="aiot-split">
            <div className="aiot-split-resizer" />
            <div className="aiot-split-collapse" onClick={ this.handleDoubleClick } />
        </span>;
    }
    
    _getContainer(find, singleFind = true) {
        const elem = document.getElementById(this.props.containerId),
            findObj = find ? elem && elem.querySelectorAll(find) : elem;
        this._container = elem;
            
        if (find && singleFind) {
            return findObj && findObj[0];
        }
            
        return findObj;
    }
    
    _getContainerWidth() {
        const computed = window.getComputedStyle(this._container),
            width = parseInt(computed.width, 10);
        return width - parseInt(computed.borderLeftWidth, 10) - parseInt(computed.borderRightWidth, 10);
    }
}