@mhamz.01/easyflow-whiteboard 2.71.0 → 2.72.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":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAS9C,MAAM,WAAW,IAAI;IACnB,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,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,UAAU,uBAAuB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAaD,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,UAAc,EACd,cAA+B,EAC/B,YAAmB,EACnB,qBAA0B,EAC1B,YAAY,GACb,EAAE,uBAAuB,2CA6jBzB"}
1
+ {"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAS9C,MAAM,WAAW,IAAI;IACnB,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,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,UAAU,uBAAuB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAaD,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,UAAc,EACd,cAA+B,EAC/B,YAAmB,EACnB,qBAA0B,EAC1B,YAAY,GACb,EAAE,uBAAuB,2CAikBzB"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect, useRef, useCallback } from "react";
3
+ import { useState, useEffect, useRef } from "react";
4
4
  import TaskNode from "./custom-node";
5
5
  import DocumentNode from "./document-node";
6
6
  // ─── Component ────────────────────────────────────────────────────────────────
@@ -22,19 +22,9 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
22
22
  offsetX: 0,
23
23
  offsetY: 0,
24
24
  });
25
- // 2. High-Frequency Refs (Bypasses React Render Cycle)
26
- const taskRefs = useRef(new Map());
27
- const docRefs = useRef(new Map());
28
- const localTasksRef = useRef(tasks);
29
- const localDocsRef = useRef(documents);
30
- const selectedIdsRef = useRef(new Set());
31
25
  const rafIdRef = useRef(null);
32
26
  const overlayRef = useRef(null);
33
- // const selectedIdsRef = useRef<Set<string>>(selectedIds);
34
- selectedIdsRef.current = selectedIds;
35
- // Sync Refs immediately
36
- localTasksRef.current = localTasks;
37
- localDocsRef.current = localDocuments;
27
+ const selectedIdsRef = useRef(selectedIds);
38
28
  selectedIdsRef.current = selectedIds;
39
29
  // ── Sync props → local state ────────────────────────────────────────────────
40
30
  useEffect(() => { setLocalTasks(tasks); }, [tasks]);
@@ -118,17 +108,6 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
118
108
  };
119
109
  }, [fabricCanvas, canvasZoom, canvasReady]); // Re-bind when zoom changes to keep closure fresh
120
110
  // ── Fabric → Overlay Sync (Fixes Dragging from Fabric area) ──────────────────
