react-range
Version:
Range input. Slides in all directions.
619 lines (618 loc) • 30.4 kB
JavaScript
;
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
var React = __importStar(require("react"));
var utils_1 = require("./utils");
var types_1 = require("./types");
var INCREASE_KEYS = ['ArrowRight', 'ArrowUp', 'k', 'PageUp'];
var DECREASE_KEYS = ['ArrowLeft', 'ArrowDown', 'j', 'PageDown'];
var Range = /** @class */ (function (_super) {
__extends(Range, _super);
function Range(props) {
var _this = _super.call(this, props) || this;
_this.trackRef = React.createRef();
_this.thumbRefs = [];
_this.state = {
draggedTrackPos: [-1, -1],
draggedThumbIndex: -1,
thumbZIndexes: new Array(_this.props.values.length).fill(0).map(function (t, i) { return i; }),
isChanged: false,
markOffsets: []
};
_this.getOffsets = function () {
var _a = _this.props, direction = _a.direction, values = _a.values, min = _a.min, max = _a.max;
var trackElement = _this.trackRef.current;
var trackRect = trackElement.getBoundingClientRect();
var trackPadding = (0, utils_1.getPaddingAndBorder)(trackElement);
return _this.getThumbs().map(function (thumb, index) {
var thumbOffsets = { x: 0, y: 0 };
var thumbRect = thumb.getBoundingClientRect();
var thumbMargins = (0, utils_1.getMargin)(thumb);
switch (direction) {
case types_1.Direction.Right:
thumbOffsets.x = (thumbMargins.left + trackPadding.left) * -1;
thumbOffsets.y =
((thumbRect.height - trackRect.height) / 2 + trackPadding.top) * -1;
thumbOffsets.x +=
trackRect.width * (0, utils_1.relativeValue)(values[index], min, max) -
thumbRect.width / 2;
return thumbOffsets;
case types_1.Direction.Left:
thumbOffsets.x = (thumbMargins.right + trackPadding.right) * -1;
thumbOffsets.y =
((thumbRect.height - trackRect.height) / 2 + trackPadding.top) * -1;
thumbOffsets.x +=
trackRect.width -
trackRect.width * (0, utils_1.relativeValue)(values[index], min, max) -
thumbRect.width / 2;
return thumbOffsets;
case types_1.Direction.Up:
thumbOffsets.x =
((thumbRect.width - trackRect.width) / 2 +
thumbMargins.left +
trackPadding.left) *
-1;
thumbOffsets.y = -trackPadding.left;
thumbOffsets.y +=
trackRect.height -
trackRect.height * (0, utils_1.relativeValue)(values[index], min, max) -
thumbRect.height / 2;
return thumbOffsets;
case types_1.Direction.Down:
thumbOffsets.x =
((thumbRect.width - trackRect.width) / 2 +
thumbMargins.left +
trackPadding.left) *
-1;
thumbOffsets.y = -trackPadding.left;
thumbOffsets.y +=
trackRect.height * (0, utils_1.relativeValue)(values[index], min, max) -
thumbRect.height / 2;
return thumbOffsets;
default:
return (0, utils_1.assertUnreachable)(direction);
}
});
};
_this.getThumbs = function () {
if (_this.trackRef && _this.trackRef.current) {
return Array.from(_this.trackRef.current.children).filter(function (el) {
return el.hasAttribute('aria-valuenow');
});
}
console.warn('No thumbs found in the track container. Did you forget to pass & spread the `props` param in renderTrack?');
return [];
};
_this.getTargetIndex = function (e) {
return _this.getThumbs().findIndex(function (child) { return child === e.target || child.contains(e.target); });
};
_this.addTouchEvents = function (e) {
document.addEventListener('touchmove', _this.schdOnTouchMove, {
passive: false
});
document.addEventListener('touchend', _this.schdOnEnd, {
passive: false
});
document.addEventListener('touchcancel', _this.schdOnEnd, {
passive: false
});
};
_this.addMouseEvents = function (e) {
document.addEventListener('mousemove', _this.schdOnMouseMove);
document.addEventListener('mouseup', _this.schdOnEnd);
};
_this.onMouseDownTrack = function (e) {
var _a;
if (e.button !== 0)
return;
e.persist();
e.preventDefault();
_this.addMouseEvents(e.nativeEvent);
if (_this.props.values.length > 1 && _this.props.draggableTrack) {
if (_this.thumbRefs.some(function (thumbRef) { var _a; return (_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target); }))
return;
// handle dragging the whole track
_this.setState({
draggedTrackPos: [e.clientX, e.clientY]
}, function () { return _this.onMove(e.clientX, e.clientY); });
}
else {
// get the index of the thumb that is closest to the place where the track is clicked
var draggedThumbIndex = (0, utils_1.getClosestThumbIndex)(_this.thumbRefs.map(function (t) { return t.current; }), e.clientX, e.clientY, _this.props.direction);
// move the thumb which is closest to the place where the track is clicked
(_a = _this.thumbRefs[draggedThumbIndex].current) === null || _a === void 0 ? void 0 : _a.focus();
_this.setState({
draggedThumbIndex: draggedThumbIndex
}, function () { return _this.onMove(e.clientX, e.clientY); });
}
};
_this.onResize = function () {
(0, utils_1.translateThumbs)(_this.getThumbs(), _this.getOffsets(), _this.props.rtl);
_this.calculateMarkOffsets();
};
_this.onTouchStartTrack = function (e) {
var _a;
e.persist();
_this.addTouchEvents(e.nativeEvent);
if (_this.props.values.length > 1 && _this.props.draggableTrack) {
if (_this.thumbRefs.some(function (thumbRef) { var _a; return (_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target); }))
return;
// handle dragging the whole track
_this.setState({
draggedTrackPos: [e.touches[0].clientX, e.touches[0].clientY]
}, function () { return _this.onMove(e.touches[0].clientX, e.touches[0].clientY); });
}
else {
// get the index of the thumb that is closest to the place where the track is clicked
var draggedThumbIndex = (0, utils_1.getClosestThumbIndex)(_this.thumbRefs.map(function (t) { return t.current; }), e.touches[0].clientX, e.touches[0].clientY, _this.props.direction);
// move the thumb which is closest to the place where the track is clicked
(_a = _this.thumbRefs[draggedThumbIndex].current) === null || _a === void 0 ? void 0 : _a.focus();
_this.setState({
draggedThumbIndex: draggedThumbIndex
}, function () { return _this.onMove(e.touches[0].clientX, e.touches[0].clientY); });
}
};
_this.onMouseOrTouchStart = function (e) {
if (_this.props.disabled)
return;
var isTouch = (0, utils_1.isTouchEvent)(e);
if (!isTouch && e.button !== 0)
return;
var index = _this.getTargetIndex(e);
if (index === -1)
return;
if (isTouch) {
_this.addTouchEvents(e);
}
else {
_this.addMouseEvents(e);
}
_this.setState({
draggedThumbIndex: index,
thumbZIndexes: _this.state.thumbZIndexes.map(function (t, i) {
if (i === index) {
return Math.max.apply(Math, _this.state.thumbZIndexes);
}
return t <= _this.state.thumbZIndexes[index] ? t : t - 1;
})
});
};
_this.onMouseMove = function (e) {
e.preventDefault();
_this.onMove(e.clientX, e.clientY);
};
_this.onTouchMove = function (e) {
e.preventDefault();
_this.onMove(e.touches[0].clientX, e.touches[0].clientY);
};
_this.onKeyDown = function (e) {
var _a = _this.props, values = _a.values, onChange = _a.onChange, step = _a.step, rtl = _a.rtl, direction = _a.direction;
var isChanged = _this.state.isChanged;
var index = _this.getTargetIndex(e.nativeEvent);
var inverter = rtl || direction === types_1.Direction.Left || direction === types_1.Direction.Down
? -1
: 1;
if (index === -1)
return;
if (INCREASE_KEYS.includes(e.key)) {
e.preventDefault();
_this.setState({
draggedThumbIndex: index,
isChanged: true
});
onChange((0, utils_1.replaceAt)(values, index, _this.normalizeValue(values[index] + inverter * (e.key === 'PageUp' ? step * 10 : step), index)));
}
else if (DECREASE_KEYS.includes(e.key)) {
e.preventDefault();
_this.setState({
draggedThumbIndex: index,
isChanged: true
});
onChange((0, utils_1.replaceAt)(values, index, _this.normalizeValue(values[index] -
inverter * (e.key === 'PageDown' ? step * 10 : step), index)));
}
else if (e.key === 'Tab') {
_this.setState({ draggedThumbIndex: -1 }, function () {
// If key pressed when thumb was moving, fire onFinalChange
if (isChanged) {
_this.fireOnFinalChange();
}
});
}
else {
if (isChanged) {
_this.fireOnFinalChange();
}
}
};
_this.onKeyUp = function (e) {
var isChanged = _this.state.isChanged;
_this.setState({
draggedThumbIndex: -1
}, function () {
if (isChanged) {
_this.fireOnFinalChange();
}
});
};
_this.onMove = function (clientX, clientY) {
var _a = _this.state, draggedThumbIndex = _a.draggedThumbIndex, draggedTrackPos = _a.draggedTrackPos;
var _b = _this.props, direction = _b.direction, min = _b.min, max = _b.max, onChange = _b.onChange, values = _b.values, step = _b.step, rtl = _b.rtl;
if (draggedThumbIndex === -1 &&
draggedTrackPos[0] === -1 &&
draggedTrackPos[1] === -1)
return null;
var trackElement = _this.trackRef.current;
// If component was closed down prematurely, A last onMove could be triggered based on requestAnimationFrame()
if (!trackElement)
return null;
var trackRect = trackElement.getBoundingClientRect();
var trackLength = (0, utils_1.isVertical)(direction)
? trackRect.height
: trackRect.width;
if (draggedTrackPos[0] !== -1 && draggedTrackPos[1] !== -1) {
// calculate how much it moved since the last update
var dX = clientX - draggedTrackPos[0];
var dY = clientY - draggedTrackPos[1];
// calculate the delta of the value
var deltaValue = 0;
switch (direction) {
case types_1.Direction.Right:
case types_1.Direction.Left:
deltaValue = (dX / trackLength) * (max - min);
break;
case types_1.Direction.Down:
case types_1.Direction.Up:
deltaValue = (dY / trackLength) * (max - min);
break;
default:
(0, utils_1.assertUnreachable)(direction);
}
// invert for RTL
if (rtl) {
deltaValue *= -1;
}
if (Math.abs(deltaValue) >= step / 2) {
// adjust delta so it fits into the range
for (var i = 0; i < _this.thumbRefs.length; i++) {
if ((values[i] === max && Math.sign(deltaValue) === 1) ||
(values[i] === min && Math.sign(deltaValue) === -1))
return;
var thumbValue = values[i] + deltaValue;
if (thumbValue > max)
deltaValue = max - values[i];
else if (thumbValue < min)
deltaValue = min - values[i];
}
// add the delta to each thumb
var newValues = values.slice(0);
for (var i = 0; i < _this.thumbRefs.length; i++) {
newValues = (0, utils_1.replaceAt)(newValues, i, _this.normalizeValue(values[i] + deltaValue, i));
}
_this.setState({
draggedTrackPos: [clientX, clientY]
});
onChange(newValues);
}
}
else {
var newValue = 0;
switch (direction) {
case types_1.Direction.Right:
newValue =
((clientX - trackRect.left) / trackLength) * (max - min) + min;
break;
case types_1.Direction.Left:
newValue =
((trackLength - (clientX - trackRect.left)) / trackLength) *
(max - min) +
min;
break;
case types_1.Direction.Down:
newValue =
((clientY - trackRect.top) / trackLength) * (max - min) + min;
break;
case types_1.Direction.Up:
newValue =
((trackLength - (clientY - trackRect.top)) / trackLength) *
(max - min) +
min;
break;
default:
(0, utils_1.assertUnreachable)(direction);
}
// invert for RTL
if (rtl) {
newValue = max + min - newValue;
}
if (Math.abs(values[draggedThumbIndex] - newValue) >= step / 2) {
onChange((0, utils_1.replaceAt)(values, draggedThumbIndex, _this.normalizeValue(newValue, draggedThumbIndex)));
}
}
};
_this.normalizeValue = function (value, index) {
var _a = _this.props, min = _a.min, max = _a.max, step = _a.step, allowOverlap = _a.allowOverlap, values = _a.values;
return (0, utils_1.normalizeValue)(value, index, min, max, step, allowOverlap, values);
};
_this.onEnd = function (e) {
e.preventDefault();
document.removeEventListener('mousemove', _this.schdOnMouseMove);
document.removeEventListener('touchmove', _this.schdOnTouchMove);
document.removeEventListener('mouseup', _this.schdOnEnd);
document.removeEventListener('touchend', _this.schdOnEnd);
document.removeEventListener('touchcancel', _this.schdOnEnd);
if (_this.state.draggedThumbIndex === -1 &&
_this.state.draggedTrackPos[0] === -1 &&
_this.state.draggedTrackPos[1] === -1)
return null;
_this.setState({ draggedThumbIndex: -1, draggedTrackPos: [-1, -1] }, function () {
_this.fireOnFinalChange();
});
};
_this.fireOnFinalChange = function () {
_this.setState({ isChanged: false });
var _a = _this.props, onFinalChange = _a.onFinalChange, values = _a.values;
if (onFinalChange) {
onFinalChange(values);
}
};
_this.updateMarkRefs = function (props) {
if (!props.renderMark) { // don't create mark refs unless we are rendering marks
_this.numOfMarks = undefined;
_this.markRefs = undefined;
return;
}
_this.numOfMarks = (props.max - props.min) / _this.props.step;
_this.markRefs = [];
for (var i = 0; i < _this.numOfMarks + 1; i++) {
_this.markRefs[i] = React.createRef();
}
};
_this.calculateMarkOffsets = function () {
if (!_this.props.renderMark ||
!_this.trackRef ||
!_this.numOfMarks ||
!_this.markRefs ||
_this.trackRef.current === null)
return;
var elStyles = window.getComputedStyle(_this.trackRef.current);
var trackWidth = parseInt(elStyles.width, 10);
var trackHeight = parseInt(elStyles.height, 10);
var paddingLeft = parseInt(elStyles.paddingLeft, 10);
var paddingTop = parseInt(elStyles.paddingTop, 10);
var res = [];
for (var i = 0; i < _this.numOfMarks + 1; i++) {
var markHeight = 9999;
var markWidth = 9999;
if (_this.markRefs[i].current) {
var markRect = _this.markRefs[i].current.getBoundingClientRect();
markHeight = markRect.height;
markWidth = markRect.width;
}
if (_this.props.direction === types_1.Direction.Left ||
_this.props.direction === types_1.Direction.Right) {
res.push([
Math.round((trackWidth / _this.numOfMarks) * i + paddingLeft - markWidth / 2),
-Math.round((markHeight - trackHeight) / 2)
]);
}
else {
res.push([
Math.round((trackHeight / _this.numOfMarks) * i + paddingTop - markHeight / 2),
-Math.round((markWidth - trackWidth) / 2)
]);
}
}
_this.setState({ markOffsets: res });
};
if (props.step === 0) {
throw new Error('"step" property should be a positive number');
}
_this.schdOnMouseMove = (0, utils_1.schd)(_this.onMouseMove);
_this.schdOnTouchMove = (0, utils_1.schd)(_this.onTouchMove);
_this.schdOnEnd = (0, utils_1.schd)(_this.onEnd);
_this.thumbRefs = props.values.map(function () { return React.createRef(); });
_this.updateMarkRefs(props);
return _this;
}
Range.prototype.componentDidMount = function () {
var _this = this;
var _a = this.props, values = _a.values, min = _a.min, step = _a.step;
this.resizeObserver = window.ResizeObserver
? new window.ResizeObserver(this.onResize)
: {
observe: function () { return window.addEventListener('resize', _this.onResize); },
unobserve: function () { return window.removeEventListener('resize', _this.onResize); }
};
document.addEventListener('touchstart', this.onMouseOrTouchStart, {
passive: false
});
document.addEventListener('mousedown', this.onMouseOrTouchStart, {
passive: false
});
!this.props.allowOverlap && (0, utils_1.checkInitialOverlap)(this.props.values);
this.props.values.forEach(function (value) {
return (0, utils_1.checkBoundaries)(value, _this.props.min, _this.props.max);
});
this.resizeObserver.observe(this.trackRef.current);
(0, utils_1.translateThumbs)(this.getThumbs(), this.getOffsets(), this.props.rtl);
this.calculateMarkOffsets();
values.forEach(function (value) {
if (!(0, utils_1.isStepDivisible)(min, value, step)) {
console.warn('The `values` property is in conflict with the current `step`, `min`, and `max` properties. Please provide values that are accessible using the min, max, and step values.');
}
});
};
Range.prototype.componentDidUpdate = function (prevProps, prevState) {
var _a = this.props, max = _a.max, min = _a.min, step = _a.step, values = _a.values, rtl = _a.rtl;
if (prevProps.max !== max ||
prevProps.min !== min ||
prevProps.step !== step) {
this.updateMarkRefs(this.props);
}
(0, utils_1.translateThumbs)(this.getThumbs(), this.getOffsets(), rtl);
// ensure offsets are calculated when the refs for the marks have been created
// and those refs have been mounted to the dom
// on the state update in calculateOffsets with new markOffsets are calculated
if (prevProps.max !== max ||
prevProps.min !== min ||
prevProps.step !== step ||
prevState.markOffsets.length !== this.state.markOffsets.length) {
this.calculateMarkOffsets();
values.forEach(function (value) {
if (!(0, utils_1.isStepDivisible)(min, value, step)) {
console.warn('The `values` property is in conflict with the current `step`, `min`, and `max` properties. Please provide values that are accessible using the min, max, and step values.');
}
});
}
};
Range.prototype.componentWillUnmount = function () {
var options = {
passive: false
};
document.removeEventListener('mousedown', this.onMouseOrTouchStart, options);
// These need to be removed!!
document.removeEventListener('mousemove', this.schdOnMouseMove);
document.removeEventListener('touchmove', this.schdOnTouchMove);
document.removeEventListener('touchstart', this.onMouseOrTouchStart);
document.removeEventListener('mouseup', this.schdOnEnd);
document.removeEventListener('touchend', this.schdOnEnd);
this.resizeObserver.unobserve(this.trackRef.current);
};
Range.prototype.render = function () {
var _this = this;
var _a = this.props, renderTrack = _a.renderTrack, renderThumb = _a.renderThumb, _b = _a.renderMark, renderMark = _b === void 0 ? function () { return null; } : _b, values = _a.values, min = _a.min, max = _a.max, allowOverlap = _a.allowOverlap, disabled = _a.disabled;
var _c = this.state, draggedThumbIndex = _c.draggedThumbIndex, thumbZIndexes = _c.thumbZIndexes, markOffsets = _c.markOffsets;
return renderTrack({
props: {
style: {
// creates stacking context that prevents z-index applied to thumbs
// interfere with other elements
transform: 'scale(1)',
cursor: draggedThumbIndex > -1
? 'grabbing'
: this.props.draggableTrack
? (0, utils_1.isVertical)(this.props.direction)
? 'ns-resize'
: 'ew-resize'
: values.length === 1 && !disabled
? 'pointer'
: 'inherit'
},
onMouseDown: disabled ? utils_1.voidFn : this.onMouseDownTrack,
onTouchStart: disabled ? utils_1.voidFn : this.onTouchStartTrack,
ref: this.trackRef
},
isDragged: this.state.draggedThumbIndex > -1,
disabled: disabled,
children: __spreadArray(__spreadArray([], markOffsets.map(function (offset, index, arr) {
return renderMark({
props: {
style: _this.props.direction === types_1.Direction.Left ||
_this.props.direction === types_1.Direction.Right
? {
position: 'absolute',
left: "".concat(offset[0], "px"),
marginTop: "".concat(offset[1], "px")
}
: {
position: 'absolute',
top: "".concat(offset[0], "px"),
marginLeft: "".concat(offset[1], "px")
},
key: "mark".concat(index),
ref: _this.markRefs[index]
},
index: index
});
}), true), values.map(function (value, index) {
var isDragged = _this.state.draggedThumbIndex === index;
return renderThumb({
index: index,
value: value,
isDragged: isDragged,
props: {
style: {
position: 'absolute',
zIndex: thumbZIndexes[index],
cursor: disabled ? 'inherit' : isDragged ? 'grabbing' : 'grab',
userSelect: 'none',
touchAction: 'none',
WebkitUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none'
},
key: index,
tabIndex: disabled ? undefined : 0,
'aria-valuemax': allowOverlap ? max : values[index + 1] || max,
'aria-valuemin': allowOverlap ? min : values[index - 1] || min,
'aria-valuenow': value,
draggable: false,
ref: _this.thumbRefs[index],
role: 'slider',
onKeyDown: disabled ? utils_1.voidFn : _this.onKeyDown,
onKeyUp: disabled ? utils_1.voidFn : _this.onKeyUp
}
});
}), true)
});
};
Range.defaultProps = {
step: 1,
direction: types_1.Direction.Right,
rtl: false,
disabled: false,
allowOverlap: false,
draggableTrack: false,
min: 0,
max: 100
};
return Range;
}(React.Component));
exports.default = Range;