| import React from "react"; | |
| import { useDebouncedValue } from "@mantine/hooks"; | |
| import { searchQuery, cleanupHighlight, highlightMatchedNodes } from "src/lib/utils/graph/search"; | |
| import useGraph from "src/store/useGraph"; | |
| export const useFocusNode = () => { | |
| const viewPort = useGraph(state => state.viewPort); | |
| const [selectedNode, setSelectedNode] = React.useState(0); | |
| const [nodeCount, setNodeCount] = React.useState(0); | |
| const [value, setValue] = React.useState(""); | |
| const [debouncedValue] = useDebouncedValue(value, 600); | |
| const skip = () => setSelectedNode(current => (current + 1) % nodeCount); | |
| React.useEffect(() => { | |
| if (!value) { | |
| cleanupHighlight(); | |
| setSelectedNode(0); | |
| setNodeCount(0); | |
| return; | |
| } | |
| if (!viewPort || !debouncedValue) return; | |
| const matchedNodes: NodeListOf<Element> = searchQuery(`span[data-key*='${debouncedValue}' i]`); | |
| const matchedNode: Element | null = matchedNodes[selectedNode] || null; | |
| cleanupHighlight(); | |
| if (matchedNode && matchedNode.parentElement) { | |
| highlightMatchedNodes(matchedNodes, selectedNode); | |
| setNodeCount(matchedNodes.length); | |
| viewPort?.camera.centerFitElementIntoView(matchedNode.parentElement, { | |
| elementExtraMarginForZoom: 400, | |
| }); | |
| } else { | |
| setSelectedNode(0); | |
| setNodeCount(0); | |
| } | |
| }, [selectedNode, debouncedValue, value, viewPort]); | |
| return [value, setValue, skip, nodeCount, selectedNode] as const; | |
| }; | |