rc-slider
Version:
slider ui component for react
693 lines (562 loc) • 23.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
var _defineProperty3 = _interopRequireDefault(_defineProperty2);
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _reactDom = require('react-dom');
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _addEventListener = require('rc-util/lib/Dom/addEventListener');
var _addEventListener2 = _interopRequireDefault(_addEventListener);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _Track = require('./Track');
var _Track2 = _interopRequireDefault(_Track);
var _Handle = require('./Handle');
var _Handle2 = _interopRequireDefault(_Handle);
var _Steps = require('./Steps');
var _Steps2 = _interopRequireDefault(_Steps);
var _Marks = require('./Marks');
var _Marks2 = _interopRequireDefault(_Marks);
var _warning = require('warning');
var _warning2 = _interopRequireDefault(_warning);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function noop() {}
function isNotTouchEvent(e) {
return e.touches.length > 1 || e.type.toLowerCase() === 'touchend' && e.touches.length > 0;
}
function getTouchPosition(vertical, e) {
return vertical ? e.touches[0].clientY : e.touches[0].pageX;
}
function getMousePosition(vertical, e) {
return vertical ? e.clientY : e.pageX;
}
function getHandleCenterPosition(vertical, handle) {
var coords = handle.getBoundingClientRect();
return vertical ? coords.top + coords.height * 0.5 : coords.left + coords.width * 0.5;
}
function pauseEvent(e) {
e.stopPropagation();
e.preventDefault();
}
var Slider = function (_React$Component) {
(0, _inherits3["default"])(Slider, _React$Component);
function Slider(props) {
(0, _classCallCheck3["default"])(this, Slider);
var _this = (0, _possibleConstructorReturn3["default"])(this, _React$Component.call(this, props));
var range = props.range,
min = props.min,
max = props.max,
step = props.step;
var initialValue = range ? Array.apply(null, Array(range + 1)).map(function () {
return min;
}) : min;
var defaultValue = 'defaultValue' in props ? props.defaultValue : initialValue;
var value = props.value !== undefined ? props.value : defaultValue;
var bounds = (range ? value : [min, value]).map(function (v) {
return _this.trimAlignValue(v);
});
var recent = void 0;
if (range && bounds[0] === bounds[bounds.length - 1] && bounds[0] === max) {
recent = 0;
} else {
recent = bounds.length - 1;
}
if (process.env.NODE_ENV !== 'production' && step && Math.floor(step) === step && (max - min) % step !== 0) {
(0, _warning2["default"])(false, 'Slider[max] - Slider[min] (%s) should be a multiple of Slider[step] (%s)', max - min, step);
}
_this.state = {
handle: null,
recent: recent,
bounds: bounds
};
return _this;
}
Slider.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
var _this2 = this;
if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return;
var bounds = this.state.bounds;
if (nextProps.range) {
var value = nextProps.value || bounds;
var nextBounds = value.map(function (v) {
return _this2.trimAlignValue(v, nextProps);
});
if (nextBounds.every(function (v, i) {
return v === bounds[i];
})) return;
this.setState({ bounds: nextBounds });
if (bounds.some(function (v) {
return _this2.isValueOutOfBounds(v, nextProps);
})) {
this.props.onChange(nextBounds);
}
} else {
var _value = nextProps.value !== undefined ? nextProps.value : bounds[1];
var nextValue = this.trimAlignValue(_value, nextProps);
if (nextValue === bounds[1] && bounds[0] === nextProps.min) return;
this.setState({ bounds: [nextProps.min, nextValue] });
if (this.isValueOutOfBounds(bounds[1], nextProps)) {
this.props.onChange(nextValue);
}
}
};
Slider.prototype.onChange = function onChange(state) {
var props = this.props;
var isNotControlled = !('value' in props);
if (isNotControlled) {
this.setState(state);
} else if (state.handle !== undefined) {
this.setState({ handle: state.handle });
}
var data = (0, _extends3["default"])({}, this.state, state);
var changedValue = props.range ? data.bounds : data.bounds[1];
props.onChange(changedValue);
};
Slider.prototype.onMouseDown = function onMouseDown(e) {
if (e.button !== 0) {
return;
}
var position = getMousePosition(this.props.vertical, e);
if (!this.isEventFromHandle(e)) {
this.dragOffset = 0;
} else {
var handlePosition = getHandleCenterPosition(this.props.vertical, e.target);
this.dragOffset = position - handlePosition;
position = handlePosition;
}
this.onStart(position);
this.addDocumentEvents('mouse');
pauseEvent(e);
};
Slider.prototype.onMouseMove = function onMouseMove(e) {
var position = getMousePosition(this.props.vertical, e);
this.onMove(e, position - this.dragOffset);
};
Slider.prototype.onMove = function onMove(e, position) {
pauseEvent(e);
var props = this.props;
var state = this.state;
var diffPosition = position - this.startPosition;
diffPosition = this.props.vertical ? -diffPosition : diffPosition;
var diffValue = diffPosition / this.getSliderLength() * (props.max - props.min);
var value = this.trimAlignValue(this.startValue + diffValue);
var oldValue = state.bounds[state.handle];
if (value === oldValue) return;
var nextBounds = [].concat((0, _toConsumableArray3["default"])(state.bounds));
nextBounds[state.handle] = value;
var nextHandle = state.handle;
if (props.pushable !== false) {
var originalValue = state.bounds[nextHandle];
this.pushSurroundingHandles(nextBounds, nextHandle, originalValue);
} else if (props.allowCross) {
nextBounds.sort(function (a, b) {
return a - b;
});
nextHandle = nextBounds.indexOf(value);
}
this.onChange({
handle: nextHandle,
bounds: nextBounds
});
};
Slider.prototype.onStart = function onStart(position) {
var props = this.props;
props.onBeforeChange(this.getValue());
var value = this.calcValueByPos(position);
this.startValue = value;
this.startPosition = position;
var state = this.state;
var bounds = state.bounds;
var valueNeedChanging = 1;
if (this.props.range) {
var closestBound = 0;
for (var i = 1; i < bounds.length - 1; ++i) {
if (value > bounds[i]) {
closestBound = i;
}
}
if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) {
closestBound = closestBound + 1;
}
valueNeedChanging = closestBound;
var isAtTheSamePoint = bounds[closestBound + 1] === bounds[closestBound];
if (isAtTheSamePoint) {
valueNeedChanging = state.recent;
}
if (isAtTheSamePoint && value !== bounds[closestBound + 1]) {
valueNeedChanging = value < bounds[closestBound + 1] ? closestBound : closestBound + 1;
}
}
this.setState({
handle: valueNeedChanging,
recent: valueNeedChanging
});
var oldValue = state.bounds[valueNeedChanging];
if (value === oldValue) return;
var nextBounds = [].concat((0, _toConsumableArray3["default"])(state.bounds));
nextBounds[valueNeedChanging] = value;
this.onChange({ bounds: nextBounds });
};
Slider.prototype.onTouchMove = function onTouchMove(e) {
if (isNotTouchEvent(e)) {
this.end('touch');
return;
}
var position = getTouchPosition(this.props.vertical, e);
this.onMove(e, position - this.dragOffset);
};
Slider.prototype.onTouchStart = function onTouchStart(e) {
if (isNotTouchEvent(e)) return;
var position = getTouchPosition(this.props.vertical, e);
if (!this.isEventFromHandle(e)) {
this.dragOffset = 0;
} else {
var handlePosition = getHandleCenterPosition(this.props.vertical, e.target);
this.dragOffset = position - handlePosition;
position = handlePosition;
}
this.onStart(position);
this.addDocumentEvents('touch');
pauseEvent(e);
};
/**
* Returns an array of possible slider points, taking into account both
* `marks` and `step`. The result is cached.
*/
Slider.prototype.getPoints = function getPoints() {
var _props = this.props,
marks = _props.marks,
step = _props.step,
min = _props.min,
max = _props.max;
var cache = this._getPointsCache;
if (!cache || cache.marks !== marks || cache.step !== step) {
var pointsObject = (0, _extends3["default"])({}, marks);
if (step !== null) {
for (var point = min; point <= max; point += step) {
pointsObject[point] = point;
}
}
var points = Object.keys(pointsObject).map(parseFloat);
points.sort(function (a, b) {
return a - b;
});
this._getPointsCache = { marks: marks, step: step, points: points };
}
return this._getPointsCache.points;
};
Slider.prototype.getPrecision = function getPrecision(step) {
var stepString = step.toString();
var precision = 0;
if (stepString.indexOf('.') >= 0) {
precision = stepString.length - stepString.indexOf('.') - 1;
}
return precision;
};
Slider.prototype.getSliderLength = function getSliderLength() {
var slider = this.refs.slider;
if (!slider) {
return 0;
}
return this.props.vertical ? slider.clientHeight : slider.clientWidth;
};
Slider.prototype.getSliderStart = function getSliderStart() {
var slider = this.refs.slider;
var rect = slider.getBoundingClientRect();
return this.props.vertical ? rect.top : rect.left;
};
Slider.prototype.getValue = function getValue() {
var bounds = this.state.bounds;
return this.props.range ? bounds : bounds[1];
};
Slider.prototype.addDocumentEvents = function addDocumentEvents(type) {
if (type === 'touch') {
// just work for chrome iOS Safari and Android Browser
this.onTouchMoveListener = (0, _addEventListener2["default"])(document, 'touchmove', this.onTouchMove.bind(this));
this.onTouchUpListener = (0, _addEventListener2["default"])(document, 'touchend', this.end.bind(this, 'touch'));
} else if (type === 'mouse') {
this.onMouseMoveListener = (0, _addEventListener2["default"])(document, 'mousemove', this.onMouseMove.bind(this));
this.onMouseUpListener = (0, _addEventListener2["default"])(document, 'mouseup', this.end.bind(this, 'mouse'));
}
};
Slider.prototype.calcOffset = function calcOffset(value) {
var _props2 = this.props,
min = _props2.min,
max = _props2.max;
var ratio = (value - min) / (max - min);
return ratio * 100;
};
Slider.prototype.calcValue = function calcValue(offset) {
var _props3 = this.props,
vertical = _props3.vertical,
min = _props3.min,
max = _props3.max;
var ratio = Math.abs(offset / this.getSliderLength());
var value = vertical ? (1 - ratio) * (max - min) + min : ratio * (max - min) + min;
return value;
};
Slider.prototype.calcValueByPos = function calcValueByPos(position) {
var pixelOffset = position - this.getSliderStart();
var nextValue = this.trimAlignValue(this.calcValue(pixelOffset));
return nextValue;
};
Slider.prototype.end = function end(type) {
this.removeEvents(type);
this.props.onAfterChange(this.getValue());
this.setState({ handle: null });
};
Slider.prototype.isEventFromHandle = function isEventFromHandle(e) {
var _this3 = this;
return this.state.bounds.some(function (x, i) {
return _this3.refs['handle-' + i] && e.target === (0, _reactDom.findDOMNode)(_this3.refs['handle-' + i]);
});
};
Slider.prototype.isValueOutOfBounds = function isValueOutOfBounds(value, props) {
return value < props.min || value > props.max;
};
Slider.prototype.pushHandle = function pushHandle(bounds, handle, direction, amount) {
var originalValue = bounds[handle];
var currentValue = bounds[handle];
while (direction * (currentValue - originalValue) < amount) {
if (!this.pushHandleOnePoint(bounds, handle, direction)) {
// can't push handle enough to create the needed `amount` gap, so we
// revert its position to the original value
bounds[handle] = originalValue;
return false;
}
currentValue = bounds[handle];
}
// the handle was pushed enough to create the needed `amount` gap
return true;
};
Slider.prototype.pushHandleOnePoint = function pushHandleOnePoint(bounds, handle, direction) {
var points = this.getPoints();
var pointIndex = points.indexOf(bounds[handle]);
var nextPointIndex = pointIndex + direction;
if (nextPointIndex >= points.length || nextPointIndex < 0) {
// reached the minimum or maximum available point, can't push anymore
return false;
}
var nextHandle = handle + direction;
var nextValue = points[nextPointIndex];
var threshold = this.props.pushable;
var diffToNext = direction * (bounds[nextHandle] - nextValue);
if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) {
// couldn't push next handle, so we won't push this one either
return false;
}
// push the handle
bounds[handle] = nextValue;
return true;
};
Slider.prototype.pushSurroundingHandles = function pushSurroundingHandles(bounds, handle, originalValue) {
var threshold = this.props.pushable;
var value = bounds[handle];
var direction = 0;
if (bounds[handle + 1] - value < threshold) {
direction = +1;
} else if (value - bounds[handle - 1] < threshold) {
direction = -1;
}
if (direction === 0) {
return;
}
var nextHandle = handle + direction;
var diffToNext = direction * (bounds[nextHandle] - value);
if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) {
// revert to original value if pushing is impossible
bounds[handle] = originalValue;
}
};
Slider.prototype.removeEvents = function removeEvents(type) {
if (type === 'touch') {
this.onTouchMoveListener.remove();
this.onTouchUpListener.remove();
} else if (type === 'mouse') {
this.onMouseMoveListener.remove();
this.onMouseUpListener.remove();
}
};
Slider.prototype.trimAlignValue = function trimAlignValue(v, nextProps) {
var state = this.state || {};
var handle = state.handle,
bounds = state.bounds;
var _props4 = (0, _extends3["default"])({}, this.props, nextProps || {}),
marks = _props4.marks,
step = _props4.step,
min = _props4.min,
max = _props4.max,
allowCross = _props4.allowCross;
var val = v;
if (val <= min) {
val = min;
}
if (val >= max) {
val = max;
}
/* eslint-disable eqeqeq */
if (!allowCross && handle != null && handle > 0 && val <= bounds[handle - 1]) {
val = bounds[handle - 1];
}
if (!allowCross && handle != null && handle < bounds.length - 1 && val >= bounds[handle + 1]) {
val = bounds[handle + 1];
}
/* eslint-enable eqeqeq */
var points = Object.keys(marks).map(parseFloat);
if (step !== null) {
var closestStep = Math.round((val - min) / step) * step + min;
points.push(closestStep);
}
var diffs = points.map(function (point) {
return Math.abs(val - point);
});
var closestPoint = points[diffs.indexOf(Math.min.apply(Math, diffs))];
return step !== null ? parseFloat(closestPoint.toFixed(this.getPrecision(step))) : closestPoint;
};
Slider.prototype.render = function render() {
var _this4 = this,
_classNames3;
var _state = this.state,
handle = _state.handle,
bounds = _state.bounds;
var _props5 = this.props,
className = _props5.className,
prefixCls = _props5.prefixCls,
tooltipPrefixCls = _props5.tooltipPrefixCls,
disabled = _props5.disabled,
vertical = _props5.vertical,
dots = _props5.dots,
included = _props5.included,
range = _props5.range,
step = _props5.step,
marks = _props5.marks,
max = _props5.max,
min = _props5.min,
tipTransitionName = _props5.tipTransitionName,
tipFormatter = _props5.tipFormatter,
children = _props5.children;
var customHandle = this.props.handle;
var offsets = bounds.map(function (v) {
return _this4.calcOffset(v);
});
var handleClassName = prefixCls + '-handle';
var handlesClassNames = bounds.map(function (v, i) {
var _classNames;
return (0, _classnames2["default"])((_classNames = {}, (0, _defineProperty3["default"])(_classNames, handleClassName, true), (0, _defineProperty3["default"])(_classNames, handleClassName + '-' + (i + 1), true), (0, _defineProperty3["default"])(_classNames, handleClassName + '-lower', i === 0), (0, _defineProperty3["default"])(_classNames, handleClassName + '-upper', i === bounds.length - 1), _classNames));
});
var isNoTip = step === null || tipFormatter === null;
var commonHandleProps = {
prefixCls: prefixCls,
tooltipPrefixCls: tooltipPrefixCls,
noTip: isNoTip,
tipTransitionName: tipTransitionName,
tipFormatter: tipFormatter,
vertical: vertical
};
var handles = bounds.map(function (v, i) {
return (0, _react.cloneElement)(customHandle, (0, _extends3["default"])({}, commonHandleProps, {
className: handlesClassNames[i],
value: v,
offset: offsets[i],
dragging: handle === i,
index: i,
key: i,
ref: 'handle-' + i
}));
});
if (!range) {
handles.shift();
}
var isIncluded = included || range;
var tracks = [];
for (var i = 1; i < bounds.length; ++i) {
var _classNames2;
var trackClassName = (0, _classnames2["default"])((_classNames2 = {}, (0, _defineProperty3["default"])(_classNames2, prefixCls + '-track', true), (0, _defineProperty3["default"])(_classNames2, prefixCls + '-track-' + i, true), _classNames2));
tracks.push(_react2["default"].createElement(_Track2["default"], { className: trackClassName, vertical: vertical, included: isIncluded,
offset: offsets[i - 1], length: offsets[i] - offsets[i - 1], key: i
}));
}
var sliderClassName = (0, _classnames2["default"])((_classNames3 = {}, (0, _defineProperty3["default"])(_classNames3, prefixCls, true), (0, _defineProperty3["default"])(_classNames3, prefixCls + '-with-marks', Object.keys(marks).length), (0, _defineProperty3["default"])(_classNames3, prefixCls + '-disabled', disabled), (0, _defineProperty3["default"])(_classNames3, prefixCls + '-vertical', this.props.vertical), (0, _defineProperty3["default"])(_classNames3, className, !!className), _classNames3));
return _react2["default"].createElement(
'div',
{ ref: 'slider', className: sliderClassName,
onTouchStart: disabled ? noop : this.onTouchStart.bind(this),
onMouseDown: disabled ? noop : this.onMouseDown.bind(this)
},
_react2["default"].createElement('div', { className: prefixCls + '-rail' }),
tracks,
_react2["default"].createElement(_Steps2["default"], { prefixCls: prefixCls, vertical: vertical, marks: marks, dots: dots, step: step,
included: isIncluded, lowerBound: bounds[0],
upperBound: bounds[bounds.length - 1], max: max, min: min
}),
handles,
_react2["default"].createElement(_Marks2["default"], { className: prefixCls + '-mark', vertical: vertical, marks: marks,
included: isIncluded, lowerBound: bounds[0],
upperBound: bounds[bounds.length - 1], max: max, min: min
}),
children
);
};
return Slider;
}(_react2["default"].Component);
Slider.propTypes = {
min: _react2["default"].PropTypes.number,
max: _react2["default"].PropTypes.number,
step: _react2["default"].PropTypes.number,
defaultValue: _react2["default"].PropTypes.oneOfType([_react2["default"].PropTypes.number, _react2["default"].PropTypes.arrayOf(_react2["default"].PropTypes.number)]),
value: _react2["default"].PropTypes.oneOfType([_react2["default"].PropTypes.number, _react2["default"].PropTypes.arrayOf(_react2["default"].PropTypes.number)]),
marks: _react2["default"].PropTypes.object,
included: _react2["default"].PropTypes.bool,
className: _react2["default"].PropTypes.string,
prefixCls: _react2["default"].PropTypes.string,
tooltipPrefixCls: _react2["default"].PropTypes.string,
disabled: _react2["default"].PropTypes.bool,
children: _react2["default"].PropTypes.any,
onBeforeChange: _react2["default"].PropTypes.func,
onChange: _react2["default"].PropTypes.func,
onAfterChange: _react2["default"].PropTypes.func,
handle: _react2["default"].PropTypes.element,
tipTransitionName: _react2["default"].PropTypes.string,
tipFormatter: _react2["default"].PropTypes.func,
dots: _react2["default"].PropTypes.bool,
range: _react2["default"].PropTypes.oneOfType([_react2["default"].PropTypes.bool, _react2["default"].PropTypes.number]),
vertical: _react2["default"].PropTypes.bool,
allowCross: _react2["default"].PropTypes.bool,
pushable: _react2["default"].PropTypes.oneOfType([_react2["default"].PropTypes.bool, _react2["default"].PropTypes.number])
};
Slider.defaultProps = {
prefixCls: 'rc-slider',
className: '',
tipTransitionName: '',
min: 0,
max: 100,
step: 1,
marks: {},
handle: _react2["default"].createElement(_Handle2["default"], null),
onBeforeChange: noop,
onChange: noop,
onAfterChange: noop,
tipFormatter: function tipFormatter(value) {
return value;
},
included: true,
disabled: false,
dots: false,
range: false,
vertical: false,
allowCross: true,
pushable: false
};
exports["default"] = Slider;
module.exports = exports['default'];