UNPKG

@hitachivantara/uikit-react-core

Version:

Core React components for the NEXT Design System.

280 lines (279 loc) 9.3 kB
import { jsxs, jsx } from "react/jsx-runtime"; import { forwardRef, useRef, useState, useMemo, useCallback, useEffect } from "react"; import { useForkRef } from "@mui/material/utils"; import { useDefaultProps, useTheme } from "@hitachivantara/uikit-react-utils"; import { DEFAULT_ERROR_MESSAGES, validateInput, computeValidationState, computeValidationMessage, hasBuiltInValidations } from "../BaseInput/validations.js"; import { HvLabelContainer } from "../FormElement/LabelContainer.js"; import { useControlled } from "../hooks/useControlled.js"; import { useUniqueId } from "../hooks/useUniqueId.js"; import { setId } from "../utils/setId.js"; import { useClasses } from "./TextArea.styles.js"; import { staticClasses } from "./TextArea.styles.js"; import { HvFormElement } from "../FormElement/FormElement.js"; import { HvCharCounter } from "../FormElement/CharCounter/CharCounter.js"; import { HvBaseInput } from "../BaseInput/BaseInput.js"; import { isInvalid } from "../FormElement/utils.js"; import { HvWarningText } from "../FormElement/WarningText/WarningText.js"; import { HvInfoMessage } from "../FormElement/InfoMessage/InfoMessage.js"; const HvTextArea = forwardRef(function HvTextArea2(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( status, "standBy" ); const [validationMessage, setValidationMessage] = useControlled( statusMessage, "" ); const [value, setValue] = useControlled(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, "none", validation ); setValidationState(computeValidationState(inputValidity, isEmptyValue)); setValidationMessage( computeValidationMessage(inputValidity, errorMessages) ); return inputValidity; }, [ errorMessages, inputRef, isEmptyValue, maxCharQuantity, minCharQuantity, required, setValidationMessage, setValidationState, validation ]); const limitValue = (currentValue) => { if (currentValue === void 0 || !blockMax) return currentValue; const isOverflow = maxCharQuantity == null ? false : currentValue.length > maxCharQuantity; return !isOverflow ? currentValue : currentValue.substring(0, maxCharQuantity); }; const onContainerBlurHandler = (event) => { setFocused(false); const inputValidity = performValidation(); onBlur?.(event, String(value), inputValidity); }; const onChangeHandler = (event, currentValue) => { isDirty.current = true; const limitedValue = blockMax ? limitValue(currentValue) : currentValue; setValue(limitedValue); onChange?.(event, limitedValue); }; 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(() => { const scrollHandler = { handleEvent: () => { setAutoScrolling(isScrolledDown()); } }; inputRef.current?.addEventListener("scroll", scrollHandler); }, [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, "none", minCharQuantity, // If blockMax is true maxCharQuantity will never produce an error // unless the value is controlled, so we can't prevent it to overflow maxCharQuantity maxCharQuantity != null && (blockMax !== true || value != null) ? maxCharQuantity : null, validation, inputProps )); const willShowError = canShowError && isStateInvalid; const canShowInfo = !!infoMessage && !willShowError; 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.disabled]: disabled, [classes.invalid]: isStateInvalid }, 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 }) ] } ); }); export { HvTextArea, staticClasses as textAreaClasses };