UNPKG

@carbon/react

Version:

React components for the Carbon Design System

890 lines (888 loc) 31.9 kB
/** * 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 };