@mhamz.01/easyflow-whiteboard 2.58.0 → 2.59.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,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
+ {"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,SAmE/D,CAAC"}
@@ -1,8 +1,7 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { ActiveSelection } from "fabric";
3
3
  export const useCopyPaste = ({ fabricCanvas }) => {
4
- // Store cloned individual objects — never a canvas-attached ActiveSelection
5
- const clipboardRef = useRef([]);
4
+ const clipboardRef = useRef(null);
6
5
  useEffect(() => {
7
6
  const handleKeyDown = async (e) => {
8
7
  if (e.target instanceof HTMLInputElement ||
@@ -16,67 +15,45 @@ export const useCopyPaste = ({ fabricCanvas }) => {
16
15
  const active = canvas.getActiveObject();
17
16
  if (!active)
18
17
  return;
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
- }
18
+ // Clone once at copy time store as-is, exactly like official docs
19
+ const cloned = await active.clone();
20
+ clipboardRef.current = cloned;
45
21
  return;
46
22
  }
47
23
  // ── Paste ────────────────────────────────────────────────────────────
48
24
  if ((e.ctrlKey || e.metaKey) && e.key === "v") {
49
- if (clipboardRef.current.length === 0)
25
+ const copied = clipboardRef.current;
26
+ if (!copied)
50
27
  return;
28
+ // Clone again so multiple pastes work independently
29
+ const clonedObj = await copied.clone();
51
30
  canvas.discardActiveObject();
52
- const offset = 20;
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,
31
+ clonedObj.set({
32
+ left: clonedObj.left + 10,
33
+ top: clonedObj.top + 10,
34
+ evented: true,
35
+ });
36
+ if (clonedObj instanceof ActiveSelection) {
37
+ // Official docs pattern exactly:
38
+ // set canvas reference on the ActiveSelection FIRST,
39
+ // then use forEachObject to add children — not getObjects()
40
+ // forEachObject is the correct API for iterating ActiveSelection
41
+ clonedObj.canvas = canvas;
42
+ clonedObj.forEachObject((obj) => {
43
+ canvas.add(obj);
63
44
  });
64
- clone.setCoords();
65
- canvas.add(clone);
66
- addedObjects.push(clone);
67
- }
68
- if (addedObjects.length === 1) {
69
- canvas.setActiveObject(addedObjects[0]);
45
+ // setCoords on the selection itself — not on individual children
46
+ clonedObj.setCoords();
70
47
  }
71
48
  else {
72
- const newSelection = new ActiveSelection(addedObjects, { canvas });
73
- canvas.setActiveObject(newSelection);
49
+ canvas.add(clonedObj);
74
50
  }
51
+ // Official docs pattern: offset clipboard itself for cascading paste
52
+ // This is simpler and safer than storing new clones as clipboard
53
+ copied.top += 10;
54
+ copied.left += 10;
55
+ canvas.setActiveObject(clonedObj);
75
56
  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()));
80
57
  }
81
58
  };
82
59
  window.addEventListener("keydown", handleKeyDown);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.58.0",
3
+ "version": "2.59.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",