canvu-react 0.4.17 → 0.4.18
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/native.cjs +607 -17
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +20 -1
- package/dist/native.d.ts +20 -1
- package/dist/native.js +607 -17
- package/dist/native.js.map +1 -1
- package/package.json +1 -1
package/dist/native.js
CHANGED
|
@@ -940,6 +940,10 @@ function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
|
|
|
940
940
|
const ly = sin * dx + cos * dy;
|
|
941
941
|
return { x: c.x + lx, y: c.y + ly };
|
|
942
942
|
}
|
|
943
|
+
function itemPivotWorld(item) {
|
|
944
|
+
const r = normalizeRect(item.bounds);
|
|
945
|
+
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
946
|
+
}
|
|
943
947
|
function boundsAabbForRotatedItem(item) {
|
|
944
948
|
const rot = getItemRotationRad(item);
|
|
945
949
|
if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
|
|
@@ -970,6 +974,7 @@ function boundsAabbForRotatedItem(item) {
|
|
|
970
974
|
}
|
|
971
975
|
|
|
972
976
|
// src/interaction/resize-handles.ts
|
|
977
|
+
var HANDLE_IDS = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
|
|
973
978
|
function getHandleWorldPosition(bounds, id) {
|
|
974
979
|
const r = normalizeRect(bounds);
|
|
975
980
|
const cx = r.x + r.width / 2;
|
|
@@ -993,6 +998,30 @@ function getHandleWorldPosition(bounds, id) {
|
|
|
993
998
|
return { x: r.x, y: cy };
|
|
994
999
|
}
|
|
995
1000
|
}
|
|
1001
|
+
function hitTestResizeHandle(bounds, worldX, worldY, radiusWorld, rotationRad = 0) {
|
|
1002
|
+
const r = normalizeRect(bounds);
|
|
1003
|
+
const pl = worldToItemLocal(
|
|
1004
|
+
worldX,
|
|
1005
|
+
worldY,
|
|
1006
|
+
r.x,
|
|
1007
|
+
r.y,
|
|
1008
|
+
r.width,
|
|
1009
|
+
r.height,
|
|
1010
|
+
rotationRad
|
|
1011
|
+
);
|
|
1012
|
+
const localBounds2 = { x: 0, y: 0, width: r.width, height: r.height };
|
|
1013
|
+
let best = null;
|
|
1014
|
+
let bestD = radiusWorld;
|
|
1015
|
+
for (const id of HANDLE_IDS) {
|
|
1016
|
+
const p = getHandleWorldPosition(localBounds2, id);
|
|
1017
|
+
const d = Math.hypot(pl.x - p.x, pl.y - p.y);
|
|
1018
|
+
if (d <= bestD) {
|
|
1019
|
+
bestD = d;
|
|
1020
|
+
best = id;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return best;
|
|
1024
|
+
}
|
|
996
1025
|
function getHandleWorldPositionRotated(bounds, handle, rotationRad) {
|
|
997
1026
|
const r = normalizeRect(bounds);
|
|
998
1027
|
const p = getHandleWorldPosition(
|
|
@@ -1013,6 +1042,10 @@ function getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld)
|
|
|
1013
1042
|
rotationRad
|
|
1014
1043
|
);
|
|
1015
1044
|
}
|
|
1045
|
+
function hitTestRotateHandle(bounds, rotationRad, worldX, worldY, radiusWorld, handleOffsetWorld) {
|
|
1046
|
+
const p = getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld);
|
|
1047
|
+
return Math.hypot(worldX - p.x, worldY - p.y) <= radiusWorld;
|
|
1048
|
+
}
|
|
1016
1049
|
function rectFromCorners(a, b) {
|
|
1017
1050
|
const minX = Math.min(a.x, b.x);
|
|
1018
1051
|
const maxX = Math.max(a.x, b.x);
|
|
@@ -1020,6 +1053,155 @@ function rectFromCorners(a, b) {
|
|
|
1020
1053
|
const maxY = Math.max(a.y, b.y);
|
|
1021
1054
|
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
1022
1055
|
}
|
|
1056
|
+
function clampMinSize(r, min) {
|
|
1057
|
+
const n = normalizeRect(r);
|
|
1058
|
+
return {
|
|
1059
|
+
x: n.x,
|
|
1060
|
+
y: n.y,
|
|
1061
|
+
width: Math.max(min, n.width),
|
|
1062
|
+
height: Math.max(min, n.height)
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
function computeResizeBounds(bounds, handle, currentWorld) {
|
|
1066
|
+
const r = normalizeRect(bounds);
|
|
1067
|
+
const x0 = r.x;
|
|
1068
|
+
const y0 = r.y;
|
|
1069
|
+
const x1 = r.x + r.width;
|
|
1070
|
+
const y1 = r.y + r.height;
|
|
1071
|
+
const minSize = 8;
|
|
1072
|
+
switch (handle) {
|
|
1073
|
+
case "nw":
|
|
1074
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x1, y: y1 }), minSize);
|
|
1075
|
+
case "ne":
|
|
1076
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x0, y: y1 }), minSize);
|
|
1077
|
+
case "se":
|
|
1078
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x0, y: y0 }), minSize);
|
|
1079
|
+
case "sw":
|
|
1080
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x1, y: y0 }), minSize);
|
|
1081
|
+
case "n":
|
|
1082
|
+
return clampMinSize(
|
|
1083
|
+
rectFromCorners({ x: x0, y: currentWorld.y }, { x: x1, y: y1 }),
|
|
1084
|
+
minSize
|
|
1085
|
+
);
|
|
1086
|
+
case "s":
|
|
1087
|
+
return clampMinSize(
|
|
1088
|
+
rectFromCorners({ x: x0, y: y0 }, { x: x1, y: currentWorld.y }),
|
|
1089
|
+
minSize
|
|
1090
|
+
);
|
|
1091
|
+
case "e":
|
|
1092
|
+
return clampMinSize(
|
|
1093
|
+
rectFromCorners({ x: x0, y: y0 }, { x: currentWorld.x, y: y1 }),
|
|
1094
|
+
minSize
|
|
1095
|
+
);
|
|
1096
|
+
case "w":
|
|
1097
|
+
return clampMinSize(
|
|
1098
|
+
rectFromCorners({ x: currentWorld.x, y: y0 }, { x: x1, y: y1 }),
|
|
1099
|
+
minSize
|
|
1100
|
+
);
|
|
1101
|
+
default:
|
|
1102
|
+
return r;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function computeResizeBoundsFixedAspect(bounds, handle, currentWorld, aspect) {
|
|
1106
|
+
const r = normalizeRect(bounds);
|
|
1107
|
+
const x0 = r.x;
|
|
1108
|
+
const y0 = r.y;
|
|
1109
|
+
const x1 = r.x + r.width;
|
|
1110
|
+
const y1 = r.y + r.height;
|
|
1111
|
+
const w = r.width;
|
|
1112
|
+
const h = r.height;
|
|
1113
|
+
const minSize = 8;
|
|
1114
|
+
const a = Math.max(1e-9, aspect);
|
|
1115
|
+
function cornerAspectRect(anchor, cursor) {
|
|
1116
|
+
const dx = cursor.x - anchor.x;
|
|
1117
|
+
const dy = cursor.y - anchor.y;
|
|
1118
|
+
const aw = Math.abs(dx);
|
|
1119
|
+
const ah = Math.abs(dy);
|
|
1120
|
+
let W;
|
|
1121
|
+
let H;
|
|
1122
|
+
if (aw < 1e-12 && ah < 1e-12) {
|
|
1123
|
+
W = minSize;
|
|
1124
|
+
H = minSize / a;
|
|
1125
|
+
} else if (ah < 1e-12) {
|
|
1126
|
+
W = Math.max(minSize, aw);
|
|
1127
|
+
H = W / a;
|
|
1128
|
+
} else if (aw < 1e-12) {
|
|
1129
|
+
H = Math.max(minSize, ah);
|
|
1130
|
+
W = H * a;
|
|
1131
|
+
} else if (aw / ah > a) {
|
|
1132
|
+
W = aw;
|
|
1133
|
+
H = aw / a;
|
|
1134
|
+
} else {
|
|
1135
|
+
H = ah;
|
|
1136
|
+
W = ah * a;
|
|
1137
|
+
}
|
|
1138
|
+
if (W < minSize || H < minSize) {
|
|
1139
|
+
const scale = Math.max(minSize / W, minSize / H);
|
|
1140
|
+
W *= scale;
|
|
1141
|
+
H *= scale;
|
|
1142
|
+
}
|
|
1143
|
+
const sx = Math.sign(dx) || 1;
|
|
1144
|
+
const sy = Math.sign(dy) || 1;
|
|
1145
|
+
return rectFromCorners(anchor, {
|
|
1146
|
+
x: anchor.x + sx * W,
|
|
1147
|
+
y: anchor.y + sy * H
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
switch (handle) {
|
|
1151
|
+
case "se":
|
|
1152
|
+
return cornerAspectRect({ x: x0, y: y0 }, currentWorld);
|
|
1153
|
+
case "nw":
|
|
1154
|
+
return cornerAspectRect({ x: x1, y: y1 }, currentWorld);
|
|
1155
|
+
case "ne":
|
|
1156
|
+
return cornerAspectRect({ x: x0, y: y1 }, currentWorld);
|
|
1157
|
+
case "sw":
|
|
1158
|
+
return cornerAspectRect({ x: x1, y: y0 }, currentWorld);
|
|
1159
|
+
case "e": {
|
|
1160
|
+
const rawW = currentWorld.x - x0;
|
|
1161
|
+
const aw = Math.max(minSize, Math.abs(rawW));
|
|
1162
|
+
const H = aw / a;
|
|
1163
|
+
const newY = y0 + h / 2 - H / 2;
|
|
1164
|
+
const sign = Math.sign(rawW) || 1;
|
|
1165
|
+
return normalizeRect({ x: x0, y: newY, width: sign * aw, height: H });
|
|
1166
|
+
}
|
|
1167
|
+
case "w": {
|
|
1168
|
+
const rawW = x1 - currentWorld.x;
|
|
1169
|
+
const aw = Math.max(minSize, Math.abs(rawW));
|
|
1170
|
+
const H = aw / a;
|
|
1171
|
+
const newY = y0 + h / 2 - H / 2;
|
|
1172
|
+
const sign = Math.sign(rawW) || 1;
|
|
1173
|
+
return normalizeRect({
|
|
1174
|
+
x: x1 - sign * aw,
|
|
1175
|
+
y: newY,
|
|
1176
|
+
width: sign * aw,
|
|
1177
|
+
height: H
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
case "n": {
|
|
1181
|
+
const rawH = y1 - currentWorld.y;
|
|
1182
|
+
const ah = Math.max(minSize, Math.abs(rawH));
|
|
1183
|
+
const W = ah * a;
|
|
1184
|
+
const newX = x0 + w / 2 - W / 2;
|
|
1185
|
+
const sign = Math.sign(rawH) || 1;
|
|
1186
|
+
return normalizeRect({
|
|
1187
|
+
x: newX,
|
|
1188
|
+
y: y1 - sign * ah,
|
|
1189
|
+
width: W,
|
|
1190
|
+
height: sign * ah
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
case "s": {
|
|
1194
|
+
const rawH = currentWorld.y - y0;
|
|
1195
|
+
const ah = Math.max(minSize, Math.abs(rawH));
|
|
1196
|
+
const W = ah * a;
|
|
1197
|
+
const newX = x0 + w / 2 - W / 2;
|
|
1198
|
+
const sign = Math.sign(rawH) || 1;
|
|
1199
|
+
return normalizeRect({ x: newX, y: y0, width: W, height: sign * ah });
|
|
1200
|
+
}
|
|
1201
|
+
default:
|
|
1202
|
+
return r;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1023
1205
|
|
|
1024
1206
|
// src/scene/freehand-path.ts
|
|
1025
1207
|
function smoothFreehandPointsToPathD(points) {
|
|
@@ -3581,6 +3763,211 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
|
|
|
3581
3763
|
return ids;
|
|
3582
3764
|
}
|
|
3583
3765
|
|
|
3766
|
+
// src/interaction/mutations.ts
|
|
3767
|
+
function computeNewBoundsForResize(item, sb, handle, currentWorld) {
|
|
3768
|
+
const rot = getItemRotationRad(item);
|
|
3769
|
+
if (Math.abs(rot) < 1e-12) {
|
|
3770
|
+
if (item.toolKind === "image") {
|
|
3771
|
+
let aspect;
|
|
3772
|
+
if (item.imageIntrinsicSize) {
|
|
3773
|
+
const iw = Math.max(1e-9, item.imageIntrinsicSize.width);
|
|
3774
|
+
const ih = Math.max(1e-9, item.imageIntrinsicSize.height);
|
|
3775
|
+
aspect = iw / ih;
|
|
3776
|
+
} else if (item.imageVectorLocalSize) {
|
|
3777
|
+
const lw = Math.max(1e-9, item.imageVectorLocalSize.width);
|
|
3778
|
+
const lh = Math.max(1e-9, item.imageVectorLocalSize.height);
|
|
3779
|
+
aspect = lw / lh;
|
|
3780
|
+
}
|
|
3781
|
+
return aspect !== void 0 ? computeResizeBoundsFixedAspect(sb, handle, currentWorld, aspect) : computeResizeBounds(sb, handle, currentWorld);
|
|
3782
|
+
}
|
|
3783
|
+
return computeResizeBounds(sb, handle, currentWorld);
|
|
3784
|
+
}
|
|
3785
|
+
const local = worldToItemLocal(
|
|
3786
|
+
currentWorld.x,
|
|
3787
|
+
currentWorld.y,
|
|
3788
|
+
sb.x,
|
|
3789
|
+
sb.y,
|
|
3790
|
+
sb.width,
|
|
3791
|
+
sb.height,
|
|
3792
|
+
rot
|
|
3793
|
+
);
|
|
3794
|
+
const localBounds2 = { x: 0, y: 0, width: sb.width, height: sb.height };
|
|
3795
|
+
let nbLocal;
|
|
3796
|
+
if (item.toolKind === "image") {
|
|
3797
|
+
let aspect;
|
|
3798
|
+
if (item.imageIntrinsicSize) {
|
|
3799
|
+
const iw = Math.max(1e-9, item.imageIntrinsicSize.width);
|
|
3800
|
+
const ih = Math.max(1e-9, item.imageIntrinsicSize.height);
|
|
3801
|
+
aspect = iw / ih;
|
|
3802
|
+
} else if (item.imageVectorLocalSize) {
|
|
3803
|
+
const lw = Math.max(1e-9, item.imageVectorLocalSize.width);
|
|
3804
|
+
const lh = Math.max(1e-9, item.imageVectorLocalSize.height);
|
|
3805
|
+
aspect = lw / lh;
|
|
3806
|
+
}
|
|
3807
|
+
nbLocal = aspect !== void 0 ? computeResizeBoundsFixedAspect(localBounds2, handle, local, aspect) : computeResizeBounds(localBounds2, handle, local);
|
|
3808
|
+
} else {
|
|
3809
|
+
nbLocal = computeResizeBounds(localBounds2, handle, local);
|
|
3810
|
+
}
|
|
3811
|
+
return {
|
|
3812
|
+
x: sb.x + nbLocal.x,
|
|
3813
|
+
y: sb.y + nbLocal.y,
|
|
3814
|
+
width: nbLocal.width,
|
|
3815
|
+
height: nbLocal.height
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
function applyRotationFromPointer(item, startRotation, startPointerAngleRad, pointerAngleRad) {
|
|
3819
|
+
let delta = pointerAngleRad - startPointerAngleRad;
|
|
3820
|
+
while (delta > Math.PI) {
|
|
3821
|
+
delta -= 2 * Math.PI;
|
|
3822
|
+
}
|
|
3823
|
+
while (delta < -Math.PI) {
|
|
3824
|
+
delta += 2 * Math.PI;
|
|
3825
|
+
}
|
|
3826
|
+
return { ...item, rotation: startRotation + delta };
|
|
3827
|
+
}
|
|
3828
|
+
function resizeItemByHandle(item, start, handle, currentWorld) {
|
|
3829
|
+
const sb = normalizeRect(start.bounds);
|
|
3830
|
+
const newBoundsRaw = computeNewBoundsForResize(item, sb, handle, currentWorld);
|
|
3831
|
+
const nb = normalizeRect(newBoundsRaw);
|
|
3832
|
+
const k = item.toolKind;
|
|
3833
|
+
if (k === "rect") {
|
|
3834
|
+
const style = resolveStrokeStyle(item);
|
|
3835
|
+
return {
|
|
3836
|
+
...item,
|
|
3837
|
+
x: nb.x,
|
|
3838
|
+
y: nb.y,
|
|
3839
|
+
bounds: nb,
|
|
3840
|
+
childrenSvg: buildRectSvg(nb.width, nb.height, style)
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
if (k === "ellipse") {
|
|
3844
|
+
const style = resolveStrokeStyle(item);
|
|
3845
|
+
return {
|
|
3846
|
+
...item,
|
|
3847
|
+
x: nb.x,
|
|
3848
|
+
y: nb.y,
|
|
3849
|
+
bounds: nb,
|
|
3850
|
+
childrenSvg: buildEllipseSvg(nb.width, nb.height, style)
|
|
3851
|
+
};
|
|
3852
|
+
}
|
|
3853
|
+
if (k === "architectural-cloud") {
|
|
3854
|
+
const style = resolveStrokeStyle(item);
|
|
3855
|
+
return {
|
|
3856
|
+
...item,
|
|
3857
|
+
x: nb.x,
|
|
3858
|
+
y: nb.y,
|
|
3859
|
+
bounds: nb,
|
|
3860
|
+
childrenSvg: buildArchitecturalCloudSvg(nb.width, nb.height, style)
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
if (k === "text" && item.text !== void 0) {
|
|
3864
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3865
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3866
|
+
const baseFs = item.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
|
|
3867
|
+
const areaRatio = nb.width * nb.height / (sfw * sfh);
|
|
3868
|
+
const scale = Math.sqrt(Math.max(areaRatio, 1e-12));
|
|
3869
|
+
const nextFs = Math.min(256, Math.max(6, baseFs * scale));
|
|
3870
|
+
return rebuildItemSvg({
|
|
3871
|
+
...item,
|
|
3872
|
+
x: nb.x,
|
|
3873
|
+
y: nb.y,
|
|
3874
|
+
bounds: nb,
|
|
3875
|
+
textFixedBounds: true,
|
|
3876
|
+
textFontSize: nextFs
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3879
|
+
if (k === "image") {
|
|
3880
|
+
if (item.imageRasterHref && item.imageIntrinsicSize) {
|
|
3881
|
+
return {
|
|
3882
|
+
...item,
|
|
3883
|
+
x: nb.x,
|
|
3884
|
+
y: nb.y,
|
|
3885
|
+
bounds: nb,
|
|
3886
|
+
childrenSvg: buildRasterImageChildrenSvg(
|
|
3887
|
+
item.imageRasterHref,
|
|
3888
|
+
item.imageIntrinsicSize,
|
|
3889
|
+
nb
|
|
3890
|
+
)
|
|
3891
|
+
};
|
|
3892
|
+
}
|
|
3893
|
+
if (item.imageVectorInnerSvg && item.imageVectorLocalSize) {
|
|
3894
|
+
const lw = Math.max(1e-6, item.imageVectorLocalSize.width);
|
|
3895
|
+
const lh = Math.max(1e-6, item.imageVectorLocalSize.height);
|
|
3896
|
+
const arB = nb.width / Math.max(1e-9, nb.height);
|
|
3897
|
+
const arI = lw / lh;
|
|
3898
|
+
let childrenSvg;
|
|
3899
|
+
if (Math.abs(arB - arI) < 1e-3) {
|
|
3900
|
+
const s = nb.width / lw;
|
|
3901
|
+
childrenSvg = `<g transform="scale(${s})">${item.imageVectorInnerSvg}</g>`;
|
|
3902
|
+
} else {
|
|
3903
|
+
const s = Math.min(nb.width / lw, nb.height / lh);
|
|
3904
|
+
const tx = (nb.width - lw * s) / 2;
|
|
3905
|
+
const ty = (nb.height - lh * s) / 2;
|
|
3906
|
+
childrenSvg = `<g transform="translate(${tx}, ${ty}) scale(${s})">${item.imageVectorInnerSvg}</g>`;
|
|
3907
|
+
}
|
|
3908
|
+
return {
|
|
3909
|
+
...item,
|
|
3910
|
+
x: nb.x,
|
|
3911
|
+
y: nb.y,
|
|
3912
|
+
bounds: nb,
|
|
3913
|
+
childrenSvg
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
if ((k === "line" || k === "arrow") && start.line) {
|
|
3918
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3919
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3920
|
+
const f = start.line;
|
|
3921
|
+
const u1 = f.x1 / sfw;
|
|
3922
|
+
const v1 = f.y1 / sfh;
|
|
3923
|
+
const u2 = f.x2 / sfw;
|
|
3924
|
+
const v2 = f.y2 / sfh;
|
|
3925
|
+
const newLine = {
|
|
3926
|
+
x1: u1 * nb.width,
|
|
3927
|
+
y1: v1 * nb.height,
|
|
3928
|
+
x2: u2 * nb.width,
|
|
3929
|
+
y2: v2 * nb.height
|
|
3930
|
+
};
|
|
3931
|
+
const style = resolveStrokeStyle(item);
|
|
3932
|
+
const childrenSvg = k === "arrow" ? buildArrowSvg(item.id, newLine, style) : buildLineSvg(newLine, style);
|
|
3933
|
+
return {
|
|
3934
|
+
...item,
|
|
3935
|
+
x: nb.x,
|
|
3936
|
+
y: nb.y,
|
|
3937
|
+
bounds: nb,
|
|
3938
|
+
line: newLine,
|
|
3939
|
+
childrenSvg
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
3943
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3944
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3945
|
+
const sx = nb.width / sfw;
|
|
3946
|
+
const sy = nb.height / sfh;
|
|
3947
|
+
const scaledPoints = item.pathPointsLocal.map((p) => ({
|
|
3948
|
+
x: p.x * sx,
|
|
3949
|
+
y: p.y * sy,
|
|
3950
|
+
...p.pressure != null ? { pressure: p.pressure } : {}
|
|
3951
|
+
}));
|
|
3952
|
+
return rebuildItemSvg({
|
|
3953
|
+
...item,
|
|
3954
|
+
x: nb.x,
|
|
3955
|
+
y: nb.y,
|
|
3956
|
+
bounds: nb,
|
|
3957
|
+
pathPointsLocal: scaledPoints
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
if (k === "custom" && item.customIntrinsicSize && item.customInnerSvg) {
|
|
3961
|
+
return rebuildItemSvg({
|
|
3962
|
+
...item,
|
|
3963
|
+
x: nb.x,
|
|
3964
|
+
y: nb.y,
|
|
3965
|
+
bounds: nb
|
|
3966
|
+
});
|
|
3967
|
+
}
|
|
3968
|
+
return { ...item, x: nb.x, y: nb.y, bounds: nb };
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3584
3971
|
// src/native/native-tool-cursors.ts
|
|
3585
3972
|
var ICON_SIZE = 24;
|
|
3586
3973
|
var CENTER_HOTSPOT = { x: 12, y: 12 };
|
|
@@ -3606,10 +3993,72 @@ function nativeCursorForVectorToolId(toolId) {
|
|
|
3606
3993
|
return null;
|
|
3607
3994
|
}
|
|
3608
3995
|
}
|
|
3996
|
+
function nativeCrosshairToolCursor() {
|
|
3997
|
+
return { kind: "crosshair", size: ICON_SIZE, hotspot: CENTER_HOTSPOT };
|
|
3998
|
+
}
|
|
3609
3999
|
function nativeFallbackToolCursorPoint(size) {
|
|
3610
4000
|
if (size.width <= 0 || size.height <= 0) return null;
|
|
3611
4001
|
return { x: size.width / 2, y: size.height / 2 };
|
|
3612
4002
|
}
|
|
4003
|
+
|
|
4004
|
+
// src/native/native-vector-interactions.ts
|
|
4005
|
+
function supportsNativeResizeHandles(item) {
|
|
4006
|
+
const k = item?.toolKind;
|
|
4007
|
+
if (k === "rect" || k === "ellipse" || k === "architectural-cloud" || k === "line" || k === "arrow" || k === "image" || k === "text") {
|
|
4008
|
+
return true;
|
|
4009
|
+
}
|
|
4010
|
+
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item?.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
4011
|
+
return true;
|
|
4012
|
+
}
|
|
4013
|
+
return k === "custom" && !!item?.customIntrinsicSize && !!item?.customInnerSvg;
|
|
4014
|
+
}
|
|
4015
|
+
function hitTestNativeSelectionHandle({
|
|
4016
|
+
selectedItem,
|
|
4017
|
+
selectedCount,
|
|
4018
|
+
worldPoint,
|
|
4019
|
+
zoom
|
|
4020
|
+
}) {
|
|
4021
|
+
if (selectedCount !== 1) return null;
|
|
4022
|
+
if (!selectedItem || selectedItem.locked) return null;
|
|
4023
|
+
if (!supportsNativeResizeHandles(selectedItem)) return null;
|
|
4024
|
+
const bounds = normalizeRect(selectedItem.bounds);
|
|
4025
|
+
const rotation = selectedItem.rotation ?? 0;
|
|
4026
|
+
const handleRadiusWorld = 6 / Math.max(zoom, 1e-9);
|
|
4027
|
+
const rotateOffsetWorld = 24 / Math.max(zoom, 1e-9);
|
|
4028
|
+
if (hitTestRotateHandle(
|
|
4029
|
+
bounds,
|
|
4030
|
+
rotation,
|
|
4031
|
+
worldPoint.x,
|
|
4032
|
+
worldPoint.y,
|
|
4033
|
+
handleRadiusWorld,
|
|
4034
|
+
rotateOffsetWorld
|
|
4035
|
+
)) {
|
|
4036
|
+
return { kind: "rotate" };
|
|
4037
|
+
}
|
|
4038
|
+
const handle = hitTestResizeHandle(
|
|
4039
|
+
bounds,
|
|
4040
|
+
worldPoint.x,
|
|
4041
|
+
worldPoint.y,
|
|
4042
|
+
handleRadiusWorld,
|
|
4043
|
+
rotation
|
|
4044
|
+
);
|
|
4045
|
+
return handle ? { kind: "resize", handle } : null;
|
|
4046
|
+
}
|
|
4047
|
+
function resolveNativeCustomPlacement(toolId, customPlacement, customPlacements) {
|
|
4048
|
+
if (customPlacement?.toolId === toolId) return customPlacement;
|
|
4049
|
+
return customPlacements?.find((placement) => placement.toolId === toolId) ?? null;
|
|
4050
|
+
}
|
|
4051
|
+
function nativeRotationDragStart(input) {
|
|
4052
|
+
const pivotWorld = itemPivotWorld(input.item);
|
|
4053
|
+
return {
|
|
4054
|
+
pivotWorld,
|
|
4055
|
+
startPointerAngleRad: Math.atan2(
|
|
4056
|
+
input.worldPoint.y - pivotWorld.y,
|
|
4057
|
+
input.worldPoint.x - pivotWorld.x
|
|
4058
|
+
),
|
|
4059
|
+
startRotation: input.item.rotation ?? 0
|
|
4060
|
+
};
|
|
4061
|
+
}
|
|
3613
4062
|
var MIN_PLACE_SIZE = 8;
|
|
3614
4063
|
var MIN_ARROW_DRAG_PX = 8;
|
|
3615
4064
|
var TAP_PX = 20;
|
|
@@ -3624,16 +4073,6 @@ function isPlacementTool(toolId) {
|
|
|
3624
4073
|
function isDefaultMarkerToolStyle(style) {
|
|
3625
4074
|
return style.stroke === MARKER_TOOL_STYLE.stroke && style.strokeWidth === MARKER_TOOL_STYLE.strokeWidth && style.strokeOpacity === MARKER_TOOL_STYLE.strokeOpacity;
|
|
3626
4075
|
}
|
|
3627
|
-
function supportsNativeResizeHandles(item) {
|
|
3628
|
-
const k = item?.toolKind;
|
|
3629
|
-
if (k === "rect" || k === "ellipse" || k === "architectural-cloud" || k === "line" || k === "arrow" || k === "image" || k === "text") {
|
|
3630
|
-
return true;
|
|
3631
|
-
}
|
|
3632
|
-
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item?.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
3633
|
-
return true;
|
|
3634
|
-
}
|
|
3635
|
-
return k === "custom" && !!item?.customIntrinsicSize && !!item?.customInnerSvg;
|
|
3636
|
-
}
|
|
3637
4076
|
function placementPreviewForTool(toolId, start, end) {
|
|
3638
4077
|
if (toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud") {
|
|
3639
4078
|
return { kind: toolId, rect: rectFromCorners(start, end) };
|
|
@@ -3701,7 +4140,11 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
3701
4140
|
onSelectionChange,
|
|
3702
4141
|
onItemsChange,
|
|
3703
4142
|
onToolChangeRequest,
|
|
4143
|
+
onWorldPointerDown,
|
|
3704
4144
|
onCameraChange,
|
|
4145
|
+
customPlacement,
|
|
4146
|
+
customPlacements = [],
|
|
4147
|
+
customCrosshairToolIds = [],
|
|
3705
4148
|
toolbar,
|
|
3706
4149
|
showStyleInspector = false,
|
|
3707
4150
|
styleInspectorPlacement = "bottom"
|
|
@@ -3714,10 +4157,18 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
3714
4157
|
toolLockedRef.current = toolLocked;
|
|
3715
4158
|
const onToolChangeRequestRef = useRef(onToolChangeRequest);
|
|
3716
4159
|
onToolChangeRequestRef.current = onToolChangeRequest;
|
|
4160
|
+
const onWorldPointerDownRef = useRef(onWorldPointerDown);
|
|
4161
|
+
onWorldPointerDownRef.current = onWorldPointerDown;
|
|
3717
4162
|
const onCameraChangeRef = useRef(onCameraChange);
|
|
3718
4163
|
onCameraChangeRef.current = onCameraChange;
|
|
3719
4164
|
const onItemsChangeRef = useRef(onItemsChange);
|
|
3720
4165
|
onItemsChangeRef.current = onItemsChange;
|
|
4166
|
+
const customPlacementRef = useRef(customPlacement);
|
|
4167
|
+
customPlacementRef.current = customPlacement;
|
|
4168
|
+
const customPlacementsRef = useRef(customPlacements);
|
|
4169
|
+
customPlacementsRef.current = customPlacements;
|
|
4170
|
+
const customCrosshairToolIdsRef = useRef(customCrosshairToolIds);
|
|
4171
|
+
customCrosshairToolIdsRef.current = customCrosshairToolIds;
|
|
3721
4172
|
const onSelectionChangeRef = useRef(onSelectionChange);
|
|
3722
4173
|
onSelectionChangeRef.current = onSelectionChange;
|
|
3723
4174
|
const itemsRef = useRef(items);
|
|
@@ -3825,6 +4276,11 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
3825
4276
|
setCameraTick((n) => n + 1);
|
|
3826
4277
|
onCameraChangeRef.current?.();
|
|
3827
4278
|
}, []);
|
|
4279
|
+
const cursorForToolId = useCallback((nextToolId) => {
|
|
4280
|
+
const builtInCursor = nativeCursorForVectorToolId(nextToolId);
|
|
4281
|
+
if (builtInCursor) return builtInCursor;
|
|
4282
|
+
return customCrosshairToolIdsRef.current.includes(nextToolId) ? nativeCrosshairToolCursor() : null;
|
|
4283
|
+
}, []);
|
|
3828
4284
|
const onLayout = useCallback(
|
|
3829
4285
|
(e) => {
|
|
3830
4286
|
const { width, height } = e.nativeEvent.layout;
|
|
@@ -3834,7 +4290,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
3834
4290
|
setToolCursorPoint(null);
|
|
3835
4291
|
return;
|
|
3836
4292
|
}
|
|
3837
|
-
if (!
|
|
4293
|
+
if (!cursorForToolId(toolIdRef.current)) {
|
|
3838
4294
|
setToolCursorPoint(null);
|
|
3839
4295
|
return;
|
|
3840
4296
|
}
|
|
@@ -3842,15 +4298,15 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
3842
4298
|
(current) => current ?? nativeFallbackToolCursorPoint(nextSize)
|
|
3843
4299
|
);
|
|
3844
4300
|
},
|
|
3845
|
-
[interactive]
|
|
4301
|
+
[cursorForToolId, interactive]
|
|
3846
4302
|
);
|
|
3847
4303
|
const updateToolCursorPoint = useCallback(
|
|
3848
4304
|
(point) => {
|
|
3849
4305
|
if (!interactive) return;
|
|
3850
|
-
if (!
|
|
4306
|
+
if (!cursorForToolId(toolIdRef.current)) return;
|
|
3851
4307
|
setToolCursorPoint(point);
|
|
3852
4308
|
},
|
|
3853
|
-
[interactive]
|
|
4309
|
+
[cursorForToolId, interactive]
|
|
3854
4310
|
);
|
|
3855
4311
|
const hideToolCursor = useCallback(() => {
|
|
3856
4312
|
setToolCursorPoint(null);
|
|
@@ -3861,7 +4317,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
3861
4317
|
setToolCursorPoint(null);
|
|
3862
4318
|
return;
|
|
3863
4319
|
}
|
|
3864
|
-
if (!
|
|
4320
|
+
if (!cursorForToolId(nextToolId)) {
|
|
3865
4321
|
setToolCursorPoint(null);
|
|
3866
4322
|
return;
|
|
3867
4323
|
}
|
|
@@ -3869,7 +4325,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
3869
4325
|
(current) => current ?? nativeFallbackToolCursorPoint(size)
|
|
3870
4326
|
);
|
|
3871
4327
|
},
|
|
3872
|
-
[interactive, size]
|
|
4328
|
+
[cursorForToolId, interactive, size]
|
|
3873
4329
|
);
|
|
3874
4330
|
useEffect(() => {
|
|
3875
4331
|
showFallbackToolCursor(toolId);
|
|
@@ -3921,6 +4377,40 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
3921
4377
|
return;
|
|
3922
4378
|
}
|
|
3923
4379
|
if (tool === "select") {
|
|
4380
|
+
const currentSelectedIds = selectedIdsRef.current;
|
|
4381
|
+
const selectedItem = currentSelectedIds.length === 1 ? itemsRef.current.find((item) => item.id === currentSelectedIds[0]) : void 0;
|
|
4382
|
+
const selectionHandle = hitTestNativeSelectionHandle({
|
|
4383
|
+
selectedItem,
|
|
4384
|
+
selectedCount: currentSelectedIds.length,
|
|
4385
|
+
worldPoint: { x: worldX, y: worldY },
|
|
4386
|
+
zoom: cam.zoom
|
|
4387
|
+
});
|
|
4388
|
+
if (selectionHandle && selectedItem) {
|
|
4389
|
+
if (selectionHandle.kind === "rotate") {
|
|
4390
|
+
const rotationStart = nativeRotationDragStart({
|
|
4391
|
+
item: selectedItem,
|
|
4392
|
+
worldPoint: { x: worldX, y: worldY }
|
|
4393
|
+
});
|
|
4394
|
+
dragStateRef.current = {
|
|
4395
|
+
kind: "rotate",
|
|
4396
|
+
id: selectedItem.id,
|
|
4397
|
+
snapshot: selectedItem,
|
|
4398
|
+
...rotationStart
|
|
4399
|
+
};
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
dragStateRef.current = {
|
|
4403
|
+
kind: "resize",
|
|
4404
|
+
id: selectedItem.id,
|
|
4405
|
+
handle: selectionHandle.handle,
|
|
4406
|
+
snapshot: selectedItem,
|
|
4407
|
+
start: {
|
|
4408
|
+
bounds: selectedItem.bounds,
|
|
4409
|
+
line: selectedItem.line
|
|
4410
|
+
}
|
|
4411
|
+
};
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
3924
4414
|
const hit = hitTestWorldPoint(itemsRef.current, worldX, worldY, {
|
|
3925
4415
|
lineHitWorld: 10 / cam.zoom,
|
|
3926
4416
|
ignoreLocked: true
|
|
@@ -4013,6 +4503,25 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
4013
4503
|
);
|
|
4014
4504
|
return;
|
|
4015
4505
|
}
|
|
4506
|
+
const customPlacement2 = resolveNativeCustomPlacement(
|
|
4507
|
+
tool,
|
|
4508
|
+
customPlacementRef.current,
|
|
4509
|
+
customPlacementsRef.current
|
|
4510
|
+
);
|
|
4511
|
+
if (customPlacement2) {
|
|
4512
|
+
dragStateRef.current = {
|
|
4513
|
+
kind: "custom-place",
|
|
4514
|
+
tool,
|
|
4515
|
+
placement: customPlacement2,
|
|
4516
|
+
startWorld: { x: worldX, y: worldY },
|
|
4517
|
+
startScreen: { x: sx, y: sy }
|
|
4518
|
+
};
|
|
4519
|
+
setPlacementPreview({
|
|
4520
|
+
kind: "rect",
|
|
4521
|
+
rect: { x: worldX, y: worldY, width: 0, height: 0 }
|
|
4522
|
+
});
|
|
4523
|
+
return;
|
|
4524
|
+
}
|
|
4016
4525
|
if (tool === "note" || tool === "text") {
|
|
4017
4526
|
dragStateRef.current = {
|
|
4018
4527
|
kind: "tap",
|
|
@@ -4022,6 +4531,18 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
4022
4531
|
};
|
|
4023
4532
|
return;
|
|
4024
4533
|
}
|
|
4534
|
+
const handleWorldPointerDown = onWorldPointerDownRef.current;
|
|
4535
|
+
if (handleWorldPointerDown) {
|
|
4536
|
+
handleWorldPointerDown({
|
|
4537
|
+
toolId: tool,
|
|
4538
|
+
worldX,
|
|
4539
|
+
worldY,
|
|
4540
|
+
screenX: sx,
|
|
4541
|
+
screenY: sy
|
|
4542
|
+
});
|
|
4543
|
+
requestSelectToolAfterUse();
|
|
4544
|
+
return;
|
|
4545
|
+
}
|
|
4025
4546
|
dragStateRef.current = { kind: "pan" };
|
|
4026
4547
|
},
|
|
4027
4548
|
onPanResponderMove: (evt) => {
|
|
@@ -4117,6 +4638,36 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
4117
4638
|
change(nextList);
|
|
4118
4639
|
return;
|
|
4119
4640
|
}
|
|
4641
|
+
if (st.kind === "rotate") {
|
|
4642
|
+
const change = onItemsChangeRef.current;
|
|
4643
|
+
if (!change) return;
|
|
4644
|
+
const angle = Math.atan2(
|
|
4645
|
+
worldY - st.pivotWorld.y,
|
|
4646
|
+
worldX - st.pivotWorld.x
|
|
4647
|
+
);
|
|
4648
|
+
const next = applyRotationFromPointer(
|
|
4649
|
+
st.snapshot,
|
|
4650
|
+
st.startRotation,
|
|
4651
|
+
st.startPointerAngleRad,
|
|
4652
|
+
angle
|
|
4653
|
+
);
|
|
4654
|
+
change(
|
|
4655
|
+
itemsRef.current.map((item) => item.id === st.id ? next : item)
|
|
4656
|
+
);
|
|
4657
|
+
return;
|
|
4658
|
+
}
|
|
4659
|
+
if (st.kind === "resize") {
|
|
4660
|
+
const change = onItemsChangeRef.current;
|
|
4661
|
+
if (!change) return;
|
|
4662
|
+
const next = resizeItemByHandle(st.snapshot, st.start, st.handle, {
|
|
4663
|
+
x: worldX,
|
|
4664
|
+
y: worldY
|
|
4665
|
+
});
|
|
4666
|
+
change(
|
|
4667
|
+
itemsRef.current.map((item) => item.id === st.id ? next : item)
|
|
4668
|
+
);
|
|
4669
|
+
return;
|
|
4670
|
+
}
|
|
4120
4671
|
if (st.kind === "marquee") {
|
|
4121
4672
|
const a = st.startWorld;
|
|
4122
4673
|
const b = { x: worldX, y: worldY };
|
|
@@ -4155,6 +4706,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
4155
4706
|
);
|
|
4156
4707
|
return;
|
|
4157
4708
|
}
|
|
4709
|
+
if (st.kind === "custom-place") {
|
|
4710
|
+
setPlacementPreview({
|
|
4711
|
+
kind: "rect",
|
|
4712
|
+
rect: rectFromCorners(st.startWorld, { x: worldX, y: worldY })
|
|
4713
|
+
});
|
|
4714
|
+
return;
|
|
4715
|
+
}
|
|
4158
4716
|
},
|
|
4159
4717
|
onPanResponderRelease: (evt) => {
|
|
4160
4718
|
lastPinchDist.current = null;
|
|
@@ -4198,6 +4756,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
4198
4756
|
dragStateRef.current = { kind: "idle" };
|
|
4199
4757
|
return;
|
|
4200
4758
|
}
|
|
4759
|
+
if (st.kind === "resize" || st.kind === "rotate") {
|
|
4760
|
+
dragStateRef.current = { kind: "idle" };
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4201
4763
|
if (st.kind === "marquee") {
|
|
4202
4764
|
dragStateRef.current = { kind: "idle" };
|
|
4203
4765
|
setPlacementPreview(null);
|
|
@@ -4295,6 +4857,34 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
4295
4857
|
requestSelectToolAfterUse();
|
|
4296
4858
|
return;
|
|
4297
4859
|
}
|
|
4860
|
+
if (st.kind === "custom-place") {
|
|
4861
|
+
dragStateRef.current = { kind: "idle" };
|
|
4862
|
+
setPlacementPreview(null);
|
|
4863
|
+
const change = onItemsChangeRef.current;
|
|
4864
|
+
if (!change) return;
|
|
4865
|
+
const { worldX, worldY } = screenToWorld(
|
|
4866
|
+
evt.nativeEvent.locationX,
|
|
4867
|
+
evt.nativeEvent.locationY
|
|
4868
|
+
);
|
|
4869
|
+
const center = {
|
|
4870
|
+
x: (st.startWorld.x + worldX) / 2,
|
|
4871
|
+
y: (st.startWorld.y + worldY) / 2
|
|
4872
|
+
};
|
|
4873
|
+
const raw = rectFromCorners(st.startWorld, { x: worldX, y: worldY });
|
|
4874
|
+
const normalized = normalizeRect(raw);
|
|
4875
|
+
const bounds = normalized.width < MIN_PLACE_SIZE || normalized.height < MIN_PLACE_SIZE ? {
|
|
4876
|
+
x: center.x - 60,
|
|
4877
|
+
y: center.y - 40,
|
|
4878
|
+
width: 120,
|
|
4879
|
+
height: 80
|
|
4880
|
+
} : normalized;
|
|
4881
|
+
const id = createShapeId();
|
|
4882
|
+
const item = st.placement.createItem({ id, bounds });
|
|
4883
|
+
change([...itemsRef.current, item]);
|
|
4884
|
+
onSelectionChangeRef.current?.([id]);
|
|
4885
|
+
requestSelectToolAfterUse();
|
|
4886
|
+
return;
|
|
4887
|
+
}
|
|
4298
4888
|
if (st.kind === "tap") {
|
|
4299
4889
|
dragStateRef.current = { kind: "idle" };
|
|
4300
4890
|
const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
|
|
@@ -4386,7 +4976,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
|
|
|
4386
4976
|
[requestRender, size]
|
|
4387
4977
|
);
|
|
4388
4978
|
const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
|
|
4389
|
-
const activeToolCursor =
|
|
4979
|
+
const activeToolCursor = cursorForToolId(toolId);
|
|
4390
4980
|
const toolCursor = activeToolCursor && toolCursorPoint ? { cursor: activeToolCursor, point: toolCursorPoint } : null;
|
|
4391
4981
|
return /* @__PURE__ */ jsx(
|
|
4392
4982
|
View,
|