canvu-react 0.4.46 → 0.4.48

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.
Files changed (51) hide show
  1. package/dist/{asset-hydration-Dc7fsnTG.d.ts → asset-hydration-BSjiek7Q.d.ts} +2 -2
  2. package/dist/{asset-hydration-Cy_2FyV5.d.cts → asset-hydration-F6aM5C7x.d.cts} +2 -2
  3. package/dist/{asset-store-TzOPvlgn.d.cts → asset-store-35ysK28r.d.cts} +1 -1
  4. package/dist/{asset-store-DQPRZEcy.d.ts → asset-store-D_FjW_CN.d.ts} +1 -1
  5. package/dist/chatbot.d.cts +6 -6
  6. package/dist/chatbot.d.ts +6 -6
  7. package/dist/index.cjs +49 -1
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +53 -9
  10. package/dist/index.d.ts +53 -9
  11. package/dist/index.js +49 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/{link-item-DwzXOwU5.d.cts → link-item-BMV3VUCr.d.cts} +1 -1
  14. package/dist/{link-item-IW4GTnxl.d.ts → link-item-COoNNvCu.d.ts} +1 -1
  15. package/dist/native.cjs +104 -51
  16. package/dist/native.cjs.map +1 -1
  17. package/dist/native.d.cts +6 -6
  18. package/dist/native.d.ts +6 -6
  19. package/dist/native.js +104 -51
  20. package/dist/native.js.map +1 -1
  21. package/dist/react.cjs +259 -18
  22. package/dist/react.cjs.map +1 -1
  23. package/dist/react.d.cts +29 -13
  24. package/dist/react.d.ts +29 -13
  25. package/dist/react.js +259 -18
  26. package/dist/react.js.map +1 -1
  27. package/dist/realtime.cjs +3 -1
  28. package/dist/realtime.cjs.map +1 -1
  29. package/dist/realtime.d.cts +8 -8
  30. package/dist/realtime.d.ts +8 -8
  31. package/dist/realtime.js +3 -1
  32. package/dist/realtime.js.map +1 -1
  33. package/dist/realtimeNative.d.cts +4 -4
  34. package/dist/realtimeNative.d.ts +4 -4
  35. package/dist/{shape-builders-Cyh8zvDG.d.ts → shape-builders-BCOAG0pS.d.ts} +1 -1
  36. package/dist/{shape-builders-CKEMjivV.d.cts → shape-builders-BmLS8CNh.d.cts} +1 -1
  37. package/dist/tldraw.cjs +3 -1
  38. package/dist/tldraw.cjs.map +1 -1
  39. package/dist/tldraw.d.cts +1 -1
  40. package/dist/tldraw.d.ts +1 -1
  41. package/dist/tldraw.js +3 -1
  42. package/dist/tldraw.js.map +1 -1
  43. package/dist/{types-BUPc2Zgw.d.cts → types-6HszqSa5.d.cts} +1 -1
  44. package/dist/{types-B7xZAKVJ.d.ts → types-BAEHaIYO.d.ts} +43 -6
  45. package/dist/{types-B82WiQQh.d.ts → types-BMMPUak7.d.ts} +1 -1
  46. package/dist/{types-BQUbxMgz.d.cts → types-BOQLWyCw.d.cts} +1 -1
  47. package/dist/{types-CYtq9Pr9.d.ts → types-BtWbGOqh.d.ts} +1 -1
  48. package/dist/{types-BCCvY6ie.d.cts → types-fJNwEnHf.d.cts} +35 -1
  49. package/dist/{types-BCCvY6ie.d.ts → types-fJNwEnHf.d.ts} +35 -1
  50. package/dist/{types-C4wI3Jyc.d.cts → types-uzeExFkd.d.cts} +43 -6
  51. package/package.json +1 -1
