@ankorar/nodex 0.0.1

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.
Files changed (62) hide show
  1. package/README.md +228 -0
  2. package/package.json +54 -0
  3. package/src/components/mindMap/Background.tsx +39 -0
  4. package/src/components/mindMap/Board.tsx +159 -0
  5. package/src/components/mindMap/CentalNode.tsx +121 -0
  6. package/src/components/mindMap/DefaultNode.tsx +205 -0
  7. package/src/components/mindMap/Header.tsx +247 -0
  8. package/src/components/mindMap/ImageNode.tsx +345 -0
  9. package/src/components/mindMap/KeyboardHelpDialog.tsx +108 -0
  10. package/src/components/mindMap/MineMap.tsx +237 -0
  11. package/src/components/mindMap/NodeStylePopover.tsx +486 -0
  12. package/src/components/mindMap/Nodes.tsx +113 -0
  13. package/src/components/mindMap/Nodex.tsx +65 -0
  14. package/src/components/mindMap/SaveStatusIndicator.tsx +61 -0
  15. package/src/components/mindMap/Segments.tsx +270 -0
  16. package/src/components/mindMap/ZenCard.tsx +41 -0
  17. package/src/components/ui/dialog.tsx +141 -0
  18. package/src/components/ui/popover.tsx +46 -0
  19. package/src/components/ui/select.tsx +192 -0
  20. package/src/components/ui/toggle-group.tsx +83 -0
  21. package/src/components/ui/toggle.tsx +45 -0
  22. package/src/config/rootKeyBinds.ts +191 -0
  23. package/src/config/shortCuts.ts +28 -0
  24. package/src/contexts/MindMapNodeEditorContext.tsx +47 -0
  25. package/src/handlers/rootKeyBinds/handleAltEKeyBind.ts +6 -0
  26. package/src/handlers/rootKeyBinds/handleAltHKeyBind.ts +6 -0
  27. package/src/handlers/rootKeyBinds/handleAltWKeyBind.ts +6 -0
  28. package/src/handlers/rootKeyBinds/handleAltZKeyBind.ts +6 -0
  29. package/src/handlers/rootKeyBinds/handleArrowHorizontalRootKeyBind.ts +46 -0
  30. package/src/handlers/rootKeyBinds/handleArrowVerticalRootKeyBind.ts +44 -0
  31. package/src/handlers/rootKeyBinds/handleBackEspaceKeyBind.ts +12 -0
  32. package/src/handlers/rootKeyBinds/handleERootKeyBind.ts +16 -0
  33. package/src/handlers/rootKeyBinds/handleEnterRootKeyBind.ts +35 -0
  34. package/src/handlers/rootKeyBinds/handleEscapeKeyBind.ts +24 -0
  35. package/src/handlers/rootKeyBinds/handleEspaceKeyBind.ts +11 -0
  36. package/src/handlers/rootKeyBinds/handleMoveByWorldKeyBind.ts +6 -0
  37. package/src/handlers/rootKeyBinds/handleRedoRootKeyBind.ts +23 -0
  38. package/src/handlers/rootKeyBinds/handleTabRootKeyBind.ts +49 -0
  39. package/src/handlers/rootKeyBinds/handleTransformNodeKeyBind.ts +39 -0
  40. package/src/handlers/rootKeyBinds/handleUndoRootKeyBind.ts +23 -0
  41. package/src/handlers/rootKeyBinds/handleZoonByKeyBind.ts +31 -0
  42. package/src/helpers/centerNode.ts +19 -0
  43. package/src/helpers/getNodeSide.ts +16 -0
  44. package/src/hooks/mindMap/useHelpers.tsx +9 -0
  45. package/src/hooks/mindMap/useMindMapDebounce.ts +47 -0
  46. package/src/hooks/mindMap/useMindMapHistoryDebounce.ts +69 -0
  47. package/src/hooks/mindMap/useMindMapNode.tsx +203 -0
  48. package/src/hooks/mindMap/useMindMapNodeEditor.ts +91 -0
  49. package/src/hooks/mindMap/useMindMapNodeMouseHandlers.ts +24 -0
  50. package/src/hooks/mindMap/useRootKeyBindHandlers.ts +49 -0
  51. package/src/hooks/mindMap/useRootMouseHandlers.ts +124 -0
  52. package/src/hooks/mindMap/useUpdateCenter.ts +54 -0
  53. package/src/index.ts +76 -0
  54. package/src/lib/utils.ts +6 -0
  55. package/src/state/mindMap.ts +793 -0
  56. package/src/state/mindMapHistory.ts +96 -0
  57. package/src/styles.input.css +95 -0
  58. package/src/utils/exportMindMapAsHighQualityImage.ts +327 -0
  59. package/src/utils/exportMindMapAsMarkdown.ts +102 -0
  60. package/src/utils/exportMindMapAsPdf.ts +241 -0
  61. package/src/utils/getMindMapPreviewDataUrl.ts +60 -0
  62. package/styles.css +2 -0
