canvu-react 0.4.13 → 0.4.15

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,13 +1822,52 @@ function NativeShapeRenderer({ item }) {
1822
1822
  }
1823
1823
  return null;
1824
1824
  }
1825
+
1826
+ // src/native/native-overlay-style.ts
1827
+ var ERASER_TINT = "#cbd5e1";
1828
+ var ERASER_TINT_OPACITY = 0.95;
1829
+ var LASER_TINT = "#f43f5e";
1830
+ var LASER_TINT_OPACITY = 0.9;
1831
+ var ERASER_TRAIL_MAX_AGE_MS = 150;
1832
+ var LASER_TRAIL_MAX_AGE_MS = 650;
1833
+ function colorWithOpacity(hex, alpha) {
1834
+ if (alpha == null || alpha >= 1) return hex;
1835
+ const r = parseInt(hex.slice(1, 3), 16);
1836
+ const g = parseInt(hex.slice(3, 5), 16);
1837
+ const b = parseInt(hex.slice(5, 7), 16);
1838
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return hex;
1839
+ return `rgba(${r},${g},${b},${alpha})`;
1840
+ }
1841
+ function freshTimedTrailPoints(points, now, maxAgeMs) {
1842
+ return points.filter((point) => now - point.t <= maxAgeMs);
1843
+ }
1844
+ function timedTrailHeadOpacity(point, now, maxAgeMs) {
1845
+ if (!point) return 0;
1846
+ return Math.max(0, 1 - (now - point.t) / maxAgeMs);
1847
+ }
1848
+ function resolveNativeStrokePreviewStyle(tool, previewStrokeStyle) {
1849
+ const isLaser = tool === "laser";
1850
+ return {
1851
+ stroke: isLaser ? LASER_TINT : previewStrokeStyle?.stroke ?? "#64748b",
1852
+ strokeWidth: isLaser ? 4 : previewStrokeStyle?.strokeWidth ?? (tool === "marker" ? 16 : 3),
1853
+ ...previewStrokeStyle?.strokeOpacity != null && !isLaser ? { strokeOpacity: previewStrokeStyle.strokeOpacity } : {},
1854
+ ...previewStrokeStyle?.strokeDash != null && !isLaser ? { strokeDash: previewStrokeStyle.strokeDash } : {}
1855
+ };
1856
+ }
1825
1857
  var HANDLE_ORDER = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
1826
1858
  var ERASER_PREVIEW_OPACITY = 0.3;
1859
+ var OVERLAY_STROKE_PX = 1.25;
1860
+ var MARQUEE_DASH_PX = 4;
1827
1861
  function pointsToSmoothPathD(points) {
1828
1862
  if (points.length < 2) return null;
1829
1863
  const d = smoothFreehandPointsToPathD(points);
1830
1864
  return d || null;
1831
1865
  }
1866
+ function dashIntervalsFromStrokeDasharray(strokeDasharray) {
1867
+ if (!strokeDasharray) return null;
1868
+ const intervals = strokeDasharray.split(/\s+/).map((part) => Number(part)).filter((part) => Number.isFinite(part) && part > 0);
1869
+ return intervals.length > 0 ? intervals : null;
1870
+ }
1832
1871
  function NativeInteractionOverlay({
1833
1872
  camera,
1834
1873
  width,
@@ -1838,13 +1877,18 @@ function NativeInteractionOverlay({
1838
1877
  placementPreview,
1839
1878
  eraserTrail,
1840
1879
  laserTrail,
1841
- eraserPreviewItems = []
1880
+ eraserPreviewItems = [],
1881
+ previewStrokeStyle
1842
1882
  }) {
1843
1883
  const z = camera.zoom;
1844
1884
  const camTransform = skiaCameraTransform(z, camera.x, camera.y);
1845
1885
  const handleR = 5 / z;
1846
- const overlayStrokePx = 1.25;
1886
+ const overlayStrokeWorld = OVERLAY_STROKE_PX / z;
1887
+ const marqueeDashWorld = MARQUEE_DASH_PX / z;
1847
1888
  const rotateOffsetWorld = 24 / z;
1889
+ const rotateIconWorld = 16 / z;
1890
+ const rotateIconScale = rotateIconWorld / 24;
1891
+ const rotateIconStroke = OVERLAY_STROKE_PX / (z * rotateIconScale);
1848
1892
  const selectionElements = react.useMemo(() => {
1849
1893
  if (selectedItems.length === 0) return null;
1850
1894
  const single = selectedItems.length === 1 ? selectedItems[0] : void 0;
@@ -1866,7 +1910,7 @@ function NativeInteractionOverlay({
1866
1910
  height: b.height,
1867
1911
  color: "#3b82f6",
1868
1912
  style: "stroke",
1869
- strokeWidth: overlayStrokePx,
1913
+ strokeWidth: overlayStrokeWorld,
1870
1914
  antiAlias: true
1871
1915
  }
1872
1916
  ) }, it.id);
@@ -1874,83 +1918,167 @@ function NativeInteractionOverlay({
1874
1918
  showResizeHandles && bSingle && single && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1875
1919
  HANDLE_ORDER.map((hid) => {
1876
1920
  const p = getHandleWorldPositionRotated(bSingle, hid, rotSingle);
1877
- return /* @__PURE__ */ jsxRuntime.jsx(
1878
- reactNativeSkia.Circle,
1879
- {
1880
- cx: p.x,
1881
- cy: p.y,
1882
- r: handleR,
1883
- color: "#ffffff",
1884
- style: "fill",
1885
- antiAlias: true
1886
- },
1887
- hid
1888
- );
1921
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNativeSkia.Group, { children: [
1922
+ /* @__PURE__ */ jsxRuntime.jsx(
1923
+ reactNativeSkia.Circle,
1924
+ {
1925
+ cx: p.x,
1926
+ cy: p.y,
1927
+ r: handleR,
1928
+ color: "#ffffff",
1929
+ style: "fill",
1930
+ antiAlias: true
1931
+ }
1932
+ ),
1933
+ /* @__PURE__ */ jsxRuntime.jsx(
1934
+ reactNativeSkia.Circle,
1935
+ {
1936
+ cx: p.x,
1937
+ cy: p.y,
1938
+ r: handleR,
1939
+ color: "#3b82f6",
1940
+ style: "stroke",
1941
+ strokeWidth: overlayStrokeWorld,
1942
+ antiAlias: true
1943
+ }
1944
+ )
1945
+ ] }, hid);
1889
1946
  }),
1890
- rotHandlePos && /* @__PURE__ */ jsxRuntime.jsx(
1891
- reactNativeSkia.Circle,
1947
+ rotHandlePos && /* @__PURE__ */ jsxRuntime.jsxs(
1948
+ reactNativeSkia.Group,
1892
1949
  {
1893
- cx: rotHandlePos.x,
1894
- cy: rotHandlePos.y,
1895
- r: handleR * 1.5,
1896
- color: "#3b82f6",
1897
- style: "stroke",
1898
- strokeWidth: overlayStrokePx * 1.2,
1899
- antiAlias: true
1950
+ transform: [
1951
+ { translateX: rotHandlePos.x },
1952
+ { translateY: rotHandlePos.y },
1953
+ { rotate: rotSingle },
1954
+ { translateX: -rotateIconWorld / 2 },
1955
+ { translateY: -rotateIconWorld / 2 },
1956
+ { scale: rotateIconScale }
1957
+ ],
1958
+ children: [
1959
+ /* @__PURE__ */ jsxRuntime.jsx(
1960
+ reactNativeSkia.Path,
1961
+ {
1962
+ path: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8",
1963
+ color: "#3b82f6",
1964
+ style: "stroke",
1965
+ strokeWidth: rotateIconStroke,
1966
+ strokeCap: "round",
1967
+ strokeJoin: "round",
1968
+ antiAlias: true
1969
+ }
1970
+ ),
1971
+ /* @__PURE__ */ jsxRuntime.jsx(
1972
+ reactNativeSkia.Path,
1973
+ {
1974
+ path: "M21 3v5h-5",
1975
+ color: "#3b82f6",
1976
+ style: "stroke",
1977
+ strokeWidth: rotateIconStroke,
1978
+ strokeCap: "round",
1979
+ strokeJoin: "round",
1980
+ antiAlias: true
1981
+ }
1982
+ )
1983
+ ]
1900
1984
  }
1901
1985
  )
1902
1986
  ] })
1903
1987
  ] });
