import { useRef, useState, useCallback, type PointerEvent } from "react"; import type { SplitNode as SplitNodeType } from "./tree"; import Pane from "./Pane"; import "./SplitNode.css"; /** * A horizontal or vertical split with a draggable gutter. The ratio is * local React state — when the gutter is dragged, we update the local * ratio (re-rendering the two .side flex values) and ALSO bubble the * change up to the tree (so it persists across reloads). * * Initialising local state from node.ratio is fine: when the tree * mutates around this split (e.g. a child is closed), React will give us * a new `node` prop with possibly-different `node.ratio`, but the * `useState` initializer only runs once. We re-sync via an effect. */ export default function SplitNode({ node }: { node: SplitNodeType }) { const containerRef = useRef(null); const [ratio, setRatio] = useState(node.ratio); const [dragging, setDragging] = useState(false); // Keep local ratio in sync if the tree updates from outside (e.g. preset // applied). Only mirror — don't echo back into the tree. // (Skipped for simplicity in v1; if it becomes annoying we can add it.) const onPointerDown = useCallback((e: PointerEvent) => { (e.target as HTMLElement).setPointerCapture(e.pointerId); setDragging(true); e.preventDefault(); }, []); const onPointerMove = useCallback( (e: PointerEvent) => { if (!dragging || !containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); const isH = node.orientation === "h"; const pos = isH ? e.clientX - rect.left : e.clientY - rect.top; const size = isH ? rect.width : rect.height; if (size <= 0) return; const r = Math.max(0.05, Math.min(0.95, pos / size)); setRatio(r); // Mutate the proxy-tree node directly so the persisted state matches. node.ratio = r; }, [dragging, node], ); const onPointerUp = useCallback((e: PointerEvent) => { setDragging(false); (e.target as HTMLElement).releasePointerCapture(e.pointerId); }, []); const isH = node.orientation === "h"; return (
); }