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.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 (!nativeCursorForVectorToolId(toolIdRef.current)) {
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 (!nativeCursorForVectorToolId(toolIdRef.current)) return;
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 (!nativeCursorForVectorToolId(nextToolId)) {
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 = nativeCursorForVectorToolId(toolId);
4979
+ const activeToolCursor = cursorForToolId(toolId);
4390
4980
  const toolCursor = activeToolCursor && toolCursorPoint ? { cursor: activeToolCursor, point: toolCursorPoint } : null;
4391
4981
  return /* @__PURE__ */ jsx(
4392
4982
  View,