@mhamz.01/easyflow-whiteboard 2.55.0 → 2.57.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":"whiteboard-test.d.ts","sourceRoot":"","sources":["../../../src/components/whiteboard/whiteboard-test.tsx"],"names":[],"mappings":"AAQE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAmBlE,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,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,GACf,EAAE,qBAAqB,2CAwPvB"}
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;AAoBlE,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,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,GACf,EAAE,qBAAqB,2CA2PvB"}
@@ -21,6 +21,7 @@ import { useLiveUpdate } from "../../hooks/useLiveUpdate";
21
21
  import { useMouseHandlers } from "../../hooks/useMouseHandlers";
22
22
  import { usePersistence } from "../../hooks/usePersistance";
23
23
  import { ToolbarSkeleton } from "../toolbar/toolbar-skeleton/toolbar-skeleton";
24
+ import { useCopyPaste } from "../../hooks/useCopyPaste";
24
25
  classRegistry.setClass(Frame, "frame");
25
26
  export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs, }) {
26
27
  // Refs
@@ -100,6 +101,8 @@ export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs,
100
101
  pushHistory,
101
102
  addCanvasObject,
102
103
  });
104
+ // Copy-paste handlers
105
+ useCopyPaste({ fabricCanvas: fabricCanvasRef });
103
106
  // Eraser handlers
104
107
  const eraserHandlers = useEraser({
105
108
  fabricCanvas: fabricCanvasRef,
@@ -0,0 +1,8 @@
1
+ import { RefObject } from "react";
2
+ import { Canvas } from "fabric";
3
+ interface UseCopyPasteProps {
4
+ fabricCanvas: RefObject<Canvas | null>;
5
+ }
6
+ export declare const useCopyPaste: ({ fabricCanvas }: UseCopyPasteProps) => void;
7
+ export {};
8
+ //# sourceMappingURL=useCopyPaste.d.ts.map
@@ -0,0 +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,EAAgB,MAAM,QAAQ,CAAC;AAE9C,UAAU,iBAAiB;IACzB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,YAAY,GAAI,kBAAkB,iBAAiB,SA2F/D,CAAC"}
@@ -0,0 +1,78 @@
1
+ import { useEffect, useRef } from "react";
2
+ export const useCopyPaste = ({ fabricCanvas }) => {
3
+ // Clipboard lives in a ref — no React state needed, never causes re-renders
4
+ const clipboardRef = useRef(null);
5
+ useEffect(() => {
6
+ const handleKeyDown = async (e) => {
7
+ // Don't intercept when typing in inputs
8
+ if (e.target instanceof HTMLInputElement ||
9
+ e.target instanceof HTMLTextAreaElement)
10
+ return;
11
+ const canvas = fabricCanvas.current;
12
+ if (!canvas)
13
+ return;
14
+ // ── Copy ──────────────────────────────────────────────────────────────
15
+ if ((e.ctrlKey || e.metaKey) && e.key === "c") {
16
+ const active = canvas.getActiveObject();
17
+ if (!active)
18
+ return;
19
+ // clone() returns a deep copy with no canvas reference —
20
+ // safe to store and paste multiple times
21
+ const cloned = await active.clone();
22
+ clipboardRef.current = cloned;
23
+ return;
24
+ }
25
+ // ── Paste ─────────────────────────────────────────────────────────────────
26
+ if ((e.ctrlKey || e.metaKey) && e.key === "v") {
27
+ const copied = clipboardRef.current;
28
+ if (!copied)
29
+ return;
30
+ canvas.discardActiveObject();
31
+ const cloned = await copied.clone();
32
+ const offset = 20;
33
+ if (cloned.type === "activeSelection" && "getObjects" in cloned) {
34
+ const objects = cloned.getObjects();
35
+ // Apply the group's transform to each child to get absolute positions
36
+ const groupLeft = cloned.left ?? 0;
37
+ const groupTop = cloned.top ?? 0;
38
+ const groupAngle = cloned.angle ?? 0;
39
+ const addedObjects = [];
40
+ for (const obj of objects) {
41
+ const childClone = await obj.clone();
42
+ // Convert from group-local to canvas-absolute coords
43
+ // Fabric stores child positions relative to group center
44
+ childClone.set({
45
+ left: (obj.left ?? 0) + groupLeft + offset,
46
+ top: (obj.top ?? 0) + groupTop + offset,
47
+ angle: (obj.angle ?? 0) + groupAngle,
48
+ });
49
+ childClone.setCoords();
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
+ }
61
+ }
62
+ else {
63
+ cloned.set({
64
+ left: (cloned.left ?? 0) + offset,
65
+ top: (cloned.top ?? 0) + offset,
66
+ });
67
+ cloned.setCoords();
68
+ canvas.add(cloned);
69
+ canvas.setActiveObject(cloned);
70
+ clipboardRef.current = cloned;
71
+ }
72
+ canvas.requestRenderAll();
73
+ }
74
+ };
75
+ window.addEventListener("keydown", handleKeyDown);
76
+ return () => window.removeEventListener("keydown", handleKeyDown);
77
+ }, [fabricCanvas]); // registered once — canvas read via ref
78
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.55.0",
3
+ "version": "2.57.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",