canvu-react 0.4.5 → 0.4.7

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,148 +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
- function rectFromCorners(a, b) {
131
- const minX = Math.min(a.x, b.x);
132
- const maxX = Math.max(a.x, b.x);
133
- const minY = Math.min(a.y, b.y);
134
- const maxY = Math.max(a.y, b.y);
135
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
136
- }
137
-
138
- // src/scene/freehand-path.ts
139
- function smoothFreehandPointsToPathD(points) {
140
- const n = points.length;
141
- if (n === 0) return "";
142
- if (n === 1) {
143
- const p = points[0];
144
- if (!p) return "";
145
- return `M ${p.x} ${p.y}`;
146
- }
147
- if (n === 2) {
148
- const a = points[0];
149
- const b = points[1];
150
- if (!a || !b) return "";
151
- return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
152
- }
153
- const p0 = points[0];
154
- if (!p0) return "";
155
- let d = `M ${p0.x} ${p0.y}`;
156
- let i = 1;
157
- for (; i < n - 2; i++) {
158
- const pi = points[i];
159
- const pi1 = points[i + 1];
160
- if (!pi || !pi1) continue;
161
- const xc = (pi.x + pi1.x) / 2;
162
- const yc = (pi.y + pi1.y) / 2;
163
- d += ` Q ${pi.x} ${pi.y} ${xc} ${yc}`;
164
- }
165
- const pLast = points[i];
166
- const pEnd = points[i + 1];
167
- if (!pLast || !pEnd) return d;
168
- d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
169
- return d;
170
- }
171
-
172
30
  // src/scene/custom-shape.ts
173
31
  function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
174
32
  const b = normalizeRect(bounds);
@@ -1041,6 +899,167 @@ function buildRasterImageChildrenSvg(dataUrl, intrinsic, bounds) {
1041
899
  const ty = (r.height - ih * s) / 2;
1042
900
  return `<g transform="translate(${tx}, ${ty}) scale(${s})"><image href="${href}" x="0" y="0" width="${iw}" height="${ih}" /></g>`;
1043
901
  }
