useFocusNode.tsx 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. import React from "react";
  2. import {
  3. searchQuery,
  4. cleanupHighlight,
  5. highlightMatchedNodes,
  6. } from "src/utils/search";
  7. import useConfig from "./store/useConfig";
  8. export const useFocusNode = () => {
  9. const setConfig = useConfig((state) => state.setConfig);
  10. const zoomPanPinch = useConfig((state) => state.zoomPanPinch);
  11. const [selectedNode, setSelectedNode] = React.useState(0);
  12. const [content, setContent] = React.useState({
  13. value: "",
  14. debounced: "",
  15. });
  16. const skip = () => setSelectedNode((current) => current + 1);
  17. React.useEffect(() => {
  18. setConfig("performanceMode", !content.value.length);
  19. const debouncer = setTimeout(() => {
  20. setContent((val) => ({ ...val, debounced: content.value }));
  21. }, 1500);
  22. return () => clearTimeout(debouncer);
  23. }, [content.value, setConfig]);
  24. React.useEffect(() => {
  25. if (!zoomPanPinch) return;
  26. const ref = zoomPanPinch.instance.wrapperComponent;
  27. const matchedNodes: NodeListOf<Element> = searchQuery(
  28. `span[data-key*='${content.debounced}' i]`
  29. );
  30. const matchedNode: Element | null = matchedNodes[selectedNode] || null;
  31. cleanupHighlight();
  32. if (ref && matchedNode && matchedNode.parentElement) {
  33. const newScale = 1;
  34. const x = Number(matchedNode.getAttribute("data-x"));
  35. const y = Number(matchedNode.getAttribute("data-y"));
  36. const newPositionX =
  37. (ref.offsetLeft - x) * newScale +
  38. ref.clientWidth / 2 -
  39. matchedNode.getBoundingClientRect().width / 2;
  40. const newPositionY =
  41. (ref.offsetLeft - y) * newScale +
  42. ref.clientHeight / 2 -
  43. matchedNode.getBoundingClientRect().height / 2;
  44. highlightMatchedNodes(matchedNodes, selectedNode);
  45. zoomPanPinch?.setTransform(newPositionX, newPositionY, newScale);
  46. } else {
  47. setSelectedNode(0);
  48. }
  49. return () => {
  50. if (!content.value) setSelectedNode(0);
  51. };
  52. }, [content.debounced, content.value, selectedNode, zoomPanPinch]);
  53. return [content, setContent, skip] as const;
  54. };