canvu-react 0.3.7 → 0.3.8

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
@@ -46,93 +46,6 @@ var init_custom_shape = __esm({
46
46
  }
47
47
  });
48
48
 
49
- // src/scene/freehand-path.ts
50
- function dedupeFreehandPoints(points, minDist) {
51
- if (points.length <= 2) {
52
- return points.map((p) => ({ ...p }));
53
- }
54
- const minSq = minDist * minDist;
55
- const first = points[0];
56
- if (!first) return [];
57
- const out = [{ ...first }];
58
- for (let i = 1; i < points.length - 1; i++) {
59
- const p = points[i];
60
- const last = out[out.length - 1];
61
- if (!p || !last) continue;
62
- const dx = p.x - last.x;
63
- const dy = p.y - last.y;
64
- if (dx * dx + dy * dy >= minSq) {
65
- out.push({ ...p });
66
- }
67
- }
68
- const end = points[points.length - 1];
69
- const lastKept = out[out.length - 1];
70
- if (!end || !lastKept) return out;
71
- if ((end.x - lastKept.x) ** 2 + (end.y - lastKept.y) ** 2 > 1e-12) {
72
- out.push({ ...end });
73
- }
74
- return out;
75
- }
76
- function smoothFreehandPointsToPathD(points) {
77
- const n = points.length;
78
- if (n === 0) return "";
79
- if (n === 1) {
80
- const p = points[0];
81
- if (!p) return "";
82
- return `M ${p.x} ${p.y}`;
83
- }
84
- if (n === 2) {
85
- const a = points[0];
86
- const b = points[1];
87
- if (!a || !b) return "";
88
- return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
89
- }
90
- const p0 = points[0];
91
- if (!p0) return "";
92
- let d = `M ${p0.x} ${p0.y}`;
93
- let i = 1;
94
- for (; i < n - 2; i++) {
95
- const pi = points[i];
96
- const pi1 = points[i + 1];
97
- if (!pi || !pi1) continue;
98
- const xc = (pi.x + pi1.x) / 2;
99
- const yc = (pi.y + pi1.y) / 2;
100
- d += ` Q ${pi.x} ${pi.y} ${xc} ${yc}`;
101
- }
102
- const pLast = points[i];
103
- const pEnd = points[i + 1];
104
- if (!pLast || !pEnd) return d;
105
- d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
106
- return d;
107
- }
108
- function outlineStrokeToClosedPathD(outline) {
109
- const len = outline.length;
110
- if (len === 0) return "";
111
- const first = outline[0];
112
- if (!first) return "";
113
- if (len < 3) {
114
- let d2 = `M ${first[0]} ${first[1]}`;
115
- for (let i = 1; i < len; i++) {
116
- const pt = outline[i];
117
- if (!pt) continue;
118
- d2 += ` L ${pt[0]} ${pt[1]}`;
119
- }
120
- return `${d2} Z`;
121
- }
122
- let d = `M ${first[0]} ${first[1]} Q`;
123
- for (let i = 0; i < len; i++) {
124
- const p0 = outline[i];
125
- const p1 = outline[(i + 1) % len];
126
- if (!p0 || !p1) continue;
127
- d += ` ${p0[0]} ${p0[1]} ${(p0[0] + p1[0]) / 2} ${(p0[1] + p1[1]) / 2}`;
128
- }
129
- return `${d} Z`;
130
- }
131
- var init_freehand_path = __esm({
132
- "src/scene/freehand-path.ts"() {
133
- }
134
- });
135
-
136
49
  // src/scene/text-svg.ts
137
50
  function escapeSvgTextContent(s) {
138
51
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -318,18 +231,18 @@ function perfectFreehandOptions(toolKind, style, strokeComplete, pressureAware =
318
231
  ...base2,
319
232
  size: Math.max(2, sw * 1.05),
320
233
  thinning: 0.42,
321
- smoothing: 0.56,
322
- streamline: 0.18,
323
- simulatePressure: false
234
+ smoothing: 0.78,
235
+ streamline: 0.62,
236
+ simulatePressure: true
324
237
  };
325
238
  }
326
239
  return {
327
240
  ...base2,
328
241
  size: Math.max(2, sw * 1.18),
329
242
  thinning: 0.12,
330
- smoothing: 0.72,
331
- streamline: 0.42,
332
- simulatePressure: false
243
+ smoothing: 0.85,
244
+ streamline: 0.78,
245
+ simulatePressure: true
333
246
  };
334
247
  }
335
248
  if (toolKind === "brush") {
@@ -347,7 +260,7 @@ function perfectFreehandOptions(toolKind, style, strokeComplete, pressureAware =
347
260
  thinning: 0.08,
348
261
  smoothing: 0.88,
349
262
  streamline: 0.84,
350
- simulatePressure: false
263
+ simulatePressure: true
351
264
  };
352
265
  }
