Files
tubestation/devtools/client/shared/components/splitter/SplitBox.js

296 lines
8.8 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Draggable = createFactory(require("devtools/client/shared/components/splitter/Draggable"));
/**
* This component represents a Splitter. The splitter supports vertical
* as well as horizontal mode.
*/
class SplitBox extends Component {
static get propTypes() {
return {
// Custom class name. You can use more names separated by a space.
className: PropTypes.string,
// Initial size of controlled panel.
initialSize: PropTypes.string,
// Initial width of controlled panel.
initialWidth: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]),
// Initial height of controlled panel.
initialHeight: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]),
// Left/top panel
startPanel: PropTypes.any,
// Min panel size.
minSize: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]),
// Max panel size.
maxSize: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]),
// Right/bottom panel
endPanel: PropTypes.any,
// True if the right/bottom panel should be controlled.
endPanelControl: PropTypes.bool,
// Size of the splitter handle bar.
splitterSize: PropTypes.number,
// True if the splitter bar is vertical (default is vertical).
vert: PropTypes.bool,
// Style object.
style: PropTypes.object,
// Call when controlled panel was resized.
onControlledPanelResized: PropTypes.func,
};
}
static get defaultProps() {
return {
splitterSize: 5,
vert: true,
endPanelControl: false,
};
}
constructor(props) {
super(props);
/**
* The state stores whether or not the end panel should be controlled, the current
* orientation (vertical or horizontal), the splitter size, and the current size
* (width/height). All these values can change during the component's life time.
*/
this.state = {
// True if the right/bottom panel should be controlled.
endPanelControl: props.endPanelControl,
// True if the splitter bar is vertical (default is vertical).
vert: props.vert,
// Size of the splitter handle bar.
splitterSize: props.splitterSize,
// Width of controlled panel.
width: props.initialWidth || props.initialSize,
// Height of controlled panel.
height: props.initialHeight || props.initialSize,
};
this.onStartMove = this.onStartMove.bind(this);
this.onStopMove = this.onStopMove.bind(this);
this.onMove = this.onMove.bind(this);
}
componentWillReceiveProps(nextProps) {
const {
endPanelControl,
splitterSize,
vert,
} = nextProps;
if (endPanelControl != this.props.endPanelControl) {
this.setState({ endPanelControl });
}
if (splitterSize != this.props.splitterSize) {
this.setState({ splitterSize });
}
if (vert !== this.props.vert) {
this.setState({ vert });
}
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.width != this.state.width ||
nextState.endPanelControl != this.props.endPanelControl ||
nextState.height != this.state.height ||
nextState.vert != this.state.vert ||
nextState.splitterSize != this.state.splitterSize ||
nextProps.startPanel != this.props.startPanel ||
nextProps.endPanel != this.props.endPanel ||
nextProps.minSize != this.props.minSize ||
nextProps.maxSize != this.props.maxSize;
}
componentDidUpdate(prevProps, prevState) {
if (this.props.onControlledPanelResized && (prevState.width !== this.state.width ||
prevState.height !== this.state.height)) {
this.props.onControlledPanelResized(this.state.width, this.state.height);
}
}
// Dragging Events
/**
* Set 'resizing' cursor on entire document during splitter dragging.
* This avoids cursor-flickering that happens when the mouse leaves
* the splitter bar area (happens frequently).
*/
onStartMove() {
const doc = this.splitBox.ownerDocument;
const defaultCursor = doc.documentElement.style.cursor;
doc.documentElement.style.cursor = (this.state.vert ? "ew-resize" : "ns-resize");
this.splitBox.classList.add("dragging");
this.setState({
defaultCursor: defaultCursor,
});
}
onStopMove() {
const doc = this.splitBox.ownerDocument;
doc.documentElement.style.cursor = this.state.defaultCursor;
this.splitBox.classList.remove("dragging");
}
/**
* Adjust size of the controlled panel. Depending on the current
* orientation we either remember the width or height of
* the splitter box.
*/
onMove(x, y) {
const nodeBounds = this.splitBox.getBoundingClientRect();
let size;
let { endPanelControl, vert } = this.state;
if (vert) {
// Use the document owning the SplitBox to detect rtl. The global document might be
// the one bound to the toolbox shared BrowserRequire, which is irrelevant here.
const doc = this.splitBox.ownerDocument;
// Switch the control flag in case of RTL. Note that RTL
// has impact on vertical splitter only.
if (doc.dir === "rtl") {
endPanelControl = !endPanelControl;
}
size = endPanelControl ?
(nodeBounds.left + nodeBounds.width) - x :
x - nodeBounds.left;
this.setState({
width: size,
});
} else {
size = endPanelControl ?
(nodeBounds.top + nodeBounds.height) - y :
y - nodeBounds.top;
this.setState({
height: size,
});
}
}
// Rendering
render() {
const { endPanelControl, splitterSize, vert } = this.state;
const { startPanel, endPanel, minSize, maxSize } = this.props;
const style = Object.assign({
// Set the size of the controlled panel (height or width depending on the
// current state). This can be used to help with styling of dependent
// panels.
"--split-box-controlled-panel-size":
`${vert ? this.state.width : this.state.height}`,
}, this.props.style);
// Calculate class names list.
let classNames = ["split-box"];
classNames.push(vert ? "vert" : "horz");
if (this.props.className) {
classNames = classNames.concat(this.props.className.split(" "));
}
let leftPanelStyle;
let rightPanelStyle;
// Set proper size for panels depending on the current state.
if (vert) {
leftPanelStyle = {
maxWidth: endPanelControl ? null : maxSize,
minWidth: endPanelControl ? null : minSize,
width: endPanelControl ? null : this.state.width,
};
rightPanelStyle = {
maxWidth: endPanelControl ? maxSize : null,
minWidth: endPanelControl ? minSize : null,
width: endPanelControl ? this.state.width : null,
};
} else {
leftPanelStyle = {
maxHeight: endPanelControl ? null : maxSize,
minHeight: endPanelControl ? null : minSize,
height: endPanelControl ? null : this.state.height,
};
rightPanelStyle = {
maxHeight: endPanelControl ? maxSize : null,
minHeight: endPanelControl ? minSize : null,
height: endPanelControl ? this.state.height : null,
};
}
// Calculate splitter size
const splitterStyle = {
flex: "0 0 " + splitterSize + "px",
};
return (
dom.div(
{
className: classNames.join(" "),
ref: div => {
this.splitBox = div;
},
style },
startPanel ?
dom.div({
className: endPanelControl ? "uncontrolled" : "controlled",
style: leftPanelStyle,
role: "presentation",
ref: div => {
this.startPanelContainer = div;
}},
startPanel
) : null,
splitterSize > 0 ?
Draggable({
className: "splitter",
style: splitterStyle,
onStart: this.onStartMove,
onStop: this.onStopMove,
onMove: this.onMove,
}) : null,
endPanel ?
dom.div({
className: endPanelControl ? "controlled" : "uncontrolled",
style: rightPanelStyle,
role: "presentation",
ref: div => {
this.endPanelContainer = div;
}},
endPanel
) : null
)
);
}
}
module.exports = SplitBox;