@carbon/react
Version:
React components for the Carbon Design System
890 lines (888 loc) • 31.9 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { usePrefix } from "../../internal/usePrefix.js";
import { Text } from "../Text/Text.js";
import { ArrowDown, ArrowLeft, ArrowRight as ArrowRight$1, ArrowUp as ArrowUp$1, Enter } from "../../internal/keyboard/keys.js";
import { matches } from "../../internal/keyboard/match.js";
import { useId } from "../../internal/useId.js";
import { deprecate } from "../../prop-types/deprecate.js";
import { Tooltip } from "../Tooltip/Tooltip.js";
import { useNormalizedInputProps } from "../../internal/useNormalizedInputProps.js";
import { clamp } from "../../internal/clamp.js";
import { LowerHandle, LowerHandleFocus, UpperHandle, UpperHandleFocus } from "./SliderHandles.js";
import classNames from "classnames";
import { useEffect, useMemo, useReducer, useRef } from "react";
import PropTypes from "prop-types";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { WarningAltFilled, WarningFilled } from "@carbon/icons-react";
import { throttle } from "es-toolkit/compat";
//#region src/components/Slider/Slider.tsx
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const ThumbWrapper = ({ hasTooltip, className, style, children, ...rest }) => {
if (hasTooltip) return /* @__PURE__ */ jsx(Tooltip, {
className,
style,
...rest,
children
});
else return /* @__PURE__ */ jsx("div", {
className,
style,
children
});
};
const translationIds = { "carbon.slider.auto-correct-announcement": "carbon.slider.auto-correct-announcement" };
const defaultTranslations = { [translationIds["carbon.slider.auto-correct-announcement"]]: "The inputted value \"{correctedValue}\" was corrected to the nearest allowed digit." };
const defaultTranslateWithId = (messageId, args) => {
const template = defaultTranslations[messageId];
const correctedValue = args?.correctedValue ?? "";
return template.replace("{correctedValue}", correctedValue);
};
const defaultFormatLabel = (value, label) => {
return `${value}${label ?? ""}`;
};
const hasUpperValue = (valueUpper) => typeof valueUpper !== "undefined";
const calcRawLeftPercent = ({ max, min, value }) => {
const range = max - min;
if (range === 0) return 0;
return clamp((value - min) / range, 0, 1);
};
/**
* Minimum time between processed "drag" events in milliseconds.
*/
const EVENT_THROTTLE = 16;
const DRAG_EVENT_TYPES = new Set(["mousemove", "touchmove"]);
const DRAG_STOP_EVENT_TYPES = new Set([
"mouseup",
"touchend",
"touchcancel"
]);
const Slider = (props) => {
const controlledValue = props.value;
const controlledValueUpper = props.unstable_valueUpper;
const controlledMax = props.max;
const controlledMin = props.min;
const onChange = props.onChange;
const onRelease = props.onRelease;
const [state, setState] = useReducer((prev, args) => ({
...prev,
...args
}), {
value: props.value,
valueUpper: props.unstable_valueUpper,
left: 0,
leftUpper: 0,
needsOnRelease: false,
isValid: true,
isValidUpper: true,
activeHandle: void 0,
correctedValue: null,
correctedPosition: null,
isRtl: false
});
const stateRef = useRef(state);
useEffect(() => {
stateRef.current = state;
}, [state]);
const propsRef = useRef(props);
useEffect(() => {
propsRef.current = props;
}, [props]);
const thumbRef = useRef(null);
const thumbRefUpper = useRef(null);
const filledTrackRef = useRef(null);
const elementRef = useRef(null);
const trackRef = useRef(null);
const generatedId = useId();
const prefix = usePrefix();
const twoHandles = hasUpperValue(state.valueUpper);
/**
* Sets up initial slider position and value in response to component mount.
*/
useEffect(() => {
if (elementRef.current) {
const isRtl = document?.dir === "rtl";
if (twoHandles) {
const { value, left } = calcValue({
value: stateRef.current.value,
useRawValue: true
});
const { value: valueUpper, left: leftUpper } = calcValue({
value: stateRef.current.valueUpper,
useRawValue: true
});
setState({
isRtl,
value,
left,
valueUpper,
leftUpper
});
} else {
const { value, left } = calcValue({
value: stateRef.current.value,
useRawValue: true
});
setState({
isRtl,
value,
left
});
}
}
return () => {
DRAG_STOP_EVENT_TYPES.forEach((element) => elementRef.current?.ownerDocument.removeEventListener(element, onDragStop));
DRAG_EVENT_TYPES.forEach((element) => elementRef.current?.ownerDocument.removeEventListener(element, handleDrag));
};
}, []);
useEffect(() => {
const el = filledTrackRef.current;
if (!el) return;
if (twoHandles) el.style.transform = state.isRtl ? `translate(${100 - state.leftUpper}%, -50%) scaleX(${(state.leftUpper - state.left) / 100})` : `translate(${state.left}%, -50%) scaleX(${(state.leftUpper - state.left) / 100})`;
else el.style.transform = state.isRtl ? `translate(100%, -50%) scaleX(-${state.left / 100})` : `translate(0%, -50%) scaleX(${state.left / 100})`;
}, [
state.isRtl,
state.left,
state.leftUpper,
twoHandles
]);
const prevValsRef = useRef(null);
useEffect(() => {
const prev = prevValsRef.current;
if (prev && (prev.value !== state.value || prev.valueUpper !== state.valueUpper) && typeof onChange === "function") onChange({
value: state.value,
valueUpper: state.valueUpper
});
prevValsRef.current = {
value: state.value,
valueUpper: state.valueUpper
};
}, [
state.value,
state.valueUpper,
onChange
]);
useEffect(() => {
if (state.needsOnRelease && typeof onRelease === "function") {
onRelease({
value: state.value,
valueUpper: state.valueUpper
});
setState({ needsOnRelease: false });
}
}, [
onRelease,
state.needsOnRelease,
state.value,
state.valueUpper
]);
const prevSyncKeysRef = useRef(null);
useEffect(() => {
const prev = prevSyncKeysRef.current;
const next = [
controlledValue,
controlledValueUpper,
controlledMax,
controlledMin
];
if (!prev || prev[0] !== next[0] || prev[1] !== next[1] || prev[2] !== next[2] || prev[3] !== next[3]) {
setState({
value: controlledValue,
left: calcRawLeftPercent({
max: controlledMax,
min: controlledMin,
value: controlledValue
}) * 100
});
if (typeof controlledValueUpper !== "undefined") setState({
valueUpper: controlledValueUpper,
leftUpper: calcRawLeftPercent({
max: controlledMax,
min: controlledMin,
value: controlledValueUpper
}) * 100
});
else setState({
valueUpper: void 0,
leftUpper: void 0
});
prevSyncKeysRef.current = next;
}
}, [
controlledMax,
controlledMin,
controlledValue,
controlledValueUpper
]);
/**
* Rounds a given value to the nearest step defined by the slider's `step`
* prop.
*
* @param value - The value to adjust to the nearest step. Defaults to `0`.
* @returns The value rounded to the precision determined by the step.
*/
const nearestStepValue = (value = 0) => {
const decimals = (props.step?.toString().split(".")[1] ?? "").length;
return Number(value.toFixed(decimals));
};
const handleDrag = (event) => {
if (event instanceof globalThis.MouseEvent || event instanceof globalThis.TouchEvent) onDrag(event);
};
/**
* Sets up "drag" event handlers and calls `onDrag` in case dragging
* started on somewhere other than the thumb without a corresponding "move"
* event.
*/
const onDragStart = (evt) => {
if (props.disabled || props.readOnly) return;
evt.preventDefault();
DRAG_STOP_EVENT_TYPES.forEach((element) => {
elementRef.current?.ownerDocument.addEventListener(element, onDragStop);
});
DRAG_EVENT_TYPES.forEach((element) => {
elementRef.current?.ownerDocument.addEventListener(element, handleDrag);
});
const clientX = getClientXFromEvent(evt.nativeEvent);
let activeHandle;
if (twoHandles) {
if (evt.target == thumbRef.current) activeHandle = "lower";
else if (evt.target == thumbRefUpper.current) activeHandle = "upper";
else if (clientX) if (calcDistanceToHandle("lower", clientX) <= calcDistanceToHandle("upper", clientX)) activeHandle = "lower";
else activeHandle = "upper";
}
const focusOptions = { preventScroll: true };
if (twoHandles) {
if (thumbRef.current && activeHandle === "lower") thumbRef.current.focus(focusOptions);
else if (thumbRefUpper.current && activeHandle === "upper") thumbRefUpper.current.focus(focusOptions);
} else if (thumbRef.current) thumbRef.current.focus(focusOptions);
setState({ activeHandle });
onDrag(evt.nativeEvent, activeHandle);
};
/**
* Removes "drag" and "drag stop" event handlers and calls sets the flag
* indicating that the `onRelease` callback should be called.
*/
const onDragStop = () => {
if (props.disabled || props.readOnly) return;
DRAG_STOP_EVENT_TYPES.forEach((element) => {
elementRef.current?.ownerDocument.removeEventListener(element, onDragStop);
});
DRAG_EVENT_TYPES.forEach((element) => {
elementRef.current?.ownerDocument.removeEventListener(element, handleDrag);
});
setState({
needsOnRelease: true,
isValid: true,
isValidUpper: true
});
};
/**
* Handles a "drag" event by recalculating the value/thumb and setting state
* accordingly.
*
* @param evt The event.
* @param activeHandle The first drag event call, we may have an explicit
* activeHandle value, which is to be used before state is used.
*/
const _onDragRef = useRef(null);
_onDragRef.current = (evt, activeHandle) => {
activeHandle = activeHandle ?? stateRef.current.activeHandle;
if (propsRef.current.disabled || propsRef.current.readOnly) return;
const { value, left } = calcValue({
clientX: getClientXFromEvent(evt),
value: stateRef.current.value
});
if (twoHandles && activeHandle) setValueLeftForHandle(activeHandle, {
value: nearestStepValue(value),
left
});
else setState({
value: nearestStepValue(value),
left,
isValid: true
});
setState({
correctedValue: null,
correctedPosition: null
});
};
/**
* Throttles calls to `_onDrag` by limiting events to being processed at
* most once every `EVENT_THROTTLE` milliseconds.
*/
const onDrag = useMemo(() => throttle((evt, activeHandle) => {
_onDragRef.current?.(evt, activeHandle);
}, EVENT_THROTTLE, {
leading: true,
trailing: false
}), []);
/**
* Handles a `keydown` event by recalculating the value/thumb and setting
* state accordingly.
*/
const onKeyDown = (evt) => {
if (props.disabled || props.readOnly) return;
const { step = 1, stepMultiplier = 4 } = props;
let delta = 0;
if (matches(evt, [ArrowDown, ArrowLeft])) delta = -step;
else if (matches(evt, [ArrowUp$1, ArrowRight$1])) delta = step;
else return;
if (evt.shiftKey) delta *= stepMultiplier;
if (twoHandles && state.activeHandle) {
const { value, left } = calcValue({ value: calcValueForDelta((state.activeHandle === "lower" ? state.value : state.valueUpper) ?? props.min, delta, props.step) });
setValueLeftForHandle(state.activeHandle, {
value: nearestStepValue(value),
left
});
} else {
const { value, left } = calcValue({ value: calcValueForDelta(state.value, delta, props.step) });
setState({
value: nearestStepValue(value),
left,
isValid: true
});
}
setState({
correctedValue: null,
correctedPosition: null
});
};
/**
* Provides the two-way binding for the input field of the Slider. It also
* Handles a change to the input field by recalculating the value/thumb and
* setting state accordingly.
*/
const onChangeInput = (evt) => {
if (props.disabled || props.readOnly) return;
const activeHandle = evt.target.dataset.handlePosition ?? "lower";
const targetValue = Number.parseFloat(evt.target.value);
if (twoHandles) if (isNaN(targetValue)) setValueForHandle(activeHandle, evt.target.value);
else if (isValidValueForPosition({
handle: activeHandle,
value: targetValue,
min: props.min,
max: props.max
})) processNewInputValue(evt.target);
else setValueForHandle(activeHandle, targetValue);
else if (isNaN(targetValue)) setState({ value: evt.target.value });
else if (isValidValue({
value: targetValue,
min: props.min,
max: props.max
})) processNewInputValue(evt.target);
else setState({ value: targetValue });
};
/**
* Checks for validity of input value after clicking out of the input. It also
* Handles state change to isValid state.
*/
const onBlurInput = (evt) => {
const { value: targetValue } = evt.target;
processNewInputValue(evt.target);
props.onBlur?.({
value: targetValue,
handlePosition: evt.target.dataset.handlePosition
});
};
const onInputKeyDown = (evt) => {
if (props.disabled || props.readOnly || !(evt.target instanceof HTMLInputElement)) return;
if (matches(evt, [Enter])) processNewInputValue(evt.target);
};
const processNewInputValue = (input) => {
setState({
correctedValue: null,
correctedPosition: null
});
const targetValue = Number.parseFloat(input.value);
const validity = !isNaN(targetValue);
const handlePosition = input.dataset.handlePosition;
if (handlePosition === "lower") setState({ isValid: validity });
else if (handlePosition === "upper") setState({ isValidUpper: validity });
setState({ isValid: validity });
if (validity) {
const adjustedValue = handlePosition ? getAdjustedValueForPosition({
handle: handlePosition,
value: targetValue,
min: props.min,
max: props.max
}) : getAdjustedValue({
value: targetValue,
min: props.min,
max: props.max
});
if (adjustedValue !== targetValue) setState({
correctedValue: targetValue.toString(),
correctedPosition: handlePosition ?? null
});
else setState({
correctedValue: null,
correctedPosition: null
});
const { value, left } = calcValue({
value: adjustedValue,
useRawValue: true
});
if (handlePosition) setValueLeftForHandle(handlePosition, {
value: nearestStepValue(value),
left
});
else setState({
value,
left
});
}
};
const calcLeftPercent = ({ clientX, value }) => {
const boundingRect = elementRef.current?.getBoundingClientRect?.();
let width = boundingRect ? boundingRect.right - boundingRect.left : 0;
const nextValue = value ?? props.min;
if (width <= 0) width = 1;
if (clientX) return (state.isRtl ? (boundingRect?.right ?? 0) - clientX : clientX - (boundingRect?.left ?? 0)) / width;
return calcRawLeftPercent({
max: props.max,
min: props.min,
value: nextValue
});
};
/**
* Calculates the discrete value (snapped to the nearest step) along
* with the corresponding handle position percentage.
*/
const calcDiscreteValueAndPercent = ({ leftPercent }) => {
const { step = 1, min, max } = props;
const numSteps = Math.floor((max - min) / step) + 1;
/** Index of the step that corresponds to `leftPercent`. */
const stepIndex = Math.round(leftPercent * (numSteps - 1));
return {
discreteValue: stepIndex === numSteps - 1 ? max : min + step * stepIndex,
discretePercent: stepIndex / (numSteps - 1)
};
};
/**
* Calculates the slider's value and handle position based on either a
* mouse/touch event or an explicit value.
*/
const calcValue = ({ clientX, value, useRawValue }) => {
/** `leftPercentRaw` clamped between 0 and 1. */
const leftPercent = clamp(calcLeftPercent({
clientX,
value
}), 0, 1);
if (useRawValue) return {
value,
left: leftPercent * 100
};
const { discreteValue, discretePercent } = calcDiscreteValueAndPercent({ leftPercent });
return {
value: discreteValue,
left: discretePercent * 100
};
};
const calcDistanceToHandle = (handle, clientX) => {
const handleBoundingRect = getHandleBoundingRect(handle);
/** x-coordinate of the midpoint. */
const handleX = handleBoundingRect.left + handleBoundingRect.width / 2;
return Math.abs(handleX - clientX);
};
/**
* Calculates a new slider value based on the current value, a change delta,
* and a step.
*
* @param currentValue - The starting value from which the slider is moving.
* @param delta - The amount to adjust the current value by.
* @param step - The step. Defaults to `1`.
* @returns The new slider value, rounded to the same number of decimal places
* as the step.
*/
const calcValueForDelta = (currentValue, delta, step = 1) => {
const newValue = (delta > 0 ? Math.round(currentValue / step) * step : currentValue) + delta;
const decimals = (step.toString().split(".")[1] || "").length;
return Number(newValue.toFixed(decimals));
};
/**
* Sets state relevant to the given handle position.
*
* Guards against setting either lower or upper values beyond its counterpart.
*/
const setValueLeftForHandle = (handle, { value: newValue, left: newLeft }) => {
const { value, valueUpper, left, leftUpper } = state;
if (handle === "lower") setState({
value: valueUpper && newValue > valueUpper ? valueUpper : newValue,
left: valueUpper && newValue > valueUpper ? leftUpper : newLeft,
isValid: true
});
else setState({
valueUpper: value && newValue < value ? value : newValue,
leftUpper: value && newValue < value ? left : newLeft,
isValidUpper: true
});
};
const setValueForHandle = (handle, value) => {
if (handle === "lower") setState({
value,
isValid: true
});
else setState({
valueUpper: value,
isValidUpper: true
});
};
const isValidValueForPosition = ({ handle, value: newValue, min, max }) => {
const { value, valueUpper } = state;
if (!isValidValue({
value: newValue,
min,
max
})) return false;
if (handle === "lower") return !valueUpper || newValue <= valueUpper;
return !value || newValue >= value;
};
const isValidValue = ({ value, min, max }) => {
return !(value < min || value > max);
};
const getAdjustedValueForPosition = ({ handle, value: newValueInput, min, max }) => {
const { value, valueUpper } = state;
let newValue = getAdjustedValue({
value: newValueInput,
min,
max
});
if (handle === "lower" && valueUpper) newValue = newValue > valueUpper ? valueUpper : newValue;
else if (handle === "upper" && value) newValue = newValue < value ? value : newValue;
return newValue;
};
const getAdjustedValue = ({ value, min, max }) => {
if (value < min) value = min;
if (value > max) value = max;
return value;
};
/**
* Get the bounding rect for the requested handles' DOM element.
*
* If the bounding rect is not available, a new, empty DOMRect is returned.
*/
const getHandleBoundingRect = (handle) => {
let boundingRect;
if (handle === "lower") boundingRect = thumbRef.current?.getBoundingClientRect();
else boundingRect = thumbRefUpper.current?.getBoundingClientRect();
return boundingRect ?? new DOMRect();
};
const getClientXFromEvent = (event) => {
let clientX;
if ("clientX" in event) clientX = event.clientX;
else if ("touches" in event && 0 in event.touches && "clientX" in event.touches[0]) clientX = event.touches[0].clientX;
return clientX;
};
useEffect(() => {
const { isValid, isValidUpper } = stateRef.current;
const derivedState = {};
if (props.invalid === true) {
if (isValid === true) derivedState.isValid = false;
if (isValidUpper === true) derivedState.isValidUpper = false;
} else if (props.invalid === false) {
if (isValid === false) derivedState.isValid = true;
if (isValidUpper === false) derivedState.isValidUpper = true;
}
if (Object.keys(derivedState).length) setState(derivedState);
}, [props.invalid]);
const { ariaLabelInput, unstable_ariaLabelInputUpper: ariaLabelInputUpper, className, hideTextInput = false, id: idProp, min, minLabel, max, maxLabel, formatLabel = defaultFormatLabel, labelText, hideLabel, step = 1, inputType = "number", invalidText, required, disabled = false, name, unstable_nameUpper: nameUpper, light, readOnly = false, warn = false, warnText, translateWithId: t = defaultTranslateWithId, ...other } = props;
const id = idProp ?? generatedId;
const { value, valueUpper, isValid, isValidUpper, correctedValue, correctedPosition, isRtl } = state;
const normalizedProps = useNormalizedInputProps({
id,
disabled,
readOnly,
invalid: !isValid,
warn
});
const normalizedUpperProps = useNormalizedInputProps({
id,
disabled,
readOnly,
invalid: !isValidUpper,
warn
});
delete other.invalid;
delete other.onRelease;
delete other.stepMultiplier;
delete other.unstable_valueUpper;
const showWarning = normalizedProps.warn || correctedPosition === "lower" && isValid;
const showWarningUpper = normalizedUpperProps.warn || correctedPosition === (twoHandles ? "upper" : "lower") && (twoHandles ? isValidUpper : isValid);
const labelId = `${id}-label`;
const labelClasses = classNames(`${prefix}--label`, {
[`${prefix}--visually-hidden`]: hideLabel,
[`${prefix}--label--disabled`]: disabled
});
const containerClasses = classNames(`${prefix}--slider-container`, {
[`${prefix}--slider-container--two-handles`]: twoHandles,
[`${prefix}--slider-container--disabled`]: disabled,
[`${prefix}--slider-container--readonly`]: readOnly,
[`${prefix}--slider-container--rtl`]: isRtl
});
const sliderClasses = classNames(`${prefix}--slider`, {
[`${prefix}--slider--disabled`]: disabled,
[`${prefix}--slider--readonly`]: readOnly
});
const fixedInputClasses = [`${prefix}--text-input`, `${prefix}--slider-text-input`];
const conditionalInputClasses = { [`${prefix}--text-input--light`]: light };
const lowerInputClasses = classNames([
...fixedInputClasses,
`${prefix}--slider-text-input--lower`,
conditionalInputClasses,
{
[`${prefix}--text-input--invalid`]: normalizedProps.invalid,
[`${prefix}--slider-text-input--warn`]: showWarning
}
]);
const upperInputClasses = classNames([
...fixedInputClasses,
`${prefix}--slider-text-input--upper`,
conditionalInputClasses,
{
[`${prefix}--text-input--invalid`]: twoHandles ? normalizedUpperProps.invalid : normalizedProps.invalid,
[`${prefix}--slider-text-input--warn`]: showWarningUpper
}
]);
const lowerInputWrapperClasses = classNames([
`${prefix}--text-input-wrapper`,
`${prefix}--slider-text-input-wrapper`,
`${prefix}--slider-text-input-wrapper--lower`,
{
[`${prefix}--text-input-wrapper--readonly`]: readOnly,
[`${prefix}--slider-text-input-wrapper--hidden`]: hideTextInput
}
]);
const upperInputWrapperClasses = classNames([
`${prefix}--text-input-wrapper`,
`${prefix}--slider-text-input-wrapper`,
`${prefix}--slider-text-input-wrapper--upper`,
{
[`${prefix}--text-input-wrapper--readonly`]: readOnly,
[`${prefix}--slider-text-input-wrapper--hidden`]: hideTextInput
}
]);
const lowerThumbClasses = classNames(`${prefix}--slider__thumb`, { [`${prefix}--slider__thumb--lower`]: twoHandles });
const upperThumbClasses = classNames(`${prefix}--slider__thumb`, { [`${prefix}--slider__thumb--upper`]: twoHandles });
const lowerThumbWrapperClasses = classNames([
`${prefix}--icon-tooltip`,
`${prefix}--slider__thumb-wrapper`,
{ [`${prefix}--slider__thumb-wrapper--lower`]: twoHandles }
]);
const upperThumbWrapperClasses = classNames([
`${prefix}--icon-tooltip`,
`${prefix}--slider__thumb-wrapper`,
{ [`${prefix}--slider__thumb-wrapper--upper`]: twoHandles }
]);
const lowerThumbWrapperProps = { style: { insetInlineStart: `${state.left}%` } };
const upperThumbWrapperProps = { style: { insetInlineStart: `${state.leftUpper}%` } };
return /* @__PURE__ */ jsxs("div", {
className: classNames(`${prefix}--form-item`, className),
children: [
/* @__PURE__ */ jsx(Text, {
as: "label",
htmlFor: twoHandles ? void 0 : id,
className: labelClasses,
id: labelId,
children: labelText
}),
/* @__PURE__ */ jsxs("div", {
className: containerClasses,
children: [
twoHandles ? /* @__PURE__ */ jsxs("div", {
className: lowerInputWrapperClasses,
children: [
/* @__PURE__ */ jsx("input", {
type: hideTextInput ? "hidden" : inputType,
id: `${id}-lower-input-for-slider`,
name,
className: lowerInputClasses,
value,
"aria-label": ariaLabelInput,
disabled,
required,
min,
max,
step,
onChange: onChangeInput,
onBlur: onBlurInput,
onKeyUp: props.onInputKeyUp,
onKeyDown: onInputKeyDown,
"data-invalid": normalizedProps.invalid ? true : null,
"data-handle-position": "lower",
"aria-invalid": normalizedProps.invalid ? true : void 0,
readOnly
}),
normalizedProps.invalid && /* @__PURE__ */ jsx(WarningFilled, { className: `${prefix}--slider__invalid-icon` }),
showWarning && /* @__PURE__ */ jsx(WarningAltFilled, { className: `${prefix}--slider__invalid-icon ${prefix}--slider__invalid-icon--warning` })
]
}) : null,
/* @__PURE__ */ jsx(Text, {
className: `${prefix}--slider__range-label`,
children: formatLabel(min, minLabel)
}),
/* @__PURE__ */ jsxs("div", {
className: sliderClasses,
ref: (node) => {
elementRef.current = node;
},
onMouseDown: onDragStart,
onTouchStart: onDragStart,
onKeyDown,
role: "presentation",
tabIndex: -1,
"data-invalid": (twoHandles ? normalizedProps.invalid || normalizedUpperProps.invalid : normalizedProps.invalid) ? true : null,
...other,
children: [
/* @__PURE__ */ jsx(ThumbWrapper, {
hasTooltip: hideTextInput,
className: lowerThumbWrapperClasses,
label: formatLabel(value, void 0),
align: "top",
...lowerThumbWrapperProps,
children: /* @__PURE__ */ jsx("div", {
className: lowerThumbClasses,
role: "slider",
id: twoHandles ? void 0 : id,
tabIndex: readOnly || disabled ? void 0 : 0,
"aria-valuetext": formatLabel(value, void 0),
"aria-valuemax": twoHandles ? valueUpper : max,
"aria-valuemin": min,
"aria-valuenow": value,
"aria-labelledby": twoHandles ? void 0 : labelId,
"aria-label": twoHandles ? ariaLabelInput : void 0,
ref: thumbRef,
onFocus: () => setState({ activeHandle: "lower" }),
children: twoHandles && !isRtl ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LowerHandle, { "aria-label": ariaLabelInput }), /* @__PURE__ */ jsx(LowerHandleFocus, { "aria-label": ariaLabelInput })] }) : twoHandles && isRtl ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(UpperHandle, { "aria-label": ariaLabelInputUpper }), /* @__PURE__ */ jsx(UpperHandleFocus, { "aria-label": ariaLabelInputUpper })] }) : void 0
})
}),
hasUpperValue(valueUpper) ? /* @__PURE__ */ jsx(ThumbWrapper, {
hasTooltip: hideTextInput,
className: upperThumbWrapperClasses,
label: formatLabel(valueUpper, void 0),
align: "top",
...upperThumbWrapperProps,
children: /* @__PURE__ */ jsx("div", {
className: upperThumbClasses,
role: "slider",
tabIndex: readOnly || disabled ? void 0 : 0,
"aria-valuemax": max,
"aria-valuemin": value,
"aria-valuenow": valueUpper,
"aria-label": ariaLabelInputUpper,
ref: thumbRefUpper,
onFocus: () => setState({ activeHandle: "upper" }),
children: !isRtl ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(UpperHandle, { "aria-label": ariaLabelInputUpper }), /* @__PURE__ */ jsx(UpperHandleFocus, { "aria-label": ariaLabelInputUpper })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LowerHandle, { "aria-label": ariaLabelInput }), /* @__PURE__ */ jsx(LowerHandleFocus, { "aria-label": ariaLabelInput })] })
})
}) : null,
/* @__PURE__ */ jsx("div", {
className: `${prefix}--slider__track`,
ref: (node) => {
trackRef.current = node;
}
}),
/* @__PURE__ */ jsx("div", {
className: `${prefix}--slider__filled-track`,
ref: filledTrackRef
})
]
}),
/* @__PURE__ */ jsx(Text, {
className: `${prefix}--slider__range-label`,
children: formatLabel(max, maxLabel)
}),
/* @__PURE__ */ jsxs("div", {
className: upperInputWrapperClasses,
children: [
/* @__PURE__ */ jsx("input", {
type: hideTextInput ? "hidden" : inputType,
id: `${id}-${twoHandles ? "upper-" : ""}input-for-slider`,
name: twoHandles ? nameUpper : name,
className: upperInputClasses,
value: twoHandles ? valueUpper : value,
"aria-labelledby": !ariaLabelInput && !twoHandles ? labelId : void 0,
"aria-label": twoHandles ? ariaLabelInputUpper : ariaLabelInput ? ariaLabelInput : void 0,
disabled,
required,
min,
max,
step,
onChange: onChangeInput,
onBlur: onBlurInput,
onKeyDown: onInputKeyDown,
onKeyUp: props.onInputKeyUp,
"data-invalid": (twoHandles ? normalizedUpperProps.invalid : normalizedProps.invalid) ? true : null,
"data-handle-position": twoHandles ? "upper" : null,
"aria-invalid": (twoHandles ? normalizedUpperProps.invalid : normalizedProps.invalid) ? true : void 0,
readOnly
}),
(twoHandles ? normalizedUpperProps.invalid : normalizedProps.invalid) && /* @__PURE__ */ jsx(WarningFilled, { className: `${prefix}--slider__invalid-icon` }),
showWarningUpper && /* @__PURE__ */ jsx(WarningAltFilled, { className: `${prefix}--slider__invalid-icon ${prefix}--slider__invalid-icon--warning` })
]
})
]
}),
(normalizedProps.invalid || normalizedUpperProps.invalid) && /* @__PURE__ */ jsx(Text, {
as: "div",
className: classNames(`${prefix}--slider__validation-msg`, `${prefix}--slider__validation-msg--invalid`, `${prefix}--form-requirement`),
children: invalidText
}),
(normalizedProps.warn || normalizedUpperProps.warn) && /* @__PURE__ */ jsx(Text, {
as: "div",
className: classNames(`${prefix}--slider__validation-msg`, `${prefix}--form-requirement`),
children: warnText
}),
correctedValue && /* @__PURE__ */ jsx(Text, {
as: "div",
role: "alert",
className: classNames(`${prefix}--slider__status-msg`, `${prefix}--form-requirement`),
children: t(translationIds["carbon.slider.auto-correct-announcement"], { correctedValue })
})
]
});
};
Slider.propTypes = {
ariaLabelInput: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string,
disabled: PropTypes.bool,
formatLabel: PropTypes.func,
hideTextInput: PropTypes.bool,
id: PropTypes.string,
inputType: PropTypes.string,
invalid: PropTypes.bool,
invalidText: PropTypes.node,
labelText: PropTypes.node,
hideLabel: PropTypes.bool,
light: deprecate(PropTypes.bool, "The `light` prop for `Slider` is no longer needed and has been deprecated in v11 in favor of the new `Layer` component. It will be moved in the next major release."),
max: PropTypes.number.isRequired,
maxLabel: PropTypes.string,
min: PropTypes.number.isRequired,
minLabel: PropTypes.string,
name: PropTypes.string,
onBlur: PropTypes.func,
onChange: PropTypes.func,
onInputKeyUp: PropTypes.func,
onRelease: PropTypes.func,
readOnly: PropTypes.bool,
required: PropTypes.bool,
step: PropTypes.number,
stepMultiplier: PropTypes.number,
translateWithId: PropTypes.func,
unstable_ariaLabelInputUpper: PropTypes.string,
unstable_nameUpper: PropTypes.string,
unstable_valueUpper: PropTypes.number,
value: PropTypes.number.isRequired,
warn: PropTypes.bool,
warnText: PropTypes.node
};
//#endregion
export { Slider as default };