canvu-react 0.4.45 → 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
@@ -1869,6 +1869,63 @@ function smoothFreehandPointsToPathD(points) {
1869
1869
  return d;
1870
1870
  }
1871
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
+
1872
1929
  // src/native/native-link-card.ts
1873
1930
  function linkHostname(href) {
1874
1931
  try {
@@ -2871,7 +2928,12 @@ function NativeShapeRenderer({ item }) {
2871
2928
  )) });
2872
2929
  }
2873
2930
  if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item.pathPointsLocal && item.pathPointsLocal.length > 0) {
2874
- 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
+ });
2875
2937
  if (!payload) return null;
2876
2938
  const color = rgba(style.stroke, style.strokeOpacity);
2877
2939
  if (payload.kind === "circle") {
@@ -5175,6 +5237,36 @@ function hitTestNativeRemotePresence(peers, camera, point) {
5175
5237
  }
5176
5238
  return null;
5177
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
+ }
5178
5270
  var DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS = {
5179
5271
  title: "Add link",
5180
5272
  description: "Paste the link you want to add to the board.",
@@ -5321,13 +5413,41 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5321
5413
  customPlacementsRef.current = customPlacements;
5322
5414
  const onSelectionChangeRef = react.useRef(onSelectionChange);
5323
5415
  onSelectionChangeRef.current = onSelectionChange;
5324
- const itemsRef = react.useRef(items);
5325
- 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;
5326
5423
  const selectedIdsRef = react.useRef(selectedIds);
5327
5424
  selectedIdsRef.current = selectedIds;
5328
5425
  const remotePresenceRef = react.useRef(remotePresence);
5329
5426
  remotePresenceRef.current = remotePresence;
5330
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
+ }, []);
5331
5451
  const [placementPreview, setPlacementPreviewState] = react.useState(null);
5332
5452
  const setRealtimePlacementPreview = react.useCallback(
5333
5453
  (nextPreview) => {
@@ -5468,8 +5588,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5468
5588
  const hideToolCursor = react.useCallback(() => {
5469
5589
  }, []);
5470
5590
  const selectedItems = react.useMemo(
5471
- () => items.filter((it) => selectedIds.includes(it.id)),
5472
- [items, selectedIds]
5591
+ () => activeItems.filter((item) => selectedIds.includes(item.id)),
5592
+ [activeItems, selectedIds]
5473
5593
  );
5474
5594
  const selectedStyleInspectorState = react.useMemo(() => {
5475
5595
  const styleableItems = selectedItems.filter(
@@ -5485,10 +5605,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5485
5605
  };
5486
5606
  }, [selectedItems]);
5487
5607
  const sceneItems = react.useMemo(() => {
5488
- if (eraserPreviewIds.length === 0) return items;
5608
+ if (eraserPreviewIds.length === 0) return activeItems;
5489
5609
  const hidden = new Set(eraserPreviewIds);
5490
- return items.filter((it) => !hidden.has(it.id));
5491
- }, [items, eraserPreviewIds]);
5610
+ return activeItems.filter((item) => !hidden.has(item.id));
5611
+ }, [activeItems, eraserPreviewIds]);
5492
5612
  const showResizeHandles = interactive && selectedItems.length === 1 && !selectedItems[0]?.locked && supportsNativeResizeHandles(selectedItems[0]);
5493
5613
  const patchSelectedItemsStrokeStyle = react.useCallback(
5494
5614
  (patch) => {
@@ -5786,21 +5906,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5786
5906
  const dy = worldY - st.startWorld.y;
5787
5907
  const change = onItemsChangeRef.current;
5788
5908
  if (!change) return;
5789
- const nextList = itemsRef.current.map((it) => {
5790
- const snap = st.snapshots[it.id];
5791
- if (!snap) return it;
5792
- return {
5793
- ...snap,
5794
- x: snap.x + dx,
5795
- y: snap.y + dy,
5796
- bounds: {
5797
- ...snap.bounds,
5798
- x: snap.bounds.x + dx,
5799
- y: snap.bounds.y + dy
5800
- }
5801
- };
5909
+ const nextItems = moveNativeTransientItems({
5910
+ items: itemsRef.current,
5911
+ snapshots: st.snapshots,
5912
+ dx,
5913
+ dy
5802
5914
  });
5803
- change(nextList);
5915
+ setTransientItemsPreview(nextItems);
5804
5916
  return;
5805
5917
  }
5806
5918
  if (st.kind === "rotate") {
@@ -5813,7 +5925,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5813
5925
  st.startPointerAngleRad,
5814
5926
  angle
5815
5927
  );
5816
- 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
+ );
5817
5935
  return;
5818
5936
  }
5819
5937
  if (st.kind === "resize") {
@@ -5823,7 +5941,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5823
5941
  x: worldX,
5824
5942
  y: worldY
5825
5943
  });
5826
- 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
+ );
5827
5951
  return;
5828
5952
  }
5829
5953
  if (st.kind === "marquee") {
@@ -5873,6 +5997,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5873
5997
  requestRender,
5874
5998
  screenToWorld,
5875
5999
  setRealtimePlacementPreview,
6000
+ setTransientItemsPreview,
5876
6001
  updateToolCursorPoint
5877
6002
  ]
5878
6003
  );
@@ -5914,10 +6039,12 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5914
6039
  }
5915
6040
  if (st.kind === "move") {
5916
6041
  dragStateRef.current = { kind: "idle" };
6042
+ commitTransientItemsPreview();
5917
6043
  return;
5918
6044
  }
5919
6045
  if (st.kind === "resize" || st.kind === "rotate") {
5920
6046
  dragStateRef.current = { kind: "idle" };
6047
+ commitTransientItemsPreview();
5921
6048
  return;
5922
6049
  }
5923
6050
  if (st.kind === "marquee") {
@@ -6127,6 +6254,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6127
6254
  requestSelectToolAfterUse,
6128
6255
  screenToWorld,
6129
6256
  setRealtimePlacementPreview,
6257
+ commitTransientItemsPreview,
6130
6258
  updateToolCursorPoint
6131
6259
  ]
6132
6260
  );
@@ -6217,6 +6345,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6217
6345
  notifyWorldPointerLeave();
6218
6346
  dragStateRef.current = { kind: "idle" };
6219
6347
  setRealtimePlacementPreview(null);
6348
+ clearTransientItemsPreview();
6220
6349
  setLaserTrail([]);
6221
6350
  setEraserTrail([]);
6222
6351
  setEraserPreviewIds([]);
@@ -6230,7 +6359,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6230
6359
  requestRender,
6231
6360
  hideToolCursor,
6232
6361
  notifyWorldPointerLeave,
6233
- setRealtimePlacementPreview
6362
+ setRealtimePlacementPreview,
6363
+ clearTransientItemsPreview
6234
6364
  ]
6235
6365
  );
6236
6366
  react.useImperativeHandle(
@@ -6300,8 +6430,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
6300
6430
  placementPreview,
6301
6431
  laserTrail,
6302
6432
  eraserTrail,
6303
- eraserPreviewItems: items.filter(
6304
- (it) => eraserPreviewIds.includes(it.id)
6433
+ eraserPreviewItems: activeItems.filter(
6434
+ (item) => eraserPreviewIds.includes(item.id)
6305
6435
  ),
6306
6436
  previewStrokeStyle: strokeStyleState,
6307
6437
  remotePresence