@hitachivantara/uikit-react-core
Version:
Core React components for the NEXT Design System.
481 lines (480 loc) • 16.9 kB
JavaScript
"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;