Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / widgets / input-range-slider / react-rangeslider.jsx
1
2 /*
3  * 
4  *   Copyright 2016 RIFT.IO Inc
5  *
6  *   Licensed under the Apache License, Version 2.0 (the "License");
7  *   you may not use this file except in compliance with the License.
8  *   You may obtain a copy of the License at
9  *
10  *       http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *   Unless required by applicable law or agreed to in writing, software
13  *   distributed under the License is distributed on an "AS IS" BASIS,
14  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *   See the License for the specific language governing permissions and
16  *   limitations under the License.
17  *
18  */
19 /**
20  * https://github.com/larsmaultsby/react-rangeslider
21  *
22  *
23  * Forked from: https://github.com/whoisandie/react-rangeslider
24  *
25  *
26     The MIT License (MIT)
27
28     Copyright (c) 2015 Bhargav Anand
29
30     Permission is hereby granted, free of charge, to any person obtaining a copy
31     of this software and associated documentation files (the "Software"), to deal
32     in the Software without restriction, including without limitation the rights
33     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34     copies of the Software, and to permit persons to whom the Software is
35     furnished to do so, subject to the following conditions:
36
37     The above copyright notice and this permission notice shall be included in all
38     copies or substantial portions of the Software.
39
40     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
46     SOFTWARE.
47  *
48  */
49
50 import React, { PropTypes, Component, findDOMNode } from 'react';
51 import joinClasses from 'react/lib/joinClasses';
52
53 function capitalize(str) {
54   return str.charAt(0).toUpperCase() + str.substr(1);
55 }
56
57 function maxmin(pos, min, max) {
58   if (pos < min) { return min; }
59   if (pos > max) { return max; }
60   return pos;
61 }
62
63 const constants = {
64   orientation: {
65     horizontal: {
66       dimension: 'width',
67       direction: 'left',
68       coordinate: 'x',
69     },
70     vertical: {
71       dimension: 'height',
72       direction: 'top',
73       coordinate: 'y',
74     }
75   }
76 };
77
78 class Slider extends Component {
79   static propTypes = {
80     min: PropTypes.number,
81     max: PropTypes.number,
82     step: PropTypes.number,
83     value: PropTypes.number,
84     orientation: PropTypes.string,
85     onChange: PropTypes.func,
86     className: PropTypes.string,
87   }
88
89   static defaultProps = {
90     min: 0,
91     max: 100,
92     step: 1,
93     value: 0,
94     orientation: 'horizontal',
95   }
96
97   state = {
98     limit: 0,
99     grab: 0
100   }
101
102   // Add window resize event listener here
103   componentDidMount() {
104     this.calculateDimensions();
105     window.addEventListener('resize', this.calculateDimensions);
106   }
107
108   componentWillUnmount() {
109     window.removeEventListener('resize', this.calculateDimensions);
110   }
111
112   handleSliderMouseDown = (e) => {
113     let value, { onChange } = this.props;
114     if (!onChange) return;
115     value = this.position(e);
116     onChange && onChange(value);
117   }
118
119   handleKnobMouseDown = () => {
120     document.addEventListener('mousemove', this.handleDrag);
121     document.addEventListener('mouseup', this.handleDragEnd);
122   }
123
124   handleDrag = (e) => {
125     let value, { onChange } = this.props;
126     if (!onChange) return;
127     value = this.position(e);
128     onChange && onChange(value);
129   }
130
131   handleDragEnd = () => {
132     document.removeEventListener('mousemove', this.handleDrag);
133     document.removeEventListener('mouseup', this.handleDragEnd);
134   }
135
136   handleNoop = (e) => {
137     e.stopPropagation();
138     e.preventDefault();
139   }
140
141   calculateDimensions = () => {
142     let { orientation } = this.props;
143     let dimension = capitalize(constants.orientation[orientation].dimension);
144     const sliderPos = findDOMNode(this.refs.slider)['offset' + dimension];
145     const handlePos = findDOMNode(this.refs.handle)['offset' + dimension]
146     this.setState({
147       limit: sliderPos - handlePos,
148       grab: handlePos / 2,
149     });
150   }
151   getPositionFromValue = (value) => {
152     let percentage, pos;
153     let { limit } = this.state;
154     let { min, max } = this.props;
155     percentage = (value - min) / (max - min);
156     pos = Math.round(percentage * limit);
157
158     return pos;
159   }
160
161   getValueFromPosition = (pos) => {
162     let percentage, value;
163     let { limit } = this.state;
164     let { orientation, min, max, step } = this.props;
165     percentage = (maxmin(pos, 0, limit) / (limit || 1));
166
167     if (orientation === 'horizontal') {
168       value = step * Math.round(percentage * (max - min) / step) + min;
169     } else {
170       value = max - (step * Math.round(percentage * (max - min) / step) + min);
171     }
172
173     return value;
174   }
175
176   position = (e) => {
177     let pos, value, { grab } = this.state;
178     let { orientation } = this.props;
179     const node = findDOMNode(this.refs.slider);
180     const coordinateStyle = constants.orientation[orientation].coordinate;
181     const directionStyle = constants.orientation[orientation].direction;
182     const coordinate = e['client' + capitalize(coordinateStyle)];
183     const direction = node.getBoundingClientRect()[directionStyle];
184
185     pos = coordinate - direction - grab;
186     value = this.getValueFromPosition(pos);
187
188     return value;
189   }
190
191   coordinates = (pos) => {
192     let value, fillPos, handlePos;
193     let { limit, grab } = this.state;
194     let { orientation } = this.props;
195     value = this.getValueFromPosition(pos);
196     handlePos = this.getPositionFromValue(value);
197
198     if (orientation === 'horizontal') {
199       fillPos = handlePos + grab;
200     } else {
201       fillPos = limit - handlePos + grab;
202     }
203
204     return {
205       fill: fillPos,
206       handle: handlePos,
207     };
208   }
209
210   render() {
211     let dimension, direction, position, coords, fillStyle, handleStyle, displayValue;
212     let { value, orientation, className } = this.props;
213
214     dimension = constants.orientation[orientation].dimension;
215     direction = constants.orientation[orientation].direction;
216
217     position = this.getPositionFromValue(value);
218     coords = this.coordinates(position);
219
220     fillStyle = {[dimension]: `${coords.fill}px`};
221     handleStyle = {[direction]: `${coords.handle}px`};
222
223     if(this.props.displayValue) {
224       displayValue = <div>{this.props.value}</div>;
225     }
226
227     return (
228       <div
229         ref="slider"
230         className={joinClasses('rangeslider ', 'rangeslider-' + orientation, className)}
231         onMouseDown={this.handleSliderMouseDown}
232         onClick={this.handleNoop}
233         style={{display:'flex'}}>
234         <div
235           ref="fill"
236           className="rangeslider__fill"
237           style={fillStyle} />
238         <div
239           ref="handle"
240           className="rangeslider__handle"
241           onMouseDown={this.handleKnobMouseDown}
242           onClick={this.handleNoop}
243           style={handleStyle}>
244             {displayValue}
245           </div>
246       </div>
247     );
248   }
249 }
250
251 export default Slider;