121
- const updateNodeStyles = useCallback((id, x, y, zoom, vp) => {
122
- const el = taskRefs.current.get(id) || docRefs.current.get(id);
123
- if (!el)
124
- return;
125
- // Calculate final screen position
126
- const screenX = x * zoom + vp.x;
127
- const screenY = y * zoom + vp.y;
128
- // Update via CSS Variables or Direct Transform
129
- // This is 10x faster than a React State update
130
- el.style.transform = `translate3d(${screenX}px, ${screenY}px, 0) scale(${zoom})`;
131
- }, []);
132
111
  useEffect(() => {
133
112
  const canvas = fabricCanvas?.current;
134
113
  if (!canvas)
@@ -311,38 +290,63 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
311
290
  if (!dragging)
312
291
  return;
313
292
  // Inside the useEffect that watches [dragging, localTasks, localDocuments, fabricCanvas]
314
- const handleMove = useCallback((e) => {
293
+ const handleMove = (e) => {
315
294
  if (!dragStateRef.current.isDragging)
316
295
  return;
296
+ if (e.cancelable)
297
+ e.preventDefault();
317
298
  const pointer = getPointerEvent(e);
318
- requestAnimationFrame(() => {
319
- const { itemIds, startPositions, offsetX, offsetY } = dragStateRef.current;
299
+ // 1. Throttle updates using requestAnimationFrame for 120Hz/144Hz screen support
300
+ if (rafIdRef.current !== null)
301
+ cancelAnimationFrame(rafIdRef.current);
302
+ rafIdRef.current = requestAnimationFrame(() => {
303
+ const { itemIds, startPositions, canvasObjectsStartPos, offsetX, offsetY } = dragStateRef.current;
320
304
  const canvas = fabricCanvas?.current;
321
305
  if (!canvas)
322
306
  return;
307
+ // 2. Read the "Source of Truth" transform from the canvas
323
308
  const vpt = canvas.viewportTransform;
324
- const liveZoom = vpt[0];
325
- const liveVpX = vpt[4];
326
- const liveVpY = vpt[5];
309
+ const liveZoom = vpt[0]; // Scale
310
+ const liveVpX = vpt[4]; // Pan X
311
+ const liveVpY = vpt[5]; // Pan Y
312
+ // 3. Convert current Mouse Screen Position → World Position
327
313
  const currentWorldX = (pointer.clientX - liveVpX) / liveZoom;
328
314
  const currentWorldY = (pointer.clientY - liveVpY) / liveZoom;
329
- const deltaX = (currentWorldX - offsetX) - (startPositions.get(itemIds[0])?.x ?? 0);
330
- const deltaY = (currentWorldY - offsetY) - (startPositions.get(itemIds[0])?.y ?? 0);
331
- // DOM UPDATE (Instant)
332
- itemIds.forEach(id => {
333
- const start = startPositions.get(id);
334
- if (start) {
335
- updateNodeStyles(id, start.x + deltaX, start.y + deltaY, liveZoom, { x: liveVpX, y: liveVpY });
336
- }
337
- });
338
- // FABRIC UPDATE (Batched)
339
- dragStateRef.current.canvasObjectsStartPos.forEach((pos, obj) => {
340
- obj.set({ left: pos.left + deltaX, top: pos.top + deltaY });
341
- obj.setCoords();
315
+ // 4. Calculate where the "Anchor" node should be in World Units
316
+ // (Current Mouse World - Initial World Offset from Start)
317
+ const newWorldX = currentWorldX - offsetX;
318
+ const newWorldY = currentWorldY - offsetY;
319
+ // 5. Calculate the Movement Delta in World Units
320
+ // We compare where the first item started vs where it is now.
321
+ const firstId = itemIds[0];
322
+ const firstStart = startPositions.get(firstId);
323
+ if (!firstStart)
324
+ return;
325
+ const deltaX = newWorldX - firstStart.x;
326
+ const deltaY = newWorldY - firstStart.y;
327
+ // 6. Update HTML Nodes (Batching these into one state update)
328
+ setLocalTasks((prev) => prev.map((t) => itemIds.includes(t.id) ? {
329
+ ...t,
330
+ x: (startPositions.get(t.id)?.x ?? t.x) + deltaX,
331
+ y: (startPositions.get(t.id)?.y ?? t.y) + deltaY,
332
+ } : t));
333
+ setLocalDocuments((prev) => prev.map((d) => itemIds.includes(d.id) ? {
334
+ ...d,
335
+ x: (startPositions.get(d.id)?.x ?? d.x) + deltaX,
336
+ y: (startPositions.get(d.id)?.y ?? d.y) + deltaY,
337
+ } : d));
338
+ // 7. Sync Fabric Objects (Imperative update for performance)
339
+ canvasObjectsStartPos.forEach((startPos, obj) => {
340
+ obj.set({
341
+ left: startPos.left + deltaX,
342
+ top: startPos.top + deltaY,
343
+ });
344
+ obj.setCoords(); // Required for selection/intersection accuracy
342
345
  });
346
+ // 8. Single render call for all Fabric changes
343
347
  canvas.requestRenderAll();
344
348
  });
345
- }, [fabricCanvas, updateNodeStyles]);
349
+ };
346
350
  const handleEnd = () => {
347
351
  if (rafIdRef.current !== null)
348
352
  cancelAnimationFrame(rafIdRef.current);
@@ -6,5 +6,5 @@ import { Slider } from "../../../components/ui/slider";
6
6
  export default function ImageOptions() {
7
7
  const toolOptions = useWhiteboardStore((state) => state.toolOptions);
8
8
  const setToolOption = useWhiteboardStore((state) => state.setToolOption);
9
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { className: "text-xs text-neutral-400", children: "Opacity" }), _jsxs("span", { className: "text-xs text-neutral-500", children: [Math.round(toolOptions.image.opacity * 100), "%"] })] }), _jsx(Slider, { value: [toolOptions.image.opacity], min: 0, max: 1, step: 0.01, onValueChange: ([value]) => setToolOption("image", "opacity", value), className: "w-full" })] }), _jsxs("div", { className: "mt-4 p-3 bg-neutral-800/50 rounded-lg border border-neutral-700", children: [_jsxs("p", { className: "text-xs text-neutral-400", children: ["Click the ", _jsx("strong", { className: "text-neutral-200", children: "Image" }), " button to upload an image from your computer."] }), _jsx("p", { className: "text-xs text-neutral-500 mt-2", children: "Supported formats: JPG, PNG, GIF, WebP" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { className: "text-xs text-neutral-400", children: "Tips" }), _jsxs("ul", { className: "text-xs text-neutral-500 space-y-1 list-disc list-inside", children: [_jsx("li", { children: "Drag corners to resize" }), _jsx("li", { children: "Click and drag to move" }), _jsx("li", { children: "Use Delete key to remove" }), _jsx("li", { children: "Double-click to edit properties" })] })] })] }));
9
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { className: "text-xs text-neutral-400", children: "Opacity" }), _jsxs("span", { className: "text-xs text-neutral-500", children: [Math.round(toolOptions.image.opacity * 100), "%"] })] }), _jsx(Slider, { value: [toolOptions.image.opacity], min: 0, max: 100, step: 1, onValueChange: ([value]) => setToolOption("image", "opacity", value / 100), className: "w-full" })] }), _jsxs("div", { className: "mt-4 p-3 bg-neutral-800/50 rounded-lg border border-neutral-700", children: [_jsxs("p", { className: "text-xs text-neutral-400", children: ["Click the ", _jsx("strong", { className: "text-neutral-200", children: "Image" }), " button to upload an image from your computer."] }), _jsx("p", { className: "text-xs text-neutral-500 mt-2", children: "Supported formats: JPG, PNG, GIF, WebP" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { className: "text-xs text-neutral-400", children: "Tips" }), _jsxs("ul", { className: "text-xs text-neutral-500 space-y-1 list-disc list-inside", children: [_jsx("li", { children: "Drag corners to resize" }), _jsx("li", { children: "Click and drag to move" }), _jsx("li", { children: "Use Delete key to remove" }), _jsx("li", { children: "Double-click to edit properties" })] })] })] }));
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.71.0",
3
+ "version": "2.72.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",