902
+ function createImageItem(id, bounds, imageRasterHref, imageIntrinsicSize) {
903
+ const r = normalizeRect(bounds);
904
+ const iw = Math.max(1e-6, imageIntrinsicSize.width);
905
+ const ih = Math.max(1e-6, imageIntrinsicSize.height);
906
+ return {
907
+ id,
908
+ x: r.x,
909
+ y: r.y,
910
+ bounds: { ...r },
911
+ toolKind: "image",
912
+ imageRasterHref,
913
+ imageIntrinsicSize: { width: iw, height: ih },
914
+ childrenSvg: buildRasterImageChildrenSvg(
915
+ imageRasterHref,
916
+ { width: iw, height: ih },
917
+ r
918
+ )
919
+ };
920
+ }
921
+
922
+ // src/math/item-transform.ts
923
+ function getItemRotationRad(item) {
924
+ return item.rotation ?? 0;
925
+ }
926
+ function itemLocalToWorld(lx, ly, itemX, itemY, w, h, rotationRad) {
927
+ const c = { x: w / 2, y: h / 2 };
928
+ const dlx = lx - c.x;
929
+ const dly = ly - c.y;
930
+ const cos = Math.cos(rotationRad);
931
+ const sin = Math.sin(rotationRad);
932
+ return {
933
+ x: itemX + c.x + cos * dlx - sin * dly,
934
+ y: itemY + c.y + sin * dlx + cos * dly
935
+ };
936
+ }
937
+ function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
938
+ const c = { x: w / 2, y: h / 2 };
939
+ const vx = wx - itemX;
940
+ const vy = wy - itemY;
941
+ const dx = vx - c.x;
942
+ const dy = vy - c.y;
943
+ const cos = Math.cos(-rotationRad);
944
+ const sin = Math.sin(-rotationRad);
945
+ const lx = cos * dx - sin * dy;
946
+ const ly = sin * dx + cos * dy;
947
+ return { x: c.x + lx, y: c.y + ly };
948
+ }
949
+ function boundsAabbForRotatedItem(item) {
950
+ const rot = getItemRotationRad(item);
951
+ if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
952
+ return item.bounds;
953
+ }
954
+ const r = normalizeRect(item.bounds);
955
+ if (Math.abs(rot) < 1e-12) {
956
+ return r;
957
+ }
958
+ const corners = [
959
+ [0, 0],
960
+ [r.width, 0],
961
+ [r.width, r.height],
962
+ [0, r.height]
963
+ ];
964
+ let minX = Infinity;
965
+ let minY = Infinity;
966
+ let maxX = -Infinity;
967
+ let maxY = -Infinity;
968
+ for (const [lx, ly] of corners) {
969
+ const p = itemLocalToWorld(lx, ly, item.x, item.y, r.width, r.height, rot);
970
+ minX = Math.min(minX, p.x);
971
+ minY = Math.min(minY, p.y);
972
+ maxX = Math.max(maxX, p.x);
973
+ maxY = Math.max(maxY, p.y);
974
+ }
975
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
976
+ }
977
+
978
+ // src/interaction/resize-handles.ts
979
+ function getHandleWorldPosition(bounds, id) {
980
+ const r = normalizeRect(bounds);
981
+ const cx = r.x + r.width / 2;
982
+ const cy = r.y + r.height / 2;
983
+ switch (id) {
984
+ case "nw":
985
+ return { x: r.x, y: r.y };
986
+ case "n":
987
+ return { x: cx, y: r.y };
988
+ case "ne":
989
+ return { x: r.x + r.width, y: r.y };
990
+ case "e":
991
+ return { x: r.x + r.width, y: cy };
992
+ case "se":
993
+ return { x: r.x + r.width, y: r.y + r.height };
994
+ case "s":
995
+ return { x: cx, y: r.y + r.height };
996
+ case "sw":
997
+ return { x: r.x, y: r.y + r.height };
998
+ case "w":
999
+ return { x: r.x, y: cy };
1000
+ }
1001
+ }
1002
+ function getHandleWorldPositionRotated(bounds, handle, rotationRad) {
1003
+ const r = normalizeRect(bounds);
1004
+ const p = getHandleWorldPosition(
1005
+ { x: 0, y: 0, width: r.width, height: r.height },
1006
+ handle
1007
+ );
1008
+ return itemLocalToWorld(p.x, p.y, r.x, r.y, r.width, r.height, rotationRad);
1009
+ }
1010
+ function getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld) {
1011
+ const r = normalizeRect(bounds);
1012
+ return itemLocalToWorld(
1013
+ r.width / 2,
1014
+ -handleOffsetWorld,
1015
+ r.x,
1016
+ r.y,
1017
+ r.width,
1018
+ r.height,
1019
+ rotationRad
1020
+ );
1021
+ }
1022
+ function rectFromCorners(a, b) {
1023
+ const minX = Math.min(a.x, b.x);
1024
+ const maxX = Math.max(a.x, b.x);
1025
+ const minY = Math.min(a.y, b.y);
1026
+ const maxY = Math.max(a.y, b.y);
1027
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
1028
+ }
1029
+
1030
+ // src/scene/freehand-path.ts
1031
+ function smoothFreehandPointsToPathD(points) {
1032
+ const n = points.length;
1033
+ if (n === 0) return "";
1034
+ if (n === 1) {
1035
+ const p = points[0];
1036
+ if (!p) return "";
1037
+ return `M ${p.x} ${p.y}`;
1038
+ }
1039
+ if (n === 2) {
1040
+ const a = points[0];
1041
+ const b = points[1];
1042
+ if (!a || !b) return "";
1043
+ return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
1044
+ }
1045
+ const p0 = points[0];
1046
+ if (!p0) return "";
1047
+ let d = `M ${p0.x} ${p0.y}`;
1048
+ let i = 1;
1049
+ for (; i < n - 2; i++) {
1050
+ const pi = points[i];
1051
+ const pi1 = points[i + 1];
1052
+ if (!pi || !pi1) continue;
1053
+ const xc = (pi.x + pi1.x) / 2;
1054
+ const yc = (pi.y + pi1.y) / 2;
1055
+ d += ` Q ${pi.x} ${pi.y} ${xc} ${yc}`;
1056
+ }
1057
+ const pLast = points[i];
1058
+ const pEnd = points[i + 1];
1059
+ if (!pLast || !pEnd) return d;
1060
+ d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
1061
+ return d;
1062
+ }
1044
1063
 
1045
1064
  // src/native/skia-transform.ts
1046
1065
  function parseNum(s) {
@@ -1287,6 +1306,9 @@ function SvgNodeItem({ node }) {
1287
1306
  }
1288
1307
  );
1289
1308
  }
1309
+ case "image": {
1310
+ return /* @__PURE__ */ jsxRuntime.jsx(SvgImageNodeItem, { node });
1311
+ }
1290
1312
  case "g": {
1291
1313
  const transform = node.transform ? parseSvgTransform(node.transform) : void 0;
1292
1314
  return /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.Group, { transform, children: node.children.map((child, i) => /* @__PURE__ */ jsxRuntime.jsx(SvgNodeItem, { node: child }, i)) });
@@ -1298,6 +1320,23 @@ function SvgNodeItem({ node }) {
1298
1320
  return null;
1299
1321
  }
1300
1322
  }
1323
+ function SvgImageNodeItem({
1324
+ node
1325
+ }) {
1326
+ const image = reactNativeSkia.useImage(node.href);
1327
+ if (!image) return null;
1328
+ return /* @__PURE__ */ jsxRuntime.jsx(
1329
+ reactNativeSkia.Image,
1330
+ {
1331
+ image,
1332
+ x: toNum(node.x),
1333
+ y: toNum(node.y),
1334
+ width: toNum(node.width),
1335
+ height: toNum(node.height),
1336
+ fit: "contain"
1337
+ }
1338
+ );
1339
+ }
1301
1340
  function polygonPointsToPath(points) {
1302
1341
  const nums = points.split(/[\s,]+/).filter(Boolean).map(Number);
1303
1342
  if (nums.length < 4) return "";
@@ -2200,146 +2239,353 @@ function NativeSceneRenderer({
2200
2239
  if (width <= 0 || height <= 0) return null;
2201
2240
  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)) }) });
