@signalsafe/tree-spec-editor-react 0.1.0 → 0.1.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -7
  3. package/dist/GraphEditorCanvasContext.d.ts +26 -0
  4. package/dist/GraphEditorCanvasContext.d.ts.map +1 -0
  5. package/dist/GraphEditorCanvasContext.js +20 -0
  6. package/dist/TreeSpecGraphEditor.d.ts +22 -1
  7. package/dist/TreeSpecGraphEditor.d.ts.map +1 -1
  8. package/dist/TreeSpecGraphEditor.js +133 -257
  9. package/dist/canvas/constants.d.ts +41 -0
  10. package/dist/canvas/constants.d.ts.map +1 -0
  11. package/dist/canvas/constants.js +40 -0
  12. package/dist/canvas/edgeBuilders.d.ts +6 -0
  13. package/dist/canvas/edgeBuilders.d.ts.map +1 -0
  14. package/dist/canvas/edgeBuilders.js +57 -0
  15. package/dist/canvas/edgeStyle.d.ts +16 -0
  16. package/dist/canvas/edgeStyle.d.ts.map +1 -0
  17. package/dist/canvas/edgeStyle.js +44 -0
  18. package/dist/canvas/focusChoice.d.ts +3 -0
  19. package/dist/canvas/focusChoice.d.ts.map +1 -0
  20. package/dist/canvas/focusChoice.js +12 -0
  21. package/dist/canvas/typeGuards.d.ts +3 -0
  22. package/dist/canvas/typeGuards.d.ts.map +1 -0
  23. package/dist/canvas/typeGuards.js +16 -0
  24. package/dist/contextMenu/GraphCanvasContextMenu.d.ts +10 -0
  25. package/dist/contextMenu/GraphCanvasContextMenu.d.ts.map +1 -0
  26. package/dist/contextMenu/GraphCanvasContextMenu.js +39 -0
  27. package/dist/contextMenu/types.d.ts +11 -0
  28. package/dist/contextMenu/types.d.ts.map +1 -0
  29. package/dist/contextMenu/types.js +1 -0
  30. package/dist/hooks/keyboardShortcutDispatch.d.ts +30 -0
  31. package/dist/hooks/keyboardShortcutDispatch.d.ts.map +1 -0
  32. package/dist/hooks/keyboardShortcutDispatch.js +88 -0
  33. package/dist/hooks/types.d.ts +34 -4
  34. package/dist/hooks/types.d.ts.map +1 -1
  35. package/dist/hooks/useCanvasContextMenu.d.ts +15 -0
  36. package/dist/hooks/useCanvasContextMenu.d.ts.map +1 -0
  37. package/dist/hooks/useCanvasContextMenu.js +50 -0
  38. package/dist/hooks/useCanvasGraphState.d.ts +29 -0
  39. package/dist/hooks/useCanvasGraphState.d.ts.map +1 -0
  40. package/dist/hooks/useCanvasGraphState.js +150 -0
  41. package/dist/hooks/useCanvasIssueIndex.d.ts +12 -0
  42. package/dist/hooks/useCanvasIssueIndex.d.ts.map +1 -0
  43. package/dist/hooks/useCanvasIssueIndex.js +32 -0
  44. package/dist/hooks/useCanvasNodeResize.d.ts +17 -0
  45. package/dist/hooks/useCanvasNodeResize.d.ts.map +1 -0
  46. package/dist/hooks/useCanvasNodeResize.js +53 -0
  47. package/dist/hooks/useCanvasViewport.d.ts +12 -0
  48. package/dist/hooks/useCanvasViewport.d.ts.map +1 -0
  49. package/dist/hooks/useCanvasViewport.js +84 -0
  50. package/dist/hooks/useChoiceDragDrop.d.ts +25 -0
  51. package/dist/hooks/useChoiceDragDrop.d.ts.map +1 -0
  52. package/dist/hooks/useChoiceDragDrop.js +40 -0
  53. package/dist/hooks/useEditorAdapter.d.ts +46 -0
  54. package/dist/hooks/useEditorAdapter.d.ts.map +1 -0
  55. package/dist/hooks/useEditorAdapter.js +281 -0
  56. package/dist/hooks/useEditorAutosave.d.ts +18 -0
  57. package/dist/hooks/useEditorAutosave.d.ts.map +1 -0
  58. package/dist/hooks/useEditorAutosave.js +37 -0
  59. package/dist/hooks/useEditorHistory.d.ts +16 -0
  60. package/dist/hooks/useEditorHistory.d.ts.map +1 -0
  61. package/dist/hooks/useEditorHistory.js +103 -0
  62. package/dist/hooks/useEditorSelection.d.ts +22 -0
  63. package/dist/hooks/useEditorSelection.d.ts.map +1 -0
  64. package/dist/hooks/useEditorSelection.js +76 -0
  65. package/dist/hooks/useGraphConnect.d.ts +22 -0
  66. package/dist/hooks/useGraphConnect.d.ts.map +1 -0
  67. package/dist/hooks/useGraphConnect.js +75 -0
  68. package/dist/hooks/useTreeSpecEditor.d.ts +0 -8
  69. package/dist/hooks/useTreeSpecEditor.d.ts.map +1 -1
  70. package/dist/hooks/useTreeSpecEditor.js +231 -462
  71. package/dist/nodes/ChoiceCanvasRow.d.ts +10 -0
  72. package/dist/nodes/ChoiceCanvasRow.d.ts.map +1 -0
  73. package/dist/nodes/ChoiceCanvasRow.js +55 -0
  74. package/dist/nodes/EndNode.d.ts +3 -0
  75. package/dist/nodes/EndNode.d.ts.map +1 -0
  76. package/dist/nodes/EndNode.js +7 -0
  77. package/dist/nodes/PromptNode.d.ts +6 -0
  78. package/dist/nodes/PromptNode.d.ts.map +1 -0
  79. package/dist/nodes/PromptNode.js +51 -0
  80. package/dist/nodes/PromptNodeChoicesList.d.ts +14 -0
  81. package/dist/nodes/PromptNodeChoicesList.d.ts.map +1 -0
  82. package/dist/nodes/PromptNodeChoicesList.js +24 -0
  83. package/dist/nodes/PromptNodeHeader.d.ts +10 -0
  84. package/dist/nodes/PromptNodeHeader.d.ts.map +1 -0
  85. package/dist/nodes/PromptNodeHeader.js +6 -0
  86. package/dist/nodes/PromptNodeIssueBadges.d.ts +7 -0
  87. package/dist/nodes/PromptNodeIssueBadges.d.ts.map +1 -0
  88. package/dist/nodes/PromptNodeIssueBadges.js +6 -0
  89. package/dist/nodes/PromptNodeToolbar.d.ts +6 -0
  90. package/dist/nodes/PromptNodeToolbar.d.ts.map +1 -0
  91. package/dist/nodes/PromptNodeToolbar.js +5 -0
  92. package/dist/nodes/types.d.ts +13 -0
  93. package/dist/nodes/types.d.ts.map +1 -0
  94. package/dist/nodes/types.js +1 -0
  95. package/dist/utils/joinClasses.d.ts +2 -0
  96. package/dist/utils/joinClasses.d.ts.map +1 -0
  97. package/dist/utils/joinClasses.js +3 -0
  98. package/package.json +30 -12