1904
- }, [selectedItems, showResizeHandles, rotateOffsetWorld, handleR]);
1988
+ }, [
1989
+ selectedItems,
1990
+ showResizeHandles,
1991
+ rotateOffsetWorld,
1992
+ handleR,
1993
+ overlayStrokeWorld,
1994
+ rotateIconWorld,
1995
+ rotateIconScale,
1996
+ rotateIconStroke
1997
+ ]);
1905
1998
  const previewElements = react.useMemo(() => {
1906
1999
  if (!placementPreview) return null;
1907
2000
  const p = placementPreview;
2001
+ const shapeStroke = previewStrokeStyle?.stroke ?? "#1d1d1d";
2002
+ const shapeWidth = previewStrokeStyle?.strokeWidth ?? 2;
2003
+ const shapeColor = colorWithOpacity(
2004
+ shapeStroke,
2005
+ previewStrokeStyle?.strokeOpacity
2006
+ );
1908
2007
  if (p.kind === "rect" || p.kind === "ellipse" || p.kind === "architectural-cloud") {
1909
2008
  const r = normalizeRect(p.rect);
1910
- return p.kind === "rect" || p.kind === "architectural-cloud" ? /* @__PURE__ */ jsxRuntime.jsx(
1911
- reactNativeSkia.Rect,
2009
+ return p.kind === "rect" ? /* @__PURE__ */ jsxRuntime.jsx(
2010
+ reactNativeSkia.RoundedRect,
1912
2011
  {
1913
2012
  x: r.x,
1914
2013
  y: r.y,
1915
2014
  width: r.width,
1916
2015
  height: r.height,
1917
- color: "#64748b",
2016
+ r: 4,
2017
+ color: shapeColor,
1918
2018
  style: "stroke",
1919
- strokeWidth: overlayStrokePx,
2019
+ strokeWidth: shapeWidth,
1920
2020
  antiAlias: true
1921
2021
  }
1922
- ) : /* @__PURE__ */ jsxRuntime.jsx(
1923
- reactNativeSkia.Circle,
1924
- {
1925
- cx: r.x + r.width / 2,
1926
- cy: r.y + r.height / 2,
1927
- r: Math.max(0, r.width / 2),
1928
- color: "#64748b",
1929
- style: "stroke",
1930
- strokeWidth: overlayStrokePx,
1931
- antiAlias: true
1932
- }
1933
- );
1934
- }
1935
- if (p.kind === "marquee") {
1936
- const r = normalizeRect(p.rect);
1937
- return /* @__PURE__ */ jsxRuntime.jsx(
1938
- reactNativeSkia.Rect,
2022
+ ) : p.kind === "ellipse" ? /* @__PURE__ */ jsxRuntime.jsx(
2023
+ reactNativeSkia.Oval,
1939
2024
  {
1940
2025
  x: r.x,
1941
2026
  y: r.y,
1942
2027
  width: r.width,
1943
2028
  height: r.height,
1944
- color: "rgba(59, 130, 246, 0.12)",
1945
- style: "fill",
2029
+ color: shapeColor,
2030
+ style: "stroke",
2031
+ strokeWidth: shapeWidth,
1946
2032
  antiAlias: true
1947
2033
  }
1948
- );
2034
+ ) : /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.Group, { transform: [{ translateX: r.x }, { translateY: r.y }], children: /* @__PURE__ */ jsxRuntime.jsx(
2035
+ reactNativeSkia.Path,
2036
+ {
2037
+ path: buildArchitecturalCloudPathD(r.width, r.height, shapeWidth),
2038
+ color: shapeColor,
2039
+ style: "stroke",
2040
+ strokeWidth: shapeWidth,
2041
+ strokeCap: "round",
2042
+ strokeJoin: "round",
2043
+ antiAlias: true
2044
+ }
2045
+ ) });
2046
+ }
2047
+ if (p.kind === "marquee") {
2048
+ const r = normalizeRect(p.rect);
2049
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2050
+ /* @__PURE__ */ jsxRuntime.jsx(
2051
+ reactNativeSkia.Rect,
2052
+ {
2053
+ x: r.x,
2054
+ y: r.y,
2055
+ width: r.width,
2056
+ height: r.height,
2057
+ color: "rgba(59, 130, 246, 0.12)",
2058
+ style: "fill",
2059
+ antiAlias: true
2060
+ }
2061
+ ),
2062
+ /* @__PURE__ */ jsxRuntime.jsx(
2063
+ reactNativeSkia.Rect,
2064
+ {
2065
+ x: r.x,
2066
+ y: r.y,
2067
+ width: r.width,
2068
+ height: r.height,
2069
+ color: "#3b82f6",
2070
+ style: "stroke",
2071
+ strokeWidth: overlayStrokeWorld,
2072
+ antiAlias: true,
2073
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.DashPathEffect, { intervals: [marqueeDashWorld, marqueeDashWorld] })
2074
+ }
2075
+ )
2076
+ ] });
1949
2077
  }
