UNPKG

@hitachivantara/uikit-react-core

Version:
212 lines (211 loc) 8.53 kB
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 };