canvu-react 0.3.10 → 0.3.12

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.cjs CHANGED
@@ -928,35 +928,336 @@ function canvasToBlob2(canvas, mime, quality) {
928
928
  );
929
929
  });
930
930
  }
931
+ function normalizePdfPageNumbers(pageNumbers, pageCount) {
932
+ if (!pageNumbers || pageNumbers.length === 0) {
933
+ return Array.from({ length: pageCount }, (_, index) => index + 1);
934
+ }
935
+ return [...new Set(pageNumbers)].filter((pageNumber) => pageNumber >= 1 && pageNumber <= pageCount).sort((left, right) => left - right);
936
+ }
937
+ async function runWithConcurrency(items, concurrency, execute) {
938
+ const results = new Array(items.length);
939
+ let nextIndex = 0;
940
+ const workerCount = Math.max(1, Math.min(concurrency, items.length));
941
+ await Promise.all(
942
+ Array.from({ length: workerCount }, async () => {
943
+ while (nextIndex < items.length) {
944
+ const currentIndex = nextIndex;
945
+ nextIndex += 1;
946
+ const item = items[currentIndex];
947
+ if (item === void 0) {
948
+ continue;
949
+ }
950
+ results[currentIndex] = await execute(item);
951
+ }
952
+ })
953
+ );
954
+ return results;
955
+ }
931
956
  async function loadPdfToStore(file, store, options) {
932
957
  const scale = options?.scale ?? 1.5;
958
+ const pageConcurrency = options?.pageConcurrency ?? 2;
933
959
  const pdfjs = await getPdfJs();
934
960
  const arrayBuffer = await file.arrayBuffer();
935
961
  const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
936
- const results = [];
937
- for (let i = 1; i <= pdf.numPages; i++) {
938
- const page = await pdf.getPage(i);
939
- const { canvas, width, height } = await renderPageToCanvas(page, scale);
940
- const mime = "image/png";
941
- const pageBlob = await canvasToBlob2(canvas, mime);
942
- const blobId = await store.storeOriginal(pageBlob);
943
- const thumbScale = Math.min(1, 256 / Math.max(width, height));
944
- const tw = Math.max(1, Math.round(width * thumbScale));
945
- const th = Math.max(1, Math.round(height * thumbScale));
946
- const thumbCanvas = document.createElement("canvas");
947
- thumbCanvas.width = tw;
948
- thumbCanvas.height = th;
949
- const tCtx = thumbCanvas.getContext("2d");
950
- if (tCtx) {
951
- tCtx.imageSmoothingEnabled = true;
952
- tCtx.imageSmoothingQuality = "high";
953
- tCtx.drawImage(canvas, 0, 0, tw, th);
962
+ const pageNumbers = normalizePdfPageNumbers(options?.pageNumbers, pdf.numPages);
963
+ return await runWithConcurrency(
964
+ pageNumbers,
965
+ pageConcurrency,
966
+ async (pageNumber) => {
967
+ const page = await pdf.getPage(pageNumber);
968
+ const { canvas, width, height } = await renderPageToCanvas(page, scale);
969
+ const mime = "image/png";
970
+ const pageBlob = await canvasToBlob2(canvas, mime);
971
+ const blobId = await store.storeOriginal(pageBlob);
972
+ const thumbScale = Math.min(1, 256 / Math.max(width, height));
973
+ const tw = Math.max(1, Math.round(width * thumbScale));
974
+ const th = Math.max(1, Math.round(height * thumbScale));
975
+ const thumbCanvas = document.createElement("canvas");
976
+ thumbCanvas.width = tw;
977
+ thumbCanvas.height = th;
978
+ const tCtx = thumbCanvas.getContext("2d");
979
+ if (tCtx) {
980
+ tCtx.imageSmoothingEnabled = true;
981
+ tCtx.imageSmoothingQuality = "high";
982
+ tCtx.drawImage(canvas, 0, 0, tw, th);
983
+ }
984
+ const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
985
+ const thumbnailBlobId = await store.storeThumbnail(thumbBlob);
986
+ return {
987
+ blobId,
988
+ thumbnailBlobId,
989
+ width,
990
+ height,
991
+ pageNumber
992
+ };
954
993
  }
955
- const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
956
- const thumbnailBlobId = await store.storeThumbnail(thumbBlob);
957
- results.push({ blobId, thumbnailBlobId, width, height, pageNumber: i });
994
+ );
995
+ }
996
+
997
+ // src/react/asset-hydration.ts
998
+ init_shape_builders();
999
+ var HYDRATION_CACHE_NAME = "canvu-asset-hydration-v1";
1000
+ var DEFAULT_PDF_SCALE = 1.15;
1001
+ var DEFAULT_PDF_PAGE_CONCURRENCY = 2;
1002
+ var hydrationBlobPromises = /* @__PURE__ */ new Map();
1003
+ function buildImageHydrationKey(assetId) {
1004
+ return `image:${assetId}`;
1005
+ }
1006
+ function buildPdfHydrationKey(assetId, pageNumber, scale) {
1007
+ return `pdf:${assetId}:page:${pageNumber}:scale:${scale}`;
1008
+ }
1009
+ function getCacheRequest(key) {
1010
+ return new Request(
1011
+ `https://canvu.local/asset-hydration/${encodeURIComponent(key)}`
1012
+ );
1013
+ }
1014
+ async function openHydrationCache() {
1015
+ if (typeof caches === "undefined") return null;
1016
+ try {
1017
+ return await caches.open(HYDRATION_CACHE_NAME);
1018
+ } catch {
1019
+ return null;
958
1020
  }
959
- return results;
1021
+ }
1022
+ async function readCachedHydrationBlob(key) {
1023
+ const cache = await openHydrationCache();
1024
+ if (!cache) return null;
1025
+ try {
1026
+ const response = await cache.match(getCacheRequest(key));
1027
+ if (!response?.ok) return null;
1028
+ return await response.blob();
1029
+ } catch {
1030
+ return null;
1031
+ }
1032
+ }
1033
+ async function writeCachedHydrationBlob(key, blob) {
1034
+ const cache = await openHydrationCache();
1035
+ if (!cache) return;
1036
+ try {
1037
+ await cache.put(getCacheRequest(key), new Response(blob));
1038
+ } catch {
1039
+ }
1040
+ }
1041
+ async function fetchBlob(url) {
1042
+ try {
1043
+ const response = await fetch(url);
1044
+ if (!response.ok) return null;
1045
+ return await response.blob();
1046
+ } catch {
1047
+ return null;
1048
+ }
1049
+ }
1050
+ async function getHydrationBlob(key, preferCachedRasters, loader) {
1051
+ const cached = preferCachedRasters ? await readCachedHydrationBlob(key) : null;
1052
+ if (cached) return cached;
1053
+ const inFlight = hydrationBlobPromises.get(key);
1054
+ if (inFlight) return await inFlight;
1055
+ const nextPromise = (async () => {
1056
+ const blob = await loader();
1057
+ if (blob) {
1058
+ await writeCachedHydrationBlob(key, blob);
1059
+ }
1060
+ return blob;
1061
+ })();
1062
+ hydrationBlobPromises.set(key, nextPromise);
1063
+ try {
1064
+ return await nextPromise;
1065
+ } finally {
1066
+ hydrationBlobPromises.delete(key);
1067
+ }
1068
+ }
1069
+ function registerObjectUrl(objectUrls, blob) {
1070
+ const href = URL.createObjectURL(blob);
1071
+ objectUrls.push(href);
1072
+ return href;
1073
+ }
1074
+ function getHydrationRequests(items, assetStore, pdfScale) {
1075
+ if (!assetStore.getHydrationRequest) return [];
1076
+ return items.flatMap((item) => {
1077
+ const request = assetStore.getHydrationRequest?.(item);
1078
+ if (!request) return [];
1079
+ return [
1080
+ {
1081
+ request: {
1082
+ ...request,
1083
+ scale: request.scale ?? pdfScale
1084
+ }
1085
+ }
1086
+ ];
1087
+ });
1088
+ }
1089
+ function createPdfHydrationGroups(requests) {
1090
+ const groups = /* @__PURE__ */ new Map();
1091
+ for (const entry of requests) {
1092
+ if (entry.request.kind !== "pdf" || entry.request.pageNumber == null) continue;
1093
+ const scale = entry.request.scale ?? DEFAULT_PDF_SCALE;
1094
+ const key = `${entry.request.assetId}:${scale}`;
1095
+ const existingGroup = groups.get(key);
1096
+ if (existingGroup) {
1097
+ if (!existingGroup.pageNumbers.includes(entry.request.pageNumber)) {
1098
+ existingGroup.pageNumbers.push(entry.request.pageNumber);
1099
+ }
1100
+ continue;
1101
+ }
1102
+ groups.set(key, {
1103
+ assetId: entry.request.assetId,
1104
+ scale,
1105
+ pageNumbers: [entry.request.pageNumber]
1106
+ });
1107
+ }
1108
+ return [...groups.values()];
1109
+ }
1110
+ async function hydrateImageAssets(requests, resolvedAssetUrls, objectUrls, preferCachedRasters) {
1111
+ const uniqueAssetIds = [
1112
+ ...new Set(requests.map((entry) => entry.request.assetId))
1113
+ ];
1114
+ const hydratedEntries = await Promise.all(
1115
+ uniqueAssetIds.map(async (assetId) => {
1116
+ const resolvedAsset = resolvedAssetUrls[assetId];
1117
+ if (!resolvedAsset?.url) {
1118
+ return [assetId, null];
1119
+ }
1120
+ const blob = await getHydrationBlob(
1121
+ buildImageHydrationKey(assetId),
1122
+ preferCachedRasters,
1123
+ async () => await fetchBlob(resolvedAsset.url)
1124
+ );
1125
+ if (!blob) {
1126
+ return [assetId, null];
1127
+ }
1128
+ return [
1129
+ assetId,
1130
+ {
1131
+ href: registerObjectUrl(objectUrls, blob)
1132
+ }
1133
+ ];
1134
+ })
1135
+ );
1136
+ return new Map(hydratedEntries);
1137
+ }
1138
+ async function hydratePdfAssets(requests, resolvedAssetUrls, objectUrls, options) {
1139
+ const hydratedPages = /* @__PURE__ */ new Map();
1140
+ const groups = createPdfHydrationGroups(requests);
1141
+ for (const group of groups) {
1142
+ const resolvedAsset = resolvedAssetUrls[group.assetId];
1143
+ if (!resolvedAsset?.url) {
1144
+ continue;
1145
+ }
1146
+ const missingPages = [];
1147
+ for (const pageNumber of group.pageNumbers) {
1148
+ const cacheKey = buildPdfHydrationKey(group.assetId, pageNumber, group.scale);
1149
+ const cachedBlob = await readCachedHydrationBlob(cacheKey);
1150
+ if (!cachedBlob) {
1151
+ missingPages.push(pageNumber);
1152
+ continue;
1153
+ }
1154
+ hydratedPages.set(cacheKey, {
1155
+ href: registerObjectUrl(objectUrls, cachedBlob)
1156
+ });
1157
+ }
1158
+ if (missingPages.length === 0) {
1159
+ continue;
1160
+ }
1161
+ const pdfBlob = await fetchBlob(resolvedAsset.url);
1162
+ if (!pdfBlob) {
1163
+ continue;
1164
+ }
1165
+ const renderedPages = await loadPdfToStore(pdfBlob, options.imageStore, {
1166
+ scale: group.scale,
1167
+ pageNumbers: missingPages,
1168
+ pageConcurrency: options.pdfPageConcurrency
1169
+ });
1170
+ for (const renderedPage of renderedPages) {
1171
+ const cacheKey = buildPdfHydrationKey(
1172
+ group.assetId,
1173
+ renderedPage.pageNumber,
1174
+ group.scale
1175
+ );
1176
+ const pageBlob = await options.imageStore.getOriginal(renderedPage.blobId);
1177
+ if (!pageBlob) {
1178
+ continue;
1179
+ }
1180
+ await writeCachedHydrationBlob(cacheKey, pageBlob);
1181
+ hydratedPages.set(cacheKey, {
1182
+ href: registerObjectUrl(objectUrls, pageBlob),
1183
+ width: renderedPage.width,
1184
+ height: renderedPage.height
1185
+ });
1186
+ }
1187
+ }
1188
+ return hydratedPages;
1189
+ }
1190
+ async function hydrateSceneItemsWithAssets(items, assetStore, options = {}) {
1191
+ if (!assetStore.resolve || !assetStore.getHydrationRequest) {
1192
+ return {
1193
+ items: [...items],
1194
+ objectUrls: []
1195
+ };
1196
+ }
1197
+ const pdfScale = options.pdfScale ?? DEFAULT_PDF_SCALE;
1198
+ const pdfPageConcurrency = options.pdfPageConcurrency ?? DEFAULT_PDF_PAGE_CONCURRENCY;
1199
+ const preferCachedRasters = options.preferCachedRasters ?? true;
1200
+ const imageStore = options.imageStore ?? new IndexedDbImageStore();
1201
+ const hydrationRequests = getHydrationRequests(items, assetStore, pdfScale);
1202
+ if (hydrationRequests.length === 0) {
1203
+ return {
1204
+ items: [...items],
1205
+ objectUrls: []
1206
+ };
1207
+ }
1208
+ const assetIds = [
1209
+ ...new Set(hydrationRequests.map((entry) => entry.request.assetId))
1210
+ ];
1211
+ const resolvedAssetUrls = await assetStore.resolve({ assetIds });
1212
+ const objectUrls = [];
1213
+ const hydratedImages = await hydrateImageAssets(
1214
+ hydrationRequests.filter((entry) => entry.request.kind === "image"),
1215
+ resolvedAssetUrls,
1216
+ objectUrls,
1217
+ preferCachedRasters
1218
+ );
1219
+ const hydratedPdfPages = await hydratePdfAssets(
1220
+ hydrationRequests.filter((entry) => entry.request.kind === "pdf"),
1221
+ resolvedAssetUrls,
1222
+ objectUrls,
1223
+ {
1224
+ imageStore,
1225
+ pdfPageConcurrency}
1226
+ );
1227
+ return {
1228
+ items: items.map((item) => {
1229
+ const request = assetStore.getHydrationRequest?.(item);
1230
+ if (!request || item.toolKind !== "image") return item;
1231
+ if (request.kind === "image") {
1232
+ const hydratedImage = hydratedImages.get(request.assetId);
1233
+ if (!hydratedImage) return item;
1234
+ return rebuildItemSvg({
1235
+ ...item,
1236
+ imageRasterHref: hydratedImage.href,
1237
+ imageThumbnailHref: hydratedImage.href
1238
+ });
1239
+ }
1240
+ if (request.pageNumber == null) return item;
1241
+ const hydratedPdfPage = hydratedPdfPages.get(
1242
+ buildPdfHydrationKey(
1243
+ request.assetId,
1244
+ request.pageNumber,
1245
+ request.scale ?? pdfScale
1246
+ )
1247
+ );
1248
+ if (!hydratedPdfPage) return item;
1249
+ return rebuildItemSvg({
1250
+ ...item,
1251
+ imageRasterHref: hydratedPdfPage.href,
1252
+ imageThumbnailHref: hydratedPdfPage.href,
1253
+ imageIntrinsicSize: {
1254
+ width: item.imageIntrinsicSize?.width ?? Math.max(1, Math.round(hydratedPdfPage.width ?? item.bounds.width)),
1255
+ height: item.imageIntrinsicSize?.height ?? Math.max(1, Math.round(hydratedPdfPage.height ?? item.bounds.height))
1256
+ }
1257
+ });
1258
+ }),
1259
+ objectUrls
1260
+ };
960
1261
  }
961
1262
 
962
1263
  // src/react/asset-ingestion.ts
@@ -999,6 +1300,7 @@ async function ingestAssetFilesToSceneItems(options) {
999
1300
  gapWorld = 16,
1000
1301
  stepWorld = 48,
1001
1302
  pdfScale = 1.5,
1303
+ pdfPageConcurrency = 2,
1002
1304
  decorateItem,
1003
1305
  onError
1004
1306
  } = options;
@@ -1020,9 +1322,14 @@ async function ingestAssetFilesToSceneItems(options) {
1020
1322
  continue;
1021
1323
  }
1022
1324
  try {
1023
- const uploadResult = await uploadAssetIfNeeded(assetStore, file, kind);
1024
1325
  if (kind === "pdf") {
1025
- const pages = await loadPdfToStore(file, imageStore, { scale: pdfScale });
1326
+ const [uploadResult2, pages] = await Promise.all([
1327
+ uploadAssetIfNeeded(assetStore, file, kind),
1328
+ loadPdfToStore(file, imageStore, {
1329
+ scale: pdfScale,
1330
+ pageConcurrency: pdfPageConcurrency
1331
+ })
1332
+ ]);
1026
1333
  for (const page of pages) {
1027
1334
  const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
1028
1335
  const naturalTopY2 = worldCenter.y - page.height / 2;
@@ -1059,7 +1366,7 @@ async function ingestAssetFilesToSceneItems(options) {
1059
1366
  ) : ""
1060
1367
  },
1061
1368
  itemContext2,
1062
- uploadResult,
1369
+ uploadResult2,
1063
1370
  decorateItem
1064
1371
  );
1065
1372
  items.push(item2);
@@ -1070,10 +1377,11 @@ async function ingestAssetFilesToSceneItems(options) {
1070
1377
  imageYOffsetAdjustment = 0;
1071
1378
  continue;
1072
1379
  }
1073
- const { blobId, thumbnailBlobId, width, height } = await loadImageToStore(
1074
- file,
1075
- imageStore
1076
- );
1380
+ const [uploadResult, storedImage] = await Promise.all([
1381
+ uploadAssetIfNeeded(assetStore, file, kind),
1382
+ loadImageToStore(file, imageStore)
1383
+ ]);
1384
+ const { blobId, thumbnailBlobId, width, height } = storedImage;
1077
1385
  const fullUrl = await createBlobUrlFromStore(imageStore, blobId);
1078
1386
  const thumbBlob = await imageStore.getThumbnail(thumbnailBlobId);
1079
1387
  const thumbnailHref = thumbBlob ? URL.createObjectURL(thumbBlob) : null;
@@ -4233,16 +4541,14 @@ function itemHitTestWorldPoint(item, worldX, worldY, options) {
4233
4541
  return true;
4234
4542
  }
4235
4543
  }
4544
+ return false;
4236
4545
  }
4237
- if (pts?.length === 1) {
4546
+ if (pts && pts.length === 1) {
4238
4547
  const p = pts[0];
4239
- if (p) {
4240
- const cw = itemLocalToWorld(p.x, p.y, item.x, item.y, w, h, rot);
4241
- const dsq = (worldX - cw.x) ** 2 + (worldY - cw.y) ** 2;
4242
- if (dsq <= tolSq) {
4243
- return true;
4244
- }
4245
- }
4548
+ if (!p) return false;
4549
+ const cw = itemLocalToWorld(p.x, p.y, item.x, item.y, w, h, rot);
4550
+ const dsq = (worldX - cw.x) ** 2 + (worldY - cw.y) ** 2;
4551
+ return dsq <= tolSq;
4246
4552
  }
4247
4553
  return hitTestFilledShape(item, worldX, worldY);
4248
4554
  }
@@ -4787,101 +5093,12 @@ init_shape_builders();
4787
5093
  var HANDLE_ORDER = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
4788
5094
  var ERASER_TINT = "#cbd5e1";
4789
5095
  var ERASER_TINT_OPACITY = 0.95;
5096
+ var ERASER_PREVIEW_OPACITY = 0.3;
4790
5097
  function pointsToSmoothPath(points) {
4791
5098
  if (points.length === 0) return null;
4792
5099
  const d = smoothFreehandPointsToPathD(points);
4793
5100
  return d || null;
4794
5101
  }
4795
- function renderEraserSkeleton(it, overlayStrokePx) {
4796
- const b = normalizeRect(it.bounds);
4797
- const tool = it.toolKind;
4798
- const strokeWidth = Math.max(it.strokeWidth ?? 2, overlayStrokePx);
4799
- const common = {
4800
- stroke: ERASER_TINT,
4801
- strokeWidth,
4802
- strokeOpacity: ERASER_TINT_OPACITY,
4803
- vectorEffect: "non-scaling-stroke",
4804
- strokeLinecap: "round",
4805
- strokeLinejoin: "round",
4806
- fill: "none"
4807
- };
4808
- if (tool === "rect") {
4809
- return /* @__PURE__ */ jsxRuntime.jsx("rect", { x: 0, y: 0, width: b.width, height: b.height, ...common });
4810
- }
4811
- if (tool === "ellipse") {
4812
- return /* @__PURE__ */ jsxRuntime.jsx(
4813
- "ellipse",
4814
- {
4815
- cx: b.width / 2,
4816
- cy: b.height / 2,
4817
- rx: Math.max(0, b.width / 2),
4818
- ry: Math.max(0, b.height / 2),
4819
- ...common
4820
- }
4821
- );
4822
- }
4823
- if (tool === "line" || tool === "arrow") {
4824
- const ln = it.line;
4825
- if (!ln)
4826
- return /* @__PURE__ */ jsxRuntime.jsx("rect", { x: 0, y: 0, width: b.width, height: b.height, ...common });
4827
- const geometry = tool === "arrow" ? computeStraightArrowGeometry(
4828
- ln,
4829
- Math.max(it.strokeWidth ?? 2, overlayStrokePx)
4830
- ) : null;
4831
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4832
- /* @__PURE__ */ jsxRuntime.jsx(
4833
- "line",
4834
- {
4835
- x1: ln.x1,
4836
- y1: ln.y1,
4837
- x2: geometry?.shaftEndX ?? ln.x2,
4838
- y2: geometry?.shaftEndY ?? ln.y2,
4839
- ...common
4840
- }
4841
- ),
4842
- tool === "arrow" && geometry ? /* @__PURE__ */ jsxRuntime.jsx(
4843
- "path",
4844
- {
4845
- d: `M ${geometry.headLeftX} ${geometry.headLeftY} L ${geometry.headTipX} ${geometry.headTipY} L ${geometry.headRightX} ${geometry.headRightY}`,
4846
- ...common
4847
- }
4848
- ) : null
4849
- ] });
4850
- }
4851
- if (tool === "draw" || tool === "marker" || tool === "pencil" || tool === "brush") {
4852
- const pts = it.pathPointsLocal ?? [];
4853
- if (pts.length === 1) {
4854
- const p = pts[0];
4855
- if (!p) return null;
4856
- return /* @__PURE__ */ jsxRuntime.jsx(
4857
- "circle",
4858
- {
4859
- cx: p.x,
4860
- cy: p.y,
4861
- r: Math.max((it.strokeWidth ?? 2) / 2, 2),
4862
- fill: ERASER_TINT,
4863
- fillOpacity: 0.8,
4864
- vectorEffect: "non-scaling-stroke"
4865
- }
4866
- );
4867
- }
4868
- const d = pointsToSmoothPath(pts);
4869
- if (d) {
4870
- return /* @__PURE__ */ jsxRuntime.jsx("path", { d, ...common, shapeRendering: "geometricPrecision" });
4871
- }
4872
- }
4873
- return /* @__PURE__ */ jsxRuntime.jsx(
4874
- "rect",
4875
- {
4876
- x: 0,
4877
- y: 0,
4878
- width: b.width,
4879
- height: b.height,
4880
- ...common,
4881
- strokeDasharray: "4 4"
4882
- }
4883
- );
4884
- }
4885
5102
  function InteractionOverlay({
4886
5103
  camera,
4887
5104
  cameraVersion: _cameraVersion,
@@ -5129,16 +5346,15 @@ function InteractionOverlay({
5129
5346
  }
5130
5347
  let eraserPreview = null;
5131
5348
  if (eraserPreviewItems.length > 0) {
5132
- eraserPreview = /* @__PURE__ */ jsxRuntime.jsx("g", { children: eraserPreviewItems.map((it) => {
5133
- return /* @__PURE__ */ jsxRuntime.jsx(
5134
- "g",
5135
- {
5136
- transform: formatItemPlacementTransform(it),
5137
- children: renderEraserSkeleton(it, overlayStrokePx)
5138
- },
5139
- `erase-preview-${it.id}`
5140
- );
5141
- }) });
5349
+ eraserPreview = /* @__PURE__ */ jsxRuntime.jsx("g", { children: eraserPreviewItems.map((it) => /* @__PURE__ */ jsxRuntime.jsx(
5350
+ "g",
5351
+ {
5352
+ transform: formatItemPlacementTransform(it),
5353
+ opacity: ERASER_PREVIEW_OPACITY,
5354
+ dangerouslySetInnerHTML: { __html: it.childrenSvg }
5355
+ },
5356
+ `erase-preview-${it.id}`
5357
+ )) });
5142
5358
  }
5143
5359
  let marqueeCandidates = null;
5144
5360
  if (marqueeCandidateItems.length > 0) {
@@ -8637,6 +8853,7 @@ exports.createNoopPersistenceAdapter = createNoopPersistenceAdapter;
8637
8853
  exports.createToolPlugin = createToolPlugin;
8638
8854
  exports.cursorForVectorToolId = cursorForVectorToolId;
8639
8855
  exports.getBoardPositionStyle = getBoardPositionStyle;
8856
+ exports.hydrateSceneItemsWithAssets = hydrateSceneItemsWithAssets;
8640
8857
  exports.ingestAssetFilesToSceneItems = ingestAssetFilesToSceneItems;
8641
8858
  exports.useCanvuChromeContext = useCanvuChromeContext;
8642
8859
  exports.useCanvuDocumentContext = useCanvuDocumentContext;