@@ -0,0 +1,29 @@
1
+ import { type Edge, type Node, type OnEdgesChange, type OnNodesChange, type Viewport } from 'reactflow';
2
+ import { type EditorTree, type GraphEditorIssue } from '@signalsafe/tree-spec-editor-core';
3
+ export type UseCanvasGraphStateOptions = {
4
+ tree: EditorTree;
5
+ onChange: (next: EditorTree) => void;
6
+ issues: GraphEditorIssue[];
7
+ issuesByNode: Map<string, {
8
+ total: number;
9
+ errors: number;
10
+ warnings: number;
11
+ info: number;
12
+ }>;
13
+ readOnly: boolean;
14
+ resizeHeightByNodeId: Record<string, number>;
15
+ isResizingRef: React.MutableRefObject<boolean>;
16
+ suppressViewportSaveRef: React.MutableRefObject<boolean>;
17
+ };
18
+ export type UseCanvasGraphStateResult = {
19
+ nodes: Node[];
20
+ edges: Edge[];
21
+ onNodesChangeWrapped: OnNodesChange;
22
+ onEdgesChangeWrapped: OnEdgesChange;
23
+ onNodeDragStart: () => void;
24
+ onNodeDragStop: () => void;
25
+ onMoveEnd: (event: MouseEvent | TouchEvent, viewport: Viewport) => void;
26
+ setNodes: (value: Node[] | ((nodes: Node[]) => Node[])) => void;
27
+ };
28
+ export declare function useCanvasGraphState(options: UseCanvasGraphStateOptions): UseCanvasGraphStateResult;
29
+ //# sourceMappingURL=useCanvasGraphState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasGraphState.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasGraphState.ts"],"names":[],"mappings":"AACA,OAAO,EAGH,KAAK,IAAI,EACT,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,QAAQ,EAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EASH,KAAK,UAAU,EACf,KAAK,gBAAgB,EAGxB,MAAM,mCAAmC,CAAC;AAM3C,MAAM,MAAM,0BAA0B,GAAG;IACrC,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7F,QAAQ,EAAE,OAAO,CAAC;IAClB,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,aAAa,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C,uBAAuB,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;CAC5D,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACpC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,oBAAoB,EAAE,aAAa,CAAC;IACpC,oBAAoB,EAAE,aAAa,CAAC;IACpC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACxE,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;CACnE,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,yBAAyB,CA4KlG"}
@@ -0,0 +1,150 @@
1
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
2
+ import { useEdgesState, useNodesState, } from 'reactflow';
3
+ import { END_NODE_ID, getEditorHints, isNodeLocked, patchGraphEditorMeta, resolveCanvasNodeWidth, resolveEndNodePosition, snapPosition, } from '@signalsafe/tree-spec-editor-core';
4
+ import { buildEdgesFromTransitions, buildTransitionsFromEdges } from '../canvas/edgeBuilders';
5
+ import { NODE_DRAG_HANDLE_SELECTOR } from '../canvas/constants';
6
+ export function useCanvasGraphState(options) {
7
+ const { tree, onChange, issues, issuesByNode, readOnly, resizeHeightByNodeId, isResizingRef, suppressViewportSaveRef, } = options;
8
+ const isDraggingRef = useRef(false);
9
+ const nodesRef = useRef([]);
10
+ const edgesRef = useRef([]);
11
+ const treeRef = useRef(tree);
12
+ treeRef.current = tree;
13
+ const endNodePosition = useMemo(() => resolveEndNodePosition(tree), [tree._meta, tree.nodes]);
14
+ useEffect(() => {
15
+ suppressViewportSaveRef.current = true;
16
+ const id = globalThis.setTimeout(() => {
17
+ suppressViewportSaveRef.current = false;
18
+ }, 0);
19
+ return () => globalThis.clearTimeout(id);
20
+ }, [tree._meta, suppressViewportSaveRef]);
21
+ const initialNodes = useMemo(() => {
22
+ const arr = [];
23
+ for (const n of Object.values(tree.nodes)) {
24
+ const editor = getEditorHints(n);
25
+ const locked = isNodeLocked(n);
26
+ const nodeWidth = resolveCanvasNodeWidth(editor);
27
+ const nodeHeight = editor.height ?? resizeHeightByNodeId[n.id];
28
+ arr.push({
29
+ id: n.id,
30
+ type: 'promptNode',
31
+ position: n.position ?? { x: 0, y: 0 },
32
+ draggable: !locked,
33
+ dragHandle: locked || readOnly ? undefined : NODE_DRAG_HANDLE_SELECTOR,
34
+ style: {
35
+ width: nodeWidth,
36
+ ...(nodeHeight === undefined ? {} : { height: nodeHeight }),
37
+ },
38
+ data: {
39
+ node: n,
40
+ isStart: tree.start_node === n.id,
41
+ issuesTotal: issuesByNode.get(n.id)?.total ?? 0,
42
+ issuesErrors: issuesByNode.get(n.id)?.errors ?? 0,
43
+ issuesWarnings: issuesByNode.get(n.id)?.warnings ?? 0,
44
+ issuesInfo: issuesByNode.get(n.id)?.info ?? 0,
45
+ lockedResizeHeight: resizeHeightByNodeId[n.id],
46
+ },
47
+ });
48
+ }
49
+ arr.push({
50
+ id: END_NODE_ID,
51
+ type: 'endNode',
52
+ position: endNodePosition,
53
+ data: {},
54
+ selectable: true,
55
+ draggable: true,
56
+ });
57
+ return arr;
58
+ }, [tree.nodes, tree.start_node, endNodePosition, issuesByNode, readOnly, resizeHeightByNodeId]);
59
+ const initialEdges = useMemo(() => buildEdgesFromTransitions(tree), [tree]);
60
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
61
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
62
+ useEffect(() => {
63
+ nodesRef.current = nodes;
64
+ }, [nodes]);
65
+ useEffect(() => {
66
+ edgesRef.current = edges;
67
+ }, [edges]);
68
+ const lastTreeRef = useRef('');
69
+ useEffect(() => {
70
+ const nextKey = JSON.stringify({ tree, issues: issues.length });
71
+ if (nextKey === lastTreeRef.current)
72
+ return;
73
+ if (isResizingRef.current)
74
+ return;
75
+ lastTreeRef.current = nextKey;
76
+ setNodes(initialNodes);
77
+ setEdges(initialEdges);
78
+ }, [tree, issues.length, initialNodes, initialEdges, setNodes, setEdges, isResizingRef]);
79
+ const commit = useCallback((nextNodes, nextEdges) => {
80
+ if (isDraggingRef.current)
81
+ return;
82
+ const updatedNodes = { ...tree.nodes };
83
+ for (const n of nextNodes) {
84
+ if (n.id === END_NODE_ID)
85
+ continue;
86
+ const existing = updatedNodes[n.id];
87
+ if (!existing)
88
+ continue;
89
+ if (isNodeLocked(existing))
90
+ continue;
91
+ updatedNodes[n.id] = {
92
+ ...existing,
93
+ position: snapPosition({ x: n.position.x, y: n.position.y }),
94
+ };
95
+ }
96
+ const transitions = buildTransitionsFromEdges(nextEdges, tree.transitions);
97
+ const endNode = nextNodes.find((n) => n.id === END_NODE_ID);
98
+ let nextTree = {
99
+ ...tree,
100
+ nodes: updatedNodes,
101
+ transitions,
102
+ };
103
+ if (endNode) {
104
+ nextTree = patchGraphEditorMeta(nextTree, {
105
+ end_position: { x: endNode.position.x, y: endNode.position.y },
106
+ });
107
+ }
108
+ onChange(nextTree);
109
+ }, [tree, onChange]);
110
+ const onMoveEnd = useCallback((_event, viewport) => {
111
+ if (suppressViewportSaveRef.current)
112
+ return;
113
+ onChange(patchGraphEditorMeta(tree, { viewport }));
114
+ }, [tree, onChange, suppressViewportSaveRef]);
115
+ const onNodesChangeWrapped = useCallback((changes) => {
116
+ onNodesChange(changes);
117
+ const shouldCommit = changes.some((c) => {
118
+ if (c?.type === 'select' || c?.type === 'dimensions')
119
+ return false;
120
+ if (c?.type === 'position' && c?.dragging)
121
+ return false;
122
+ return true;
123
+ });
124
+ if (shouldCommit)
125
+ queueMicrotask(() => commit(nodesRef.current, edgesRef.current));
126
+ }, [onNodesChange, commit]);
127
+ const onEdgesChangeWrapped = useCallback((changes) => {
128
+ onEdgesChange(changes);
129
+ const shouldCommit = changes.some((c) => c?.type !== 'select');
130
+ if (shouldCommit)
131
+ queueMicrotask(() => commit(nodesRef.current, edgesRef.current));
132
+ }, [onEdgesChange, commit]);
133
+ const onNodeDragStart = useCallback(() => {
134
+ isDraggingRef.current = true;
135
+ }, []);
136
+ const onNodeDragStop = useCallback(() => {
137
+ isDraggingRef.current = false;
138
+ commit(nodesRef.current, edgesRef.current);
139
+ }, [commit]);
140
+ return {
141
+ nodes,
142
+ edges,
143
+ onNodesChangeWrapped,
144
+ onEdgesChangeWrapped,
145
+ onNodeDragStart,
146
+ onNodeDragStop,
147
+ onMoveEnd,
148
+ setNodes,
149
+ };
150
+ }
@@ -0,0 +1,12 @@
1
+ import type { GraphEditorIssue } from '@signalsafe/tree-spec-editor-core';
2
+ export type CanvasIssueIndex = {
3
+ issuesByNode: Map<string, {
4
+ total: number;
5
+ errors: number;
6
+ warnings: number;
7
+ info: number;
8
+ }>;
9
+ issueKeySet: Set<string>;
10
+ };
11
+ export declare function useCanvasIssueIndex(issues: GraphEditorIssue[]): CanvasIssueIndex;
12
+ //# sourceMappingURL=useCanvasIssueIndex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasIssueIndex.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasIssueIndex.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,MAAM,gBAAgB,GAAG;IAC3B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7F,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,CA0BhF"}
@@ -0,0 +1,32 @@
1
+ import { useMemo } from 'react';
2
+ import { TREE_SPEC_ISSUE_SEVERITY } from '@signalsafe/tree-spec';
3
+ export function useCanvasIssueIndex(issues) {
4
+ const issuesByNode = useMemo(() => {
5
+ const m = new Map();
6
+ for (const i of issues) {
7
+ if (!i.node_id)
8
+ continue;
9
+ const cur = m.get(i.node_id) ?? { total: 0, errors: 0, warnings: 0, info: 0 };
10
+ cur.total += 1;
11
+ const sev = String(i.severity ?? '').toLowerCase();
12
+ if (sev === TREE_SPEC_ISSUE_SEVERITY.WARNING)
13
+ cur.warnings += 1;
14
+ else if (sev === TREE_SPEC_ISSUE_SEVERITY.INFO)
15
+ cur.info += 1;
16
+ else
17
+ cur.errors += 1;
18
+ m.set(i.node_id, cur);
19
+ }
20
+ return m;
21
+ }, [issues]);
22
+ const issueKeySet = useMemo(() => {
23
+ const s = new Set();
24
+ for (const i of issues) {
25
+ if (!i.node_id || !i.choice_id)
26
+ continue;
27
+ s.add(`${i.node_id}::${i.choice_id}`);
28
+ }
29
+ return s;
30
+ }, [issues]);
31
+ return { issuesByNode, issueKeySet };
32
+ }
@@ -0,0 +1,17 @@
1
+ import type { Node } from 'reactflow';
2
+ import { type EditorTree } from '@signalsafe/tree-spec-editor-core';
3
+ export type UseCanvasNodeResizeOptions = {
4
+ readOnly: boolean;
5
+ treeRef: React.MutableRefObject<EditorTree>;
6
+ onChange: (next: EditorTree) => void;
7
+ setNodes: (value: Node[] | ((nodes: Node[]) => Node[])) => void;
8
+ isResizingRef: React.MutableRefObject<boolean>;
9
+ resizeHeightByNodeId: Record<string, number>;
10
+ setResizeHeightByNodeId: React.Dispatch<React.SetStateAction<Record<string, number>>>;
11
+ };
12
+ export type UseCanvasNodeResizeResult = {
13
+ handleResizeNodeStart: (nodeId: string, width: number, height: number) => void;
14
+ handleResizeNode: (nodeId: string, width: number, height: number) => void;
15
+ };
16
+ export declare function useCanvasNodeResize(options: UseCanvasNodeResizeOptions): UseCanvasNodeResizeResult;
17
+ //# sourceMappingURL=useCanvasNodeResize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasNodeResize.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasNodeResize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAEpG,MAAM,MAAM,0BAA0B,GAAG;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,KAAK,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC5C,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAChE,aAAa,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,uBAAuB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CACzF,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACpC,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/E,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7E,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,yBAAyB,CAmElG"}
@@ -0,0 +1,53 @@
1
+ import { useCallback } from 'react';
2
+ import { isNodeLocked, patchEditorHints } from '@signalsafe/tree-spec-editor-core';
3
+ export function useCanvasNodeResize(options) {
4
+ const { readOnly, treeRef, onChange, setNodes, isResizingRef, setResizeHeightByNodeId, } = options;
5
+ const handleResizeNodeStart = useCallback((nodeId, width, height) => {
6
+ if (readOnly)
7
+ return;
8
+ isResizingRef.current = true;
9
+ const lockedHeight = Math.round(height);
10
+ setResizeHeightByNodeId((prev) => ({ ...prev, [nodeId]: lockedHeight }));
11
+ setNodes((current) => current.map((node) => node.id === nodeId
12
+ ? {
13
+ ...node,
14
+ style: {
15
+ ...node.style,
16
+ width: Math.round(width),
17
+ height: lockedHeight,
18
+ },
19
+ data: {
20
+ ...node.data,
21
+ lockedResizeHeight: lockedHeight,
22
+ },
23
+ }
24
+ : node));
25
+ }, [readOnly, setNodes, isResizingRef, setResizeHeightByNodeId]);
26
+ const handleResizeNode = useCallback((nodeId, width, height) => {
27
+ const node = treeRef.current.nodes[nodeId];
28
+ if (!node || isNodeLocked(node) || readOnly)
29
+ return;
30
+ isResizingRef.current = false;
31
+ setResizeHeightByNodeId((prev) => {
32
+ if (!(nodeId in prev))
33
+ return prev;
34
+ const next = { ...prev };
35
+ delete next[nodeId];
36
+ return next;
37
+ });
38
+ onChange({
39
+ ...treeRef.current,
40
+ nodes: {
41
+ ...treeRef.current.nodes,
42
+ [nodeId]: patchEditorHints(node, {
43
+ width: Math.round(width),
44
+ height: Math.round(height),
45
+ }),
46
+ },
47
+ });
48
+ }, [onChange, readOnly, treeRef, isResizingRef, setResizeHeightByNodeId]);
49
+ return {
50
+ handleResizeNodeStart,
51
+ handleResizeNode,
52
+ };
53
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactFlowInstance } from 'reactflow';
2
+ import { type GraphSelection } from '@signalsafe/tree-spec-editor-core';
3
+ export type UseCanvasViewportOptions = {
4
+ rf: ReactFlowInstance;
5
+ focusNodeId?: string | null;
6
+ fitViewNonce?: number;
7
+ contextualZoom?: boolean;
8
+ selected?: GraphSelection;
9
+ suppressViewportSaveRef: React.MutableRefObject<boolean>;
10
+ };
11
+ export declare function useCanvasViewport(options: UseCanvasViewportOptions): void;
12
+ //# sourceMappingURL=useCanvasViewport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasViewport.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasViewport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAAqC,KAAK,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAE3G,MAAM,MAAM,wBAAwB,GAAG;IACnC,EAAE,EAAE,iBAAiB,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,uBAAuB,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;CAC5D,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAqFzE"}
@@ -0,0 +1,84 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { END_NODE_ID, GRAPH_SELECTION_KIND } from '@signalsafe/tree-spec-editor-core';
3
+ export function useCanvasViewport(options) {
4
+ const { rf, focusNodeId, fitViewNonce, contextualZoom = true, selected, suppressViewportSaveRef, } = options;
5
+ const lastContextualSelectionRef = useRef(null);
6
+ useEffect(() => {
7
+ if (!focusNodeId)
8
+ return;
9
+ suppressViewportSaveRef.current = true;
10
+ try {
11
+ const n = rf.getNode(focusNodeId);
12
+ if (!n)
13
+ return;
14
+ rf.setCenter(n.position.x + 150, n.position.y + 60, { zoom: 1, duration: 300 });
15
+ }
16
+ catch {
17
+ // ignore
18
+ }
19
+ const releaseId = globalThis.setTimeout(() => {
20
+ suppressViewportSaveRef.current = false;
21
+ }, 350);
22
+ return () => globalThis.clearTimeout(releaseId);
23
+ }, [focusNodeId, rf, suppressViewportSaveRef]);
24
+ useEffect(() => {
25
+ if (!fitViewNonce)
26
+ return;
27
+ suppressViewportSaveRef.current = true;
28
+ const id = globalThis.setTimeout(() => {
29
+ try {
30
+ rf.fitView({ padding: 0.2, duration: 300 });
31
+ }
32
+ catch {
33
+ // ignore
34
+ }
35
+ }, 100);
36
+ const releaseId = globalThis.setTimeout(() => {
37
+ suppressViewportSaveRef.current = false;
38
+ }, 450);
39
+ return () => {
40
+ globalThis.clearTimeout(id);
41
+ globalThis.clearTimeout(releaseId);
42
+ };
43
+ }, [fitViewNonce, rf, suppressViewportSaveRef]);
44
+ useEffect(() => {
45
+ if (!contextualZoom || !selected?.id)
46
+ return;
47
+ if (selected.kind === GRAPH_SELECTION_KIND.EDGE)
48
+ return;
49
+ if (focusNodeId && selected.kind === GRAPH_SELECTION_KIND.NODE && focusNodeId === selected.id) {
50
+ return;
51
+ }
52
+ const selectionKey = `${selected.kind}:${selected.id}`;
53
+ if (lastContextualSelectionRef.current === selectionKey)
54
+ return;
55
+ lastContextualSelectionRef.current = selectionKey;
56
+ const nodeIds = [];
57
+ if (selected.kind === GRAPH_SELECTION_KIND.NODE && selected.id !== END_NODE_ID) {
58
+ nodeIds.push(selected.id);
59
+ }
60
+ if (nodeIds.length === 0)
61
+ return;
62
+ suppressViewportSaveRef.current = true;
63
+ const id = globalThis.setTimeout(() => {
64
+ try {
65
+ rf.fitView({
66
+ nodes: nodeIds.map((nodeId) => ({ id: nodeId })),
67
+ padding: 0.35,
68
+ duration: 250,
69
+ maxZoom: 1.25,
70
+ });
71
+ }
72
+ catch {
73
+ // ignore
74
+ }
75
+ }, 0);
76
+ const releaseId = globalThis.setTimeout(() => {
77
+ suppressViewportSaveRef.current = false;
78
+ }, 300);
79
+ return () => {
80
+ globalThis.clearTimeout(id);
81
+ globalThis.clearTimeout(releaseId);
82
+ };
83
+ }, [contextualZoom, focusNodeId, rf, selected, suppressViewportSaveRef]);
84
+ }
@@ -0,0 +1,25 @@
1
+ import { type GraphSelection } from '@signalsafe/tree-spec-editor-core';
2
+ export type ChoiceDragState = {
3
+ sourceNodeId: string;
4
+ choiceId: string;
5
+ };
6
+ export type ChoiceDropTarget = {
7
+ nodeId: string;
8
+ index: number;
9
+ };
10
+ export type UseChoiceDragDropOptions = {
11
+ readOnly: boolean;
12
+ onChoiceSelect?: (nodeId: string, choiceId: string) => void;
13
+ onSelect?: (sel: GraphSelection) => void;
14
+ onRepositionChoice?: (fromNodeId: string, choiceId: string, toNodeId: string, toIndex: number) => void;
15
+ };
16
+ export type UseChoiceDragDropResult = {
17
+ choiceDrag: ChoiceDragState | null;
18
+ choiceDropTarget: ChoiceDropTarget | null;
19
+ handleChoiceDragStart: (nodeId: string, choiceId: string) => void;
20
+ handleChoiceDragEnd: () => void;
21
+ handleChoiceDragOver: (nodeId: string, index: number) => void;
22
+ handleChoiceDrop: (targetNodeId: string, targetIndex: number) => void;
23
+ };
24
+ export declare function useChoiceDragDrop(options: UseChoiceDragDropOptions): UseChoiceDragDropResult;
25
+ //# sourceMappingURL=useChoiceDragDrop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChoiceDragDrop.d.ts","sourceRoot":"","sources":["../../src/hooks/useChoiceDragDrop.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAE9F,MAAM,MAAM,eAAe,GAAG;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,kBAAkB,CAAC,EAAE,CACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,KACd,IAAI,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IAClC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAClE,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9D,gBAAgB,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,uBAAuB,CAmD5F"}
@@ -0,0 +1,40 @@
1
+ import { useCallback, useState } from 'react';
2
+ import { GRAPH_SELECTION_KIND } from '@signalsafe/tree-spec-editor-core';
3
+ export function useChoiceDragDrop(options) {
4
+ const { readOnly, onChoiceSelect, onSelect, onRepositionChoice } = options;
5
+ const [choiceDrag, setChoiceDrag] = useState(null);
6
+ const [choiceDropTarget, setChoiceDropTarget] = useState(null);
7
+ const handleChoiceDragStart = useCallback((nodeId, choiceId) => {
8
+ if (readOnly)
9
+ return;
10
+ setChoiceDrag({ sourceNodeId: nodeId, choiceId });
11
+ if (onChoiceSelect) {
12
+ onChoiceSelect(nodeId, choiceId);
13
+ }
14
+ else {
15
+ onSelect?.({ kind: GRAPH_SELECTION_KIND.NODE, id: nodeId });
16
+ }
17
+ }, [readOnly, onChoiceSelect, onSelect]);
18
+ const handleChoiceDragEnd = useCallback(() => {
19
+ setChoiceDrag(null);
20
+ setChoiceDropTarget(null);
21
+ }, []);
22
+ const handleChoiceDragOver = useCallback((nodeId, index) => {
23
+ setChoiceDropTarget({ nodeId, index });
24
+ }, []);
25
+ const handleChoiceDrop = useCallback((targetNodeId, targetIndex) => {
26
+ if (readOnly || !choiceDrag)
27
+ return;
28
+ onRepositionChoice?.(choiceDrag.sourceNodeId, choiceDrag.choiceId, targetNodeId, targetIndex);
29
+ setChoiceDrag(null);
30
+ setChoiceDropTarget(null);
31
+ }, [readOnly, choiceDrag, onRepositionChoice]);
32
+ return {
33
+ choiceDrag,
34
+ choiceDropTarget,
35
+ handleChoiceDragStart,
36
+ handleChoiceDragEnd,
37
+ handleChoiceDragOver,
38
+ handleChoiceDrop,
39
+ };
40
+ }
@@ -0,0 +1,46 @@
1
+ import { type MutableRefObject, type RefObject } from 'react';
2
+ import { type TreeSpecIssue, type TreeSpecWire } from '@signalsafe/tree-spec';
3
+ import { type AutosaveStatus, type EditorTree, type TreeSpecAuditEventItem, type TreeSpecSnapshotItem } from '@signalsafe/tree-spec-editor-core';
4
+ import type { GraphEditorVersionInfo, UseTreeSpecEditorActions, UseTreeSpecEditorOptions } from './types';
5
+ export type UseEditorAdapterOptions = {
6
+ options: UseTreeSpecEditorOptions;
7
+ tree: EditorTree | null;
8
+ isPublished: boolean;
9
+ setIsPublished: (next: boolean) => void;
10
+ replaceTreeWithoutHistory: (next: EditorTree | null) => void;
11
+ lastSavedKeyRef: MutableRefObject<string>;
12
+ setAutosaveStatus: (status: AutosaveStatus) => void;
13
+ };
14
+ export type UseEditorAdapterResult = {
15
+ loading: boolean;
16
+ saving: boolean;
17
+ publishing: boolean;
18
+ setPublishing: (next: boolean) => void;
19
+ creatingSnapshot: boolean;
20
+ cloning: boolean;
21
+ restoringSnapshotId: string | null;
22
+ lastValidatedAt: string | null;
23
+ rawTreeSpec: TreeSpecWire | null;
24
+ versionInfo: GraphEditorVersionInfo | null;
25
+ localIssues: TreeSpecIssue[];
26
+ serverIssues: TreeSpecIssue[];
27
+ snapshots: TreeSpecSnapshotItem[];
28
+ auditEvents: TreeSpecAuditEventItem[];
29
+ loadingSnapshots: boolean;
30
+ loadingAudit: boolean;
31
+ showDraftHistory: boolean;
32
+ setShowDraftHistory: (next: boolean) => void;
33
+ showAudit: boolean;
34
+ setShowAudit: (next: boolean) => void;
35
+ showPublishModal: boolean;
36
+ setShowPublishModal: (next: boolean) => void;
37
+ validate: UseTreeSpecEditorActions['validate'];
38
+ saveDraft: UseTreeSpecEditorActions['saveDraft'];
39
+ createSnapshot: UseTreeSpecEditorActions['createSnapshot'];
40
+ restoreSnapshot: UseTreeSpecEditorActions['restoreSnapshot'];
41
+ cloneToDraft: UseTreeSpecEditorActions['cloneToDraft'];
42
+ validateRef: RefObject<UseTreeSpecEditorActions['validate']>;
43
+ saveDraftRef: RefObject<UseTreeSpecEditorActions['saveDraft']>;
44
+ };
45
+ export declare function useEditorAdapter(adapterOptions: UseEditorAdapterOptions): UseEditorAdapterResult;
46
+ //# sourceMappingURL=useEditorAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEditorAdapter.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqD,KAAK,gBAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEjH,OAAO,EAKH,KAAK,aAAa,EAClB,KAAK,YAAY,EACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAQH,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAC5B,MAAM,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EAER,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EAC3B,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,uBAAuB,GAAG;IAClC,OAAO,EAAE,wBAAwB,CAAC;IAClC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,yBAAyB,EAAE,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7D,eAAe,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC1C,iBAAiB,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC3C,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC,WAAW,EAAE,sBAAsB,EAAE,CAAC;IACtC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,QAAQ,EAAE,wBAAwB,CAAC,UAAU,CAAC,CAAC;IAC/C,SAAS,EAAE,wBAAwB,CAAC,WAAW,CAAC,CAAC;IACjD,cAAc,EAAE,wBAAwB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,eAAe,EAAE,wBAAwB,CAAC,iBAAiB,CAAC,CAAC;IAC7D,YAAY,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAC;IACvD,WAAW,EAAE,SAAS,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7D,YAAY,EAAE,SAAS,CAAC,wBAAwB,CAAC,WAAW,CAAC,CAAC,CAAC;CAClE,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,uBAAuB,GAAG,sBAAsB,CAsShG"}