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.cjs
CHANGED
|
@@ -946,6 +946,10 @@ function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
|
|
|
946
946
|
const ly = sin * dx + cos * dy;
|
|
947
947
|
return { x: c.x + lx, y: c.y + ly };
|
|
948
948
|
}
|
|
949
|
+
function itemPivotWorld(item) {
|
|
950
|
+
const r = normalizeRect(item.bounds);
|
|
951
|
+
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
952
|
+
}
|
|
949
953
|
function boundsAabbForRotatedItem(item) {
|
|
950
954
|
const rot = getItemRotationRad(item);
|
|
951
955
|
if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
|
|
@@ -976,6 +980,7 @@ function boundsAabbForRotatedItem(item) {
|
|
|
976
980
|
}
|
|
977
981
|
|
|
978
982
|
// src/interaction/resize-handles.ts
|
|
983
|
+
var HANDLE_IDS = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
|
|
979
984
|
function getHandleWorldPosition(bounds, id) {
|
|
980
985
|
const r = normalizeRect(bounds);
|
|
981
986
|
const cx = r.x + r.width / 2;
|
|
@@ -999,6 +1004,30 @@ function getHandleWorldPosition(bounds, id) {
|
|
|
999
1004
|
return { x: r.x, y: cy };
|
|
1000
1005
|
}
|
|
1001
1006
|
}
|
|
1007
|
+
function hitTestResizeHandle(bounds, worldX, worldY, radiusWorld, rotationRad = 0) {
|
|
1008
|
+
const r = normalizeRect(bounds);
|
|
1009
|
+
const pl = worldToItemLocal(
|
|
1010
|
+
worldX,
|
|
1011
|
+
worldY,
|
|
1012
|
+
r.x,
|
|
1013
|
+
r.y,
|
|
1014
|
+
r.width,
|
|
1015
|
+
r.height,
|
|
1016
|
+
rotationRad
|
|
1017
|
+
);
|
|
1018
|
+
const localBounds2 = { x: 0, y: 0, width: r.width, height: r.height };
|
|
1019
|
+
let best = null;
|
|
1020
|
+
let bestD = radiusWorld;
|
|
1021
|
+
for (const id of HANDLE_IDS) {
|
|
1022
|
+
const p = getHandleWorldPosition(localBounds2, id);
|
|
1023
|
+
const d = Math.hypot(pl.x - p.x, pl.y - p.y);
|
|
1024
|
+
if (d <= bestD) {
|
|
1025
|
+
bestD = d;
|
|
1026
|
+
best = id;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return best;
|
|
1030
|
+
}
|
|
1002
1031
|
function getHandleWorldPositionRotated(bounds, handle, rotationRad) {
|
|
1003
1032
|
const r = normalizeRect(bounds);
|
|
1004
1033
|
const p = getHandleWorldPosition(
|
|
@@ -1019,6 +1048,10 @@ function getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld)
|
|
|
1019
1048
|
rotationRad
|
|
1020
1049
|
);
|
|
1021
1050
|
}
|
|
1051
|
+
function hitTestRotateHandle(bounds, rotationRad, worldX, worldY, radiusWorld, handleOffsetWorld) {
|
|
1052
|
+
const p = getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld);
|
|
1053
|
+
return Math.hypot(worldX - p.x, worldY - p.y) <= radiusWorld;
|
|
1054
|
+
}
|
|
1022
1055
|
function rectFromCorners(a, b) {
|
|
1023
1056
|
const minX = Math.min(a.x, b.x);
|
|
1024
1057
|
const maxX = Math.max(a.x, b.x);
|
|
@@ -1026,6 +1059,155 @@ function rectFromCorners(a, b) {
|
|
|
1026
1059
|
const maxY = Math.max(a.y, b.y);
|
|
1027
1060
|
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
1028
1061
|
}
|
|
1062
|
+
function clampMinSize(r, min) {
|
|
1063
|
+
const n = normalizeRect(r);
|
|
1064
|
+
return {
|
|
1065
|
+
x: n.x,
|
|
1066
|
+
y: n.y,
|
|
1067
|
+
width: Math.max(min, n.width),
|
|
1068
|
+
height: Math.max(min, n.height)
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function computeResizeBounds(bounds, handle, currentWorld) {
|
|
1072
|
+
const r = normalizeRect(bounds);
|
|
1073
|
+
const x0 = r.x;
|
|
1074
|
+
const y0 = r.y;
|
|
1075
|
+
const x1 = r.x + r.width;
|
|
1076
|
+
const y1 = r.y + r.height;
|
|
1077
|
+
const minSize = 8;
|
|
1078
|
+
switch (handle) {
|
|
1079
|
+
case "nw":
|
|
1080
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x1, y: y1 }), minSize);
|
|
1081
|
+
case "ne":
|
|
1082
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x0, y: y1 }), minSize);
|
|
1083
|
+
case "se":
|
|
1084
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x0, y: y0 }), minSize);
|
|
1085
|
+
case "sw":
|
|
1086
|
+
return clampMinSize(rectFromCorners(currentWorld, { x: x1, y: y0 }), minSize);
|
|
1087
|
+
case "n":
|
|
1088
|
+
return clampMinSize(
|
|
1089
|
+
rectFromCorners({ x: x0, y: currentWorld.y }, { x: x1, y: y1 }),
|
|
1090
|
+
minSize
|
|
1091
|
+
);
|
|
1092
|
+
case "s":
|
|
1093
|
+
return clampMinSize(
|
|
1094
|
+
rectFromCorners({ x: x0, y: y0 }, { x: x1, y: currentWorld.y }),
|
|
1095
|
+
minSize
|
|
1096
|
+
);
|
|
1097
|
+
case "e":
|
|
1098
|
+
return clampMinSize(
|
|
1099
|
+
rectFromCorners({ x: x0, y: y0 }, { x: currentWorld.x, y: y1 }),
|
|
1100
|
+
minSize
|
|
1101
|
+
);
|
|
1102
|
+
case "w":
|
|
1103
|
+
return clampMinSize(
|
|
1104
|
+
rectFromCorners({ x: currentWorld.x, y: y0 }, { x: x1, y: y1 }),
|
|
1105
|
+
minSize
|
|
1106
|
+
);
|
|
1107
|
+
default:
|
|
1108
|
+
return r;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function computeResizeBoundsFixedAspect(bounds, handle, currentWorld, aspect) {
|
|
1112
|
+
const r = normalizeRect(bounds);
|
|
1113
|
+
const x0 = r.x;
|
|
1114
|
+
const y0 = r.y;
|
|
1115
|
+
const x1 = r.x + r.width;
|
|
1116
|
+
const y1 = r.y + r.height;
|
|
1117
|
+
const w = r.width;
|
|
1118
|
+
const h = r.height;
|
|
1119
|
+
const minSize = 8;
|
|
1120
|
+
const a = Math.max(1e-9, aspect);
|
|
1121
|
+
function cornerAspectRect(anchor, cursor) {
|
|
1122
|
+
const dx = cursor.x - anchor.x;
|
|
1123
|
+
const dy = cursor.y - anchor.y;
|
|
1124
|
+
const aw = Math.abs(dx);
|
|
1125
|
+
const ah = Math.abs(dy);
|
|
1126
|
+
let W;
|
|
1127
|
+
let H;
|
|
1128
|
+
if (aw < 1e-12 && ah < 1e-12) {
|
|
1129
|
+
W = minSize;
|
|
1130
|
+
H = minSize / a;
|
|
1131
|
+
} else if (ah < 1e-12) {
|
|
1132
|
+
W = Math.max(minSize, aw);
|
|
1133
|
+
H = W / a;
|
|
1134
|
+
} else if (aw < 1e-12) {
|
|
1135
|
+
H = Math.max(minSize, ah);
|
|
1136
|
+
W = H * a;
|
|
1137
|
+
} else if (aw / ah > a) {
|
|
1138
|
+
W = aw;
|
|
1139
|
+
H = aw / a;
|
|
1140
|
+
} else {
|
|
1141
|
+
H = ah;
|
|
1142
|
+
W = ah * a;
|
|
1143
|
+
}
|
|
1144
|
+
if (W < minSize || H < minSize) {
|
|
1145
|
+
const scale = Math.max(minSize / W, minSize / H);
|
|
1146
|
+
W *= scale;
|
|
1147
|
+
H *= scale;
|
|
1148
|
+
}
|
|
1149
|
+
const sx = Math.sign(dx) || 1;
|
|
1150
|
+
const sy = Math.sign(dy) || 1;
|
|
1151
|
+
return rectFromCorners(anchor, {
|
|
1152
|
+
x: anchor.x + sx * W,
|
|
1153
|
+
y: anchor.y + sy * H
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
switch (handle) {
|
|
1157
|
+
case "se":
|
|
1158
|
+
return cornerAspectRect({ x: x0, y: y0 }, currentWorld);
|
|
1159
|
+
case "nw":
|
|
1160
|
+
return cornerAspectRect({ x: x1, y: y1 }, currentWorld);
|
|
1161
|
+
case "ne":
|
|
1162
|
+
return cornerAspectRect({ x: x0, y: y1 }, currentWorld);
|
|
1163
|
+
case "sw":
|
|
1164
|
+
return cornerAspectRect({ x: x1, y: y0 }, currentWorld);
|
|
1165
|
+
case "e": {
|
|
1166
|
+
const rawW = currentWorld.x - x0;
|
|
1167
|
+
const aw = Math.max(minSize, Math.abs(rawW));
|
|
1168
|
+
const H = aw / a;
|
|
1169
|
+
const newY = y0 + h / 2 - H / 2;
|
|
1170
|
+
const sign = Math.sign(rawW) || 1;
|
|
1171
|
+
return normalizeRect({ x: x0, y: newY, width: sign * aw, height: H });
|
|
1172
|
+
}
|
|
1173
|
+
case "w": {
|
|
1174
|
+
const rawW = x1 - currentWorld.x;
|
|
1175
|
+
const aw = Math.max(minSize, Math.abs(rawW));
|
|
1176
|
+
const H = aw / a;
|
|
1177
|
+
const newY = y0 + h / 2 - H / 2;
|
|
1178
|
+
const sign = Math.sign(rawW) || 1;
|
|
1179
|
+
return normalizeRect({
|
|
1180
|
+
x: x1 - sign * aw,
|
|
1181
|
+
y: newY,
|
|
1182
|
+
width: sign * aw,
|
|
1183
|
+
height: H
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
case "n": {
|
|
1187
|
+
const rawH = y1 - currentWorld.y;
|
|
1188
|
+
const ah = Math.max(minSize, Math.abs(rawH));
|
|
1189
|
+
const W = ah * a;
|
|
1190
|
+
const newX = x0 + w / 2 - W / 2;
|
|
1191
|
+
const sign = Math.sign(rawH) || 1;
|
|
1192
|
+
return normalizeRect({
|
|
1193
|
+
x: newX,
|
|
1194
|
+
y: y1 - sign * ah,
|
|
1195
|
+
width: W,
|
|
1196
|
+
height: sign * ah
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
case "s": {
|
|
1200
|
+
const rawH = currentWorld.y - y0;
|
|
1201
|
+
const ah = Math.max(minSize, Math.abs(rawH));
|
|
1202
|
+
const W = ah * a;
|
|
1203
|
+
const newX = x0 + w / 2 - W / 2;
|
|
1204
|
+
const sign = Math.sign(rawH) || 1;
|
|
1205
|
+
return normalizeRect({ x: newX, y: y0, width: W, height: sign * ah });
|
|
1206
|
+
}
|
|
1207
|
+
default:
|
|
1208
|
+
return r;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1029
1211
|
|
|
1030
1212
|
// src/scene/freehand-path.ts
|
|
1031
1213
|
function smoothFreehandPointsToPathD(points) {
|
|
@@ -3587,6 +3769,211 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
|
|
|
3587
3769
|
return ids;
|
|
3588
3770
|
}
|
|
3589
3771
|
|
|
3772
|
+
// src/interaction/mutations.ts
|
|
3773
|
+
function computeNewBoundsForResize(item, sb, handle, currentWorld) {
|
|
3774
|
+
const rot = getItemRotationRad(item);
|
|
3775
|
+
if (Math.abs(rot) < 1e-12) {
|
|
3776
|
+
if (item.toolKind === "image") {
|
|
3777
|
+
let aspect;
|
|
3778
|
+
if (item.imageIntrinsicSize) {
|
|
3779
|
+
const iw = Math.max(1e-9, item.imageIntrinsicSize.width);
|
|
3780
|
+
const ih = Math.max(1e-9, item.imageIntrinsicSize.height);
|
|
3781
|
+
aspect = iw / ih;
|
|
3782
|
+
} else if (item.imageVectorLocalSize) {
|
|
3783
|
+
const lw = Math.max(1e-9, item.imageVectorLocalSize.width);
|
|
3784
|
+
const lh = Math.max(1e-9, item.imageVectorLocalSize.height);
|
|
3785
|
+
aspect = lw / lh;
|
|
3786
|
+
}
|
|
3787
|
+
return aspect !== void 0 ? computeResizeBoundsFixedAspect(sb, handle, currentWorld, aspect) : computeResizeBounds(sb, handle, currentWorld);
|
|
3788
|
+
}
|
|
3789
|
+
return computeResizeBounds(sb, handle, currentWorld);
|
|
3790
|
+
}
|
|
3791
|
+
const local = worldToItemLocal(
|
|
3792
|
+
currentWorld.x,
|
|
3793
|
+
currentWorld.y,
|
|
3794
|
+
sb.x,
|
|
3795
|
+
sb.y,
|
|
3796
|
+
sb.width,
|
|
3797
|
+
sb.height,
|
|
3798
|
+
rot
|
|
3799
|
+
);
|
|
3800
|
+
const localBounds2 = { x: 0, y: 0, width: sb.width, height: sb.height };
|
|
3801
|
+
let nbLocal;
|
|
3802
|
+
if (item.toolKind === "image") {
|
|
3803
|
+
let aspect;
|
|
3804
|
+
if (item.imageIntrinsicSize) {
|
|
3805
|
+
const iw = Math.max(1e-9, item.imageIntrinsicSize.width);
|
|
3806
|
+
const ih = Math.max(1e-9, item.imageIntrinsicSize.height);
|
|
3807
|
+
aspect = iw / ih;
|
|
3808
|
+
} else if (item.imageVectorLocalSize) {
|
|
3809
|
+
const lw = Math.max(1e-9, item.imageVectorLocalSize.width);
|
|
3810
|
+
const lh = Math.max(1e-9, item.imageVectorLocalSize.height);
|
|
3811
|
+
aspect = lw / lh;
|
|
3812
|
+
}
|
|
3813
|
+
nbLocal = aspect !== void 0 ? computeResizeBoundsFixedAspect(localBounds2, handle, local, aspect) : computeResizeBounds(localBounds2, handle, local);
|
|
3814
|
+
} else {
|
|
3815
|
+
nbLocal = computeResizeBounds(localBounds2, handle, local);
|
|
3816
|
+
}
|
|
3817
|
+
return {
|
|
3818
|
+
x: sb.x + nbLocal.x,
|
|
3819
|
+
y: sb.y + nbLocal.y,
|
|
3820
|
+
width: nbLocal.width,
|
|
3821
|
+
height: nbLocal.height
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
function applyRotationFromPointer(item, startRotation, startPointerAngleRad, pointerAngleRad) {
|
|
3825
|
+
let delta = pointerAngleRad - startPointerAngleRad;
|
|
3826
|
+
while (delta > Math.PI) {
|
|
3827
|
+
delta -= 2 * Math.PI;
|
|
3828
|
+
}
|
|
3829
|
+
while (delta < -Math.PI) {
|
|
3830
|
+
delta += 2 * Math.PI;
|
|
3831
|
+
}
|
|
3832
|
+
return { ...item, rotation: startRotation + delta };
|
|
3833
|
+
}
|
|
3834
|
+
function resizeItemByHandle(item, start, handle, currentWorld) {
|
|
3835
|
+
const sb = normalizeRect(start.bounds);
|
|
3836
|
+
const newBoundsRaw = computeNewBoundsForResize(item, sb, handle, currentWorld);
|
|
3837
|
+
const nb = normalizeRect(newBoundsRaw);
|
|
3838
|
+
const k = item.toolKind;
|
|
3839
|
+
if (k === "rect") {
|
|
3840
|
+
const style = resolveStrokeStyle(item);
|
|
3841
|
+
return {
|
|
3842
|
+
...item,
|
|
3843
|
+
x: nb.x,
|
|
3844
|
+
y: nb.y,
|
|
3845
|
+
bounds: nb,
|
|
3846
|
+
childrenSvg: buildRectSvg(nb.width, nb.height, style)
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
if (k === "ellipse") {
|
|
3850
|
+
const style = resolveStrokeStyle(item);
|
|
3851
|
+
return {
|
|
3852
|
+
...item,
|
|
3853
|
+
x: nb.x,
|
|
3854
|
+
y: nb.y,
|
|
3855
|
+
bounds: nb,
|
|
3856
|
+
childrenSvg: buildEllipseSvg(nb.width, nb.height, style)
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
if (k === "architectural-cloud") {
|
|
3860
|
+
const style = resolveStrokeStyle(item);
|
|
3861
|
+
return {
|
|
3862
|
+
...item,
|
|
3863
|
+
x: nb.x,
|
|
3864
|
+
y: nb.y,
|
|
3865
|
+
bounds: nb,
|
|
3866
|
+
childrenSvg: buildArchitecturalCloudSvg(nb.width, nb.height, style)
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
if (k === "text" && item.text !== void 0) {
|
|
3870
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3871
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3872
|
+
const baseFs = item.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
|
|
3873
|
+
const areaRatio = nb.width * nb.height / (sfw * sfh);
|
|
3874
|
+
const scale = Math.sqrt(Math.max(areaRatio, 1e-12));
|
|
3875
|
+
const nextFs = Math.min(256, Math.max(6, baseFs * scale));
|
|
3876
|
+
return rebuildItemSvg({
|
|
3877
|
+
...item,
|
|
3878
|
+
x: nb.x,
|
|
3879
|
+
y: nb.y,
|
|
3880
|
+
bounds: nb,
|
|
3881
|
+
textFixedBounds: true,
|
|
3882
|
+
textFontSize: nextFs
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
if (k === "image") {
|
|
3886
|
+
if (item.imageRasterHref && item.imageIntrinsicSize) {
|
|
3887
|
+
return {
|
|
3888
|
+
...item,
|
|
3889
|
+
x: nb.x,
|
|
3890
|
+
y: nb.y,
|
|
3891
|
+
bounds: nb,
|
|
3892
|
+
childrenSvg: buildRasterImageChildrenSvg(
|
|
3893
|
+
item.imageRasterHref,
|
|
3894
|
+
item.imageIntrinsicSize,
|
|
3895
|
+
nb
|
|
3896
|
+
)
|
|
3897
|
+
};
|
|
3898
|
+
}
|
|
3899
|
+
if (item.imageVectorInnerSvg && item.imageVectorLocalSize) {
|
|
3900
|
+
const lw = Math.max(1e-6, item.imageVectorLocalSize.width);
|
|
3901
|
+
const lh = Math.max(1e-6, item.imageVectorLocalSize.height);
|
|
3902
|
+
const arB = nb.width / Math.max(1e-9, nb.height);
|
|
3903
|
+
const arI = lw / lh;
|
|
3904
|
+
let childrenSvg;
|
|
3905
|
+
if (Math.abs(arB - arI) < 1e-3) {
|
|
3906
|
+
const s = nb.width / lw;
|
|
3907
|
+
childrenSvg = `<g transform="scale(${s})">${item.imageVectorInnerSvg}</g>`;
|
|
3908
|
+
} else {
|
|
3909
|
+
const s = Math.min(nb.width / lw, nb.height / lh);
|
|
3910
|
+
const tx = (nb.width - lw * s) / 2;
|
|
3911
|
+
const ty = (nb.height - lh * s) / 2;
|
|
3912
|
+
childrenSvg = `<g transform="translate(${tx}, ${ty}) scale(${s})">${item.imageVectorInnerSvg}</g>`;
|
|
3913
|
+
}
|
|
3914
|
+
return {
|
|
3915
|
+
...item,
|
|
3916
|
+
x: nb.x,
|
|
3917
|
+
y: nb.y,
|
|
3918
|
+
bounds: nb,
|
|
3919
|
+
childrenSvg
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
if ((k === "line" || k === "arrow") && start.line) {
|
|
3924
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3925
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3926
|
+
const f = start.line;
|
|
3927
|
+
const u1 = f.x1 / sfw;
|
|
3928
|
+
const v1 = f.y1 / sfh;
|
|
3929
|
+
const u2 = f.x2 / sfw;
|
|
3930
|
+
const v2 = f.y2 / sfh;
|
|
3931
|
+
const newLine = {
|
|
3932
|
+
x1: u1 * nb.width,
|
|
3933
|
+
y1: v1 * nb.height,
|
|
3934
|
+
x2: u2 * nb.width,
|
|
3935
|
+
y2: v2 * nb.height
|
|
3936
|
+
};
|
|
3937
|
+
const style = resolveStrokeStyle(item);
|
|
3938
|
+
const childrenSvg = k === "arrow" ? buildArrowSvg(item.id, newLine, style) : buildLineSvg(newLine, style);
|
|
3939
|
+
return {
|
|
3940
|
+
...item,
|
|
3941
|
+
x: nb.x,
|
|
3942
|
+
y: nb.y,
|
|
3943
|
+
bounds: nb,
|
|
3944
|
+
line: newLine,
|
|
3945
|
+
childrenSvg
|
|
3946
|
+
};
|
|
3947
|
+
}
|
|
3948
|
+
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
3949
|
+
const sfw = Math.max(sb.width, 1e-6);
|
|
3950
|
+
const sfh = Math.max(sb.height, 1e-6);
|
|
3951
|
+
const sx = nb.width / sfw;
|
|
3952
|
+
const sy = nb.height / sfh;
|
|
3953
|
+
const scaledPoints = item.pathPointsLocal.map((p) => ({
|
|
3954
|
+
x: p.x * sx,
|
|
3955
|
+
y: p.y * sy,
|
|
3956
|
+
...p.pressure != null ? { pressure: p.pressure } : {}
|
|
3957
|
+
}));
|
|
3958
|
+
return rebuildItemSvg({
|
|
3959
|
+
...item,
|
|
3960
|
+
x: nb.x,
|
|
3961
|
+
y: nb.y,
|
|
3962
|
+
bounds: nb,
|
|
3963
|
+
pathPointsLocal: scaledPoints
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
if (k === "custom" && item.customIntrinsicSize && item.customInnerSvg) {
|
|
3967
|
+
return rebuildItemSvg({
|
|
3968
|
+
...item,
|
|
3969
|
+
x: nb.x,
|
|
3970
|
+
y: nb.y,
|
|
3971
|
+
bounds: nb
|
|
3972
|
+
});
|
|
3973
|
+
}
|
|
3974
|
+
return { ...item, x: nb.x, y: nb.y, bounds: nb };
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3590
3977
|
// src/native/native-tool-cursors.ts
|
|
3591
3978
|
var ICON_SIZE = 24;
|
|
3592
3979
|
var CENTER_HOTSPOT = { x: 12, y: 12 };
|
|
@@ -3612,10 +3999,72 @@ function nativeCursorForVectorToolId(toolId) {
|
|
|
3612
3999
|
return null;
|
|
3613
4000
|
}
|
|
3614
4001
|
}
|
|
4002
|
+
function nativeCrosshairToolCursor() {
|
|
4003
|
+
return { kind: "crosshair", size: ICON_SIZE, hotspot: CENTER_HOTSPOT };
|
|
4004
|
+
}
|
|
3615
4005
|
function nativeFallbackToolCursorPoint(size) {
|
|
3616
4006
|
if (size.width <= 0 || size.height <= 0) return null;
|
|
3617
4007
|
return { x: size.width / 2, y: size.height / 2 };
|
|
3618
4008
|
}
|
|
4009
|
+
|
|
4010
|
+
// src/native/native-vector-interactions.ts
|
|
4011
|
+
function supportsNativeResizeHandles(item) {
|
|
4012
|
+
const k = item?.toolKind;
|
|
4013
|
+
if (k === "rect" || k === "ellipse" || k === "architectural-cloud" || k === "line" || k === "arrow" || k === "image" || k === "text") {
|
|
4014
|
+
return true;
|
|
4015
|
+
}
|
|
4016
|
+
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item?.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
4017
|
+
return true;
|
|
4018
|
+
}
|
|
4019
|
+
return k === "custom" && !!item?.customIntrinsicSize && !!item?.customInnerSvg;
|
|
4020
|
+
}
|
|
4021
|
+
function hitTestNativeSelectionHandle({
|
|
4022
|
+
selectedItem,
|
|
4023
|
+
selectedCount,
|
|
4024
|
+
worldPoint,
|
|
4025
|
+
zoom
|
|
4026
|
+
}) {
|
|
4027
|
+
if (selectedCount !== 1) return null;
|
|
4028
|
+
if (!selectedItem || selectedItem.locked) return null;
|
|
4029
|
+
if (!supportsNativeResizeHandles(selectedItem)) return null;
|
|
4030
|
+
const bounds = normalizeRect(selectedItem.bounds);
|
|
4031
|
+
const rotation = selectedItem.rotation ?? 0;
|
|
4032
|
+
const handleRadiusWorld = 6 / Math.max(zoom, 1e-9);
|
|
4033
|
+
const rotateOffsetWorld = 24 / Math.max(zoom, 1e-9);
|
|
4034
|
+
if (hitTestRotateHandle(
|
|
4035
|
+
bounds,
|
|
4036
|
+
rotation,
|
|
4037
|
+
worldPoint.x,
|
|
4038
|
+
worldPoint.y,
|
|
4039
|
+
handleRadiusWorld,
|
|
4040
|
+
rotateOffsetWorld
|
|
4041
|
+
)) {
|
|
4042
|
+
return { kind: "rotate" };
|
|
4043
|
+
}
|
|
4044
|
+
const handle = hitTestResizeHandle(
|
|
4045
|
+
bounds,
|
|
4046
|
+
worldPoint.x,
|
|
4047
|
+
worldPoint.y,
|
|
4048
|
+
handleRadiusWorld,
|
|
4049
|
+
rotation
|
|
4050
|
+
);
|
|
4051
|
+
return handle ? { kind: "resize", handle } : null;
|
|
4052
|
+
}
|
|
4053
|
+
function resolveNativeCustomPlacement(toolId, customPlacement, customPlacements) {
|
|
4054
|
+
if (customPlacement?.toolId === toolId) return customPlacement;
|
|
4055
|
+
return customPlacements?.find((placement) => placement.toolId === toolId) ?? null;
|
|
4056
|
+
}
|
|
4057
|
+
function nativeRotationDragStart(input) {
|
|
4058
|
+
const pivotWorld = itemPivotWorld(input.item);
|
|
4059
|
+
return {
|
|
4060
|
+
pivotWorld,
|
|
4061
|
+
startPointerAngleRad: Math.atan2(
|
|
4062
|
+
input.worldPoint.y - pivotWorld.y,
|
|
4063
|
+
input.worldPoint.x - pivotWorld.x
|
|
4064
|
+
),
|
|
4065
|
+
startRotation: input.item.rotation ?? 0
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
3619
4068
|
var MIN_PLACE_SIZE = 8;
|
|
3620
4069
|
var MIN_ARROW_DRAG_PX = 8;
|
|
3621
4070
|
var TAP_PX = 20;
|
|
@@ -3630,16 +4079,6 @@ function isPlacementTool(toolId) {
|
|
|
3630
4079
|
function isDefaultMarkerToolStyle(style) {
|
|
3631
4080
|
return style.stroke === MARKER_TOOL_STYLE.stroke && style.strokeWidth === MARKER_TOOL_STYLE.strokeWidth && style.strokeOpacity === MARKER_TOOL_STYLE.strokeOpacity;
|
|
3632
4081
|
}
|
|
3633
|
-
function supportsNativeResizeHandles(item) {
|
|
3634
|
-
const k = item?.toolKind;
|
|
3635
|
-
if (k === "rect" || k === "ellipse" || k === "architectural-cloud" || k === "line" || k === "arrow" || k === "image" || k === "text") {
|
|
3636
|
-
return true;
|
|
3637
|
-
}
|
|
3638
|
-
if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item?.pathPointsLocal && item.pathPointsLocal.length > 0) {
|
|
3639
|
-
return true;
|
|
3640
|
-
}
|
|
3641
|
-
return k === "custom" && !!item?.customIntrinsicSize && !!item?.customInnerSvg;
|
|
3642
|
-
}
|
|
3643
4082
|
function placementPreviewForTool(toolId, start, end) {
|
|
3644
4083
|
if (toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud") {
|
|
3645
4084
|
return { kind: toolId, rect: rectFromCorners(start, end) };
|
|
@@ -3707,7 +4146,11 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3707
4146
|
onSelectionChange,
|
|
3708
4147
|
onItemsChange,
|
|
3709
4148
|
onToolChangeRequest,
|
|
4149
|
+
onWorldPointerDown,
|
|
3710
4150
|
onCameraChange,
|
|
4151
|
+
customPlacement,
|
|
4152
|
+
customPlacements = [],
|
|
4153
|
+
customCrosshairToolIds = [],
|
|
3711
4154
|
toolbar,
|
|
3712
4155
|
showStyleInspector = false,
|
|
3713
4156
|
styleInspectorPlacement = "bottom"
|
|
@@ -3720,10 +4163,18 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3720
4163
|
toolLockedRef.current = toolLocked;
|
|
3721
4164
|
const onToolChangeRequestRef = react.useRef(onToolChangeRequest);
|
|
3722
4165
|
onToolChangeRequestRef.current = onToolChangeRequest;
|
|
4166
|
+
const onWorldPointerDownRef = react.useRef(onWorldPointerDown);
|
|
4167
|
+
onWorldPointerDownRef.current = onWorldPointerDown;
|
|
3723
4168
|
const onCameraChangeRef = react.useRef(onCameraChange);
|
|
3724
4169
|
onCameraChangeRef.current = onCameraChange;
|
|
3725
4170
|
const onItemsChangeRef = react.useRef(onItemsChange);
|
|
3726
4171
|
onItemsChangeRef.current = onItemsChange;
|
|
4172
|
+
const customPlacementRef = react.useRef(customPlacement);
|
|
4173
|
+
customPlacementRef.current = customPlacement;
|
|
4174
|
+
const customPlacementsRef = react.useRef(customPlacements);
|
|
4175
|
+
customPlacementsRef.current = customPlacements;
|
|
4176
|
+
const customCrosshairToolIdsRef = react.useRef(customCrosshairToolIds);
|
|
4177
|
+
customCrosshairToolIdsRef.current = customCrosshairToolIds;
|
|
3727
4178
|
const onSelectionChangeRef = react.useRef(onSelectionChange);
|
|
3728
4179
|
onSelectionChangeRef.current = onSelectionChange;
|
|
3729
4180
|
const itemsRef = react.useRef(items);
|
|
@@ -3831,6 +4282,11 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3831
4282
|
setCameraTick((n) => n + 1);
|
|
3832
4283
|
onCameraChangeRef.current?.();
|
|
3833
4284
|
}, []);
|
|
4285
|
+
const cursorForToolId = react.useCallback((nextToolId) => {
|
|
4286
|
+
const builtInCursor = nativeCursorForVectorToolId(nextToolId);
|
|
4287
|
+
if (builtInCursor) return builtInCursor;
|
|
4288
|
+
return customCrosshairToolIdsRef.current.includes(nextToolId) ? nativeCrosshairToolCursor() : null;
|
|
4289
|
+
}, []);
|
|
3834
4290
|
const onLayout = react.useCallback(
|
|
3835
4291
|
(e) => {
|
|
3836
4292
|
const { width, height } = e.nativeEvent.layout;
|
|
@@ -3840,7 +4296,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3840
4296
|
setToolCursorPoint(null);
|
|
3841
4297
|
return;
|
|
3842
4298
|
}
|
|
3843
|
-
if (!
|
|
4299
|
+
if (!cursorForToolId(toolIdRef.current)) {
|
|
3844
4300
|
setToolCursorPoint(null);
|
|
3845
4301
|
return;
|
|
3846
4302
|
}
|
|
@@ -3848,15 +4304,15 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3848
4304
|
(current) => current ?? nativeFallbackToolCursorPoint(nextSize)
|
|
3849
4305
|
);
|
|
3850
4306
|
},
|
|
3851
|
-
[interactive]
|
|
4307
|
+
[cursorForToolId, interactive]
|
|
3852
4308
|
);
|
|
3853
4309
|
const updateToolCursorPoint = react.useCallback(
|
|
3854
4310
|
(point) => {
|
|
3855
4311
|
if (!interactive) return;
|
|
3856
|
-
if (!
|
|
4312
|
+
if (!cursorForToolId(toolIdRef.current)) return;
|
|
3857
4313
|
setToolCursorPoint(point);
|
|
3858
4314
|
},
|
|
3859
|
-
[interactive]
|
|
4315
|
+
[cursorForToolId, interactive]
|
|
3860
4316
|
);
|
|
3861
4317
|
const hideToolCursor = react.useCallback(() => {
|
|
3862
4318
|
setToolCursorPoint(null);
|
|
@@ -3867,7 +4323,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3867
4323
|
setToolCursorPoint(null);
|
|
3868
4324
|
return;
|
|
3869
4325
|
}
|
|
3870
|
-
if (!
|
|
4326
|
+
if (!cursorForToolId(nextToolId)) {
|
|
3871
4327
|
setToolCursorPoint(null);
|
|
3872
4328
|
return;
|
|
3873
4329
|
}
|
|
@@ -3875,7 +4331,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3875
4331
|
(current) => current ?? nativeFallbackToolCursorPoint(size)
|
|
3876
4332
|
);
|
|
3877
4333
|
},
|
|
3878
|
-
[interactive, size]
|
|
4334
|
+
[cursorForToolId, interactive, size]
|
|
3879
4335
|
);
|
|
3880
4336
|
react.useEffect(() => {
|
|
3881
4337
|
showFallbackToolCursor(toolId);
|
|
@@ -3927,6 +4383,40 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
3927
4383
|
return;
|
|
3928
4384
|
}
|
|
3929
4385
|
if (tool === "select") {
|
|
4386
|
+
const currentSelectedIds = selectedIdsRef.current;
|
|
4387
|
+
const selectedItem = currentSelectedIds.length === 1 ? itemsRef.current.find((item) => item.id === currentSelectedIds[0]) : void 0;
|
|
4388
|
+
const selectionHandle = hitTestNativeSelectionHandle({
|
|
4389
|
+
selectedItem,
|
|
4390
|
+
selectedCount: currentSelectedIds.length,
|
|
4391
|
+
worldPoint: { x: worldX, y: worldY },
|
|
4392
|
+
zoom: cam.zoom
|
|
4393
|
+
});
|
|
4394
|
+
if (selectionHandle && selectedItem) {
|
|
4395
|
+
if (selectionHandle.kind === "rotate") {
|
|
4396
|
+
const rotationStart = nativeRotationDragStart({
|
|
4397
|
+
item: selectedItem,
|
|
4398
|
+
worldPoint: { x: worldX, y: worldY }
|
|
4399
|
+
});
|
|
4400
|
+
dragStateRef.current = {
|
|
4401
|
+
kind: "rotate",
|
|
4402
|
+
id: selectedItem.id,
|
|
4403
|
+
snapshot: selectedItem,
|
|
4404
|
+
...rotationStart
|
|
4405
|
+
};
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4408
|
+
dragStateRef.current = {
|
|
4409
|
+
kind: "resize",
|
|
4410
|
+
id: selectedItem.id,
|
|
4411
|
+
handle: selectionHandle.handle,
|
|
4412
|
+
snapshot: selectedItem,
|
|
4413
|
+
start: {
|
|
4414
|
+
bounds: selectedItem.bounds,
|
|
4415
|
+
line: selectedItem.line
|
|
4416
|
+
}
|
|
4417
|
+
};
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
3930
4420
|
const hit = hitTestWorldPoint(itemsRef.current, worldX, worldY, {
|
|
3931
4421
|
lineHitWorld: 10 / cam.zoom,
|
|
3932
4422
|
ignoreLocked: true
|
|
@@ -4019,6 +4509,25 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4019
4509
|
);
|
|
4020
4510
|
return;
|
|
4021
4511
|
}
|
|
4512
|
+
const customPlacement2 = resolveNativeCustomPlacement(
|
|
4513
|
+
tool,
|
|
4514
|
+
customPlacementRef.current,
|
|
4515
|
+
customPlacementsRef.current
|
|
4516
|
+
);
|
|
4517
|
+
if (customPlacement2) {
|
|
4518
|
+
dragStateRef.current = {
|
|
4519
|
+
kind: "custom-place",
|
|
4520
|
+
tool,
|
|
4521
|
+
placement: customPlacement2,
|
|
4522
|
+
startWorld: { x: worldX, y: worldY },
|
|
4523
|
+
startScreen: { x: sx, y: sy }
|
|
4524
|
+
};
|
|
4525
|
+
setPlacementPreview({
|
|
4526
|
+
kind: "rect",
|
|
4527
|
+
rect: { x: worldX, y: worldY, width: 0, height: 0 }
|
|
4528
|
+
});
|
|
4529
|
+
return;
|
|
4530
|
+
}
|
|
4022
4531
|
if (tool === "note" || tool === "text") {
|
|
4023
4532
|
dragStateRef.current = {
|
|
4024
4533
|
kind: "tap",
|
|
@@ -4028,6 +4537,18 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4028
4537
|
};
|
|
4029
4538
|
return;
|
|
4030
4539
|
}
|
|
4540
|
+
const handleWorldPointerDown = onWorldPointerDownRef.current;
|
|
4541
|
+
if (handleWorldPointerDown) {
|
|
4542
|
+
handleWorldPointerDown({
|
|
4543
|
+
toolId: tool,
|
|
4544
|
+
worldX,
|
|
4545
|
+
worldY,
|
|
4546
|
+
screenX: sx,
|
|
4547
|
+
screenY: sy
|
|
4548
|
+
});
|
|
4549
|
+
requestSelectToolAfterUse();
|
|
4550
|
+
return;
|
|
4551
|
+
}
|
|
4031
4552
|
dragStateRef.current = { kind: "pan" };
|
|
4032
4553
|
},
|
|
4033
4554
|
onPanResponderMove: (evt) => {
|
|
@@ -4123,6 +4644,36 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4123
4644
|
change(nextList);
|
|
4124
4645
|
return;
|
|
4125
4646
|
}
|
|
4647
|
+
if (st.kind === "rotate") {
|
|
4648
|
+
const change = onItemsChangeRef.current;
|
|
4649
|
+
if (!change) return;
|
|
4650
|
+
const angle = Math.atan2(
|
|
4651
|
+
worldY - st.pivotWorld.y,
|
|
4652
|
+
worldX - st.pivotWorld.x
|
|
4653
|
+
);
|
|
4654
|
+
const next = applyRotationFromPointer(
|
|
4655
|
+
st.snapshot,
|
|
4656
|
+
st.startRotation,
|
|
4657
|
+
st.startPointerAngleRad,
|
|
4658
|
+
angle
|
|
4659
|
+
);
|
|
4660
|
+
change(
|
|
4661
|
+
itemsRef.current.map((item) => item.id === st.id ? next : item)
|
|
4662
|
+
);
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
if (st.kind === "resize") {
|
|
4666
|
+
const change = onItemsChangeRef.current;
|
|
4667
|
+
if (!change) return;
|
|
4668
|
+
const next = resizeItemByHandle(st.snapshot, st.start, st.handle, {
|
|
4669
|
+
x: worldX,
|
|
4670
|
+
y: worldY
|
|
4671
|
+
});
|
|
4672
|
+
change(
|
|
4673
|
+
itemsRef.current.map((item) => item.id === st.id ? next : item)
|
|
4674
|
+
);
|
|
4675
|
+
return;
|
|
4676
|
+
}
|
|
4126
4677
|
if (st.kind === "marquee") {
|
|
4127
4678
|
const a = st.startWorld;
|
|
4128
4679
|
const b = { x: worldX, y: worldY };
|
|
@@ -4161,6 +4712,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4161
4712
|
);
|
|
4162
4713
|
return;
|
|
4163
4714
|
}
|
|
4715
|
+
if (st.kind === "custom-place") {
|
|
4716
|
+
setPlacementPreview({
|
|
4717
|
+
kind: "rect",
|
|
4718
|
+
rect: rectFromCorners(st.startWorld, { x: worldX, y: worldY })
|
|
4719
|
+
});
|
|
4720
|
+
return;
|
|
4721
|
+
}
|
|
4164
4722
|
},
|
|
4165
4723
|
onPanResponderRelease: (evt) => {
|
|
4166
4724
|
lastPinchDist.current = null;
|
|
@@ -4204,6 +4762,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4204
4762
|
dragStateRef.current = { kind: "idle" };
|
|
4205
4763
|
return;
|
|
4206
4764
|
}
|
|
4765
|
+
if (st.kind === "resize" || st.kind === "rotate") {
|
|
4766
|
+
dragStateRef.current = { kind: "idle" };
|
|
4767
|
+
return;
|
|
4768
|
+
}
|
|
4207
4769
|
if (st.kind === "marquee") {
|
|
4208
4770
|
dragStateRef.current = { kind: "idle" };
|
|
4209
4771
|
setPlacementPreview(null);
|
|
@@ -4301,6 +4863,34 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4301
4863
|
requestSelectToolAfterUse();
|
|
4302
4864
|
return;
|
|
4303
4865
|
}
|
|
4866
|
+
if (st.kind === "custom-place") {
|
|
4867
|
+
dragStateRef.current = { kind: "idle" };
|
|
4868
|
+
setPlacementPreview(null);
|
|
4869
|
+
const change = onItemsChangeRef.current;
|
|
4870
|
+
if (!change) return;
|
|
4871
|
+
const { worldX, worldY } = screenToWorld(
|
|
4872
|
+
evt.nativeEvent.locationX,
|
|
4873
|
+
evt.nativeEvent.locationY
|
|
4874
|
+
);
|
|
4875
|
+
const center = {
|
|
4876
|
+
x: (st.startWorld.x + worldX) / 2,
|
|
4877
|
+
y: (st.startWorld.y + worldY) / 2
|
|
4878
|
+
};
|
|
4879
|
+
const raw = rectFromCorners(st.startWorld, { x: worldX, y: worldY });
|
|
4880
|
+
const normalized = normalizeRect(raw);
|
|
4881
|
+
const bounds = normalized.width < MIN_PLACE_SIZE || normalized.height < MIN_PLACE_SIZE ? {
|
|
4882
|
+
x: center.x - 60,
|
|
4883
|
+
y: center.y - 40,
|
|
4884
|
+
width: 120,
|
|
4885
|
+
height: 80
|
|
4886
|
+
} : normalized;
|
|
4887
|
+
const id = createShapeId();
|
|
4888
|
+
const item = st.placement.createItem({ id, bounds });
|
|
4889
|
+
change([...itemsRef.current, item]);
|
|
4890
|
+
onSelectionChangeRef.current?.([id]);
|
|
4891
|
+
requestSelectToolAfterUse();
|
|
4892
|
+
return;
|
|
4893
|
+
}
|
|
4304
4894
|
if (st.kind === "tap") {
|
|
4305
4895
|
dragStateRef.current = { kind: "idle" };
|
|
4306
4896
|
const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
|
|
@@ -4392,7 +4982,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
|
|
|
4392
4982
|
[requestRender, size]
|
|
4393
4983
|
);
|
|
4394
4984
|
const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
|
|
4395
|
-
const activeToolCursor =
|
|
4985
|
+
const activeToolCursor = cursorForToolId(toolId);
|
|
4396
4986
|
const toolCursor = activeToolCursor && toolCursorPoint ? { cursor: activeToolCursor, point: toolCursorPoint } : null;
|
|
4397
4987
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4398
4988
|
reactNative.View,
|