UNPKG

react-input-range

Version:

React component for inputting numeric values within a range

900 lines (730 loc) 27.2 kB
'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