@mhamz.01/easyflow-whiteboard 2.3.0 → 2.4.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;AAM9C,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,2CA2dzB"}
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;AAM9C,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,2CAofzB"}
@@ -172,7 +172,6 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
172
172
  return e;
173
173
  };
174
174
  const handleDragStart = (itemId, e) => {
175
- // Prevent browser scrolling while dragging
176
175
  if (e.cancelable)
177
176
  e.preventDefault();
178
177
  const pointer = getPointerEvent(e);
@@ -184,33 +183,64 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
184
183
  itemsToDrag = [itemId];
185
184
  setSelectedIds(new Set([itemId]));
186
185
  }
186
+ // ── Read VPT first — single source of truth for coordinate space ──
187
+ const vpt = fabricCanvas?.current?.viewportTransform;
188
+ const liveZoom = vpt ? vpt[0] : 1;
189
+ const liveVpX = vpt ? vpt[4] : 0;
190
+ const liveVpY = vpt ? vpt[5] : 0;
191
+ // ── Snapshot world positions from DOM directly ──
192
+ // Avoids stale localTasks/localDocuments state by back-calculating
193
+ // world position from the actual rendered screen position of the element
187
194
  const startPositions = new Map();
188
195
  itemsToDrag.forEach((id) => {
189
- const pos = getItemPosition(id);
190
- if (pos)
191
- startPositions.set(id, pos);
196
+ const el = document.querySelector(`[data-node-id="${id}"]`);
197
+ if (el) {
198
+ const rect = el.getBoundingClientRect();
199
+ // Back-calculate world coords from actual DOM position — always accurate
200
+ const worldX = (rect.left - liveVpX) / liveZoom;
201
+ const worldY = (rect.top - liveVpY) / liveZoom;
202
+ startPositions.set(id, { x: worldX, y: worldY });
203
+ }
204
+ else {
205
+ // Fallback to state if DOM element not found
206
+ const pos = getItemPosition(id);
207
+ if (pos)
208
+ startPositions.set(id, pos);
209
+ }
192
210
  });
193
211
  const canvasObjectsStartPos = new Map();
194
212
  selectedCanvasObjects.forEach((obj) => {
195
213
  canvasObjectsStartPos.set(obj, { left: obj.left || 0, top: obj.top || 0 });
196
214
  });
197
- const clickedPos = getItemPosition(itemId);
198
- if (!clickedPos)
199
- return;
200
- const screenX = clickedPos.x * canvasZoom + canvasViewport.x;
201
- const screenY = clickedPos.y * canvasZoom + canvasViewport.y;
215
+ // ── Offset from pointer to node's top-left in screen space ──
216
+ const clickedEl = document.querySelector(`[data-node-id="${itemId}"]`);
217
+ let offsetX = 0;
218
+ let offsetY = 0;
219
+ if (clickedEl) {
220
+ const rect = clickedEl.getBoundingClientRect();
221
+ offsetX = pointer.clientX - rect.left;
222
+ offsetY = pointer.clientY - rect.top;
223
+ }
224
+ else {
225
+ const clickedPos = getItemPosition(itemId);
226
+ if (!clickedPos)
227
+ return;
228
+ const screenX = clickedPos.x * liveZoom + liveVpX;
229
+ const screenY = clickedPos.y * liveZoom + liveVpY;
230
+ offsetX = pointer.clientX - screenX;
231
+ offsetY = pointer.clientY - screenY;
232
+ }
202
233
  dragStateRef.current = {
203
234
  isDragging: true,
204
235
  itemIds: itemsToDrag,
205
236
  startPositions,
206
237
  canvasObjectsStartPos,
207
- offsetX: pointer.clientX - screenX,
208
- offsetY: pointer.clientY - screenY,
238
+ offsetX,
239
+ offsetY,
209
240
  };
210
241
  setDragging({ itemIds: itemsToDrag });
211
242
  document.body.style.cursor = "grabbing";
212
243
  document.body.style.userSelect = "none";
213
- // Prevents mobile pull-to-refresh
214
244
  document.body.style.touchAction = "none";
215
245
  };
216
246
  // ── Drag move (HTML Node side) ───────────────────────────────────────────────