353
266
  function resolveStrokeStyle(item) {
@@ -417,70 +330,41 @@ function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeCompl
417
330
  if (pathPointsLocal.length === 1) {
418
331
  const p = pathPointsLocal[0];
419
332
  if (!p) return null;
420
- const r = Math.max(0.5, style.strokeWidth / 2);
421
- return {
422
- kind: "circle",
423
- cx: p.x,
424
- cy: p.y,
425
- r,
426
- fill: style.stroke,
427
- fillOpacity: style.strokeOpacity
428
- };
429
- }
430
- const minDist = Math.min(0.25, Math.max(0.02, style.strokeWidth * 0.02));
431
- const pts = dedupeFreehandPoints(pathPointsLocal, minDist);
432
- if (pts.length === 0) return null;
433
- if (pts.length === 1) {
434
- const p = pts[0];
435
- if (!p) return null;
436
- const r = Math.max(0.5, style.strokeWidth / 2);
437
333
  return {
438
334
  kind: "circle",
439
335
  cx: p.x,
440
336
  cy: p.y,
441
- r,
337
+ r: Math.max(0.5, style.strokeWidth / 2),
442
338
  fill: style.stroke,
443
339
  fillOpacity: style.strokeOpacity
444
340
  };
445
341
  }
446
- const hasPressure = toolKind === "draw" && pts.some((p) => p.pressure != null && Number.isFinite(p.pressure));
447
- if (toolKind === "draw" && !hasPressure) {
448
- const d2 = smoothFreehandPointsToPathD(pts);
449
- return {
450
- kind: "strokePath",
451
- d: d2,
452
- stroke: style.stroke,
453
- strokeWidth: style.strokeWidth,
454
- strokeOpacity: style.strokeOpacity
455
- };
456
- }
457
- const input = hasPressure ? pts.map(
342
+ const hasPressure = pathPointsLocal.some(
343
+ (p) => p.pressure != null && Number.isFinite(p.pressure)
344
+ );
345
+ const input = hasPressure ? pathPointsLocal.map(
458
346
  (p) => [p.x, p.y, Math.min(1, Math.max(0, p.pressure ?? 0.5))]
459
- ) : pts.map((p) => [p.x, p.y]);
460
- const opts = perfectFreehandOptions(toolKind, style, strokeComplete, hasPressure);
461
- let outline = [];
462
- try {
463
- const raw = getStroke(input, opts);
464
- outline = raw.map(([x, y]) => [x, y]);
465
- } catch {
466
- outline = [];
467
- }
468
- if (outline.length >= 3) {
469
- const d2 = outlineStrokeToClosedPathD(outline);
470
- return {
471
- kind: "fillPath",
472
- d: d2,
473
- fill: style.stroke,
474
- fillOpacity: style.strokeOpacity
475
- };
347
+ ) : pathPointsLocal.map((p) => [p.x, p.y]);
348
+ const stroke = getStroke(
349
+ input,
350
+ perfectFreehandOptions(toolKind, style, strokeComplete, hasPressure)
351
+ );
352
+ if (stroke.length < 3) return null;
353
+ const first = stroke[0];
354
+ if (!first) return null;
355
+ let d = `M ${first[0]} ${first[1]} Q`;
356
+ for (let i = 0; i < stroke.length; i++) {
357
+ const a = stroke[i];
358
+ const b = stroke[(i + 1) % stroke.length];
359
+ if (!a || !b) continue;
360
+ d += ` ${a[0]} ${a[1]} ${(a[0] + b[0]) / 2} ${(a[1] + b[1]) / 2}`;
476
361
  }
477
- const d = smoothFreehandPointsToPathD(pts);
362
+ d += " Z";
478
363
  return {
479
- kind: "strokePath",
364
+ kind: "fillPath",
480
365
  d,
481
- stroke: style.stroke,
482
- strokeWidth: style.strokeWidth,
483
- strokeOpacity: style.strokeOpacity
366
+ fill: style.stroke,
367
+ fillOpacity: style.strokeOpacity
484
368
  };
485
369
  }
486
370
  function freehandPayloadToSvgString(payload) {
@@ -810,1118 +694,1369 @@ var init_shape_builders = __esm({
810
694
  "src/scene/shape-builders.ts"() {
811
695
  init_rect();
812
696
  init_custom_shape();
813
- init_freehand_path();
814
697
  init_text_svg();
815
698
  DEFAULT_STROKE_STYLE = {
816
699
  stroke: "#2563eb",
817
700
  strokeWidth: 2
818
701
  };
819
702
  TOOL_FREEHAND_DEFAULTS = {
820
- draw: { strokeWidth: 3 },
703
+ draw: { strokeWidth: 10 },
821
704
  pencil: { strokeWidth: 3 },
822
705
  brush: { strokeWidth: 10 },
823
706
  marker: { stroke: "#fde047", strokeWidth: 16, strokeOpacity: 0.5 }
824
707
  };
825
708
  }
826
709
  });
827
- var CanvuChromeContext = createContext(
828
- null
829
- );
830
- function useCanvuChromeContext() {
831
- return useContext(CanvuChromeContext);
832
- }
833
710
 
834
- // src/math/item-transform.ts
835
- init_rect();
836
- function getItemRotationRad(item) {
837
- return item.rotation ?? 0;
711
+ // src/image/indexed-db-image-store.ts
712
+ var DB_NAME = "canvu-image-store";
713
+ var DB_VERSION = 1;
714
+ function openDb() {
715
+ return new Promise((resolve, reject) => {
716
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
717
+ req.onupgradeneeded = () => {
718
+ const db = req.result;
719
+ if (!db.objectStoreNames.contains("images")) {
720
+ db.createObjectStore("images");
721
+ }
722
+ if (!db.objectStoreNames.contains("thumbnails")) {
723
+ db.createObjectStore("thumbnails");
724
+ }
725
+ };
726
+ req.onsuccess = () => resolve(req.result);
727
+ req.onerror = () => reject(req.error);
728
+ });
838
729
  }
839
- function itemLocalToWorld(lx, ly, itemX, itemY, w, h, rotationRad) {
840
- const c = { x: w / 2, y: h / 2 };
841
- const dlx = lx - c.x;
842
- const dly = ly - c.y;
843
- const cos = Math.cos(rotationRad);
844
- const sin = Math.sin(rotationRad);
845
- return {
846
- x: itemX + c.x + cos * dlx - sin * dly,
847
- y: itemY + c.y + sin * dlx + cos * dly
848
- };
730
+ function getFromStore(db, storeName, id) {
731
+ return new Promise((resolve, reject) => {
732
+ const tx = db.transaction(storeName, "readonly");
733
+ const req = tx.objectStore(storeName).get(id);
734
+ req.onsuccess = () => resolve(req.result ?? void 0);
735
+ req.onerror = () => reject(req.error);
736
+ });
849
737
  }
850
- function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
851
- const c = { x: w / 2, y: h / 2 };
852
- const vx = wx - itemX;
853
- const vy = wy - itemY;
854
- const dx = vx - c.x;
855
- const dy = vy - c.y;
856
- const cos = Math.cos(-rotationRad);
857
- const sin = Math.sin(-rotationRad);
858
- const lx = cos * dx - sin * dy;
859
- const ly = sin * dx + cos * dy;
860
- return { x: c.x + lx, y: c.y + ly };
738
+ function putInStore(db, storeName, id, blob) {
739
+ return new Promise((resolve, reject) => {
740
+ const tx = db.transaction(storeName, "readwrite");
741
+ const req = tx.objectStore(storeName).put(blob, id);
742
+ req.onsuccess = () => resolve();
743
+ req.onerror = () => reject(req.error);
744
+ });
861
745
  }
862
- function itemPivotWorld(item) {
863
- const r = normalizeRect(item.bounds);
864
- return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
746
+ function deleteFromStore(db, storeName, id) {
747
+ return new Promise((resolve, reject) => {
748
+ const tx = db.transaction(storeName, "readwrite");
749
+ const req = tx.objectStore(storeName).delete(id);
750
+ req.onsuccess = () => resolve();
751
+ req.onerror = () => reject(req.error);
752
+ });
865
753
  }
866
- function boundsAabbForRotatedItem(item) {
867
- const rot = getItemRotationRad(item);
868
- if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
869
- return item.bounds;
754
+ function generateBlobId() {
755
+ return crypto.randomUUID();
756
+ }
757
+ var IndexedDbImageStore = class {
758
+ dbPromise = null;
759
+ getDb() {
760
+ if (!this.dbPromise) {
761
+ this.dbPromise = openDb();
762
+ }
763
+ return this.dbPromise;
870
764
  }
871
- const r = normalizeRect(item.bounds);
872
- if (Math.abs(rot) < 1e-12) {
873
- return r;
765
+ async storeOriginal(blob) {
766
+ const id = generateBlobId();
767
+ const db = await this.getDb();
768
+ await putInStore(db, "images", id, blob);
769
+ return id;
874
770
  }
875
- const corners = [
876
- [0, 0],
877
- [r.width, 0],
878
- [r.width, r.height],
879
- [0, r.height]
880
- ];
881
- let minX = Infinity;
882
- let minY = Infinity;
883
- let maxX = -Infinity;
884
- let maxY = -Infinity;
885
- for (const [lx, ly] of corners) {
886
- const p = itemLocalToWorld(lx, ly, item.x, item.y, r.width, r.height, rot);
887
- minX = Math.min(minX, p.x);
888
- minY = Math.min(minY, p.y);
889
- maxX = Math.max(maxX, p.x);
890
- maxY = Math.max(maxY, p.y);
771
+ async getOriginal(id) {
772
+ const db = await this.getDb();
773
+ return getFromStore(db, "images", id);
774
+ }
775
+ async deleteOriginal(id) {
776
+ const db = await this.getDb();
777
+ await deleteFromStore(db, "images", id);
778
+ }
779
+ async storeThumbnail(blob) {
780
+ const id = generateBlobId();
781
+ const db = await this.getDb();
782
+ await putInStore(db, "thumbnails", id, blob);
783
+ return id;
784
+ }
785
+ async getThumbnail(id) {
786
+ const db = await this.getDb();
787
+ return getFromStore(db, "thumbnails", id);
788
+ }
789
+ async deleteThumbnail(id) {
790
+ const db = await this.getDb();
791
+ await deleteFromStore(db, "thumbnails", id);
891
792
  }
892
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
893
- }
894
-
895
- // src/react/NavMenu.tsx
896
- init_rect();
897
- var shellLook = {
898
- display: "flex",
899
- flexDirection: "column",
900
- gap: 8,
901
- minWidth: 160,
902
- padding: "10px 12px",
903
- borderRadius: 8,
904
- border: "1px solid rgba(0,0,0,0.12)",
905
- background: "rgba(255,255,255,0.96)",
906
- boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
907
- pointerEvents: "auto"
908
- };
909
- var labelStyle = {
910
- display: "flex",
911
- flexDirection: "column",
912
- gap: 4,
913
- fontSize: 11,
914
- fontWeight: 600,
915
- color: "#52525b"
916
793
  };
917
- function normalizeHex(stroke) {
918
- if (/^#[0-9A-Fa-f]{6}$/.test(stroke)) return stroke;
919
- return "#2563eb";
920
- }
921
- function isStylableKind(tk) {
922
- return tk === "rect" || tk === "ellipse" || tk === "line" || tk === "arrow" || tk === "draw" || tk === "pencil" || tk === "brush" || tk === "marker" || tk === "text";
923
- }
924
- function VectorSelectionInspector({
925
- items: itemsProp,
926
- activeToolStyle: activeToolStyleProp,
927
- onChange: onChangeProp,
928
- position = "top-left",
929
- inset = 12,
930
- zIndex = 24,
931
- className,
932
- style
933
- }) {
934
- const ctx = useCanvuChromeContext();
935
- const items = itemsProp ?? ctx?.selectedItems ?? [];
936
- const activeToolStyle = activeToolStyleProp === void 0 ? ctx?.activeToolStyle ?? null : activeToolStyleProp;
937
- const onChange = onChangeProp ?? ctx?.onSelectionStyleChange ?? null;
938
- if (!onChange) return null;
939
- const shell = {
940
- ...getBoardPositionStyle(position, inset, zIndex),
941
- ...shellLook,
942
- ...style
943
- };
944
- if (activeToolStyle) {
945
- const stroke2 = activeToolStyle.stroke;
946
- const strokeWidth2 = activeToolStyle.strokeWidth;
947
- const hex2 = normalizeHex(stroke2);
948
- const showMarkerOpacity2 = activeToolStyle.toolKind === "marker";
949
- const opacityPct2 = Math.round((activeToolStyle.strokeOpacity ?? 1) * 100);
950
- return /* @__PURE__ */ jsxs(
951
- "section",
952
- {
953
- "data-slot": "vector-selection-inspector",
954
- "data-position": position,
955
- className,
956
- "aria-label": activeToolStyle.label ?? "Estilo da ferramenta",
957
- style: shell,
958
- children: [
959
- /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
960
- "Cor",
961
- /* @__PURE__ */ jsx(
962
- "input",
963
- {
964
- type: "color",
965
- value: hex2,
966
- onChange: (e) => onChange({
967
- stroke: e.target.value,
968
- strokeWidth: strokeWidth2,
969
- ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
970
- }),
971
- style: {
972
- width: "100%",
973
- height: 32,
974
- padding: 0,
975
- border: "none",
976
- cursor: "pointer",
977
- background: "transparent"
978
- }
979
- }
980
- )
981
- ] }),
982
- /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
983
- "Grossura",
984
- /* @__PURE__ */ jsx(
985
- "input",
986
- {
987
- type: "range",
988
- min: 1,
989
- max: 48,
990
- value: strokeWidth2,
991
- onChange: (e) => onChange({
992
- stroke: hex2,
993
- strokeWidth: Number(e.target.value),
994
- ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
995
- })
996
- }
997
- ),
998
- /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
999
- strokeWidth2,
1000
- "px"
1001
- ] })
1002
- ] }),
1003
- showMarkerOpacity2 && /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1004
- "Opacidade (marcador)",
1005
- /* @__PURE__ */ jsx(
1006
- "input",
1007
- {
1008
- type: "range",
1009
- min: 10,
1010
- max: 100,
1011
- value: opacityPct2,
1012
- onChange: (e) => {
1013
- const v = Number(e.target.value) / 100;
1014
- onChange({
1015
- stroke: hex2,
1016
- strokeWidth: strokeWidth2,
1017
- strokeOpacity: v
1018
- });
1019
- }
1020
- }
1021
- ),
1022
- /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1023
- opacityPct2,
1024
- "%"
1025
- ] })
1026
- ] })
1027
- ]
794
+ function decodeImageToCanvas(blob, maxDimension) {
795
+ return new Promise((resolve, reject) => {
796
+ const img = new Image();
797
+ img.onload = () => {
798
+ const w0 = img.naturalWidth;
799
+ const h0 = img.naturalHeight;
800
+ if (w0 < 1 || h0 < 1) {
801
+ reject(new Error("Image has no dimensions"));
802
+ return;
803
+ }
804
+ const scale = 1;
805
+ const cw = Math.max(1, Math.round(w0 * scale));
806
+ const ch = Math.max(1, Math.round(h0 * scale));
807
+ const canvas = document.createElement("canvas");
808
+ canvas.width = cw;
809
+ canvas.height = ch;
810
+ const ctx = canvas.getContext("2d");
811
+ if (!ctx) {
812
+ reject(new Error("Canvas 2D context unavailable"));
813
+ return;
1028
814
  }
815
+ ctx.imageSmoothingEnabled = true;
816
+ ctx.imageSmoothingQuality = "high";
817
+ ctx.drawImage(img, 0, 0, cw, ch);
818
+ URL.revokeObjectURL(img.src);
819
+ resolve({ canvas, width: cw, height: ch });
820
+ };
821
+ img.onerror = () => {
822
+ URL.revokeObjectURL(img.src);
823
+ reject(new Error("Could not decode image"));
824
+ };
825
+ img.src = URL.createObjectURL(blob);
826
+ });
827
+ }
828
+ function canvasToBlob(canvas, mime, quality) {
829
+ return new Promise((resolve, reject) => {
830
+ canvas.toBlob(
831
+ (blob) => {
832
+ if (!blob) {
833
+ reject(new Error("Could not encode blob"));
834
+ return;
835
+ }
836
+ resolve(blob);
837
+ },
838
+ mime,
839
+ quality
1029
840
  );
841
+ });
842
+ }
843
+ async function loadImageToStore(file, store) {
844
+ const originalBlob = file;
845
+ const blobId = await store.storeOriginal(originalBlob);
846
+ const { canvas, width, height } = await decodeImageToCanvas(originalBlob);
847
+ const thumbScale = Math.min(1, 256 / Math.max(width, height));
848
+ const tw = Math.max(1, Math.round(width * thumbScale));
849
+ const th = Math.max(1, Math.round(height * thumbScale));
850
+ const thumbCanvas = document.createElement("canvas");
851
+ thumbCanvas.width = tw;
852
+ thumbCanvas.height = th;
853
+ const tCtx = thumbCanvas.getContext("2d");
854
+ if (tCtx) {
855
+ tCtx.imageSmoothingEnabled = true;
856
+ tCtx.imageSmoothingQuality = "high";
857
+ tCtx.drawImage(canvas, 0, 0, tw, th);
1030
858
  }
1031
- const stylable = items.filter(
1032
- (it) => !it.locked && it.toolKind && isStylableKind(it.toolKind)
1033
- );
1034
- if (stylable.length === 0) return null;
1035
- const first = stylable[0];
1036
- if (!first) return null;
1037
- const allSameStroke = stylable.every(
1038
- (it) => (it.stroke ?? "#2563eb") === (first.stroke ?? "#2563eb")
1039
- );
1040
- const allSameWidth = stylable.every(
1041
- (it) => (it.strokeWidth ?? 2) === (first.strokeWidth ?? 2)
1042
- );
1043
- const stroke = first.stroke ?? "#2563eb";
1044
- const strokeWidth = first.strokeWidth ?? 2;
1045
- const hex = normalizeHex(stroke);
1046
- const markers = stylable.filter((it) => it.toolKind === "marker");
1047
- const showMarkerOpacity = markers.length > 0;
1048
- const firstMarker = markers[0];
1049
- const allSameMarkerOpacity = markers.length > 0 && markers.every(
1050
- (it) => (it.strokeOpacity ?? 1) === (firstMarker?.strokeOpacity ?? 1)
1051
- );
1052
- const opacityPct = firstMarker ? Math.round((firstMarker.strokeOpacity ?? 1) * 100) : 100;
1053
- return /* @__PURE__ */ jsxs(
1054
- "section",
1055
- {
1056
- "data-slot": "vector-selection-inspector",
1057
- "data-position": position,
1058
- className,
1059
- "aria-label": "Estilo da sele\xE7\xE3o",
1060
- style: shell,
1061
- children: [
1062
- stylable.length > 1 && /* @__PURE__ */ jsxs("p", { style: { margin: 0, fontSize: 11, color: "#71717a" }, children: [
1063
- stylable.length,
1064
- " objetos selecionados"
1065
- ] }),
1066
- /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1067
- "Cor",
1068
- !allSameStroke && /* @__PURE__ */ jsxs("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: [
1069
- " ",
1070
- "(valores misturados \u2014 novo valor aplica a todos)"
1071
- ] }),
1072
- /* @__PURE__ */ jsx(
1073
- "input",
1074
- {
1075
- type: "color",
1076
- value: hex,
1077
- onChange: (e) => onChange({
1078
- stroke: e.target.value,
1079
- strokeWidth
1080
- }),
1081
- style: {
1082
- width: "100%",
1083
- height: 32,
1084
- padding: 0,
1085
- border: "none",
1086
- cursor: "pointer",
1087
- background: "transparent"
1088
- }
1089
- }
1090
- )
1091
- ] }),
1092
- /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1093
- "Grossura",
1094
- !allSameWidth && /* @__PURE__ */ jsx("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: " (misturado)" }),
1095
- /* @__PURE__ */ jsx(
1096
- "input",
1097
- {
1098
- type: "range",
1099
- min: 1,
1100
- max: 48,
1101
- value: strokeWidth,
1102
- onChange: (e) => onChange({
1103
- stroke: hex,
1104
- strokeWidth: Number(e.target.value)
1105
- })
1106
- }
1107
- ),
1108
- /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1109
- strokeWidth,
1110
- "px"
1111
- ] })
1112
- ] }),
1113
- showMarkerOpacity && firstMarker && /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1114
- "Opacidade (marcador)",
1115
- !allSameMarkerOpacity && markers.length > 1 && /* @__PURE__ */ jsx("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: " (misturado)" }),
1116
- /* @__PURE__ */ jsx(
1117
- "input",
1118
- {
1119
- type: "range",
1120
- min: 10,
1121
- max: 100,
1122
- value: opacityPct,
1123
- onChange: (e) => {
1124
- const v = Number(e.target.value) / 100;
1125
- onChange({
1126
- stroke: hex,
1127
- strokeWidth,
1128
- strokeOpacity: v
1129
- });
1130
- }
1131
- }
1132
- ),
1133
- /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1134
- opacityPct,
1135
- "%"
1136
- ] })
1137
- ] })
1138
- ]
1139
- }
859
+ const thumbBlob = await canvasToBlob(
860
+ thumbCanvas,
861
+ file.type === "image/jpeg" || file.type === "image/jpg" ? "image/jpeg" : "image/png",
862
+ file.type.startsWith("image/jpeg") ? 0.85 : void 0
1140
863
  );
864
+ const thumbnailBlobId = await store.storeThumbnail(thumbBlob);
865
+ return { blobId, thumbnailBlobId, width, height };
1141
866
  }
