{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "scrub-bar",
	"title": "Scrub Bar",
	"type": "registry:ui",
	"description": "A media scrub bar primitive with track, progress, thumb, and time label subcomponents.",
	"registryDependencies": [
		"progress"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport type { Snippet } from \"svelte\";\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { cn } from \"$UTILS$.js\";\n\timport { setScrubBarContext } from \"./context.js\";\n\n\tlet {\n\t\tclass: className,\n\t\tduration,\n\t\tvalue,\n\t\tonScrub,\n\t\tonScrubStart,\n\t\tonScrubEnd,\n\t\tchildren,\n\t\t...restProps\n\t}: HTMLAttributes<HTMLDivElement> & {\n\t\t/** Total duration of the timeline, in seconds. */\n\t\tduration: number;\n\t\t/** Current playback time, in seconds. */\n\t\tvalue: number;\n\t\t/** Called with the new time (in seconds) as the user scrubs. */\n\t\tonScrub?: (time: number) => void;\n\t\t/** Called when the user presses down on the track or thumb. */\n\t\tonScrubStart?: () => void;\n\t\t/** Called when the user releases the pointer after scrubbing. */\n\t\tonScrubEnd?: () => void;\n\t\tchildren?: Snippet;\n\t} = $props();\n\n\tconst progress = $derived(duration > 0 ? (value / duration) * 100 : 0);\n\n\tsetScrubBarContext({\n\t\tget duration() {\n\t\t\treturn duration;\n\t\t},\n\t\tget value() {\n\t\t\treturn value;\n\t\t},\n\t\tget progress() {\n\t\t\treturn progress;\n\t\t},\n\t\tget onScrub() {\n\t\t\treturn onScrub;\n\t\t},\n\t\tget onScrubStart() {\n\t\t\treturn onScrubStart;\n\t\t},\n\t\tget onScrubEnd() {\n\t\t\treturn onScrubEnd;\n\t\t},\n\t});\n</script>\n\n<div data-slot=\"scrub-bar-root\" class={cn(\"flex w-full items-center\", className)} {...restProps}>\n\t{@render children?.()}\n</div>\n",
			"type": "registry:ui",
			"target": "scrub-bar/scrub-bar.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport type { ComponentProps } from \"svelte\";\n\timport { cn } from \"$UTILS$.js\";\n\timport { Progress } from \"$UI$/progress/index.js\";\n\timport { getScrubBarContext } from \"./context.js\";\n\n\tlet { class: className, ...restProps }: Omit<ComponentProps<typeof Progress>, \"value\"> = $props();\n\n\tconst ctx = getScrubBarContext();\n</script>\n\n<Progress\n\tdata-slot=\"scrub-bar-progress\"\n\tvalue={ctx.progress}\n\tclass={cn(\"absolute h-full [&>div]:transition-none\", className)}\n\t{...restProps}\n/>\n",
			"type": "registry:ui",
			"target": "scrub-bar/scrub-bar-progress.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport type { Snippet } from \"svelte\";\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { cn } from \"$UTILS$.js\";\n\timport { getScrubBarContext } from \"./context.js\";\n\n\tlet {\n\t\tclass: className,\n\t\tchildren,\n\t\t...restProps\n\t}: HTMLAttributes<HTMLDivElement> & {\n\t\tchildren?: Snippet;\n\t} = $props();\n\n\tconst ctx = getScrubBarContext();\n</script>\n\n<div\n\tdata-slot=\"scrub-bar-thumb\"\n\tclass={cn(\n\t\t\"bg-primary absolute top-1/2 block h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full transition-colors disabled:pointer-events-none disabled:opacity-50\",\n\t\tclassName\n\t)}\n\tstyle=\"left: {ctx.progress}%\"\n\t{...restProps}\n>\n\t{@render children?.()}\n</div>\n",
			"type": "registry:ui",
			"target": "scrub-bar/scrub-bar-thumb.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { cn } from \"$UTILS$.js\";\n\n\tfunction formatTimestamp(value: number): string {\n\t\tif (!Number.isFinite(value) || value < 0) return \"0:00\";\n\t\tconst totalSeconds = Math.floor(value);\n\t\tconst minutes = Math.floor(totalSeconds / 60);\n\t\tconst seconds = totalSeconds % 60;\n\t\treturn `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n\t}\n\n\tlet {\n\t\tclass: className,\n\t\ttime,\n\t\tformat = formatTimestamp,\n\t\t...restProps\n\t}: HTMLAttributes<HTMLSpanElement> & {\n\t\t/** Time value in seconds to render. */\n\t\ttime: number;\n\t\t/**\n\t\t * Custom formatter applied to `time` before rendering. Defaults to\n\t\t * `m:ss` (e.g. `1:07`).\n\t\t */\n\t\tformat?: (time: number) => string;\n\t} = $props();\n</script>\n\n<span data-slot=\"scrub-bar-time-label\" class={cn(\"tabular-nums\", className)} {...restProps}>\n\t{format(time)}\n</span>\n",
			"type": "registry:ui",
			"target": "scrub-bar/scrub-bar-time-label.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport type { Snippet } from \"svelte\";\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { cn } from \"$UTILS$.js\";\n\timport { getScrubBarContext } from \"./context.js\";\n\n\tlet {\n\t\tclass: className,\n\t\tchildren,\n\t\t...restProps\n\t}: HTMLAttributes<HTMLDivElement> & {\n\t\tchildren?: Snippet;\n\t} = $props();\n\n\tconst ctx = getScrubBarContext();\n\n\tlet trackEl: HTMLDivElement | null = $state(null);\n\n\tfunction getTimeFromClientX(clientX: number): number | null {\n\t\tif (!trackEl || !ctx.duration) return null;\n\t\tconst rect = trackEl.getBoundingClientRect();\n\t\tconst ratio = (clientX - rect.left) / rect.width;\n\t\tconst clamped = Math.min(Math.max(ratio, 0), 1);\n\t\treturn ctx.duration * clamped;\n\t}\n\n\tfunction handlePointerDown(event: PointerEvent) {\n\t\tif (!ctx.duration) return;\n\t\tevent.preventDefault();\n\t\tctx.onScrubStart?.();\n\t\tconst time = getTimeFromClientX(event.clientX);\n\t\tif (time != null) ctx.onScrub?.(time);\n\n\t\tconst handleMove = (moveEvent: PointerEvent) => {\n\t\t\tconst next = getTimeFromClientX(moveEvent.clientX);\n\t\t\tif (next != null) ctx.onScrub?.(next);\n\t\t};\n\n\t\tconst handleUp = () => {\n\t\t\tctx.onScrubEnd?.();\n\t\t\twindow.removeEventListener(\"pointermove\", handleMove);\n\t\t\twindow.removeEventListener(\"pointerup\", handleUp);\n\t\t};\n\n\t\twindow.addEventListener(\"pointermove\", handleMove);\n\t\twindow.addEventListener(\"pointerup\", handleUp, { once: true });\n\t}\n\n\tconst clampedValue = $derived(Math.min(Math.max(ctx.value, 0), ctx.duration || 0));\n</script>\n\n<div\n\tbind:this={trackEl}\n\tdata-slot=\"scrub-bar-track\"\n\tclass={cn(\n\t\t\"bg-secondary relative h-2 w-full grow cursor-pointer touch-none rounded-full transition-none select-none\",\n\t\tclassName\n\t)}\n\tonpointerdown={handlePointerDown}\n\trole=\"slider\"\n\ttabindex={0}\n\taria-valuemin={0}\n\taria-valuemax={ctx.duration || 0}\n\taria-valuenow={clampedValue}\n\t{...restProps}\n>\n\t{@render children?.()}\n</div>\n",
			"type": "registry:ui",
			"target": "scrub-bar/scrub-bar-track.svelte"
		},
		{
			"content": "import { getContext, setContext } from \"svelte\";\n\nconst SCRUB_BAR_CONTEXT_KEY = Symbol(\"scrub-bar\");\n\nexport interface ScrubBarContext {\n\treadonly duration: number;\n\treadonly value: number;\n\treadonly progress: number;\n\treadonly onScrub?: (time: number) => void;\n\treadonly onScrubStart?: () => void;\n\treadonly onScrubEnd?: () => void;\n}\n\nexport function setScrubBarContext(ctx: ScrubBarContext): void {\n\tsetContext(SCRUB_BAR_CONTEXT_KEY, ctx);\n}\n\nexport function getScrubBarContext(): ScrubBarContext {\n\tconst ctx = getContext<ScrubBarContext | undefined>(SCRUB_BAR_CONTEXT_KEY);\n\tif (!ctx) {\n\t\tthrow new Error(\"ScrubBar sub-components must be used within a <ScrubBar.Root>\");\n\t}\n\treturn ctx;\n}\n",
			"type": "registry:ui",
			"target": "scrub-bar/context.ts"
		},
		{
			"content": "import Root from \"./scrub-bar.svelte\";\nimport Track from \"./scrub-bar-track.svelte\";\nimport Progress from \"./scrub-bar-progress.svelte\";\nimport Thumb from \"./scrub-bar-thumb.svelte\";\nimport TimeLabel from \"./scrub-bar-time-label.svelte\";\n\nexport {\n\tRoot,\n\tTrack,\n\tProgress,\n\tThumb,\n\tTimeLabel,\n\t//\n\tRoot as ScrubBar,\n\tTrack as ScrubBarTrack,\n\tProgress as ScrubBarProgress,\n\tThumb as ScrubBarThumb,\n\tTimeLabel as ScrubBarTimeLabel,\n};\n\nexport { getScrubBarContext, setScrubBarContext } from \"./context.js\";\nexport type { ScrubBarContext } from \"./context.js\";\n",
			"type": "registry:ui",
			"target": "scrub-bar/index.ts"
		}
	]
}