@mhamz.01/easyflow-whiteboard 2.79.0 → 2.81.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 (31) 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 +198 -0
  4. package/dist/components/node/custom-node-overlay-layer.d.ts +0 -44
  5. package/dist/components/node/custom-node-overlay-layer.d.ts.map +1 -1
  6. package/dist/components/node/custom-node-overlay-layer.js +545 -458
  7. package/dist/components/node/hooks/useCanvasReady.d.ts +7 -0
  8. package/dist/components/node/hooks/useCanvasReady.d.ts.map +1 -0
  9. package/dist/components/node/hooks/useCanvasReady.js +21 -0
  10. package/dist/components/node/hooks/useKeyboardShortcuts.d.ts +12 -0
  11. package/dist/components/node/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  12. package/dist/components/node/hooks/useKeyboardShortcuts.js +37 -0
  13. package/dist/components/node/hooks/useNodeDrag.d.ts +25 -0
  14. package/dist/components/node/hooks/useNodeDrag.d.ts.map +1 -0
  15. package/dist/components/node/hooks/useNodeDrag.js +119 -0
  16. package/dist/components/node/hooks/useNodeSync.d.ts +13 -0
  17. package/dist/components/node/hooks/useNodeSync.d.ts.map +1 -0
  18. package/dist/components/node/hooks/useNodeSync.js +56 -0
  19. package/dist/components/node/hooks/useSelectionBox.d.ts +20 -0
  20. package/dist/components/node/hooks/useSelectionBox.d.ts.map +1 -0
  21. package/dist/components/node/hooks/useSelectionBox.js +30 -0
  22. package/dist/components/node/hooks/useWheelZoom.d.ts +16 -0
  23. package/dist/components/node/hooks/useWheelZoom.d.ts.map +1 -0
  24. package/dist/components/node/hooks/useWheelZoom.js +76 -0
  25. package/dist/components/node/types/overlay-types.d.ts +67 -0
  26. package/dist/components/node/types/overlay-types.d.ts.map +1 -0
  27. package/dist/components/node/types/overlay-types.js +1 -0
  28. package/dist/components/whiteboard/whiteboard-test.d.ts +1 -1
  29. package/dist/components/whiteboard/whiteboard-test.d.ts.map +1 -1
  30. package/dist/components/whiteboard/whiteboard-test.js +2 -2
  31. package/package.json +1 -1
@@ -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,16 @@
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
+ onViewportChange: (viewport: {
8
+ x: number;
9
+ y: number;
10
+ }, zoom: number) => void;
11
+ }
12
+ export declare function useWheelZoom({ overlayRef, fabricCanvas, canvasZoom, canvasReady, onViewportChange, }: UseWheelZoomProps): {
13
+ handleOverlayWheel: (e: React.WheelEvent) => void;
14
+ };
15
+ export {};
16
+ //# 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;IACtB,gBAAgB,EAAE,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9E;AAED,wBAAgB,YAAY,CAAC,EAC3B,UAAU,EACV,YAAY,EACZ,UAAc,EACd,WAAmB,EACnB,gBAAgB,GACjB,EAAE,iBAAiB;4BAEa,KAAK,CAAC,UAAU;EAqFhD"}
@@ -0,0 +1,76 @@
1
+ import { useEffect } from "react";
2
+ export function useWheelZoom({ overlayRef, fabricCanvas, canvasZoom = 1, canvasReady = false, onViewportChange, }) {
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
+ useEffect(() => {
26
+ const canvas = fabricCanvas?.current;
27
+ if (!canvas || !canvasReady)
28
+ return;
29
+ // THE FIX: Listen to Fabric's internal render loop
30
+ // This fires immediately after Fabric moves, reducing the lag time
31
+ const syncViewport = () => {
32
+ const vpt = canvas.viewportTransform;
33
+ if (vpt) {
34
+ onViewportChange({ x: vpt[4], y: vpt[5] }, // Pan position
35
+ canvas.getZoom() // Zoom level
36
+ );
37
+ }
38
+ };
39
+ // Use 'after:render' for the tightest possible sync
40
+ canvas.on("after:render", syncViewport);
41
+ return () => {
42
+ canvas.off("after:render", syncViewport);
43
+ };
44
+ }, [fabricCanvas, canvasReady, onViewportChange]);
45
+ // Global wheel event for hovering over nodes
46
+ useEffect(() => {
47
+ const overlayEl = overlayRef.current;
48
+ const canvas = fabricCanvas?.current;
49
+ if (!overlayEl || !canvas || !canvasReady)
50
+ return;
51
+ const handleGlobalWheel = (e) => {
52
+ const target = e.target;
53
+ const isOverNode = target !== overlayEl;
54
+ if ((e.ctrlKey || e.metaKey) && isOverNode) {
55
+ e.preventDefault();
56
+ e.stopPropagation();
57
+ const scenePoint = canvas.getScenePoint(e);
58
+ const rect = canvas.getElement().getBoundingClientRect();
59
+ const viewportPoint = {
60
+ x: e.clientX - rect.left,
61
+ y: e.clientY - rect.top,
62
+ };
63
+ canvas.fire("mouse:wheel", {
64
+ e: e,
65
+ scenePoint,
66
+ viewportPoint,
67
+ });
68
+ }
69
+ };
70
+ overlayEl.addEventListener("wheel", handleGlobalWheel, { passive: false });
71
+ return () => {
72
+ overlayEl.removeEventListener("wheel", handleGlobalWheel);
73
+ };
74
+ }, [fabricCanvas, canvasZoom, canvasReady, overlayRef]);
75
+ return { handleOverlayWheel };
76
+ }
@@ -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.81.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",