apeman-react-range
Version:
apeman react package for range input component.
263 lines (225 loc) • 6.02 kB
JSX
/**
* Range input component.
* @class ApRange
*/
import React, {PropTypes as types} from 'react'
import classnames from 'classnames'
import ReactDOM from 'react-dom'
import chopcal from 'chopcal'
import rangecal from 'rangecal'
import ApRangeHandle from './ap_range_handle'
import ApRangeLabel from './ap_range_label'
import {ApTouchable} from 'apeman-react-touchable'
/** @lends ApRange */
const ApRange = React.createClass({
// --------------------
// Specs
// --------------------
propTypes: {
/** Range from value */
from: types.number,
/** Range to value */
to: types.number,
/** Min value for range from */
min: types.number,
/** Max value for range to */
max: types.number,
/** Step for value */
step: types.number,
/** Handler for change */
onChange: types.func,
barOnly: types.bool
},
statics: {},
getInitialState () {
const s = this
let { props } = s
return {
minX: 0,
maxX: 1200,
fromX: 0,
toX: 1200,
fromValue: props.from,
toValue: props.to
}
},
getDefaultProps () {
return {
from: 25,
to: 75,
min: 0,
max: 100,
step: 0.01,
barOnly: false
}
},
render () {
const s = this
const { state, props } = s
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>
<ApRangeHandle onMove={ s.rangeFromHandleDidMove }
shouldMove={ s.shouldRangeFromHandleMove }
x={ state.fromX }
minX={ state.minX }
maxX={ state.maxX }
className="ap-range-handle-from"/>
<ApRangeHandle onMove={ s.rangeToHandleDidMove }
shouldMove={ s.shouldRangeToHandleMove }
x={ state.toX }
minX={ state.minX }
maxX={ state.maxX }
className="ap-range-handle-to"/>
</div>
{ s._renderLabel(props.max) }
</div>
</div>
)
},
// --------------------
// Lifecycle
// --------------------
componentDidMount () {
const s = this
window.addEventListener('resize', s.resizeRange)
s.resizeRange()
s.resetRangeValues()
},
componentWillReceiveProps (nextProps) {
const s = this
s.resetRangeValues()
},
componentWillUnmount () {
const s = this
window.removeEventListener('resize', s.resizeRange)
},
// ------------------
// Helper
// ------------------
resizeRange (e) {
const s = this
let state = s.state
let w = ReactDOM.findDOMNode(s).offsetWidth
let minX = 0
let maxX = w
let fromRate = s._rateWithValue(state.fromValue)
let toRate = s._rateWithValue(state.toValue)
s.setState({
minX: minX,
maxX: maxX,
fromX: rangecal.value(minX, maxX, fromRate),
toX: rangecal.value(minX, maxX, toRate)
})
},
rangeBarDidTap () {
},
rangeFromHandleDidMove (e) {
const s = this
let fromValue = s._valueWithX(e.detail.x)
s.setRangeValues(fromValue, s.state.toValue, true)
},
rangeToHandleDidMove (e) {
const s = this
let toValue = s._valueWithX(e.detail.x)
s.setRangeValues(s.state.fromValue, toValue, false)
},
shouldRangeFromHandleMove () {
const s = this
return true
},
shouldRangeToHandleMove () {
const s = this
return true
},
resetRangeValues () {
const s = this
setTimeout(function () {
let state = s.state
s.setRangeValues(state.fromValue, state.toValue, true)
}, 0)
},
setRangeValues (fromValue, toValue, forwarding) {
const s = this
let { state, props } = s
let { minX, maxX } = state
let step = props.step
if (toValue < fromValue) {
if (forwarding) {
toValue = fromValue
} else {
fromValue = toValue
}
}
let fromRate = s._rateWithValue(fromValue)
let 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 (value) {
const s = this
let { min, max } = s.props
value = rangecal.round(min, max, value)
return chopcal.round(rangecal.rate(min, max, value), 0.01)
},
_valueWithRate (rate) {
const s = this
let { min, max } = s.props
let value = chopcal.round(rangecal.value(min, max, rate), 0.01)
return rangecal.round(min, max, value)
},
_valueWithX (x) {
const s = this
let { minX, maxX } = s.state
let rate = rangecal.rate(minX, maxX, x + 2)
return s._valueWithRate(rate)
},
_renderLabel (value) {
const s = this
let { props } = s
if (props.barOnly) {
return null
}
return (
<ApRangeLabel value={ value }/>
)
}
})
export default ApRange