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