canvu-react 0.4.63 → 0.4.64

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/react.js CHANGED
@@ -3146,7 +3146,9 @@ function ToolPluginComponent({
3146
3146
  toolTransform,
3147
3147
  createItem,
3148
3148
  selectAfterCreate,
3149
- onSelectModeItemClick
3149
+ onSelectModeItemClick,
3150
+ onBeforeInteraction,
3151
+ onAfterInteraction
3150
3152
  }) {
3151
3153
  const contribution = useMemo(
3152
3154
  () => ({
@@ -3159,9 +3161,27 @@ function ToolPluginComponent({
3159
3161
  selectAfterCreate,
3160
3162
  onSelectModeItemClick
3161
3163
  }
3162
- ] : void 0
3164
+ ] : void 0,
3165
+ interactionHooks: onBeforeInteraction || onAfterInteraction ? {
3166
+ onBeforeInteraction: onBeforeInteraction ? (detail) => {
3167
+ if (detail.toolId !== tool.id) return void 0;
3168
+ return onBeforeInteraction(detail);
3169
+ } : void 0,
3170
+ onAfterInteraction: onAfterInteraction ? (detail) => {
3171
+ if (detail.toolId !== tool.id) return;
3172
+ onAfterInteraction(detail);
3173
+ } : void 0
3174
+ } : void 0
3163
3175
  }),
3164
- [createItem, onSelectModeItemClick, selectAfterCreate, tool, toolTransform]
3176
+ [
3177
+ createItem,
3178
+ onAfterInteraction,
3179
+ onBeforeInteraction,
3180
+ onSelectModeItemClick,
3181
+ selectAfterCreate,
3182
+ tool,
3183
+ toolTransform
3184
+ ]
3165
3185
  );
3166
3186
  useCanvuPluginContribution(pluginId, contribution);
3167
3187
  return null;
@@ -3172,6 +3192,8 @@ function createToolPlugin(options) {
3172
3192
  toolTransform,
3173
3193
  selectAfterCreate,
3174
3194
  onSelectModeItemClick,
3195
+ onBeforeInteraction,
3196
+ onAfterInteraction,
3175
3197
  ...tool
3176
3198
  } = options;
3177
3199
  const pluginId = `canvu.plugin.tool:${tool.id}`;
@@ -3186,7 +3208,9 @@ function createToolPlugin(options) {
3186
3208
  toolTransform,
3187
3209
  createItem,
3188
3210
  selectAfterCreate,
3189
- onSelectModeItemClick
3211
+ onSelectModeItemClick,
3212
+ onBeforeInteraction,
3213
+ onAfterInteraction
3190
3214
  }
3191
3215
  );
3192
3216
  }
@@ -8136,6 +8160,8 @@ var VectorViewport = forwardRef(
8136
8160
  selectedIds: selectedIdsProp,
8137
8161
  onSelectionChange,
8138
8162
  onItemsChange: consumerOnItemsChange,
8163
+ onBeforeInteraction: consumerOnBeforeInteraction,
8164
+ onAfterInteraction: consumerOnAfterInteraction,
8139
8165
  onActivateLink,
8140
8166
  onWorldPointerDown: consumerOnWorldPointerDown,
8141
8167
  toolbar,
@@ -8261,6 +8287,24 @@ var VectorViewport = forwardRef(
8261
8287
  (contribution) => contribution.callbacks?.onCameraChange
8262
8288
  )
8263
8289
  );
