canvu-react 0.4.4 → 0.4.6

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ var getStroke = require('perfect-freehand');
3
4
  var reactNativeSkia = require('@shopify/react-native-skia');
4
5
  var react = require('react');
5
- var getStroke = require('perfect-freehand');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var reactNative = require('react-native');
8
8
 
@@ -10,7 +10,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
10
 
11
11
  var getStroke__default = /*#__PURE__*/_interopDefault(getStroke);
12
12
 
13
- // src/native/NativeInteractionOverlay.tsx
13
+ // src/scene/shape-builders.ts
14
14
 
15
15
  // src/math/rect.ts
16
16
  function rectsIntersect(a, b) {
@@ -27,141 +27,6 @@ function normalizeRect(r) {
27
27
  };
28
28
  }
29
29
 
30
- // src/math/item-transform.ts
31
- function getItemRotationRad(item) {
32
- return item.rotation ?? 0;
33
- }
34
- function itemLocalToWorld(lx, ly, itemX, itemY, w, h, rotationRad) {
35
- const c = { x: w / 2, y: h / 2 };
36
- const dlx = lx - c.x;
37
- const dly = ly - c.y;
38
- const cos = Math.cos(rotationRad);
39
- const sin = Math.sin(rotationRad);
40
- return {
41
- x: itemX + c.x + cos * dlx - sin * dly,
42
- y: itemY + c.y + sin * dlx + cos * dly
43
- };
44
- }
45
- function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
46
- const c = { x: w / 2, y: h / 2 };
47
- const vx = wx - itemX;
48
- const vy = wy - itemY;
49
- const dx = vx - c.x;
50
- const dy = vy - c.y;
51
- const cos = Math.cos(-rotationRad);
52
- const sin = Math.sin(-rotationRad);
53
- const lx = cos * dx - sin * dy;
54
- const ly = sin * dx + cos * dy;
55
- return { x: c.x + lx, y: c.y + ly };
56
- }
57
- function boundsAabbForRotatedItem(item) {
58
- const rot = getItemRotationRad(item);
59
- if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
60
- return item.bounds;
61
- }
62
- const r = normalizeRect(item.bounds);
63
- if (Math.abs(rot) < 1e-12) {
64
- return r;
65
- }
66
- const corners = [
67
- [0, 0],
68
- [r.width, 0],
69
- [r.width, r.height],
70
- [0, r.height]
71
- ];
72
- let minX = Infinity;
73
- let minY = Infinity;
74
- let maxX = -Infinity;
75
- let maxY = -Infinity;
76
- for (const [lx, ly] of corners) {
77
- const p = itemLocalToWorld(lx, ly, item.x, item.y, r.width, r.height, rot);
78
- minX = Math.min(minX, p.x);
79
- minY = Math.min(minY, p.y);
80
- maxX = Math.max(maxX, p.x);
81
- maxY = Math.max(maxY, p.y);
82
- }
83
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
84
- }
85
-
86
- // src/interaction/resize-handles.ts
87
- function getHandleWorldPosition(bounds, id) {
88
- const r = normalizeRect(bounds);
89
- const cx = r.x + r.width / 2;
90
- const cy = r.y + r.height / 2;
91
- switch (id) {
92
- case "nw":
93
- return { x: r.x, y: r.y };
94
- case "n":
95
- return { x: cx, y: r.y };
96
- case "ne":
97
- return { x: r.x + r.width, y: r.y };
98
- case "e":
99
- return { x: r.x + r.width, y: cy };
100
- case "se":
101
- return { x: r.x + r.width, y: r.y + r.height };
102
- case "s":
103
- return { x: cx, y: r.y + r.height };
104
- case "sw":
105
- return { x: r.x, y: r.y + r.height };
106
- case "w":
107
- return { x: r.x, y: cy };
108
- }
109
- }
110
- function getHandleWorldPositionRotated(bounds, handle, rotationRad) {
111
- const r = normalizeRect(bounds);
112
- const p = getHandleWorldPosition(
113
- { x: 0, y: 0, width: r.width, height: r.height },
114
- handle
115
- );
116
- return itemLocalToWorld(p.x, p.y, r.x, r.y, r.width, r.height, rotationRad);
117
- }
118
- function getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld) {
119
- const r = normalizeRect(bounds);
120
- return itemLocalToWorld(
121
- r.width / 2,
122
- -handleOffsetWorld,
123
- r.x,
124
- r.y,
125
- r.width,
126
- r.height,
127
- rotationRad
128
- );
129
- }
130
-
131
- // src/scene/freehand-path.ts
132
- function smoothFreehandPointsToPathD(points) {
133
- const n = points.length;
134
- if (n === 0) return "";
135
- if (n === 1) {
136
- const p = points[0];
137
- if (!p) return "";
138
- return `M ${p.x} ${p.y}`;
139
- }
140
- if (n === 2) {
141
- const a = points[0];
142
- const b = points[1];
143
- if (!a || !b) return "";
144
- return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
145
- }
146
- const p0 = points[0];
147
- if (!p0) return "";
148
- let d = `M ${p0.x} ${p0.y}`;
149
- let i = 1;
150
- for (; i < n - 2; i++) {
151
- const pi = points[i];
152
- const pi1 = points[i + 1];
153
- if (!pi || !pi1) continue;
154
- const xc = (pi.x + pi1.x) / 2;
155
- const yc = (pi.y + pi1.y) / 2;
156
- d += ` Q ${pi.x} ${pi.y} ${xc} ${yc}`;
157
- }
158
- const pLast = points[i];
159
- const pEnd = points[i + 1];
160
- if (!pLast || !pEnd) return d;
161
- d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
162
- return d;
163
- }
164
-
165
30
  // src/scene/custom-shape.ts
