canvu-react 0.4.15 → 0.4.17

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
@@ -12,6 +12,16 @@ type TimedTrailPoint = {
12
12
  readonly t: number;
13
13
  };
14
14
 
15
+ type NativeVectorToolCursorKind = "crosshair" | "draw" | "marker" | "eraser" | "text";
16
+ type NativeVectorToolCursor = {
17
+ readonly kind: NativeVectorToolCursorKind;
18
+ readonly size: number;
19
+ readonly hotspot: {
20
+ readonly x: number;
21
+ readonly y: number;
22
+ };
23
+ };
24
+
15
25
  type PlacementPreview = {
16
26
  readonly kind: "rect" | "ellipse" | "architectural-cloud";
17
27
  readonly rect: {
@@ -58,8 +68,15 @@ type NativeInteractionOverlayProps = {
58
68
  readonly laserTrail?: readonly TimedTrailPoint[];
59
69
  readonly eraserPreviewItems?: readonly VectorSceneItem[];
60
70
  readonly previewStrokeStyle?: StrokeStyle;
71
+ readonly toolCursor?: {
72
+ readonly cursor: NativeVectorToolCursor;
73
+ readonly point: {
74
+ readonly x: number;
75
+ readonly y: number;
76
+ };
77
+ } | null;
61
78
  };
62
- declare function NativeInteractionOverlay({ camera, width, height, selectedItems, showResizeHandles, placementPreview, eraserTrail, laserTrail, eraserPreviewItems, previewStrokeStyle, }: NativeInteractionOverlayProps): react_jsx_runtime.JSX.Element | null;
79
+ declare function NativeInteractionOverlay({ camera, width, height, selectedItems, showResizeHandles, placementPreview, eraserTrail, laserTrail, eraserPreviewItems, previewStrokeStyle, toolCursor, }: NativeInteractionOverlayProps): react_jsx_runtime.JSX.Element | null;
63
80
 
64
81
  type NativeSceneRendererProps = {
65
82
  readonly items: readonly VectorSceneItem[];
package/dist/native.d.ts CHANGED
@@ -12,6 +12,16 @@ type TimedTrailPoint = {
12
12
  readonly t: number;
13
13
  };
14
14
 
15
+ type NativeVectorToolCursorKind = "crosshair" | "draw" | "marker" | "eraser" | "text";
16
+ type NativeVectorToolCursor = {
17
+ readonly kind: NativeVectorToolCursorKind;
18
+ readonly size: number;
19
+ readonly hotspot: {
20
+ readonly x: number;
21
+ readonly y: number;
22
+ };
23
+ };
24
+
15
25
  type PlacementPreview = {
16
26
  readonly kind: "rect" | "ellipse" | "architectural-cloud";
17
27
  readonly rect: {
@@ -58,8 +68,15 @@ type NativeInteractionOverlayProps = {
58
68
  readonly laserTrail?: readonly TimedTrailPoint[];
59
69
  readonly eraserPreviewItems?: readonly VectorSceneItem[];
60
70
  readonly previewStrokeStyle?: StrokeStyle;
71
+ readonly toolCursor?: {
72
+ readonly cursor: NativeVectorToolCursor;
73
+ readonly point: {
74
+ readonly x: number;
75
+ readonly y: number;
76
+ };
77
+ } | null;
61
78
  };
62
- declare function NativeInteractionOverlay({ camera, width, height, selectedItems, showResizeHandles, placementPreview, eraserTrail, laserTrail, eraserPreviewItems, previewStrokeStyle, }: NativeInteractionOverlayProps): react_jsx_runtime.JSX.Element | null;
79
+ declare function NativeInteractionOverlay({ camera, width, height, selectedItems, showResizeHandles, placementPreview, eraserTrail, laserTrail, eraserPreviewItems, previewStrokeStyle, toolCursor, }: NativeInteractionOverlayProps): react_jsx_runtime.JSX.Element | null;
63
80
 
64
81
  type NativeSceneRendererProps = {
65
82
  readonly items: readonly VectorSceneItem[];
package/dist/native.js CHANGED
@@ -1816,6 +1816,178 @@ function NativeShapeRenderer({ item }) {
1816
1816
  }
1817
1817
  return null;
1818
1818
  }
1819
+ var CURSOR_STROKE = "#18181b";
1820
+ var CURSOR_STROKE_WIDTH = 2;
1821
+ var CROSSHAIR_STROKE_WIDTH = 1.5;
1822
+ function NativeDrawCursor() {
1823
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1824
+ /* @__PURE__ */ jsx(
1825
+ Path,
1826
+ {
1827
+ path: "M13 21h8",
1828
+ color: CURSOR_STROKE,
1829
+ style: "stroke",
1830
+ strokeWidth: CURSOR_STROKE_WIDTH,
1831
+ strokeCap: "round",
1832
+ strokeJoin: "round",
1833
+ antiAlias: true
1834
+ }
1835
+ ),
1836
+ /* @__PURE__ */ jsx(
1837
+ Path,
1838
+ {
1839
+ path: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
1840
+ color: CURSOR_STROKE,
1841
+ style: "stroke",
1842
+ strokeWidth: CURSOR_STROKE_WIDTH,
1843
+ strokeCap: "round",
1844
+ strokeJoin: "round",
1845
+ antiAlias: true
1846
+ }
1847
+ )
1848
+ ] });
1849
+ }
1850
+ function NativeMarkerCursor() {
1851
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1852
+ /* @__PURE__ */ jsx(
1853
+ Path,
1854
+ {
1855
+ path: "m9 11-6 6v3h9l3-3",
1856
+ color: CURSOR_STROKE,
1857
+ style: "stroke",
1858
+ strokeWidth: CURSOR_STROKE_WIDTH,
1859
+ strokeCap: "round",
1860
+ strokeJoin: "round",
1861
+ antiAlias: true
1862
+ }
1863
+ ),
1864
+ /* @__PURE__ */ jsx(
1865
+ Path,
1866
+ {
1867
+ path: "m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4",
1868
+ color: CURSOR_STROKE,
1869
+ style: "stroke",
1870
+ strokeWidth: CURSOR_STROKE_WIDTH,
1871
+ strokeCap: "round",
1872
+ strokeJoin: "round",
1873
+ antiAlias: true
1874
+ }
1875
+ )
1876
+ ] });
1877
+ }
1878
+ function NativeEraserCursor() {
1879
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1880
+ /* @__PURE__ */ jsx(
1881
+ Path,
1882
+ {
1883
+ path: "M21 21H8a2 2 0 0 1-1.42-.587l-3.994-3.999a2 2 0 0 1 0-2.828l10-10a2 2 0 0 1 2.829 0l5.999 6a2 2 0 0 1 0 2.828L12.834 21",
1884
+ color: CURSOR_STROKE,
1885
+ style: "stroke",
1886
+ strokeWidth: CURSOR_STROKE_WIDTH,
1887
+ strokeCap: "round",
1888
+ strokeJoin: "round",
1889
+ antiAlias: true
1890
+ }
1891
+ ),
1892
+ /* @__PURE__ */ jsx(
1893
+ Path,
1894
+ {
1895
+ path: "m5.082 11.09 8.828 8.828",
1896
+ color: CURSOR_STROKE,
1897
+ style: "stroke",
1898
+ strokeWidth: CURSOR_STROKE_WIDTH,
1899
+ strokeCap: "round",
1900
+ strokeJoin: "round",
1901
+ antiAlias: true
1902
+ }
1903
+ )
1904
+ ] });
1905
+ }
1906
+ function NativeCrosshairCursor() {
1907
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1908
+ /* @__PURE__ */ jsx(
1909
+ Line,
1910
+ {
1911
+ p1: vec(12, 3),
1912
+ p2: vec(12, 21),
1913
+ color: CURSOR_STROKE,
1914
+ style: "stroke",
1915
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
1916
+ strokeCap: "round",
1917
+ antiAlias: true
1918
+ }
1919
+ ),
1920
+ /* @__PURE__ */ jsx(
1921
+ Line,
1922
+ {
1923
+ p1: vec(3, 12),
1924
+ p2: vec(21, 12),
1925
+ color: CURSOR_STROKE,
1926
+ style: "stroke",
1927
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
1928
+ strokeCap: "round",
1929
+ antiAlias: true
1930
+ }
1931
+ )
1932
+ ] });
1933
+ }
1934
+ function NativeTextCursor() {
1935
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1936
+ /* @__PURE__ */ jsx(
1937
+ Line,
1938
+ {
1939
+ p1: vec(12, 4),
1940
+ p2: vec(12, 20),
1941
+ color: CURSOR_STROKE,
1942
+ style: "stroke",
1943
+ strokeWidth: CURSOR_STROKE_WIDTH,
1944
+ strokeCap: "round",
1945
+ antiAlias: true
1946
+ }
1947
+ ),
1948
+ /* @__PURE__ */ jsx(
1949
+ Line,
1950
+ {
1951
+ p1: vec(8, 4),
1952
+ p2: vec(16, 4),
1953
+ color: CURSOR_STROKE,
1954
+ style: "stroke",
1955
+ strokeWidth: CURSOR_STROKE_WIDTH,
1956
+ strokeCap: "round",
1957
+ antiAlias: true
1958
+ }
1959
+ ),
1960
+ /* @__PURE__ */ jsx(
1961
+ Line,
1962
+ {
1963
+ p1: vec(8, 20),
1964
+ p2: vec(16, 20),
1965
+ color: CURSOR_STROKE,
1966
+ style: "stroke",
1967
+ strokeWidth: CURSOR_STROKE_WIDTH,
1968
+ strokeCap: "round",
1969
+ antiAlias: true
1970
+ }
1971
+ )
1972
+ ] });
1973
+ }
1974
+ function NativeToolCursorGlyph({
1975
+ cursor
1976
+ }) {
1977
+ if (cursor.kind === "draw") return /* @__PURE__ */ jsx(NativeDrawCursor, {});
1978
+ if (cursor.kind === "marker") return /* @__PURE__ */ jsx(NativeMarkerCursor, {});
1979
+ if (cursor.kind === "eraser") return /* @__PURE__ */ jsx(NativeEraserCursor, {});
1980
+ if (cursor.kind === "text") return /* @__PURE__ */ jsx(NativeTextCursor, {});
1981
+ return /* @__PURE__ */ jsx(NativeCrosshairCursor, {});
1982
+ }
1983
+ function NativeToolCursorRenderer({
1984
+ cursor,
1985
+ point
1986
+ }) {
1987
+ const x = point.x - cursor.hotspot.x;
1988
+ const y = point.y - cursor.hotspot.y;
1989
+ return /* @__PURE__ */ jsx(Group, { transform: [{ translateX: x }, { translateY: y }], children: /* @__PURE__ */ jsx(NativeToolCursorGlyph, { cursor }) });
1990
+ }
1819
1991
 