1950
2078
  if (p.kind === "line" || p.kind === "arrow") {
1951
2079
  const geometry = p.kind === "arrow" ? computeStraightArrowGeometry(
1952
2080
  { x1: p.start.x, y1: p.start.y, x2: p.end.x, y2: p.end.y },
1953
- overlayStrokePx
2081
+ shapeWidth
1954
2082
  ) : null;
1955
2083
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1956
2084
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1958,9 +2086,9 @@ function NativeInteractionOverlay({
1958
2086
  {
1959
2087
  p1: reactNativeSkia.vec(p.start.x, p.start.y),
1960
2088
  p2: reactNativeSkia.vec(geometry?.shaftEndX ?? p.end.x, geometry?.shaftEndY ?? p.end.y),
1961
- color: "#64748b",
2089
+ color: shapeColor,
1962
2090
  style: "stroke",
1963
- strokeWidth: overlayStrokePx,
2091
+ strokeWidth: shapeWidth,
1964
2092
  strokeCap: "round",
1965
2093
  antiAlias: true
1966
2094
  }
@@ -1969,9 +2097,9 @@ function NativeInteractionOverlay({
1969
2097
  reactNativeSkia.Path,
1970
2098
  {
1971
2099
  path: `M ${geometry.headLeftX} ${geometry.headLeftY} L ${geometry.headTipX} ${geometry.headTipY} L ${geometry.headRightX} ${geometry.headRightY}`,
1972
- color: "#64748b",
2100
+ color: shapeColor,
1973
2101
  style: "stroke",
1974
- strokeWidth: overlayStrokePx,
2102
+ strokeWidth: shapeWidth,
1975
2103
  strokeCap: "round",
1976
2104
  strokeJoin: "round",
1977
2105
  antiAlias: true
@@ -1980,10 +2108,15 @@ function NativeInteractionOverlay({
1980
2108
  ] });
1981
2109
  }
1982
2110
  if (p.kind === "stroke" && p.points.length >= 1) {
2111
+ const isLaser = p.tool === "laser";
2112
+ const style = resolveNativeStrokePreviewStyle(
2113
+ p.tool,
2114
+ p.style ?? previewStrokeStyle
2115
+ );
1983
2116
  const payload = computeFreehandSvgPayload(
1984
2117
  p.points,
1985
- { stroke: "#64748b", strokeWidth: 3 },
1986
- "draw",
2118
+ style,
2119
+ isLaser ? "draw" : p.tool,
1987
2120
  p.points.length === 2
1988
2121
  );
1989
2122
  if (!payload) return null;
@@ -1994,33 +2127,47 @@ function NativeInteractionOverlay({
1994
2127
  cx: payload.cx,
1995
2128
  cy: payload.cy,
1996
2129
  r: payload.r,
1997
- color: payload.fill,
2130
+ color: colorWithOpacity(payload.fill, payload.fillOpacity),
1998
2131
  style: "fill",
1999
2132
  antiAlias: true
2000
2133
  }
2001
2134
  );
2002
2135
  }
2003
2136
  if (payload.kind === "fillPath") {
2004
- return /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.Path, { path: payload.d, color: payload.fill, style: "fill", antiAlias: true });
2137
+ return /* @__PURE__ */ jsxRuntime.jsx(
2138
+ reactNativeSkia.Path,
2139
+ {
2140
+ path: payload.d,
2141
+ color: colorWithOpacity(payload.fill, payload.fillOpacity),
2142
+ style: "fill",
2143
+ fillType: "winding",
2144
+ antiAlias: true
2145
+ }
2146
+ );
2005
2147
  }
2006
2148
  if (payload.kind === "strokePath") {
2149
+ const intervals = dashIntervalsFromStrokeDasharray(payload.strokeDasharray);
2007
2150
  return /* @__PURE__ */ jsxRuntime.jsx(
2008
2151
  reactNativeSkia.Path,
2009
2152
  {
2010
2153
  path: payload.d,
2011
- color: "#64748b",
2154
+ color: colorWithOpacity(
2155
+ payload.stroke,
2156
+ isLaser ? 0.85 : payload.strokeOpacity
2157
+ ),
2012
2158
  style: "stroke",
2013
- strokeWidth: 3,
2159
+ strokeWidth: payload.strokeWidth,
2014
2160
  strokeCap: "round",
2015
2161
  strokeJoin: "round",
2016
- antiAlias: true
2162
+ antiAlias: true,
2163
+ children: intervals ? /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.DashPathEffect, { intervals }) : null
2017
2164
  }
2018
2165
  );
2019
2166
  }
2020
2167
  return null;
2021
2168
  }
2022
2169
  return null;
2023
- }, [placementPreview]);
2170
+ }, [placementPreview, previewStrokeStyle, overlayStrokeWorld, marqueeDashWorld]);
2024
2171
  const eraserPreviewElements = react.useMemo(() => {
2025
2172
  if (eraserPreviewItems.length === 0) return null;
2026
2173
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: eraserPreviewItems.map((it) => {
@@ -2041,59 +2188,71 @@ function NativeInteractionOverlay({
2041
2188
  }, [eraserPreviewItems]);
2042
2189
  const eraserTrailElements = react.useMemo(() => {
2043
2190
  if (!eraserTrail || eraserTrail.length < 1) return null;
2044
- const d = pointsToSmoothPathD(eraserTrail);
2045
- if (!d)
2046
- return eraserTrail[0] ? /* @__PURE__ */ jsxRuntime.jsx(
2191
+ const now = Date.now();
2192
+ const alive = freshTimedTrailPoints(eraserTrail, now, ERASER_TRAIL_MAX_AGE_MS);
2193
+ if (alive.length === 0) return null;
2194
+ const d = pointsToSmoothPathD(alive);
2195
+ const newest = alive[alive.length - 1];
2196
+ const headOpacity = timedTrailHeadOpacity(newest, now, ERASER_TRAIL_MAX_AGE_MS);
2197
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2198
+ alive.length >= 2 && d ? /* @__PURE__ */ jsxRuntime.jsx(
2199
+ reactNativeSkia.Path,
2200
+ {
2201
+ path: d,
2202
+ color: colorWithOpacity(ERASER_TINT, ERASER_TINT_OPACITY),
2203
+ style: "stroke",
2204
+ strokeWidth: Math.max(3.5, OVERLAY_STROKE_PX) / z,
2205
+ strokeCap: "round",
2206
+ strokeJoin: "round",
2207
+ antiAlias: true
2208
+ }
2209
+ ) : null,
2210
+ /* @__PURE__ */ jsxRuntime.jsx(
2047
2211
  reactNativeSkia.Circle,
2048
2212
  {
2049
- cx: eraserTrail[0].x,
2050
- cy: eraserTrail[0].y,
2213
+ cx: newest?.x ?? alive[0]?.x ?? 0,
2214
+ cy: newest?.y ?? alive[0]?.y ?? 0,
2051
2215
  r: Math.max(5 / z, 3),
2052
- color: "#cbd5e1",
2216
+ color: colorWithOpacity(ERASER_TINT, headOpacity),
2053
2217
  style: "fill",
2054
2218
  antiAlias: true
2055
2219
  }
2056
- ) : null;
2057
- return /* @__PURE__ */ jsxRuntime.jsx(
2058
- reactNativeSkia.Path,
2059
- {
2060
- path: d,
2061
- color: "#cbd5e1",
2062
- style: "stroke",
2063
- strokeWidth: Math.max(3.5 / z, overlayStrokePx),
2064
- strokeCap: "round",
2065
- strokeJoin: "round",
2066
- antiAlias: true
2067
- }
2068
- );
2220
+ )
2221
+ ] });
2069
2222
  }, [eraserTrail, z]);
2070
2223
  const laserTrailElements = react.useMemo(() => {
2071
2224
  if (!laserTrail || laserTrail.length < 1) return null;
2072
- const d = pointsToSmoothPathD(laserTrail);
2073
- if (!d)
2074
- return laserTrail[0] ? /* @__PURE__ */ jsxRuntime.jsx(
2225
+ const now = Date.now();
2226
+ const alive = freshTimedTrailPoints(laserTrail, now, LASER_TRAIL_MAX_AGE_MS);
2227
+ if (alive.length === 0) return null;
2228
+ const d = pointsToSmoothPathD(alive);
2229
+ const newest = alive[alive.length - 1];
2230
+ const headOpacity = timedTrailHeadOpacity(newest, now, LASER_TRAIL_MAX_AGE_MS);
2231
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2232
+ alive.length >= 2 && d ? /* @__PURE__ */ jsxRuntime.jsx(
2233
+ reactNativeSkia.Path,
2234
+ {
2235
+ path: d,
2236
+ color: colorWithOpacity(LASER_TINT, LASER_TINT_OPACITY),
2237
+ style: "stroke",
2238
+ strokeWidth: Math.max(4, OVERLAY_STROKE_PX) / z,
2239
+ strokeCap: "round",
2240
+ strokeJoin: "round",
2241
+ antiAlias: true
2242
+ }
2243
+ ) : null,
2244
+ /* @__PURE__ */ jsxRuntime.jsx(
2075
2245
  reactNativeSkia.Circle,
2076
2246
  {
2077
- cx: laserTrail[0].x,
2078
- cy: laserTrail[0].y,
2247
+ cx: newest?.x ?? alive[0]?.x ?? 0,
2248
+ cy: newest?.y ?? alive[0]?.y ?? 0,
2079
2249
  r: Math.max(5 / z, 3),
2080
- color: "#f43f5e",
2250
+ color: colorWithOpacity(LASER_TINT, headOpacity),
2081
2251
  style: "fill",
2082
2252
  antiAlias: true
2083
2253
  }
2084
- ) : null;
2085
- return /* @__PURE__ */ jsxRuntime.jsx(
2086
- reactNativeSkia.Path,
2087
- {
2088
- path: d,
2089
- color: "#f43f5e",
2090
- style: "stroke",
2091
- strokeWidth: Math.max(4 / z, overlayStrokePx),
2092
- strokeCap: "round",
2093
- strokeJoin: "round",
2094
- antiAlias: true
2095
- }
2096
- );
2254
+ )
2255
+ ] });
2097
2256
  }, [laserTrail, z]);
2098
2257
  if (width <= 0 || height <= 0) return null;
