canvu-react 0.4.15 → 0.4.16

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
@@ -1822,6 +1822,178 @@ function NativeShapeRenderer({ item }) {
1822
1822
  }
1823
1823
  return null;
1824
1824
  }
1825
+ var CURSOR_STROKE = "#18181b";
1826
+ var CURSOR_STROKE_WIDTH = 2;
1827
+ var CROSSHAIR_STROKE_WIDTH = 1.5;
1828
+ function NativeDrawCursor() {
1829
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1830
+ /* @__PURE__ */ jsxRuntime.jsx(
1831
+ reactNativeSkia.Path,
1832
+ {
1833
+ path: "M13 21h8",
1834
+ color: CURSOR_STROKE,
1835
+ style: "stroke",
1836
+ strokeWidth: CURSOR_STROKE_WIDTH,
1837
+ strokeCap: "round",
1838
+ strokeJoin: "round",
1839
+ antiAlias: true
1840
+ }
1841
+ ),
1842
+ /* @__PURE__ */ jsxRuntime.jsx(
1843
+ reactNativeSkia.Path,
1844
+ {
1845
+ 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",
1846
+ color: CURSOR_STROKE,
1847
+ style: "stroke",
1848
+ strokeWidth: CURSOR_STROKE_WIDTH,
1849
+ strokeCap: "round",
1850
+ strokeJoin: "round",
1851
+ antiAlias: true
1852
+ }
1853
+ )
1854
+ ] });
1855
+ }
1856
+ function NativeMarkerCursor() {
1857
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1858
+ /* @__PURE__ */ jsxRuntime.jsx(
1859
+ reactNativeSkia.Path,
1860
+ {
1861
+ path: "m9 11-6 6v3h9l3-3",
1862
+ color: CURSOR_STROKE,
1863
+ style: "stroke",
1864
+ strokeWidth: CURSOR_STROKE_WIDTH,
1865
+ strokeCap: "round",
1866
+ strokeJoin: "round",
1867
+ antiAlias: true
1868
+ }
1869
+ ),
1870
+ /* @__PURE__ */ jsxRuntime.jsx(
1871
+ reactNativeSkia.Path,
1872
+ {
1873
+ 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",
1874
+ color: CURSOR_STROKE,
1875
+ style: "stroke",
1876
+ strokeWidth: CURSOR_STROKE_WIDTH,
1877
+ strokeCap: "round",
1878
+ strokeJoin: "round",
1879
+ antiAlias: true
1880
+ }
1881
+ )
1882
+ ] });
1883
+ }
1884
+ function NativeEraserCursor() {
1885
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1886
+ /* @__PURE__ */ jsxRuntime.jsx(
1887
+ reactNativeSkia.Path,
1888
+ {
1889
+ 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",
1890
+ color: CURSOR_STROKE,
1891
+ style: "stroke",
1892
+ strokeWidth: CURSOR_STROKE_WIDTH,
1893
+ strokeCap: "round",
1894
+ strokeJoin: "round",
1895
+ antiAlias: true
1896
+ }
1897
+ ),
1898
+ /* @__PURE__ */ jsxRuntime.jsx(
1899
+ reactNativeSkia.Path,
1900
+ {
1901
+ path: "m5.082 11.09 8.828 8.828",
1902
+ color: CURSOR_STROKE,
1903
+ style: "stroke",
1904
+ strokeWidth: CURSOR_STROKE_WIDTH,
1905
+ strokeCap: "round",
1906
+ strokeJoin: "round",
1907
+ antiAlias: true
1908
+ }
1909
+ )
1910
+ ] });
1911
+ }
1912
+ function NativeCrosshairCursor() {
1913
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1914
+ /* @__PURE__ */ jsxRuntime.jsx(
1915
+ reactNativeSkia.Line,
1916
+ {
1917
+ p1: reactNativeSkia.vec(12, 3),
1918
+ p2: reactNativeSkia.vec(12, 21),
1919
+ color: CURSOR_STROKE,
1920
+ style: "stroke",
1921
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
1922
+ strokeCap: "round",
1923
+ antiAlias: true
1924
+ }
1925
+ ),
1926
+ /* @__PURE__ */ jsxRuntime.jsx(
1927
+ reactNativeSkia.Line,
1928
+ {
1929
+ p1: reactNativeSkia.vec(3, 12),
1930
+ p2: reactNativeSkia.vec(21, 12),
1931
+ color: CURSOR_STROKE,
1932
+ style: "stroke",
1933
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
1934
+ strokeCap: "round",
1935
+ antiAlias: true
1936
+ }
1937
+ )
1938
+ ] });
1939
+ }
1940
+ function NativeTextCursor() {
1941
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1942
+ /* @__PURE__ */ jsxRuntime.jsx(
1943
+ reactNativeSkia.Line,
1944
+ {
1945
+ p1: reactNativeSkia.vec(12, 4),
1946
+ p2: reactNativeSkia.vec(12, 20),
1947
+ color: CURSOR_STROKE,
1948
+ style: "stroke",
1949
+ strokeWidth: CURSOR_STROKE_WIDTH,
1950
+ strokeCap: "round",
1951
+ antiAlias: true
1952
+ }
1953
+ ),
1954
+ /* @__PURE__ */ jsxRuntime.jsx(
1955
+ reactNativeSkia.Line,
1956
+ {
1957
+ p1: reactNativeSkia.vec(8, 4),
1958
+ p2: reactNativeSkia.vec(16, 4),
1959
+ color: CURSOR_STROKE,
1960
+ style: "stroke",
1961
+ strokeWidth: CURSOR_STROKE_WIDTH,
1962
+ strokeCap: "round",
1963
+ antiAlias: true
1964
+ }
1965
+ ),
1966
+ /* @__PURE__ */ jsxRuntime.jsx(
1967
+ reactNativeSkia.Line,
1968
+ {
1969
+ p1: reactNativeSkia.vec(8, 20),
1970
+ p2: reactNativeSkia.vec(16, 20),
1971
+ color: CURSOR_STROKE,
1972
+ style: "stroke",
1973
+ strokeWidth: CURSOR_STROKE_WIDTH,
1974
+ strokeCap: "round",
1975
+ antiAlias: true
1976
+ }
1977
+ )
1978
+ ] });
1979
+ }
1980
+ function NativeToolCursorGlyph({
1981
+ cursor
1982
+ }) {
1983
+ if (cursor.kind === "draw") return /* @__PURE__ */ jsxRuntime.jsx(NativeDrawCursor, {});
1984
+ if (cursor.kind === "marker") return /* @__PURE__ */ jsxRuntime.jsx(NativeMarkerCursor, {});
1985
+ if (cursor.kind === "eraser") return /* @__PURE__ */ jsxRuntime.jsx(NativeEraserCursor, {});
1986
+ if (cursor.kind === "text") return /* @__PURE__ */ jsxRuntime.jsx(NativeTextCursor, {});
1987
+ return /* @__PURE__ */ jsxRuntime.jsx(NativeCrosshairCursor, {});
1988
+ }
1989
+ function NativeToolCursorRenderer({
1990
+ cursor,
1991
+ point
1992
+ }) {
1993
+ const x = point.x - cursor.hotspot.x;
1994
+ const y = point.y - cursor.hotspot.y;
1995
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.Group, { transform: [{ translateX: x }, { translateY: y }], children: /* @__PURE__ */ jsxRuntime.jsx(NativeToolCursorGlyph, { cursor }) });
1996
+ }
1825
1997
 