2202
2241
  }
2242
+ var DEFAULT_NATIVE_OVERFLOW_TOOL_IDS = [
2243
+ "rect",
2244
+ "ellipse",
2245
+ "architectural-cloud",
2246
+ "line",
2247
+ "marker",
2248
+ "laser",
2249
+ "image"
2250
+ ];
2203
2251
  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" }
2252
+ { id: "hand", label: "Hand", shortcutHint: "H", shortLabel: "H" },
2253
+ { id: "select", label: "Select", shortcutHint: "V", shortLabel: "V" },
2254
+ { id: "rect", label: "Rectangle", shortcutHint: "R", shortLabel: "R" },
2255
+ { id: "ellipse", label: "Ellipse", shortcutHint: "O", shortLabel: "O" },
2256
+ {
2257
+ id: "architectural-cloud",
2258
+ label: "Nuvem arquitetural",
2259
+ tooltipLabel: "Architectural cloud",
2260
+ shortcutHint: "C",
2261
+ shortLabel: "C"
2262
+ },
2263
+ { id: "line", label: "Line", shortcutHint: "L", shortLabel: "L" },
2264
+ { id: "arrow", label: "Arrow", shortcutHint: "A", shortLabel: "A" },
2265
+ {
2266
+ id: "draw",
2267
+ label: "Desenhar",
2268
+ tooltipLabel: "Draw",
2269
+ shortcutHint: "D",
2270
+ shortLabel: "D"
2271
+ },
2272
+ {
2273
+ id: "marker",
2274
+ label: "Realce",
2275
+ tooltipLabel: "Highlighter",
2276
+ shortcutHint: "M",
2277
+ shortLabel: "M"
2278
+ },
2279
+ { id: "laser", label: "Laser", shortcutHint: "K", shortLabel: "K" },
2280
+ {
2281
+ id: "eraser",
2282
+ label: "Borracha",
2283
+ tooltipLabel: "Eraser",
2284
+ shortcutHint: "E",
2285
+ shortLabel: "E"
2286
+ },
2287
+ { id: "text", label: "Text", shortcutHint: "T", shortLabel: "T" },
2288
+ { id: "image", label: "File", shortcutHint: "I", shortLabel: "I" }
2216
2289
  ];
