@mhamz.01/easyflow-whiteboard 2.0.1 → 2.2.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.
- package/dist/components/node/custom-node-overlay-layer.d.ts.map +1 -1
- package/dist/components/node/custom-node-overlay-layer.js +15 -10
- package/dist/components/toolbar/document-dropdown.js +1 -1
- package/dist/hooks/useSelection.d.ts +1 -1
- package/dist/hooks/useSelection.d.ts.map +1 -1
- package/dist/hooks/useSelection.js +34 -53
- package/package.json +1 -1
|
@@ -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) ──────────────────────────────────────────────
|
|
@@ -6,7 +6,7 @@ import { useWhiteboardStore } from "../../store/whiteboard-store";
|
|
|
6
6
|
const AVAILABLE_DOCUMENTS = [
|
|
7
7
|
{
|
|
8
8
|
id: "doc-1",
|
|
9
|
-
title: "Product Requirements Document",
|
|
9
|
+
title: "Product Not Requirements Document",
|
|
10
10
|
project: "Website Redesign",
|
|
11
11
|
breadcrumb: ["Design", "Specs"],
|
|
12
12
|
preview: "This document outlines the core requirements for the new landing page including user flows, component specs, and acceptance criteria.",
|
|
@@ -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,SAwJnB,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,11 @@ 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
|
-
//
|
|
26
|
+
// Hide Fabric's native rubber-band rect — we draw our own
|
|
27
|
+
canvas.selection = false;
|
|
34
28
|
const p = canvas.getScenePoint(e.e);
|
|
35
29
|
selStart = { x: p.x, y: p.y };
|
|
36
30
|
selRect = new Rect({
|
|
@@ -40,11 +34,9 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
40
34
|
height: 0,
|
|
41
35
|
fill: "rgba(2, 154, 255, 0.08)",
|
|
42
36
|
stroke: "#029AFF",
|
|
43
|
-
strokeWidth: 1 /
|
|
37
|
+
strokeWidth: 1 / canvas.getZoom(),
|
|
44
38
|
selectable: false,
|
|
45
39
|
evented: false,
|
|
46
|
-
// ── KEY FIX 3: Make it visible so user sees the selection rect ──
|
|
47
|
-
// was `visible: false` before — invisible but still blocking
|
|
48
40
|
excludeFromExport: true,
|
|
49
41
|
});
|
|
50
42
|
canvas.add(selRect);
|
|
@@ -64,20 +56,20 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
64
56
|
top: h < 0 ? p.y : selStart.y,
|
|
65
57
|
width: Math.abs(w),
|
|
66
58
|
height: Math.abs(h),
|
|
67
|
-
strokeWidth: 1 /
|
|
59
|
+
strokeWidth: 1 / canvas.getZoom(),
|
|
68
60
|
});
|
|
69
61
|
selRect.setCoords();
|
|
70
62
|
canvas.renderAll();
|
|
71
|
-
// ──
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
// ── Read directly from Fabric VPT — always frame-perfect, zero React lag ──
|
|
64
|
+
const vpt = canvas.viewportTransform;
|
|
65
|
+
const zoom = vpt[0]; // scaleX
|
|
66
|
+
const vpX = vpt[4]; // panX
|
|
67
|
+
const vpY = vpt[5]; // panY
|
|
76
68
|
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 +
|
|
69
|
+
x1: Math.min(selStart.x, p.x) * zoom + vpX,
|
|
70
|
+
y1: Math.min(selStart.y, p.y) * zoom + vpY,
|
|
71
|
+
x2: Math.max(selStart.x, p.x) * zoom + vpX,
|
|
72
|
+
y2: Math.max(selStart.y, p.y) * zoom + vpY,
|
|
81
73
|
});
|
|
82
74
|
});
|
|
83
75
|
};
|
|
@@ -93,11 +85,9 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
93
85
|
canvas.renderAll();
|
|
94
86
|
selRect = null;
|
|
95
87
|
}
|
|
88
|
+
// Restore Fabric's native selection for object clicking
|
|
89
|
+
canvas.selection = true;
|
|
96
90
|
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
91
|
setTimeout(() => setSelectionBox(null), 150);
|
|
102
92
|
};
|
|
103
93
|
const onSelected = () => {
|
|
@@ -106,34 +96,24 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
106
96
|
return;
|
|
107
97
|
setSelectedCanvasObjects(sel.type === "activeSelection" ? sel.getObjects() : [sel]);
|
|
108
98
|
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",
|
|
99
|
+
rect: "rectangle", circle: "circle", line: "line",
|
|
100
|
+
arrow: "arrow", "bidirectional-arrow": "arrow",
|
|
101
|
+
"i-text": "text", text: "text", path: "pen", image: "image",
|
|
118
102
|
};
|
|
119
|
-
const t = sel instanceof Frame
|
|
120
|
-
? "
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
: sel instanceof Arrow
|
|
124
|
-
? "arrow"
|
|
125
|
-
: sel instanceof BidirectionalArrow
|
|
126
|
-
? "arrow"
|
|
103
|
+
const t = sel instanceof Frame ? "frame"
|
|
104
|
+
: sel instanceof FabricImage ? "image"
|
|
105
|
+
: sel instanceof Arrow ? "arrow"
|
|
106
|
+
: sel instanceof BidirectionalArrow ? "arrow"
|
|
127
107
|
: typeMap[sel.type] ?? null;
|
|
128
108
|
setSelectedObjectType(t);
|
|
129
109
|
};
|
|
130
110
|
const onDeselected = () => {
|
|
131
111
|
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.
|
|
112
|
+
// ── Do NOT touch selectionBox here — onUp owns its lifecycle ──
|
|
135
113
|
setSelectedCanvasObjects([]);
|
|
136
|
-
if (!isDrawingRef.current &&
|
|
114
|
+
if (!isDrawingRef.current &&
|
|
115
|
+
activeToolRef.current !== "select" &&
|
|
116
|
+
activeToolRef.current !== "pan") {
|
|
137
117
|
setActiveTool("select");
|
|
138
118
|
}
|
|
139
119
|
};
|
|
@@ -152,10 +132,11 @@ export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewp
|
|
|
152
132
|
canvas.off("mouse:up", onUp);
|
|
153
133
|
if (rafId !== null)
|
|
154
134
|
cancelAnimationFrame(rafId);
|
|
155
|
-
if (selRect)
|
|
135
|
+
if (selRect) {
|
|
156
136
|
canvas.remove(selRect);
|
|
137
|
+
}
|
|
138
|
+
canvas.selection = true; // always restore on cleanup
|
|
157
139
|
};
|
|
158
|
-
// ──
|
|
159
|
-
// They are now read via refs — effect only registers once per canvas mount.
|
|
140
|
+
// ── Stable deps only — effect registers once per canvas mount ──
|
|
160
141
|
}, [fabricCanvas, setSelectionBox, setSelectedCanvasObjects, setSelectedObjectType, setActiveTool, isDrawingRef]);
|
|
161
142
|
};
|