{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "conversation",
	"title": "Conversation",
	"type": "registry:ui",
	"description": "A scroll-anchored conversation container with empty state and scroll-to-bottom button.",
	"dependencies": [
		"@lucide/svelte",
		"stick-to-bottom-svelte"
	],
	"devDependencies": [
		"stick-to-bottom-svelte@^1.0.3",
		"@lucide/svelte@^1.7.0"
	],
	"registryDependencies": [
		"button"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport type { Snippet } from \"svelte\";\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { StickToBottom } from \"stick-to-bottom-svelte\";\n\timport { cn } from \"$UTILS$.js\";\n\timport { setConversationContext } from \"./context.js\";\n\n\tlet {\n\t\tclass: className,\n\t\tchildren,\n\t\tinitial = \"smooth\" as ScrollBehavior,\n\t\tresize = \"smooth\" as ScrollBehavior,\n\t\t...restProps\n\t}: HTMLAttributes<HTMLDivElement> & {\n\t\t/** Conversation body — typically a `Conversation.Content` and optional `Conversation.ScrollButton`. */\n\t\tchildren?: Snippet;\n\t\t/**\n\t\t * Scroll behavior used on first mount to anchor to the bottom.\n\t\t * @default \"smooth\"\n\t\t */\n\t\tinitial?: ScrollBehavior;\n\t\t/**\n\t\t * Scroll behavior used when content resizes (e.g. new messages streamed in)\n\t\t * while the user is anchored to the bottom.\n\t\t * @default \"smooth\"\n\t\t */\n\t\tresize?: ScrollBehavior;\n\t} = $props();\n\n\tlet scrollEl: HTMLDivElement | null = $state(null);\n\tlet contentEl: HTMLElement | null = $state(null);\n\n\tconst stickToBottom = new StickToBottom({\n\t\tscrollElement: () => scrollEl as HTMLElement,\n\t\tcontentElement: () => contentEl as HTMLElement,\n\t\tinitial,\n\t\tresize,\n\t});\n\n\tsetConversationContext({\n\t\tsetContentElement(el) {\n\t\t\tcontentEl = el;\n\t\t},\n\t\tget isAtBottom() {\n\t\t\treturn stickToBottom.isAtBottom;\n\t\t},\n\t\tscrollToBottom() {\n\t\t\tvoid stickToBottom.scrollToBottom({});\n\t\t},\n\t});\n</script>\n\n<div\n\tbind:this={scrollEl}\n\t{...restProps}\n\tdata-slot=\"conversation\"\n\trole=\"log\"\n\taria-live=\"polite\"\n\taria-relevant=\"additions\"\n\tclass={cn(\"relative flex-1 overflow-y-auto\", className)}\n>\n\t{@render children?.()}\n</div>\n",
			"type": "registry:ui",
			"target": "conversation/conversation.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 { getConversationContext } 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 = getConversationContext();\n\n\tlet el: HTMLDivElement | null = $state(null);\n\n\t$effect(() => {\n\t\tctx.setContentElement(el);\n\t\treturn () => ctx.setContentElement(null);\n\t});\n</script>\n\n<div\n\tbind:this={el}\n\tdata-slot=\"conversation-content\"\n\tclass={cn(\"flex min-h-full flex-col p-4\", className)}\n\t{...restProps}\n>\n\t{@render children?.()}\n</div>\n",
			"type": "registry:ui",
			"target": "conversation/conversation-content.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\n\tlet {\n\t\tclass: className,\n\t\ttitle = \"No messages yet\",\n\t\tdescription = \"Start a conversation to see messages here\",\n\t\ticon,\n\t\tchildren,\n\t\t...restProps\n\t}: Omit<HTMLAttributes<HTMLDivElement>, \"title\"> & {\n\t\ttitle?: string | Snippet;\n\t\tdescription?: string | Snippet;\n\t\ticon?: Snippet;\n\t\tchildren?: Snippet;\n\t} = $props();\n</script>\n\n<div\n\tdata-slot=\"conversation-empty-state\"\n\tclass={cn(\"flex size-full flex-col items-center justify-center gap-3 p-8 text-center\", className)}\n\t{...restProps}\n>\n\t{#if children}\n\t\t{@render children()}\n\t{:else}\n\t\t{#if icon}\n\t\t\t<div class=\"text-muted-foreground\">\n\t\t\t\t{@render icon()}\n\t\t\t</div>\n\t\t{/if}\n\t\t<div class=\"space-y-1\">\n\t\t\t<h3 class=\"text-sm font-medium\">\n\t\t\t\t{#if typeof title === \"string\"}\n\t\t\t\t\t{title}\n\t\t\t\t{:else if title}\n\t\t\t\t\t{@render title()}\n\t\t\t\t{/if}\n\t\t\t</h3>\n\t\t\t{#if description}\n\t\t\t\t<p class=\"text-muted-foreground text-sm\">\n\t\t\t\t\t{#if typeof description === \"string\"}\n\t\t\t\t\t\t{description}\n\t\t\t\t\t{:else}\n\t\t\t\t\t\t{@render description()}\n\t\t\t\t\t{/if}\n\t\t\t\t</p>\n\t\t\t{/if}\n\t\t</div>\n\t{/if}\n</div>\n",
			"type": "registry:ui",
			"target": "conversation/conversation-empty-state.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport ArrowDownIcon from \"@lucide/svelte/icons/arrow-down\";\n\timport { Button, type ButtonProps } from \"$UI$/button/index.js\";\n\timport { cn } from \"$UTILS$.js\";\n\timport { getConversationContext } from \"./context.js\";\n\n\tlet { class: className, ...restProps }: ButtonProps = $props();\n\n\tconst ctx = getConversationContext();\n</script>\n\n{#if !ctx.isAtBottom}\n\t<Button\n\t\tclass={cn(\n\t\t\t\"bg-background dark:bg-background absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full shadow-md\",\n\t\t\tclassName\n\t\t)}\n\t\tonclick={() => ctx.scrollToBottom()}\n\t\tsize=\"icon\"\n\t\ttype=\"button\"\n\t\tvariant=\"outline\"\n\t\t{...restProps}\n\t>\n\t\t<ArrowDownIcon class=\"size-4\" />\n\t</Button>\n{/if}\n",
			"type": "registry:ui",
			"target": "conversation/conversation-scroll-button.svelte"
		},
		{
			"content": "import { getContext, setContext } from \"svelte\";\n\nconst CONVERSATION_CONTEXT_KEY = Symbol(\"conversation\");\n\nexport interface ConversationContext {\n\tsetContentElement(el: HTMLElement | null): void;\n\treadonly isAtBottom: boolean;\n\tscrollToBottom(): void;\n}\n\nexport function setConversationContext(ctx: ConversationContext): void {\n\tsetContext(CONVERSATION_CONTEXT_KEY, ctx);\n}\n\nexport function getConversationContext(): ConversationContext {\n\tconst ctx = getContext<ConversationContext | undefined>(CONVERSATION_CONTEXT_KEY);\n\tif (!ctx) {\n\t\tthrow new Error(\"Conversation sub-components must be used within a <Conversation>\");\n\t}\n\treturn ctx;\n}\n",
			"type": "registry:ui",
			"target": "conversation/context.ts"
		},
		{
			"content": "import Root from \"./conversation.svelte\";\nimport Content from \"./conversation-content.svelte\";\nimport EmptyState from \"./conversation-empty-state.svelte\";\nimport ScrollButton from \"./conversation-scroll-button.svelte\";\n\nexport {\n\tRoot,\n\tContent,\n\tEmptyState,\n\tScrollButton,\n\t//\n\tRoot as Conversation,\n\tContent as ConversationContent,\n\tEmptyState as ConversationEmptyState,\n\tScrollButton as ConversationScrollButton,\n};\n\nexport { getConversationContext, setConversationContext } from \"./context.js\";\nexport type { ConversationContext } from \"./context.js\";\n",
			"type": "registry:ui",
			"target": "conversation/index.ts"
		}
	]
}