diff --git a/assets/index.less b/assets/index.less
index a736195..66717e3 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -131,55 +131,6 @@
cursor: not-allowed!important;
}
}
-
- // slider tooltip style
- &-tooltip {
- position: absolute;
- left: -9999px;
- top: -9999px;
- z-index: 4;
- visibility: visible;
-
- .borderBox();
-
- &-hidden {
- display: none;
- }
-
- &-placement-top {
- padding: @tooltip-arrow-width 0 @tooltip-distance 0;
- }
-
- &-inner {
- padding: 6px 2px;
- min-width: 24px;
- height: 24px;
- font-size: 12px;
- line-height: 1;
- color: @tooltip-color;
- text-align: center;
- text-decoration: none;
- background-color: @tooltip-bg;
- border-radius: @border-radius-base;
- box-shadow: 0 0 4px #d9d9d9;
- }
-
- &-arrow {
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
- }
-
- &-placement-top &-arrow {
- bottom: @tooltip-distance - @tooltip-arrow-width;
- left: 50%;
- margin-left: -@tooltip-arrow-width;
- border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
- border-top-color: @tooltip-arrow-color;
- }
- }
}
.motion-common() {
@@ -241,3 +192,51 @@
transform: scale(0, 0);
}
}
+
+.rc-tooltip {
+ position: absolute;
+ left: -9999px;
+ top: -9999px;
+ z-index: 4;
+ visibility: visible;
+
+ .borderBox();
+
+ &-hidden {
+ display: none;
+ }
+
+ &-placement-top {
+ padding: @tooltip-arrow-width 0 @tooltip-distance 0;
+ }
+
+ &-inner {
+ padding: 6px 2px;
+ min-width: 24px;
+ height: 24px;
+ font-size: 12px;
+ line-height: 1;
+ color: @tooltip-color;
+ text-align: center;
+ text-decoration: none;
+ background-color: @tooltip-bg;
+ border-radius: @border-radius-base;
+ box-shadow: 0 0 4px #d9d9d9;
+ }
+
+ &-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ }
+
+ &-placement-top &-arrow {
+ bottom: @tooltip-distance - @tooltip-arrow-width;
+ left: 50%;
+ margin-left: -@tooltip-arrow-width;
+ border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
+ border-top-color: @tooltip-arrow-color;
+ }
+}
\ No newline at end of file
diff --git a/examples/marks.js b/examples/marks.js
index a487bc0..7592dca 100644
--- a/examples/marks.js
+++ b/examples/marks.js
@@ -7,7 +7,13 @@ var ReactDOM = require('react-dom');
var Slider = require('rc-slider');
var style = {width: 400, margin: 50};
-var marks = ['A','B','C','D', 'E', 'F'];
+var marks = {
+ 0: '0°C',
+ 26: '26°C',
+ 37: '37°C',
+ 100: '100°C'
+};
+
var log = function(value) {
console.log(value);
};
@@ -16,29 +22,29 @@ ReactDOM.render(
Slider with marks, `included=true`
-
+
Slider with marks and steps, `included=true`
-
+
Slider with marks, `included=false`
-
+
Slider with marks and steps, `included=false`
-
+
Range with marks and steps
-
+
, document.getElementById('__react-content'));
diff --git a/package.json b/package.json
index 6ea1296..fd1ec6a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rc-slider",
- "version": "2.4.0",
+ "version": "3.0.0",
"description": "slider ui component for react",
"keywords": [
"react",
diff --git a/src/Handle.jsx b/src/Handle.jsx
index 80df82e..929d330 100644
--- a/src/Handle.jsx
+++ b/src/Handle.jsx
@@ -39,7 +39,7 @@ export default class Handle extends React.Component {
const isTooltipVisible = dragging || this.state.isTooltipVisible;
return ({tipFormatter ? tipFormatter(value) : value}}
diff --git a/src/Marks.jsx b/src/Marks.jsx
index 7a03023..db7ace7 100644
--- a/src/Marks.jsx
+++ b/src/Marks.jsx
@@ -1,35 +1,36 @@
import React from 'react';
import rcUtil from 'rc-util';
-const Marks = ({className, marks, index, included}) => {
- const marksLen = marks.length;
- const unit = 100 / (marksLen - 1);
+const Marks = ({className, marks, included, upperBound, lowerBound, max, min}) => {
+ const marksKeys = Object.keys(marks);
+ const marksCount = marksKeys.length;
+ const unit = 100 / (marksCount - 1);
const markWidth = unit / 2 + '%';
- const elements = [];
- for (let i = 0; i < marksLen; i++) {
- const isActived = (included && i <= index) || (!included && i === index);
+ const range = max - min;
+ const elements = marksKeys.map(parseFloat).map((point) => {
+ const isActived = (!included && point === upperBound) ||
+ (included && point <= upperBound && point >= lowerBound);
const markClassName = rcUtil.classSet({
[className + '-text']: true,
[className + '-text-active']: isActived,
});
const style = { width: markWidth };
- const offset = unit * i;
- if (i === marksLen - 1) {
+ if (point === marksCount - 1) {
style.right = -unit / 4 + '%';
+ } else if (point === 0) {
+ style.left = -unit / 4 + '%';
} else {
- style.left = (i > 0 ? offset - unit / 4 : -unit / 4) + '%';
+ style.left = (point - min) / range * 100 - unit / 4 + '%';
}
- elements.push(
- {marks[i]}
- );
- }
+ return (
+ {marks[point]}
+ );
+ });
- return (
- {elements}
-
);
+ return {elements}
;
};
export default Marks;
diff --git a/src/Slider.jsx b/src/Slider.jsx
index 4dde98a..5cecc7c 100644
--- a/src/Slider.jsx
+++ b/src/Slider.jsx
@@ -25,40 +25,34 @@ function pauseEvent(e) {
e.preventDefault();
}
-// This is an utility method, tries to get property, then defaultPropery with
-// special check using 'in', because propery can be '0'
-function propOrDefault(props, name, fallback) {
- const defaultName = 'default' + name.charAt(0).toUpperCase() + name.substring(1);
- const defaultValue = (defaultName in props ? props[defaultName] : fallback);
- return (name in props ? props[name] : defaultValue);
+function isEmpty(collection) {
+ return Object.keys(collection).length === 0;
}
class Slider extends React.Component {
constructor(props) {
super(props);
+ const {range, min, max} = props;
+ const initialValue = range ? [min, min] : min;
+ const defaultValue = ('defaultValue' in props ? props.defaultValue : initialValue);
+ const value = ('value' in props ? props.value : defaultValue);
+
let upperBound;
let lowerBound;
- const initialValue = props.range ? [0, 0] : 0;
- if (props.marks.length > 0) {
- const index = propOrDefault(props, 'index', initialValue);
- ({lowerBound, upperBound} = this.getBoundsFromIndex(index, props));
+ if (props.range) {
+ lowerBound = this.trimAlignValue(value[0]);
+ upperBound = this.trimAlignValue(value[1]);
} else {
- const value = propOrDefault(props, 'value', initialValue);
- if (props.range) {
- lowerBound = this.trimAlignValue(value[0]);
- upperBound = this.trimAlignValue(value[1]);
- } else {
- upperBound = this.trimAlignValue(value);
- }
+ upperBound = this.trimAlignValue(value);
}
let recent;
if (props.range && upperBound === lowerBound) {
- if (lowerBound === props.max) {
+ if (lowerBound === max) {
recent = 'lowerBound';
}
- if (upperBound === props.min) {
+ if (upperBound === min) {
recent = 'upperBound';
}
} else {
@@ -70,7 +64,7 @@ class Slider extends React.Component {
recent: recent,
upperBound: upperBound,
// If Slider is not range, set `lowerBound` equal to `min`.
- lowerBound: (lowerBound || props.min),
+ lowerBound: (lowerBound || min),
};
}
@@ -87,12 +81,26 @@ class Slider extends React.Component {
this.setState({
upperBound: nextProps.value,
});
- } else if ('index' in nextProps) {
- const index = ('index' in nextProps ? nextProps.index : nextProps.defaultIndex);
- this.setState(this.getBoundsFromIndex(index, nextProps));
}
}
+ onChange(handle, value) {
+ const props = this.props;
+ const isNotControlled = !('value' in props);
+ if (isNotControlled) {
+ this.setState({[handle]: value});
+ }
+
+ const state = this.state;
+ const data = {
+ upperBound: state.upperBound,
+ lowerBound: state.lowerBound,
+ };
+ data[handle] = value;
+ const changedValue = props.range ? [data.lowerBound, data.upperBound] : data.upperBound;
+ props.onChange(changedValue);
+ }
+
onMouseMove(e) {
const position = getMousePosition(e);
this.onMove(e, position);
@@ -120,14 +128,7 @@ class Slider extends React.Component {
const oldValue = state[state.handle];
if (value === oldValue) return;
- // If it is not controlled component
- if (!('value' in props) && !('index' in props)) {
- this.setState({[state.handle]: value}, () => {
- this.triggerEvents('onChange', this.getValue());
- });
- } else {
- this.triggerEvents('onChange', this.getChangedValue(state.handle, value));
- }
+ this.onChange(state.handle, value);
}
onTouchStart(e) {
@@ -139,7 +140,7 @@ class Slider extends React.Component {
pauseEvent(e);
}
- onSliderMouseDown(e) {
+ onMouseDown(e) {
const position = getMousePosition(e);
this.onStart(position);
this.addDocumentEvents('mouse');
@@ -147,7 +148,8 @@ class Slider extends React.Component {
}
onStart(position) {
- this.triggerEvents('onBeforeChange', this.getValue());
+ const props = this.props;
+ props.onBeforeChange(this.getValue());
const value = this.calcValueByPos(position);
this.startValue = value;
@@ -178,17 +180,10 @@ class Slider extends React.Component {
recent: valueNeedChanging,
});
- const props = this.props;
- // If it is not controlled component
- if (!('value' in props) && !('index' in props)) {
- this.setState({
- [valueNeedChanging]: value,
- }, () => {
- this.triggerEvents('onChange', this.getValue());
- });
- } else {
- this.triggerEvents('onChange', this.getChangedValue(valueNeedChanging, value));
- }
+ const oldValue = state[valueNeedChanging];
+ if (value === oldValue) return;
+
+ this.onChange(valueNeedChanging, value);
}
getValue() {
@@ -196,36 +191,15 @@ class Slider extends React.Component {
return this.props.range ? [lowerBound, upperBound] : upperBound;
}
- getChangedValue(valueNeedChanging, value) {
- const state = this.state;
- const data = {
- upperBound: state.upperBound,
- lowerBound: state.lowerBound,
- };
- data[valueNeedChanging] = value;
- return this.props.range ? [data.lowerBound, data.upperBound] : data.upperBound;
- }
-
- getIndex(value) {
- const {marks, min, max, step} = this.props;
-
- if (marks.length === 0) {
- return Math.floor((value - min) / step);
+ getPoints() {
+ const {marks, step, min, max} = this.props;
+ const points = Object.keys(marks);
+ if (isEmpty(marks) || step > 1) {
+ for (let i = min; i <= max; i = i + step) {
+ points.push(i);
+ }
}
- const unit = ((max - min) / (marks.length - 1)).toFixed(5);
- return Math.round(value / unit);
- }
-
- getBoundsFromIndex(indexes, props) {
- if (props.range) {
- return {
- lowerBound: this.calcValueFromIndex(indexes[0], props),
- upperBound: this.calcValueFromIndex(indexes[1], props),
- };
- }
- return {
- upperBound: this.calcValueFromIndex(indexes, props),
- };
+ return points;
}
getSliderLength() {
@@ -247,10 +221,7 @@ class Slider extends React.Component {
trimAlignValue(v) {
const state = this.state || {};
const {handle, lowerBound, upperBound} = state;
- const props = this.props;
- const {marks, min, max} = props;
- const marksLen = marks.length;
- const step = (marksLen > 0) ? (max - min) / (marksLen - 1) : props.step;
+ const {min, max} = this.props;
let val = v;
if (val <= min) {
@@ -266,14 +237,11 @@ class Slider extends React.Component {
val = upperBound;
}
- const valModStep = (val - min) % step;
+ const points = this.getPoints().map(parseFloat);
+ const diffs = points.map((point) => Math.abs(val - point));
+ const closestPoint = points[diffs.indexOf(Math.min.apply(Math, diffs))];
- let alignValue = val - valModStep;
- if (Math.abs(valModStep) * 2 >= step) {
- alignValue += (valModStep > 0) ? step : (-step);
- }
-
- return parseFloat(alignValue.toFixed(5));
+ return closestPoint;
}
calcOffset(value) {
@@ -294,35 +262,6 @@ class Slider extends React.Component {
return nextValue;
}
- calcValueFromIndex(index, props) {
- const marksLen = props.marks.length;
- if (marksLen > 0) {
- const value = ((props.max - props.min) / (marksLen - 1)) * (index);
- return parseFloat(value.toFixed(5));
- }
- return ('value' in props ? props.value : props.defaultValue);
- }
-
- triggerEvents(event, v) {
- const props = this.props;
- const hasMarks = (props.marks.length > 0);
- if (props[event]) {
- let data;
- if (hasMarks) {
- if (props.range) {
- data = v.map(bound => this.getIndex(bound));
- } else {
- data = this.getIndex(v);
- }
- } else if (v === undefined) {
- data = this.state.value;
- } else {
- data = v;
- }
- props[event](data);
- }
- }
-
addDocumentEvents(type) {
if (type === 'touch') {
// just work for chrome iOS Safari and Android Browser
@@ -346,15 +285,15 @@ class Slider extends React.Component {
end(type) {
this.removeEventons(type);
- this.triggerEvents('onAfterChange', this.getValue());
+ this.props.onAfterChange(this.getValue());
this.setState({handle: null});
}
render() {
const {handle, upperBound, lowerBound} = this.state;
- const {className, prefixCls, disabled, included, isIncluded, dots, range,
- marks, step, max, min, tipTransitionName, tipFormatter, children} = this.props;
- const marksLen = marks.length;
+ const {className, prefixCls, disabled, dots, included, range,
+ marks, max, min, tipTransitionName, tipFormatter, children} = this.props;
+ const marksCount = Object.keys(marks).length;
const sliderClassName = classSet({
[prefixCls]: true,
@@ -366,13 +305,13 @@ class Slider extends React.Component {
const lowerOffset = this.calcOffset(lowerBound);
let track = null;
- if ((included && isIncluded) || range) {
+ if (included || range) {
const trackClassName = prefixCls + '-track';
track = ;
}
const handleClassName = prefixCls + '-handle';
- const isNoTip = (marksLen > 0) && !tipFormatter;
+ const isNoTip = (marksCount > 0) && !tipFormatter;
const upper = ();
@@ -382,33 +321,20 @@ class Slider extends React.Component {
offset={lowerOffset} value={lowerBound} dragging={handle === 'lowerBound'} />);
}
- const upperIndex = this.getIndex(upperBound);
-
- let steps = null;
- if (marksLen > 0 || (step > 1 && dots)) {
- 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 = ();
- }
-
+ const isIncluded = included || range;
return (
+ onMouseDown={disabled ? noop : this.onMouseDown.bind(this)}>
{track}
{upper}
{lower}
- {steps}
- {mark}
+
+
{children}
);
@@ -423,20 +349,11 @@ Slider.propTypes = {
React.PropTypes.number,
React.PropTypes.arrayOf(React.PropTypes.number),
]),
- defaultIndex: React.PropTypes.oneOfType([
- React.PropTypes.number,
- React.PropTypes.arrayOf(React.PropTypes.number),
- ]),
value: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.arrayOf(React.PropTypes.number),
]),
- index: React.PropTypes.oneOfType([
- React.PropTypes.number,
- React.PropTypes.arrayOf(React.PropTypes.number),
- ]),
- marks: React.PropTypes.array,
- isIncluded: React.PropTypes.bool, // @Deprecated
+ marks: React.PropTypes.object,
included: React.PropTypes.bool,
className: React.PropTypes.string,
prefixCls: React.PropTypes.string,
@@ -452,17 +369,18 @@ Slider.propTypes = {
};
Slider.defaultProps = {
+ prefixCls: 'rc-slider',
+ className: '',
+ tipTransitionName: '',
min: 0,
max: 100,
step: 1,
- defaultIndex: 0,
- marks: [],
- isIncluded: true, // @Deprecated
+ marks: {},
+ onBeforeChange: noop,
+ onChange: noop,
+ onAfterChange: noop,
included: true,
- className: '',
- prefixCls: 'rc-slider',
disabled: false,
- tipTransitionName: '',
dots: false,
range: false,
};
diff --git a/src/Steps.jsx b/src/Steps.jsx
index da2cdb0..38e3394 100644
--- a/src/Steps.jsx
+++ b/src/Steps.jsx
@@ -1,28 +1,24 @@
import React from 'react';
-import rcUtil from 'rc-util';
+import { classSet } from 'rc-util';
-const Steps = ({className, stepNum, included, lowerIndex, upperIndex}) => {
- const dotClassName = className.replace('step', 'dot');
- const unit = 100 / (stepNum - 1);
+const Steps = ({prefixCls, points, dots, included, lowerBound, upperBound, max, min}) => {
+ const range = max - min;
+ const elements = points.filter((point) => typeof point === 'string' || dots).map(parseFloat)
+ .map((point) => {
+ const offset = (point - min) / range * 100 + '%';
+ const style = { left: offset };
- const elements = [];
- for (let i = 0; i < stepNum; i++) {
- const offset = unit * i + '%';
- const style = { left: offset };
+ const isActived = (!included && point === upperBound) ||
+ (included && point <= upperBound && point >= lowerBound);
+ const pointClassName = classSet({
+ [prefixCls + '-dot']: true,
+ [prefixCls + '-dot-active']: isActived,
+ });
- const isActived = (included && i <= upperIndex && i >= lowerIndex ) ||
- (!included && i === upperIndex);
- const stepClassName = rcUtil.classSet({
- [dotClassName]: true,
- [dotClassName + '-active']: isActived,
- });
+ return ;
+ });
- elements.push();
- }
-
- return (
- {elements}
-
);
+ return {elements}
;
};
export default Steps;
diff --git a/tests/index.spec.js b/tests/index.spec.js
index 07c55ec..68acbef 100644
--- a/tests/index.spec.js
+++ b/tests/index.spec.js
@@ -52,17 +52,17 @@ describe('rc-slider', function () {
it('should render a slider with marks correctly!', function () {
var slider = ReactDOM.render(
- ,
+ ,
div
);
var node = $(div);
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(node.find('.rc-slider-dot').length).to.be(slider.props.marks.length);
+ expect(node.find('.rc-slider-dot').length).to.be(Object.keys(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(slider.state.upperBound)).to.be(3);
+ expect(node.find('.rc-slider-mark-text').length).to.be(Object.keys(slider.props.marks).length);
+ expect(slider.state.upperBound).to.be(40);
});
// it('should mouseDown works!', function (done) {