@coral-viz/viz 0.2.3

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 (191) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +11 -0
  3. package/dist/compatibility/CompatibilityEdge.d.ts +106 -0
  4. package/dist/compatibility/CompatibilityEdge.d.ts.map +1 -0
  5. package/dist/compatibility/CompatibilityEdge.js +233 -0
  6. package/dist/compatibility/CompatibilityEdge.js.map +1 -0
  7. package/dist/compatibility/IncompatibilityTooltip.d.ts +44 -0
  8. package/dist/compatibility/IncompatibilityTooltip.d.ts.map +1 -0
  9. package/dist/compatibility/IncompatibilityTooltip.js +145 -0
  10. package/dist/compatibility/IncompatibilityTooltip.js.map +1 -0
  11. package/dist/compatibility/index.d.ts +10 -0
  12. package/dist/compatibility/index.d.ts.map +1 -0
  13. package/dist/compatibility/index.js +10 -0
  14. package/dist/compatibility/index.js.map +1 -0
  15. package/dist/compatibility/useEdgeCompatibility.d.ts +82 -0
  16. package/dist/compatibility/useEdgeCompatibility.d.ts.map +1 -0
  17. package/dist/compatibility/useEdgeCompatibility.js +170 -0
  18. package/dist/compatibility/useEdgeCompatibility.js.map +1 -0
  19. package/dist/compatibility/validateConnection.d.ts +56 -0
  20. package/dist/compatibility/validateConnection.d.ts.map +1 -0
  21. package/dist/compatibility/validateConnection.js +382 -0
  22. package/dist/compatibility/validateConnection.js.map +1 -0
  23. package/dist/components/EditContext.d.ts +45 -0
  24. package/dist/components/EditContext.d.ts.map +1 -0
  25. package/dist/components/EditContext.js +41 -0
  26. package/dist/components/EditContext.js.map +1 -0
  27. package/dist/components/ShapeRenderer.d.ts +35 -0
  28. package/dist/components/ShapeRenderer.d.ts.map +1 -0
  29. package/dist/components/ShapeRenderer.js +77 -0
  30. package/dist/components/ShapeRenderer.js.map +1 -0
  31. package/dist/components/SymbolNode.d.ts +53 -0
  32. package/dist/components/SymbolNode.d.ts.map +1 -0
  33. package/dist/components/SymbolNode.js +333 -0
  34. package/dist/components/SymbolNode.js.map +1 -0
  35. package/dist/components/index.d.ts +8 -0
  36. package/dist/components/index.d.ts.map +1 -0
  37. package/dist/components/index.js +10 -0
  38. package/dist/components/index.js.map +1 -0
  39. package/dist/components/nodes.d.ts +43 -0
  40. package/dist/components/nodes.d.ts.map +1 -0
  41. package/dist/components/nodes.js +111 -0
  42. package/dist/components/nodes.js.map +1 -0
  43. package/dist/editor/CoralEditor.d.ts +29 -0
  44. package/dist/editor/CoralEditor.d.ts.map +1 -0
  45. package/dist/editor/CoralEditor.js +112 -0
  46. package/dist/editor/CoralEditor.js.map +1 -0
  47. package/dist/editor/FileControls.d.ts +34 -0
  48. package/dist/editor/FileControls.d.ts.map +1 -0
  49. package/dist/editor/FileControls.js +139 -0
  50. package/dist/editor/FileControls.js.map +1 -0
  51. package/dist/editor/LayoutControls.d.ts +41 -0
  52. package/dist/editor/LayoutControls.d.ts.map +1 -0
  53. package/dist/editor/LayoutControls.js +124 -0
  54. package/dist/editor/LayoutControls.js.map +1 -0
  55. package/dist/editor/converter.d.ts +37 -0
  56. package/dist/editor/converter.d.ts.map +1 -0
  57. package/dist/editor/converter.js +256 -0
  58. package/dist/editor/converter.js.map +1 -0
  59. package/dist/editor/index.d.ts +8 -0
  60. package/dist/editor/index.d.ts.map +1 -0
  61. package/dist/editor/index.js +8 -0
  62. package/dist/editor/index.js.map +1 -0
  63. package/dist/file/deserialize.d.ts +15 -0
  64. package/dist/file/deserialize.d.ts.map +1 -0
  65. package/dist/file/deserialize.js +145 -0
  66. package/dist/file/deserialize.js.map +1 -0
  67. package/dist/file/index.d.ts +15 -0
  68. package/dist/file/index.d.ts.map +1 -0
  69. package/dist/file/index.js +20 -0
  70. package/dist/file/index.js.map +1 -0
  71. package/dist/file/migrate.d.ts +29 -0
  72. package/dist/file/migrate.d.ts.map +1 -0
  73. package/dist/file/migrate.js +103 -0
  74. package/dist/file/migrate.js.map +1 -0
  75. package/dist/file/schema.d.ts +385 -0
  76. package/dist/file/schema.d.ts.map +1 -0
  77. package/dist/file/schema.js +180 -0
  78. package/dist/file/schema.js.map +1 -0
  79. package/dist/file/serialize.d.ts +30 -0
  80. package/dist/file/serialize.d.ts.map +1 -0
  81. package/dist/file/serialize.js +218 -0
  82. package/dist/file/serialize.js.map +1 -0
  83. package/dist/file/validate.d.ts +21 -0
  84. package/dist/file/validate.d.ts.map +1 -0
  85. package/dist/file/validate.js +165 -0
  86. package/dist/file/validate.js.map +1 -0
  87. package/dist/index.d.ts +24 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +42 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/layout/elk.d.ts +77 -0
  92. package/dist/layout/elk.d.ts.map +1 -0
  93. package/dist/layout/elk.js +379 -0
  94. package/dist/layout/elk.js.map +1 -0
  95. package/dist/layout/index.d.ts +12 -0
  96. package/dist/layout/index.d.ts.map +1 -0
  97. package/dist/layout/index.js +12 -0
  98. package/dist/layout/index.js.map +1 -0
  99. package/dist/layout/nodeSizing.d.ts +65 -0
  100. package/dist/layout/nodeSizing.d.ts.map +1 -0
  101. package/dist/layout/nodeSizing.js +319 -0
  102. package/dist/layout/nodeSizing.js.map +1 -0
  103. package/dist/layout/positionStability.d.ts +73 -0
  104. package/dist/layout/positionStability.d.ts.map +1 -0
  105. package/dist/layout/positionStability.js +199 -0
  106. package/dist/layout/positionStability.js.map +1 -0
  107. package/dist/layout/useAxisConstrain.d.ts +20 -0
  108. package/dist/layout/useAxisConstrain.d.ts.map +1 -0
  109. package/dist/layout/useAxisConstrain.js +106 -0
  110. package/dist/layout/useAxisConstrain.js.map +1 -0
  111. package/dist/layout/useDiagramState.d.ts +92 -0
  112. package/dist/layout/useDiagramState.d.ts.map +1 -0
  113. package/dist/layout/useDiagramState.js +256 -0
  114. package/dist/layout/useDiagramState.js.map +1 -0
  115. package/dist/layout/useLayoutHistory.d.ts +53 -0
  116. package/dist/layout/useLayoutHistory.d.ts.map +1 -0
  117. package/dist/layout/useLayoutHistory.js +124 -0
  118. package/dist/layout/useLayoutHistory.js.map +1 -0
  119. package/dist/properties/NodePropertiesPanel.d.ts +47 -0
  120. package/dist/properties/NodePropertiesPanel.d.ts.map +1 -0
  121. package/dist/properties/NodePropertiesPanel.js +199 -0
  122. package/dist/properties/NodePropertiesPanel.js.map +1 -0
  123. package/dist/properties/index.d.ts +10 -0
  124. package/dist/properties/index.d.ts.map +1 -0
  125. package/dist/properties/index.js +10 -0
  126. package/dist/properties/index.js.map +1 -0
  127. package/dist/properties/propertyUtils.d.ts +60 -0
  128. package/dist/properties/propertyUtils.d.ts.map +1 -0
  129. package/dist/properties/propertyUtils.js +89 -0
  130. package/dist/properties/propertyUtils.js.map +1 -0
  131. package/dist/settings/FontSettingsForm.d.ts +28 -0
  132. package/dist/settings/FontSettingsForm.d.ts.map +1 -0
  133. package/dist/settings/FontSettingsForm.js +129 -0
  134. package/dist/settings/FontSettingsForm.js.map +1 -0
  135. package/dist/settings/LayoutSettingsForm.d.ts +37 -0
  136. package/dist/settings/LayoutSettingsForm.d.ts.map +1 -0
  137. package/dist/settings/LayoutSettingsForm.js +165 -0
  138. package/dist/settings/LayoutSettingsForm.js.map +1 -0
  139. package/dist/settings/SettingsPanel.d.ts +55 -0
  140. package/dist/settings/SettingsPanel.d.ts.map +1 -0
  141. package/dist/settings/SettingsPanel.js +84 -0
  142. package/dist/settings/SettingsPanel.js.map +1 -0
  143. package/dist/settings/index.d.ts +13 -0
  144. package/dist/settings/index.d.ts.map +1 -0
  145. package/dist/settings/index.js +13 -0
  146. package/dist/settings/index.js.map +1 -0
  147. package/dist/settings/useSettings.d.ts +51 -0
  148. package/dist/settings/useSettings.d.ts.map +1 -0
  149. package/dist/settings/useSettings.js +154 -0
  150. package/dist/settings/useSettings.js.map +1 -0
  151. package/dist/text-editor/SplitEditor.d.ts +72 -0
  152. package/dist/text-editor/SplitEditor.d.ts.map +1 -0
  153. package/dist/text-editor/SplitEditor.js +172 -0
  154. package/dist/text-editor/SplitEditor.js.map +1 -0
  155. package/dist/text-editor/TextEditor.d.ts +53 -0
  156. package/dist/text-editor/TextEditor.d.ts.map +1 -0
  157. package/dist/text-editor/TextEditor.js +181 -0
  158. package/dist/text-editor/TextEditor.js.map +1 -0
  159. package/dist/text-editor/hooks/useBidirectionalSync.d.ts +106 -0
  160. package/dist/text-editor/hooks/useBidirectionalSync.d.ts.map +1 -0
  161. package/dist/text-editor/hooks/useBidirectionalSync.js +151 -0
  162. package/dist/text-editor/hooks/useBidirectionalSync.js.map +1 -0
  163. package/dist/text-editor/index.d.ts +9 -0
  164. package/dist/text-editor/index.d.ts.map +1 -0
  165. package/dist/text-editor/index.js +11 -0
  166. package/dist/text-editor/index.js.map +1 -0
  167. package/dist/types.d.ts +242 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/types.js +5 -0
  170. package/dist/types.js.map +1 -0
  171. package/dist/waypoints/index.d.ts +8 -0
  172. package/dist/waypoints/index.d.ts.map +1 -0
  173. package/dist/waypoints/index.js +7 -0
  174. package/dist/waypoints/index.js.map +1 -0
  175. package/dist/waypoints/operations.d.ts +29 -0
  176. package/dist/waypoints/operations.d.ts.map +1 -0
  177. package/dist/waypoints/operations.js +85 -0
  178. package/dist/waypoints/operations.js.map +1 -0
  179. package/dist/waypoints/router.d.ts +23 -0
  180. package/dist/waypoints/router.d.ts.map +1 -0
  181. package/dist/waypoints/router.js +109 -0
  182. package/dist/waypoints/router.js.map +1 -0
  183. package/dist/waypoints/types.d.ts +15 -0
  184. package/dist/waypoints/types.d.ts.map +1 -0
  185. package/dist/waypoints/types.js +5 -0
  186. package/dist/waypoints/types.js.map +1 -0
  187. package/dist/waypoints/useWaypointManager.d.ts +38 -0
  188. package/dist/waypoints/useWaypointManager.d.ts.map +1 -0
  189. package/dist/waypoints/useWaypointManager.js +106 -0
  190. package/dist/waypoints/useWaypointManager.js.map +1 -0
  191. package/package.json +76 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * useAxisConstrain — Shift+drag axis-locked node movement