package/dist/react.cjs CHANGED
@@ -3362,7 +3362,9 @@ function ToolPluginComponent({
3362
3362
  pluginId,
3363
3363
  tool,
3364
3364
  toolTransform,
3365
- createItem
3365
+ createItem,
3366
+ selectAfterCreate,
3367
+ onSelectModeItemClick
3366
3368
  }) {
3367
3369
  const contribution = react.useMemo(
3368
3370
  () => ({
@@ -3371,17 +3373,25 @@ function ToolPluginComponent({
3371
3373
  customPlacements: createItem ? [
3372
3374
  {
3373
3375
  toolId: tool.id,
3374
- createItem
3376
+ createItem,
3377
+ selectAfterCreate,
3378
+ onSelectModeItemClick
3375
3379
  }
3376
3380
  ] : void 0
3377
3381
  }),
3378
- [createItem, tool, toolTransform]
3382
+ [createItem, onSelectModeItemClick, selectAfterCreate, tool, toolTransform]
3379
3383
  );
3380
3384
  useCanvuPluginContribution(pluginId, contribution);
3381
3385
  return null;
3382
3386
  }
3383
3387
  function createToolPlugin(options) {
3384
- const { createItem, toolTransform, ...tool } = options;
3388
+ const {
3389
+ createItem,
3390
+ toolTransform,
3391
+ selectAfterCreate,
3392
+ onSelectModeItemClick,
3393
+ ...tool
3394
+ } = options;
3385
3395
  const pluginId = `canvu.plugin.tool:${tool.id}`;
3386
3396
  return createCanvuPlugin({
3387
3397
  id: pluginId,
@@ -3392,7 +3402,9 @@ function createToolPlugin(options) {
3392
3402
  pluginId,
3393
3403
  tool,
3394
3404
  toolTransform,
3395
- createItem
3405
+ createItem,
3406
+ selectAfterCreate,
3407
+ onSelectModeItemClick
3396
3408
  }
3397
3409
  );
3398
3410
  }
@@ -5993,7 +6005,43 @@ init_shape_builders();
5993
6005
 
5994
6006
  // src/interaction/resize-handles.ts
5995
6007
  init_rect();
5996
- var HANDLE_IDS = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
6008
+ init_link_item();
6009
+ var ALL_RESIZE_HANDLES = [
6010
+ "nw",
6011
+ "n",
6012
+ "ne",
6013
+ "e",
6014
+ "se",
6015
+ "s",
6016
+ "sw",
6017
+ "w"
6018
+ ];
6019
+ var CORNER_RESIZE_HANDLES = [
6020
+ "nw",
6021
+ "ne",
6022
+ "se",
6023
+ "sw"
6024
+ ];
6025
+ function dedupeHandles(handles) {
6026
+ const allowed = new Set(handles);
6027
+ return ALL_RESIZE_HANDLES.filter((handle) => allowed.has(handle));
6028
+ }
6029
+ function resolveCustomResizeHandles(handles) {
6030
+ if (handles === "corners") return CORNER_RESIZE_HANDLES;
6031
+ if (handles === "all" || handles === void 0) return ALL_RESIZE_HANDLES;
6032
+ return dedupeHandles(handles);
6033
+ }
6034
+ function resolveResizeHandlesForItem(item) {
6035
+ if (!item) return ALL_RESIZE_HANDLES;
6036
+ if (getLinkData(item)) return CORNER_RESIZE_HANDLES;
6037
+ if (item.toolKind === "custom") {
6038
+ return resolveCustomResizeHandles(item.customResizeHandles);
6039
+ }
6040
+ return ALL_RESIZE_HANDLES;
6041
+ }
6042
+ function itemAllowsResizeHandle(item, handle) {
6043
+ return resolveResizeHandlesForItem(item).includes(handle);
6044
+ }
5997
6045
  function getHandleWorldPosition(bounds, id) {
5998
6046
  const r = normalizeRect(bounds);
5999
6047
  const cx = r.x + r.width / 2;
@@ -6017,7 +6065,7 @@ function getHandleWorldPosition(bounds, id) {
6017
6065
  return { x: r.x, y: cy };
6018
6066
  }
6019
6067
  }
6020
- function hitTestResizeHandle(bounds, worldX, worldY, radiusWorld, rotationRad = 0) {
6068
+ function hitTestResizeHandle(bounds, worldX, worldY, radiusWorld, rotationRad = 0, handles = ALL_RESIZE_HANDLES) {
6021
6069
  const r = normalizeRect(bounds);
6022
6070
  const pl = worldToItemLocal(
6023
6071
  worldX,
@@ -6031,7 +6079,7 @@ function hitTestResizeHandle(bounds, worldX, worldY, radiusWorld, rotationRad =
6031
6079
  const localBounds = { x: 0, y: 0, width: r.width, height: r.height };
6032
6080
  let best = null;
6033
6081
  let bestD = radiusWorld;
6034
- for (const id of HANDLE_IDS) {
6082
+ for (const id of handles) {
6035
6083
  const p = getHandleWorldPosition(localBounds, id);
6036
6084
  const d = Math.hypot(pl.x - p.x, pl.y - p.y);
6037
6085
  if (d <= bestD) {
@@ -6663,6 +6711,9 @@ function moveItemByDelta(item, dx, dy) {
6663
6711
  }
6664
6712
  function resizeItemByHandle(item, start, handle, currentWorld) {
6665
6713
  const sb = normalizeRect(start.bounds);
6714
+ if (!itemAllowsResizeHandle(item, handle)) {
6715
+ return item;
6716
+ }
6666
6717
  const newBoundsRaw = computeNewBoundsForResize(item, sb, handle, currentWorld);
6667
6718
  const nb = normalizeRect(newBoundsRaw);
6668
6719
  const k = item.toolKind;
@@ -6923,6 +6974,14 @@ function formatItemPlacementTransform(item) {
6923
6974
  const cy = r.height / 2;
6924
6975
  return `translate(${item.x}, ${item.y}) translate(${cx}, ${cy}) rotate(${deg}) translate(${-cx}, ${-cy})`;
6925
6976
  }
6977
+ function itemClassName(item) {
6978
+ const kind = item.toolKind ?? "unknown";
6979
+ const classes = [`canvu-item`, `canvu-item--${kind}`];
6980
+ if (item.svgClassName) {
6981
+ classes.push(item.svgClassName);
6982
+ }
6983
+ return classes.join(" ");
6984
+ }
6926
6985
  var SvgVectorRenderer = class {
6927
6986
  container;
6928
6987
  scene;
@@ -6930,6 +6989,8 @@ var SvgVectorRenderer = class {
6930
6989
  svg;
6931
6990
  rootG;
6932
6991
  itemNodeCache = /* @__PURE__ */ new Map();
6992
+ selectedIds = /* @__PURE__ */ new Set();
6993
+ hoveredItemId = null;
6933
6994
  liveOverlay = null;
6934
6995
  resizeObserver;
6935
6996
  constructor(options) {
@@ -6950,6 +7011,20 @@ var SvgVectorRenderer = class {
6950
7011
  this.resizeObserver = new ResizeObserver(() => this.render());
6951
7012
  this.resizeObserver.observe(this.container);
6952
7013
  }
7014
+ /**
7015
+ * Updates interaction attributes on item groups for CSS hooks.
7016
+ *
7017
+ * Use `[data-canvu-selected="true"]` and `[data-canvu-hovered="true"]`
7018
+ * for animations in interactive canvases where the scene SVG may ignore
7019
+ * pointer events.
7020
+ */
7021
+ setInteractionState(state) {
7022
+ this.selectedIds = new Set(state.selectedIds ?? []);
7023
+ this.hoveredItemId = state.hoveredItemId ?? null;
7024
+ for (const [id, cached] of this.itemNodeCache) {
7025
+ this.applyInteractionAttributes(cached.g, id);
7026
+ }
7027
+ }
6953
7028
  /**
6954
7029
  * Reads container size, culls items, and updates the SVG (incrementally when possible).
6955
7030
  */
@@ -6986,6 +7061,7 @@ var SvgVectorRenderer = class {
6986
7061
  if (!this.liveOverlay) {
6987
7062
  const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
6988
7063
  g.setAttribute("data-live-overlay", "true");
7064
+ g.setAttribute("data-canvu-item", "true");
6989
7065
  this.liveOverlay = {
6990
7066
  g,
6991
7067
  lastChildrenSvg: "",
@@ -6995,6 +7071,11 @@ var SvgVectorRenderer = class {
6995
7071
  }
6996
7072
  const cached = this.liveOverlay;
6997
7073
  const t = formatItemPlacementTransform(item);
7074
+ cached.g.setAttribute("class", itemClassName(item));
7075
+ cached.g.setAttribute("data-item-id", item.id);
7076
+ cached.g.setAttribute("data-tool-kind", item.toolKind ?? "");
7077
+ cached.g.setAttribute("data-canvu-selected", "false");
7078
+ cached.g.setAttribute("data-canvu-hovered", "false");
6998
7079
  if (cached.lastTransform !== t) {
6999
7080
  cached.g.setAttribute("transform", t);
7000
7081
  cached.lastTransform = t;
@@ -7025,6 +7106,7 @@ var SvgVectorRenderer = class {
7025
7106
  let cached = this.itemNodeCache.get(item.id);
7026
7107
  if (!cached) {
7027
7108
  const g2 = document.createElementNS("http://www.w3.org/2000/svg", "g");
7109
+ g2.setAttribute("data-canvu-item", "true");
7028
7110
  g2.setAttribute("data-item-id", item.id);
7029
7111
  cached = {
7030
7112
  g: g2,
@@ -7034,6 +7116,11 @@ var SvgVectorRenderer = class {
7034
7116
  this.itemNodeCache.set(item.id, cached);
7035
7117
  }
7036
7118
  const { g } = cached;
7119
+ g.setAttribute("class", itemClassName(item));
7120
+ g.setAttribute("data-canvu-item", "true");
7121
+ g.setAttribute("data-item-id", item.id);
7122
+ g.setAttribute("data-tool-kind", item.toolKind ?? "");
7123
+ this.applyInteractionAttributes(g, item.id);
7037
7124
  const t = formatItemPlacementTransform(item);
7038
7125
  if (cached.lastTransform !== t) {
7039
7126
  g.setAttribute("transform", t);
@@ -7050,6 +7137,16 @@ var SvgVectorRenderer = class {
7050
7137
  previousNode = g;
7051
7138
  }
7052
7139
  }
7140
+ applyInteractionAttributes(g, itemId) {
7141
+ g.setAttribute(
7142
+ "data-canvu-selected",
7143
+ this.selectedIds.has(itemId) ? "true" : "false"
7144
+ );
7145
+ g.setAttribute(
7146
+ "data-canvu-hovered",
7147
+ this.hoveredItemId === itemId ? "true" : "false"
7148
+ );
7149
+ }
7053
7150
  destroy() {
7054
7151
  this.resizeObserver.disconnect();
7055
7152
  this.itemNodeCache.clear();
@@ -7157,10 +7254,7 @@ function smoothFreehandPointsToPathD(points) {
7157
7254
  }
7158
7255
 
7159
7256
  // src/react/InteractionOverlay.tsx
7160
- init_link_item();
7161
7257
  init_shape_builders();
7162
- var HANDLE_ORDER = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
7163
- var LINK_HANDLE_ORDER = ["nw", "ne", "se", "sw"];
7164
7258
  var ERASER_TINT = "#cbd5e1";
7165
7259
  var ERASER_TINT_OPACITY = 0.95;
7166
7260
  var ERASER_PREVIEW_OPACITY = 0.3;
@@ -7213,7 +7307,7 @@ function InteractionOverlay({
7213
7307
  ) }, it.id);
7214
7308
  }),
7215
7309
  showResizeHandles && bSingle && single && rotHandlePos && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7216
- (getLinkData(single) ? LINK_HANDLE_ORDER : HANDLE_ORDER).map((hid) => {
7310
+ resolveResizeHandlesForItem(single).map((hid) => {
7217
7311
  const p = getHandleWorldPositionRotated(bSingle, hid, rotSingle);
7218
7312
  return /* @__PURE__ */ jsxRuntime.jsx(
7219
7313
  "circle",
@@ -8027,7 +8121,6 @@ function defaultPlacementWorld(tool, center) {
8027
8121
  lineWorld: [a, b]
8028
8122
  };
8029
8123
  }
8030
- var LINK_CORNER_HANDLES2 = /* @__PURE__ */ new Set(["nw", "ne", "se", "sw"]);
8031
8124
  function pointInSelectedItemBounds(item, worldX, worldY) {
8032
8125
  const bounds = normalizeRect(item.bounds);
8033
8126
  const local = worldToItemLocal(
@@ -8121,6 +8214,16 @@ function reorderItemsByIds(items, ids, direction) {
8121
8214
  function isDefaultMarkerToolStyle(style) {
8122
8215
  return style.stroke === MARKER_TOOL_STYLE.stroke && style.strokeWidth === MARKER_TOOL_STYLE.strokeWidth && style.strokeOpacity === MARKER_TOOL_STYLE.strokeOpacity;
8123
8216
  }
8217
+ function tagCustomPlacementItem(item, toolId) {
8218
+ return item.customToolId === toolId ? item : { ...item, customToolId: toolId };
8219
+ }
8220
+ function findSelectModeItemClickPlacement(item, placements) {
8221
+ const toolId = item.customToolId;
8222
+ if (!toolId) return null;
8223
+ return [...placements].reverse().find(
8224
+ (placement) => placement.toolId === toolId && placement.onSelectModeItemClick
8225
+ ) ?? null;
8226
+ }
8124
8227
  function mergeToolListById(baseTools, pluginTools) {
8125
8228
  const next = [...baseTools];
8126
8229
  for (const tool of pluginTools) {
@@ -8285,6 +8388,7 @@ var VectorViewport = react.forwardRef(
8285
8388
  const [cameraForOverlay, setCameraForOverlay] = react.useState(null);
8286
8389
  const sceneRef = react.useRef(null);
8287
8390
  const rendererRef = react.useRef(null);
8391
+ const hoveredItemIdRef = react.useRef(null);
8288
8392
  const pluginViewportRef = react.useRef(null);
8289
8393
  const [pluginContributions, setPluginContributions] = react.useState({});
8290
8394
  const registerContribution = react.useCallback(
@@ -8439,6 +8543,8 @@ var VectorViewport = react.forwardRef(
8439
8543
  assetStoreRef.current = assetStore;
8440
8544
  const customPlacementRef = react.useRef(customPlacement);
8441
8545
  customPlacementRef.current = customPlacement;
8546
+ const allCustomPlacementsRef = react.useRef(allCustomPlacements);
8547
+ allCustomPlacementsRef.current = allCustomPlacements;
8442
8548
  const dragStateRef = react.useRef({ kind: "idle" });
8443
8549
  const clipboardRef = react.useRef(null);
8444
8550
  const undoStackRef = react.useRef([]);
@@ -8490,6 +8596,12 @@ var VectorViewport = react.forwardRef(
8490
8596
  }, [isSelectionControlled, controlledSelectedKey, uncontrolledSel]);
8491
8597
  const effectiveSelectedIdsRef = react.useRef([]);
8492
8598
  effectiveSelectedIdsRef.current = effectiveSelectedIds;
8599
+ const syncRendererInteractionState = react.useCallback(() => {
8600
+ rendererRef.current?.setInteractionState({
8601
+ selectedIds: effectiveSelectedIdsRef.current,
8602
+ hoveredItemId: hoveredItemIdRef.current
8603
+ });
8604
+ }, []);
8493
8605
  const setEffectiveSelectedIds = react.useCallback(
8494
8606
  (ids) => {
8495
8607
  const next = [...ids];
@@ -8932,6 +9044,10 @@ var VectorViewport = react.forwardRef(
8932
9044
  pointerEventsNone: interactiveRef.current
8933
9045
  });
8934
9046
  rendererRef.current = renderer;
9047
+ renderer.setInteractionState({
9048
+ selectedIds: effectiveSelectedIdsRef.current,
9049
+ hoveredItemId: hoveredItemIdRef.current
9050
+ });
8935
9051
  renderer.render();
8936
9052
  if (!reducedMotionRef.current) {
8937
9053
  setZoomPercent(Math.round(camera.zoom * 100));
@@ -8969,11 +9085,53 @@ var VectorViewport = react.forwardRef(
8969
9085
  setCameraForOverlay(null);
8970
9086
  };
8971
9087
  }, [applePencilNav, renderFrame]);
9088
+ react.useEffect(() => {
9089
+ rendererRef.current?.setInteractionState({
9090
+ selectedIds: effectiveSelectedIds,
9091
+ hoveredItemId: hoveredItemIdRef.current
9092
+ });
9093
+ }, [effectiveSelectedIds]);
8972
9094
  react.useEffect(() => {
8973
9095
  const r = rendererRef.current;
8974
9096
  if (!r) return;
8975
9097
  r.setPointerEventsNone(interactive);
8976
9098
  }, [interactive]);
9099
+ const clearHoveredItem = react.useCallback(() => {
9100
+ if (hoveredItemIdRef.current === null) return;
9101
+ hoveredItemIdRef.current = null;
9102
+ syncRendererInteractionState();
9103
+ }, [syncRendererInteractionState]);
9104
+ const updateHoveredItem = react.useCallback(
9105
+ (clientX, clientY) => {
9106
+ const cam = cameraRef.current;
9107
+ const rect = sceneContainerRef.current?.getBoundingClientRect();
9108
+ if (!cam || !rect) return;
9109
+ const { worldX, worldY } = cam.screenToWorld(
9110
+ clientX - rect.left,
9111
+ clientY - rect.top
9112
+ );
9113
+ const hit = hitTestWorldPoint(resolvedItemsRef.current, worldX, worldY, {
9114
+ lineHitWorld: 10 / cam.zoom,
9115
+ ignoreLocked: true
9116
+ });
9117
+ const next = hit?.id ?? null;
9118
+ if (hoveredItemIdRef.current === next) return;
9119
+ hoveredItemIdRef.current = next;
9120
+ syncRendererInteractionState();
9121
+ },
9122
+ [syncRendererInteractionState]
9123
+ );
9124
+ react.useEffect(() => {
9125
+ const root = wrapperRef.current;
9126
+ if (!root) return;
9127
+ const move = (e) => updateHoveredItem(e.clientX, e.clientY);
9128
+ root.addEventListener("pointermove", move, { passive: true });
9129
+ root.addEventListener("pointerleave", clearHoveredItem);
9130
+ return () => {
9131
+ root.removeEventListener("pointermove", move);
9132
+ root.removeEventListener("pointerleave", clearHoveredItem);
9133
+ };
9134
+ }, [clearHoveredItem, updateHoveredItem]);
8977
9135
  react.useEffect(() => {
8978
9136
  if (!eraserActive && laserTrail.length === 0) return;
8979
9137
  const timer = window.setInterval(() => {
@@ -9952,10 +10110,10 @@ var VectorViewport = react.forwardRef(
9952
10110
  worldX,
9953
10111
  worldY,
9954
10112
  handleRadiusWorld,
9955
- rot
10113
+ rot,
10114
+ resolveResizeHandlesForItem(selected)
9956
10115
  );
9957
- const isLinkResizeHandle = hb && getLinkData(selected) ? LINK_CORNER_HANDLES2.has(hb) : Boolean(hb);
9958
- if (hb && isLinkResizeHandle) {
10116
+ if (hb) {
9959
10117
  const snapRs = bakedSnapshot(selected.id);
9960
10118
  if (!snapRs) return;
9961
10119
  dragStateRef.current = {
@@ -9979,6 +10137,31 @@ var VectorViewport = react.forwardRef(
9979
10137
  ignoreLocked: true
9980
10138
  });
9981
10139
  if (hit) {
10140
+ const selectModeClickPlacement = !e.shiftKey ? findSelectModeItemClickPlacement(hit, allCustomPlacementsRef.current) : null;
10141
+ if (selectModeClickPlacement) {
10142
+ const isAlreadySelected = cur.includes(hit.id);
10143
+ const moveIds = isAlreadySelected ? [...cur] : [hit.id];
10144
+ const moveSnapshots = {};
10145
+ for (const id of moveIds) {
10146
+ const snap = bakedSnapshot(id);
10147
+ if (snap) {
10148
+ moveSnapshots[id] = snap;
10149
+ }
10150
+ }
10151
+ dragStateRef.current = {
10152
+ kind: "select-mode-item-click",
10153
+ id: hit.id,
10154
+ startWorld: { x: worldX, y: worldY },
10155
+ startScreen: { x: e.clientX, y: e.clientY },
10156
+ moveIds,
10157
+ moveSnapshots,
10158
+ ...isAlreadySelected ? {} : { selectIdsOnDrag: [hit.id] }
10159
+ };
10160
+ captureInteractionPointer(e.currentTarget, e.pointerId);
10161
+ e.preventDefault();
10162
+ e.stopPropagation();
10163
+ return;
10164
+ }
9982
10165
  if (e.shiftKey) {
9983
10166
  const next = cur.includes(hit.id) ? cur.filter((id) => id !== hit.id) : [...cur, hit.id];
9984
10167
  setEffectiveSelectedIdsRef.current(next);
@@ -10546,6 +10729,21 @@ var VectorViewport = react.forwardRef(
10546
10729
  const change = onItemsChangeRef.current;
10547
10730
  if (!change) return;
10548
10731
  const { worldX, worldY } = screenToWorld(ev.clientX, ev.clientY);
10732
+ if (st.kind === "select-mode-item-click") {
10733
+ const screenDx = ev.clientX - st.startScreen.x;
10734
+ const screenDy = ev.clientY - st.startScreen.y;
10735
+ if (Math.hypot(screenDx, screenDy) <= TAP_PX) return;
10736
+ if (st.selectIdsOnDrag) {
10737
+ setEffectiveSelectedIdsRef.current(st.selectIdsOnDrag);
10738
+ }
10739
+ dragStateRef.current = {
10740
+ kind: "move",
10741
+ ids: st.moveIds,
10742
+ snapshots: st.moveSnapshots,
10743
+ startWorld: st.startWorld
10744
+ };
10745
+ return;
10746
+ }
10549
10747
  if (st.kind === "move") {
10550
10748
  const dx = worldX - st.startWorld.x;
10551
10749
  const dy = worldY - st.startWorld.y;
@@ -10664,6 +10862,43 @@ var VectorViewport = react.forwardRef(
10664
10862
  releaseInteractionPointer();
10665
10863
  return;
10666
10864
  }
10865
+ if (st.kind === "select-mode-item-click") {
10866
+ dragStateRef.current = { kind: "idle" };
10867
+ releaseInteractionPointer();
10868
+ if (ev.type === "pointercancel") return;
10869
+ const dx = ev.clientX - st.startScreen.x;
10870
+ const dy = ev.clientY - st.startScreen.y;
10871
+ if (Math.hypot(dx, dy) > TAP_PX) return;
10872
+ const item = itemsRef.current.find((candidate) => candidate.id === st.id) ?? resolvedItemsRef.current.find((candidate) => candidate.id === st.id);
10873
+ if (!item) return;
10874
+ const placement = findSelectModeItemClickPlacement(
10875
+ item,
10876
+ allCustomPlacementsRef.current
10877
+ );
10878
+ const onSelectModeItemClick = placement?.onSelectModeItemClick;
10879
+ if (!onSelectModeItemClick) return;
10880
+ const { worldX, worldY } = screenToWorld(ev.clientX, ev.clientY);
10881
+ onSelectModeItemClick({
10882
+ item,
10883
+ worldX,
10884
+ worldY,
10885
+ clientX: ev.clientX,
10886
+ clientY: ev.clientY,
10887
+ pointerType: ev.pointerType,
10888
+ shiftKey: ev.shiftKey,
10889
+ altKey: ev.altKey,
10890
+ metaKey: ev.metaKey,
10891
+ ctrlKey: ev.ctrlKey,
10892
+ items: itemsRef.current,
10893
+ updateItem: (next) => {
10894
+ onItemsChangeRef.current?.(
10895
+ replaceItem(itemsRef.current, item.id, next)
10896
+ );
10897
+ },
10898
+ setSelectedIds: (ids) => setEffectiveSelectedIdsRef.current(ids)
10899
+ });
10900
+ return;
10901
+ }
10667
10902
  if (st.kind === "marquee") {
10668
10903
  dragStateRef.current = { kind: "idle" };
10669
10904
  releaseInteractionPointer();
@@ -10859,8 +11094,14 @@ var VectorViewport = react.forwardRef(
10859
11094
  const id = createShapeId();
10860
11095
  const pen = strokeStyleRef.current;
10861
11096
  if (cpUp && st.tool === cpUp.toolId) {
10862
- change(itemsRef.current.concat(cpUp.createItem({ id, bounds: br })));
10863
- setEffectiveSelectedIdsRef.current([id]);
11097
+ const item = tagCustomPlacementItem(
11098
+ cpUp.createItem({ id, bounds: br }),
11099
+ cpUp.toolId
11100
+ );
11101
+ change(itemsRef.current.concat(item));
11102
+ if (cpUp.selectAfterCreate !== false) {
11103
+ setEffectiveSelectedIdsRef.current([id]);
11104
+ }
10864
11105
  return;
10865
11106
  }
10866
11107
  if (st.tool === "rect") {