canvu-react 0.4.45 → 0.4.47

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,6 +1,6 @@
1
1
  import getStroke from 'perfect-freehand';
2
2
  import { Group, Canvas, Rect, Circle, Path, RoundedRect, Oval, DashPathEffect, Line, vec, matchFont, Text as Text$1, Image } from '@shopify/react-native-skia';
3
- import { memo, forwardRef, useState, useRef, useCallback, useEffect, useMemo, useImperativeHandle } from 'react';
3
+ import { memo, forwardRef, useState, useRef, useEffect, useCallback, useMemo, useImperativeHandle } from 'react';
4
4
  import { StyleSheet, PanResponder, View, Modal, Text, TextInput, Pressable, ScrollView, Image as Image$1 } from 'react-native';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
 
@@ -1863,6 +1863,63 @@ function smoothFreehandPointsToPathD(points) {
1863
1863
  return d;
1864
1864
  }
1865
1865
 
1866
+ // src/native/native-freehand-payload-cache.ts
1867
+ var MAX_NATIVE_FREEHAND_PAYLOAD_CACHE_SIZE = 800;
1868
+ var pathSignatureCache = /* @__PURE__ */ new WeakMap();
1869
+ var payloadCache = /* @__PURE__ */ new Map();
1870
+ function hashNumber(hash, value) {
1871
+ const normalizedValue = Math.round(value * 100);
1872
+ return Math.imul(hash ^ normalizedValue, 16777619) >>> 0;
1873
+ }
1874
+ function getPathPointsSignature(pathPointsLocal) {
1875
+ const cachedSignature = pathSignatureCache.get(pathPointsLocal);
1876
+ if (cachedSignature) return cachedSignature;
1877
+ const hash = pathPointsLocal.reduce((currentHash, point) => {
1878
+ const withX = hashNumber(currentHash, point.x);
1879
+ const withY = hashNumber(withX, point.y);
1880
+ return hashNumber(withY, point.pressure ?? -1);
1881
+ }, 2166136261);
1882
+ const signature = `${pathPointsLocal.length}:${hash}`;
1883
+ pathSignatureCache.set(pathPointsLocal, signature);
1884
+ return signature;
1885
+ }
1886
+ function getStyleSignature(style) {
1887
+ return [
1888
+ style.stroke,
1889
+ style.strokeWidth,
1890
+ style.strokeOpacity ?? "",
1891
+ style.strokeDash ?? ""
1892
+ ].join(":");
1893
+ }
1894
+ function getPayloadCacheKey(input) {
1895
+ return [
1896
+ input.itemId,
1897
+ input.toolKind,
1898
+ getStyleSignature(input.style),
1899
+ getPathPointsSignature(input.pathPointsLocal)
1900
+ ].join("|");
1901
+ }
1902
+ function storePayload(key, payload) {
1903
+ payloadCache.set(key, payload);
1904
+ if (payloadCache.size > MAX_NATIVE_FREEHAND_PAYLOAD_CACHE_SIZE) {
1905
+ const oldestKey = payloadCache.keys().next().value;
1906
+ if (oldestKey !== void 0) {
1907
+ payloadCache.delete(oldestKey);
1908
+ }
1909
+ }
1910
+ return payload;
1911
+ }
1912
+ function getNativeFreehandSvgPayload(input) {
1913
+ const key = getPayloadCacheKey(input);
1914
+ if (payloadCache.has(key)) {
1915
+ return payloadCache.get(key) ?? null;
1916
+ }
1917
+ return storePayload(
1918
+ key,
1919
+ computeFreehandSvgPayload(input.pathPointsLocal, input.style, input.toolKind)
1920
+ );
1921
+ }
1922
+
1866
1923
  // src/native/native-link-card.ts
1867
1924
  function linkHostname(href) {
1868
1925
  try {
@@ -2865,7 +2922,12 @@ function NativeShapeRenderer({ item }) {
2865
2922
  )) });
2866
2923
  }
