{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "back-top",
  "title": "Back Top",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/back-top/BackTop.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n  import type { BackTopVariants } from './back-top.variants'\n\n  export interface BackTopProps extends Omit<HTMLButtonAttributes, 'onclick'> {\n    /** Merged with the internal scroll-to-top behavior. */\n    onclick?: (e: MouseEvent) => void\n    /** Visibility threshold in pixels. Button appears once scroll passes it. */\n    threshold?: number\n    /** Target container. Defaults to the window. Pass a CSS selector or an HTMLElement. */\n    target?: string | HTMLElement | Window\n    /** Scroll behavior: 'smooth' or 'auto' (instant). */\n    behavior?: ScrollBehavior\n    /** Size variant. */\n    size?: BackTopVariants['size']\n    /** Edge anchor position. */\n    position?: BackTopVariants['position']\n    /** Distance from the viewport edge (px). */\n    offset?: number\n    /** Use absolute positioning (for section-level containers) instead of fixed (viewport). */\n    absolute?: boolean\n    /** Accessible label. */\n    ariaLabel?: string\n    /** Override the default arrow icon. */\n    icon?: Snippet\n    /** The rendered <button>, via `bind:ref`. */\n    ref?: HTMLButtonElement | null\n    /** Fires whenever the button toggles visibility. */\n    onvisiblechange?: (visible: boolean) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { scale } from 'svelte/transition'\n  import { ArrowUp } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { backTopVariants } from './back-top.variants'\n\n  let {\n    class: className,\n    threshold = 200,\n    target = undefined,\n    behavior = 'smooth',\n    size = 'default',\n    position = 'bottom-right',\n    offset = 24,\n    absolute = false,\n    ariaLabel = 'Scroll to top',\n    icon,\n    children,\n    ref = $bindable(null),\n    type = 'button',\n    onclick,\n    onvisiblechange,\n    ...restProps\n  }: BackTopProps = $props()\n\n  // Destructured (not rendered): BackTop has no default slot — this keeps a\n  // stray `children` prop out of the button's spread attributes.\n  void children\n\n  let visible = $state(false)\n\n  function resolveTarget(t: BackTopProps['target']): HTMLElement | Window | null {\n    if (typeof window === 'undefined') return null\n    if (t === undefined || t === null) return window\n    if (typeof t === 'string') {\n      const el = document.querySelector<HTMLElement>(t)\n      return el ?? window\n    }\n    return t\n  }\n\n  function getScrollTop(el: HTMLElement | Window): number {\n    if (el === window) {\n      return window.scrollY ?? document.documentElement.scrollTop ?? document.body.scrollTop ?? 0\n    }\n    return (el as HTMLElement).scrollTop\n  }\n\n  function scrollToTop(el: HTMLElement | Window) {\n    const reduceMotion =\n      typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches\n    const behaviorValue: ScrollBehavior = reduceMotion ? 'auto' : behavior\n    if (el === window) {\n      window.scrollTo({ top: 0, behavior: behaviorValue })\n    } else {\n      ;(el as HTMLElement).scrollTo({ top: 0, behavior: behaviorValue })\n    }\n  }\n\n  function handleClick(e: MouseEvent) {\n    onclick?.(e)\n    const el = resolveTarget(target)\n    if (el) scrollToTop(el)\n  }\n\n  const positionStyle = $derived.by(() => {\n    const offsetVar = `var(--back-top-offset, ${offset}px)`\n    const edges =\n      position === 'bottom-left'\n        ? (['left', 'bottom'] as const)\n        : position === 'top-right'\n          ? (['right', 'top'] as const)\n          : position === 'top-left'\n            ? (['left', 'top'] as const)\n            : (['right', 'bottom'] as const)\n    return `${edges[0]}: ${offsetVar}; ${edges[1]}: ${offsetVar}; --back-top-offset: ${offset}px`\n  })\n\n  // Re-subscribes when `target` changes (mirrors the Vue watch on target).\n  $effect(() => {\n    const el = resolveTarget(target)\n    if (!el) return\n    const onScroll = () => {\n      // `>=` so threshold={0} always shows (size/position demos, forced-visible cases).\n      const next = getScrollTop(el) >= threshold\n      if (next !== visible) {\n        visible = next\n        onvisiblechange?.(next)\n      }\n    }\n    el.addEventListener('scroll', onScroll, { passive: true })\n    // If target is a container, also listen to window scroll for safety on resize.\n    if (el !== window) {\n      window.addEventListener('scroll', onScroll, { passive: true })\n    }\n    onScroll()\n    return () => {\n      el.removeEventListener('scroll', onScroll)\n      if (el !== window) {\n        window.removeEventListener('scroll', onScroll)\n      }\n    }\n  })\n</script>\n\n{#if visible}\n  <button\n    bind:this={ref}\n    type={type}\n    data-uipkge=\"\"\n    data-slot=\"back-top\"\n    data-state={visible ? 'open' : 'closed'}\n    data-size={size}\n    data-position={position}\n    aria-label={ariaLabel}\n    class={cn(backTopVariants({ size, position }), absolute ? 'absolute' : 'fixed', className)}\n    style={positionStyle}\n    onclick={handleClick}\n    transition:scale={{ duration: 200, start: 0.9 }}\n    {...restProps}\n  >\n    {#if icon}\n      {@render icon()}\n    {:else}\n      <ArrowUp aria-hidden=\"true\" />\n    {/if}\n  </button>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/back-top/BackTop.svelte"
    },
    {
      "path": "packages/registry-svelte/components/back-top/back-top.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts`) so components can import `backTopVariants` from './back-top.variants' without creating a circular dependency through the index.\n */\nexport const backTopVariants = cva(\n  'inline-flex items-center justify-center rounded-full border bg-background text-foreground shadow-lg transition-[color,background-color,box-shadow] duration-200 hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none cursor-pointer motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95 [&_svg:not([class*=size-])]:size-5 [&_svg]:pointer-events-none [&_svg]:shrink-0',\n  {\n    variants: {\n      size: {\n        sm: 'size-8 [&_svg:not([class*=size-])]:size-4',\n        default: 'size-10',\n        lg: 'size-12 [&_svg:not([class*=size-])]:size-6',\n      },\n      position: {\n        'bottom-right': '',\n        'bottom-left': '',\n        'top-right': '',\n        'top-left': '',\n      },\n    },\n    defaultVariants: {\n      size: 'default',\n      position: 'bottom-right',\n    },\n  },\n)\n\nexport type BackTopVariants = VariantProps<typeof backTopVariants>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/back-top/back-top.variants.ts"
    },
    {
      "path": "packages/registry-svelte/components/back-top/index.ts",
      "content": "export { default as BackTop, type BackTopProps } from './BackTop.svelte'\nexport { backTopVariants, type BackTopVariants } from './back-top.variants'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/back-top/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte",
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Scroll-to-top floating button. Appears after the target container scrolls past a threshold and smooth-scrolls back on click. Supports custom target container, visibility threshold, scroll behavior, custom icon snippet, four edge anchors, size variants, and edge offset.",
  "categories": [
    "navigation",
    "utility"
  ]
}