@ship-it-ui/graph-editor 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/GraphEditorCanvas.tsx","../src/adapter.ts","../src/DefaultNode.tsx","../src/GraphNodeShell.tsx","../src/history.ts","../src/keyboard.ts","../src/MiniMap.tsx","../src/theme-bridge.ts"],"sourcesContent":["'use client';\n\nimport { IconGlyph } from '@ship-it-ui/icons';\nimport {\n Background,\n BackgroundVariant,\n ReactFlow,\n ReactFlowProvider,\n useEdgesState,\n useNodesState,\n useReactFlow,\n type Connection,\n type Edge,\n type EdgeTypes,\n type Node,\n type NodeChange,\n type NodeTypes,\n type OnConnect,\n type OnNodesChange,\n type ReactFlowProps,\n} from '@xyflow/react';\nimport {\n forwardRef,\n useCallback,\n useImperativeHandle,\n useMemo,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\n\n// NOTE: this file does NOT import `./styles.css`. Stylesheet loading is the\n// consumer's responsibility — Next.js (and other frameworks with strict\n// global-CSS rules) reject library-internal CSS imports. Consumers should\n// add the side-effect import once at their app entry:\n//\n// import '@ship-it-ui/graph-editor/styles.css';\n//\n// The docs-site examples in `apps/docs-site/examples/graph-editor/*` show\n// the pattern.\n\nimport { toFlowElements, type GraphElement, type GraphElementsSplit } from './adapter';\nimport { DefaultNode } from './DefaultNode';\nimport { useHistory, type Command } from './history';\nimport { useGraphEditorKeyboard } from './keyboard';\nimport { GraphEditorMiniMap } from './MiniMap';\nimport { useGraphEditorTheme } from './theme-bridge';\n\n/**\n * `<GraphEditorCanvas>` — the editing analog of `<GraphCanvas>`. Same\n * `elements[]` shape; React Flow under the hood. Consumers wire editing\n * events to their domain model; the canvas does not know about ShipIt-AI's\n * \"node types\" / \"schemas\".\n *\n * Built-in behaviors:\n * - Pan + zoom (React Flow).\n * - Click to select; pane click to clear.\n * - Drag from a node handle to another node to connect.\n * - Drag a node to move it.\n * - Delete / Backspace to remove selected; Esc to clear selection; arrows\n * to nudge selected nodes (Shift = 32px steps).\n * - ⌘Z / ⌘⇧Z for undo / redo, bounded by `historySize` (default 50).\n * - Built-in \"+ Add\" button when `toolbar` is omitted.\n * - Mini-map (wraps `<GraphMinimap>` from `@ship-it-ui/shipit`, fed live\n * viewport + node positions).\n * - Theme-token sync — repaints React Flow's CSS variables from the\n * Ship-It token palette on mount and on every `data-theme` flip.\n *\n * Undo policy: undo mutates internal RF state directly AND fires the\n * relevant consumer event in the reverse direction (e.g., undoing a\n * `delete-edge` calls `onConnect`). Node `add` / `delete` round-trips also\n * fire the matching consumer event; consumers persisting via these events\n * will see un-undone state in their store and should treat the canvas as\n * the editing source of truth during a session.\n */\n\nexport interface NodeRenderProps {\n id: string;\n data: Record<string, unknown>;\n selected: boolean;\n position: { x: number; y: number };\n}\n\nexport interface EdgeRenderProps {\n id: string;\n source: string;\n target: string;\n data: Record<string, unknown>;\n selected: boolean;\n}\n\nexport interface GraphEditorCanvasProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onSelect' | 'children'\n> {\n /** Same shape as `<GraphCanvas>`'s `elements`. */\n elements: GraphElement[];\n\n /* Editing events.\n *\n * `id` on `onNodeAdd` / `onConnect` is always present and authoritative —\n * the canvas generates it on user-initiated adds and replays the original\n * id during undo of a delete, so consumers can use it directly as the\n * identity in their persisted store.\n */\n onNodeAdd?: (node: {\n id: string;\n position: { x: number; y: number };\n /** Empty on fresh adds; populated with the prior node's data on undo. */\n data?: Record<string, unknown>;\n }) => void;\n onConnect?: (edge: { id: string; source: string; target: string }) => void;\n onNodeMove?: (nodeId: string, position: { x: number; y: number }) => void;\n onNodeDelete?: (nodeId: string) => void;\n onEdgeDelete?: (edgeId: string) => void;\n\n /* Selection (matches `<GraphCanvas>`'s `onSelect` shape). */\n onSelect?: (target: { kind: 'node' | 'edge'; id: string }) => void;\n onClearSelection?: () => void;\n\n /* Render slots. */\n renderNode?: (props: NodeRenderProps) => ReactNode;\n renderEdge?: (props: EdgeRenderProps) => ReactNode;\n toolbar?: ReactNode;\n inspector?: ReactNode;\n\n /* Behaviors. */\n panZoom?: boolean;\n /** 0 disables undo/redo. Default 50. */\n historySize?: number;\n /** Show the floating mini-map (bottom-right). Default true. */\n miniMap?: boolean;\n /** Show a faint dot grid in the background. Default 'none'. */\n background?: 'none' | 'dots';\n\n /** Accessible label for the canvas root. */\n 'aria-label'?: string;\n}\n\nexport interface GraphEditorCanvasHandle {\n /** Re-read tokens and re-paint the canvas CSS variables. */\n refreshStyles: () => void;\n /** Programmatic undo. */\n undo: () => void;\n /** Programmatic redo. */\n redo: () => void;\n}\n\n/** Inner component — assumes a `<ReactFlowProvider>` ancestor. */\nconst GraphEditorCanvasInner = forwardRef<GraphEditorCanvasHandle, GraphEditorCanvasProps>(\n function GraphEditorCanvasInner(\n {\n elements,\n onNodeAdd,\n onConnect,\n onNodeMove,\n onNodeDelete,\n onEdgeDelete,\n onSelect,\n onClearSelection,\n renderNode,\n renderEdge,\n toolbar,\n inspector,\n panZoom = true,\n historySize = 50,\n miniMap = true,\n background = 'none',\n className,\n 'aria-label': ariaLabel = 'Graph editor',\n ...rest\n },\n forwardedRef,\n ) {\n const wrapperRef = useRef<HTMLDivElement | null>(null);\n const { refresh } = useGraphEditorTheme(wrapperRef);\n const { getViewport, screenToFlowPosition } = useReactFlow();\n\n // `elements` is read once as the initial value. The canvas is the source\n // of truth for editing state from this point on; consumers observe via\n // the event callbacks (`onConnect`, `onNodeMove`, etc.) and persist as\n // they see fit. To load a different graph, remount via a `key` prop.\n //\n // Earlier revisions re-synced state on every `elements` change, but that\n // clobbered the undo history any time a consumer pushed state back from\n // an event (the common pattern shown in the docs examples).\n const initialRef = useRef<GraphElementsSplit | null>(null);\n if (initialRef.current === null) initialRef.current = toFlowElements(elements);\n const [nodes, setNodes, baseOnNodesChange] = useNodesState(initialRef.current.nodes);\n const [edges, setEdges, baseOnEdgesChange] = useEdgesState(initialRef.current.edges);\n\n const history = useHistory({ maxSize: historySize });\n\n const nodeTypes = useMemo<NodeTypes>(() => {\n if (!renderNode) return { default: DefaultNode };\n const ConsumerNode = (props: {\n id: string;\n data: unknown;\n selected?: boolean;\n positionAbsoluteX?: number;\n positionAbsoluteY?: number;\n }) => (\n <>\n {renderNode({\n id: props.id,\n data: (props.data ?? {}) as Record<string, unknown>,\n selected: Boolean(props.selected),\n position: { x: props.positionAbsoluteX ?? 0, y: props.positionAbsoluteY ?? 0 },\n })}\n </>\n );\n return { default: ConsumerNode as unknown as NodeTypes['default'] };\n }, [renderNode]);\n\n const edgeTypes = useMemo<EdgeTypes | undefined>(() => {\n if (!renderEdge) return undefined;\n const ConsumerEdge = (props: {\n id: string;\n source: string;\n target: string;\n data: unknown;\n selected?: boolean;\n }) => (\n <>\n {renderEdge({\n id: props.id,\n source: props.source,\n target: props.target,\n data: (props.data ?? {}) as Record<string, unknown>,\n selected: Boolean(props.selected),\n })}\n </>\n );\n return { default: ConsumerEdge as unknown as EdgeTypes['default'] };\n }, [renderEdge]);\n\n const handleConnect = useCallback<OnConnect>(\n (connection: Connection) => {\n if (!connection.source || !connection.target) return;\n const id = `e-${connection.source}-${connection.target}-${Date.now()}`;\n const edge: Edge = {\n id,\n source: connection.source,\n target: connection.target,\n };\n setEdges((prev) => [...prev, edge]);\n history.push({ kind: 'add-edge', edge });\n onConnect?.({ id, source: connection.source, target: connection.target });\n },\n [setEdges, history, onConnect],\n );\n\n // Track in-progress drags so the history stack captures the position\n // delta, not each intermediate frame. `dragging=true` events are the\n // drag-start; `dragging=false` is drag-end.\n const dragStartPositions = useRef(new Map<string, { x: number; y: number }>());\n\n const handleNodesChange = useCallback<OnNodesChange>(\n (changes: NodeChange[]) => {\n baseOnNodesChange(changes);\n for (const change of changes) {\n if (change.type !== 'position') continue;\n if (change.dragging === true) {\n // First frame of a drag — capture origin if we don't have one.\n if (!dragStartPositions.current.has(change.id)) {\n const node = nodes.find((n) => n.id === change.id);\n if (node) dragStartPositions.current.set(change.id, { ...node.position });\n }\n continue;\n }\n if (change.dragging === false && change.position) {\n const from = dragStartPositions.current.get(change.id);\n dragStartPositions.current.delete(change.id);\n if (from) {\n history.push({\n kind: 'move-node',\n id: change.id,\n from,\n to: change.position,\n });\n }\n onNodeMove?.(change.id, change.position);\n }\n }\n },\n [baseOnNodesChange, history, nodes, onNodeMove],\n );\n\n const handleEdgesChange = baseOnEdgesChange;\n\n const handleNodeClick = useCallback<NonNullable<ReactFlowProps['onNodeClick']>>(\n (_event, node: Node) => {\n onSelect?.({ kind: 'node', id: node.id });\n },\n [onSelect],\n );\n\n const handleEdgeClick = useCallback<NonNullable<ReactFlowProps['onEdgeClick']>>(\n (_event, edge: Edge) => {\n onSelect?.({ kind: 'edge', id: edge.id });\n },\n [onSelect],\n );\n\n const handlePaneClick = useCallback<NonNullable<ReactFlowProps['onPaneClick']>>(() => {\n onClearSelection?.();\n }, [onClearSelection]);\n\n // Forward-direction delete handler. Combined node + edge so we never\n // record an incident edge in *both* the node-delete batch and the\n // standalone edge-delete list — undo would re-add it twice otherwise.\n const deleteSelected = useCallback(() => {\n const selectedNodes = nodes.filter((n) => n.selected);\n const selectedEdges = edges.filter((e) => e.selected);\n if (selectedNodes.length === 0 && selectedEdges.length === 0) return;\n\n const removedNodeIds = new Set(selectedNodes.map((n) => n.id));\n // Edges that disappear *because* their endpoint is being removed. These\n // ride along inside the `delete-node` command and must not also be\n // recorded as standalone `delete-edge` commands.\n const incidentByNodeId = new Map<string, Edge[]>();\n const incidentEdgeIds = new Set<string>();\n for (const node of selectedNodes) {\n const incident = edges.filter((e) => e.source === node.id || e.target === node.id);\n incidentByNodeId.set(node.id, incident);\n for (const e of incident) incidentEdgeIds.add(e.id);\n }\n\n // History — nodes first (so undo re-adds them before their edges).\n for (const node of selectedNodes) {\n history.push({\n kind: 'delete-node',\n node,\n incidentEdges: incidentByNodeId.get(node.id) ?? [],\n });\n }\n for (const edge of selectedEdges) {\n if (incidentEdgeIds.has(edge.id)) continue; // already captured above\n history.push({ kind: 'delete-edge', edge });\n }\n\n // Consumer events — same dedup.\n for (const node of selectedNodes) onNodeDelete?.(node.id);\n for (const edge of selectedEdges) {\n if (incidentEdgeIds.has(edge.id)) continue;\n onEdgeDelete?.(edge.id);\n }\n\n // Internal RF state.\n if (removedNodeIds.size > 0) {\n setNodes((prev) => prev.filter((n) => !removedNodeIds.has(n.id)));\n }\n const droppedEdgeIds = new Set<string>(incidentEdgeIds);\n for (const e of selectedEdges) droppedEdgeIds.add(e.id);\n if (droppedEdgeIds.size > 0) {\n setEdges((prev) => prev.filter((e) => !droppedEdgeIds.has(e.id)));\n }\n }, [edges, history, nodes, onEdgeDelete, onNodeDelete, setEdges, setNodes]);\n\n // Undo / redo. When we apply a command (forward or reverse), we mutate\n // internal RF state directly and re-emit the matching consumer event so\n // the consumer's persistence layer can stay in sync.\n const applyCommand = useCallback(\n (command: Command) => {\n switch (command.kind) {\n case 'add-node':\n setNodes((prev) => [...prev, command.node]);\n // Replay carries the full node so the consumer can restore the\n // exact identity + data they previously persisted. Fresh adds\n // (from `handleAddClick`) pass an empty `data` bag.\n onNodeAdd?.({\n id: command.node.id,\n position: command.node.position,\n data: (command.node.data ?? {}) as Record<string, unknown>,\n });\n return;\n case 'delete-node': {\n const removedId = command.node.id;\n setNodes((prev) => prev.filter((n) => n.id !== removedId));\n const incidentIds = new Set(command.incidentEdges.map((e) => e.id));\n setEdges((prev) => prev.filter((e) => !incidentIds.has(e.id)));\n onNodeDelete?.(removedId);\n return;\n }\n case 'add-edge':\n setEdges((prev) => [...prev, command.edge]);\n onConnect?.({\n id: command.edge.id,\n source: command.edge.source,\n target: command.edge.target,\n });\n return;\n case 'delete-edge':\n setEdges((prev) => prev.filter((e) => e.id !== command.edge.id));\n onEdgeDelete?.(command.edge.id);\n return;\n case 'move-node':\n setNodes((prev) =>\n prev.map((n) => (n.id === command.id ? { ...n, position: command.to } : n)),\n );\n onNodeMove?.(command.id, command.to);\n return;\n case 'batch':\n for (const child of command.commands) applyCommand(child);\n return;\n }\n },\n [onConnect, onEdgeDelete, onNodeAdd, onNodeDelete, onNodeMove, setEdges, setNodes],\n );\n\n const undo = useCallback(() => {\n const inverse = history.undo();\n if (inverse) applyCommand(inverse);\n }, [history, applyCommand]);\n\n const redo = useCallback(() => {\n const cmd = history.redo();\n if (cmd) applyCommand(cmd);\n }, [history, applyCommand]);\n\n // Keyboard handler — owns Delete/Arrow/Esc/⌘Z. The keyboard hook reports\n // selection-based removals via `onNodesChange` + `onEdgesChange` in two\n // separate calls within a single keypress. We re-route the first batch\n // through `deleteSelected` (which dedupes node + incident-edge\n // bookkeeping) and let the ref short-circuit the second call so we don't\n // double-fire history pushes.\n const deleteFlushedRef = useRef(false);\n const onKeyDown = useGraphEditorKeyboard({\n nodes,\n edges,\n onNodesChange: (changes) => {\n const removals = changes.filter((c) => c.type === 'remove');\n if (removals.length > 0) {\n if (!deleteFlushedRef.current) {\n deleteSelected();\n deleteFlushedRef.current = true;\n // Reset after the current microtask so the next Delete keypress\n // starts fresh.\n queueMicrotask(() => {\n deleteFlushedRef.current = false;\n });\n }\n const remaining = changes.filter((c) => c.type !== 'remove');\n if (remaining.length > 0) baseOnNodesChange(remaining);\n } else {\n baseOnNodesChange(changes);\n }\n },\n onEdgesChange: (changes) => {\n const removals = changes.filter((c) => c.type === 'remove');\n if (removals.length > 0) {\n if (!deleteFlushedRef.current) {\n deleteSelected();\n deleteFlushedRef.current = true;\n queueMicrotask(() => {\n deleteFlushedRef.current = false;\n });\n }\n const remaining = changes.filter((c) => c.type !== 'remove');\n if (remaining.length > 0) baseOnEdgesChange(remaining);\n } else {\n baseOnEdgesChange(changes);\n }\n },\n // Don't double-fire delete events — deleteSelected*() already does that.\n onNodeDelete: undefined,\n onEdgeDelete: undefined,\n onNodeMove,\n onArrowNudge: (id, from, to) => {\n history.push({ kind: 'move-node', id, from, to });\n },\n onClearSelection,\n undo,\n redo,\n });\n\n // Built-in \"+ Add\" button. Pressed → fires `onNodeAdd` with the current\n // viewport center, expressed in graph (flow) coordinates so the consumer\n // can drop a new node where the user is looking. Suppressed when the\n // consumer supplies their own toolbar.\n const handleAddClick = useCallback(() => {\n const vp = getViewport();\n const wrapper = wrapperRef.current;\n const rect = wrapper?.getBoundingClientRect();\n const position = rect\n ? screenToFlowPosition({\n x: rect.left + rect.width / 2,\n y: rect.top + rect.height / 2,\n })\n : { x: -vp.x, y: -vp.y };\n // The canvas owns the id so subsequent events (delete, undo) carry the\n // same identity the consumer just persisted.\n const id = `node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;\n // Mutate internal RF state and push to history — matches the pattern\n // in `applyCommand('add-node')` so the node appears immediately and\n // ⌘Z can undo the add. The consumer callback runs last so persistence\n // sees the state the canvas already shows.\n const newNode: Node = { id, position, data: {} };\n setNodes((prev) => [...prev, newNode]);\n history.push({ kind: 'add-node', node: newNode });\n onNodeAdd?.({ id, position, data: {} });\n }, [getViewport, screenToFlowPosition, setNodes, history, onNodeAdd]);\n\n useImperativeHandle(forwardedRef, () => ({ refreshStyles: refresh, undo, redo }), [\n refresh,\n undo,\n redo,\n ]);\n\n return (\n // `role=\"application\"` is the ARIA-recommended container for a custom\n // widget that owns its keyboard semantics — the canvas implements its\n // own arrow-nudge / Delete / Escape / ⌘Z bindings. eslint-jsx-a11y\n // doesn't recognize `application` as interactive, so the rules below\n // are disabled for this single element.\n /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */\n <div\n ref={wrapperRef}\n role=\"application\"\n aria-label={ariaLabel}\n tabIndex={0}\n data-background={background}\n className={['ship-graph-editor', className].filter(Boolean).join(' ')}\n onKeyDown={onKeyDown}\n {...rest}\n >\n <ReactFlow\n nodes={nodes}\n edges={edges}\n nodeTypes={nodeTypes}\n edgeTypes={edgeTypes}\n onNodesChange={handleNodesChange}\n onEdgesChange={handleEdgesChange}\n onConnect={handleConnect}\n onNodeClick={handleNodeClick}\n onEdgeClick={handleEdgeClick}\n onPaneClick={handlePaneClick}\n panOnDrag={panZoom}\n zoomOnScroll={panZoom}\n zoomOnPinch={panZoom}\n zoomOnDoubleClick={false}\n // We own keyboard via onKeyDown above — disable RF's defaults so\n // they don't double-fire alongside ours.\n deleteKeyCode={null}\n proOptions={{ hideAttribution: true }}\n fitView\n >\n {background === 'dots' && (\n <Background variant={BackgroundVariant.Dots} gap={20} size={1} />\n )}\n </ReactFlow>\n\n {/* Toolbar slot (top-left). Built-in \"+ Add\" rendered when none provided. */}\n {toolbar ? (\n <div className=\"absolute top-4 left-4 z-10\">{toolbar}</div>\n ) : (\n onNodeAdd && (\n <div className=\"absolute top-4 left-4 z-10\">\n <button\n type=\"button\"\n onClick={handleAddClick}\n aria-label=\"Add node\"\n className=\"bg-panel border-border text-text hover:bg-panel-2 focus-visible:ring-accent-dim flex h-8 items-center gap-1 rounded-md border px-3 text-[12px] font-medium transition-colors outline-none focus-visible:ring-[3px]\"\n >\n <IconGlyph name=\"add\" size={14} />\n <span>Add</span>\n </button>\n </div>\n )\n )}\n\n {/* Inspector slot (top-right). */}\n {inspector && <div className=\"absolute top-4 right-4 z-10\">{inspector}</div>}\n\n {/* Mini-map (bottom-right). */}\n {miniMap && (\n <div className=\"absolute right-4 bottom-4 z-10\">\n <GraphEditorMiniMap />\n </div>\n )}\n </div>\n /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */\n );\n },\n);\n\n/**\n * Public component. Wraps the inner canvas in a `<ReactFlowProvider>` so\n * consumers don't need to set one up themselves — and so `useReactFlow`\n * hooks inside the inner tree work even when the canvas is rendered\n * standalone.\n */\nexport const GraphEditorCanvas = forwardRef<GraphEditorCanvasHandle, GraphEditorCanvasProps>(\n function GraphEditorCanvas(props, ref) {\n return (\n <ReactFlowProvider>\n <GraphEditorCanvasInner {...props} ref={ref} />\n </ReactFlowProvider>\n );\n },\n);\n\nGraphEditorCanvas.displayName = 'GraphEditorCanvas';\n","/**\n * Adapter between Cytoscape's flat `ElementDefinition[]` shape and React Flow's\n * split `{ nodes, edges }` arrays. The contract:\n *\n * - Position round-trips lossless. `{ x, y }` in equals `{ x, y }` out.\n * - Edge identity is `data.id`; if absent, an `${source}->${target}` id is\n * synthesised. Round-trip preserves synthesised ids verbatim.\n * - Every key inside `data` (other than `id`/`source`/`target`) passes\n * through untouched in both directions — this is the consumer's domain\n * bag, the adapter is not allowed to interpret it.\n * - Cytoscape `classes` (space-separated string) ↔ React Flow `className`.\n *\n * Untested edge cases that fall through to \"do nothing surprising\":\n * - Elements without `data.id` on the node side are dropped (Cytoscape\n * auto-generates ids; we'd lose track of identity across round-trips).\n * - Unknown top-level keys on the input element are preserved on a\n * `_extra` field so we never silently lose data.\n */\n\nimport type { Edge as RFEdge, Node as RFNode } from '@xyflow/react';\n\nexport interface GraphElementData {\n id?: string;\n source?: string;\n target?: string;\n [key: string]: unknown;\n}\n\nexport interface GraphElement {\n data: GraphElementData;\n position?: { x: number; y: number };\n classes?: string;\n /** Free-form metadata preserved across round-trip (e.g., `grabbable`). */\n [extraKey: string]: unknown;\n}\n\nexport interface GraphElementsSplit {\n nodes: RFNode[];\n edges: RFEdge[];\n}\n\nconst RF_NODE_RESERVED = new Set<string>(['_extra']);\nconst RF_EDGE_RESERVED = new Set<string>(['_extra']);\n\nfunction pickDataPassthrough(\n data: GraphElementData,\n exclude: ReadonlyArray<string>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(data)) {\n if (exclude.includes(key)) continue;\n out[key] = data[key];\n }\n return out;\n}\n\nfunction pickElementExtras(element: GraphElement): {\n extras: Record<string, unknown> | undefined;\n hasExtras: boolean;\n} {\n const extras: Record<string, unknown> = {};\n let hasExtras = false;\n for (const key of Object.keys(element)) {\n if (key === 'data' || key === 'position' || key === 'classes') continue;\n extras[key] = element[key];\n hasExtras = true;\n }\n return { extras: hasExtras ? extras : undefined, hasExtras };\n}\n\n/**\n * Partition a Cytoscape `elements[]` array into React Flow `{ nodes, edges }`.\n * An element is an edge if `data.source` is set; otherwise a node.\n */\nexport function toFlowElements(elements: ReadonlyArray<GraphElement>): GraphElementsSplit {\n const nodes: RFNode[] = [];\n const edges: RFEdge[] = [];\n\n for (const el of elements) {\n const data = el.data ?? {};\n const isEdge = typeof data.source === 'string' && typeof data.target === 'string';\n if (isEdge) {\n const id = typeof data.id === 'string' ? data.id : `${data.source}->${data.target}`;\n const { extras, hasExtras } = pickElementExtras(el);\n const edge: RFEdge = {\n id,\n source: data.source as string,\n target: data.target as string,\n data: {\n ...pickDataPassthrough(data, ['id', 'source', 'target']),\n ...(hasExtras ? { _extra: extras } : {}),\n },\n };\n if (el.classes) edge.className = el.classes;\n edges.push(edge);\n } else {\n const id = data.id;\n if (typeof id !== 'string') continue; // Identity-less nodes dropped — see file header.\n const { extras, hasExtras } = pickElementExtras(el);\n const node: RFNode = {\n id,\n position: el.position ?? { x: 0, y: 0 },\n data: {\n ...pickDataPassthrough(data, ['id']),\n ...(hasExtras ? { _extra: extras } : {}),\n },\n };\n if (el.classes) node.className = el.classes;\n nodes.push(node);\n }\n }\n\n return { nodes, edges };\n}\n\n/**\n * Inverse of `toFlowElements`. Nodes are emitted before edges to match\n * Cytoscape's expectation (an edge's source/target nodes must exist by the\n * time the engine sees the edge).\n */\nexport function toCytoscapeElements(split: GraphElementsSplit): GraphElement[] {\n const out: GraphElement[] = [];\n\n for (const node of split.nodes) {\n const data = (node.data ?? {}) as Record<string, unknown>;\n const passthrough = { ...data };\n const extras = passthrough._extra as Record<string, unknown> | undefined;\n delete passthrough._extra;\n const element: GraphElement = {\n data: { id: node.id, ...passthrough },\n position: { x: node.position.x, y: node.position.y },\n };\n if (node.className) element.classes = node.className;\n if (extras) {\n for (const key of Object.keys(extras)) {\n if (key === 'data' || key === 'position' || key === 'classes') continue;\n element[key] = extras[key];\n }\n }\n out.push(element);\n }\n\n for (const edge of split.edges) {\n const data = (edge.data ?? {}) as Record<string, unknown>;\n const passthrough = { ...data };\n const extras = passthrough._extra as Record<string, unknown> | undefined;\n delete passthrough._extra;\n const element: GraphElement = {\n data: {\n id: edge.id,\n source: edge.source,\n target: edge.target,\n ...passthrough,\n },\n };\n if (edge.className) element.classes = edge.className;\n if (extras) {\n for (const key of Object.keys(extras)) {\n if (key === 'data' || key === 'position' || key === 'classes') continue;\n element[key] = extras[key];\n }\n }\n out.push(element);\n }\n\n return out;\n}\n\n// Re-export for consumers writing their own renderers, so they don't need to\n// pull `@xyflow/react` types directly when they only need the node/edge shape.\nexport type { RFNode as FlowNode, RFEdge as FlowEdge };\n\n// Cytoscape-side reserved keys exposed for tests that want to assert the\n// passthrough excludes them.\nexport const _RF_NODE_RESERVED = RF_NODE_RESERVED;\nexport const _RF_EDGE_RESERVED = RF_EDGE_RESERVED;\n","'use client';\n\nimport { Handle, Position, type NodeProps } from '@xyflow/react';\n\nimport { GraphNodeShell } from './GraphNodeShell';\n\n/**\n * Default React Flow node for `<GraphEditorCanvas>`. Wraps `<GraphNodeShell>`\n * (the canonical visual) and adds the four edge-attachment handles React Flow\n * needs to draw connections from. Handles are invisible by default — they\n * become discoverable on hover so the canvas doesn't look busier than the\n * viewer when idle.\n *\n * When the consumer passes their own `renderNode`, this default is replaced\n * via `nodeTypes`. The shell + handle helpers stay exported so a custom node\n * can still adopt the canonical selection ring without re-rolling it.\n */\n\nexport interface DefaultNodeData {\n /** Caption rendered below the square. */\n label?: string;\n /** Registered entity type. Drives glyph + color. */\n entityType?: string;\n /** Optional state override — `path` / `dim` show through to the shell. */\n state?: 'default' | 'hover' | 'selected' | 'path' | 'dim';\n [key: string]: unknown;\n}\n\nexport function DefaultNode(props: NodeProps) {\n const data = (props.data ?? {}) as DefaultNodeData;\n const type = (data.entityType ?? 'service') as Parameters<typeof GraphNodeShell>[0]['type'];\n const state = props.selected ? 'selected' : (data.state ?? 'default');\n return (\n <>\n <Handle type=\"target\" position={Position.Top} className=\"ship-graph-handle\" />\n <Handle type=\"target\" position={Position.Left} className=\"ship-graph-handle\" />\n <GraphNodeShell type={type} state={state} label={data.label} />\n <Handle type=\"source\" position={Position.Bottom} className=\"ship-graph-handle\" />\n <Handle type=\"source\" position={Position.Right} className=\"ship-graph-handle\" />\n </>\n );\n}\n","'use client';\n\nimport { DynamicIconGlyph } from '@ship-it-ui/icons';\nimport { getEntityTypeMeta, type EntityType } from '@ship-it-ui/shipit';\nimport { cn } from '@ship-it-ui/ui';\nimport { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\n\n/**\n * Visual shell used by the editor's default node renderer. Exported so\n * consumers writing custom `renderNode` can opt back into the selection ring\n * / glow without re-implementing the canonical visual.\n *\n * Mirrors `<GraphNode>` from `@ship-it-ui/shipit/graph` (52px round-rectangle,\n * entity-type color, glow, label below). Visual parity with the cytoscape\n * viewer is a design contract — when this drifts, the editor will start to\n * look like a React Flow demo rather than a Ship-It surface.\n */\n\nexport type GraphNodeShellState = 'default' | 'hover' | 'selected' | 'path' | 'dim';\n\nexport interface GraphNodeShellProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {\n /** Registered entity type. Drives both glyph and color. */\n type: EntityType;\n /** Visual state. Selection ring + glow are state-driven. */\n state?: GraphNodeShellState;\n /** Override the leading glyph. */\n glyph?: ReactNode;\n /** Caption rendered below the square. */\n label?: ReactNode;\n /** Pixel size of the square. Default 52 — matches `<GraphNode>`. */\n size?: number;\n /** Override the \"on-path\" purple ring color. */\n pathColor?: string;\n}\n\nexport const GraphNodeShell = forwardRef<HTMLDivElement, GraphNodeShellProps>(\n function GraphNodeShell(\n { type, state = 'default', glyph, label, size = 52, pathColor, className, style, ...props },\n ref,\n ) {\n const meta = getEntityTypeMeta(type);\n const color = state === 'path' ? (pathColor ?? 'var(--color-purple)') : meta.colorVar;\n const glowPct = state === 'hover' ? 50 : 25;\n const opacity = state === 'dim' ? 0.35 : 1;\n const showRing = state === 'selected' || state === 'path';\n // Accessible-name strategy:\n // - string label → `role=\"img\" aria-label={label}` (the canvas reads\n // as a single named image).\n // - no label → `role=\"img\" aria-label=\"${type} node\"` (generic\n // fallback so the image still has a name).\n // - ReactNode lbl → drop `role=\"img\"` entirely. The child element\n // (e.g. `<InlineEdit>`) is self-describing, and a\n // nameless `role=\"img\"` would fail axe's\n // `role-img-alt` rule.\n const ariaRole: 'img' | undefined =\n typeof label === 'string' || label == null ? 'img' : undefined;\n const ariaLabel =\n typeof label === 'string' ? label : label == null ? `${type} node` : undefined;\n return (\n <div\n ref={ref}\n role={ariaRole}\n aria-label={ariaLabel}\n data-state={state}\n data-entity-type={type}\n className={cn('inline-flex flex-col items-center gap-[6px]', className)}\n style={style}\n {...props}\n >\n <div\n className=\"bg-panel grid place-items-center rounded-[14px] border-[1.5px] transition-all duration-(--duration-micro)\"\n style={{\n width: size,\n height: size,\n borderColor: color,\n color,\n fontSize: Math.round(size * 0.42),\n boxShadow: `0 0 ${state === 'hover' ? 30 : 20}px color-mix(in oklab, ${color} ${glowPct}%, transparent)`,\n outline: showRing ? `2px solid ${color}` : undefined,\n outlineOffset: showRing ? 4 : undefined,\n opacity,\n }}\n >\n {glyph ?? <DynamicIconGlyph name={meta.iconName} size={Math.round(size * 0.42)} />}\n </div>\n {label && <span className=\"text-text-dim font-mono text-[10px]\">{label}</span>}\n </div>\n );\n },\n);\n\nGraphNodeShell.displayName = 'GraphNodeShell';\n","'use client';\n\nimport type { Edge, Node } from '@xyflow/react';\nimport { useCallback, useMemo, useRef } from 'react';\n\n/**\n * Editor history stack. Every user action pushes a `Command`; `undo()` pops\n * the top and returns its inverse for the caller to apply. The caller is\n * responsible for translating the inverse into the right internal-state\n * mutation + consumer-callback combination — `history.ts` is engine-agnostic.\n *\n * The stack is bounded by `maxSize`. Pushing past the cap drops the oldest\n * entry (FIFO eviction). A new push after `undo()` truncates any redo\n * entries — same as every editor.\n */\n\nexport type Command =\n | { kind: 'add-node'; node: Node }\n | { kind: 'delete-node'; node: Node; incidentEdges: Edge[] }\n | { kind: 'add-edge'; edge: Edge }\n | { kind: 'delete-edge'; edge: Edge }\n | { kind: 'move-node'; id: string; from: { x: number; y: number }; to: { x: number; y: number } }\n | { kind: 'batch'; commands: Command[] };\n\nexport function inverseOf(command: Command): Command {\n switch (command.kind) {\n case 'add-node':\n return { kind: 'delete-node', node: command.node, incidentEdges: [] };\n case 'delete-node':\n // Re-introducing a deleted node also restores any edges that were\n // dropped with it. The batch shape lets a single undo replay both.\n return {\n kind: 'batch',\n commands: [\n { kind: 'add-node', node: command.node },\n ...command.incidentEdges.map<Command>((edge) => ({ kind: 'add-edge', edge })),\n ],\n };\n case 'add-edge':\n return { kind: 'delete-edge', edge: command.edge };\n case 'delete-edge':\n return { kind: 'add-edge', edge: command.edge };\n case 'move-node':\n return { kind: 'move-node', id: command.id, from: command.to, to: command.from };\n case 'batch':\n return { kind: 'batch', commands: command.commands.map(inverseOf).reverse() };\n }\n}\n\nexport interface UseHistoryOptions {\n /** Cap on the undo stack length. 0 disables history entirely. */\n maxSize?: number;\n}\n\nexport interface UseHistoryReturn {\n /** Push a command onto the undo stack. Truncates pending redo. */\n push: (cmd: Command) => void;\n /** Pop the undo-stack top and return its inverse, or null if empty. */\n undo: () => Command | null;\n /** Pop the redo-stack top and return its inverse, or null if empty. */\n redo: () => Command | null;\n /** Clear both stacks (used when a fresh `elements` prop arrives). */\n reset: () => void;\n /** Read-only count snapshot for inspection. */\n size: () => { undo: number; redo: number };\n}\n\nexport function useHistory({ maxSize = 50 }: UseHistoryOptions = {}): UseHistoryReturn {\n // Refs rather than state — pushing a command shouldn't trigger a render.\n // The canvas re-renders for its own reasons when the underlying RF state\n // changes; history is bookkeeping.\n const undoStack = useRef<Command[]>([]);\n const redoStack = useRef<Command[]>([]);\n\n const push = useCallback(\n (cmd: Command) => {\n if (maxSize <= 0) return;\n undoStack.current.push(cmd);\n if (undoStack.current.length > maxSize) undoStack.current.shift();\n // Any new edit invalidates the redo path.\n redoStack.current.length = 0;\n },\n [maxSize],\n );\n\n const undo = useCallback(() => {\n if (maxSize <= 0) return null;\n const cmd = undoStack.current.pop();\n if (!cmd) return null;\n redoStack.current.push(cmd);\n return inverseOf(cmd);\n }, [maxSize]);\n\n const redo = useCallback(() => {\n if (maxSize <= 0) return null;\n const cmd = redoStack.current.pop();\n if (!cmd) return null;\n undoStack.current.push(cmd);\n return cmd;\n }, [maxSize]);\n\n const reset = useCallback(() => {\n undoStack.current.length = 0;\n redoStack.current.length = 0;\n }, []);\n\n const size = useCallback(\n () => ({ undo: undoStack.current.length, redo: redoStack.current.length }),\n [],\n );\n\n // Memoize the wrapper so `useEffect` deps that include this object don't\n // fire on every render. The individual fns are already `useCallback`-stable.\n return useMemo(() => ({ push, undo, redo, reset, size }), [push, undo, redo, reset, size]);\n}\n","'use client';\n\nimport type { Edge, Node, OnEdgesChange, OnNodesChange } from '@xyflow/react';\nimport { useCallback, type KeyboardEvent as ReactKeyboardEvent } from 'react';\n\n/**\n * Keyboard handler factory for `<GraphEditorCanvas>`. Returns a single\n * `onKeyDown` handler that the canvas wires to its `role=application` root.\n *\n * Wires:\n * - `Delete` / `Backspace` — fires `onNodeDelete` / `onEdgeDelete` for each\n * selected entity, then mutates internal RF state via the supplied\n * change handlers.\n * - `Escape` — clears selection and fires `onClearSelection`.\n * - Arrow keys — nudge selected nodes by 8px (Shift = 32px) and fire\n * `onNodeMove` per affected node.\n * - `⌘Z` / `Ctrl+Z` — calls `undo` callback (history wiring in the canvas).\n * - `⌘⇧Z` / `Ctrl+Shift+Z` — calls `redo` callback.\n *\n * The handler is attached to the canvas root (`role=application`), not to\n * `document`, so a graph hosted inside a modal doesn't swallow keys from\n * outside the canvas.\n */\n\nconst NUDGE_BASE = 8;\nconst NUDGE_LARGE = 32;\n\nexport interface KeyboardHandlerOptions {\n /** Current internal nodes (for reading selection state). */\n nodes: Node[];\n /** Current internal edges (for reading selection state). */\n edges: Edge[];\n /** RF's onNodesChange — mutates internal state (delete / move). */\n onNodesChange: OnNodesChange;\n /** RF's onEdgesChange — mutates internal state (delete). */\n onEdgesChange: OnEdgesChange;\n\n /** Consumer-facing events. All optional. */\n onNodeDelete?: (nodeId: string) => void;\n onEdgeDelete?: (edgeId: string) => void;\n onNodeMove?: (nodeId: string, position: { x: number; y: number }) => void;\n onClearSelection?: () => void;\n /**\n * Internal-only hook for the canvas to record an arrow-nudge in its\n * history stack. Drag moves already have a from/to pair from the\n * `tapstart` / `tapend` lifecycle; keyboard nudges synthesize their own\n * frames so the canvas can't recover the `from` after the fact.\n */\n onArrowNudge?: (id: string, from: { x: number; y: number }, to: { x: number; y: number }) => void;\n /** Called when ⌘Z / Ctrl+Z is pressed. */\n undo?: () => void;\n /** Called when ⌘⇧Z / Ctrl+Shift+Z is pressed. */\n redo?: () => void;\n}\n\nexport function useGraphEditorKeyboard({\n nodes,\n edges,\n onNodesChange,\n onEdgesChange,\n onNodeDelete,\n onEdgeDelete,\n onNodeMove,\n onArrowNudge,\n onClearSelection,\n undo,\n redo,\n}: KeyboardHandlerOptions) {\n return useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const key = event.key;\n const meta = event.metaKey || event.ctrlKey;\n\n // Don't hijack keys when the user is typing inside an inline editor or\n // other text field that bubbled into the canvas root.\n const target = event.target as HTMLElement | null;\n if (target) {\n const tag = target.tagName;\n const editable = target.isContentEditable;\n if (editable || tag === 'INPUT' || tag === 'TEXTAREA') return;\n }\n\n if (meta && (key === 'z' || key === 'Z')) {\n event.preventDefault();\n if (event.shiftKey) redo?.();\n else undo?.();\n return;\n }\n\n if (key === 'Delete' || key === 'Backspace') {\n const selectedNodes = nodes.filter((n) => n.selected);\n const selectedEdges = edges.filter((e) => e.selected);\n if (selectedNodes.length === 0 && selectedEdges.length === 0) return;\n event.preventDefault();\n for (const n of selectedNodes) onNodeDelete?.(n.id);\n for (const e of selectedEdges) onEdgeDelete?.(e.id);\n if (selectedNodes.length > 0) {\n onNodesChange(selectedNodes.map((n) => ({ type: 'remove', id: n.id })));\n }\n if (selectedEdges.length > 0) {\n onEdgesChange(selectedEdges.map((e) => ({ type: 'remove', id: e.id })));\n }\n return;\n }\n\n if (key === 'Escape') {\n const selectedNodes = nodes.filter((n) => n.selected);\n const selectedEdges = edges.filter((e) => e.selected);\n if (selectedNodes.length === 0 && selectedEdges.length === 0) return;\n event.preventDefault();\n onNodesChange(selectedNodes.map((n) => ({ type: 'select', id: n.id, selected: false })));\n onEdgesChange(selectedEdges.map((e) => ({ type: 'select', id: e.id, selected: false })));\n onClearSelection?.();\n return;\n }\n\n const dx = key === 'ArrowLeft' ? -1 : key === 'ArrowRight' ? 1 : 0;\n const dy = key === 'ArrowUp' ? -1 : key === 'ArrowDown' ? 1 : 0;\n if (dx === 0 && dy === 0) return;\n const selectedNodes = nodes.filter((n) => n.selected);\n if (selectedNodes.length === 0) return;\n event.preventDefault();\n const step = event.shiftKey ? NUDGE_LARGE : NUDGE_BASE;\n const changes = selectedNodes.map((n) => {\n const from = { x: n.position.x, y: n.position.y };\n const nextPos = { x: from.x + dx * step, y: from.y + dy * step };\n onArrowNudge?.(n.id, from, nextPos);\n onNodeMove?.(n.id, nextPos);\n return {\n type: 'position' as const,\n id: n.id,\n position: nextPos,\n dragging: false,\n };\n });\n onNodesChange(changes);\n },\n [\n nodes,\n edges,\n onNodesChange,\n onEdgesChange,\n onNodeDelete,\n onEdgeDelete,\n onNodeMove,\n onArrowNudge,\n onClearSelection,\n undo,\n redo,\n ],\n );\n}\n","'use client';\n\nimport { GraphMinimap } from '@ship-it-ui/shipit';\nimport { useNodes, useViewport, useStore, type Node } from '@xyflow/react';\nimport { useMemo } from 'react';\n\n/**\n * Mini-map for `<GraphEditorCanvas>`. Wraps `<GraphMinimap>` (already shipped\n * by `@ship-it-ui/shipit`) and feeds it normalized node positions + viewport\n * coordinates from React Flow's internal store. We don't use RF's built-in\n * `<MiniMap>` — visual parity with the cytoscape viewer is a contract, so we\n * adopt the existing component.\n *\n * Padding is added to the bounding box so nodes near the corners aren't\n * pinned against the minimap edges. The viewport rectangle is computed in\n * the same normalized space.\n */\n\nconst PADDING = 60; // graph-coordinate padding around the bbox\n\nfunction nodeBounds(nodes: Node[]): { x: number; y: number; width: number; height: number } | null {\n if (nodes.length === 0) return null;\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const node of nodes) {\n const { x, y } = node.position;\n const w = node.measured?.width ?? node.width ?? 52;\n const h = node.measured?.height ?? node.height ?? 52;\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x + w > maxX) maxX = x + w;\n if (y + h > maxY) maxY = y + h;\n }\n return {\n x: minX - PADDING,\n y: minY - PADDING,\n width: maxX - minX + 2 * PADDING,\n height: maxY - minY + 2 * PADDING,\n };\n}\n\nexport interface GraphEditorMiniMapProps {\n /** Pixel width. Default 120. */\n width?: number;\n /** Pixel height. Default 72. */\n height?: number;\n className?: string;\n}\n\nexport function GraphEditorMiniMap({\n width = 120,\n height = 72,\n className,\n}: GraphEditorMiniMapProps) {\n const nodes = useNodes();\n const viewport = useViewport();\n const canvasSize = useStore((s) => ({ width: s.width, height: s.height }));\n\n const { points, viewportRect } = useMemo(() => {\n const bbox = nodeBounds(nodes);\n if (!bbox || bbox.width === 0 || bbox.height === 0) {\n return { points: [], viewportRect: undefined };\n }\n const norm = (x: number, y: number) => ({\n x: (x - bbox.x) / bbox.width,\n y: (y - bbox.y) / bbox.height,\n });\n const points = nodes.map((node) => {\n const center = {\n x: node.position.x + (node.measured?.width ?? node.width ?? 52) / 2,\n y: node.position.y + (node.measured?.height ?? node.height ?? 52) / 2,\n };\n return norm(center.x, center.y);\n });\n\n // Visible viewport in graph coordinates: (−viewport.x, −viewport.y)\n // through (−viewport.x + width/zoom, −viewport.y + height/zoom).\n const z = viewport.zoom || 1;\n const vp = {\n x: -viewport.x / z,\n y: -viewport.y / z,\n w: canvasSize.width / z,\n h: canvasSize.height / z,\n };\n const tl = norm(vp.x, vp.y);\n const br = norm(vp.x + vp.w, vp.y + vp.h);\n // Clamp both corners independently into [0, 1] graph-space, then derive\n // width/height from the clamped corners. Without this, panning past the\n // node bounding box yielded `width`/`height` > 1, which `<GraphMinimap>`\n // renders as percentages and ends up overflowing the minimap chrome.\n const clampedX = Math.max(0, Math.min(1, tl.x));\n const clampedY = Math.max(0, Math.min(1, tl.y));\n const clampedRight = Math.max(0, Math.min(1, br.x));\n const clampedBottom = Math.max(0, Math.min(1, br.y));\n const viewportRect = {\n x: clampedX,\n y: clampedY,\n width: Math.max(0, clampedRight - clampedX),\n height: Math.max(0, clampedBottom - clampedY),\n };\n return { points, viewportRect };\n }, [nodes, viewport, canvasSize.width, canvasSize.height]);\n\n return (\n <GraphMinimap\n points={points}\n viewport={viewportRect}\n width={width}\n height={height}\n className={className}\n />\n );\n}\n","'use client';\n\nimport { readThemeTokens, type ThemeTokenPalette } from '@ship-it-ui/graph-tokens';\nimport { useCallback, useEffect, useState, type RefObject } from 'react';\n\n/**\n * Mirrors `@ship-it-ui/cytoscape`'s `useShipItStylesheet` for React Flow.\n * RF reads its own CSS variables (`--xy-node-background-color-default`, etc.)\n * off the canvas wrapper, so the bridge:\n *\n * 1. Reads the canonical Ship-It tokens (oklch-coerced to sRGB).\n * 2. Writes them onto the wrapper as RF's expected CSS variable names.\n * 3. Subscribes to `data-theme` flips on `<html>` and re-runs.\n *\n * The bridge also exposes the resolved palette so consumers writing custom\n * `renderNode` can read the same values without re-resolving.\n */\n\nconst RF_VAR_MAP = {\n '--xy-background-color-default': 'bg',\n '--xy-node-background-color-default': 'panel',\n '--xy-node-border-default': 'accent',\n '--xy-node-color-default': 'text',\n '--xy-edge-stroke-default': 'accent',\n '--xy-edge-stroke-selected-default': 'accent',\n '--xy-selection-background-color-default': 'accent',\n '--xy-controls-button-background-color-default': 'panel',\n '--xy-controls-button-background-color-hover-default': 'panel-2',\n '--xy-controls-button-color-default': 'text',\n '--xy-controls-button-border-color-default': 'border',\n} as const;\n\ntype PaletteKey =\n | 'bg'\n | 'panel'\n | 'panel-2'\n | 'border'\n | 'border-strong'\n | 'text'\n | 'text-muted'\n | 'text-dim'\n | 'accent'\n | 'ok'\n | 'warn'\n | 'err'\n | 'purple'\n | 'pink';\n\nfunction paletteLookup(palette: ThemeTokenPalette, key: PaletteKey): string {\n switch (key) {\n case 'bg':\n return palette.bg;\n case 'panel':\n return palette.panel;\n case 'panel-2':\n return palette.panel2;\n case 'border':\n return palette.border;\n case 'border-strong':\n return palette.borderStrong;\n case 'text':\n return palette.text;\n case 'text-muted':\n return palette.textMuted;\n case 'text-dim':\n return palette.textDim;\n case 'accent':\n return palette.accent;\n case 'ok':\n return palette.ok;\n case 'warn':\n return palette.warn;\n case 'err':\n return palette.err;\n case 'purple':\n return palette.purple;\n case 'pink':\n return palette.pink;\n }\n}\n\nfunction applyPalette(el: HTMLElement, palette: ThemeTokenPalette): void {\n for (const [cssVar, key] of Object.entries(RF_VAR_MAP) as Array<[string, PaletteKey]>) {\n el.style.setProperty(cssVar, paletteLookup(palette, key));\n }\n}\n\nexport interface UseGraphEditorThemeReturn {\n /** Current resolved palette. Stable until the next `data-theme` flip. */\n palette: ThemeTokenPalette;\n /** Re-read tokens and re-apply. */\n refresh: () => void;\n}\n\n/**\n * Read tokens, paint them onto the wrapper, and re-read on `data-theme`\n * changes. Returns the live palette so consumers can use it elsewhere\n * (e.g., to color SVG arrowheads inside a custom edge renderer).\n */\nexport function useGraphEditorTheme(\n wrapperRef: RefObject<HTMLElement | null>,\n options: { observe?: boolean } = {},\n): UseGraphEditorThemeReturn {\n const { observe = true } = options;\n const [palette, setPalette] = useState<ThemeTokenPalette>(() => readThemeTokens());\n\n const apply = useCallback(() => {\n const next = readThemeTokens();\n setPalette(next);\n if (wrapperRef.current) applyPalette(wrapperRef.current, next);\n }, [wrapperRef]);\n\n useEffect(() => {\n apply();\n if (!observe || typeof document === 'undefined') return undefined;\n const observer = new MutationObserver(apply);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['data-theme'],\n });\n return () => observer.disconnect();\n }, [apply, observe]);\n\n return { palette, refresh: apply };\n}\n"],"mappings":"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","names":["forwardRef","useCallback","useMemo","useRef","GraphNodeShell","jsx","jsxs","useCallback","selectedNodes","useMemo","jsx","points","viewportRect","useCallback","Fragment","jsx","jsxs","forwardRef","GraphEditorCanvasInner","useRef","useMemo","useCallback","GraphEditorCanvas"]}
@@ -0,0 +1,49 @@
1
+ @import "@xyflow/react/dist/style.css";
2
+
3
+ /* src/styles.css */
4
+ .ship-graph-editor {
5
+ width: 100%;
6
+ height: 100%;
7
+ position: relative;
8
+ }
9
+ .ship-graph-handle {
10
+ width: 8px !important;
11
+ height: 8px !important;
12
+ background: var(--xy-edge-stroke-default, currentColor) !important;
13
+ border: 1px solid var(--xy-node-background-color-default, #fff) !important;
14
+ opacity: 0;
15
+ transition: opacity var(--duration-micro, 120ms) ease;
16
+ }
17
+ .react-flow__node:hover .ship-graph-handle,
18
+ .react-flow__node.selected .ship-graph-handle,
19
+ .react-flow__node:focus-within .ship-graph-handle {
20
+ opacity: 1;
21
+ }
22
+ .ship-graph-editor .react-flow__node-default,
23
+ .ship-graph-editor .react-flow__node-input,
24
+ .ship-graph-editor .react-flow__node-output,
25
+ .ship-graph-editor .react-flow__node-group {
26
+ padding: 0;
27
+ border: none;
28
+ background: transparent;
29
+ color: inherit;
30
+ border-radius: 0;
31
+ width: auto;
32
+ font-size: inherit;
33
+ text-align: left;
34
+ }
35
+ .ship-graph-editor .react-flow__node.selectable:hover,
36
+ .ship-graph-editor .react-flow__node.selectable.selected,
37
+ .ship-graph-editor .react-flow__node.selectable:focus,
38
+ .ship-graph-editor .react-flow__node.selectable:focus-visible,
39
+ .ship-graph-editor .react-flow__node.selected {
40
+ box-shadow: none !important;
41
+ outline: none;
42
+ }
43
+ .ship-graph-editor .react-flow__background-pattern {
44
+ display: none;
45
+ }
46
+ .ship-graph-editor[data-background=dots] .react-flow__background-pattern {
47
+ display: block;
48
+ }
49
+ /*# sourceMappingURL=styles.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/styles.css"],"sourcesContent":["/**\n * Ship-It overrides for React Flow base styles. Imported once by\n * `<GraphEditorCanvas>` so consumers don't need to do it manually.\n *\n * Approach: re-use the `--xy-*` CSS variables React Flow already exposes,\n * which `theme-bridge.ts` paints from the Ship-It token palette on mount and\n * on every `data-theme` flip. The bits below tweak chrome that the variables\n * don't cover: connection handles, the empty-canvas affordance, and the\n * selection ring offsets so they match the cytoscape viewer.\n */\n@import '@xyflow/react/dist/style.css';\n\n.ship-graph-editor {\n /* The wrapper takes the available space; consumers control its size. */\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n/* Connection handles — invisible until the node is hovered or selected.\n When you can't see them, the node reads the same as the viewer. */\n.ship-graph-handle {\n width: 8px !important;\n height: 8px !important;\n background: var(--xy-edge-stroke-default, currentColor) !important;\n border: 1px solid var(--xy-node-background-color-default, #fff) !important;\n opacity: 0;\n transition: opacity var(--duration-micro, 120ms) ease;\n}\n\n.react-flow__node:hover .ship-graph-handle,\n.react-flow__node.selected .ship-graph-handle,\n.react-flow__node:focus-within .ship-graph-handle {\n opacity: 1;\n}\n\n/* Reset React Flow's built-in node-default chrome. RF paints every node of\n type `default` (which is also where our `<DefaultNode>` registers) with a\n white 150×… box, a 1px border, and 10px of padding — the wrapper that\n shows around the `<GraphNodeShell>` in screenshots. Our shell already\n owns the visual, so we strip the wrapper to nothing. The selectors below\n are scoped to `.ship-graph-editor` so other React Flow surfaces on the\n same page (if any) aren't affected. */\n.ship-graph-editor .react-flow__node-default,\n.ship-graph-editor .react-flow__node-input,\n.ship-graph-editor .react-flow__node-output,\n.ship-graph-editor .react-flow__node-group {\n padding: 0;\n border: none;\n background: transparent;\n color: inherit;\n border-radius: 0;\n width: auto;\n font-size: inherit;\n text-align: left;\n}\n\n/* Suppress RF's hover/selected box-shadow on those node types too — our\n `<GraphNodeShell>` draws its own selection ring. */\n.ship-graph-editor .react-flow__node.selectable:hover,\n.ship-graph-editor .react-flow__node.selectable.selected,\n.ship-graph-editor .react-flow__node.selectable:focus,\n.ship-graph-editor .react-flow__node.selectable:focus-visible,\n.ship-graph-editor .react-flow__node.selected {\n box-shadow: none !important;\n outline: none;\n}\n\n/* Background dot pattern is opt-in via the `background=\"dots\"` prop. The\n default render uses the bare panel color from the token bridge so the\n editor looks like the viewer's empty canvas. */\n.ship-graph-editor .react-flow__background-pattern {\n display: none;\n}\n.ship-graph-editor[data-background='dots'] .react-flow__background-pattern {\n display: block;\n}\n"],"mappings":";;;AAYA,CAAC;AAEC,SAAO;AACP,UAAQ;AACR,YAAU;AACZ;AAIA,CAAC;AACC,SAAO;AACP,UAAQ;AACR,cAAY,IAAI,wBAAwB,EAAE;AAC1C,UAAQ,IAAI,MAAM,IAAI,kCAAkC,EAAE;AAC1D,WAAS;AACT,cAAY,QAAQ,IAAI,gBAAgB,EAAE,OAAO;AACnD;AAEA,CAAC,gBAAgB,OAAO,CATvB;AAUD,CADC,gBACgB,CAAC,SAAS,CAV1B;AAWD,CAFC,gBAEgB,cAAc,CAX9B;AAYC,WAAS;AACX;AASA,CA/BC,kBA+BkB,CAAC;AACpB,CAhCC,kBAgCkB,CAAC;AACpB,CAjCC,kBAiCkB,CAAC;AACpB,CAlCC,kBAkCkB,CAAC;AAClB,WAAS;AACT,UAAQ;AACR,cAAY;AACZ,SAAO;AACP,iBAAe;AACf,SAAO;AACP,aAAW;AACX,cAAY;AACd;AAIA,CA/CC,kBA+CkB,CA7BlB,gBA6BmC,CAAC,UAAU;AAC/C,CAhDC,kBAgDkB,CA9BlB,gBA8BmC,CADC,UACU,CA7B7B;AA8BlB,CAjDC,kBAiDkB,CA/BlB,gBA+BmC,CAFC,UAEU;AAC/C,CAlDC,kBAkDkB,CAhClB,gBAgCmC,CAHC,UAGU;AAC/C,CAnDC,kBAmDkB,CAjClB,gBAiCmC,CAhClB;AAiChB,cAAY;AACZ,WAAS;AACX;AAKA,CA3DC,kBA2DkB,CAAC;AAClB,WAAS;AACX;AACA,CA9DC,iBA8DiB,CAAC,sBAAwB,CAHvB;AAIlB,WAAS;AACX;","names":[]}
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@ship-it-ui/graph-editor",
3
+ "version": "0.0.2",
4
+ "description": "Graph-editing canvas for the Ship-It design system. React Flow under the hood; same `elements[]` shape as `@ship-it-ui/cytoscape` so consumers can swap viewer ↔ editor without reshaping data.",
5
+ "license": "MIT",
6
+ "homepage": "https://ship-it-ops.github.io/ship-it-design/",
7
+ "bugs": {
8
+ "url": "https://github.com/ship-it-ops/ship-it-design/issues"
9
+ },
10
+ "author": "Ship-It Ops",
11
+ "keywords": [
12
+ "design-system",
13
+ "graph",
14
+ "graph-editor",
15
+ "react-flow",
16
+ "shipit"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/ship-it-ops/ship-it-design.git",
21
+ "directory": "packages/graph-editor"
22
+ },
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ },
33
+ "./styles.css": "./dist/styles.css"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md"
38
+ ],
39
+ "sideEffects": [
40
+ "./dist/styles.css"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public",
44
+ "provenance": true
45
+ },
46
+ "dependencies": {
47
+ "@xyflow/react": "^12.3.5",
48
+ "@ship-it-ui/graph-tokens": "0.0.2"
49
+ },
50
+ "peerDependencies": {
51
+ "react": "^18.0.0 || ^19.0.0",
52
+ "react-dom": "^18.0.0 || ^19.0.0",
53
+ "@ship-it-ui/icons": "0.0.7",
54
+ "@ship-it-ui/shipit": "0.0.8",
55
+ "@ship-it-ui/ui": "0.0.7"
56
+ },
57
+ "devDependencies": {
58
+ "@testing-library/jest-dom": "^6.6.3",
59
+ "@testing-library/react": "^16.0.1",
60
+ "@types/react": "^18.3.12",
61
+ "@types/react-dom": "^18.3.1",
62
+ "axe-core": "^4.10.2",
63
+ "esbuild-plugin-preserve-directives": "^0.0.11",
64
+ "jsdom": "^29.1.1",
65
+ "react": "^18.3.1",
66
+ "react-dom": "^18.3.1",
67
+ "tsup": "^8.3.0",
68
+ "typescript": "^5.6.3",
69
+ "vitest": "^2.1.3",
70
+ "vitest-axe": "^0.1.0",
71
+ "@ship-it-ui/eslint-config": "0.0.1",
72
+ "@ship-it-ui/icons": "0.0.7",
73
+ "@ship-it-ui/shipit": "0.0.8",
74
+ "@ship-it-ui/tokens": "0.0.5",
75
+ "@ship-it-ui/tsconfig": "0.0.1",
76
+ "@ship-it-ui/ui": "0.0.7"
77
+ },
78
+ "scripts": {
79
+ "build": "tsup",
80
+ "dev": "tsup --watch",
81
+ "lint": "eslint src",
82
+ "lint:fix": "eslint src --fix",
83
+ "test": "vitest run --passWithNoTests",
84
+ "test:watch": "vitest",
85
+ "typecheck": "tsc --noEmit",
86
+ "clean": "rm -rf dist .turbo"
87
+ }
88
+ }