2867
2924
  if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item.pathPointsLocal && item.pathPointsLocal.length > 0) {
2868
- const payload = computeFreehandSvgPayload(item.pathPointsLocal, style, k);
2925
+ const payload = getNativeFreehandSvgPayload({
2926
+ itemId: item.id,
2927
+ pathPointsLocal: item.pathPointsLocal,
2928
+ style,
2929
+ toolKind: k
2930
+ });
2869
2931
  if (!payload) return null;
2870
2932
  const color = rgba(style.stroke, style.strokeOpacity);
2871
2933
  if (payload.kind === "circle") {
@@ -2964,6 +3026,35 @@ function resolveNativeStrokePreviewStyle(tool, previewStrokeStyle) {
2964
3026
  };
2965
3027
  }
2966
3028
 
3029
+ // src/native/native-stroke-preview.ts
3030
+ var NATIVE_STROKE_PREVIEW_MAX_POINTS = 160;
3031
+ function sampleNativeStrokePreviewPoints(points, maxPoints = NATIVE_STROKE_PREVIEW_MAX_POINTS) {
3032
+ if (points.length <= maxPoints) return points.map((point) => ({ ...point }));
3033
+ if (maxPoints <= 1) {
3034
+ const last2 = points[points.length - 1];
3035
+ return last2 ? [{ ...last2 }] : [];
3036
+ }
3037
+ const lastIndex = points.length - 1;
3038
+ const last = points[lastIndex];
3039
+ if (!last) return [];
3040
+ const step = lastIndex / (maxPoints - 1);
3041
+ return Array.from({ length: maxPoints }, (_, index) => {
3042
+ const point = points[Math.round(index * step)] ?? last;
3043
+ return { x: point.x, y: point.y };
3044
+ });
3045
+ }
3046
+ function buildNativeStrokePreviewPath(points) {
3047
+ if (points.length < 2) return null;
3048
+ const d = smoothFreehandPointsToPathD(points);
3049
+ return d || null;
3050
+ }
3051
+ function nativeStrokePreviewDashArray(style, tool) {
3052
+ if (style.strokeDash !== "dashed" || tool !== "draw") return void 0;
3053
+ const dash = Math.max(style.strokeWidth * 1.8, 4);
3054
+ const gap = Math.max(style.strokeWidth * 1.4, 3);
3055
+ return `${dash} ${gap}`;
3056
+ }
3057
+
2967
3058
  // src/native/native-vector-interactions.ts
2968
3059
  var NATIVE_SELECTION_HANDLE_HIT_RADIUS_PX = 24;
2969
3060
  function nativeItemPlacementBounds(item) {
@@ -3335,58 +3426,42 @@ function NativeInteractionOverlay({
3335
3426
  p.tool,
3336
3427
  p.style ?? previewStrokeStyle
3337
3428
  );
3338
- const payload = computeFreehandSvgPayload(
3339
- p.points,
3340
- style,
3341
- isLaser ? "draw" : p.tool,
3342
- p.points.length === 2
3429
+ const strokeColor = colorWithOpacity(
3430
+ style.stroke,
3431
+ isLaser ? 0.85 : style.strokeOpacity
3343
3432
  );
3344
- if (!payload) return null;
3345
- if (payload.kind === "circle") {
3433
+ if (p.points.length === 1) {
3434
+ const point = p.points[0];
3435
+ if (!point) return null;
3346
3436
  return /* @__PURE__ */ jsx(
3347
3437
  Circle,
3348
3438
  {
3349
- cx: payload.cx,
3350
- cy: payload.cy,
3351
- r: payload.r,
3352
- color: colorWithOpacity(payload.fill, payload.fillOpacity),
3439
+ cx: point.x,
3440
+ cy: point.y,
3441
+ r: Math.max(0.5, style.strokeWidth / 2),
3442
+ color: strokeColor,
3353
3443
  style: "fill",
3354
3444
  antiAlias: true
3355
3445
  }
3356
3446
  );
3357
3447
  }
3358
- if (payload.kind === "fillPath") {
3359
- return /* @__PURE__ */ jsx(
3360
- Path,
3361
- {
3362
- path: payload.d,
3363
- color: colorWithOpacity(payload.fill, payload.fillOpacity),
3364
- style: "fill",
3365
- fillType: "winding",
3366
- antiAlias: true
3367
- }
3368
- );
3369
- }
3370
- if (payload.kind === "strokePath") {
3371
- const intervals = dashIntervalsFromStrokeDasharray3(payload.strokeDasharray);
3372
- return /* @__PURE__ */ jsx(
3373
- Path,
3374
- {
3375
- path: payload.d,
3376
- color: colorWithOpacity(
3377
- payload.stroke,
3378
- isLaser ? 0.85 : payload.strokeOpacity
3379
- ),
3380
- style: "stroke",
3381
- strokeWidth: payload.strokeWidth,
3382
- strokeCap: "round",
3383
- strokeJoin: "round",
3384
- antiAlias: true,
3385
- children: intervals ? /* @__PURE__ */ jsx(DashPathEffect, { intervals }) : null
3386
- }
3387
- );
3388
- }
3389
- return null;
3448
+ const path = buildNativeStrokePreviewPath(p.points);
3449
+ if (!path) return null;
3450
+ const strokeDasharray = nativeStrokePreviewDashArray(style, p.tool);
3451
+ const intervals = dashIntervalsFromStrokeDasharray3(strokeDasharray);
3452
+ return /* @__PURE__ */ jsx(
3453
+ Path,
3454
+ {
3455
+ path,
3456
+ color: strokeColor,
3457
+ style: "stroke",
3458
+ strokeWidth: style.strokeWidth,
3459
+ strokeCap: "round",
3460
+ strokeJoin: "round",
3461
+ antiAlias: true,
3462
+ children: intervals ? /* @__PURE__ */ jsx(DashPathEffect, { intervals }) : null
3463
+ }
3464
+ );
3390
3465
  }
3391
3466
  return null;
3392
3467
  }, [placementPreview, previewStrokeStyle, overlayStrokeWorld, marqueeDashWorld]);
@@ -5169,6 +5244,36 @@ function hitTestNativeRemotePresence(peers, camera, point) {
5169
5244
  }
5170
5245
  return null;
5171
5246
  }
5247
+
5248
+ // src/native/native-transient-items.ts
5249
+ function moveNativeTransientItems({
5250
+ items,
5251
+ snapshots,
5252
+ dx,
5253
+ dy
5254
+ }) {
5255
+ return items.map((item) => {
5256
+ const snapshot = snapshots[item.id];
5257
+ if (!snapshot) return item;
5258
+ return {
5259
+ ...snapshot,
5260
+ x: snapshot.x + dx,
5261
+ y: snapshot.y + dy,
5262
+ bounds: {
5263
+ ...snapshot.bounds,
5264
+ x: snapshot.bounds.x + dx,
5265
+ y: snapshot.bounds.y + dy
5266
+ }
5267
+ };
5268
+ });
5269
+ }
5270
+ function replaceNativeTransientItem({
5271
+ items,
5272
+ itemId,
5273
+ item: replacement
5274
+ }) {
5275
+ return items.map((item) => item.id === itemId ? replacement : item);
5276
+ }
5172
5277
  var DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS = {
5173
5278
  title: "Add link",
5174
5279
  description: "Paste the link you want to add to the board.",
@@ -5315,13 +5420,41 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5315
5420
  customPlacementsRef.current = customPlacements;
5316
5421
  const onSelectionChangeRef = useRef(onSelectionChange);
5317
5422
  onSelectionChangeRef.current = onSelectionChange;
5318
- const itemsRef = useRef(items);
5319
- itemsRef.current = items;
5423
+ const [transientItems, setTransientItems] = useState(
5424
+ null
5425
+ );
5426
+ const transientItemsRef = useRef(null);
5427
+ const activeItems = transientItems ?? items;
5428
+ const itemsRef = useRef(activeItems);
5429
+ itemsRef.current = activeItems;
5320
5430
  const selectedIdsRef = useRef(selectedIds);
5321
5431
  selectedIdsRef.current = selectedIds;
5322
5432
  const remotePresenceRef = useRef(remotePresence);
5323
5433
  remotePresenceRef.current = remotePresence;
5324
5434
  const dragStateRef = useRef({ kind: "idle" });
5435
+ useEffect(() => {
5436
+ const committedItems = items;
5437
+ if (transientItemsRef.current === null && itemsRef.current === committedItems) {
5438
+ return;
5439
+ }
5440
+ transientItemsRef.current = null;
5441
+ setTransientItems(null);
5442
+ }, [items]);
5443
+ const setTransientItemsPreview = useCallback((nextItems) => {
5444
+ transientItemsRef.current = nextItems;
5445
+ setTransientItems(nextItems);
5446
+ }, []);
5447
+ const clearTransientItemsPreview = useCallback(() => {
5448
+ transientItemsRef.current = null;
5449
+ setTransientItems(null);
5450
+ }, []);
5451
+ const commitTransientItemsPreview = useCallback(() => {
5452
+ const nextItems = transientItemsRef.current;
5453
+ const change = onItemsChangeRef.current;
5454
+ if (!nextItems || !change) return false;
5455
+ change(nextItems);
5456
+ return true;
5457
+ }, []);
5325
5458
  const [placementPreview, setPlacementPreviewState] = useState(null);
5326
5459
  const setRealtimePlacementPreview = useCallback(
5327
5460
  (nextPreview) => {
@@ -5462,8 +5595,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5462
5595
  const hideToolCursor = useCallback(() => {
5463
5596
  }, []);
5464
5597
  const selectedItems = useMemo(
5465
- () => items.filter((it) => selectedIds.includes(it.id)),
5466
- [items, selectedIds]
5598
+ () => activeItems.filter((item) => selectedIds.includes(item.id)),
5599
+ [activeItems, selectedIds]
5467
5600
  );
5468
5601
  const selectedStyleInspectorState = useMemo(() => {
5469
5602
  const styleableItems = selectedItems.filter(
@@ -5479,10 +5612,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5479
5612
  };
5480
5613
  }, [selectedItems]);
5481
5614
  const sceneItems = useMemo(() => {
5482
- if (eraserPreviewIds.length === 0) return items;
5615
+ if (eraserPreviewIds.length === 0) return activeItems;
5483
5616
  const hidden = new Set(eraserPreviewIds);
5484
- return items.filter((it) => !hidden.has(it.id));
5485
- }, [items, eraserPreviewIds]);
5617
+ return activeItems.filter((item) => !hidden.has(item.id));
5618
+ }, [activeItems, eraserPreviewIds]);
5486
5619
  const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked && supportsNativeResizeHandles(selectedItems[0]);
5487
5620
  const patchSelectedItemsStrokeStyle = useCallback(
5488
5621
  (patch) => {
@@ -5770,7 +5903,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5770
5903
  setRealtimePlacementPreview({
5771
5904
  kind: "stroke",
5772
5905
  tool: st.tool,
5773
- points: [...pts],
5906
+ points: sampleNativeStrokePreviewPoints(pts),
5774
5907
  style: { ...strokeStyleRef.current }
5775
5908
  });
5776
5909
  return;
@@ -5780,21 +5913,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5780
5913
  const dy = worldY - st.startWorld.y;
5781
5914
  const change = onItemsChangeRef.current;
5782
5915
  if (!change) return;
5783
- const nextList = itemsRef.current.map((it) => {
5784
- const snap = st.snapshots[it.id];
5785
- if (!snap) return it;
5786
- return {
5787
- ...snap,
5788
- x: snap.x + dx,
5789
- y: snap.y + dy,
5790
- bounds: {
5791
- ...snap.bounds,
5792
- x: snap.bounds.x + dx,
5793
- y: snap.bounds.y + dy
5794
- }
5795
- };
5916
+ const nextItems = moveNativeTransientItems({
5917
+ items: itemsRef.current,
5918
+ snapshots: st.snapshots,
5919
+ dx,
5920
+ dy
5796
5921
  });
5797
- change(nextList);
5922
+ setTransientItemsPreview(nextItems);
5798
5923
  return;
5799
5924
  }
5800
5925
  if (st.kind === "rotate") {
@@ -5807,7 +5932,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5807
5932
  st.startPointerAngleRad,
5808
5933
  angle
5809
5934
  );
5810
- change(itemsRef.current.map((item) => item.id === st.id ? next : item));
5935
+ setTransientItemsPreview(
5936
+ replaceNativeTransientItem({
5937
+ items: itemsRef.current,
5938
+ itemId: st.id,
5939
+ item: next
5940
+ })
5941
+ );
5811
5942
  return;
5812
5943
  }
5813
5944
  if (st.kind === "resize") {
@@ -5817,7 +5948,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5817
5948
  x: worldX,
5818
5949
  y: worldY
5819
5950
  });
5820
- change(itemsRef.current.map((item) => item.id === st.id ? next : item));
5951
+ setTransientItemsPreview(
5952
+ replaceNativeTransientItem({
5953
+ items: itemsRef.current,
5954
+ itemId: st.id,
5955
+ item: next
5956
+ })
5957
+ );
5821
5958
  return;
5822
5959
  }
5823
5960
  if (st.kind === "marquee") {
@@ -5867,6 +6004,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5867
6004
  requestRender,
5868
6005
  screenToWorld,
5869
6006
  setRealtimePlacementPreview,
6007
+ setTransientItemsPreview,
5870
6008
  updateToolCursorPoint
5871
6009
  ]
5872
6010
  );
@@ -5908,10 +6046,12 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5908
6046
  }
