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.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,36 @@ 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
+ }
3615
+ function nativeFallbackToolCursorPoint(size) {
3616
+ if (size.width <= 0 || size.height <= 0) return null;
3617
+ return { x: size.width / 2, y: size.height / 2 };
3618
+ }
3407
3619
  var MIN_PLACE_SIZE = 8;
3408
3620
  var MIN_ARROW_DRAG_PX = 8;
3409
3621
  var TAP_PX = 20;
@@ -3464,6 +3676,12 @@ function collectIdsInRect(items, marquee) {
3464
3676
  }
3465
3677
  return out;
3466
3678
  }
3679
+ function screenPointFromPointerEvent(event) {
3680
+ const nativeEvent = event.nativeEvent;
3681
+ const x = Number.isFinite(nativeEvent.offsetX) ? nativeEvent.offsetX : nativeEvent.x;
3682
+ const y = Number.isFinite(nativeEvent.offsetY) ? nativeEvent.offsetY : nativeEvent.y;
3683
+ return { x, y };
3684
+ }
3467
3685
  function fitCameraToWorldRect(camera, viewportW, viewportH, worldRect, padding) {
3468
3686
  const r = normalizeRect(worldRect);
3469
3687
  if (r.width <= 0 || r.height <= 0 || viewportW <= 0 || viewportH <= 0) return;
@@ -3518,6 +3736,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3518
3736
  );
3519
3737
  const [eraserTrail, setEraserTrail] = react.useState([]);
3520
3738
  const [laserTrail, setLaserTrail] = react.useState([]);
3739
+ const [toolCursorPoint, setToolCursorPoint] = react.useState(null);
3521
3740
  const laserClearTimerRef = react.useRef(null);
3522
3741
  const strokeStyleRef = react.useRef({ ...DEFAULT_STROKE_STYLE });
3523
3742
  const markerStrokeStyleRef = react.useRef({ ...MARKER_TOOL_STYLE });
@@ -3612,10 +3831,61 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3612
3831
  setCameraTick((n) => n + 1);
3613
3832
  onCameraChangeRef.current?.();
3614
3833
  }, []);