2290
+ function splitToolbarTools(tools, overflowToolIds) {
2291
+ const overflowIds = new Set(overflowToolIds);
2292
+ if (overflowIds.size === 0) return { primary: tools, overflow: [] };
2293
+ return {
2294
+ primary: tools.filter((tool) => !overflowIds.has(tool.id)),
2295
+ overflow: tools.filter((tool) => overflowIds.has(tool.id))
2296
+ };
2297
+ }
2298
+ function getPromotedOverflowTool(input) {
2299
+ const selectedOverflowTool = input.overflowTools.find((tool) => tool.id === input.selectedId) ?? null;
2300
+ const fallbackTool = input.overflowTools.find((tool) => tool.id === input.initialPromotedId) ?? input.overflowTools[0] ?? null;
2301
+ const promotedTool = selectedOverflowTool ?? fallbackTool;
2302
+ if (!promotedTool) {
2303
+ return { promotedTool: null, remainingOverflowTools: input.overflowTools };
2304
+ }
2305
+ return {
2306
+ promotedTool,
2307
+ remainingOverflowTools: input.overflowTools.filter(
2308
+ (tool) => tool.id !== promotedTool.id
2309
+ )
2310
+ };
2311
+ }
2312
+ function tooltipTextForTool(tool) {
2313
+ const name = tool.tooltipLabel ?? tool.label;
2314
+ return tool.shortcutHint ? `${name} - ${tool.shortcutHint}` : name;
2315
+ }
2217
2316
  function NativeVectorToolbar({
2218
2317
  value,
2219
2318
  onChange,
2220
2319
  tools = DEFAULT_NATIVE_VECTOR_TOOLS,
2320
+ overflowToolIds = DEFAULT_NATIVE_OVERFLOW_TOOL_IDS,
2321
+ overflowMenuAccessibilityLabel = "More tools",
2221
2322
  disabled = false,
2222
2323
  disabledToolIds = [],
2324
+ showToolLockToggle = true,
2325
+ toolLocked = false,
2326
+ onToolLockedChange,
2327
+ density = "compact",
2223
2328
  accessibilityLabel = "Canvas tools",
2224
2329
  style,
2225
2330
  contentContainerStyle,
2331
+ overflowPanelStyle,
2226
2332
  toolButtonStyle,
2227
2333
  activeToolButtonStyle,
2228
2334
  toolLabelStyle,
2229
2335
  activeToolLabelStyle,
2230
2336
  renderToolIcon,
2337
+ renderToolLockIcon,
2338
+ renderOverflowIcon,
2339
+ renderOverflowChevronIcon,
2231
2340
  renderToolButton
2232
2341
  }) {
2342
+ const [overflowOpen, setOverflowOpen] = react.useState(false);
2233
2343
  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,
2344
+ const { primary: primaryTools, overflow: overflowTools } = react.useMemo(
2345
+ () => splitToolbarTools(tools, overflowToolIds),
2346
+ [tools, overflowToolIds]
2347
+ );
2348
+ const { promotedTool, remainingOverflowTools } = react.useMemo(
2349
+ () => getPromotedOverflowTool({
2350
+ overflowTools,
2351
+ selectedId: value,
2352
+ initialPromotedId: overflowToolIds[0] ?? null
2353
+ }),
2354
+ [overflowTools, overflowToolIds, value]
2355
+ );
2356
+ const activeOverflowTool = overflowTools.find((tool) => tool.id === value) ?? null;
2357
+ const toolbarTools = promotedTool !== null ? [...primaryTools, promotedTool] : primaryTools;
2358
+ const showOverflowMenu = remainingOverflowTools.length > 0;
2359
+ const toolLockDisabled = disabled || !onToolLockedChange;
2360
+ const toggleToolLock = () => onToolLockedChange?.(!toolLocked);
2361
+ const toggleOverflow = () => setOverflowOpen((open) => !open);
2362
+ const overflowRenderInput = {
2363
+ open: overflowOpen,
2364
+ activeTool: activeOverflowTool,
2365
+ disabled,
2366
+ foregroundColor: "#18181b",
2367
+ onToggle: toggleOverflow
2368
+ };
2369
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2370
+ reactNative.View,
2236
2371
  {
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,
2372
+ accessibilityLabel,
2373
+ style: [styles.shell, style],
2374
+ pointerEvents: "box-none",
2375
+ children: [
2376
+ /* @__PURE__ */ jsxRuntime.jsxs(
2377
+ reactNative.ScrollView,
2261
2378
  {
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
2379
+ horizontal: true,
2380
+ showsHorizontalScrollIndicator: false,
2381
+ contentContainerStyle: [
2382
+ styles.content,
2383
+ density === "comfortable" ? styles.comfortableContent : void 0,
2384
+ contentContainerStyle
2286
2385
  ],
2287
2386
  children: [
2288
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.iconSlot, children: icon }),
2289
- /* @__PURE__ */ jsxRuntime.jsx(
2290
- reactNative.Text,
2387
+ showToolLockToggle ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2388
+ /* @__PURE__ */ jsxRuntime.jsx(
2389
+ reactNative.Pressable,
2390
+ {
2391
+ accessibilityLabel: toolLocked ? "Unlock tool" : "Lock tool",
2392
+ accessibilityRole: "button",
2393
+ accessibilityState: {
2394
+ checked: toolLocked,
2395
+ disabled: toolLockDisabled
2396
+ },
2397
+ disabled: toolLockDisabled,
2398
+ onPress: toggleToolLock,
2399
+ style: ({ pressed }) => [
2400
+ styles.toolButton,
2401
+ density === "comfortable" ? styles.comfortableToolButton : void 0,
2402
+ toolLocked ? styles.activeToolButton : void 0,
2403
+ pressed && !toolLockDisabled ? styles.pressedToolButton : void 0,
2404
+ toolLockDisabled ? styles.disabledToolButton : void 0
2405
+ ],
2406
+ children: renderToolLockIcon?.({
2407
+ locked: toolLocked,
2408
+ disabled: toolLockDisabled,
2409
+ foregroundColor: "#18181b",
2410
+ onToggle: toggleToolLock
2411
+ }) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.lockGlyph, children: toolLocked ? "L" : "U" })
2412
+ }
2413
+ ),
2414
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.toolLockDivider })
2415
+ ] }) : null,
2416
+ toolbarTools.map(
2417
+ (tool) => renderNativeToolButton({
2418
+ tool,
2419
+ value,
2420
+ onChange,
2421
+ disabled,
2422
+ disabledIds,
2423
+ density,
2424
+ toolButtonStyle,
2425
+ activeToolButtonStyle,
2426
+ toolLabelStyle,
2427
+ activeToolLabelStyle,
2428
+ renderToolIcon,
2429
+ renderToolButton
2430
+ })
2431
+ ),
2432
+ showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.overflowSpacer }) : null,
2433
+ showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsxs(
2434
+ reactNative.Pressable,
2291
2435
  {
2292
- numberOfLines: 1,
2293
- style: [
2294
- styles.toolLabel,
2295
- { color: foregroundColor },
2296
- toolLabelStyle,
2297
- selected ? activeToolLabelStyle : void 0
2436
+ accessibilityLabel: activeOverflowTool ? `${overflowMenuAccessibilityLabel}: ${activeOverflowTool.accessibilityLabel ?? activeOverflowTool.label}` : overflowMenuAccessibilityLabel,
2437
+ accessibilityRole: "button",
2438
+ accessibilityState: {
2439
+ expanded: overflowOpen,
2440
+ selected: overflowOpen || activeOverflowTool !== null,
2441
+ disabled
2442
+ },
2443
+ disabled,
2444
+ onPress: toggleOverflow,
2445
+ style: ({ pressed }) => [
2446
+ styles.overflowTrigger,
2447
+ overflowOpen || activeOverflowTool ? styles.activeToolButton : void 0,
2448
+ pressed && !disabled ? styles.pressedToolButton : void 0,
2449
+ disabled ? styles.disabledToolButton : void 0
2298
2450
  ],
2299
- children: tool.label
2451
+ children: [
2452
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.iconSlot, children: activeOverflowTool ? renderToolIcon?.({
2453
+ tool: activeOverflowTool,
2454
+ selected: true,
2455
+ disabled,
2456
+ foregroundColor: "#18181b",
2457
+ onSelect: () => onChange(activeOverflowTool.id)
2458
+ }) ?? renderNativeToolFallback(activeOverflowTool, "#18181b") : renderOverflowIcon?.(overflowRenderInput) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.shapesGlyph, children: "S" }) }),
2459
+ renderOverflowChevronIcon?.(overflowRenderInput) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.chevronGlyph, children: overflowOpen ? "^" : "v" })
2460
+ ]
2300
2461
  }
