@mhamz.01/easyflow-whiteboard 2.39.0 → 2.40.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":"usePan.d.ts","sourceRoot":"","sources":["../../src/hooks/usePan.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,WAAW;IACnB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,iBAAiB,EAAE,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjE;AAED,eAAO,MAAM,MAAM,GAAI,8DAKpB,WAAW,SAkGb,CAAC"}
1
+ {"version":3,"file":"usePan.d.ts","sourceRoot":"","sources":["../../src/hooks/usePan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,WAAW;IACnB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,iBAAiB,EAAE,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjE;AAED,eAAO,MAAM,MAAM,GAAI,8DAKpB,WAAW,SA8Gb,CAAC"}
@@ -1,5 +1,14 @@
1
- import { useEffect } from "react";
1
+ import { useEffect, useRef } from "react";
2
2
  export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport, }) => {
3
+ // Refs so the effect registers ONCE but always reads latest values.
4
+ // Avoids tearing down/reattaching mouse:down/move/up on every tool change.
5
+ const activeToolRef = useRef(activeTool);
6
+ const handleZoomRef = useRef(handleZoom);
7
+ const setCanvasViewportRef = useRef(setCanvasViewport);
8
+ // Assigned synchronously in render — always fresh before any event fires
9
+ activeToolRef.current = activeTool;
10
+ handleZoomRef.current = handleZoom;
11
+ setCanvasViewportRef.current = setCanvasViewport;
3
12
  useEffect(() => {
4
13
  const canvas = fabricCanvas.current;
5
14
  if (!canvas)
@@ -11,16 +20,14 @@ export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport
11
20
  let lastY = 0;
12
21
  let lastTouchDistance = 0;
13
22
  const onDown = (opt) => {
14
- if (activeTool !== "pan")
23
+ if (activeToolRef.current !== "pan")
15
24
  return;
16
25
  const e = opt.e;
17
- // Pinch initialization
18
26
  if (e.touches && e.touches.length === 2) {
19
27
  isPanning = false;
20
28
  lastTouchDistance = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
21
29
  return;
22
30
  }
23
- // Pan initialization
24
31
  const pointer = e.touches ? e.touches[0] : e;
25
32
  isPanning = true;
26
33
  lastX = pointer.clientX;
@@ -28,45 +35,50 @@ export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport
28
35
  canvas.setCursor("grabbing");
29
36
  };
30
37
  const onMove = (opt) => {
31
- if (activeTool !== "pan")
38
+ if (activeToolRef.current !== "pan")
32
39
  return;
33
40
  const e = opt.e;
34
- // Handle pinch zoom (two fingers)
35
41
  if (e.touches && e.touches.length === 2) {
36
42
  const currentDistance = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
37
43
  if (lastTouchDistance > 0) {
38
44
  const zoom = canvas.getZoom();
39
45
  const delta = (currentDistance - lastTouchDistance) * 0.01;
40
- const newZoom = zoom + delta;
41
46
  const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
42
47
  const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
43
48
  const rect = canvasEl.getBoundingClientRect();
44
- handleZoom(newZoom, { x: midX - rect.left, y: midY - rect.top });
49
+ handleZoomRef.current(zoom + delta, {
50
+ x: midX - rect.left,
51
+ y: midY - rect.top,
52
+ });
45
53
  }
46
54
  lastTouchDistance = currentDistance;
47
55
  return;
48
56
  }
49
- // Handle panning (one finger or mouse)
50
- if (isPanning) {
51
- const pointer = e.touches ? e.touches[0] : e;
52
- const vpt = canvas.viewportTransform;
53
- if (vpt) {
54
- vpt[4] += pointer.clientX - lastX;
55
- vpt[5] += pointer.clientY - lastY;
56
- canvas.requestRenderAll();
57
- }
58
- lastX = pointer.clientX;
59
- lastY = pointer.clientY;
60
- }
57
+ if (!isPanning)
58
+ return; // early exit — avoids any work on non-pan moves
59
+ const pointer = e.touches ? e.touches[0] : e;
60
+ const vpt = canvas.viewportTransform;
61
+ if (!vpt)
62
+ return;
63
+ // VPT mutation is synchronous — NO rAF here intentionally.
64
+ // rAF delays the mutation by one frame which makes pan feel like 20fps.
65
+ vpt[4] += pointer.clientX - lastX;
66
+ vpt[5] += pointer.clientY - lastY;
67
+ canvas.requestRenderAll();
68
+ lastX = pointer.clientX;
69
+ lastY = pointer.clientY;
61
70
  };
62
71
  const onUp = () => {
72
+ if (!isPanning && lastTouchDistance === 0)
73
+ return; // nothing to clean up
63
74
  const vpt = canvas.viewportTransform;
64
75
  if (vpt) {
65
- setCanvasViewport({ x: vpt[4], y: vpt[5] });
76
+ // React state update only on mouse:up not per-frame during pan
77
+ setCanvasViewportRef.current({ x: vpt[4], y: vpt[5] });
66
78
  }
67
79
  isPanning = false;
68
80
  lastTouchDistance = 0;
69
- canvas.setCursor(activeTool === "pan" ? "grab" : "default");
81
+ canvas.setCursor(activeToolRef.current === "pan" ? "grab" : "default");
70
82
  };
71
83
  canvas.on("mouse:down", onDown);
72
84
  canvas.on("mouse:move", onMove);
@@ -76,5 +88,5 @@ export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport
76
88
  canvas.off("mouse:move", onMove);
77
89
  canvas.off("mouse:up", onUp);
78
90
  };
79
- }, [activeTool, handleZoom, setCanvasViewport, fabricCanvas]);
91
+ }, [fabricCanvas]); // registered once per canvas mount
80
92
  };
@@ -1 +1 @@
1
- {"version":3,"file":"usePersistance.d.ts","sourceRoot":"","sources":["../../src/hooks/usePersistance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,mBAAmB;IAC3B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,SAAS,EAAE,GAAG,EAAE,CAAC;IACjB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACzC,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;CAC9C;AAKD,eAAO,MAAM,cAAc,GAAI,sFAO5B,mBAAmB,SAgFrB,CAAC"}
1
+ {"version":3,"file":"usePersistance.d.ts","sourceRoot":"","sources":["../../src/hooks/usePersistance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,mBAAmB;IAC3B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,SAAS,EAAE,GAAG,EAAE,CAAC;IACjB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACzC,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;CAC9C;AAKD,eAAO,MAAM,cAAc,GAAI,sFAO5B,mBAAmB,SAkFrB,CAAC"}
@@ -12,6 +12,7 @@ export const usePersistence = ({ fabricCanvas, tasks, documents, pushHistory, is
12
12
  const json = JSON.stringify(canvas.toJSON());
13
13
  localStorage.setItem(CANVAS_KEY, json);
14
14
  pushHistory(json);
15
+ console.log("💾 Canvas content saving to storage is: " + json);
15
16
  console.log("💾 Canvas saved via requestIdleCallback");
16
17
  }
17
18
  catch (err) {
@@ -34,6 +35,7 @@ export const usePersistence = ({ fabricCanvas, tasks, documents, pushHistory, is
34
35
  try {
35
36
  const nodesData = { tasks, documents };
36
37
  localStorage.setItem(NODES_KEY, JSON.stringify(nodesData));
38
+ console.log("💾 Nodes saving to storage is: " + JSON.stringify(nodesData));
37
39
  console.log("💾 Custom nodes saved");
38
40
  }
39
41
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.39.0",
3
+ "version": "2.40.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",