canvu-react 0.4.44 → 0.4.46

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
 
@@ -957,6 +957,9 @@ function rebuildItemSvg(item) {
957
957
  }
958
958
  return item;
959
959
  }
960
+ function applyStrokeToItem(item, patch) {
961
+ return rebuildItemSvg({ ...item, ...patch });
962
+ }
960
963
  function createRectangleItem(id, bounds, style) {
961
964
  const r = normalizeRect(bounds);
962
965
  const s = { ...DEFAULT_STROKE_STYLE, ...style };
@@ -1860,6 +1863,63 @@ function smoothFreehandPointsToPathD(points) {
1860
1863
  return d;
1861
1864
  }
1862
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
+
1863
1923
  // src/native/native-link-card.ts
1864
1924
  function linkHostname(href) {
1865
1925
  try {
@@ -2862,7 +2922,12 @@ function NativeShapeRenderer({ item }) {
2862
2922
  )) });
2863
2923
  }
2864
2924
  if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item.pathPointsLocal && item.pathPointsLocal.length > 0) {
2865
- 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
+ });
2866
2931
  if (!payload) return null;
2867
2932
  const color = rgba(style.stroke, style.strokeOpacity);
2868
2933
  if (payload.kind === "circle") {
@@ -5166,6 +5231,36 @@ function hitTestNativeRemotePresence(peers, camera, point) {
5166
5231
  }
5167
5232
  return null;
5168
5233
  }
