@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.
- package/LICENSE +4 -0
- package/README.md +11 -0
- package/dist/compatibility/CompatibilityEdge.d.ts +106 -0
- package/dist/compatibility/CompatibilityEdge.d.ts.map +1 -0
- package/dist/compatibility/CompatibilityEdge.js +233 -0
- package/dist/compatibility/CompatibilityEdge.js.map +1 -0
- package/dist/compatibility/IncompatibilityTooltip.d.ts +44 -0
- package/dist/compatibility/IncompatibilityTooltip.d.ts.map +1 -0
- package/dist/compatibility/IncompatibilityTooltip.js +145 -0
- package/dist/compatibility/IncompatibilityTooltip.js.map +1 -0
- package/dist/compatibility/index.d.ts +10 -0
- package/dist/compatibility/index.d.ts.map +1 -0
- package/dist/compatibility/index.js +10 -0
- package/dist/compatibility/index.js.map +1 -0
- package/dist/compatibility/useEdgeCompatibility.d.ts +82 -0
- package/dist/compatibility/useEdgeCompatibility.d.ts.map +1 -0
- package/dist/compatibility/useEdgeCompatibility.js +170 -0
- package/dist/compatibility/useEdgeCompatibility.js.map +1 -0
- package/dist/compatibility/validateConnection.d.ts +56 -0
- package/dist/compatibility/validateConnection.d.ts.map +1 -0
- package/dist/compatibility/validateConnection.js +382 -0
- package/dist/compatibility/validateConnection.js.map +1 -0
- package/dist/components/EditContext.d.ts +45 -0
- package/dist/components/EditContext.d.ts.map +1 -0
- package/dist/components/EditContext.js +41 -0
- package/dist/components/EditContext.js.map +1 -0
- package/dist/components/ShapeRenderer.d.ts +35 -0
- package/dist/components/ShapeRenderer.d.ts.map +1 -0
- package/dist/components/ShapeRenderer.js +77 -0
- package/dist/components/ShapeRenderer.js.map +1 -0
- package/dist/components/SymbolNode.d.ts +53 -0
- package/dist/components/SymbolNode.d.ts.map +1 -0
- package/dist/components/SymbolNode.js +333 -0
- package/dist/components/SymbolNode.js.map +1 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +10 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/nodes.d.ts +43 -0
- package/dist/components/nodes.d.ts.map +1 -0
- package/dist/components/nodes.js +111 -0
- package/dist/components/nodes.js.map +1 -0
- package/dist/editor/CoralEditor.d.ts +29 -0
- package/dist/editor/CoralEditor.d.ts.map +1 -0
- package/dist/editor/CoralEditor.js +112 -0
- package/dist/editor/CoralEditor.js.map +1 -0
- package/dist/editor/FileControls.d.ts +34 -0
- package/dist/editor/FileControls.d.ts.map +1 -0
- package/dist/editor/FileControls.js +139 -0
- package/dist/editor/FileControls.js.map +1 -0
- package/dist/editor/LayoutControls.d.ts +41 -0
- package/dist/editor/LayoutControls.d.ts.map +1 -0
- package/dist/editor/LayoutControls.js +124 -0
- package/dist/editor/LayoutControls.js.map +1 -0
- package/dist/editor/converter.d.ts +37 -0
- package/dist/editor/converter.d.ts.map +1 -0
- package/dist/editor/converter.js +256 -0
- package/dist/editor/converter.js.map +1 -0
- package/dist/editor/index.d.ts +8 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +8 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/file/deserialize.d.ts +15 -0
- package/dist/file/deserialize.d.ts.map +1 -0
- package/dist/file/deserialize.js +145 -0
- package/dist/file/deserialize.js.map +1 -0
- package/dist/file/index.d.ts +15 -0
- package/dist/file/index.d.ts.map +1 -0
- package/dist/file/index.js +20 -0
- package/dist/file/index.js.map +1 -0
- package/dist/file/migrate.d.ts +29 -0
- package/dist/file/migrate.d.ts.map +1 -0
- package/dist/file/migrate.js +103 -0
- package/dist/file/migrate.js.map +1 -0
- package/dist/file/schema.d.ts +385 -0
- package/dist/file/schema.d.ts.map +1 -0
- package/dist/file/schema.js +180 -0
- package/dist/file/schema.js.map +1 -0
- package/dist/file/serialize.d.ts +30 -0
- package/dist/file/serialize.d.ts.map +1 -0
- package/dist/file/serialize.js +218 -0
- package/dist/file/serialize.js.map +1 -0
- package/dist/file/validate.d.ts +21 -0
- package/dist/file/validate.d.ts.map +1 -0
- package/dist/file/validate.js +165 -0
- package/dist/file/validate.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/elk.d.ts +77 -0
- package/dist/layout/elk.d.ts.map +1 -0
- package/dist/layout/elk.js +379 -0
- package/dist/layout/elk.js.map +1 -0
- package/dist/layout/index.d.ts +12 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +12 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/nodeSizing.d.ts +65 -0
- package/dist/layout/nodeSizing.d.ts.map +1 -0
- package/dist/layout/nodeSizing.js +319 -0
- package/dist/layout/nodeSizing.js.map +1 -0
- package/dist/layout/positionStability.d.ts +73 -0
- package/dist/layout/positionStability.d.ts.map +1 -0
- package/dist/layout/positionStability.js +199 -0
- package/dist/layout/positionStability.js.map +1 -0
- package/dist/layout/useAxisConstrain.d.ts +20 -0
- package/dist/layout/useAxisConstrain.d.ts.map +1 -0
- package/dist/layout/useAxisConstrain.js +106 -0
- package/dist/layout/useAxisConstrain.js.map +1 -0
- package/dist/layout/useDiagramState.d.ts +92 -0
- package/dist/layout/useDiagramState.d.ts.map +1 -0
- package/dist/layout/useDiagramState.js +256 -0
- package/dist/layout/useDiagramState.js.map +1 -0
- package/dist/layout/useLayoutHistory.d.ts +53 -0
- package/dist/layout/useLayoutHistory.d.ts.map +1 -0
- package/dist/layout/useLayoutHistory.js +124 -0
- package/dist/layout/useLayoutHistory.js.map +1 -0
- package/dist/properties/NodePropertiesPanel.d.ts +47 -0
- package/dist/properties/NodePropertiesPanel.d.ts.map +1 -0
- package/dist/properties/NodePropertiesPanel.js +199 -0
- package/dist/properties/NodePropertiesPanel.js.map +1 -0
- package/dist/properties/index.d.ts +10 -0
- package/dist/properties/index.d.ts.map +1 -0
- package/dist/properties/index.js +10 -0
- package/dist/properties/index.js.map +1 -0
- package/dist/properties/propertyUtils.d.ts +60 -0
- package/dist/properties/propertyUtils.d.ts.map +1 -0
- package/dist/properties/propertyUtils.js +89 -0
- package/dist/properties/propertyUtils.js.map +1 -0
- package/dist/settings/FontSettingsForm.d.ts +28 -0
- package/dist/settings/FontSettingsForm.d.ts.map +1 -0
- package/dist/settings/FontSettingsForm.js +129 -0
- package/dist/settings/FontSettingsForm.js.map +1 -0
- package/dist/settings/LayoutSettingsForm.d.ts +37 -0
- package/dist/settings/LayoutSettingsForm.d.ts.map +1 -0
- package/dist/settings/LayoutSettingsForm.js +165 -0
- package/dist/settings/LayoutSettingsForm.js.map +1 -0
- package/dist/settings/SettingsPanel.d.ts +55 -0
- package/dist/settings/SettingsPanel.d.ts.map +1 -0
- package/dist/settings/SettingsPanel.js +84 -0
- package/dist/settings/SettingsPanel.js.map +1 -0
- package/dist/settings/index.d.ts +13 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +13 -0
- package/dist/settings/index.js.map +1 -0
- package/dist/settings/useSettings.d.ts +51 -0
- package/dist/settings/useSettings.d.ts.map +1 -0
- package/dist/settings/useSettings.js +154 -0
- package/dist/settings/useSettings.js.map +1 -0
- package/dist/text-editor/SplitEditor.d.ts +72 -0
- package/dist/text-editor/SplitEditor.d.ts.map +1 -0
- package/dist/text-editor/SplitEditor.js +172 -0
- package/dist/text-editor/SplitEditor.js.map +1 -0
- package/dist/text-editor/TextEditor.d.ts +53 -0
- package/dist/text-editor/TextEditor.d.ts.map +1 -0
- package/dist/text-editor/TextEditor.js +181 -0
- package/dist/text-editor/TextEditor.js.map +1 -0
- package/dist/text-editor/hooks/useBidirectionalSync.d.ts +106 -0
- package/dist/text-editor/hooks/useBidirectionalSync.d.ts.map +1 -0
- package/dist/text-editor/hooks/useBidirectionalSync.js +151 -0
- package/dist/text-editor/hooks/useBidirectionalSync.js.map +1 -0
- package/dist/text-editor/index.d.ts +9 -0
- package/dist/text-editor/index.d.ts.map +1 -0
- package/dist/text-editor/index.js +11 -0
- package/dist/text-editor/index.js.map +1 -0
- package/dist/types.d.ts +242 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/waypoints/index.d.ts +8 -0
- package/dist/waypoints/index.d.ts.map +1 -0
- package/dist/waypoints/index.js +7 -0
- package/dist/waypoints/index.js.map +1 -0
- package/dist/waypoints/operations.d.ts +29 -0
- package/dist/waypoints/operations.d.ts.map +1 -0
- package/dist/waypoints/operations.js +85 -0
- package/dist/waypoints/operations.js.map +1 -0
- package/dist/waypoints/router.d.ts +23 -0
- package/dist/waypoints/router.d.ts.map +1 -0
- package/dist/waypoints/router.js +109 -0
- package/dist/waypoints/router.js.map +1 -0
- package/dist/waypoints/types.d.ts +15 -0
- package/dist/waypoints/types.d.ts.map +1 -0
- package/dist/waypoints/types.js +5 -0
- package/dist/waypoints/types.js.map +1 -0
- package/dist/waypoints/useWaypointManager.d.ts +38 -0
- package/dist/waypoints/useWaypointManager.d.ts.map +1 -0
- package/dist/waypoints/useWaypointManager.js +106 -0
- package/dist/waypoints/useWaypointManager.js.map +1 -0
- 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
|