@mhamz.01/easyflow-whiteboard 2.25.0 → 2.28.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;CAC/C;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,2CAoiBzB"}
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;CAC/C;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,2CAgiBzB"}
@@ -92,30 +92,52 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
92
92
  const target = e.transform?.target || e.target;
93
93
  if (!target)
94
94
  return;
95
- // 1. Calculate delta in raw Scene Coordinates
96
- // We do NOT divide by zoom here because target.left/top are world units.
97
95
  const deltaX = target.left - (target._prevLeft ?? target.left);
98
96
  const deltaY = target.top - (target._prevTop ?? target.top);
99
97
  target._prevLeft = target.left;
100
98
  target._prevTop = target.top;
101
99
  if (deltaX === 0 && deltaY === 0)
102
100
  return;
103
- // 2. Apply the raw delta to HTML items
104
- setLocalTasks((prev) => prev.map((t) => (selectedIds.has(t.id) ? { ...t, x: t.x + deltaX, y: t.y + deltaY } : t)));
105
- setLocalDocuments((prev) => prev.map((d) => (selectedIds.has(d.id) ? { ...d, x: d.x + deltaX, y: d.y + deltaY } : d)));
101
+ setLocalTasks((prev) => prev.map((t) => selectedIds.has(t.id) ? { ...t, x: t.x + deltaX, y: t.y + deltaY } : t));
102
+ setLocalDocuments((prev) => prev.map((d) => selectedIds.has(d.id) ? { ...d, x: d.x + deltaX, y: d.y + deltaY } : d));
106
103
  };
107
104
  const handleMouseDown = (e) => {
108
105
  const target = e.target;
106
+ // Always snapshot for delta tracking
109
107
  if (target) {
110
108
  target._prevLeft = target.left;
111
109
  target._prevTop = target.top;
112
110
  }
111
+ if (!target) {
112
+ // Clicked empty canvas — deselect HTML nodes
113
+ setSelectedIds(new Set());
114
+ return;
115
+ }
116
+ // Clicked a Fabric object — only deselect HTML nodes if that object
117
+ // is NOT already part of the active selection (i.e. not a group drag)
118
+ const activeObjects = canvas.getActiveObjects();
119
+ const isTargetAlreadySelected = activeObjects.includes(target);
120
+ if (!isTargetAlreadySelected) {
121
+ setSelectedIds(new Set());
122
+ }
123
+ // If already selected: preserve HTML selectedIds for mixed-group drag
124
+ };
125
+ const handleFabricSelection = () => {
126
+ // Only clear HTML selection when no HTML drag is active
127
+ // Prevents clearing during programmatic selection updates mid-drag
128
+ if (!dragStateRef.current.isDragging) {
129
+ setSelectedIds(new Set());
130
+ }
113
131
  };
114
132
  canvas.on("object:moving", handleObjectMoving);
115
133
  canvas.on("mouse:down", handleMouseDown);
134
+ canvas.on("selection:created", handleFabricSelection);
135
+ canvas.on("selection:updated", handleFabricSelection);
116
136
  return () => {
117
137
  canvas.off("object:moving", handleObjectMoving);
118
138
  canvas.off("mouse:down", handleMouseDown);
139
+ canvas.off("selection:created", handleFabricSelection);
140
+ canvas.off("selection:updated", handleFabricSelection);
119
141
  };
120
142
  }, [canvasZoom, selectedIds, fabricCanvas]);
121
143
  // ── Helpers ─────────────────────────────────────────────────────────────────
@@ -228,7 +250,7 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
228
250
  offsetX: worldOffsetX, // Now stored as World Units
229
251
  offsetY: worldOffsetY, // Now stored as World Units
230
252
  };
231
- if (!selectedIds.has(itemId)) {
253
+ if (!selectedIds.has(itemId) && dragStateRef.current.itemIds.length === 0) {
232
254
  setSelectedIds(new Set([itemId]));
233
255
  }
234
256
  // 11. Trigger UI states
@@ -370,22 +392,6 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
370
392
  window.addEventListener("keydown", handleKeyDown);
371
393
  return () => window.removeEventListener("keydown", handleKeyDown);
372
394
  }, [localTasks, localDocuments, selectedIds, onTasksUpdate, onDocumentsUpdate]);
373
- // Deselection helper
374
- useEffect(() => {
375
- const canvas = fabricCanvas?.current;
376
- if (!canvas)
377
- return;
378
- const handleSelectionCleared = (e) => {
379
- // If Fabric clears its selection, we clear ours.
380
- // e.deselected contains the objects that were just dropped.
381
- setSelectedIds(new Set());
382
- };
383
- // This handles clicking the 'empty' part of the canvas
384
- canvas.on("selection:cleared", handleSelectionCleared);
385
- return () => {
386
- canvas.off("selection:cleared", handleSelectionCleared);
387
- };
388
- }, [fabricCanvas]);
389
395
  // ── Render helper ────────────────────────────────────────────────────────────
390
396
  const renderItem = (id, x, y, children) => {
391
397
  const screenX = x * canvasZoom;
@@ -409,15 +415,8 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
409
415
  }, children: children }, id));
410
416
  };
411
417
  return (_jsx("div", { ref: overlayRef, className: "absolute inset-0 pointer-events-none", style: { zIndex: 50 }, onWheel: handleOverlayWheel, onClick: (e) => {
412
- // If the user clicks the empty overlay area:
413
- if (e.target === e.currentTarget) {
414
- if (fabricCanvas?.current) {
415
- // Tell Fabric to clear selection
416
- fabricCanvas.current.discardActiveObject();
417
- fabricCanvas.current.requestRenderAll();
418
- // The 'selection:cleared' event listener will handle the React state
419
- }
420
- }
418
+ if (e.target === e.currentTarget)
419
+ setSelectedIds(new Set());
421
420
  }, children: _jsxs("div", { className: "absolute top-0 left-0 pointer-events-none", style: {
422
421
  transform: `translate(${canvasViewport.x}px, ${canvasViewport.y}px)`,
423
422
  transformOrigin: "top left",
@@ -1 +1 @@
1
- {"version":3,"file":"whiteboard-toolbar.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/whiteboard-toolbar.tsx"],"names":[],"mappings":"AAEA,OAAa,EAAqB,gBAAgB,EAAY,MAAM,OAAO,CAAC;AAK5E,OAAO,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAOtC,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC7F;AACD,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5D;AAkBD,UAAU,sBAAsB;IAC9B,YAAY,EAAE,gBAAgB,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,cAAc,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACjD;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,sBAAsB,2CAqM3H"}
1
+ {"version":3,"file":"whiteboard-toolbar.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/whiteboard-toolbar.tsx"],"names":[],"mappings":"AAEA,OAAa,EAAqB,gBAAgB,EAAY,MAAM,OAAO,CAAC;AAK5E,OAAO,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAOtC,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC7F;AACD,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5D;AAkBD,UAAU,sBAAsB;IAC9B,YAAY,EAAE,gBAAgB,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,cAAc,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACjD;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,sBAAsB,2CAkM3H"}
@@ -84,10 +84,6 @@ export default function WhiteboardToolbar({ fabricCanvas, isRestoringRef, onAddT
84
84
  return;
85
85
  canvas.discardActiveObject();
86
86
  canvas.renderAll();
87
- if (fabricCanvas?.current) {
88
- fabricCanvas.current.discardActiveObject();
89
- fabricCanvas.current.requestRenderAll();
90
- }
91
87
  if (toolId === "undo") {
92
88
  const state = undo();
93
89
  if (state)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.25.0",
3
+ "version": "2.28.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",