@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.cjs
CHANGED
|
@@ -10,6 +10,7 @@ function SelectionOverlay({
|
|
|
10
10
|
selectionBrush,
|
|
11
11
|
selectionBounds,
|
|
12
12
|
selectionRotationDeg,
|
|
13
|
+
vertexHandleScreenPositions,
|
|
13
14
|
currentTool,
|
|
14
15
|
selectedCount,
|
|
15
16
|
onRotatePointerDown,
|
|
@@ -37,7 +38,8 @@ function SelectionOverlay({
|
|
|
37
38
|
top: selectionBounds.top,
|
|
38
39
|
width: selectionBounds.width,
|
|
39
40
|
height: selectionBounds.height,
|
|
40
|
-
transform: `rotate(${selectionRotationDeg}deg)
|
|
41
|
+
transform: `rotate(${selectionRotationDeg}deg)`,
|
|
42
|
+
transformOrigin: "center center"
|
|
41
43
|
},
|
|
42
44
|
children: [
|
|
43
45
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "tsdraw-selection-bounds" }),
|
|
@@ -95,7 +97,15 @@ function SelectionOverlay({
|
|
|
95
97
|
] })
|
|
96
98
|
]
|
|
97
99
|
}
|
|
98
|
-
)
|
|
100
|
+
),
|
|
101
|
+
currentTool === "select" && vertexHandleScreenPositions.map((pos, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
102
|
+
"div",
|
|
103
|
+
{
|
|
104
|
+
className: "tsdraw-vertex-handle",
|
|
105
|
+
style: { position: "absolute", left: pos.left, top: pos.top, transform: "translate(-50%, -50%)", pointerEvents: "none", zIndex: 95 }
|
|
106
|
+
},
|
|
107
|
+
`vertex-${index.toString()}`
|
|
108
|
+
))
|
|
99
109
|
] });
|
|
100
110
|
}
|
|
101
111
|
function parseAnchor(anchor) {
|
|
@@ -454,6 +464,7 @@ function getCanvasCursor(currentTool, state) {
|
|
|
454
464
|
if (state.isRotatingSelection) return "grabbing";
|
|
455
465
|
if (state.isResizingSelection) return "nwse-resize";
|
|
456
466
|
if (state.isMovingSelection) return "grabbing";
|
|
467
|
+
if (state.isDraggingVertex) return "grabbing";
|
|
457
468
|
if (state.isHoveringSelectionBounds) return "move";
|
|
458
469
|
return "default";
|
|
459
470
|
}
|
|
@@ -896,6 +907,16 @@ function toScreenRect(editor, bounds) {
|
|
|
896
907
|
height: Math.max(0, maxY - minY)
|
|
897
908
|
};
|
|
898
909
|
}
|
|
910
|
+
function selectionCenteredOnRotation(editor, startBoundsPage, centerPage) {
|
|
911
|
+
const startScreen = toScreenRect(editor, startBoundsPage);
|
|
912
|
+
const centerScreen = core.pageToScreen(editor.viewport, centerPage.x, centerPage.y);
|
|
913
|
+
return {
|
|
914
|
+
left: centerScreen.x - startScreen.width / 2,
|
|
915
|
+
top: centerScreen.y - startScreen.height / 2,
|
|
916
|
+
width: startScreen.width,
|
|
917
|
+
height: startScreen.height
|
|
918
|
+
};
|
|
919
|
+
}
|
|
899
920
|
function resolveDrawColor(colorStyle, theme) {
|
|
900
921
|
return core.resolveThemeColor(colorStyle, theme);
|
|
901
922
|
}
|
|
@@ -956,7 +977,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
956
977
|
center: null,
|
|
957
978
|
startAngle: 0,
|
|
958
979
|
startSelectionRotationDeg: 0,
|
|
959
|
-
startShapes: /* @__PURE__ */ new Map()
|
|
980
|
+
startShapes: /* @__PURE__ */ new Map(),
|
|
981
|
+
startBoundsPage: null
|
|
960
982
|
});
|
|
961
983
|
const selectDragRef = react.useRef({
|
|
962
984
|
mode: "none",
|
|
@@ -978,6 +1000,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
978
1000
|
const [isMovingSelection, setIsMovingSelection] = react.useState(false);
|
|
979
1001
|
const [isResizingSelection, setIsResizingSelection] = react.useState(false);
|
|
980
1002
|
const [isRotatingSelection, setIsRotatingSelection] = react.useState(false);
|
|
1003
|
+
const [isDraggingVertex, setIsDraggingVertex] = react.useState(false);
|
|
1004
|
+
const [vertexHandleScreenPositions, setVertexHandleScreenPositions] = react.useState([]);
|
|
981
1005
|
const [canUndo, setCanUndo] = react.useState(false);
|
|
982
1006
|
const [canRedo, setCanRedo] = react.useState(false);
|
|
983
1007
|
const [isPersistenceReady, setIsPersistenceReady] = react.useState(!options.persistenceKey);
|
|
@@ -1071,9 +1095,12 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1071
1095
|
setIsMovingSelection(false);
|
|
1072
1096
|
setIsResizingSelection(false);
|
|
1073
1097
|
setIsRotatingSelection(false);
|
|
1098
|
+
setIsDraggingVertex(false);
|
|
1074
1099
|
selectDragRef.current.mode = "none";
|
|
1100
|
+
selectDragRef.current.vertexRefs = void 0;
|
|
1101
|
+
selectDragRef.current.vertexSnapshots = void 0;
|
|
1075
1102
|
resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map(), cursorHandleOffset: { x: 0, y: 0 } };
|
|
1076
|
-
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map() };
|
|
1103
|
+
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map(), startBoundsPage: null };
|
|
1077
1104
|
}, []);
|
|
1078
1105
|
const handleResizePointerDown = react.useCallback(
|
|
1079
1106
|
(e, handle) => {
|
|
@@ -1127,8 +1154,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1127
1154
|
center,
|
|
1128
1155
|
startAngle: Math.atan2(p.y - center.y, p.x - center.x),
|
|
1129
1156
|
startSelectionRotationDeg: selectionRotationRef.current,
|
|
1130
|
-
startShapes: core.buildTransformSnapshots(editor, selectedShapeIdsRef.current)
|
|
1157
|
+
startShapes: core.buildTransformSnapshots(editor, selectedShapeIdsRef.current),
|
|
1158
|
+
startBoundsPage: bounds
|
|
1131
1159
|
};
|
|
1160
|
+
setSelectionBounds(selectionCenteredOnRotation(editor, bounds, center));
|
|
1132
1161
|
isPointerActiveRef.current = true;
|
|
1133
1162
|
activePointerIdsRef.current.add(e.pointerId);
|
|
1134
1163
|
canvas.setPointerCapture(e.pointerId);
|
|
@@ -1394,6 +1423,30 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1394
1423
|
const pressure = (first.pressure ?? 0.5) * pressureSensitivity;
|
|
1395
1424
|
const isPen = first.pointerType === "pen" || hasRealPressure(first.pressure);
|
|
1396
1425
|
if (currentToolRef.current === "select") {
|
|
1426
|
+
if (!readOnlyRef.current) {
|
|
1427
|
+
const zoom = editor.viewport.zoom;
|
|
1428
|
+
const marginPage = 12 / zoom;
|
|
1429
|
+
const clusterTolerance = 20 / zoom;
|
|
1430
|
+
const vertexHit = core.findVertexHit(editor, { x, y }, marginPage, clusterTolerance);
|
|
1431
|
+
if (vertexHit) {
|
|
1432
|
+
const involvedIds = [...new Set(vertexHit.refs.map((r) => r.shapeId))];
|
|
1433
|
+
selectDragRef.current = {
|
|
1434
|
+
mode: "vertex",
|
|
1435
|
+
startPage: { x, y },
|
|
1436
|
+
currentPage: { x, y },
|
|
1437
|
+
startPositions: /* @__PURE__ */ new Map(),
|
|
1438
|
+
additive: false,
|
|
1439
|
+
initialSelection: [...selectedShapeIdsRef.current],
|
|
1440
|
+
vertexRefs: vertexHit.refs,
|
|
1441
|
+
vertexSnapshots: vertexHit.snapshots
|
|
1442
|
+
};
|
|
1443
|
+
setIsDraggingVertex(true);
|
|
1444
|
+
setSelectedShapeIds(involvedIds);
|
|
1445
|
+
selectedShapeIdsRef.current = involvedIds;
|
|
1446
|
+
refreshSelectionBounds(editor, involvedIds);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1397
1450
|
const hit = core.getTopShapeAtPoint(editor, { x, y });
|
|
1398
1451
|
const isHitSelected = !!(hit && selectedShapeIdsRef.current.includes(hit.id));
|
|
1399
1452
|
const isInsideSelectionBounds = (() => {
|
|
@@ -1409,7 +1462,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1409
1462
|
currentPage: { x, y },
|
|
1410
1463
|
startPositions: core.buildStartPositions(editor, selectedShapeIdsRef.current),
|
|
1411
1464
|
additive: false,
|
|
1412
|
-
initialSelection: [...selectedShapeIdsRef.current]
|
|
1465
|
+
initialSelection: [...selectedShapeIdsRef.current],
|
|
1466
|
+
moveFromEmptyInsideBounds: isInsideSelectionBounds && !hit
|
|
1413
1467
|
};
|
|
1414
1468
|
setIsMovingSelection(true);
|
|
1415
1469
|
return;
|
|
@@ -1465,13 +1519,14 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1465
1519
|
const mode = selectDragRef.current.mode;
|
|
1466
1520
|
const { x: px, y: py } = editor.input.getCurrentPagePoint();
|
|
1467
1521
|
if (mode === "rotate") {
|
|
1468
|
-
const { center, startAngle, startSelectionRotationDeg, startShapes } = rotateRef.current;
|
|
1469
|
-
if (!center) return;
|
|
1522
|
+
const { center, startAngle, startSelectionRotationDeg, startShapes, startBoundsPage } = rotateRef.current;
|
|
1523
|
+
if (!center || !startBoundsPage) return;
|
|
1470
1524
|
const angle = Math.atan2(py - center.y, px - center.x);
|
|
1471
1525
|
const delta = angle - startAngle;
|
|
1472
1526
|
setSelectionRotationDeg(startSelectionRotationDeg + delta * 180 / Math.PI);
|
|
1473
1527
|
core.applyRotation(editor, startShapes, center, delta);
|
|
1474
1528
|
render();
|
|
1529
|
+
setSelectionBounds(selectionCenteredOnRotation(editor, startBoundsPage, center));
|
|
1475
1530
|
return;
|
|
1476
1531
|
}
|
|
1477
1532
|
if (mode === "resize") {
|
|
@@ -1489,6 +1544,20 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1489
1544
|
refreshSelectionBounds(editor);
|
|
1490
1545
|
return;
|
|
1491
1546
|
}
|
|
1547
|
+
if (mode === "vertex") {
|
|
1548
|
+
const drag = selectDragRef.current;
|
|
1549
|
+
const refs = drag.vertexRefs;
|
|
1550
|
+
const snapshots = drag.vertexSnapshots;
|
|
1551
|
+
if (refs && snapshots) {
|
|
1552
|
+
core.applyVertexDrag(editor, snapshots, refs, {
|
|
1553
|
+
x: px - drag.startPage.x,
|
|
1554
|
+
y: py - drag.startPage.y
|
|
1555
|
+
});
|
|
1556
|
+
render();
|
|
1557
|
+
refreshSelectionBounds(editor);
|
|
1558
|
+
}
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1492
1561
|
if (mode === "marquee") {
|
|
1493
1562
|
selectDragRef.current.currentPage = { x: px, y: py };
|
|
1494
1563
|
const pageRect = core.normalizeSelectionBounds(selectDragRef.current.startPage, selectDragRef.current.currentPage);
|
|
@@ -1526,7 +1595,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1526
1595
|
setIsRotatingSelection(false);
|
|
1527
1596
|
selectDragRef.current.mode = "none";
|
|
1528
1597
|
setSelectionRotationDeg(0);
|
|
1529
|
-
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map() };
|
|
1598
|
+
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map(), startBoundsPage: null };
|
|
1530
1599
|
render();
|
|
1531
1600
|
refreshSelectionBounds(editor);
|
|
1532
1601
|
editor.endHistoryEntry();
|
|
@@ -1544,6 +1613,24 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1544
1613
|
if (drag.mode === "move") {
|
|
1545
1614
|
setIsMovingSelection(false);
|
|
1546
1615
|
selectDragRef.current.mode = "none";
|
|
1616
|
+
const distSq = (x - drag.startPage.x) ** 2 + (y - drag.startPage.y) ** 2;
|
|
1617
|
+
if (drag.moveFromEmptyInsideBounds && distSq < 4) {
|
|
1618
|
+
setSelectedShapeIds([]);
|
|
1619
|
+
selectedShapeIdsRef.current = [];
|
|
1620
|
+
setSelectionBounds(null);
|
|
1621
|
+
} else {
|
|
1622
|
+
refreshSelectionBounds(editor);
|
|
1623
|
+
}
|
|
1624
|
+
selectDragRef.current.moveFromEmptyInsideBounds = void 0;
|
|
1625
|
+
render();
|
|
1626
|
+
editor.endHistoryEntry();
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
if (drag.mode === "vertex") {
|
|
1630
|
+
setIsDraggingVertex(false);
|
|
1631
|
+
selectDragRef.current.mode = "none";
|
|
1632
|
+
selectDragRef.current.vertexRefs = void 0;
|
|
1633
|
+
selectDragRef.current.vertexSnapshots = void 0;
|
|
1547
1634
|
render();
|
|
1548
1635
|
refreshSelectionBounds(editor);
|
|
1549
1636
|
editor.endHistoryEntry();
|
|
@@ -1622,9 +1709,21 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1622
1709
|
editor.input.pointerUp();
|
|
1623
1710
|
if (currentToolRef.current === "select") {
|
|
1624
1711
|
const drag = selectDragRef.current;
|
|
1625
|
-
if (drag.mode === "rotate")
|
|
1712
|
+
if (drag.mode === "rotate") {
|
|
1713
|
+
setIsRotatingSelection(false);
|
|
1714
|
+
setSelectionRotationDeg(0);
|
|
1715
|
+
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map(), startBoundsPage: null };
|
|
1716
|
+
}
|
|
1626
1717
|
if (drag.mode === "resize") setIsResizingSelection(false);
|
|
1627
|
-
if (drag.mode === "move")
|
|
1718
|
+
if (drag.mode === "move") {
|
|
1719
|
+
setIsMovingSelection(false);
|
|
1720
|
+
selectDragRef.current.moveFromEmptyInsideBounds = void 0;
|
|
1721
|
+
}
|
|
1722
|
+
if (drag.mode === "vertex") {
|
|
1723
|
+
setIsDraggingVertex(false);
|
|
1724
|
+
selectDragRef.current.vertexRefs = void 0;
|
|
1725
|
+
selectDragRef.current.vertexSnapshots = void 0;
|
|
1726
|
+
}
|
|
1628
1727
|
if (drag.mode === "marquee") setSelectionBrush(null);
|
|
1629
1728
|
if (drag.mode !== "none") {
|
|
1630
1729
|
selectDragRef.current.mode = "none";
|
|
@@ -1961,10 +2060,25 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1961
2060
|
}, [render]);
|
|
1962
2061
|
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;
|
|
1963
2062
|
const showToolOverlay = isPointerInsideCanvas && (currentTool === "pen" || currentTool === "eraser");
|
|
2063
|
+
react.useEffect(() => {
|
|
2064
|
+
const editor = editorRef.current;
|
|
2065
|
+
if (!editor || currentTool !== "select" || selectedShapeIds.length === 0) {
|
|
2066
|
+
setVertexHandleScreenPositions([]);
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
const pagePts = core.getVertexHandlePagePositions(editor, selectedShapeIds);
|
|
2070
|
+
setVertexHandleScreenPositions(
|
|
2071
|
+
pagePts.map((pg) => {
|
|
2072
|
+
const s = core.pageToScreen(editor.viewport, pg.x, pg.y);
|
|
2073
|
+
return { left: s.x, top: s.y };
|
|
2074
|
+
})
|
|
2075
|
+
);
|
|
2076
|
+
}, [currentTool, selectedShapeIds, selectionBounds, selectionRotationDeg, isPersistenceReady]);
|
|
1964
2077
|
const canvasCursor = getCanvasCursor(currentTool, {
|
|
1965
2078
|
isMovingSelection,
|
|
1966
2079
|
isResizingSelection,
|
|
1967
2080
|
isRotatingSelection,
|
|
2081
|
+
isDraggingVertex,
|
|
1968
2082
|
isHoveringSelectionBounds,
|
|
1969
2083
|
showToolOverlay
|
|
1970
2084
|
});
|
|
@@ -1974,7 +2088,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1974
2088
|
showToolOverlay,
|
|
1975
2089
|
isMovingSelection,
|
|
1976
2090
|
isResizingSelection,
|
|
1977
|
-
isRotatingSelection
|
|
2091
|
+
isRotatingSelection,
|
|
2092
|
+
isDraggingVertex
|
|
1978
2093
|
};
|
|
1979
2094
|
const toolOverlay = {
|
|
1980
2095
|
visible: showToolOverlay,
|
|
@@ -1997,6 +2112,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1997
2112
|
selectionBrush,
|
|
1998
2113
|
selectionBounds,
|
|
1999
2114
|
selectionRotationDeg,
|
|
2115
|
+
vertexHandleScreenPositions,
|
|
2000
2116
|
canvasCursor,
|
|
2001
2117
|
cursorContext,
|
|
2002
2118
|
toolOverlay,
|
|
@@ -2107,6 +2223,7 @@ function Tsdraw(props) {
|
|
|
2107
2223
|
selectionBrush,
|
|
2108
2224
|
selectionBounds,
|
|
2109
2225
|
selectionRotationDeg,
|
|
2226
|
+
vertexHandleScreenPositions,
|
|
2110
2227
|
canvasCursor: defaultCanvasCursor,
|
|
2111
2228
|
cursorContext,
|
|
2112
2229
|
toolOverlay,
|
|
@@ -2288,6 +2405,7 @@ function Tsdraw(props) {
|
|
|
2288
2405
|
selectionBrush,
|
|
2289
2406
|
selectionBounds,
|
|
2290
2407
|
selectionRotationDeg,
|
|
2408
|
+
vertexHandleScreenPositions,
|
|
2291
2409
|
currentTool,
|
|
2292
2410
|
selectedCount: selectedShapeIds.length,
|
|
2293
2411
|
onRotatePointerDown: handleRotatePointerDown,
|