5234
+
5235
+ // src/native/native-transient-items.ts
5236
+ function moveNativeTransientItems({
5237
+ items,
5238
+ snapshots,
5239
+ dx,
5240
+ dy
5241
+ }) {
5242
+ return items.map((item) => {
5243
+ const snapshot = snapshots[item.id];
5244
+ if (!snapshot) return item;
5245
+ return {
5246
+ ...snapshot,
5247
+ x: snapshot.x + dx,
5248
+ y: snapshot.y + dy,
5249
+ bounds: {
5250
+ ...snapshot.bounds,
5251
+ x: snapshot.bounds.x + dx,
5252
+ y: snapshot.bounds.y + dy
5253
+ }
5254
+ };
5255
+ });
5256
+ }
5257
+ function replaceNativeTransientItem({
5258
+ items,
5259
+ itemId,
5260
+ item: replacement
5261
+ }) {
5262
+ return items.map((item) => item.id === itemId ? replacement : item);
5263
+ }
5169
5264
  var DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS = {
5170
5265
  title: "Add link",
5171
5266
  description: "Paste the link you want to add to the board.",
@@ -5191,6 +5286,14 @@ function isPlacementTool(toolId) {
5191
5286
  function isDefaultMarkerToolStyle(style) {
5192
5287
  return style.stroke === MARKER_TOOL_STYLE.stroke && style.strokeWidth === MARKER_TOOL_STYLE.strokeWidth && style.strokeOpacity === MARKER_TOOL_STYLE.strokeOpacity;
5193
5288
  }
5289
+ function getNativeStyleInspectorToolId(item) {
5290
+ const kind = item.toolKind;
5291
+ if (kind === "marker") return "marker";
5292
+ if (kind === "draw" || kind === "pencil" || kind === "brush" || kind === "rect" || kind === "ellipse" || kind === "architectural-cloud" || kind === "line" || kind === "arrow") {
5293
+ return "draw";
5294
+ }
5295
+ return null;
5296
+ }
5194
5297
  function placementPreviewForTool(toolId, start, end) {
5195
5298
  if (toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud") {
5196
5299
  return { kind: toolId, rect: rectFromCorners(start, end) };
@@ -5304,13 +5407,41 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5304
5407
  customPlacementsRef.current = customPlacements;
5305
5408
  const onSelectionChangeRef = useRef(onSelectionChange);
5306
5409
  onSelectionChangeRef.current = onSelectionChange;
5307
- const itemsRef = useRef(items);
5308
- itemsRef.current = items;
5410
+ const [transientItems, setTransientItems] = useState(
5411
+ null
5412
+ );
5413
+ const transientItemsRef = useRef(null);
5414
+ const activeItems = transientItems ?? items;
5415
+ const itemsRef = useRef(activeItems);
5416
+ itemsRef.current = activeItems;
5309
5417
  const selectedIdsRef = useRef(selectedIds);
5310
5418
  selectedIdsRef.current = selectedIds;
5311
5419
  const remotePresenceRef = useRef(remotePresence);
5312
5420
  remotePresenceRef.current = remotePresence;
5313
5421
  const dragStateRef = useRef({ kind: "idle" });
5422
+ useEffect(() => {
5423
+ const committedItems = items;
5424
+ if (transientItemsRef.current === null && itemsRef.current === committedItems) {
5425
+ return;
5426
+ }
5427
+ transientItemsRef.current = null;
5428
+ setTransientItems(null);
5429
+ }, [items]);
5430
+ const setTransientItemsPreview = useCallback((nextItems) => {
5431
+ transientItemsRef.current = nextItems;
5432
+ setTransientItems(nextItems);
5433
+ }, []);
5434
+ const clearTransientItemsPreview = useCallback(() => {
5435
+ transientItemsRef.current = null;
5436
+ setTransientItems(null);
5437
+ }, []);
5438
+ const commitTransientItemsPreview = useCallback(() => {
5439
+ const nextItems = transientItemsRef.current;
5440
+ const change = onItemsChangeRef.current;
5441
+ if (!nextItems || !change) return false;
5442
+ change(nextItems);
5443
+ return true;
5444
+ }, []);
5314
5445
  const [placementPreview, setPlacementPreviewState] = useState(null);
5315
5446
  const setRealtimePlacementPreview = useCallback(
5316
5447
  (nextPreview) => {
@@ -5451,15 +5582,48 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5451
5582
  const hideToolCursor = useCallback(() => {
5452
5583
  }, []);
5453
5584
  const selectedItems = useMemo(
5454
- () => items.filter((it) => selectedIds.includes(it.id)),
5455
- [items, selectedIds]
5585
+ () => activeItems.filter((item) => selectedIds.includes(item.id)),
5586
+ [activeItems, selectedIds]
5456
5587
  );
5588
+ const selectedStyleInspectorState = useMemo(() => {
5589
+ const styleableItems = selectedItems.filter(
5590
+ (item) => !item.locked && getNativeStyleInspectorToolId(item) !== null
5591
+ );
5592
+ if (styleableItems.length === 0) return null;
5593
+ const toolId2 = styleableItems.every((item) => item.toolKind === "marker") ? "marker" : "draw";
5594
+ const primaryItem = toolId2 === "marker" ? styleableItems[0] : styleableItems.find((item) => item.toolKind !== "marker") ?? styleableItems[0];
5595
+ if (!primaryItem) return null;
5596
+ return {
5597
+ toolId: toolId2,
5598
+ value: resolveStrokeStyle(primaryItem)
5599
+ };
5600
+ }, [selectedItems]);
5457
5601
  const sceneItems = useMemo(() => {
5458
- if (eraserPreviewIds.length === 0) return items;
5602
+ if (eraserPreviewIds.length === 0) return activeItems;
5459
5603
  const hidden = new Set(eraserPreviewIds);
5460
- return items.filter((it) => !hidden.has(it.id));
5461
- }, [items, eraserPreviewIds]);
5604
+ return activeItems.filter((item) => !hidden.has(item.id));
5605
+ }, [activeItems, eraserPreviewIds]);
5462
5606
  const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked && supportsNativeResizeHandles(selectedItems[0]);
5607
+ const patchSelectedItemsStrokeStyle = useCallback(
5608
+ (patch) => {
5609
+ const change = onItemsChangeRef.current;
5610
+ if (!change) return;
5611
+ const selectedIdSet = new Set(selectedIdsRef.current);
5612
+ if (selectedIdSet.size === 0) return;
5613
+ let changed = false;
5614
+ const nextItems = itemsRef.current.map((item) => {
5615
+ if (!selectedIdSet.has(item.id) || item.locked || getNativeStyleInspectorToolId(item) === null) {
5616
+ return item;
5617
+ }
5618
+ changed = true;
5619
+ return applyStrokeToItem(item, patch);
5620
+ });
5621
+ if (!changed) return;
5622
+ change(nextItems);
5623
+ patchCurrentStrokeStyle(patch);
5624
+ },
5625
+ [patchCurrentStrokeStyle]
5626
+ );
5463
5627
  const lastPinchDist = useRef(null);
5464
5628
  const lastPanPoint = useRef(null);
5465
5629
  const beginDragAtScreenPoint = useCallback(
@@ -5736,21 +5900,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5736
5900
  const dy = worldY - st.startWorld.y;
5737
5901
  const change = onItemsChangeRef.current;
5738
5902
  if (!change) return;
5739
- const nextList = itemsRef.current.map((it) => {
5740
- const snap = st.snapshots[it.id];
5741
- if (!snap) return it;
5742
- return {
5743
- ...snap,
5744
- x: snap.x + dx,
5745
- y: snap.y + dy,
5746
- bounds: {
5747
- ...snap.bounds,
5748
- x: snap.bounds.x + dx,
5749
- y: snap.bounds.y + dy
5750
- }
5751
- };
5903
+ const nextItems = moveNativeTransientItems({
5904
+ items: itemsRef.current,
5905
+ snapshots: st.snapshots,
5906
+ dx,
5907
+ dy
5752
5908
  });
5753
- change(nextList);
5909
+ setTransientItemsPreview(nextItems);
5754
5910
  return;
5755
5911
  }
5756
5912
  if (st.kind === "rotate") {
@@ -5763,7 +5919,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5763
5919
  st.startPointerAngleRad,
5764
5920
  angle
5765
5921
  );
5766
- change(itemsRef.current.map((item) => item.id === st.id ? next : item));
5922
+ setTransientItemsPreview(
5923
+ replaceNativeTransientItem({
5924
+ items: itemsRef.current,
5925
+ itemId: st.id,
5926
+ item: next
5927
+ })
5928
+ );
5767
5929
  return;
5768
5930
  }
5769
5931
  if (st.kind === "resize") {
@@ -5773,7 +5935,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5773
5935
  x: worldX,
5774
5936
  y: worldY
5775
5937
  });
5776
- change(itemsRef.current.map((item) => item.id === st.id ? next : item));
5938
+ setTransientItemsPreview(
5939
+ replaceNativeTransientItem({
5940
+ items: itemsRef.current,
5941
+ itemId: st.id,
5942
+ item: next
5943
+ })
5944
+ );
5777
5945
  return;
5778
5946
  }
5779
5947
  if (st.kind === "marquee") {
@@ -5823,6 +5991,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5823
5991
  requestRender,
5824
5992
  screenToWorld,
5825
5993
  setRealtimePlacementPreview,
5994
+ setTransientItemsPreview,
5826
5995
  updateToolCursorPoint
5827
5996
  ]
5828
5997
  );
@@ -5864,10 +6033,12 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5864
6033
  }
5865
6034
  if (st.kind === "move") {
5866
6035
  dragStateRef.current = { kind: "idle" };
6036
+ commitTransientItemsPreview();
5867
6037
  return;
5868
6038
  }
5869
6039
  if (st.kind === "resize" || st.kind === "rotate") {
5870
6040
  dragStateRef.current = { kind: "idle" };
6041
+ commitTransientItemsPreview();
5871
6042
  return;
5872
6043
  }
5873
6044
  if (st.kind === "marquee") {
@@ -6077,6 +6248,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6077
6248
  requestSelectToolAfterUse,
6078
6249
  screenToWorld,
6079
6250
  setRealtimePlacementPreview,
6251
+ commitTransientItemsPreview,
6080
6252
  updateToolCursorPoint
6081
6253
  ]
6082
6254
  );
