{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "resizable",
  "title": "Resizable",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/resizable/ResizableHandle.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { ResizableDirection, ResizableGroupContext } from './ResizablePanelGroup.svelte'\n\n  export interface ResizableHandleProps extends HTMLAttributes<HTMLDivElement> {\n    /** Render a grip indicator on the divider. */\n    withHandle?: boolean\n    /** When `true`, this handle cannot be dragged. */\n    disabled?: boolean\n    children?: Snippet\n    ref?: HTMLDivElement | null\n  }\n\n  export type { ResizableDirection }\n</script>\n\n<script lang=\"ts\">\n  import { getContext } from 'svelte'\n  import { GripVertical } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    withHandle = false,\n    disabled = undefined,\n    children,\n    onkeydown: onkeydownProp,\n    ref = $bindable(null),\n    ...restProps\n  }: ResizableHandleProps = $props()\n\n  const group = getContext<ResizableGroupContext | undefined>('resizableGroup')\n\n  let el: HTMLDivElement | null = null\n\n  $effect(() => {\n    ref = el\n  })\n\n  const effectiveDisabled = $derived(disabled ?? group?.disabled ?? false)\n  const direction = $derived(group?.direction ?? 'horizontal')\n  const vertical = $derived(direction === 'vertical')\n\n  /** Panels adjacent to this handle, resolved through DOM order. */\n  function adjacentPanels(): [string, string] | null {\n    if (!el || !group) return null\n    const panels = [...(el.parentElement?.querySelectorAll(':scope > [data-slot=\"resizable-panel\"]') ?? [])]\n    const handleIndex = [...(el.parentElement?.children ?? [])].indexOf(el)\n    const before = [...panels].filter((p) => [...(el!.parentElement?.children ?? [])].indexOf(p) < handleIndex).pop()\n    const after = [...panels].find((p) => [...(el!.parentElement?.children ?? [])].indexOf(p) > handleIndex)\n    const beforeId = before?.getAttribute('data-panel-id')\n    const afterId = after?.getAttribute('data-panel-id')\n    if (!beforeId || !afterId) return null\n    return [beforeId, afterId]\n  }\n\n  function groupSpan(): number {\n    const parent = el?.parentElement\n    if (!parent) return 0\n    const rect = parent.getBoundingClientRect()\n    return vertical ? rect.height : rect.width\n  }\n\n  function onPointerDown(event: PointerEvent) {\n    if (effectiveDisabled || !group) return\n    const pair = adjacentPanels()\n    if (!pair) return\n    event.preventDefault()\n    ;(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId)\n    const span = groupSpan()\n    const start = vertical ? event.clientY : event.clientX\n    const [beforeId, afterId] = pair\n    let last = start\n    const move = (e: PointerEvent) => {\n      const pos = vertical ? e.clientY : e.clientX\n      if (span > 0) group.resize(beforeId, afterId, ((pos - last) / span) * 100)\n      last = pos\n    }\n    const up = () => {\n      window.removeEventListener('pointermove', move)\n      window.removeEventListener('pointerup', up)\n      window.removeEventListener('pointercancel', up)\n    }\n    window.addEventListener('pointermove', move)\n    window.addEventListener('pointerup', up)\n    window.addEventListener('pointercancel', up)\n  }\n\n  function handleKeydown(event: KeyboardEvent) {\n    if (effectiveDisabled || !group) return\n    const pair = adjacentPanels()\n    if (!pair) return\n    const step = event.shiftKey ? 10 : 2\n    let delta: number | null = null\n    if (!vertical && event.key === 'ArrowRight') delta = step\n    else if (!vertical && event.key === 'ArrowLeft') delta = -step\n    else if (vertical && event.key === 'ArrowDown') delta = step\n    else if (vertical && event.key === 'ArrowUp') delta = -step\n    else if (event.key === 'Home') delta = -100\n    else if (event.key === 'End') delta = 100\n    else return\n    event.preventDefault()\n    group.resize(pair[0], pair[1], delta)\n  }\n\n  const ariaValue = $derived.by(() => {\n    if (!group) return undefined\n    const pair = adjacentPanels()\n    if (!pair) return undefined\n    return Math.round(group.sizeOf(pair[0]) * 10) / 10\n  })\n</script>\n\n<!-- role=\"separator\" owns keyboard + pointer resizing; the svelte a11y rule only whitelists a subset of interactive roles. -->\n<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->\n<!-- svelte-ignore a11y_no_noninteractive_tabindex -->\n<div\n  bind:this={el}\n  role=\"separator\"\n  tabindex={effectiveDisabled ? -1 : 0}\n  aria-orientation={vertical ? 'vertical' : 'horizontal'}\n  aria-valuemin={0}\n  aria-valuemax={100}\n  aria-valuenow={ariaValue}\n  aria-disabled={effectiveDisabled || undefined}\n  data-uipkge=\"\"\n  data-slot=\"resizable-handle\"\n  data-orientation={direction}\n  data-disabled={effectiveDisabled || undefined}\n  class={cn(\n    'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[orientation=vertical]:h-px data-[orientation=vertical]:w-full data-[orientation=vertical]:after:left-0 data-[orientation=vertical]:after:h-1 data-[orientation=vertical]:after:w-full data-[orientation=vertical]:after:translate-x-0 data-[orientation=vertical]:after:-translate-y-1/2 [&[data-orientation=vertical]>div]:rotate-90',\n    effectiveDisabled && 'pointer-events-none opacity-50',\n    className,\n  )}\n  {...restProps}\n  onpointerdown={onPointerDown}\n  onkeydown={(e) => {\n    handleKeydown(e)\n    onkeydownProp?.(e)\n  }}\n>\n  {#if withHandle}\n    <div class=\"bg-border z-10 flex h-4 w-3 items-center justify-center rounded-sm border\">\n      {#if children}\n        {@render children()}\n      {:else}\n        <GripVertical class=\"size-2.5\" aria-hidden=\"true\" />\n      {/if}\n    </div>\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/resizable/ResizableHandle.svelte"
    },
    {
      "path": "packages/registry-svelte/components/resizable/ResizablePanel.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { ResizableGroupContext } from './ResizablePanelGroup.svelte'\n\n  export interface ResizablePanelProps extends HTMLAttributes<HTMLDivElement> {\n    /** Stable id used for layout bookkeeping + handle aria-controls. Auto-generated when omitted. */\n    id?: string\n    /** Initial size in percent. Panels without one split the remainder equally. */\n    defaultSize?: number\n    /** Minimum size in percent. */\n    minSize?: number\n    /** Maximum size in percent. */\n    maxSize?: number\n    children?: Snippet\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { getContext } from 'svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    id: idProp = undefined,\n    defaultSize = undefined,\n    minSize = undefined,\n    maxSize = undefined,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: ResizablePanelProps = $props()\n\n  const group = getContext<ResizableGroupContext | undefined>('resizableGroup')\n  const autoId = $props.id()\n  const panelId = $derived(idProp ?? `resizable-panel-${autoId}`)\n\n  $effect(() => {\n    if (!group) return\n    group.registerPanel(panelId, { defaultSize, minSize, maxSize })\n    return () => group.unregisterPanel(panelId)\n  })\n\n  const size = $derived(group?.sizeOf(panelId) ?? 0)\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"resizable-panel\"\n  data-panel-id={panelId}\n  id={panelId}\n  style=\"flex: {size} 1 0px; min-height: 0; min-width: 0;\"\n  class={cn('relative', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/resizable/ResizablePanel.svelte"
    },
    {
      "path": "packages/registry-svelte/components/resizable/ResizablePanelGroup.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export type ResizableDirection = 'horizontal' | 'vertical'\n\n  export interface ResizablePanelSpec {\n    defaultSize?: number\n    minSize?: number\n    maxSize?: number\n  }\n\n  /** Context shared with ResizablePanel / ResizableHandle. Getters stay reactive in children. */\n  export interface ResizableGroupContext {\n    readonly direction: ResizableDirection\n    readonly disabled: boolean\n    sizeOf: (id: string) => number\n    registerPanel: (id: string, spec: ResizablePanelSpec) => void\n    unregisterPanel: (id: string) => void\n    resize: (beforeId: string, afterId: string, delta: number) => void\n    panelIdAt: (index: number) => string | undefined\n    panelCount: () => number\n  }\n\n  export interface ResizablePanelGroupProps extends HTMLAttributes<HTMLDivElement> {\n    /** Split direction. Default 'horizontal'. */\n    direction?: ResizableDirection\n    /** When `true`, handles cannot be dragged. */\n    disabled?: boolean\n    /** Called with the ordered panel sizes (percent) after every resize commit. */\n    onLayoutChange?: (sizes: number[]) => void\n    children?: Snippet\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { setContext } from 'svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    direction = 'horizontal',\n    disabled = false,\n    onLayoutChange,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: ResizablePanelGroupProps = $props()\n\n  let root: HTMLDivElement | null = null\n  // Explicit sizes written by drag/keyboard commits, keyed by panel id.\n  let overrides = $state<Record<string, number>>({})\n  let specs = $state<Record<string, ResizablePanelSpec>>({})\n  // Bump to recompute order after mount/unmount (DOM order is the source of truth).\n  let registryVersion = $state(0)\n\n  $effect(() => {\n    ref = root\n  })\n\n  /** Panel ids in DOM order. */\n  function orderedIds(): string[] {\n    if (!root) return Object.keys(specs)\n    return [...root.querySelectorAll(':scope > [data-slot=\"resizable-panel\"]')]\n      .map((el) => el.getAttribute('data-panel-id'))\n      .filter((id): id is string => id != null && id in specs)\n  }\n\n  /** Resolved layout: overrides win, then defaultSize, remainder split equally, normalized to 100. */\n  const layout = $derived.by(() => {\n    registryVersion\n    const ids = orderedIds()\n    const explicit = new Map<string, number>()\n    let remainder = 100\n    const flexible: string[] = []\n    for (const id of ids) {\n      const spec = specs[id]!\n      const pinned = overrides[id] ?? spec.defaultSize\n      if (pinned != null) {\n        explicit.set(id, pinned)\n        remainder -= pinned\n      } else {\n        flexible.push(id)\n      }\n    }\n    if (flexible.length > 0) {\n      const share = remainder / flexible.length\n      for (const id of flexible) explicit.set(id, share)\n    }\n    const total = [...explicit.values()].reduce((a, b) => a + b, 0) || 1\n    return ids.map((id) => ({ id, size: ((explicit.get(id) ?? 0) / total) * 100 }))\n  })\n\n  function sizeOf(id: string): number {\n    return layout.find((p) => p.id === id)?.size ?? 0\n  }\n\n  function emitLayout() {\n    onLayoutChange?.(layout.map((p) => Math.round(p.size * 100) / 100))\n  }\n\n  function resize(beforeId: string, afterId: string, delta: number) {\n    if (disabled || delta === 0) return\n    const before = layout.find((p) => p.id === beforeId)\n    const after = layout.find((p) => p.id === afterId)\n    if (!before || !after) return\n    const beforeSpec = specs[beforeId] ?? {}\n    const afterSpec = specs[afterId] ?? {}\n    const beforeMin = beforeSpec.minSize ?? 0\n    const beforeMax = beforeSpec.maxSize ?? 100\n    const afterMin = afterSpec.minSize ?? 0\n    const afterMax = afterSpec.maxSize ?? 100\n    // Clamp the delta so neither panel leaves its [min, max] window.\n    const clamped = Math.min(\n      Math.max(delta, beforeMin - before.size, after.size - afterMax),\n      beforeMax - before.size,\n      after.size - afterMin,\n    )\n    if (clamped === 0) return\n    overrides = {\n      ...overrides,\n      [beforeId]: before.size + clamped,\n      [afterId]: after.size - clamped,\n    }\n    emitLayout()\n  }\n\n  setContext<ResizableGroupContext>('resizableGroup', {\n    get direction() {\n      return direction\n    },\n    get disabled() {\n      return disabled\n    },\n    sizeOf,\n    registerPanel: (id, spec) => {\n      specs = { ...specs, [id]: spec }\n      registryVersion += 1\n    },\n    unregisterPanel: (id) => {\n      const { [id]: _removedSpec, ...restSpecs } = specs\n      void _removedSpec\n      specs = restSpecs\n      const { [id]: _removedOverride, ...restOverrides } = overrides\n      void _removedOverride\n      overrides = restOverrides\n      registryVersion += 1\n    },\n    resize,\n    panelIdAt: (index) => orderedIds()[index],\n    panelCount: () => orderedIds().length,\n  })\n</script>\n\n<div\n  bind:this={root}\n  data-uipkge=\"\"\n  data-slot=\"resizable-panel-group\"\n  data-orientation={direction}\n  data-disabled={disabled || undefined}\n  class={cn('flex h-full w-full data-[orientation=vertical]:flex-col', className)}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/resizable/ResizablePanelGroup.svelte"
    },
    {
      "path": "packages/registry-svelte/components/resizable/index.ts",
      "content": "export {\n  default as ResizablePanelGroup,\n  type ResizableDirection,\n  type ResizableGroupContext,\n  type ResizablePanelGroupProps,\n  type ResizablePanelSpec,\n} from './ResizablePanelGroup.svelte'\nexport { default as ResizablePanel, type ResizablePanelProps } from './ResizablePanel.svelte'\nexport { default as ResizableHandle, type ResizableHandleProps } from './ResizableHandle.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/resizable/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Drag-to-resize panel layout — horizontal or vertical splits with persistent sizes. Use for IDE-style sidebars, split views, and any layout the user should be able to reshape.",
  "categories": [
    "layout"
  ]
}