2301
- )
2462
+ ) : null
2302
2463
  ]
2303
- },
2304
- tool.id
2305
- );
2306
- })
2464
+ }
2465
+ ),
2466
+ overflowOpen && showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.overflowPanel, overflowPanelStyle], children: remainingOverflowTools.map(
2467
+ (tool) => renderNativeToolButton({
2468
+ tool,
2469
+ value,
2470
+ onChange: (toolId) => {
2471
+ setOverflowOpen(false);
2472
+ onChange(toolId);
2473
+ },
2474
+ disabled,
2475
+ disabledIds,
2476
+ density: "compact",
2477
+ toolButtonStyle: styles.overflowToolButton,
2478
+ activeToolButtonStyle,
2479
+ toolLabelStyle,
2480
+ activeToolLabelStyle,
2481
+ renderToolIcon,
2482
+ renderToolButton
2483
+ })
2484
+ ) }) : null
2485
+ ]
2307
2486
  }
2308
- ) });
2487
+ );
2488
+ }
2489
+ function renderNativeToolButton(input) {
2490
+ const selected = input.tool.id === input.value;
2491
+ const toolDisabled = input.disabled || input.tool.disabled || input.disabledIds.has(input.tool.id);
2492
+ const foregroundColor = "#18181b";
2493
+ const onSelect = () => {
2494
+ if (!toolDisabled) {
2495
+ input.onChange(input.tool.id);
2496
+ }
2497
+ };
2498
+ const renderInput = {
2499
+ tool: input.tool,
2500
+ selected,
2501
+ disabled: toolDisabled,
2502
+ foregroundColor,
2503
+ onSelect
2504
+ };
2505
+ if (input.renderToolButton) {
2506
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { children: input.renderToolButton(renderInput) }, input.tool.id);
2507
+ }
2508
+ const icon = input.renderToolIcon?.(renderInput) ?? renderNativeToolFallback(input.tool, foregroundColor, input.toolLabelStyle);
2509
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2510
+ reactNative.Pressable,
2511
+ {
2512
+ accessibilityLabel: input.tool.accessibilityLabel ?? tooltipTextForTool(input.tool),
2513
+ accessibilityRole: "button",
2514
+ accessibilityState: { selected, disabled: toolDisabled },
2515
+ disabled: toolDisabled,
2516
+ onPress: onSelect,
2517
+ style: ({ pressed }) => [
2518
+ styles.toolButton,
2519
+ input.density === "comfortable" ? styles.comfortableToolButton : void 0,
2520
+ input.toolButtonStyle,
2521
+ selected ? styles.activeToolButton : void 0,
2522
+ selected ? input.activeToolButtonStyle : void 0,
2523
+ pressed && !toolDisabled ? styles.pressedToolButton : void 0,
2524
+ toolDisabled ? styles.disabledToolButton : void 0
2525
+ ],
2526
+ children: [
2527
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.iconSlot, children: icon }),
2528
+ input.density === "comfortable" ? /* @__PURE__ */ jsxRuntime.jsx(
2529
+ reactNative.Text,
2530
+ {
2531
+ numberOfLines: 1,
2532
+ style: [
2533
+ styles.toolLabel,
2534
+ { color: foregroundColor },
2535
+ input.toolLabelStyle,
2536
+ selected ? input.activeToolLabelStyle : void 0
2537
+ ],
2538
+ children: input.tool.label
2539
+ }
2540
+ ) : null
2541
+ ]
2542
+ },
2543
+ input.tool.id
2544
+ );
2545
+ }
2546
+ function renderNativeToolFallback(tool, foregroundColor, toolLabelStyle) {
2547
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.shortLabel, { color: foregroundColor }, toolLabelStyle], children: tool.shortLabel ?? tool.label.slice(0, 1).toUpperCase() });
2309
2548
  }