2099
2258
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -2239,6 +2398,383 @@ function NativeSceneRenderer({
2239
2398
  if (width <= 0 || height <= 0) return null;
2240
2399
  return /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.Canvas, { style: { width, height }, children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.Group, { transform: cameraTransform, children: visible.map((item) => /* @__PURE__ */ jsxRuntime.jsx(MemoShape, { item }, item.id)) }) });
2241
2400
  }
2401
+
2402
+ // src/native/native-style-inspector-values.ts
2403
+ var NATIVE_STYLE_PALETTE = [
2404
+ { name: "black", hex: "#1d1d1d" },
2405
+ { name: "grey", hex: "#9fa8b2" },
2406
+ { name: "light-violet", hex: "#e085f4" },
2407
+ { name: "violet", hex: "#ae3ec9" },
2408
+ { name: "blue", hex: "#4263eb" },
2409
+ { name: "light-blue", hex: "#4dabf7" },
2410
+ { name: "yellow", hex: "#ffc078" },
2411
+ { name: "orange", hex: "#f76707" },
2412
+ { name: "green", hex: "#099268" },
2413
+ { name: "light-green", hex: "#40c057" },
2414
+ { name: "light-red", hex: "#ff8787" },
2415
+ { name: "red", hex: "#e03131" }
2416
+ ];
2417
+ function normalizeNativeStyleHex(stroke) {
2418
+ if (stroke && /^#[0-9A-Fa-f]{6}$/.test(stroke)) return stroke;
2419
+ return "#1d1d1d";
2420
+ }
2421
+ function nativeStyleColorWithOpacity(hex, opacity) {
2422
+ const alpha = opacity ?? 1;
2423
+ if (alpha >= 1) return hex;
2424
+ const r = Number.parseInt(hex.slice(1, 3), 16);
2425
+ const g = Number.parseInt(hex.slice(3, 5), 16);
2426
+ const b = Number.parseInt(hex.slice(5, 7), 16);
2427
+ return `rgba(${r},${g},${b},${alpha})`;
2428
+ }
2429
+ function hexesEqual(a, b) {
2430
+ return a.toLowerCase() === b.toLowerCase();
2431
+ }
2432
+ function DashPreview({
2433
+ color,
2434
+ width,
2435
+ dashed = false
2436
+ }) {
2437
+ const strokeHeight = Math.max(3, Math.min(6, width));
2438
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.dashPreviewTrack, children: [
2439
+ /* @__PURE__ */ jsxRuntime.jsx(
2440
+ reactNative.View,
2441
+ {
2442
+ style: [
2443
+ styles.dashPreviewStroke,
2444
+ {
2445
+ width: dashed ? 11 : 26,
2446
+ height: strokeHeight,
2447
+ borderRadius: strokeHeight / 2,
2448
+ backgroundColor: color
2449
+ }
2450
+ ]
2451
+ }
2452
+ ),
2453
+ dashed ? /* @__PURE__ */ jsxRuntime.jsx(
2454
+ reactNative.View,
2455
+ {
2456
+ style: [
2457
+ styles.dashPreviewStroke,
2458
+ {
2459
+ width: 11,
2460
+ height: strokeHeight,
2461
+ borderRadius: strokeHeight / 2,
2462
+ backgroundColor: color
2463
+ }
2464
+ ]
2465
+ }
2466
+ ) : null
2467
+ ] });
2468
+ }
2469
+ function clampValue(value, min, max) {
2470
+ return Math.min(max, Math.max(min, value));
2471
+ }
2472
+ function RangeControl({
2473
+ value,
2474
+ min,
2475
+ max,
2476
+ onChange,
2477
+ valueLabel
2478
+ }) {
2479
+ const [trackWidth, setTrackWidth] = react.useState(1);
2480
+ const normalized = (clampValue(value, min, max) - min) / (max - min);
2481
+ const updateFromX = react.useCallback(
2482
+ (x) => {
2483
+ const next = min + clampValue(x / trackWidth, 0, 1) * (max - min);
2484
+ onChange(Math.round(next));
2485
+ },
2486
+ [max, min, onChange, trackWidth]
2487
+ );
2488
+ const panResponder = react.useMemo(
2489
+ () => reactNative.PanResponder.create({
2490
+ onMoveShouldSetPanResponder: () => true,
2491
+ onStartShouldSetPanResponder: () => true,
2492
+ onPanResponderGrant: (event) => {
2493
+ updateFromX(event.nativeEvent.locationX);
2494
+ },
2495
+ onPanResponderMove: (event) => {
2496
+ updateFromX(event.nativeEvent.locationX);
2497
+ }
2498
+ }),
2499
+ [updateFromX]
2500
+ );
2501
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.rangeRow, children: [
2502
+ /* @__PURE__ */ jsxRuntime.jsxs(
2503
+ reactNative.View,
2504
+ {
2505
+ style: styles.rangeTrack,
2506
+ onLayout: (event) => setTrackWidth(event.nativeEvent.layout.width),
2507
+ ...panResponder.panHandlers,
2508
+ children: [
2509
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.rangeBase }),
2510
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.rangeFill, { width: `${normalized * 100}%` }] }),
2511
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.rangeThumb, { left: `${normalized * 100}%` }] })
2512
+ ]
2513
+ }
2514
+ ),
2515
+ valueLabel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.rangeValue, children: valueLabel }) : null
2516
+ ] });
2517
+ }
2518
+ function InspectorSection({
2519
+ label,
2520
+ children
2521
+ }) {
2522
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.section, children: [
2523
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.sectionLabel, children: label }),
2524
+ children
2525
+ ] });
2526
+ }
2527
+ function SegmentControl({
2528
+ segments,
2529
+ value,
2530
+ onChange
2531
+ }) {
2532
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.segmentGroup, children: segments.map((segment) => {
2533
+ const selected = segment.value === value;
2534
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2535
+ reactNative.Pressable,
2536
+ {
2537
+ accessibilityRole: "button",
2538
+ accessibilityState: { selected },
2539
+ accessibilityLabel: segment.label,
2540
+ onPress: () => onChange(segment.value),
2541
+ style: [styles.segment, selected ? styles.segmentSelected : null],
2542
+ children: [
2543
+ segment.preview,
2544
+ /* @__PURE__ */ jsxRuntime.jsx(
2545
+ reactNative.Text,
2546
+ {
2547
+ style: [
2548
+ styles.segmentLabel,
2549
+ selected ? styles.segmentLabelSelected : null
2550
+ ],
2551
+ children: segment.label
2552
+ }
2553
+ )
2554
+ ]
2555
+ },
2556
+ String(segment.value)
2557
+ );
2558
+ }) });
2559
+ }
2560
+ function NativeVectorStyleInspector({
2561
+ toolId,
2562
+ value,
2563
+ onChange,
2564
+ style
2565
+ }) {
2566
+ const hex = normalizeNativeStyleHex(value.stroke);
2567
+ const opacity = toolId === "marker" ? value.strokeOpacity ?? 0.5 : void 0;
2568
+ const dashSegments = [
2569
+ {
2570
+ value: "solid",
2571
+ label: "Cont\xEDnuo",
2572
+ preview: /* @__PURE__ */ jsxRuntime.jsx(DashPreview, { color: "#18181b", width: 4 })
2573
+ },
2574
+ {
2575
+ value: "dashed",
2576
+ label: "Tracejado",
2577
+ preview: /* @__PURE__ */ jsxRuntime.jsx(DashPreview, { color: "#18181b", width: 4, dashed: true })
2578
+ }
2579
+ ];
2580
+ const opacityPercent = Math.round((opacity ?? 1) * 100);
2581
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2582
+ reactNative.View,
2583
+ {
2584
+ pointerEvents: "auto",
2585
+ style: [styles.shell, style],
2586
+ accessibilityRole: "summary",
2587
+ accessibilityLabel: toolId === "marker" ? "Configura\xE7\xF5es do marcador" : "Configura\xE7\xF5es da caneta",
2588
+ children: [
2589
+ /* @__PURE__ */ jsxRuntime.jsx(InspectorSection, { label: "Cor", children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.palette, children: NATIVE_STYLE_PALETTE.map((color) => {
2590
+ const selected = hexesEqual(color.hex, hex);
2591
+ return /* @__PURE__ */ jsxRuntime.jsx(
2592
+ reactNative.Pressable,
2593
+ {
2594
+ accessibilityRole: "button",
2595
+ accessibilityState: { selected },
2596
+ accessibilityLabel: color.name,
2597
+ onPress: () => onChange({ stroke: color.hex }),
2598
+ style: [
2599
+ styles.swatch,
2600
+ {
2601
+ backgroundColor: color.hex,
2602
+ borderWidth: selected ? 2 : 1,
2603
+ transform: [{ scale: selected ? 1.08 : 1 }],
2604
+ shadowOpacity: selected ? 0.08 : 0
2605
+ }
2606
+ ]
2607
+ },
2608
+ color.name
2609
+ );
2610
+ }) }) }),
2611
+ /* @__PURE__ */ jsxRuntime.jsx(InspectorSection, { label: "Grossura", children: /* @__PURE__ */ jsxRuntime.jsx(
2612
+ RangeControl,
2613
+ {
2614
+ value: value.strokeWidth,
2615
+ min: 1,
2616
+ max: 48,
2617
+ onChange: (strokeWidth) => onChange({ strokeWidth })
2618
+ }
2619
+ ) }),
2620
+ toolId === "draw" ? /* @__PURE__ */ jsxRuntime.jsx(InspectorSection, { label: "Tra\xE7o", children: /* @__PURE__ */ jsxRuntime.jsx(
2621
+ SegmentControl,
2622
+ {
2623
+ segments: dashSegments,
2624
+ value: value.strokeDash === "dashed" ? "dashed" : "solid",
2625
+ onChange: (strokeDash) => onChange({ strokeDash })
2626
+ }
2627
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(InspectorSection, { label: "Opacidade", children: /* @__PURE__ */ jsxRuntime.jsx(
2628
+ RangeControl,
2629
+ {
2630
+ value: opacityPercent,
2631
+ min: 10,
2632
+ max: 100,
2633
+ valueLabel: `${opacityPercent}%`,
2634
+ onChange: (nextOpacity) => onChange({ strokeOpacity: nextOpacity / 100 })
2635
+ }
2636
+ ) })
2637
+ ]
2638
+ }
2639
+ );
2640
+ }
2641
+ var styles = reactNative.StyleSheet.create({
2642
+ shell: {
2643
+ minWidth: 240,
2644
+ paddingHorizontal: 14,
2645
+ paddingVertical: 12,
2646
+ borderRadius: 10,
2647
+ borderWidth: 1,
2648
+ borderColor: "rgba(0,0,0,0.1)",
2649
+ backgroundColor: "rgba(255,255,255,0.97)",
2650
+ shadowColor: "#000000",
2651
+ shadowOpacity: 0.06,
2652
+ shadowRadius: 16,
2653
+ shadowOffset: { width: 0, height: 4 },
2654
+ gap: 10
2655
+ },
2656
+ section: {
2657
+ gap: 6
2658
+ },
2659
+ sectionLabel: {
2660
+ fontSize: 11,
2661
+ fontWeight: "600",
2662
+ color: "#52525b",
2663
+ textTransform: "uppercase",
2664
+ letterSpacing: 0.22
2665
+ },
2666
+ palette: {
2667
+ flexDirection: "row",
2668
+ flexWrap: "wrap",
2669
+ columnGap: 6,
2670
+ rowGap: 6,
2671
+ maxWidth: 174
2672
+ },
2673
+ swatch: {
2674
+ width: 24,
2675
+ height: 24,
2676
+ borderRadius: 12,
2677
+ borderColor: "rgba(0,0,0,0.12)",
2678
+ shadowColor: "#000000",
2679
+ shadowRadius: 2,
2680
+ shadowOffset: { width: 0, height: 1 }
2681
+ },
2682
+ rangeRow: {
2683
+ minHeight: 28,
2684
+ flexDirection: "row",
2685
+ alignItems: "center",
2686
+ gap: 8
2687
+ },
2688
+ rangeTrack: {
2689
+ position: "relative",
2690
+ flex: 1,
2691
+ height: 28,
2692
+ justifyContent: "center"
2693
+ },
2694
+ rangeBase: {
2695
+ position: "absolute",
2696
+ left: 0,
2697
+ right: 0,
2698
+ height: 3,
2699
+ borderRadius: 2,
2700
+ backgroundColor: "rgba(24,24,27,0.12)"
2701
+ },
2702
+ rangeFill: {
2703
+ position: "absolute",
2704
+ left: 0,
2705
+ height: 3,
2706
+ borderRadius: 2,
2707
+ backgroundColor: "#18181b"
2708
+ },
2709
+ rangeThumb: {
2710
+ position: "absolute",
2711
+ width: 18,
2712
+ height: 18,
2713
+ marginLeft: -9,
2714
+ borderRadius: 9,
2715
+ borderWidth: 2,
2716
+ borderColor: "#18181b",
2717
+ backgroundColor: "#ffffff",
2718
+ shadowColor: "#000000",
2719
+ shadowOpacity: 0.12,
2720
+ shadowRadius: 3,
2721
+ shadowOffset: { width: 0, height: 1 }
2722
+ },
2723
+ rangeValue: {
2724
+ minWidth: 34,
2725
+ textAlign: "right",
2726
+ fontSize: 11,
2727
+ fontWeight: "500",
2728
+ color: "#71717a"
2729
+ },
2730
+ segmentGroup: {
2731
+ flexDirection: "row",
2732
+ alignItems: "center",
2733
+ gap: 3,
2734
+ padding: 3,
2735
+ borderRadius: 8,
2736
+ backgroundColor: "rgba(24,24,27,0.06)"
2737
+ },
2738
+ segment: {
2739
+ flex: 1,
2740
+ minWidth: 96,
2741
+ minHeight: 32,
2742
+ flexDirection: "row",
2743
+ alignItems: "center",
2744
+ justifyContent: "center",
2745
+ gap: 6,
2746
+ paddingHorizontal: 8,
2747
+ paddingVertical: 5,
2748
+ borderRadius: 6
2749
+ },
2750
+ segmentSelected: {
2751
+ backgroundColor: "#ffffff",
2752
+ shadowColor: "#000000",
2753
+ shadowOpacity: 0.1,
2754
+ shadowRadius: 2,
2755
+ shadowOffset: { width: 0, height: 1 }
2756
+ },
2757
+ segmentLabel: {
2758
+ fontSize: 11,
2759
+ fontWeight: "600",
2760
+ color: "#71717a",
2761
+ flexShrink: 0
2762
+ },
2763
+ segmentLabelSelected: {
2764
+ color: "#18181b"
2765
+ },
2766
+ dashPreviewTrack: {
2767
+ width: 26,
2768
+ height: 6,
2769
+ flexDirection: "row",
2770
+ alignItems: "center",
2771
+ justifyContent: "center",
2772
+ gap: 3
2773
+ },
2774
+ dashPreviewStroke: {
2775
+ flexShrink: 0
2776
+ }
2777
+ });
2242
2778
  var DEFAULT_NATIVE_OVERFLOW_TOOL_IDS = [
2243
2779
  "rect",
2244
2780
  "ellipse",
@@ -2370,7 +2906,7 @@ function NativeVectorToolbar({
2370
2906
  reactNative.View,
2371
2907
  {
2372
2908
  accessibilityLabel,
2373
- style: [styles.shell, style],
2909
+ style: [styles2.shell, style],
2374
2910
  pointerEvents: "box-none",
2375
2911
  children: [
2376
2912
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -2379,8 +2915,8 @@ function NativeVectorToolbar({
2379
2915
  horizontal: true,
2380
2916
  showsHorizontalScrollIndicator: false,
2381
2917
  contentContainerStyle: [
2382
- styles.content,
2383
- density === "comfortable" ? styles.comfortableContent : void 0,
2918
+ styles2.content,
2919
+ density === "comfortable" ? styles2.comfortableContent : void 0,
2384
2920
  contentContainerStyle
2385
2921
  ],
2386
2922
  children: [
@@ -2397,21 +2933,21 @@ function NativeVectorToolbar({
2397
2933
  disabled: toolLockDisabled,
2398
2934
  onPress: toggleToolLock,
2399
2935
  style: ({ pressed }) => [
2400
- styles.toolButton,
2401
- density === "comfortable" ? styles.comfortableToolButton : void 0,
2402
- toolLocked ? styles.activeToolButton : void 0,
2403
- pressed && !toolLockDisabled ? styles.pressedToolButton : void 0,
2404
- toolLockDisabled ? styles.disabledToolButton : void 0
2936
+ styles2.toolButton,
2937
+ density === "comfortable" ? styles2.comfortableToolButton : void 0,
2938
+ toolLocked ? styles2.activeToolButton : void 0,
2939
+ pressed && !toolLockDisabled ? styles2.pressedToolButton : void 0,
2940
+ toolLockDisabled ? styles2.disabledToolButton : void 0
2405
2941
  ],
2406
2942
  children: renderToolLockIcon?.({
2407
2943
  locked: toolLocked,
2408
2944
  disabled: toolLockDisabled,
2409
2945
  foregroundColor: "#18181b",
2410
2946
  onToggle: toggleToolLock
2411
- }) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.lockGlyph, children: toolLocked ? "L" : "U" })
2947
+ }) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.lockGlyph, children: toolLocked ? "L" : "U" })
2412
2948
  }
2413
2949
  ),
2414
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.toolLockDivider })
2950
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.toolLockDivider })
2415
2951
  ] }) : null,
