UNPKG

grommet

Version:

focus on the essential experience

351 lines (344 loc) 14.6 kB
"use strict"; exports.__esModule = true; exports.valueToStepPrecision = exports.getDecimalCount = exports.RangeSelector = void 0; var _react = _interopRequireWildcard(require("react")); var _styledComponents = _interopRequireDefault(require("styled-components")); var _useIsomorphicLayoutEffect = require("../../utils/use-isomorphic-layout-effect"); var _Box = require("../Box"); var _EdgeControl = require("./EdgeControl"); var _FormContext = require("../Form/FormContext"); var _Text = require("../Text"); var _utils = require("../../utils"); var _propTypes = require("./propTypes"); var _DataFormContext = require("../../contexts/DataFormContext"); var _useThemeValue2 = require("../../utils/useThemeValue"); var _excluded = ["color", "defaultValues", "direction", "invert", "label", "max", "messages", "min", "name", "onChange", "opacity", "round", "size", "step", "values"]; function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } var Container = (0, _styledComponents["default"])(_Box.Box).withConfig({ displayName: "RangeSelector__Container", componentId: "sc-siof5p-0" })(["user-select:none;"]); var getDecimalCount = exports.getDecimalCount = function getDecimalCount(number) { if (Number.isInteger(number)) { return 0; } // handle small numbers (0.00000001) which javascript // will turn into `e-` if (Math.abs(number) < 1) { var parts = number.toExponential().split('e-'); var _decimalPart = parts[0].split('.')[1] || ''; return _decimalPart.length + parseInt(parts[1], 10); } var decimalPart = number.toString().split('.')[1] || ''; return decimalPart.length; }; // avoid floating point issues like 0.15 + 0.3 = 0.44999999999999996 // and turn into 0.15 + 0.3 = 0.45 var valueToStepPrecision = exports.valueToStepPrecision = function valueToStepPrecision(value, step, min) { var nearestTrueStep = Math.round((value - min) / step) * step + min; return Number(nearestTrueStep.toFixed(getDecimalCount(step))); }; // ensure values are within min/max var clamp = function clamp(value, min, max) { return Math.min(Math.max(min, value), max); }; var RangeSelector = exports.RangeSelector = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) { var color = _ref.color, _ref$defaultValues = _ref.defaultValues, defaultValues = _ref$defaultValues === void 0 ? [] : _ref$defaultValues, _ref$direction = _ref.direction, direction = _ref$direction === void 0 ? 'horizontal' : _ref$direction, invert = _ref.invert, label = _ref.label, _ref$max = _ref.max, max = _ref$max === void 0 ? 100 : _ref$max, messages = _ref.messages, _ref$min = _ref.min, min = _ref$min === void 0 ? 0 : _ref$min, name = _ref.name, onChange = _ref.onChange, _ref$opacity = _ref.opacity, opacity = _ref$opacity === void 0 ? 'medium' : _ref$opacity, round = _ref.round, _ref$size = _ref.size, size = _ref$size === void 0 ? 'medium' : _ref$size, _ref$step = _ref.step, step = _ref$step === void 0 ? 1 : _ref$step, valuesProp = _ref.values, rest = _objectWithoutPropertiesLoose(_ref, _excluded); var _useThemeValue = (0, _useThemeValue2.useThemeValue)(), theme = _useThemeValue.theme; var formContext = (0, _react.useContext)(_FormContext.FormContext); var _useState = (0, _react.useState)(), changing = _useState[0], setChanging = _useState[1]; var _useState2 = (0, _react.useState)(), lastChange = _useState2[0], setLastChange = _useState2[1]; var _useState3 = (0, _react.useState)(), moveValue = _useState3[0], setMoveValue = _useState3[1]; var containerRef = (0, _react.useRef)(); var maxRef = (0, _react.useRef)(); var minRef = (0, _react.useRef)(); var labelWidthRef = (0, _react.useRef)(0); var _formContext$useFormI = formContext.useFormInput({ name: name, value: valuesProp == null ? void 0 : valuesProp.map(function (n) { return clamp(n, min, max); }), initialValue: defaultValues }), values = _formContext$useFormI[0], setValues = _formContext$useFormI[1]; // for DataFilters to know when RangeSelector is set to its min/max var _useContext = (0, _react.useContext)(_DataFormContext.DataFormContext), pendingReset = _useContext.pendingReset; var updatePendingReset = (0, _react.useCallback)(function (nextMin, nextMax) { var _pendingReset$current; if (nextMin === min && nextMax === max) { pendingReset == null || pendingReset.current.add(name); } else if (pendingReset != null && (_pendingReset$current = pendingReset.current) != null && _pendingReset$current.has(name)) { pendingReset == null || pendingReset.current["delete"](name); } }, [max, min, name, pendingReset]); var change = (0, _react.useCallback)(function (nextValues) { var nextMin = nextValues[0], nextMax = nextValues[1]; // only adjust value to step precision if it's not the min/max if (nextMin !== min && nextMin !== max) nextMin = valueToStepPrecision(nextValues[0], step, min); if (nextMax !== min && nextMax !== max) nextMax = valueToStepPrecision(nextValues[1], step, min); // ensure values are within min/max nextMin = clamp(nextMin, min, max); nextMax = clamp(nextMax, min, max); // make sure this is only called if both of the values // are actually distinct from the previous values if (nextMin !== values[0] || nextMax !== values[1]) { updatePendingReset(nextMin, nextMax); setValues([nextMin, nextMax]); if (onChange) onChange([nextMin, nextMax]); } }, [onChange, setValues, step, max, min, values, updatePendingReset]); var valueForMouseCoord = (0, _react.useCallback)(function (event) { var rect = containerRef.current.getBoundingClientRect(); var value; if (direction === 'vertical') { // there is no x and y in unit testing var y = event.clientY - (rect.top || 0); // test resilience var scaleY = rect.height / (max - min + 1) || 1; // test resilience value = Math.floor(y / scaleY) + min; } else { var x = event.clientX - (rect.left || 0); // test resilience var scaleX = rect.width / (max - min + 1) || 1; // test resilience value = Math.floor(x / scaleX) + min; } // align with closest step within [min, max] var result = Math.ceil(value / step) * step; if (result < min) { return min; } if (result > max) { return max; } return result; }, [direction, max, min, step]); var onMouseMove = (0, _react.useCallback)(function (event) { var value = valueForMouseCoord(event); var nextValues; if (changing === 'lower' && value <= values[1] && value !== moveValue) { nextValues = [value, values[1]]; } else if (changing === 'upper' && value >= values[0] && value !== moveValue) { nextValues = [values[0], value]; } else if (changing === 'selection' && value !== moveValue) { if (value === max) { nextValues = [max - (values[1] - values[0]), max]; } else if (value === min) { nextValues = [min, min + (values[1] - values[0])]; } else { var delta = value - moveValue; if (values[0] + delta >= min && values[1] + delta <= max) { nextValues = [values[0] + delta, values[1] + delta]; } } } if (nextValues) { setMoveValue(value); change(nextValues); } }, [values, change, changing, moveValue, max, min, setMoveValue, valueForMouseCoord]); (0, _react.useEffect)(function () { var onMouseUp = function onMouseUp() { return setChanging(undefined); }; if (changing) { window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); return function () { window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); }; } return undefined; }, [changing, onMouseMove]); var onClick = (0, _react.useCallback)(function (event) { var value = valueForMouseCoord(event); if (value <= values[0] || value < values[1] && lastChange === 'lower') { setLastChange('lower'); change([value, values[1]]); } else if (value >= values[1] || value > values[0] && lastChange === 'upper') { setLastChange('upper'); change([values[0], value]); } }, [change, lastChange, valueForMouseCoord, values]); var onTouchMove = (0, _react.useCallback)(function (event) { var touchEvent = event.changedTouches[0]; onMouseMove(touchEvent); }, [onMouseMove]); // keep the text values size consistent (0, _useIsomorphicLayoutEffect.useLayoutEffect)(function () { if (maxRef.current && minRef.current) { maxRef.current.style.width = ''; minRef.current.style.width = ''; var width = Math.max(labelWidthRef.current, maxRef.current.getBoundingClientRect().width, minRef.current.getBoundingClientRect().width); maxRef.current.style.width = width + "px"; minRef.current.style.width = width + "px"; labelWidthRef.current = width; } }); var lower = values[0], upper = values[1]; // It needs to be true when vertical, due to how browsers manage height // const fill = direction === 'vertical' ? true : 'horizontal'; var thickness = size === 'full' ? undefined : (0, _utils.parseMetricToNum)(theme.global.edgeSize[size] || size) + "px"; var layoutProps = { fill: direction, round: round }; if (direction === 'vertical') layoutProps.width = thickness;else layoutProps.height = thickness; if (size === 'full') layoutProps.alignSelf = 'stretch'; var content = /*#__PURE__*/_react["default"].createElement(Container, _extends({ ref: containerRef, direction: direction === 'vertical' ? 'column' : 'row', align: "center", fill: true }, label ? {} : rest, { tabIndex: "-1", onClick: onClick, onTouchMove: onTouchMove }), /*#__PURE__*/_react["default"].createElement(_Box.Box, _extends({ style: { flex: lower - min + " 0 0" }, background: invert ? // preserve existing dark, instead of using darknes // of this color { color: color || theme.rangeSelector.background.invert.color, opacity: opacity, dark: theme.dark } : undefined }, layoutProps)), /*#__PURE__*/_react["default"].createElement(_EdgeControl.EdgeControl, { ref: ref, color: color, direction: direction, thickness: thickness, edge: "lower", min: min, max: max, messages: messages, value: lower, step: step, onMouseDown: function onMouseDown() { return setChanging('lower'); }, onTouchStart: function onTouchStart() { return setChanging('lower'); }, onDecrease: function onDecrease() { return change([lower - step, upper]); }, onIncrease: lower + step <= upper ? function () { return change([lower + step, upper]); } : function () { return change([upper, upper]); } }), /*#__PURE__*/_react["default"].createElement(_Box.Box, _extends({ style: { flex: upper - lower + 1 + " 0 0", cursor: direction === 'vertical' ? 'ns-resize' : 'ew-resize' }, background: invert ? undefined : // preserve existing dark, instead of using darknes of // this color { color: color || 'control', opacity: opacity, dark: theme.dark } }, layoutProps, { onMouseDown: function onMouseDown(event) { var nextMoveValue = valueForMouseCoord(event); setChanging('selection'); setMoveValue(nextMoveValue); } })), /*#__PURE__*/_react["default"].createElement(_EdgeControl.EdgeControl, { color: color, direction: direction, thickness: thickness, edge: "upper", min: min, max: max, value: upper, step: step, onMouseDown: function onMouseDown() { return setChanging('upper'); }, onTouchStart: function onTouchStart() { return setChanging('upper'); }, onDecrease: upper - step >= lower ? function () { return change([lower, upper - step]); } : function () { return change([lower, lower]); }, onIncrease: function onIncrease() { return change([lower, upper + step]); } }), /*#__PURE__*/_react["default"].createElement(_Box.Box, _extends({ style: { flex: max - upper + " 0 0" }, background: invert ? // preserve existing dark, instead of using darknes of this // color { color: color || theme.rangeSelector.background.invert.color, opacity: opacity, dark: theme.dark } : undefined }, layoutProps, { round: round }))); if (label) { content = /*#__PURE__*/_react["default"].createElement(_Box.Box, _extends({ direction: direction === 'vertical' ? 'column' : 'row', align: "center", fill: true }, rest), /*#__PURE__*/_react["default"].createElement(_Text.Text, { ref: minRef, textAlign: "end", size: "small", margin: theme.rangeSelector.label.margin }, typeof label === 'function' ? label(lower) : lower), content, /*#__PURE__*/_react["default"].createElement(_Text.Text, { ref: maxRef, size: "small", margin: theme.rangeSelector.label.margin }, typeof label === 'function' ? label(upper) : upper)); } return content; }); RangeSelector.displayName = 'RangeSelector'; RangeSelector.propTypes = _propTypes.RangeSelectorPropTypes;