UNPKG

apeman-react-range

Version:
273 lines (233 loc) 7.73 kB
/** * apeman react package for range input component. * @constructor ApRange */ "use strict"; const React = require('react'), ReactDOM = require('react-dom'), types = React.PropTypes, chopcal = require('chopcal'), rangecal = require('rangecal'), numcal = require('numcal'), classnames = require('classnames'), ApTouchable = require('apeman-react-touchable')['ApTouchable'], ApDraggable = require('apeman-react-draggable')['ApDraggable'], ApRangeLabel = require('./ap_range_label'); /** @lends ApRange */ const ApRange = React.createClass({ //-------------------- // Specs //-------------------- propTypes: { from: types.number, to: types.number, min: types.number, max: types.number, step: types.number, onChange: types.func, barOnly: types.bool }, statics: {}, getInitialState: function () { let s = this, props = s.props; return { minX: 0, maxX: 1200, fromX: 0, toX: 1200, fromValue: props.from, toValue: props.to }; }, getDefaultProps: function () { return { from: 25, to: 75, min: 0, max: 100, step: 0.01, barOnly: false } }, render: function () { let s = this, props = s.props, state = s.state; return ( <div className={classnames('ap-range', props.className)}> <div className="ap-range-inner"> {s._renderLabel(props.min)} <div className="ap-range-bar-wrap"> <ApTouchable onTap={s.rangeBarDidTap}> <div className="ap-range-bar"> <div className="ap-range-bar-bg"></div> <div className="ap-range-bar-highlight" style={ {left:state.fromX, width:(state.toX - state.fromX)} }> </div> </div> </ApTouchable> <ApDraggable onMove={s.rangeFromHandleDidMove} shouldMove={s.shouldRangeFromHandleMove} x={state.fromX} minX={state.minX} maxX={state.maxX} className="ap-range-handle ap-range-handle-from" direction="HORIZONTAL"> <div className="ap-range-handle-icon"> </div> </ApDraggable> <ApDraggable onMove={s.rangeToHandleDidMove} shouldMove={s.shouldRangeToHandleMove} x={state.toX} minX={state.minX} maxX={state.maxX} className="ap-range-handle ap-range-handle-to" direction="HORIZONTAL"> <div className="ap-range-handle-icon"> </div> </ApDraggable> </div> {s._renderLabel(props.max)} </div> </div> ) }, //-------------------- // Lifecycle //-------------------- componentDidMount: function () { let s = this; window.addEventListener('resize', s.resizeRange); s.resizeRange(); s.resetRangeValues(); }, componentWillReceiveProps: function (nextProps) { let s = this; s.resetRangeValues(); }, componentWillUnmount: function () { let s = this; window.removeEventListener('resize', s.resizeRange); }, //------------------ // Helper //------------------ resizeRange: function (e) { let s = this; let state = s.state; let w = ReactDOM.findDOMNode(s).offsetWidth; let minX = 0, maxX = w; let fromRate = s._rateWithValue(state.fromValue), toRate = s._rateWithValue(state.toValue); s.setState({ minX: minX, maxX: maxX, fromX: rangecal.value(minX, maxX, fromRate), toX: rangecal.value(minX, maxX, toRate) }); }, rangeBarDidTap: function () { }, rangeFromHandleDidMove: function (e) { let s = this, fromValue = s._valueWithX(e.detail.x); s.setRangeValues(fromValue, s.state.toValue, true); }, rangeToHandleDidMove: function (e) { let s = this, toValue = s._valueWithX(e.detail.x); s.setRangeValues(s.state.fromValue, toValue, false); }, shouldRangeFromHandleMove: function () { let s = this; return true; }, shouldRangeToHandleMove: function () { let s = this; return true; }, resetRangeValues: function () { let s = this; setTimeout(function () { let state = s.state; s.setRangeValues(state.fromValue, state.toValue, true); }); }, setRangeValues: function (fromValue, toValue, forwarding) { let s = this; let state = s.state, props = s.props; let minX = state.minX, maxX = state.maxX; let step = props.step; if (toValue < fromValue) { if (forwarding) { toValue = fromValue; } else { fromValue = toValue; } } let fromRate = s._rateWithValue(fromValue), toRate = s._rateWithValue(toValue); s.setState({ fromValue: fromValue, toValue: toValue, fromX: rangecal.value(minX, maxX, fromRate), toX: rangecal.value(minX, maxX, toRate) }); fromValue = chopcal.round(fromValue, step); toValue = chopcal.round(toValue, step); let duplicate = (s._fromValue === fromValue) && (s._toValue === toValue); if (duplicate) { return; } s._fromValue = fromValue; s._toValue = toValue; if (props.onChange) { props.onChange( fromValue, toValue, { element: s } ); } }, //------------------ // Private //------------------ _rateWithValue: function (value) { let s = this; let min = s.props.min, max = s.props.max; value = rangecal.round(min, max, value); return chopcal.round(rangecal.rate(min, max, value), 0.01); }, _valueWithRate: function (rate) { let s = this; let min = s.props.min, max = s.props.max; let value = chopcal.round(rangecal.value(min, max, rate), 0.01); return rangecal.round(min, max, value); }, _valueWithX: function (x) { let s = this; let minX = s.state.minX, maxX = s.state.maxX; let rate = rangecal.rate(minX, maxX, x + 2); return s._valueWithRate(rate); }, _renderLabel: function (value) { let s = this, props = s.props; if (props.barOnly) { return null; } return (<ApRangeLabel value={value}/>); } }); module.exports = ApRange;