@mhamz.01/easyflow-whiteboard 2.70.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,2CAulBzB"}
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"}
@@ -22,15 +22,10 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
22
22
  offsetX: 0,
23
23
  offsetY: 0,
24
24
  });
25
- const finalPositionsRef = useRef(null);
26
25
  const rafIdRef = useRef(null);
27
26
  const overlayRef = useRef(null);
28
- const localTasksRef = useRef(localTasks);
29
- const localDocumentsRef = useRef(localDocuments);
30
27
  const selectedIdsRef = useRef(selectedIds);
31
28
  selectedIdsRef.current = selectedIds;
32
- localTasksRef.current = localTasks;
33
- localDocumentsRef.current = localDocuments;
34
29
  // ── Sync props → local state ────────────────────────────────────────────────
35
30
  useEffect(() => { setLocalTasks(tasks); }, [tasks]);
36
31
  useEffect(() => { setLocalDocuments(documents); }, [documents]);
@@ -236,8 +231,8 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
236
231
  // 3. Determine which items are being dragged
237
232
  // selection update DOES NOT trigger before drag snapshot
238
233
  let itemsToDrag;
239
- if (selectedIdsRef.current.has(itemId)) {
240
- itemsToDrag = Array.from(selectedIdsRef.current);
234
+ if (selectedIds.has(itemId)) {
235
+ itemsToDrag = Array.from(selectedIds);
241
236
  }
242
237
  else {
243
238
  itemsToDrag = [itemId];
@@ -281,7 +276,7 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
281
276
  offsetX: worldOffsetX, // Now stored as World Units
282
277
  offsetY: worldOffsetY, // Now stored as World Units
283
278
  };
284
- if (!selectedIdsRef.current.has(itemId) && dragStateRef.current.itemIds.length === 0) {
279
+ if (!selectedIds.has(itemId) && dragStateRef.current.itemIds.length === 0) {
285
280
  setSelectedIds(new Set([itemId]));
286
281
  }
287
282
  // 11. Trigger UI states
@@ -330,22 +325,16 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
330
325
  const deltaX = newWorldX - firstStart.x;
331
326
  const deltaY = newWorldY - firstStart.y;
332
327
  // 6. Update HTML Nodes (Batching these into one state update)
333
- setLocalTasks((prev) => {
334
- const next = prev.map((t) => itemIds.includes(t.id)
335
- ? { ...t, x: (startPositions.get(t.id)?.x ?? t.x) + deltaX,
336
- y: (startPositions.get(t.id)?.y ?? t.y) + deltaY }
337
- : t);
338
- localTasksRef.current = next; // write-through: ref always has latest
339
- return next;
340
- });
341
- setLocalDocuments((prev) => {
342
- const next = prev.map((d) => itemIds.includes(d.id)
343
- ? { ...d, x: (startPositions.get(d.id)?.x ?? d.x) + deltaX,
344
- y: (startPositions.get(d.id)?.y ?? d.y) + deltaY }
345
- : d);
346
- localDocumentsRef.current = next; // ← write-through
347
- return next;
348
- });
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));
349
338
  // 7. Sync Fabric Objects (Imperative update for performance)
350
339
  canvasObjectsStartPos.forEach((startPos, obj) => {
351
340
  obj.set({
@@ -354,29 +343,20 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
354
343
  });
355
344
  obj.setCoords(); // Required for selection/intersection accuracy
356
345
  });
357
- finalPositionsRef.current = {
358
- tasks: localTasksRef.current, // will be updated by React after setState flushes
359
- documents: localDocumentsRef.current,
360
- };
361
346
  // 8. Single render call for all Fabric changes
362
347
  canvas.requestRenderAll();
363
348
  });
364
349
  };
365
350
  const handleEnd = () => {
366
- if (rafIdRef.current !== null) {
351
+ if (rafIdRef.current !== null)
367
352
  cancelAnimationFrame(rafIdRef.current);
368
- rafIdRef.current = null;
369
- }
370
353
  dragStateRef.current.isDragging = false;
371
354
  setDragging(null);
372
355
  document.body.style.cursor = "";
373
356
  document.body.style.userSelect = "";
374
357
  document.body.style.touchAction = "";
375
- // ✅ FIX 1+3: Read from live refs — never from the stale closure.
376
- // localTasksRef is kept in sync on every render, so this is always
377
- // the position after the last committed setState, not the t=0 snapshot.
378
- onTasksUpdate?.(localTasksRef.current);
379
- onDocumentsUpdate?.(localDocumentsRef.current);
358
+ onTasksUpdate?.(localTasks);
359
+ onDocumentsUpdate?.(localDocuments);
380
360
  };