@@ -220,7 +250,6 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
220
250
  const handleMove = (e) => {
221
251
  if (!dragStateRef.current.isDragging)
222
252
  return;
223
- // Prevent mobile scrolling
224
253
  if (e.cancelable)
225
254
  e.preventDefault();
226
255
  const pointer = getPointerEvent(e);
@@ -232,22 +261,27 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
232
261
  const firstStart = startPositions.get(firstId);
233
262
  if (!firstStart)
234
263
  return;
235
- // Calculate new world coordinates
236
- const newX = (pointer.clientX - offsetX - canvasViewport.x) / canvasZoom;
237
- const newY = (pointer.clientY - offsetY - canvasViewport.y) / canvasZoom;
264
+ // ── Live VPT — no React state, no staleness ──
265
+ const vpt = fabricCanvas?.current?.viewportTransform;
266
+ const liveZoom = vpt ? vpt[0] : 1;
267
+ const liveVpX = vpt ? vpt[4] : 0;
268
+ const liveVpY = vpt ? vpt[5] : 0;
269
+ // pointer - offset = top-left of node in screen space
270
+ // subtract vpX, divide by zoom = world position
271
+ const newX = (pointer.clientX - offsetX - liveVpX) / liveZoom;
272
+ const newY = (pointer.clientY - offsetY - liveVpY) / liveZoom;
238
273
  const deltaX = newX - firstStart.x;
239
274
  const deltaY = newY - firstStart.y;
240
- // Batch state updates for React
241
- setLocalTasks((prev) => prev.map((t) => (itemIds.includes(t.id) ? {
275
+ setLocalTasks((prev) => prev.map((t) => itemIds.includes(t.id) ? {
242
276
  ...t,
243
- x: (startPositions.get(t.id)?.x || 0) + deltaX,
244
- y: (startPositions.get(t.id)?.y || 0) + deltaY
245
- } : t)));
246
- setLocalDocuments((prev) => prev.map((d) => (itemIds.includes(d.id) ? {
277
+ x: (startPositions.get(t.id)?.x ?? t.x) + deltaX,
278
+ y: (startPositions.get(t.id)?.y ?? t.y) + deltaY,
279
+ } : t));
280
+ setLocalDocuments((prev) => prev.map((d) => itemIds.includes(d.id) ? {
247
281
  ...d,
248
- x: (startPositions.get(d.id)?.x || 0) + deltaX,
249
- y: (startPositions.get(d.id)?.y || 0) + deltaY
250
- } : d)));
282
+ x: (startPositions.get(d.id)?.x ?? d.x) + deltaX,
283
+ y: (startPositions.get(d.id)?.y ?? d.y) + deltaY,
284
+ } : d));
251
285
  if (fabricCanvas?.current) {
252
286
  canvasObjectsStartPos.forEach((startPos, obj) => {
253
287
  obj.set({
@@ -335,22 +369,15 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
335
369
  const renderItem = (id, x, y, children) => {
336
370
  const screenX = x * canvasZoom;
337
371
  const screenY = y * canvasZoom;
338
- // 1. Detect if the user is interacting with the canvas at all
339
- // 'dragging' is your existing state.
340
- // You might want to pass 'isZooming' or 'isPanning' from your main canvas component here.
341
- const isDragging = dragging?.itemIds.includes(id);
342
- return (_jsx("div", { className: "pointer-events-auto absolute", style: {
372
+ const isDraggingItem = dragging?.itemIds.includes(id);
373
+ return (_jsx("div", { "data-node-id": id, className: "pointer-events-auto absolute", style: {
343
374
  left: 0,
344
375
  top: 0,
345
- // 2. Use translate3d for GPU performance
346
376
  transform: `translate3d(${screenX}px, ${screenY}px, 0) scale(${canvasZoom})`,
347
377
  transformOrigin: "top left",
348
- // 3. THE FIX: Remove transition entirely during any viewport change
349
- // Any 'ease' during zoom causes the "shaking" behavior.
350
378
  transition: "none",
351
- // 4. Optimization
352
379
  willChange: "transform",
353
- zIndex: isDragging ? 1000 : 1,
380
+ zIndex: isDraggingItem ? 1000 : 1,
354
381
  }, children: children }, id));
355
382
  };
356
383
  return (_jsx("div", { ref: overlayRef, className: "absolute inset-0 pointer-events-none", style: { zIndex: 50 }, onWheel: handleOverlayWheel, onClick: (e) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhamz.01/easyflow-whiteboard",
3
- "version": "2.3.0",
3
+ "version": "2.4.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",