@mhamz.01/easyflow-whiteboard 2.1.0 → 2.3.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.
|
@@ -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;AAM9C,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;CAC/C;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,
|
|
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;AAM9C,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;CAC/C;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,2CA2dzB"}
|
|
@@ -142,21 +142,26 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
142
142
|
// ── Selection box detection ──────────────────────────────────────────────────
|
|
143
143
|
useEffect(() => {
|
|
144
144
|
if (!selectionBox)
|
|
145
|
-
return;
|
|
145
|
+
return;
|
|
146
|
+
// ── O(n) single pass — no sort, no join, no extra allocations ──
|
|
146
147
|
const newSelected = new Set();
|
|
147
|
-
|
|
148
|
+
for (const task of localTasks) {
|
|
148
149
|
if (isItemInSelectionBox(task.x, task.y, 300, 140, selectionBox))
|
|
149
150
|
newSelected.add(task.id);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (isItemInSelectionBox(doc.x, doc.y,
|
|
151
|
+
}
|
|
152
|
+
for (const doc of localDocuments) {
|
|
153
|
+
if (isItemInSelectionBox(doc.x, doc.y, 320, 160, selectionBox))
|
|
153
154
|
newSelected.add(doc.id);
|
|
154
|
-
}
|
|
155
|
-
//
|
|
155
|
+
}
|
|
156
|
+
// ── O(n) equality check: size first (fast path), then membership ──
|
|
156
157
|
setSelectedIds((prev) => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
if (prev.size !== newSelected.size)
|
|
159
|
+
return newSelected;
|
|
160
|
+
for (const id of newSelected) {
|
|
161
|
+
if (!prev.has(id))
|
|
162
|
+
return newSelected; // found a difference, swap
|
|
163
|
+
}
|
|
164
|
+
return prev; // identical — return same reference, no re-render
|
|
160
165
|
});
|
|
161
166
|
}, [selectionBox, localTasks, localDocuments]);
|
|
162
167
|
// ── Drag start (HTML Node side) ──────────────────────────────────────────────
|
|
@@ -16,6 +16,6 @@ interface UseSelectionProps {
|
|
|
16
16
|
setSelectedCanvasObjects: (objects: FabricObject[]) => void;
|
|
17
17
|
isDrawingRef: React.MutableRefObject<boolean>;
|
|
18
18
|
}
|
|
19
|
-
export declare const useSelection: ({ fabricCanvas, activeTool,
|
|
19
|
+
export declare const useSelection: ({ fabricCanvas, activeTool, setSelectionBox, setSelectedCanvasObjects, isDrawingRef, }: UseSelectionProps) => void;
|
|
20
20
|
export {};
|
|
21
21
|
//# sourceMappingURL=useSelection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSelection.d.ts","sourceRoot":"","sources":["../../src/hooks/useSelection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAQ,YAAY,EAAe,MAAM,QAAQ,CAAC;AAMjE,UAAU,iBAAiB;IACzB,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,eAAe,EAAE,CAAC,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1F,wBAAwB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAC5D,YAAY,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED,eAAO,MAAM,YAAY,GAAI,
|
|
1
|
+
{"version":3,"file":"useSelection.d.ts","sourceRoot":"","sources":["../../src/hooks/useSelection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAQ,YAAY,EAAe,MAAM,QAAQ,CAAC;AAMjE,UAAU,iBAAiB;IACzB,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,eAAe,EAAE,CAAC,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1F,wBAAwB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAC5D,YAAY,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED,eAAO,MAAM,YAAY,GAAI,wFAM1B,iBAAiB,SAsJnB,CAAC"}
|
|
@@ -5,17 +5,11 @@ import { Frame } from "../lib/fabric-frame";
|
|
|
5
5
|
import { Arrow } from "../lib/fabric-arrow";
|
|
6
6
|
import { BidirectionalArrow } from "../lib/fabric-bidirectional-arrow";
|
|
7
7
|
import { useWhiteboardStore } from "../store/whiteboard-store";
|
|
8
|
-
export const useSelection = ({ fabricCanvas, activeTool,
|
|
9
|
-
const setSelectedObjectType = useWhiteboardStore((
|
|
10
|
-
const setActiveTool = useWhiteboardStore((
|
|
11
|
-
//
|
|
12
|
-
// This prevents listener teardown/re-attach during pan/zoom gestures
|
|
13
|
-
const zoomRef = useRef(canvasZoom);
|
|
14
|
-
const viewportRef = useRef(canvasViewport);
|
|
8
|
+
export const useSelection = ({ fabricCanvas, activeTool, setSelectionBox, setSelectedCanvasObjects, isDrawingRef, }) => {
|
|
9
|
+
const setSelectedObjectType = useWhiteboardStore((s) => s.setSelectedObjectType);
|
|
10
|
+
const setActiveTool = useWhiteboardStore((s) => s.setActiveTool);
|
|
11
|
+
// activeTool ref — so effect never re-registers on tool change
|
|
15
12
|
const activeToolRef = useRef(activeTool);
|
|
16
|
-
// Keep refs in sync with latest props on every render
|
|
17
|
-
useEffect(() => { zoomRef.current = canvasZoom; }, [canvasZoom]);
|
|
18
|
-
useEffect(() => { viewportRef.current = canvasViewport; }, [canvasViewport]);
|
|
19
13
|
useEffect(() => { activeToolRef.current = activeTool; }, [activeTool]);
|
|
20
14
|
useEffect(() => {
|
|
21
15
|
const canvas = fabricCanvas.current;
|
|
@@ -26,11 +20,9 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
26
20
|
let selRect = null;
|
|
27
21
|
let rafId = null;
|
|
28
22
|
const onDown = (e) => {
|
|
29
|
-
// ── KEY FIX 2: Use ref instead of closure-captured activeTool ──
|
|
30
23
|
if (activeToolRef.current !== "select" || e.target)
|
|
31
24
|
return;
|
|
32
25
|
isSelecting = true;
|
|
33
|
-
// getScenePoint returns world coordinates (zoom + pan already factored in by Fabric)
|
|
34
26
|
const p = canvas.getScenePoint(e.e);
|
|
35
27
|
selStart = { x: p.x, y: p.y };
|
|
36
28
|
selRect = new Rect({
|
|
@@ -38,13 +30,12 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
38
30
|
top: p.y,
|
|
39
31
|
width: 0,
|
|
40
32
|
height: 0,
|
|
41
|
-
fill: "
|
|
42
|
-
stroke: "
|
|
43
|
-
strokeWidth:
|
|
33
|
+
fill: "transparent",
|
|
34
|
+
stroke: "transparent",
|
|
35
|
+
strokeWidth: 0,
|
|
44
36
|
selectable: false,
|
|
45
37
|
evented: false,
|
|
46
|
-
|
|
47
|
-
// was `visible: false` before — invisible but still blocking
|
|
38
|
+
visible: false, // ← hidden, pure coordinate tracker
|
|
48
39
|
excludeFromExport: true,
|
|
49
40
|
});
|
|
50
41
|
canvas.add(selRect);
|
|
@@ -64,20 +55,20 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
64
55
|
top: h < 0 ? p.y : selStart.y,
|
|
65
56
|
width: Math.abs(w),
|
|
66
57
|
height: Math.abs(h),
|
|
67
|
-
|
|
58
|
+
// strokeWidth line removed
|
|
68
59
|
});
|
|
69
60
|
selRect.setCoords();
|
|
70
61
|
canvas.renderAll();
|
|
71
|
-
// ──
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
62
|
+
// ── Read directly from Fabric VPT — always frame-perfect, zero React lag ──
|
|
63
|
+
const vpt = canvas.viewportTransform;
|
|
64
|
+
const zoom = vpt[0]; // scaleX
|
|
65
|
+
const vpX = vpt[4]; // panX
|
|
66
|
+
const vpY = vpt[5]; // panY
|
|
76
67
|
setSelectionBox({
|
|
77
|
-
x1: Math.min(selStart.x, p.x) * zoom +
|
|
78
|
-
y1: Math.min(selStart.y, p.y) * zoom +
|
|
79
|
-
x2: Math.max(selStart.x, p.x) * zoom +
|
|
80
|
-
y2: Math.max(selStart.y, p.y) * zoom +
|
|
68
|
+
x1: Math.min(selStart.x, p.x) * zoom + vpX,
|
|
69
|
+
y1: Math.min(selStart.y, p.y) * zoom + vpY,
|
|
70
|
+
x2: Math.max(selStart.x, p.x) * zoom + vpX,
|
|
71
|
+
y2: Math.max(selStart.y, p.y) * zoom + vpY,
|
|
81
72
|
});
|
|
82
73
|
});
|
|
83
74
|
};
|
|
@@ -94,10 +85,6 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
94
85
|
selRect = null;
|
|
95
86
|
}
|
|
96
87
|
isSelecting = false;
|
|
97
|
-
// ── KEY FIX 5: Delay clear long enough for overlay useEffect to fire ──
|
|
98
|
-
// onDeselected (selection:cleared) was calling setSelectionBox(null) immediately,
|
|
99
|
-
// racing with the overlay's useEffect. 150ms ensures the React render cycle
|
|
100
|
-
// processes the final box position before it's cleared.
|
|
101
88
|
setTimeout(() => setSelectionBox(null), 150);
|
|
102
89
|
};
|
|
103
90
|
const onSelected = () => {
|
|
@@ -106,34 +93,24 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
106
93
|
return;
|
|
107
94
|
setSelectedCanvasObjects(sel.type === "activeSelection" ? sel.getObjects() : [sel]);
|
|
108
95
|
const typeMap = {
|
|
109
|
-
rect: "rectangle",
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
arrow: "arrow",
|
|
113
|
-
"bidirectional-arrow": "arrow",
|
|
114
|
-
"i-text": "text",
|
|
115
|
-
text: "text",
|
|
116
|
-
path: "pen",
|
|
117
|
-
image: "image",
|
|
96
|
+
rect: "rectangle", circle: "circle", line: "line",
|
|
97
|
+
arrow: "arrow", "bidirectional-arrow": "arrow",
|
|
98
|
+
"i-text": "text", text: "text", path: "pen", image: "image",
|
|
118
99
|
};
|
|
119
|
-
const t = sel instanceof Frame
|
|
120
|
-
? "
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
: sel instanceof Arrow
|
|
124
|
-
? "arrow"
|
|
125
|
-
: sel instanceof BidirectionalArrow
|
|
126
|
-
? "arrow"
|
|
100
|
+
const t = sel instanceof Frame ? "frame"
|
|
101
|
+
: sel instanceof FabricImage ? "image"
|
|
102
|
+
: sel instanceof Arrow ? "arrow"
|
|
103
|
+
: sel instanceof BidirectionalArrow ? "arrow"
|
|
127
104
|
: typeMap[sel.type] ?? null;
|
|
128
105
|
setSelectedObjectType(t);
|
|
129
106
|
};
|
|
130
107
|
const onDeselected = () => {
|
|
131
108
|
setSelectedObjectType(null);
|
|
132
|
-
// ──
|
|
133
|
-
// This was racing with onUp's setTimeout and clearing before overlay processed it.
|
|
134
|
-
// onUp owns the selectionBox lifecycle. Only clear canvas objects here.
|
|
109
|
+
// ── Do NOT touch selectionBox here — onUp owns its lifecycle ──
|
|
135
110
|
setSelectedCanvasObjects([]);
|
|
136
|
-
if (!isDrawingRef.current &&
|
|
111
|
+
if (!isDrawingRef.current &&
|
|
112
|
+
activeToolRef.current !== "select" &&
|
|
113
|
+
activeToolRef.current !== "pan") {
|
|
137
114
|
setActiveTool("select");
|
|
138
115
|
}
|
|
139
116
|
};
|
|
@@ -152,10 +129,10 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
152
129
|
canvas.off("mouse:up", onUp);
|
|
153
130
|
if (rafId !== null)
|
|
154
131
|
cancelAnimationFrame(rafId);
|
|
155
|
-
if (selRect)
|
|
132
|
+
if (selRect) {
|
|
156
133
|
canvas.remove(selRect);
|
|
134
|
+
}
|
|
157
135
|
};
|
|
158
|
-
// ──
|
|
159
|
-
// They are now read via refs — effect only registers once per canvas mount.
|
|
136
|
+
// ── Stable deps only — effect registers once per canvas mount ──
|
|
160
137
|
}, [fabricCanvas, setSelectionBox, setSelectedCanvasObjects, setSelectedObjectType, setActiveTool, isDrawingRef]);
|
|
161
138
|
};
|