1826
1998
  // src/native/native-overlay-style.ts
1827
1999
  var ERASER_TINT = "#cbd5e1";
@@ -1878,7 +2050,8 @@ function NativeInteractionOverlay({
1878
2050
  eraserTrail,
1879
2051
  laserTrail,
1880
2052
  eraserPreviewItems = [],
1881
- previewStrokeStyle
2053
+ previewStrokeStyle,
2054
+ toolCursor
1882
2055
  }) {
1883
2056
  const z = camera.zoom;
1884
2057
  const camTransform = skiaCameraTransform(z, camera.x, camera.y);
@@ -2255,7 +2428,7 @@ function NativeInteractionOverlay({
2255
2428
  ] });
2256
2429
  }, [laserTrail, z]);
2257
2430
  if (width <= 0 || height <= 0) return null;
2258
- return /* @__PURE__ */ jsxRuntime.jsx(
2431
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2259
2432
  reactNativeSkia.Canvas,
2260
2433
  {
2261
2434
  style: {
@@ -2266,13 +2439,22 @@ function NativeInteractionOverlay({
2266
2439
  height
2267
2440
  },
2268
2441
  pointerEvents: "none",
2269
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNativeSkia.Group, { transform: camTransform, children: [
2270
- previewElements,
2271
- laserTrailElements,
2272
- eraserTrailElements,
2273
- eraserPreviewElements,
2274
- selectionElements
2275
- ] })
2442
+ children: [
2443
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNativeSkia.Group, { transform: camTransform, children: [
2444
+ previewElements,
2445
+ laserTrailElements,
2446
+ eraserTrailElements,
2447
+ eraserPreviewElements,
2448
+ selectionElements
2449
+ ] }),
2450
+ toolCursor ? /* @__PURE__ */ jsxRuntime.jsx(
2451
+ NativeToolCursorRenderer,
2452
+ {
2453
+ cursor: toolCursor.cursor,
2454
+ point: toolCursor.point
2455
+ }
2456
+ ) : null
2457
+ ]
2276
2458
  }
2277
2459
  );