@@ -0,0 +1,44 @@
1
+ import { centerNode } from "../../helpers/centerNode";
2
+ import { useMindMapState } from "../../state/mindMap";
3
+
4
+ export const handleArrowVerticalRootKeyBind = (dir: "up" | "down") => {
5
+ const {
6
+ selectedNodeId,
7
+ getCentralNode,
8
+ setSelectedNode,
9
+ findNode,
10
+ findNodeParent,
11
+ } = useMindMapState.getState();
12
+
13
+ const centralNode = getCentralNode();
14
+ const node = findNode(selectedNodeId ?? centralNode?.id ?? "");
15
+
16
+ if (!node) return;
17
+
18
+ if (node.type === "central") {
19
+ centerNode(node);
20
+ return;
21
+ }
22
+
23
+ const parent = findNodeParent(node.id);
24
+ if (!parent) return;
25
+
26
+ const siblings = parent.childrens
27
+ .filter((child) => child.isVisible)
28
+ .sort((a, b) => a.sequence - b.sequence);
29
+
30
+ const currentIndex = siblings.findIndex((child) => child.id === node.id);
31
+ const step = dir === "down" ? 1 : -1;
32
+ const siblingTarget = siblings[currentIndex + step];
33
+
34
+ if (siblingTarget) {
35
+ setSelectedNode(siblingTarget.id);
36
+ centerNode(siblingTarget);
37
+ return;
38
+ }
39
+
40
+ if (!siblingTarget && parent) {
41
+ setSelectedNode(parent.id);
42
+ centerNode(parent);
43
+ }
44
+ };
@@ -0,0 +1,12 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleBackEspaceKeyBind = () => {
4
+ const { selectedNodeId, findNode, setSelectedNode, removeNode } =
5
+ useMindMapState.getState();
6
+
7
+ const node = findNode(selectedNodeId ?? "");
8
+ if (!node) return;
9
+
10
+ setSelectedNode(null);
11
+ removeNode(node.id);
12
+ };
@@ -0,0 +1,16 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleERootKeyBind = () => {
4
+ const { findNode, selectedNodeId, updateNode } = useMindMapState.getState();
5
+
6
+ const node = findNode(selectedNodeId ?? "");
7
+
8
+ if (!node) return;
9
+
10
+ node.childrens = node.childrens.map((child) => ({
11
+ ...child,
12
+ isVisible: !child.isVisible,
13
+ }));
14
+
15
+ updateNode(node);
16
+ };
@@ -0,0 +1,35 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleEnterRootKeyBind = () => {
4
+ const {
5
+ findNode,
6
+ getCentralNode,
7
+ findNodeParent,
8
+ makeChildNode,
9
+ updateNode,
10
+ setEditingNode,
11
+ setSelectedNode,
12
+ selectedNodeId,
13
+ } = useMindMapState.getState();
14
+
15
+ const centralNode = getCentralNode();
16
+ const node = findNode(selectedNodeId ?? centralNode?.id ?? "");
17
+
18
+ if (!node) return;
19
+
20
+ const parent = findNodeParent(node.id);
21
+ if (!parent) return;
22
+
23
+ const newNode = makeChildNode(parent);
24
+ parent?.childrens.push(newNode);
25
+
26
+ const textValue = node.text.trim();
27
+
28
+ if (textValue.length === 0 && selectedNodeId) {
29
+ parent.childrens = parent.childrens.filter((child) => child.id !== node.id);
30
+ }
31
+
32
+ updateNode(parent);
33
+ setSelectedNode(newNode.id);
34
+ setEditingNode(newNode.id);
35
+ };
@@ -0,0 +1,24 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleEscapeKeyBind = () => {
4
+ const {
5
+ editingNodeId,
6
+ findNode,
7
+ removeNode,
8
+ setSelectedNode,
9
+ setEditingNode,
10
+ } = useMindMapState.getState();
11
+
12
+ setSelectedNode(null);
13
+ const node = findNode(editingNodeId ?? "");
14
+
15
+ if (!node) return;
16
+
17
+ const textValue = node.text.trim();
18
+
19
+ if (textValue.length === 0 && editingNodeId) {
20
+ removeNode(editingNodeId);
21
+ }
22
+
23
+ setEditingNode(null);
24
+ };
@@ -0,0 +1,11 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleEspaceKeyBind = () => {
4
+ const { selectedNodeId, findNode, setEditingNode } =
5
+ useMindMapState.getState();
6
+
7
+ const node = findNode(selectedNodeId ?? "");
8
+ if (!node) return;
9
+
10
+ setEditingNode(selectedNodeId);
11
+ };
@@ -0,0 +1,6 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleMoveByWorldKeyBind = (x: number, y: number) => {
4
+ const { offset, setOffset } = useMindMapState.getState();
5
+ setOffset({ x: offset.x + x, y: offset.y + y });
6
+ };
@@ -0,0 +1,23 @@
1
+ import { useMindMapHistory, createMindMapSnapshot } from "../../state/mindMapHistory";
2
+ import { useMindMapState } from "../../state/mindMap";
3
+
4
+ export const handleRedoRootKeyBind = () => {
5
+ const state = useMindMapState.getState();
6
+ const current = createMindMapSnapshot({
7
+ nodes: state.nodes,
8
+ selectedNodeId: state.selectedNodeId,
9
+ editingNodeId: state.editingNodeId,
10
+ offset: state.offset,
11
+ scale: state.scale,
12
+ });
13
+
14
+ useMindMapHistory.getState().redo(current, (snapshot) => {
15
+ useMindMapState.setState({
16
+ nodes: snapshot.nodes,
17
+ selectedNodeId: snapshot.selectedNodeId,
18
+ editingNodeId: snapshot.editingNodeId,
19
+ offset: snapshot.offset,
20
+ scale: snapshot.scale,
21
+ });
22
+ });
23
+ };
@@ -0,0 +1,49 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleTabRootKeyBind = () => {
4
+ const {
5
+ editingNodeId,
6
+ selectedNodeId,
7
+ findNode,
8
+ getCentralNode,
9
+ removeNode,
10
+ updateNode,
11
+ makeChildNode,
12
+ setEditingNode,
13
+ setSelectedNode,
14
+ } = useMindMapState.getState();
15
+
16
+ const isEditing = !!editingNodeId;
17
+ const nodeEditing = findNode(editingNodeId ?? "");
18
+ const centralNode = getCentralNode();
19
+ const currentNode = findNode(selectedNodeId ?? centralNode?.id ?? "");
20
+
21
+ if (isEditing && nodeEditing) {
22
+ const textValue = nodeEditing.text.trim();
23
+
24
+ if (textValue.length === 0) {
25
+ removeNode(editingNodeId);
26
+ return;
27
+ }
28
+ }
29
+
30
+ if (!currentNode) {
31
+ return;
32
+ }
33
+
34
+ const newNode = currentNode;
35
+
36
+ if (currentNode.childrens.some((child) => !child.isVisible)) {
37
+ newNode.childrens = currentNode.childrens.map((child) => ({
38
+ ...child,
39
+ isVisible: true,
40
+ }));
41
+ }
42
+
43
+ const newChildNode = makeChildNode(newNode);
44
+ newNode.childrens.push(newChildNode);
45
+
46
+ updateNode(newNode);
47
+ setSelectedNode(newChildNode.id);
48
+ setEditingNode(newChildNode.id);
49
+ };
@@ -0,0 +1,39 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleTransformNodeKeyBind = (
4
+ transformation: ("bold" | "italic" | "image")[],
5
+ ) => {
6
+ const {
7
+ getCentralNode,
8
+ findNode,
9
+ updateNode,
10
+ setSelectedNode,
11
+ setEditingNode,
12
+ selectedNodeId,
13
+ } = useMindMapState.getState();
14
+
15
+ const centralNode = getCentralNode();
16
+ const node = findNode(selectedNodeId ?? centralNode?.id ?? "");
17
+
18
+ if (!node) return;
19
+
20
+ if (transformation.includes("bold")) {
21
+ node.style.isBold = !node.style.isBold;
22
+ }
23
+
24
+ if (transformation.includes("italic")) {
25
+ node.style.isItalic = !node.style.isItalic;
26
+ }
27
+
28
+ if (transformation.includes("image")) {
29
+ node.style.isBold = false;
30
+ node.style.isItalic = false;
31
+ node.text = "";
32
+ node.type = "image";
33
+
34
+ setSelectedNode(node.id);
35
+ setEditingNode(node.id);
36
+ }
37
+
38
+ updateNode(node);
39
+ };
@@ -0,0 +1,23 @@
1
+ import { useMindMapHistory, createMindMapSnapshot } from "../../state/mindMapHistory";
2
+ import { useMindMapState } from "../../state/mindMap";
3
+
4
+ export const handleUndoRootKeyBind = () => {
5
+ const state = useMindMapState.getState();
6
+ const current = createMindMapSnapshot({
7
+ nodes: state.nodes,
8
+ selectedNodeId: state.selectedNodeId,
9
+ editingNodeId: state.editingNodeId,
10
+ offset: state.offset,
11
+ scale: state.scale,
12
+ });
13
+
14
+ useMindMapHistory.getState().undo(current, (snapshot) => {
15
+ useMindMapState.setState({
16
+ nodes: snapshot.nodes,
17
+ selectedNodeId: snapshot.selectedNodeId,
18
+ editingNodeId: snapshot.editingNodeId,
19
+ offset: snapshot.offset,
20
+ scale: snapshot.scale,
21
+ });
22
+ });
23
+ };
@@ -0,0 +1,31 @@
1
+ import { useMindMapState } from "../../state/mindMap";
2
+
3
+ export const handleZoomByKeyBind = (delta: number) => {
4
+ const { clampScale, setScale, setOffset, scale, offset } =
5
+ useMindMapState.getState();
6
+ const rootElement = document.querySelector(
7
+ "[data-nodex-root]",
8
+ ) as HTMLElement | null;
9
+
10
+ const bounds = rootElement?.getBoundingClientRect();
11
+
12
+ const viewportWidth = bounds?.width ?? window.innerWidth;
13
+ const viewportHeight = bounds?.height ?? window.innerHeight;
14
+
15
+ const pointerX = viewportWidth / 2;
16
+ const pointerY = viewportHeight / 2;
17
+
18
+ const nextScale = clampScale(scale + delta);
19
+
20
+ if (nextScale === scale) {
21
+ return;
22
+ }
23
+ const worldX = (pointerX - offset.x) / scale;
24
+ const worldY = (pointerY - offset.y) / scale;
25
+
26
+ setScale(nextScale);
27
+ setOffset({
28
+ x: pointerX - worldX * nextScale,
29
+ y: pointerY - worldY * nextScale,
30
+ });
31
+ };
@@ -0,0 +1,19 @@
1
+ import { type MindMapNode, useMindMapState } from "../state/mindMap";
2
+
3
+ export const centerNode = (node: MindMapNode | null) => {
4
+ if (!node) return;
5
+
6
+ const { setOffset, scale } = useMindMapState.getState();
7
+
8
+ const rootElement = document.querySelector(
9
+ "[data-nodex-root]"
10
+ ) as HTMLElement | null;
11
+ const bounds = rootElement?.getBoundingClientRect();
12
+ const viewportWidth = bounds?.width ?? window.innerWidth;
13
+ const viewportHeight = bounds?.height ?? window.innerHeight;
14
+
15
+ setOffset({
16
+ x: viewportWidth / 2 - (node.pos.x + node.style.w / 2) * scale,
17
+ y: viewportHeight / 2 - (node.pos.y + node.style.h / 2) * scale,
18
+ });
19
+ };
@@ -0,0 +1,16 @@
1
+ import { type MindMapNode, useMindMapState } from "../state/mindMap";
2
+
3
+ export const getNodeSide = (node: MindMapNode | null) => {
4
+ const { getCentralNode } = useMindMapState.getState();
5
+
6
+ const centralNode = getCentralNode();
7
+
8
+ if (!node || node.type === "central" || !centralNode) {
9
+ return "center";
10
+ }
11
+
12
+ const nodeCenterX = node.pos.x + node.style.w / 2;
13
+ const centralCenterX = centralNode?.pos.x + centralNode.style.w / 2;
14
+
15
+ return nodeCenterX < centralCenterX ? "left" : "right";
16
+ };
@@ -0,0 +1,9 @@
1
+ import { centerNode } from "../../helpers/centerNode";
2
+ import { getNodeSide } from "../../helpers/getNodeSide";
3
+
4
+ export function useHelpers() {
5
+ return {
6
+ getNodeSide,
7
+ centerNode,
8
+ };
9
+ }
@@ -0,0 +1,47 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ import { useMindMapState } from "../../state/mindMap";
4
+ import { getMindMapPreviewDataUrl } from "../../utils/getMindMapPreviewDataUrl";
5
+
6
+ type UseMindMapDebouncedChangeOptions = {
7
+ delayMs?: number;
8
+ };
9
+
10
+ export function useMindMapDebounce(
11
+ onChange: (
12
+ nodes: ReturnType<typeof useMindMapState.getState>["nodes"],
13
+ previewDataUrl: string | null,
14
+ ) => void | Promise<void>,
15
+ options: UseMindMapDebouncedChangeOptions = {},
16
+ ) {
17
+ const nodes = useMindMapState((state) => state.nodes);
18
+ const delayMs = options.delayMs ?? 1500;
19
+ const timeoutRef = useRef<number | null>(null);
20
+ const latestNodesRef = useRef(nodes);
21
+ const callbackRef = useRef(onChange);
22
+
23
+ useEffect(() => {
24
+ callbackRef.current = onChange;
25
+ }, [onChange]);
26
+
27
+ useEffect(() => {
28
+ latestNodesRef.current = nodes;
29
+ if (timeoutRef.current) {
30
+ window.clearTimeout(timeoutRef.current);
31
+ }
32
+ timeoutRef.current = window.setTimeout(() => {
33
+ const nextNodes = latestNodesRef.current;
34
+ if (nextNodes.length === 0) {
35
+ return;
36
+ }
37
+ const previewDataUrl = getMindMapPreviewDataUrl(nextNodes);
38
+ callbackRef.current(nextNodes, previewDataUrl);
39
+ }, delayMs);
40
+
41
+ return () => {
42
+ if (timeoutRef.current) {
43
+ window.clearTimeout(timeoutRef.current);
44
+ }
45
+ };
46
+ }, [nodes, delayMs]);
47
+ }
@@ -0,0 +1,69 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ import {
4
+ type MindMapSnapshot,
5
+ createMindMapSnapshot,
6
+ useMindMapHistory,
7
+ } from "../../state/mindMapHistory";
8
+ import { useMindMapState } from "../../state/mindMap";
9
+ import { useMindMapDebounce } from "./useMindMapDebounce";
10
+
11
+ type UseMindMapHistoryDebounceOptions = {
12
+ delayMs?: number;
13
+ };
14
+
15
+ export function useMindMapHistoryDebounce(
16
+ options: UseMindMapHistoryDebounceOptions = {},
17
+ ) {
18
+ const delayMs = options.delayMs ?? 3000;
19
+ const nodes = useMindMapState((state) => state.nodes);
20
+ const resetVersion = useMindMapHistory((state) => state.resetVersion);
21
+ const lastSnapshotRef = useRef<MindMapSnapshot | null>(null);
22
+ const pendingSnapshotRef = useRef<MindMapSnapshot | null>(null);
23
+ const pendingResetVersionRef = useRef(resetVersion);
24
+
25
+ useEffect(() => {
26
+ pendingResetVersionRef.current = resetVersion;
27
+ lastSnapshotRef.current = null;
28
+ pendingSnapshotRef.current = null;
29
+ }, [resetVersion]);
30
+
31
+ useEffect(() => {
32
+ const state = useMindMapState.getState();
33
+ const currentSnapshot = createMindMapSnapshot({
34
+ nodes: state.nodes,
35
+ selectedNodeId: state.selectedNodeId,
36
+ editingNodeId: state.editingNodeId,
37
+ offset: state.offset,
38
+ scale: state.scale,
39
+ });
40
+
41
+ if (!lastSnapshotRef.current) {
42
+ lastSnapshotRef.current = currentSnapshot;
43
+ return;
44
+ }
45
+
46
+ if (!pendingSnapshotRef.current) {
47
+ pendingSnapshotRef.current = lastSnapshotRef.current;
48
+ pendingResetVersionRef.current = resetVersion;
49
+ }
50
+
51
+ lastSnapshotRef.current = currentSnapshot;
52
+ }, [nodes, resetVersion]);
53
+
54
+ useMindMapDebounce(
55
+ () => {
56
+ const pendingSnapshot = pendingSnapshotRef.current;
57
+ if (!pendingSnapshot) {
58
+ return;
59
+ }
60
+ if (pendingResetVersionRef.current !== useMindMapHistory.getState().resetVersion) {
61
+ pendingSnapshotRef.current = null;
62
+ return;
63
+ }
64
+ useMindMapHistory.getState().pushSnapshot(pendingSnapshot);
65
+ pendingSnapshotRef.current = null;
66
+ },
67
+ { delayMs },
68
+ );
69
+ }
@@ -0,0 +1,203 @@
1
+ import {
2
+ type MindMapNodeFontSize,
3
+ type MindMapNodeTextAlign,
4
+ type MindMapNodeType,
5
+ useMindMapState,
6
+ } from "../../state/mindMap";
7
+ import { useMemo } from "react";
8
+ import { useShallow } from "zustand/react/shallow";
9
+ import { useHelpers } from "./useHelpers";
10
+
11
+ interface UseMindMapNodeProps {
12
+ nodeId?: string | null;
13
+ }
14
+
15
+ export function useMindMapNode({ nodeId }: UseMindMapNodeProps) {
16
+ const helpers = useHelpers();
17
+ const {
18
+ nodes,
19
+ findNode,
20
+ findNodeParent,
21
+ updateNode,
22
+ setSelectedNode,
23
+ setEditingNode,
24
+ removeNode,
25
+ toggleNodeChildrenVisibility,
26
+ makeChildNode,
27
+ readOnly,
28
+ } = useMindMapState(
29
+ useShallow((state) => ({
30
+ nodes: state.nodes,
31
+ findNode: state.findNode,
32
+ findNodeParent: state.findNodeParent,
33
+ updateNode: state.updateNode,
34
+ setSelectedNode: state.setSelectedNode,
35
+ setEditingNode: state.setEditingNode,
36
+ removeNode: state.removeNode,
37
+ toggleNodeChildrenVisibility: state.toggleNodeChildrenVisibility,
38
+ makeChildNode: state.makeChildNode,
39
+ readOnly: state.readOnly,
40
+ }))
41
+ );
42
+
43
+ const nId = nodeId ?? "";
44
+ const { node, parent } = useMemo(
45
+ () => ({
46
+ node: findNode(nId),
47
+ parent: findNodeParent(nId),
48
+ }),
49
+ [nodeId, nodes]
50
+ );
51
+
52
+ const updater = (cb: () => void) => {
53
+ if (readOnly) {
54
+ return createLogicalNode();
55
+ }
56
+
57
+ cb();
58
+ return createLogicalNode();
59
+ };
60
+
61
+ const toggleBold = () =>
62
+ updater(() => {
63
+ node!.style.isBold = !node!.style.isBold;
64
+ });
65
+
66
+ const toggleItalic = () =>
67
+ updater(() => {
68
+ node!.style.isItalic = !node!.style.isItalic;
69
+ });
70
+
71
+ const updateFontSize = (fontSize: MindMapNodeFontSize) =>
72
+ updater(() => {
73
+ node!.style.fontSize = fontSize;
74
+ });
75
+
76
+ const updateType = (type: MindMapNodeType) =>
77
+ updater(() => {
78
+ if (type === "central") return createLogicalNode();
79
+ node!.type = type;
80
+ });
81
+
82
+ const updateTextAling = (textAling: MindMapNodeTextAlign) =>
83
+ updater(() => {
84
+ node!.style.textAlign = textAling;
85
+ });
86
+
87
+ const clearText = () =>
88
+ updater(() => {
89
+ node!.text = "";
90
+ });
91
+
92
+ const updateBackgroundColor = (backgroundCollor: string) =>
93
+ updater(() => {
94
+ node!.style.backgroundColor = backgroundCollor;
95
+ });
96
+
97
+ const updateTextColor = (textColor: string) =>
98
+ updater(() => {
99
+ node!.style.textColor = textColor;
100
+ });
101
+
102
+ const updateText = (text?: string | null) =>
103
+ updater(() => {
104
+ if (text === undefined) return;
105
+ node!.text = text ?? "";
106
+ });
107
+
108
+ const updateSize = (w?: number, h?: number) =>
109
+ updater(() => {
110
+ if (w) {
111
+ node!.style.w = w;
112
+ }
113
+
114
+ if (h) {
115
+ node!.style.h = h;
116
+ }
117
+ });
118
+
119
+ const commit = () => {
120
+ if (!node || readOnly) return;
121
+ updateNode(node);
122
+ };
123
+
124
+ const chain = () => {
125
+ return createLogicalNode();
126
+ };
127
+
128
+ const select = () => {
129
+ if (!node) return;
130
+ setSelectedNode(node.id);
131
+ };
132
+
133
+ const edit = () => {
134
+ if (!node || readOnly) return;
135
+ setEditingNode(node.id);
136
+ };
137
+
138
+ const getSide = () => {
139
+ return helpers.getNodeSide(node);
140
+ };
141
+
142
+ const destroy = () => {
143
+ if (!node || readOnly) return;
144
+ removeNode(node.id);
145
+ };
146
+
147
+ const togglechildrensVisibility = () => {
148
+ if (!node) return;
149
+ toggleNodeChildrenVisibility(node.id);
150
+ };
151
+
152
+ const addChild = () => {
153
+ if (!node || readOnly) return;
154
+ const newchildrens = node.childrens;
155
+
156
+ const newChilderen = makeChildNode(node);
157
+
158
+ newchildrens.push(newChilderen);
159
+
160
+ node.childrens = newchildrens;
161
+ updateNode(node);
162
+ setSelectedNode(newChilderen.id);
163
+ setEditingNode(newChilderen.id);
164
+ };
165
+
166
+ const createLogicalNode = () => {
167
+ return {
168
+ ...node!,
169
+ toggleBold,
170
+ toggleItalic,
171
+
172
+ updateFontSize,
173
+ updateType,
174
+ updateTextAling,
175
+ updateBackgroundColor,
176
+ updateTextColor,
177
+ updateText,
178
+ updateSize,
179
+ commit,
180
+
181
+ clearText,
182
+ };
183
+ };
184
+
185
+ const createNode = () => {
186
+ if (!node) return null;
187
+ return {
188
+ ...node,
189
+ parent,
190
+ chain,
191
+ select,
192
+ edit,
193
+ getSide,
194
+ destroy,
195
+ togglechildrensVisibility,
196
+ addChild,
197
+ };
198
+ };
199
+
200
+ return {
201
+ node: createNode(),
202
+ };
203
+ }