1820
1992
  // src/native/native-overlay-style.ts
1821
1993
  var ERASER_TINT = "#cbd5e1";
@@ -1872,7 +2044,8 @@ function NativeInteractionOverlay({
1872
2044
  eraserTrail,
1873
2045
  laserTrail,
1874
2046
  eraserPreviewItems = [],
1875
- previewStrokeStyle
2047
+ previewStrokeStyle,
2048
+ toolCursor
1876
2049
  }) {
1877
2050
  const z = camera.zoom;
1878
2051
  const camTransform = skiaCameraTransform(z, camera.x, camera.y);
@@ -2249,7 +2422,7 @@ function NativeInteractionOverlay({
2249
2422
  ] });
2250
2423
  }, [laserTrail, z]);
2251
2424
  if (width <= 0 || height <= 0) return null;
2252
- return /* @__PURE__ */ jsx(
2425
+ return /* @__PURE__ */ jsxs(
2253
2426
  Canvas,
2254
2427
  {
2255
2428
  style: {
@@ -2260,13 +2433,22 @@ function NativeInteractionOverlay({
2260
2433
  height
2261
2434
  },
2262
2435
  pointerEvents: "none",
2263
- children: /* @__PURE__ */ jsxs(Group, { transform: camTransform, children: [
2264
- previewElements,
2265
- laserTrailElements,
2266
- eraserTrailElements,
2267
- eraserPreviewElements,
2268
- selectionElements
2269
- ] })
2436
+ children: [
2437
+ /* @__PURE__ */ jsxs(Group, { transform: camTransform, children: [
2438
+ previewElements,
2439
+ laserTrailElements,
2440
+ eraserTrailElements,
2441
+ eraserPreviewElements,
2442
+ selectionElements
2443
+ ] }),
2444
+ toolCursor ? /* @__PURE__ */ jsx(
2445
+ NativeToolCursorRenderer,
2446
+ {
2447
+ cursor: toolCursor.cursor,
2448
+ point: toolCursor.point
2449
+ }
2450
+ ) : null
2451
+ ]
2270
2452
  }
2271
2453
  );
2272
2454
  }
@@ -3398,6 +3580,36 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
3398
3580
  }
3399
3581
  return ids;
3400
3582
  }
