"use client"; // src/use-input.ts import { mapPropsVariants, useProviderContext } from "@heroui/system"; import { useSafeLayoutEffect } from "@heroui/use-safe-layout-effect"; import { useFocusRing } from "@react-aria/focus"; import { input } from "@heroui/theme"; import { useDOMRef, filterDOMProps } from "@heroui/react-utils"; import { useFocusWithin, useHover, usePress } from "@react-aria/interactions"; import { clsx, dataAttr, isEmpty, objectToDeps, safeAriaLabel } from "@heroui/shared-utils"; import { useControlledState } from "@react-stately/utils"; import { useMemo, useCallback, useState } from "react"; import { chain, mergeProps } from "@react-aria/utils"; import { useTextField } from "@react-aria/textfield"; import { FormContext, useSlottedContext } from "@heroui/form"; function useInput(originalProps) { var _a, _b, _c, _d; const globalContext = useProviderContext(); const { validationBehavior: formValidationBehavior } = useSlottedContext(FormContext) || {}; const [props, variantProps] = mapPropsVariants(originalProps, input.variantKeys); const { ref, as, type, label, baseRef, wrapperRef, description, className, classNames, autoFocus, startContent, endContent, onClear, onChange, validationState, validationBehavior = (_a = formValidationBehavior != null ? formValidationBehavior : globalContext == null ? void 0 : globalContext.validationBehavior) != null ? _a : "native", innerWrapperRef: innerWrapperRefProp, onValueChange = () => { }, ...otherProps } = props; const handleValueChange = useCallback( (value) => { onValueChange(value != null ? value : ""); }, [onValueChange] ); const [isFocusWithin, setFocusWithin] = useState(false); const Component = as || "div"; const disableAnimation = (_c = (_b = originalProps.disableAnimation) != null ? _b : globalContext == null ? void 0 : globalContext.disableAnimation) != null ? _c : false; const domRef = useDOMRef(ref); const baseDomRef = useDOMRef(baseRef); const inputWrapperRef = useDOMRef(wrapperRef); const innerWrapperRef = useDOMRef(innerWrapperRefProp); const [inputValue, setInputValue] = useControlledState( props.value, (_d = props.defaultValue) != null ? _d : "", handleValueChange ); const isFilledByDefault = ["date", "time", "month", "week", "range"].includes(type); const isFilled = !isEmpty(inputValue) || isFilledByDefault; const isFilledWithin = isFilled || isFocusWithin; const isHiddenType = type === "hidden"; const isMultiline = originalProps.isMultiline; const isFileTypeInput = type === "file"; const baseStyles = clsx(classNames == null ? void 0 : classNames.base, className, isFilled ? "is-filled" : ""); const handleClear = useCallback(() => { var _a2; setInputValue(""); onClear == null ? void 0 : onClear(); (_a2 = domRef.current) == null ? void 0 : _a2.focus(); }, [setInputValue, onClear]); useSafeLayoutEffect(() => { if (!domRef.current) return; setInputValue(domRef.current.value); }, [domRef.current]); const { labelProps, inputProps, isInvalid: isAriaInvalid, validationErrors, validationDetails, descriptionProps, errorMessageProps } = useTextField( { ...originalProps, validationBehavior, autoCapitalize: originalProps.autoCapitalize, value: inputValue, "aria-label": safeAriaLabel( originalProps["aria-label"], originalProps.label, originalProps.placeholder ), inputElementType: isMultiline ? "textarea" : "input", onChange: setInputValue }, domRef ); if (isFileTypeInput) { delete inputProps.value; delete inputProps.onChange; } const { isFocusVisible, isFocused, focusProps } = useFocusRing({ autoFocus, isTextInput: true }); const { isHovered, hoverProps } = useHover({ isDisabled: !!(originalProps == null ? void 0 : originalProps.isDisabled) }); const { isHovered: isLabelHovered, hoverProps: labelHoverProps } = useHover({ isDisabled: !!(originalProps == null ? void 0 : originalProps.isDisabled) }); const { focusProps: clearFocusProps, isFocusVisible: isClearButtonFocusVisible } = useFocusRing(); const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setFocusWithin }); const { pressProps: clearPressProps } = usePress({ isDisabled: !!(originalProps == null ? void 0 : originalProps.isDisabled) || !!(originalProps == null ? void 0 : originalProps.isReadOnly), onPress: handleClear }); const isInvalid = validationState === "invalid" || isAriaInvalid; const labelPlacement = useMemo(() => { var _a2; if ((!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && !label) { return "outside"; } return (_a2 = originalProps.labelPlacement) != null ? _a2 : "inside"; }, [originalProps.labelPlacement, label]); const errorMessage = typeof props.errorMessage === "function" ? props.errorMessage({ isInvalid, validationErrors, validationDetails }) : props.errorMessage || (validationErrors == null ? void 0 : validationErrors.join(" ")); const isClearable = !!onClear || originalProps.isClearable; const hasElements = !!label || !!description || !!errorMessage; const hasPlaceholder = !!props.placeholder; const hasLabel = !!label; const hasHelper = !!description || !!errorMessage; const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left"; const shouldLabelBeInside = labelPlacement === "inside"; const isPlaceholderShown = domRef.current ? (!domRef.current.value || domRef.current.value === "" || !inputValue || inputValue === "") && hasPlaceholder : false; const isOutsideLeft = labelPlacement === "outside-left"; const hasStartContent = !!startContent; const isLabelOutside = shouldLabelBeOutside ? labelPlacement === "outside-left" || hasPlaceholder || labelPlacement === "outside" && hasStartContent : false; const isLabelOutsideAsPlaceholder = labelPlacement === "outside" && !hasPlaceholder && !hasStartContent; const slots = useMemo( () => input({ ...variantProps, isInvalid, labelPlacement, isClearable, disableAnimation }), [ objectToDeps(variantProps), isInvalid, labelPlacement, isClearable, hasStartContent, disableAnimation ] ); const getBaseProps = useCallback( (props2 = {}) => { return { ref: baseDomRef, className: slots.base({ class: baseStyles }), "data-slot": "base", "data-filled": dataAttr( isFilled || hasPlaceholder || hasStartContent || isPlaceholderShown || isFileTypeInput ), "data-filled-within": dataAttr( isFilledWithin || hasPlaceholder || hasStartContent || isPlaceholderShown || isFileTypeInput ), "data-focus-within": dataAttr(isFocusWithin), "data-focus-visible": dataAttr(isFocusVisible), "data-readonly": dataAttr(originalProps.isReadOnly), "data-focus": dataAttr(isFocused), "data-hover": dataAttr(isHovered || isLabelHovered), "data-required": dataAttr(originalProps.isRequired), "data-invalid": dataAttr(isInvalid), "data-disabled": dataAttr(originalProps.isDisabled), "data-has-elements": dataAttr(hasElements), "data-has-helper": dataAttr(hasHelper), "data-has-label": dataAttr(hasLabel), "data-has-value": dataAttr(!isPlaceholderShown), "data-hidden": dataAttr(isHiddenType), ...focusWithinProps, ...props2 }; }, [ slots, baseStyles, isFilled, isFocused, isHovered, isLabelHovered, isInvalid, hasHelper, hasLabel, hasElements, isPlaceholderShown, hasStartContent, isFocusWithin, isFocusVisible, isFilledWithin, hasPlaceholder, focusWithinProps, isHiddenType, originalProps.isReadOnly, originalProps.isRequired, originalProps.isDisabled ] ); const getLabelProps = useCallback( (props2 = {}) => { return { "data-slot": "label", className: slots.label({ class: classNames == null ? void 0 : classNames.label }), ...mergeProps(labelProps, labelHoverProps, props2) }; }, [slots, isLabelHovered, labelProps, classNames == null ? void 0 : classNames.label] ); const getInputProps = useCallback( (props2 = {}) => { return { "data-slot": "input", "data-filled": dataAttr(isFilled), "data-filled-within": dataAttr(isFilledWithin), "data-has-start-content": dataAttr(hasStartContent), "data-has-end-content": dataAttr(!!endContent), className: slots.input({ class: clsx(classNames == null ? void 0 : classNames.input, isFilled ? "is-filled" : "", isMultiline ? "pe-0" : "") }), ...mergeProps( focusProps, inputProps, filterDOMProps(otherProps, { enabled: true, labelable: true, omitEventNames: new Set(Object.keys(inputProps)) }), props2 ), "aria-readonly": dataAttr(originalProps.isReadOnly), onChange: chain(inputProps.onChange, onChange), ref: domRef }; }, [ slots, inputValue, focusProps, inputProps, otherProps, isFilled, isFilledWithin, hasStartContent, endContent, classNames == null ? void 0 : classNames.input, originalProps.isReadOnly, originalProps.isRequired, onChange ] ); const getInputWrapperProps = useCallback( (props2 = {}) => { return { ref: inputWrapperRef, "data-slot": "input-wrapper", "data-hover": dataAttr(isHovered || isLabelHovered), "data-focus-visible": dataAttr(isFocusVisible), "data-focus": dataAttr(isFocused), className: slots.inputWrapper({ class: clsx(classNames == null ? void 0 : classNames.inputWrapper, isFilled ? "is-filled" : "") }), ...mergeProps(props2, hoverProps), onClick: (e) => { if (domRef.current && e.currentTarget === e.target) { domRef.current.focus(); } }, style: { cursor: "text", ...props2.style } }; }, [ slots, isHovered, isLabelHovered, isFocusVisible, isFocused, inputValue, classNames == null ? void 0 : classNames.inputWrapper ] ); const getInnerWrapperProps = useCallback( (props2 = {}) => { return { ...props2, ref: innerWrapperRef, "data-slot": "inner-wrapper", onClick: (e) => { if (domRef.current && e.currentTarget === e.target) { domRef.current.focus(); } }, className: slots.innerWrapper({ class: clsx(classNames == null ? void 0 : classNames.innerWrapper, props2 == null ? void 0 : props2.className) }) }; }, [slots, classNames == null ? void 0 : classNames.innerWrapper] ); const getMainWrapperProps = useCallback( (props2 = {}) => { return { ...props2, "data-slot": "main-wrapper", className: slots.mainWrapper({ class: clsx(classNames == null ? void 0 : classNames.mainWrapper, props2 == null ? void 0 : props2.className) }) }; }, [slots, classNames == null ? void 0 : classNames.mainWrapper] ); const getHelperWrapperProps = useCallback( (props2 = {}) => { return { ...props2, "data-slot": "helper-wrapper", className: slots.helperWrapper({ class: clsx(classNames == null ? void 0 : classNames.helperWrapper, props2 == null ? void 0 : props2.className) }) }; }, [slots, classNames == null ? void 0 : classNames.helperWrapper] ); const getDescriptionProps = useCallback( (props2 = {}) => { return { ...props2, ...descriptionProps, "data-slot": "description", className: slots.description({ class: clsx(classNames == null ? void 0 : classNames.description, props2 == null ? void 0 : props2.className) }) }; }, [slots, classNames == null ? void 0 : classNames.description] ); const getErrorMessageProps = useCallback( (props2 = {}) => { return { ...props2, ...errorMessageProps, "data-slot": "error-message", className: slots.errorMessage({ class: clsx(classNames == null ? void 0 : classNames.errorMessage, props2 == null ? void 0 : props2.className) }) }; }, [slots, errorMessageProps, classNames == null ? void 0 : classNames.errorMessage] ); const getClearButtonProps = useCallback( (props2 = {}) => { return { ...props2, type: "button", tabIndex: -1, disabled: originalProps.isDisabled, "aria-label": "clear input", "data-slot": "clear-button", "data-focus-visible": dataAttr(isClearButtonFocusVisible), className: slots.clearButton({ class: clsx(classNames == null ? void 0 : classNames.clearButton, props2 == null ? void 0 : props2.className) }), ...mergeProps(clearPressProps, clearFocusProps) }; }, [slots, isClearButtonFocusVisible, clearPressProps, clearFocusProps, classNames == null ? void 0 : classNames.clearButton] ); return { Component, classNames, domRef, label, description, startContent, endContent, labelPlacement, isClearable, hasHelper, hasStartContent, isLabelOutside, isOutsideLeft, isLabelOutsideAsPlaceholder, shouldLabelBeOutside, shouldLabelBeInside, hasPlaceholder, isInvalid, errorMessage, getBaseProps, getLabelProps, getInputProps, getMainWrapperProps, getInputWrapperProps, getInnerWrapperProps, getHelperWrapperProps, getDescriptionProps, getErrorMessageProps, getClearButtonProps }; } export { useInput };