@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
212 lines (211 loc) • 8.53 kB
JavaScript
import { setId } from "../utils/setId.js";
import { HvCharCounter } from "../FormElement/CharCounter/CharCounter.js";
import { useUniqueId } from "../hooks/useUniqueId.js";
import { isInvalid } from "../FormElement/utils.js";
import { HvFormElement } from "../FormElement/FormElement.js";
import { HvInfoMessage } from "../FormElement/InfoMessage/InfoMessage.js";
import { HvWarningText } from "../FormElement/WarningText/WarningText.js";
import { useControlled as useControlled$1 } from "../hooks/useControlled.js";
import { HvLabelContainer } from "../FormElement/LabelContainer.js";
import { HvBaseInput } from "../BaseInput/BaseInput.js";
import { DEFAULT_ERROR_MESSAGES, computeValidationMessage, computeValidationState, hasBuiltInValidations, validateInput } from "../BaseInput/validations.js";
import { useClasses } from "./TextArea.styles.js";
import { useDefaultProps, useTheme } from "@hitachivantara/uikit-react-utils";
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { useForkRef } from "@mui/material/utils";
//#region src/TextArea/TextArea.tsx
/**
* A text area is a multiline text input box, with an optional character counter when there is a length limit.
*/
var HvTextArea = forwardRef(function HvTextArea(props, ref) {
const { id, className, classes: classesProp, name, label, description: descriptionProp, placeholder, status, statusMessage, infoMessage: infoMessageProp, validationMessages, maxCharQuantity, minCharQuantity, value: valueProp, inputRef: inputRefProp, rows = 1, defaultValue = "", middleCountLabel = "/", countCharProps, inputProps, required, readOnly, disabled, autoFocus, resizable, autoScroll, hideCounter, blockMax, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-errormessage": ariaErrorMessage, validation, onChange, onBlur, onFocus, ...others } = useDefaultProps("HvTextArea", props);
const { classes, cx } = useClasses(classesProp);
const elementId = useUniqueId(id);
const { activeTheme } = useTheme();
const isDirty = useRef(false);
const inputRef = useRef(null);
const forkedRef = useForkRef(ref, inputRefProp, inputRef);
const [description, infoMessage] = activeTheme?.name === "pentahoPlus" ? [infoMessageProp, descriptionProp] : [descriptionProp, infoMessageProp];
const [focused, setFocused] = useState(false);
const [autoScrolling, setAutoScrolling] = useState(autoScroll);
const [validationState, setValidationState] = useControlled$1(status, "standBy");
const [validationMessage, setValidationMessage] = useControlled$1(statusMessage, "");
const [value, setValue] = useControlled$1(valueProp, defaultValue);
const isStateInvalid = isInvalid(validationState);
const isEmptyValue = value == null || value === "";
const hasLabel = label != null;
const hasCounter = maxCharQuantity != null && !hideCounter;
const errorMessages = useMemo(() => ({
...DEFAULT_ERROR_MESSAGES,
...validationMessages
}), [validationMessages]);
const performValidation = useCallback(() => {
const inputValidity = validateInput(inputRef.current, required, minCharQuantity, maxCharQuantity, validation);
setValidationState(computeValidationState(inputValidity, isEmptyValue));
setValidationMessage(computeValidationMessage(inputValidity, errorMessages) || "");
return inputValidity;
}, [
errorMessages,
inputRef,
isEmptyValue,
maxCharQuantity,
minCharQuantity,
required,
setValidationMessage,
setValidationState,
validation
]);
/**
* Limit the string to the maxCharQuantity length.
*/
const limitValue = (currentValue) => {
if (currentValue === void 0 || !blockMax) return currentValue;
return !(maxCharQuantity == null ? false : currentValue.length > maxCharQuantity) ? currentValue : currentValue.slice(0, maxCharQuantity);
};
/**
* Validates the text area updating the state and modifying the warning text, also executes
* the user provided onBlur passing the current validation status and value.
*/
const onContainerBlurHandler = (event) => {
setFocused(false);
const inputValidity = performValidation();
onBlur?.(event, String(value), inputValidity);
};
/**
* Updates the length of the string while is being inputted, also executes the user onChange
* allowing the customization of the input if required.
*/
const onChangeHandler = (event, currentValue) => {
isDirty.current = true;
const limitedValue = blockMax ? limitValue(currentValue) : currentValue;
setValue(limitedValue);
onChange?.(event, limitedValue);
};
/**
* Updates the state putting again the value from the state because the input value is
* not automatically manage, it also executes the onFocus function from the user passing the value
*/
const onFocusHandler = (event) => {
setFocused(true);
setValidationState("standBy");
onFocus?.(event, String(value));
};
const isScrolledDown = useCallback(() => {
const el = inputRef.current;
return el == null || el.offsetHeight + el.scrollTop >= el.scrollHeight;
}, [inputRef]);
const scrollDown = useCallback(() => {
const el = inputRef.current;
if (el != null) el.scrollTop = el.scrollHeight - el.clientHeight;
}, [inputRef]);
const addScrollListener = useCallback(() => {
inputRef.current?.addEventListener("scroll", { handleEvent: () => {
setAutoScrolling(isScrolledDown());
} });
}, [inputRef, isScrolledDown]);
useEffect(() => {
if (autoScroll) addScrollListener();
}, [autoScroll, addScrollListener]);
useEffect(() => {
if (autoScrolling) scrollDown();
}, [
valueProp,
autoScrolling,
scrollDown
]);
useEffect(() => {
if (focused || !isDirty.current && isEmptyValue) return;
performValidation();
}, [
focused,
isEmptyValue,
performValidation
]);
const canShowError = ariaErrorMessage == null && (status !== void 0 && statusMessage !== void 0 || status === void 0 && hasBuiltInValidations(required, "text", minCharQuantity, maxCharQuantity != null && (blockMax !== true || value != null) ? maxCharQuantity : null, validation, inputProps));
const canShowInfo = !!infoMessage && !(canShowError && isStateInvalid);
let errorMessageId;
if (isStateInvalid) errorMessageId = canShowError ? setId(elementId, "error") : ariaErrorMessage;
return /* @__PURE__ */ jsxs(HvFormElement, {
id,
name,
status: validationState,
disabled,
required,
readOnly,
className: cx(classes.root, {
[classes.resizable]: resizable,
[classes.invalid]: isStateInvalid,
[classes.disabled]: disabled
}, className),
onBlur: onContainerBlurHandler,
children: [
/* @__PURE__ */ jsx(HvLabelContainer, {
label,
description,
inputId: setId(elementId, "input"),
labelId: setId(elementId, "label"),
descriptionId: setId(elementId, "description"),
classes: {
root: classes.labelContainer,
label: classes.label,
description: classes.description
},
children: hasCounter && /* @__PURE__ */ jsx(HvCharCounter, {
disableGutter: true,
id: setId(elementId, "charCounter"),
className: classes.characterCounter,
separator: middleCountLabel,
currentCharQuantity: String(value).length,
maxCharQuantity,
...countCharProps
})
}),
/* @__PURE__ */ jsx(HvBaseInput, {
classes: {
root: classes.baseInput,
input: classes.input,
inputResizable: classes.inputResizable
},
id: hasLabel ? setId(elementId, "input") : setId(id, "input"),
name,
value,
required,
readOnly,
disabled,
onChange: onChangeHandler,
autoFocus,
onFocus: onFocusHandler,
placeholder,
invalid: isStateInvalid,
resizable,
multiline: true,
rows,
inputProps: {
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledBy,
"aria-invalid": isStateInvalid ? true : void 0,
"aria-errormessage": errorMessageId,
"aria-describedby": ariaDescribedBy != null ? ariaDescribedBy : description && setId(elementId, "description") || void 0,
"aria-controls": maxCharQuantity ? setId(elementId, "charCounter") : void 0,
...inputProps
},
inputRef: forkedRef,
...others
}),
canShowError && /* @__PURE__ */ jsx(HvWarningText, {
id: setId(elementId, "error"),
className: classes.error,
disableBorder: true,
children: validationMessage
}),
canShowInfo && /* @__PURE__ */ jsx(HvInfoMessage, {
disableGutter: true,
variant: "caption1",
children: infoMessage
})
]
});
});
//#endregion
export { HvTextArea };