2278
2460
  }
@@ -3404,6 +3586,32 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
3404
3586
  }
3405
3587
  return ids;
3406
3588
  }
3589
+
3590
+ // src/native/native-tool-cursors.ts
3591
+ var ICON_SIZE = 24;
3592
+ var CENTER_HOTSPOT = { x: 12, y: 12 };
3593
+ function nativeCursorForVectorToolId(toolId) {
3594
+ switch (toolId) {
3595
+ case "rect":
3596
+ case "ellipse":
3597
+ case "architectural-cloud":
3598
+ case "line":
3599
+ case "arrow":
3600
+ case "laser":
3601
+ case "image":
3602
+ return { kind: "crosshair", size: ICON_SIZE, hotspot: CENTER_HOTSPOT };
3603
+ case "draw":
3604
+ return { kind: "draw", size: ICON_SIZE, hotspot: { x: 4, y: 18 } };
3605
+ case "marker":
3606
+ return { kind: "marker", size: ICON_SIZE, hotspot: { x: 11, y: 14 } };
3607
+ case "eraser":
3608
+ return { kind: "eraser", size: ICON_SIZE, hotspot: { x: 12, y: 14 } };
3609
+ case "text":
3610
+ return { kind: "text", size: ICON_SIZE, hotspot: CENTER_HOTSPOT };
3611
+ default:
3612
+ return null;
3613
+ }
3614
+ }
3407
3615
  var MIN_PLACE_SIZE = 8;
3408
3616
  var MIN_ARROW_DRAG_PX = 8;
3409
3617
  var TAP_PX = 20;
@@ -3464,6 +3672,12 @@ function collectIdsInRect(items, marquee) {
3464
3672
  }
3465
3673
  return out;
3466
3674
  }
3675
+ function screenPointFromPointerEvent(event) {
3676
+ const nativeEvent = event.nativeEvent;
3677
+ const x = Number.isFinite(nativeEvent.offsetX) ? nativeEvent.offsetX : nativeEvent.x;
3678
+ const y = Number.isFinite(nativeEvent.offsetY) ? nativeEvent.offsetY : nativeEvent.y;
3679
+ return { x, y };
3680
+ }
3467
3681
  function fitCameraToWorldRect(camera, viewportW, viewportH, worldRect, padding) {
3468
3682
  const r = normalizeRect(worldRect);
3469
3683
  if (r.width <= 0 || r.height <= 0 || viewportW <= 0 || viewportH <= 0) return;
@@ -3518,6 +3732,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3518
3732
  );
3519
3733
  const [eraserTrail, setEraserTrail] = react.useState([]);
3520
3734
  const [laserTrail, setLaserTrail] = react.useState([]);
3735
+ const [toolCursorPoint, setToolCursorPoint] = react.useState(null);
3521
3736
  const laserClearTimerRef = react.useRef(null);
3522
3737
  const strokeStyleRef = react.useRef({ ...DEFAULT_STROKE_STYLE });
3523
3738
  const markerStrokeStyleRef = react.useRef({ ...MARKER_TOOL_STYLE });
@@ -3616,6 +3831,23 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3616
3831
  const { width, height } = e.nativeEvent.layout;
3617
3832
  setSize({ width, height });
3618
3833
  }, []);
