@julo-ui/sliders
Version:
A React Slider component that implements input[type='range']
242 lines (235 loc) • 8.31 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/range-slider/usecase/index.ts
var usecase_exports = {};
__export(usecase_exports, {
useHandleFocusThumb: () => use_handle_focus_thumb_default,
useHandlePanEvent: () => use_handle_pan_event_default
});
module.exports = __toCommonJS(usecase_exports);
// src/range-slider/usecase/use-handle-focus-thumb.ts
var import_react = require("react");
function useHandleFocusThumb(options) {
const { focusThumbOnChange, rootRef, ids, activeIndex } = options;
const onFocusThumb = (0, import_react.useCallback)(
(index) => {
var _a;
const targetIndex = index != null ? index : activeIndex;
if (targetIndex === -1 || !focusThumbOnChange)
return;
const id = ids.getThumb(targetIndex);
const thumb = (_a = rootRef.current) == null ? void 0 : _a.ownerDocument.getElementById(id);
if (thumb) {
setTimeout(() => thumb.focus());
}
},
[activeIndex, focusThumbOnChange, ids, rootRef]
);
return { onFocusThumb };
}
var use_handle_focus_thumb_default = useHandleFocusThumb;
// src/range-slider/usecase/use-handle-pan-event.ts
var import_react2 = require("react");
var import_react_use_pan_event = require("@chakra-ui/react-use-pan-event");
var import_function_utils = require("@julo-ui/function-utils");
// src/utils.ts
var import_number_utils = require("@julo-ui/number-utils");
function percentToValue(percent, min, max) {
return (max - min) * percent + min;
}
function isMouseEvent(event) {
return !("touches" in event);
}
function roundValueToStep(value, from, step) {
const nextValue = Math.round((value - from) / step) * step + from;
const precision = (0, import_number_utils.countDecimalPlaces)(step);
return (0, import_number_utils.toPreciseDecimal)(nextValue, precision);
}
// src/range-slider/utils.ts
var import_number_utils2 = require("@julo-ui/number-utils");
function getThumbStateOnChange({
bounds,
index,
values,
pointerValue,
prevValue,
step,
isDisableSwap,
distanceBetweenThumbs
}) {
const prevValueAtIndex = prevValue[index];
const boundsAtIndex = bounds[index];
let valueAtIndex = (0, import_number_utils2.clampValue)(
parseFloat(roundValueToStep(pointerValue, boundsAtIndex.min, step)),
isDisableSwap ? boundsAtIndex.min : bounds[0].min,
isDisableSwap ? boundsAtIndex.max : bounds[bounds.length - 1].max
);
const isDecreasing = pointerValue < prevValueAtIndex;
const isValueExceedBoundedValue = !isDisableSwap && (isDecreasing ? valueAtIndex <= boundsAtIndex.min - distanceBetweenThumbs : valueAtIndex >= boundsAtIndex.max + distanceBetweenThumbs);
if (isValueExceedBoundedValue) {
const totalThumb = prevValue.length;
let isFoundNewThumb = false;
let isNoMoreThumb = false;
values[index] = isDecreasing ? boundsAtIndex.min : boundsAtIndex.max;
for (let i = isDecreasing ? index - 1 : index + 1; !isFoundNewThumb && !isNoMoreThumb; isDecreasing ? i-- : i++) {
if (i >= totalThumb || i < 0) {
isNoMoreThumb = true;
if (isDecreasing ? valueAtIndex < boundsAtIndex.min : valueAtIndex > boundsAtIndex.max) {
valueAtIndex = isDecreasing ? boundsAtIndex.min : boundsAtIndex.max;
}
continue;
}
if (!isDecreasing ? valueAtIndex >= bounds[i].min && valueAtIndex < bounds[i].max + distanceBetweenThumbs : valueAtIndex > bounds[i].min - distanceBetweenThumbs && valueAtIndex <= bounds[i].max) {
isFoundNewThumb = true;
index = i;
}
}
} else {
if (!isDecreasing && pointerValue > boundsAtIndex.max) {
valueAtIndex = boundsAtIndex.max;
}
if (isDecreasing && pointerValue < boundsAtIndex.min) {
valueAtIndex = boundsAtIndex.min;
}
}
values[index] = valueAtIndex;
return { index, value: values };
}
// src/range-slider/usecase/use-handle-pan-event.ts
function useHandlePanEvent(options) {
const {
rootRef,
trackRef,
sliderStates,
activeIndex,
actions,
onFocusThumb,
onDraggingStart,
onDraggingEnd,
onChangeStart = import_function_utils._noop,
onChangeEnd = import_function_utils._noop,
isDisableSwap,
prevValue,
distanceBetweenThumbs
} = options;
const getValueFromPointer = (0, import_react2.useCallback)(
(event) => {
var _a;
if (!trackRef.current)
return;
sliderStates.eventSource = "pointer";
const trackRect = trackRef.current.getBoundingClientRect();
const { clientX, clientY } = !isMouseEvent(event) ? (_a = event.touches) == null ? void 0 : _a[0] : event;
const diff = sliderStates.isVertical ? trackRect.bottom - clientY : clientX - trackRect.left;
const length = sliderStates.isVertical ? trackRect.height : trackRect.width;
let percent = diff / length;
if (sliderStates.isReversed)
percent = 1 - percent;
return percentToValue(percent, sliderStates.min, sliderStates.max);
},
[trackRef, sliderStates]
);
const onPanSessionStart = (0, import_react2.useCallback)(
(event) => {
var _a;
if (!sliderStates.isInteractive)
return;
onDraggingStart();
const pointValue = (_a = getValueFromPointer(event)) != null ? _a : 0;
const distances = sliderStates.value.map(
(value) => Math.abs(value - pointValue)
);
const closest = Math.min(...distances);
let targetIndex = distances.indexOf(closest);
const thumbsPosition = distances.filter(
(distance) => distance === closest
);
const isThumbStacked = thumbsPosition.length > 1;
if (isThumbStacked && pointValue > sliderStates.value[targetIndex]) {
targetIndex = targetIndex + thumbsPosition.length - 1;
}
actions.setActiveIndex(targetIndex);
actions.setValueAtIndex(targetIndex, pointValue);
onFocusThumb(targetIndex);
onChangeStart(sliderStates.value);
},
[
actions,
getValueFromPointer,
onChangeStart,
onDraggingStart,
onFocusThumb,
sliderStates.isInteractive,
sliderStates.value
]
);
const onPan = (0, import_react2.useCallback)(
(event) => {
var _a;
if (!sliderStates.isInteractive || activeIndex === -1)
return;
const pointerValue = (_a = getValueFromPointer(event)) != null ? _a : 0;
const values = [...sliderStates.value];
const bounds = [...sliderStates.valueBounds];
const { index, value } = getThumbStateOnChange({
pointerValue,
values,
bounds,
isDisableSwap,
index: activeIndex,
prevValue: prevValue.current,
step: sliderStates.step,
distanceBetweenThumbs
});
actions.setActiveIndex(index);
actions.setValue(value);
onFocusThumb(index);
},
[
actions,
activeIndex,
distanceBetweenThumbs,
getValueFromPointer,
isDisableSwap,
onFocusThumb,
prevValue,
sliderStates.isInteractive,
sliderStates.step,
sliderStates.value,
sliderStates.valueBounds
]
);
(0, import_react_use_pan_event.usePanEvent)(rootRef, {
onPanSessionStart,
onPanSessionEnd() {
if (!sliderStates.isInteractive)
return;
onDraggingEnd();
prevValue.current = sliderStates.value;
onChangeEnd(sliderStates.value);
},
onPan
});
}
var use_handle_pan_event_default = useHandlePanEvent;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
useHandleFocusThumb,
useHandlePanEvent
});