From b569fe2075048ea6ef0fa3b1b4f8028690888685 Mon Sep 17 00:00:00 2001 From: abhay-codes07 Date: Fri, 24 Jul 2026 11:42:46 +0530 Subject: [PATCH] feat(memory-graph): add onNodeSelect callback for selection changes Selection lived entirely inside MemoryGraph, so a host embedding the graph had no way to observe which node is selected outside of slideshow mode (onSlideshowNodeChange) or the open-document action (onOpenDocument). That makes common integrations awkward: driving a side panel, syncing selection to the URL, or firing analytics on select. Add an optional onNodeSelect(nodeId) prop that fires whenever the selection changes, regardless of source (click, keyboard navigation, slideshow, or clearing), with null when nothing is selected. It is emitted from an effect keyed on the selection state and reads the callback through a ref, so a changing callback identity does not re-fire it and the selection stays uncontrolled. The initial mount is skipped so a host does not receive a spurious null before any interaction. Purely additive and backward compatible. --- packages/memory-graph/README.md | 1 + .../memory-graph/src/components/memory-graph.tsx | 16 ++++++++++++++++ packages/memory-graph/src/types.ts | 7 +++++++ 3 files changed, 24 insertions(+) diff --git a/packages/memory-graph/README.md b/packages/memory-graph/README.md index 83710f5a..4ec4db0a 100644 --- a/packages/memory-graph/README.md +++ b/packages/memory-graph/README.md @@ -66,6 +66,7 @@ function App() { | `error` | `Error \| null` | Error to display | | `loadMoreDocuments` | `() => Promise` | Function to load more data | | `highlightDocumentIds` | `string[]` | IDs of documents to highlight | +| `onNodeSelect` | `(nodeId: string \| null) => void` | Called when the selected node changes (click, keyboard, slideshow, or cleared) | ## Documentation diff --git a/packages/memory-graph/src/components/memory-graph.tsx b/packages/memory-graph/src/components/memory-graph.tsx index d165ac71..f0d5e342 100644 --- a/packages/memory-graph/src/components/memory-graph.tsx +++ b/packages/memory-graph/src/components/memory-graph.tsx @@ -39,6 +39,7 @@ export function MemoryGraph({ colors: colorOverrides, totalCount, onOpenDocument, + onNodeSelect, labels: labelOverrides, layering, }: MemoryGraphProps) { @@ -73,6 +74,21 @@ export function MemoryGraph({ // Used as a dependency proxy to recalculate popover positions const [viewportVersion, setViewportVersion] = useState(0) + // Surface selection changes to the host without making selection controlled. + // Keyed on selectedNode only; the callback is read through a ref so a changing + // callback identity does not re-fire it. The initial mount is skipped so a + // host does not receive a spurious null before any interaction. + const onNodeSelectRef = useRef(onNodeSelect) + onNodeSelectRef.current = onNodeSelect + const hasEmittedSelectionRef = useRef(false) + useEffect(() => { + if (!hasEmittedSelectionRef.current) { + hasEmittedSelectionRef.current = true + return + } + onNodeSelectRef.current?.(selectedNode) + }, [selectedNode]) + // Limit documents so total node count (documents + their memories) stays under maxNodes const limitedDocuments = useMemo(() => { if (!maxNodes || documents.length === 0) return documents diff --git a/packages/memory-graph/src/types.ts b/packages/memory-graph/src/types.ts index f871b3da..32bfcaae 100644 --- a/packages/memory-graph/src/types.ts +++ b/packages/memory-graph/src/types.ts @@ -216,6 +216,13 @@ export interface MemoryGraphProps { totalCount?: number /** Callback when user wants to view full document content */ onOpenDocument?: (documentId: string) => void + /** + * Called whenever the selected node changes, by click, keyboard navigation, + * slideshow, or clearing the selection (nodeId is null when nothing is + * selected). Lets a host observe selection without owning the state, e.g. to + * drive a side panel or sync the URL. + */ + onNodeSelect?: (nodeId: string | null) => void /** Custom user-facing labels (partial) - merged with defaults */ labels?: MemoryGraphLabels /** Overlay layering controls */