@mhamz.01/easyflow-whiteboard 2.79.0 → 2.80.0

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 (30) hide show
  1. package/dist/components/node/custom-node-overlay-fix.d.ts +3 -0
  2. package/dist/components/node/custom-node-overlay-fix.d.ts.map +1 -0
  3. package/dist/components/node/custom-node-overlay-fix.js +192 -0
  4. package/dist/components/node/custom-node-overlay-layer.d.ts.map +1 -1
  5. package/dist/components/node/custom-node-overlay-layer.js +11 -11
  6. package/dist/components/node/hooks/useCanvasReady.d.ts +7 -0
  7. package/dist/components/node/hooks/useCanvasReady.d.ts.map +1 -0
  8. package/dist/components/node/hooks/useCanvasReady.js +21 -0
  9. package/dist/components/node/hooks/useKeyboardShortcuts.d.ts +12 -0
  10. package/dist/components/node/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  11. package/dist/components/node/hooks/useKeyboardShortcuts.js +37 -0
  12. package/dist/components/node/hooks/useNodeDrag.d.ts +25 -0
  13. package/dist/components/node/hooks/useNodeDrag.d.ts.map +1 -0
  14. package/dist/components/node/hooks/useNodeDrag.js +119 -0
  15. package/dist/components/node/hooks/useNodeSync.d.ts +13 -0
  16. package/dist/components/node/hooks/useNodeSync.d.ts.map +1 -0
  17. package/dist/components/node/hooks/useNodeSync.js +56 -0
  18. package/dist/components/node/hooks/useSelectionBox.d.ts +20 -0
  19. package/dist/components/node/hooks/useSelectionBox.d.ts.map +1 -0
  20. package/dist/components/node/hooks/useSelectionBox.js +30 -0
  21. package/dist/components/node/hooks/useWheelZoom.d.ts +12 -0
  22. package/dist/components/node/hooks/useWheelZoom.d.ts.map +1 -0
  23. package/dist/components/node/hooks/useWheelZoom.js +56 -0
  24. package/dist/components/node/types/overlay-types.d.ts +67 -0
  25. package/dist/components/node/types/overlay-types.d.ts.map +1 -0
  26. package/dist/components/node/types/overlay-types.js +1 -0
  27. package/dist/components/whiteboard/whiteboard-test.d.ts +1 -1
  28. package/dist/components/whiteboard/whiteboard-test.d.ts.map +1 -1
  29. package/dist/components/whiteboard/whiteboard-test.js +2 -2
  30. package/package.json +1 -1
