| |
| /* |
| * |
| * Copyright 2016 RIFT.IO Inc |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| /** |
| * https://github.com/larsmaultsby/react-rangeslider |
| * |
| * |
| * Forked from: https://github.com/whoisandie/react-rangeslider |
| * |
| * |
| The MIT License (MIT) |
| |
| Copyright (c) 2015 Bhargav Anand |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in all |
| copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| SOFTWARE. |
| * |
| */ |
| |
| import React, { PropTypes, Component, findDOMNode } from 'react'; |
| import joinClasses from 'react/lib/joinClasses'; |
| |
| function capitalize(str) { |
| return str.charAt(0).toUpperCase() + str.substr(1); |
| } |
| |
| function maxmin(pos, min, max) { |
| if (pos < min) { return min; } |
| if (pos > max) { return max; } |
| return pos; |
| } |
| |
| const constants = { |
| orientation: { |
| horizontal: { |
| dimension: 'width', |
| direction: 'left', |
| coordinate: 'x', |
| }, |
| vertical: { |
| dimension: 'height', |
| direction: 'top', |
| coordinate: 'y', |
| } |
| } |
| }; |
| |
| class Slider extends Component { |
| static propTypes = { |
| min: PropTypes.number, |
| max: PropTypes.number, |
| step: PropTypes.number, |
| value: PropTypes.number, |
| orientation: PropTypes.string, |
| onChange: PropTypes.func, |
| className: PropTypes.string, |
| } |
| |
| static defaultProps = { |
| min: 0, |
| max: 100, |
| step: 1, |
| value: 0, |
| orientation: 'horizontal', |
| } |
| |
| state = { |
| limit: 0, |
| grab: 0 |
| } |
| |
| // Add window resize event listener here |
| componentDidMount() { |
| this.calculateDimensions(); |
| window.addEventListener('resize', this.calculateDimensions); |
| } |
| |
| componentWillUnmount() { |
| window.removeEventListener('resize', this.calculateDimensions); |
| } |
| |
| handleSliderMouseDown = (e) => { |
| let value, { onChange } = this.props; |
| if (!onChange) return; |
| value = this.position(e); |
| onChange && onChange(value); |
| } |
| |
| handleKnobMouseDown = () => { |
| document.addEventListener('mousemove', this.handleDrag); |
| document.addEventListener('mouseup', this.handleDragEnd); |
| } |
| |
| handleDrag = (e) => { |
| let value, { onChange } = this.props; |
| if (!onChange) return; |
| value = this.position(e); |
| onChange && onChange(value); |
| } |
| |
| handleDragEnd = () => { |
| document.removeEventListener('mousemove', this.handleDrag); |
| document.removeEventListener('mouseup', this.handleDragEnd); |
| } |
| |
| handleNoop = (e) => { |
| e.stopPropagation(); |
| e.preventDefault(); |
| } |
| |
| calculateDimensions = () => { |
| let { orientation } = this.props; |
| let dimension = capitalize(constants.orientation[orientation].dimension); |
| const sliderPos = findDOMNode(this.refs.slider)['offset' + dimension]; |
| const handlePos = findDOMNode(this.refs.handle)['offset' + dimension] |
| this.setState({ |
| limit: sliderPos - handlePos, |
| grab: handlePos / 2, |
| }); |
| } |
| getPositionFromValue = (value) => { |
| let percentage, pos; |
| let { limit } = this.state; |
| let { min, max } = this.props; |
| percentage = (value - min) / (max - min); |
| pos = Math.round(percentage * limit); |
| |
| return pos; |
| } |
| |
| getValueFromPosition = (pos) => { |
| let percentage, value; |
| let { limit } = this.state; |
| let { orientation, min, max, step } = this.props; |
| percentage = (maxmin(pos, 0, limit) / (limit || 1)); |
| |
| if (orientation === 'horizontal') { |
| value = step * Math.round(percentage * (max - min) / step) + min; |
| } else { |
| value = max - (step * Math.round(percentage * (max - min) / step) + min); |
| } |
| |
| return value; |
| } |
| |
| position = (e) => { |
| let pos, value, { grab } = this.state; |
| let { orientation } = this.props; |
| const node = findDOMNode(this.refs.slider); |
| const coordinateStyle = constants.orientation[orientation].coordinate; |
| const directionStyle = constants.orientation[orientation].direction; |
| const coordinate = e['client' + capitalize(coordinateStyle)]; |
| const direction = node.getBoundingClientRect()[directionStyle]; |
| |
| pos = coordinate - direction - grab; |
| value = this.getValueFromPosition(pos); |
| |
| return value; |
| } |
| |
| coordinates = (pos) => { |
| let value, fillPos, handlePos; |
| let { limit, grab } = this.state; |
| let { orientation } = this.props; |
| value = this.getValueFromPosition(pos); |
| handlePos = this.getPositionFromValue(value); |
| |
| if (orientation === 'horizontal') { |
| fillPos = handlePos + grab; |
| } else { |
| fillPos = limit - handlePos + grab; |
| } |
| |
| return { |
| fill: fillPos, |
| handle: handlePos, |
| }; |
| } |
| |
| render() { |
| let dimension, direction, position, coords, fillStyle, handleStyle, displayValue; |
| let { value, orientation, className } = this.props; |
| |
| dimension = constants.orientation[orientation].dimension; |
| direction = constants.orientation[orientation].direction; |
| |
| position = this.getPositionFromValue(value); |
| coords = this.coordinates(position); |
| |
| fillStyle = {[dimension]: `${coords.fill}px`}; |
| handleStyle = {[direction]: `${coords.handle}px`}; |
| |
| if(this.props.displayValue) { |
| displayValue = <div>{this.props.value}</div>; |
| } |
| |
| return ( |
| <div |
| ref="slider" |
| className={joinClasses('rangeslider ', 'rangeslider-' + orientation, className)} |
| onMouseDown={this.handleSliderMouseDown} |
| onClick={this.handleNoop} |
| style={{display:'flex'}}> |
| <div |
| ref="fill" |
| className="rangeslider__fill" |
| style={fillStyle} /> |
| <div |
| ref="handle" |
| className="rangeslider__handle" |
| onMouseDown={this.handleKnobMouseDown} |
| onClick={this.handleNoop} |
| style={handleStyle}> |
| {displayValue} |
| </div> |
| </div> |
| ); |
| } |
| } |
| |
| export default Slider; |