3583
+
3584
+ // src/native/native-tool-cursors.ts
3585
+ var ICON_SIZE = 24;
3586
+ var CENTER_HOTSPOT = { x: 12, y: 12 };
3587
+ function nativeCursorForVectorToolId(toolId) {
3588
+ switch (toolId) {
3589
+ case "rect":
3590
+ case "ellipse":
3591
+ case "architectural-cloud":
3592
+ case "line":
3593
+ case "arrow":
3594
+ case "laser":
3595
+ case "image":
3596
+ return { kind: "crosshair", size: ICON_SIZE, hotspot: CENTER_HOTSPOT };
3597
+ case "draw":
3598
+ return { kind: "draw", size: ICON_SIZE, hotspot: { x: 4, y: 18 } };
3599
+ case "marker":
3600
+ return { kind: "marker", size: ICON_SIZE, hotspot: { x: 11, y: 14 } };
3601
+ case "eraser":
3602
+ return { kind: "eraser", size: ICON_SIZE, hotspot: { x: 12, y: 14 } };
3603
+ case "text":
3604
+ return { kind: "text", size: ICON_SIZE, hotspot: CENTER_HOTSPOT };
3605
+ default:
3606
+ return null;
3607
+ }
3608
+ }
3609
+ function nativeFallbackToolCursorPoint(size) {
3610
+ if (size.width <= 0 || size.height <= 0) return null;
3611
+ return { x: size.width / 2, y: size.height / 2 };
3612
+ }
3401
3613
  var MIN_PLACE_SIZE = 8;