@@ -0,0 +1,3 @@
1
+ import { CanvasOverlayLayerProps } from "./types/overlay-types";
2
+ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, onDocumentsUpdate, canvasZoom, canvasViewport, selectionBox, selectedCanvasObjects, fabricCanvas, }: CanvasOverlayLayerProps): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=custom-node-overlay-fix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-node-overlay-fix.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-fix.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAkB,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAQhF,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,UAAc,EACd,cAA+B,EAC/B,YAAmB,EACnB,qBAA0B,EAC1B,YAAY,GACb,EAAE,uBAAuB,2CAmSzB"}
@@ -0,0 +1,192 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useRef, useCallback } from "react";
4
+ import TaskNode from "./custom-node";
5
+ import DocumentNode from "./document-node";
6
+ import { useWheelZoom } from "./hooks/useWheelZoom";
7
+ import { useNodeDrag } from "./hooks/useNodeDrag";
8
+ import { useSelectionBox } from "./hooks/useSelectionBox";
9
+ import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts";
10
+ import { useCanvasReady } from "./hooks/useCanvasReady";
11
+ import { useNodeSync } from "./hooks/useNodeSync";
12
+ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, onDocumentsUpdate, canvasZoom = 1, canvasViewport = { x: 0, y: 0 }, selectionBox = null, selectedCanvasObjects = [], fabricCanvas, }) {
13
+ // ─── State Management ──────────────────────────────────────────────────
14
+ const [localTasks, setLocalTasks] = useState(tasks);
15
+ const [localDocuments, setLocalDocuments] = useState(documents);
16
+ const [selectedIds, setSelectedIds] = useState(new Set());
17
+ const [dragging, setDragging] = useState(null);
18
+ const overlayRef = useRef(null);
19
+ const dragStateRef = useRef(null);
20
+ // ─── Hooks ────────────────────────────────────────────────────────────
21
+ const canvasReady = useCanvasReady({ fabricCanvas });
22
+ const { handleOverlayWheel } = useWheelZoom({
23
+ overlayRef,
24
+ fabricCanvas,
25
+ canvasZoom,
26
+ canvasReady,
27
+ });
28
+ const { dragStateRef: dragRef, rafIdRef, getPointerEvent, getViewportTransform, handleDragStart: handleDragStartHook, handleDragMove: handleDragMoveHook, handleDragEnd: handleDragEndHook, } = useNodeDrag({
29
+ selectedIds,
30
+ fabricCanvas,
31
+ selectedCanvasObjects,
32
+ onDragStateChange: setDragging,
33
+ });
34
+ dragStateRef.current = dragRef.current;
35
+ useSelectionBox({
36
+ selectionBox,
37
+ localTasks,
38
+ localDocuments,
39
+ canvasZoom,
40
+ canvasViewport,
41
+ onSelectionChange: setSelectedIds,
42
+ });
43
+ useNodeSync({
44
+ tasks: localTasks,
45
+ documents: localDocuments,
46
+ selectedIds,
47
+ fabricCanvas,
48
+ onTasksUpdate,
49
+ onDocumentsUpdate,
50
+ });
51
+ useKeyboardShortcuts({
52
+ selectedIds,
53
+ localTasks,
54
+ localDocuments,
55
+ onSelectAll: () => setSelectedIds(new Set([...localTasks.map((t) => t.id), ...localDocuments.map((d) => d.id)])),
56
+ onClearSelection: () => setSelectedIds(new Set()),
57
+ onDeleteSelected: (tasks, docs) => {
58
+ setLocalTasks(tasks);
59
+ setLocalDocuments(docs);
60
+ setSelectedIds(new Set());
61
+ onTasksUpdate?.(tasks);
62
+ onDocumentsUpdate?.(docs);
63
+ },
64
+ });
65
+ // ─── Effects ──────────────────────────────────────────────────────────
66
+ useEffect(() => {
67
+ setLocalTasks(tasks);
68
+ }, [tasks]);
69
+ useEffect(() => {
70
+ setLocalDocuments(documents);
71
+ }, [documents]);
72
+ // ─── Handlers ─────────────────────────────────────────────────────────
73
+ const getItemPosition = useCallback((id) => {
74
+ const task = localTasks.find((t) => t.id === id);
75
+ if (task)
76
+ return { x: task.x, y: task.y };
77
+ const doc = localDocuments.find((d) => d.id === id);
78
+ if (doc)
79
+ return { x: doc.x, y: doc.y };
80
+ return undefined;
81
+ }, [localTasks, localDocuments]);
82
+ const handleDragStart = useCallback((itemId, e) => {
83
+ handleDragStartHook(itemId, getItemPosition, e);
84
+ }, [handleDragStartHook, getItemPosition]);
85
+ // ─── Drag Move Effect ─────────────────────────────────────────────────
86
+ useEffect(() => {
87
+ if (!dragging)
88
+ return;
89
+ const handleMove = (e) => {
90
+ if (!dragStateRef.current?.isDragging)
91
+ return;
92
+ if (e.cancelable)
93
+ e.preventDefault();
94
+ const pointer = getPointerEvent(e);
95
+ if (rafIdRef.current !== null)
96
+ cancelAnimationFrame(rafIdRef.current);
97
+ rafIdRef.current = requestAnimationFrame(() => {
98
+ const { itemIds, startPositions, canvasObjectsStartPos, offsetX, offsetY } = dragStateRef.current;
99
+ const canvas = fabricCanvas?.current;
100
+ if (!canvas)
101
+ return;
102
+ const { zoom: liveZoom, vpX: liveVpX, vpY: liveVpY } = getViewportTransform(canvas);
103
+ const currentWorldX = (pointer.clientX - liveVpX) / liveZoom;
104
+ const currentWorldY = (pointer.clientY - liveVpY) / liveZoom;
105
+ const deltaX = currentWorldX - offsetX;
106
+ const deltaY = currentWorldY - offsetY;
107
+ // Update HTML Nodes
108
+ setLocalTasks((prev) => prev.map((t) => itemIds.includes(t.id)
109
+ ? {
110
+ ...t,
111
+ x: (startPositions.get(t.id)?.x ?? t.x) + deltaX,
112
+ y: (startPositions.get(t.id)?.y ?? t.y) + deltaY,
113
+ }
114
+ : t));
115
+ setLocalDocuments((prev) => prev.map((d) => itemIds.includes(d.id)
116
+ ? {
117
+ ...d,
118
+ x: (startPositions.get(d.id)?.x ?? d.x) + deltaX,
119
+ y: (startPositions.get(d.id)?.y ?? d.y) + deltaY,
120
+ }
121
+ : d));
122
+ // Update Fabric Objects
123
+ canvasObjectsStartPos.forEach((startPos, obj) => {
124
+ obj.set({
125
+ left: startPos.left + deltaX,
126
+ top: startPos.top + deltaY,
127
+ });
128
+ obj.setCoords();
129
+ });
130
+ canvas.requestRenderAll();
131
+ });
132
+ };
133
+ const handleEnd = () => {
134
+ handleDragEndHook();
135
+ onTasksUpdate?.(localTasks);
136
+ onDocumentsUpdate?.(localDocuments);
137
+ };
138
+ window.addEventListener("mousemove", handleMove, { passive: false });
139
+ window.addEventListener("mouseup", handleEnd);
140
+ window.addEventListener("touchmove", handleMove, { passive: false });
141
+ window.addEventListener("touchend", handleEnd);
142
+ window.addEventListener("touchcancel", handleEnd);
143
+ return () => {
144
+ window.removeEventListener("mousemove", handleMove);
145
+ window.removeEventListener("mouseup", handleEnd);
146
+ window.removeEventListener("touchmove", handleMove);
147
+ window.removeEventListener("touchend", handleEnd);
148
+ window.removeEventListener("touchcancel", handleEnd);
149
+ };
150
+ }, [dragging, localTasks, localDocuments, fabricCanvas, getViewportTransform, getPointerEvent, handleDragEndHook, onTasksUpdate, onDocumentsUpdate]);
151
+ // ─── Selection Handlers ────────────────────────────────────────────────
152
+ const handleSelect = useCallback((id, e) => {
153
+ if (e?.shiftKey || e?.ctrlKey || e?.metaKey) {
154
+ setSelectedIds((prev) => {
155
+ const next = new Set(prev);
156
+ next.has(id) ? next.delete(id) : next.add(id);
157
+ return next;
158
+ });
159
+ }
160
+ else {
161
+ setSelectedIds(new Set([id]));
162
+ }
163
+ }, []);
164
+ const handleStatusChange = useCallback((taskId, newStatus) => {
165
+ const updated = localTasks.map((t) => t.id === taskId ? { ...t, status: newStatus } : t);
166
+ setLocalTasks(updated);
167
+ onTasksUpdate?.(updated);
168
+ }, [localTasks, onTasksUpdate]);
169
+ // ─── Render Helper ────────────────────────────────────────────────────
170
+ const renderItem = useCallback((id, x, y, children) => {
171
+ const screenX = x * canvasZoom;
172
+ const screenY = y * canvasZoom;
173
+ const isDragging = dragging?.itemIds.includes(id);
174
+ return (_jsx("div", { className: "pointer-events-auto absolute", style: {
175
+ left: 0,
176
+ top: 0,
177
+ transform: `translate3d(${screenX}px, ${screenY}px, 0) scale(${canvasZoom})`,
178
+ transformOrigin: "top left",
179
+ transition: "none",
180
+ willChange: "transform",
181
+ zIndex: isDragging ? 1000 : 1,
182
+ }, children: children }, id));
183
+ }, [canvasZoom, dragging]);
184
+ // ─── Render ───────────────────────────────────────────────────────────
185
+ return (_jsx("div", { ref: overlayRef, className: "absolute inset-0 pointer-events-none", style: { zIndex: 50 }, onWheel: handleOverlayWheel, onClick: (e) => {
186
+ if (e.target === e.currentTarget)
187
+ setSelectedIds(new Set());
188
+ }, children: _jsxs("div", { className: "absolute top-0 left-0 pointer-events-none", style: {
189
+ transform: `translate(${canvasViewport.x}px, ${canvasViewport.y}px)`,
190
+ transformOrigin: "top left",
191
+ }, children: [localTasks.map((task) => renderItem(task.id, task.x, task.y, _jsx(TaskNode, { ...task, isSelected: selectedIds.has(task.id), onSelect: handleSelect, onDragStart: handleDragStart, onStatusChange: handleStatusChange, zoom: 1 }))), localDocuments.map((doc) => renderItem(doc.id, doc.x, doc.y, _jsx(DocumentNode, { ...doc, isSelected: selectedIds.has(doc.id), onSelect: handleSelect, onDragStart: handleDragStart })))] }) }));
192
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAS9C,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,UAAU,uBAAuB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAaD,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,UAAc,EACd,cAA+B,EAC/B,YAAmB,EACnB,qBAA0B,EAC1B,YAAY,GACb,EAAE,uBAAuB,2CA8kBzB"}
1
+ {"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAS9C,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,UAAU,uBAAuB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAaD,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,UAAc,EACd,cAA+B,EAC/B,YAAmB,EACnB,qBAA0B,EAC1B,YAAY,GACb,EAAE,uBAAuB,2CAglBzB"}
@@ -314,14 +314,14 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
314
314
  const currentWorldY = (pointer.clientY - liveVpY) / liveZoom;
315
315
  // 4. Calculate where the "Anchor" node should be in World Units
316
316
  // (Current Mouse World - Initial World Offset from Start)
317
- const newWorldX = currentWorldX - offsetX;
318
- const newWorldY = currentWorldY - offsetY;
317
+ const deltaX = currentWorldX - offsetX;
318
+ const deltaY = currentWorldY - offsetY;
319
319
  if (itemIds.length > 0) {
320
320
  console.groupCollapsed(`Dragging Node: ${itemIds[0]}`);
321
321
  console.log("Screen Mouse:", { x: pointer.clientX, y: pointer.clientY });
322
322
  console.log("World Mouse:", { x: currentWorldX.toFixed(2), y: currentWorldY.toFixed(2) });
323
323
  console.log("Canvas Zoom:", liveZoom.toFixed(2));
324
- console.log("New Node Pos:", { x: newWorldX.toFixed(2), y: newWorldY.toFixed(2) });
324
+ console.log("New Node Pos:", { x: deltaX.toFixed(2), y: deltaY.toFixed(2) });
325
325
  console.groupEnd();
326
326
  }
327
327
  // 5. Calculate the Movement Delta in World Units
@@ -330,24 +330,24 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
330
330
  const firstStart = startPositions.get(firstId);
331
331
  if (!firstStart)
332
332
  return;
333
- // const deltaX = newWorldX - firstStart.x;
334
- // const deltaY = newWorldY - firstStart.y;
333
+ // The real problem of task jumps
334
+ // const deltaX = deltaX - firstStart.x;
335
335
  // 6. Update HTML Nodes (Batching these into one state update)
336
336
  setLocalTasks((prev) => prev.map((t) => itemIds.includes(t.id) ? {
337
337
  ...t,
338
- x: (startPositions.get(t.id)?.x ?? t.x) + newWorldX,
339
- y: (startPositions.get(t.id)?.y ?? t.y) + newWorldY,
338
+ x: (startPositions.get(t.id)?.x ?? t.x) + deltaX,
339
+ y: (startPositions.get(t.id)?.y ?? t.y) + deltaY,
340
340
  } : t));
341
341
  setLocalDocuments((prev) => prev.map((d) => itemIds.includes(d.id) ? {
342
342
  ...d,
343
- x: (startPositions.get(d.id)?.x ?? d.x) + newWorldX,
344
- y: (startPositions.get(d.id)?.y ?? d.y) + newWorldY,
343
+ x: (startPositions.get(d.id)?.x ?? d.x) + deltaX,
344
+ y: (startPositions.get(d.id)?.y ?? d.y) + deltaY,
345
345
  } : d));
346
346
  // 7. Sync Fabric Objects (Imperative update for performance)
347
347
  canvasObjectsStartPos.forEach((startPos, obj) => {
348
348
  obj.set({
349
- left: startPos.left + newWorldX,
350
- top: startPos.top + newWorldY,
349
+ left: startPos.left + deltaX,
350
+ top: startPos.top + deltaY,
351
351
  });
352
352
  obj.setCoords(); // Required for selection/intersection accuracy
353
353
  });
@@ -0,0 +1,7 @@
1
+ import { Canvas } from "fabric";
2
+ interface UseCanvasReadyProps {
3
+ fabricCanvas?: React.RefObject<Canvas | null>;
4
+ }
5
+ export declare function useCanvasReady({ fabricCanvas }: UseCanvasReadyProps): boolean;
6
+ export {};
7
+ //# sourceMappingURL=useCanvasReady.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasReady.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useCanvasReady.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,mBAAmB;IAC3B,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC/C;AAED,wBAAgB,cAAc,CAAC,EAAE,YAAY,EAAE,EAAE,mBAAmB,WAuBnE"}
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from "react";
2
+ export function useCanvasReady({ fabricCanvas }) {
3
+ const [canvasReady, setCanvasReady] = useState(false);
4
+ useEffect(() => {
5
+ if (canvasReady)
6
+ return;
7
+ if (fabricCanvas?.current) {
8
+ setCanvasReady(true);
9
+ return;
10
+ }
11
+ // Poll every 50ms until canvas is ready
12
+ const interval = setInterval(() => {
13
+ if (fabricCanvas?.current) {
14
+ setCanvasReady(true);
15
+ clearInterval(interval);
16
+ }
17
+ }, 50);
18
+ return () => clearInterval(interval);
19
+ }, [fabricCanvas, canvasReady]);
20
+ return canvasReady;
21
+ }
@@ -0,0 +1,12 @@
1
+ import { Task, Document } from "../types/overlay-types";
2
+ interface UseKeyboardShortcutsProps {
3
+ selectedIds: Set<string>;
4
+ localTasks: Task[];
5
+ localDocuments: Document[];
6
+ onSelectAll: () => void;
7
+ onClearSelection: () => void;
8
+ onDeleteSelected: (tasks: Task[], docs: Document[]) => void;
9
+ }
10
+ export declare function useKeyboardShortcuts({ selectedIds, localTasks, localDocuments, onSelectAll, onClearSelection, onDeleteSelected, }: UseKeyboardShortcutsProps): void;
11
+ export {};
12
+ //# sourceMappingURL=useKeyboardShortcuts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useKeyboardShortcuts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAExD,UAAU,yBAAyB;IACjC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,UAAU,EAAE,IAAI,EAAE,CAAC;IACnB,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,gBAAgB,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CAC7D;AAED,wBAAgB,oBAAoB,CAAC,EACnC,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,EAAE,yBAAyB,QA2C3B"}
@@ -0,0 +1,37 @@
1
+ import { useEffect } from "react";
2
+ export function useKeyboardShortcuts({ selectedIds, localTasks, localDocuments, onSelectAll, onClearSelection, onDeleteSelected, }) {
3
+ useEffect(() => {
4
+ const handleKeyDown = (e) => {
5
+ // Don't trigger if typing in input
6
+ if (e.target instanceof HTMLInputElement ||
7
+ e.target instanceof HTMLTextAreaElement) {
8
+ return;
9
+ }
10
+ // Select All (Ctrl+A / Cmd+A)
11
+ if ((e.ctrlKey || e.metaKey) && e.key === "a") {
12
+ e.preventDefault();
13
+ onSelectAll();
14
+ }
15
+ // Clear selection (Escape)
16
+ if (e.key === "Escape") {
17
+ onClearSelection();
18
+ }
19
+ // Delete selected nodes (Delete / Backspace)
20
+ if ((e.key === "Delete" || e.key === "Backspace") && selectedIds.size > 0) {
21
+ e.preventDefault();
22
+ const updatedTasks = localTasks.filter((t) => !selectedIds.has(t.id));
23
+ const updatedDocs = localDocuments.filter((d) => !selectedIds.has(d.id));
24
+ onDeleteSelected(updatedTasks, updatedDocs);
25
+ }
26
+ };
27
+ window.addEventListener("keydown", handleKeyDown);
28
+ return () => window.removeEventListener("keydown", handleKeyDown);
29
+ }, [
30
+ selectedIds,
31
+ localTasks,
32
+ localDocuments,
33
+ onSelectAll,
34
+ onClearSelection,
35
+ onDeleteSelected,
36
+ ]);
37
+ }
@@ -0,0 +1,25 @@
1
+ import { FabricObject, Canvas } from "fabric";
2
+ import { DragState, PointerCoords, ViewportTransform } from "../types/overlay-types";
3
+ import { Task, Document } from "../types/overlay-types";
4
+ interface UseNodeDragProps {
5
+ selectedIds: Set<string>;
6
+ fabricCanvas?: React.RefObject<Canvas | null>;
7
+ selectedCanvasObjects: FabricObject[];
8
+ onDragStateChange: (dragging: {
9
+ itemIds: string[];
10
+ } | null) => void;
11
+ }
12
+ export declare function useNodeDrag({ selectedIds, fabricCanvas, selectedCanvasObjects, onDragStateChange, }: UseNodeDragProps): {
13
+ dragStateRef: import("react").RefObject<DragState>;
14
+ rafIdRef: import("react").RefObject<number | null>;
15
+ getPointerEvent: (e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent) => PointerCoords;
16
+ getViewportTransform: (canvas: Canvas) => ViewportTransform;
17
+ handleDragStart: (itemId: string, getItemPosition: (id: string) => {
18
+ x: number;
19
+ y: number;
20
+ } | undefined, e: React.MouseEvent | React.TouchEvent) => void;
21
+ handleDragMove: (pointer: PointerCoords, onTasksUpdate: (tasks: Task[], docs: Document[]) => void) => void;
22
+ handleDragEnd: () => void;
23
+ };
24
+ export {};
25
+ //# sourceMappingURL=useNodeDrag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNodeDrag.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeDrag.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAExD,UAAU,gBAAgB;IACxB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,qBAAqB,EAAE,YAAY,EAAE,CAAC;IACtC,iBAAiB,EAAE,CAAC,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAC;CACrE;AAED,wBAAgB,WAAW,CAAC,EAC1B,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,GAClB,EAAE,gBAAgB;;;yBAaZ,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,GAAG,UAAU,GAAG,UAAU,KAC/D,aAAa;mCAasB,MAAM,KAAG,iBAAiB;8BAWpD,MAAM,mBACG,CAAC,EAAE,EAAE,MAAM,KAAK;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,KAClE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU;8BAqD7B,aAAa,iBACP,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,IAAI;;EAsD7D"}
@@ -0,0 +1,119 @@
1
+ import { useRef, useCallback } from "react";
2
+ export function useNodeDrag({ selectedIds, fabricCanvas, selectedCanvasObjects, onDragStateChange, }) {
3
+ const dragStateRef = useRef({
4
+ isDragging: false,
5
+ itemIds: [],
6
+ startPositions: new Map(),
7
+ canvasObjectsStartPos: new Map(),
8
+ offsetX: 0,
9
+ offsetY: 0,
10
+ });
11
+ const rafIdRef = useRef(null);
12
+ const getPointerEvent = (e) => {
13
+ if ("touches" in e && e.touches.length > 0) {
14
+ return {
15
+ clientX: e.touches[0].clientX,
16
+ clientY: e.touches[0].clientY,
17
+ };
18
+ }
19
+ return {
20
+ clientX: e.clientX,
21
+ clientY: e.clientY,
22
+ };
23
+ };
24
+ const getViewportTransform = (canvas) => {
25
+ const vpt = canvas.viewportTransform || [1, 0, 0, 1, 0, 0];
26
+ return {
27
+ zoom: vpt[0],
28
+ vpX: vpt[4],
29
+ vpY: vpt[5],
30
+ };
31
+ };
32
+ const handleDragStart = useCallback((itemId, getItemPosition, e) => {
33
+ const canvas = fabricCanvas?.current;
34
+ if (!canvas)
35
+ return;
36
+ if (e.cancelable)
37
+ e.preventDefault();
38
+ const pointer = getPointerEvent(e);
39
+ let itemsToDrag;
40
+ if (selectedIds.has(itemId)) {
41
+ itemsToDrag = Array.from(selectedIds);
42
+ }
43
+ else {
44
+ itemsToDrag = [itemId];
45
+ }
46
+ const { zoom: liveZoom, vpX: liveVpX, vpY: liveVpY } = getViewportTransform(canvas);
47
+ const clickWorldX = (pointer.clientX - liveVpX) / liveZoom;
48
+ const clickWorldY = (pointer.clientY - liveVpY) / liveZoom;
49
+ const clickedPos = getItemPosition(itemId);
50
+ if (!clickedPos)
51
+ return;
52
+ const startPositions = new Map();
53
+ itemsToDrag.forEach((id) => {
54
+ const pos = getItemPosition(id);
55
+ if (pos)
56
+ startPositions.set(id, pos);
57
+ });
58
+ const canvasObjectsStartPos = new Map();
59
+ selectedCanvasObjects.forEach((obj) => {
60
+ canvasObjectsStartPos.set(obj, { left: obj.left || 0, top: obj.top || 0 });
61
+ });
62
+ dragStateRef.current = {
63
+ isDragging: true,
64
+ itemIds: itemsToDrag,
65
+ startPositions,
66
+ canvasObjectsStartPos,
67
+ offsetX: clickWorldX,
68
+ offsetY: clickWorldY,
69
+ };
70
+ onDragStateChange({ itemIds: itemsToDrag });
71
+ document.body.style.cursor = "grabbing";
72
+ document.body.style.userSelect = "none";
73
+ document.body.style.touchAction = "none";
74
+ }, [selectedIds, fabricCanvas, selectedCanvasObjects, onDragStateChange]);
75
+ const handleDragMove = useCallback((pointer, onTasksUpdate) => {
76
+ if (!dragStateRef.current.isDragging)
77
+ return;
78
+ if (rafIdRef.current !== null)
79
+ cancelAnimationFrame(rafIdRef.current);
80
+ rafIdRef.current = requestAnimationFrame(() => {
81
+ const { itemIds, startPositions, canvasObjectsStartPos, offsetX, offsetY } = dragStateRef.current;
82
+ const canvas = fabricCanvas?.current;
83
+ if (!canvas)
84
+ return;
85
+ const { zoom: liveZoom, vpX: liveVpX, vpY: liveVpY } = getViewportTransform(canvas);
86
+ const currentWorldX = (pointer.clientX - liveVpX) / liveZoom;
87
+ const currentWorldY = (pointer.clientY - liveVpY) / liveZoom;
88
+ const deltaX = currentWorldX - offsetX;
89
+ const deltaY = currentWorldY - offsetY;
90
+ // Update Fabric objects imperatively
91
+ canvasObjectsStartPos.forEach((startPos, obj) => {
92
+ obj.set({
93
+ left: startPos.left + deltaX,
94
+ top: startPos.top + deltaY,
95
+ });
96
+ obj.setCoords();
97
+ });
98
+ canvas.requestRenderAll();
99
+ });
100
+ }, [fabricCanvas]);
101
+ const handleDragEnd = useCallback(() => {
102
+ if (rafIdRef.current !== null)
103
+ cancelAnimationFrame(rafIdRef.current);
104
+ dragStateRef.current.isDragging = false;
105
+ onDragStateChange(null);
106
+ document.body.style.cursor = "";
107
+ document.body.style.userSelect = "";
108
+ document.body.style.touchAction = "";
109
+ }, [onDragStateChange]);
110
+ return {
111
+ dragStateRef,
112
+ rafIdRef,
113
+ getPointerEvent,
114
+ getViewportTransform,
115
+ handleDragStart,
116
+ handleDragMove,
117
+ handleDragEnd,
118
+ };
119
+ }
@@ -0,0 +1,13 @@
1
+ import { Canvas } from "fabric";
2
+ import { Task, Document } from "../types/overlay-types";
3
+ interface UseNodeSyncProps {
4
+ tasks: Task[];
5
+ documents: Document[];
6
+ selectedIds: Set<string>;
7
+ fabricCanvas?: React.RefObject<Canvas | null>;
8
+ onTasksUpdate?: (tasks: Task[]) => void;
9
+ onDocumentsUpdate?: (documents: Document[]) => void;
10
+ }
11
+ export declare function useNodeSync({ tasks, documents, selectedIds, fabricCanvas, onTasksUpdate, onDocumentsUpdate, }: UseNodeSyncProps): import("react").RefObject<Set<string>>;
12
+ export {};
13
+ //# sourceMappingURL=useNodeSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNodeSync.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeSync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAExD,UAAU,gBAAgB;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,SAAS,EACT,WAAW,EACX,YAAY,EACZ,aAAa,EACb,iBAAiB,GAClB,EAAE,gBAAgB,0CAuElB"}
@@ -0,0 +1,56 @@
1
+ import { useEffect, useRef } from "react";
2
+ export function useNodeSync({ tasks, documents, selectedIds, fabricCanvas, onTasksUpdate, onDocumentsUpdate, }) {
3
+ const selectedIdsRef = useRef(selectedIds);
4
+ selectedIdsRef.current = selectedIds;
5
+ useEffect(() => {
6
+ const canvas = fabricCanvas?.current;
7
+ if (!canvas)
8
+ return;
9
+ const handleObjectMoving = (e) => {
10
+ const target = e.transform?.target || e.target;
11
+ if (!target)
12
+ return;
13
+ const deltaX = target.left - (target._prevLeft ?? target.left);
14
+ const deltaY = target.top - (target._prevTop ?? target.top);
15
+ target._prevLeft = target.left;
16
+ target._prevTop = target.top;
17
+ if (deltaX === 0 && deltaY === 0)
18
+ return;
19
+ const sel = selectedIdsRef.current;
20
+ const updatedTasks = tasks.map((t) => sel.has(t.id) ? { ...t, x: t.x + deltaX, y: t.y + deltaY } : t);
21
+ const updatedDocs = documents.map((d) => sel.has(d.id) ? { ...d, x: d.x + deltaX, y: d.y + deltaY } : d);
22
+ onTasksUpdate?.(updatedTasks);
23
+ onDocumentsUpdate?.(updatedDocs);
24
+ };
25
+ const handleMouseDown = (e) => {
26
+ const target = e.target;
27
+ if (target) {
28
+ target._prevLeft = target.left;
29
+ target._prevTop = target.top;
30
+ }
31
+ if (!target)
32
+ return;
33
+ const transformTarget = e.transform?.target;
34
+ const activeObject = canvas.getActiveObject();
35
+ const activeObjects = canvas.getActiveObjects();
36
+ const isPartOfActiveSelection = transformTarget === target ||
37
+ activeObject === target ||
38
+ activeObjects.includes(target);
39
+ if (!isPartOfActiveSelection) {
40
+ // Clear selection if clicking unselected object
41
+ }
42
+ };
43
+ const handleSelectionCleared = () => {
44
+ // Selection cleared event
45
+ };
46
+ canvas.on("object:moving", handleObjectMoving);
47
+ canvas.on("mouse:down", handleMouseDown);
48
+ canvas.on("selection:cleared", handleSelectionCleared);
49
+ return () => {
50
+ canvas.off("object:moving", handleObjectMoving);
51
+ canvas.off("mouse:down", handleMouseDown);
52
+ canvas.off("selection:cleared", handleSelectionCleared);
53
+ };
54
+ }, [fabricCanvas, tasks, documents, onTasksUpdate, onDocumentsUpdate]);
55
+ return selectedIdsRef;
56
+ }
@@ -0,0 +1,20 @@
1
+ import { Task, Document } from "../types/overlay-types";
2
+ interface UseSelectionBoxProps {
3
+ selectionBox: {
4
+ x1: number;
5
+ y1: number;
6
+ x2: number;
7
+ y2: number;
8
+ } | null;
9
+ localTasks: Task[];
10
+ localDocuments: Document[];
11
+ canvasZoom: number;
12
+ canvasViewport: {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ onSelectionChange: (selectedIds: Set<string>) => void;
17
+ }
18
+ export declare function useSelectionBox({ selectionBox, localTasks, localDocuments, canvasZoom, canvasViewport, onSelectionChange, }: UseSelectionBoxProps): void;
19
+ export {};
20
+ //# sourceMappingURL=useSelectionBox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSelectionBox.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useSelectionBox.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAExD,UAAU,oBAAoB;IAC5B,YAAY,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACxE,UAAU,EAAE,IAAI,EAAE,CAAC;IACnB,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,iBAAiB,EAAE,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;CACvD;AAED,wBAAgB,eAAe,CAAC,EAC9B,YAAY,EACZ,UAAU,EACV,cAAc,EACd,UAAU,EACV,cAAc,EACd,iBAAiB,GAClB,EAAE,oBAAoB,QAwCtB"}
@@ -0,0 +1,30 @@
1
+ import { useEffect } from "react";
2
+ export function useSelectionBox({ selectionBox, localTasks, localDocuments, canvasZoom, canvasViewport, onSelectionChange, }) {
3
+ const isItemInSelectionBox = (x, y, width, height, box) => {
4
+ const itemX1 = x * canvasZoom + canvasViewport.x;
5
+ const itemY1 = y * canvasZoom + canvasViewport.y;
6
+ const itemX2 = itemX1 + width * canvasZoom;
7
+ const itemY2 = itemY1 + height * canvasZoom;
8
+ const boxX1 = Math.min(box.x1, box.x2);
9
+ const boxY1 = Math.min(box.y1, box.y2);
10
+ const boxX2 = Math.max(box.x1, box.x2);
11
+ const boxY2 = Math.max(box.y1, box.y2);
12
+ return !(boxX2 < itemX1 || boxX1 > itemX2 || boxY2 < itemY1 || boxY1 > itemY2);
13
+ };
14
+ useEffect(() => {
15
+ if (!selectionBox)
16
+ return;
17
+ const newSelected = new Set();
18
+ for (const task of localTasks) {
19
+ if (isItemInSelectionBox(task.x, task.y, 300, 140, selectionBox)) {
20
+ newSelected.add(task.id);
21
+ }
22
+ }
23
+ for (const doc of localDocuments) {
24
+ if (isItemInSelectionBox(doc.x, doc.y, 320, 160, selectionBox)) {
25
+ newSelected.add(doc.id);
26
+ }
27
+ }
28
+ onSelectionChange(newSelected);
29
+ }, [selectionBox, localTasks, localDocuments, canvasZoom, canvasViewport]);
30
+ }
@@ -0,0 +1,12 @@
1
+ import { Canvas } from "fabric";
2
+ interface UseWheelZoomProps {
3
+ overlayRef: React.RefObject<HTMLDivElement | null>;
4
+ fabricCanvas?: React.RefObject<Canvas | null>;
5
+ canvasZoom?: number;
6
+ canvasReady?: boolean;
7
+ }
8
+ export declare function useWheelZoom({ overlayRef, fabricCanvas, canvasZoom, canvasReady, }: UseWheelZoomProps): {
9
+ handleOverlayWheel: (e: React.WheelEvent) => void;
10
+ };
11
+ export {};
12
+ //# sourceMappingURL=useWheelZoom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWheelZoom.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useWheelZoom.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,iBAAiB;IACvB,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,EAC3B,UAAU,EACV,YAAY,EACZ,UAAc,EACd,WAAmB,GACpB,EAAE,iBAAiB;4BAEa,KAAK,CAAC,UAAU;EA6DhD"}
@@ -0,0 +1,56 @@
1
+ import { useEffect } from "react";
2
+ export function useWheelZoom({ overlayRef, fabricCanvas, canvasZoom = 1, canvasReady = false, }) {
3
+ // React event handler for overlay wheel
4
+ const handleOverlayWheel = (e) => {
5
+ if (e.ctrlKey || e.metaKey || e.shiftKey) {
6
+ const canvas = fabricCanvas?.current;
7
+ if (!canvas)
8
+ return;
9
+ const nativeEvent = e.nativeEvent;
10
+ const scenePoint = canvas.getScenePoint(nativeEvent);
11
+ const rect = canvas.getElement().getBoundingClientRect();
12
+ const viewportPoint = {
13
+ x: nativeEvent.clientX - rect.left,
14
+ y: nativeEvent.clientY - rect.top,
15
+ };
16
+ canvas.fire("mouse:wheel", {
17
+ e: nativeEvent,
18
+ scenePoint,
19
+ viewportPoint,
20
+ });
21
+ e.preventDefault();
22
+ e.stopPropagation();
23
+ }
24
+ };
25
+ // Global wheel event for hovering over nodes
26
+ useEffect(() => {
27
+ const overlayEl = overlayRef.current;
28
+ const canvas = fabricCanvas?.current;
29
+ if (!overlayEl || !canvas || !canvasReady)
30
+ return;
31
+ const handleGlobalWheel = (e) => {
32
+ const target = e.target;
33
+ const isOverNode = target !== overlayEl;
34
+ if ((e.ctrlKey || e.metaKey) && isOverNode) {
35
+ e.preventDefault();
36
+ e.stopPropagation();
37
+ const scenePoint = canvas.getScenePoint(e);
38
+ const rect = canvas.getElement().getBoundingClientRect();
39
+ const viewportPoint = {
40
+ x: e.clientX - rect.left,
41
+ y: e.clientY - rect.top,
42
+ };
43
+ canvas.fire("mouse:wheel", {
44
+ e: e,
45
+ scenePoint,
46
+ viewportPoint,
47
+ });
48
+ }
49
+ };
50
+ overlayEl.addEventListener("wheel", handleGlobalWheel, { passive: false });
51
+ return () => {
52
+ overlayEl.removeEventListener("wheel", handleGlobalWheel);
53
+ };
54
+ }, [fabricCanvas, canvasZoom, canvasReady, overlayRef]);
55
+ return { handleOverlayWheel };
56
+ }
@@ -0,0 +1,67 @@
1
+ import { FabricObject, Canvas } from "fabric";
2
+ export interface Task {
3
+ id: string;
4
+ title: string;
5
+ status: "todo" | "in-progress" | "done";
6
+ x: number;
7
+ y: number;
8
+ assignee?: string;
9
+ project?: string;
10
+ priority?: "low" | "medium" | "high";
11
+ dueDate?: string;
12
+ }
13
+ export interface Document {
14
+ id: string;
15
+ title: string;
16
+ project: string;
17
+ breadcrumb?: string[];
18
+ preview: string;
19
+ updatedAt?: string;
20
+ x: number;
21
+ y: number;
22
+ }
23
+ export interface CanvasOverlayLayerProps {
24
+ tasks: Task[];
25
+ documents: Document[];
26
+ onTasksUpdate?: (tasks: Task[]) => void;
27
+ onDocumentsUpdate?: (documents: Document[]) => void;
28
+ canvasZoom?: number;
29
+ canvasViewport?: {
30
+ x: number;
31
+ y: number;
32
+ };
33
+ selectionBox?: {
34
+ x1: number;
35
+ y1: number;
36
+ x2: number;
37
+ y2: number;
38
+ } | null;
39
+ selectedCanvasObjects?: FabricObject[];
40
+ overlayRef: React.RefObject<HTMLDivElement | null>;
41
+ fabricCanvas?: React.RefObject<Canvas | null>;
42
+ canvasReady?: boolean;
43
+ }
44
+ export interface DragState {
45
+ isDragging: boolean;
46
+ itemIds: string[];
47
+ startPositions: Map<string, {
48
+ x: number;
49
+ y: number;
50
+ }>;
51
+ canvasObjectsStartPos: Map<FabricObject, {
52
+ left: number;
53
+ top: number;
54
+ }>;
55
+ offsetX: number;
56
+ offsetY: number;
57
+ }
58
+ export interface PointerCoords {
59
+ clientX: number;
60
+ clientY: number;
61
+ }
62
+ export interface ViewportTransform {
63
+ zoom: number;
64
+ vpX: number;
65
+ vpY: number;
66
+ }
67
+ //# sourceMappingURL=overlay-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay-types.d.ts","sourceRoot":"","sources":["../../../../src/components/node/types/overlay-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,IAAI,EAAE,CAAC;IAEd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACnD,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,qBAAqB,EAAE,GAAG,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import type { Document } from "../node/custom-node-overlay-layer";
1
+ import type { Document } from "../node/types/overlay-types";
2
2
  import { DocumentTemplate } from "../toolbar/document-dropdown";
3
3
  import { TaskTemplate } from "../toolbar/task-dropdown";
4
4
  export type { WhiteboardInitialData } from "../../hooks/useCanvasInit";
@@ -1 +1 @@
1
- {"version":3,"file":"whiteboard-test.d.ts","sourceRoot":"","sources":["../../../src/components/whiteboard/whiteboard-test.tsx"],"names":[],"mappings":"AAQE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAkBlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGtD,YAAY,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAIxE,UAAU,IAAI;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,UAAU,qBAAqB;IAC7B,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;QACf,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;KACxB,CAAC;IACF,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAClC,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,SAAS,EAAE,GAAG,EAAE,CAAC;KAClB,KAAK,IAAI,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,WAAW,EACX,MAAM,EACN,cAAc,EACd,cAAmB,EACnB,kBAAuB,EACvB,aAAqB,GACtB,EAAE,qBAAqB,2CA8PvB"}
1
+ {"version":3,"file":"whiteboard-test.d.ts","sourceRoot":"","sources":["../../../src/components/whiteboard/whiteboard-test.tsx"],"names":[],"mappings":"AAQC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAkB3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGtD,YAAY,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAIxE,UAAU,IAAI;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,UAAU,qBAAqB;IAC7B,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;QACf,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;KACxB,CAAC;IACF,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAClC,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,SAAS,EAAE,GAAG,EAAE,CAAC;KAClB,KAAK,IAAI,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,WAAW,EACX,MAAM,EACN,cAAc,EACd,cAAmB,EACnB,kBAAuB,EACvB,aAAqB,GACtB,EAAE,qBAAqB,2CA8PvB"}
@@ -5,7 +5,7 @@ import { classRegistry } from "fabric";
5
5
  import { useWhiteboardStore } from "../../store/whiteboard-store";
6
6
  import WhiteboardToolbar from "../toolbar/whiteboard-toolbar";
7
7
  import ToolOptionsPanel from "../toolbar/tooloptions-panel";
8
- import CanvasOverlayLayer from "../node/custom-node-overlay-layer";
8
+ import CanvasOverlayLayer from "../node/custom-node-overlay-fix";
9
9
  import ZoomControls from "../zoomcontrol/zoom-control";
10
10
  import { Frame } from "../../lib/fabric-frame";
11
11
  // Import all hooks
@@ -200,7 +200,7 @@ export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs,
200
200
  backgroundImage: `radial-gradient(circle, rgba(255,255,255,0.2) 1.2px, transparent 1.2px)`,
201
201
  backgroundSize: "40px 40px",
202
202
  zIndex: 0,
203
- } }), _jsx("canvas", { ref: canvasRef, className: "absolute inset-0", style: { zIndex: 1 } }), _jsx(CanvasOverlayLayer, { tasks: tasks, documents: documents, onTasksUpdate: setTasks, onDocumentsUpdate: setDocuments, canvasZoom: canvasZoom, canvasViewport: canvasViewport, selectionBox: selectionBox, selectedCanvasObjects: selectedCanvasObjects, fabricCanvas: fabricCanvasRef, canvasReady: canvasReady }), _jsxs("div", { className: "absolute inset-0 pointer-events-none", style: { zIndex: 100 }, children: [_jsx("div", { className: "pointer-events-auto", children: canvasReady ?
203
+ } }), _jsx("canvas", { ref: canvasRef, className: "absolute inset-0", style: { zIndex: 1 } }), _jsx(CanvasOverlayLayer, { tasks: tasks, documents: documents, onTasksUpdate: setTasks, onDocumentsUpdate: setDocuments, canvasZoom: canvasZoom, canvasViewport: canvasViewport, selectionBox: selectionBox, selectedCanvasObjects: selectedCanvasObjects, fabricCanvas: fabricCanvasRef, canvasReady: canvasReady, overlayRef: useRef(null) }), _jsxs("div", { className: "absolute inset-0 pointer-events-none", style: { zIndex: 100 }, children: [_jsx("div", { className: "pointer-events-auto", children: canvasReady ?
204
204
  _jsx(WhiteboardToolbar, { fabricCanvas: fabricCanvasRef, isRestoringRef: isRestoringRef, onAddTask: handleAddTaskFromDropdown, onAddDocument: handleAddDocumentFromDropdown, availableDocuments: availableDocuments, availableTasks: availableTasks, isLoadingData: isLoadingData })
205
205
  : _jsx(ToolbarSkeleton, {}) }), _jsx("div", { className: "pointer-events-auto", children: _jsx(ToolOptionsPanel, { fabricCanvas: fabricCanvasRef }) }), _jsx("div", { className: "pointer-events-auto", children: _jsx(ZoomControls, { zoom: canvasZoom, onZoomIn: handleZoomIn, onZoomOut: handleZoomOut, onResetZoom: handleResetZoom }) })] })] }) }));
206
206
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.79.0",
3
+ "version": "2.80.0",
4
4
  "description": "A feature-rich whiteboard component built with Fabric.js and React",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",