2416
2952
  toolbarTools.map(
2417
2953
  (tool) => renderNativeToolButton({
@@ -2429,7 +2965,7 @@ function NativeVectorToolbar({
2429
2965
  renderToolButton
2430
2966
  })
2431
2967
  ),
2432
- showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.overflowSpacer }) : null,
2968
+ showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.overflowSpacer }) : null,
2433
2969
  showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsxs(
2434
2970
  reactNative.Pressable,
2435
2971
  {
@@ -2443,27 +2979,27 @@ function NativeVectorToolbar({
2443
2979
  disabled,
2444
2980
  onPress: toggleOverflow,
2445
2981
  style: ({ pressed }) => [
2446
- styles.overflowTrigger,
2447
- overflowOpen || activeOverflowTool ? styles.activeToolButton : void 0,
2448
- pressed && !disabled ? styles.pressedToolButton : void 0,
2449
- disabled ? styles.disabledToolButton : void 0
2982
+ styles2.overflowTrigger,
2983
+ overflowOpen || activeOverflowTool ? styles2.activeToolButton : void 0,
2984
+ pressed && !disabled ? styles2.pressedToolButton : void 0,
2985
+ disabled ? styles2.disabledToolButton : void 0
2450
2986
  ],
2451
2987
  children: [
2452
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.iconSlot, children: activeOverflowTool ? renderToolIcon?.({
2988
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.iconSlot, children: activeOverflowTool ? renderToolIcon?.({
2453
2989
  tool: activeOverflowTool,
2454
2990
  selected: true,
2455
2991
  disabled,
2456
2992
  foregroundColor: "#18181b",
2457
2993
  onSelect: () => onChange(activeOverflowTool.id)
2458
- }) ?? renderNativeToolFallback(activeOverflowTool, "#18181b") : renderOverflowIcon?.(overflowRenderInput) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.shapesGlyph, children: "S" }) }),
2459
- renderOverflowChevronIcon?.(overflowRenderInput) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.chevronGlyph, children: overflowOpen ? "^" : "v" })
2994
+ }) ?? renderNativeToolFallback(activeOverflowTool, "#18181b") : renderOverflowIcon?.(overflowRenderInput) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.shapesGlyph, children: "S" }) }),
2995
+ renderOverflowChevronIcon?.(overflowRenderInput) ?? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.chevronGlyph, children: overflowOpen ? "^" : "v" })
2460
2996
  ]