2310
2549
  var styles = reactNative.StyleSheet.create({
2311
2550
  shell: {
2312
- borderRadius: 10,
2551
+ borderRadius: 8,
2313
2552
  borderWidth: reactNative.StyleSheet.hairlineWidth,
2314
- borderColor: "rgba(24, 24, 27, 0.14)",
2315
- backgroundColor: "rgba(255, 255, 255, 0.96)",
2553
+ borderColor: "rgba(0, 0, 0, 0.12)",
2554
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
2316
2555
  shadowColor: "#18181b",
2317
- shadowOpacity: 0.12,
2318
- shadowRadius: 16,
2319
- shadowOffset: { width: 0, height: 8 },
2320
- elevation: 6
2556
+ shadowOpacity: 0.08,
2557
+ shadowRadius: 3,
2558
+ shadowOffset: { width: 0, height: 1 },
2559
+ elevation: 3
2321
2560
  },
2322
2561
  content: {
2323
2562
  alignItems: "center",
2324
- gap: 6,
2325
- paddingHorizontal: 6,
2563
+ gap: 4,
2564
+ paddingHorizontal: 8,
2326
2565
  paddingVertical: 6
2327
2566
  },
2567
+ comfortableContent: {
2568
+ gap: 6
2569
+ },
2328
2570
  toolButton: {
2329
- minWidth: 58,
2330
- height: 52,
2571
+ minWidth: 40,
2572
+ height: 40,
2331
2573
  alignItems: "center",
2332
2574
  justifyContent: "center",
2333
- gap: 3,
2334
- borderRadius: 8,
2575
+ gap: 2,
2576
+ borderRadius: 6,
2335
2577
  paddingHorizontal: 8,
2336
2578
  borderWidth: reactNative.StyleSheet.hairlineWidth,
2337
2579
  borderColor: "transparent",
2338
2580
  backgroundColor: "transparent"
2339
2581
  },
2582
+ comfortableToolButton: {
2583
+ minWidth: 56,
2584
+ height: 48
2585
+ },
2340
2586
  activeToolButton: {
2341
- borderColor: "rgba(24, 24, 27, 0.24)",
2342
- backgroundColor: "#18181b"
2587
+ borderColor: "rgba(24, 24, 27, 0.28)",
2588
+ backgroundColor: "rgba(24, 24, 27, 0.1)"
2343
2589
  },
2344
2590
  pressedToolButton: {
2345
2591
  backgroundColor: "rgba(24, 24, 27, 0.08)"
@@ -2363,6 +2609,76 @@ var styles = reactNative.StyleSheet.create({
2363
2609
  fontSize: 10,
2364
2610
  fontWeight: "600",
2365
2611
  lineHeight: 12
2612
+ },
2613
+ toolLockDivider: {
2614
+ width: reactNative.StyleSheet.hairlineWidth,
2615
+ alignSelf: "stretch",
2616
+ backgroundColor: "rgba(24, 24, 27, 0.14)",
2617
+ marginHorizontal: 6,
2618
+ marginVertical: 4
2619
+ },
2620
+ lockGlyph: {
2621
+ color: "#18181b",
2622
+ fontSize: 13,
2623
+ fontWeight: "700",
2624
+ lineHeight: 16
2625
+ },
2626
+ overflowSpacer: {
2627
+ minWidth: 8,
2628
+ flexGrow: 1
2629
+ },
2630
+ overflowTrigger: {
2631
+ minWidth: 48,
2632
+ height: 40,
2633
+ flexDirection: "row",
2634
+ alignItems: "center",
2635
+ justifyContent: "center",
2636
+ gap: 4,
2637
+ borderRadius: 6,
2638
+ paddingHorizontal: 8,
2639
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
2640
+ borderColor: "transparent",
2641
+ backgroundColor: "transparent"
2642
+ },
2643
+ overflowPanel: {
2644
+ position: "absolute",
2645
+ right: 8,
2646
+ bottom: 52,
2647
+ width: 148,
2648
+ flexDirection: "row",
2649
+ flexWrap: "wrap",
2650
+ gap: 6,
2651
+ padding: 10,
2652
+ borderRadius: 10,
2653
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
2654
+ borderColor: "rgba(0, 0, 0, 0.1)",
2655
+ backgroundColor: "rgba(255, 255, 255, 0.98)",
2656
+ shadowColor: "#18181b",
2657
+ shadowOpacity: 0.12,
2658
+ shadowRadius: 24,
2659
+ shadowOffset: { width: 0, height: -4 },
2660
+ elevation: 8
2661
+ },
2662
+ overflowToolButton: {
2663
+ width: 40,
2664
+ minWidth: 40,
2665
+ height: 40,
2666
+ paddingHorizontal: 0,
2667
+ borderRadius: 8,
2668
+ backgroundColor: "rgba(24, 24, 27, 0.05)"
2669
+ },
2670
+ shapesGlyph: {
2671
+ color: "#18181b",
2672
+ fontSize: 17,
2673
+ fontWeight: "700",
2674
+ lineHeight: 20
2675
+ },
2676
+ chevronGlyph: {
2677
+ color: "#18181b",
2678
+ fontSize: 13,
2679
+ fontWeight: "700",
2680
+ lineHeight: 16,
2681
+ opacity: 0.75
2366
2682
  }
2367
2683
  });
2368
2684
 
@@ -2614,9 +2930,11 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2614
2930
  items,
2615
2931
  selectedIds = [],
2616
2932
  toolId = "hand",
2933
+ toolLocked = false,
2617
2934
  interactive = false,
2618
2935
  onSelectionChange,
2619
2936
  onItemsChange,
2937
+ onToolChangeRequest,
2620
2938
  onCameraChange,
2621
2939
  toolbar
2622
2940
  }, ref) {
@@ -2624,6 +2942,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2624
2942
  const cameraRef = react.useRef(null);
2625
2943
  const toolIdRef = react.useRef(toolId);
2626
2944
  toolIdRef.current = toolId;
2945
+ const toolLockedRef = react.useRef(toolLocked);
2946
+ toolLockedRef.current = toolLocked;
2947
+ const onToolChangeRequestRef = react.useRef(onToolChangeRequest);
2948
+ onToolChangeRequestRef.current = onToolChangeRequest;
2627
2949
  const onCameraChangeRef = react.useRef(onCameraChange);
2628
2950
  onCameraChangeRef.current = onCameraChange;
2629
2951
  const onItemsChangeRef = react.useRef(onItemsChange);
@@ -2639,8 +2961,23 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2639
2961
  null
2640
2962
  );