3615
- const onLayout = react.useCallback((e) => {
3616
- const { width, height } = e.nativeEvent.layout;
3617
- setSize({ width, height });
3834
+ const onLayout = react.useCallback(
3835
+ (e) => {
3836
+ const { width, height } = e.nativeEvent.layout;
3837
+ const nextSize = { width, height };
3838
+ setSize(nextSize);
3839
+ if (!interactive) {
3840
+ setToolCursorPoint(null);
3841
+ return;
3842
+ }
3843
+ if (!nativeCursorForVectorToolId(toolIdRef.current)) {
3844
+ setToolCursorPoint(null);
3845
+ return;
3846
+ }
3847
+ setToolCursorPoint(
3848
+ (current) => current ?? nativeFallbackToolCursorPoint(nextSize)
3849
+ );
3850
+ },
3851
+ [interactive]
3852
+ );
3853
+ const updateToolCursorPoint = react.useCallback(
3854
+ (point) => {
3855
+ if (!interactive) return;
3856
+ if (!nativeCursorForVectorToolId(toolIdRef.current)) return;
3857
+ setToolCursorPoint(point);
3858
+ },
3859
+ [interactive]
3860
+ );
3861
+ const hideToolCursor = react.useCallback(() => {
3862
+ setToolCursorPoint(null);
3618
3863
  }, []);
3864
+ const showFallbackToolCursor = react.useCallback(
3865
+ (nextToolId) => {
3866
+ if (!interactive) {
3867
+ setToolCursorPoint(null);
3868
+ return;
3869
+ }
3870
+ if (!nativeCursorForVectorToolId(nextToolId)) {
3871
+ setToolCursorPoint(null);
3872
+ return;
3873
+ }
3874
+ setToolCursorPoint(
3875
+ (current) => current ?? nativeFallbackToolCursorPoint(size)
3876
+ );
3877
+ },
3878
+ [interactive, size]
3879
+ );
3880
+ react.useEffect(() => {
3881
+ showFallbackToolCursor(toolId);
3882
+ }, [showFallbackToolCursor, toolId]);
3883
+ const handlePointerMove = react.useCallback(
3884
+ (event) => {
3885
+ updateToolCursorPoint(screenPointFromPointerEvent(event));
3886
+ },
3887
+ [updateToolCursorPoint]
3888
+ );
3619
3889
  const selectedItems = react.useMemo(
3620
3890
  () => items.filter((it) => selectedIds.includes(it.id)),
3621
3891
  [items, selectedIds]
@@ -3639,9 +3909,11 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3639
3909
  const sx = evt.nativeEvent.locationX;
3640
3910
  const sy = evt.nativeEvent.locationY;
3641
3911
  if (touches && touches.length >= 2) {
3912
+ hideToolCursor();
3642
3913
  dragStateRef.current = { kind: "pan" };
3643
3914
  return;
3644
3915
  }
3916
+ updateToolCursorPoint({ x: sx, y: sy });
3645
3917
  if (!interactive) {
3646
3918
  dragStateRef.current = { kind: "pan" };
3647
3919
  return;
@@ -3767,6 +4039,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3767
4039
  const pageX = evt.nativeEvent.pageX;
3768
4040
  const pageY = evt.nativeEvent.pageY;
3769
4041
  if (touches && touches.length >= 2) {
4042
+ hideToolCursor();
3770
4043
  const t0 = touches[0];
3771
4044
  const t1 = touches[1];
3772
4045
  if (t0 && t1) {
@@ -3786,6 +4059,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3786
4059
  return;
3787
4060
  }
3788
4061
  lastPinchDist.current = null;
4062
+ updateToolCursorPoint({ x: sx, y: sy });
3789
4063
  const { worldX, worldY } = screenToWorld(sx, sy);
3790
4064
  const st = dragStateRef.current;
3791
4065
  if (st.kind === "pan") {
@@ -3891,6 +4165,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3891
4165
  onPanResponderRelease: (evt) => {
3892
4166
  lastPinchDist.current = null;
3893
4167
  lastPanPoint.current = null;
4168
+ updateToolCursorPoint({
4169
+ x: evt.nativeEvent.locationX,
4170
+ y: evt.nativeEvent.locationY
4171
+ });
3894
4172
  const st = dragStateRef.current;
3895
4173
  if (st.kind === "draw") {
3896
4174
  dragStateRef.current = { kind: "idle" };
@@ -4074,6 +4352,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4074
4352
  onPanResponderTerminate: () => {
4075
4353
  lastPinchDist.current = null;
4076
4354
  lastPanPoint.current = null;
4355
+ hideToolCursor();
4077
4356
  dragStateRef.current = { kind: "idle" };
4078
4357
  setPlacementPreview(null);
4079
4358
  setLaserTrail([]);
@@ -4082,7 +4361,14 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4082
4361
  eraserPreviewIdSetRef.current.clear();
4083
4362
  }
4084
4363
  }),
4085
- [screenToWorld, requestRender, requestSelectToolAfterUse, interactive]
4364
+ [
4365
+ screenToWorld,
4366
+ requestRender,
4367
+ requestSelectToolAfterUse,
4368
+ interactive,
4369
+ updateToolCursorPoint,
4370
+ hideToolCursor
4371
+ ]
4086
4372
  );
4087
4373
  react.useImperativeHandle(
4088
4374
  ref,
@@ -4106,11 +4392,16 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4106
4392
  [requestRender, size]
4107
4393
  );
4108
4394
  const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
4395
+ const activeToolCursor = nativeCursorForVectorToolId(toolId);
4396
+ const toolCursor = activeToolCursor && toolCursorPoint ? { cursor: activeToolCursor, point: toolCursorPoint } : null;
4109
4397
  return /* @__PURE__ */ jsxRuntime.jsx(
4110
4398
  reactNative.View,
4111
4399
  {
4112
4400
  style: { flex: 1, overflow: "hidden" },
4113
4401
  onLayout,
4402
+ onPointerMove: handlePointerMove,
4403
+ onPointerEnter: handlePointerMove,
4404
+ onPointerLeave: hideToolCursor,
4114
4405
  ...panResponder.panHandlers,
4115
4406
  children: size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4116
4407
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4136,7 +4427,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4136
4427
  eraserPreviewItems: items.filter(
4137
4428
  (it) => eraserPreviewIds.includes(it.id)
4138
4429
  ),
4139
- previewStrokeStyle: strokeStyleState
4430
+ previewStrokeStyle: strokeStyleState,
4431
+ toolCursor
4140
4432
  }
4141
4433
  ),
4142
4434
  interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsxRuntime.jsx(