8290
+ const beforeInteractionHooks = useMemo(
8291
+ () => [
8292
+ consumerOnBeforeInteraction,
8293
+ ...orderedPluginContributions.map(
8294
+ (contribution) => contribution.interactionHooks?.onBeforeInteraction
8295
+ )
8296
+ ].filter((hook) => hook != null),
8297
+ [consumerOnBeforeInteraction, orderedPluginContributions]
8298
+ );
8299
+ const afterInteractionHooks = useMemo(
8300
+ () => [
8301
+ consumerOnAfterInteraction,
8302
+ ...orderedPluginContributions.map(
8303
+ (contribution) => contribution.interactionHooks?.onAfterInteraction
8304
+ )
8305
+ ].filter((hook) => hook != null),
8306
+ [consumerOnAfterInteraction, orderedPluginContributions]
8307
+ );
8264
8308
  const onItemsChange = useMemo(() => {
8265
8309
  const middlewares = orderedPluginContributions.map((contribution) => contribution.wrapOnItemsChange).filter(
8266
8310
  (middleware) => middleware != null
@@ -8404,6 +8448,81 @@ var VectorViewport = forwardRef(
8404
8448
  [items]
8405
8449
  );
8406
8450
  itemsRef.current = normalizedItems;
8451
+ const beforeInteractionHooksRef = useRef(beforeInteractionHooks);
8452
+ beforeInteractionHooksRef.current = beforeInteractionHooks;
8453
+ const afterInteractionHooksRef = useRef(afterInteractionHooks);
8454
+ afterInteractionHooksRef.current = afterInteractionHooks;
8455
+ const canvuInteractionSeqRef = useRef(0);
8456
+ const activeCanvuInteractionRef = useRef(null);
8457
+ const makeInteractionPoint = useCallback(
8458
+ (input) => ({
8459
+ worldX: input.worldX,
8460
+ worldY: input.worldY,
8461
+ clientX: input.clientX,
8462
+ clientY: input.clientY
8463
+ }),
8464
+ []
8465
+ );
8466
+ const startCanvuInteraction = useCallback(
8467
+ (input) => {
8468
+ const start = makeInteractionPoint(input);
8469
+ const detail = {
8470
+ interactionId: `canvu-interaction-${++canvuInteractionSeqRef.current}`,
8471
+ kind: input.kind,
8472
+ toolId: input.toolId,
8473
+ pointerType: input.pointerType,
8474
+ button: input.button,
8475
+ shiftKey: input.shiftKey,
8476
+ altKey: input.altKey,
8477
+ metaKey: input.metaKey,
8478
+ ctrlKey: input.ctrlKey,
8479
+ start,
8480
+ current: start,
8481
+ selectedIds: [...effectiveSelectedIdsRef.current],
8482
+ itemIds: [...input.itemIds ?? []],
8483
+ items: itemsRef.current
8484
+ };
8485
+ for (const hook of beforeInteractionHooksRef.current) {
8486
+ if (hook(detail) === "handled") {
8487
+ return null;
8488
+ }
8489
+ }
8490
+ activeCanvuInteractionRef.current = detail;
8491
+ return detail;
8492
+ },
8493
+ [makeInteractionPoint]
8494
+ );
8495
+ const updateCanvuInteractionCurrent = useCallback(
8496
+ (input) => {
8497
+ const detail = activeCanvuInteractionRef.current;
8498
+ if (!detail) return;
8499
+ activeCanvuInteractionRef.current = {
8500
+ ...detail,
8501
+ current: makeInteractionPoint(input)
8502
+ };
8503
+ },
8504
+ [makeInteractionPoint]
8505
+ );
8506
+ const finishCanvuInteraction = useCallback(
8507
+ (outcome, options) => {
8508
+ const detail = activeCanvuInteractionRef.current;
8509
+ if (!detail) return;
8510
+ activeCanvuInteractionRef.current = null;
8511
+ const current = options?.current ? makeInteractionPoint(options.current) : detail.current;
8512
+ const info = options?.info;
8513
+ const afterDetail = {
8514
+ ...detail,
8515
+ current,
8516
+ itemIds: detail.itemIds.length > 0 ? detail.itemIds : [...info?.itemIds ?? []],
8517
+ outcome,
8518
+ ...info ? { info } : {}
8519
+ };
8520
+ for (const hook of afterInteractionHooksRef.current) {
8521
+ hook(afterDetail);
8522
+ }
8523
+ },
8524
+ [makeInteractionPoint]
8525
+ );
8407
8526
  onWorldPointerDownRef.current = onWorldPointerDown;
8408
8527
  const originalOnItemsChangeRef = useRef(onItemsChange);
8409
8528
  originalOnItemsChangeRef.current = onItemsChange;
@@ -8644,9 +8763,14 @@ var VectorViewport = forwardRef(
8644
8763
  );
8645
8764
  if (item) {
8646
8765
  const exists = itemsRef.current.some((it) => it.id === id);
8766
+ const info = {
8767
+ motive: "draw",
8768
+ itemIds: [id],
8769
+ toolId: args.tool
8770
+ };
8647
8771
  change(
8648
8772
  exists ? replaceItem(itemsRef.current, id, item) : [...itemsRef.current, item],
8649
- { motive: "draw", itemIds: [id], toolId: args.tool }
8773
+ info
8650
8774
  );
8651
8775
  patchCurrentStrokeStyle({
8652
8776
  stroke: item.stroke ?? DEFAULT_STROKE_STYLE.stroke,
@@ -8654,10 +8778,15 @@ var VectorViewport = forwardRef(
8654
8778
  strokeOpacity: item.strokeOpacity,
8655
8779
  strokeDash: item.strokeDash
8656
8780
  });
8781
+ if (!shouldKeepToolForContinuousPenInput(args.tool, args.pointerType)) {
8782
+ requestAutoResetTool(args.tool);
8783
+ }
8784
+ return info;
8657
8785
  }
8658
8786
  if (!shouldKeepToolForContinuousPenInput(args.tool, args.pointerType)) {
8659
8787
  requestAutoResetTool(args.tool);
8660
8788
  }
8789
+ return void 0;
8661
8790
  },
8662
8791
  [
8663
8792
  patchCurrentStrokeStyle,
@@ -8788,17 +8917,19 @@ var VectorViewport = forwardRef(
8788
8917
  }
8789
8918
  emitRemoteStrokePreviewClear();
8790
8919
  setPlacementPreview(null);
8791
- commitCompletedStroke({
8920
+ const info = commitCompletedStroke({
8792
8921
  tool,
8793
8922
  pointerType,
8794
8923
  points: [...pts],
8795
8924
  style,
8796
8925
  itemId
8797
8926
  });
8927
+ finishCanvuInteraction("completed", { info });
8798
8928
  },
8799
8929
  [
8800
8930
  commitCompletedStroke,
8801
8931
  emitRemoteStrokePreviewClear,
8932
+ finishCanvuInteraction,
8802
8933
  releaseInteractionPointer,
8803
8934
  renderSceneWithLivePenStroke
8804
8935
  ]
@@ -9868,12 +9999,26 @@ var VectorViewport = forwardRef(
9868
9999
  if (!raw) return void 0;
9869
10000
  return raw.toolKind === "arrow" && raw.arrowBind ? bakeArrowItemToAbsolute(raw, canonical) : raw;
9870
10001
  };
10002
+ const startInteraction = (kind, itemIds) => startCanvuInteraction({
10003
+ kind,
10004
+ toolId: tool,
10005
+ pointerType: e.pointerType,
10006
+ button: e.button,
10007
+ worldX,
10008
+ worldY,
10009
+ clientX: e.clientX,
10010
+ clientY: e.clientY,
10011
+ shiftKey: e.shiftKey,
10012
+ altKey: e.altKey,
10013
+ metaKey: e.metaKey,
10014
+ ctrlKey: e.ctrlKey,
10015
+ itemIds
10016
+ });
10017
+ const stopHandledInteraction = () => {
10018
+ e.preventDefault();
10019
+ e.stopPropagation();
10020
+ };
9871
10021
  if (tool === "eraser") {
9872
- dragStateRef.current = { kind: "erase" };
9873
- eraserPreviewIdsRef.current = /* @__PURE__ */ new Set();
9874
- setEraserPreviewIds([]);
9875
- setEraserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
9876
- setEraserActive(true);
9877
10022
  const toErase = collectEraserTargetsAtWorldPoint(
9878
10023
  resolved,
9879
10024
  worldX,
@@ -9883,6 +10028,15 @@ var VectorViewport = forwardRef(
9883
10028
  ignoreLocked: true
9884
10029
  }
9885
10030
  );
10031
+ if (!startInteraction("erase", toErase)) {
10032
+ stopHandledInteraction();
10033
+ return;
10034
+ }
10035
+ dragStateRef.current = { kind: "erase" };
10036
+ eraserPreviewIdsRef.current = /* @__PURE__ */ new Set();
10037
+ setEraserPreviewIds([]);
10038
+ setEraserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
10039
+ setEraserActive(true);
9886
10040
  if (toErase.length > 0) {
9887
10041
  for (const id of toErase) {
9888
10042
  eraserPreviewIdsRef.current.add(id);
@@ -9915,6 +10069,10 @@ var VectorViewport = forwardRef(
9915
10069
  const snapSpin = bakedSnapshot(selected.id);
9916
10070
  if (!snapSpin) return;
9917
10071
  const pivot = itemPivotWorld(selected);
10072
+ if (!startInteraction("rotate", [selected.id])) {
10073
+ stopHandledInteraction();
10074
+ return;
10075
+ }
9918
10076
  dragStateRef.current = {
9919
10077
  kind: "rotate",
9920
10078
  id: selected.id,
@@ -9939,6 +10097,10 @@ var VectorViewport = forwardRef(
9939
10097
  if (hb) {
9940
10098
  const snapRs = bakedSnapshot(selected.id);
9941
10099
  if (!snapRs) return;
10100
+ if (!startInteraction("resize", [selected.id])) {
10101
+ stopHandledInteraction();
10102
+ return;
10103
+ }
9942
10104
  dragStateRef.current = {
9943
10105
  kind: "resize",
9944
10106
  id: selected.id,
@@ -9971,6 +10133,10 @@ var VectorViewport = forwardRef(
9971
10133
  moveSnapshots[id] = snap;
9972
10134
  }
9973
10135
  }
10136
+ if (!startInteraction("select-mode-item-click", [hit.id])) {
10137
+ stopHandledInteraction();
10138
+ return;
10139
+ }
9974
10140
  dragStateRef.current = {
9975
10141
  kind: "select-mode-item-click",
9976
10142
  id: hit.id,
@@ -9987,7 +10153,14 @@ var VectorViewport = forwardRef(
9987
10153
  }
9988
10154
  if (e.shiftKey) {
9989
10155
  const next = cur.includes(hit.id) ? cur.filter((id) => id !== hit.id) : [...cur, hit.id];
10156
+ if (!startInteraction("select-mode-item-click", [hit.id])) {
10157
+ stopHandledInteraction();
10158
+ return;
10159
+ }
9990
10160
  setEffectiveSelectedIdsRef.current(next);
10161
+ finishCanvuInteraction("completed", {
10162
+ info: { motive: "custom", itemIds: [hit.id], toolId: tool }
10163
+ });
9991
10164
  e.preventDefault();
9992
10165
  e.stopPropagation();
9993
10166
  return;
@@ -9995,7 +10168,6 @@ var VectorViewport = forwardRef(
9995
10168
  let idsToMove;
9996
10169
  if (!cur.includes(hit.id)) {
9997
10170
  idsToMove = [hit.id];
9998
- setEffectiveSelectedIdsRef.current(idsToMove);
9999
10171
  } else {
10000
10172
  idsToMove = [...cur];
10001
10173
  }
@@ -10006,6 +10178,13 @@ var VectorViewport = forwardRef(
10006
10178
  snapshots[id] = snap;
10007
10179
  }
10008
10180
  }
10181
+ if (!startInteraction("move", idsToMove)) {
10182
+ stopHandledInteraction();
10183
+ return;
10184
+ }
10185
+ if (!cur.includes(hit.id)) {
10186
+ setEffectiveSelectedIdsRef.current(idsToMove);
10187
+ }
10009
10188
  dragStateRef.current = {
10010
10189
  kind: "move",
10011
10190
  ids: idsToMove,
@@ -10030,9 +10209,14 @@ var VectorViewport = forwardRef(
10030
10209
  }
10031
10210
  }
10032
10211
  if (Object.keys(snapshots).length > 0) {
10212
+ const moveIds = Object.keys(snapshots);
10213
+ if (!startInteraction("move", moveIds)) {
10214
+ stopHandledInteraction();
10215
+ return;
10216
+ }
10033
10217
  dragStateRef.current = {
10034
10218
  kind: "move",
10035
- ids: Object.keys(snapshots),
10219
+ ids: moveIds,
10036
10220
  snapshots,
10037
10221
  startWorld: { x: worldX, y: worldY }
10038
10222
  };
@@ -10043,6 +10227,10 @@ var VectorViewport = forwardRef(
10043
10227
  }
10044
10228
  }
10045
10229
  }
10230
+ if (!startInteraction("marquee")) {
10231
+ stopHandledInteraction();
10232
+ return;
10233
+ }
10046
10234
  dragStateRef.current = {
10047
10235
  kind: "marquee",
10048
10236
  startWorld: { x: worldX, y: worldY },
@@ -10071,6 +10259,10 @@ var VectorViewport = forwardRef(
10071
10259
  );
10072
10260
  const straightLine = tool === "draw" ? createStraightStrokeState(startPoint, e.clientX, e.clientY) : void 0;
10073
10261
  const directPenStroke = e.pointerType === "pen" && (tool === "draw" || tool === "marker");
10262
+ if (!startInteraction("stroke")) {
10263
+ stopHandledInteraction();
10264
+ return;
10265
+ }
10074
10266
  let itemId;
10075
10267
  if (directPenStroke) {
10076
10268
  itemId = createShapeId();
@@ -10114,6 +10306,10 @@ var VectorViewport = forwardRef(
10114
10306
  return;
10115
10307
  }
10116
10308
  if (tool === "text" || tool === "image") {
10309
+ if (!startInteraction("tap")) {
10310
+ stopHandledInteraction();
10311
+ return;
10312
+ }
10117
10313
  dragStateRef.current = {
10118
10314
  kind: "tap",
10119
10315
  tool,
@@ -10127,6 +10323,10 @@ var VectorViewport = forwardRef(
10127
10323
  }
10128
10324
  const cp = customPlacementRef.current;
10129
10325
  if (tool === "rect" || tool === "ellipse" || tool === "architectural-cloud" || tool === "line" || tool === "arrow" || cp && tool === cp.toolId) {
10326
+ if (!startInteraction("place")) {
10327
+ stopHandledInteraction();
10328
+ return;
10329
+ }
10130
10330
  dragStateRef.current = {
10131
10331
  kind: "place",
10132
10332
  tool,
@@ -10144,8 +10344,10 @@ var VectorViewport = forwardRef(
10144
10344
  captureInteractionPointer,
10145
10345
  emitRemoteStrokePreview,
10146
10346
  finalizeStrokeDragState,
10347
+ finishCanvuInteraction,
10147
10348
  renderSceneWithLivePenStroke,
10148
10349
  screenToWorld,
10350
+ startCanvuInteraction,
10149
10351
  startOrRestartStraightStrokeHoldTimer
10150
10352
  ]
10151
10353
  );
@@ -10174,14 +10376,32 @@ var VectorViewport = forwardRef(
10174
10376
  e.stopImmediatePropagation();
10175
10377
  return;
10176
10378
  }
10177
- wrapperRef.current?.focus({ preventScroll: true });
10178
- setContextMenu(null);
10179
10379
  const startPoint = pointerSampleToWorldPoint(
10180
10380
  screenToWorld,
10181
10381
  e.clientX,
10182
10382
  e.clientY,
10183
10383
  e.pressure
10184
10384
  );
10385
+ if (!startCanvuInteraction({
10386
+ kind: "stroke",
10387
+ toolId: tool,
10388
+ pointerType: e.pointerType,
10389
+ button: e.button,
10390
+ worldX: startPoint.x,
10391
+ worldY: startPoint.y,
10392
+ clientX: e.clientX,
10393
+ clientY: e.clientY,
10394
+ shiftKey: e.shiftKey,
10395
+ altKey: e.altKey,
10396
+ metaKey: e.metaKey,
10397
+ ctrlKey: e.ctrlKey
10398
+ })) {
10399
+ e.preventDefault();
10400
+ e.stopImmediatePropagation();
10401
+ return;
10402
+ }
10403
+ wrapperRef.current?.focus({ preventScroll: true });
10404
+ setContextMenu(null);
10185
10405
  const straightLine = tool === "draw" ? createStraightStrokeState(startPoint, e.clientX, e.clientY) : void 0;
10186
10406
  const itemId = createShapeId();
10187
10407
  const item = createFreehandStrokeItem(
@@ -10229,6 +10449,7 @@ var VectorViewport = forwardRef(
10229
10449
  interactive,
10230
10450
  renderSceneWithLivePenStroke,
10231
10451
  screenToWorld,
10452
+ startCanvuInteraction,
10232
10453
  startOrRestartStraightStrokeHoldTimer
10233
10454
  ]);
10234
10455
  useEffect(() => {
@@ -10279,15 +10500,32 @@ var VectorViewport = forwardRef(
10279
10500
  stopTouchEvent(ev);
10280
10501
  return;
10281
10502
  }
10282
- wrapperRef.current?.focus({ preventScroll: true });
10283
- setContextMenu(null);
10284
- penDetectedRef.current = true;
10285
10503
  const startPoint = pointerSampleToWorldPoint(
10286
10504
  screenToWorld,
10287
10505
  touch.clientX,
10288
10506
  touch.clientY,
10289
10507
  touchPressure(touch)
10290
10508
  );
10509
+ if (!startCanvuInteraction({
10510
+ kind: "stroke",
10511
+ toolId: tool,
10512
+ pointerType: "pen",
10513
+ button: 0,
10514
+ worldX: startPoint.x,
10515
+ worldY: startPoint.y,
10516
+ clientX: touch.clientX,
10517
+ clientY: touch.clientY,
10518
+ shiftKey: ev.shiftKey,
10519
+ altKey: ev.altKey,
10520
+ metaKey: ev.metaKey,
10521
+ ctrlKey: ev.ctrlKey
10522
+ })) {
10523
+ stopTouchEvent(ev);
10524
+ return;
10525
+ }
10526
+ wrapperRef.current?.focus({ preventScroll: true });
10527
+ setContextMenu(null);
10528
+ penDetectedRef.current = true;
10291
10529
  const straightLine = tool === "draw" ? createStraightStrokeState(startPoint, touch.clientX, touch.clientY) : void 0;
10292
10530
  const itemId = createShapeId();
10293
10531
  const item = createFreehandStrokeItem(
@@ -10333,6 +10571,12 @@ var VectorViewport = forwardRef(
10333
10571
  touch.clientY,
10334
10572
  touchPressure(touch)
10335
10573
  );
10574
+ updateCanvuInteractionCurrent({
10575
+ worldX: endpoint.x,
10576
+ worldY: endpoint.y,
10577
+ clientX: touch.clientX,
10578
+ clientY: touch.clientY
10579
+ });
10336
10580
  if (updateStraightStrokeForMove(st, touch.clientX, touch.clientY, endpoint)) {
10337
10581
  debugApplePencilPointer("touchmove-stroke", {
10338
10582
  touchId: touch.identifier,
@@ -10374,16 +10618,42 @@ var VectorViewport = forwardRef(
10374
10618
  if (st.kind !== "stroke") return;
10375
10619
  const touch = findChangedTouch(ev.changedTouches);
10376
10620
  if (!touch) return;
10621
+ const currentPoint = pointerSampleToWorldPoint(
10622
+ screenToWorld,
10623
+ touch.clientX,
10624
+ touch.clientY,
10625
+ touchPressure(touch)
10626
+ );
10627
+ updateCanvuInteractionCurrent({
10628
+ worldX: currentPoint.x,
10629
+ worldY: currentPoint.y,
10630
+ clientX: touch.clientX,
10631
+ clientY: touch.clientY
10632
+ });
10633
+ if (ev.type === "touchcancel") {
10634
+ clearStraightStrokeHoldTimer(st);
10635
+ dragStateRef.current = { kind: "idle" };
10636
+ releaseInteractionPointer();
10637
+ if (st.itemId) {
10638
+ renderSceneWithLivePenStroke(null);
10639
+ }
10640
+ emitRemoteStrokePreviewClear();
10641
+ setPlacementPreview(null);
10642
+ finishCanvuInteraction("cancelled", {
10643
+ current: {
10644
+ worldX: currentPoint.x,
10645
+ worldY: currentPoint.y,
10646
+ clientX: touch.clientX,
10647
+ clientY: touch.clientY
10648
+ }
10649
+ });
10650
+ stopTouchEvent(ev);
10651
+ return;
10652
+ }
10377
10653
  const cam = cameraRef.current;
10378
10654
  if (cam) {
10379
10655
  if (st.straightLine?.active) {
10380
- const endpoint = pointerSampleToWorldPoint(
10381
- screenToWorld,
10382
- touch.clientX,
10383
- touch.clientY,
10384
- touchPressure(touch)
10385
- );
10386
- setStraightStrokeEndpoint(st, endpoint);
10656
+ setStraightStrokeEndpoint(st, currentPoint);
10387
10657
  } else {
10388
10658
  const completedPoints = appendTouchToStrokePoints(
10389
10659
  st.points,
@@ -10434,12 +10704,17 @@ var VectorViewport = forwardRef(
10434
10704
  }, [
10435
10705
  applePencilNav,
10436
10706
  emitRemoteStrokePreview,
10707
+ emitRemoteStrokePreviewClear,
10437
10708
  finalizeStrokeDragState,
10709
+ finishCanvuInteraction,
10438
10710
  interactive,
10711
+ releaseInteractionPointer,
10439
10712
  renderSceneWithLivePenStroke,
10440
10713
  screenToWorld,
10441
10714
  setStraightStrokeEndpoint,
10715
+ startCanvuInteraction,
10442
10716
  startOrRestartStraightStrokeHoldTimer,
10717
+ updateCanvuInteractionCurrent,
10443
10718
  updateStraightStrokeForMove
10444
10719
  ]);
10445
10720
  useEffect(() => {
@@ -10452,6 +10727,12 @@ var VectorViewport = forwardRef(
10452
10727
  if (st.kind === "tap") return;
10453
10728
  if (st.kind === "marquee") {
10454
10729
  const { worldX: worldX2, worldY: worldY2 } = screenToWorld(ev.clientX, ev.clientY);
10730
+ updateCanvuInteractionCurrent({
10731
+ worldX: worldX2,
10732
+ worldY: worldY2,
10733
+ clientX: ev.clientX,
10734
+ clientY: ev.clientY
10735
+ });
10455
10736
  const raw = rectFromCorners(st.startWorld, { x: worldX2, y: worldY2 });
10456
10737
  setPlacementPreview({ kind: "marquee", rect: raw });
10457
10738
  const nextCand = collectItemIdsInRect(
@@ -10479,6 +10760,12 @@ var VectorViewport = forwardRef(
10479
10760
  ev.clientY,
10480
10761
  ev.pointerType === "pen" ? ev.pressure : void 0
10481
10762
  );
10763
+ updateCanvuInteractionCurrent({
10764
+ worldX: endpoint.x,
10765
+ worldY: endpoint.y,
10766
+ clientX: ev.clientX,
10767
+ clientY: ev.clientY
10768
+ });
10482
10769
  if (updateStraightStrokeForMove(st, ev.clientX, ev.clientY, endpoint)) {
10483
10770
  return;
10484
10771
  }
@@ -10527,6 +10814,12 @@ var VectorViewport = forwardRef(
10527
10814
  }
10528
10815
  if (st.kind === "erase") {
10529
10816
  const { worldX: worldX2, worldY: worldY2 } = screenToWorld(ev.clientX, ev.clientY);
10817
+ updateCanvuInteractionCurrent({
10818
+ worldX: worldX2,
10819
+ worldY: worldY2,
10820
+ clientX: ev.clientX,
10821
+ clientY: ev.clientY
10822
+ });
10530
10823
  const lineHitWorld = 10 / cam.zoom;
10531
10824
  setEraserTrail(
10532
10825
  (prev) => pruneEraserTrail([...prev, { x: worldX2, y: worldY2, t: Date.now() }])
@@ -10552,6 +10845,12 @@ var VectorViewport = forwardRef(
10552
10845
  const change = onItemsChangeRef.current;
10553
10846
  if (!change) return;
10554
10847
  const { worldX, worldY } = screenToWorld(ev.clientX, ev.clientY);
10848
+ updateCanvuInteractionCurrent({
10849
+ worldX,
10850
+ worldY,
10851
+ clientX: ev.clientX,
10852
+ clientY: ev.clientY
10853
+ });
10555
10854
  if (st.kind === "select-mode-item-click") {
10556
10855
  const screenDx = ev.clientX - st.startScreen.x;
10557
10856
  const screenDy = ev.clientY - st.startScreen.y;
@@ -10684,29 +10983,64 @@ var VectorViewport = forwardRef(
10684
10983
  setPlacementPreview(null);
10685
10984
  marqueeCandidateIdsRef.current = [];
10686
10985
  setMarqueeCandidateIds([]);
10986
+ finishCanvuInteraction("cancelled");
10687
10987
  return;
10688
10988
  }
10989
+ const finishOutcome = ev.type === "pointercancel" ? "cancelled" : "completed";
10990
+ const currentWorld = screenToWorld(ev.clientX, ev.clientY);
10991
+ const currentInteractionPoint = {
10992
+ worldX: currentWorld.worldX,
10993
+ worldY: currentWorld.worldY,
10994
+ clientX: ev.clientX,
10995
+ clientY: ev.clientY
10996
+ };
10997
+ updateCanvuInteractionCurrent(currentInteractionPoint);
10689
10998
  if (st.kind === "move" || st.kind === "resize" || st.kind === "rotate") {
10999
+ const info = st.kind === "move" ? { motive: "move", itemIds: [...st.ids] } : st.kind === "resize" ? { motive: "resize", itemIds: [st.id] } : { motive: "rotate", itemIds: [st.id] };
10690
11000
  dragStateRef.current = { kind: "idle" };
10691
11001
  releaseInteractionPointer();
11002
+ finishCanvuInteraction(finishOutcome, {
11003
+ current: currentInteractionPoint,
11004
+ ...finishOutcome === "completed" ? { info } : {}
11005
+ });
10692
11006
  return;
10693
11007
  }
10694
11008
  if (st.kind === "select-mode-item-click") {
10695
11009
  dragStateRef.current = { kind: "idle" };
10696
11010
  releaseInteractionPointer();
10697
- if (ev.type === "pointercancel") return;
11011
+ if (finishOutcome === "cancelled") {
11012
+ finishCanvuInteraction("cancelled", {
11013
+ current: currentInteractionPoint
11014
+ });
11015
+ return;
11016
+ }
10698
11017
  const dx = ev.clientX - st.startScreen.x;
10699
11018
  const dy = ev.clientY - st.startScreen.y;
10700
- if (Math.hypot(dx, dy) > TAP_PX) return;
11019
+ if (Math.hypot(dx, dy) > TAP_PX) {
11020
+ finishCanvuInteraction("cancelled", {
11021
+ current: currentInteractionPoint
11022
+ });
11023
+ return;
11024
+ }
10701
11025
  const item = itemsRef.current.find((candidate) => candidate.id === st.id) ?? resolvedItemsRef.current.find((candidate) => candidate.id === st.id);
10702
- if (!item) return;
11026
+ if (!item) {
11027
+ finishCanvuInteraction("cancelled", {
11028
+ current: currentInteractionPoint
11029
+ });
11030
+ return;
11031
+ }
10703
11032
  const placement = findSelectModeItemClickPlacement(
10704
11033
  item,
10705
11034
  allCustomPlacementsRef.current
10706
11035
  );
10707
11036
  const onSelectModeItemClick = placement?.onSelectModeItemClick;
10708
- if (!onSelectModeItemClick) return;
10709
- const { worldX, worldY } = screenToWorld(ev.clientX, ev.clientY);
11037
+ if (!onSelectModeItemClick) {
11038
+ finishCanvuInteraction("cancelled", {
11039
+ current: currentInteractionPoint
11040
+ });
11041
+ return;
11042
+ }
11043
+ const { worldX, worldY } = currentWorld;
10710
11044
  onSelectModeItemClick({
10711
11045
  item,
10712
11046
  worldX,
@@ -10727,6 +11061,10 @@ var VectorViewport = forwardRef(
10727
11061
  },
10728
11062
  setSelectedIds: (ids) => setEffectiveSelectedIdsRef.current(ids)
10729
11063
  });
11064
+ finishCanvuInteraction("completed", {
11065
+ current: currentInteractionPoint,
11066
+ info: { motive: "custom", itemIds: [item.id], toolId: "select" }
11067
+ });
10730
11068
  return;
10731
11069
  }
10732
11070
  if (st.kind === "marquee") {
@@ -10735,16 +11073,25 @@ var VectorViewport = forwardRef(
10735
11073
  setPlacementPreview(null);
10736
11074
  marqueeCandidateIdsRef.current = [];
10737
11075
  setMarqueeCandidateIds([]);
10738
- const { worldX, worldY } = screenToWorld(ev.clientX, ev.clientY);
11076
+ const { worldX, worldY } = currentWorld;
10739
11077
  const raw = rectFromCorners(st.startWorld, { x: worldX, y: worldY });
10740
11078
  const br = normalizeRect(raw);
10741
11079
  const screenDx = ev.clientX - st.startScreen.x;
10742
11080
  const screenDy = ev.clientY - st.startScreen.y;
11081
+ if (finishOutcome === "cancelled") {
11082
+ finishCanvuInteraction("cancelled", {
11083
+ current: currentInteractionPoint
11084
+ });
11085
+ return;
11086
+ }
10743
11087
  const tooSmall = Math.hypot(screenDx, screenDy) < TAP_PX || br.width < MIN_MARQUEE_WORLD && br.height < MIN_MARQUEE_WORLD;
10744
11088
  if (tooSmall) {
10745
11089
  if (!st.shiftKey) {
10746
11090
  setEffectiveSelectedIdsRef.current([]);
10747
11091
  }
11092
+ finishCanvuInteraction("cancelled", {
11093
+ current: currentInteractionPoint
11094
+ });
10748
11095
  return;
10749
11096
  }
10750
11097
  const picked = collectItemIdsInRect(resolvedItemsRef.current, br);
@@ -10761,9 +11108,26 @@ var VectorViewport = forwardRef(
10761
11108
  } else {
10762
11109
  setEffectiveSelectedIdsRef.current(picked);
10763
11110
  }
11111
+ finishCanvuInteraction("completed", {
11112
+ current: currentInteractionPoint
11113
+ });
10764
11114
  return;
10765
11115
  }
10766
11116
  if (st.kind === "stroke") {
11117
+ if (finishOutcome === "cancelled") {
11118
+ clearStraightStrokeHoldTimer(st);
11119
+ dragStateRef.current = { kind: "idle" };
11120
+ releaseInteractionPointer();
11121
+ if (st.itemId) {
11122
+ renderSceneWithLivePenStroke(null);
11123
+ }
11124
+ emitRemoteStrokePreviewClear();
11125
+ setPlacementPreview(null);
11126
+ finishCanvuInteraction("cancelled", {
11127
+ current: currentInteractionPoint
11128
+ });
11129
+ return;
11130
+ }
10767
11131
  const completedPoints = (() => {
10768
11132
  if (st.straightLine?.active) {
10769
11133
  const endpoint = pointerSampleToWorldPoint(
@@ -10794,15 +11158,20 @@ var VectorViewport = forwardRef(
10794
11158
  }
10795
11159
  if (st.kind === "erase") {
10796
11160
  const change = onItemsChangeRef.current;
11161
+ const erasedIds = [...eraserPreviewIdsRef.current];
11162
+ const info = erasedIds.length > 0 ? {
11163
+ motive: "erase",
11164
+ itemIds: erasedIds,
11165
+ toolId: "eraser"
11166
+ } : void 0;
10797
11167
  if (change && eraserPreviewIdsRef.current.size > 0) {
10798
11168
  const idSet = new Set(eraserPreviewIdsRef.current);
10799
- change(
10800
- itemsRef.current.filter((i) => !idSet.has(i.id)),
10801
- {
10802
- motive: "erase",
10803
- itemIds: [...idSet]
10804
- }
10805
- );
11169
+ if (finishOutcome === "completed") {
11170
+ change(
11171
+ itemsRef.current.filter((i) => !idSet.has(i.id)),
11172
+ info
11173
+ );
11174
+ }
10806
11175
  }
10807
11176
  eraserPreviewIdsRef.current.clear();
10808
11177
  setEraserPreviewIds([]);
@@ -10810,7 +11179,13 @@ var VectorViewport = forwardRef(
10810
11179
  setEraserActive(false);
10811
11180
  dragStateRef.current = { kind: "idle" };
10812
11181
  releaseInteractionPointer();
10813
- requestAutoResetTool("eraser");
11182
+ if (finishOutcome === "completed") {
11183
+ requestAutoResetTool("eraser");
11184
+ }
11185
+ finishCanvuInteraction(finishOutcome, {
11186
+ current: currentInteractionPoint,
11187
+ ...finishOutcome === "completed" && info ? { info } : {}
11188
+ });
10814
11189
  return;
10815
11190
  }
10816
11191
  if (st.kind === "tap") {
@@ -10818,11 +11193,22 @@ var VectorViewport = forwardRef(
10818
11193
  const dy = ev.clientY - st.startScreen.y;
10819
11194
  dragStateRef.current = { kind: "idle" };
10820
11195
  releaseInteractionPointer();
10821
- if (Math.hypot(dx, dy) > TAP_PX) return;
11196
+ if (finishOutcome === "cancelled" || Math.hypot(dx, dy) > TAP_PX) {
11197
+ finishCanvuInteraction("cancelled", {
11198
+ current: currentInteractionPoint
11199
+ });
11200
+ return;
11201
+ }
10822
11202
  const change = onItemsChangeRef.current;
10823
- if (!change) return;
11203
+ if (!change) {
11204
+ finishCanvuInteraction("cancelled", {
11205
+ current: currentInteractionPoint
11206
+ });
11207
+ return;
11208
+ }
10824
11209
  const id = createShapeId();
10825
11210
  const { x: worldX, y: worldY } = st.startWorld;
11211
+ let info;
10826
11212
  if (st.tool === "text") {
10827
11213
  const fs = strokeStyleRef.current.textFontSize;
10828
11214
  const baseline = textBaselineYFor(fs);
@@ -10845,11 +11231,12 @@ var VectorViewport = forwardRef(
10845
11231
  bounds: { ...newItem.bounds }
10846
11232
  };
10847
11233
  const hidden = applyTextDraftWhileEditing(newItem, "");
10848
- change([...itemsRef.current, hidden], {
11234
+ info = {
10849
11235
  motive: "text-create",
10850
11236
  itemIds: [id],
10851
11237
  toolId: st.tool
10852
- });
11238
+ };
11239
+ change([...itemsRef.current, hidden], info);
10853
11240
  setEffectiveSelectedIdsRef.current([id]);
10854
11241
  setEditingTextId(id);
10855
11242
  setDraftText("");
@@ -10858,6 +11245,10 @@ var VectorViewport = forwardRef(
10858
11245
  imageInputRef.current?.click();
10859
11246
  }
10860
11247
  requestAutoResetTool(st.tool);
11248
+ finishCanvuInteraction("completed", {
11249
+ current: currentInteractionPoint,
11250
+ ...info ? { info } : {}
11251
+ });
10861
11252
  return;
10862
11253
  }
10863
11254
  if (st.kind === "place") {
@@ -10868,11 +11259,19 @@ var VectorViewport = forwardRef(
10868
11259
  dragStateRef.current = { kind: "idle" };
10869
11260
  releaseInteractionPointer();
10870
11261
  setPlacementPreview(null);
10871
- if (!change) return;
11262
+ if (finishOutcome === "cancelled" || !change) {
11263
+ finishCanvuInteraction("cancelled", {
11264
+ current: currentInteractionPoint
11265
+ });
11266
+ return;
11267
+ }
10872
11268
  if (st.tool === "arrow") {
10873
11269
  const screenDx = ev.clientX - st.startScreen.x;
10874
11270
  const screenDy = ev.clientY - st.startScreen.y;
10875
11271
  if (Math.hypot(screenDx, screenDy) < MIN_ARROW_DRAG_PX) {
11272
+ finishCanvuInteraction("cancelled", {
11273
+ current: currentInteractionPoint
11274
+ });
10876
11275
  return;
10877
11276
  }
10878
11277
  const maxDist = ARROW_BIND_SNAP_PX / cam.zoom;
@@ -10904,18 +11303,23 @@ var VectorViewport = forwardRef(
10904
11303
  ...snapB ? { end: snapB.binding } : {}
10905
11304
  };
10906
11305
  }
11306
+ const info2 = {
11307
+ motive: "place",
11308
+ itemIds: [id2],
11309
+ toolId: st.tool
11310
+ };
10907
11311
  change(
10908
11312
  [
10909
11313
  ...itemsRef.current,
10910
11314
  createLineItem(id2, rawArrow, line, "arrow", pen2, arrowBind)
10911
11315
  ],
10912
- {
10913
- motive: "place",
10914
- itemIds: [id2],
10915
- toolId: st.tool
10916
- }
11316
+ info2
10917
11317
  );
10918
11318
  setEffectiveSelectedIdsRef.current([id2]);
11319
+ finishCanvuInteraction("completed", {
11320
+ current: currentInteractionPoint,
11321
+ info: info2
11322
+ });
10919
11323
  return;
10920
11324
  }
10921
11325
  let raw = rectFromCorners(a, b);
@@ -10940,47 +11344,61 @@ var VectorViewport = forwardRef(
10940
11344
  }
10941
11345
  const id = createShapeId();
10942
11346
  const pen = strokeStyleRef.current;
11347
+ let info;
10943
11348
  if (cpUp && st.tool === cpUp.toolId) {
10944
11349
  const item = tagCustomPlacementItem(
10945
11350
  cpUp.createItem({ id, bounds: br }),
10946
11351
  cpUp.toolId
10947
11352
  );
10948
- change(itemsRef.current.concat(item), {
11353
+ info = {
10949
11354
  motive: "place",
10950
11355
  itemIds: [id],
10951
11356
  toolId: st.tool
10952
- });
11357
+ };
11358
+ change(itemsRef.current.concat(item), info);
10953
11359
  if (cpUp.selectAfterCreate !== false) {
10954
11360
  setEffectiveSelectedIdsRef.current([id]);
10955
11361
  }
11362
+ finishCanvuInteraction("completed", {
11363
+ current: currentInteractionPoint,
11364
+ info
11365
+ });
10956
11366
  return;
10957
11367
  }
10958
11368
  if (st.tool === "rect") {
10959
- change([...itemsRef.current, createRectangleItem(id, raw, pen)], {
11369
+ info = {
10960
11370
  motive: "place",
10961
11371
  itemIds: [id],
10962
11372
  toolId: st.tool
10963
- });
11373
+ };
11374
+ change([...itemsRef.current, createRectangleItem(id, raw, pen)], info);
10964
11375
  setEffectiveSelectedIdsRef.current([id]);
10965
11376
  } else if (st.tool === "ellipse") {
10966
- change([...itemsRef.current, createEllipseItem(id, raw, pen)], {
11377
+ info = {
10967
11378
  motive: "place",
10968
11379
  itemIds: [id],
10969
11380
  toolId: st.tool
10970
- });
11381
+ };
11382
+ change([...itemsRef.current, createEllipseItem(id, raw, pen)], info);
10971
11383
  setEffectiveSelectedIdsRef.current([id]);
10972
11384
  } else if (st.tool === "architectural-cloud") {
11385
+ info = {
11386
+ motive: "place",
11387
+ itemIds: [id],
11388
+ toolId: st.tool
11389
+ };
10973
11390
  change(
10974
11391
  [...itemsRef.current, createArchitecturalCloudItem(id, raw, pen)],
10975
- {
10976
- motive: "place",
10977
- itemIds: [id],
10978
- toolId: st.tool
10979
- }
11392
+ info
10980
11393
  );
10981
11394
  setEffectiveSelectedIdsRef.current([id]);
10982
11395
  } else if (st.tool === "line" || st.tool === "arrow") {
10983
11396
  const line = lineEndpointsToLocal(raw, lineA, lineB);
11397
+ info = {
11398
+ motive: "place",
11399
+ itemIds: [id],
11400
+ toolId: st.tool
11401
+ };
10984
11402
  change(
10985
11403
  [
10986
11404
  ...itemsRef.current,
@@ -10992,15 +11410,15 @@ var VectorViewport = forwardRef(
10992
11410
  pen
10993
11411
  )
10994
11412
  ],
10995
- {
10996
- motive: "place",
10997
- itemIds: [id],
10998
- toolId: st.tool
10999
- }
11413
+ info
11000
11414
  );
11001
11415
  setEffectiveSelectedIdsRef.current([id]);
11002
11416
  }
11003
11417
  requestAutoResetTool(st.tool);
11418
+ finishCanvuInteraction("completed", {
11419
+ current: currentInteractionPoint,
11420
+ ...info ? { info } : {}
11421
+ });
11004
11422
  }
11005
11423
  };
11006
11424
  document.addEventListener("pointermove", onMove);
@@ -11018,11 +11436,13 @@ var VectorViewport = forwardRef(
11018
11436
  pruneEraserTrail,
11019
11437
  pruneLaserTrail,
11020
11438
  finalizeStrokeDragState,
11439
+ finishCanvuInteraction,
11021
11440
  renderSceneWithLivePenStroke,
11022
11441
  releaseInteractionPointer,
11023
11442
  requestAutoResetTool,
11024
11443
  screenToWorld,
11025
11444
  setStraightStrokeEndpoint,
11445
+ updateCanvuInteractionCurrent,
11026
11446
  updateStraightStrokeForMove
11027
11447
  ]);
11028
11448
  const selectedItemsForOverlay = useMemo(() => {