web-toolkit
Version:
A GTK inspired toolkit designed to build awesome web apps
729 lines (600 loc) • 21.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/objectSpread2"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/toConsumableArray"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/slicedToArray"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/objectWithoutProperties"));
var _react = _interopRequireDefault(require("react"));
var _clsx = _interopRequireDefault(require("clsx"));
var _rambda = require("rambda");
var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument"));
var _useIsFocusVisible2 = _interopRequireDefault(require("../utils/useIsFocusVisible"));
var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
/*
* Range.js
* adapted from: https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Range/Range.js
*/
var axisProps = {
horizontal: {
offset: function offset(percent) {
return {
left: "".concat(percent, "%")
};
},
leap: function leap(percent) {
return {
width: "".concat(percent, "%")
};
}
},
'horizontal-reverse': {
offset: function offset(percent) {
return {
right: "".concat(percent, "%")
};
},
leap: function leap(percent) {
return {
width: "".concat(percent, "%")
};
}
},
vertical: {
offset: function offset(percent) {
return {
bottom: "".concat(percent, "%")
};
},
leap: function leap(percent) {
return {
height: "".concat(percent, "%")
};
}
}
};
var Range = _react.default.forwardRef(function Range(props, ref) {
var ariaLabel = props['aria-label'],
ariaLabelledby = props['aria-labelledby'],
ariaValuetext = props['aria-valuetext'],
className = props.className,
_props$size = props.size,
size = _props$size === void 0 ? 'medium' : _props$size,
_props$defaultValue = props.defaultValue,
defaultValue = _props$defaultValue === void 0 ? 0 : _props$defaultValue,
_props$disabled = props.disabled,
disabled = _props$disabled === void 0 ? false : _props$disabled,
_props$marks = props.marks,
marksProp = _props$marks === void 0 ? false : _props$marks,
_props$max = props.max,
max = _props$max === void 0 ? 100 : _props$max,
_props$min = props.min,
min = _props$min === void 0 ? 0 : _props$min,
name = props.name,
onChange = props.onChange,
onChangeCommitted = props.onChangeCommitted,
onMouseDown = props.onMouseDown,
_props$vertical = props.vertical,
vertical = _props$vertical === void 0 ? false : _props$vertical,
_props$scale = props.scale,
scale = _props$scale === void 0 ? _rambda.identity : _props$scale,
_props$step = props.step,
step = _props$step === void 0 ? 1 : _props$step,
_props$ThumbComponent = props.ThumbComponent,
ThumbComponent = _props$ThumbComponent === void 0 ? 'span' : _props$ThumbComponent,
_props$track = props.track,
track = _props$track === void 0 ? 'normal' : _props$track,
valueProp = props.value,
_props$valueLabelDisp = props.valueLabelDisplay,
valueLabelDisplay = _props$valueLabelDisp === void 0 ? 'off' : _props$valueLabelDisp,
_props$valueLabelForm = props.valueLabelFormat,
valueLabelFormat = _props$valueLabelForm === void 0 ? _rambda.identity : _props$valueLabelForm,
other = (0, _objectWithoutProperties2.default)(props, ["aria-label", "aria-labelledby", "aria-valuetext", "className", "size", "defaultValue", "disabled", "marks", "max", "min", "name", "onChange", "onChangeCommitted", "onMouseDown", "vertical", "scale", "step", "ThumbComponent", "track", "value", "valueLabelDisplay", "valueLabelFormat"]);
var orientation = vertical ? 'vertical' : 'horizontal';
var touchId = _react.default.useRef(); // We can't use the :active browser pseudo-classes.
// - The active state isn't triggered when clicking on the rail.
// - The active state isn't transfered when inversing a range slider.
var _React$useState = _react.default.useState(-1),
_React$useState2 = (0, _slicedToArray2.default)(_React$useState, 2),
active = _React$useState2[0],
setActive = _React$useState2[1];
var _React$useState3 = _react.default.useState(-1),
_React$useState4 = (0, _slicedToArray2.default)(_React$useState3, 2),
open = _React$useState4[0],
setOpen = _React$useState4[1];
var _useControlled = useControlled({
controlled: valueProp,
default: defaultValue,
name: 'Range'
}),
_useControlled2 = (0, _slicedToArray2.default)(_useControlled, 2),
valueDerived = _useControlled2[0],
setValueState = _useControlled2[1];
var range = Array.isArray(valueDerived);
var values = range ? valueDerived.slice().sort(asc) : [valueDerived];
values = values.map(function (value) {
return clamp(value, min, max);
});
var marks = marksProp === true && step !== null ? (0, _toConsumableArray2.default)(Array(Math.floor((max - min) / step) + 1)).map(function (_, index) {
return {
value: min + step * index
};
}) : marksProp || [];
var _useIsFocusVisible = (0, _useIsFocusVisible2.default)(),
isFocusVisible = _useIsFocusVisible.isFocusVisible,
onBlurVisible = _useIsFocusVisible.onBlurVisible,
focusVisibleRef = _useIsFocusVisible.ref;
var _React$useState5 = _react.default.useState(-1),
_React$useState6 = (0, _slicedToArray2.default)(_React$useState5, 2),
focusVisible = _React$useState6[0],
setFocusVisible = _React$useState6[1];
var sliderRef = _react.default.useRef();
var handleFocusRef = (0, _useForkRef.default)(focusVisibleRef, sliderRef);
var handleRef = (0, _useForkRef.default)(ref, handleFocusRef);
var handleFocus = function handleFocus(event) {
var index = Number(event.currentTarget.getAttribute('data-index'));
if (isFocusVisible(event)) {
setFocusVisible(index);
}
setOpen(index);
};
var handleBlur = function handleBlur() {
if (focusVisible !== -1) {
setFocusVisible(-1);
onBlurVisible();
}
setOpen(-1);
};
var handleMouseOver = function handleMouseOver(event) {
var index = Number(event.currentTarget.getAttribute('data-index'));
setOpen(index);
};
var handleMouseLeave = function handleMouseLeave() {
setOpen(-1);
};
var isRtl = false;
var handleKeyDown = function handleKeyDown(event) {
var index = Number(event.currentTarget.getAttribute('data-index'));
var value = values[index];
var tenPercents = (max - min) / 10;
var marksValues = marks.map(function (mark) {
return mark.value;
});
var marksIndex = marksValues.indexOf(value);
var newValue;
var increaseKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
var decreaseKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
switch (event.key) {
case 'Home':
newValue = min;
break;
case 'End':
newValue = max;
break;
case 'PageUp':
if (step) {
newValue = value + tenPercents;
}
break;
case 'PageDown':
if (step) {
newValue = value - tenPercents;
}
break;
case increaseKey:
case 'ArrowUp':
if (step) {
newValue = value + step;
} else {
newValue = marksValues[marksIndex + 1] || marksValues[marksValues.length - 1];
}
break;
case decreaseKey:
case 'ArrowDown':
if (step) {
newValue = value - step;
} else {
newValue = marksValues[marksIndex - 1] || marksValues[0];
}
break;
default:
return;
} // Prevent scroll of the page
event.preventDefault();
if (step) {
newValue = roundValueToStep(newValue, step, min);
}
newValue = clamp(newValue, min, max);
if (range) {
var previousValue = newValue;
newValue = setValueIndex({
values: values,
source: valueDerived,
newValue: newValue,
index: index
}).sort(asc);
focusThumb({
sliderRef: sliderRef,
activeIndex: newValue.indexOf(previousValue)
});
}
setValueState(newValue);
setFocusVisible(index);
if (onChange) {
onChange(newValue, event);
}
if (onChangeCommitted) {
onChangeCommitted(event, newValue);
}
};
var previousIndex = _react.default.useRef();
var axis = orientation;
if (isRtl && vertical == false) {
axis += '-reverse';
}
var getFingerNewValue = function getFingerNewValue(_ref) {
var finger = _ref.finger,
_ref$move = _ref.move,
move = _ref$move === void 0 ? false : _ref$move,
values2 = _ref.values,
source = _ref.source;
var slider = sliderRef.current;
var _slider$getBoundingCl = slider.getBoundingClientRect(),
width = _slider$getBoundingCl.width,
height = _slider$getBoundingCl.height,
bottom = _slider$getBoundingCl.bottom,
left = _slider$getBoundingCl.left;
var percent;
if (axis.indexOf('vertical') === 0) {
percent = (bottom - finger.y) / height;
} else {
percent = (finger.x - left) / width;
}
if (axis.indexOf('-reverse') !== -1) {
percent = 1 - percent;
}
var newValue;
newValue = percentToValue(percent, min, max);
if (step) {
newValue = roundValueToStep(newValue, step, min);
} else {
var marksValues = marks.map(function (mark) {
return mark.value;
});
var closestIndex = findClosest(marksValues, newValue);
newValue = marksValues[closestIndex];
}
newValue = clamp(newValue, min, max);
var activeIndex = 0;
if (range) {
if (!move) {
activeIndex = findClosest(values2, newValue);
} else {
activeIndex = previousIndex.current;
}
var previousValue = newValue;
newValue = setValueIndex({
values: values2,
source: source,
newValue: newValue,
index: activeIndex
}).sort(asc);
activeIndex = newValue.indexOf(previousValue);
previousIndex.current = activeIndex;
}
return {
newValue: newValue,
activeIndex: activeIndex
};
};
var _React$useState7 = _react.default.useState(false),
_React$useState8 = (0, _slicedToArray2.default)(_React$useState7, 2),
isMoving = _React$useState8[0],
setIsMoving = _React$useState8[1];
var handleTouchMove = function handleTouchMove(event) {
var finger = trackFinger(event, touchId);
if (!finger) {
return;
}
var _getFingerNewValue = getFingerNewValue({
finger: finger,
move: true,
values: values,
source: valueDerived
}),
newValue = _getFingerNewValue.newValue,
activeIndex = _getFingerNewValue.activeIndex;
focusThumb({
sliderRef: sliderRef,
activeIndex: activeIndex,
setActive: setActive
});
setValueState(newValue);
if (onChange) {
onChange(newValue, event);
}
};
var handleTouchEnd = function handleTouchEnd(event) {
var finger = trackFinger(event, touchId);
if (!finger) {
return;
}
var _getFingerNewValue2 = getFingerNewValue({
finger: finger,
values: values,
source: valueDerived
}),
newValue = _getFingerNewValue2.newValue;
setActive(-1);
if (event.type === 'touchend') {
setOpen(-1);
}
if (onChangeCommitted) {
onChangeCommitted(event, newValue);
}
touchId.current = undefined;
var doc = (0, _ownerDocument.default)(sliderRef.current);
doc.removeEventListener('mousemove', handleTouchMove);
doc.removeEventListener('mouseup', handleTouchEnd);
doc.removeEventListener('touchmove', handleTouchMove);
doc.removeEventListener('touchend', handleTouchEnd);
setIsMoving(false);
};
var handleTouchStart = function handleTouchStart(event) {
// Workaround as Safari has partial support for touchAction: 'none'.
event.preventDefault();
var touch = event.changedTouches[0];
if (touch != null) {
// A number that uniquely identifies the current finger in the touch session.
touchId.current = touch.identifier;
}
var finger = trackFinger(event, touchId);
var _getFingerNewValue3 = getFingerNewValue({
finger: finger,
values: values,
source: valueDerived
}),
newValue = _getFingerNewValue3.newValue,
activeIndex = _getFingerNewValue3.activeIndex;
focusThumb({
sliderRef: sliderRef,
activeIndex: activeIndex,
setActive: setActive
});
setValueState(newValue);
if (onChange) {
onChange(newValue, event);
}
var doc = (0, _ownerDocument.default)(sliderRef.current);
doc.addEventListener('touchmove', handleTouchMove);
doc.addEventListener('touchend', handleTouchEnd);
};
_react.default.useEffect(function () {
var slider = sliderRef.current;
slider.addEventListener('touchstart', handleTouchStart);
var doc = (0, _ownerDocument.default)(slider);
if (isMoving) {
var _doc = (0, _ownerDocument.default)(sliderRef.current);
_doc.addEventListener('mousemove', handleTouchMove);
_doc.addEventListener('mouseup', handleTouchEnd);
}
return function () {
slider.removeEventListener('touchstart', handleTouchStart);
doc.removeEventListener('mousemove', handleTouchMove);
doc.removeEventListener('mouseup', handleTouchEnd);
doc.removeEventListener('touchmove', handleTouchMove);
doc.removeEventListener('touchend', handleTouchEnd);
};
}, [handleTouchEnd, handleTouchMove, handleTouchStart]);
var handleMouseDown = function handleMouseDown(event) {
if (onMouseDown) {
onMouseDown(event);
}
event.preventDefault();
var finger = trackFinger(event, touchId);
var _getFingerNewValue4 = getFingerNewValue({
finger: finger,
values: values,
source: valueDerived
}),
newValue = _getFingerNewValue4.newValue,
activeIndex = _getFingerNewValue4.activeIndex;
focusThumb({
sliderRef: sliderRef,
activeIndex: activeIndex,
setActive: setActive
});
setValueState(newValue);
if (onChange) {
onChange(newValue, event);
}
var doc = (0, _ownerDocument.default)(sliderRef.current);
doc.addEventListener('mousemove', handleTouchMove);
doc.addEventListener('mouseup', handleTouchEnd);
setIsMoving(true);
};
var trackOffset = valueToPercent(range ? values[0] : min, min, max);
var trackLeap = valueToPercent(values[values.length - 1], min, max) - trackOffset;
var trackStyle = (0, _objectSpread2.default)((0, _objectSpread2.default)({}, axisProps[axis].offset(trackOffset)), axisProps[axis].leap(trackLeap));
return /*#__PURE__*/_react.default.createElement("span", Object.assign({
ref: handleRef,
className: (0, _clsx.default)('Range', size, {
disabled: disabled,
marked: marksProp,
vertical: vertical,
focus: focusVisible !== -1,
'no-track': track === false
}, className),
onMouseDown: handleMouseDown
}, other), /*#__PURE__*/_react.default.createElement("span", {
className: "Range__content"
}, /*#__PURE__*/_react.default.createElement("span", {
className: "Range__rail"
}), /*#__PURE__*/_react.default.createElement("span", {
className: "Range__track",
style: trackStyle
}), /*#__PURE__*/_react.default.createElement("input", {
value: values.join(','),
name: name,
type: "hidden"
}), marks.map(function (mark, index) {
var percent = valueToPercent(mark.value, min, max);
var style = axisProps[axis].offset(percent);
var markActive;
if (track === false) {
markActive = values.indexOf(mark.value) !== -1;
} else {
markActive = track === 'normal' && (range ? mark.value >= values[0] && mark.value <= values[values.length - 1] : mark.value <= values[0]) || track === 'inverted' && (range ? mark.value <= values[0] || mark.value >= values[values.length - 1] : mark.value >= values[0]);
}
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
key: mark.value
}, /*#__PURE__*/_react.default.createElement("span", {
style: style,
"data-index": index,
className: (0, _clsx.default)('Range__mark', {
active: markActive
})
}), mark.label != null ? /*#__PURE__*/_react.default.createElement("span", {
"aria-hidden": true,
"data-index": index,
style: style,
className: "Range__mark__label"
}, mark.label) : null);
}), values.map(function (value, index) {
var percent = valueToPercent(value, min, max);
var style = axisProps[axis].offset(percent);
return /*#__PURE__*/_react.default.createElement(ThumbComponent, {
key: index,
className: (0, _clsx.default)('Range__thumb', {
active: active === index,
disabled: disabled,
focus: focusVisible === index
}),
tabIndex: disabled ? null : 0,
role: "slider",
style: style,
"data-index": index,
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledby,
"aria-orientation": orientation,
"aria-valuemax": scale(max),
"aria-valuemin": scale(min),
"aria-valuenow": scale(value),
"aria-valuetext": ariaValuetext,
onKeyDown: handleKeyDown,
onFocus: handleFocus,
onBlur: handleBlur,
onMouseOver: handleMouseOver,
onMouseLeave: handleMouseLeave
});
})));
});
var _default = Range; // Helpers
exports.default = _default;
function asc(a, b) {
return a - b;
}
function clamp(value, min, max) {
return Math.min(Math.max(min, value), max);
}
function findClosest(values, currentValue) {
var _values$reduce = values.reduce(function (acc, value, index) {
var distance = Math.abs(currentValue - value);
if (acc === null || distance < acc.distance || distance === acc.distance) {
return {
distance: distance,
index: index
};
}
return acc;
}, null),
closestIndex = _values$reduce.index;
return closestIndex;
}
function trackFinger(event, touchId) {
if (touchId.current !== undefined && event.changedTouches) {
for (var i = 0; i < event.changedTouches.length; i += 1) {
var touch = event.changedTouches[i];
if (touch.identifier === touchId.current) {
return {
x: touch.clientX,
y: touch.clientY
};
}
}
return false;
}
return {
x: event.clientX,
y: event.clientY
};
}
function valueToPercent(value, min, max) {
return (value - min) * 100 / (max - min);
}
function percentToValue(percent, min, max) {
return (max - min) * percent + min;
}
function getDecimalPrecision(num) {
// This handles the case when num is very small (0.00000001), js will turn this into 1e-8.
// When num is bigger than 1 or less than -1 it won't get converted to this notation so it's fine.
if (Math.abs(num) < 1) {
var parts = num.toExponential().split('e-');
var matissaDecimalPart = parts[0].split('.')[1];
return (matissaDecimalPart ? matissaDecimalPart.length : 0) + parseInt(parts[1], 10);
}
var decimalPart = num.toString().split('.')[1];
return decimalPart ? decimalPart.length : 0;
}
function roundValueToStep(value, step, min) {
var nearest = Math.round((value - min) / step) * step + min;
return Number(nearest.toFixed(getDecimalPrecision(step)));
}
function setValueIndex(_ref2) {
var values = _ref2.values,
source = _ref2.source,
newValue = _ref2.newValue,
index = _ref2.index;
// Performance shortcut
if (values[index] === newValue) {
return source;
}
var output = values.slice();
output[index] = newValue;
return output;
}
function focusThumb(_ref3) {
var sliderRef = _ref3.sliderRef,
activeIndex = _ref3.activeIndex,
setActive = _ref3.setActive;
if (!sliderRef.current.contains(document.activeElement) || Number(document.activeElement.getAttribute('data-index')) !== activeIndex) {
sliderRef.current.querySelector("[role=\"slider\"][data-index=\"".concat(activeIndex, "\"]")).focus();
}
if (setActive) {
setActive(activeIndex);
}
}
function useControlled(_ref4) {
var controlled = _ref4.controlled,
defaultProp = _ref4.default,
name = _ref4.name,
_ref4$state = _ref4.state,
state = _ref4$state === void 0 ? 'value' : _ref4$state;
var _React$useRef = _react.default.useRef(controlled !== undefined),
isControlled = _React$useRef.current;
var _React$useState9 = _react.default.useState(defaultProp),
_React$useState10 = (0, _slicedToArray2.default)(_React$useState9, 2),
valueState = _React$useState10[0],
setValue = _React$useState10[1];
var value = isControlled ? controlled : valueState;
var setValueIfUncontrolled = _react.default.useCallback(function (newValue) {
if (!isControlled) {
setValue(newValue);
}
}, []);
return [value, setValueIfUncontrolled];
}
//# sourceMappingURL=Range.js.map