canvu-react 0.3.14 → 0.3.16

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 = {
@@ -6010,7 +5997,7 @@ body[data-canvu-pen-active="true"] [data-slot="shape-context-menu"] * {
6010
5997
  }
6011
5998
  `;
6012
5999
  function debugApplePencilPointer(phase, detail) {
6013
- console.debug(`[canvu][apple-pencil] ${phase}`, detail);
6000
+ return;
6014
6001
  }
6015
6002
  var MARKER_TOOL_STYLE = {
6016
6003
  stroke: "#fde047",
@@ -6610,7 +6597,6 @@ var VectorViewport = react.forwardRef(
6610
6597
  const captureInteractionPointer = react.useCallback(
6611
6598
  (target, pointerId, options) => {
6612
6599
  const captureNativePointer = options?.captureNativePointer ?? true;
6613
- debugApplePencilPointer("capture", { pointerId, captureNativePointer });
6614
6600
  activeInteractionPointerIdRef.current = pointerId;
6615
6601
  activeInteractionPointerTargetRef.current = captureNativePointer ? target : null;
6616
6602
  if (captureNativePointer) {
@@ -6622,7 +6608,6 @@ var VectorViewport = react.forwardRef(
6622
6608
  const releaseInteractionPointer = react.useCallback(() => {
6623
6609
  const pointerId = activeInteractionPointerIdRef.current;
6624
6610
  const target = activeInteractionPointerTargetRef.current;
6625
- debugApplePencilPointer("release", { pointerId });
6626
6611
  if (pointerId != null && target) {
6627
6612
  try {
6628
6613
  if (target.hasPointerCapture(pointerId)) {
@@ -6655,8 +6640,6 @@ var VectorViewport = react.forwardRef(
6655
6640
  const itemId = strokeState.itemId;
6656
6641
  const style = { ...strokeStyleRef.current };
6657
6642
  debugApplePencilPointer("finalize-stroke", {
6658
- pointerType,
6659
- tool,
6660
6643
  points: pts.length
6661
6644
  });
6662
6645
  dragStateRef.current = { kind: "idle" };
@@ -7406,6 +7389,7 @@ var VectorViewport = react.forwardRef(
7406
7389
  }
7407
7390
  const result = await ingestAssetFilesToSceneItems({
7408
7391
  files,
7392
+ existingItems: itemsRef.current,
7409
7393
  worldCenter: {
7410
7394
  x: worldX,
7411
7395
  y: worldY
@@ -7433,12 +7417,12 @@ var VectorViewport = react.forwardRef(
7433
7417
  );
7434
7418
  const handleImageFileChange = react.useCallback(
7435
7419
  async (e) => {
7436
- const file = e.target.files?.[0];
7420
+ const files = e.target.files ? Array.from(e.target.files) : [];
7437
7421
  e.target.value = "";
7438
7422
  const pending = pendingImagePlacementRef.current;
7439
7423
  pendingImagePlacementRef.current = null;
7440
- if (!file || !pending) return;
7441
- await placeImageFilesAtWorld([file], pending.worldX, pending.worldY);
7424
+ if (files.length === 0 || !pending) return;
7425
+ await placeImageFilesAtWorld(files, pending.worldX, pending.worldY);
7442
7426
  },
7443
7427
  [placeImageFilesAtWorld]
7444
7428
  );
@@ -7534,14 +7518,12 @@ var VectorViewport = react.forwardRef(
7534
7518
  pointerType: e.pointerType,
7535
7519
  pointerId: e.pointerId,
7536
7520
  pressure: e.pointerType === "pen" ? e.pressure : void 0,
7537
- activePointerId,
7538
7521
  dragKind: currentDragState.kind
7539
7522
  });
7540
7523
  }
7541
7524
  if (e.pointerType === "pen" && currentDragState.kind === "stroke" && currentDragState.pointerType === "pen") {
7542
7525
  debugApplePencilPointer("pen-reentry", {
7543
7526
  pointerId: e.pointerId,
7544
- previousPointerId: activePointerId,
7545
7527
  previousPoints: currentDragState.points.length
7546
7528
  });
7547
7529
  finalizeStrokeDragState(currentDragState);
@@ -7887,9 +7869,7 @@ var VectorViewport = react.forwardRef(
7887
7869
  debugApplePencilPointer("native-pointerdown", {
7888
7870
  pointerType: e.pointerType,
7889
7871
  pointerId: e.pointerId,
7890
- pressure: e.pressure,
7891
- tool
7892
- });
7872
+ pressure: e.pressure});
7893
7873
  e.preventDefault();
7894
7874
  e.stopImmediatePropagation();
7895
7875
  };
@@ -7989,9 +7969,7 @@ var VectorViewport = react.forwardRef(
7989
7969
  debugApplePencilPointer("touchstart-stroke", {
7990
7970
  touchId: touch.identifier,
7991
7971
  touchType: touchKind(touch),
7992
- force: touchPressure(touch),
7993
- tool
7994
- });
7972
+ force: touchPressure(touch)});
7995
7973
  stopTouchEvent(ev);
7996
7974
  };
7997
7975
  const onTouchMove = (ev) => {
@@ -8713,6 +8691,7 @@ var VectorViewport = react.forwardRef(
8713
8691
  {
8714
8692
  ref: imageInputRef,
8715
8693
  type: "file",
8694
+ multiple: true,
8716
8695
  accept: "image/*,application/pdf",
8717
8696
  style: { display: "none" },
8718
8697
  "aria-label": "Select image or PDF to place on canvas",