3834
+ const updateToolCursorPoint = react.useCallback(
3835
+ (point) => {
3836
+ if (!interactive) return;
3837
+ if (!nativeCursorForVectorToolId(toolIdRef.current)) return;
3838
+ setToolCursorPoint(point);
3839
+ },
3840
+ [interactive]
3841
+ );
3842
+ const hideToolCursor = react.useCallback(() => {
3843
+ setToolCursorPoint(null);
3844
+ }, []);
3845
+ const handlePointerMove = react.useCallback(
3846
+ (event) => {
3847
+ updateToolCursorPoint(screenPointFromPointerEvent(event));
3848
+ },
3849
+ [updateToolCursorPoint]
3850
+ );
3619
3851
  const selectedItems = react.useMemo(
3620
3852
  () => items.filter((it) => selectedIds.includes(it.id)),
3621
3853
  [items, selectedIds]
@@ -3639,9 +3871,11 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3639
3871
  const sx = evt.nativeEvent.locationX;
3640
3872
  const sy = evt.nativeEvent.locationY;
3641
3873
  if (touches && touches.length >= 2) {
3874
+ hideToolCursor();
3642
3875
  dragStateRef.current = { kind: "pan" };
3643
3876
  return;
3644
3877
  }
3878
+ updateToolCursorPoint({ x: sx, y: sy });
3645
3879
  if (!interactive) {
3646
3880
  dragStateRef.current = { kind: "pan" };
3647
3881
  return;
@@ -3767,6 +4001,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3767
4001
  const pageX = evt.nativeEvent.pageX;
3768
4002
  const pageY = evt.nativeEvent.pageY;
3769
4003
  if (touches && touches.length >= 2) {
4004
+ hideToolCursor();
3770
4005
  const t0 = touches[0];
3771
4006
  const t1 = touches[1];
3772
4007
  if (t0 && t1) {
@@ -3786,6 +4021,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3786
4021
  return;
3787
4022
  }
3788
4023
  lastPinchDist.current = null;
4024
+ updateToolCursorPoint({ x: sx, y: sy });
3789
4025
  const { worldX, worldY } = screenToWorld(sx, sy);
3790
4026
  const st = dragStateRef.current;
3791
4027
  if (st.kind === "pan") {
@@ -3891,6 +4127,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3891
4127
  onPanResponderRelease: (evt) => {
3892
4128
  lastPinchDist.current = null;
3893
4129
  lastPanPoint.current = null;
4130
+ hideToolCursor();
3894
4131
  const st = dragStateRef.current;
3895
4132
  if (st.kind === "draw") {
3896
4133
  dragStateRef.current = { kind: "idle" };
@@ -4074,6 +4311,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4074
4311
  onPanResponderTerminate: () => {
4075
4312
  lastPinchDist.current = null;
4076
4313
  lastPanPoint.current = null;
4314
+ hideToolCursor();
4077
4315
  dragStateRef.current = { kind: "idle" };
4078
4316
  setPlacementPreview(null);
4079
4317
  setLaserTrail([]);
@@ -4082,7 +4320,14 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4082
4320
  eraserPreviewIdSetRef.current.clear();
4083
4321
  }
4084
4322
  }),
4085
- [screenToWorld, requestRender, requestSelectToolAfterUse, interactive]
4323
+ [
4324
+ screenToWorld,
4325
+ requestRender,
4326
+ requestSelectToolAfterUse,
4327
+ interactive,
4328
+ updateToolCursorPoint,
4329
+ hideToolCursor
4330
+ ]
4086
4331
  );
4087
4332
  react.useImperativeHandle(
4088
4333
  ref,
@@ -4106,11 +4351,16 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4106
4351
  [requestRender, size]
4107
4352
  );
4108
4353
  const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
4354
+ const activeToolCursor = nativeCursorForVectorToolId(toolId);
4355
+ const toolCursor = activeToolCursor && toolCursorPoint ? { cursor: activeToolCursor, point: toolCursorPoint } : null;
4109
4356
  return /* @__PURE__ */ jsxRuntime.jsx(
4110
4357
  reactNative.View,
4111
4358
  {
4112
4359
  style: { flex: 1, overflow: "hidden" },
4113
4360
  onLayout,
4361
+ onPointerMove: handlePointerMove,
4362
+ onPointerEnter: handlePointerMove,
4363
+ onPointerLeave: hideToolCursor,
4114
4364
  ...panResponder.panHandlers,
4115
4365
  children: size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4116
4366
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4136,7 +4386,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4136
4386
  eraserPreviewItems: items.filter(
4137
4387
  (it) => eraserPreviewIds.includes(it.id)
4138
4388
  ),
4139
- previewStrokeStyle: strokeStyleState
4389
+ previewStrokeStyle: strokeStyleState,
4390
+ toolCursor
4140
4391
  }
4141
4392
  ),
4142
4393
  interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsxRuntime.jsx(