{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gpuix-dock",
  "type": "registry:lib",
  "title": "Runtime: dock",
  "description": "The `@gpuixui/dock` modules the component items import. Installed automatically with the component; not a component itself.",
  "author": "Cyberistic <https://github.com/Cyberistic>",
  "dependencies": [
    "@gpuix/native@0.11.0",
    "@gpuix/react@0.11.0",
    "effect@4.1.2",
    "react@^19.2.4"
  ],
  "registryDependencies": [
    "https://gpuixui.sudokw.com/gpuix-motion.json",
    "https://gpuixui.sudokw.com/gpuix-primitives.json",
    "https://gpuixui.sudokw.com/gpuix-tokens.json",
    "https://gpuixui.sudokw.com/gpuix-tw.json"
  ],
  "files": [
    {
      "path": "packages/dock/src/divider.tsx",
      "target": "@ui/gpuixui/dock/divider.tsx",
      "type": "registry:component",
      "content": "import { useRef, useState, type ReactElement } from \"react\";\nimport type { EventPayload, StyleDesc } from \"@gpuix/react\";\nimport { motionTiming } from \"../motion/timing.js\";\nimport { controlFocusRing } from \"./theme.js\";\nimport { DIVIDER, mergeStyle, recipeStyle } from \"./recipe.js\";\nimport { DIVIDER_WIDTH, isDividerKey, type DividerKey } from \"./logic.js\";\nimport { useTheme } from \"./theme.js\";\n\nexport interface DividerProps {\n  readonly orientation: \"horizontal\" | \"vertical\";\n\n  readonly valueText: string;\n\n  readonly label?: string;\n  readonly disabled?: boolean;\n\n  readonly seam?: boolean;\n\n  readonly testId?: string;\n\n  readonly onDragStart: () => void;\n\n  readonly onDragDelta: (delta: number) => void;\n\n  readonly onDragEnd?: () => void;\n\n  readonly onKey: (key: DividerKey) => void;\n}\n\nexport const Divider = ({\n  orientation,\n  valueText,\n  label,\n  disabled,\n  seam = false,\n  testId,\n  onDragStart,\n  onDragDelta,\n  onDragEnd,\n  onKey,\n}: DividerProps): ReactElement => {\n  const theme = useTheme();\n  const origin = useRef<number | null>(null);\n  const [dragging, setDragging] = useState(false);\n\n  const axis = (event: EventPayload): number =>\n    orientation === \"horizontal\" ? (event.x ?? 0) : (event.y ?? 0);\n\n  const geometry: StyleDesc =\n    orientation === \"horizontal\"\n      ? { width: DIVIDER_WIDTH, cursor: \"col-resize\" }\n      : { height: DIVIDER_WIDTH, cursor: \"row-resize\" };\n\n  const placement: StyleDesc = seam\n    ? { position: \"absolute\", top: 0, right: 0, bottom: 0 }\n    : mergeStyle(mergeStyle(recipeStyle(DIVIDER, theme), geometry), {\n        flexShrink: 0,\n        flexGrow: 0,\n        position: \"relative\",\n      });\n  const style = mergeStyle(\n    mergeStyle(placement, geometry),\n    controlFocusRing(theme, { disabled }),\n  );\n\n  const highlight: StyleDesc = {\n    position: \"absolute\",\n    top: 0,\n    right: 0,\n    bottom: 0,\n    left: 0,\n    backgroundColor: theme.colors.primary,\n    pointerEvents: \"none\",\n  };\n\n  return (\n    <div\n      role=\"separator\"\n      aria-label={label}\n      aria-valuetext={valueText}\n      tabIndex={disabled === true ? undefined : 0}\n      testId={testId}\n      style={style}\n      onMouseDown={(event) => {\n        if (disabled === true) return;\n        origin.current = axis(event);\n        setDragging(true);\n        onDragStart();\n      }}\n      onMouseMove={(event) => {\n        if (disabled === true || origin.current === null) return;\n        if (event.pressedButton !== 0) {\n          origin.current = null;\n          setDragging(false);\n          onDragEnd?.();\n          return;\n        }\n        onDragDelta(axis(event) - origin.current);\n      }}\n      onMouseUp={() => {\n        if (origin.current === null) return;\n        origin.current = null;\n        setDragging(false);\n        onDragEnd?.();\n      }}\n      onKeyDown={(event) => {\n        if (disabled === true) return;\n        const key = event.key;\n        if (key !== undefined && isDividerKey(key)) onKey(key);\n      }}\n    >\n      {seam ? null : (\n        <div\n          style={highlight}\n          motion={{\n            animate: { opacity: dragging ? 1 : 0 },\n            transition: motionTiming(\"fast\", { phase: \"enter\" }),\n          }}\n        />\n      )}\n    </div>\n  );\n};\n"
    },
    {
      "path": "packages/dock/src/dock-layout.tsx",
      "target": "@ui/gpuixui/dock/dock-layout.tsx",
      "type": "registry:component",
      "content": "import type { ReactElement, ReactNode } from \"react\";\nimport { useRef, useState } from \"react\";\nimport type { EventPayload, StyleDesc } from \"@gpuix/react\";\nimport { cn, TABS_LIST, TABS_TRIGGER } from \"../tw/index.js\";\nimport { Divider } from \"./divider.js\";\nimport {\n  DIVIDER_STEP,\n  DIVIDER_WIDTH,\n  resizeSplit,\n  resizeSplitByKey,\n  setActiveTab,\n  type DockNode,\n  type DockSplit,\n  type DockTabs,\n} from \"./logic.js\";\nimport { DOCK_PANE, DOCK_ROOT, mergeStyle, recipeStyle } from \"./recipe.js\";\nimport { textStyle, useTheme } from \"./theme.js\";\n\nexport interface DockLayoutProps {\n  readonly layout: DockNode;\n\n  readonly onLayoutChange?: (layout: DockNode) => void;\n\n  readonly renderTab: (id: string) => ReactNode;\n\n  readonly className?: string;\n\n  readonly testId?: string;\n}\n\ninterface NodeViewProps {\n  readonly node: DockNode;\n  readonly onLayoutChange?: (layout: DockNode) => void;\n  readonly renderTab: (id: string) => ReactNode;\n  readonly testId?: string;\n}\n\nconst DockTabsView = ({\n  node,\n  onLayoutChange,\n  renderTab,\n  testId,\n}: {\n  readonly node: DockTabs;\n  readonly onLayoutChange?: (layout: DockNode) => void;\n  readonly renderTab: (id: string) => ReactNode;\n  readonly testId?: string;\n}): ReactElement => {\n  const theme = useTheme();\n  const strip = recipeStyle(TABS_LIST, theme);\n  const panel = mergeStyle(recipeStyle(DOCK_PANE, theme), {\n    display: \"flex\",\n    flexDirection: \"column\",\n    flexGrow: 1,\n    flexShrink: 1,\n    flexBasis: 0,\n  });\n\n  return (\n    <div\n      testId={testId}\n      style={{\n        display: \"flex\",\n        flexDirection: \"column\",\n        flexGrow: 1,\n        flexShrink: 1,\n        flexBasis: 0,\n        minHeight: 0,\n        minWidth: 0,\n      }}\n    >\n      <div style={strip}>\n        {node.tabs.map((tab) => {\n          const active = tab.id === node.activeTab;\n          const trigger = recipeStyle(\n            cn(TABS_TRIGGER, active && \"bg-selected text-selected-foreground\"),\n            theme,\n          );\n          const inactive: StyleDesc = active\n            ? {}\n            : {\n                color: theme.colors.mutedForeground,\n                hover: { backgroundColor: theme.colors.hover },\n                cursor: \"pointer\",\n              };\n          return (\n            <div\n              key={tab.id}\n              role=\"tab\"\n              aria-selected={active}\n              tabIndex={active ? 0 : -1}\n              testId={\n                testId === undefined ? undefined : `${testId}-tab-${tab.id}`\n              }\n              onClick={() =>\n                onLayoutChange?.(setActiveTab(node, node.id, tab.id))\n              }\n              style={mergeStyle(trigger, inactive)}\n            >\n              <text\n                style={{\n                  ...textStyle(theme, \"label\"),\n                  color: active\n                    ? theme.colors.selectedForeground\n                    : theme.colors.mutedForeground,\n                }}\n              >\n                {tab.label}\n              </text>\n            </div>\n          );\n        })}\n      </div>\n      <div style={panel}>{renderTab(node.activeTab)}</div>\n    </div>\n  );\n};\n\nconst DockSplitView = ({\n  node,\n  onLayoutChange,\n  renderTab,\n  testId,\n}: {\n  readonly node: DockSplit;\n  readonly onLayoutChange?: (layout: DockNode) => void;\n  readonly renderTab: (id: string) => ReactNode;\n  readonly testId?: string;\n}): ReactElement => {\n  const theme = useTheme();\n  const orientation = node.orientation;\n  const [total, setTotal] = useState(0);\n  const dragStart = useRef<number | null>(null);\n  const available = total - DIVIDER_WIDTH;\n\n  const root = mergeStyle(recipeStyle(DOCK_ROOT, theme), {\n    flexDirection: orientation === \"horizontal\" ? \"row\" : \"column\",\n    alignItems: \"stretch\",\n    flexGrow: 1,\n    flexShrink: 1,\n    flexBasis: 0,\n  });\n\n  const child: StyleDesc = {\n    minHeight: 0,\n    minWidth: 0,\n    overflow: \"hidden\",\n    display: \"flex\",\n  };\n\n  const first = mergeStyle(child, {\n    flexGrow: node.size,\n    flexShrink: 1,\n    flexBasis: 0,\n  });\n  const second = mergeStyle(child, {\n    flexGrow: 1 - node.size,\n    flexShrink: 1,\n    flexBasis: 0,\n  });\n\n  const onLayout = (event: EventPayload): void => {\n    const next =\n      orientation === \"horizontal\" ? (event.width ?? 0) : (event.height ?? 0);\n    setTotal((previous) => (previous === next ? previous : next));\n  };\n\n  return (\n    <div testId={testId} style={root} onLayout={onLayout}>\n      <div style={first}>\n        <DockNodeView\n          node={node.first}\n          onLayoutChange={onLayoutChange}\n          renderTab={renderTab}\n        />\n      </div>\n      <Divider\n        orientation={orientation}\n        valueText={`${Math.round(node.size * 100)}%`}\n        label=\"Resize panes\"\n        testId={testId === undefined ? undefined : `${testId}-divider`}\n        onDragStart={() => {\n          dragStart.current = node.size;\n        }}\n        onDragDelta={(delta) => {\n          const start = dragStart.current;\n          if (start === null || available <= 0) return;\n          onLayoutChange?.({\n            ...node,\n            size: resizeSplit(start, delta / available),\n          });\n        }}\n        onDragEnd={() => {\n          dragStart.current = null;\n        }}\n        onKey={(key) => {\n          if (available <= 0) return;\n          onLayoutChange?.({\n            ...node,\n            size: resizeSplitByKey(node.size, key, DIVIDER_STEP / available),\n          });\n        }}\n      />\n      <div style={second}>\n        <DockNodeView\n          node={node.second}\n          onLayoutChange={onLayoutChange}\n          renderTab={renderTab}\n        />\n      </div>\n    </div>\n  );\n};\n\nconst DockNodeView = ({\n  node,\n  onLayoutChange,\n  renderTab,\n  testId,\n}: NodeViewProps): ReactElement =>\n  node.kind === \"tabs\" ? (\n    <DockTabsView\n      node={node}\n      onLayoutChange={onLayoutChange}\n      renderTab={renderTab}\n      testId={testId}\n    />\n  ) : (\n    <DockSplitView\n      node={node}\n      onLayoutChange={onLayoutChange}\n      renderTab={renderTab}\n      testId={testId}\n    />\n  );\n\nexport const DockLayout = ({\n  layout,\n  onLayoutChange,\n  renderTab,\n  className,\n  testId,\n}: DockLayoutProps): ReactElement => {\n  const theme = useTheme();\n  const root = mergeStyle(recipeStyle(cn(DOCK_ROOT, className), theme), {\n    flexDirection: \"column\",\n    alignItems: \"stretch\",\n    flexGrow: 1,\n    flexShrink: 1,\n    flexBasis: 0,\n  });\n  return (\n    <div testId={testId} style={root}>\n      <DockNodeView\n        node={layout}\n        onLayoutChange={onLayoutChange}\n        renderTab={renderTab}\n      />\n    </div>\n  );\n};\n"
    },
    {
      "path": "packages/dock/src/index.ts",
      "target": "@ui/gpuixui/dock/index.ts",
      "type": "registry:lib",
      "content": "export { Divider } from \"./divider.js\";\nexport type { DividerProps } from \"./divider.js\";\n\nexport { SplitPane } from \"./split-pane.js\";\nexport type { SplitPaneProps } from \"./split-pane.js\";\n\nexport { PanelGroup } from \"./panel-group.js\";\nexport type { PanelGroupProps } from \"./panel-group.js\";\n\nexport {\n  layoutOfDeclarations,\n  ResizableHandle,\n  ResizablePanel,\n  ResizablePanelGroup,\n} from \"./resizable.js\";\nexport type {\n  ResizableHandleProps,\n  ResizablePanelGroupProps,\n  ResizablePanelProps,\n} from \"./resizable.js\";\n\nexport { DockLayout } from \"./dock-layout.js\";\nexport type { DockLayoutProps } from \"./dock-layout.js\";\n\nexport {\n  DIVIDER_STEP,\n  DIVIDER_WIDTH,\n  DockNode,\n  DockNodeJson,\n  DockSplit,\n  DockTab,\n  DockTabs,\n  Panel,\n  PanelLayout,\n  PanelLayoutJson,\n  clampSize,\n  dragSize,\n  isDividerKey,\n  normalizePanels,\n  resizeByKey,\n  resizePanels,\n  resizePanelsByKey,\n  resizeSplit,\n  resizeSplitByKey,\n  setActiveTab,\n  sizeBounds,\n  togglePanelCollapsed,\n} from \"./logic.js\";\nexport type { DividerKey, SizeBounds } from \"./logic.js\";\n"
    },
    {
      "path": "packages/dock/src/logic.ts",
      "target": "@ui/gpuixui/dock/logic.ts",
      "type": "registry:lib",
      "content": "import { Effect, Match, pipe } from \"effect\";\nimport * as Arr from \"effect/Array\";\nimport * as Number from \"effect/Number\";\nimport * as Schema from \"effect/Schema\";\n\nexport const DIVIDER_STEP = 16;\n\nexport const DIVIDER_WIDTH = 6;\n\nexport interface SizeBounds {\n  readonly min: number;\n  readonly max: number;\n}\n\nexport const sizeBounds = (min = 0, max = Infinity): SizeBounds => {\n  const floor = Math.max(0, min);\n  return { min: floor, max: Math.max(floor, max) };\n};\n\nexport const clampSize = (size: number, bounds: SizeBounds): number =>\n  Number.clamp({ minimum: bounds.min, maximum: bounds.max })(size);\n\nexport const dragSize = (\n  start: number,\n  delta: number,\n  bounds: SizeBounds,\n): number => clampSize(start + delta, bounds);\n\nexport const dividerKeys = [\n  \"left\",\n  \"right\",\n  \"up\",\n  \"down\",\n  \"home\",\n  \"end\",\n] as const;\n\nexport const DividerKey = Schema.Literals(dividerKeys);\n\nexport type DividerKey = typeof DividerKey.Type;\n\nexport const isDividerKey = Schema.is(DividerKey);\n\nexport const Orientation = Schema.Literals([\"horizontal\", \"vertical\"]);\n\nexport type Orientation = typeof Orientation.Type;\n\nexport const resizeByKey = (\n  size: number,\n  key: DividerKey,\n  bounds: SizeBounds,\n  step: number = DIVIDER_STEP,\n): number =>\n  pipe(\n    Match.value(key),\n    Match.when(\"left\", () => clampSize(size - step, bounds)),\n    Match.when(\"up\", () => clampSize(size - step, bounds)),\n    Match.when(\"right\", () => clampSize(size + step, bounds)),\n    Match.when(\"down\", () => clampSize(size + step, bounds)),\n    Match.when(\"home\", () => bounds.min),\n    Match.when(\"end\", () => bounds.max),\n    Match.exhaustive,\n  );\n\nconst DEFAULT_FALSE = Schema.Boolean.pipe(\n  Schema.withDecodingDefault(Effect.succeed(false)),\n  Schema.withConstructorDefault(Effect.succeed(false)),\n);\n\nexport class Panel extends Schema.Class<Panel>(\"Panel\")({\n  id: Schema.NonEmptyString,\n  label: Schema.NonEmptyString,\n  size: Schema.Number,\n  collapsible: DEFAULT_FALSE,\n  collapsed: DEFAULT_FALSE,\n}) {}\n\nexport class PanelLayout extends Schema.Class<PanelLayout>(\"PanelLayout\")({\n  orientation: Orientation,\n  panels: Schema.Array(Panel),\n}) {}\n\nexport const PanelLayoutJson = Schema.fromJsonString(PanelLayout);\n\nexport const normalizePanels = (\n  panels: ReadonlyArray<Panel>,\n): ReadonlyArray<Panel> => {\n  const total = Arr.reduce(\n    panels,\n    0,\n    (sum, panel) => sum + Math.max(0, panel.size),\n  );\n  return total <= 0\n    ? panels\n    : Arr.map(panels, (panel) => ({\n        ...panel,\n        size: Math.max(0, panel.size) / total,\n      }));\n};\n\nexport const togglePanelCollapsed = (\n  layout: PanelLayout,\n  id: string,\n): PanelLayout => {\n  const has = Arr.some(\n    layout.panels,\n    (panel) => panel.id === id && panel.collapsible,\n  );\n  if (!has) return layout;\n  return new PanelLayout({\n    ...layout,\n    panels: Arr.map(layout.panels, (panel) =>\n      panel.id === id\n        ? new Panel({ ...panel, collapsed: !panel.collapsed })\n        : panel,\n    ),\n  });\n};\n\nexport const resizePanels = (\n  panels: ReadonlyArray<Panel>,\n  index: number,\n  delta: number,\n): ReadonlyArray<Panel> => {\n  const first = panels[index];\n  const second = panels[index + 1];\n  if (first === undefined || second === undefined) return panels;\n  const moved = clampSize(first.size + delta, {\n    min: 0,\n    max: first.size + second.size,\n  });\n  if (moved === first.size) return panels;\n  return Arr.map(panels, (panel, at) =>\n    at === index\n      ? new Panel({ ...first, size: moved })\n      : at === index + 1\n        ? new Panel({ ...second, size: first.size + second.size - moved })\n        : panel,\n  );\n};\n\nexport const resizePanelsByKey = (\n  panels: ReadonlyArray<Panel>,\n  index: number,\n  key: DividerKey,\n  step: number,\n): ReadonlyArray<Panel> =>\n  pipe(\n    Match.value(key),\n    Match.when(\"left\", () => resizePanels(panels, index, -step)),\n    Match.when(\"up\", () => resizePanels(panels, index, -step)),\n    Match.when(\"right\", () => resizePanels(panels, index, step)),\n    Match.when(\"down\", () => resizePanels(panels, index, step)),\n    Match.when(\"home\", () => resizePanels(panels, index, -Infinity)),\n    Match.when(\"end\", () => resizePanels(panels, index, Infinity)),\n    Match.exhaustive,\n  );\n\nexport class DockTab extends Schema.Class<DockTab>(\"DockTab\")({\n  id: Schema.NonEmptyString,\n  label: Schema.NonEmptyString,\n}) {}\n\nexport interface DockSplit {\n  readonly kind: \"split\";\n  readonly id: string;\n  readonly orientation: Orientation;\n  readonly size: number;\n  readonly first: DockNode;\n  readonly second: DockNode;\n}\n\nexport interface DockTabs {\n  readonly kind: \"tabs\";\n  readonly id: string;\n  readonly activeTab: string;\n  readonly tabs: ReadonlyArray<DockTab>;\n}\n\nexport type DockNode = DockSplit | DockTabs;\n\nconst splitSize = Schema.Number.pipe(\n  Schema.withConstructorDefault(Effect.succeed(0.5)),\n);\n\nexport const DockSplit: Schema.Codec<DockSplit> = Schema.Struct({\n  kind: Schema.tag(\"split\"),\n  id: Schema.NonEmptyString,\n  orientation: Orientation,\n  size: splitSize,\n  first: Schema.suspend((): Schema.Codec<DockNode> => DockNode),\n  second: Schema.suspend((): Schema.Codec<DockNode> => DockNode),\n});\n\nexport const DockTabs: Schema.Codec<DockTabs> = Schema.Struct({\n  kind: Schema.tag(\"tabs\"),\n  id: Schema.NonEmptyString,\n  activeTab: Schema.NonEmptyString,\n  tabs: Schema.Array(DockTab),\n});\n\nexport const DockNode: Schema.Codec<DockNode> = Schema.Union([\n  DockSplit,\n  DockTabs,\n]).pipe(Schema.toTaggedUnion(\"kind\"));\n\nexport const DockNodeJson = Schema.fromJsonString(DockNode);\n\nexport const resizeSplit = (size: number, delta: number): number =>\n  clampSize(size + delta, { min: 0, max: 1 });\n\nexport const resizeSplitByKey = (\n  size: number,\n  key: DividerKey,\n  step: number,\n): number =>\n  pipe(\n    Match.value(key),\n    Match.when(\"left\", () => resizeSplit(size, -step)),\n    Match.when(\"up\", () => resizeSplit(size, -step)),\n    Match.when(\"right\", () => resizeSplit(size, step)),\n    Match.when(\"down\", () => resizeSplit(size, step)),\n    Match.when(\"home\", () => resizeSplit(size, -Infinity)),\n    Match.when(\"end\", () => resizeSplit(size, Infinity)),\n    Match.exhaustive,\n  );\n\nexport const setActiveTab = (\n  node: DockNode,\n  groupId: string,\n  tabId: string,\n): DockNode => {\n  if (node.kind === \"tabs\") {\n    if (node.id !== groupId || node.activeTab === tabId) return node;\n    if (!Arr.some(node.tabs, (tab) => tab.id === tabId)) return node;\n    return { ...node, activeTab: tabId };\n  }\n  const first = setActiveTab(node.first, groupId, tabId);\n  const second = setActiveTab(node.second, groupId, tabId);\n  if (first === node.first && second === node.second) return node;\n  return { ...node, first, second };\n};\n"
    },
    {
      "path": "packages/dock/src/panel-group.tsx",
      "target": "@ui/gpuixui/dock/panel-group.tsx",
      "type": "registry:component",
      "content": "import type { ReactElement, ReactNode } from \"react\";\nimport { Fragment, useRef, useState } from \"react\";\nimport type { EventPayload, StyleDesc } from \"@gpuix/react\";\nimport { Button } from \"../primitives/button.js\";\nimport { cn } from \"../tw/index.js\";\nimport { Divider } from \"./divider.js\";\nimport {\n  DIVIDER_STEP,\n  DIVIDER_WIDTH,\n  PanelLayout,\n  normalizePanels,\n  resizePanels,\n  resizePanelsByKey,\n  togglePanelCollapsed,\n  type Panel,\n} from \"./logic.js\";\nimport { PANE, PANEL_ROOT, mergeStyle, recipeStyle } from \"./recipe.js\";\nimport { useTheme, type Theme } from \"./theme.js\";\n\nexport interface PanelGroupProps {\n  readonly layout: PanelLayout;\n\n  readonly onLayoutChange?: (layout: PanelLayout) => void;\n\n  readonly renderPanel: (id: string) => ReactNode;\n\n  readonly dividerLabel?: (index: number) => string | undefined;\n\n  readonly className?: string;\n\n  readonly testId?: string;\n}\n\nconst paneStyle = (panel: Panel, theme: Theme): StyleDesc =>\n  mergeStyle(recipeStyle(PANE, theme), {\n    display: \"flex\",\n    flexDirection: \"column\",\n    flexGrow: panel.collapsed ? 0 : panel.size,\n    flexShrink: panel.collapsed ? 0 : 1,\n    ...(panel.collapsed ? {} : { flexBasis: 0 }),\n  });\n\nexport const PanelGroup = ({\n  layout,\n  onLayoutChange,\n  renderPanel,\n  dividerLabel,\n  className,\n  testId,\n}: PanelGroupProps): ReactElement => {\n  const theme = useTheme();\n  const orientation = layout.orientation;\n  const panels = normalizePanels(layout.panels);\n\n  const [total, setTotal] = useState(0);\n  const dragStart = useRef<ReadonlyArray<Panel> | null>(null);\n\n  const available = total - (panels.length - 1) * DIVIDER_WIDTH;\n\n  const root = mergeStyle(recipeStyle(cn(PANEL_ROOT, className), theme), {\n    flexDirection: orientation === \"horizontal\" ? \"row\" : \"column\",\n    alignItems: \"stretch\",\n    flexGrow: 1,\n    flexShrink: 1,\n    flexBasis: 0,\n  });\n\n  const onLayout = (event: EventPayload): void => {\n    const next =\n      orientation === \"horizontal\" ? (event.width ?? 0) : (event.height ?? 0);\n    setTotal((previous) => (previous === next ? previous : next));\n  };\n\n  const content: StyleDesc = {\n    flexGrow: 1,\n    flexShrink: 1,\n    flexBasis: 0,\n    minHeight: 0,\n    minWidth: 0,\n    overflow: \"hidden\",\n  };\n\n  return (\n    <div testId={testId} style={root} onLayout={onLayout}>\n      {panels.map((panel, index) => {\n        const last = index === panels.length - 1;\n        const pair = panels[index + 1];\n        return (\n          <Fragment key={panel.id}>\n            <div\n              style={paneStyle(panel, theme)}\n              testId={\n                testId === undefined ? undefined : `${testId}-pane-${panel.id}`\n              }\n            >\n              {panel.collapsible && (\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  fullWidth\n                  label={\n                    panel.collapsed\n                      ? `Expand ${panel.label}`\n                      : `Collapse ${panel.label}`\n                  }\n                  onActivate={() =>\n                    onLayoutChange?.(togglePanelCollapsed(layout, panel.id))\n                  }\n                >\n                  {panel.label}\n                </Button>\n              )}\n              {!panel.collapsed && (\n                <div style={content}>{renderPanel(panel.id)}</div>\n              )}\n            </div>\n            {!last && pair !== undefined && (\n              <Divider\n                orientation={orientation}\n                valueText={`${Math.round(panel.size * 100)}%`}\n                label={dividerLabel?.(index) ?? `Resize ${panel.label}`}\n                testId={\n                  testId === undefined\n                    ? undefined\n                    : `${testId}-divider-${index}`\n                }\n                onDragStart={() => {\n                  dragStart.current = panels;\n                }}\n                onDragDelta={(delta) => {\n                  const start = dragStart.current;\n                  if (start === null || available <= 0) return;\n                  onLayoutChange?.(\n                    new PanelLayout({\n                      ...layout,\n                      panels: resizePanels(start, index, delta / available),\n                    }),\n                  );\n                }}\n                onDragEnd={() => {\n                  dragStart.current = null;\n                }}\n                onKey={(key) => {\n                  if (available <= 0) return;\n                  onLayoutChange?.(\n                    new PanelLayout({\n                      ...layout,\n                      panels: resizePanelsByKey(\n                        panels,\n                        index,\n                        key,\n                        DIVIDER_STEP / available,\n                      ),\n                    }),\n                  );\n                }}\n              />\n            )}\n          </Fragment>\n        );\n      })}\n    </div>\n  );\n};\n"
    },
    {
      "path": "packages/dock/src/recipe.ts",
      "target": "@ui/gpuixui/dock/recipe.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { classesToStyle } from \"../tw/index.js\";\nimport * as MutableHashMap from \"effect/MutableHashMap\";\nimport * as Option from \"effect/Option\";\nimport type { Theme } from \"./theme.js\";\n\nexport const PANEL_ROOT = \"flex min-h-0 min-w-0\";\n\nexport const PANE = \"min-h-0 min-w-0 overflow-hidden\";\n\nexport const DIVIDER = \"bg-border hover:bg-primary\";\n\nexport const DOCK_ROOT = \"flex min-h-0 min-w-0\";\n\nexport const DOCK_PANE = \"min-h-0 min-w-0 overflow-hidden\";\n\nconst CACHE_LIMIT = 256;\n\nconst compiled = new WeakMap<\n  Theme,\n  MutableHashMap.MutableHashMap<string, StyleDesc>\n>();\n\nexport const recipeStyle = (recipe: string, theme: Theme): StyleDesc => {\n  const byRecipe = compiled.get(theme);\n  if (byRecipe !== undefined) {\n    const hit = MutableHashMap.get(byRecipe, recipe);\n    if (Option.isSome(hit)) return hit.value;\n  }\n  const style = Object.freeze(classesToStyle(recipe, theme).style);\n  if (byRecipe === undefined) {\n    compiled.set(theme, MutableHashMap.make([recipe, style]));\n  } else if (MutableHashMap.size(byRecipe) < CACHE_LIMIT) {\n    MutableHashMap.set(byRecipe, recipe, style);\n  }\n  return style;\n};\n\nexport const mergeStyle = (\n  base: StyleDesc,\n  override: StyleDesc | undefined,\n): StyleDesc => (override === undefined ? base : { ...base, ...override });\n"
    },
    {
      "path": "packages/dock/src/resizable.tsx",
      "target": "@ui/gpuixui/dock/resizable.tsx",
      "type": "registry:component",
      "content": "import type { ReactElement, ReactNode } from \"react\";\nimport { Children, isValidElement, useState } from \"react\";\nimport { Panel, PanelLayout } from \"./logic.js\";\nimport { PanelGroup } from \"./panel-group.js\";\n\nexport interface ResizablePanelProps {\n  readonly id: string;\n\n  readonly defaultSize: number;\n\n  readonly label?: string;\n\n  readonly collapsible?: boolean;\n  readonly children?: ReactNode;\n}\n\nexport const ResizablePanel = (_props: ResizablePanelProps): null => null;\n\nexport interface ResizableHandleProps {\n  readonly label?: string;\n}\n\nexport const ResizableHandle = (_props: ResizableHandleProps): null => null;\n\nexport interface ResizablePanelGroupProps {\n  readonly direction?: \"horizontal\" | \"vertical\";\n\n  readonly layout?: PanelLayout;\n\n  readonly defaultLayout?: PanelLayout;\n\n  readonly onLayoutChange?: (layout: PanelLayout) => void;\n\n  readonly className?: string;\n\n  readonly testId?: string;\n  readonly children?: ReactNode;\n}\n\ninterface Declaration {\n  readonly panels: ReadonlyArray<ResizablePanelProps>;\n  readonly handles: ReadonlyArray<ResizableHandleProps>;\n}\n\nconst panelOf = (child: ReactNode): ResizablePanelProps | null =>\n  isValidElement<ResizablePanelProps>(child) && child.type === ResizablePanel\n    ? child.props\n    : null;\n\nconst handleOf = (child: ReactNode): ResizableHandleProps | null =>\n  isValidElement<ResizableHandleProps>(child) && child.type === ResizableHandle\n    ? child.props\n    : null;\n\nconst readDeclarations = (children: ReactNode): Declaration => {\n  const panels: Array<ResizablePanelProps> = [];\n  const handles: Array<ResizableHandleProps> = [];\n  Children.toArray(children).forEach((child) => {\n    const panel = panelOf(child);\n    if (panel !== null) panels.push(panel);\n    const handle = handleOf(child);\n    if (handle !== null) handles.push(handle);\n  });\n  return { panels, handles };\n};\n\nexport const layoutOfDeclarations = (\n  direction: \"horizontal\" | \"vertical\",\n  panels: ReadonlyArray<ResizablePanelProps>,\n): PanelLayout =>\n  new PanelLayout({\n    orientation: direction,\n    panels: panels.map(\n      (panel) =>\n        new Panel({\n          id: panel.id,\n          label: panel.label ?? panel.id,\n          size: panel.defaultSize,\n        }),\n    ),\n  });\n\nexport const ResizablePanelGroup = ({\n  direction = \"horizontal\",\n  layout,\n  defaultLayout,\n  onLayoutChange,\n  className,\n  testId,\n  children,\n}: ResizablePanelGroupProps): ReactElement => {\n  const { panels, handles } = readDeclarations(children);\n  const [held, setHeld] = useState(\n    () => defaultLayout ?? layoutOfDeclarations(direction, panels),\n  );\n  const current = layout ?? held;\n\n  return (\n    <PanelGroup\n      className={className}\n      dividerLabel={(index) => handles[index]?.label}\n      layout={current}\n      onLayoutChange={(next) => {\n        if (layout === undefined) setHeld(next);\n        onLayoutChange?.(next);\n      }}\n      renderPanel={(id) => panels.find((panel) => panel.id === id)?.children}\n      testId={testId}\n    />\n  );\n};\n"
    },
    {
      "path": "packages/dock/src/split-pane.tsx",
      "target": "@ui/gpuixui/dock/split-pane.tsx",
      "type": "registry:component",
      "content": "import type { ReactElement, ReactNode } from \"react\";\nimport { useRef } from \"react\";\nimport { cn } from \"../tw/index.js\";\nimport { Divider } from \"./divider.js\";\nimport { dragSize, resizeByKey, sizeBounds } from \"./logic.js\";\nimport { PANE, PANEL_ROOT, mergeStyle, recipeStyle } from \"./recipe.js\";\nimport { useTheme } from \"./theme.js\";\n\nexport interface SplitPaneProps {\n  readonly orientation?: \"horizontal\" | \"vertical\";\n\n  readonly size: number;\n\n  readonly onResize?: (size: number) => void;\n\n  readonly min?: number;\n\n  readonly max?: number;\n\n  readonly first: ReactNode;\n\n  readonly second: ReactNode;\n\n  readonly dividerLabel?: string;\n  readonly disabled?: boolean;\n\n  readonly className?: string;\n\n  readonly testId?: string;\n}\n\nexport const SplitPane = ({\n  orientation = \"horizontal\",\n  size,\n  onResize,\n  min,\n  max,\n  first,\n  second,\n  dividerLabel,\n  disabled,\n  className,\n  testId,\n}: SplitPaneProps): ReactElement => {\n  const theme = useTheme();\n  const bounds = sizeBounds(min, max);\n  const dragStart = useRef(size);\n\n  const root = mergeStyle(recipeStyle(cn(PANEL_ROOT, className), theme), {\n    flexDirection: orientation === \"horizontal\" ? \"row\" : \"column\",\n    alignItems: \"stretch\",\n    flexGrow: 1,\n    flexShrink: 1,\n    flexBasis: 0,\n  });\n\n  const firstPane = mergeStyle(recipeStyle(PANE, theme), {\n    flexGrow: 0,\n    flexShrink: 0,\n    flexBasis: size,\n  });\n  const secondPane = mergeStyle(recipeStyle(PANE, theme), {\n    flexGrow: 1,\n    flexShrink: 1,\n    flexBasis: 0,\n  });\n\n  return (\n    <div testId={testId} style={root}>\n      <div style={firstPane}>{first}</div>\n      <Divider\n        orientation={orientation}\n        valueText={`${Math.round(size)} px`}\n        label={dividerLabel}\n        disabled={disabled}\n        testId={testId === undefined ? undefined : `${testId}-divider`}\n        onDragStart={() => {\n          dragStart.current = size;\n        }}\n        onDragDelta={(delta) => {\n          onResize?.(dragSize(dragStart.current, delta, bounds));\n        }}\n        onKey={(key) => {\n          onResize?.(resizeByKey(size, key, bounds));\n        }}\n      />\n      <div style={secondPane}>{second}</div>\n    </div>\n  );\n};\n"
    },
    {
      "path": "packages/dock/src/theme.ts",
      "target": "@ui/gpuixui/dock/theme.ts",
      "type": "registry:lib",
      "content": "import type { Theme } from \"../tokens/index.js\";\nimport { useTheme as useTokenTheme } from \"../tokens/react.js\";\n\nexport {\n  controlFocusRing,\n  keyboardFocusRing,\n  textStyle,\n} from \"../tokens/index.js\";\nexport type { Theme };\n\nexport const useTheme = (): Theme => useTokenTheme();\n"
    }
  ],
  "docs": "[Runtime: dock](https://gpuixui.sudokw.com/registry) — These are GPUix native components: the files render through GPUix's reconciler against `@gpuix/react`, not the DOM, and need the per-platform `@gpuix/native` binding. The CLI installs the `@gpuixui/*` runtime the components import as registry items, so the copied tree resolves on its own; wrap it in `ThemeProvider` before it renders.",
  "meta": {
    "url": "/registry",
    "kind": "runtime",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}