UNPKG

@hitachivantara/uikit-react-core

Version:

Core React components for the NEXT Design System.

481 lines (480 loc) 16.9 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const jsxRuntime = require("react/jsx-runtime"); const React = require("react"); const utils = require("@mui/material/utils"); const uikitReactUtils = require("@hitachivantara/uikit-react-utils"); const validations = require("../BaseInput/validations.cjs"); const LabelContainer = require("../FormElement/LabelContainer.cjs"); const Suggestions = require("../FormElement/Suggestions/Suggestions.cjs"); const useControlled = require("../hooks/useControlled.cjs"); const useIsMounted = require("../hooks/useIsMounted.cjs"); const useLabels = require("../hooks/useLabels.cjs"); const useUniqueId = require("../hooks/useUniqueId.cjs"); const icons = require("../icons.cjs"); const generic = require("../types/generic.cjs"); const keyboardUtils = require("../utils/keyboardUtils.cjs"); const setId = require("../utils/setId.cjs"); const icons$1 = require("./icons.cjs"); const Input_styles = require("./Input.styles.cjs"); const utils$1 = require("./utils.cjs"); const Adornment = require("../FormElement/Adornment/Adornment.cjs"); const utils$2 = require("../FormElement/utils.cjs"); const BaseInput = require("../BaseInput/BaseInput.cjs"); const Tooltip = require("../Tooltip/Tooltip.cjs"); const FormElement = require("../FormElement/FormElement.cjs"); const WarningText = require("../FormElement/WarningText/WarningText.cjs"); const InfoMessage = require("../FormElement/InfoMessage/InfoMessage.cjs"); const DEFAULT_LABELS = { /** The label of the clear button. */ clearButtonLabel: "Clear the text", /** The label of the reveal password button. @deprecated unused */ revealPasswordButtonLabel: "Reveal password", /** The tooltip of the reveal password button when the password is hidden. */ revealPasswordButtonClickToShowTooltip: "Click to show password.", /** The tooltip of the reveal password button when the password is revealed. */ revealPasswordButtonClickToHideTooltip: "Click to hide password.", /** The label of the search button. */ searchButtonLabel: "Search" }; function eventTargetIsInsideContainer(container, event) { return !!container?.contains(event.relatedTarget); } const HvInput = generic.fixedForwardRef(function HvInput2(props, ref) { const { classes: classesProp, className, id, name, value: valueProp, defaultValue, required, readOnly, disabled, enablePortal, suggestOnFocus, label, description: descriptionProp, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, onChange, onEnter, status, statusMessage, infoMessage: infoMessageProp, "aria-errormessage": ariaErrorMessage, type = "text", placeholder, autoFocus, labels: labelsProp, validationMessages, disableClear, disableRevealPassword, disableSearchButton, endAdornment, maxCharQuantity, minCharQuantity, validation, showValidationIcon, suggestionListCallback, inputRef: inputRefProp, onBlur, onFocus, onKeyDown, inputProps = {}, ...others } = uikitReactUtils.useDefaultProps("HvInput", props); const { classes, cx } = Input_styles.useClasses(classesProp); const labels = useLabels.useLabels(DEFAULT_LABELS, labelsProp); const elementId = useUniqueId.useUniqueId(id); const { activeTheme } = uikitReactUtils.useTheme(); const inputRef = React.useRef(null); const forkedRef = utils.useForkRef(ref, inputRef, inputRefProp); const suggestionsRef = React.useRef(null); const [description, infoMessage] = activeTheme?.name === "pentahoPlus" ? [infoMessageProp, descriptionProp] : [descriptionProp, infoMessageProp]; const [focused, setFocused] = React.useState(false); const isDirty = React.useRef(false); const isEmptyValue = !inputRef.current?.value; const [validationState, setValidationState] = useControlled.useControlled( status, "standBy" ); const [validationMessage, setValidationMessage] = useControlled.useControlled( statusMessage, "" ); const errorMessages = React.useMemo( () => ({ ...validations.DEFAULT_ERROR_MESSAGES, ...validationMessages }), // eslint-disable-next-line react-hooks/exhaustive-deps [ validationMessages?.error, validationMessages?.requiredError, validationMessages?.minCharError, validationMessages?.maxCharError, validationMessages?.typeMismatchError ] ); const validationType = React.useMemo(() => validations.computeValidationType(type), [type]); const performValidation = React.useCallback(() => { const inputValidity = validations.validateInput( inputRef.current, required, minCharQuantity, maxCharQuantity, validationType, validation ); setValidationState(validations.computeValidationState(inputValidity, isEmptyValue)); setValidationMessage( validations.computeValidationMessage(inputValidity, errorMessages) ); return inputValidity; }, [ errorMessages, isEmptyValue, maxCharQuantity, minCharQuantity, required, setValidationMessage, setValidationState, validation, validationType ]); const canShowError = ariaErrorMessage == null && (status !== void 0 && statusMessage !== void 0 || status === void 0 && validations.hasBuiltInValidations( required, validationType, minCharQuantity, maxCharQuantity, validation, inputProps )); const isStateInvalid = utils$2.isInvalid(validationState); const willShowError = canShowError && isStateInvalid; const canShowInfo = !!infoMessage && !willShowError; const [revealPassword, setRevealPassword] = React.useState(false); const realType = React.useMemo(() => { if (type === "password") { return revealPassword ? "text" : "password"; } if (type === "search") { return "search"; } if (type === "number") { return "number"; } return "text"; }, [revealPassword, type]); const [suggestionValues, setSuggestionValues] = React.useState(null); const canShowSuggestions = suggestionListCallback != null; const hasSuggestions = !!suggestionValues; const hasLabel = label != null; const focusInput = () => { inputRef.current?.focus(); }; const isMounted = useIsMounted.useIsMounted(); const suggestionClearHandler = () => { if (isMounted.current) { setSuggestionValues(null); } }; const suggestionHandler = (val) => { const suggestionsArray = suggestionListCallback?.(val); if (suggestionsArray?.[0]?.label) { setSuggestionValues(suggestionsArray); } else { suggestionClearHandler(); } }; const suggestionSelectedHandler = (event, item) => { const newValue = item.value || item.label; utils$1.changeInputValue(inputRef.current, newValue); focusInput(); suggestionClearHandler(); if (type === "search") { onEnter?.(event, newValue); } }; const onChangeHandler = (event, newValue) => { isDirty.current = true; onChange?.(event, newValue); if (canShowSuggestions) { suggestionHandler(newValue); } }; const onInputBlurHandler = (event) => { if (eventTargetIsInsideContainer(suggestionsRef.current, event)) return; setFocused(false); const inputValidity = performValidation(); onBlur?.(event, event.target.value, inputValidity); }; const onFocusHandler = (event) => { setFocused(true); setValidationState("standBy"); onFocus?.(event, event.target.value); }; const getSuggestions = (li) => { const listEl = document.getElementById( setId.setId(elementId, "suggestions-list") || "" ); return li != null ? listEl?.getElementsByTagName("li")?.[li] : listEl; }; const onSuggestionKeyDown = (event) => { if (keyboardUtils.isKey(event, "Esc")) { suggestionClearHandler(); focusInput(); } else if (keyboardUtils.isKey(event, "Tab")) { suggestionClearHandler(); } }; const onKeyDownHandler = (event) => { const { value } = event.currentTarget; if (keyboardUtils.isKey(event, "ArrowDown") && hasSuggestions) { const li = getSuggestions(0); li?.focus(); } else if (keyboardUtils.isKey(event, "Enter")) { onEnter?.(event, value); } onKeyDown?.(event, value); }; const onContainerBlurHandler = (event) => { if (event.relatedTarget) { setTimeout(() => { const list = getSuggestions(null); if (!list?.contains(document.activeElement)) suggestionClearHandler(); }, 10); } }; const showClear = !disabled && !readOnly && !disableClear && !isEmptyValue && (!onEnter || type !== "search" || disableSearchButton || validationState !== "standBy"); const showSearchIcon = type === "search" && !disableSearchButton; const showRevealPasswordButton = type === "password" && !disableRevealPassword; const handleClear = React.useCallback( (event) => { setValidationState("standBy"); utils$1.changeInputValue(inputRef.current, ""); if (canShowSuggestions && suggestOnFocus) event.stopPropagation(); else { setTimeout(focusInput); } }, [canShowSuggestions, setValidationState, suggestOnFocus] ); const clearButton = React.useMemo(() => { if (!showClear) { return null; } return /* @__PURE__ */ jsxRuntime.jsx( Adornment.HvAdornment, { className: cx(classes.adornmentButton, { [classes.iconClear]: !showSearchIcon }), onClick: handleClear, "aria-label": labels?.clearButtonLabel, "aria-controls": setId.setId(elementId, "input"), icon: /* @__PURE__ */ jsxRuntime.jsx(icons.HvIcon, { compact: true, name: "Close", size: "xs" }) } ); }, [ showClear, classes.adornmentButton, classes.iconClear, showSearchIcon, handleClear, labels?.clearButtonLabel, elementId, cx ]); const searchButton = React.useMemo(() => { const reallyShowIt = showSearchIcon && (isEmptyValue || onEnter && validationState === "standBy"); if (!reallyShowIt) return null; return /* @__PURE__ */ jsxRuntime.jsx( Adornment.HvAdornment, { className: classes.adornmentButton, onClick: onEnter && ((evt) => onEnter?.(evt, inputRef.current?.value ?? "")), icon: /* @__PURE__ */ jsxRuntime.jsx(icons.HvIcon, { compact: true, name: "Search", title: labels.searchButtonLabel }) } ); }, [ showSearchIcon, isEmptyValue, onEnter, validationState, classes.adornmentButton, labels.searchButtonLabel ]); const revealPasswordButton = React.useMemo(() => { if (!showRevealPasswordButton) return null; return /* @__PURE__ */ jsxRuntime.jsx( Tooltip.HvTooltip, { title: revealPassword ? labels?.revealPasswordButtonClickToHideTooltip : labels?.revealPasswordButtonClickToShowTooltip, children: /* @__PURE__ */ jsxRuntime.jsx( Adornment.HvAdornment, { className: classes.adornmentButton, onClick: () => setRevealPassword((s) => !s), "aria-controls": setId.setId(elementId, "input"), icon: /* @__PURE__ */ jsxRuntime.jsx(icons$1.EyeIcon, { selected: revealPassword }), tabIndex: 0, ...{ selected: revealPassword } } ) } ); }, [ showRevealPasswordButton, revealPassword, labels?.revealPasswordButtonClickToHideTooltip, labels?.revealPasswordButtonClickToShowTooltip, classes.adornmentButton, elementId ]); const validationIcon = React.useMemo(() => { if (!showValidationIcon) return null; if (!utils$2.isValid(validationState)) return null; return /* @__PURE__ */ jsxRuntime.jsx(icons.HvIcon, { name: "Success", color: "positive", className: classes.icon }); }, [showValidationIcon, validationState, classes.icon]); const adornments = React.useMemo(() => { if (!clearButton && !revealPasswordButton && !searchButton && !validationIcon && !endAdornment) return null; return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes.adornmentsBox, children: [ clearButton, revealPasswordButton, searchButton, validationIcon || endAdornment ] }); }, [ classes.adornmentsBox, clearButton, endAdornment, revealPasswordButton, searchButton, validationIcon ]); React.useEffect(() => { if (focused || !isDirty.current && isEmptyValue) { return; } performValidation(); }, [focused, isEmptyValue, performValidation]); const errorMessageId = isStateInvalid ? canShowError ? setId.setId(elementId, "error") : ariaErrorMessage : void 0; return /* @__PURE__ */ jsxRuntime.jsxs( FormElement.HvFormElement, { id, name, status: validationState, disabled, required, readOnly, className: cx( classes.root, { [classes.hasSuggestions]: hasSuggestions }, className ), onBlur: onContainerBlurHandler, children: [ /* @__PURE__ */ jsxRuntime.jsx( LabelContainer.HvLabelContainer, { label, description, inputId: setId.setId(elementId, "input"), labelId: setId.setId(elementId, "label"), descriptionId: setId.setId(elementId, "description"), classes: { root: classes.labelContainer, label: classes.label, description: classes.description } } ), /* @__PURE__ */ jsxRuntime.jsx( BaseInput.HvBaseInput, { id: hasLabel || showClear || showRevealPasswordButton ? setId.setId(elementId, "input") : setId.setId(id, "input"), name, value: valueProp, defaultValue, required, readOnly, disabled, onChange: onChangeHandler, autoFocus, onKeyDown: onKeyDownHandler, onBlur: onInputBlurHandler, onFocus: onFocusHandler, placeholder, type: realType, classes: { input: classes.input, root: classes.inputRoot, focused: classes.inputRootFocused, disabled: classes.inputRootDisabled, multiline: classes.inputRootMultiline }, invalid: isStateInvalid, inputProps: { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-invalid": isStateInvalid ? true : void 0, "aria-errormessage": errorMessageId, "aria-describedby": ariaDescribedBy != null ? ariaDescribedBy : description ? setId.setId(elementId, "description") : void 0, "aria-controls": canShowSuggestions ? setId.setId(elementId, "suggestions") : void 0, // prevent browsers auto-fill/suggestions when we have our own autoComplete: canShowSuggestions ? "off" : void 0, onFocus: (event) => { inputProps.onFocus?.(event); if (canShowSuggestions && suggestOnFocus) { suggestionHandler(event.currentTarget.value); } }, onClick: (event) => { inputProps.onClick?.(event); if (canShowSuggestions && suggestOnFocus) event.stopPropagation(); }, ...inputProps }, ref: forkedRef, endAdornment: adornments, ...others } ), canShowSuggestions && /* @__PURE__ */ jsxRuntime.jsx( Suggestions.HvSuggestions, { id: setId.setId(elementId, "suggestions"), classes: { root: classes.suggestionsContainer, list: classes.suggestionList }, expanded: hasSuggestions, anchorEl: inputRef.current?.parentElement, onClose: suggestionClearHandler, onKeyDown: onSuggestionKeyDown, onSuggestionSelected: suggestionSelectedHandler, suggestionValues, enablePortal, popperProps: { ref: suggestionsRef } } ), canShowError && /* @__PURE__ */ jsxRuntime.jsx( WarningText.HvWarningText, { id: setId.setId(elementId, "error"), disableBorder: true, className: classes.error, children: validationMessage } ), canShowInfo && /* @__PURE__ */ jsxRuntime.jsx(InfoMessage.HvInfoMessage, { disableGutter: true, variant: "caption1", children: infoMessage }) ] } ); }); exports.inputClasses = Input_styles.staticClasses; exports.HvInput = HvInput;