@itwin/itwinui-react
Version:
A react component library for iTwinUI
385 lines (384 loc) • 11.9 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', {
value: true,
});
Object.defineProperty(exports, 'Slider', {
enumerable: true,
get: function () {
return Slider;
},
});
const _interop_require_default = require('@swc/helpers/_/_interop_require_default');
const _interop_require_wildcard = require('@swc/helpers/_/_interop_require_wildcard');
const _classnames = /*#__PURE__*/ _interop_require_default._(
require('classnames'),
);
const _react = /*#__PURE__*/ _interop_require_wildcard._(require('react'));
const _index = require('../../utils/index.js');
const _Track = require('./Track.js');
const _Thumb = require('./Thumb.js');
const _react1 = require('@floating-ui/react');
const _Tooltip = require('../Tooltip/Tooltip.js');
const getPercentageOfRectangle = (rect, pointerX, pointerY, orientation) => {
if ('horizontal' === orientation) {
let position = (0, _index.getBoundedValue)(pointerX, rect.left, rect.right);
return (position - rect.left) / rect.width;
}
let position = (0, _index.getBoundedValue)(pointerY, rect.top, rect.bottom);
return (rect.bottom - position) / rect.height;
};
const getClosestValueIndex = (values, pointerValue) => {
if (1 === values.length) return 0;
let distances = values.map((value) => Math.abs(value - pointerValue));
let smallest = Math.min(...distances);
return distances.indexOf(smallest);
};
const getDefaultTrackDisplay = (trackDisplayMode, values) => {
if ('auto' !== trackDisplayMode) return trackDisplayMode;
return values.length % 2 ? 'even-segments' : 'odd-segments';
};
const roundValueToClosestStep = (value, step, min) =>
Math.round((value - min) / step) * step + min;
const formatNumberValue = (value, step, numDecimals) => {
if (Number.isInteger(step)) return value.toFixed(0);
return value.toFixed(numDecimals);
};
const focusThumb = (sliderContainer, activeIndex) => {
let doc = sliderContainer.ownerDocument;
if (
!sliderContainer.contains(doc.activeElement) ||
Number(doc.activeElement?.getAttribute('data-index')) !== activeIndex
) {
let thumbToFocus = sliderContainer.querySelector(
`[data-index="${activeIndex}"]`,
);
thumbToFocus && thumbToFocus.focus();
}
};
const Slider = _react.forwardRef((props, ref) => {
let {
min = 0,
max = 100,
values,
step = 1,
tooltipProps,
disabled = false,
tickLabels,
minLabel,
maxLabel,
trackDisplayMode = 'auto',
thumbMode = 'inhibit-crossing',
onChange,
onUpdate,
thumbProps,
className,
trackContainerProps,
minProps,
maxProps,
trackProps,
tickProps,
ticksProps,
orientation = 'horizontal',
...rest
} = props;
let [currentValues, setCurrentValues] = _react.useState(values);
_react.useEffect(() => {
setCurrentValues(values);
}, [values]);
let [minValueLabel, setMinValueLabel] = _react.useState(
() => minLabel ?? min.toString(),
);
_react.useEffect(() => {
setMinValueLabel(minLabel ?? min.toString());
}, [minLabel, min]);
let [maxValueLabel, setMaxValueLabel] = _react.useState(
() => maxLabel ?? max.toString(),
);
_react.useEffect(() => {
setMaxValueLabel(maxLabel ?? max.toString());
}, [maxLabel, max]);
let [trackDisplay, setTrackDisplay] = _react.useState(() =>
getDefaultTrackDisplay(trackDisplayMode, currentValues),
);
_react.useEffect(() => {
setTrackDisplay(getDefaultTrackDisplay(trackDisplayMode, currentValues));
}, [trackDisplayMode, currentValues]);
let containerRef = _react.useRef(null);
let getNumDecimalPlaces = _react.useMemo(() => {
let stepString = step.toString();
let decimalIndex = stepString.indexOf('.');
return stepString.length - (decimalIndex + 1);
}, [step]);
let getAllowableThumbRange = _react.useCallback(
(index) => {
if ('inhibit-crossing' === thumbMode) {
let minVal = 0 === index ? min : currentValues[index - 1] + step;
let maxVal =
index < currentValues.length - 1
? currentValues[index + 1] - step
: max;
return [minVal, maxVal];
}
return [min, max];
},
[max, min, step, thumbMode, currentValues],
);
let [activeThumbIndex, setActiveThumbIndex] = _react.useState(void 0);
let updateThumbValue = _react.useCallback(
(event, callbackType) => {
if (containerRef.current && void 0 !== activeThumbIndex) {
let percent = getPercentageOfRectangle(
containerRef.current.getBoundingClientRect(),
event.clientX,
event.clientY,
orientation,
);
let pointerValue = min + (max - min) * percent;
pointerValue = roundValueToClosestStep(pointerValue, step, min);
let [minVal, maxVal] = getAllowableThumbRange(activeThumbIndex);
pointerValue = (0, _index.getBoundedValue)(
pointerValue,
minVal,
maxVal,
);
if (pointerValue !== currentValues[activeThumbIndex]) {
let newValues = [...currentValues];
newValues[activeThumbIndex] = pointerValue;
setCurrentValues(newValues);
'onChange' === callbackType
? onChange?.(newValues)
: onUpdate?.(newValues);
} else if ('onChange' === callbackType) onChange?.(currentValues);
}
},
[
activeThumbIndex,
min,
max,
step,
getAllowableThumbRange,
currentValues,
onUpdate,
onChange,
orientation,
],
);
let handlePointerMove = _react.useCallback(
(event) => {
if (void 0 === activeThumbIndex) return;
event.preventDefault();
event.stopPropagation();
updateThumbValue(event, 'onUpdate');
},
[activeThumbIndex, updateThumbValue],
);
let onThumbValueChanged = _react.useCallback(
(index, value, keyboardReleased) => {
if (currentValues[index] === value && !keyboardReleased) return;
if (keyboardReleased) onChange?.(currentValues);
else {
let newValues = [...currentValues];
newValues[index] = value;
onUpdate?.(newValues);
setCurrentValues(newValues);
}
},
[currentValues, onUpdate, onChange],
);
let onThumbActivated = _react.useCallback((index) => {
setActiveThumbIndex(index);
}, []);
let handlePointerUp = _react.useCallback(
(event) => {
if (void 0 === activeThumbIndex) return;
updateThumbValue(event, 'onChange');
setActiveThumbIndex(void 0);
event.preventDefault();
event.stopPropagation();
},
[activeThumbIndex, updateThumbValue],
);
let handlePointerDownOnSlider = _react.useCallback(
(event) => {
if (containerRef.current) {
let percent = getPercentageOfRectangle(
containerRef.current.getBoundingClientRect(),
event.clientX,
event.clientY,
orientation,
);
let pointerValue = min + (max - min) * percent;
pointerValue = roundValueToClosestStep(pointerValue, step, min);
let closestValueIndex = getClosestValueIndex(
currentValues,
pointerValue,
);
let [minVal, maxVal] = getAllowableThumbRange(closestValueIndex);
pointerValue = (0, _index.getBoundedValue)(
pointerValue,
minVal,
maxVal,
);
if (pointerValue === currentValues[closestValueIndex]) return;
let newValues = [...currentValues];
newValues[closestValueIndex] = pointerValue;
setCurrentValues(newValues);
onChange?.(newValues);
onUpdate?.(newValues);
focusThumb(containerRef.current, closestValueIndex);
event.preventDefault();
event.stopPropagation();
}
},
[
min,
max,
step,
currentValues,
getAllowableThumbRange,
onChange,
onUpdate,
orientation,
],
);
(0, _index.useEventListener)(
'pointermove',
handlePointerMove,
containerRef.current?.ownerDocument,
);
(0, _index.useEventListener)(
'pointerup',
handlePointerUp,
containerRef.current?.ownerDocument,
);
let tickMarkArea = _react.useMemo(() => {
if (!tickLabels) return null;
if (Array.isArray(tickLabels))
return _react.createElement(
_index.Box,
{
as: 'div',
...ticksProps,
className: (0, _classnames.default)(
'iui-slider-ticks',
ticksProps?.className,
),
},
tickLabels.map((label, index) =>
_react.createElement(
_index.Box,
{
as: 'span',
...tickProps,
key: index,
className: (0, _classnames.default)(
'iui-slider-tick',
tickProps?.className,
),
},
label,
),
),
);
return tickLabels;
}, [tickLabels, tickProps, ticksProps]);
let generateTooltipProps = _react.useCallback(
(index, val) => {
let outProps = tooltipProps ? tooltipProps(index, val, step) : {};
return {
...outProps,
content: outProps.content
? outProps.content
: formatNumberValue(val, step, getNumDecimalPlaces),
};
},
[getNumDecimalPlaces, step, tooltipProps],
);
return _react.createElement(
_index.Box,
{
ref: ref,
className: (0, _classnames.default)('iui-slider-container', className),
'data-iui-orientation': orientation,
'data-iui-disabled': disabled ? 'true' : void 0,
...rest,
},
minValueLabel &&
_react.createElement(
_index.Box,
{
as: 'span',
...minProps,
className: (0, _classnames.default)(
'iui-slider-min',
minProps?.className,
),
},
minValueLabel,
),
_react.createElement(
_react1.FloatingDelayGroup,
{
delay: _Tooltip.defaultTooltipDelay,
},
_react.createElement(
_index.Box,
{
ref: containerRef,
...trackContainerProps,
className: (0, _classnames.default)(
'iui-slider',
{
'iui-grabbing': void 0 !== activeThumbIndex,
},
trackContainerProps?.className,
),
onPointerDown: handlePointerDownOnSlider,
},
currentValues.map((thumbValue, index) => {
let [minVal, maxVal] = getAllowableThumbRange(index);
let thisThumbProps = thumbProps?.(index);
return _react.createElement(_Thumb.Thumb, {
key: thisThumbProps?.id ?? index,
index: index,
disabled: disabled,
isActive: activeThumbIndex === index,
onThumbActivated: onThumbActivated,
onThumbValueChanged: onThumbValueChanged,
minVal: minVal,
maxVal: maxVal,
value: thumbValue,
tooltipProps: generateTooltipProps(index, thumbValue),
thumbProps: thisThumbProps,
step: step,
sliderMin: min,
sliderMax: max,
});
}),
_react.createElement(_Track.Track, {
trackDisplayMode: trackDisplay,
sliderMin: min,
sliderMax: max,
values: currentValues,
orientation: orientation,
...trackProps,
}),
),
),
tickMarkArea,
maxValueLabel &&
_react.createElement(
_index.Box,
{
as: 'span',
...maxProps,
className: (0, _classnames.default)(
'iui-slider-max',
maxProps?.className,
),
},
maxValueLabel,
),
);
});
if ('development' === process.env.NODE_ENV) Slider.displayName = 'Slider';