{"file":"useForm-B-YJsg_G.js","mappings":"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","names":[],"sources":["../../../../composables/useForm.js"],"sourcesContent":["import { ref, computed, watch, nextTick } from \"vue\";\n\n// ===== 유효성 검사 함수들 =====\n\n// 이메일 형식 검증\nexport const validateEmail = (email) => {\n  if (!email) {\n    return { valid: false, errorCode: \"empty\" };\n  }\n  const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n  if (!emailRegex.test(email)) {\n    return { valid: false, errorCode: \"invalid\" };\n  }\n  return { valid: true };\n};\n\n// 비밀번호 유효성 검사 (영문숫자 포함 8자 이상)\nexport const validatePassword = (password) => {\n  if (!password) {\n    return { valid: false, errorCode: \"empty\" };\n  }\n  const hasLetter = /[a-zA-Z]/.test(password);\n  const hasNumber = /[0-9]/.test(password);\n  const isLongEnough = password.length >= 8;\n  if (!isLongEnough || !hasLetter || !hasNumber) {\n    return { valid: false, errorCode: \"invalid\" };\n  }\n  return { valid: true };\n};\n\n// ===== 기본 폼 필드 Composable =====\n\n// 개별 폼 필드의 상태와 유효성 검사를 관리하는 composable\nexport const useFormField = (initialValue = \"\", options = {}) => {\n  const {\n    validator = null,\n    errorMessages = {},\n    required = false,\n    onValidate = null,\n    onAuth = null,\n    checkDuplicate = null,\n  } = options;\n\n  // ===== 상태 =====\n  const value = ref(initialValue);\n  const isError = ref(false);\n  const errorCode = ref(null);\n  const shakeKey = ref(0);\n  const focusMoved = ref(false);\n  const inputRef = ref(null);\n\n  // ===== 계산된 값 =====\n  const errorMessage = computed(() => {\n    if (!errorCode.value) return \"\";\n    return errorMessages[errorCode.value] || \"\";\n  });\n\n  // ===== 메서드 =====\n  // 필드 유효성 검사\n  const validate = () => {\n    // 필수 입력 검사\n    if (required && !value.value.trim()) {\n      isError.value = true;\n      errorCode.value = \"empty\";\n      return false;\n    }\n\n    // 커스텀 validator 실행\n    if (validator && value.value.trim()) {\n      const result = validator(value.value);\n      if (!result.valid) {\n        isError.value = true;\n        errorCode.value = result.errorCode;\n        return false;\n      }\n    }\n\n    // 추가 커스텀 검사\n    if (onValidate) {\n      const result = onValidate(value.value);\n      if (!result.valid) {\n        isError.value = true;\n        errorCode.value = result.errorCode;\n        return false;\n      }\n    }\n\n    isError.value = false;\n    errorCode.value = null;\n    return true;\n  };\n\n  // 수동으로 에러 설정\n  const setError = (code) => {\n    isError.value = true;\n    errorCode.value = code;\n  };\n\n  // blur 이벤트 핸들러 (유효성 검사 실패 시 자동 포커스)\n  const handleBlur = async () => {\n    const isValid = validate();\n    if (!isValid && !focusMoved.value && inputRef.value) {\n      await nextTick();\n      inputRef.value.focus();\n      focusMoved.value = true;\n    } else if (isValid) {\n      focusMoved.value = false;\n    }\n  };\n\n  // 에러 상태 초기화\n  const reset = () => {\n    isError.value = false;\n    errorCode.value = null;\n    focusMoved.value = false;\n  };\n\n  // 값과 에러 상태 모두 초기화\n  const clear = () => {\n    value.value = \"\";\n    reset();\n  };\n\n  // 인증 처리 (이메일 등)\n  const handleAuth = async () => {\n    // 기본 유효성 검사\n    if (!validate()) {\n      if (inputRef.value) {\n        await nextTick();\n        inputRef.value.focus();\n      }\n      return false;\n    }\n\n    // 인증 함수 실행\n    if (onAuth) {\n      try {\n        const result = await onAuth(value.value);\n        if (!result.success) {\n          setError(result.errorCode || \"auth-failed\");\n          if (inputRef.value) {\n            await nextTick();\n            inputRef.value.focus();\n          }\n          return false;\n        }\n        return true;\n      } catch (error) {\n        setError(\"auth-failed\");\n        if (inputRef.value) {\n          await nextTick();\n          inputRef.value.focus();\n        }\n        return false;\n      }\n    }\n\n    return true;\n  };\n\n  // 중복 체크\n  const handleDuplicateCheck = async () => {\n    // 기본 유효성 검사\n    if (!validate()) {\n      return false;\n    }\n\n    // 중복 체크 함수 실행\n    if (checkDuplicate) {\n      try {\n        const result = await checkDuplicate(value.value);\n        if (!result.available) {\n          setError(\"duplicate\");\n          return false;\n        }\n        return true;\n      } catch (error) {\n        console.error(\"Duplicate check error:\", error);\n        return false;\n      }\n    }\n\n    return true;\n  };\n\n  // ===== Watchers =====\n  // 에러 상태 변경 감지 (shake 애니메이션 트리거)\n  watch(isError, (newVal, oldVal) => {\n    if (newVal && !oldVal) {\n      shakeKey.value++;\n    }\n  });\n\n  // 값 변경 감지 (에러 상태 자동 재검증)\n  watch(value, () => {\n    if (focusMoved.value && value.value.trim()) {\n      focusMoved.value = false;\n    }\n    if (isError.value) {\n      // 값이 변경되면 자동으로 재검증\n      validate();\n    }\n  });\n\n  return {\n    // 상태\n    value,\n    isError,\n    errorCode,\n    errorMessage,\n    shakeKey,\n    inputRef,\n    focusMoved,\n    // 메서드\n    validate,\n    setError,\n    handleBlur,\n    handleAuth,\n    handleDuplicateCheck,\n    reset,\n    clear,\n  };\n};\n\n// ===== 비밀번호 필드 Composables =====\n\n// 비밀번호 필드 관리 composable (표시/숨김, 유효성 검사 포함)\nexport const usePasswordField = (initialValue = \"\", options = {}) => {\n  const {\n    required = false,\n    errorMessages: userErrorMessages = {},\n    validateOnInput = false,\n  } = options;\n\n  // 기본 에러 메시지와 사용자 정의 에러 메시지 병합\n  const defaultErrorMessages = {\n    empty: \"비밀번호를 입력해주세요.\",\n    invalid: \"비밀번호는 영문숫자 포함 8자 이상이어야 합니다.\",\n  };\n  const errorMessages = { ...defaultErrorMessages, ...userErrorMessages };\n\n  // 기본 필드 기능 상속\n  const field = useFormField(initialValue, {\n    required,\n    validator: validatePassword,\n    errorMessages,\n  });\n\n  // 비밀번호 표시/숨김 상태\n  const showPassword = ref(false);\n  // 포커스 상태\n  const isFocused = ref(false);\n\n  // 비밀번호 표시/숨김 토글\n  const togglePassword = () => {\n    showPassword.value = !showPassword.value;\n  };\n\n  // 포커스 이벤트 핸들러\n  const handleFocus = () => {\n    isFocused.value = true;\n  };\n\n  // blur 이벤트 핸들러 (유효성 검사 + 포커스 상태 업데이트)\n  const handleBlur = async () => {\n    isFocused.value = false;\n    await field.handleBlur();\n  };\n\n  // 입력값 변경 시 즉시 유효성 검사 (옵션)\n  if (validateOnInput) {\n    watch(field.value, () => {\n      if (field.value.value.trim()) {\n        field.validate();\n      }\n    });\n  }\n\n  return {\n    // 기본 필드 속성\n    ...field,\n    // 비밀번호 전용 속성\n    showPassword,\n    isFocused,\n    // 비밀번호 전용 메서드\n    togglePassword,\n    handleFocus,\n    handleBlur,\n  };\n};\n\n// 비밀번호 확인 필드 관리 composable\nexport const usePasswordConfirmField = (\n  initialValue = \"\",\n  passwordField,\n  options = {}\n) => {\n  const {\n    errorMessages = {\n      empty: \"비밀번호를 입력해주세요.\",\n      mismatch: \"비밀번호가 일치하지 않습니다.\",\n    },\n  } = options;\n\n  // 비밀번호 확인 필드 생성\n  const confirmField = usePasswordField(initialValue, {\n    required: false,\n    errorMessages,\n  });\n\n  // 비밀번호 일치 여부 검사\n  const validateMatch = () => {\n    if (!confirmField.value.value) {\n      return true; // 빈 값이면 검사 안 함\n    }\n    if (passwordField.value.value !== confirmField.value.value) {\n      confirmField.isError.value = true;\n      confirmField.errorCode.value = \"mismatch\";\n      return false;\n    }\n    confirmField.isError.value = false;\n    confirmField.errorCode.value = null;\n    return true;\n  };\n\n  // blur 이벤트 핸들러 (일치 검사 포함)\n  const handleBlur = async () => {\n    confirmField.isFocused.value = false;\n    validateMatch();\n    if (\n      confirmField.isError.value &&\n      !confirmField.focusMoved.value &&\n      confirmField.inputRef.value\n    ) {\n      await nextTick();\n      confirmField.inputRef.value.focus();\n      confirmField.focusMoved.value = true;\n    } else if (!confirmField.isError.value) {\n      confirmField.focusMoved.value = false;\n    }\n  };\n\n  // 원본 비밀번호가 변경되면 확인 필드도 재검증\n  watch(passwordField.value, () => {\n    if (confirmField.value.value) {\n      validateMatch();\n    }\n  });\n\n  // 확인 비밀번호 입력 시 실시간 검증\n  watch(confirmField.value, () => {\n    if (confirmField.focusMoved.value) {\n      confirmField.focusMoved.value = false;\n    }\n    if (confirmField.value.value) {\n      validateMatch();\n    } else if (confirmField.isError.value) {\n      confirmField.isError.value = false;\n      confirmField.errorCode.value = null;\n    }\n  });\n\n  return {\n    ...confirmField,\n    validateMatch,\n    handleBlur,\n  };\n};\n\n// ===== 폼 전체 관리 Composables =====\n\n// 폼 전체 유효성 검사 및 제출 관리 composable\nexport const useFormValidation = (fields = {}, options = {}) => {\n  const { onSubmit = null, additionalValidations = [] } = options;\n\n  // 폼 전체 유효성 검사 여부\n  const isFormValid = computed(() => {\n    // 모든 필드가 유효한지 확인\n    const allFieldsValid = Object.values(fields).every((field) => {\n      // 에러가 있으면 무효\n      if (field.isError?.value) return false;\n      // required 필드는 값이 있어야 함\n      if (field.required && !field.value?.value?.trim()) return false;\n      return true;\n    });\n\n    if (!allFieldsValid) return false;\n\n    // 추가 유효성 검사 조건 확인\n    const additionalValid = additionalValidations.every((validation) => {\n      return validation.condition !== false;\n    });\n\n    return additionalValid;\n  });\n\n  // 특정 필드들이 변경되었는지 확인\n  const hasChanges = (initialValues = {}) => {\n    return Object.entries(fields).some(([fieldName, field]) => {\n      const initialValue = initialValues[fieldName];\n      const currentValue = field.value?.value;\n      // undefined/null은 빈 문자열로 취급\n      const initial = initialValue ?? \"\";\n      const current = currentValue ?? \"\";\n      return initial !== current;\n    });\n  };\n\n  // 모든 필드 유효성 검사 실행\n  const validateAll = () => {\n    let isValid = true;\n    let firstErrorField = null;\n\n    // 모든 필드 검증\n    Object.values(fields).forEach((field) => {\n      if (field.validate && !field.validate()) {\n        isValid = false;\n        if (!firstErrorField) {\n          firstErrorField = field;\n        }\n      }\n    });\n\n    // 추가 유효성 검사\n    additionalValidations.forEach((validation) => {\n      if (!validation.condition) {\n        isValid = false;\n        if (!firstErrorField && validation.field) {\n          validation.field.setError?.(validation.errorCode);\n          firstErrorField = validation.field;\n        }\n      }\n    });\n\n    return { isValid, firstErrorField };\n  };\n\n  // 폼 제출 처리\n  const handleSubmit = async () => {\n    const { isValid, firstErrorField } = validateAll();\n\n    // 유효성 검사 실패 시 첫 번째 에러 필드로 포커스\n    if (!isValid) {\n      await nextTick();\n      if (firstErrorField?.inputRef?.value) {\n        firstErrorField.inputRef.value.focus();\n      }\n      return false;\n    }\n\n    // 제출 함수 실행\n    if (onSubmit) {\n      try {\n        const values = Object.entries(fields).reduce((acc, [key, field]) => {\n          acc[key] = field.value?.value;\n          return acc;\n        }, {});\n\n        const result = await onSubmit(values);\n        return result !== false;\n      } catch (error) {\n        console.error(\"Form submission error:\", error);\n        return false;\n      }\n    }\n\n    return true;\n  };\n\n  // 모든 필드 초기화\n  const resetAll = () => {\n    Object.values(fields).forEach((field) => {\n      field.reset?.();\n    });\n  };\n\n  // 모든 필드 값 및 에러 초기화\n  const clearAll = () => {\n    Object.values(fields).forEach((field) => {\n      field.clear?.();\n    });\n  };\n\n  // 폼 값 가져오기\n  const getValues = () => {\n    return Object.entries(fields).reduce((acc, [key, field]) => {\n      acc[key] = field.value?.value;\n      return acc;\n    }, {});\n  };\n\n  // 폼 값 설정하기\n  const setValues = (values = {}) => {\n    Object.entries(values).forEach(([key, value]) => {\n      if (fields[key]?.value) {\n        fields[key].value.value = value;\n      }\n    });\n  };\n\n  return {\n    // 상태\n    isFormValid,\n    // 메서드\n    hasChanges,\n    validateAll,\n    handleSubmit,\n    resetAll,\n    clearAll,\n    getValues,\n    setValues,\n  };\n};\n\n// 변경 사항 추적 composable\nexport const useFormChanges = (fields = {}, initialValues = {}) => {\n  // 개별 필드 변경 여부\n  const fieldChanges = computed(() => {\n    const changes = {};\n    Object.entries(fields).forEach(([fieldName, field]) => {\n      const initialValue = initialValues[fieldName] ?? \"\";\n      const currentValue = field.value?.value ?? \"\";\n      changes[fieldName] = initialValue !== currentValue;\n    });\n    return changes;\n  });\n\n  // 전체 변경 여부\n  const hasAnyChanges = computed(() => {\n    return Object.values(fieldChanges.value).some((changed) => changed);\n  });\n\n  // 변경된 필드 목록\n  const changedFields = computed(() => {\n    return Object.entries(fieldChanges.value)\n      .filter(([, changed]) => changed)\n      .map(([fieldName]) => fieldName);\n  });\n\n  return {\n    fieldChanges,\n    hasAnyChanges,\n    changedFields,\n  };\n};\n\n"],"version":3}