5909
6047
  if (st.kind === "move") {
5910
6048
  dragStateRef.current = { kind: "idle" };
6049
+ commitTransientItemsPreview();
5911
6050
  return;
5912
6051
  }
5913
6052
  if (st.kind === "resize" || st.kind === "rotate") {
5914
6053
  dragStateRef.current = { kind: "idle" };
6054
+ commitTransientItemsPreview();
5915
6055
  return;
5916
6056
  }
5917
6057
  if (st.kind === "marquee") {
@@ -6121,6 +6261,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6121
6261
  requestSelectToolAfterUse,
6122
6262
  screenToWorld,
6123
6263
  setRealtimePlacementPreview,
6264
+ commitTransientItemsPreview,
6124
6265
  updateToolCursorPoint
6125
6266
  ]
6126
6267
  );
@@ -6211,6 +6352,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6211
6352
  notifyWorldPointerLeave();
6212
6353
  dragStateRef.current = { kind: "idle" };
6213
6354
  setRealtimePlacementPreview(null);
6355
+ clearTransientItemsPreview();
6214
6356
  setLaserTrail([]);
6215
6357
  setEraserTrail([]);
6216
6358
  setEraserPreviewIds([]);
@@ -6224,7 +6366,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6224
6366
  requestRender,
6225
6367
  hideToolCursor,
6226
6368
  notifyWorldPointerLeave,
6227
- setRealtimePlacementPreview
6369
+ setRealtimePlacementPreview,
6370
+ clearTransientItemsPreview
6228
6371
  ]
6229
6372
  );
6230
6373
  useImperativeHandle(
@@ -6294,8 +6437,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6294
6437
  placementPreview,
6295
6438
  laserTrail,
6296
6439
  eraserTrail,
6297
- eraserPreviewItems: items.filter(
6298
- (it) => eraserPreviewIds.includes(it.id)
6440
+ eraserPreviewItems: activeItems.filter(
6441
+ (item) => eraserPreviewIds.includes(item.id)
6299
6442
  ),
6300
6443
  previewStrokeStyle: strokeStyleState,
6301
6444
  remotePresence