2461
2997
  }
2462
2998
  ) : null
2463
2999
  ]
2464
3000
  }
2465
3001
  ),
2466
- overflowOpen && showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.overflowPanel, overflowPanelStyle], children: remainingOverflowTools.map(
3002
+ overflowOpen && showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles2.overflowPanel, overflowPanelStyle], children: remainingOverflowTools.map(
2467
3003
  (tool) => renderNativeToolButton({
2468
3004
  tool,
2469
3005
  value,
@@ -2474,7 +3010,7 @@ function NativeVectorToolbar({
2474
3010
  disabled,
2475
3011
  disabledIds,
2476
3012
  density: "compact",
2477
- toolButtonStyle: styles.overflowToolButton,
3013
+ toolButtonStyle: styles2.overflowToolButton,
2478
3014
  activeToolButtonStyle,
2479
3015
  toolLabelStyle,
2480
3016
  activeToolLabelStyle,
@@ -2515,22 +3051,22 @@ function renderNativeToolButton(input) {
2515
3051
  disabled: toolDisabled,
2516
3052
  onPress: onSelect,
2517
3053
  style: ({ pressed }) => [
2518
- styles.toolButton,
2519
- input.density === "comfortable" ? styles.comfortableToolButton : void 0,
3054
+ styles2.toolButton,
3055
+ input.density === "comfortable" ? styles2.comfortableToolButton : void 0,
2520
3056
  input.toolButtonStyle,
2521
- selected ? styles.activeToolButton : void 0,
3057
+ selected ? styles2.activeToolButton : void 0,
2522
3058
  selected ? input.activeToolButtonStyle : void 0,
2523
- pressed && !toolDisabled ? styles.pressedToolButton : void 0,
2524
- toolDisabled ? styles.disabledToolButton : void 0
3059
+ pressed && !toolDisabled ? styles2.pressedToolButton : void 0,
3060
+ toolDisabled ? styles2.disabledToolButton : void 0
2525
3061
  ],
2526
3062
  children: [
2527
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.iconSlot, children: icon }),
3063
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.iconSlot, children: icon }),
2528
3064
  input.density === "comfortable" ? /* @__PURE__ */ jsxRuntime.jsx(
2529
3065
  reactNative.Text,
2530
3066
  {
2531
3067
  numberOfLines: 1,
2532
3068
  style: [
2533
- styles.toolLabel,
3069
+ styles2.toolLabel,
2534
3070
  { color: foregroundColor },
2535
3071
  input.toolLabelStyle,
2536
3072
  selected ? input.activeToolLabelStyle : void 0
@@ -2544,9 +3080,9 @@ function renderNativeToolButton(input) {
2544
3080
  );
2545
3081
  }
2546
3082
  function renderNativeToolFallback(tool, foregroundColor, toolLabelStyle) {
2547
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.shortLabel, { color: foregroundColor }, toolLabelStyle], children: tool.shortLabel ?? tool.label.slice(0, 1).toUpperCase() });
3083
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.shortLabel, { color: foregroundColor }, toolLabelStyle], children: tool.shortLabel ?? tool.label.slice(0, 1).toUpperCase() });
2548
3084
  }
