@tsdraw/react 0.8.1 → 0.8.3

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.
package/dist/index.cjs CHANGED
@@ -292,6 +292,7 @@ function getCanvasCursor(currentTool, state) {
292
292
  if (state.isRotatingSelection) return "grabbing";
293
293
  if (state.isResizingSelection) return "nwse-resize";
294
294
  if (state.isMovingSelection) return "grabbing";
295
+ if (state.isHoveringSelectionBounds) return "move";
295
296
  return "default";
296
297
  }
297
298
  return state.showToolOverlay ? "none" : "crosshair";
@@ -320,8 +321,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
320
321
  previousCenter: { x: 0, y: 0 },
321
322
  initialCenter: { x: 0, y: 0 },
322
323
  previousDistance: 1,
323
- initialDistance: 1,
324
- previousAngle: 0
324
+ initialDistance: 1
325
325
  };
326
326
  const isTouchPointer = (event) => event.pointerType === "touch";
327
327
  const endTouchCameraGesture = () => {
@@ -329,7 +329,6 @@ function createTouchInteractionController(editor, canvas, handlers) {
329
329
  touchCameraState.mode = "not-sure";
330
330
  touchCameraState.previousDistance = 1;
331
331
  touchCameraState.initialDistance = 1;
332
- touchCameraState.previousAngle = 0;
333
332
  };
334
333
  const maybeHandleTouchTapGesture = () => {
335
334
  if (activeTouchPoints.size > 0) return;
@@ -360,14 +359,12 @@ function createTouchInteractionController(editor, canvas, handlers) {
360
359
  const second = points[1];
361
360
  const center = { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 };
362
361
  const distance = Math.hypot(second.x - first.x, second.y - first.y);
363
- const angle = Math.atan2(second.y - first.y, second.x - first.x);
364
362
  touchCameraState.active = true;
365
363
  touchCameraState.mode = "not-sure";
366
364
  touchCameraState.previousCenter = center;
367
365
  touchCameraState.initialCenter = center;
368
366
  touchCameraState.previousDistance = Math.max(1, distance);
369
367
  touchCameraState.initialDistance = Math.max(1, distance);
370
- touchCameraState.previousAngle = angle;
371
368
  };
372
369
  const updateTouchCameraGesture = () => {
373
370
  if (!touchCameraState.active) return false;
@@ -380,7 +377,6 @@ function createTouchInteractionController(editor, canvas, handlers) {
380
377
  const second = points[1];
381
378
  const center = { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 };
382
379
  const distance = Math.max(1, Math.hypot(second.x - first.x, second.y - first.y));
383
- const angle = Math.atan2(second.y - first.y, second.x - first.x);
384
380
  const centerDx = center.x - touchCameraState.previousCenter.x;
385
381
  const centerDy = center.y - touchCameraState.previousCenter.y;
386
382
  const touchDistance = Math.abs(distance - touchCameraState.initialDistance);
@@ -396,11 +392,9 @@ function createTouchInteractionController(editor, canvas, handlers) {
396
392
  if (touchCameraState.mode === "zooming") {
397
393
  const zoomFactor = distance / touchCameraState.previousDistance;
398
394
  editor.zoomAt(zoomFactor, centerOnCanvasX, centerOnCanvasY);
399
- editor.rotateAt(angle - touchCameraState.previousAngle, centerOnCanvasX, centerOnCanvasY);
400
395
  }
401
396
  touchCameraState.previousCenter = center;
402
397
  touchCameraState.previousDistance = distance;
403
- touchCameraState.previousAngle = angle;
404
398
  handlers.refreshView();
405
399
  return true;
406
400
  };
@@ -681,6 +675,18 @@ function toScreenRect(editor, bounds) {
681
675
  function resolveDrawColor(colorStyle, theme) {
682
676
  return core.resolveThemeColor(colorStyle, theme);
683
677
  }
678
+ function getHandlePagePoint(bounds, handle) {
679
+ switch (handle) {
680
+ case "nw":
681
+ return { x: bounds.minX, y: bounds.minY };
682
+ case "ne":
683
+ return { x: bounds.maxX, y: bounds.minY };
684
+ case "sw":
685
+ return { x: bounds.minX, y: bounds.maxY };
686
+ case "se":
687
+ return { x: bounds.maxX, y: bounds.maxY };
688
+ }
689
+ }
684
690
  var ZOOM_WHEEL_CAP = 10;
685
691
  function useTsdrawCanvasController(options = {}) {
686
692
  const onMountRef = react.useRef(options.onMount);
@@ -702,7 +708,8 @@ function useTsdrawCanvasController(options = {}) {
702
708
  const resizeRef = react.useRef({
703
709
  handle: null,
704
710
  startBounds: null,
705
- startShapes: /* @__PURE__ */ new Map()
711
+ startShapes: /* @__PURE__ */ new Map(),
712
+ cursorHandleOffset: { x: 0, y: 0 }
706
713
  });
707
714
  const rotateRef = react.useRef({
708
715
  center: null,
@@ -789,33 +796,38 @@ function useTsdrawCanvasController(options = {}) {
789
796
  setIsResizingSelection(false);
790
797
  setIsRotatingSelection(false);
791
798
  selectDragRef.current.mode = "none";
792
- resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map() };
793
- rotateRef.current = {
794
- center: null,
795
- startAngle: 0,
796
- startSelectionRotationDeg: selectionRotationRef.current,
797
- startShapes: /* @__PURE__ */ new Map()
798
- };
799
+ resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map(), cursorHandleOffset: { x: 0, y: 0 } };
800
+ rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map() };
799
801
  }, []);
800
802
  const handleResizePointerDown = react.useCallback(
801
803
  (e, handle) => {
802
804
  e.preventDefault();
803
805
  e.stopPropagation();
804
806
  const editor = editorRef.current;
805
- if (!editor || selectedShapeIdsRef.current.length === 0) return;
807
+ const canvas = canvasRef.current;
808
+ if (!editor || !canvas || selectedShapeIdsRef.current.length === 0) return;
806
809
  const bounds = core.getSelectionBoundsPage(editor, selectedShapeIdsRef.current);
807
810
  if (!bounds) return;
811
+ const handlePagePoint = getHandlePagePoint(bounds, handle);
812
+ const pointerPage = getPagePointFromClient(editor, e.clientX, e.clientY);
813
+ const cursorOffset = {
814
+ x: pointerPage.x - handlePagePoint.x,
815
+ y: pointerPage.y - handlePagePoint.y
816
+ };
808
817
  resizeRef.current = {
809
818
  handle,
810
819
  startBounds: bounds,
811
- startShapes: core.buildTransformSnapshots(editor, selectedShapeIdsRef.current)
820
+ startShapes: core.buildTransformSnapshots(editor, selectedShapeIdsRef.current),
821
+ cursorHandleOffset: cursorOffset
812
822
  };
823
+ isPointerActiveRef.current = true;
824
+ activePointerIdsRef.current.add(e.pointerId);
825
+ canvas.setPointerCapture(e.pointerId);
813
826
  editor.beginHistoryEntry();
814
827
  selectDragRef.current.mode = "resize";
815
- const p = getPagePointFromClient(editor, e.clientX, e.clientY);
816
- editor.input.pointerDown(p.x, p.y, 0.5, false);
817
- selectDragRef.current.startPage = p;
818
- selectDragRef.current.currentPage = p;
828
+ editor.input.pointerDown(handlePagePoint.x, handlePagePoint.y, 0.5, false);
829
+ selectDragRef.current.startPage = handlePagePoint;
830
+ selectDragRef.current.currentPage = handlePagePoint;
819
831
  lastPointerClientRef.current = { x: e.clientX, y: e.clientY };
820
832
  setIsResizingSelection(true);
821
833
  },
@@ -826,7 +838,8 @@ function useTsdrawCanvasController(options = {}) {
826
838
  e.preventDefault();
827
839
  e.stopPropagation();
828
840
  const editor = editorRef.current;
829
- if (!editor || selectedShapeIdsRef.current.length === 0) return;
841
+ const canvas = canvasRef.current;
842
+ if (!editor || !canvas || selectedShapeIdsRef.current.length === 0) return;
830
843
  const bounds = core.getSelectionBoundsPage(editor, selectedShapeIdsRef.current);
831
844
  if (!bounds) return;
832
845
  const center = {
@@ -840,6 +853,9 @@ function useTsdrawCanvasController(options = {}) {
840
853
  startSelectionRotationDeg: selectionRotationRef.current,
841
854
  startShapes: core.buildTransformSnapshots(editor, selectedShapeIdsRef.current)
842
855
  };
856
+ isPointerActiveRef.current = true;
857
+ activePointerIdsRef.current.add(e.pointerId);
858
+ canvas.setPointerCapture(e.pointerId);
843
859
  editor.beginHistoryEntry();
844
860
  selectDragRef.current.mode = "rotate";
845
861
  editor.input.pointerDown(p.x, p.y, 0.5, false);
@@ -969,7 +985,6 @@ function useTsdrawCanvasController(options = {}) {
969
985
  setDrawDash(nextDrawStyle.dash);
970
986
  setDrawFill(nextDrawStyle.fill);
971
987
  setDrawSize(nextDrawStyle.size);
972
- setSelectionRotationDeg(0);
973
988
  render();
974
989
  refreshSelectionBounds(editor, nextSelectionIds);
975
990
  ignorePersistenceChanges = false;
@@ -985,7 +1000,6 @@ function useTsdrawCanvasController(options = {}) {
985
1000
  const applyDocumentChangeResult = (changed) => {
986
1001
  if (!changed) return false;
987
1002
  reconcileSelectionAfterDocumentLoad();
988
- setSelectionRotationDeg(0);
989
1003
  render();
990
1004
  syncHistoryState();
991
1005
  return true;
@@ -1012,8 +1026,7 @@ function useTsdrawCanvasController(options = {}) {
1012
1026
  setSelectedShapeIds([]);
1013
1027
  selectedShapeIdsRef.current = [];
1014
1028
  setSelectionBounds(null);
1015
- setSelectionBrush(null);
1016
- setSelectionRotationDeg(0);
1029
+ resetSelectUi();
1017
1030
  render();
1018
1031
  syncHistoryState();
1019
1032
  return true;
@@ -1024,12 +1037,7 @@ function useTsdrawCanvasController(options = {}) {
1024
1037
  lastPointerClientRef.current = null;
1025
1038
  editor.input.pointerUp();
1026
1039
  if (currentToolRef.current === "select") {
1027
- const dragMode = selectDragRef.current.mode;
1028
- if (dragMode === "rotate") setIsRotatingSelection(false);
1029
- if (dragMode === "resize") setIsResizingSelection(false);
1030
- if (dragMode === "move") setIsMovingSelection(false);
1031
- if (dragMode === "marquee") setSelectionBrush(null);
1032
- selectDragRef.current.mode = "none";
1040
+ resetSelectUi();
1033
1041
  } else {
1034
1042
  editor.tools.pointerUp();
1035
1043
  }
@@ -1083,7 +1091,13 @@ function useTsdrawCanvasController(options = {}) {
1083
1091
  if (currentToolRef.current === "select") {
1084
1092
  const hit = core.getTopShapeAtPoint(editor, { x, y });
1085
1093
  const isHitSelected = !!(hit && selectedShapeIdsRef.current.includes(hit.id));
1086
- if (isHitSelected) {
1094
+ const isInsideSelectionBounds = (() => {
1095
+ if (selectedShapeIdsRef.current.length === 0) return false;
1096
+ const pageBounds = core.getSelectionBoundsPage(editor, selectedShapeIdsRef.current);
1097
+ if (!pageBounds) return false;
1098
+ return x >= pageBounds.minX && x <= pageBounds.maxX && y >= pageBounds.minY && y <= pageBounds.maxY;
1099
+ })();
1100
+ if (isHitSelected || isInsideSelectionBounds) {
1087
1101
  selectDragRef.current = {
1088
1102
  mode: "move",
1089
1103
  startPage: { x, y },
@@ -1154,9 +1168,9 @@ function useTsdrawCanvasController(options = {}) {
1154
1168
  return;
1155
1169
  }
1156
1170
  if (mode === "resize") {
1157
- const { handle, startBounds, startShapes } = resizeRef.current;
1171
+ const { handle, startBounds, startShapes, cursorHandleOffset } = resizeRef.current;
1158
1172
  if (!handle || !startBounds) return;
1159
- core.applyResize(editor, handle, startBounds, startShapes, { x: px, y: py }, e.shiftKey);
1173
+ core.applyResize(editor, handle, startBounds, startShapes, { x: px - cursorHandleOffset.x, y: py - cursorHandleOffset.y }, e.shiftKey);
1160
1174
  render();
1161
1175
  refreshSelectionBounds(editor);
1162
1176
  return;
@@ -1176,7 +1190,6 @@ function useTsdrawCanvasController(options = {}) {
1176
1190
  const nextIds = selectDragRef.current.additive ? Array.from(/* @__PURE__ */ new Set([...selectDragRef.current.initialSelection, ...ids])) : ids;
1177
1191
  setSelectedShapeIds(nextIds);
1178
1192
  selectedShapeIdsRef.current = nextIds;
1179
- setSelectionRotationDeg(0);
1180
1193
  refreshSelectionBounds(editor, nextIds);
1181
1194
  return;
1182
1195
  }
@@ -1206,12 +1219,7 @@ function useTsdrawCanvasController(options = {}) {
1206
1219
  setIsRotatingSelection(false);
1207
1220
  selectDragRef.current.mode = "none";
1208
1221
  setSelectionRotationDeg(0);
1209
- rotateRef.current = {
1210
- center: null,
1211
- startAngle: 0,
1212
- startSelectionRotationDeg: selectionRotationRef.current,
1213
- startShapes: /* @__PURE__ */ new Map()
1214
- };
1222
+ rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map() };
1215
1223
  render();
1216
1224
  refreshSelectionBounds(editor);
1217
1225
  editor.endHistoryEntry();
@@ -1220,7 +1228,7 @@ function useTsdrawCanvasController(options = {}) {
1220
1228
  if (drag.mode === "resize") {
1221
1229
  setIsResizingSelection(false);
1222
1230
  selectDragRef.current.mode = "none";
1223
- resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map() };
1231
+ resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map(), cursorHandleOffset: { x: 0, y: 0 } };
1224
1232
  render();
1225
1233
  refreshSelectionBounds(editor);
1226
1234
  editor.endHistoryEntry();
@@ -1253,7 +1261,6 @@ function useTsdrawCanvasController(options = {}) {
1253
1261
  }
1254
1262
  setSelectedShapeIds(ids);
1255
1263
  selectedShapeIdsRef.current = ids;
1256
- setSelectionRotationDeg(0);
1257
1264
  setSelectionBrush(null);
1258
1265
  selectDragRef.current.mode = "none";
1259
1266
  render();
@@ -1436,11 +1443,17 @@ function useTsdrawCanvasController(options = {}) {
1436
1443
  resize();
1437
1444
  const ro = new ResizeObserver(resize);
1438
1445
  ro.observe(container);
1446
+ const handlePointerLeaveViewport = (e) => {
1447
+ if (e.relatedTarget === null) {
1448
+ setIsPointerInsideCanvas(false);
1449
+ }
1450
+ };
1439
1451
  canvas.addEventListener("pointerdown", handlePointerDown);
1440
1452
  container.addEventListener("wheel", handleWheel, { passive: false });
1441
1453
  document.addEventListener("gesturestart", handleGestureEvent);
1442
1454
  document.addEventListener("gesturechange", handleGestureEvent);
1443
1455
  document.addEventListener("gestureend", handleGestureEvent);
1456
+ document.documentElement.addEventListener("pointerleave", handlePointerLeaveViewport);
1444
1457
  window.addEventListener("pointermove", handlePointerMove);
1445
1458
  window.addEventListener("pointerup", handlePointerUp);
1446
1459
  window.addEventListener("pointercancel", handlePointerCancel);
@@ -1464,7 +1477,6 @@ function useTsdrawCanvasController(options = {}) {
1464
1477
  const changed = editor.undo();
1465
1478
  if (!changed) return false;
1466
1479
  reconcileSelectionAfterDocumentLoad();
1467
- setSelectionRotationDeg(0);
1468
1480
  render();
1469
1481
  syncHistoryState();
1470
1482
  return true;
@@ -1473,7 +1485,6 @@ function useTsdrawCanvasController(options = {}) {
1473
1485
  const changed = editor.redo();
1474
1486
  if (!changed) return false;
1475
1487
  reconcileSelectionAfterDocumentLoad();
1476
- setSelectionRotationDeg(0);
1477
1488
  render();
1478
1489
  syncHistoryState();
1479
1490
  return true;
@@ -1501,6 +1512,7 @@ function useTsdrawCanvasController(options = {}) {
1501
1512
  document.removeEventListener("gesturestart", handleGestureEvent);
1502
1513
  document.removeEventListener("gesturechange", handleGestureEvent);
1503
1514
  document.removeEventListener("gestureend", handleGestureEvent);
1515
+ document.documentElement.removeEventListener("pointerleave", handlePointerLeaveViewport);
1504
1516
  window.removeEventListener("pointermove", handlePointerMove);
1505
1517
  window.removeEventListener("pointerup", handlePointerUp);
1506
1518
  window.removeEventListener("pointercancel", handlePointerCancel);
@@ -1565,7 +1577,6 @@ function useTsdrawCanvasController(options = {}) {
1565
1577
  selectedShapeIdsRef.current = nextSelectedShapeIds;
1566
1578
  setSelectedShapeIds(nextSelectedShapeIds);
1567
1579
  }
1568
- setSelectionRotationDeg(0);
1569
1580
  render();
1570
1581
  setCanUndo(editor.canUndo());
1571
1582
  setCanRedo(editor.canRedo());
@@ -1581,17 +1592,18 @@ function useTsdrawCanvasController(options = {}) {
1581
1592
  selectedShapeIdsRef.current = nextSelectedShapeIds;
1582
1593
  setSelectedShapeIds(nextSelectedShapeIds);
1583
1594
  }
1584
- setSelectionRotationDeg(0);
1585
1595
  render();
1586
1596
  setCanUndo(editor.canUndo());
1587
1597
  setCanRedo(editor.canRedo());
1588
1598
  return true;
1589
1599
  }, [render]);
1600
+ const isHoveringSelectionBounds = isPointerInsideCanvas && currentTool === "select" && selectedShapeIds.length > 0 && selectionBounds != null && pointerScreenPoint.x >= selectionBounds.left && pointerScreenPoint.x <= selectionBounds.left + selectionBounds.width && pointerScreenPoint.y >= selectionBounds.top && pointerScreenPoint.y <= selectionBounds.top + selectionBounds.height;
1590
1601
  const showToolOverlay = isPointerInsideCanvas && (currentTool === "pen" || currentTool === "eraser");
1591
1602
  const canvasCursor = getCanvasCursor(currentTool, {
1592
1603
  isMovingSelection,
1593
1604
  isResizingSelection,
1594
1605
  isRotatingSelection,
1606
+ isHoveringSelectionBounds,
1595
1607
  showToolOverlay
1596
1608
  });
1597
1609
  const cursorContext = {