3402
3614
  var MIN_ARROW_DRAG_PX = 8;
3403
3615
  var TAP_PX = 20;
@@ -3458,6 +3670,12 @@ function collectIdsInRect(items, marquee) {
3458
3670
  }
3459
3671
  return out;
3460
3672
  }
3673
+ function screenPointFromPointerEvent(event) {
3674
+ const nativeEvent = event.nativeEvent;
3675
+ const x = Number.isFinite(nativeEvent.offsetX) ? nativeEvent.offsetX : nativeEvent.x;
3676
+ const y = Number.isFinite(nativeEvent.offsetY) ? nativeEvent.offsetY : nativeEvent.y;
3677
+ return { x, y };
3678
+ }
3461
3679
  function fitCameraToWorldRect(camera, viewportW, viewportH, worldRect, padding) {
3462
3680
  const r = normalizeRect(worldRect);
3463
3681
  if (r.width <= 0 || r.height <= 0 || viewportW <= 0 || viewportH <= 0) return;
@@ -3512,6 +3730,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3512
3730
  );
3513
3731
  const [eraserTrail, setEraserTrail] = useState([]);
3514
3732
  const [laserTrail, setLaserTrail] = useState([]);
3733
+ const [toolCursorPoint, setToolCursorPoint] = useState(null);
3515
3734
  const laserClearTimerRef = useRef(null);
3516
3735
  const strokeStyleRef = useRef({ ...DEFAULT_STROKE_STYLE });
3517
3736
  const markerStrokeStyleRef = useRef({ ...MARKER_TOOL_STYLE });
@@ -3606,10 +3825,61 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3606
3825
  setCameraTick((n) => n + 1);
3607
3826
  onCameraChangeRef.current?.();
3608
3827
  }, []);