2641
2963
  const [eraserTrail, setEraserTrail] = react.useState([]);
2964
+ const [laserTrail, setLaserTrail] = react.useState([]);
2965
+ const laserClearTimerRef = react.useRef(null);
2966
+ react.useEffect(
2967
+ () => () => {
2968
+ if (laserClearTimerRef.current) {
2969
+ clearTimeout(laserClearTimerRef.current);
2970
+ }
2971
+ },
2972
+ []
2973
+ );
2642
2974
  const [eraserPreviewIds, setEraserPreviewIds] = react.useState([]);
2643
2975
  const eraserPreviewIdSetRef = react.useRef(/* @__PURE__ */ new Set());
2976
+ const requestSelectToolAfterUse = react.useCallback(() => {
2977
+ if (!toolLockedRef.current) {
2978
+ onToolChangeRequestRef.current?.("select");
2979
+ }
2980
+ }, []);
2644
2981
  if (!cameraRef.current) {
2645
2982
  cameraRef.current = new Camera2D({ minZoom: 0.05, maxZoom: 32 });
2646
2983
  }
@@ -2735,12 +3072,19 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2735
3072
  }
2736
3073
  return;
2737
3074
  }
2738
- if (tool === "draw" || tool === "marker") {
3075
+ if (tool === "draw" || tool === "marker" || tool === "laser") {
2739
3076
  dragStateRef.current = {
2740
3077
  kind: "draw",
2741
3078
  tool,
2742
3079
  points: [{ x: worldX, y: worldY }]
2743
3080
  };
3081
+ if (tool === "laser") {
3082
+ if (laserClearTimerRef.current) {
3083
+ clearTimeout(laserClearTimerRef.current);
3084
+ laserClearTimerRef.current = null;
3085
+ }
3086
+ setLaserTrail([{ x: worldX, y: worldY }]);
3087
+ }
2744
3088
  return;
2745
3089
  }
2746
3090
  if (tool === "eraser") {
@@ -2841,11 +3185,15 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2841
3185
  if (Math.hypot(dx, dy) > 0.5 / cam.zoom) {
2842
3186
  pts.push({ x: worldX, y: worldY });
2843
3187
  }
2844
- setPlacementPreview({
2845
- kind: "stroke",
2846
- tool: st.tool,
2847
- points: [...pts]
2848
- });
3188
+ if (st.tool === "laser") {
3189
+ setLaserTrail([...pts]);
3190
+ } else {
3191
+ setPlacementPreview({
3192
+ kind: "stroke",
3193
+ tool: st.tool,
3194
+ points: [...pts]
3195
+ });
3196
+ }
2849
3197
  return;
2850
3198
  }
2851
3199
  if (st.kind === "move") {
@@ -2913,6 +3261,17 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2913
3261
  if (st.kind === "draw") {
2914
3262
  dragStateRef.current = { kind: "idle" };
2915
3263
  setPlacementPreview(null);
3264
+ if (st.tool === "laser") {
3265
+ if (laserClearTimerRef.current) {
3266
+ clearTimeout(laserClearTimerRef.current);
3267
+ }
3268
+ laserClearTimerRef.current = setTimeout(() => {
3269
+ setLaserTrail([]);
3270
+ laserClearTimerRef.current = null;
3271
+ }, 650);
3272
+ requestSelectToolAfterUse();
3273
+ return;
3274
+ }
2916
3275
  if (st.points.length < 1) return;
2917
3276
  const change = onItemsChangeRef.current;
2918
3277
  if (!change) return;
@@ -2921,6 +3280,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2921
3280
  if (item) {
2922
3281
  change([...itemsRef.current, item]);
2923
3282
  }
3283
+ requestSelectToolAfterUse();
2924
3284
  return;
2925
3285
  }
2926
3286
  if (st.kind === "move") {
@@ -2958,6 +3318,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2958
3318
  setEraserPreviewIds([]);
2959
3319
  setEraserTrail([]);
2960
3320
  dragStateRef.current = { kind: "idle" };
3321
+ requestSelectToolAfterUse();
2961
3322
  return;
2962
3323
  }
2963
3324
  if (st.kind === "place") {
@@ -2995,21 +3356,25 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2995
3356
  if (st.tool === "rect") {
2996
3357
  change([...itemsRef.current, createRectangleItem(id, raw)]);
2997
3358
  onSelectionChangeRef.current?.([id]);
3359
+ requestSelectToolAfterUse();
2998
3360
  return;
2999
3361
  }
3000
3362
  if (st.tool === "ellipse") {
3001
3363
  change([...itemsRef.current, createEllipseItem(id, raw)]);
3002
3364
  onSelectionChangeRef.current?.([id]);
3365
+ requestSelectToolAfterUse();
3003
3366
  return;
3004
3367
  }
3005
3368
  if (st.tool === "architectural-cloud") {
3006
3369
  change([...itemsRef.current, createArchitecturalCloudItem(id, raw)]);
3007
3370
  onSelectionChangeRef.current?.([id]);
3371
+ requestSelectToolAfterUse();
3008
3372
  return;
3009
3373
  }
3010
3374
  const line = lineEndpointsToLocal(br, lineStart, lineEnd);
3011
3375
  change([...itemsRef.current, createLineItem(id, br, line, st.tool)]);
3012
3376
  onSelectionChangeRef.current?.([id]);
3377
+ requestSelectToolAfterUse();
3013
3378
  return;
3014
3379
  }
3015
3380
  if (st.kind === "tap") {
@@ -3035,6 +3400,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3035
3400
  );
3036
3401
  change([...itemsRef.current, item]);
3037
3402
  onSelectionChangeRef.current?.([id]);
3403
+ requestSelectToolAfterUse();
3038
3404
  }
3039
3405
  if (st.tool === "note") {
3040
3406
  const id = createShapeId();
@@ -3053,6 +3419,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3053
3419
  };
3054
3420
  change([...itemsRef.current, note]);
3055
3421
  onSelectionChangeRef.current?.([id]);
3422
+ requestSelectToolAfterUse();
3056
3423
  }
