@mhamz.01/easyflow-whiteboard 2.47.0 → 2.48.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":"useCanvasInit.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasInit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAK,MAAM,CAAC;IACnB,KAAK,CAAC,EAAM,GAAG,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;CACnB;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAW,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACxD,eAAe,EAAK,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAU,MAAM,CAAC;IAC3B,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,cAAc,EAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,WAAW,EAAS,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,EAAY,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC3C,YAAY,EAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAG1C,WAAW,CAAC,EAAQ,qBAAqB,CAAC;CAC3C;AAID,eAAO,MAAM,aAAa,GAAI,mIAU3B,kBAAkB,SA+FpB,CAAC"}
1
+ {"version":3,"file":"useCanvasInit.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasInit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAK,MAAM,CAAC;IACnB,KAAK,CAAC,EAAM,GAAG,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;CACnB;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAW,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACxD,eAAe,EAAK,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAU,MAAM,CAAC;IAC3B,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,cAAc,EAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,WAAW,EAAS,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,EAAY,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC3C,YAAY,EAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAQ,qBAAqB,CAAC;CAC3C;AAID,eAAO,MAAM,aAAa,GAAI,mIAU3B,kBAAkB,SAuHpB,CAAC"}
@@ -4,7 +4,6 @@ import { initializeFabricCanvas, addWelcomeContent } from "../lib/fabric-utils";
4
4
  // ─── Hook ─────────────────────────────────────────────────────────────────────
5
5
  export const useCanvasInit = ({ canvasRef, fabricCanvasRef, activeTool, suppressHistoryRef, isRestoringRef, pushHistory, setTasks, setDocuments, initialData, }) => {
6
6
  useEffect(() => {
7
- // Guard: prevent re-initialization if canvas already exists
8
7
  if (!canvasRef.current || fabricCanvasRef.current)
9
8
  return;
10
9
  const canvas = new Canvas(canvasRef.current, {
@@ -20,31 +19,44 @@ export const useCanvasInit = ({ canvasRef, fabricCanvasRef, activeTool, suppress
20
19
  initializeFabricCanvas(canvas);
21
20
  const canvasElement = canvas.getElement();
22
21
  canvasElement.style.touchAction = "none";
23
- // ── Hydrate from initialData (replaces localStorage.getItem) ─────────────
24
- // Shape is identical to what usePersistence sends via onSave —
25
- // consumer reads from DB and passes straight back in as initialData.
22
+ // Tracks whether cleanup has run prevents hydrateCanvas from
23
+ // running after canvas.dispose() if rAFs fire post-unmount
24
+ let disposed = false;
25
+ let raf1;
26
+ let raf2;
26
27
  const hydrateCanvas = async () => {
28
+ // Guard 1: unmounted before rAF fired — canvas already disposed
29
+ if (disposed)
30
+ return;
31
+ // Guard 2: check Fabric's internal ctx directly.
32
+ // canvas.getContext() is NOT a valid Fabric v6 API — always undefined.
33
+ // contextContainer is the actual 2D ctx Fabric uses internally.
34
+ // If undefined, Fabric hasn't finished attaching to the DOM yet.
35
+ const fabricCtx = canvas.contextContainer;
36
+ if (!fabricCtx) {
37
+ console.warn("[useCanvasInit] Fabric ctx not ready, skipping hydration");
38
+ return;
39
+ }
27
40
  const hasCanvas = !!initialData?.canvas;
28
41
  const hasNodes = !!(initialData?.tasks || initialData?.documents);
29
42
  if (hasCanvas || hasNodes) {
30
43
  isRestoringRef.current = true;
31
44
  if (hasCanvas) {
32
45
  try {
33
- // Guard: ensure the canvas context is available before loading
34
- if (!canvas.getContext()) {
35
- console.warn("[useCanvasInit] Canvas context not ready, skipping load");
36
- return;
37
- }
38
46
  await canvas.loadFromJSON(JSON.parse(initialData.canvas));
47
+ if (disposed)
48
+ return; // re-check after async gap — dispose may have run during await
39
49
  canvas.renderAll();
40
50
  pushHistory(JSON.stringify(canvas.toJSON()));
41
51
  }
42
52
  catch (err) {
43
53
  console.error("[useCanvasInit] Canvas load failed:", err);
44
- addWelcomeContent(canvas, "https://res.cloudinary.com/dqyjffc9q/image/upload/v1773420161/easyflow-logo_nbpfd7.png");
54
+ if (!disposed) {
55
+ addWelcomeContent(canvas, "https://res.cloudinary.com/dqyjffc9q/image/upload/v1773420161/easyflow-logo_nbpfd7.png");
56
+ }
45
57
  }
46
58
  }
47
- if (hasNodes) {
59
+ if (hasNodes && !disposed) {
48
60
  try {
49
61
  if (initialData?.tasks)
50
62
  setTasks(initialData.tasks);
@@ -58,13 +70,17 @@ export const useCanvasInit = ({ canvasRef, fabricCanvasRef, activeTool, suppress
58
70
  isRestoringRef.current = false;
59
71
  }
60
72
  else {
61
- addWelcomeContent(canvas, "https://res.cloudinary.com/dqyjffc9q/image/upload/v1773420161/easyflow-logo_nbpfd7.png");
62
- pushHistory(JSON.stringify(canvas.toJSON()));
73
+ if (!disposed) {
74
+ addWelcomeContent(canvas, "https://res.cloudinary.com/dqyjffc9q/image/upload/v1773420161/easyflow-logo_nbpfd7.png");
75
+ pushHistory(JSON.stringify(canvas.toJSON()));
76
+ }
63
77
  }
64
78
  };
65
- // Defer by one tick so Fabric's internal canvas context is fully ready
66
- requestAnimationFrame(() => {
67
- requestAnimationFrame(() => {
79
+ // Double rAF ensures Fabric's internal ctx is fully attached:
80
+ // - raf1: fires when browser is about to paint (Fabric constructor done)
81
+ // - raf2: fires after first paint (contextContainer guaranteed attached)
82
+ raf1 = requestAnimationFrame(() => {
83
+ raf2 = requestAnimationFrame(() => {
68
84
  hydrateCanvas();
69
85
  });
70
86
  });
@@ -83,11 +99,17 @@ export const useCanvasInit = ({ canvasRef, fabricCanvasRef, activeTool, suppress
83
99
  canvasElement.addEventListener("touchmove", preventDefaults, { passive: false });
84
100
  // ── Cleanup ───────────────────────────────────────────────────────────────
85
101
  return () => {
102
+ // Set disposed FIRST and cancel rAFs BEFORE canvas.dispose().
103
+ // This ensures hydrateCanvas cannot run on an already-disposed canvas
104
+ // which is what causes the ctx/clearRect errors.
105
+ disposed = true;
106
+ cancelAnimationFrame(raf1);
107
+ cancelAnimationFrame(raf2);
86
108
  window.removeEventListener("resize", handleResize);
87
109
  canvasElement.removeEventListener("touchstart", preventDefaults);
88
110
  canvasElement.removeEventListener("touchmove", preventDefaults);
89
111
  canvas.dispose();
90
112
  fabricCanvasRef.current = null;
91
113
  };
92
- }, []); // Empty — must never re-run, initialData read once on mount
114
+ }, []);
93
115
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.47.0",
3
+ "version": "2.48.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",