2549
- var styles = reactNative.StyleSheet.create({
3085
+ var styles2 = reactNative.StyleSheet.create({
2550
3086
  shell: {
2551
3087
  borderRadius: 8,
2552
3088
  borderWidth: reactNative.StyleSheet.hairlineWidth,
@@ -2871,9 +3407,27 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
2871
3407
  var MIN_PLACE_SIZE = 8;
2872
3408
  var MIN_ARROW_DRAG_PX = 8;
2873
3409
  var TAP_PX = 20;
3410
+ var MARKER_TOOL_STYLE = {
3411
+ stroke: "#fde047",
3412
+ strokeWidth: 16,
3413
+ strokeOpacity: 0.5
3414
+ };
2874
3415
  function isPlacementTool(toolId) {
2875
3416
  return toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud" || toolId === "line" || toolId === "arrow";
2876
3417
  }
3418
+ function isDefaultMarkerToolStyle(style) {
3419
+ return style.stroke === MARKER_TOOL_STYLE.stroke && style.strokeWidth === MARKER_TOOL_STYLE.strokeWidth && style.strokeOpacity === MARKER_TOOL_STYLE.strokeOpacity;
3420
+ }
3421
+ function supportsNativeResizeHandles(item) {
3422
+ const k = item?.toolKind;
3423
+ if (k === "rect" || k === "ellipse" || k === "architectural-cloud" || k === "line" || k === "arrow" || k === "image" || k === "text") {
3424
+ return true;
3425
+ }
3426
+ if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item?.pathPointsLocal && item.pathPointsLocal.length > 0) {
3427
+ return true;
3428
+ }
3429
+ return k === "custom" && !!item?.customIntrinsicSize && !!item?.customInnerSvg;
3430
+ }
2877
3431
  function placementPreviewForTool(toolId, start, end) {
2878
3432
  if (toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud") {
2879
3433
  return { kind: toolId, rect: rectFromCorners(start, end) };
@@ -2936,7 +3490,9 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2936
3490
  onItemsChange,
2937
3491
  onToolChangeRequest,
2938
3492
  onCameraChange,
2939
- toolbar
3493
+ toolbar,
3494
+ showStyleInspector = false,
3495
+ styleInspectorPlacement = "bottom"
2940
3496
  }, ref) {
2941
3497
  const [size, setSize] = react.useState({ width: 0, height: 0 });
2942
3498
  const cameraRef = react.useRef(null);
@@ -2963,6 +3519,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2963
3519
  const [eraserTrail, setEraserTrail] = react.useState([]);
2964
3520
  const [laserTrail, setLaserTrail] = react.useState([]);
2965
3521
  const laserClearTimerRef = react.useRef(null);
3522
+ const strokeStyleRef = react.useRef({ ...DEFAULT_STROKE_STYLE });
3523
+ const markerStrokeStyleRef = react.useRef({ ...MARKER_TOOL_STYLE });
3524
+ const styleBeforeMarkerRef = react.useRef({ ...DEFAULT_STROKE_STYLE });
3525
+ const lastToolIdRef = react.useRef(toolId);
3526
+ const [strokeStyleState, setStrokeStyleState] = react.useState({
3527
+ ...DEFAULT_STROKE_STYLE
3528
+ });
2966
3529
  react.useEffect(
2967
3530
  () => () => {
2968
3531
  if (laserClearTimerRef.current) {
@@ -2971,6 +3534,60 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
2971
3534
  },
2972
3535
  []
2973
3536
  );
3537
+ react.useEffect(() => {
3538
+ if (eraserTrail.length === 0 && laserTrail.length === 0) return;
3539
+ const timer = setInterval(() => {
3540
+ const now = Date.now();
3541
+ setEraserTrail(
3542
+ (prev) => freshTimedTrailPoints(prev, now, ERASER_TRAIL_MAX_AGE_MS)
3543
+ );
3544
+ setLaserTrail(
3545
+ (prev) => freshTimedTrailPoints(prev, now, LASER_TRAIL_MAX_AGE_MS)
3546
+ );
3547
+ }, 50);
3548
+ return () => clearInterval(timer);
3549
+ }, [eraserTrail.length, laserTrail.length]);
3550
+ react.useEffect(() => {
3551
+ const previousToolId = lastToolIdRef.current;
3552
+ const current = strokeStyleRef.current;
3553
+ let next = current;
3554
+ if (toolId === "marker") {
3555
+ if (previousToolId !== "marker") {
3556
+ styleBeforeMarkerRef.current = current;
3557
+ }
3558
+ next = markerStrokeStyleRef.current;
3559
+ } else if (previousToolId === "marker") {
3560
+ markerStrokeStyleRef.current = current;
3561
+ const restored = styleBeforeMarkerRef.current;
3562
+ next = {
3563
+ stroke: restored.stroke,
3564
+ strokeWidth: toolId === "draw" && restored.strokeWidth === DEFAULT_STROKE_STYLE.strokeWidth ? 10 : restored.strokeWidth,
3565
+ ...restored.strokeDash != null ? { strokeDash: restored.strokeDash } : {}
3566
+ };
3567
+ } else if (toolId === "draw") {
3568
+ next = {
3569
+ ...current,
3570
+ strokeWidth: current.strokeWidth === DEFAULT_STROKE_STYLE.strokeWidth ? 10 : current.strokeWidth
3571
+ };
3572
+ } else if (isDefaultMarkerToolStyle(current)) {
3573
+ next = {
3574
+ stroke: DEFAULT_STROKE_STYLE.stroke,
3575
+ strokeWidth: DEFAULT_STROKE_STYLE.strokeWidth,
3576
+ ...current.strokeDash != null ? { strokeDash: current.strokeDash } : {}
3577
+ };
3578
+ }
3579
+ strokeStyleRef.current = next;
3580
+ setStrokeStyleState(next);
3581
+ lastToolIdRef.current = toolId;
3582
+ }, [toolId]);
3583
+ const patchCurrentStrokeStyle = react.useCallback((patch) => {
3584
+ const next = { ...strokeStyleRef.current, ...patch };
3585
+ strokeStyleRef.current = next;
3586
+ if (toolIdRef.current === "marker") {
3587
+ markerStrokeStyleRef.current = next;
3588
+ }
3589
+ setStrokeStyleState(next);
3590
+ }, []);
2974
3591
  const [eraserPreviewIds, setEraserPreviewIds] = react.useState([]);
2975
3592
  const eraserPreviewIdSetRef = react.useRef(/* @__PURE__ */ new Set());
2976
3593
  const requestSelectToolAfterUse = react.useCallback(() => {
@@ -3008,7 +3625,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3008
3625
  const hidden = new Set(eraserPreviewIds);
3009
3626
  return items.filter((it) => !hidden.has(it.id));
3010
3627
  }, [items, eraserPreviewIds]);
3011
- const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked;
3628
+ const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked && supportsNativeResizeHandles(selectedItems[0]);
3012
3629
  const lastPinchDist = react.useRef(null);
3013
3630
  const lastPanPoint = react.useRef(null);
3014
3631
  const panResponder = react.useMemo(
@@ -3083,7 +3700,14 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3083
3700
  clearTimeout(laserClearTimerRef.current);
3084
3701
  laserClearTimerRef.current = null;
3085
3702
  }
3086
- setLaserTrail([{ x: worldX, y: worldY }]);
3703
+ setLaserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
3704
+ } else {
3705
+ setPlacementPreview({
3706
+ kind: "stroke",
3707
+ tool,
3708
+ points: [{ x: worldX, y: worldY }],
3709
+ style: { ...strokeStyleRef.current }
3710
+ });
3087
3711
  }
3088
3712
  return;
3089
3713
  }
@@ -3091,7 +3715,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3091
3715
  dragStateRef.current = { kind: "erase" };
3092
3716
  eraserPreviewIdSetRef.current = /* @__PURE__ */ new Set();
3093
3717
  setEraserPreviewIds([]);
3094
- setEraserTrail([{ x: worldX, y: worldY }]);
3718
+ setEraserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
3095
3719
  const toErase = collectEraserTargetsAtWorldPoint(
3096
3720
  itemsRef.current,
3097
3721
  worldX,
@@ -3182,18 +3806,25 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3182
3806
  const last = pts[pts.length - 1];
3183
3807
  const dx = worldX - (last?.x ?? worldX);
3184
3808
  const dy = worldY - (last?.y ?? worldY);
3185
- if (Math.hypot(dx, dy) > 0.5 / cam.zoom) {
3809
+ const shouldAppendPoint = Math.hypot(dx, dy) > 0.5 / cam.zoom;
3810
+ if (shouldAppendPoint) {
3186
3811
  pts.push({ x: worldX, y: worldY });
3187
3812
  }
3188
3813
  if (st.tool === "laser") {
3189
- setLaserTrail([...pts]);
3190
- } else {
3191
- setPlacementPreview({
3192
- kind: "stroke",
3193
- tool: st.tool,
3194
- points: [...pts]
3195
- });
3814
+ if (shouldAppendPoint) {
3815
+ setLaserTrail((prev) => [
3816
+ ...prev,
3817
+ { x: worldX, y: worldY, t: Date.now() }
3818
+ ]);
3819
+ }
3820
+ return;
3196
3821
  }
3822
+ setPlacementPreview({
3823
+ kind: "stroke",
3824
+ tool: st.tool,
3825
+ points: [...pts],
3826
+ style: { ...strokeStyleRef.current }
3827
+ });
3197
3828
  return;
3198
3829
  }
3199
3830
  if (st.kind === "move") {
@@ -3231,7 +3862,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3231
3862
  return;
3232
3863
  }
3233
3864
  if (st.kind === "erase") {
3234
- setEraserTrail((prev) => [...prev, { x: worldX, y: worldY }]);
3865
+ setEraserTrail((prev) => [
3866
+ ...prev,
3867
+ { x: worldX, y: worldY, t: Date.now() }
3868
+ ]);
3235
3869
  const toErase = collectEraserTargetsAtWorldPoint(
3236
3870
  itemsRef.current,
3237
3871
  worldX,
@@ -3276,7 +3910,12 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3276
3910
  const change = onItemsChangeRef.current;
3277
3911
  if (!change) return;
3278
3912
  const id = createShapeId();
3279
- const item = createFreehandStrokeItem(id, st.points, st.tool);
3913
+ const item = createFreehandStrokeItem(
3914
+ id,
3915
+ st.points,
3916
+ st.tool,
3917
+ strokeStyleRef.current
3918
+ );
3280
3919
  if (item) {
3281
3920
  change([...itemsRef.current, item]);
3282
3921
  }
@@ -3353,26 +3992,33 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3353
3992
  }
3354
3993
  }
3355
3994
  const id = createShapeId();
3995
+ const style = strokeStyleRef.current;
3356
3996
  if (st.tool === "rect") {
3357
- change([...itemsRef.current, createRectangleItem(id, raw)]);
3997
+ change([...itemsRef.current, createRectangleItem(id, raw, style)]);
3358
3998
  onSelectionChangeRef.current?.([id]);
3359
3999
  requestSelectToolAfterUse();
3360
4000
  return;
3361
4001
  }
3362
4002
  if (st.tool === "ellipse") {
3363
- change([...itemsRef.current, createEllipseItem(id, raw)]);
4003
+ change([...itemsRef.current, createEllipseItem(id, raw, style)]);
3364
4004
  onSelectionChangeRef.current?.([id]);
3365
4005
  requestSelectToolAfterUse();
3366
4006
  return;
3367
4007
  }
3368
4008
  if (st.tool === "architectural-cloud") {
3369
- change([...itemsRef.current, createArchitecturalCloudItem(id, raw)]);
4009
+ change([
4010
+ ...itemsRef.current,
4011
+ createArchitecturalCloudItem(id, raw, style)
4012
+ ]);
3370
4013
  onSelectionChangeRef.current?.([id]);
3371
4014
  requestSelectToolAfterUse();
3372
4015
  return;
3373
4016
  }
3374
4017
  const line = lineEndpointsToLocal(br, lineStart, lineEnd);
3375
- change([...itemsRef.current, createLineItem(id, br, line, st.tool)]);
4018
+ change([
4019
+ ...itemsRef.current,
4020
+ createLineItem(id, br, line, st.tool, style)
4021
+ ]);
3376
4022
  onSelectionChangeRef.current?.([id]);
3377
4023
  requestSelectToolAfterUse();
3378
4024
  return;
@@ -3395,7 +4041,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3395
4041
  height: 26
3396
4042
  },
3397
4043
  "Text",
3398
- void 0,
4044
+ strokeStyleRef.current,
3399
4045
  18
3400
4046
  );
3401
4047
  change([...itemsRef.current, item]);
@@ -3431,6 +4077,9 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3431
4077
  dragStateRef.current = { kind: "idle" };
3432
4078
  setPlacementPreview(null);
3433
4079
  setLaserTrail([]);
4080
+ setEraserTrail([]);
4081
+ setEraserPreviewIds([]);
4082
+ eraserPreviewIdSetRef.current.clear();
3434
4083
  }
3435
4084
  }),