1142
- function getBoardPositionStyle(position, inset = 12, zIndex = 40) {
1143
- const base2 = { position: "absolute", zIndex };
1144
- switch (position) {
1145
- case "fill":
1146
- return { ...base2, inset };
1147
- case "top-left":
1148
- case "left-top":
1149
- return { ...base2, top: inset, left: inset };
1150
- case "top-center":
1151
- return {
1152
- ...base2,
1153
- top: inset,
1154
- left: "50%",
1155
- transform: "translateX(-50%)"
1156
- };
1157
- case "top-right":
1158
- case "right-top":
1159
- return { ...base2, top: inset, right: inset };
1160
- case "bottom-left":
1161
- case "left-bottom":
1162
- return { ...base2, bottom: inset, left: inset };
1163
- case "bottom-center":
1164
- return {
1165
- ...base2,
1166
- bottom: inset,
1167
- left: "50%",
1168
- transform: "translateX(-50%)"
1169
- };
1170
- case "bottom-right":
1171
- case "right-bottom":
1172
- return { ...base2, bottom: inset, right: inset };
1173
- case "left-center":
1174
- return {
1175
- ...base2,
1176
- left: inset,
1177
- top: "50%",
1178
- transform: "translateY(-50%)"
1179
- };
1180
- case "right-center":
1181
- return {
1182
- ...base2,
1183
- right: inset,
1184
- top: "50%",
1185
- transform: "translateY(-50%)"
1186
- };
1187
- case "center":
1188
- return {
1189
- ...base2,
1190
- top: "50%",
1191
- left: "50%",
1192
- transform: "translate(-50%, -50%)"
1193
- };
1194
- default:
1195
- return base2;
867
+ async function createBlobUrlFromStore(store, blobId) {
868
+ const blob = await store.getOriginal(blobId);
869
+ if (!blob) return null;
870
+ return URL.createObjectURL(blob);
871
+ }
872
+ async function createThumbnailBlobUrlFromStore(store, thumbnailBlobId) {
873
+ const blob = await store.getThumbnail(thumbnailBlobId);
874
+ if (!blob) return null;
875
+ return URL.createObjectURL(blob);
876
+ }
877
+
878
+ // src/image/pdf-loader.ts
879
+ var pdfjsPromise = null;
880
+ function getPdfJs() {
881
+ if (!pdfjsPromise) {
882
+ pdfjsPromise = import('pdfjs-dist').then((mod) => {
883
+ const workerSrc = new URL(
884
+ "pdfjs-dist/build/pdf.worker.min.mjs",
885
+ import.meta.url
886
+ ).toString();
887
+ mod.GlobalWorkerOptions.workerSrc = workerSrc;
888
+ return mod;
889
+ });
1196
890
  }
891
+ return pdfjsPromise;
1197
892
  }
1198
- var rootStyle = {
1199
- display: "flex",
1200
- flexDirection: "column",
1201
- height: "100%",
1202
- minHeight: 0,
1203
- width: "100%"
1204
- };
1205
- var headerStyle = {
1206
- flexShrink: 0,
1207
- display: "flex",
1208
- flexWrap: "wrap",
1209
- alignItems: "center",
1210
- gap: "0.75rem",
1211
- padding: "0.75rem 1rem",
1212
- borderBottom: "1px solid #e4e4e7",
1213
- background: "#fff",
1214
- fontSize: "0.875rem"
1215
- };
1216
- var bodyStyle = {
1217
- flex: 1,
1218
- minHeight: 0,
1219
- display: "flex",
1220
- flexDirection: "column"
1221
- };
1222
- var mainStyle = {
1223
- flex: 1,
1224
- minHeight: 0,
1225
- position: "relative",
1226
- display: "flex",
1227
- flexDirection: "column"
1228
- };
1229
- var viewportSurfaceStyle = {
1230
- flex: 1,
1231
- minHeight: 0,
1232
- position: "relative",
1233
- width: "100%",
1234
- alignSelf: "stretch",
1235
- background: "#fff",
1236
- touchAction: "none"
1237
- };
1238
- function mergeStyle(base2, style) {
1239
- return style ? { ...base2, ...style } : base2;
893
+ async function renderPageToCanvas(page, scale) {
894
+ const raw = page.getViewport({ scale: 1 });
895
+ const adjustedScale = Math.round(raw.width * scale) / raw.width;
896
+ const viewport = page.getViewport({ scale: adjustedScale });
897
+ const w = Math.round(viewport.width);
898
+ const h = Math.round(viewport.height);
899
+ const canvas = document.createElement("canvas");
900
+ canvas.width = w;
901
+ canvas.height = h;
902
+ const ctx = canvas.getContext("2d");
903
+ if (!ctx) throw new Error("Canvas 2D context unavailable");
904
+ ctx.imageSmoothingEnabled = true;
905
+ ctx.imageSmoothingQuality = "high";
906
+ await page.render({ canvas, viewport }).promise;
907
+ return { canvas, width: w, height: h };
1240
908
  }
1241
- function vectorCanvasSpaceStyle(position, inset, zIndex) {
1242
- return {
1243
- ...getBoardPositionStyle(position, inset, zIndex),
1244
- pointerEvents: "none"
1245
- };
909
+ function canvasToBlob2(canvas, mime, quality) {
910
+ return new Promise((resolve, reject) => {
911
+ canvas.toBlob(
912
+ (blob) => {
913
+ if (!blob) {
914
+ reject(new Error("Could not encode blob"));
915
+ return;
916
+ }
917
+ resolve(blob);
918
+ },
919
+ mime,
920
+ quality
921
+ );
922
+ });
1246
923
  }
1247
- function VectorCanvasRoot({
1248
- children,
1249
- className,
1250
- style
1251
- }) {
1252
- return /* @__PURE__ */ jsx(
1253
- "div",
1254
- {
1255
- "data-slot": "vector-canvas-root",
1256
- className,
1257
- style: mergeStyle(rootStyle, style),
1258
- children
924
+ async function loadPdfToStore(file, store, options) {
925
+ const scale = options?.scale ?? 1.5;
926
+ const pdfjs = await getPdfJs();
927
+ const arrayBuffer = await file.arrayBuffer();
928
+ const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
929
+ const results = [];
930
+ for (let i = 1; i <= pdf.numPages; i++) {
931
+ const page = await pdf.getPage(i);
932
+ const { canvas, width, height } = await renderPageToCanvas(page, scale);
933
+ const mime = "image/png";
934
+ const pageBlob = await canvasToBlob2(canvas, mime);
935
+ const blobId = await store.storeOriginal(pageBlob);
936
+ const thumbScale = Math.min(1, 256 / Math.max(width, height));
937
+ const tw = Math.max(1, Math.round(width * thumbScale));
938
+ const th = Math.max(1, Math.round(height * thumbScale));
939
+ const thumbCanvas = document.createElement("canvas");
940
+ thumbCanvas.width = tw;
941
+ thumbCanvas.height = th;
942
+ const tCtx = thumbCanvas.getContext("2d");
943
+ if (tCtx) {
944
+ tCtx.imageSmoothingEnabled = true;
945
+ tCtx.imageSmoothingQuality = "high";
946
+ tCtx.drawImage(canvas, 0, 0, tw, th);
1259
947
  }
1260
- );
948
+ const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
949
+ const thumbnailBlobId = await store.storeThumbnail(thumbBlob);
950
+ results.push({ blobId, thumbnailBlobId, width, height, pageNumber: i });
951
+ }
952
+ return results;
1261
953
  }
1262
- function VectorCanvasHeader({
1263
- children,
1264
- className,
1265
- style
1266
- }) {
1267
- return /* @__PURE__ */ jsx(
1268
- "header",
1269
- {
1270
- "data-slot": "vector-canvas-header",
1271
- className,
1272
- style: mergeStyle(headerStyle, style),
1273
- children
954
+
955
+ // src/react/asset-ingestion.ts
956
+ init_shape_builders();
957
+
958
+ // src/react/asset-store.ts
959
+ function applyAssetUploadResultToItem(item, result) {
960
+ if (!result?.pluginData) return item;
961
+ return {
962
+ ...item,
963
+ pluginData: {
964
+ ...item.pluginData ?? {},
965
+ ...result.pluginData
1274
966
  }
1275
- );
967
+ };
1276
968
  }
1277
- function VectorCanvasBody({
1278
- children,
1279
- className,
1280
- style
1281
- }) {
1282
- return /* @__PURE__ */ jsx(
1283
- "div",
1284
- {
1285
- "data-slot": "vector-canvas-body",
1286
- className,
1287
- style: mergeStyle(bodyStyle, style),
1288
- children
1289
- }
1290
- );
969
+
970
+ // src/react/asset-ingestion.ts
971
+ function getAssetKindForFile(file) {
972
+ if (file.type === "application/pdf") return "pdf";
973
+ if (file.type.startsWith("image/")) return "image";
974
+ return null;
1291
975
  }
1292
- function VectorCanvasMain({
1293
- children,
1294
- className,
1295
- style
1296
- }) {
1297
- return /* @__PURE__ */ jsx(
1298
- "div",
1299
- {
1300
- "data-slot": "vector-canvas-main",
1301
- className,
1302
- style: mergeStyle(mainStyle, style),
1303
- children
1304
- }
1305
- );
976
+ function finalizeIngestedItem(item, context, uploadResult, decorateItem) {
977
+ const itemWithAssetData = applyAssetUploadResultToItem(item, uploadResult);
978
+ return decorateItem ? decorateItem(itemWithAssetData, context) : itemWithAssetData;
1306
979
  }
1307
- function VectorCanvasViewportSurface({
1308
- children,
1309
- className,
1310
- style
1311
- }) {
1312
- return /* @__PURE__ */ jsx(
1313
- "div",
1314
- {
1315
- "data-slot": "vector-canvas-viewport-surface",
1316
- className,
1317
- style: mergeStyle(viewportSurfaceStyle, style),
1318
- children
980
+ async function uploadAssetIfNeeded(assetStore, file, kind) {
981
+ if (!assetStore) return null;
982
+ const result = await assetStore.upload({ file, kind });
983
+ return result ?? null;
984
+ }
985
+ async function ingestAssetFilesToSceneItems(options) {
986
+ const {
987
+ files,
988
+ worldCenter,
989
+ assetStore,
990
+ imageStore = new IndexedDbImageStore(),
991
+ createId = createShapeId,
992
+ gapWorld = 16,
993
+ stepWorld = 48,
994
+ pdfScale = 1.5,
995
+ decorateItem,
996
+ onError
997
+ } = options;
998
+ const items = [];
999
+ const errors = [];
1000
+ let imagePlacementIndex = 0;
1001
+ let occupiedBottomY = null;
1002
+ let imageYOffsetAdjustment = 0;
1003
+ let hasImagePlacementBase = false;
1004
+ for (const file of files) {
1005
+ const kind = getAssetKindForFile(file);
1006
+ if (!kind) {
1007
+ const error = {
1008
+ file,
1009
+ error: new Error(`Unsupported asset type: ${file.type || "unknown"}`)
1010
+ };
1011
+ errors.push(error);
1012
+ onError?.(error);
1013
+ continue;
1319
1014
  }
1320
- );
1015
+ try {
1016
+ const uploadResult = await uploadAssetIfNeeded(assetStore, file, kind);
1017
+ if (kind === "pdf") {
1018
+ const pages = await loadPdfToStore(file, imageStore, { scale: pdfScale });
1019
+ for (const page of pages) {
1020
+ const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
1021
+ const naturalTopY2 = worldCenter.y - page.height / 2;
1022
+ const stackedTopY = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
1023
+ const bounds2 = {
1024
+ x: worldCenter.x - page.width / 2,
1025
+ y: Math.max(naturalTopY2, stackedTopY),
1026
+ width: page.width,
1027
+ height: page.height
1028
+ };
1029
+ const itemContext2 = {
1030
+ file,
1031
+ kind,
1032
+ itemIndex: items.length,
1033
+ pageNumber: page.pageNumber
1034
+ };
1035
+ const item2 = finalizeIngestedItem(
1036
+ {
1037
+ id: createId(),
1038
+ x: bounds2.x,
1039
+ y: bounds2.y,
1040
+ bounds: { ...bounds2 },
1041
+ toolKind: "image",
1042
+ imageBlobId: page.blobId,
1043
+ imageRasterHref: fullUrl2 ?? void 0,
1044
+ imageIntrinsicSize: {
1045
+ width: page.width,
1046
+ height: page.height
1047
+ },
1048
+ childrenSvg: fullUrl2 ? buildRasterImageChildrenSvg(
1049
+ fullUrl2,
1050
+ { width: page.width, height: page.height },
1051
+ bounds2
1052
+ ) : ""
1053
+ },
1054
+ itemContext2,
1055
+ uploadResult,
1056
+ decorateItem
1057
+ );
1058
+ items.push(item2);
1059
+ occupiedBottomY = bounds2.y + page.height;
1060
+ }
1061
+ hasImagePlacementBase = false;
1062
+ imagePlacementIndex = 0;
1063
+ imageYOffsetAdjustment = 0;
1064
+ continue;
1065
+ }
1066
+ const { blobId, thumbnailBlobId, width, height } = await loadImageToStore(
1067
+ file,
1068
+ imageStore
1069
+ );
1070
+ const fullUrl = await createBlobUrlFromStore(imageStore, blobId);
1071
+ const thumbBlob = await imageStore.getThumbnail(thumbnailBlobId);
1072
+ const thumbnailHref = thumbBlob ? URL.createObjectURL(thumbBlob) : null;
1073
+ const ox = imagePlacementIndex % 8 * stepWorld;
1074
+ const oy = Math.floor(imagePlacementIndex / 8) * stepWorld;
1075
+ const naturalTopY = worldCenter.y - height / 2 + oy;
1076
+ if (!hasImagePlacementBase) {
1077
+ const minimumTopY = occupiedBottomY == null ? naturalTopY : occupiedBottomY + gapWorld;
1078
+ imageYOffsetAdjustment = Math.max(0, minimumTopY - naturalTopY);
1079
+ hasImagePlacementBase = true;
1080
+ }
1081
+ const bounds = {
1082
+ x: worldCenter.x - width / 2 + ox,
1083
+ y: naturalTopY + imageYOffsetAdjustment,
1084
+ width,
1085
+ height
1086
+ };
1087
+ const href = thumbnailHref ?? fullUrl ?? "";
1088
+ const itemContext = {
1089
+ file,
1090
+ kind,
1091
+ itemIndex: items.length
1092
+ };
1093
+ const item = finalizeIngestedItem(
1094
+ {
1095
+ id: createId(),
1096
+ x: bounds.x,
1097
+ y: bounds.y,
1098
+ bounds: { ...bounds },
1099
+ toolKind: "image",
1100
+ imageBlobId: blobId,
1101
+ imageThumbnailBlobId: thumbnailBlobId,
1102
+ imageRasterHref: fullUrl ?? void 0,
1103
+ imageThumbnailHref: thumbnailHref ?? void 0,
1104
+ imageIntrinsicSize: { width, height },
1105
+ childrenSvg: buildRasterImageChildrenSvg(href, { width, height }, bounds)
1106
+ },
1107
+ itemContext,
1108
+ uploadResult,
1109
+ decorateItem
1110
+ );
1111
+ items.push(item);
1112
+ imagePlacementIndex++;
1113
+ occupiedBottomY = occupiedBottomY == null ? bounds.y + height : Math.max(occupiedBottomY, bounds.y + height);
1114
+ } catch (error) {
1115
+ const fileError = {
1116
+ file,
1117
+ kind,
1118
+ error
1119
+ };
1120
+ errors.push(fileError);
1121
+ onError?.(fileError);
1122
+ }
1123
+ }
1124
+ return {
1125
+ items,
1126
+ errors
1127
+ };
1321
1128
  }
1322
- function VectorCanvasToolbar({
1323
- children,
1324
- className,
1325
- style,
1326
- position = "bottom-center",
1327
- inset = 12,
1328
- zIndex = 30
1329
- }) {
1330
- const base2 = {
1331
- ...getBoardPositionStyle(position, inset, zIndex),
1332
- display: "flex",
1333
- justifyContent: "center",
1334
- alignItems: "center",
1335
- maxWidth: "calc(100% - 24px)",
1336
- pointerEvents: "none"
1129
+ var CanvuChromeContext = createContext(
1130
+ null
1131
+ );
1132
+ function useCanvuChromeContext() {
1133
+ return useContext(CanvuChromeContext);
1134
+ }
1135
+
1136
+ // src/math/item-transform.ts
1137
+ init_rect();
1138
+ function getItemRotationRad(item) {
1139
+ return item.rotation ?? 0;
1140
+ }
1141
+ function itemLocalToWorld(lx, ly, itemX, itemY, w, h, rotationRad) {
1142
+ const c = { x: w / 2, y: h / 2 };
1143
+ const dlx = lx - c.x;
1144
+ const dly = ly - c.y;
1145
+ const cos = Math.cos(rotationRad);
1146
+ const sin = Math.sin(rotationRad);
1147
+ return {
1148
+ x: itemX + c.x + cos * dlx - sin * dly,
1149
+ y: itemY + c.y + sin * dlx + cos * dly
1337
1150
  };
1338
- return /* @__PURE__ */ jsx(
1339
- "div",
1340
- {
1341
- "data-slot": "vector-canvas-toolbar",
1342
- "data-position": position,
1343
- className,
1344
- style: mergeStyle(base2, style),
1345
- children: /* @__PURE__ */ jsx("div", { style: { pointerEvents: "auto" }, children })
1346
- }
1347
- );
1348
1151
  }
1349
- function VectorCanvasSpace({
1350
- children,
1351
- className,
1352
- style,
1353
- position = "top-right",
1354
- inset = 12,
1355
- zIndex = 40,
1356
- contentPointerEvents = "auto"
1357
- }) {
1358
- return /* @__PURE__ */ jsx(
1359
- "div",
1360
- {
1361
- "data-slot": `vector-canvas-space-${position}`,
1362
- className,
1363
- style: mergeStyle(vectorCanvasSpaceStyle(position, inset, zIndex), style),
1364
- children: /* @__PURE__ */ jsx("div", { style: { pointerEvents: contentPointerEvents }, children })
1365
- }
1366
- );
1152
+ function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
1153
+ const c = { x: w / 2, y: h / 2 };
1154
+ const vx = wx - itemX;
1155
+ const vy = wy - itemY;
1156
+ const dx = vx - c.x;
1157
+ const dy = vy - c.y;
1158
+ const cos = Math.cos(-rotationRad);
1159
+ const sin = Math.sin(-rotationRad);
1160
+ const lx = cos * dx - sin * dy;
1161
+ const ly = sin * dx + cos * dy;
1162
+ return { x: c.x + lx, y: c.y + ly };
1367
1163
  }
1368
- var VectorCanvas = {
1369
- Root: VectorCanvasRoot,
1370
- Header: VectorCanvasHeader,
1371
- Body: VectorCanvasBody,
1372
- Main: VectorCanvasMain,
1373
- ViewportSurface: VectorCanvasViewportSurface,
1374
- Toolbar: VectorCanvasToolbar,
1375
- Space: VectorCanvasSpace,
1376
- NavMenu,
1377
- SelectionInspector: VectorSelectionInspector
1378
- };
1379
- var MINIMAP_W = 180;
1380
- var MINIMAP_H = 120;
1381
- var PADDING = 12;
1382
- var BORDER_RADIUS = 8;
1383
- var BG = "#ffffff";
1384
- var BORDER_COLOR = "rgba(0,0,0,0.12)";
1385
- var ITEM_FILL = "rgba(0,0,0,0.08)";
1386
- var ITEM_STROKE = "rgba(0,0,0,0.15)";
1387
- var VIEWPORT_FILL = "rgba(59,130,246,0.08)";
1388
- var VIEWPORT_STROKE = "#3b82f6";
1389
- var btnStyle = {
1390
- pointerEvents: "auto",
1391
- width: 36,
1392
- height: 36,
1393
- display: "inline-flex",
1394
- alignItems: "center",
1395
- justifyContent: "center",
1396
- cursor: "pointer",
1397
- fontSize: 18,
1398
- lineHeight: 1,
1399
- color: "#18181b",
1400
- padding: 0,
1401
- border: "none",
1402
- outline: "none",
1403
- background: "none",
1404
- WebkitTapHighlightColor: "transparent"
1405
- };
1406
- var chevronBtnStyle = {
1407
- pointerEvents: "auto",
1408
- width: 24,
1409
- height: 36,
1410
- display: "inline-flex",
1411
- alignItems: "center",
1412
- justifyContent: "center",
1413
- cursor: "pointer",
1414
- fontSize: 12,
1415
- lineHeight: 1,
1416
- color: "#52525b",
1417
- padding: 0,
1418
- border: "none",
1419
- outline: "none",
1420
- background: "none",
1421
- WebkitTapHighlightColor: "transparent"
1422
- };
1423
- var undoBtnStyle = {
1424
- ...chevronBtnStyle,
1425
- width: 30,
1426
- opacity: 1,
1427
- color: "#18181b"
1428
- };
1429
- var labelStyle2 = {
1430
- fontSize: 10,
1431
- fontWeight: 600,
1432
- color: "#52525b",
1433
- textAlign: "center",
1434
- userSelect: "none",
1435
- padding: "2px 0"
1436
- };
1437
- var panelLayoutStyle = {
1164
+ function itemPivotWorld(item) {
1165
+ const r = normalizeRect(item.bounds);
1166
+ return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
1167
+ }
1168
+ function boundsAabbForRotatedItem(item) {
1169
+ const rot = getItemRotationRad(item);
1170
+ if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
1171
+ return item.bounds;
1172
+ }
1173
+ const r = normalizeRect(item.bounds);
1174
+ if (Math.abs(rot) < 1e-12) {
1175
+ return r;
1176
+ }
1177
+ const corners = [
1178
+ [0, 0],
1179
+ [r.width, 0],
1180
+ [r.width, r.height],
1181
+ [0, r.height]
1182
+ ];
1183
+ let minX = Infinity;
1184
+ let minY = Infinity;
1185
+ let maxX = -Infinity;
1186
+ let maxY = -Infinity;
1187
+ for (const [lx, ly] of corners) {
1188
+ const p = itemLocalToWorld(lx, ly, item.x, item.y, r.width, r.height, rot);
1189
+ minX = Math.min(minX, p.x);
1190
+ minY = Math.min(minY, p.y);
1191
+ maxX = Math.max(maxX, p.x);
1192
+ maxY = Math.max(maxY, p.y);
1193
+ }
1194
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
1195
+ }
1196
+
1197
+ // src/react/NavMenu.tsx
1198
+ init_rect();
1199
+ var shellLook = {
1438
1200
  display: "flex",
1439
1201
  flexDirection: "column",
1440
- alignItems: "flex-start",
1441
- gap: 4,
1442
- touchAction: "none",
1443
- pointerEvents: "none",
1444
- userSelect: "none"
1202
+ gap: 8,
1203
+ minWidth: 160,
1204
+ padding: "10px 12px",
1205
+ borderRadius: 8,
1206
+ border: "1px solid rgba(0,0,0,0.12)",
1207
+ background: "rgba(255,255,255,0.96)",
1208
+ boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
1209
+ pointerEvents: "auto"
1445
1210
  };
1446
- var innerStyle = {
1447
- pointerEvents: "auto",
1211
+ var labelStyle = {
1448
1212
  display: "flex",
1449
- flexDirection: "row",
1450
- alignItems: "center",
1213
+ flexDirection: "column",
1451
1214
  gap: 4,
1452
- background: BG,
1453
- borderRadius: BORDER_RADIUS,
1454
- border: `1px solid ${BORDER_COLOR}`,
1455
- boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
1456
- padding: 4
1215
+ fontSize: 11,
1216
+ fontWeight: 600,
1217
+ color: "#52525b"
1457
1218
  };
1458
- function NavMenu({
1459
- camera: cameraProp,
1460
- viewportWidth: viewportWidthProp,
1461
- viewportHeight: viewportHeightProp,
1219
+ function normalizeHex(stroke) {
1220
+ if (/^#[0-9A-Fa-f]{6}$/.test(stroke)) return stroke;
1221
+ return "#2563eb";
1222
+ }
1223
+ function isStylableKind(tk) {
1224
+ return tk === "rect" || tk === "ellipse" || tk === "line" || tk === "arrow" || tk === "draw" || tk === "pencil" || tk === "brush" || tk === "marker" || tk === "text";
1225
+ }
1226
+ function VectorSelectionInspector({
1462
1227
  items: itemsProp,
1463
- zoomPercent: zoomPercentProp,
1464
- onZoomIn: onZoomInProp,
1465
- onZoomOut: onZoomOutProp,
1466
- onUndo: onUndoProp,
1467
- onRedo: onRedoProp,
1468
- canUndo: canUndoProp,
1469
- canRedo: canRedoProp,
1470
- onRequestRender: onRequestRenderProp,
1471
- position = "bottom-left",
1228
+ activeToolStyle: activeToolStyleProp,
1229
+ onChange: onChangeProp,
1230
+ position = "top-left",
1472
1231
  inset = 12,
1473
- zIndex = 23,
1232
+ zIndex = 24,
1474
1233
  className,
1475
1234
  style
1476
1235
  }) {
1477
1236
  const ctx = useCanvuChromeContext();
1478
- const camera = cameraProp ?? ctx?.camera ?? null;
1479
- const viewportWidth = viewportWidthProp ?? ctx?.viewportWidth ?? 0;
1480
- const viewportHeight = viewportHeightProp ?? ctx?.viewportHeight ?? 0;
1481
- const items = itemsProp ?? ctx?.items ?? [];
1482
- const zoomPercent = zoomPercentProp ?? ctx?.zoomPercent ?? 100;
1483
- const onZoomIn = onZoomInProp ?? ctx?.onZoomIn ?? noop;
1484
- const onZoomOut = onZoomOutProp ?? ctx?.onZoomOut ?? noop;
1485
- const onUndo = onUndoProp ?? ctx?.onUndo ?? noop;
1486
- const onRedo = onRedoProp ?? ctx?.onRedo ?? noop;
1487
- const canUndo = canUndoProp ?? ctx?.canUndo ?? false;
1488
- const canRedo = canRedoProp ?? ctx?.canRedo ?? false;
1489
- const onRequestRender = onRequestRenderProp ?? ctx?.onRequestRender ?? noop;
1490
- const [expanded, setExpanded] = useState(false);
1491
- const svgRef = useRef(null);
1492
- const [dragging, setDragging] = useState(false);
1493
- const [mouseDown, setMouseDown] = useState(false);
1494
- const worldBounds = computeWorldBounds(items);
1495
- const isEmpty = worldBounds.width <= 0 || worldBounds.height <= 0;
1496
- const scale = Math.min(
1497
- (MINIMAP_W - PADDING * 2) / Math.max(worldBounds.width, 1),
1498
- (MINIMAP_H - PADDING * 2) / Math.max(worldBounds.height, 1)
1499
- );
1500
- const originX = worldBounds.x;
1501
- const originY = worldBounds.y;
1502
- const toMinimapX = (wx) => (wx - originX) * scale + PADDING;
1503
- const toMinimapY = (wy) => (wy - originY) * scale + PADDING;
1504
- const toMinimapW = (ww) => ww * scale;
1505
- const toMinimapH = (wh) => wh * scale;
1506
- const viewportWorld = camera ? camera.getVisibleWorldRect(viewportWidth, viewportHeight) : { x: 0, y: 0, width: 0, height: 0 };
1507
- const vpMinimap = {
1508
- x: toMinimapX(viewportWorld.x),
1509
- y: toMinimapY(viewportWorld.y),
1510
- width: toMinimapW(viewportWorld.width),
1511
- height: toMinimapH(viewportWorld.height)
1237
+ const items = itemsProp ?? ctx?.selectedItems ?? [];
1238
+ const activeToolStyle = activeToolStyleProp === void 0 ? ctx?.activeToolStyle ?? null : activeToolStyleProp;
1239
+ const onChange = onChangeProp ?? ctx?.onSelectionStyleChange ?? null;
1240
+ if (!onChange) return null;
1241
+ const shell = {
1242
+ ...getBoardPositionStyle(position, inset, zIndex),
1243
+ ...shellLook,
1244
+ ...style
1512
1245
  };
1513
- const worldFromMinimap = useCallback(
1514
- (mx, my) => ({
1515
- worldX: (mx - PADDING) / scale + originX,
1516
- worldY: (my - PADDING) / scale + originY
1517
- }),
1518
- [scale, originX, originY]
1246
+ if (activeToolStyle) {
1247
+ const stroke2 = activeToolStyle.stroke;
1248
+ const strokeWidth2 = activeToolStyle.strokeWidth;
1249
+ const hex2 = normalizeHex(stroke2);
1250
+ const showMarkerOpacity2 = activeToolStyle.toolKind === "marker";
1251
+ const opacityPct2 = Math.round((activeToolStyle.strokeOpacity ?? 1) * 100);
1252
+ return /* @__PURE__ */ jsxs(
1253
+ "section",
1254
+ {
1255
+ "data-slot": "vector-selection-inspector",
1256
+ "data-position": position,
1257
+ className,
1258
+ "aria-label": activeToolStyle.label ?? "Estilo da ferramenta",
1259
+ style: shell,
1260
+ children: [
1261
+ /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1262
+ "Cor",
1263
+ /* @__PURE__ */ jsx(
1264
+ "input",
1265
+ {
1266
+ type: "color",
1267
+ value: hex2,
1268
+ onChange: (e) => onChange({
1269
+ stroke: e.target.value,
1270
+ strokeWidth: strokeWidth2,
1271
+ ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
1272
+ }),
1273
+ style: {
1274
+ width: "100%",
1275
+ height: 32,
1276
+ padding: 0,
1277
+ border: "none",
1278
+ cursor: "pointer",
1279
+ background: "transparent"
1280
+ }
1281
+ }
1282
+ )
1283
+ ] }),
1284
+ /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1285
+ "Grossura",
1286
+ /* @__PURE__ */ jsx(
1287
+ "input",
1288
+ {
1289
+ type: "range",
1290
+ min: 1,
1291
+ max: 48,
1292
+ value: strokeWidth2,
1293
+ onChange: (e) => onChange({
1294
+ stroke: hex2,
1295
+ strokeWidth: Number(e.target.value),
1296
+ ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
1297
+ })
1298
+ }
1299
+ ),
1300
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1301
+ strokeWidth2,
1302
+ "px"
1303
+ ] })
1304
+ ] }),
1305
+ showMarkerOpacity2 && /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1306
+ "Opacidade (marcador)",
1307
+ /* @__PURE__ */ jsx(
1308
+ "input",
1309
+ {
1310
+ type: "range",
1311
+ min: 10,
1312
+ max: 100,
1313
+ value: opacityPct2,
1314
+ onChange: (e) => {
1315
+ const v = Number(e.target.value) / 100;
1316
+ onChange({
1317
+ stroke: hex2,
1318
+ strokeWidth: strokeWidth2,
1319
+ strokeOpacity: v
1320
+ });
1321
+ }
1322
+ }
1323
+ ),
1324
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1325
+ opacityPct2,
1326
+ "%"
1327
+ ] })
1328
+ ] })
1329
+ ]
1330
+ }
1331
+ );
1332
+ }
1333
+ const stylable = items.filter(
1334
+ (it) => !it.locked && it.toolKind && isStylableKind(it.toolKind)
1519
1335
  );
1520
- const panTo = useCallback(
1521
- (mx, my) => {
1522
- if (!camera) return;
1523
- const { worldX, worldY } = worldFromMinimap(mx, my);
1524
- camera.x = viewportWidth / 2 - worldX * camera.zoom;
1525
- camera.y = viewportHeight / 2 - worldY * camera.zoom;
1526
- onRequestRender();
1527
- },
1528
- [worldFromMinimap, camera, viewportWidth, viewportHeight, onRequestRender]
1336
+ if (stylable.length === 0) return null;
1337
+ const first = stylable[0];
1338
+ if (!first) return null;
1339
+ const allSameStroke = stylable.every(
1340
+ (it) => (it.stroke ?? "#2563eb") === (first.stroke ?? "#2563eb")
1529
1341
  );
1530
- const handlePointerDown = useCallback(
1531
- (e) => {
1532
- if (isEmpty) return;
1533
- setDragging(true);
1534
- setMouseDown(true);
1535
- const rect = svgRef.current?.getBoundingClientRect();
1536
- if (!rect) return;
1537
- panTo(e.clientX - rect.left, e.clientY - rect.top);
1538
- svgRef.current?.setPointerCapture(e.pointerId);
1539
- },
1540
- [isEmpty, panTo]
1342
+ const allSameWidth = stylable.every(
1343
+ (it) => (it.strokeWidth ?? 2) === (first.strokeWidth ?? 2)
1541
1344
  );
1542
- const handlePointerMove = useCallback(
1543
- (e) => {
1544
- if (!mouseDown || isEmpty) return;
1545
- const rect = svgRef.current?.getBoundingClientRect();
1546
- if (!rect) return;
1547
- panTo(e.clientX - rect.left, e.clientY - rect.top);
1548
- },
1549
- [mouseDown, isEmpty, panTo]
1345
+ const stroke = first.stroke ?? "#2563eb";
1346
+ const strokeWidth = first.strokeWidth ?? 2;
1347
+ const hex = normalizeHex(stroke);
1348
+ const markers = stylable.filter((it) => it.toolKind === "marker");
1349
+ const showMarkerOpacity = markers.length > 0;
1350
+ const firstMarker = markers[0];
1351
+ const allSameMarkerOpacity = markers.length > 0 && markers.every(
1352
+ (it) => (it.strokeOpacity ?? 1) === (firstMarker?.strokeOpacity ?? 1)
1550
1353
  );
1551
- const handlePointerUp = useCallback(() => {
1552
- setDragging(false);
1553
- setMouseDown(false);
1554
- }, []);
1555
- const toggleExpanded = useCallback(() => {
1556
- setExpanded((v) => !v);
1557
- }, []);
1558
- const anchorStyle = getBoardPositionStyle(position, inset, zIndex);
1354
+ const opacityPct = firstMarker ? Math.round((firstMarker.strokeOpacity ?? 1) * 100) : 100;
1559
1355
  return /* @__PURE__ */ jsxs(
1560
- "fieldset",
1356
+ "section",
1561
1357
  {
1562
- "data-slot": "canvu-nav-menu",
1358
+ "data-slot": "vector-selection-inspector",
1563
1359
  "data-position": position,
1564
1360
  className,
1565
- style: {
1566
- ...anchorStyle,
1567
- ...panelLayoutStyle,
1568
- border: "none",
1569
- margin: 0,
1570
- padding: 0,
1571
- minWidth: 0,
1572
- ...style
1573
- },
1574
- "aria-label": "Zoom and minimap controls",
1361
+ "aria-label": "Estilo da sele\xE7\xE3o",
1362
+ style: shell,
1575
1363
  children: [
1576
- /* @__PURE__ */ jsxs("div", { style: innerStyle, children: [
1577
- /* @__PURE__ */ jsx(
1578
- "button",
1579
- {
1580
- type: "button",
1581
- style: btnStyle,
1582
- "aria-label": "Zoom out",
1583
- title: "Zoom out",
1584
- onPointerDown: (e) => {
1585
- if (e.pointerType === "mouse") e.preventDefault();
1586
- },
1587
- onClick: onZoomOut,
1588
- children: "\u2212"
1589
- }
1590
- ),
1591
- /* @__PURE__ */ jsxs("span", { style: labelStyle2, "aria-hidden": true, children: [
1592
- zoomPercent,
1593
- "%"
1364
+ stylable.length > 1 && /* @__PURE__ */ jsxs("p", { style: { margin: 0, fontSize: 11, color: "#71717a" }, children: [
1365
+ stylable.length,
1366
+ " objetos selecionados"
1367
+ ] }),
1368
+ /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1369
+ "Cor",
1370
+ !allSameStroke && /* @__PURE__ */ jsxs("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: [
1371
+ " ",
1372
+ "(valores misturados \u2014 novo valor aplica a todos)"
1594
1373
  ] }),
1595
1374
  /* @__PURE__ */ jsx(
1596
- "button",
1597
- {
1598
- type: "button",
1599
- style: btnStyle,
1600
- "aria-label": "Zoom in",
1601
- title: "Zoom in",
1602
- onPointerDown: (e) => {
1603
- if (e.pointerType === "mouse") e.preventDefault();
1604
- },
1605
- onClick: onZoomIn,
1606
- children: "+"
1607
- }
1608
- ),
1609
- /* @__PURE__ */ jsx(
1610
- "button",
1375
+ "input",
1611
1376
  {
1612
- type: "button",
1613
- style: undoBtnStyle,
1614
- "aria-label": "Undo",
1615
- title: "Undo",
1616
- disabled: !canUndo,
1617
- onPointerDown: (e) => {
1618
- if (e.pointerType === "mouse") e.preventDefault();
1619
- },
1620
- onClick: onUndo,
1621
- children: /* @__PURE__ */ jsx(Undo2, { size: 16 })
1377
+ type: "color",
1378
+ value: hex,
1379
+ onChange: (e) => onChange({
1380
+ stroke: e.target.value,
1381
+ strokeWidth
1382
+ }),
1383
+ style: {
1384
+ width: "100%",
1385
+ height: 32,
1386
+ padding: 0,
1387
+ border: "none",
1388
+ cursor: "pointer",
1389
+ background: "transparent"
1390
+ }
1622
1391
  }
1623
- ),
1392
+ )
1393
+ ] }),
1394
+ /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1395
+ "Grossura",
1396
+ !allSameWidth && /* @__PURE__ */ jsx("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: " (misturado)" }),
1624
1397
  /* @__PURE__ */ jsx(
1625
- "button",
1398
+ "input",
1626
1399
  {
1627
- type: "button",
1628
- style: undoBtnStyle,
1629
- "aria-label": "Redo",
1630
- title: "Redo",
1631
- disabled: !canRedo,
1632
- onPointerDown: (e) => {
1633
- if (e.pointerType === "mouse") e.preventDefault();
1634
- },
1635
- onClick: onRedo,
1636
- children: /* @__PURE__ */ jsx(Redo2, { size: 16 })
1400
+ type: "range",
1401
+ min: 1,
1402
+ max: 48,
1403
+ value: strokeWidth,
1404
+ onChange: (e) => onChange({
1405
+ stroke: hex,
1406
+ strokeWidth: Number(e.target.value)
1407
+ })
1637
1408
  }
1638
1409
  ),
1410
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1411
+ strokeWidth,
1412
+ "px"
1413
+ ] })
1414
+ ] }),
1415
+ showMarkerOpacity && firstMarker && /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
1416
+ "Opacidade (marcador)",
1417
+ !allSameMarkerOpacity && markers.length > 1 && /* @__PURE__ */ jsx("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: " (misturado)" }),
1639
1418
  /* @__PURE__ */ jsx(
1640
- "button",
1419
+ "input",
1641
1420
  {
1642
- type: "button",
1643
- style: chevronBtnStyle,
1644
- "aria-label": expanded ? "Hide minimap" : "Show minimap",
1645
- title: expanded ? "Hide minimap" : "Show minimap",
1646
- onPointerDown: (e) => {
1647
- if (e.pointerType === "mouse") e.preventDefault();
1648
- },
1649
- onClick: toggleExpanded,
1650
- children: /* @__PURE__ */ jsx(
1651
- "svg",
1652
- {
1653
- width: "12",
1654
- height: "12",
1655
- viewBox: "0 0 12 12",
1656
- style: {
1657
- transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
1658
- transition: "transform 0.15s ease"
1659
- },
1660
- "aria-hidden": true,
1661
- children: /* @__PURE__ */ jsx(
1662
- "path",
1663
- {
1664
- d: "M2 4 L6 8 L10 4",
1665
- fill: "none",
1666
- stroke: "currentColor",
1667
- strokeWidth: "1.5",
1668
- strokeLinecap: "round",
1669
- strokeLinejoin: "round"
1670
- }
1671
- )
1672
- }
1673
- )
1421
+ type: "range",
1422
+ min: 10,
1423
+ max: 100,
1424
+ value: opacityPct,
1425
+ onChange: (e) => {
1426
+ const v = Number(e.target.value) / 100;
1427
+ onChange({
1428
+ stroke: hex,
1429
+ strokeWidth,
1430
+ strokeOpacity: v
1431
+ });
1432
+ }
1674
1433
  }
1675
- )
1676
- ] }),
1677
- expanded && /* @__PURE__ */ jsx("nav", { "aria-label": "Minimap", children: /* @__PURE__ */ jsx(
1678
- "svg",
1679
- {
1680
- ref: svgRef,
1681
- style: {
1682
- width: MINIMAP_W,
1683
- height: MINIMAP_H,
1684
- borderRadius: BORDER_RADIUS,
1685
- border: `1px solid ${BORDER_COLOR}`,
1686
- background: BG,
1687
- boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
1688
- cursor: isEmpty ? "default" : dragging ? "grabbing" : "grab",
1689
- display: "block",
1690
- pointerEvents: "auto"
1691
- },
1692
- onPointerDown: handlePointerDown,
1693
- onPointerMove: handlePointerMove,
1694
- onPointerUp: handlePointerUp,
1695
- onPointerCancel: handlePointerUp,
1696
- children: isEmpty ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
1697
- items.map((it) => {
1698
- const b = normalizeRect(boundsAabbForRotatedItem(it));
1699
- return /* @__PURE__ */ jsx(
1700
- "rect",
1701
- {
1702
- x: toMinimapX(b.x),
1703
- y: toMinimapY(b.y),
1704
- width: toMinimapW(b.width),
1705
- height: toMinimapH(b.height),
1706
- fill: ITEM_FILL,
1707
- stroke: ITEM_STROKE,
1708
- strokeWidth: 0.5,
1709
- rx: 1
1710
- },
1711
- it.id
1712
- );
1713
- }),
1714
- /* @__PURE__ */ jsx(
1715
- "rect",
1716
- {
1717
- x: vpMinimap.x,
1718
- y: vpMinimap.y,
1719
- width: vpMinimap.width,
1720
- height: vpMinimap.height,
1721
- fill: VIEWPORT_FILL,
1722
- stroke: VIEWPORT_STROKE,
1723
- strokeWidth: 1.5,
1724
- rx: 2
1725
- }
1726
- )
1727
- ] })
1728
- }
1729
- ) })
1434
+ ),
1435
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
1436
+ opacityPct,
1437
+ "%"
1438
+ ] })
1439
+ ] })
1730
1440
  ]
