canvu-react 0.3.14 → 0.3.17

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/README.md CHANGED
@@ -308,6 +308,11 @@ canvu can now stream PDF pages into the canvas progressively through
308
308
  `onItemsReady(...)`, skip PDF thumbnails during ingest, and use a lower initial
309
309
  raster scale by default to improve time-to-first-render.
310
310
 
311
+ The native file tool now accepts multiple images and PDFs from the picker and
312
+ stacks every imported asset vertically. When the board already has uploaded
313
+ images or PDFs, new imports are inserted immediately after the existing stack
314
+ instead of overlapping previous content.
315
+
311
316
  This helper is the same ingestion layer used internally by the native file
312
317
  tool, so external imports do not need to reimplement PDF rasterization, local
313
318
  blob persistence, or `pluginData` attachment.
package/dist/react.cjs CHANGED
@@ -1365,12 +1365,12 @@ async function uploadAssetIfNeeded(assetStore, file, kind) {
1365
1365
  async function ingestAssetFilesToSceneItems(options) {
1366
1366
  const {
1367
1367
  files,
1368
+ existingItems = [],
1368
1369
  worldCenter,
1369
1370
  assetStore,
1370
1371
  imageStore = new IndexedDbImageStore(),
1371
1372
  createId = createShapeId,
1372
1373
  gapWorld = 16,
1373
- stepWorld = 48,
1374
1374
  pdfScale = 1.15,
1375
1375
  pdfPageConcurrency = 2,
1376
1376
  decorateItem,
@@ -1379,10 +1379,7 @@ async function ingestAssetFilesToSceneItems(options) {
1379
1379
  } = options;
1380
1380
  const items = [];
1381
1381
  const errors = [];
1382
- let imagePlacementIndex = 0;
1383
- let occupiedBottomY = null;
1384
- let imageYOffsetAdjustment = 0;
1385
- let hasImagePlacementBase = false;
1382
+ let occupiedBottomY = existingItems.length > 0 ? Math.max(...existingItems.map((item) => item.bounds.y + item.bounds.height)) : null;
1386
1383
  for (const file of files) {
1387
1384
  const kind = getAssetKindForFile(file);
1388
1385
  if (!kind) {
@@ -1405,10 +1402,10 @@ async function ingestAssetFilesToSceneItems(options) {
1405
1402
  const uploadResult2 = await uploadResultPromise;
1406
1403
  const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
1407
1404
  const naturalTopY2 = worldCenter.y - page.height / 2;
1408
- const stackedTopY = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
1405
+ const stackedTopY2 = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
1409
1406
  const bounds2 = {
1410
1407
  x: worldCenter.x - page.width / 2,
1411
- y: Math.max(naturalTopY2, stackedTopY),
1408
+ y: Math.max(naturalTopY2, stackedTopY2),
1412
1409
  width: page.width,
1413
1410
  height: page.height
1414
1411
  };
@@ -1446,9 +1443,6 @@ async function ingestAssetFilesToSceneItems(options) {
1446
1443
  onItemsReady?.([item2], { file, kind });
1447
1444
  }
1448
1445
  });
1449
- hasImagePlacementBase = false;
1450
- imagePlacementIndex = 0;
1451
- imageYOffsetAdjustment = 0;
1452
1446
  continue;
1453
1447
  }
1454
1448
  const [uploadResult, storedImage] = await Promise.all([
@@ -1459,17 +1453,11 @@ async function ingestAssetFilesToSceneItems(options) {
1459
1453
  const fullUrl = await createBlobUrlFromStore(imageStore, blobId);
1460
1454
  const thumbBlob = await imageStore.getThumbnail(thumbnailBlobId);
1461
1455
  const thumbnailHref = thumbBlob ? URL.createObjectURL(thumbBlob) : null;
1462
- const ox = imagePlacementIndex % 8 * stepWorld;
1463
- const oy = Math.floor(imagePlacementIndex / 8) * stepWorld;
1464
- const naturalTopY = worldCenter.y - height / 2 + oy;
1465
- if (!hasImagePlacementBase) {
1466
- const minimumTopY = occupiedBottomY == null ? naturalTopY : occupiedBottomY + gapWorld;
1467
- imageYOffsetAdjustment = Math.max(0, minimumTopY - naturalTopY);
1468
- hasImagePlacementBase = true;
1469
- }
1456
+ const naturalTopY = worldCenter.y - height / 2;
1457
+ const stackedTopY = occupiedBottomY == null ? naturalTopY : occupiedBottomY + gapWorld;
1470
1458
  const bounds = {
1471
- x: worldCenter.x - width / 2 + ox,
1472
- y: naturalTopY + imageYOffsetAdjustment,
1459
+ x: worldCenter.x - width / 2,
1460
+ y: Math.max(naturalTopY, stackedTopY),
1473
1461
  width,
1474
1462
  height
1475
1463
  };
@@ -1499,7 +1487,6 @@ async function ingestAssetFilesToSceneItems(options) {
1499
1487
  );
1500
1488
  items.push(item);
1501
1489
  onItemsReady?.([item], { file, kind });
1502
- imagePlacementIndex++;
1503
1490
  occupiedBottomY = occupiedBottomY == null ? bounds.y + height : Math.max(occupiedBottomY, bounds.y + height);
1504
1491
  } catch (error) {
1505
1492
  const fileError = {
@@ -1686,11 +1673,7 @@ function VectorSelectionInspector({
1686
1673
  ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
1687
1674
  })
1688
1675
  }
1689
- ),
1690
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1691
- strokeWidth2,
1692
- "px"
1693
- ] })
1676
+ )
1694
1677
  ] }),