3436
4085
  [screenToWorld, requestRender, requestSelectToolAfterUse, interactive]
@@ -3456,6 +4105,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3456
4105
  }),
3457
4106
  [requestRender, size]
3458
4107
  );
4108
+ const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
3459
4109
  return /* @__PURE__ */ jsxRuntime.jsx(
3460
4110
  reactNative.View,
3461
4111
  {
@@ -3485,9 +4135,36 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3485
4135
  eraserTrail,
3486
4136
  eraserPreviewItems: items.filter(
3487
4137
  (it) => eraserPreviewIds.includes(it.id)
3488
- )
4138
+ ),
4139
+ previewStrokeStyle: strokeStyleState
3489
4140
  }
3490
4141
  ),
4142
+ interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsxRuntime.jsx(
4143
+ reactNative.View,
4144
+ {
4145
+ pointerEvents: "box-none",
4146
+ style: styleInspectorPlacement === "top-left" ? {
4147
+ position: "absolute",
4148
+ left: 16,
4149
+ top: 104,
4150
+ alignItems: "flex-start"
4151
+ } : {
4152
+ position: "absolute",
4153
+ left: 16,
4154
+ right: 16,
4155
+ bottom: 84,
4156
+ alignItems: "center"
4157
+ },
4158
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4159
+ NativeVectorStyleInspector,
4160
+ {
4161
+ toolId: activeStyleToolId,
4162
+ value: strokeStyleState,
4163
+ onChange: patchCurrentStrokeStyle
4164
+ }
4165
+ )
4166
+ }
4167
+ ) : null,
3491
4168
  toolbar && /* @__PURE__ */ jsxRuntime.jsx(
3492
4169
  reactNative.View,
3493
4170
  {
@@ -3511,14 +4188,18 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
3511
4188
 
3512
4189
  exports.DEFAULT_NATIVE_OVERFLOW_TOOL_IDS = DEFAULT_NATIVE_OVERFLOW_TOOL_IDS;
3513
4190
  exports.DEFAULT_NATIVE_VECTOR_TOOLS = DEFAULT_NATIVE_VECTOR_TOOLS;
4191
+ exports.NATIVE_STYLE_PALETTE = NATIVE_STYLE_PALETTE;
3514
4192
  exports.NativeInteractionOverlay = NativeInteractionOverlay;
3515
4193
  exports.NativeSceneRenderer = NativeSceneRenderer;
3516
4194
  exports.NativeShapeRenderer = NativeShapeRenderer;
4195
+ exports.NativeVectorStyleInspector = NativeVectorStyleInspector;
3517
4196
  exports.NativeVectorToolbar = NativeVectorToolbar;
3518
4197
  exports.NativeVectorViewport = NativeVectorViewport;
3519
4198
  exports.createFreehandStrokeItem = createFreehandStrokeItem;
3520
4199
  exports.createImageItem = createImageItem;
3521
4200
  exports.createShapeId = createShapeId;
4201
+ exports.nativeStyleColorWithOpacity = nativeStyleColorWithOpacity;
4202
+ exports.normalizeNativeStyleHex = normalizeNativeStyleHex;
3522
4203
  exports.parseSvgFragment = parseSvgFragment;
3523
4204
  //# sourceMappingURL=native.cjs.map
3524
4205
  //# sourceMappingURL=native.cjs.map