1731
1441
  }
1732
1442
  );
1733
1443
  }
1734
- function noop() {
1735
- }
1736
- function computeWorldBounds(items) {
1737
- if (items.length === 0) {
1738
- return { x: 0, y: 0, width: 0, height: 0 };
1739
- }
1740
- let minX = Infinity;
1741
- let minY = Infinity;
1742
- let maxX = -Infinity;
1743
- let maxY = -Infinity;
1744
- for (const it of items) {
1745
- const b = boundsAabbForRotatedItem(it);
1746
- minX = Math.min(minX, b.x);
1747
- minY = Math.min(minY, b.y);
1748
- maxX = Math.max(maxX, b.x + b.width);
1749
- maxY = Math.max(maxY, b.y + b.height);
1444
+ function getBoardPositionStyle(position, inset = 12, zIndex = 40) {
1445
+ const base2 = { position: "absolute", zIndex };
1446
+ switch (position) {
1447
+ case "fill":
1448
+ return { ...base2, inset };
1449
+ case "top-left":
1450
+ case "left-top":
1451
+ return { ...base2, top: inset, left: inset };
1452
+ case "top-center":
1453
+ return {
1454
+ ...base2,
1455
+ top: inset,
1456
+ left: "50%",
1457
+ transform: "translateX(-50%)"
1458
+ };
1459
+ case "top-right":
1460
+ case "right-top":
1461
+ return { ...base2, top: inset, right: inset };
1462
+ case "bottom-left":
1463
+ case "left-bottom":
1464
+ return { ...base2, bottom: inset, left: inset };
1465
+ case "bottom-center":
1466
+ return {
1467
+ ...base2,
1468
+ bottom: inset,
1469
+ left: "50%",
1470
+ transform: "translateX(-50%)"
1471
+ };
1472
+ case "bottom-right":
1473
+ case "right-bottom":
1474
+ return { ...base2, bottom: inset, right: inset };
1475
+ case "left-center":
1476
+ return {
1477
+ ...base2,
1478
+ left: inset,
1479
+ top: "50%",
1480
+ transform: "translateY(-50%)"
1481
+ };
1482
+ case "right-center":
1483
+ return {
1484
+ ...base2,
1485
+ right: inset,
1486
+ top: "50%",
1487
+ transform: "translateY(-50%)"
1488
+ };
1489
+ case "center":
1490
+ return {
1491
+ ...base2,
1492
+ top: "50%",
1493
+ left: "50%",
1494
+ transform: "translate(-50%, -50%)"
1495
+ };
1496
+ default:
1497
+ return base2;
1750
1498
  }
1751
- const pad = Math.max((maxX - minX) * 0.1, (maxY - minY) * 0.1, 40);
1752
- return {
1753
- x: minX - pad,
1754
- y: minY - pad,
1755
- width: maxX - minX + pad * 2,
1756
- height: maxY - minY + pad * 2
1757
- };
1758
1499
  }
1759
-
1760
- // src/image/indexed-db-image-store.ts
1761
- var DB_NAME = "canvu-image-store";
1762
- var DB_VERSION = 1;
1763
- function openDb() {
1764
- return new Promise((resolve, reject) => {
1765
- const req = indexedDB.open(DB_NAME, DB_VERSION);
1766
- req.onupgradeneeded = () => {
1767
- const db = req.result;
1768
- if (!db.objectStoreNames.contains("images")) {
1769
- db.createObjectStore("images");
1770
- }
1771
- if (!db.objectStoreNames.contains("thumbnails")) {
1772
- db.createObjectStore("thumbnails");
1773
- }
1774
- };
1775
- req.onsuccess = () => resolve(req.result);
1776
- req.onerror = () => reject(req.error);
1777
- });
1500
+ var rootStyle = {
1501
+ display: "flex",
1502
+ flexDirection: "column",
1503
+ height: "100%",
1504
+ minHeight: 0,
1505
+ width: "100%"
1506
+ };
1507
+ var headerStyle = {
1508
+ flexShrink: 0,
1509
+ display: "flex",
1510
+ flexWrap: "wrap",
1511
+ alignItems: "center",
1512
+ gap: "0.75rem",
1513
+ padding: "0.75rem 1rem",
1514
+ borderBottom: "1px solid #e4e4e7",
1515
+ background: "#fff",
1516
+ fontSize: "0.875rem"
1517
+ };
1518
+ var bodyStyle = {
1519
+ flex: 1,
1520
+ minHeight: 0,
1521
+ display: "flex",
1522
+ flexDirection: "column"
1523
+ };
1524
+ var mainStyle = {
1525
+ flex: 1,
1526
+ minHeight: 0,
1527
+ position: "relative",
1528
+ display: "flex",
1529
+ flexDirection: "column"
1530
+ };
1531
+ var viewportSurfaceStyle = {
1532
+ flex: 1,
1533
+ minHeight: 0,
1534
+ position: "relative",
1535
+ width: "100%",
1536
+ alignSelf: "stretch",
1537
+ background: "#fff",
1538
+ touchAction: "none"
1539
+ };
1540
+ function mergeStyle(base2, style) {
1541
+ return style ? { ...base2, ...style } : base2;
1778
1542
  }
1779
- function getFromStore(db, storeName, id) {
1780
- return new Promise((resolve, reject) => {
1781
- const tx = db.transaction(storeName, "readonly");
1782
- const req = tx.objectStore(storeName).get(id);
1783
- req.onsuccess = () => resolve(req.result ?? void 0);
1784
- req.onerror = () => reject(req.error);
1785
- });
1543
+ function vectorCanvasSpaceStyle(position, inset, zIndex) {
1544
+ return {
1545
+ ...getBoardPositionStyle(position, inset, zIndex),
1546
+ pointerEvents: "none"
1547
+ };
1786
1548
  }
1787
- function putInStore(db, storeName, id, blob) {
1788
- return new Promise((resolve, reject) => {
1789
- const tx = db.transaction(storeName, "readwrite");
1790
- const req = tx.objectStore(storeName).put(blob, id);
1791
- req.onsuccess = () => resolve();
1792
- req.onerror = () => reject(req.error);
1793
- });
1549
+ function VectorCanvasRoot({
1550
+ children,
1551
+ className,
1552
+ style
1553
+ }) {
1554
+ return /* @__PURE__ */ jsx(
1555
+ "div",
1556
+ {
1557
+ "data-slot": "vector-canvas-root",
1558
+ className,
1559
+ style: mergeStyle(rootStyle, style),
1560
+ children
1561
+ }
1562
+ );
1794
1563
  }
1795
- function deleteFromStore(db, storeName, id) {
1796
- return new Promise((resolve, reject) => {
1797
- const tx = db.transaction(storeName, "readwrite");
1798
- const req = tx.objectStore(storeName).delete(id);
1799
- req.onsuccess = () => resolve();
1800
- req.onerror = () => reject(req.error);
1801
- });
1564
+ function VectorCanvasHeader({
1565
+ children,
1566
+ className,
1567
+ style
1568
+ }) {
1569
+ return /* @__PURE__ */ jsx(
1570
+ "header",
1571
+ {
1572
+ "data-slot": "vector-canvas-header",
1573
+ className,
1574
+ style: mergeStyle(headerStyle, style),
1575
+ children
1576
+ }
1577
+ );
1802
1578
  }
1803
- function generateBlobId() {
1804
- return crypto.randomUUID();
1579
+ function VectorCanvasBody({
1580
+ children,
1581
+ className,
1582
+ style
1583
+ }) {
1584
+ return /* @__PURE__ */ jsx(
1585
+ "div",
1586
+ {
1587
+ "data-slot": "vector-canvas-body",
1588
+ className,
1589
+ style: mergeStyle(bodyStyle, style),
1590
+ children
1591
+ }
1592
+ );
1805
1593
  }
1806
- var IndexedDbImageStore = class {
1807
- dbPromise = null;
1808
- getDb() {
1809
- if (!this.dbPromise) {
1810
- this.dbPromise = openDb();
1594
+ function VectorCanvasMain({
1595
+ children,
1596
+ className,
1597
+ style
1598
+ }) {
1599
+ return /* @__PURE__ */ jsx(
1600
+ "div",
1601
+ {
1602
+ "data-slot": "vector-canvas-main",
1603
+ className,
1604
+ style: mergeStyle(mainStyle, style),
1605
+ children
1811
1606
  }
1812
- return this.dbPromise;
1813
- }
1814
- async storeOriginal(blob) {
1815
- const id = generateBlobId();
1816
- const db = await this.getDb();
1817
- await putInStore(db, "images", id, blob);
1818
- return id;
1819
- }
1820
- async getOriginal(id) {
1821
- const db = await this.getDb();
1822
- return getFromStore(db, "images", id);
1823
- }
1824
- async deleteOriginal(id) {
1825
- const db = await this.getDb();
1826
- await deleteFromStore(db, "images", id);
1827
- }
1828
- async storeThumbnail(blob) {
1829
- const id = generateBlobId();
1830
- const db = await this.getDb();
1831
- await putInStore(db, "thumbnails", id, blob);
1832
- return id;
1833
- }
1834
- async getThumbnail(id) {
1835
- const db = await this.getDb();
1836
- return getFromStore(db, "thumbnails", id);
1837
- }
1838
- async deleteThumbnail(id) {
1839
- const db = await this.getDb();
1840
- await deleteFromStore(db, "thumbnails", id);
1841
- }
1842
- };
1843
- function decodeImageToCanvas(blob, maxDimension) {
1844
- return new Promise((resolve, reject) => {
1845
- const img = new Image();
1846
- img.onload = () => {
1847
- const w0 = img.naturalWidth;
1848
- const h0 = img.naturalHeight;
1849
- if (w0 < 1 || h0 < 1) {
1850
- reject(new Error("Image has no dimensions"));
1851
- return;
1852
- }
1853
- const scale = 1;
1854
- const cw = Math.max(1, Math.round(w0 * scale));
1855
- const ch = Math.max(1, Math.round(h0 * scale));
1856
- const canvas = document.createElement("canvas");
1857
- canvas.width = cw;
1858
- canvas.height = ch;
1859
- const ctx = canvas.getContext("2d");
1860
- if (!ctx) {
1861
- reject(new Error("Canvas 2D context unavailable"));
1862
- return;
1863
- }
1864
- ctx.imageSmoothingEnabled = true;
1865
- ctx.imageSmoothingQuality = "high";
1866
- ctx.drawImage(img, 0, 0, cw, ch);
1867
- URL.revokeObjectURL(img.src);
1868
- resolve({ canvas, width: cw, height: ch });
1869
- };
1870
- img.onerror = () => {
1871
- URL.revokeObjectURL(img.src);
1872
- reject(new Error("Could not decode image"));
1873
- };
1874
- img.src = URL.createObjectURL(blob);
1875
- });
1607
+ );
1876
1608
  }
1877
- function canvasToBlob(canvas, mime, quality) {
1878
- return new Promise((resolve, reject) => {
1879
- canvas.toBlob(
1880
- (blob) => {
1881
- if (!blob) {
1882
- reject(new Error("Could not encode blob"));
1883
- return;
1884
- }
1885
- resolve(blob);
1886
- },
1887
- mime,
1888
- quality
1889
- );
1890
- });
1609
+ function VectorCanvasViewportSurface({
1610
+ children,
1611
+ className,
1612
+ style
1613
+ }) {
1614
+ return /* @__PURE__ */ jsx(
1615
+ "div",
1616
+ {
1617
+ "data-slot": "vector-canvas-viewport-surface",
1618
+ className,
1619
+ style: mergeStyle(viewportSurfaceStyle, style),
1620
+ children
1621
+ }
1622
+ );
1891
1623
  }
1892
- async function loadImageToStore(file, store) {
1893
- const originalBlob = file;
1894
- const blobId = await store.storeOriginal(originalBlob);
1895
- const { canvas, width, height } = await decodeImageToCanvas(originalBlob);
1896
- const thumbScale = Math.min(1, 256 / Math.max(width, height));
1897
- const tw = Math.max(1, Math.round(width * thumbScale));
1898
- const th = Math.max(1, Math.round(height * thumbScale));
1899
- const thumbCanvas = document.createElement("canvas");
1900
- thumbCanvas.width = tw;
1901
- thumbCanvas.height = th;
1902
- const tCtx = thumbCanvas.getContext("2d");
1903
- if (tCtx) {
1904
- tCtx.imageSmoothingEnabled = true;
1905
- tCtx.imageSmoothingQuality = "high";
1906
- tCtx.drawImage(canvas, 0, 0, tw, th);
1907
- }
1908
- const thumbBlob = await canvasToBlob(
1909
- thumbCanvas,
1910
- file.type === "image/jpeg" || file.type === "image/jpg" ? "image/jpeg" : "image/png",
1911
- file.type.startsWith("image/jpeg") ? 0.85 : void 0
1624
+ function VectorCanvasToolbar({
1625
+ children,
1626
+ className,
1627
+ style,
1628
+ position = "bottom-center",
1629
+ inset = 12,
1630
+ zIndex = 30
1631
+ }) {
1632
+ const base2 = {
1633
+ ...getBoardPositionStyle(position, inset, zIndex),
1634
+ display: "flex",
1635
+ justifyContent: "center",
1636
+ alignItems: "center",
1637
+ maxWidth: "calc(100% - 24px)",
1638
+ pointerEvents: "none"
1639
+ };
1640
+ return /* @__PURE__ */ jsx(
1641
+ "div",
1642
+ {
1643
+ "data-slot": "vector-canvas-toolbar",
1644
+ "data-position": position,
1645
+ className,
1646
+ style: mergeStyle(base2, style),
1647
+ children: /* @__PURE__ */ jsx("div", { style: { pointerEvents: "auto" }, children })
1648
+ }
1649
+ );
1650
+ }
1651
+ function VectorCanvasSpace({
1652
+ children,
1653
+ className,
1654
+ style,
1655
+ position = "top-right",
1656
+ inset = 12,
1657
+ zIndex = 40,
1658
+ contentPointerEvents = "auto"
1659
+ }) {
1660
+ return /* @__PURE__ */ jsx(
1661
+ "div",
1662
+ {
1663
+ "data-slot": `vector-canvas-space-${position}`,
1664
+ className,
1665
+ style: mergeStyle(vectorCanvasSpaceStyle(position, inset, zIndex), style),
1666
+ children: /* @__PURE__ */ jsx("div", { style: { pointerEvents: contentPointerEvents }, children })
1667
+ }
1668
+ );
1669
+ }
1670
+ var VectorCanvas = {
1671
+ Root: VectorCanvasRoot,
1672
+ Header: VectorCanvasHeader,
1673
+ Body: VectorCanvasBody,
1674
+ Main: VectorCanvasMain,
1675
+ ViewportSurface: VectorCanvasViewportSurface,
1676
+ Toolbar: VectorCanvasToolbar,
1677
+ Space: VectorCanvasSpace,
1678
+ NavMenu,
1679
+ SelectionInspector: VectorSelectionInspector
1680
+ };
1681
+ var MINIMAP_W = 180;
1682
+ var MINIMAP_H = 120;
1683
+ var PADDING = 12;
1684
+ var BORDER_RADIUS = 8;
1685
+ var BG = "#ffffff";
1686
+ var BORDER_COLOR = "rgba(0,0,0,0.12)";
1687
+ var ITEM_FILL = "rgba(0,0,0,0.08)";
1688
+ var ITEM_STROKE = "rgba(0,0,0,0.15)";
1689
+ var VIEWPORT_FILL = "rgba(59,130,246,0.08)";
1690
+ var VIEWPORT_STROKE = "#3b82f6";
1691
+ var btnStyle = {
1692
+ pointerEvents: "auto",
1693
+ width: 36,
1694
+ height: 36,
1695
+ display: "inline-flex",
1696
+ alignItems: "center",
1697
+ justifyContent: "center",
1698
+ cursor: "pointer",
1699
+ fontSize: 18,
1700
+ lineHeight: 1,
1701
+ color: "#18181b",
1702
+ padding: 0,
1703
+ border: "none",
1704
+ outline: "none",
1705
+ background: "none",
1706
+ WebkitTapHighlightColor: "transparent"
1707
+ };
1708
+ var chevronBtnStyle = {
1709
+ pointerEvents: "auto",
1710
+ width: 24,
1711
+ height: 36,
1712
+ display: "inline-flex",
1713
+ alignItems: "center",
1714
+ justifyContent: "center",
1715
+ cursor: "pointer",
1716
+ fontSize: 12,
1717
+ lineHeight: 1,
1718
+ color: "#52525b",
1719
+ padding: 0,
1720
+ border: "none",
1721
+ outline: "none",
1722
+ background: "none",
1723
+ WebkitTapHighlightColor: "transparent"
1724
+ };
1725
+ var undoBtnStyle = {
1726
+ ...chevronBtnStyle,
1727
+ width: 30,
1728
+ opacity: 1,
1729
+ color: "#18181b"
1730
+ };
1731
+ var labelStyle2 = {
1732
+ fontSize: 10,
1733
+ fontWeight: 600,
1734
+ color: "#52525b",
1735
+ textAlign: "center",
1736
+ userSelect: "none",
1737
+ padding: "2px 0"
1738
+ };
1739
+ var panelLayoutStyle = {
1740
+ display: "flex",
1741
+ flexDirection: "column",
1742
+ alignItems: "flex-start",
1743
+ gap: 4,
1744
+ touchAction: "none",
1745
+ pointerEvents: "none",
1746
+ userSelect: "none"
1747
+ };
1748
+ var innerStyle = {
1749
+ pointerEvents: "auto",
1750
+ display: "flex",
1751
+ flexDirection: "row",
1752
+ alignItems: "center",
1753
+ gap: 4,
1754
+ background: BG,
1755
+ borderRadius: BORDER_RADIUS,
1756
+ border: `1px solid ${BORDER_COLOR}`,
1757
+ boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
1758
+ padding: 4
1759
+ };
1760
+ function NavMenu({
1761
+ camera: cameraProp,
1762
+ viewportWidth: viewportWidthProp,
1763
+ viewportHeight: viewportHeightProp,
1764
+ items: itemsProp,
1765
+ zoomPercent: zoomPercentProp,
1766
+ onZoomIn: onZoomInProp,
1767
+ onZoomOut: onZoomOutProp,
1768
+ onUndo: onUndoProp,
1769
+ onRedo: onRedoProp,
1770
+ canUndo: canUndoProp,
1771
+ canRedo: canRedoProp,
1772
+ onRequestRender: onRequestRenderProp,
1773
+ position = "bottom-left",
1774
+ inset = 12,
1775
+ zIndex = 23,
1776
+ className,
1777
+ style
1778
+ }) {
1779
+ const ctx = useCanvuChromeContext();
1780
+ const camera = cameraProp ?? ctx?.camera ?? null;
1781
+ const viewportWidth = viewportWidthProp ?? ctx?.viewportWidth ?? 0;
1782
+ const viewportHeight = viewportHeightProp ?? ctx?.viewportHeight ?? 0;
1783
+ const items = itemsProp ?? ctx?.items ?? [];
1784
+ const zoomPercent = zoomPercentProp ?? ctx?.zoomPercent ?? 100;
1785
+ const onZoomIn = onZoomInProp ?? ctx?.onZoomIn ?? noop;
1786
+ const onZoomOut = onZoomOutProp ?? ctx?.onZoomOut ?? noop;
1787
+ const onUndo = onUndoProp ?? ctx?.onUndo ?? noop;
1788
+ const onRedo = onRedoProp ?? ctx?.onRedo ?? noop;
1789
+ const canUndo = canUndoProp ?? ctx?.canUndo ?? false;
1790
+ const canRedo = canRedoProp ?? ctx?.canRedo ?? false;
1791
+ const onRequestRender = onRequestRenderProp ?? ctx?.onRequestRender ?? noop;
1792
+ const [expanded, setExpanded] = useState(false);
1793
+ const svgRef = useRef(null);
1794
+ const [dragging, setDragging] = useState(false);
1795
+ const [mouseDown, setMouseDown] = useState(false);
1796
+ const worldBounds = computeWorldBounds(items);
1797
+ const isEmpty = worldBounds.width <= 0 || worldBounds.height <= 0;
1798
+ const scale = Math.min(
1799
+ (MINIMAP_W - PADDING * 2) / Math.max(worldBounds.width, 1),
1800
+ (MINIMAP_H - PADDING * 2) / Math.max(worldBounds.height, 1)
1801
+ );
1802
+ const originX = worldBounds.x;
1803
+ const originY = worldBounds.y;
1804
+ const toMinimapX = (wx) => (wx - originX) * scale + PADDING;
1805
+ const toMinimapY = (wy) => (wy - originY) * scale + PADDING;
1806
+ const toMinimapW = (ww) => ww * scale;
1807
+ const toMinimapH = (wh) => wh * scale;
1808
+ const viewportWorld = camera ? camera.getVisibleWorldRect(viewportWidth, viewportHeight) : { x: 0, y: 0, width: 0, height: 0 };
1809
+ const vpMinimap = {
1810
+ x: toMinimapX(viewportWorld.x),
1811
+ y: toMinimapY(viewportWorld.y),
1812
+ width: toMinimapW(viewportWorld.width),
1813
+ height: toMinimapH(viewportWorld.height)
1814
+ };
1815
+ const worldFromMinimap = useCallback(
1816
+ (mx, my) => ({
1817
+ worldX: (mx - PADDING) / scale + originX,
1818
+ worldY: (my - PADDING) / scale + originY
1819
+ }),
1820
+ [scale, originX, originY]
1821
+ );
1822
+ const panTo = useCallback(
1823
+ (mx, my) => {
1824
+ if (!camera) return;
1825
+ const { worldX, worldY } = worldFromMinimap(mx, my);
1826
+ camera.x = viewportWidth / 2 - worldX * camera.zoom;
1827
+ camera.y = viewportHeight / 2 - worldY * camera.zoom;
1828
+ onRequestRender();
1829
+ },
1830
+ [worldFromMinimap, camera, viewportWidth, viewportHeight, onRequestRender]
1831
+ );
1832
+ const handlePointerDown = useCallback(
1833
+ (e) => {
1834
+ if (isEmpty) return;
1835
+ setDragging(true);
1836
+ setMouseDown(true);
1837
+ const rect = svgRef.current?.getBoundingClientRect();
1838
+ if (!rect) return;
1839
+ panTo(e.clientX - rect.left, e.clientY - rect.top);
1840
+ svgRef.current?.setPointerCapture(e.pointerId);
1841
+ },
1842
+ [isEmpty, panTo]
1843
+ );
1844
+ const handlePointerMove = useCallback(
1845
+ (e) => {
1846
+ if (!mouseDown || isEmpty) return;
1847
+ const rect = svgRef.current?.getBoundingClientRect();
1848
+ if (!rect) return;
1849
+ panTo(e.clientX - rect.left, e.clientY - rect.top);
1850
+ },
1851
+ [mouseDown, isEmpty, panTo]
1852
+ );
1853
+ const handlePointerUp = useCallback(() => {
1854
+ setDragging(false);
1855
+ setMouseDown(false);
1856
+ }, []);
1857
+ const toggleExpanded = useCallback(() => {
1858
+ setExpanded((v) => !v);
1859
+ }, []);
1860
+ const anchorStyle = getBoardPositionStyle(position, inset, zIndex);
1861
+ return /* @__PURE__ */ jsxs(
1862
+ "fieldset",
1863
+ {
1864
+ "data-slot": "canvu-nav-menu",
1865
+ "data-position": position,
1866
+ className,
1867
+ style: {
1868
+ ...anchorStyle,
1869
+ ...panelLayoutStyle,
1870
+ border: "none",
1871
+ margin: 0,
1872
+ padding: 0,
1873
+ minWidth: 0,
1874
+ ...style
1875
+ },
1876
+ "aria-label": "Zoom and minimap controls",
1877
+ children: [
1878
+ /* @__PURE__ */ jsxs("div", { style: innerStyle, children: [
1879
+ /* @__PURE__ */ jsx(
1880
+ "button",
1881
+ {
1882
+ type: "button",
1883
+ style: btnStyle,
1884
+ "aria-label": "Zoom out",
1885
+ title: "Zoom out",
1886
+ onPointerDown: (e) => {
1887
+ if (e.pointerType === "mouse") e.preventDefault();
1888
+ },
1889
+ onClick: onZoomOut,
1890
+ children: "\u2212"
1891
+ }
1892
+ ),
1893
+ /* @__PURE__ */ jsxs("span", { style: labelStyle2, "aria-hidden": true, children: [
1894
+ zoomPercent,
1895
+ "%"
1896
+ ] }),
1897
+ /* @__PURE__ */ jsx(
1898
+ "button",
1899
+ {
1900
+ type: "button",
1901
+ style: btnStyle,
1902
+ "aria-label": "Zoom in",
1903
+ title: "Zoom in",
1904
+ onPointerDown: (e) => {
1905
+ if (e.pointerType === "mouse") e.preventDefault();
1906
+ },
1907
+ onClick: onZoomIn,
1908
+ children: "+"
1909
+ }
1910
+ ),
1911
+ /* @__PURE__ */ jsx(
1912
+ "button",
1913
+ {
1914
+ type: "button",
1915
+ style: undoBtnStyle,
1916
+ "aria-label": "Undo",
1917
+ title: "Undo",
1918
+ disabled: !canUndo,
1919
+ onPointerDown: (e) => {
1920
+ if (e.pointerType === "mouse") e.preventDefault();
1921
+ },
1922
+ onClick: onUndo,
1923
+ children: /* @__PURE__ */ jsx(Undo2, { size: 16 })
1924
+ }
1925
+ ),
1926
+ /* @__PURE__ */ jsx(
1927
+ "button",
1928
+ {
1929
+ type: "button",
1930
+ style: undoBtnStyle,
1931
+ "aria-label": "Redo",
1932
+ title: "Redo",
1933
+ disabled: !canRedo,
1934
+ onPointerDown: (e) => {
1935
+ if (e.pointerType === "mouse") e.preventDefault();
1936
+ },
1937
+ onClick: onRedo,
1938
+ children: /* @__PURE__ */ jsx(Redo2, { size: 16 })
1939
+ }
1940
+ ),
1941
+ /* @__PURE__ */ jsx(
1942
+ "button",
1943
+ {
1944
+ type: "button",
1945
+ style: chevronBtnStyle,
1946
+ "aria-label": expanded ? "Hide minimap" : "Show minimap",
1947
+ title: expanded ? "Hide minimap" : "Show minimap",
1948
+ onPointerDown: (e) => {
1949
+ if (e.pointerType === "mouse") e.preventDefault();
1950
+ },
1951
+ onClick: toggleExpanded,
1952
+ children: /* @__PURE__ */ jsx(
1953
+ "svg",
1954
+ {
1955
+ width: "12",
1956
+ height: "12",
1957
+ viewBox: "0 0 12 12",
1958
+ style: {
1959
+ transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
1960
+ transition: "transform 0.15s ease"
1961
+ },
1962
+ "aria-hidden": true,
1963
+ children: /* @__PURE__ */ jsx(
1964
+ "path",
1965
+ {
1966
+ d: "M2 4 L6 8 L10 4",
1967
+ fill: "none",
1968
+ stroke: "currentColor",
1969
+ strokeWidth: "1.5",
1970
+ strokeLinecap: "round",
1971
+ strokeLinejoin: "round"
1972
+ }
1973
+ )
1974
+ }
1975
+ )
1976
+ }
1977
+ )
1978
+ ] }),
1979
+ expanded && /* @__PURE__ */ jsx("nav", { "aria-label": "Minimap", children: /* @__PURE__ */ jsx(
1980
+ "svg",
1981
+ {
1982
+ ref: svgRef,
1983
+ style: {
1984
+ width: MINIMAP_W,
1985
+ height: MINIMAP_H,
1986
+ borderRadius: BORDER_RADIUS,
1987
+ border: `1px solid ${BORDER_COLOR}`,
1988
+ background: BG,
1989
+ boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
1990
+ cursor: isEmpty ? "default" : dragging ? "grabbing" : "grab",
1991
+ display: "block",
1992
+ pointerEvents: "auto"
1993
+ },
1994
+ onPointerDown: handlePointerDown,
1995
+ onPointerMove: handlePointerMove,
1996
+ onPointerUp: handlePointerUp,
1997
+ onPointerCancel: handlePointerUp,
1998
+ children: isEmpty ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
1999
+ items.map((it) => {
2000
+ const b = normalizeRect(boundsAabbForRotatedItem(it));
2001
+ return /* @__PURE__ */ jsx(
2002
+ "rect",
2003
+ {
2004
+ x: toMinimapX(b.x),
2005
+ y: toMinimapY(b.y),
2006
+ width: toMinimapW(b.width),
2007
+ height: toMinimapH(b.height),
2008
+ fill: ITEM_FILL,
2009
+ stroke: ITEM_STROKE,
2010
+ strokeWidth: 0.5,
2011
+ rx: 1
2012
+ },
2013
+ it.id
2014
+ );
2015
+ }),
2016
+ /* @__PURE__ */ jsx(
2017
+ "rect",
2018
+ {
2019
+ x: vpMinimap.x,
2020
+ y: vpMinimap.y,
2021
+ width: vpMinimap.width,
2022
+ height: vpMinimap.height,
2023
+ fill: VIEWPORT_FILL,
2024
+ stroke: VIEWPORT_STROKE,
2025
+ strokeWidth: 1.5,
2026
+ rx: 2
2027
+ }
2028
+ )
2029
+ ] })
2030
+ }
2031
+ ) })
2032
+ ]
2033
+ }
1912
2034
  );
1913
- const thumbnailBlobId = await store.storeThumbnail(thumbBlob);
1914
- return { blobId, thumbnailBlobId, width, height };
1915
2035
  }
1916
- async function createBlobUrlFromStore(store, blobId) {
1917
- const blob = await store.getOriginal(blobId);
1918
- if (!blob) return null;
1919
- return URL.createObjectURL(blob);
2036
+ function noop() {
1920
2037
  }
1921
- async function createThumbnailBlobUrlFromStore(store, thumbnailBlobId) {
1922
- const blob = await store.getThumbnail(thumbnailBlobId);
1923
- if (!blob) return null;
1924
- return URL.createObjectURL(blob);
2038
+ function computeWorldBounds(items) {
2039
+ if (items.length === 0) {
2040
+ return { x: 0, y: 0, width: 0, height: 0 };
2041
+ }
2042
+ let minX = Infinity;
2043
+ let minY = Infinity;
2044
+ let maxX = -Infinity;
2045
+ let maxY = -Infinity;
2046
+ for (const it of items) {
2047
+ const b = boundsAabbForRotatedItem(it);
2048
+ minX = Math.min(minX, b.x);
2049
+ minY = Math.min(minY, b.y);
2050
+ maxX = Math.max(maxX, b.x + b.width);
2051
+ maxY = Math.max(maxY, b.y + b.height);
2052
+ }
2053
+ const pad = Math.max((maxX - minX) * 0.1, (maxY - minY) * 0.1, 40);
2054
+ return {
2055
+ x: minX - pad,
2056
+ y: minY - pad,
2057
+ width: maxX - minX + pad * 2,
2058
+ height: maxY - minY + pad * 2
2059
+ };
1925
2060
  }
1926
2061
 
1927
2062
  // src/react/persistence/indexed-db-adapter.ts
@@ -3253,83 +3388,6 @@ var Camera2D = class {
3253
3388
  }
3254
3389
  };
3255
3390
 
3256
- // src/image/pdf-loader.ts
3257
- var pdfjsPromise = null;
3258
- function getPdfJs() {
3259
- if (!pdfjsPromise) {
3260
- pdfjsPromise = import('pdfjs-dist').then((mod) => {
3261
- const workerSrc = new URL(
3262
- "pdfjs-dist/build/pdf.worker.min.mjs",
3263
- import.meta.url
3264
- ).toString();
3265
- mod.GlobalWorkerOptions.workerSrc = workerSrc;
3266
- return mod;
3267
- });
3268
- }
3269
- return pdfjsPromise;
3270
- }
3271
- async function renderPageToCanvas(page, scale) {
3272
- const raw = page.getViewport({ scale: 1 });
3273
- const adjustedScale = Math.round(raw.width * scale) / raw.width;
3274
- const viewport = page.getViewport({ scale: adjustedScale });
3275
- const w = Math.round(viewport.width);
3276
- const h = Math.round(viewport.height);
3277
- const canvas = document.createElement("canvas");
3278
- canvas.width = w;
3279
- canvas.height = h;
3280
- const ctx = canvas.getContext("2d");
3281
- if (!ctx) throw new Error("Canvas 2D context unavailable");
3282
- ctx.imageSmoothingEnabled = true;
3283
- ctx.imageSmoothingQuality = "high";
3284
- await page.render({ canvas, viewport }).promise;
3285
- return { canvas, width: w, height: h };
3286
- }
3287
- function canvasToBlob2(canvas, mime, quality) {
3288
- return new Promise((resolve, reject) => {
3289
- canvas.toBlob(
3290
- (blob) => {
3291
- if (!blob) {
3292
- reject(new Error("Could not encode blob"));
3293
- return;
3294
- }
3295
- resolve(blob);
3296
- },
3297
- mime,
3298
- quality
3299
- );
3300
- });
3301
- }
3302
- async function loadPdfToStore(file, store, options) {
3303
- const scale = 1.5;
3304
- const pdfjs = await getPdfJs();
3305
- const arrayBuffer = await file.arrayBuffer();
3306
- const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
3307
- const results = [];
3308
- for (let i = 1; i <= pdf.numPages; i++) {
3309
- const page = await pdf.getPage(i);
3310
- const { canvas, width, height } = await renderPageToCanvas(page, scale);
3311
- const mime = "image/png";
3312
- const pageBlob = await canvasToBlob2(canvas, mime);
3313
- const blobId = await store.storeOriginal(pageBlob);
3314
- const thumbScale = Math.min(1, 256 / Math.max(width, height));
3315
- const tw = Math.max(1, Math.round(width * thumbScale));
3316
- const th = Math.max(1, Math.round(height * thumbScale));
3317
- const thumbCanvas = document.createElement("canvas");
3318
- thumbCanvas.width = tw;
3319
- thumbCanvas.height = th;
3320
- const tCtx = thumbCanvas.getContext("2d");
3321
- if (tCtx) {
3322
- tCtx.imageSmoothingEnabled = true;
3323
- tCtx.imageSmoothingQuality = "high";
3324
- tCtx.drawImage(canvas, 0, 0, tw, th);
3325
- }
3326
- const thumbBlob = await canvasToBlob2(thumbCanvas, mime);
3327
- const thumbnailBlobId = await store.storeThumbnail(thumbBlob);
3328
- results.push({ blobId, thumbnailBlobId, width, height, pageNumber: i });
3329
- }
3330
- return results;
3331
- }
3332
-
3333
3391
  // src/input/apple-pencil-navigation.ts
3334
3392
  var DRAWING_LIKE_TOOLS = /* @__PURE__ */ new Set([
3335
3393
  "draw",
@@ -3528,7 +3586,7 @@ function attachViewportInput(options) {
3528
3586
  if (e.ctrlKey || e.metaKey) {
3529
3587
  e.preventDefault();
3530
3588
  const dy = wheelDeltaYPixels(e);
3531
- const normDy = dy < 20 ? dy * 12 : dy;
3589
+ const normDy = Math.abs(dy) < 20 ? dy * 12 : dy;
3532
3590
  const factor = Math.exp(-normDy * wheelZoomSensitivity);
3533
3591
  const rect = element.getBoundingClientRect();
3534
3592
  camera.setZoom(camera.zoom * factor, {
@@ -4682,7 +4740,42 @@ init_shape_builders();
4682
4740
 
4683
4741
  // src/react/InteractionOverlay.tsx
4684
4742
  init_rect();
4685
- init_freehand_path();
4743
+
4744
+ // src/scene/freehand-path.ts
4745
+ function smoothFreehandPointsToPathD(points) {
4746
+ const n = points.length;
4747
+ if (n === 0) return "";
4748
+ if (n === 1) {
4749
+ const p = points[0];
4750
+ if (!p) return "";
4751
+ return `M ${p.x} ${p.y}`;
4752
+ }
4753
+ if (n === 2) {
4754
+ const a = points[0];
4755
+ const b = points[1];
4756
+ if (!a || !b) return "";
4757
+ return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
4758
+ }
4759
+ const p0 = points[0];
4760
+ if (!p0) return "";
4761
+ let d = `M ${p0.x} ${p0.y}`;
4762
+ let i = 1;
4763
+ for (; i < n - 2; i++) {
4764
+ const pi = points[i];
4765
+ const pi1 = points[i + 1];
4766
+ if (!pi || !pi1) continue;
4767
+ const xc = (pi.x + pi1.x) / 2;
4768
+ const yc = (pi.y + pi1.y) / 2;
4769
+ d += ` Q ${pi.x} ${pi.y} ${xc} ${yc}`;
4770
+ }
4771
+ const pLast = points[i];
4772
+ const pEnd = points[i + 1];
4773
+ if (!pLast || !pEnd) return d;
4774
+ d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
4775
+ return d;
4776
+ }
4777
+
4778
+ // src/react/InteractionOverlay.tsx
4686
4779
  init_shape_builders();
4687
4780
  var HANDLE_ORDER = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
4688
4781
  var ERASER_TINT = "#cbd5e1";
@@ -5148,7 +5241,6 @@ function InteractionOverlay({
5148
5241
  }
5149
5242
  );
5150
5243
  }
5151
- init_freehand_path();
5152
5244
 
5153
5245
  // src/react/presence/peer-color.ts
5154
5246
  function defaultPresenceColorForId(id) {
@@ -5467,7 +5559,8 @@ var visuallyHidden = {
5467
5559
  whiteSpace: "nowrap",
5468
5560
  border: 0
5469
5561
  };
5470
- var VIEWPORT_ZOOM_STEP = 1.15;
5562
+ var VIEWPORT_ZOOM_IN_STEP = 1.15;
5563
+ var VIEWPORT_ZOOM_OUT_STEP = 1 / 1.15;
5471
5564
  function replaceItem(items, id, next) {
5472
5565
  return items.map((it) => it.id === id ? next : it);
5473
5566
  }
@@ -5671,22 +5764,8 @@ function appendInterpolatedPoints(points, next, maxStepWorld) {
5671
5764
  if (!last) return [...points, next];
5672
5765
  const dx = next.x - last.x;
5673
5766
  const dy = next.y - last.y;
5674
- const dist = Math.hypot(dx, dy);
5675
- if (dist < 1e-6) return points;
5676
- const safeStep = Math.max(maxStepWorld, 1e-4);
5677
- const steps = Math.max(1, Math.ceil(dist / safeStep));
5678
- if (steps === 1) return [...points, next];
5679
- const out = [...points];
5680
- for (let i = 1; i <= steps; i++) {
5681
- const t = i / steps;
5682
- const pressure = last.pressure != null || next.pressure != null ? (last.pressure ?? next.pressure ?? 0.5) + ((next.pressure ?? last.pressure ?? 0.5) - (last.pressure ?? next.pressure ?? 0.5)) * t : void 0;
5683
- out.push({
5684
- x: last.x + dx * t,
5685
- y: last.y + dy * t,
5686
- ...pressure != null ? { pressure } : {}
5687
- });
5688
- }
5689
- return out;
5767
+ if (dx * dx + dy * dy < 1e-12) return points;
5768
+ return [...points, next];
5690
5769
  }
5691
5770
  function pointerSampleToWorldPoint(screenToWorldFn, clientX, clientY, pressure) {
5692
5771
  const { worldX, worldY } = screenToWorldFn(clientX, clientY);
@@ -5718,7 +5797,7 @@ function isLikelyStylusTouch(touch) {
5718
5797
  }
5719
5798
  function appendPointerEventSamplesToStrokePoints(points, ev, zoom, screenToWorldFn) {
5720
5799
  let interpolated = points;
5721
- const maxStepWorld = ev.pointerType === "pen" ? 0.35 / zoom : 1 / zoom;
5800
+ ev.pointerType === "pen" ? 0.35 / zoom : 1 / zoom;
5722
5801
  for (const sample of pointerEventSamples(ev)) {
5723
5802
  const nextPoint = pointerSampleToWorldPoint(
5724
5803
  screenToWorldFn,
@@ -5726,7 +5805,7 @@ function appendPointerEventSamplesToStrokePoints(points, ev, zoom, screenToWorld
5726
5805
  sample.clientY,
5727
5806
  sample.pointerType === "pen" ? sample.pressure : void 0
5728
5807
  );
5729
- interpolated = appendInterpolatedPoints(interpolated, nextPoint, maxStepWorld);
5808
+ interpolated = appendInterpolatedPoints(interpolated, nextPoint);
5730
5809
  }
5731
5810
  return interpolated;
5732
5811
  }
@@ -5737,7 +5816,7 @@ function appendTouchToStrokePoints(points, touch, zoom, screenToWorldFn) {
5737
5816
  touch.clientY,
5738
5817
  touchPressure(touch)
5739
5818
  );
5740
- return appendInterpolatedPoints(points, nextPoint, 0.35 / zoom);
5819
+ return appendInterpolatedPoints(points, nextPoint);
5741
5820
  }
5742
5821
  var VectorViewport = forwardRef(
5743
5822
  function VectorViewport2({
@@ -5759,6 +5838,7 @@ var VectorViewport = forwardRef(
5759
5838
  onCameraChange: consumerOnCameraChange,
5760
5839
  customPlacement: consumerCustomPlacement,
5761
5840
  customPlacements: consumerCustomPlacements,
5841
+ assetStore,
5762
5842
  toolLocked = false,
5763
5843
  autoResetToolTo = "select",
5764
5844
  onToolChangeRequest,
@@ -5929,6 +6009,8 @@ var VectorViewport = forwardRef(
5929
6009
  const itemsRef = useRef(items);
5930
6010
  const onWorldPointerDownRef = useRef(onWorldPointerDown);
5931
6011
  const onItemsChangeRef = useRef(onItemsChange);
6012
+ const assetStoreRef = useRef(assetStore);
6013
+ assetStoreRef.current = assetStore;
5932
6014
  const customPlacementRef = useRef(customPlacement);
5933
6015
  customPlacementRef.current = customPlacement;
5934
6016
  const dragStateRef = useRef({ kind: "idle" });
@@ -6545,14 +6627,14 @@ var VectorViewport = forwardRef(
6545
6627
  handled = true;
6546
6628
  } else if (e.key === "+" || e.key === "=") {
6547
6629
  const rect = el.getBoundingClientRect();
6548
- camera.setZoom(camera.zoom * VIEWPORT_ZOOM_STEP, {
6630
+ camera.setZoom(camera.zoom * VIEWPORT_ZOOM_IN_STEP, {
6549
6631
  x: rect.width / 2,
6550
6632
  y: rect.height / 2
6551
6633
  });
6552
6634
  handled = true;
6553
6635
  } else if (e.key === "-" || e.key === "_") {
6554
6636
  const rect = el.getBoundingClientRect();
6555
- camera.setZoom(camera.zoom / VIEWPORT_ZOOM_STEP, {
6637
+ camera.setZoom(camera.zoom * VIEWPORT_ZOOM_OUT_STEP, {
6556
6638
  x: rect.width / 2,
6557
6639
  y: rect.height / 2
6558
6640
  });
@@ -6821,7 +6903,7 @@ var VectorViewport = forwardRef(
6821
6903
  const el = sceneContainerRef.current;
6822
6904
  if (!camera || !el) return;
6823
6905
  const rect = el.getBoundingClientRect();
6824
- camera.setZoom(camera.zoom * VIEWPORT_ZOOM_STEP, {
6906
+ camera.setZoom(camera.zoom * VIEWPORT_ZOOM_IN_STEP, {
6825
6907
  x: rect.width / 2,
6826
6908
  y: rect.height / 2
6827
6909
  });
@@ -6832,7 +6914,7 @@ var VectorViewport = forwardRef(
6832
6914
  const el = sceneContainerRef.current;
6833
6915
  if (!camera || !el) return;
6834
6916
  const rect = el.getBoundingClientRect();
6835
- camera.setZoom(camera.zoom / VIEWPORT_ZOOM_STEP, {
6917
+ camera.setZoom(camera.zoom * VIEWPORT_ZOOM_OUT_STEP, {
6836
6918
  x: rect.width / 2,
6837
6919
  y: rect.height / 2
6838
6920
  });
@@ -6850,13 +6932,13 @@ var VectorViewport = forwardRef(
6850
6932
  if (isDefaultMarkerToolStyle(current)) {
6851
6933
  setCurrentStrokeStyle({
6852
6934
  stroke: DEFAULT_STROKE_STYLE.stroke,
6853
- strokeWidth: toolId === "draw" ? 3 : DEFAULT_STROKE_STYLE.strokeWidth
6935
+ strokeWidth: toolId === "draw" ? 10 : DEFAULT_STROKE_STYLE.strokeWidth
6854
6936
  });
6855
6937
  return;
6856
6938
  }
6857
6939
  setCurrentStrokeStyle({
6858
6940
  stroke: current.stroke,
6859
- strokeWidth: toolId === "draw" ? 3 : current.strokeWidth
6941
+ strokeWidth: toolId === "draw" ? 10 : current.strokeWidth
6860
6942
  });
6861
6943
  }, [setCurrentStrokeStyle, toolId]);
6862
6944
  useEffect(() => {
@@ -6958,97 +7040,49 @@ var VectorViewport = forwardRef(
6958
7040
  if (!change || files.length === 0) return;
6959
7041
  const store = imageStoreRef.current;
6960
7042
  if (!store) return;
6961
- const { buildRasterImageChildrenSvg: buildRasterImageChildrenSvg2 } = await Promise.resolve().then(() => (init_shape_builders(), shape_builders_exports));
6962
- const newItems = [];
6963
- const gapWorld = 16;
6964
- const stepWorld = 48;
6965
- let placed = 0;
6966
- for (const file of files) {
6967
- try {
6968
- if (file.type === "application/pdf") {
6969
- const estW = 1241;
6970
- const estH = 1754;
6971
- const estPages = 10;
6972
- const skels = [];
6973
- const skelIds = [];
6974
- for (let i = 0; i < estPages; i++) {
7043
+ try {
7044
+ const pdfFiles = files.filter((file) => file.type === "application/pdf");
7045
+ if (pdfFiles.length > 0) {
7046
+ const gapWorld = 16;
7047
+ const estW = 1241;
7048
+ const estH = 1754;
7049
+ const estPages = 10;
7050
+ const skels = [];
7051
+ for (const [fileIndex] of pdfFiles.entries()) {
7052
+ for (let pageIndex = 0; pageIndex < estPages; pageIndex++) {
6975
7053
  const id = `skeleton-${skeletonSeqRef.current++}`;
6976
- skelIds.push(id);
7054
+ const offsetIndex = fileIndex * estPages + pageIndex;
6977
7055
  skels.push({
6978
7056
  id,
6979
7057
  x: worldX - estW / 2,
6980
- y: worldY - estH / 2 + i * (estH + gapWorld),
7058
+ y: worldY - estH / 2 + offsetIndex * (estH + gapWorld),
6981
7059
  width: estW,
6982
7060
  height: estH
6983
7061
  });
6984
7062
  }
6985
- setLoadingSkeletons(skels);
6986
- const pages = await loadPdfToStore(file, store);
6987
- setLoadingSkeletons([]);
6988
- for (const page of pages) {
6989
- const { blobId, width, height } = page;
6990
- const fullUrl = await createBlobUrlFromStore(store, blobId);
6991
- const id = createShapeId();
6992
- const bounds = {
6993
- x: worldX - width / 2,
6994
- y: worldY - height / 2 + placed * (height + gapWorld),
6995
- width,
6996
- height
6997
- };
6998
- newItems.push({
6999
- id,
7000
- x: bounds.x,
7001
- y: bounds.y,
7002
- bounds: { ...bounds },
7003
- toolKind: "image",
7004
- imageBlobId: blobId,
7005
- imageRasterHref: fullUrl ?? void 0,
7006
- imageIntrinsicSize: { width, height },
7007
- childrenSvg: fullUrl ? buildRasterImageChildrenSvg2(fullUrl, { width, height }, bounds) : ""
7008
- });
7009
- placed++;
7010
- }
7011
- } else if (file.type.startsWith("image/")) {
7012
- const { blobId, thumbnailBlobId, width, height } = await loadImageToStore(file, store);
7013
- const fullUrl = await createBlobUrlFromStore(store, blobId);
7014
- const thumbBlob = await store.getThumbnail(thumbnailBlobId);
7015
- const thumbnailHref = thumbBlob ? URL.createObjectURL(thumbBlob) : null;
7016
- const id = createShapeId();
7017
- const ox = placed % 8 * stepWorld;
7018
- const oy = Math.floor(placed / 8) * stepWorld;
7019
- const bounds = {
7020
- x: worldX - width / 2 + ox,
7021
- y: worldY - height / 2 + oy,
7022
- width,
7023
- height
7024
- };
7025
- const href = thumbnailHref ?? fullUrl;
7026
- newItems.push({
7027
- id,
7028
- x: bounds.x,
7029
- y: bounds.y,
7030
- bounds: { ...bounds },
7031
- toolKind: "image",
7032
- imageBlobId: blobId,
7033
- imageThumbnailBlobId: thumbnailBlobId,
7034
- imageRasterHref: fullUrl ?? void 0,
7035
- imageThumbnailHref: thumbnailHref ?? void 0,
7036
- imageIntrinsicSize: { width, height },
7037
- childrenSvg: buildRasterImageChildrenSvg2(
7038
- href ?? "",
7039
- { width, height },
7040
- bounds
7041
- )
7042
- });
7043
- placed++;
7044
7063
  }
7045
- } catch (err) {
7046
- console.error("Failed to add file:", err);
7064
+ setLoadingSkeletons(skels);
7065
+ }
7066
+ const result = await ingestAssetFilesToSceneItems({
7067
+ files,
7068
+ worldCenter: {
7069
+ x: worldX,
7070
+ y: worldY
7071
+ },
7072
+ imageStore: store,
7073
+ assetStore: assetStoreRef.current ?? void 0
7074
+ });
7075
+ if (result.errors.length > 0) {
7076
+ for (const error of result.errors) {
7077
+ console.error("Failed to add file:", error.error);
7078
+ }
7047
7079
  }
7080
+ if (result.items.length === 0) return;
7081
+ change([...itemsRef.current, ...result.items]);
7082
+ setEffectiveSelectedIdsRef.current(result.items.map((item) => item.id));
7083
+ } finally {
7084
+ setLoadingSkeletons([]);
7048
7085
  }
7049
- if (newItems.length === 0) return;
7050
- change([...itemsRef.current, ...newItems]);
7051
- setEffectiveSelectedIdsRef.current(newItems.map((it) => it.id));
7052
7086
  },
7053
7087
  []
7054
7088
  );
@@ -8500,6 +8534,6 @@ function ViewportZoomControls({
8500
8534
  );
8501
8535
  }
8502
8536
 
8503
- export { CanvuChromeContext, CanvuPluginContext, DEFAULT_OVERFLOW_TOOL_IDS, DEFAULT_VECTOR_CANVAS_STORAGE_KEY, DEFAULT_VECTOR_TOOLS, IconArrow, IconDraw, IconEllipse, IconHand, IconImage, IconLaser, IconLine, IconRect, IconSelect, IconText, NavMenu, ShapeContextMenu, VectorCanvas, VectorCanvasBody, VectorCanvasHeader, VectorCanvasMain, VectorCanvasRoot, VectorCanvasToolbar, VectorCanvasViewportSurface, VectorSelectionInspector, VectorToolbar, VectorViewport, ViewportZoomControls, createCanvuPlugin, createIndexedDbPersistenceAdapter, createLocalStoragePersistenceAdapter, createNoopPersistenceAdapter, createToolPlugin, cursorForVectorToolId, getBoardPositionStyle, useCanvuChromeContext, useCanvuDocumentContext, useCanvuPluginContext, useCanvuPluginContribution, useCanvuResolvedTools, useCanvuViewportContext, useVectorCanvasDocument };
8537
+ export { CanvuChromeContext, CanvuPluginContext, DEFAULT_OVERFLOW_TOOL_IDS, DEFAULT_VECTOR_CANVAS_STORAGE_KEY, DEFAULT_VECTOR_TOOLS, IconArrow, IconDraw, IconEllipse, IconHand, IconImage, IconLaser, IconLine, IconRect, IconSelect, IconText, NavMenu, ShapeContextMenu, VectorCanvas, VectorCanvasBody, VectorCanvasHeader, VectorCanvasMain, VectorCanvasRoot, VectorCanvasToolbar, VectorCanvasViewportSurface, VectorSelectionInspector, VectorToolbar, VectorViewport, ViewportZoomControls, createCanvuPlugin, createIndexedDbPersistenceAdapter, createLocalStoragePersistenceAdapter, createNoopPersistenceAdapter, createToolPlugin, cursorForVectorToolId, getBoardPositionStyle, ingestAssetFilesToSceneItems, useCanvuChromeContext, useCanvuDocumentContext, useCanvuPluginContext, useCanvuPluginContribution, useCanvuResolvedTools, useCanvuViewportContext, useVectorCanvasDocument };
8504
8538
  //# sourceMappingURL=react.js.map
8505
8539
  //# sourceMappingURL=react.js.map