canvu-react 0.4.4 → 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.cjs +400 -5
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +79 -2
- package/dist/native.d.ts +79 -2
- package/dist/native.js +400 -7
- package/dist/native.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
2641
|
-
|
|
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
|