{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-form",
  "type": "registry:ui",
  "title": "",
  "description": "",
  "dependencies": [
    "@kashi-ui/persian@^2.9.0"
  ],
  "registryDependencies": [
    "https://kashi.sepantapouya.com/r/cx.json",
    "https://kashi.sepantapouya.com/r/kashi-provider.json"
  ],
  "files": [
    {
      "path": "registry/kashi/useForm.ts",
      "type": "registry:ui",
      "target": "components/kashi/useForm.ts",
      "content": "\"use client\";\n\nimport { useCallback, useId, useMemo, useRef, useState, type FormEvent } from \"react\";\nimport {\n  isValidCardNumber, isValidIban, isValidLandline, isValidLegalId, isValidMobile, isValidNationalId, isValidPostalCode, toEnDigits,\n} from \"@kashi-ui/persian\";\nimport type { KashiMessages } from \"@kashi-ui/persian\";\nimport { useKashi } from \"./KashiProvider\";\n\ntype Values = Record<string, unknown>;\n/** پیام خطا: متن آماده یا کلیدی که با زبان KashiProvider ترجمه می‌شود */\ntype Message = string | ((M: KashiMessages, n: (v: number | string) => string) => string);\n/** هر قاعده مقدار را می‌گیرد و اگر درست نبود پیام خطا برمی‌گرداند */\nexport type FormRule<V extends Values = Values> = (value: unknown, values: V) => Message | null | undefined | false;\n\nconst isEmpty = (v: unknown) => v === undefined || v === null || v === \"\" || v === false || (Array.isArray(v) && v.length === 0);\nconst str = (v: unknown) => toEnDigits(String(v ?? \"\")).trim();\n/** قاعده‌ای که مقدار خالی را رد نمی‌کند؛ خالی بودن کار required است */\nconst check = (ok: (v: unknown, values: Values) => boolean, msg: Message): FormRule => (v, values) => (isEmpty(v) || ok(v, values) ? null : msg);\nconst len = (v: unknown) => [...String(v ?? \"\")].length;\n\n/**\n * قاعده‌های آماده با پیام فارسی و انگلیسی. پیام دلخواه را به‌عنوان آخرین آرگومان بدهید.\n * همهٔ قاعده‌ها جز required مقدار خالی را رد نمی‌کنند و ارقام فارسی را می‌پذیرند.\n */\nexport const rules = {\n  required: (message?: string): FormRule => Object.assign((v: unknown) => (isEmpty(typeof v === \"string\" ? v.trim() : v) ? message ?? ((M: KashiMessages) => M.form.required) : null), { isRequired: true }),\n  minLength: (n: number, message?: string) => check((v) => len(v) >= n, message ?? ((M, f) => M.form.minLength(f(n)))),\n  maxLength: (n: number, message?: string) => check((v) => len(v) <= n, message ?? ((M, f) => M.form.maxLength(f(n)))),\n  min: (n: number, message?: string) => check((v) => Number(str(v)) >= n, message ?? ((M, f) => M.form.min(f(n)))),\n  max: (n: number, message?: string) => check((v) => Number(str(v)) <= n, message ?? ((M, f) => M.form.max(f(n)))),\n  pattern: (re: RegExp, message?: string) => check((v) => re.test(String(v)), message ?? ((M) => M.form.pattern)),\n  email: (message?: string) => check((v) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(str(v)), message ?? ((M) => M.form.email)),\n  nationalId: (message?: string) => check((v) => isValidNationalId(str(v)), message ?? ((M) => M.invalid.nationalId)),\n  legalId: (message?: string) => check((v) => isValidLegalId(str(v)), message ?? ((M) => M.invalid.legalId)),\n  mobile: (message?: string) => check((v) => isValidMobile(str(v)), message ?? ((M) => M.invalid.mobile)),\n  landline: (message?: string) => check((v) => isValidLandline(str(v)), message ?? ((M) => M.invalid.landline)),\n  card: (message?: string) => check((v) => isValidCardNumber(str(v)), message ?? ((M) => M.invalid.card)),\n  iban: (message?: string) => check((v) => isValidIban(str(v)), message ?? ((M) => M.invalid.iban)),\n  postalCode: (message?: string) => check((v) => isValidPostalCode(str(v)), message ?? ((M) => M.invalid.postal)),\n  /** برابر بودن با فیلد دیگر، مثل تکرار رمز */\n  match: (field: string, message?: string) => check((v, values) => v === values[field], message ?? ((M) => M.form.match)),\n  /** قاعدهٔ دلخواه: true یعنی درست */\n  custom: <V extends Values = Values>(ok: (value: unknown, values: V) => boolean, message: string): FormRule<V> =>\n    check(ok as (v: unknown, values: Values) => boolean, message) as FormRule<V>,\n};\n\nexport interface UseFormOptions<V extends Values> {\n  defaultValues: V;\n  /** قاعده‌های هر فیلد به ترتیب؛ اولین خطا نمایش داده می‌شود */\n  rules?: { [K in keyof V]?: FormRule<V>[] };\n  /**\n   * زمان بررسی: «blur» پس از خروج از فیلد (پیش‌فرض)، «change» با هر تغییر، «submit» فقط هنگام ارسال.\n   * پس از اولین ارسال، فیلدهای دارای خطا با هر تغییر دوباره بررسی می‌شوند تا خطا همان لحظه برود.\n   */\n  validateOn?: \"blur\" | \"change\" | \"submit\";\n}\n\n/** آنچه روی هر فیلد پخش می‌شود: <Input {...form.field(\"name\")} /> */\nexport interface FieldBinding {\n  id: string;\n  name: string;\n  error?: string;\n  \"aria-invalid\"?: true;\n  required?: boolean;\n  ref: (el: HTMLElement | null) => void;\n  onBlur: () => void;\n  value?: string;\n  checked?: boolean;\n  /** چک‌باکس و سوییچ: حالت خطا؛ متن خطا در description نمایش داده می‌شود */\n  invalid?: boolean;\n  description?: string;\n  onChange?: (eventOrValue: unknown) => void;\n  onValueChange?: (value: unknown) => void;\n}\n\nexport interface UseFormReturn<V extends Values> {\n  values: V;\n  errors: Partial<Record<keyof V, string>>;\n  touched: Partial<Record<keyof V, boolean>>;\n  /**\n   * اتصال یک فیلد. type: «value» برای فیلدهای فارسی و هر کامپوننتی که onValueChange دارد (کد ملی، موبایل، شبا…)،\n   * «checkbox» برای چک‌باکس و سوییچ، پیش‌فرض برای Input، Textarea و input بومی.\n   */\n  field: (name: keyof V & string, options?: { type?: \"text\" | \"value\" | \"checkbox\" }) => FieldBinding;\n  setValue: <K extends keyof V>(name: K, value: V[K]) => void;\n  setError: (name: keyof V, message: string | undefined) => void;\n  /** همهٔ فیلدها را بررسی می‌کند؛ true یعنی بدون خطا */\n  validate: () => boolean;\n  /** onSubmit فرم: بررسی، تمرکز روی اولین فیلد نادرست و در صورت درستی اجرای fn */\n  handleSubmit: (fn: (values: V) => void | Promise<void>) => (e?: FormEvent) => Promise<void>;\n  reset: (values?: V) => void;\n  isSubmitting: boolean;\n  isDirty: boolean;\n  submitCount: number;\n  /** پیام خلاصه برای بالای فرم، مثل «۲ مورد را اصلاح کنید.»؛ بدون خطا undefined */\n  summary?: string;\n}\n\n/**\n * مدیریت مقدار و اعتبارسنجی فرم با پیام‌های فارسی (و انگلیسی با KashiProvider).\n * بدون وابستگی؛ خطاها مستقیم به ویژگی error کامپوننت‌های کاشی می‌رسند.\n */\nexport function useForm<V extends Values>({ defaultValues, rules: fieldRules = {}, validateOn = \"blur\" }: UseFormOptions<V>): UseFormReturn<V> {\n  const { messages: M, n } = useKashi();\n  const uid = useId();\n  const [values, setValues] = useState<V>(defaultValues);\n  const [errors, setErrors] = useState<Partial<Record<keyof V, string>>>({});\n  const [touched, setTouched] = useState<Partial<Record<keyof V, boolean>>>({});\n  const [isSubmitting, setSubmitting] = useState(false);\n  const [submitCount, setSubmitCount] = useState(0);\n  const initial = useRef(defaultValues);\n  const els = useRef<Partial<Record<keyof V, HTMLElement | null>>>({});\n  const latest = useRef(values);\n  latest.current = values;\n\n  const fmt = useCallback((v: number | string) => (typeof v === \"number\" ? n(v) : v), [n]);\n  const run = useCallback(\n    (name: keyof V, all: V): string | undefined => {\n      for (const rule of (fieldRules[name] ?? []) as FormRule<V>[]) {\n        const m = rule(all[name], all);\n        if (m) return typeof m === \"function\" ? m(M, fmt) : m;\n      }\n      return undefined;\n    },\n    [fieldRules, M, fmt],\n  );\n  const validateOne = (name: keyof V, all: V) => {\n    const e = run(name, all);\n    setErrors((prev) => (prev[name] === e ? prev : { ...prev, [name]: e }));\n    return e;\n  };\n\n  const setValue = <K extends keyof V>(name: K, value: V[K]) => {\n    const next = { ...latest.current, [name]: value };\n    latest.current = next;\n    setValues(next);\n    if (validateOn === \"change\" || errors[name] !== undefined || (submitCount > 0 && validateOn !== \"submit\")) validateOne(name, next);\n  };\n\n  const validate = () => {\n    const all = latest.current;\n    const next: Partial<Record<keyof V, string>> = {};\n    for (const k of Object.keys(fieldRules) as (keyof V)[]) {\n      const e = run(k, all);\n      if (e) next[k] = e;\n    }\n    setErrors(next);\n    return Object.keys(next).length === 0;\n  };\n\n  const handleSubmit = (fn: (values: V) => void | Promise<void>) => async (e?: FormEvent) => {\n    e?.preventDefault();\n    setSubmitCount((c) => c + 1);\n    setTouched(Object.fromEntries(Object.keys(fieldRules).map((k) => [k, true])) as Partial<Record<keyof V, boolean>>);\n    const all = latest.current;\n    const bad = (Object.keys(fieldRules) as (keyof V)[]).find((k) => run(k, all));\n    if (!validate()) {\n      if (bad) els.current[bad]?.focus?.();\n      return;\n    }\n    setSubmitting(true);\n    try {\n      await fn(all);\n    } finally {\n      setSubmitting(false);\n    }\n  };\n\n  const field = (name: keyof V & string, { type = \"text\" }: { type?: \"text\" | \"value\" | \"checkbox\" } = {}): FieldBinding => {\n    const error = errors[name];\n    const required = ((fieldRules[name] ?? []) as (FormRule<V> & { isRequired?: boolean })[]).some((r) => r.isRequired);\n    const read = (x: unknown) => {\n      if (x && typeof x === \"object\" && \"target\" in x) {\n        const t = (x as { target: HTMLInputElement }).target;\n        return type === \"checkbox\" ? t.checked : t.value;\n      }\n      return x;\n    };\n    const base: FieldBinding = {\n      id: `${uid}-${name}`,\n      name,\n      error,\n      \"aria-invalid\": error ? true : undefined,\n      required: required || undefined,\n      ref: (el) => { els.current[name] = el; },\n      onBlur: () => {\n        setTouched((t) => (t[name] ? t : { ...t, [name]: true }));\n        if (validateOn !== \"submit\") validateOne(name, latest.current);\n      },\n    };\n    if (type === \"checkbox\") {\n      // Checkbox و Switch ویژگی error ندارند: invalid و متن خطا در description\n      const { error: err, ...rest } = base;\n      return { ...rest, checked: !!values[name], onChange: (x) => setValue(name, read(x) as V[typeof name]), ...(err ? { invalid: true, description: err } : {}) };\n    }\n    if (type === \"value\") return { ...base, value: (values[name] ?? \"\") as string, onValueChange: (x) => setValue(name, x as V[typeof name]) };\n    return { ...base, value: (values[name] ?? \"\") as string, onChange: (x) => setValue(name, read(x) as V[typeof name]) };\n  };\n\n  const errorCount = Object.values(errors).filter(Boolean).length;\n  const isDirty = useMemo(() => Object.keys(values).some((k) => values[k] !== initial.current[k]), [values]);\n\n  return {\n    values, errors, touched, field, setValue,\n    setError: (name, message) => setErrors((prev) => ({ ...prev, [name]: message })),\n    validate, handleSubmit,\n    reset: (v = initial.current) => {\n      initial.current = v;\n      latest.current = v;\n      setValues(v);\n      setErrors({});\n      setTouched({});\n      setSubmitCount(0);\n    },\n    isSubmitting, isDirty, submitCount,\n    summary: errorCount > 0 && submitCount > 0 ? M.form.summary(n(errorCount)) : undefined,\n  };\n}\n"
    }
  ]
}