3
+ *
4
+ * Wraps React Flow's onNodesChange to constrain drag movement to a single
5
+ * axis (horizontal or vertical) when the Shift key is held. The dominant
6
+ * axis is detected after a small movement threshold.
7
+ */
8
+ import { useCallback, useRef, useEffect } from 'react';
9
+ /** Threshold in pixels before locking to an axis */
10
+ const AXIS_LOCK_THRESHOLD = 5;
11
+ /**
12
+ * Wraps onNodesChange to add Shift+drag axis constraint.
13
+ *
14
+ * @param originalOnNodesChange - The onNodesChange from useNodesState
15
+ * @param nodes - Current nodes array (to read start positions)
16
+ */
17
+ export function useAxisConstrain(originalOnNodesChange, nodes) {
18
+ const shiftHeld = useRef(false);
19
+ const dragState = useRef(null);
20
+ // Track Shift key globally
21
+ useEffect(() => {
22
+ const onKeyDown = (e) => {
23
+ if (e.key === 'Shift')
24
+ shiftHeld.current = true;
25
+ };
26
+ const onKeyUp = (e) => {
27
+ if (e.key === 'Shift')
28
+ shiftHeld.current = false;
29
+ };
30
+ window.addEventListener('keydown', onKeyDown);
31
+ window.addEventListener('keyup', onKeyUp);
32
+ return () => {
33
+ window.removeEventListener('keydown', onKeyDown);
34
+ window.removeEventListener('keyup', onKeyUp);
35
+ };
36
+ }, []);
37
+ const onNodesChange = useCallback((changes) => {
38
+ if (!shiftHeld.current) {
39
+ // No Shift held — pass through unmodified, reset drag state
40
+ if (dragState.current)
41
+ dragState.current = null;
42
+ originalOnNodesChange(changes);
43
+ return;
44
+ }
45
+ const modified = changes.map((change) => {
46
+ if (change.type !== 'position')
47
+ return change;
48
+ const posChange = change;
49
+ // Drag ended
50
+ if (posChange.dragging === false) {
51
+ if (dragState.current?.nodeId === posChange.id) {
52
+ dragState.current = null;
53
+ }
54
+ return change;
55
+ }
56
+ // No position data yet
57
+ if (!posChange.position)
58
+ return change;
59
+ // Drag starting — record start position from current node state
60
+ if (!dragState.current || dragState.current.nodeId !== posChange.id) {
61
+ const node = nodes.find((n) => n.id === posChange.id);
62
+ if (!node)
63
+ return change;
64
+ dragState.current = {
65
+ nodeId: posChange.id,
66
+ startPos: { x: node.position.x, y: node.position.y },
67
+ axis: null,
68
+ };
69
+ }
70
+ const state = dragState.current;
71
+ const dx = posChange.position.x - state.startPos.x;
72
+ const dy = posChange.position.y - state.startPos.y;
73
+ // Detect axis if not yet locked
74
+ if (!state.axis) {
75
+ const absDx = Math.abs(dx);
76
+ const absDy = Math.abs(dy);
77
+ if (absDx > AXIS_LOCK_THRESHOLD || absDy > AXIS_LOCK_THRESHOLD) {
78
+ state.axis = absDx >= absDy ? 'x' : 'y';
79
+ }
80
+ else {
81
+ // Below threshold — freeze in place
82
+ return {
83
+ ...posChange,
84
+ position: { ...state.startPos },
85
+ };
86
+ }
87
+ }
88
+ // Apply axis constraint
89
+ if (state.axis === 'x') {
90
+ return {
91
+ ...posChange,
92
+ position: { x: posChange.position.x, y: state.startPos.y },
93
+ };
94
+ }
95
+ else {
96
+ return {
97
+ ...posChange,
98
+ position: { x: state.startPos.x, y: posChange.position.y },
99
+ };
100
+ }
101
+ });
102
+ originalOnNodesChange(modified);
103
+ }, [originalOnNodesChange, nodes]);
104
+ return { onNodesChange };
105
+ }
106
+ //# sourceMappingURL=useAxisConstrain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAxisConstrain.js","sourceRoot":"","sources":["../../src/layout/useAxisConstrain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * useDiagramState Hook (CORAL-REQ-013)
3
+ *
4
+ * Manages diagram state with separated parse, render, and layout operations.
5
+ * Preserves node positions across DSL changes and supports undo/redo.
6
+ */
7
+ import type { Node, Edge } from '@xyflow/react';
8
+ import type { Position, PositionSource } from '../types.js';
9
+ /**
10
+ * Parsed graph from DSL - minimal structure for diffing
11
+ */
12
+ export interface ParsedGraph {
13
+ nodes: Array<{
14
+ id: string;
15
+ type: string;
16
+ label: string;
17
+ width?: number;
18
+ height?: number;
19
+ }>;
20
+ edges: Array<{
21
+ id: string;
22
+ source: string;
23
+ target: string;
24
+ label?: string;
25
+ }>;
26
+ }
27
+ /**
28
+ * Node data for React Flow
29
+ */
30
+ export interface DiagramNodeData {
31
+ label: string;
32
+ nodeType: string;
33
+ [key: string]: unknown;
34
+ }
35
+ /**
36
+ * React Flow node with diagram data
37
+ */
38
+ export type DiagramNode = Node<DiagramNodeData>;
39
+ /**
40
+ * React Flow edge
41
+ */
42
+ export type DiagramEdge = Edge;
43
+ /**
44
+ * useDiagramState options
45
+ */
46
+ export interface UseDiagramStateOptions {
47
+ /** Maximum history entries to keep */
48
+ maxHistory?: number;
49
+ /** Layout direction */
50
+ direction?: 'DOWN' | 'UP' | 'LEFT' | 'RIGHT';
51
+ /** Node spacing */
52
+ spacing?: number;
53
+ }
54
+ /**
55
+ * useDiagramState return type
56
+ */
57
+ export interface UseDiagramStateResult {
58
+ /** Current React Flow nodes */
59
+ nodes: DiagramNode[];
60
+ /** Current React Flow edges */
61
+ edges: DiagramEdge[];
62
+ /** Loading state during layout operations */
63
+ isLoading: boolean;
64
+ /** Update the graph from parsed DSL */
65
+ setGraph: (graph: ParsedGraph) => Promise<void>;
66
+ /** Handle node drag - updates position */
67
+ onNodeDrag: (nodeId: string, position: Position) => void;
68
+ /** Handle drag end - saves snapshot for undo */
69
+ onDragEnd: () => void;
70
+ /** Run full ELK layout */
71
+ reflow: () => Promise<void>;
72
+ /** Set node positions directly (e.g., from loaded document) */
73
+ setNodePositions: (positions: Map<string, Position>, source: PositionSource) => void;
74
+ /** Get position source for a node */
75
+ getPositionSource: (nodeId: string) => PositionSource | undefined;
76
+ /** Undo last position change */
77
+ undo: () => void;
78
+ /** Redo last undone change */
79
+ redo: () => void;
80
+ /** Can undo? */
81
+ canUndo: boolean;
82
+ /** Can redo? */
83
+ canRedo: boolean;
84
+ }
85
+ /**
86
+ * Hook for managing diagram state with position stability.
87
+ *
88
+ * Separates parse, render, and layout operations to prevent undo/redo
89
+ * positions from being overwritten by automatic layout.
90
+ */
91
+ export declare function useDiagramState(options?: UseDiagramStateOptions): UseDiagramStateResult;
92
+ //# sourceMappingURL=useDiagramState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDiagramState.d.ts","sourceRoot":"","sources":["../../src/layout/useDiagramState.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAiB,MAAM,aAAa,CAAC;AAG3E;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;AAEhD;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC;AAgB/B;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IAC7C,mBAAmB;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,+BAA+B;IAC/B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IAEnB,uCAAuC;IACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,0CAA0C;IAC1C,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACzD,gDAAgD;IAChD,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,0BAA0B;IAC1B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,+DAA+D;IAC/D,gBAAgB,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAErF,qCAAqC;IACrC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;IAElE,gCAAgC;IAChC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,gBAAgB;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,qBAAqB,CAwS3F"}
@@ -0,0 +1,256 @@
1
+ /**
2
+ * useDiagramState Hook (CORAL-REQ-013)
3
+ *
4
+ * Manages diagram state with separated parse, render, and layout operations.
5
+ * Preserves node positions across DSL changes and supports undo/redo.
6
+ */
7
+ import { useState, useCallback, useRef } from 'react';
8
+ import { resolvePositions, incrementalLayout } from './positionStability.js';
9
+ /**
10
+ * Default node dimensions
11
+ */
12
+ const DEFAULT_NODE_WIDTH = 150;
13
+ const DEFAULT_NODE_HEIGHT = 50;
14
+ /**
15
+ * Hook for managing diagram state with position stability.
16
+ *
17
+ * Separates parse, render, and layout operations to prevent undo/redo
18
+ * positions from being overwritten by automatic layout.
19
+ */
20
+ export function useDiagramState(options = {}) {
21
+ const { maxHistory = 50, direction = 'DOWN', spacing = 50 } = options;
22
+ // React Flow state
23
+ const [nodes, setNodes] = useState([]);
24
+ const [edges, setEdges] = useState([]);
25
+ const [isLoading, setIsLoading] = useState(false);
26
+ // Position source tracking
27
+ const positionSourcesRef = useRef(new Map());
28
+ // Previous graph for diffing
29
+ const previousGraphRef = useRef({ nodes: [], edges: [] });
30
+ // Current positions (for diffing and incremental layout)
31
+ const currentPositionsRef = useRef(new Map());
32
+ // Undo/redo history
33
+ const undoStackRef = useRef([]);
34
+ const redoStackRef = useRef([]);
35
+ // Track if we're in the middle of a drag
36
+ const isDraggingRef = useRef(false);
37
+ const dragStartPositionsRef = useRef(new Map());
38
+ // Get current positions from nodes
39
+ const getCurrentPositions = useCallback(() => {
40
+ const positions = new Map();
41
+ for (const node of nodes) {
42
+ positions.set(node.id, { ...node.position });
43
+ }
44
+ return positions;
45
+ }, [nodes]);
46
+ // Save snapshot to undo stack
47
+ const saveSnapshot = useCallback((positions) => {
48
+ undoStackRef.current.push({
49
+ positions: new Map(positions),
50
+ timestamp: Date.now(),
51
+ });
52
+ // Trim history if too long
53
+ if (undoStackRef.current.length > maxHistory) {
54
+ undoStackRef.current.shift();
55
+ }
56
+ // Clear redo stack on new action
57
+ redoStackRef.current = [];
58
+ }, [maxHistory]);
59
+ // Update nodes with new positions (without triggering layout)
60
+ const applyPositions = useCallback((positions) => {
61
+ setNodes((currentNodes) => currentNodes.map((node) => {
62
+ const pos = positions.get(node.id);
63
+ return pos ? { ...node, position: pos } : node;
64
+ }));
65
+ // Update current positions ref
66
+ for (const [id, pos] of positions) {
67
+ currentPositionsRef.current.set(id, pos);
68
+ }
69
+ }, []);
70
+ // Set graph from parsed DSL
71
+ const setGraph = useCallback(async (graph) => {
72
+ setIsLoading(true);
73
+ try {
74
+ const newDiffableGraph = {
75
+ nodes: graph.nodes.map((n) => ({
76
+ id: n.id,
77
+ type: n.type,
78
+ label: n.label,
79
+ })),
80
+ edges: graph.edges.map((e) => ({
81
+ id: e.id,
82
+ source: e.source,
83
+ target: e.target,
84
+ })),
85
+ };
86
+ // Resolve positions based on diff
87
+ const resolution = resolvePositions(previousGraphRef.current, newDiffableGraph, currentPositionsRef.current);
88
+ // If there are nodes that need layout, run incremental layout
89
+ let finalPositions;
90
+ if (resolution.needsLayout.length > 0) {
91
+ const nodeInfos = graph.nodes.map((n) => ({
92
+ id: n.id,
93
+ type: n.type,
94
+ label: n.label,
95
+ width: n.width || DEFAULT_NODE_WIDTH,
96
+ height: n.height || DEFAULT_NODE_HEIGHT,
97
+ }));
98
+ finalPositions = await incrementalLayout(nodeInfos, graph.edges, resolution.positions, resolution.needsLayout, { direction, spacing });
99
+ // Mark new nodes as elk-computed
100
+ for (const id of resolution.needsLayout) {
101
+ positionSourcesRef.current.set(id, 'elk-computed');
102
+ }
103
+ }
104
+ else {
105
+ finalPositions = resolution.positions;
106
+ }
107
+ // Build React Flow nodes
108
+ const newNodes = graph.nodes.map((n) => {
109
+ const pos = finalPositions.get(n.id) || { x: 0, y: 0 };
110
+ return {
111
+ id: n.id,
112
+ type: 'default',
113
+ position: pos,
114
+ data: {
115
+ label: n.label,
116
+ nodeType: n.type,
117
+ },
118
+ };
119
+ });
120
+ // Build React Flow edges
121
+ const newEdges = graph.edges.map((e) => ({
122
+ id: e.id,
123
+ source: e.source,
124
+ target: e.target,
125
+ label: e.label,
126
+ }));
127
+ // Update state
128
+ setNodes(newNodes);
129
+ setEdges(newEdges);
130
+ // Update refs
131
+ previousGraphRef.current = newDiffableGraph;
132
+ currentPositionsRef.current = finalPositions;
133
+ // Clean up position sources for removed nodes
134
+ const currentNodeIds = new Set(graph.nodes.map((n) => n.id));
135
+ for (const id of positionSourcesRef.current.keys()) {
136
+ if (!currentNodeIds.has(id)) {
137
+ positionSourcesRef.current.delete(id);
138
+ }
139
+ }
140
+ }
141
+ finally {
142
+ setIsLoading(false);
143
+ }
144
+ }, [direction, spacing]);
145
+ // Handle node drag
146
+ const onNodeDrag = useCallback((nodeId, position) => {
147
+ // Save start positions on first drag event
148
+ if (!isDraggingRef.current) {
149
+ isDraggingRef.current = true;
150
+ dragStartPositionsRef.current = getCurrentPositions();
151
+ }
152
+ // Update position in state
153
+ setNodes((currentNodes) => currentNodes.map((node) => node.id === nodeId ? { ...node, position } : node));
154
+ // Update position source
155
+ positionSourcesRef.current.set(nodeId, 'user-dragged');
156
+ currentPositionsRef.current.set(nodeId, position);
157
+ }, [getCurrentPositions]);
158
+ // Handle drag end
159
+ const onDragEnd = useCallback(() => {
160
+ if (isDraggingRef.current) {
161
+ // Save snapshot of positions before drag for undo
162
+ saveSnapshot(dragStartPositionsRef.current);
163
+ isDraggingRef.current = false;
164
+ dragStartPositionsRef.current = new Map();
165
+ }
166
+ }, [saveSnapshot]);
167
+ // Run full ELK layout
168
+ const reflow = useCallback(async () => {
169
+ if (nodes.length === 0)
170
+ return;
171
+ setIsLoading(true);
172
+ try {
173
+ // Save current positions for undo
174
+ saveSnapshot(getCurrentPositions());
175
+ // Run full layout (all nodes need layout)
176
+ const nodeInfos = nodes.map((n) => ({
177
+ id: n.id,
178
+ type: n.data.nodeType,
179
+ label: n.data.label,
180
+ width: (n.measured?.width || n.width || DEFAULT_NODE_WIDTH),
181
+ height: (n.measured?.height || n.height || DEFAULT_NODE_HEIGHT),
182
+ }));
183
+ const edgeInfos = edges.map((e) => ({
184
+ id: e.id,
185
+ source: e.source,
186
+ target: e.target,
187
+ }));
188
+ const allNodeIds = nodes.map((n) => n.id);
189
+ const newPositions = await incrementalLayout(nodeInfos, edgeInfos, new Map(), // No pinned positions - full layout
190
+ allNodeIds, // All nodes need layout
191
+ { direction, spacing });
192
+ // Update nodes with new positions
193
+ applyPositions(newPositions);
194
+ // Mark all as elk-computed
195
+ for (const id of allNodeIds) {
196
+ positionSourcesRef.current.set(id, 'elk-computed');
197
+ }
198
+ }
199
+ finally {
200
+ setIsLoading(false);
201
+ }
202
+ }, [nodes, edges, direction, spacing, saveSnapshot, getCurrentPositions, applyPositions]);
203
+ // Set node positions directly (e.g., from loaded document)
204
+ const setNodePositions = useCallback((positions, source) => {
205
+ applyPositions(positions);
206
+ for (const id of positions.keys()) {
207
+ positionSourcesRef.current.set(id, source);
208
+ }
209
+ }, [applyPositions]);
210
+ // Get position source
211
+ const getPositionSource = useCallback((nodeId) => {
212
+ return positionSourcesRef.current.get(nodeId);
213
+ }, []);
214
+ // Undo
215
+ const undo = useCallback(() => {
216
+ const entry = undoStackRef.current.pop();
217
+ if (!entry)
218
+ return;
219
+ // Save current positions to redo stack
220
+ redoStackRef.current.push({
221
+ positions: getCurrentPositions(),
222
+ timestamp: Date.now(),
223
+ });
224
+ // Apply undo positions (without triggering layout)
225
+ applyPositions(entry.positions);
226
+ }, [getCurrentPositions, applyPositions]);
227
+ // Redo
228
+ const redo = useCallback(() => {
229
+ const entry = redoStackRef.current.pop();
230
+ if (!entry)
231
+ return;
232
+ // Save current positions to undo stack
233
+ undoStackRef.current.push({
234
+ positions: getCurrentPositions(),
235
+ timestamp: Date.now(),
236
+ });
237
+ // Apply redo positions (without triggering layout)
238
+ applyPositions(entry.positions);
239
+ }, [getCurrentPositions, applyPositions]);
240
+ return {
241
+ nodes,
242
+ edges,
243
+ isLoading,
244
+ setGraph,
245
+ onNodeDrag,
246
+ onDragEnd,
247
+ reflow,
248
+ setNodePositions,
249
+ getPositionSource,
250
+ undo,
251
+ redo,
252
+ canUndo: undoStackRef.current.length > 0,
253
+ canRedo: redoStackRef.current.length > 0,
254
+ };
255
+ }
256
+ //# sourceMappingURL=useDiagramState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDiagramState.js","sourceRoot":"","sources":["../../src/layout/useDiagramState.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * useLayoutHistory - Hook for managing undo/redo stack for node positions
3
+ *
4
+ * This hook tracks node position changes and allows users to undo/redo
5
+ * layout operations, particularly useful after automatic reflow.
6
+ */
7
+ import type { Node, XYPosition } from '@xyflow/react';
8
+ import type { EdgeWaypoint } from '../waypoints/types.js';
9
+ /** Snapshot of node positions (and optional waypoints) at a point in time */
10
+ export interface PositionSnapshot {
11
+ /** Map of node ID to position */
12
+ positions: Map<string, XYPosition>;
13
+ /** Timestamp when snapshot was taken */
14
+ timestamp: number;
15
+ /** Description of what caused this snapshot (e.g., 'reflow', 'manual') */
16
+ source: string;
17
+ /** Optional waypoint state (CORAL-REQ-015) */
18
+ waypoints?: Map<string, EdgeWaypoint[]>;
19
+ }
20
+ /** Result returned from undo/redo operations */
21
+ export interface UndoRedoResult {
22
+ positions: Map<string, XYPosition>;
23
+ waypoints?: Map<string, EdgeWaypoint[]>;
24
+ }
25
+ /** Options for the useLayoutHistory hook */
26
+ export interface UseLayoutHistoryOptions {
27
+ /** Maximum number of history entries to keep (default: 50) */
28
+ maxHistory?: number;
29
+ }
30
+ /** Return type of the useLayoutHistory hook */
31
+ export interface UseLayoutHistoryResult {
32
+ /** Save current node positions (and optional waypoints) to history */
33
+ saveSnapshot: (nodes: Node[], source?: string, waypoints?: Map<string, EdgeWaypoint[]>) => void;
34
+ /** Undo to previous position state, returns positions + waypoints or null if can't undo */
35
+ undo: () => UndoRedoResult | null;
36
+ /** Redo to next position state, returns positions + waypoints or null if can't redo */
37
+ redo: () => UndoRedoResult | null;
38
+ /** Whether undo is available */
39
+ canUndo: boolean;
40
+ /** Whether redo is available */
41
+ canRedo: boolean;
42
+ /** Number of available undo steps */
43
+ undoCount: number;
44
+ /** Number of available redo steps */
45
+ redoCount: number;
46
+ /** Clear all history */
47
+ clearHistory: () => void;
48
+ }
49
+ /**
50
+ * Hook for managing layout history with undo/redo capability
51
+ */
52
+ export declare function useLayoutHistory(options?: UseLayoutHistoryOptions): UseLayoutHistoryResult;
53
+ //# sourceMappingURL=useLayoutHistory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLayoutHistory.d.ts","sourceRoot":"","sources":["../../src/layout/useLayoutHistory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;CACzC;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;CACzC;AAED,4CAA4C;AAC5C,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+CAA+C;AAC/C,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,YAAY,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,KAAK,IAAI,CAAC;IAChG,2FAA2F;IAC3F,IAAI,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IAClC,uFAAuF;IACvF,IAAI,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IAClC,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CA4IxB"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * useLayoutHistory - Hook for managing undo/redo stack for node positions
3
+ *
4
+ * This hook tracks node position changes and allows users to undo/redo
5
+ * layout operations, particularly useful after automatic reflow.
6
+ */
7
+ import { useState, useCallback, useRef } from 'react';
8
+ /**
9
+ * Hook for managing layout history with undo/redo capability
10
+ */
11
+ export function useLayoutHistory(options = {}) {
12
+ const { maxHistory = 50 } = options;
13
+ // History stack - array of position snapshots
14
+ const [history, setHistory] = useState([]);
15
+ // Current position in history (-1 means at most recent)
16
+ const [historyIndex, setHistoryIndex] = useState(-1);
17
+ // Track if we're in the middle of an undo/redo to avoid saving
18
+ const isUndoingRef = useRef(false);
19
+ /**
20
+ * Save current node positions to history
21
+ */
22
+ const saveSnapshot = useCallback((nodes, source = 'manual', waypoints) => {
23
+ // Don't save during undo/redo operations
24
+ if (isUndoingRef.current)
25
+ return;
26
+ const positions = new Map();
27
+ for (const node of nodes) {
28
+ positions.set(node.id, { ...node.position });
29
+ }
30
+ const snapshot = {
31
+ positions,
32
+ timestamp: Date.now(),
33
+ source,
34
+ waypoints: waypoints ? new Map(waypoints) : undefined,
35
+ };
36
+ setHistory((prev) => {
37
+ // If we're not at the end of history, truncate future entries
38
+ let newHistory = historyIndex >= 0 ? prev.slice(0, historyIndex + 1) : prev;
39
+ // Add new snapshot
40
+ newHistory = [...newHistory, snapshot];
41
+ // Trim to max history
42
+ if (newHistory.length > maxHistory) {
43
+ newHistory = newHistory.slice(newHistory.length - maxHistory);
44
+ }
45
+ return newHistory;
46
+ });
47
+ // Reset history index to point to end
48
+ setHistoryIndex(-1);
49
+ }, [historyIndex, maxHistory]);
50
+ /**
51
+ * Undo to previous position state
52
+ */
53
+ const undo = useCallback(() => {
54
+ // Calculate actual index (handle -1 as end of array)
55
+ const currentIndex = historyIndex === -1 ? history.length - 1 : historyIndex;
56
+ // Need at least 2 entries to undo (current + previous)
57
+ if (currentIndex <= 0) {
58
+ return null;
59
+ }
60
+ const targetIndex = currentIndex - 1;
61
+ const snapshot = history[targetIndex];
62
+ if (!snapshot) {
63
+ return null;
64
+ }
65
+ isUndoingRef.current = true;
66
+ setHistoryIndex(targetIndex);
67
+ // Allow next saveSnapshot to work
68
+ setTimeout(() => {
69
+ isUndoingRef.current = false;
70
+ }, 0);
71
+ return {
72
+ positions: snapshot.positions,
73
+ waypoints: snapshot.waypoints,
74
+ };
75
+ }, [history, historyIndex]);
76
+ /**
77
+ * Redo to next position state
78
+ */
79
+ const redo = useCallback(() => {
80
+ // Can only redo if we've undone something
81
+ if (historyIndex === -1 || historyIndex >= history.length - 1) {
82
+ return null;
83
+ }
84
+ const targetIndex = historyIndex + 1;
85
+ const snapshot = history[targetIndex];
86
+ if (!snapshot) {
87
+ return null;
88
+ }
89
+ isUndoingRef.current = true;
90
+ setHistoryIndex(targetIndex);
91
+ // Allow next saveSnapshot to work
92
+ setTimeout(() => {
93
+ isUndoingRef.current = false;
94
+ }, 0);
95
+ return {
96
+ positions: snapshot.positions,
97
+ waypoints: snapshot.waypoints,
98
+ };
99
+ }, [history, historyIndex]);
100
+ /**
101
+ * Clear all history
102
+ */
103
+ const clearHistory = useCallback(() => {
104
+ setHistory([]);
105
+ setHistoryIndex(-1);
106
+ }, []);
107
+ // Calculate derived state
108
+ const currentIndex = historyIndex === -1 ? history.length - 1 : historyIndex;
109
+ const canUndo = currentIndex > 0;
110
+ const canRedo = historyIndex !== -1 && historyIndex < history.length - 1;
111
+ const undoCount = currentIndex;
112
+ const redoCount = historyIndex === -1 ? 0 : history.length - 1 - historyIndex;
113
+ return {
114
+ saveSnapshot,
115
+ undo,
116
+ canUndo,
117
+ redo,
118
+ canRedo,
119
+ undoCount,
120
+ redoCount,
121
+ clearHistory,
122
+ };
123
+ }
124
+ //# sourceMappingURL=useLayoutHistory.js.map