1695
1678
  showMarkerOpacity2 && /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle, children: [
1696
1679
  "Opacidade (marcador)",
@@ -1796,11 +1779,7 @@ function VectorSelectionInspector({
1796
1779
  strokeWidth: Number(e.target.value)
1797
1780
  })
1798
1781
  }
1799
- ),
1800
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1801
- strokeWidth,
1802
- "px"
1803
- ] })
1782
+ )
1804
1783
  ] }),
1805
1784
  showMarkerOpacity && firstMarker && /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle, children: [
1806
1785
  "Opacidade (marcador)",
@@ -6010,7 +5989,7 @@ body[data-canvu-pen-active="true"] [data-slot="shape-context-menu"] * {
6010
5989
  }
6011
5990
  `;
6012
5991
  function debugApplePencilPointer(phase, detail) {
6013
- console.debug(`[canvu][apple-pencil] ${phase}`, detail);
5992
+ return;
6014
5993
  }
6015
5994
  var MARKER_TOOL_STYLE = {
6016
5995
  stroke: "#fde047",
@@ -6610,7 +6589,6 @@ var VectorViewport = react.forwardRef(
6610
6589
  const captureInteractionPointer = react.useCallback(
6611
6590
  (target, pointerId, options) => {
6612
6591
  const captureNativePointer = options?.captureNativePointer ?? true;
6613
- debugApplePencilPointer("capture", { pointerId, captureNativePointer });
6614
6592
  activeInteractionPointerIdRef.current = pointerId;
6615
6593
  activeInteractionPointerTargetRef.current = captureNativePointer ? target : null;
6616
6594
  if (captureNativePointer) {
@@ -6622,7 +6600,6 @@ var VectorViewport = react.forwardRef(
6622
6600
  const releaseInteractionPointer = react.useCallback(() => {
6623
6601
  const pointerId = activeInteractionPointerIdRef.current;
6624
6602
  const target = activeInteractionPointerTargetRef.current;
6625
- debugApplePencilPointer("release", { pointerId });
6626
6603
  if (pointerId != null && target) {
6627
6604
  try {
6628
6605
  if (target.hasPointerCapture(pointerId)) {
@@ -6655,8 +6632,6 @@ var VectorViewport = react.forwardRef(
6655
6632
  const itemId = strokeState.itemId;
6656
6633
  const style = { ...strokeStyleRef.current };
6657
6634
  debugApplePencilPointer("finalize-stroke", {
6658
- pointerType,
6659
- tool,
6660
6635
  points: pts.length
6661
6636
  });
6662
6637
  dragStateRef.current = { kind: "idle" };
@@ -7406,6 +7381,7 @@ var VectorViewport = react.forwardRef(
7406
7381
  }
7407
7382
  const result = await ingestAssetFilesToSceneItems({
7408
7383
  files,
7384
+ existingItems: itemsRef.current,
7409
7385
  worldCenter: {
7410
7386
  x: worldX,
7411
7387
  y: worldY
@@ -7433,12 +7409,12 @@ var VectorViewport = react.forwardRef(
7433
7409
  );
7434
7410
  const handleImageFileChange = react.useCallback(
7435
7411
  async (e) => {
7436
- const file = e.target.files?.[0];
7412
+ const files = e.target.files ? Array.from(e.target.files) : [];
7437
7413
  e.target.value = "";
7438
7414
  const pending = pendingImagePlacementRef.current;
7439
7415
  pendingImagePlacementRef.current = null;
7440
- if (!file || !pending) return;
7441
- await placeImageFilesAtWorld([file], pending.worldX, pending.worldY);
7416
+ if (files.length === 0 || !pending) return;
7417
+ await placeImageFilesAtWorld(files, pending.worldX, pending.worldY);
7442
7418
  },
7443
7419
  [placeImageFilesAtWorld]
7444
7420
  );
@@ -7534,14 +7510,12 @@ var VectorViewport = react.forwardRef(
7534
7510
  pointerType: e.pointerType,
7535
7511
  pointerId: e.pointerId,
7536
7512
  pressure: e.pointerType === "pen" ? e.pressure : void 0,
7537
- activePointerId,
7538
7513
  dragKind: currentDragState.kind
7539
7514
  });
7540
7515
  }
7541
7516
  if (e.pointerType === "pen" && currentDragState.kind === "stroke" && currentDragState.pointerType === "pen") {
7542
7517
  debugApplePencilPointer("pen-reentry", {
7543
7518
  pointerId: e.pointerId,
7544
- previousPointerId: activePointerId,
7545
7519
  previousPoints: currentDragState.points.length
7546
7520
  });
7547
7521
  finalizeStrokeDragState(currentDragState);
@@ -7706,10 +7680,10 @@ var VectorViewport = react.forwardRef(
7706
7680
  return;
7707
7681
  }
7708
7682
  if (!e.shiftKey && cur.length > 0) {
7709
- const selectedArrowOrLine = cur.map((id) => resolved.find((it) => it.id === id)).filter(
7710
- (it) => it != null && !it.locked && (it.toolKind === "line" || it.toolKind === "arrow")
7683
+ const selectedUnlockedItems = cur.map((id) => resolved.find((it) => it.id === id)).filter(
7684
+ (it) => it != null && !it.locked
7711
7685
  );
7712
- if (selectedArrowOrLine.some(
7686
+ if (selectedUnlockedItems.some(
7713
7687
  (it) => pointInSelectedItemBounds(it, worldX, worldY)
7714
7688
  )) {
7715
7689
  const snapshots = {};
@@ -7887,9 +7861,7 @@ var VectorViewport = react.forwardRef(
7887
7861
  debugApplePencilPointer("native-pointerdown", {
7888
7862
  pointerType: e.pointerType,
7889
7863
  pointerId: e.pointerId,
7890
- pressure: e.pressure,
7891
- tool
7892
- });
7864
+ pressure: e.pressure});
7893
7865
  e.preventDefault();
7894
7866
  e.stopImmediatePropagation();
7895
7867
  };
@@ -7989,9 +7961,7 @@ var VectorViewport = react.forwardRef(
7989
7961
  debugApplePencilPointer("touchstart-stroke", {
7990
7962
  touchId: touch.identifier,
7991
7963
  touchType: touchKind(touch),
7992
- force: touchPressure(touch),
7993
- tool
7994
- });
7964
+ force: touchPressure(touch)});
7995
7965
  stopTouchEvent(ev);
7996
7966
  };
7997
7967
  const onTouchMove = (ev) => {
@@ -8713,6 +8683,7 @@ var VectorViewport = react.forwardRef(
8713
8683
  {
8714
8684
  ref: imageInputRef,
8715
8685
  type: "file",
8686
+ multiple: true,
8716
8687
  accept: "image/*,application/pdf",
8717
8688
  style: { display: "none" },
8718
8689
  "aria-label": "Select image or PDF to place on canvas",