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.cjs CHANGED
@@ -963,6 +963,9 @@ function rebuildItemSvg(item) {
963
963
  }
964
964
  return item;
965
965
  }
966
+ function applyStrokeToItem(item, patch) {
967
+ return rebuildItemSvg({ ...item, ...patch });
968
+ }
966
969
  function createRectangleItem(id, bounds, style) {
967
970
  const r = normalizeRect(bounds);
968
971
  const s = { ...DEFAULT_STROKE_STYLE, ...style };
@@ -1866,6 +1869,63 @@ function smoothFreehandPointsToPathD(points) {
1866
1869
  return d;
1867
1870
  }
1868
1871
 
1872
+ // src/native/native-freehand-payload-cache.ts
1873
+ var MAX_NATIVE_FREEHAND_PAYLOAD_CACHE_SIZE = 800;
1874
+ var pathSignatureCache = /* @__PURE__ */ new WeakMap();
1875
+ var payloadCache = /* @__PURE__ */ new Map();
1876
+ function hashNumber(hash, value) {
1877
+ const normalizedValue = Math.round(value * 100);
1878
+ return Math.imul(hash ^ normalizedValue, 16777619) >>> 0;
1879
+ }
1880
+ function getPathPointsSignature(pathPointsLocal) {
1881
+ const cachedSignature = pathSignatureCache.get(pathPointsLocal);
1882
+ if (cachedSignature) return cachedSignature;
1883
+ const hash = pathPointsLocal.reduce((currentHash, point) => {
1884
+ const withX = hashNumber(currentHash, point.x);
1885
+ const withY = hashNumber(withX, point.y);
1886
+ return hashNumber(withY, point.pressure ?? -1);
1887
+ }, 2166136261);
1888
+ const signature = `${pathPointsLocal.length}:${hash}`;
1889
+ pathSignatureCache.set(pathPointsLocal, signature);
1890
+ return signature;
1891
+ }
1892
+ function getStyleSignature(style) {
1893
+ return [
1894
+ style.stroke,
1895
+ style.strokeWidth,
1896
+ style.strokeOpacity ?? "",
1897
+ style.strokeDash ?? ""
1898
+ ].join(":");
1899
+ }
1900
+ function getPayloadCacheKey(input) {
1901
+ return [
1902
+ input.itemId,
1903
+ input.toolKind,
1904
+ getStyleSignature(input.style),
1905
+ getPathPointsSignature(input.pathPointsLocal)
1906
+ ].join("|");
1907
+ }
1908
+ function storePayload(key, payload) {
1909
+ payloadCache.set(key, payload);
1910
+ if (payloadCache.size > MAX_NATIVE_FREEHAND_PAYLOAD_CACHE_SIZE) {
1911
+ const oldestKey = payloadCache.keys().next().value;
1912
+ if (oldestKey !== void 0) {
1913
+ payloadCache.delete(oldestKey);
1914
+ }
1915
+ }
1916
+ return payload;
1917
+ }
1918
+ function getNativeFreehandSvgPayload(input) {
1919
+ const key = getPayloadCacheKey(input);
1920
+ if (payloadCache.has(key)) {
1921
+ return payloadCache.get(key) ?? null;
1922
+ }
1923
+ return storePayload(
1924
+ key,
1925
+ computeFreehandSvgPayload(input.pathPointsLocal, input.style, input.toolKind)
1926
+ );
1927
+ }
1928
+
1869
1929
  // src/native/native-link-card.ts
1870
1930
  function linkHostname(href) {
1871
1931
  try {
@@ -2868,7 +2928,12 @@ function NativeShapeRenderer({ item }) {
2868
2928
  )) });
2869
2929
  }