166
31
  function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
167
32
  const b = normalizeRect(bounds);
@@ -786,6 +651,15 @@ function createShapeId() {
786
651
  const uid = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : String(Date.now());
787
652
  return `user-shape-${uid}`;
788
653
  }
654
+ function lineEndpointsToLocal(bounds, worldEndA, worldEndB) {
655
+ const b = normalizeRect(bounds);
656
+ return {
657
+ x1: worldEndA.x - b.x,
658
+ y1: worldEndA.y - b.y,
659
+ x2: worldEndB.x - b.x,
660
+ y2: worldEndB.y - b.y
661
+ };
662
+ }
789
663
  function rebuildItemSvg(item) {
790
664
  const style = resolveStrokeStyle(item);
791
665
  const k = item.toolKind;
@@ -889,6 +763,85 @@ function rebuildItemSvg(item) {
889
763
  }
890
764
  return item;
891
765
  }
766
+ function createRectangleItem(id, bounds, style) {
767
+ const r = normalizeRect(bounds);
768
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
769
+ return rebuildItemSvg({
770
+ id,
771
+ x: r.x,
772
+ y: r.y,
773
+ bounds: { ...r },
774
+ toolKind: "rect",
775
+ stroke: s.stroke,
776
+ strokeWidth: s.strokeWidth,
777
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
778
+ childrenSvg: ""
779
+ });
780
+ }
781
+ function createEllipseItem(id, bounds, style) {
782
+ const r = normalizeRect(bounds);
783
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
784
+ return rebuildItemSvg({
785
+ id,
786
+ x: r.x,
787
+ y: r.y,
788
+ bounds: { ...r },
789
+ toolKind: "ellipse",
790
+ stroke: s.stroke,
791
+ strokeWidth: s.strokeWidth,
792
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
793
+ childrenSvg: ""
794
+ });
795
+ }
796
+ function createArchitecturalCloudItem(id, bounds, style) {
797
+ const r = normalizeRect(bounds);
798
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
799
+ return rebuildItemSvg({
800
+ id,
801
+ x: r.x,
802
+ y: r.y,
803
+ bounds: { ...r },
804
+ toolKind: "architectural-cloud",
805
+ stroke: s.stroke,
806
+ strokeWidth: s.strokeWidth,
807
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
808
+ childrenSvg: ""
809
+ });
810
+ }
811
+ function createLineItem(id, bounds, line, toolKind, style, arrowBind) {
812
+ const r = normalizeRect(bounds);
813
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
814
+ return rebuildItemSvg({
815
+ id,
816
+ x: r.x,
817
+ y: r.y,
818
+ bounds: { ...r },
819
+ toolKind,
820
+ line: { ...line },
821
+ stroke: s.stroke,
822
+ strokeWidth: s.strokeWidth,
823
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
824
+ ...{},
825
+ childrenSvg: ""
826
+ });
827
+ }
828
+ function createTextItem(id, bounds, text = "", style, textFontSize) {
829
+ const r = normalizeRect(bounds);
830
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
831
+ return rebuildItemSvg({
832
+ id,
833
+ x: r.x,
834
+ y: r.y,
835
+ bounds: { ...r },
836
+ toolKind: "text",
837
+ text,
838
+ stroke: s.stroke,
839
+ strokeWidth: s.strokeWidth,
840
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
841
+ ...{ textFontSize } ,
842
+ childrenSvg: ""
843
+ });
844
+ }
892
845
  function createFreehandStrokeItem(id, pointsWorld, toolKind, style) {
893
846
  if (pointsWorld.length === 0) return null;
894
847
  const merged = {
@@ -947,6 +900,148 @@ function buildRasterImageChildrenSvg(dataUrl, intrinsic, bounds) {
947
900
  return `<g transform="translate(${tx}, ${ty}) scale(${s})"><image href="${href}" x="0" y="0" width="${iw}" height="${ih}" /></g>`;
948
901
  }
949
902
 
903
+ // src/math/item-transform.ts
904
+ function getItemRotationRad(item) {
905
+ return item.rotation ?? 0;
906
+ }
907
+ function itemLocalToWorld(lx, ly, itemX, itemY, w, h, rotationRad) {
908
+ const c = { x: w / 2, y: h / 2 };
909
+ const dlx = lx - c.x;
910
+ const dly = ly - c.y;
911
+ const cos = Math.cos(rotationRad);
912
+ const sin = Math.sin(rotationRad);
913
+ return {
914
+ x: itemX + c.x + cos * dlx - sin * dly,
915
+ y: itemY + c.y + sin * dlx + cos * dly
916
+ };
917
+ }
918
+ function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
919
+ const c = { x: w / 2, y: h / 2 };
920
+ const vx = wx - itemX;
921
+ const vy = wy - itemY;
922
+ const dx = vx - c.x;
923
+ const dy = vy - c.y;
924
+ const cos = Math.cos(-rotationRad);
925
+ const sin = Math.sin(-rotationRad);
926
+ const lx = cos * dx - sin * dy;
927
+ const ly = sin * dx + cos * dy;
928
+ return { x: c.x + lx, y: c.y + ly };
929
+ }
930
+ function boundsAabbForRotatedItem(item) {
931
+ const rot = getItemRotationRad(item);
932
+ if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
933
+ return item.bounds;
934
+ }
935
+ const r = normalizeRect(item.bounds);
936
+ if (Math.abs(rot) < 1e-12) {
937
+ return r;
938
+ }
939
+ const corners = [
940
+ [0, 0],
941
+ [r.width, 0],
942
+ [r.width, r.height],
943
+ [0, r.height]
944
+ ];
945
+ let minX = Infinity;
946
+ let minY = Infinity;
947
+ let maxX = -Infinity;
948
+ let maxY = -Infinity;
949
+ for (const [lx, ly] of corners) {
950
+ const p = itemLocalToWorld(lx, ly, item.x, item.y, r.width, r.height, rot);
951
+ minX = Math.min(minX, p.x);
952
+ minY = Math.min(minY, p.y);
953
+ maxX = Math.max(maxX, p.x);
954
+ maxY = Math.max(maxY, p.y);
955
+ }
956
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
957
+ }
958
+
959
+ // src/interaction/resize-handles.ts
960
+ function getHandleWorldPosition(bounds, id) {
961
+ const r = normalizeRect(bounds);
962
+ const cx = r.x + r.width / 2;
963
+ const cy = r.y + r.height / 2;
964
+ switch (id) {
965
+ case "nw":
966
+ return { x: r.x, y: r.y };
967
+ case "n":
968
+ return { x: cx, y: r.y };
969
+ case "ne":
970
+ return { x: r.x + r.width, y: r.y };
971
+ case "e":
972
+ return { x: r.x + r.width, y: cy };
973
+ case "se":
974
+ return { x: r.x + r.width, y: r.y + r.height };
975
+ case "s":
976
+ return { x: cx, y: r.y + r.height };
977
+ case "sw":
978
+ return { x: r.x, y: r.y + r.height };
979
+ case "w":
980
+ return { x: r.x, y: cy };
981
+ }
982
+ }
983
+ function getHandleWorldPositionRotated(bounds, handle, rotationRad) {
984
+ const r = normalizeRect(bounds);
985
+ const p = getHandleWorldPosition(
986
+ { x: 0, y: 0, width: r.width, height: r.height },
987
+ handle
988
+ );
989
+ return itemLocalToWorld(p.x, p.y, r.x, r.y, r.width, r.height, rotationRad);
990
+ }
991
+ function getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld) {
992
+ const r = normalizeRect(bounds);
993
+ return itemLocalToWorld(
994
+ r.width / 2,
995
+ -handleOffsetWorld,
996
+ r.x,
997
+ r.y,
998
+ r.width,
999
+ r.height,
1000
+ rotationRad
1001
+ );
1002
+ }
1003
+ function rectFromCorners(a, b) {
1004
+ const minX = Math.min(a.x, b.x);
1005
+ const maxX = Math.max(a.x, b.x);
1006
+ const minY = Math.min(a.y, b.y);
1007
+ const maxY = Math.max(a.y, b.y);
1008
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
1009
+ }
1010
+
1011
+ // src/scene/freehand-path.ts
1012
+ function smoothFreehandPointsToPathD(points) {
1013
+ const n = points.length;
1014
+ if (n === 0) return "";
1015
+ if (n === 1) {
1016
+ const p = points[0];
1017
+ if (!p) return "";
1018
+ return `M ${p.x} ${p.y}`;
1019
+ }
1020
+ if (n === 2) {
1021
+ const a = points[0];
1022
+ const b = points[1];
1023
+ if (!a || !b) return "";
1024
+ return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
1025
+ }
1026
+ const p0 = points[0];
1027
+ if (!p0) return "";
1028
+ let d = `M ${p0.x} ${p0.y}`;
1029
+ let i = 1;
1030
+ for (; i < n - 2; i++) {
1031
+ const pi = points[i];
1032
+ const pi1 = points[i + 1];
1033
+ if (!pi || !pi1) continue;
1034
+ const xc = (pi.x + pi1.x) / 2;
1035
+ const yc = (pi.y + pi1.y) / 2;
1036
+ d += ` Q ${pi.x} ${pi.y} ${xc} ${yc}`;
1037
+ }
1038
+ const pLast = points[i];
1039
+ const pEnd = points[i + 1];
1040
+ if (!pLast || !pEnd) return d;
1041
+ d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
1042
+ return d;
1043
+ }
1044
+
950
1045
  // src/native/skia-transform.ts
