diff --git a/examples/simple.js b/examples/simple.js index 8893e40..5f57cca 100644 --- a/examples/simple.js +++ b/examples/simple.js @@ -4,6 +4,7 @@ require('rc-slider/assets/index.less'); var React = require('react'); var Slider = require('rc-slider'); +var style = {width:400,margin:100}; function onChange(v) { console.log(v); @@ -11,23 +12,35 @@ function onChange(v) { React.render(
-
- +
+
-
- +
+
-
- +
+
-
- +
+
-
+
+ +
+
+ +
+
+ +
+
+ +
+

包含关系

-
+

并列关系

diff --git a/src/Handle.jsx b/src/Handle.jsx index d355c92..3869059 100644 --- a/src/Handle.jsx +++ b/src/Handle.jsx @@ -10,6 +10,18 @@ export default class Handle extends React.Component { }; } + showTooltip() { + this.setState({ + isTooltipVisible: true, + }); + } + + hideTooltip() { + this.setState({ + isTooltipVisible: false, + }); + } + render() { const props = this.props; const {className, tipTransitionName, offset, value} = props; @@ -36,17 +48,6 @@ export default class Handle extends React.Component { {handle} ); } - - showTooltip() { - this.setState({ - isTooltipVisible: true, - }); - } - hideTooltip() { - this.setState({ - isTooltipVisible: false, - }); - } } Handle.propTypes = { diff --git a/src/Slider.jsx b/src/Slider.jsx index 1555004..9ea885e 100644 --- a/src/Slider.jsx +++ b/src/Slider.jsx @@ -30,46 +30,62 @@ function pauseEvent(e) { } } -function getValueFromIndex(props) { - const marksLen = props.marks.length; - const index = ('index' in props ? props.index : props.defaultIndex); - - let value; - if (marksLen > 0) { - value = ((props.max - props.min) / (marksLen - 1)) * (index); - value = value.toFixed(5); - } - return value; -} - class Slider extends React.Component { constructor(props) { super(props); - // Note: Maybe `value` is `0`, `false` and so on. - // So, check the existence of `value` with `in`. - let value = ('value' in props ? props.value : props.defaultValue); - value = this._trimAlignValue(value); + let upperBound; + let lowerBound; + if (props.range) { + const values = (props.values || props.defaultValues); + upperBound = this.trimAlignValue(values[1]); + lowerBound = this.trimAlignValue(values[0]); + } else if (props.marks.length > 0) { + upperBound = this.calcValueFromProps(props); + } else { + // Note: Maybe `value` is `0`. + // So, check the existence of `value` with `in`. + const value = ('value' in props ? props.value : props.defaultValue); + upperBound = this.trimAlignValue(value); + } - const marksLen = props.marks.length; - if (marksLen > 0) { - value = getValueFromIndex(props); + let recent; + if (props.range && upperBound === lowerBound) { + if (lowerBound === props.max) { + recent = 'lowerBound'; + } + if (upperBound === props.min) { + recent = 'upperBound'; + } + } else { + recent = 'upperBound'; } this.state = { - dragging: false, - value: value, + handle: null, + recent: recent, + upperBound: upperBound, + // If Slider is not range, set `lowerBound` equal to `min`. + lowerBound: (lowerBound || props.min), }; } componentWillReceiveProps(nextProps) { - if ('value' in nextProps) { + if (nextProps.range) { + const values = nextProps.values; + if (values) { + this.setState({ + upperBound: values[1], + lowerBound: values[0], + }); + } + } else if ('value' in nextProps) { this.setState({ - value: nextProps.value, + upperBound: nextProps.value, }); } else if ('index' in nextProps) { this.setState({ - value: getValueFromIndex(nextProps), + upperBound: this.calcValueFromProps(nextProps), }); } } @@ -94,19 +110,24 @@ class Slider extends React.Component { const props = this.props; const state = this.state; - let value = state.value; - const oldValue = value; - const diffPosition = position - this.startPosition; - const diffValue = diffPosition / this.getSliderLength() * (props.max - props.min); - value = this._trimAlignValue(this.startValue + diffValue); - if (value !== oldValue && !('value' in props) && !('index' in props)) { - this.setState({value: value}); + const value = this.trimAlignValue(this.startValue + diffValue); + const oldValue = state[state.handle]; + if (value === oldValue) return; + + if (!('value' in props) && !('index' in props)) { + this.setState({[state.handle]: value}); } - if (value !== oldValue) { - this._triggerEvents('onChange', value); + + if (props.range) { + // `this.state` will not be updated immediately after `this.setState`. + // So, create a similar object. + const data = Object.assign({}, state, {[state.handle]: value}); + this.triggerEvents('onChange', [data.lowerBound, data.upperBound]); + } else { + this.triggerEvents('onChange', value); } } @@ -114,30 +135,52 @@ class Slider extends React.Component { if (isNotTouchEvent(e)) return; const position = getTouchPosition(e); - const value = this._calValueByPos(position); - this._triggerEvents('onChange', value); - this._start(position, value); - this._addDocumentEvents('touch'); + this.onStart(position); + this.addDocumentEvents('touch'); pauseEvent(e); } onSliderMouseDown(e) { const position = getMousePosition(e); - const value = this._calValueByPos(position); - this._triggerEvents('onChange', value); - this._start(position, value); - this._addDocumentEvents('mouse'); + this.onStart(position); + this.addDocumentEvents('mouse'); pauseEvent(e); } - getIndex(v) { - const props = this.props; - const value = v === undefined ? this.state.value : v; + onStart(position) { + this.triggerEvents('onBeforeChange'); - if (props.marks.length === 0) { - return Math.floor((value - props.min) / props.step); + const value = this.calcValueByPos(position); + this.startValue = value; + this.startPosition = position; + + const {upperBound, lowerBound} = this.state; + const isUpperBoundCloser = Math.abs(upperBound - value) < Math.abs(lowerBound - value); + let valueNeedChanging = (!this.props.range || isUpperBoundCloser) ? 'upperBound' : 'lowerBound'; + const isAtTheSamePoint = (upperBound === lowerBound); + valueNeedChanging = isAtTheSamePoint ? this.state.recent : valueNeedChanging; + + this.setState({ + handle: valueNeedChanging, + recent: valueNeedChanging, + [valueNeedChanging]: value, + }); + + if (this.props.range) { + const data = Object.assign({}, this.state, {[valueNeedChanging]: value}); + this.triggerEvents('onChange', [data.lowerBound, data.upperBound]); + } else { + this.triggerEvents('onChange', value); } - const unit = ((props.max - props.min) / (props.marks.length - 1)).toFixed(5); + } + + getIndex(value) { + const {marks, min, max, step} = this.props; + + if (marks.length === 0) { + return Math.floor((value - min) / step); + } + const unit = ((max - min) / (marks.length - 1)).toFixed(5); return Math.round(value / unit); } @@ -157,71 +200,25 @@ class Slider extends React.Component { return rect.left; } - render() { - const state = this.state; - const {value, dragging} = state; + trimAlignValue(v) { + const state = this.state || {}; const props = this.props; - const {className, prefixCls, disabled, isIncluded, withDots} = props; - const {marks, step, max, min, tipTransitionName, children} = props; - const marksLen = marks.length; - - const sliderClassName = rcUtil.classSet({ - [prefixCls]: true, - [prefixCls + '-disabled']: disabled, - [className]: !!className, - }); - - const offset = this._calcOffset(value); - - let track = null; - if (isIncluded) { - const trackClassName = prefixCls + '-track'; - track = ; - } - - const handleClassName = prefixCls + '-handle'; - - let steps = null; - if (marksLen > 0 || (step > 1 && withDots)) { - const stepsClassName = prefixCls + '-step'; - const stepNum = marksLen > 0 ? marksLen : Math.floor((max - min) / step) + 1; - steps = (); - } - - let mark = null; - if (marksLen > 0) { - const markClassName = prefixCls + '-mark'; - mark = (); - } - - return ( -
- {track} - 0} /> - {steps} - {mark} - {children} -
- ); - } - - _trimAlignValue(v, propsArg) { - let val = v; - const props = propsArg || this.props; const {marks, min, max} = props; const step = marks.length > 0 ? (max - min) / (marks.length - 1) : props.step; + let val = v; if (val <= min) { val = min; } if (val >= max) { val = max; } + if (state.handle === 'upperBound' && val <= state.lowerBound) { + val = state.lowerBound; + } + if (state.handle === 'lowerBound' && val >= state.upperBound) { + val = state.upperBound; + } const valModStep = (val - min) % step; let alignValue = val - valModStep; @@ -233,28 +230,36 @@ class Slider extends React.Component { return parseFloat(alignValue.toFixed(5)); } - _calcOffset(value) { + calcOffset(value) { const {min, max} = this.props; const ratio = (value - min) / (max - min); return ratio * 100; } - _calcValue(offset) { + calcValue(offset) { const {min, max} = this.props; const ratio = offset / this.getSliderLength(); return ratio * (max - min) + min; } - _calValueByPos(position) { + calcValueByPos(position) { const pixelOffset = position - this.getSliderStart(); - const nextValue = this._trimAlignValue(this._calcValue(pixelOffset)); - this.setState({ - value: nextValue, - }); + const nextValue = this.trimAlignValue(this.calcValue(pixelOffset)); return nextValue; } - _triggerEvents(event, v) { + calcValueFromProps(props) { + const marksLen = props.marks.length; + if (marksLen > 0) { + const index = ('index' in props ? props.index : props.defaultIndex); + const value = ((props.max - props.min) / (marksLen - 1)) * (index); + // `'1' / 1 => 1`, to make sure that the returned value is a `Number`. + return value.toFixed(5) / 1; + } + return ('value' in props ? props.value : props.defaultValue); + } + + triggerEvents(event, v) { const props = this.props; const hasMarks = (props.marks.length > 0); if (props[event]) { @@ -270,7 +275,7 @@ class Slider extends React.Component { } } - _addDocumentEvents(type) { + addDocumentEvents(type) { if (type === 'touch') { // just work for chrome iOS Safari and Android Browser this.onTouchMoveListener = DomUtils.addEventListener(document, 'touchmove', this.onTouchMove.bind(this)); @@ -281,7 +286,7 @@ class Slider extends React.Component { } } - _removeEventons(type) { + removeEventons(type) { if (type === 'touch') { this.onTouchMoveListener.remove(); this.onTouchUpListener.remove(); @@ -291,21 +296,75 @@ class Slider extends React.Component { } } - _start(position, value) { - this._triggerEvents('onBeforeChange'); - this.startValue = value; - this.startPosition = position; - this.setState({ - dragging: true, - }); + end(type) { + this.removeEventons(type); + this.triggerEvents('onAfterChange'); + this.setState({ handle: null }); } - end(type) { - this._removeEventons(type); - this._triggerEvents('onAfterChange'); - this.setState({ - dragging: false, + render() { + const {handle, upperBound, lowerBound} = this.state; + const props = this.props; + const {className, prefixCls, disabled, isIncluded, withDots, range} = props; + const {marks, step, max, min, tipTransitionName, children} = props; + const marksLen = marks.length; + + const sliderClassName = rcUtil.classSet({ + [prefixCls]: true, + [prefixCls + '-disabled']: disabled, + [className]: !!className, }); + + const upperOffset = this.calcOffset(upperBound); + const lowerOffset = this.calcOffset(lowerBound); + + let track = null; + if (isIncluded || range) { + const trackClassName = prefixCls + '-track'; + track = ; + } + + const handleClassName = prefixCls + '-handle'; + const isNoTip = marksLen > 0; + const upper = (); + + let lower = null; + if (range) { + lower = (); + } + + const upperIndex = this.getIndex(upperBound); + + let steps = null; + if (marksLen > 0 || (step > 1 && withDots)) { + const stepsClassName = prefixCls + '-step'; + const stepNum = marksLen > 0 ? marksLen : Math.floor((max - min) / step) + 1; + steps = (); + } + + let mark = null; + if (marksLen > 0) { + const markClassName = prefixCls + '-mark'; + mark = (); + } + + return ( +
+ {track} + {upper} + {lower} + {steps} + {mark} + {children} +
+ ); } } @@ -314,8 +373,10 @@ Slider.propTypes = { max: React.PropTypes.number, step: React.PropTypes.number, defaultValue: React.PropTypes.number, + defaultValues: React.PropTypes.arrayOf(React.PropTypes.number), defaultIndex: React.PropTypes.number, value: React.PropTypes.number, + values: React.PropTypes.arrayOf(React.PropTypes.number), index: React.PropTypes.number, marks: React.PropTypes.array, isIncluded: React.PropTypes.bool, @@ -328,6 +389,7 @@ Slider.propTypes = { onAfterChange: React.PropTypes.func, tipTransitionName: React.PropTypes.string, withDots: React.PropTypes.bool, + range: React.PropTypes.bool, }; Slider.defaultProps = { @@ -335,6 +397,7 @@ Slider.defaultProps = { max: 100, step: 1, defaultValue: 0, + defaultValues: [0, 0], defaultIndex: 0, marks: [], isIncluded: true, @@ -343,6 +406,7 @@ Slider.defaultProps = { disabled: false, tipTransitionName: '', withDots: false, + range: false, }; export default Slider; diff --git a/src/Steps.jsx b/src/Steps.jsx index dc49d2d..ea7ec08 100644 --- a/src/Steps.jsx +++ b/src/Steps.jsx @@ -1,7 +1,7 @@ import React from 'react'; import rcUtil from 'rc-util'; -const Steps = ({className, stepNum, isIncluded, index}) => { +const Steps = ({className, stepNum, isIncluded, lowerIndex, upperIndex}) => { const dotClassName = className.replace('step', 'dot'); const unit = 100 / (stepNum - 1); @@ -10,7 +10,8 @@ const Steps = ({className, stepNum, isIncluded, index}) => { const offset = unit * i + '%'; const style = { left: offset }; - const isActived = (isIncluded && i <= index) || (!isIncluded && i === index); + const isActived = (isIncluded && i <= upperIndex && i >= lowerIndex ) || + (!isIncluded && i === upperIndex); const stepClassName = rcUtil.classSet({ [dotClassName]: true, [dotClassName + '-active']: isActived, diff --git a/tests/index.spec.js b/tests/index.spec.js index 8c18335..b2b5057 100644 --- a/tests/index.spec.js +++ b/tests/index.spec.js @@ -26,7 +26,7 @@ describe('rc-slider', function () { expect(node.find('.rc-slider').length).to.be(1); expect(node.find('.rc-slider-handle').length).to.be(1); expect(node.find('.rc-slider-track').length).to.be(1); - expect(slider.state.value).to.be(40); + expect(slider.state.upperBound).to.be(40); var trackWidth = node.find('.rc-slider-track')[0].style.width; expect(trackWidth).to.eql(node.find('.rc-slider-handle')[0].style.left); }); @@ -40,7 +40,7 @@ describe('rc-slider', function () { expect(node.find('.rc-slider').length).to.be(1); expect(node.find('.rc-slider-handle').length).to.be(1); expect(node.find('.rc-slider-track').length).to.be(1); - expect(slider.state.value).to.be(0); + expect(slider.state.upperBound).to.be(0); var sliderWithDots = React.render( , @@ -62,7 +62,7 @@ describe('rc-slider', function () { expect(node.find('.rc-slider-dot').length).to.be(slider.props.marks.length); expect(node.find('.rc-slider-mark').length).to.be(1); expect(node.find('.rc-slider-mark-text').length).to.be(slider.props.marks.length); - expect(slider.getIndex()).to.be(3); + expect(slider.getIndex(slider.state.upperBound)).to.be(3); }); // it('should mouseDown works!', function (done) {