canvu-react 0.4.3 → 0.4.5

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.d.cts CHANGED
@@ -2,9 +2,11 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { C as Camera2D } from './camera-CVVG7z56.cjs';
3
3
  import { V as VectorSceneItem, R as Rect } from './types-BCCvY6ie.cjs';
4
4
  import * as react from 'react';
5
+ import { ReactNode } from 'react';
6
+ import { StyleProp, ViewStyle, TextStyle } from 'react-native';
5
7
 
6
8
  type PlacementPreview = {
7
- readonly kind: "rect" | "ellipse";
9
+ readonly kind: "rect" | "ellipse" | "architectural-cloud";
8
10
  readonly rect: {
9
11
  readonly x: number;
10
12
  readonly y: number;
@@ -69,6 +71,81 @@ type NativeShapeRendererProps = {
69
71
  };
70
72
  declare function NativeShapeRenderer({ item }: NativeShapeRendererProps): react_jsx_runtime.JSX.Element | null;
71
73
 
74
+ /**
75
+ * Describes a single tool button rendered by {@link NativeVectorToolbar}.
76
+ *
77
+ * Use the built-in ids from {@link DEFAULT_NATIVE_VECTOR_TOOLS} when pairing
78
+ * the toolbar with {@link NativeVectorViewport}. Custom ids are useful only
79
+ * when the app also implements matching viewport behavior.
80
+ */
81
+ type NativeVectorToolDefinition = {
82
+ readonly id: string;
83
+ readonly label: string;
84
+ readonly shortLabel?: string;
85
+ readonly accessibilityLabel?: string;
86
+ readonly disabled?: boolean;
87
+ };
88
+ /**
89
+ * Render input for custom tool icons or complete custom tool buttons.
90
+ *
91
+ * Prefer `renderToolIcon` for app-specific icon packs. Use
92
+ * `renderToolButton` only when you need to replace the full touch target while
93
+ * keeping the toolbar's horizontal scrolling and tool list behavior.
94
+ */
95
+ type NativeVectorToolbarRenderToolInput = {
96
+ readonly tool: NativeVectorToolDefinition;
97
+ readonly selected: boolean;
98
+ readonly disabled: boolean;
99
+ readonly foregroundColor: string;
100
+ readonly onSelect: () => void;
101
+ };
102
+ /**
103
+ * Props for {@link NativeVectorToolbar}.
104
+ *
105
+ * The component is controlled: pass the same `value` to `NativeVectorViewport`
106
+ * as `toolId`, and update it from `onChange`. It is intentionally lightweight
107
+ * and native-only, with no dependency on the web toolbar implementation.
108
+ */
109
+ type NativeVectorToolbarProps = {
110
+ readonly value: string;
111
+ readonly onChange: (toolId: string) => void;
112
+ readonly tools?: readonly NativeVectorToolDefinition[];
113
+ readonly disabled?: boolean;
114
+ readonly disabledToolIds?: readonly string[];
115
+ readonly accessibilityLabel?: string;
116
+ readonly style?: StyleProp<ViewStyle>;
117
+ readonly contentContainerStyle?: StyleProp<ViewStyle>;
118
+ readonly toolButtonStyle?: StyleProp<ViewStyle>;
119
+ readonly activeToolButtonStyle?: StyleProp<ViewStyle>;
120
+ readonly toolLabelStyle?: StyleProp<TextStyle>;
121
+ readonly activeToolLabelStyle?: StyleProp<TextStyle>;
122
+ readonly renderToolIcon?: (input: NativeVectorToolbarRenderToolInput) => ReactNode;
123
+ readonly renderToolButton?: (input: NativeVectorToolbarRenderToolInput) => ReactNode;
124
+ };
125
+ /**
126
+ * Default mobile-friendly tool list for {@link NativeVectorToolbar}.
127
+ *
128
+ * The ids match the native viewport's built-in tool handling. Apps can pass a
129
+ * filtered or reordered list when permissions or product scope require it.
130
+ */
131
+ declare const DEFAULT_NATIVE_VECTOR_TOOLS: readonly NativeVectorToolDefinition[];
132
+ /**
133
+ * Horizontal, touch-sized toolbar for `canvu/native`.
134
+ *
135
+ * Use it with {@link NativeVectorViewport} by controlling the same `toolId`
136
+ * state. It intentionally uses React Native primitives only, so it works in
137
+ * Expo and bare React Native apps without a WebView.
138
+ *
139
+ * @example
140
+ * ```tsx
141
+ * const [toolId, setToolId] = useState("select");
142
+ *
143
+ * <NativeVectorViewport toolId={toolId} ... />
144
+ * <NativeVectorToolbar value={toolId} onChange={setToolId} />
145
+ * ```
146
+ */
147
+ declare function NativeVectorToolbar({ value, onChange, tools, disabled, disabledToolIds, accessibilityLabel, style, contentContainerStyle, toolButtonStyle, activeToolButtonStyle, toolLabelStyle, activeToolLabelStyle, renderToolIcon, renderToolButton, }: NativeVectorToolbarProps): react_jsx_runtime.JSX.Element;
148
+
72
149
  type NativeVectorViewportHandle = {
73
150
  getCamera: () => Camera2D | null;
74
151
  requestRender: () => void;
@@ -214,4 +291,4 @@ type SvgNode = SvgRectNode | SvgEllipseNode | SvgCircleNode | SvgLineNode | SvgP
214
291
  */
215
292
  declare function parseSvgFragment(xml: string): SvgNode[];
216
293
 
217
- export { NativeInteractionOverlay, type NativeInteractionOverlayProps, NativeSceneRenderer, type NativeSceneRendererProps, NativeShapeRenderer, type NativeShapeRendererProps, NativeVectorViewport, type NativeVectorViewportHandle, type NativeVectorViewportProps, type PlacementPreview, type SvgNode, parseSvgFragment };
294
+ export { DEFAULT_NATIVE_VECTOR_TOOLS, NativeInteractionOverlay, type NativeInteractionOverlayProps, NativeSceneRenderer, type NativeSceneRendererProps, NativeShapeRenderer, type NativeShapeRendererProps, type NativeVectorToolDefinition, NativeVectorToolbar, type NativeVectorToolbarProps, type NativeVectorToolbarRenderToolInput, NativeVectorViewport, type NativeVectorViewportHandle, type NativeVectorViewportProps, type PlacementPreview, type SvgNode, parseSvgFragment };
package/dist/native.d.ts CHANGED
@@ -2,9 +2,11 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { C as Camera2D } from './camera-CoRYN_IV.js';
3
3
  import { V as VectorSceneItem, R as Rect } from './types-BCCvY6ie.js';
4
4
  import * as react from 'react';
5
+ import { ReactNode } from 'react';
6
+ import { StyleProp, ViewStyle, TextStyle } from 'react-native';
5
7
 
6
8
  type PlacementPreview = {
7
- readonly kind: "rect" | "ellipse";
9
+ readonly kind: "rect" | "ellipse" | "architectural-cloud";
8
10
  readonly rect: {
9
11
  readonly x: number;
10
12
  readonly y: number;
@@ -69,6 +71,81 @@ type NativeShapeRendererProps = {
69
71
  };
70
72
  declare function NativeShapeRenderer({ item }: NativeShapeRendererProps): react_jsx_runtime.JSX.Element | null;
71
73
 
74
+ /**
75
+ * Describes a single tool button rendered by {@link NativeVectorToolbar}.
76
+ *
77
+ * Use the built-in ids from {@link DEFAULT_NATIVE_VECTOR_TOOLS} when pairing
78
+ * the toolbar with {@link NativeVectorViewport}. Custom ids are useful only
79
+ * when the app also implements matching viewport behavior.
80
+ */
81
+ type NativeVectorToolDefinition = {
82
+ readonly id: string;
83
+ readonly label: string;
84
+ readonly shortLabel?: string;
85
+ readonly accessibilityLabel?: string;
86
+ readonly disabled?: boolean;
87
+ };
88
+ /**
89
+ * Render input for custom tool icons or complete custom tool buttons.
90
+ *
91
+ * Prefer `renderToolIcon` for app-specific icon packs. Use
92
+ * `renderToolButton` only when you need to replace the full touch target while
93
+ * keeping the toolbar's horizontal scrolling and tool list behavior.
94
+ */
95
+ type NativeVectorToolbarRenderToolInput = {
96
+ readonly tool: NativeVectorToolDefinition;
97
+ readonly selected: boolean;
98
+ readonly disabled: boolean;
99
+ readonly foregroundColor: string;
100
+ readonly onSelect: () => void;
101
+ };
102
+ /**
103
+ * Props for {@link NativeVectorToolbar}.
104
+ *
105
+ * The component is controlled: pass the same `value` to `NativeVectorViewport`
106
+ * as `toolId`, and update it from `onChange`. It is intentionally lightweight
107
+ * and native-only, with no dependency on the web toolbar implementation.
108
+ */
109
+ type NativeVectorToolbarProps = {
110
+ readonly value: string;
111
+ readonly onChange: (toolId: string) => void;
112
+ readonly tools?: readonly NativeVectorToolDefinition[];
113
+ readonly disabled?: boolean;
114
+ readonly disabledToolIds?: readonly string[];
115
+ readonly accessibilityLabel?: string;
116
+ readonly style?: StyleProp<ViewStyle>;
117
+ readonly contentContainerStyle?: StyleProp<ViewStyle>;
118
+ readonly toolButtonStyle?: StyleProp<ViewStyle>;
119
+ readonly activeToolButtonStyle?: StyleProp<ViewStyle>;
120
+ readonly toolLabelStyle?: StyleProp<TextStyle>;
121
+ readonly activeToolLabelStyle?: StyleProp<TextStyle>;
122
+ readonly renderToolIcon?: (input: NativeVectorToolbarRenderToolInput) => ReactNode;
123
+ readonly renderToolButton?: (input: NativeVectorToolbarRenderToolInput) => ReactNode;
124
+ };
125
+ /**
126
+ * Default mobile-friendly tool list for {@link NativeVectorToolbar}.
127
+ *
128
+ * The ids match the native viewport's built-in tool handling. Apps can pass a
129
+ * filtered or reordered list when permissions or product scope require it.
130
+ */
131
+ declare const DEFAULT_NATIVE_VECTOR_TOOLS: readonly NativeVectorToolDefinition[];
132
+ /**
133
+ * Horizontal, touch-sized toolbar for `canvu/native`.
134
+ *
135
+ * Use it with {@link NativeVectorViewport} by controlling the same `toolId`
136
+ * state. It intentionally uses React Native primitives only, so it works in
137
+ * Expo and bare React Native apps without a WebView.
138
+ *
139
+ * @example
140
+ * ```tsx
141
+ * const [toolId, setToolId] = useState("select");
142
+ *
143
+ * <NativeVectorViewport toolId={toolId} ... />
144
+ * <NativeVectorToolbar value={toolId} onChange={setToolId} />
145
+ * ```
146
+ */
147
+ declare function NativeVectorToolbar({ value, onChange, tools, disabled, disabledToolIds, accessibilityLabel, style, contentContainerStyle, toolButtonStyle, activeToolButtonStyle, toolLabelStyle, activeToolLabelStyle, renderToolIcon, renderToolButton, }: NativeVectorToolbarProps): react_jsx_runtime.JSX.Element;
148
+
72
149
  type NativeVectorViewportHandle = {
73
150
  getCamera: () => Camera2D | null;
74
151
  requestRender: () => void;
@@ -214,4 +291,4 @@ type SvgNode = SvgRectNode | SvgEllipseNode | SvgCircleNode | SvgLineNode | SvgP
214
291
  */
215
292
  declare function parseSvgFragment(xml: string): SvgNode[];
216
293
 
217
- export { NativeInteractionOverlay, type NativeInteractionOverlayProps, NativeSceneRenderer, type NativeSceneRendererProps, NativeShapeRenderer, type NativeShapeRendererProps, NativeVectorViewport, type NativeVectorViewportHandle, type NativeVectorViewportProps, type PlacementPreview, type SvgNode, parseSvgFragment };
294
+ export { DEFAULT_NATIVE_VECTOR_TOOLS, NativeInteractionOverlay, type NativeInteractionOverlayProps, NativeSceneRenderer, type NativeSceneRendererProps, NativeShapeRenderer, type NativeShapeRendererProps, type NativeVectorToolDefinition, NativeVectorToolbar, type NativeVectorToolbarProps, type NativeVectorToolbarRenderToolInput, NativeVectorViewport, type NativeVectorViewportHandle, type NativeVectorViewportProps, type PlacementPreview, type SvgNode, parseSvgFragment };
package/dist/native.js CHANGED
@@ -2,7 +2,7 @@ import { Group, RoundedRect, Circle, Line, vec, Path, matchFont, Text, Canvas, R
2
2
  import { memo, forwardRef, useState, useRef, useCallback, useMemo, useImperativeHandle } from 'react';
3
3
  import getStroke from 'perfect-freehand';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { PanResponder, View } from 'react-native';
5
+ import { StyleSheet, PanResponder, View, ScrollView, Text as Text$1, Pressable } from 'react-native';
6
6
 
7
7
  // src/native/NativeInteractionOverlay.tsx
8
8
 
@@ -121,6 +121,13 @@ function getRotationHandleWorldPosition(bounds, rotationRad, handleOffsetWorld)
121
121
  rotationRad
122
122
  );
123
123
  }
124
+ function rectFromCorners(a, b) {
125
+ const minX = Math.min(a.x, b.x);
126
+ const maxX = Math.max(a.x, b.x);
127
+ const minY = Math.min(a.y, b.y);
128
+ const maxY = Math.max(a.y, b.y);
129
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
130
+ }
124
131
 
125
132
  // src/scene/freehand-path.ts
126
133
  function smoothFreehandPointsToPathD(points) {
@@ -780,6 +787,15 @@ function createShapeId() {
780
787
  const uid = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : String(Date.now());
781
788
  return `user-shape-${uid}`;
782
789
  }
790
+ function lineEndpointsToLocal(bounds, worldEndA, worldEndB) {
791
+ const b = normalizeRect(bounds);
792
+ return {
793
+ x1: worldEndA.x - b.x,
794
+ y1: worldEndA.y - b.y,
795
+ x2: worldEndB.x - b.x,
796
+ y2: worldEndB.y - b.y
797
+ };
798
+ }
783
799
  function rebuildItemSvg(item) {
784
800
  const style = resolveStrokeStyle(item);
785
801
  const k = item.toolKind;
@@ -883,6 +899,85 @@ function rebuildItemSvg(item) {
883
899
  }
884
900
  return item;
885
901
  }
902
+ function createRectangleItem(id, bounds, style) {
903
+ const r = normalizeRect(bounds);
904
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
905
+ return rebuildItemSvg({
906
+ id,
907
+ x: r.x,
908
+ y: r.y,
909
+ bounds: { ...r },
910
+ toolKind: "rect",
911
+ stroke: s.stroke,
912
+ strokeWidth: s.strokeWidth,
913
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
914
+ childrenSvg: ""
915
+ });
916
+ }
917
+ function createEllipseItem(id, bounds, style) {
918
+ const r = normalizeRect(bounds);
919
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
920
+ return rebuildItemSvg({
921
+ id,
922
+ x: r.x,
923
+ y: r.y,
924
+ bounds: { ...r },
925
+ toolKind: "ellipse",
926
+ stroke: s.stroke,
927
+ strokeWidth: s.strokeWidth,
928
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
929
+ childrenSvg: ""
930
+ });
931
+ }
932
+ function createArchitecturalCloudItem(id, bounds, style) {
933
+ const r = normalizeRect(bounds);
934
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
935
+ return rebuildItemSvg({
936
+ id,
937
+ x: r.x,
938
+ y: r.y,
939
+ bounds: { ...r },
940
+ toolKind: "architectural-cloud",
941
+ stroke: s.stroke,
942
+ strokeWidth: s.strokeWidth,
943
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
944
+ childrenSvg: ""
945
+ });
946
+ }
947
+ function createLineItem(id, bounds, line, toolKind, style, arrowBind) {
948
+ const r = normalizeRect(bounds);
949
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
950
+ return rebuildItemSvg({
951
+ id,
952
+ x: r.x,
953
+ y: r.y,
954
+ bounds: { ...r },
955
+ toolKind,
956
+ line: { ...line },
957
+ stroke: s.stroke,
958
+ strokeWidth: s.strokeWidth,
959
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
960
+ ...{},
961
+ childrenSvg: ""
962
+ });
963
+ }
964
+ function createTextItem(id, bounds, text = "", style, textFontSize) {
965
+ const r = normalizeRect(bounds);
966
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
967
+ return rebuildItemSvg({
968
+ id,
969
+ x: r.x,
970
+ y: r.y,
971
+ bounds: { ...r },
972
+ toolKind: "text",
973
+ text,
974
+ stroke: s.stroke,
975
+ strokeWidth: s.strokeWidth,
976
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
977
+ ...{ textFontSize } ,
978
+ childrenSvg: ""
979
+ });
980
+ }
886
981
  function createFreehandStrokeItem(id, pointsWorld, toolKind, style) {
887
982
  if (pointsWorld.length === 0) return null;
888
983
  const merged = {
@@ -1765,9 +1860,9 @@ function NativeInteractionOverlay({
1765
1860
  const previewElements = useMemo(() => {
1766
1861
  if (!placementPreview) return null;
1767
1862
  const p = placementPreview;
1768
- if (p.kind === "rect" || p.kind === "ellipse") {
1863
+ if (p.kind === "rect" || p.kind === "ellipse" || p.kind === "architectural-cloud") {
1769
1864
  const r = normalizeRect(p.rect);
1770
- return p.kind === "rect" ? /* @__PURE__ */ jsx(
1865
+ return p.kind === "rect" || p.kind === "architectural-cloud" ? /* @__PURE__ */ jsx(
1771
1866
  Rect,
1772
1867
  {
1773
1868
  x: r.x,
@@ -2099,6 +2194,171 @@ function NativeSceneRenderer({
2099
2194
  if (width <= 0 || height <= 0) return null;
2100
2195
  return /* @__PURE__ */ jsx(Canvas, { style: { width, height }, children: /* @__PURE__ */ jsx(Group, { transform: cameraTransform, children: visible.map((item) => /* @__PURE__ */ jsx(MemoShape, { item }, item.id)) }) });
2101
2196
  }
2197
+ var DEFAULT_NATIVE_VECTOR_TOOLS = [
2198
+ { id: "hand", label: "Hand", shortLabel: "H" },
2199
+ { id: "select", label: "Select", shortLabel: "V" },
2200
+ { id: "draw", label: "Draw", shortLabel: "D" },
2201
+ { id: "marker", label: "Marker", shortLabel: "M" },
2202
+ { id: "eraser", label: "Eraser", shortLabel: "E" },
2203
+ { id: "text", label: "Text", shortLabel: "T" },
2204
+ { id: "note", label: "Note", shortLabel: "N" },
2205
+ { id: "rect", label: "Rectangle", shortLabel: "R" },
2206
+ { id: "ellipse", label: "Ellipse", shortLabel: "O" },
2207
+ { id: "architectural-cloud", label: "Cloud", shortLabel: "C" },
2208
+ { id: "line", label: "Line", shortLabel: "L" },
2209
+ { id: "arrow", label: "Arrow", shortLabel: "A" }
2210
+ ];
2211
+ function NativeVectorToolbar({
2212
+ value,
2213
+ onChange,
2214
+ tools = DEFAULT_NATIVE_VECTOR_TOOLS,
2215
+ disabled = false,
2216
+ disabledToolIds = [],
2217
+ accessibilityLabel = "Canvas tools",
2218
+ style,
2219
+ contentContainerStyle,
2220
+ toolButtonStyle,
2221
+ activeToolButtonStyle,
2222
+ toolLabelStyle,
2223
+ activeToolLabelStyle,
2224
+ renderToolIcon,
2225
+ renderToolButton
2226
+ }) {
2227
+ const disabledIds = useMemo(() => new Set(disabledToolIds), [disabledToolIds]);
2228
+ return /* @__PURE__ */ jsx(View, { accessibilityLabel, style: [styles.shell, style], children: /* @__PURE__ */ jsx(
2229
+ ScrollView,
2230
+ {
2231
+ horizontal: true,
2232
+ showsHorizontalScrollIndicator: false,
2233
+ contentContainerStyle: [styles.content, contentContainerStyle],
2234
+ children: tools.map((tool) => {
2235
+ const selected = tool.id === value;
2236
+ const toolDisabled = disabled || tool.disabled || disabledIds.has(tool.id);
2237
+ const foregroundColor = selected ? "#fafaf9" : "#18181b";
2238
+ const onSelect = () => {
2239
+ if (!toolDisabled) {
2240
+ onChange(tool.id);
2241
+ }
2242
+ };
2243
+ const input = {
2244
+ tool,
2245
+ selected,
2246
+ disabled: toolDisabled,
2247
+ foregroundColor,
2248
+ onSelect
2249
+ };
2250
+ if (renderToolButton) {
2251
+ return /* @__PURE__ */ jsx(View, { children: renderToolButton(input) }, tool.id);
2252
+ }
2253
+ const icon = renderToolIcon?.(input) ?? /* @__PURE__ */ jsx(
2254
+ Text$1,
2255
+ {
2256
+ style: [
2257
+ styles.shortLabel,
2258
+ { color: foregroundColor },
2259
+ toolLabelStyle,
2260
+ selected ? activeToolLabelStyle : void 0
2261
+ ],
2262
+ children: tool.shortLabel ?? tool.label.slice(0, 1).toUpperCase()
2263
+ }
2264
+ );
2265
+ return /* @__PURE__ */ jsxs(
2266
+ Pressable,
2267
+ {
2268
+ accessibilityLabel: tool.accessibilityLabel ?? tool.label,
2269
+ accessibilityRole: "button",
2270
+ accessibilityState: { selected, disabled: toolDisabled },
2271
+ disabled: toolDisabled,
2272
+ onPress: onSelect,
2273
+ style: ({ pressed }) => [
2274
+ styles.toolButton,
2275
+ toolButtonStyle,
2276
+ selected ? styles.activeToolButton : void 0,
2277
+ selected ? activeToolButtonStyle : void 0,
2278
+ pressed && !toolDisabled ? styles.pressedToolButton : void 0,
2279
+ toolDisabled ? styles.disabledToolButton : void 0
2280
+ ],
2281
+ children: [
2282
+ /* @__PURE__ */ jsx(View, { style: styles.iconSlot, children: icon }),
2283
+ /* @__PURE__ */ jsx(
2284
+ Text$1,
2285
+ {
2286
+ numberOfLines: 1,
2287
+ style: [
2288
+ styles.toolLabel,
2289
+ { color: foregroundColor },
2290
+ toolLabelStyle,
2291
+ selected ? activeToolLabelStyle : void 0
2292
+ ],
2293
+ children: tool.label
2294
+ }
2295
+ )
2296
+ ]
2297
+ },
2298
+ tool.id
2299
+ );
2300
+ })
2301
+ }
2302
+ ) });
2303
+ }
2304
+ var styles = StyleSheet.create({
2305
+ shell: {
2306
+ borderRadius: 10,
2307
+ borderWidth: StyleSheet.hairlineWidth,
2308
+ borderColor: "rgba(24, 24, 27, 0.14)",
2309
+ backgroundColor: "rgba(255, 255, 255, 0.96)",
2310
+ shadowColor: "#18181b",
2311
+ shadowOpacity: 0.12,
2312
+ shadowRadius: 16,
2313
+ shadowOffset: { width: 0, height: 8 },
2314
+ elevation: 6
2315
+ },
2316
+ content: {
2317
+ alignItems: "center",
2318
+ gap: 6,
2319
+ paddingHorizontal: 6,
2320
+ paddingVertical: 6
2321
+ },
2322
+ toolButton: {
2323
+ minWidth: 58,
2324
+ height: 52,
2325
+ alignItems: "center",
2326
+ justifyContent: "center",
2327
+ gap: 3,
2328
+ borderRadius: 8,
2329
+ paddingHorizontal: 8,
2330
+ borderWidth: StyleSheet.hairlineWidth,
2331
+ borderColor: "transparent",
2332
+ backgroundColor: "transparent"
2333
+ },
2334
+ activeToolButton: {
2335
+ borderColor: "rgba(24, 24, 27, 0.24)",
2336
+ backgroundColor: "#18181b"
2337
+ },
2338
+ pressedToolButton: {
2339
+ backgroundColor: "rgba(24, 24, 27, 0.08)"
2340
+ },
2341
+ disabledToolButton: {
2342
+ opacity: 0.42
2343
+ },
2344
+ iconSlot: {
2345
+ height: 22,
2346
+ minWidth: 22,
2347
+ alignItems: "center",
2348
+ justifyContent: "center"
2349
+ },
2350
+ shortLabel: {
2351
+ fontSize: 16,
2352
+ fontWeight: "700",
2353
+ lineHeight: 20
2354
+ },
2355
+ toolLabel: {
2356
+ maxWidth: 68,
2357
+ fontSize: 10,
2358
+ fontWeight: "600",
2359
+ lineHeight: 12
2360
+ }
2361
+ });
2102
2362
 
2103
2363
  // src/camera/camera.ts
2104
2364
  var Camera2D = class {
@@ -2286,6 +2546,37 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
2286
2546
  }
2287
2547
  return ids;
2288
2548
  }
2549
+ var MIN_PLACE_SIZE = 8;
2550
+ var MIN_ARROW_DRAG_PX = 8;
2551
+ var TAP_PX = 20;
2552
+ function isPlacementTool(toolId) {
2553
+ return toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud" || toolId === "line" || toolId === "arrow";
2554
+ }
2555
+ function placementPreviewForTool(toolId, start, end) {
2556
+ if (toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud") {
2557
+ return { kind: toolId, rect: rectFromCorners(start, end) };
2558
+ }
2559
+ return { kind: toolId, start, end };
2560
+ }
2561
+ function defaultPlacementWorld(toolId, center) {
2562
+ const cx = center.x;
2563
+ const cy = center.y;
2564
+ if (toolId === "rect") {
2565
+ return { raw: { x: cx - 60, y: cy - 40, width: 120, height: 80 } };
2566
+ }
2567
+ if (toolId === "ellipse") {
2568
+ return { raw: { x: cx - 70, y: cy - 45, width: 140, height: 90 } };
2569
+ }
2570
+ if (toolId === "architectural-cloud") {
2571
+ return { raw: { x: cx - 90, y: cy - 50, width: 180, height: 100 } };
2572
+ }
2573
+ const start = { x: cx - 50, y: cy };
2574
+ const end = { x: cx + 50, y: cy };
2575
+ return {
2576
+ raw: rectFromCorners(start, end),
2577
+ lineWorld: [start, end]
2578
+ };
2579
+ }
2289
2580
  function collectIdsInRect(items, marquee) {
2290
2581
  const m = normalizeRect(marquee);
2291
2582
  const out = [];
@@ -2463,10 +2754,31 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2463
2754
  setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
2464
2755
  return;
2465
2756
  }
2757
+ if (isPlacementTool(tool)) {
2758
+ dragStateRef.current = {
2759
+ kind: "place",
2760
+ tool,
2761
+ startWorld: { x: worldX, y: worldY },
2762
+ startScreen: { x: sx, y: sy }
2763
+ };
2764
+ setPlacementPreview(
2765
+ placementPreviewForTool(
2766
+ tool,
2767
+ { x: worldX, y: worldY },
2768
+ {
2769
+ x: worldX,
2770
+ y: worldY
2771
+ }
2772
+ )
2773
+ );
2774
+ return;
2775
+ }
2466
2776
  if (tool === "note" || tool === "text") {
2467
2777
  dragStateRef.current = {
2468
2778
  kind: "tap",
2469
- startWorld: { x: worldX, y: worldY }
2779
+ tool,
2780
+ startWorld: { x: worldX, y: worldY },
2781
+ startScreen: { x: sx, y: sy }
2470
2782
  };
2471
2783
  return;
2472
2784
  }
@@ -2578,6 +2890,15 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2578
2890
  setEraserPreviewIds(Array.from(eraserPreviewIdSetRef.current));
2579
2891
  return;
2580
2892
  }
2893
+ if (st.kind === "place") {
2894
+ setPlacementPreview(
2895
+ placementPreviewForTool(st.tool, st.startWorld, {
2896
+ x: worldX,
2897
+ y: worldY
2898
+ })
2899
+ );
2900
+ return;
2901
+ }
2581
2902
  },
2582
2903
  onPanResponderRelease: (evt) => {
2583
2904
  lastPinchDist.current = null;
@@ -2633,12 +2954,83 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2633
2954
  dragStateRef.current = { kind: "idle" };
2634
2955
  return;
2635
2956
  }
2957
+ if (st.kind === "place") {
2958
+ dragStateRef.current = { kind: "idle" };
2959
+ setPlacementPreview(null);
2960
+ const change = onItemsChangeRef.current;
2961
+ if (!change) return;
2962
+ const { worldX, worldY } = screenToWorld(
2963
+ evt.nativeEvent.locationX,
2964
+ evt.nativeEvent.locationY
2965
+ );
2966
+ const a = st.startWorld;
2967
+ const b = { x: worldX, y: worldY };
2968
+ const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
2969
+ const screenDy = evt.nativeEvent.locationY - st.startScreen.y;
2970
+ if (st.tool === "arrow" && Math.hypot(screenDx, screenDy) < MIN_ARROW_DRAG_PX) {
2971
+ return;
2972
+ }
2973
+ let raw = rectFromCorners(a, b);
2974
+ let br = normalizeRect(raw);
2975
+ let lineStart = a;
2976
+ let lineEnd = b;
2977
+ if (br.width < MIN_PLACE_SIZE || br.height < MIN_PLACE_SIZE) {
2978
+ const center = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
2979
+ const defaults = defaultPlacementWorld(st.tool, center);
2980
+ raw = defaults.raw;
2981
+ br = normalizeRect(raw);
2982
+ if (defaults.lineWorld) {
2983
+ const [defaultStart, defaultEnd] = defaults.lineWorld;
2984
+ lineStart = defaultStart;
2985
+ lineEnd = defaultEnd;
2986
+ }
2987
+ }
2988
+ const id = createShapeId();
2989
+ if (st.tool === "rect") {
2990
+ change([...itemsRef.current, createRectangleItem(id, raw)]);
2991
+ onSelectionChangeRef.current?.([id]);
2992
+ return;
2993
+ }
2994
+ if (st.tool === "ellipse") {
2995
+ change([...itemsRef.current, createEllipseItem(id, raw)]);
2996
+ onSelectionChangeRef.current?.([id]);
2997
+ return;
2998
+ }
2999
+ if (st.tool === "architectural-cloud") {
3000
+ change([...itemsRef.current, createArchitecturalCloudItem(id, raw)]);
3001
+ onSelectionChangeRef.current?.([id]);
3002
+ return;
3003
+ }
3004
+ const line = lineEndpointsToLocal(br, lineStart, lineEnd);
3005
+ change([...itemsRef.current, createLineItem(id, br, line, st.tool)]);
3006
+ onSelectionChangeRef.current?.([id]);
3007
+ return;
3008
+ }
2636
3009
  if (st.kind === "tap") {
2637
3010
  dragStateRef.current = { kind: "idle" };
3011
+ const screenDx = evt.nativeEvent.locationX - st.startScreen.x;
3012
+ const screenDy = evt.nativeEvent.locationY - st.startScreen.y;
3013
+ if (Math.hypot(screenDx, screenDy) > TAP_PX) return;
2638
3014
  const change = onItemsChangeRef.current;
2639
3015
  if (!change) return;
2640
- const tool = toolIdRef.current;
2641
- if (tool === "note") {
3016
+ if (st.tool === "text") {
3017
+ const id = createShapeId();
3018
+ const item = createTextItem(
3019
+ id,
3020
+ {
3021
+ x: st.startWorld.x - 4,
3022
+ y: st.startWorld.y - 18,
3023
+ width: 160,
3024
+ height: 26
3025
+ },
3026
+ "Text",
3027
+ void 0,
3028
+ 18
3029
+ );
3030
+ change([...itemsRef.current, item]);
3031
+ onSelectionChangeRef.current?.([id]);
3032
+ }
3033
+ if (st.tool === "note") {
2642
3034
  const id = createShapeId();
2643
3035
  const note = {
2644
3036
  id,
@@ -2654,6 +3046,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2654
3046
  toolKind: "custom"
2655
3047
  };
2656
3048
  change([...itemsRef.current, note]);
3049
+ onSelectionChangeRef.current?.([id]);
2657
3050
  }
2658
3051
  return;
2659
3052
  }
@@ -2741,6 +3134,6 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2741
3134
  );
2742
3135
  });
2743
3136
 
2744
- export { NativeInteractionOverlay, NativeSceneRenderer, NativeShapeRenderer, NativeVectorViewport, parseSvgFragment };
3137
+ export { DEFAULT_NATIVE_VECTOR_TOOLS, NativeInteractionOverlay, NativeSceneRenderer, NativeShapeRenderer, NativeVectorToolbar, NativeVectorViewport, parseSvgFragment };
2745
3138
  //# sourceMappingURL=native.js.map
2746
3139
  //# sourceMappingURL=native.js.map