react-input-range
Version:
React component for inputting numeric values within a range
900 lines (730 loc) • 27.2 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _desc, _value, _class;
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _autobindDecorator = require('autobind-decorator');
var _autobindDecorator2 = _interopRequireDefault(_autobindDecorator);
var _valueTransformer = require('./value-transformer');
var valueTransformer = _interopRequireWildcard(_valueTransformer);
var _defaultClassNames = require('./default-class-names');
var _defaultClassNames2 = _interopRequireDefault(_defaultClassNames);
var _label = require('./label');
var _label2 = _interopRequireDefault(_label);
var _rangePropType = require('./range-prop-type');
var _rangePropType2 = _interopRequireDefault(_rangePropType);
var _valuePropType = require('./value-prop-type');
var _valuePropType2 = _interopRequireDefault(_valuePropType);
var _slider = require('./slider');
var _slider2 = _interopRequireDefault(_slider);
var _track = require('./track');
var _track2 = _interopRequireDefault(_track);
var _utils = require('../utils');
var _keyCodes = require('./key-codes');
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
var desc = {};
Object['ke' + 'ys'](descriptor).forEach(function (key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ('value' in desc || desc.initializer) {
desc.writable = true;
}
desc = decorators.slice().reverse().reduce(function (desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object['define' + 'Property'](target, property, desc);
desc = null;
}
return desc;
}
/**
* A React component that allows users to input numeric values within a range
* by dragging its sliders.
*/
var InputRange = (_class = function (_React$Component) {
_inherits(InputRange, _React$Component);
_createClass(InputRange, null, [{
key: 'propTypes',
/**
* @ignore
* @override
* @return {Object}
*/
get: function get() {
return {
allowSameValues: _propTypes2.default.bool,
ariaLabelledby: _propTypes2.default.string,
ariaControls: _propTypes2.default.string,
classNames: _propTypes2.default.objectOf(_propTypes2.default.string),
disabled: _propTypes2.default.bool,
draggableTrack: _propTypes2.default.bool,
formatLabel: _propTypes2.default.func,
maxValue: _rangePropType2.default,
minValue: _rangePropType2.default,
name: _propTypes2.default.string,
onChangeStart: _propTypes2.default.func,
onChange: _propTypes2.default.func.isRequired,
onChangeComplete: _propTypes2.default.func,
step: _propTypes2.default.number,
value: _valuePropType2.default
};
}
/**
* @ignore
* @override
* @return {Object}
*/
}, {
key: 'defaultProps',
get: function get() {
return {
allowSameValues: false,
classNames: _defaultClassNames2.default,
disabled: false,
maxValue: 10,
minValue: 0,
step: 1
};
}
/**
* @param {Object} props
* @param {boolean} [props.allowSameValues]
* @param {string} [props.ariaLabelledby]
* @param {string} [props.ariaControls]
* @param {InputRangeClassNames} [props.classNames]
* @param {boolean} [props.disabled = false]
* @param {Function} [props.formatLabel]
* @param {number|Range} [props.maxValue = 10]
* @param {number|Range} [props.minValue = 0]
* @param {string} [props.name]
* @param {string} props.onChange
* @param {Function} [props.onChangeComplete]
* @param {Function} [props.onChangeStart]
* @param {number} [props.step = 1]
* @param {number|Range} props.value
*/
}]);
function InputRange(props) {
_classCallCheck(this, InputRange);
/**
* @private
* @type {?number}
*/
var _this = _possibleConstructorReturn(this, (InputRange.__proto__ || Object.getPrototypeOf(InputRange)).call(this, props));
_this.startValue = null;
/**
* @private
* @type {?Component}
*/
_this.node = null;
/**
* @private
* @type {?Component}
*/
_this.trackNode = null;
/**
* @private
* @type {bool}
*/
_this.isSliderDragging = false;
/**
* @private
* @type {?string}
*/
_this.lastKeyMoved = null;
return _this;
}
/**
* @ignore
* @override
* @return {void}
*/
_createClass(InputRange, [{
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.removeDocumentMouseUpListener();
this.removeDocumentTouchEndListener();
}
/**
* Return the CSS class name of the component
* @private
* @return {string}
*/
}, {
key: 'getComponentClassName',
value: function getComponentClassName() {
if (!this.props.disabled) {
return this.props.classNames.inputRange;
}
return this.props.classNames.disabledInputRange;
}
/**
* Return the bounding rect of the track
* @private
* @return {ClientRect}
*/
}, {
key: 'getTrackClientRect',
value: function getTrackClientRect() {
return this.trackNode.getClientRect();
}
/**
* Return the slider key closest to a point
* @private
* @param {Point} position
* @return {string}
*/
}, {
key: 'getKeyByPosition',
value: function getKeyByPosition(position) {
var values = valueTransformer.getValueFromProps(this.props, this.isMultiValue());
var positions = valueTransformer.getPositionsFromValues(values, this.props.minValue, this.props.maxValue, this.getTrackClientRect());
if (this.isMultiValue()) {
var distanceToMin = (0, _utils.distanceTo)(position, positions.min);
var distanceToMax = (0, _utils.distanceTo)(position, positions.max);
if (distanceToMin < distanceToMax) {
return 'min';
}
}
return 'max';
}
/**
* Return all the slider keys
* @private
* @return {string[]}
*/
}, {
key: 'getKeys',
value: function getKeys() {
if (this.isMultiValue()) {
return ['min', 'max'];
}
return ['max'];
}
/**
* Return true if the difference between the new and the current value is
* greater or equal to the step amount of the component
* @private
* @param {Range} values
* @return {boolean}
*/
}, {
key: 'hasStepDifference',
value: function hasStepDifference(values) {
var currentValues = valueTransformer.getValueFromProps(this.props, this.isMultiValue());
return (0, _utils.length)(values.min, currentValues.min) >= this.props.step || (0, _utils.length)(values.max, currentValues.max) >= this.props.step;
}
/**
* Return true if the component accepts a min and max value
* @private
* @return {boolean}
*/
}, {
key: 'isMultiValue',
value: function isMultiValue() {
return (0, _utils.isObject)(this.props.value);
}
/**
* Return true if the range is within the max and min value of the component
* @private
* @param {Range} values
* @return {boolean}
*/
}, {
key: 'isWithinRange',
value: function isWithinRange(values) {
if (this.isMultiValue()) {
return values.min >= this.props.minValue && values.max <= this.props.maxValue && this.props.allowSameValues ? values.min <= values.max : values.min < values.max;
}
return values.max >= this.props.minValue && values.max <= this.props.maxValue;
}
/**
* Return true if the new value should trigger a render
* @private
* @param {Range} values
* @return {boolean}
*/
}, {
key: 'shouldUpdate',
value: function shouldUpdate(values) {
return this.isWithinRange(values) && this.hasStepDifference(values);
}
/**
* Update the position of a slider
* @private
* @param {string} key
* @param {Point} position
* @return {void}
*/
}, {
key: 'updatePosition',
value: function updatePosition(key, position) {
var values = valueTransformer.getValueFromProps(this.props, this.isMultiValue());
var positions = valueTransformer.getPositionsFromValues(values, this.props.minValue, this.props.maxValue, this.getTrackClientRect());
positions[key] = position;
this.lastKeyMoved = key;
this.updatePositions(positions);
}
/**
* Update the positions of multiple sliders
* @private
* @param {Object} positions
* @param {Point} positions.min
* @param {Point} positions.max
* @return {void}
*/
}, {
key: 'updatePositions',
value: function updatePositions(positions) {
var values = {
min: valueTransformer.getValueFromPosition(positions.min, this.props.minValue, this.props.maxValue, this.getTrackClientRect()),
max: valueTransformer.getValueFromPosition(positions.max, this.props.minValue, this.props.maxValue, this.getTrackClientRect())
};
var transformedValues = {
min: valueTransformer.getStepValueFromValue(values.min, this.props.step),
max: valueTransformer.getStepValueFromValue(values.max, this.props.step)
};
this.updateValues(transformedValues);
}
/**
* Update the value of a slider
* @private
* @param {string} key
* @param {number} value
* @return {void}
*/
}, {
key: 'updateValue',
value: function updateValue(key, value) {
var values = valueTransformer.getValueFromProps(this.props, this.isMultiValue());
values[key] = value;
this.updateValues(values);
}
/**
* Update the values of multiple sliders
* @private
* @param {Range|number} values
* @return {void}
*/
}, {
key: 'updateValues',
value: function updateValues(values) {
if (!this.shouldUpdate(values)) {
return;
}
this.props.onChange(this.isMultiValue() ? values : values.max);
}
/**
* Increment the value of a slider by key name
* @private
* @param {string} key
* @return {void}
*/
}, {
key: 'incrementValue',
value: function incrementValue(key) {
var values = valueTransformer.getValueFromProps(this.props, this.isMultiValue());
var value = values[key] + this.props.step;
this.updateValue(key, value);
}
/**
* Decrement the value of a slider by key name
* @private
* @param {string} key
* @return {void}
*/
}, {
key: 'decrementValue',
value: function decrementValue(key) {
var values = valueTransformer.getValueFromProps(this.props, this.isMultiValue());
var value = values[key] - this.props.step;
this.updateValue(key, value);
}
/**
* Listen to mouseup event
* @private
* @return {void}
*/
}, {
key: 'addDocumentMouseUpListener',
value: function addDocumentMouseUpListener() {
this.removeDocumentMouseUpListener();
this.node.ownerDocument.addEventListener('mouseup', this.handleMouseUp);
}
/**
* Listen to touchend event
* @private
* @return {void}
*/
}, {
key: 'addDocumentTouchEndListener',
value: function addDocumentTouchEndListener() {
this.removeDocumentTouchEndListener();
this.node.ownerDocument.addEventListener('touchend', this.handleTouchEnd);
}
/**
* Stop listening to mouseup event
* @private
* @return {void}
*/
}, {
key: 'removeDocumentMouseUpListener',
value: function removeDocumentMouseUpListener() {
this.node.ownerDocument.removeEventListener('mouseup', this.handleMouseUp);
}
/**
* Stop listening to touchend event
* @private
* @return {void}
*/
}, {
key: 'removeDocumentTouchEndListener',
value: function removeDocumentTouchEndListener() {
this.node.ownerDocument.removeEventListener('touchend', this.handleTouchEnd);
}
/**
* Handle any "mousemove" event received by the slider
* @private
* @param {SyntheticEvent} event
* @param {string} key
* @return {void}
*/
}, {
key: 'handleSliderDrag',
value: function handleSliderDrag(event, key) {
var _this2 = this;
if (this.props.disabled) {
return;
}
var position = valueTransformer.getPositionFromEvent(event, this.getTrackClientRect());
this.isSliderDragging = true;
requestAnimationFrame(function () {
return _this2.updatePosition(key, position);
});
}
/**
* Handle any "mousemove" event received by the track
* @private
* @param {SyntheticEvent} event
* @return {void}
*/
}, {
key: 'handleTrackDrag',
value: function handleTrackDrag(event, prevEvent) {
if (this.props.disabled || !this.props.draggableTrack || this.isSliderDragging) {
return;
}
var _props = this.props,
maxValue = _props.maxValue,
minValue = _props.minValue,
_props$value = _props.value,
max = _props$value.max,
min = _props$value.min;
var position = valueTransformer.getPositionFromEvent(event, this.getTrackClientRect());
var value = valueTransformer.getValueFromPosition(position, minValue, maxValue, this.getTrackClientRect());
var stepValue = valueTransformer.getStepValueFromValue(value, this.props.step);
var prevPosition = valueTransformer.getPositionFromEvent(prevEvent, this.getTrackClientRect());
var prevValue = valueTransformer.getValueFromPosition(prevPosition, minValue, maxValue, this.getTrackClientRect());
var prevStepValue = valueTransformer.getStepValueFromValue(prevValue, this.props.step);
var offset = prevStepValue - stepValue;
var transformedValues = {
min: min - offset,
max: max - offset
};
this.updateValues(transformedValues);
}
/**
* Handle any "keydown" event received by the slider
* @private
* @param {SyntheticEvent} event
* @param {string} key
* @return {void}
*/
}, {
key: 'handleSliderKeyDown',
value: function handleSliderKeyDown(event, key) {
if (this.props.disabled) {
return;
}
switch (event.keyCode) {
case _keyCodes.LEFT_ARROW:
case _keyCodes.DOWN_ARROW:
event.preventDefault();
this.decrementValue(key);
break;
case _keyCodes.RIGHT_ARROW:
case _keyCodes.UP_ARROW:
event.preventDefault();
this.incrementValue(key);
break;
default:
break;
}
}
/**
* Handle any "mousedown" event received by the track
* @private
* @param {SyntheticEvent} event
* @param {Point} position
* @return {void}
*/
}, {
key: 'handleTrackMouseDown',
value: function handleTrackMouseDown(event, position) {
if (this.props.disabled) {
return;
}
var _props2 = this.props,
maxValue = _props2.maxValue,
minValue = _props2.minValue,
_props2$value = _props2.value,
max = _props2$value.max,
min = _props2$value.min;
event.preventDefault();
var value = valueTransformer.getValueFromPosition(position, minValue, maxValue, this.getTrackClientRect());
var stepValue = valueTransformer.getStepValueFromValue(value, this.props.step);
if (!this.props.draggableTrack || stepValue > max || stepValue < min) {
this.updatePosition(this.getKeyByPosition(position), position);
}
}
/**
* Handle the start of any mouse/touch event
* @private
* @return {void}
*/
}, {
key: 'handleInteractionStart',
value: function handleInteractionStart() {
if (this.props.onChangeStart) {
this.props.onChangeStart(this.props.value);
}
if (this.props.onChangeComplete && !(0, _utils.isDefined)(this.startValue)) {
this.startValue = this.props.value;
}
}
/**
* Handle the end of any mouse/touch event
* @private
* @return {void}
*/
}, {
key: 'handleInteractionEnd',
value: function handleInteractionEnd() {
if (this.isSliderDragging) {
this.isSliderDragging = false;
}
if (!this.props.onChangeComplete || !(0, _utils.isDefined)(this.startValue)) {
return;
}
if (this.startValue !== this.props.value) {
this.props.onChangeComplete(this.props.value);
}
this.startValue = null;
}
/**
* Handle any "keydown" event received by the component
* @private
* @param {SyntheticEvent} event
* @return {void}
*/
}, {
key: 'handleKeyDown',
value: function handleKeyDown(event) {
this.handleInteractionStart(event);
}
/**
* Handle any "keyup" event received by the component
* @private
* @param {SyntheticEvent} event
* @return {void}
*/
}, {
key: 'handleKeyUp',
value: function handleKeyUp(event) {
this.handleInteractionEnd(event);
}
/**
* Handle any "mousedown" event received by the component
* @private
* @param {SyntheticEvent} event
* @return {void}
*/
}, {
key: 'handleMouseDown',
value: function handleMouseDown(event) {
this.handleInteractionStart(event);
this.addDocumentMouseUpListener();
}
/**
* Handle any "mouseup" event received by the component
* @private
* @param {SyntheticEvent} event
*/
}, {
key: 'handleMouseUp',
value: function handleMouseUp(event) {
this.handleInteractionEnd(event);
this.removeDocumentMouseUpListener();
}
/**
* Handle any "touchstart" event received by the component
* @private
* @param {SyntheticEvent} event
* @return {void}
*/
}, {
key: 'handleTouchStart',
value: function handleTouchStart(event) {
this.handleInteractionStart(event);
this.addDocumentTouchEndListener();
}
/**
* Handle any "touchend" event received by the component
* @private
* @param {SyntheticEvent} event
*/
}, {
key: 'handleTouchEnd',
value: function handleTouchEnd(event) {
this.handleInteractionEnd(event);
this.removeDocumentTouchEndListener();
}
/**
* Return JSX of sliders
* @private
* @return {JSX.Element}
*/
}, {
key: 'renderSliders',
value: function renderSliders() {
var _this3 = this;
var values = valueTransformer.getValueFromProps(this.props, this.isMultiValue());
var percentages = valueTransformer.getPercentagesFromValues(values, this.props.minValue, this.props.maxValue);
var keys = this.props.allowSameValues && this.lastKeyMoved === 'min' ? this.getKeys().reverse() : this.getKeys();
return keys.map(function (key) {
var value = values[key];
var percentage = percentages[key];
var _props3 = _this3.props,
maxValue = _props3.maxValue,
minValue = _props3.minValue;
if (key === 'min') {
maxValue = values.max;
} else {
minValue = values.min;
}
var slider = _react2.default.createElement(_slider2.default, {
ariaLabelledby: _this3.props.ariaLabelledby,
ariaControls: _this3.props.ariaControls,
classNames: _this3.props.classNames,
formatLabel: _this3.props.formatLabel,
key: key,
maxValue: maxValue,
minValue: minValue,
onSliderDrag: _this3.handleSliderDrag,
onSliderKeyDown: _this3.handleSliderKeyDown,
percentage: percentage,
type: key,
value: value });
return slider;
});
}
/**
* Return JSX of hidden inputs
* @private
* @return {JSX.Element}
*/
}, {
key: 'renderHiddenInputs',
value: function renderHiddenInputs() {
var _this4 = this;
if (!this.props.name) {
return [];
}
var isMultiValue = this.isMultiValue();
var values = valueTransformer.getValueFromProps(this.props, isMultiValue);
return this.getKeys().map(function (key) {
var value = values[key];
var name = isMultiValue ? '' + _this4.props.name + (0, _utils.captialize)(key) : _this4.props.name;
return _react2.default.createElement('input', { key: key, type: 'hidden', name: name, value: value });
});
}
/**
* @ignore
* @override
* @return {JSX.Element}
*/
}, {
key: 'render',
value: function render() {
var _this5 = this;
var componentClassName = this.getComponentClassName();
var values = valueTransformer.getValueFromProps(this.props, this.isMultiValue());
var percentages = valueTransformer.getPercentagesFromValues(values, this.props.minValue, this.props.maxValue);
return _react2.default.createElement(
'div',
{
'aria-disabled': this.props.disabled,
ref: function ref(node) {
_this5.node = node;
},
className: componentClassName,
onKeyDown: this.handleKeyDown,
onKeyUp: this.handleKeyUp,
onMouseDown: this.handleMouseDown,
onTouchStart: this.handleTouchStart },
_react2.default.createElement(
_label2.default,
{
classNames: this.props.classNames,
formatLabel: this.props.formatLabel,
type: 'min' },
this.props.minValue
),
_react2.default.createElement(
_track2.default,
{
classNames: this.props.classNames,
draggableTrack: this.props.draggableTrack,
ref: function ref(trackNode) {
_this5.trackNode = trackNode;
},
percentages: percentages,
onTrackDrag: this.handleTrackDrag,
onTrackMouseDown: this.handleTrackMouseDown },
this.renderSliders()
),
_react2.default.createElement(
_label2.default,
{
classNames: this.props.classNames,
formatLabel: this.props.formatLabel,
type: 'max' },
this.props.maxValue
),
this.renderHiddenInputs()
);
}
}]);
return InputRange;
}(_react2.default.Component), (_applyDecoratedDescriptor(_class.prototype, 'handleSliderDrag', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleSliderDrag'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleTrackDrag', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleTrackDrag'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleSliderKeyDown', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleSliderKeyDown'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleTrackMouseDown', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleTrackMouseDown'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleInteractionStart', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleInteractionStart'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleInteractionEnd', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleInteractionEnd'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleKeyDown', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleKeyDown'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleKeyUp', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleKeyUp'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleMouseDown', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleMouseDown'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleMouseUp', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleMouseUp'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleTouchStart', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleTouchStart'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'handleTouchEnd', [_autobindDecorator2.default], Object.getOwnPropertyDescriptor(_class.prototype, 'handleTouchEnd'), _class.prototype)), _class);
exports.default = InputRange;
module.exports = exports['default'];
//# sourceMappingURL=input-range.js.map