{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "loading-bar",
  "title": "Loading Bar",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/loading-bar/LoadingBar.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface LoadingBarProps extends HTMLAttributes<HTMLDivElement> {\n    /** 0–100 progress value. Bind it or drive via `createLoadingBar`. */\n    value?: number\n    /** Bar color. Accepts any CSS color value. */\n    color?: string\n    /** Bar height in px. */\n    height?: number\n    /** Indeterminate sliding animation (ignores value). */\n    indeterminate?: boolean\n    /** Anchor the bar to the top or bottom of the viewport. */\n    position?: 'top' | 'bottom'\n    /** Show a spinner at the trailing edge of the bar. */\n    spinner?: boolean\n    /** Error state tints the bar. */\n    error?: boolean\n    /** Hide the bar entirely (e.g. when finished). */\n    hidden?: boolean\n    /** Fires when the bar reaches 100 via `finish()` / `fail()`. */\n    onfinish?: () => void\n    ref?: HTMLDivElement | null\n  }\n\n  /** Imperative handle — grab it with `bind:this` and call `start` / `finish` /\n   *  `error` / `inc` / `set` directly, or drive it via `createLoadingBar`. */\n  export interface LoadingBarHandle {\n    start: (from?: number) => void\n    finish: () => void\n    fail: () => void\n    error: () => void\n    inc: (amount?: number) => void\n    set: (value: number) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { onDestroy } from 'svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    value = $bindable(0),\n    color = '',\n    height = 3,\n    indeterminate = false,\n    position = 'top',\n    spinner = false,\n    error: errorProp = false,\n    hidden = false,\n    onfinish,\n    ref = $bindable(null),\n    class: className,\n    ...restProps\n  }: LoadingBarProps = $props()\n\n  let internal = $state(value)\n  /** Imperative fail() tints the bar without requiring the error prop. */\n  let internalError = $state(false)\n  /** After finish/fail, fade out then reset. */\n  let fading = $state(false)\n  let raf: number | null = null\n  let hideTimer: ReturnType<typeof setTimeout> | null = null\n  /** Bumped on start/finish/fail so in-flight trickle frames abort. */\n  let generation = 0\n\n  $effect(() => {\n    internal = value\n  })\n\n  const pct = $derived(Math.min(100, Math.max(0, internal)))\n  const isError = $derived(errorProp || internalError)\n  const barColor = $derived(color || (isError ? 'var(--destructive)' : 'var(--primary)'))\n  const visible = $derived(!hidden && !fading && (indeterminate || internal > 0))\n\n  function clearTimers() {\n    if (raf) {\n      cancelAnimationFrame(raf)\n      raf = null\n    }\n    if (hideTimer) {\n      clearTimeout(hideTimer)\n      hideTimer = null\n    }\n  }\n\n  /** Slowly creep the bar toward a soft ceiling so progress feels alive. */\n  function trickle(gen: number) {\n    if (raf) cancelAnimationFrame(raf)\n    const step = () => {\n      if (gen !== generation || internalError || fading) return\n      if (internal >= 95) return\n      // Asymptotic crawl — slows as it approaches the ceiling.\n      const next = Math.min(95, internal + (95 - internal) * 0.04 + 0.15)\n      set(next)\n      if (next < 95 && gen === generation) raf = requestAnimationFrame(step)\n    }\n    raf = requestAnimationFrame(step)\n  }\n\n  // Imperative API exposed for `createLoadingBar` / parent `bind:this`.\n  export function set(v: number) {\n    internal = v\n    value = v\n  }\n\n  export function start(from = 20) {\n    clearTimers()\n    generation += 1\n    const gen = generation\n    internalError = false\n    fading = false\n    set(from)\n    // Trickle toward ~95 while the consumer is still working (classic NProgress).\n    trickle(gen)\n  }\n\n  export function inc(amount = 10) {\n    if (fading) return\n    set(Math.min(99, internal + amount))\n  }\n\n  export function finish() {\n    clearTimers()\n    generation += 1\n    internalError = false\n    set(100)\n    onfinish?.()\n    // Hold full bar briefly, then fade + reset so the next start() is clean.\n    hideTimer = setTimeout(() => {\n      fading = true\n      hideTimer = setTimeout(() => {\n        internal = 0\n        value = 0\n        fading = false\n        hideTimer = null\n      }, 300)\n    }, 200)\n  }\n\n  export function fail() {\n    clearTimers()\n    generation += 1\n    internalError = true\n    internal = 100\n    value = 100\n    onfinish?.()\n    hideTimer = setTimeout(() => {\n      fading = true\n      hideTimer = setTimeout(() => {\n        internal = 0\n        internalError = false\n        value = 0\n        fading = false\n        hideTimer = null\n      }, 300)\n    }, 400)\n  }\n\n  export const error = fail\n\n  onDestroy(() => {\n    clearTimers()\n  })\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"loading-bar\"\n  data-position={position}\n  data-state={isError ? 'error' : indeterminate ? 'indeterminate' : 'determinate'}\n  class={cn(\n    'pointer-events-none fixed left-0 z-[9999] w-full transition-opacity duration-300',\n    position === 'top' ? 'top-0' : 'bottom-0',\n    visible ? 'opacity-100' : 'opacity-0',\n    className,\n  )}\n  style:height={`${height}px`}\n  role=\"progressbar\"\n  aria-valuemin={0}\n  aria-valuemax={100}\n  aria-valuenow={indeterminate ? undefined : pct}\n  aria-busy={visible && !isError ? true : undefined}\n  aria-hidden={!visible}\n  {...restProps}\n>\n  <!-- Track -->\n  <div class=\"absolute inset-0 bg-transparent\"></div>\n\n  <!-- Determinate bar -->\n  {#if !indeterminate}\n    <div\n      data-slot=\"loading-bar-fill\"\n      class=\"absolute inset-y-0 left-0 transition-[width] duration-200 ease-out\"\n      style:width={`${pct}%`}\n      style:background-color={barColor}\n    >\n      {#if spinner}\n        <div\n          data-slot=\"loading-bar-spinner\"\n          class=\"absolute top-1/2 right-0 size-3 translate-x-1/2 -translate-y-1/2 animate-spin rounded-full border-2 border-current border-t-transparent\"\n          style:color={barColor}\n        ></div>\n      {/if}\n    </div>\n  {:else}\n    <!-- Indeterminate sliding bar -->\n    <div\n      data-slot=\"loading-bar-indeterminate\"\n      class=\"loading-bar-indeterminate absolute inset-y-0 w-1/3\"\n      style:background-color={barColor}\n    >\n      {#if spinner}\n        <div\n          data-slot=\"loading-bar-spinner\"\n          class=\"absolute top-1/2 right-0 size-3 translate-x-1/2 -translate-y-1/2 animate-spin rounded-full border-2 border-current border-t-transparent\"\n          style:color={barColor}\n        ></div>\n      {/if}\n    </div>\n  {/if}\n</div>\n\n<style>\n  @media (prefers-reduced-motion: no-preference) {\n    .loading-bar-indeterminate {\n      animation: uipkge-loading-bar-slide 1.2s ease-in-out infinite;\n    }\n  }\n\n  @keyframes uipkge-loading-bar-slide {\n    0% {\n      left: -33%;\n    }\n    100% {\n      left: 100%;\n    }\n  }\n</style>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/loading-bar/LoadingBar.svelte"
    },
    {
      "path": "packages/registry-svelte/components/loading-bar/useLoadingBar.svelte.ts",
      "content": "import type { LoadingBarHandle } from './LoadingBar.svelte'\n\nexport interface LoadingBarController {\n  /** Bind the bar instance: `<LoadingBar bind:this={bar.current} />`. */\n  current: LoadingBarHandle | null\n  /** React-parity ref callback — `<LoadingBar ref={...} />` equivalent for\n   *  programmatic wiring. Prefer `bind:this={bar.current}` in templates. */\n  setRef: (handle: LoadingBarHandle | null) => void\n  readonly loading: boolean\n  readonly isError: boolean\n  start: (from?: number) => void\n  finish: () => void\n  error: () => void\n  inc: (amount?: number) => void\n  set: (value: number) => void\n}\n\n/**\n * Controller that drives a <LoadingBar> instance via `bind:this`.\n *\n * Usage:\n *   const bar = createLoadingBar()\n *   <LoadingBar bind:this={bar.current} />\n *   bar.start()\n *   await fetch(...)\n *   bar.finish()\n */\nexport function createLoadingBar(): LoadingBarController {\n  let handle = $state<LoadingBarHandle | null>(null)\n  let loading = $state(false)\n  let isError = $state(false)\n\n  function start(from = 20) {\n    isError = false\n    loading = true\n    handle?.start(from)\n  }\n\n  function finish() {\n    loading = false\n    handle?.finish()\n  }\n\n  function error() {\n    isError = true\n    loading = false\n    handle?.error()\n  }\n\n  function inc(amount = 10) {\n    handle?.inc(amount)\n  }\n\n  function set(value: number) {\n    handle?.set(value)\n  }\n\n  function setRef(h: LoadingBarHandle | null) {\n    handle = h\n  }\n\n  return {\n    get current() {\n      return handle\n    },\n    set current(h: LoadingBarHandle | null) {\n      handle = h\n    },\n    setRef,\n    get loading() {\n      return loading\n    },\n    get isError() {\n      return isError\n    },\n    start,\n    finish,\n    error,\n    inc,\n    set,\n  }\n}\n\n/** React's `useLoadingBar` name — identical to `createLoadingBar`\n *  (Svelte has no hooks; both are plain factory functions). */\nexport const useLoadingBar = createLoadingBar\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/loading-bar/useLoadingBar.svelte.ts"
    },
    {
      "path": "packages/registry-svelte/components/loading-bar/index.ts",
      "content": "export { default as LoadingBar, type LoadingBarProps, type LoadingBarHandle } from './LoadingBar.svelte'\nexport { createLoadingBar, useLoadingBar, type LoadingBarController } from './useLoadingBar.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/loading-bar/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Top-of-viewport NProgress-style progress bar. Drive it imperatively via the createLoadingBar controller (start/finish/error/inc) or with bind:value. Supports indeterminate mode, custom color and height, top/bottom anchoring, and an optional trailing spinner.",
  "categories": [
    "feedback"
  ]
}