{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "utils",
  "type": "registry:lib",
  "title": "تابع‌های کمکی ارقام فارسی",
  "description": "تابع‌های کمکی ارقام فارسی",
  "dependencies": [
    "@kashi-ui/persian@^1.7.0"
  ],
  "files": [
    {
      "path": "registry/kashi/utils.ts",
      "type": "registry:lib",
      "target": "components/kashi/utils.ts",
      "content": "import { useEffect, useLayoutEffect, type Ref, type RefCallback } from \"react\";\n\nexport { toFaDigits } from \"@kashi-ui/persian\";\n\n/** چند ref را یکی می‌کند */\nexport function mergeRefs<T>(...refs: Array<Ref<T> | undefined>): RefCallback<T> {\n  return (node) => {\n    for (const ref of refs) {\n      if (typeof ref === \"function\") ref(node);\n      else if (ref) (ref as { current: T | null }).current = node;\n    }\n  };\n}\n\nexport function isDev(): boolean {\n  return typeof process !== \"undefined\" && process.env.NODE_ENV !== \"production\";\n}\n\n/** useLayoutEffect در مرورگر، useEffect در سرور؛ تا در Next.js و رندر سمت سرور هشدار ندهد */\nexport const useIsomorphicLayoutEffect = typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nconst deprecations = new Set<string>();\n/**\n * هشدار یک‌باره برای قابلیت منسوخ، فقط در حالت توسعه (سیاست تغییرات: /docs/versioning/).\n * deprecate(\"Button.kind\", \"prop ‏kind منسوخ است؛ از variant استفاده کنید. در ۲.۰ حذف می‌شود.\")\n */\nexport function deprecate(id: string, message: string) {\n  if (!isDev() || deprecations.has(id)) return;\n  deprecations.add(id);\n  console.warn(`[kashi] منسوخ: ${message}`);\n}\n\nexport type TipPlacement = \"top\" | \"bottom\" | \"start\" | \"end\";\n\n/**\n * راهنما را در لایهٔ بالایی مرورگر (Popover API) و با position: fixed کنار عنصر می‌گذارد تا overflow: hidden\n * هیچ والدی آن را نبرد. اگر سمت خواسته‌شده جا نداشته باشد، به سمت مقابل می‌رود و داخل صفحه می‌ماند.\n * در RTL «start» سمت راست است. مرورگر بدون Popover API همان position: fixed را می‌گیرد.\n */\nexport function placeTip(anchor: HTMLElement, tip: HTMLElement, placement: TipPlacement = \"top\") {\n  const pop = tip as HTMLElement & { showPopover?: () => void };\n  tip.setAttribute(\"data-placed\", \"\");\n  try {\n    if (pop.showPopover && !tip.matches(\":popover-open\")) pop.showPopover();\n  } catch { /* بدون پشتیبانی کامل Popover */ }\n  const a = anchor.getBoundingClientRect();\n  const t = tip.getBoundingClientRect();\n  const rtl = getComputedStyle(anchor).direction === \"rtl\";\n  const gap = 8, m = 8, W = document.documentElement.clientWidth || innerWidth, H = innerHeight;\n  type Side = \"top\" | \"bottom\" | \"left\" | \"right\";\n  let side: Side = placement === \"top\" || placement === \"bottom\" ? placement : (placement === \"start\") !== rtl ? \"left\" : \"right\";\n  const room: Record<Side, number> = { top: a.top - gap - m, bottom: H - a.bottom - gap - m, left: a.left - gap - m, right: W - a.right - gap - m };\n  const opposite: Record<Side, Side> = { top: \"bottom\", bottom: \"top\", left: \"right\", right: \"left\" };\n  const need = side === \"top\" || side === \"bottom\" ? t.height : t.width;\n  if (room[side] < need && room[opposite[side]] > room[side]) side = opposite[side];\n  let x: number, y: number;\n  if (side === \"top\" || side === \"bottom\") {\n    x = Math.min(Math.max(a.left + a.width / 2 - t.width / 2, m), W - t.width - m);\n    y = side === \"top\" ? a.top - gap - t.height : a.bottom + gap;\n  } else {\n    y = Math.min(Math.max(a.top + a.height / 2 - t.height / 2, m), H - t.height - m);\n    x = side === \"left\" ? a.left - gap - t.width : a.right + gap;\n  }\n  tip.dataset.side = side;\n  const px = (v: number) => `${Math.round(v)}px`;\n  tip.style.setProperty(\"--_x\", px(x));\n  tip.style.setProperty(\"--_y\", px(y));\n  // پیکان رو به وسط عنصر، حتی وقتی راهنما برای ماندن در صفحه جابه‌جا شده است\n  tip.style.setProperty(\"--_ax\", px(Math.min(Math.max(a.left + a.width / 2 - x, 10), t.width - 10)));\n  tip.style.setProperty(\"--_ay\", px(Math.min(Math.max(a.top + a.height / 2 - y, 10), t.height - 10)));\n}\n\nexport function unplaceTip(tip: HTMLElement) {\n  const pop = tip as HTMLElement & { hidePopover?: () => void };\n  try {\n    if (pop.hidePopover && tip.matches(\":popover-open\")) pop.hidePopover();\n  } catch { /* */ }\n}\n"
    }
  ]
}