3057
3424
  return;
3058
3425
  }
@@ -3063,9 +3430,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3063
3430
  lastPanPoint.current = null;
3064
3431
  dragStateRef.current = { kind: "idle" };
3065
3432
  setPlacementPreview(null);
3433
+ setLaserTrail([]);
3066
3434
  }
3067
3435
  }),
3068
- [screenToWorld, requestRender, interactive]
3436
+ [screenToWorld, requestRender, requestSelectToolAfterUse, interactive]
3069
3437
  );
3070
3438
  react.useImperativeHandle(
3071
3439
  ref,
@@ -3113,6 +3481,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3113
3481
  selectedItems,
3114
3482
  showResizeHandles,
3115
3483
  placementPreview,
3484
+ laserTrail,
3116
3485
  eraserTrail,
3117
3486
  eraserPreviewItems: items.filter(
3118
3487
  (it) => eraserPreviewIds.includes(it.id)
@@ -3140,12 +3509,16 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3140
3509
  );
3141
3510
  });
3142
3511
 
3512
+ exports.DEFAULT_NATIVE_OVERFLOW_TOOL_IDS = DEFAULT_NATIVE_OVERFLOW_TOOL_IDS;
3143
3513
  exports.DEFAULT_NATIVE_VECTOR_TOOLS = DEFAULT_NATIVE_VECTOR_TOOLS;
3144
3514
  exports.NativeInteractionOverlay = NativeInteractionOverlay;
3145
3515
  exports.NativeSceneRenderer = NativeSceneRenderer;
3146
3516
  exports.NativeShapeRenderer = NativeShapeRenderer;
3147
3517
  exports.NativeVectorToolbar = NativeVectorToolbar;
3148
3518
  exports.NativeVectorViewport = NativeVectorViewport;
3519
+ exports.createFreehandStrokeItem = createFreehandStrokeItem;
3520
+ exports.createImageItem = createImageItem;
3521
+ exports.createShapeId = createShapeId;
3149
3522
  exports.parseSvgFragment = parseSvgFragment;
3150
3523
  //# sourceMappingURL=native.cjs.map
3151
3524
  //# sourceMappingURL=native.cjs.map