apeman-react-range
Version:
apeman react package for range input component.
273 lines (233 loc) • 7.73 kB
JSX
/**
* apeman react package for range input component.
* @constructor ApRange
*/
;
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;