2870
2930
  if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item.pathPointsLocal && item.pathPointsLocal.length > 0) {
2871
- const payload = computeFreehandSvgPayload(item.pathPointsLocal, style, k);
2931
+ const payload = getNativeFreehandSvgPayload({
2932
+ itemId: item.id,
2933
+ pathPointsLocal: item.pathPointsLocal,
2934
+ style,
2935
+ toolKind: k
2936
+ });
2872
2937
  if (!payload) return null;
2873
2938
  const color = rgba(style.stroke, style.strokeOpacity);
2874
2939
  if (payload.kind === "circle") {
@@ -5172,6 +5237,36 @@ function hitTestNativeRemotePresence(peers, camera, point) {
5172
5237
  }
5173
5238
  return null;
5174
5239
  }
5240
+
5241
+ // src/native/native-transient-items.ts
5242
+ function moveNativeTransientItems({
5243
+ items,
5244
+ snapshots,
5245
+ dx,
5246
+ dy
5247
+ }) {
5248
+ return items.map((item) => {
5249
+ const snapshot = snapshots[item.id];
5250
+ if (!snapshot) return item;
5251
+ return {
5252
+ ...snapshot,
5253
+ x: snapshot.x + dx,
5254
+ y: snapshot.y + dy,
5255
+ bounds: {
5256
+ ...snapshot.bounds,
5257
+ x: snapshot.bounds.x + dx,
5258
+ y: snapshot.bounds.y + dy
5259
+ }
5260
+ };
5261
+ });
5262
+ }
5263
+ function replaceNativeTransientItem({
5264
+ items,
5265
+ itemId,
5266
+ item: replacement
5267
+ }) {
5268
+ return items.map((item) => item.id === itemId ? replacement : item);
5269
+ }
5175
5270
  var DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS = {
5176
5271
  title: "Add link",
5177
5272
  description: "Paste the link you want to add to the board.",
@@ -5197,6 +5292,14 @@ function isPlacementTool(toolId) {
5197
5292
  function isDefaultMarkerToolStyle(style) {
5198
5293
  return style.stroke === MARKER_TOOL_STYLE.stroke && style.strokeWidth === MARKER_TOOL_STYLE.strokeWidth && style.strokeOpacity === MARKER_TOOL_STYLE.strokeOpacity;
5199
5294
  }
5295
+ function getNativeStyleInspectorToolId(item) {
5296
+ const kind = item.toolKind;
5297
+ if (kind === "marker") return "marker";
5298
+ if (kind === "draw" || kind === "pencil" || kind === "brush" || kind === "rect" || kind === "ellipse" || kind === "architectural-cloud" || kind === "line" || kind === "arrow") {
5299
+ return "draw";
5300
+ }
5301
+ return null;
5302
+ }
5200
5303
  function placementPreviewForTool(toolId, start, end) {
5201
5304
  if (toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud") {
5202
5305
  return { kind: toolId, rect: rectFromCorners(start, end) };
@@ -5310,13 +5413,41 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5310
5413
  customPlacementsRef.current = customPlacements;
5311
5414
  const onSelectionChangeRef = react.useRef(onSelectionChange);
5312
5415
  onSelectionChangeRef.current = onSelectionChange;
5313
- const itemsRef = react.useRef(items);
5314
- itemsRef.current = items;
5416
+ const [transientItems, setTransientItems] = react.useState(
5417
+ null
5418
+ );
5419
+ const transientItemsRef = react.useRef(null);
5420
+ const activeItems = transientItems ?? items;
5421
+ const itemsRef = react.useRef(activeItems);
5422
+ itemsRef.current = activeItems;
5315
5423
  const selectedIdsRef = react.useRef(selectedIds);
5316
5424
  selectedIdsRef.current = selectedIds;
5317
5425
  const remotePresenceRef = react.useRef(remotePresence);
5318
5426
  remotePresenceRef.current = remotePresence;
5319
5427
  const dragStateRef = react.useRef({ kind: "idle" });
5428
+ react.useEffect(() => {
5429
+ const committedItems = items;
5430
+ if (transientItemsRef.current === null && itemsRef.current === committedItems) {
5431
+ return;
5432
+ }
5433
+ transientItemsRef.current = null;
5434
+ setTransientItems(null);
5435
+ }, [items]);
5436
+ const setTransientItemsPreview = react.useCallback((nextItems) => {
5437
+ transientItemsRef.current = nextItems;
5438
+ setTransientItems(nextItems);
5439
+ }, []);
5440
+ const clearTransientItemsPreview = react.useCallback(() => {
5441
+ transientItemsRef.current = null;
5442
+ setTransientItems(null);
5443
+ }, []);
5444
+ const commitTransientItemsPreview = react.useCallback(() => {
5445
+ const nextItems = transientItemsRef.current;
5446
+ const change = onItemsChangeRef.current;
5447
+ if (!nextItems || !change) return false;
5448
+ change(nextItems);
5449
+ return true;
5450
+ }, []);
5320
5451
  const [placementPreview, setPlacementPreviewState] = react.useState(null);
5321
5452
  const setRealtimePlacementPreview = react.useCallback(
5322
5453
  (nextPreview) => {
@@ -5457,15 +5588,48 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5457
5588
  const hideToolCursor = react.useCallback(() => {
5458
5589
  }, []);
5459
5590
  const selectedItems = react.useMemo(
5460
- () => items.filter((it) => selectedIds.includes(it.id)),
5461
- [items, selectedIds]
5591
+ () => activeItems.filter((item) => selectedIds.includes(item.id)),
5592
+ [activeItems, selectedIds]
5462
5593
  );
5594
+ const selectedStyleInspectorState = react.useMemo(() => {
5595
+ const styleableItems = selectedItems.filter(
5596
+ (item) => !item.locked && getNativeStyleInspectorToolId(item) !== null
5597
+ );
5598
+ if (styleableItems.length === 0) return null;
5599
+ const toolId2 = styleableItems.every((item) => item.toolKind === "marker") ? "marker" : "draw";
5600
+ const primaryItem = toolId2 === "marker" ? styleableItems[0] : styleableItems.find((item) => item.toolKind !== "marker") ?? styleableItems[0];
5601
+ if (!primaryItem) return null;
5602
+ return {
5603
+ toolId: toolId2,
5604
+ value: resolveStrokeStyle(primaryItem)
5605
+ };
5606
+ }, [selectedItems]);
5463
5607
  const sceneItems = react.useMemo(() => {
5464
- if (eraserPreviewIds.length === 0) return items;
5608
+ if (eraserPreviewIds.length === 0) return activeItems;
5465
5609
  const hidden = new Set(eraserPreviewIds);
5466
- return items.filter((it) => !hidden.has(it.id));
5467
- }, [items, eraserPreviewIds]);
5610
+ return activeItems.filter((item) => !hidden.has(item.id));
5611
+ }, [activeItems, eraserPreviewIds]);
5468
5612
  const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked && supportsNativeResizeHandles(selectedItems[0]);
5613
+ const patchSelectedItemsStrokeStyle = react.useCallback(
5614
+ (patch) => {
5615
+ const change = onItemsChangeRef.current;
5616
+ if (!change) return;
5617
+ const selectedIdSet = new Set(selectedIdsRef.current);
5618
+ if (selectedIdSet.size === 0) return;
5619
+ let changed = false;
5620
+ const nextItems = itemsRef.current.map((item) => {
5621
+ if (!selectedIdSet.has(item.id) || item.locked || getNativeStyleInspectorToolId(item) === null) {
5622
+ return item;
5623
+ }
5624
+ changed = true;
5625
+ return applyStrokeToItem(item, patch);
5626
+ });
5627
+ if (!changed) return;
5628
+ change(nextItems);
5629
+ patchCurrentStrokeStyle(patch);
5630
+ },
5631
+ [patchCurrentStrokeStyle]
5632
+ );
5469
5633
  const lastPinchDist = react.useRef(null);
5470
5634
  const lastPanPoint = react.useRef(null);
5471
5635
  const beginDragAtScreenPoint = react.useCallback(
@@ -5742,21 +5906,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5742
5906
  const dy = worldY - st.startWorld.y;
5743
5907
  const change = onItemsChangeRef.current;
5744
5908
  if (!change) return;
5745
- const nextList = itemsRef.current.map((it) => {
5746
- const snap = st.snapshots[it.id];
5747
- if (!snap) return it;
5748
- return {
5749
- ...snap,
5750
- x: snap.x + dx,
5751
- y: snap.y + dy,
5752
- bounds: {
5753
- ...snap.bounds,
5754
- x: snap.bounds.x + dx,
5755
- y: snap.bounds.y + dy
5756
- }
5757
- };
5909
+ const nextItems = moveNativeTransientItems({
5910
+ items: itemsRef.current,
5911
+ snapshots: st.snapshots,
5912
+ dx,
5913
+ dy
5758
5914
  });
5759
- change(nextList);
5915
+ setTransientItemsPreview(nextItems);
5760
5916
  return;
5761
5917
  }
5762
5918
  if (st.kind === "rotate") {
@@ -5769,7 +5925,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5769
5925
  st.startPointerAngleRad,
5770
5926
  angle
5771
5927
  );
5772
- change(itemsRef.current.map((item) => item.id === st.id ? next : item));
5928
+ setTransientItemsPreview(
5929
+ replaceNativeTransientItem({
5930
+ items: itemsRef.current,
5931
+ itemId: st.id,
5932
+ item: next
5933
+ })
5934
+ );
5773
5935
  return;
5774
5936
  }
5775
5937
  if (st.kind === "resize") {
@@ -5779,7 +5941,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5779
5941
  x: worldX,
5780
5942
  y: worldY
5781
5943
  });
5782
- change(itemsRef.current.map((item) => item.id === st.id ? next : item));
5944
+ setTransientItemsPreview(
5945
+ replaceNativeTransientItem({
5946
+ items: itemsRef.current,
5947
+ itemId: st.id,
5948
+ item: next
5949
+ })
5950
+ );
5783
5951
  return;
5784
5952
  }
5785
5953
  if (st.kind === "marquee") {
@@ -5829,6 +5997,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5829
5997
  requestRender,
5830
5998
  screenToWorld,
5831
5999
  setRealtimePlacementPreview,
6000
+ setTransientItemsPreview,
5832
6001
  updateToolCursorPoint
5833
6002
  ]
5834
6003
  );
@@ -5870,10 +6039,12 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5870
6039
  }
5871
6040
  if (st.kind === "move") {
5872
6041
  dragStateRef.current = { kind: "idle" };
6042
+ commitTransientItemsPreview();
5873
6043
  return;
5874
6044
  }
5875
6045
  if (st.kind === "resize" || st.kind === "rotate") {
5876
6046
  dragStateRef.current = { kind: "idle" };
6047
+ commitTransientItemsPreview();
5877
6048
  return;
5878
6049
  }
5879
6050
  if (st.kind === "marquee") {
@@ -6083,6 +6254,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6083
6254
  requestSelectToolAfterUse,
6084
6255
  screenToWorld,
6085
6256
  setRealtimePlacementPreview,
6257
+ commitTransientItemsPreview,
6086
6258
  updateToolCursorPoint
6087
6259
  ]
6088
6260
  );
@@ -6173,6 +6345,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6173
6345
  notifyWorldPointerLeave();
6174
6346
  dragStateRef.current = { kind: "idle" };
6175
6347
  setRealtimePlacementPreview(null);
6348
+ clearTransientItemsPreview();
6176
6349
  setLaserTrail([]);
6177
6350
  setEraserTrail([]);
6178
6351
  setEraserPreviewIds([]);
@@ -6186,7 +6359,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6186
6359
  requestRender,
6187
6360
  hideToolCursor,
6188
6361
  notifyWorldPointerLeave,
6189
- setRealtimePlacementPreview
6362
+ setRealtimePlacementPreview,
6363
+ clearTransientItemsPreview
6190
6364
  ]
