{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tag-input",
  "type": "registry:ui",
  "title": "TagInput",
  "description": "ورود چند مقدار، مثل مهارت‌ها یا ایمیل‌ها؛ Enter یا ویرگول (, یا ،) ثبت می‌کند و Backspace آخری را حذف می‌کند",
  "dependencies": [
    "@kashi-ui/persian@^1.8.0"
  ],
  "registryDependencies": [
    "https://kashi.sepantapouya.com/r/badge.json",
    "https://kashi.sepantapouya.com/r/cx.json",
    "https://kashi.sepantapouya.com/r/field.json",
    "https://kashi.sepantapouya.com/r/kashi-provider.json",
    "https://kashi.sepantapouya.com/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/kashi/TagInput.tsx",
      "type": "registry:ui",
      "target": "components/kashi/TagInput.tsx",
      "content": "\"use client\";\n\nimport { forwardRef, useId, useRef, useState, type ClipboardEvent, type KeyboardEvent, type ReactNode } from \"react\";\nimport { normalizePersian, toFaDigits } from \"@kashi-ui/persian\";\nimport { Badge, type BadgeTone } from \"./Badge\";\nimport { cx } from \"./cx\";\nimport { Field, describedBy } from \"./Field\";\nimport { mergeRefs } from \"./utils\";\nimport { useKashi } from \"./KashiProvider\";\n\nexport interface TagInputProps {\n  label?: ReactNode;\n  hint?: ReactNode;\n  error?: ReactNode;\n  value?: string[];\n  defaultValue?: string[];\n  onChange?: (tags: string[]) => void;\n  placeholder?: string;\n  /** کلیدهایی که برچسب را ثبت می‌کنند @default [\"Enter\", \",\", \"،\"] */\n  separators?: string[];\n  /** بررسی هر برچسب؛ متن خطا برمی‌گرداند یا undefined */\n  validate?: (tag: string) => string | undefined;\n  /** بیشترین تعداد */\n  max?: number;\n  /** @default false */\n  allowDuplicates?: boolean;\n  tone?: BadgeTone;\n  disabled?: boolean;\n  name?: string;\n  id?: string;\n  className?: string;\n  labels?: { added?: (t: string) => string; removed?: (t: string) => string; remove?: (t: string) => string; max?: (n: number) => string; duplicate?: string };\n}\n\n/** ورود چند مقدار، مثل مهارت‌ها یا ایمیل‌ها؛ Enter یا ویرگول (, یا ،) ثبت می‌کند و Backspace آخری را حذف می‌کند */\nexport const TagInput = forwardRef<HTMLInputElement, TagInputProps>(function TagInput(\n  {\n    label, hint, error, value, defaultValue = [], onChange, placeholder, separators = [\"Enter\", \",\", \"،\"], validate, max,\n    allowDuplicates = false, tone = \"brand\", disabled, name, id, className, labels = {},\n  },\n  ref,\n) {\n  const autoId = useId();\n  const inputId = id ?? autoId;\n  const [inner, setInner] = useState<string[]>(defaultValue);\n  const tags = value !== undefined ? value : inner;\n  const [text, setText] = useState(\"\");\n  const [localError, setLocalError] = useState<string>();\n  const [announce, setAnnounce] = useState(\"\");\n  const inputRef = useRef<HTMLInputElement>(null);\n  const { messages: M, n: N } = useKashi();\n  const {\n    added = M.common.added, removed = M.common.removed, remove = M.common.remove,\n    max: maxMsg = (x: number) => M.common.max(N(x)), duplicate = M.tagInput.duplicate,\n  } = labels;\n\n  const set = (next: string[]) => {\n    if (value === undefined) setInner(next);\n    onChange?.(next);\n  };\n\n  /** چند برچسب را یکجا اضافه می‌کند؛ اگر یکی نامعتبر بود، متنش در فیلد می‌ماند */\n  const addMany = (raw: string[]) => {\n    let next = [...tags];\n    for (const r of raw) {\n      const t = normalizePersian(r).trim();\n      if (!t) continue;\n      if (max !== undefined && next.length >= max) return fail(maxMsg(max), t);\n      if (!allowDuplicates && next.includes(t)) return fail(duplicate, t);\n      const err = validate?.(t);\n      if (err) return fail(err, t);\n      next = [...next, t];\n      setAnnounce(added(t));\n    }\n    set(next);\n    setText(\"\");\n    setLocalError(undefined);\n    function fail(msg: string, t: string) {\n      set(next);\n      setText(t);\n      setLocalError(msg);\n    }\n  };\n\n  const removeAt = (i: number) => {\n    const t = tags[i];\n    set(tags.filter((_, j) => j !== i));\n    setAnnounce(removed(t));\n    inputRef.current?.focus();\n  };\n\n  const singleSeps = separators.filter((s) => s.length === 1).map((s) => `\\\\${s}`).join(\"\");\n\n  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n    if (separators.includes(e.key) && text.trim()) {\n      e.preventDefault();\n      addMany([text]);\n    } else if (e.key === \"Backspace\" && !text && tags.length) {\n      removeAt(tags.length - 1);\n    }\n  };\n\n  const onPaste = (e: ClipboardEvent<HTMLInputElement>) => {\n    const data = e.clipboardData.getData(\"text\");\n    const parts = data.split(new RegExp(`[${singleSeps}\\\\n\\\\t]`));\n    if (parts.length > 1) {\n      e.preventDefault();\n      addMany(parts);\n    }\n  };\n\n  const shownError = error ?? localError;\n\n  return (\n    <Field id={inputId} label={label} hint={hint} error={shownError} className={className}>\n      <div className=\"k-tag-input\" data-invalid={shownError ? \"\" : undefined} onClick={() => inputRef.current?.focus()}>\n        {tags.map((t, i) => (\n          <Badge key={`${t}-${i}`} tone={tone} onRemove={disabled ? undefined : () => removeAt(i)} removeLabel={remove(t)}>{t}</Badge>\n        ))}\n        <input\n          ref={mergeRefs(ref, inputRef)}\n          id={inputId}\n          className=\"k-tag-input__control\"\n          value={text}\n          placeholder={tags.length ? undefined : placeholder}\n          disabled={disabled}\n          aria-invalid={shownError ? true : undefined}\n          aria-describedby={describedBy(inputId, hint, shownError)}\n          onChange={(e) => {\n            const v = e.target.value;\n            setLocalError(undefined);\n            // صفحه‌کلید موبایل و IME رویداد کلید نمی‌فرستند؛ جداکننده را در متن هم پیدا کن\n            const parts = v.split(new RegExp(`[${singleSeps}]`));\n            if (singleSeps && parts.length > 1) {\n              const rest = parts.pop() ?? \"\";\n              addMany(parts);\n              if (rest) setText(rest);\n            } else setText(v);\n          }}\n          onKeyDown={onKeyDown}\n          onPaste={onPaste}\n          onBlur={() => text.trim() && addMany([text])}\n        />\n      </div>\n      <span className=\"k-sr-only\" aria-live=\"polite\">{announce}</span>\n      {name && tags.map((t, i) => <input key={i} type=\"hidden\" name={name} value={t} />)}\n    </Field>\n  );\n});\n"
    }
  ]
}