381
361
  window.addEventListener("mousemove", handleMove, { passive: false });
382
362
  window.addEventListener("mouseup", handleEnd);
@@ -438,27 +418,33 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
438
418
  window.addEventListener("keydown", handleKeyDown);
439
419
  return () => window.removeEventListener("keydown", handleKeyDown);
440
420
  }, [localTasks, localDocuments, selectedIds, onTasksUpdate, onDocumentsUpdate]);
441
- const getAbsoluteTransform = (x, y) => {
442
- // We calculate the screen position in one go, matching Fabric's internal VPT logic
443
- const screenX = x * canvasZoom + canvasViewport.x;
444
- const screenY = y * canvasZoom + canvasViewport.y;
445
- return `translate3d(${screenX}px, ${screenY}px, 0) scale(${canvasZoom})`;
446
- };
447
421
  // ── Render helper ────────────────────────────────────────────────────────────
448
422
  const renderItem = (id, x, y, children) => {
423
+ const screenX = x * canvasZoom;
424
+ const screenY = y * canvasZoom;
425
+ // 1. Detect if the user is interacting with the canvas at all
426
+ // 'dragging' is your existing state.
427
+ // You might want to pass 'isZooming' or 'isPanning' from your main canvas component here.
449
428
  const isDragging = dragging?.itemIds.includes(id);
450
429
  return (_jsx("div", { className: "pointer-events-auto absolute", style: {
451
430
  left: 0,
452
431
  top: 0,
453
- // FIX: Combined Transform prevents the "Pan vs Zoom" lag
454
- transform: getAbsoluteTransform(x, y),
432
+ // 2. Use translate3d for GPU performance
433
+ transform: `translate3d(${screenX}px, ${screenY}px, 0) scale(${canvasZoom})`,
455
434
  transformOrigin: "top left",
456
- transition: "none", // Critical: prevents animation-induced jitter
435
+ // 3. THE FIX: Remove transition entirely during any viewport change
436
+ // Any 'ease' during zoom causes the "shaking" behavior.
437
+ transition: "none",
438
+ // 4. Optimization
457
439
  willChange: "transform",
458
440
  zIndex: isDragging ? 1000 : 1,
459
441
  }, children: children }, id));
460
442
  };
461
- return (_jsxs("div", { ref: overlayRef, className: "absolute inset-0 pointer-events-none overflow-hidden", style: { zIndex: 50 }, children: [localTasks.map((task) => renderItem(task.id, task.x, task.y, _jsx(TaskNode, { ...task, isSelected: selectedIds.has(task.id), onSelect: handleSelect, onDragStart: handleDragStart, onStatusChange: handleStatusChange, zoom: 1 }))), localDocuments.map((doc) => renderItem(doc.id, doc.x, doc.y, _jsx(DocumentNode, { ...doc, isSelected: selectedIds.has(doc.id), onSelect: handleSelect, onDragStart: handleDragStart })))] })
462
- // </div>
463
- );
443
+ return (_jsx("div", { ref: overlayRef, className: "absolute inset-0 pointer-events-none", style: { zIndex: 50 }, onWheel: handleOverlayWheel, onClick: (e) => {
444
+ if (e.target === e.currentTarget)
445
+ setSelectedIds(new Set());
446
+ }, children: _jsxs("div", { className: "absolute top-0 left-0 pointer-events-none", style: {
447
+ transform: `translate(${canvasViewport.x}px, ${canvasViewport.y}px)`,
448
+ transformOrigin: "top left",
449
+ }, children: [localTasks.map((task) => renderItem(task.id, task.x, task.y, _jsx(TaskNode, { ...task, isSelected: selectedIds.has(task.id), onSelect: handleSelect, onDragStart: handleDragStart, onStatusChange: handleStatusChange, zoom: 1 }))), localDocuments.map((doc) => renderItem(doc.id, doc.x, doc.y, _jsx(DocumentNode, { ...doc, isSelected: selectedIds.has(doc.id), onSelect: handleSelect, onDragStart: handleDragStart })))] }) }));
464
450
  }
@@ -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.70.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",