@@ -6167,6 +6339,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6167
6339
  notifyWorldPointerLeave();
6168
6340
  dragStateRef.current = { kind: "idle" };
6169
6341
  setRealtimePlacementPreview(null);
6342
+ clearTransientItemsPreview();
6170
6343
  setLaserTrail([]);
6171
6344
  setEraserTrail([]);
6172
6345
  setEraserPreviewIds([]);
@@ -6180,7 +6353,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6180
6353
  requestRender,
6181
6354
  hideToolCursor,
6182
6355
  notifyWorldPointerLeave,
6183
- setRealtimePlacementPreview
6356
+ setRealtimePlacementPreview,
6357
+ clearTransientItemsPreview
6184
6358
  ]
6185
6359
  );
6186
6360
  useImperativeHandle(
@@ -6205,6 +6379,9 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6205
6379
  [requestRender, size]
6206
6380
  );
6207
6381
  const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
6382
+ const styleInspectorToolId = selectedStyleInspectorState?.toolId ?? activeStyleToolId;
6383
+ const styleInspectorValue = selectedStyleInspectorState?.value ?? strokeStyleState;
6384
+ const styleInspectorChange = selectedStyleInspectorState ? patchSelectedItemsStrokeStyle : patchCurrentStrokeStyle;
6208
6385
  const nativeLinkDialogLabels = {
6209
6386
  ...DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS,
6210
6387
  ...linkToolDialogLabels ?? {}
@@ -6247,8 +6424,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6247
6424
  placementPreview,
6248
6425
  laserTrail,
6249
6426
  eraserTrail,
6250
- eraserPreviewItems: items.filter(
6251
- (it) => eraserPreviewIds.includes(it.id)
6427
+ eraserPreviewItems: activeItems.filter(
6428
+ (item) => eraserPreviewIds.includes(item.id)
6252
6429
  ),
6253
6430
  previewStrokeStyle: strokeStyleState,
6254
6431
  remotePresence
@@ -6271,7 +6448,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6271
6448
  children: overlay
6272
6449
  }
6273
6450
  ) : null,
6274
- interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsx(
6451
+ interactive && showStyleInspector && styleInspectorToolId ? /* @__PURE__ */ jsx(
6275
6452
  View,
6276
6453
  {
6277
6454
  pointerEvents: "box-none",
@@ -6294,9 +6471,9 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
6294
6471
  children: /* @__PURE__ */ jsx(
6295
6472
  NativeVectorStyleInspector,
6296
6473
  {
6297
- toolId: activeStyleToolId,
6298
- value: strokeStyleState,
6299
- onChange: patchCurrentStrokeStyle
6474
+ toolId: styleInspectorToolId,
6475
+ value: styleInspectorValue,
6476
+ onChange: styleInspectorChange
6300
6477
  }
6301
6478
  )
6302
6479
  }