{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "hover-card",
  "title": "Hover Card",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/hover-card/HoverCard.svelte",
      "content": "<script lang=\"ts\" module>\n  import { getContext } from 'svelte'\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface HoverCardProps extends HTMLAttributes<HTMLDivElement> {\n    /** Controlled open state. Bind with `bind:open`. */\n    open?: boolean\n    /** Delay before opening on hover/focus, in ms. Default 700. */\n    openDelay?: number\n    /** Delay before closing after leaving, in ms. Default 300. */\n    closeDelay?: number\n    /** Called whenever the card opens or closes. */\n    onOpenChange?: (open: boolean) => void\n    children?: Snippet\n  }\n\n  export interface HoverCardContext {\n    readonly open: boolean\n    scheduleOpen: () => void\n    scheduleClose: () => void\n    cancelClose: () => void\n  }\n\n  export const HOVER_CARD_CONTEXT_KEY = Symbol('uipkge:hover-card')\n\n  export function getHoverCardContext(): HoverCardContext {\n    return getContext<HoverCardContext>(HOVER_CARD_CONTEXT_KEY)\n  }\n</script>\n\n<script lang=\"ts\">\n  import { onDestroy, setContext } from 'svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    open = $bindable(false),\n    openDelay = 700,\n    closeDelay = 300,\n    onOpenChange,\n    children,\n    ...restProps\n  }: HoverCardProps = $props()\n\n  let openTimer: ReturnType<typeof setTimeout> | null = null\n  let closeTimer: ReturnType<typeof setTimeout> | null = null\n\n  function setOpen(next: boolean) {\n    if (open === next) return\n    open = next\n    onOpenChange?.(next)\n  }\n\n  function scheduleOpen() {\n    if (closeTimer) {\n      clearTimeout(closeTimer)\n      closeTimer = null\n    }\n    if (open || openTimer) return\n    if (openDelay <= 0) {\n      setOpen(true)\n      return\n    }\n    openTimer = setTimeout(() => {\n      openTimer = null\n      setOpen(true)\n    }, openDelay)\n  }\n\n  function scheduleClose() {\n    if (openTimer) {\n      clearTimeout(openTimer)\n      openTimer = null\n    }\n    if (!open || closeTimer) return\n    if (closeDelay <= 0) {\n      setOpen(false)\n      return\n    }\n    closeTimer = setTimeout(() => {\n      closeTimer = null\n      setOpen(false)\n    }, closeDelay)\n  }\n\n  function cancelClose() {\n    if (closeTimer) {\n      clearTimeout(closeTimer)\n      closeTimer = null\n    }\n  }\n\n  onDestroy(() => {\n    if (openTimer) clearTimeout(openTimer)\n    if (closeTimer) clearTimeout(closeTimer)\n  })\n\n  setContext<HoverCardContext>(HOVER_CARD_CONTEXT_KEY, {\n    get open() {\n      return open\n    },\n    scheduleOpen,\n    scheduleClose,\n    cancelClose,\n  })\n</script>\n\n<div\n  data-uipkge\n  data-slot=\"hover-card\"\n  data-state={open ? 'open' : 'closed'}\n  {...restProps}\n  class={cn('relative inline-block', className)}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/hover-card/HoverCard.svelte"
    },
    {
      "path": "packages/registry-svelte/components/hover-card/HoverCardContent.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export type HoverCardSide = 'top' | 'right' | 'bottom' | 'left'\n  export type HoverCardAlign = 'start' | 'center' | 'end'\n\n  export interface HoverCardContentProps extends HTMLAttributes<HTMLDivElement> {\n    side?: HoverCardSide\n    align?: HoverCardAlign\n    sideOffset?: number\n    alignOffset?: number\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getHoverCardContext } from './HoverCard.svelte'\n\n  let {\n    class: className,\n    side = 'bottom',\n    align = 'center',\n    sideOffset = 4,\n    alignOffset = 0,\n    children,\n    ...restProps\n  }: HoverCardContentProps = $props()\n\n  const ctx = getHoverCardContext()\n\n  // Absolute positioning against the root wrapper (no floating-ui / portal in\n  // this hand-rolled port — the content anchors to the relatively-positioned\n  // HoverCard root instead of document.body).\n  const positionStyle = $derived.by(() => {\n    const offset = `${sideOffset}px`\n    let s = ''\n    if (side === 'bottom') s += `top: calc(100% + ${offset});`\n    else if (side === 'top') s += `bottom: calc(100% + ${offset});`\n    else if (side === 'right') s += `left: calc(100% + ${offset});`\n    else s += `right: calc(100% + ${offset});`\n\n    if (side === 'bottom' || side === 'top') {\n      if (align === 'start') s += `left: ${alignOffset}px;`\n      else if (align === 'end') s += `right: ${-alignOffset}px;`\n      else s += `left: calc(50% + ${alignOffset}px); transform: translateX(-50%);`\n    } else {\n      if (align === 'start') s += `top: ${alignOffset}px;`\n      else if (align === 'end') s += `bottom: ${-alignOffset}px;`\n      else s += `top: calc(50% + ${alignOffset}px); transform: translateY(-50%);`\n    }\n    return s\n  })\n</script>\n\n{#if ctx.open}\n  <div\n    data-uipkge\n    data-slot=\"hover-card-content\"\n    role=\"dialog\"\n    data-state=\"open\"\n    data-side={side}\n    data-align={align}\n    {...restProps}\n    style={positionStyle}\n    class={cn(\n      'bg-popover text-popover-foreground motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:ease-emphasized motion-safe:data-[state=open]:blur-in-2 motion-safe:data-[state=closed]:blur-out-2 motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2 absolute z-50 w-64 rounded-md border p-4 shadow-md outline-hidden motion-safe:data-[state=closed]:duration-[var(--dur-exit)] motion-safe:data-[state=open]:duration-200',\n      className,\n    )}\n    onmouseenter={ctx.cancelClose}\n    onmouseleave={ctx.scheduleClose}\n  >\n    {@render children?.()}\n  </div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/hover-card/HoverCardContent.svelte"
    },
    {
      "path": "packages/registry-svelte/components/hover-card/HoverCardTrigger.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface HoverCardTriggerProps extends HTMLAttributes<HTMLSpanElement> {\n    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getHoverCardContext } from './HoverCard.svelte'\n\n  let { class: className, children, ...restProps }: HoverCardTriggerProps = $props()\n\n  const ctx = getHoverCardContext()\n</script>\n\n<span\n  data-uipkge\n  data-slot=\"hover-card-trigger\"\n  data-state={ctx.open ? 'open' : 'closed'}\n  aria-expanded={ctx.open}\n  aria-haspopup=\"dialog\"\n  {...restProps}\n  class={cn(className)}\n  onmouseenter={ctx.scheduleOpen}\n  onmouseleave={ctx.scheduleClose}\n  onfocusin={ctx.scheduleOpen}\n  onfocusout={ctx.scheduleClose}\n>\n  {@render children?.()}\n</span>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/hover-card/HoverCardTrigger.svelte"
    },
    {
      "path": "packages/registry-svelte/components/hover-card/index.ts",
      "content": "export { default as HoverCard, type HoverCardProps, type HoverCardContext } from './HoverCard.svelte'\nexport {\n  default as HoverCardContent,\n  type HoverCardContentProps,\n  type HoverCardSide,\n  type HoverCardAlign,\n} from './HoverCardContent.svelte'\nexport { default as HoverCardTrigger, type HoverCardTriggerProps } from './HoverCardTrigger.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/hover-card/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Rich popover triggered by hover/focus instead of click. Use for inline previews — user cards on @mentions, link previews, KPI explanations. Hand-rolled runes port (no headless dependency) with configurable open/close delay, side/align placement, and bindable open state.",
  "categories": [
    "overlay"
  ]
}