HSplitBox.js (4790B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 // A box with a start and a end pane, separated by a dragable splitter that 8 // allows the user to resize the relative widths of the panes. 9 // 10 // +-----------------------+---------------------+ 11 // | | | 12 // | | | 13 // | S | 14 // | Start Pane p End Pane | 15 // | l | 16 // | i | 17 // | t | 18 // | t | 19 // | e | 20 // | r | 21 // | | | 22 // | | | 23 // +-----------------------+---------------------+ 24 25 const { 26 Component, 27 } = require("resource://devtools/client/shared/vendor/react.mjs"); 28 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 29 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 30 31 class HSplitBox extends Component { 32 static get propTypes() { 33 return { 34 // The contents of the start pane. 35 start: PropTypes.any.isRequired, 36 37 // The contents of the end pane. 38 end: PropTypes.any.isRequired, 39 40 // The relative width of the start pane, expressed as a number between 0 and 41 // 1. The relative width of the end pane is 1 - startWidth. For example, 42 // with startWidth = .5, both panes are of equal width; with startWidth = 43 // .25, the start panel will take up 1/4 width and the end panel will take 44 // up 3/4 width. 45 startWidth: PropTypes.number, 46 47 // A minimum css width value for the start and end panes. 48 minStartWidth: PropTypes.any, 49 minEndWidth: PropTypes.any, 50 51 // A callback fired when the user drags the splitter to resize the relative 52 // pane widths. The function is passed the startWidth value that would put 53 // the splitter underneath the users mouse. 54 onResize: PropTypes.func.isRequired, 55 }; 56 } 57 58 static get defaultProps() { 59 return { 60 startWidth: 0.5, 61 minStartWidth: "20px", 62 minEndWidth: "20px", 63 }; 64 } 65 66 constructor(props) { 67 super(props); 68 69 this.state = { 70 mouseDown: false, 71 }; 72 73 this._onMouseDown = this._onMouseDown.bind(this); 74 this._onMouseUp = this._onMouseUp.bind(this); 75 this._onMouseMove = this._onMouseMove.bind(this); 76 } 77 78 componentDidMount() { 79 document.defaultView.addEventListener("mouseup", this._onMouseUp, true); 80 document.defaultView.addEventListener("mousemove", this._onMouseMove, true); 81 } 82 83 componentWillUnmount() { 84 document.defaultView.removeEventListener("mouseup", this._onMouseUp, true); 85 document.defaultView.removeEventListener( 86 "mousemove", 87 this._onMouseMove, 88 true 89 ); 90 } 91 92 _onMouseDown(event) { 93 if (event.button !== 0) { 94 return; 95 } 96 97 this.setState({ mouseDown: true }); 98 event.preventDefault(); 99 } 100 101 _onMouseUp(event) { 102 if (event.button !== 0 || !this.state.mouseDown) { 103 return; 104 } 105 106 this.setState({ mouseDown: false }); 107 event.preventDefault(); 108 } 109 110 _onMouseMove(event) { 111 if (!this.state.mouseDown) { 112 return; 113 } 114 115 const rect = this.refs.box.getBoundingClientRect(); 116 const { left, right } = rect; 117 const width = right - left; 118 const direction = this.refs.box.ownerDocument.dir; 119 const relative = 120 direction == "rtl" ? right - event.clientX : event.clientX - left; 121 this.props.onResize(relative / width); 122 123 event.preventDefault(); 124 } 125 126 render() { 127 const { start, end, startWidth, minStartWidth, minEndWidth } = this.props; 128 129 // Values outside of [0, 1] have no effect thanks to flex + minWidth. 130 const clampedStartWidth = Math.min(Math.max(startWidth, 0), 1); 131 132 return dom.div( 133 { 134 className: "h-split-box", 135 ref: "box", 136 }, 137 138 dom.div( 139 { 140 className: "h-split-box-pane", 141 style: { flex: clampedStartWidth, minWidth: minStartWidth }, 142 }, 143 start 144 ), 145 146 dom.div({ 147 className: "devtools-side-splitter", 148 onMouseDown: this._onMouseDown, 149 }), 150 151 dom.div( 152 { 153 className: "h-split-box-pane", 154 style: { flex: 1 - clampedStartWidth, minWidth: minEndWidth }, 155 }, 156 end 157 ) 158 ); 159 } 160 } 161 162 module.exports = HSplitBox;