blob: feff1eb1c47c3d901122fe1ebee1c7033b49fae5 [file] [log] [blame]
Jeremy Mordkoffe29efc32016-09-07 18:59:17 -04001
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
50import React, { PropTypes, Component, findDOMNode } from 'react';
51import joinClasses from 'react/lib/joinClasses';
52
53function capitalize(str) {
54 return str.charAt(0).toUpperCase() + str.substr(1);
55}
56
57function maxmin(pos, min, max) {
58 if (pos < min) { return min; }
59 if (pos > max) { return max; }
60 return pos;
61}
62
63const 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
78class 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
251export default Slider;