@mhamz.01/easyflow-whiteboard 2.18.0 → 2.19.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":"useDrawing.d.ts","sourceRoot":"","sources":["../../src/hooks/useDrawing.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,iBAAiB,EAAsB,MAAM,QAAQ,CAAC;AAKrF,UAAU,eAAe;IACvB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,GAAG,CAAC;IACjB,YAAY,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,SAAS,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC1D,eAAe,EAAE,SAAS,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAChD,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,eAAe,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;CAC9C;AAED,eAAO,MAAM,UAAU,GAAI,4IAUxB,eAAe;2BACc,iBAAiB;2BAsHjB,iBAAiB;;CA8ChD,CAAC"}
1
+ {"version":3,"file":"useDrawing.d.ts","sourceRoot":"","sources":["../../src/hooks/useDrawing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,iBAAiB,EAAsB,MAAM,QAAQ,CAAC;AAMrF,UAAU,eAAe;IACvB,YAAY,EAAQ,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAU,MAAM,CAAC;IAC3B,WAAW,EAAS,GAAG,CAAC;IACxB,YAAY,EAAQ,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,aAAa,EAAO,SAAS,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC/D,eAAe,EAAK,SAAS,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACnD,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,WAAW,EAAS,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,eAAe,EAAK,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;CACjD;AAED,eAAO,MAAM,UAAU,GAAI,4IAUxB,eAAe;2BAmBS,iBAAiB;2BAmGjB,iBAAiB;;CA0C3C,CAAC"}
@@ -1,142 +1,147 @@
1
+ import { useRef } from "react";
1
2
  import { Rect, Circle, Line } from "fabric";
2
3
  import { Frame } from "../lib/fabric-frame";
3
4
  import { Arrow } from "../lib/fabric-arrow";
4
5
  import { calculateDashArray, updateDrawingObject, addText } from "../lib/fabric-utils";
5
6
  import { useWhiteboardStore } from "../store/whiteboard-store";
6
7
  export const useDrawing = ({ fabricCanvas, activeTool, toolOptions, isDrawingRef, startPointRef, currentShapeRef, suppressHistoryRef, pushHistory, addCanvasObject, }) => {
7
- const handleMouseDown = (opt) => {
8
- const canvas = fabricCanvas.current;
9
- if (!canvas)
10
- return;
11
- const pointer = canvas.getScenePoint(opt.e);
12
- if (opt.target || canvas.getActiveObject())
13
- return;
14
- // Text tool
15
- if (activeTool === "text") {
16
- addText(canvas, {
17
- x: pointer.x,
18
- y: pointer.y,
19
- fontSize: toolOptions.text.fontSize,
20
- fontFamily: toolOptions.text.fontFamily,
21
- fontWeight: toolOptions.text.fontWeight,
22
- color: toolOptions.text.color,
23
- textAlign: toolOptions.text.textAlign,
24
- });
25
- pushHistory(JSON.stringify(canvas.toJSON()));
26
- useWhiteboardStore.getState().setActiveTool("select");
27
- return;
28
- }
29
- if (!["rectangle", "circle", "frame", "line", "arrow"].includes(activeTool))
30
- return;
31
- suppressHistoryRef.current = true;
32
- isDrawingRef.current = true;
33
- startPointRef.current = { x: pointer.x, y: pointer.y };
34
- let shape = null;
35
- switch (activeTool) {
36
- case "rectangle":
37
- shape = new Rect({
38
- left: pointer.x,
39
- top: pointer.y,
40
- width: 1,
41
- height: 1,
42
- fill: toolOptions.rectangle.fillColor === "transparent"
43
- ? "transparent"
44
- : toolOptions.rectangle.fillColor,
45
- stroke: toolOptions.rectangle.strokeColor,
46
- strokeWidth: toolOptions.rectangle.strokeWidth,
47
- strokeDashArray: calculateDashArray(toolOptions.rectangle.strokeDashArray, toolOptions.rectangle.strokeWidth),
48
- rx: 5,
49
- ry: 5,
50
- strokeLineCap: "round",
51
- strokeLineJoin: "round",
52
- selectable: false,
53
- });
54
- break;
55
- case "circle":
56
- shape = new Circle({
57
- left: pointer.x,
58
- top: pointer.y,
59
- radius: 1,
60
- fill: toolOptions.circle.fillColor === "transparent"
61
- ? "transparent"
62
- : toolOptions.circle.fillColor,
63
- stroke: toolOptions.circle.strokeColor,
64
- strokeWidth: toolOptions.circle.strokeWidth,
65
- strokeDashArray: toolOptions.circle.strokeDashArray
66
- ? [...toolOptions.circle.strokeDashArray]
67
- : undefined,
68
- selectable: false,
69
- });
70
- break;
71
- case "frame":
72
- shape = new Frame({
73
- left: pointer.x,
74
- top: pointer.y,
75
- width: 1,
76
- height: 1,
77
- stroke: toolOptions.frame.strokeColor,
78
- strokeWidth: toolOptions.frame.strokeWidth,
79
- strokeDashArray: toolOptions.frame.strokeDashArray
80
- ? [...toolOptions.frame.strokeDashArray]
81
- : undefined,
82
- fill: toolOptions.frame.fillColor,
83
- selectable: false,
8
+ // ── PERF FIX 1: Stable refs for all props ────────────────────────────────
9
+ // These plain handler functions are recreated every render. useMouseHandlers
10
+ // puts drawingHandlers in its deps array, causing all 5 canvas listeners to
11
+ // re-register on every render. Wrapping in refs makes the returned object
12
+ // referentially stable — useMouseHandlers can safely use [] deps.
13
+ const activeToolRef = useRef(activeTool);
14
+ const toolOptionsRef = useRef(toolOptions);
15
+ const pushHistoryRef = useRef(pushHistory);
16
+ const addCanvasObjectRef = useRef(addCanvasObject);
17
+ activeToolRef.current = activeTool;
18
+ toolOptionsRef.current = toolOptions;
19
+ pushHistoryRef.current = pushHistory;
20
+ addCanvasObjectRef.current = addCanvasObject;
21
+ // ── Stable handler refs — identity never changes ──────────────────────────
22
+ const handlersRef = useRef({
23
+ handleMouseDown: (opt) => {
24
+ const canvas = fabricCanvas.current;
25
+ if (!canvas)
26
+ return;
27
+ const tool = activeToolRef.current;
28
+ const options = toolOptionsRef.current;
29
+ const pointer = canvas.getScenePoint(opt.e);
30
+ if (opt.target || canvas.getActiveObject())
31
+ return;
32
+ // Text tool
33
+ if (tool === "text") {
34
+ addText(canvas, {
35
+ x: pointer.x,
36
+ y: pointer.y,
37
+ fontSize: options.text.fontSize,
38
+ fontFamily: options.text.fontFamily,
39
+ fontWeight: options.text.fontWeight,
40
+ color: options.text.color,
41
+ textAlign: options.text.textAlign,
84
42
  });
85
- break;
86
- case "line":
87
- shape = new Line([pointer.x, pointer.y, pointer.x, pointer.y], {
88
- stroke: toolOptions.line.strokeColor,
89
- strokeWidth: toolOptions.line.strokeWidth,
90
- strokeDashArray: toolOptions.line.strokeDashArray || undefined,
91
- selectable: false,
92
- });
93
- break;
94
- case "arrow":
95
- shape = new Arrow([pointer.x, pointer.y, pointer.x, pointer.y], {
96
- stroke: toolOptions.arrow.strokeColor,
97
- strokeWidth: toolOptions.arrow.strokeWidth,
98
- strokeDashArray: toolOptions.arrow.strokeDashArray || undefined,
99
- selectable: false,
100
- });
101
- break;
102
- }
103
- if (shape) {
104
- canvas.add(shape);
105
- currentShapeRef.current = shape;
106
- canvas.renderAll();
107
- }
108
- };
109
- const handleMouseMove = (opt) => {
110
- const canvas = fabricCanvas.current;
111
- if (!canvas)
112
- return;
113
- if (!isDrawingRef.current ||
114
- !currentShapeRef.current ||
115
- !startPointRef.current)
116
- return;
117
- const pointer = canvas.getScenePoint(opt.e);
118
- updateDrawingObject(currentShapeRef.current, activeTool, startPointRef.current, pointer);
119
- canvas.renderAll();
120
- };
121
- const handleMouseUp = () => {
122
- const canvas = fabricCanvas.current;
123
- if (!canvas)
124
- return;
125
- if (!isDrawingRef.current || !currentShapeRef.current)
126
- return;
127
- const shape = currentShapeRef.current;
128
- shape.set({ selectable: true, evented: true });
129
- if (shape instanceof Frame && canvas)
130
- canvas.sendObjectToBack(shape);
131
- addCanvasObject(shape);
132
- suppressHistoryRef.current = false;
133
- pushHistory(JSON.stringify(canvas.toJSON()));
134
- isDrawingRef.current = false;
135
- startPointRef.current = null;
136
- currentShapeRef.current = null;
137
- useWhiteboardStore.getState().setActiveTool("select");
138
- canvas.discardActiveObject();
139
- canvas.renderAll();
140
- };
141
- return { handleMouseDown, handleMouseMove, handleMouseUp };
43
+ pushHistoryRef.current(JSON.stringify(canvas.toJSON()));
44
+ useWhiteboardStore.getState().setActiveTool("select");
45
+ return;
46
+ }
47
+ if (!["rectangle", "circle", "frame", "line", "arrow"].includes(tool))
48
+ return;
49
+ suppressHistoryRef.current = true;
50
+ isDrawingRef.current = true;
51
+ startPointRef.current = { x: pointer.x, y: pointer.y };
52
+ let shape = null;
53
+ switch (tool) {
54
+ case "rectangle":
55
+ shape = new Rect({
56
+ left: pointer.x, top: pointer.y, width: 1, height: 1,
57
+ fill: options.rectangle.fillColor === "transparent" ? "transparent" : options.rectangle.fillColor,
58
+ stroke: options.rectangle.strokeColor,
59
+ strokeWidth: options.rectangle.strokeWidth,
60
+ strokeDashArray: calculateDashArray(options.rectangle.strokeDashArray, options.rectangle.strokeWidth),
61
+ rx: 5, ry: 5,
62
+ strokeLineCap: "round", strokeLineJoin: "round",
63
+ selectable: false,
64
+ });
65
+ break;
66
+ case "circle":
67
+ shape = new Circle({
68
+ left: pointer.x, top: pointer.y, radius: 1,
69
+ fill: options.circle.fillColor === "transparent" ? "transparent" : options.circle.fillColor,
70
+ stroke: options.circle.strokeColor,
71
+ strokeWidth: options.circle.strokeWidth,
72
+ strokeDashArray: options.circle.strokeDashArray ? [...options.circle.strokeDashArray] : undefined,
73
+ selectable: false,
74
+ });
75
+ break;
76
+ case "frame":
77
+ shape = new Frame({
78
+ left: pointer.x, top: pointer.y, width: 1, height: 1,
79
+ stroke: options.frame.strokeColor,
80
+ strokeWidth: options.frame.strokeWidth,
81
+ strokeDashArray: options.frame.strokeDashArray ? [...options.frame.strokeDashArray] : undefined,
82
+ fill: options.frame.fillColor,
83
+ selectable: false,
84
+ });
85
+ break;
86
+ case "line":
87
+ shape = new Line([pointer.x, pointer.y, pointer.x, pointer.y], {
88
+ stroke: options.line.strokeColor,
89
+ strokeWidth: options.line.strokeWidth,
90
+ strokeDashArray: options.line.strokeDashArray || undefined,
91
+ selectable: false,
92
+ });
93
+ break;
94
+ case "arrow":
95
+ shape = new Arrow([pointer.x, pointer.y, pointer.x, pointer.y], {
96
+ stroke: options.arrow.strokeColor,
97
+ strokeWidth: options.arrow.strokeWidth,
98
+ strokeDashArray: options.arrow.strokeDashArray || undefined,
99
+ selectable: false,
100
+ });
101
+ break;
102
+ }
103
+ if (shape) {
104
+ canvas.add(shape);
105
+ currentShapeRef.current = shape;
106
+ // ── PERF FIX 2: requestRenderAll instead of renderAll ─────────────
107
+ // renderAll() is synchronous — blocks the main thread immediately.
108
+ // requestRenderAll() schedules the paint on Fabric's rAF loop.
109
+ canvas.requestRenderAll();
110
+ }
111
+ },
112
+ handleMouseMove: (opt) => {
113
+ const canvas = fabricCanvas.current;
114
+ if (!canvas || !isDrawingRef.current || !currentShapeRef.current || !startPointRef.current)
115
+ return;
116
+ const pointer = canvas.getScenePoint(opt.e);
117
+ updateDrawingObject(currentShapeRef.current, activeToolRef.current, startPointRef.current, pointer);
118
+ // ── PERF FIX 3: requestRenderAll instead of renderAll ─────────────────
119
+ // This fires on every mousemove during drawing — the single most frequent
120
+ // call site. Synchronous renderAll() here directly causes frame drops.
121
+ canvas.requestRenderAll();
122
+ },
123
+ handleMouseUp: () => {
124
+ const canvas = fabricCanvas.current;
125
+ if (!canvas || !isDrawingRef.current || !currentShapeRef.current)
126
+ return;
127
+ const shape = currentShapeRef.current;
128
+ shape.set({ selectable: true, evented: true });
129
+ if (shape instanceof Frame)
130
+ canvas.sendObjectToBack(shape);
131
+ addCanvasObjectRef.current(shape);
132
+ suppressHistoryRef.current = false;
133
+ pushHistoryRef.current(JSON.stringify(canvas.toJSON()));
134
+ isDrawingRef.current = false;
135
+ startPointRef.current = null;
136
+ currentShapeRef.current = null;
137
+ useWhiteboardStore.getState().setActiveTool("select");
138
+ canvas.discardActiveObject();
139
+ // ── PERF FIX 4: Single requestRenderAll instead of two renderAll() ────
140
+ // Old code called renderAll() twice (after discardActiveObject + at end).
141
+ // One requestRenderAll() covers both.
142
+ canvas.requestRenderAll();
143
+ },
144
+ });
145
+ // Return stable object reference — identity never changes across renders
146
+ return handlersRef.current;
142
147
  };
@@ -1 +1 @@
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,SAoIb,CAAC"}
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,15 +1,5 @@
1
- import { useEffect, useRef } from "react";
1
+ import { useEffect } from "react";
2
2
  export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport, }) => {
3
- // ── PERF FIX 1: All mutable values in refs — effect registers ONCE ────────
4
- // Old deps: [activeTool, handleZoom, setCanvasViewport, fabricCanvas]
5
- // handleZoom and setCanvasViewport are new references every render →
6
- // all 3 listeners re-registered on every render.
7
- const activeToolRef = useRef(activeTool);
8
- const handleZoomRef = useRef(handleZoom);
9
- const setViewportRef = useRef(setCanvasViewport);
10
- activeToolRef.current = activeTool;
11
- handleZoomRef.current = handleZoom;
12
- setViewportRef.current = setCanvasViewport;
13
3
  useEffect(() => {
14
4
  const canvas = fabricCanvas.current;
15
5
  if (!canvas)
@@ -20,18 +10,17 @@ export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport
20
10
  let lastX = 0;
21
11
  let lastY = 0;
22
12
  let lastTouchDistance = 0;
23
- let rafId = null;
24
13
  const onDown = (opt) => {
25
- if (activeToolRef.current !== "pan")
14
+ if (activeTool !== "pan")
26
15
  return;
27
16
  const e = opt.e;
28
- // Pinch zoom init
29
- if (e.touches?.length === 2) {
17
+ // Pinch initialization
18
+ if (e.touches && e.touches.length === 2) {
30
19
  isPanning = false;
31
20
  lastTouchDistance = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
32
21
  return;
33
22
  }
34
- // Pan init
23
+ // Pan initialization
35
24
  const pointer = e.touches ? e.touches[0] : e;
36
25
  isPanning = true;
37
26
  lastX = pointer.clientX;
@@ -39,63 +28,45 @@ export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport
39
28
  canvas.setCursor("grabbing");
40
29
  };
41
30
  const onMove = (opt) => {
42
- if (activeToolRef.current !== "pan")
31
+ if (activeTool !== "pan")
43
32
  return;
44
33
  const e = opt.e;
45
- // ── PERF FIX 2: rAF throttle — pan fires at raw mouse rate (200+/sec)
46
- // Without throttling, every mousemove directly mutates VPT and calls
47
- // requestRenderAll, saturating the main thread.
48
- if (rafId !== null)
49
- return; // skip if a frame is already queued
50
- // Pinch zoom
51
- if (e.touches?.length === 2) {
34
+ // Handle pinch zoom (two fingers)
35
+ if (e.touches && e.touches.length === 2) {
52
36
  const currentDistance = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
53
37
  if (lastTouchDistance > 0) {
38
+ const zoom = canvas.getZoom();
54
39
  const delta = (currentDistance - lastTouchDistance) * 0.01;
40
+ const newZoom = zoom + delta;
55
41
  const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
56
42
  const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
57
43
  const rect = canvasEl.getBoundingClientRect();
58
- handleZoomRef.current(canvas.getZoom() + delta, { x: midX - rect.left, y: midY - rect.top });
44
+ handleZoom(newZoom, { x: midX - rect.left, y: midY - rect.top });
59
45
  }
60
46
  lastTouchDistance = currentDistance;
61
47
  return;
62
48
  }
63
- if (!isPanning)
64
- return;
65
- const pointer = e.touches ? e.touches[0] : e;
66
- const dx = pointer.clientX - lastX;
67
- const dy = pointer.clientY - lastY;
68
- lastX = pointer.clientX;
69
- lastY = pointer.clientY;
70
- // Schedule the actual VPT mutation on next animation frame
71
- rafId = requestAnimationFrame(() => {
72
- rafId = null;
49
+ // Handle panning (one finger or mouse)
50
+ if (isPanning) {
51
+ const pointer = e.touches ? e.touches[0] : e;
73
52
  const vpt = canvas.viewportTransform;
74
- if (!vpt)
75
- return;
76
- vpt[4] += dx;
77
- vpt[5] += dy;
78
- canvas.requestRenderAll();
79
- // ── PERF FIX 3: Do NOT call setCanvasViewport here ────────────────
80
- // Calling setCanvasViewport per frame triggers React re-renders at
81
- // 60fps, cascading to CanvasOverlayLayer re-rendering all HTML nodes.
82
- // Viewport state is only flushed ONCE on mouse:up (see onUp below).
83
- });
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
+ }
84
61
  };
85
62
  const onUp = () => {
86
- if (rafId !== null) {
87
- cancelAnimationFrame(rafId);
88
- rafId = null;
63
+ const vpt = canvas.viewportTransform;
64
+ if (vpt) {
65
+ setCanvasViewport({ x: vpt[4], y: vpt[5] });
89
66
  }
90
67
  isPanning = false;
91
68
  lastTouchDistance = 0;
92
- canvas.setCursor(activeToolRef.current === "pan" ? "grab" : "default");
93
- // ── Flush React state ONCE per gesture end ────────────────────────────
94
- // HTML overlay nodes only need to reposition when the pan is complete.
95
- // One setCanvasViewport call here vs hundreds during the gesture.
96
- const vpt = canvas.viewportTransform;
97
- if (vpt)
98
- setViewportRef.current({ x: vpt[4], y: vpt[5] });
69
+ canvas.setCursor(activeTool === "pan" ? "grab" : "default");
99
70
  };
100
71
  canvas.on("mouse:down", onDown);
101
72
  canvas.on("mouse:move", onMove);
@@ -104,9 +75,6 @@ export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport
104
75
  canvas.off("mouse:down", onDown);
105
76
  canvas.off("mouse:move", onMove);
106
77
  canvas.off("mouse:up", onUp);
107
- if (rafId !== null)
108
- cancelAnimationFrame(rafId);
109
78
  };
110
- // ── PERF FIX 4: Empty deps — registered once, reads everything via refs ───
111
- }, [fabricCanvas]);
79
+ }, [activeTool, handleZoom, setCanvasViewport, fabricCanvas]);
112
80
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.18.0",
3
+ "version": "2.19.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",