@elixpo/lixsketch 5.5.6 → 5.5.7

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.
@@ -241,7 +241,7 @@ var SketchEngine = class {
241
241
  import("./EventDispatcher-RTXLMHPL.js"),
242
242
  import("./UndoRedo-M2JZUBKN.js"),
243
243
  import("./Selection-VVUHY5YJ.js"),
244
- import("./ZoomPan-U3TXC5AJ.js"),
244
+ import("./ZoomPan-NYPDJBD5.js"),
245
245
  import("./CopyPaste-4CKJRHJ3.js"),
246
246
  import("./EraserTrail-PA6IQLQN.js"),
247
247
  import("./ResizeShapes-LKEQTITP.js"),
@@ -410,4 +410,4 @@ export {
410
410
  SketchEngine,
411
411
  SketchEngine_default as default
412
412
  };
413
- //# sourceMappingURL=SketchEngine-DWDJP2BB.js.map
413
+ //# sourceMappingURL=SketchEngine-V45O52WG.js.map
@@ -75,6 +75,82 @@ window.zoomReset = function() {
75
75
  window.__sketchStoreApi.setZoom(1);
76
76
  }
77
77
  };
78
+ function getShapeBoundsForFit(shape) {
79
+ if (!shape) return null;
80
+ const selection = window.multiSelection;
81
+ if (selection && typeof selection.getShapeBounds === "function") {
82
+ try {
83
+ const bounds2 = selection.getShapeBounds(shape);
84
+ if (bounds2 && [bounds2.x, bounds2.y, bounds2.width, bounds2.height].every(Number.isFinite)) {
85
+ return bounds2;
86
+ }
87
+ } catch {
88
+ }
89
+ }
90
+ const bounds = {
91
+ x: Number(shape.x),
92
+ y: Number(shape.y),
93
+ width: Number(shape.width),
94
+ height: Number(shape.height)
95
+ };
96
+ return Object.values(bounds).every(Number.isFinite) ? bounds : null;
97
+ }
98
+ window.zoomFit = function() {
99
+ if (!window.shapes || window.shapes.length === 0) {
100
+ if (window.zoomReset) window.zoomReset();
101
+ return;
102
+ }
103
+ let minX = Infinity;
104
+ let minY = Infinity;
105
+ let maxX = -Infinity;
106
+ let maxY = -Infinity;
107
+ window.shapes.forEach((shape) => {
108
+ const bounds = getShapeBoundsForFit(shape);
109
+ if (!bounds) return;
110
+ const x1 = Math.min(bounds.x, bounds.x + bounds.width);
111
+ const x2 = Math.max(bounds.x, bounds.x + bounds.width);
112
+ const y1 = Math.min(bounds.y, bounds.y + bounds.height);
113
+ const y2 = Math.max(bounds.y, bounds.y + bounds.height);
114
+ minX = Math.min(minX, x1);
115
+ minY = Math.min(minY, y1);
116
+ maxX = Math.max(maxX, x2);
117
+ maxY = Math.max(maxY, y2);
118
+ });
119
+ if (minX === Infinity) {
120
+ if (window.zoomReset) window.zoomReset();
121
+ return;
122
+ }
123
+ const rect = freehandCanvas.getBoundingClientRect();
124
+ const screenW = rect.width || window.innerWidth;
125
+ const screenH = rect.height || window.innerHeight;
126
+ const contentW = maxX - minX;
127
+ const contentH = maxY - minY;
128
+ const paddingX = Math.max(50, contentW * 0.1);
129
+ const paddingY = Math.max(50, contentH * 0.1);
130
+ const paddedW = contentW + paddingX * 2;
131
+ const paddedH = contentH + paddingY * 2;
132
+ const zoomX = screenW / paddedW;
133
+ const zoomY = screenH / paddedH;
134
+ let newZoom = Math.min(zoomX, zoomY);
135
+ newZoom = Math.max(minScale, Math.min(newZoom, maxScale));
136
+ const cx = minX + contentW / 2;
137
+ const cy = minY + contentH / 2;
138
+ currentZoom = newZoom;
139
+ const vbW = screenW / currentZoom;
140
+ const vbH = screenH / currentZoom;
141
+ currentViewBox.x = cx - vbW / 2;
142
+ currentViewBox.y = cy - vbH / 2;
143
+ currentViewBox.width = vbW;
144
+ currentViewBox.height = vbH;
145
+ freehandCanvas.setAttribute(
146
+ "viewBox",
147
+ `${currentViewBox.x} ${currentViewBox.y} ${vbW} ${vbH}`
148
+ );
149
+ updateZoomDisplay();
150
+ if (window.__sketchStoreApi && window.__sketchStoreApi.setZoom) {
151
+ window.__sketchStoreApi.setZoom(currentZoom);
152
+ }
153
+ };
78
154
  freehandCanvas.addEventListener("wheel", function(e) {
79
155
  if (!e.ctrlKey) return;
80
156
  e.preventDefault();
@@ -183,4 +259,4 @@ svg.addEventListener("wheel", (e) => {
183
259
  `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`
184
260
  );
185
261
  });
186
- //# sourceMappingURL=ZoomPan-U3TXC5AJ.js.map
262
+ //# sourceMappingURL=ZoomPan-NYPDJBD5.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/core/ZoomPan.js"],
4
+ "sourcesContent": ["/* eslint-disable */\n// Zoom and Pan controls - combined from zoomFunction.js + panCanvas.js\n// Depends on globals: svg, freehandCanvas, currentZoom, currentViewBox, isPanningToolActive, isPanning\n// Depends on globals: minScale, maxScale, zoomInBtn, zoomOutBtn, zoomPercentSpan, panStart, startCanvasX, startCanvasY\n\n// === Zoom Functions ===\nlet currentY = 0;\nconst scrollRate = 50;\n\nfunction updateZoomDisplay() {\n zoomPercentSpan.innerText = Math.round(currentZoom * 100) + \"%\";\n}\n\nfunction updateViewBox(anchorX = null, anchorY = null) {\n const width = window.innerWidth;\n const height = window.innerHeight;\n const scaledWidth = width / currentZoom;\n const scaledHeight = height / currentZoom;\n\n let centerX, centerY;\n\n if (anchorX === null || anchorY === null) {\n centerX = currentViewBox.x + (currentViewBox.width / 2);\n centerY = currentViewBox.y + (currentViewBox.height / 2);\n } else {\n\n centerX = anchorX;\n centerY = anchorY;\n }\n\n const viewBoxX = centerX - (scaledWidth / 2);\n const viewBoxY = centerY - (scaledHeight / 2);\n\n freehandCanvas.setAttribute(\n \"viewBox\",\n `${viewBoxX} ${viewBoxY} ${scaledWidth} ${scaledHeight}`\n );\n\n currentViewBox.x = viewBoxX;\n currentViewBox.y = viewBoxY;\n currentViewBox.width = scaledWidth;\n currentViewBox.height = scaledHeight;\n}\n\nzoomInBtn.addEventListener(\"click\", function() {\n currentZoom *= 1.1;\n if (currentZoom > maxScale) currentZoom = maxScale;\n updateViewBox();\n updateZoomDisplay();\n});\n\nzoomOutBtn.addEventListener(\"click\", function() {\n currentZoom /= 1.1;\n if (currentZoom < minScale) currentZoom = minScale;\n updateViewBox();\n updateZoomDisplay();\n});\n\n// Exposed for React Footer buttons \u2014 zoom from center of canvas\nwindow.zoomFromCenter = function(direction) {\n if (direction > 0) {\n currentZoom *= 1.1;\n if (currentZoom > maxScale) currentZoom = maxScale;\n } else {\n currentZoom /= 1.1;\n if (currentZoom < minScale) currentZoom = minScale;\n }\n updateViewBox(); // null anchors = zoom from center\n updateZoomDisplay();\n // Sync React zoom state\n if (window.__sketchStoreApi && window.__sketchStoreApi.setZoom) {\n window.__sketchStoreApi.setZoom(currentZoom);\n }\n};\n\nwindow.zoomReset = function() {\n // Issue #24 bug #5: reset zoom in place around the current viewport\n // centre \u2014 do NOT recenter to (0, 0). Recentering used to make whatever\n // the user had on screen vanish until they panned back to it.\n const cx = currentViewBox.x + currentViewBox.width / 2;\n const cy = currentViewBox.y + currentViewBox.height / 2;\n\n currentZoom = 1;\n const rect = freehandCanvas.getBoundingClientRect();\n const vbW = rect.width || window.innerWidth;\n const vbH = rect.height || window.innerHeight;\n\n currentViewBox.x = cx - vbW / 2;\n currentViewBox.y = cy - vbH / 2;\n currentViewBox.width = vbW;\n currentViewBox.height = vbH;\n freehandCanvas.setAttribute(\n \"viewBox\",\n `${currentViewBox.x} ${currentViewBox.y} ${vbW} ${vbH}`\n );\n updateZoomDisplay();\n if (window.__sketchStoreApi && window.__sketchStoreApi.setZoom) {\n window.__sketchStoreApi.setZoom(1);\n }\n};\n\nfunction getShapeBoundsForFit(shape) {\n if (!shape) return null;\n\n const selection = window.multiSelection;\n if (selection && typeof selection.getShapeBounds === \"function\") {\n try {\n const bounds = selection.getShapeBounds(shape);\n if (bounds && [bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite)) {\n return bounds;\n }\n } catch {}\n }\n\n // Fall back to the common shape interface when selection is unavailable.\n // Accessing x/y instead of boundingBox preserves freehand move offsets.\n const bounds = {\n x: Number(shape.x),\n y: Number(shape.y),\n width: Number(shape.width),\n height: Number(shape.height),\n };\n return Object.values(bounds).every(Number.isFinite) ? bounds : null;\n}\n\nwindow.zoomFit = function() {\n if (!window.shapes || window.shapes.length === 0) {\n if (window.zoomReset) window.zoomReset();\n return;\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n window.shapes.forEach(shape => {\n const bounds = getShapeBoundsForFit(shape);\n if (!bounds) return;\n \n // Normalize coordinates just in case width/height is negative\n const x1 = Math.min(bounds.x, bounds.x + bounds.width);\n const x2 = Math.max(bounds.x, bounds.x + bounds.width);\n const y1 = Math.min(bounds.y, bounds.y + bounds.height);\n const y2 = Math.max(bounds.y, bounds.y + bounds.height);\n\n minX = Math.min(minX, x1);\n minY = Math.min(minY, y1);\n maxX = Math.max(maxX, x2);\n maxY = Math.max(maxY, y2);\n });\n\n if (minX === Infinity) {\n if (window.zoomReset) window.zoomReset();\n return;\n }\n\n const rect = freehandCanvas.getBoundingClientRect();\n const screenW = rect.width || window.innerWidth;\n const screenH = rect.height || window.innerHeight;\n\n const contentW = maxX - minX;\n const contentH = maxY - minY;\n \n // Add 10% padding\n const paddingX = Math.max(50, contentW * 0.1);\n const paddingY = Math.max(50, contentH * 0.1);\n\n const paddedW = contentW + paddingX * 2;\n const paddedH = contentH + paddingY * 2;\n\n // Calculate required zoom to fit the padded content in the screen\n const zoomX = screenW / paddedW;\n const zoomY = screenH / paddedH;\n \n let newZoom = Math.min(zoomX, zoomY);\n newZoom = Math.max(minScale, Math.min(newZoom, maxScale));\n\n // Center coordinate of the content\n const cx = minX + contentW / 2;\n const cy = minY + contentH / 2;\n\n // Set the new viewBox and zoom\n currentZoom = newZoom;\n const vbW = screenW / currentZoom;\n const vbH = screenH / currentZoom;\n\n currentViewBox.x = cx - vbW / 2;\n currentViewBox.y = cy - vbH / 2;\n currentViewBox.width = vbW;\n currentViewBox.height = vbH;\n\n freehandCanvas.setAttribute(\n \"viewBox\",\n `${currentViewBox.x} ${currentViewBox.y} ${vbW} ${vbH}`\n );\n \n updateZoomDisplay();\n if (window.__sketchStoreApi && window.__sketchStoreApi.setZoom) {\n window.__sketchStoreApi.setZoom(currentZoom);\n }\n};\n\nfreehandCanvas.addEventListener(\"wheel\", function(e) {\n if (!e.ctrlKey) return;\n e.preventDefault();\n\n // Determine the zoom delta\n const delta = e.deltaY > 0 ? -0.1 : 0.1;\n let newZoom = currentZoom + delta;\n if (newZoom < minScale) newZoom = minScale;\n if (newZoom > maxScale) newZoom = maxScale;\n\n // Get canvas bounding rect \u2014 this MUST be the SVG element's actual\n // size, not the window's. In split mode the canvas pane is narrower\n // than the viewport; using window.innerWidth here would produce a\n // viewBox aspect that doesn't match the element, and (with\n // preserveAspectRatio=\"none\" on the host) the content gets stretched\n // / squeezed on every zoom step.\n const rect = freehandCanvas.getBoundingClientRect();\n\n // Calculate mouse position relative to the canvas in pixels\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n\n // Determine what fraction of the canvas the mouse is at\n const mouseFracX = mouseX / rect.width;\n const mouseFracY = mouseY / rect.height;\n\n // Compute the current viewBox coordinate under the mouse.\n // currentViewBox.width and .height represent the current viewBox dimensions.\n const anchorViewBoxX = currentViewBox.x + mouseFracX * currentViewBox.width;\n const anchorViewBoxY = currentViewBox.y + mouseFracY * currentViewBox.height;\n\n // New viewBox dimensions sized to the *element*, scaled by the new\n // zoom. This keeps the viewBox aspect ratio matching the element so\n // shapes stay undistorted.\n const newViewBoxWidth = rect.width / newZoom;\n const newViewBoxHeight = rect.height / newZoom;\n \n // Compute the new viewBox's x and y so that the anchor remains at the same screen fraction.\n // That means:\n // newViewBox.x + mouseFracX * newViewBoxWidth === anchorViewBoxX\n // Solve for newViewBox.x:\n const newViewBoxX = anchorViewBoxX - mouseFracX * newViewBoxWidth;\n const newViewBoxY = anchorViewBoxY - mouseFracY * newViewBoxHeight;\n \n // Update the viewBox attribute with the new values.\n freehandCanvas.setAttribute(\n \"viewBox\",\n `${newViewBoxX} ${newViewBoxY} ${newViewBoxWidth} ${newViewBoxHeight}`\n );\n \n // Update our state\n currentZoom = newZoom;\n currentViewBox = {\n x: newViewBoxX,\n y: newViewBoxY,\n width: newViewBoxWidth,\n height: newViewBoxHeight\n };\n\n updateZoomDisplay();\n // Sync React zoom state\n if (window.__sketchStoreApi && window.__sketchStoreApi.setZoom) {\n window.__sketchStoreApi.setZoom(currentZoom);\n }\n});\n\n// Function to resize the canvas to fill the screen (initial setup)\nfunction resizeCanvas() {\n const width = window.innerWidth;\n const height = window.innerHeight;\n\n freehandCanvas.style.width = `${width}px`;\n freehandCanvas.style.height = `${height}px`;\n\n // Set initial viewBox based on initial zoom\n updateViewBox(); // Initial center anchor\n}\n\n\n\nlet isMiddleMousePanning = false;\n\nfreehandCanvas.addEventListener(\"mousedown\", function (e) {\n // Middle mouse button panning\n if (e.button === 1) {\n e.preventDefault();\n isMiddleMousePanning = true;\n isPanning = true;\n startCanvasX = e.clientX;\n startCanvasY = e.clientY;\n panStart = { x: e.clientX, y: e.clientY };\n freehandCanvas.style.cursor = 'grabbing';\n return;\n }\n if (isPanningToolActive) {\n isPanning = true;\n startCanvasX = e.clientX;\n startCanvasY = e.clientY;\n panStart = { x: e.clientX, y: e.clientY };\n freehandCanvas.style.cursor = 'grabbing';\n }\n});\n\nfreehandCanvas.addEventListener(\"mousemove\", (e) => {\n if (!isPanning) return;\n\n const dx = e.clientX - panStart.x;\n const dy = e.clientY - panStart.y;\n const dxViewBox = dx / currentZoom;\n const dyViewBox = dy / currentZoom;\n\n currentViewBox.x -= dxViewBox;\n currentViewBox.y -= dyViewBox;\n\n freehandCanvas.setAttribute(\n \"viewBox\",\n `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`\n );\n\n panStart = { x: e.clientX, y: e.clientY };\n});\n\nfreehandCanvas.addEventListener(\"mouseup\", (e) => {\n if (isMiddleMousePanning) {\n isMiddleMousePanning = false;\n isPanning = false;\n freehandCanvas.style.cursor = '';\n return;\n }\n if(isPanningToolActive)\n {\n isPanning = false;\n freehandCanvas.style.cursor = 'grab';\n }\n});\n\nfreehandCanvas.addEventListener(\"mouseleave\", () => {\n if (isMiddleMousePanning) {\n isMiddleMousePanning = false;\n isPanning = false;\n freehandCanvas.style.cursor = '';\n return;\n }\n if(isPanningToolActive)\n {\n isPanning = false;\n freehandCanvas.style.cursor = 'grab';\n }\n});\n\n// Prevent default middle-click auto-scroll behavior\nfreehandCanvas.addEventListener(\"auxclick\", (e) => {\n if (e.button === 1) e.preventDefault();\n});\n\n\nsvg.addEventListener(\"wheel\", (e) => {\n e.preventDefault();\n if (e.ctrlKey) return; // Ignore zoom gestures\n\n if (e.shiftKey) {\n // Pan sideways when Shift is held\n currentViewBox.x += e.deltaY > 0 ? scrollRate : -scrollRate;\n } else {\n // Pan vertically\n currentViewBox.y += e.deltaY > 0 ? scrollRate : -scrollRate;\n }\n\n svg.setAttribute(\n \"viewBox\",\n `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`\n );\n});\n"],
5
+ "mappings": ";;;AAOA,IAAM,aAAa;AAEnB,SAAS,oBAAoB;AAC3B,kBAAgB,YAAY,KAAK,MAAM,cAAc,GAAG,IAAI;AAC9D;AAEA,SAAS,cAAc,UAAU,MAAM,UAAU,MAAM;AACrD,QAAM,QAAQ,OAAO;AACrB,QAAM,SAAS,OAAO;AACtB,QAAM,cAAc,QAAQ;AAC5B,QAAM,eAAe,SAAS;AAE9B,MAAI,SAAS;AAEb,MAAI,YAAY,QAAQ,YAAY,MAAM;AACxC,cAAU,eAAe,IAAK,eAAe,QAAQ;AACrD,cAAU,eAAe,IAAK,eAAe,SAAS;AAAA,EACxD,OAAO;AAEL,cAAU;AACV,cAAU;AAAA,EACZ;AAEA,QAAM,WAAW,UAAW,cAAc;AAC1C,QAAM,WAAW,UAAW,eAAe;AAE3C,iBAAe;AAAA,IACb;AAAA,IACA,GAAG,QAAQ,IAAI,QAAQ,IAAI,WAAW,IAAI,YAAY;AAAA,EACxD;AAEA,iBAAe,IAAI;AACnB,iBAAe,IAAI;AACnB,iBAAe,QAAQ;AACvB,iBAAe,SAAS;AAC1B;AAEA,UAAU,iBAAiB,SAAS,WAAW;AAC7C,iBAAe;AACf,MAAI,cAAc,SAAU,eAAc;AAC1C,gBAAc;AACd,oBAAkB;AACpB,CAAC;AAED,WAAW,iBAAiB,SAAS,WAAW;AAC9C,iBAAe;AACf,MAAI,cAAc,SAAU,eAAc;AAC1C,gBAAc;AACd,oBAAkB;AACpB,CAAC;AAGD,OAAO,iBAAiB,SAAS,WAAW;AAC1C,MAAI,YAAY,GAAG;AACjB,mBAAe;AACf,QAAI,cAAc,SAAU,eAAc;AAAA,EAC5C,OAAO;AACL,mBAAe;AACf,QAAI,cAAc,SAAU,eAAc;AAAA,EAC5C;AACA,gBAAc;AACd,oBAAkB;AAElB,MAAI,OAAO,oBAAoB,OAAO,iBAAiB,SAAS;AAC9D,WAAO,iBAAiB,QAAQ,WAAW;AAAA,EAC7C;AACF;AAEA,OAAO,YAAY,WAAW;AAI5B,QAAM,KAAK,eAAe,IAAI,eAAe,QAAQ;AACrD,QAAM,KAAK,eAAe,IAAI,eAAe,SAAS;AAEtD,gBAAc;AACd,QAAM,OAAO,eAAe,sBAAsB;AAClD,QAAM,MAAM,KAAK,SAAS,OAAO;AACjC,QAAM,MAAM,KAAK,UAAU,OAAO;AAElC,iBAAe,IAAI,KAAK,MAAM;AAC9B,iBAAe,IAAI,KAAK,MAAM;AAC9B,iBAAe,QAAQ;AACvB,iBAAe,SAAS;AACxB,iBAAe;AAAA,IACb;AAAA,IACA,GAAG,eAAe,CAAC,IAAI,eAAe,CAAC,IAAI,GAAG,IAAI,GAAG;AAAA,EACvD;AACA,oBAAkB;AAClB,MAAI,OAAO,oBAAoB,OAAO,iBAAiB,SAAS;AAC9D,WAAO,iBAAiB,QAAQ,CAAC;AAAA,EACnC;AACF;AAEA,SAAS,qBAAqB,OAAO;AACnC,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,YAAY,OAAO;AACzB,MAAI,aAAa,OAAO,UAAU,mBAAmB,YAAY;AAC/D,QAAI;AACF,YAAMA,UAAS,UAAU,eAAe,KAAK;AAC7C,UAAIA,WAAU,CAACA,QAAO,GAAGA,QAAO,GAAGA,QAAO,OAAOA,QAAO,MAAM,EAAE,MAAM,OAAO,QAAQ,GAAG;AACtF,eAAOA;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAIA,QAAM,SAAS;AAAA,IACb,GAAG,OAAO,MAAM,CAAC;AAAA,IACjB,GAAG,OAAO,MAAM,CAAC;AAAA,IACjB,OAAO,OAAO,MAAM,KAAK;AAAA,IACzB,QAAQ,OAAO,MAAM,MAAM;AAAA,EAC7B;AACA,SAAO,OAAO,OAAO,MAAM,EAAE,MAAM,OAAO,QAAQ,IAAI,SAAS;AACjE;AAEA,OAAO,UAAU,WAAW;AAC1B,MAAI,CAAC,OAAO,UAAU,OAAO,OAAO,WAAW,GAAG;AAChD,QAAI,OAAO,UAAW,QAAO,UAAU;AACvC;AAAA,EACF;AAEA,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AAEX,SAAO,OAAO,QAAQ,WAAS;AAC7B,UAAM,SAAS,qBAAqB,KAAK;AACzC,QAAI,CAAC,OAAQ;AAGb,UAAM,KAAK,KAAK,IAAI,OAAO,GAAG,OAAO,IAAI,OAAO,KAAK;AACrD,UAAM,KAAK,KAAK,IAAI,OAAO,GAAG,OAAO,IAAI,OAAO,KAAK;AACrD,UAAM,KAAK,KAAK,IAAI,OAAO,GAAG,OAAO,IAAI,OAAO,MAAM;AACtD,UAAM,KAAK,KAAK,IAAI,OAAO,GAAG,OAAO,IAAI,OAAO,MAAM;AAEtD,WAAO,KAAK,IAAI,MAAM,EAAE;AACxB,WAAO,KAAK,IAAI,MAAM,EAAE;AACxB,WAAO,KAAK,IAAI,MAAM,EAAE;AACxB,WAAO,KAAK,IAAI,MAAM,EAAE;AAAA,EAC1B,CAAC;AAED,MAAI,SAAS,UAAU;AACrB,QAAI,OAAO,UAAW,QAAO,UAAU;AACvC;AAAA,EACF;AAEA,QAAM,OAAO,eAAe,sBAAsB;AAClD,QAAM,UAAU,KAAK,SAAS,OAAO;AACrC,QAAM,UAAU,KAAK,UAAU,OAAO;AAEtC,QAAM,WAAW,OAAO;AACxB,QAAM,WAAW,OAAO;AAGxB,QAAM,WAAW,KAAK,IAAI,IAAI,WAAW,GAAG;AAC5C,QAAM,WAAW,KAAK,IAAI,IAAI,WAAW,GAAG;AAE5C,QAAM,UAAU,WAAW,WAAW;AACtC,QAAM,UAAU,WAAW,WAAW;AAGtC,QAAM,QAAQ,UAAU;AACxB,QAAM,QAAQ,UAAU;AAExB,MAAI,UAAU,KAAK,IAAI,OAAO,KAAK;AACnC,YAAU,KAAK,IAAI,UAAU,KAAK,IAAI,SAAS,QAAQ,CAAC;AAGxD,QAAM,KAAK,OAAO,WAAW;AAC7B,QAAM,KAAK,OAAO,WAAW;AAG7B,gBAAc;AACd,QAAM,MAAM,UAAU;AACtB,QAAM,MAAM,UAAU;AAEtB,iBAAe,IAAI,KAAK,MAAM;AAC9B,iBAAe,IAAI,KAAK,MAAM;AAC9B,iBAAe,QAAQ;AACvB,iBAAe,SAAS;AAExB,iBAAe;AAAA,IACb;AAAA,IACA,GAAG,eAAe,CAAC,IAAI,eAAe,CAAC,IAAI,GAAG,IAAI,GAAG;AAAA,EACvD;AAEA,oBAAkB;AAClB,MAAI,OAAO,oBAAoB,OAAO,iBAAiB,SAAS;AAC9D,WAAO,iBAAiB,QAAQ,WAAW;AAAA,EAC7C;AACF;AAEA,eAAe,iBAAiB,SAAS,SAAS,GAAG;AACnD,MAAI,CAAC,EAAE,QAAS;AAChB,IAAE,eAAe;AAGjB,QAAM,QAAQ,EAAE,SAAS,IAAI,OAAO;AACpC,MAAI,UAAU,cAAc;AAC5B,MAAI,UAAU,SAAU,WAAU;AAClC,MAAI,UAAU,SAAU,WAAU;AAQlC,QAAM,OAAO,eAAe,sBAAsB;AAGlD,QAAM,SAAS,EAAE,UAAU,KAAK;AAChC,QAAM,SAAS,EAAE,UAAU,KAAK;AAGhC,QAAM,aAAa,SAAS,KAAK;AACjC,QAAM,aAAa,SAAS,KAAK;AAIjC,QAAM,iBAAiB,eAAe,IAAI,aAAa,eAAe;AACtE,QAAM,iBAAiB,eAAe,IAAI,aAAa,eAAe;AAKtE,QAAM,kBAAkB,KAAK,QAAQ;AACrC,QAAM,mBAAmB,KAAK,SAAS;AAMvC,QAAM,cAAc,iBAAiB,aAAa;AAClD,QAAM,cAAc,iBAAiB,aAAa;AAGlD,iBAAe;AAAA,IACb;AAAA,IACA,GAAG,WAAW,IAAI,WAAW,IAAI,eAAe,IAAI,gBAAgB;AAAA,EACtE;AAGA,gBAAc;AACd,mBAAiB;AAAA,IACf,GAAG;AAAA,IACH,GAAG;AAAA,IACH,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAEA,oBAAkB;AAElB,MAAI,OAAO,oBAAoB,OAAO,iBAAiB,SAAS;AAC9D,WAAO,iBAAiB,QAAQ,WAAW;AAAA,EAC7C;AACF,CAAC;AAgBD,IAAI,uBAAuB;AAE3B,eAAe,iBAAiB,aAAa,SAAU,GAAG;AAExD,MAAI,EAAE,WAAW,GAAG;AAChB,MAAE,eAAe;AACjB,2BAAuB;AACvB,gBAAY;AACZ,mBAAe,EAAE;AACjB,mBAAe,EAAE;AACjB,eAAW,EAAE,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ;AACxC,mBAAe,MAAM,SAAS;AAC9B;AAAA,EACJ;AACA,MAAI,qBAAqB;AACrB,gBAAY;AACZ,mBAAe,EAAE;AACjB,mBAAe,EAAE;AACjB,eAAW,EAAE,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ;AACxC,mBAAe,MAAM,SAAS;AAAA,EAClC;AACF,CAAC;AAED,eAAe,iBAAiB,aAAa,CAAC,MAAM;AAClD,MAAI,CAAC,UAAW;AAEhB,QAAM,KAAK,EAAE,UAAU,SAAS;AAChC,QAAM,KAAK,EAAE,UAAU,SAAS;AAChC,QAAM,YAAY,KAAK;AACvB,QAAM,YAAY,KAAK;AAEvB,iBAAe,KAAK;AACpB,iBAAe,KAAK;AAEpB,iBAAe;AAAA,IACX;AAAA,IACA,GAAG,eAAe,CAAC,IAAI,eAAe,CAAC,IAAI,eAAe,KAAK,IAAI,eAAe,MAAM;AAAA,EAC5F;AAEA,aAAW,EAAE,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ;AAC1C,CAAC;AAED,eAAe,iBAAiB,WAAW,CAAC,MAAM;AAChD,MAAI,sBAAsB;AACtB,2BAAuB;AACvB,gBAAY;AACZ,mBAAe,MAAM,SAAS;AAC9B;AAAA,EACJ;AACA,MAAG,qBACH;AACI,gBAAY;AACZ,mBAAe,MAAM,SAAS;AAAA,EAClC;AACF,CAAC;AAED,eAAe,iBAAiB,cAAc,MAAM;AAClD,MAAI,sBAAsB;AACtB,2BAAuB;AACvB,gBAAY;AACZ,mBAAe,MAAM,SAAS;AAC9B;AAAA,EACJ;AACA,MAAG,qBACH;AACI,gBAAY;AACZ,mBAAe,MAAM,SAAS;AAAA,EAClC;AACF,CAAC;AAGD,eAAe,iBAAiB,YAAY,CAAC,MAAM;AACjD,MAAI,EAAE,WAAW,EAAG,GAAE,eAAe;AACvC,CAAC;AAGD,IAAI,iBAAiB,SAAS,CAAC,MAAM;AACnC,IAAE,eAAe;AACjB,MAAI,EAAE,QAAS;AAEf,MAAI,EAAE,UAAU;AAEZ,mBAAe,KAAK,EAAE,SAAS,IAAI,aAAa,CAAC;AAAA,EACrD,OAAO;AAEH,mBAAe,KAAK,EAAE,SAAS,IAAI,aAAa,CAAC;AAAA,EACrD;AAEA,MAAI;AAAA,IACA;AAAA,IACA,GAAG,eAAe,CAAC,IAAI,eAAe,CAAC,IAAI,eAAe,KAAK,IAAI,eAAe,MAAM;AAAA,EAC5F;AACF,CAAC;",
6
+ "names": ["bounds"]
7
+ }
@@ -427,7 +427,7 @@ function useSketchEngine(svgRef, ready = true) {
427
427
  setZoom: (zoom) => useSketchStore_default.setState({ zoom }),
428
428
  getState: () => useSketchStore_default.getState()
429
429
  };
