@tsdraw/react 0.9.4 → 0.9.5
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 +130 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +131 -13
- package/dist/index.js.map +1 -1
- package/dist/tsdraw.css +32 -10
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useMemo, useEffect, useCallback, useRef, useLayoutEffect } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import { DEFAULT_COLORS, renderCanvasBackground, getSelectionBoundsPage, buildTransformSnapshots, Editor, STROKE_WIDTHS, ERASER_MARGIN, resolveThemeColor,
|
|
3
|
+
import { DEFAULT_COLORS, renderCanvasBackground, getSelectionBoundsPage, buildTransformSnapshots, Editor, getVertexHandlePagePositions, pageToScreen, STROKE_WIDTHS, ERASER_MARGIN, resolveThemeColor, findVertexHit, getTopShapeAtPoint, buildStartPositions, applyRotation, applyResize, applyMove, applyVertexDrag, normalizeSelectionBounds, getShapesInBounds, HandDraggingState, startCameraSlide, isSelectTool, beginCameraPan, moveCameraPan } from '@tsdraw/core';
|
|
4
4
|
import { IconPointer, IconPencil, IconSquare, IconCircle, IconEraser, IconHandStop, IconArrowBackUp, IconArrowForwardUp } from '@tabler/icons-react';
|
|
5
5
|
|
|
6
6
|
// src/components/TsdrawCanvas.tsx
|
|
@@ -8,6 +8,7 @@ function SelectionOverlay({
|
|
|
8
8
|
selectionBrush,
|
|
9
9
|
selectionBounds,
|
|
10
10
|
selectionRotationDeg,
|
|
11
|
+
vertexHandleScreenPositions,
|
|
11
12
|
currentTool,
|
|
12
13
|
selectedCount,
|
|
13
14
|
onRotatePointerDown,
|
|
@@ -35,7 +36,8 @@ function SelectionOverlay({
|
|
|
35
36
|
top: selectionBounds.top,
|
|
36
37
|
width: selectionBounds.width,
|
|
37
38
|
height: selectionBounds.height,
|
|
38
|
-
transform: `rotate(${selectionRotationDeg}deg)
|
|
39
|
+
transform: `rotate(${selectionRotationDeg}deg)`,
|
|
40
|
+
transformOrigin: "center center"
|
|
39
41
|
},
|
|
40
42
|
children: [
|
|
41
43
|
/* @__PURE__ */ jsx("div", { className: "tsdraw-selection-bounds" }),
|
|
@@ -93,7 +95,15 @@ function SelectionOverlay({
|
|
|
93
95
|
] })
|
|
94
96
|
]
|
|
95
97
|
}
|
|
96
|
-
)
|
|
98
|
+
),
|
|
99
|
+
currentTool === "select" && vertexHandleScreenPositions.map((pos, index) => /* @__PURE__ */ jsx(
|
|
100
|
+
"div",
|
|
101
|
+
{
|
|
102
|
+
className: "tsdraw-vertex-handle",
|
|
103
|
+
style: { position: "absolute", left: pos.left, top: pos.top, transform: "translate(-50%, -50%)", pointerEvents: "none", zIndex: 95 }
|
|
104
|
+
},
|
|
105
|
+
`vertex-${index.toString()}`
|
|
106
|
+
))
|
|
97
107
|
] });
|
|
98
108
|
}
|
|
99
109
|
function parseAnchor(anchor) {
|
|
@@ -452,6 +462,7 @@ function getCanvasCursor(currentTool, state) {
|
|
|
452
462
|
if (state.isRotatingSelection) return "grabbing";
|
|
453
463
|
if (state.isResizingSelection) return "nwse-resize";
|
|
454
464
|
if (state.isMovingSelection) return "grabbing";
|
|
465
|
+
if (state.isDraggingVertex) return "grabbing";
|
|
455
466
|
if (state.isHoveringSelectionBounds) return "move";
|
|
456
467
|
return "default";
|
|
457
468
|
}
|
|
@@ -894,6 +905,16 @@ function toScreenRect(editor, bounds) {
|
|
|
894
905
|
height: Math.max(0, maxY - minY)
|
|
895
906
|
};
|
|
896
907
|
}
|
|
908
|
+
function selectionCenteredOnRotation(editor, startBoundsPage, centerPage) {
|
|
909
|
+
const startScreen = toScreenRect(editor, startBoundsPage);
|
|
910
|
+
const centerScreen = pageToScreen(editor.viewport, centerPage.x, centerPage.y);
|
|
911
|
+
return {
|
|
912
|
+
left: centerScreen.x - startScreen.width / 2,
|
|
913
|
+
top: centerScreen.y - startScreen.height / 2,
|
|
914
|
+
width: startScreen.width,
|
|
915
|
+
height: startScreen.height
|
|
916
|
+
};
|
|
917
|
+
}
|
|
897
918
|
function resolveDrawColor(colorStyle, theme) {
|
|
898
919
|
return resolveThemeColor(colorStyle, theme);
|
|
899
920
|
}
|
|
@@ -954,7 +975,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
954
975
|
center: null,
|
|
955
976
|
startAngle: 0,
|
|
956
977
|
startSelectionRotationDeg: 0,
|
|
957
|
-
startShapes: /* @__PURE__ */ new Map()
|
|
978
|
+
startShapes: /* @__PURE__ */ new Map(),
|
|
979
|
+
startBoundsPage: null
|
|
958
980
|
});
|
|
959
981
|
const selectDragRef = useRef({
|
|
960
982
|
mode: "none",
|
|
@@ -976,6 +998,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
976
998
|
const [isMovingSelection, setIsMovingSelection] = useState(false);
|
|
977
999
|
const [isResizingSelection, setIsResizingSelection] = useState(false);
|
|
978
1000
|
const [isRotatingSelection, setIsRotatingSelection] = useState(false);
|
|
1001
|
+
const [isDraggingVertex, setIsDraggingVertex] = useState(false);
|
|
1002
|
+
const [vertexHandleScreenPositions, setVertexHandleScreenPositions] = useState([]);
|
|
979
1003
|
const [canUndo, setCanUndo] = useState(false);
|
|
980
1004
|
const [canRedo, setCanRedo] = useState(false);
|
|
981
1005
|
const [isPersistenceReady, setIsPersistenceReady] = useState(!options.persistenceKey);
|
|
@@ -1069,9 +1093,12 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1069
1093
|
setIsMovingSelection(false);
|
|
1070
1094
|
setIsResizingSelection(false);
|
|
1071
1095
|
setIsRotatingSelection(false);
|
|
1096
|
+
setIsDraggingVertex(false);
|
|
1072
1097
|
selectDragRef.current.mode = "none";
|
|
1098
|
+
selectDragRef.current.vertexRefs = void 0;
|
|
1099
|
+
selectDragRef.current.vertexSnapshots = void 0;
|
|
1073
1100
|
resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map(), cursorHandleOffset: { x: 0, y: 0 } };
|
|
1074
|
-
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map() };
|
|
1101
|
+
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map(), startBoundsPage: null };
|
|
1075
1102
|
}, []);
|
|
1076
1103
|
const handleResizePointerDown = useCallback(
|
|
1077
1104
|
(e, handle) => {
|
|
@@ -1125,8 +1152,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1125
1152
|
center,
|
|
1126
1153
|
startAngle: Math.atan2(p.y - center.y, p.x - center.x),
|
|
1127
1154
|
startSelectionRotationDeg: selectionRotationRef.current,
|
|
1128
|
-
startShapes: buildTransformSnapshots(editor, selectedShapeIdsRef.current)
|
|
1155
|
+
startShapes: buildTransformSnapshots(editor, selectedShapeIdsRef.current),
|
|
1156
|
+
startBoundsPage: bounds
|
|
1129
1157
|
};
|
|
1158
|
+
setSelectionBounds(selectionCenteredOnRotation(editor, bounds, center));
|
|
1130
1159
|
isPointerActiveRef.current = true;
|
|
1131
1160
|
activePointerIdsRef.current.add(e.pointerId);
|
|
1132
1161
|
canvas.setPointerCapture(e.pointerId);
|
|
@@ -1392,6 +1421,30 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1392
1421
|
const pressure = (first.pressure ?? 0.5) * pressureSensitivity;
|
|
1393
1422
|
const isPen = first.pointerType === "pen" || hasRealPressure(first.pressure);
|
|
1394
1423
|
if (currentToolRef.current === "select") {
|
|
1424
|
+
if (!readOnlyRef.current) {
|
|
1425
|
+
const zoom = editor.viewport.zoom;
|
|
1426
|
+
const marginPage = 12 / zoom;
|
|
1427
|
+
const clusterTolerance = 20 / zoom;
|
|
1428
|
+
const vertexHit = findVertexHit(editor, { x, y }, marginPage, clusterTolerance);
|
|
1429
|
+
if (vertexHit) {
|
|
1430
|
+
const involvedIds = [...new Set(vertexHit.refs.map((r) => r.shapeId))];
|
|
1431
|
+
selectDragRef.current = {
|
|
1432
|
+
mode: "vertex",
|
|
1433
|
+
startPage: { x, y },
|
|
1434
|
+
currentPage: { x, y },
|
|
1435
|
+
startPositions: /* @__PURE__ */ new Map(),
|
|
1436
|
+
additive: false,
|
|
1437
|
+
initialSelection: [...selectedShapeIdsRef.current],
|
|
1438
|
+
vertexRefs: vertexHit.refs,
|
|
1439
|
+
vertexSnapshots: vertexHit.snapshots
|
|
1440
|
+
};
|
|
1441
|
+
setIsDraggingVertex(true);
|
|
1442
|
+
setSelectedShapeIds(involvedIds);
|
|
1443
|
+
selectedShapeIdsRef.current = involvedIds;
|
|
1444
|
+
refreshSelectionBounds(editor, involvedIds);
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1395
1448
|
const hit = getTopShapeAtPoint(editor, { x, y });
|
|
1396
1449
|
const isHitSelected = !!(hit && selectedShapeIdsRef.current.includes(hit.id));
|
|
1397
1450
|
const isInsideSelectionBounds = (() => {
|
|
@@ -1407,7 +1460,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1407
1460
|
currentPage: { x, y },
|
|
1408
1461
|
startPositions: buildStartPositions(editor, selectedShapeIdsRef.current),
|
|
1409
1462
|
additive: false,
|
|
1410
|
-
initialSelection: [...selectedShapeIdsRef.current]
|
|
1463
|
+
initialSelection: [...selectedShapeIdsRef.current],
|
|
1464
|
+
moveFromEmptyInsideBounds: isInsideSelectionBounds && !hit
|
|
1411
1465
|
};
|
|
1412
1466
|
setIsMovingSelection(true);
|
|
1413
1467
|
return;
|
|
@@ -1463,13 +1517,14 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1463
1517
|
const mode = selectDragRef.current.mode;
|
|
1464
1518
|
const { x: px, y: py } = editor.input.getCurrentPagePoint();
|
|
1465
1519
|
if (mode === "rotate") {
|
|
1466
|
-
const { center, startAngle, startSelectionRotationDeg, startShapes } = rotateRef.current;
|
|
1467
|
-
if (!center) return;
|
|
1520
|
+
const { center, startAngle, startSelectionRotationDeg, startShapes, startBoundsPage } = rotateRef.current;
|
|
1521
|
+
if (!center || !startBoundsPage) return;
|
|
1468
1522
|
const angle = Math.atan2(py - center.y, px - center.x);
|
|
1469
1523
|
const delta = angle - startAngle;
|
|
1470
1524
|
setSelectionRotationDeg(startSelectionRotationDeg + delta * 180 / Math.PI);
|
|
1471
1525
|
applyRotation(editor, startShapes, center, delta);
|
|
1472
1526
|
render();
|
|
1527
|
+
setSelectionBounds(selectionCenteredOnRotation(editor, startBoundsPage, center));
|
|
1473
1528
|
return;
|
|
1474
1529
|
}
|
|
1475
1530
|
if (mode === "resize") {
|
|
@@ -1487,6 +1542,20 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1487
1542
|
refreshSelectionBounds(editor);
|
|
1488
1543
|
return;
|
|
1489
1544
|
}
|
|
1545
|
+
if (mode === "vertex") {
|
|
1546
|
+
const drag = selectDragRef.current;
|
|
1547
|
+
const refs = drag.vertexRefs;
|
|
1548
|
+
const snapshots = drag.vertexSnapshots;
|
|
1549
|
+
if (refs && snapshots) {
|
|
1550
|
+
applyVertexDrag(editor, snapshots, refs, {
|
|
1551
|
+
x: px - drag.startPage.x,
|
|
1552
|
+
y: py - drag.startPage.y
|
|
1553
|
+
});
|
|
1554
|
+
render();
|
|
1555
|
+
refreshSelectionBounds(editor);
|
|
1556
|
+
}
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1490
1559
|
if (mode === "marquee") {
|
|
1491
1560
|
selectDragRef.current.currentPage = { x: px, y: py };
|
|
1492
1561
|
const pageRect = normalizeSelectionBounds(selectDragRef.current.startPage, selectDragRef.current.currentPage);
|
|
@@ -1524,7 +1593,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1524
1593
|
setIsRotatingSelection(false);
|
|
1525
1594
|
selectDragRef.current.mode = "none";
|
|
1526
1595
|
setSelectionRotationDeg(0);
|
|
1527
|
-
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map() };
|
|
1596
|
+
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map(), startBoundsPage: null };
|
|
1528
1597
|
render();
|
|
1529
1598
|
refreshSelectionBounds(editor);
|
|
1530
1599
|
editor.endHistoryEntry();
|
|
@@ -1542,6 +1611,24 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1542
1611
|
if (drag.mode === "move") {
|
|
1543
1612
|
setIsMovingSelection(false);
|
|
1544
1613
|
selectDragRef.current.mode = "none";
|
|
1614
|
+
const distSq = (x - drag.startPage.x) ** 2 + (y - drag.startPage.y) ** 2;
|
|
1615
|
+
if (drag.moveFromEmptyInsideBounds && distSq < 4) {
|
|
1616
|
+
setSelectedShapeIds([]);
|
|
1617
|
+
selectedShapeIdsRef.current = [];
|
|
1618
|
+
setSelectionBounds(null);
|
|
1619
|
+
} else {
|
|
1620
|
+
refreshSelectionBounds(editor);
|
|
1621
|
+
}
|
|
1622
|
+
selectDragRef.current.moveFromEmptyInsideBounds = void 0;
|
|
1623
|
+
render();
|
|
1624
|
+
editor.endHistoryEntry();
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
if (drag.mode === "vertex") {
|
|
1628
|
+
setIsDraggingVertex(false);
|
|
1629
|
+
selectDragRef.current.mode = "none";
|
|
1630
|
+
selectDragRef.current.vertexRefs = void 0;
|
|
1631
|
+
selectDragRef.current.vertexSnapshots = void 0;
|
|
1545
1632
|
render();
|
|
1546
1633
|
refreshSelectionBounds(editor);
|
|
1547
1634
|
editor.endHistoryEntry();
|
|
@@ -1620,9 +1707,21 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1620
1707
|
editor.input.pointerUp();
|
|
1621
1708
|
if (currentToolRef.current === "select") {
|
|
1622
1709
|
const drag = selectDragRef.current;
|
|
1623
|
-
if (drag.mode === "rotate")
|
|
1710
|
+
if (drag.mode === "rotate") {
|
|
1711
|
+
setIsRotatingSelection(false);
|
|
1712
|
+
setSelectionRotationDeg(0);
|
|
1713
|
+
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map(), startBoundsPage: null };
|
|
1714
|
+
}
|
|
1624
1715
|
if (drag.mode === "resize") setIsResizingSelection(false);
|
|
1625
|
-
if (drag.mode === "move")
|
|
1716
|
+
if (drag.mode === "move") {
|
|
1717
|
+
setIsMovingSelection(false);
|
|
1718
|
+
selectDragRef.current.moveFromEmptyInsideBounds = void 0;
|
|
1719
|
+
}
|
|
1720
|
+
if (drag.mode === "vertex") {
|
|
1721
|
+
setIsDraggingVertex(false);
|
|
1722
|
+
selectDragRef.current.vertexRefs = void 0;
|
|
1723
|
+
selectDragRef.current.vertexSnapshots = void 0;
|
|
1724
|
+
}
|
|
1626
1725
|
if (drag.mode === "marquee") setSelectionBrush(null);
|
|
1627
1726
|
if (drag.mode !== "none") {
|
|
1628
1727
|
selectDragRef.current.mode = "none";
|
|
@@ -1959,10 +2058,25 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1959
2058
|
}, [render]);
|
|
1960
2059
|
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;
|
|
1961
2060
|
const showToolOverlay = isPointerInsideCanvas && (currentTool === "pen" || currentTool === "eraser");
|
|
2061
|
+
useEffect(() => {
|
|
2062
|
+
const editor = editorRef.current;
|
|
2063
|
+
if (!editor || currentTool !== "select" || selectedShapeIds.length === 0) {
|
|
2064
|
+
setVertexHandleScreenPositions([]);
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
const pagePts = getVertexHandlePagePositions(editor, selectedShapeIds);
|
|
2068
|
+
setVertexHandleScreenPositions(
|
|
2069
|
+
pagePts.map((pg) => {
|
|
2070
|
+
const s = pageToScreen(editor.viewport, pg.x, pg.y);
|
|
2071
|
+
return { left: s.x, top: s.y };
|
|
2072
|
+
})
|
|
2073
|
+
);
|
|
2074
|
+
}, [currentTool, selectedShapeIds, selectionBounds, selectionRotationDeg, isPersistenceReady]);
|
|
1962
2075
|
const canvasCursor = getCanvasCursor(currentTool, {
|
|
1963
2076
|
isMovingSelection,
|
|
1964
2077
|
isResizingSelection,
|
|
1965
2078
|
isRotatingSelection,
|
|
2079
|
+
isDraggingVertex,
|
|
1966
2080
|
isHoveringSelectionBounds,
|
|
1967
2081
|
showToolOverlay
|
|
1968
2082
|
});
|
|
@@ -1972,7 +2086,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1972
2086
|
showToolOverlay,
|
|
1973
2087
|
isMovingSelection,
|
|
1974
2088
|
isResizingSelection,
|
|
1975
|
-
isRotatingSelection
|
|
2089
|
+
isRotatingSelection,
|
|
2090
|
+
isDraggingVertex
|
|
1976
2091
|
};
|
|
1977
2092
|
const toolOverlay = {
|
|
1978
2093
|
visible: showToolOverlay,
|
|
@@ -1995,6 +2110,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1995
2110
|
selectionBrush,
|
|
1996
2111
|
selectionBounds,
|
|
1997
2112
|
selectionRotationDeg,
|
|
2113
|
+
vertexHandleScreenPositions,
|
|
1998
2114
|
canvasCursor,
|
|
1999
2115
|
cursorContext,
|
|
2000
2116
|
toolOverlay,
|
|
@@ -2105,6 +2221,7 @@ function Tsdraw(props) {
|
|
|
2105
2221
|
selectionBrush,
|
|
2106
2222
|
selectionBounds,
|
|
2107
2223
|
selectionRotationDeg,
|
|
2224
|
+
vertexHandleScreenPositions,
|
|
2108
2225
|
canvasCursor: defaultCanvasCursor,
|
|
2109
2226
|
cursorContext,
|
|
2110
2227
|
toolOverlay,
|
|
@@ -2286,6 +2403,7 @@ function Tsdraw(props) {
|
|
|
2286
2403
|
selectionBrush,
|
|
2287
2404
|
selectionBounds,
|
|
2288
2405
|
selectionRotationDeg,
|
|
2406
|
+
vertexHandleScreenPositions,
|
|
2289
2407
|
currentTool,
|
|
2290
2408
|
selectedCount: selectedShapeIds.length,
|
|
2291
2409
|
onRotatePointerDown: handleRotatePointerDown,
|