import React from 'react'; import PropTypes from 'prop-types'; import { findDOMNode } from 'react-dom'; const MARGIN_LR = 8; // Left/ Right margin /** * Horizontal Slider */ export default class Slider extends React.Component { static defaultProps = { axis: false, min: 0, max: 1, scale: 1 // SVG render scale }; static propTypes = { axis: PropTypes.bool, axisUnit: PropTypes.string, max: PropTypes.number, min: PropTypes.number, onChange: PropTypes.func.isRequired, onResize: PropTypes.func, percent: PropTypes.number.isRequired, scale: PropTypes.number }; /** * Constructor * @param {Object} props React Component properties */ constructor(props) { super(props); this._down = this._down.bind(this); this._move = this._move.bind(this); this._up = this._up.bind(this); this._updatePercent = this._updatePercent.bind(this); this._updateDimensions = this._updateDimensions.bind(this); this.state = { width: 0 }; } /** * On Mouse/Touch down handler * @param {SyntheticEvent} event Event */ _down(event) { let rect = event.currentTarget.getBoundingClientRect(); this.left = rect.left; this.width = rect.width; this._move(event); } /** * Update the slider percentage on move * @param {SyntheticEvent} event Event */ _move(event) { if(this.width !== null && this.left != null) { let clientX = event.touches ? event.touches[0].clientX : event.clientX; event.preventDefault(); this._updatePercent(clientX - this.left, this.width); } } /** * On Mouse/Touch up handler * @param {Event} event DOM Event */ _up(event) { event.preventDefault(); this.left = null; this.width = null; } /** * Determine if the user is still dragging * @param {SyntheticEvent} event Event */ _enter(event) { if(event.buttons !== 1) { this.left = null; this.width = null; } } /** * Update the slider percentage * @param {number} pos Slider drag position * @param {number} width Slider width * @param {Event} event DOM Event */ _updatePercent(pos, width) { this.props.onChange(Math.min(Math.max(pos / width, 0), 1)); } /** * Update dimenions from rendered DOM */ _updateDimensions() { this.setState({ outerWidth: findDOMNode(this).getBoundingClientRect().width }); } /** * Add listeners when about to mount */ componentWillMount() { if (this.props.onResize) { this.resizeListener = this.props.onResize(this._updateDimensions); } } /** * Trigger DOM updates on mount */ componentDidMount() { this._updateDimensions(); } /** * Remove listeners on unmount */ componentWillUnmount() { if (this.resizeListener) { this.resizeListener.remove(); } } /** * Render the slider * @return {React.Component} The slider */ render() { let outerWidth = this.state.outerWidth; let { axis, axisUnit, min, max, scale } = this.props; let style = { width: '100%', height: axis ? '2.5em' : '1.5em', boxSizing: 'border-box' }; if (!outerWidth) { return ; } let margin = MARGIN_LR * scale; let width = outerWidth - (margin * 2); let pctPos = width * this.props.percent; return {axis && {min + axisUnit} {(min + max / 2) + axisUnit} {max + axisUnit} } ; } }