@mhamz.01/easyflow-whiteboard 2.4.0 → 2.6.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,2CAofzB"}
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,2CAwezB"}
@@ -183,60 +183,37 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
183
183
  itemsToDrag = [itemId];
184
184
  setSelectedIds(new Set([itemId]));
185
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
194
186
  const startPositions = new Map();
195
187
  itemsToDrag.forEach((id) => {
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
- }
188
+ const pos = getItemPosition(id);
189
+ if (pos)
190
+ startPositions.set(id, pos);
210
191
  });
211
192
  const canvasObjectsStartPos = new Map();
212
193
  selectedCanvasObjects.forEach((obj) => {
213
194
  canvasObjectsStartPos.set(obj, { left: obj.left || 0, top: obj.top || 0 });
214
195
  });
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
- }
196
+ const clickedPos = getItemPosition(itemId);
197
+ if (!clickedPos)
198
+ return;
199
+ // ── FIX: Read VPT directly — never stale, always frame-perfect ──
200
+ const vpt = fabricCanvas?.current?.viewportTransform;
201
+ const liveZoom = vpt ? vpt[0] : 1;
202
+ const liveVpX = vpt ? vpt[4] : 0;
203
+ const liveVpY = vpt ? vpt[5] : 0;
204
+ const screenX = clickedPos.x * liveZoom + liveVpX;
205
+ const screenY = clickedPos.y * liveZoom + liveVpY;
206
+ console.log("pointer screen:", pointer.clientX, pointer.clientY);
207
+ console.log("clickedPos world:", clickedPos?.x, clickedPos?.y);
208
+ console.log("vpt zoom/vpX/vpY:", liveZoom, liveVpX, liveVpY);
209
+ console.log("node screen should be:", clickedPos ? clickedPos.x * liveZoom + liveVpX : "?", clickedPos ? clickedPos.y * liveZoom + liveVpY : "?");
233
210
  dragStateRef.current = {
234
211
  isDragging: true,
235
212
  itemIds: itemsToDrag,
236
213
  startPositions,
237
214
  canvasObjectsStartPos,
238
- offsetX,
239
- offsetY,
215
+ offsetX: pointer.clientX - screenX,
216
+ offsetY: pointer.clientY - screenY,
240
217
  };
241
218
  setDragging({ itemIds: itemsToDrag });
242
219
  document.body.style.cursor = "grabbing";
@@ -261,13 +238,12 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
261
238
  const firstStart = startPositions.get(firstId);
262
239
  if (!firstStart)
263
240
  return;
264
- // ── Live VPT — no React state, no staleness ──
241
+ // ── FIX: Read VPT live inside rAF perfectly in sync with Fabric ──
265
242
  const vpt = fabricCanvas?.current?.viewportTransform;
266
243
  const liveZoom = vpt ? vpt[0] : 1;
267
244
  const liveVpX = vpt ? vpt[4] : 0;
268
245
  const liveVpY = vpt ? vpt[5] : 0;
269
- // pointer - offset = top-left of node in screen space
270
- // subtract vpX, divide by zoom = world position
246
+ // Convert screen world using live VPT values
271
247
  const newX = (pointer.clientX - offsetX - liveVpX) / liveZoom;
272
248
  const newY = (pointer.clientY - offsetY - liveVpY) / liveZoom;
273
249
  const deltaX = newX - firstStart.x;
@@ -317,7 +293,7 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
317
293
  window.removeEventListener("touchend", handleEnd);
318
294
  window.removeEventListener("touchcancel", handleEnd);
319
295
  };
320
- }, [dragging, canvasZoom, canvasViewport, localTasks, localDocuments, fabricCanvas]);
296
+ }, [dragging, localTasks, localDocuments, fabricCanvas]);
321
297
  // ── Selection, Status, Keyboard Logic ────────────────────────────────────────
322
298
  const handleSelect = (id, e) => {
323
299
  if (e?.shiftKey || e?.ctrlKey || e?.metaKey) {
@@ -369,15 +345,22 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
369
345
  const renderItem = (id, x, y, children) => {
370
346
  const screenX = x * canvasZoom;
371
347
  const screenY = y * canvasZoom;
372
- const isDraggingItem = dragging?.itemIds.includes(id);
373
- return (_jsx("div", { "data-node-id": id, className: "pointer-events-auto absolute", style: {
348
+ // 1. Detect if the user is interacting with the canvas at all
349
+ // 'dragging' is your existing state.
350
+ // You might want to pass 'isZooming' or 'isPanning' from your main canvas component here.
351
+ const isDragging = dragging?.itemIds.includes(id);
352
+ return (_jsx("div", { className: "pointer-events-auto absolute", style: {
374
353
  left: 0,
375
354
  top: 0,
355
+ // 2. Use translate3d for GPU performance
376
356
  transform: `translate3d(${screenX}px, ${screenY}px, 0) scale(${canvasZoom})`,
377
357
  transformOrigin: "top left",
358
+ // 3. THE FIX: Remove transition entirely during any viewport change
359
+ // Any 'ease' during zoom causes the "shaking" behavior.
378
360
  transition: "none",
361
+ // 4. Optimization
379
362
  willChange: "transform",
380
- zIndex: isDraggingItem ? 1000 : 1,
363
+ zIndex: isDragging ? 1000 : 1,
381
364
  }, children: children }, id));
382
365
  };
383
366
  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.4.0",
3
+ "version": "2.6.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",