{
  "name": "split-pane",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "split-pane.tsx",
      "content": "\"use client\";\r\n\r\nimport { css, themeVars as theme } from \"@yugnex/core\";\r\nimport { useControllableState } from \"@yugnex/core/client\";\r\nimport { useCallback, useEffect, useId, useRef, type KeyboardEvent, type ReactNode } from \"react\";\r\n\r\nconst rootClass = css({\r\n  display: \"flex\",\r\n  width: \"100%\",\r\n  height: \"100%\",\r\n  minHeight: 0,\r\n  minWidth: 0,\r\n  overflow: \"hidden\",\r\n  '&[data-orientation=\"vertical\"]': { flexDirection: \"column\" },\r\n});\r\n\r\nconst paneClass = css({\r\n  minWidth: 0,\r\n  minHeight: 0,\r\n  overflow: \"auto\",\r\n});\r\n\r\nconst handleClass = css({\r\n  position: \"relative\",\r\n  flexShrink: 0,\r\n  border: \"none\",\r\n  padding: 0,\r\n  backgroundColor: theme.color.border,\r\n  transitionProperty: \"background-color\",\r\n  transitionDuration: theme.duration.fast,\r\n  '&[data-orientation=\"horizontal\"]': { width: \"1px\", cursor: \"col-resize\", height: \"100%\" },\r\n  '&[data-orientation=\"vertical\"]': { height: \"1px\", cursor: \"row-resize\", width: \"100%\" },\r\n  \"&:hover, &[data-dragging='true']\": { backgroundColor: theme.color.primary },\r\n  \"&:focus-visible\": { outline: `2px solid ${theme.color.ring}`, outlineOffset: \"1px\" },\r\n  // A 1px divider is too small to grab, so an invisible pseudo-element widens\r\n  // the hit area to ~9px without changing the visible line or the layout.\r\n  \"&::after\": {\r\n    content: '\"\"',\r\n    position: \"absolute\",\r\n    inset: 0,\r\n  },\r\n  '&[data-orientation=\"horizontal\"]::after': { left: \"-4px\", right: \"-4px\" },\r\n  '&[data-orientation=\"vertical\"]::after': { top: \"-4px\", bottom: \"-4px\" },\r\n});\r\n\r\nexport interface SplitPaneProps {\r\n  children: [ReactNode, ReactNode];\r\n  orientation?: \"horizontal\" | \"vertical\";\r\n  /** First pane's size as a percentage of the container. */\r\n  size?: number;\r\n  defaultSize?: number;\r\n  onSizeChange?: (size: number) => void;\r\n  minSize?: number;\r\n  maxSize?: number;\r\n  /** Percentage points moved per arrow-key press. */\r\n  keyboardStep?: number;\r\n  label?: string;\r\n  className?: string;\r\n}\r\n\r\n/**\r\n * Two resizable panes with a draggable divider — the chat-plus-preview layout\r\n * agent workspaces need.\r\n *\r\n * The divider is a real focusable `separator` with `aria-valuenow`, resizable\r\n * by arrow keys as well as pointer, so the layout isn't mouse-only. Dragging\r\n * listens on `window` (not the handle) so the pointer can leave the divider\r\n * mid-drag without the resize sticking, and sizes are stored as percentages so\r\n * the split survives container resizes.\r\n */\r\nexport function SplitPane({\r\n  children,\r\n  orientation = \"horizontal\",\r\n  size,\r\n  defaultSize = 50,\r\n  onSizeChange,\r\n  minSize = 15,\r\n  maxSize = 85,\r\n  keyboardStep = 2,\r\n  label = \"Resize panes\",\r\n  className,\r\n}: SplitPaneProps) {\r\n  const [current, setCurrent] = useControllableState({\r\n    value: size,\r\n    defaultValue: defaultSize,\r\n    onChange: onSizeChange,\r\n  });\r\n\r\n  const containerRef = useRef<HTMLDivElement | null>(null);\r\n  const draggingRef = useRef(false);\r\n  const handleId = useId();\r\n\r\n  const clamp = useCallback(\r\n    (value: number) => Math.min(Math.max(value, minSize), maxSize),\r\n    [minSize, maxSize],\r\n  );\r\n\r\n  const setFromPointer = useCallback(\r\n    (clientX: number, clientY: number) => {\r\n      const el = containerRef.current;\r\n      if (!el) return;\r\n      const rect = el.getBoundingClientRect();\r\n      const ratio =\r\n        orientation === \"horizontal\"\r\n          ? (clientX - rect.left) / rect.width\r\n          : (clientY - rect.top) / rect.height;\r\n      setCurrent(clamp(ratio * 100));\r\n    },\r\n    [orientation, clamp, setCurrent],\r\n  );\r\n\r\n  useEffect(() => {\r\n    function onMove(event: PointerEvent) {\r\n      if (!draggingRef.current) return;\r\n      event.preventDefault();\r\n      setFromPointer(event.clientX, event.clientY);\r\n    }\r\n    function onUp() {\r\n      if (!draggingRef.current) return;\r\n      draggingRef.current = false;\r\n      document.body.style.userSelect = \"\";\r\n      document.body.style.cursor = \"\";\r\n    }\r\n    window.addEventListener(\"pointermove\", onMove);\r\n    window.addEventListener(\"pointerup\", onUp);\r\n    window.addEventListener(\"pointercancel\", onUp);\r\n    return () => {\r\n      window.removeEventListener(\"pointermove\", onMove);\r\n      window.removeEventListener(\"pointerup\", onUp);\r\n      window.removeEventListener(\"pointercancel\", onUp);\r\n    };\r\n  }, [setFromPointer]);\r\n\r\n  function startDrag() {\r\n    draggingRef.current = true;\r\n    // Suppress text selection and keep the resize cursor while dragging.\r\n    document.body.style.userSelect = \"none\";\r\n    document.body.style.cursor = orientation === \"horizontal\" ? \"col-resize\" : \"row-resize\";\r\n  }\r\n\r\n  function onKeyDown(event: KeyboardEvent<HTMLButtonElement>) {\r\n    const decrease = orientation === \"horizontal\" ? \"ArrowLeft\" : \"ArrowUp\";\r\n    const increase = orientation === \"horizontal\" ? \"ArrowRight\" : \"ArrowDown\";\r\n    if (event.key === decrease) {\r\n      event.preventDefault();\r\n      setCurrent(clamp(current - keyboardStep));\r\n    } else if (event.key === increase) {\r\n      event.preventDefault();\r\n      setCurrent(clamp(current + keyboardStep));\r\n    } else if (event.key === \"Home\") {\r\n      event.preventDefault();\r\n      setCurrent(minSize);\r\n    } else if (event.key === \"End\") {\r\n      event.preventDefault();\r\n      setCurrent(maxSize);\r\n    } else if (event.key === \"Enter\") {\r\n      event.preventDefault();\r\n      setCurrent(clamp(50));\r\n    }\r\n  }\r\n\r\n  const [first, second] = children;\r\n  const firstStyle = orientation === \"horizontal\" ? { width: `${current}%` } : { height: `${current}%` };\r\n\r\n  return (\r\n    <div ref={containerRef} className={className ? `${rootClass} ${className}` : rootClass} data-orientation={orientation}>\r\n      <div className={paneClass} style={{ ...firstStyle, flexShrink: 0 }}>\r\n        {first}\r\n      </div>\r\n      <button\r\n        type=\"button\"\r\n        id={handleId}\r\n        role=\"separator\"\r\n        aria-label={label}\r\n        aria-orientation={orientation === \"horizontal\" ? \"vertical\" : \"horizontal\"}\r\n        aria-valuenow={Math.round(current)}\r\n        aria-valuemin={minSize}\r\n        aria-valuemax={maxSize}\r\n        tabIndex={0}\r\n        data-orientation={orientation}\r\n        data-dragging={draggingRef.current || undefined}\r\n        className={handleClass}\r\n        onPointerDown={startDrag}\r\n        onKeyDown={onKeyDown}\r\n      />\r\n      <div className={paneClass} style={{ flex: 1 }}>\r\n        {second}\r\n      </div>\r\n    </div>\r\n  );\r\n}\r\n",
      "type": "registry:component"
    }
  ]
}