@mhamz.01/easyflow-whiteboard 2.57.0 → 2.58.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":"useCopyPaste.d.ts","sourceRoot":"","sources":["../../src/hooks/useCopyPaste.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"useCopyPaste.d.ts","sourceRoot":"","sources":["../../src/hooks/useCopyPaste.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAiC,MAAM,QAAQ,CAAC;AAE/D,UAAU,iBAAiB;IACzB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,YAAY,GAAI,kBAAkB,iBAAiB,SAmG/D,CAAC"}
|
|
@@ -1,78 +1,85 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
|
+
import { ActiveSelection } from "fabric";
|
|
2
3
|
export const useCopyPaste = ({ fabricCanvas }) => {
|
|
3
|
-
//
|
|
4
|
-
const clipboardRef = useRef(
|
|
4
|
+
// Store cloned individual objects — never a canvas-attached ActiveSelection
|
|
5
|
+
const clipboardRef = useRef([]);
|
|
5
6
|
useEffect(() => {
|
|
6
7
|
const handleKeyDown = async (e) => {
|
|
7
|
-
// Don't intercept when typing in inputs
|
|
8
8
|
if (e.target instanceof HTMLInputElement ||
|
|
9
9
|
e.target instanceof HTMLTextAreaElement)
|
|
10
10
|
return;
|
|
11
11
|
const canvas = fabricCanvas.current;
|
|
12
12
|
if (!canvas)
|
|
13
13
|
return;
|
|
14
|
-
// ── Copy
|
|
14
|
+
// ── Copy ────────────────────────────────────────────────────────────
|
|
15
15
|
if ((e.ctrlKey || e.metaKey) && e.key === "c") {
|
|
16
16
|
const active = canvas.getActiveObject();
|
|
17
17
|
if (!active)
|
|
18
18
|
return;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
if (active.type === "activeSelection" && "getObjects" in active) {
|
|
20
|
+
// Clone each child independently — detached from canvas
|
|
21
|
+
// Store as flat array of individual objects, never as a group
|
|
22
|
+
const clones = await Promise.all(active.getObjects().map((obj) => obj.clone()));
|
|
23
|
+
// Snapshot each child's ABSOLUTE canvas position at copy time
|
|
24
|
+
// getObjects() inside activeSelection returns group-relative coords,
|
|
25
|
+
// so we must add the group's position to get true canvas coords
|
|
26
|
+
const groupLeft = active.left ?? 0;
|
|
27
|
+
const groupTop = active.top ?? 0;
|
|
28
|
+
const groupAngle = active.angle ?? 0;
|
|
29
|
+
clones.forEach((clone, i) => {
|
|
30
|
+
const src = active.getObjects()[i];
|
|
31
|
+
clone.set({
|
|
32
|
+
left: (src.left ?? 0) + groupLeft,
|
|
33
|
+
top: (src.top ?? 0) + groupTop,
|
|
34
|
+
angle: (src.angle ?? 0) + groupAngle,
|
|
35
|
+
});
|
|
36
|
+
clone.setCoords();
|
|
37
|
+
});
|
|
38
|
+
clipboardRef.current = clones;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Single object — clone detached
|
|
42
|
+
const clone = await active.clone();
|
|
43
|
+
clipboardRef.current = [clone];
|
|
44
|
+
}
|
|
23
45
|
return;
|
|
24
46
|
}
|
|
25
|
-
// ── Paste
|
|
47
|
+
// ── Paste ────────────────────────────────────────────────────────────
|
|
26
48
|
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
27
|
-
|
|
28
|
-
if (!copied)
|
|
49
|
+
if (clipboardRef.current.length === 0)
|
|
29
50
|
return;
|
|
30
51
|
canvas.discardActiveObject();
|
|
31
|
-
const cloned = await copied.clone();
|
|
32
52
|
const offset = 20;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
canvas.add(childClone);
|
|
51
|
-
addedObjects.push(childClone);
|
|
52
|
-
}
|
|
53
|
-
// Select all pasted objects together
|
|
54
|
-
if (addedObjects.length > 0) {
|
|
55
|
-
const { ActiveSelection } = await import("fabric");
|
|
56
|
-
const newSelection = new ActiveSelection(addedObjects, { canvas });
|
|
57
|
-
canvas.setActiveObject(newSelection);
|
|
58
|
-
// Update clipboard to new clones for cascading offset on next paste
|
|
59
|
-
clipboardRef.current = newSelection;
|
|
60
|
-
}
|
|
53
|
+
const addedObjects = [];
|
|
54
|
+
// Clone fresh from our stored detached objects each paste
|
|
55
|
+
// This guarantees no canvas reference contamination
|
|
56
|
+
const freshClones = await Promise.all(clipboardRef.current.map((obj) => obj.clone()));
|
|
57
|
+
for (const clone of freshClones) {
|
|
58
|
+
clone.set({
|
|
59
|
+
left: (clone.left ?? 0) + offset,
|
|
60
|
+
top: (clone.top ?? 0) + offset,
|
|
61
|
+
evented: true,
|
|
62
|
+
selectable: true,
|
|
63
|
+
});
|
|
64
|
+
clone.setCoords();
|
|
65
|
+
canvas.add(clone);
|
|
66
|
+
addedObjects.push(clone);
|
|
67
|
+
}
|
|
68
|
+
if (addedObjects.length === 1) {
|
|
69
|
+
canvas.setActiveObject(addedObjects[0]);
|
|
61
70
|
}
|
|
62
71
|
else {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
top: (cloned.top ?? 0) + offset,
|
|
66
|
-
});
|
|
67
|
-
cloned.setCoords();
|
|
68
|
-
canvas.add(cloned);
|
|
69
|
-
canvas.setActiveObject(cloned);
|
|
70
|
-
clipboardRef.current = cloned;
|
|
72
|
+
const newSelection = new ActiveSelection(addedObjects, { canvas });
|
|
73
|
+
canvas.setActiveObject(newSelection);
|
|
71
74
|
}
|
|
72
75
|
canvas.requestRenderAll();
|
|
76
|
+
// Update clipboard with the newly pasted clones (detached copies)
|
|
77
|
+
// so next Ctrl+V offsets from current paste position
|
|
78
|
+
// CRITICAL: clone them immediately so they're detached from canvas
|
|
79
|
+
clipboardRef.current = await Promise.all(addedObjects.map((obj) => obj.clone()));
|
|
73
80
|
}
|
|
74
81
|
};
|
|
75
82
|
window.addEventListener("keydown", handleKeyDown);
|
|
76
83
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
77
|
-
}, [fabricCanvas]);
|
|
84
|
+
}, [fabricCanvas]);
|
|
78
85
|
};
|