@hitachivantara/uikit-react-core
Version:
Core React components for the NEXT Design System.
436 lines (435 loc) • 14.2 kB
JavaScript
import { jsxs, jsx } from "react/jsx-runtime";
import { forwardRef, useRef, useMemo, useState, useCallback, useEffect } from "react";
import Slider from "rc-slider";
import Tooltip from "rc-tooltip";
import { useDefaultProps } from "@hitachivantara/uikit-react-utils";
import { HvLabelContainer } from "../FormElement/LabelContainer.js";
import { useControlled } from "../hooks/useControlled.js";
import { useUniqueId } from "../hooks/useUniqueId.js";
import { getElementById } from "../utils/document.js";
import { setId } from "../utils/setId.js";
import { useClasses, sliderStyles } from "./Slider.styles.js";
import { staticClasses } from "./Slider.styles.js";
import { HvSliderInput } from "./SliderInput/SliderInput.js";
import { calculateStepValue, createMark, isSingleSlider, knobsValuesToKnobsPositions, convertStatusToArray, generateDefaultKnobProperties, createTrackStyles, createKnobStyles, knobsPositionToScaledValue, knobsPositionsToKnobsValues, statusArrayToFormStatus, ensureValuesConsistency, scaledValueToKnobsPositionValue } from "./utils.js";
import { HvFormElement } from "../FormElement/FormElement.js";
import { HvWarningText } from "../FormElement/WarningText/WarningText.js";
const HvSlider = forwardRef(function HvSlider2(props, ref) {
const {
id,
className,
name,
label,
status,
statusMessage,
disabled,
classes: classesProp,
sliderProps,
knobProps,
inputProps,
requiredMessage = "The value is required",
outOfRangeMessage = "The value is out of range",
noOverlap = true,
hideInput,
required,
readOnly,
markProperties = [],
defaultValues = [void 0],
values: valuesProp = [],
knobProperties: knobPropertiesProp,
"aria-errormessage": ariaErrorMessage,
maxPointValue = 100,
minPointValue = 0,
divisionQuantity = 100,
markStep = 20,
markDigits = 0,
formatMark,
onChange,
onBlur,
onBeforeChange,
onAfterChange,
formatTooltip,
...others
} = useDefaultProps("HvSlider", props);
const { classes, cx } = useClasses(classesProp);
const hasLabel = label != null;
const isDirty = useRef(false);
const elementId = useUniqueId(id);
const sliderInputId = setId(elementId, "input");
const stepValue = useMemo(
() => calculateStepValue(maxPointValue, minPointValue, divisionQuantity),
[divisionQuantity, maxPointValue, minPointValue]
);
const inverseStepValue = 1 / stepValue;
const marks = useMemo(
() => createMark(
markProperties,
markStep,
divisionQuantity,
minPointValue,
maxPointValue,
stepValue,
markDigits,
!!disabled,
formatMark
),
[
disabled,
divisionQuantity,
formatMark,
markDigits,
markProperties,
markStep,
minPointValue,
maxPointValue,
stepValue
]
);
const canShowError = ariaErrorMessage == null && (status !== void 0 && statusMessage !== void 0 || // We always show an error when the value(s) are not between maxPointValue and minPointValue; and when required is true (set by user).
status === void 0);
const isSingle = useMemo(
() => isSingleSlider(valuesProp, defaultValues),
[defaultValues, valuesProp]
);
const value = useMemo(() => {
return valuesProp?.length > 0 ? knobsValuesToKnobsPositions(valuesProp, inverseStepValue, minPointValue) : void 0;
}, [inverseStepValue, minPointValue, valuesProp]);
const defaultKnobsPositions = useMemo(
() => knobsValuesToKnobsPositions(
defaultValues,
inverseStepValue,
minPointValue
),
[defaultValues, inverseStepValue, minPointValue]
);
const [knobsPositions, setKnobsPositions] = useControlled(
value,
defaultKnobsPositions
);
const { arrayStatus, arrayDefaultStatus } = useMemo(
() => convertStatusToArray(knobsPositions.length, status),
[knobsPositions.length, status]
);
const [validationStatus, setValidationState] = useControlled(
arrayStatus,
arrayDefaultStatus
);
const [validationMessage, setValidationMessage] = useControlled(
statusMessage,
""
);
const [isDraggingTrack, setIsDraggingTrack] = useState(false);
const knobProperties = generateDefaultKnobProperties(
knobsPositions.length,
disabled,
knobPropertiesProp
);
const rangesCount = knobProperties.length - 1;
const trackStyles = createTrackStyles(knobProperties);
const knobStyles = createKnobStyles(knobProperties);
const generateKnobsPositionAndValues = useCallback(
(knobsCurrentPosition) => {
const newKnobsPosition = knobsCurrentPosition.slice();
const knobsValues2 = [];
let duplicatedValue = null;
const findDuplicated = newKnobsPosition.filter(
(item, index) => newKnobsPosition.indexOf(item) !== index
);
if (noOverlap && findDuplicated.length > 0) {
[duplicatedValue] = findDuplicated;
}
newKnobsPosition.forEach((position, index, array) => {
const newArray = array;
let newPosition = position;
if (noOverlap && newPosition === duplicatedValue) {
const previousValue = knobsPositions[index];
if (previousValue !== newPosition) {
newPosition += newPosition > previousValue ? -1 : 1;
newArray[index] = newPosition;
}
}
knobsValues2[index] = knobsPositionToScaledValue(
newPosition,
minPointValue,
stepValue
);
});
return {
knobsPosition: newKnobsPosition,
knobsValues: knobsValues2
};
},
[knobsPositions, minPointValue, noOverlap, stepValue]
);
const performValidation = useCallback(() => {
let invalid = false;
let requiredMsg = false;
const mappedValues = generateKnobsPositionAndValues(knobsPositions).knobsValues;
const newValidationState = mappedValues.map((knobValue) => {
if (required && (knobValue == null || Number.isNaN(knobValue))) {
invalid = true;
requiredMsg = true;
return "invalid";
}
if (knobValue < minPointValue || knobValue > maxPointValue) {
invalid = true;
return "invalid";
}
return "valid";
});
setValidationState([...newValidationState]);
if (invalid) {
setValidationMessage(requiredMsg ? requiredMessage : outOfRangeMessage);
return;
}
setValidationMessage("");
}, [
generateKnobsPositionAndValues,
knobsPositions,
maxPointValue,
minPointValue,
outOfRangeMessage,
required,
requiredMessage,
setValidationMessage,
setValidationState
]);
useEffect(() => {
const stepVl = calculateStepValue(
maxPointValue,
minPointValue,
divisionQuantity
);
const inverseStepVl = 1 / stepVl;
if (valuesProp?.length > 0) {
setKnobsPositions(
knobsValuesToKnobsPositions(
valuesProp.length > 0 ? valuesProp : defaultValues,
inverseStepVl,
minPointValue
)
);
}
}, [
defaultValues,
divisionQuantity,
maxPointValue,
minPointValue,
setKnobsPositions,
valuesProp
]);
useEffect(() => {
if (!isDirty.current) {
return;
}
performValidation();
}, [knobsPositions, requiredMessage, performValidation]);
const onMouseDownHandler = (event) => {
if (event.target.className.includes("track")) {
setIsDraggingTrack(true);
}
};
const onMouseUpHandler = () => {
setIsDraggingTrack(false);
};
const onBlurHandler = (event) => {
const knobs = generateKnobsPositionAndValues(knobsPositions);
performValidation();
onBlur?.(event, knobs.knobsValues, status);
};
const onChangeHandler = (knobsPosition) => {
isDirty.current = true;
const knobs = generateKnobsPositionAndValues(knobsPosition);
knobProperties.forEach((knobProperty, index) => {
if (knobProperty.fixed) {
knobs.knobsPosition[index] = scaledValueToKnobsPositionValue(
defaultValues[index],
minPointValue,
inverseStepValue
);
}
});
if (disabled || readOnly) return;
onChange?.(knobs.knobsValues);
setKnobsPositions(knobs.knobsPosition);
};
const onInputChangeHandler = (inputValues, index) => {
let newKnobPositions = knobsValuesToKnobsPositions(
inputValues,
inverseStepValue,
minPointValue
);
newKnobPositions = ensureValuesConsistency(newKnobPositions, index);
onChangeHandler(newKnobPositions);
};
const onBeforeChangeHandler = (knobsPosition) => {
const knobs = generateKnobsPositionAndValues(knobsPosition);
onBeforeChange?.(knobs.knobsValues);
};
const onAfterChangeHandler = (knobsPosition) => {
const knobs = generateKnobsPositionAndValues(knobsPosition);
onAfterChange?.(knobs.knobsValues);
};
const createKnob = (knobNode, params) => {
const { value: knobValue, dragging, index } = params;
const { style = {}, ...restProps } = knobNode.props;
const scaledKnobValue = knobsPositionToScaledValue(
knobValue,
minPointValue,
stepValue
).toFixed(markDigits);
if (dragging) {
style.backgroundColor = knobProperties[index]?.dragColor;
} else {
style.backgroundColor = knobProperties[index]?.color;
}
const isEmpty = Number.isNaN(knobsPositions[index]) || knobsPositions[index] == null;
const handleId = setId(elementId, "knob");
const indexedHandleId = setId(handleId, index);
return /* @__PURE__ */ jsx(
"div",
{
className: cx({
[classes.handleContainer]: !!(!disabled && !isEmpty),
[classes.handleContainerDisabled]: !!(disabled && !isEmpty),
[classes.handleHiddenContainer]: isEmpty || readOnly
}),
children: /* @__PURE__ */ jsx(
Tooltip,
{
prefixCls: "rc-slider-tooltip",
overlay: formatTooltip?.(scaledKnobValue) || scaledKnobValue,
visible: dragging,
placement: "top",
overlayClassName: classes.sliderTooltip,
getTooltipContainer: () => getElementById(indexedHandleId),
children: /* @__PURE__ */ jsx(
"div",
{
id: indexedHandleId,
style,
className: classes.handle,
...restProps,
"aria-label": `${label}-knob-${index}`,
"aria-valuenow": knobsPositionToScaledValue(
knobValue,
minPointValue,
stepValue
),
"aria-valuemin": minPointValue,
"aria-valuemax": maxPointValue,
...knobProps?.[index]
}
)
}
)
},
index
);
};
const knobsValuesArray = knobsPositionsToKnobsValues(
knobsPositions,
stepValue,
minPointValue
);
const knobsValues = knobsValuesArray.map(
(v) => Number(v.toFixed(markDigits))
);
return /* @__PURE__ */ jsxs(
HvFormElement,
{
className: cx(
classes.root,
{
[classes.trackStandBy]: !readOnly && !disabled && !isSingle && !isDraggingTrack,
[classes.trackDragging]: !readOnly && !disabled && !isSingle && isDraggingTrack,
[classes.rootDisabled]: !!disabled
},
className
),
id,
name,
status: statusArrayToFormStatus(validationStatus),
disabled,
required,
readOnly,
onMouseDown: onMouseDownHandler,
onMouseUp: onMouseUpHandler,
onBlur: onBlurHandler,
...others,
children: [
/* @__PURE__ */ jsx(
HvLabelContainer,
{
label,
inputId: sliderInputId,
labelId: setId(elementId, "label"),
descriptionId: setId(elementId, "description"),
classes: {
root: cx(classes.labelContainer, {
[classes.labelIncluded]: hasLabel,
[classes.onlyInput]: !hasLabel
}),
label: classes.label
},
children: !hideInput && /* @__PURE__ */ jsx(
HvSliderInput,
{
id: sliderInputId,
label,
values: knobsValues,
onChange: onInputChangeHandler,
status: validationStatus,
disabled,
readOnly,
markDigits,
inputProps
}
)
}
),
/* @__PURE__ */ jsx("div", { className: cx(classes.sliderBase, classes.sliderContainer), children: /* @__PURE__ */ jsx(
Slider,
{
ref,
range: !isSingle,
handleRender: createKnob,
className: cx(classes.sliderRoot, {
[classes.rootRange]: !isSingle
}),
min: 0,
max: divisionQuantity,
step: 1,
marks,
dotStyle: disabled ? sliderStyles.dotDisabled : sliderStyles.dot,
onChange: (singleValue) => onChangeHandler(Array().concat(singleValue)),
onBeforeChange: (singleValue) => onBeforeChangeHandler(Array().concat(singleValue)),
onAfterChange: (singleValue) => onAfterChangeHandler(Array().concat(singleValue)),
value: knobsPositions.length === 0 ? void 0 : isSingle ? knobsPositions[0] : [...knobsPositions],
allowCross: false,
disabled,
count: rangesCount,
railStyle: sliderStyles.rail,
handleStyle: knobStyles.knobInner,
trackStyle: trackStyles,
draggableTrack: !readOnly && !isSingle,
...sliderProps
}
) }),
canShowError && /* @__PURE__ */ jsx(
HvWarningText,
{
id: setId(elementId, "error"),
className: classes.error,
disableBorder: true,
children: validationMessage
}
)
]
}
);
});
export {
HvSlider,
staticClasses as sliderClasses
};