951
1046
  function parseNum(s) {
952
1047
  return Number(s);
@@ -1771,9 +1866,9 @@ function NativeInteractionOverlay({
1771
1866
  const previewElements = react.useMemo(() => {
1772
1867
  if (!placementPreview) return null;
1773
1868
  const p = placementPreview;
1774
- if (p.kind === "rect" || p.kind === "ellipse") {
1869
+ if (p.kind === "rect" || p.kind === "ellipse" || p.kind === "architectural-cloud") {
1775
1870
  const r = normalizeRect(p.rect);
1776
- return p.kind === "rect" ? /* @__PURE__ */ jsxRuntime.jsx(
1871
+ return p.kind === "rect" || p.kind === "architectural-cloud" ? /* @__PURE__ */ jsxRuntime.jsx(
1777
1872
  reactNativeSkia.Rect,
1778
1873
  {
1779
1874
  x: r.x,
@@ -2105,6 +2200,171 @@ function NativeSceneRenderer({
2105
2200
  if (width <= 0 || height <= 0) return null;
2106
2201
  return /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.Canvas, { style: { width, height }, children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.Group, { transform: cameraTransform, children: visible.map((item) => /* @__PURE__ */ jsxRuntime.jsx(MemoShape, { item }, item.id)) }) });
2107
2202
  }
2203
+ var DEFAULT_NATIVE_VECTOR_TOOLS = [
2204
+ { id: "hand", label: "Hand", shortLabel: "H" },
2205
+ { id: "select", label: "Select", shortLabel: "V" },
2206
+ { id: "draw", label: "Draw", shortLabel: "D" },
2207
+ { id: "marker", label: "Marker", shortLabel: "M" },
2208
+ { id: "eraser", label: "Eraser", shortLabel: "E" },
2209
+ { id: "text", label: "Text", shortLabel: "T" },
2210
+ { id: "note", label: "Note", shortLabel: "N" },
2211
+ { id: "rect", label: "Rectangle", shortLabel: "R" },
2212
+ { id: "ellipse", label: "Ellipse", shortLabel: "O" },
2213
+ { id: "architectural-cloud", label: "Cloud", shortLabel: "C" },
2214
+ { id: "line", label: "Line", shortLabel: "L" },
2215
+ { id: "arrow", label: "Arrow", shortLabel: "A" }
2216
+ ];
2217
+ function NativeVectorToolbar({
2218
+ value,
2219
+ onChange,
2220
+ tools = DEFAULT_NATIVE_VECTOR_TOOLS,
2221
+ disabled = false,
2222
+ disabledToolIds = [],
2223
+ accessibilityLabel = "Canvas tools",
2224
+ style,
2225
+ contentContainerStyle,
2226
+ toolButtonStyle,
2227
+ activeToolButtonStyle,
2228
+ toolLabelStyle,
2229
+ activeToolLabelStyle,
2230
+ renderToolIcon,
2231
+ renderToolButton
2232
+ }) {
2233
+ const disabledIds = react.useMemo(() => new Set(disabledToolIds), [disabledToolIds]);
2234
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { accessibilityLabel, style: [styles.shell, style], children: /* @__PURE__ */ jsxRuntime.jsx(
2235
+ reactNative.ScrollView,
2236
+ {
2237
+ horizontal: true,
2238
+ showsHorizontalScrollIndicator: false,
2239
+ contentContainerStyle: [styles.content, contentContainerStyle],
2240
+ children: tools.map((tool) => {
2241
+ const selected = tool.id === value;
2242
+ const toolDisabled = disabled || tool.disabled || disabledIds.has(tool.id);
2243
+ const foregroundColor = selected ? "#fafaf9" : "#18181b";
2244
+ const onSelect = () => {
2245
+ if (!toolDisabled) {
2246
+ onChange(tool.id);
2247
+ }
2248
+ };
2249
+ const input = {
2250
+ tool,
2251
+ selected,
2252
+ disabled: toolDisabled,
2253
+ foregroundColor,
2254
+ onSelect
2255
+ };
2256
+ if (renderToolButton) {
2257
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { children: renderToolButton(input) }, tool.id);
2258
+ }
2259
+ const icon = renderToolIcon?.(input) ?? /* @__PURE__ */ jsxRuntime.jsx(
2260
+ reactNative.Text,
2261
+ {
2262
+ style: [
2263
+ styles.shortLabel,
2264
+ { color: foregroundColor },
2265
+ toolLabelStyle,
2266
+ selected ? activeToolLabelStyle : void 0
2267
+ ],
2268
+ children: tool.shortLabel ?? tool.label.slice(0, 1).toUpperCase()
2269
+ }
2270
+ );
2271
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2272
+ reactNative.Pressable,
2273
+ {
2274
+ accessibilityLabel: tool.accessibilityLabel ?? tool.label,
2275
+ accessibilityRole: "button",
2276
+ accessibilityState: { selected, disabled: toolDisabled },
2277
+ disabled: toolDisabled,
2278
+ onPress: onSelect,
2279
+ style: ({ pressed }) => [
2280
+ styles.toolButton,
2281
+ toolButtonStyle,
2282
+ selected ? styles.activeToolButton : void 0,
2283
+ selected ? activeToolButtonStyle : void 0,
2284
+ pressed && !toolDisabled ? styles.pressedToolButton : void 0,
2285
+ toolDisabled ? styles.disabledToolButton : void 0
2286
+ ],
2287
+ children: [
2288
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.iconSlot, children: icon }),
2289
+ /* @__PURE__ */ jsxRuntime.jsx(
2290
+ reactNative.Text,
2291
+ {
2292
+ numberOfLines: 1,
2293
+ style: [
2294
+ styles.toolLabel,
2295
+ { color: foregroundColor },
2296
+ toolLabelStyle,
2297
+ selected ? activeToolLabelStyle : void 0
2298
+ ],
2299
+ children: tool.label
2300
+ }
2301
+ )
2302
+ ]
2303
+ },
2304
+ tool.id
2305
+ );
2306
+ })
2307
+ }
2308
+ ) });
2309
+ }
2310
+ var styles = reactNative.StyleSheet.create({
2311
+ shell: {
2312
+ borderRadius: 10,
2313
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
2314
+ borderColor: "rgba(24, 24, 27, 0.14)",
2315
+ backgroundColor: "rgba(255, 255, 255, 0.96)",
2316
+ shadowColor: "#18181b",
2317
+ shadowOpacity: 0.12,
2318
+ shadowRadius: 16,
2319
+ shadowOffset: { width: 0, height: 8 },
2320
+ elevation: 6
2321
+ },
2322
+ content: {
2323
+ alignItems: "center",
2324
+ gap: 6,
2325
+ paddingHorizontal: 6,
2326
+ paddingVertical: 6
2327
+ },
2328
+ toolButton: {
2329
+ minWidth: 58,
2330
+ height: 52,
2331
+ alignItems: "center",
2332
+ justifyContent: "center",
2333
+ gap: 3,
2334
+ borderRadius: 8,
2335
+ paddingHorizontal: 8,
2336
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
2337
+ borderColor: "transparent",
2338
+ backgroundColor: "transparent"
2339
+ },
2340
+ activeToolButton: {
2341
+ borderColor: "rgba(24, 24, 27, 0.24)",
2342
+ backgroundColor: "#18181b"
2343
+ },
2344
+ pressedToolButton: {
2345
+ backgroundColor: "rgba(24, 24, 27, 0.08)"
2346
+ },
2347
+ disabledToolButton: {
2348
+ opacity: 0.42
2349
+ },
2350
+ iconSlot: {
2351
+ height: 22,
2352
+ minWidth: 22,
2353
+ alignItems: "center",
2354
+ justifyContent: "center"
2355
+ },
2356
+ shortLabel: {
2357
+ fontSize: 16,
2358
+ fontWeight: "700",
2359
+ lineHeight: 20
2360
+ },
2361
+ toolLabel: {
2362
+ maxWidth: 68,
2363
+ fontSize: 10,
2364
+ fontWeight: "600",
2365
+ lineHeight: 12
2366
+ }
2367
+ });
2108
2368
 
2109
2369
  // src/camera/camera.ts
2110
2370
  var Camera2D = class {
@@ -2292,6 +2552,37 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
2292
2552
  }
2293
2553
  return ids;
2294
2554
  }
2555
+ var MIN_PLACE_SIZE = 8;
2556
+ var MIN_ARROW_DRAG_PX = 8;
2557
+ var TAP_PX = 20;
2558
+ function isPlacementTool(toolId) {
2559
+ return toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud" || toolId === "line" || toolId === "arrow";
2560
+ }
2561
+ function placementPreviewForTool(toolId, start, end) {
2562
+ if (toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud") {
2563
+ return { kind: toolId, rect: rectFromCorners(start, end) };
2564
+ }
2565
+ return { kind: toolId, start, end };
2566
+ }
2567
+ function defaultPlacementWorld(toolId, center) {
2568
+ const cx = center.x;
2569
+ const cy = center.y;
2570
+ if (toolId === "rect") {
2571
+ return { raw: { x: cx - 60, y: cy - 40, width: 120, height: 80 } };
2572
+ }
2573
+ if (toolId === "ellipse") {
2574
+ return { raw: { x: cx - 70, y: cy - 45, width: 140, height: 90 } };
2575
+ }
2576
+ if (toolId === "architectural-cloud") {
2577
+ return { raw: { x: cx - 90, y: cy - 50, width: 180, height: 100 } };
2578
+ }
2579
+ const start = { x: cx - 50, y: cy };
2580
+ const end = { x: cx + 50, y: cy };
2581
+ return {
2582
+ raw: rectFromCorners(start, end),
2583
+ lineWorld: [start, end]
2584
+ };
2585
+ }
2295
2586
  function collectIdsInRect(items, marquee) {
2296
2587
  const m = normalizeRect(marquee);
2297
2588
  const out = [];
@@ -2469,10 +2760,31 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2469
2760
  setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
2470
2761
  return;
2471
2762
  }
2763
+ if (isPlacementTool(tool)) {
2764
+ dragStateRef.current = {
2765
+ kind: "place",
2766
+ tool,
2767
+ startWorld: { x: worldX, y: worldY },
2768
+ startScreen: { x: sx, y: sy }
2769
+ };
2770
+ setPlacementPreview(
2771
+ placementPreviewForTool(
2772
+ tool,
2773
+ { x: worldX, y: worldY },
2774
+ {
2775
+ x: worldX,
2776
+ y: worldY
2777
+ }
2778
+ )
2779
+ );
2780
+ return;
2781
+ }
2472
2782
  if (tool === "note" || tool === "text") {
2473
2783
  dragStateRef.current = {
2474
2784
  kind: "tap",
2475
- startWorld: { x: worldX, y: worldY }
2785
+ tool,
2786
+ startWorld: { x: worldX, y: worldY },
2787
+ startScreen: { x: sx, y: sy }
2476
2788
  };
2477
2789
  return;
2478
2790
  }
@@ -2584,6 +2896,15 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2584
2896
  setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
2585
2897
  return;
2586
2898
  }
2899
+ if (st.kind === "place") {
2900
+ setPlacementPreview(
2901
+ placementPreviewForTool(st.tool, st.startWorld, {
2902
+ x: worldX,
2903
+ y: worldY
2904
+ })
2905
+ );
2906
+ return;
2907
+ }
2587
2908
  },
2588
2909
  onPanResponderRelease: (evt) => {
2589
2910
  lastPinchDist.current = null;
@@ -2639,12 +2960,83 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2639
2960
  dragStateRef.current = { kind: "idle" };
2640
2961
  return;
2641
2962
  }
2963
+ if (st.kind === "place") {
2964
+ dragStateRef.current = { kind: "idle" };
2965
+ setPlacementPreview(null);
2966
+ const change = onItemsChangeRef.current;
2967
+ if (!change) return;
2968
+ const { worldX, worldY } = screenToWorld(
2969
+ evt.nativeEvent.locationX,
2970
+ evt.nativeEvent.locationY
2971
+ );
2972
+ const a = st.startWorld;
2973
+ const b = { x: worldX, y: worldY };
2974
+ const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
2975
+ const screenDy = evt.nativeEvent.locationY - st.startScreen.y;
2976
+ if (st.tool === "arrow" && Math.hypot(screenDx, screenDy) < MIN_ARROW_DRAG_PX) {
2977
+ return;
2978
+ }
2979
+ let raw = rectFromCorners(a, b);
2980
+ let br = normalizeRect(raw);
2981
+ let lineStart = a;
2982
+ let lineEnd = b;
2983
+ if (br.width < MIN_PLACE_SIZE || br.height < MIN_PLACE_SIZE) {
2984
+ const center = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
2985
+ const defaults = defaultPlacementWorld(st.tool, center);
2986
+ raw = defaults.raw;
2987
+ br = normalizeRect(raw);
2988
+ if (defaults.lineWorld) {
2989
+ const [defaultStart, defaultEnd] = defaults.lineWorld;
2990
+ lineStart = defaultStart;
2991
+ lineEnd = defaultEnd;
2992
+ }
2993
+ }
2994
+ const id = createShapeId();
2995
+ if (st.tool === "rect") {
2996
+ change([...itemsRef.current, createRectangleItem(id, raw)]);
2997
+ onSelectionChangeRef.current?.([id]);
2998
+ return;
2999
+ }
3000
+ if (st.tool === "ellipse") {
3001
+ change([...itemsRef.current, createEllipseItem(id, raw)]);
3002
+ onSelectionChangeRef.current?.([id]);
3003
+ return;
3004
+ }
3005
+ if (st.tool === "architectural-cloud") {
3006
+ change([...itemsRef.current, createArchitecturalCloudItem(id, raw)]);
3007
+ onSelectionChangeRef.current?.([id]);
3008
+ return;
3009
+ }
3010
+ const line = lineEndpointsToLocal(br, lineStart, lineEnd);
3011
+ change([...itemsRef.current, createLineItem(id, br, line, st.tool)]);
3012
+ onSelectionChangeRef.current?.([id]);
3013
+ return;
3014
+ }
2642
3015
  if (st.kind === "tap") {
2643
3016
  dragStateRef.current = { kind: "idle" };
3017
+ const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
3018
+ const screenDy = evt.nativeEvent.locationY - st.startScreen.y;
3019
+ if (Math.hypot(screenDx, screenDy) > TAP_PX) return;
2644
3020
  const change = onItemsChangeRef.current;
2645
3021
  if (!change) return;
2646
- const tool = toolIdRef.current;
2647
- if (tool === "note") {
3022
+ if (st.tool === "text") {
3023
+ const id = createShapeId();
3024
+ const item = createTextItem(
3025
+ id,
3026
+ {
3027
+ x: st.startWorld.x - 4,
3028
+ y: st.startWorld.y - 18,
3029
+ width: 160,
3030
+ height: 26
3031
+ },
3032
+ "Text",
3033
+ void 0,
3034
+ 18
3035
+ );
3036
+ change([...itemsRef.current, item]);
3037
+ onSelectionChangeRef.current?.([id]);
3038
+ }
3039
+ if (st.tool === "note") {
2648
3040
  const id = createShapeId();
2649
3041
  const note = {
2650
3042
  id,
@@ -2660,6 +3052,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2660
3052
  toolKind: "custom"
2661
3053
  };
2662
3054
  change([...itemsRef.current, note]);
3055
+ onSelectionChangeRef.current?.([id]);
2663
3056
  }
2664
3057
  return;
2665
3058
  }
@@ -2747,10 +3140,14 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2747
3140
  );
2748
3141
  });
2749
3142
 
3143
+ exports.DEFAULT_NATIVE_VECTOR_TOOLS = DEFAULT_NATIVE_VECTOR_TOOLS;
2750
3144
  exports.NativeInteractionOverlay = NativeInteractionOverlay;
2751
3145
  exports.NativeSceneRenderer = NativeSceneRenderer;
2752
3146
  exports.NativeShapeRenderer = NativeShapeRenderer;
3147
+ exports.NativeVectorToolbar = NativeVectorToolbar;
2753
3148
  exports.NativeVectorViewport = NativeVectorViewport;
3149
+ exports.createFreehandStrokeItem = createFreehandStrokeItem;
3150
+ exports.createShapeId = createShapeId;
2754
3151
  exports.parseSvgFragment = parseSvgFragment;
2755
3152
  //# sourceMappingURL=native.cjs.map
2756
3153
  //# sourceMappingURL=native.cjs.map