react-rangeslider-withkeeptooltip
Version:
A lightweight react component that acts as a HTML5 input range slider polyfill
483 lines (398 loc) • 15.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
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 _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _resizeObserverPolyfill = require('resize-observer-polyfill');
var _resizeObserverPolyfill2 = _interopRequireDefault(_resizeObserverPolyfill);
var _utils = require('./utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return 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; } /* eslint no-debugger: "warn" */
/**
* Predefined constants
* @type {Object}
*/
var constants = {
orientation: {
horizontal: {
dimension: 'width',
direction: 'left',
reverseDirection: 'right',
coordinate: 'x'
},
vertical: {
dimension: 'height',
direction: 'top',
reverseDirection: 'bottom',
coordinate: 'y'
}
}
};
var Slider = function (_Component) {
_inherits(Slider, _Component);
function Slider(props, context) {
_classCallCheck(this, Slider);
var _this = _possibleConstructorReturn(this, (Slider.__proto__ || Object.getPrototypeOf(Slider)).call(this, props, context));
_this.handleFormat = function (value) {
var format = _this.props.format;
return format ? format(value) : value;
};
_this.handleUpdate = function () {
if (!_this.slider) {
// for shallow rendering
return;
}
var orientation = _this.props.orientation;
var dimension = (0, _utils.capitalize)(constants.orientation[orientation].dimension);
var sliderPos = _this.slider['offset' + dimension];
var handlePos = _this.handle['offset' + dimension];
_this.setState({
limit: sliderPos - handlePos,
grab: handlePos / 2
});
};
_this.handleStart = function (e) {
var onChangeStart = _this.props.onChangeStart;
document.addEventListener('mousemove', _this.handleDrag);
document.addEventListener('mouseup', _this.handleEnd);
_this.setState({
active: true
}, function () {
onChangeStart && onChangeStart(e);
});
};
_this.handleDrag = function (e) {
e.stopPropagation();
var onChange = _this.props.onChange;
var _e$target = e.target,
className = _e$target.className,
classList = _e$target.classList,
dataset = _e$target.dataset;
if (!onChange || className === 'rangeslider__labels') return;
var value = _this.position(e);
if (classList && classList.contains('rangeslider__label-item') && dataset.value) {
value = parseFloat(dataset.value);
}
onChange && onChange(value, e);
};
_this.handleEnd = function (e) {
var onChangeComplete = _this.props.onChangeComplete;
_this.setState({
active: false
}, function () {
onChangeComplete && onChangeComplete(e);
});
document.removeEventListener('mousemove', _this.handleDrag);
document.removeEventListener('mouseup', _this.handleEnd);
};
_this.handleKeyDown = function (e) {
e.preventDefault();
var keyCode = e.keyCode;
var _this$props = _this.props,
value = _this$props.value,
min = _this$props.min,
max = _this$props.max,
step = _this$props.step,
onChange = _this$props.onChange;
var sliderValue = void 0;
switch (keyCode) {
case 38:
case 39:
sliderValue = value + step > max ? max : value + step;
onChange && onChange(sliderValue, e);
break;
case 37:
case 40:
sliderValue = value - step < min ? min : value - step;
onChange && onChange(sliderValue, e);
break;
}
};
_this.getPositionFromValue = function (value) {
var limit = _this.state.limit;
var _this$props2 = _this.props,
min = _this$props2.min,
max = _this$props2.max;
var diffMaxMin = max - min;
var diffValMin = value - min;
var percentage = diffValMin / diffMaxMin;
var pos = Math.round(percentage * limit);
return pos;
};
_this.getValueFromPosition = function (pos) {
var limit = _this.state.limit;
var _this$props3 = _this.props,
orientation = _this$props3.orientation,
min = _this$props3.min,
max = _this$props3.max,
step = _this$props3.step;
var percentage = (0, _utils.clamp)(pos, 0, limit) / (limit || 1);
var baseVal = step * Math.round(percentage * (max - min) / step);
var value = orientation === 'horizontal' ? baseVal + min : max - baseVal;
return (0, _utils.clamp)(value, min, max);
};
_this.position = function (e) {
var grab = _this.state.grab;
var _this$props4 = _this.props,
orientation = _this$props4.orientation,
reverse = _this$props4.reverse;
var node = _this.slider;
var coordinateStyle = constants.orientation[orientation].coordinate;
var directionStyle = reverse ? constants.orientation[orientation].reverseDirection : constants.orientation[orientation].direction;
var clientCoordinateStyle = 'client' + (0, _utils.capitalize)(coordinateStyle);
var coordinate = !e.touches ? e[clientCoordinateStyle] : e.touches[0][clientCoordinateStyle];
var direction = node.getBoundingClientRect()[directionStyle];
var pos = reverse ? direction - coordinate - grab : coordinate - direction - grab;
var value = _this.getValueFromPosition(pos);
return value;
};
_this.coordinates = function (pos) {
var _this$state = _this.state,
limit = _this$state.limit,
grab = _this$state.grab;
var orientation = _this.props.orientation;
var value = _this.getValueFromPosition(pos);
var position = _this.getPositionFromValue(value);
var handlePos = orientation === 'horizontal' ? position + grab : position;
var fillPos = orientation === 'horizontal' ? handlePos : limit - handlePos;
return {
fill: fillPos,
handle: handlePos,
label: handlePos
};
};
_this.renderLabels = function (labels) {
return _react2.default.createElement(
'ul',
{
ref: function ref(sl) {
_this.labels = sl;
},
className: (0, _classnames2.default)('rangeslider__labels')
},
labels
);
};
_this.state = {
active: false,
limit: 0,
grab: 0
};
return _this;
}
_createClass(Slider, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.handleUpdate();
var resizeObserver = new _resizeObserverPolyfill2.default(this.handleUpdate);
resizeObserver.observe(this.slider);
}
/**
* Format label/tooltip value
* @param {Number} - value
* @return {Formatted Number}
*/
/**
* Update slider state on change
* @return {void}
*/
/**
* Attach event listeners to mousemove/mouseup events
* @return {void}
*/
/**
* Handle drag/mousemove event
* @param {Object} e - Event object
* @return {void}
*/
/**
* Detach event listeners to mousemove/mouseup events
* @return {void}
*/
/**
* Support for key events on the slider handle
* @param {Object} e - Event object
* @return {void}
*/
/**
* Calculate position of slider based on its value
* @param {number} value - Current value of slider
* @return {position} pos - Calculated position of slider based on value
*/
/**
* Translate position of slider to slider value
* @param {number} pos - Current position/coordinates of slider
* @return {number} value - Slider value
*/
/**
* Calculate position of slider based on value
* @param {Object} e - Event object
* @return {number} value - Slider value
*/
/**
* Grab coordinates of slider
* @param {Object} pos - Position object
* @return {Object} - Slider fill/handle coordinates
*/
}, {
key: 'render',
value: function render() {
var _this2 = this;
var _props = this.props,
value = _props.value,
orientation = _props.orientation,
className = _props.className,
tooltip = _props.tooltip,
keepTooltip = _props.keepTooltip,
reverse = _props.reverse,
labels = _props.labels,
min = _props.min,
max = _props.max,
handleLabel = _props.handleLabel;
var active = this.state.active;
var dimension = constants.orientation[orientation].dimension;
var direction = reverse ? constants.orientation[orientation].reverseDirection : constants.orientation[orientation].direction;
var position = this.getPositionFromValue(value);
var coords = this.coordinates(position);
var fillStyle = _defineProperty({}, dimension, coords.fill + 'px');
var handleStyle = _defineProperty({}, direction, coords.handle + 'px');
var showTooltip = tooltip && (keepTooltip || active);
var labelItems = [];
var labelKeys = Object.keys(labels);
if (labelKeys.length > 0) {
labelKeys = labelKeys.sort(function (a, b) {
return reverse ? a - b : b - a;
});
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = labelKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var key = _step.value;
var labelPosition = this.getPositionFromValue(key);
var labelCoords = this.coordinates(labelPosition);
var labelStyle = _defineProperty({}, direction, labelCoords.label + 'px');
labelItems.push(_react2.default.createElement(
'li',
{
key: key,
className: (0, _classnames2.default)('rangeslider__label-item'),
'data-value': key,
onMouseDown: this.handleDrag,
onTouchStart: this.handleStart,
onTouchEnd: this.handleEnd,
style: labelStyle
},
this.props.labels[key]
));
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
return _react2.default.createElement(
'div',
{
ref: function ref(s) {
_this2.slider = s;
},
className: (0, _classnames2.default)('rangeslider', 'rangeslider-' + orientation, { 'rangeslider-reverse': reverse }, className),
onMouseDown: this.handleDrag,
onMouseUp: this.handleEnd,
onTouchStart: this.handleStart,
onTouchEnd: this.handleEnd,
'aria-valuemin': min,
'aria-valuemax': max,
'aria-valuenow': value,
'aria-orientation': orientation
},
_react2.default.createElement('div', { className: 'rangeslider__fill', style: fillStyle }),
_react2.default.createElement(
'div',
{
ref: function ref(sh) {
_this2.handle = sh;
},
className: 'rangeslider__handle',
onMouseDown: this.handleStart,
onTouchMove: this.handleDrag,
onTouchEnd: this.handleEnd,
onKeyDown: this.handleKeyDown,
style: handleStyle,
tabIndex: 0
},
showTooltip ? _react2.default.createElement(
'div',
{
ref: function ref(st) {
_this2.tooltip = st;
},
className: 'rangeslider__handle-tooltip'
},
_react2.default.createElement(
'span',
null,
this.handleFormat(value)
)
) : null,
_react2.default.createElement(
'div',
{ className: 'rangeslider__handle-label' },
handleLabel
)
),
labels ? this.renderLabels(labelItems) : null
);
}
}]);
return Slider;
}(_react.Component);
Slider.propTypes = {
min: _propTypes2.default.number,
max: _propTypes2.default.number,
step: _propTypes2.default.number,
value: _propTypes2.default.number,
orientation: _propTypes2.default.string,
tooltip: _propTypes2.default.bool,
reverse: _propTypes2.default.bool,
labels: _propTypes2.default.object,
handleLabel: _propTypes2.default.string,
format: _propTypes2.default.func,
onChangeStart: _propTypes2.default.func,
onChange: _propTypes2.default.func,
onChangeComplete: _propTypes2.default.func,
keepTooltip: _propTypes2.default.bool
};
Slider.defaultProps = {
min: 0,
max: 100,
step: 1,
value: 0,
orientation: 'horizontal',
tooltip: true,
keepTooltip: false,
reverse: false,
labels: {},
handleLabel: ''
};
exports.default = Slider;