430
- const { SketchEngine } = await import("./SketchEngine-DWDJP2BB.js");
430
+ const { SketchEngine } = await import("./SketchEngine-V45O52WG.js");
431
431
  if (cancelled) return;
432
432
  const engine = new SketchEngine(svgRef.current);
433
433
  await engine.init();
@@ -736,6 +736,9 @@ function Footer() {
736
736
  const handleZoomReset = useCallback(() => {
737
737
  if (window.zoomReset) window.zoomReset();
738
738
  }, []);
739
+ const handleZoomFit = useCallback(() => {
740
+ if (window.zoomFit) window.zoomFit();
741
+ }, []);
739
742
  const handleUndo = useCallback(() => {
740
743
  if (window.undo) window.undo();
741
744
  }, []);
@@ -766,6 +769,17 @@ function Footer() {
766
769
  )
767
770
  ] }),
768
771
  /* @__PURE__ */ jsxs3("div", { className: "flex items-center bg-surface rounded-lg overflow-hidden", children: [
772
+ /* @__PURE__ */ jsx3(
773
+ "button",
774
+ {
775
+ onClick: handleZoomFit,
776
+ "aria-label": "Fit canvas to content",
777
+ title: "Fit to Content (Shift+1)",
778
+ className: "w-9 h-9 flex items-center justify-center text-text-muted hover:text-text-primary hover:bg-surface-hover transition-all duration-200",
779
+ children: /* @__PURE__ */ jsx3("i", { className: "bx bx-target-lock text-lg" })
780
+ }
781
+ ),
782
+ /* @__PURE__ */ jsx3("div", { className: "w-px h-5 bg-border-light" }),
769
783
  /* @__PURE__ */ jsx3(
770
784
  "button",
771
785
  {
@@ -5222,6 +5236,11 @@ function installEngineShortcuts(engine, options = {}) {
5222
5236
  }
5223
5237
  return;
5224
5238
  }
5239
+ if (e.shiftKey && (key === "1" || key === "!")) {
5240
+ e.preventDefault();
5241
+ if (typeof window.zoomFit === "function") window.zoomFit();
5242
+ return;
5243
+ }
5225
5244
  if (e.key === "Delete" || e.key === "Backspace") {
5226
5245
  e.preventDefault();
5227
5246
  if (window.multiSelection?.selectedShapes?.size > 0) {