3609
- const onLayout = useCallback((e) => {
3610
- const { width, height } = e.nativeEvent.layout;
3611
- setSize({ width, height });
3828
+ const onLayout = useCallback(
3829
+ (e) => {
3830
+ const { width, height } = e.nativeEvent.layout;
3831
+ const nextSize = { width, height };
3832
+ setSize(nextSize);
3833
+ if (!interactive) {
3834
+ setToolCursorPoint(null);
3835
+ return;
3836
+ }
3837
+ if (!nativeCursorForVectorToolId(toolIdRef.current)) {
3838
+ setToolCursorPoint(null);
3839
+ return;
3840
+ }
3841
+ setToolCursorPoint(
3842
+ (current) => current ?? nativeFallbackToolCursorPoint(nextSize)
3843
+ );
3844
+ },
3845
+ [interactive]
3846
+ );
3847
+ const updateToolCursorPoint = useCallback(
3848
+ (point) => {
3849
+ if (!interactive) return;
3850
+ if (!nativeCursorForVectorToolId(toolIdRef.current)) return;
3851
+ setToolCursorPoint(point);
3852
+ },
3853
+ [interactive]
3854
+ );
3855
+ const hideToolCursor = useCallback(() => {
3856
+ setToolCursorPoint(null);
3612
3857
  }, []);
3858
+ const showFallbackToolCursor = useCallback(
3859
+ (nextToolId) => {
3860
+ if (!interactive) {
3861
+ setToolCursorPoint(null);
3862
+ return;
3863
+ }
3864
+ if (!nativeCursorForVectorToolId(nextToolId)) {
3865
+ setToolCursorPoint(null);
3866
+ return;
3867
+ }
3868
+ setToolCursorPoint(
3869
+ (current) => current ?? nativeFallbackToolCursorPoint(size)
3870
+ );
3871
+ },
3872
+ [interactive, size]
3873
+ );
3874
+ useEffect(() => {
3875
+ showFallbackToolCursor(toolId);
3876
+ }, [showFallbackToolCursor, toolId]);
3877
+ const handlePointerMove = useCallback(
3878
+ (event) => {
3879
+ updateToolCursorPoint(screenPointFromPointerEvent(event));
3880
+ },
3881
+ [updateToolCursorPoint]
3882
+ );
3613
3883
  const selectedItems = useMemo(
3614
3884
  () => items.filter((it) => selectedIds.includes(it.id)),
3615
3885
  [items, selectedIds]
@@ -3633,9 +3903,11 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3633
3903
  const sx = evt.nativeEvent.locationX;
3634
3904
  const sy = evt.nativeEvent.locationY;
3635
3905
  if (touches && touches.length >= 2) {
3906
+ hideToolCursor();
3636
3907
  dragStateRef.current = { kind: "pan" };
3637
3908
  return;
3638
3909
  }
3910
+ updateToolCursorPoint({ x: sx, y: sy });
3639
3911
  if (!interactive) {
3640
3912
  dragStateRef.current = { kind: "pan" };
3641
3913
  return;
@@ -3761,6 +4033,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3761
4033
  const pageX = evt.nativeEvent.pageX;
3762
4034
  const pageY = evt.nativeEvent.pageY;
3763
4035
  if (touches && touches.length >= 2) {
4036
+ hideToolCursor();
3764
4037
  const t0 = touches[0];
3765
4038
  const t1 = touches[1];
3766
4039
  if (t0 && t1) {
@@ -3780,6 +4053,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3780
4053
  return;
3781
4054
  }
3782
4055
  lastPinchDist.current = null;
4056
+ updateToolCursorPoint({ x: sx, y: sy });
3783
4057
  const { worldX, worldY } = screenToWorld(sx, sy);
3784
4058
  const st = dragStateRef.current;
3785
4059
  if (st.kind === "pan") {
@@ -3885,6 +4159,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3885
4159
  onPanResponderRelease: (evt) => {
3886
4160
  lastPinchDist.current = null;
3887
4161
  lastPanPoint.current = null;
4162
+ updateToolCursorPoint({
4163
+ x: evt.nativeEvent.locationX,
4164
+ y: evt.nativeEvent.locationY
4165
+ });
3888
4166
  const st = dragStateRef.current;
3889
4167
  if (st.kind === "draw") {
3890
4168
  dragStateRef.current = { kind: "idle" };
@@ -4068,6 +4346,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4068
4346
  onPanResponderTerminate: () => {
4069
4347
  lastPinchDist.current = null;
4070
4348
  lastPanPoint.current = null;
4349
+ hideToolCursor();
4071
4350
  dragStateRef.current = { kind: "idle" };
4072
4351
  setPlacementPreview(null);
4073
4352
  setLaserTrail([]);
@@ -4076,7 +4355,14 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4076
4355
  eraserPreviewIdSetRef.current.clear();
4077
4356
  }
4078
4357
  }),
4079
- [screenToWorld, requestRender, requestSelectToolAfterUse, interactive]
4358
+ [
4359
+ screenToWorld,
4360
+ requestRender,
4361
+ requestSelectToolAfterUse,
4362
+ interactive,
4363
+ updateToolCursorPoint,
4364
+ hideToolCursor
4365
+ ]
4080
4366
  );
4081
4367
  useImperativeHandle(
4082
4368
  ref,
@@ -4100,11 +4386,16 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4100
4386
  [requestRender, size]
4101
4387
  );
4102
4388
  const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
4389
+ const activeToolCursor = nativeCursorForVectorToolId(toolId);
4390
+ const toolCursor = activeToolCursor && toolCursorPoint ? { cursor: activeToolCursor, point: toolCursorPoint } : null;
4103
4391
  return /* @__PURE__ */ jsx(
4104
4392
  View,
4105
4393
  {
4106
4394
  style: { flex: 1, overflow: "hidden" },
4107
4395
  onLayout,
4396
+ onPointerMove: handlePointerMove,
4397
+ onPointerEnter: handlePointerMove,
4398
+ onPointerLeave: hideToolCursor,
4108
4399
  ...panResponder.panHandlers,
4109
4400
  children: size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
4110
4401
  /* @__PURE__ */ jsx(
@@ -4130,7 +4421,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4130
4421
  eraserPreviewItems: items.filter(
4131
4422
  (it) => eraserPreviewIds.includes(it.id)
4132
4423
  ),
4133
- previewStrokeStyle: strokeStyleState
4424
+ previewStrokeStyle: strokeStyleState,
4425
+ toolCursor
4134
4426
  }
4135
4427
  ),
4136
4428
  interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsx(