{"version":3,"file":"use-field.mjs","names":[],"sources":["../src/use-field.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\nimport { getInputOnChange } from './get-input-on-change';\nimport { FormMode, GetInputPropsType } from './types';\nimport { shouldValidateOnChange } from './validate';\n\ntype UseFieldErrorResolver = (error: unknown) => React.ReactNode;\n\nexport interface UseFieldInput<\n  T,\n  FieldType extends GetInputPropsType = 'input',\n  Mode extends FormMode = 'controlled',\n> {\n  /** Field mode, controlled by default */\n  mode?: Mode;\n\n  /** Initial field value */\n  initialValue: T;\n\n  /** Initial touched value */\n  initialTouched?: boolean;\n\n  /** Initial field error message */\n  initialError?: React.ReactNode;\n\n  /** Called with updated value when the field value changes */\n  onValueChange?: (value: T) => void;\n\n  /** Determines whether the field should be validated when value changes, false by default */\n  validateOnChange?: boolean;\n\n  /** Determines whether the field should be validated when it loses focus, false by default */\n  validateOnBlur?: boolean;\n\n  /** Determines whether the field should clear error message when value changes, true by default */\n  clearErrorOnChange?: boolean;\n\n  /** A function to validate field value, can be sync or async */\n  validate?: (value: T) => React.ReactNode | Promise<React.ReactNode>;\n\n  /** Field type, input by default */\n  type?: FieldType;\n\n  /** A function to resolve validation error from the result returned from validate function, should return react node */\n  resolveValidationError?: UseFieldErrorResolver;\n}\n\ninterface SetValueOptions {\n  updateState?: boolean;\n  updateKey?: boolean;\n}\n\ninterface GetInputPropsOptions {\n  withError?: boolean;\n  withFocus?: boolean;\n  [key: string]: any;\n}\n\ninterface GetInputPropsSharedReturn {\n  error?: React.ReactNode;\n  onFocus?: () => void;\n  onBlur: () => void;\n  onChange: (value: any) => void;\n}\n\ntype GetInputPropsTypeValue<\n  T,\n  FieldType extends GetInputPropsType,\n  Mode extends FormMode,\n> = FieldType extends 'checkbox'\n  ? Mode extends 'controlled'\n    ? { checked: boolean }\n    : { defaultChecked: boolean }\n  : FieldType extends 'radio'\n    ? Mode extends 'controlled'\n      ? { checked: boolean; value: T }\n      : { defaultChecked: boolean; value: T }\n    : Mode extends 'controlled'\n      ? { value: T }\n      : { defaultValue: T };\n\ntype GetInputPropsReturnType<\n  T,\n  FieldType extends GetInputPropsType,\n  Mode extends FormMode,\n> = GetInputPropsSharedReturn & GetInputPropsTypeValue<T, FieldType, Mode>;\n\nexport interface UseFieldReturnType<\n  T,\n  FieldType extends GetInputPropsType = 'input',\n  Mode extends FormMode = 'controlled',\n> {\n  /** Returns props to pass to the input element */\n  getInputProps: (options?: GetInputPropsOptions) => GetInputPropsReturnType<T, FieldType, Mode>;\n\n  /** Returns current input value */\n  getValue: () => T;\n\n  /** Sets input value to the given value */\n  setValue: (value: T) => void;\n\n  /** Resets field value to initial state, sets touched state to false, sets error to null */\n  reset: () => void;\n\n  /** Validates current input value when called */\n  validate: () => Promise<React.ReactNode | void>;\n\n  /** Set to true when async validate function is called, stays true until the returned promise resolves */\n  isValidating: boolean;\n\n  /** Current error message */\n  error: React.ReactNode;\n\n  /** Sets error message to the given react node */\n  setError: (error: React.ReactNode) => void;\n\n  /** Returns true if the input has been focused at least once */\n  isTouched: () => boolean;\n\n  /** Returns true if input value is different from the initial value */\n  isDirty: () => boolean;\n\n  /** Resets touched state to false */\n  resetTouched: () => void;\n\n  /** Key that should be added to the input when mode is uncontrolled */\n  key: number;\n}\n\nexport function useField<\n  T,\n  Mode extends FormMode = 'controlled',\n  FieldType extends GetInputPropsType = 'input',\n>({\n  mode = 'controlled' as Mode,\n  clearErrorOnChange = true,\n  initialValue,\n  initialError = null,\n  initialTouched = false,\n  onValueChange,\n  validateOnChange = false,\n  validateOnBlur = false,\n  validate,\n  resolveValidationError,\n  type = 'input' as FieldType,\n}: UseFieldInput<T, FieldType, Mode>): UseFieldReturnType<T, FieldType, Mode> {\n  const [valueState, setValueState] = useState(initialValue);\n  const valueRef = useRef(valueState);\n  const [key, setKey] = useState(0);\n  const [error, setError] = useState<React.ReactNode>(initialError || null);\n  const touchedRef = useRef(initialTouched || false);\n  const [, setTouchedState] = useState(touchedRef.current);\n  const [isValidating, setIsValidating] = useState(false);\n  const errorResolver: UseFieldErrorResolver = useMemo(\n    () => resolveValidationError || ((err) => err as React.ReactNode),\n    [resolveValidationError]\n  );\n\n  const setTouched = useCallback((val: boolean, { updateState = mode === 'controlled' } = {}) => {\n    touchedRef.current = val;\n    updateState && setTouchedState(val);\n  }, []);\n\n  const setValue = useCallback(\n    (\n      value: T,\n      {\n        updateKey = mode === 'uncontrolled',\n        updateState = mode === 'controlled',\n      }: SetValueOptions = {}\n    ) => {\n      if (valueRef.current === value) {\n        return;\n      }\n\n      valueRef.current = value;\n\n      onValueChange?.(value);\n\n      if (clearErrorOnChange && error !== null) {\n        setError(null);\n      }\n\n      if (updateState) {\n        setValueState(value);\n      }\n\n      if (updateKey) {\n        setKey((currentKey) => currentKey + 1);\n      }\n\n      if (validateOnChange) {\n        _validate();\n      }\n    },\n    [error, clearErrorOnChange, onValueChange]\n  );\n\n  const reset = useCallback(() => {\n    setValue(initialValue);\n    setError(null);\n    setTouched(false);\n  }, [initialValue]);\n\n  const getValue = useCallback(() => valueRef.current, []);\n\n  const isTouched = useCallback(() => touchedRef.current, []);\n\n  const isDirty = useCallback(() => valueRef.current !== initialValue, [initialValue]);\n\n  const _validate = useCallback(async () => {\n    const validationResult = validate?.(valueRef.current);\n\n    if (validationResult instanceof Promise) {\n      setIsValidating(true);\n      try {\n        const result = await validationResult;\n        setIsValidating(false);\n        setError(result);\n      } catch (err) {\n        setIsValidating(false);\n        const resolvedError = errorResolver(err);\n        setError(resolvedError);\n        return resolvedError;\n      }\n    } else {\n      setError(validationResult);\n      return validationResult;\n    }\n  }, []);\n\n  const getInputProps = ({ withError = true, withFocus = true, ...otherOptions }: any = {}) => {\n    const onChange = getInputOnChange<T>((val) => setValue(val as any, { updateKey: false }));\n\n    const payload: any = { onChange };\n\n    if (withError) {\n      payload.error = error;\n    }\n\n    if (type === 'checkbox') {\n      payload[mode === 'controlled' ? 'checked' : 'defaultChecked'] = valueRef.current;\n    } else if (type === 'radio') {\n      payload[mode === 'controlled' ? 'checked' : 'defaultChecked'] =\n        valueRef.current === otherOptions.value;\n      payload.value = otherOptions.value;\n    } else {\n      payload[mode === 'controlled' ? 'value' : 'defaultValue'] = valueRef.current;\n    }\n\n    if (withFocus) {\n      payload.onFocus = () => {\n        setTouched(true);\n      };\n\n      payload.onBlur = () => {\n        if (shouldValidateOnChange('', !!validateOnBlur)) {\n          _validate();\n        }\n      };\n    }\n\n    return payload;\n  };\n\n  const resetTouched = useCallback(() => setTouched(false), []);\n\n  return {\n    key,\n    getValue,\n    setValue,\n    reset,\n    getInputProps,\n\n    isValidating,\n    validate: _validate,\n\n    error,\n    setError,\n\n    isTouched,\n    isDirty,\n    resetTouched,\n  };\n}\n"],"mappings":"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"}