6191
6365
  );
6192
6366
  react.useImperativeHandle(
@@ -6211,6 +6385,9 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6211
6385
  [requestRender, size]
6212
6386
  );
6213
6387
  const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
6388
+ const styleInspectorToolId = selectedStyleInspectorState?.toolId ?? activeStyleToolId;
6389
+ const styleInspectorValue = selectedStyleInspectorState?.value ?? strokeStyleState;
6390
+ const styleInspectorChange = selectedStyleInspectorState ? patchSelectedItemsStrokeStyle : patchCurrentStrokeStyle;
6214
6391
  const nativeLinkDialogLabels = {
6215
6392
  ...DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS,
6216
6393
  ...linkToolDialogLabels ?? {}
@@ -6253,8 +6430,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6253
6430
  placementPreview,
6254
6431
  laserTrail,
6255
6432
  eraserTrail,
6256
- eraserPreviewItems: items.filter(
6257
- (it) => eraserPreviewIds.includes(it.id)
6433
+ eraserPreviewItems: activeItems.filter(
6434
+ (item) => eraserPreviewIds.includes(item.id)
6258
6435
  ),
6259
6436
  previewStrokeStyle: strokeStyleState,
6260
6437
  remotePresence
@@ -6277,7 +6454,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6277
6454
  children: overlay
6278
6455
  }
6279
6456
  ) : null,
6280
- interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsxRuntime.jsx(
6457
+ interactive && showStyleInspector && styleInspectorToolId ? /* @__PURE__ */ jsxRuntime.jsx(
6281
6458
  reactNative.View,
6282
6459
  {
6283
6460
  pointerEvents: "box-none",
@@ -6300,9 +6477,9 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6300
6477
  children: /* @__PURE__ */ jsxRuntime.jsx(
6301
6478
  NativeVectorStyleInspector,
6302
6479
  {
6303
- toolId: activeStyleToolId,
6304
- value: strokeStyleState,
6305
- onChange: patchCurrentStrokeStyle
6480
+ toolId: styleInspectorToolId,
6481
+ value: styleInspectorValue,
6482
+ onChange: styleInspectorChange
6306
6483
  }
6307
6484
  )
6308
6485
  }