@markup-canvas/core 1.1.6 → 1.1.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.
@@ -42,6 +42,7 @@ export declare class MarkupCanvas implements Canvas {
42
42
  };
43
43
  zoomToPoint(x: number, y: number, targetScale: number): boolean;
44
44
  resetView(): boolean;
45
+ resetViewToCenter(): boolean;
45
46
  zoomToFitContent(): boolean;
46
47
  panLeft(distance?: number): boolean;
47
48
  panRight(distance?: number): boolean;
@@ -0,0 +1,5 @@
1
+ import type { Canvas } from "@/types/index.js";
2
+ export declare function getViewportCenter(canvas: Canvas): {
3
+ x: number;
4
+ y: number;
5
+ };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 1.1.6
4
+ * @version 1.1.7
5
5
  */
6
6
  'use strict';
7
7
 
@@ -20,8 +20,8 @@ const EDITOR_PRESET = {
20
20
  enableZoom: true,
21
21
  enablePan: true,
22
22
  enableTouch: true,
23
- enableKeyboard: false,
24
- bindKeyboardEventsTo: "canvas",
23
+ enableKeyboard: true,
24
+ bindKeyboardEventsTo: "document",
25
25
  // Zoom behavior
26
26
  zoomSpeed: 1.5,
27
27
  minZoom: 0.05,
@@ -276,13 +276,6 @@ function withRulerSize(canvas, rulerSize, operation) {
276
276
  const finalRulerSize = hasRulers ? rulerSize : 0;
277
277
  return operation(finalRulerSize);
278
278
  }
279
- function withRulerOffsets(canvas, rulerSize, x, y, operation) {
280
- return withRulerSize(canvas, rulerSize, (rulerSize) => {
281
- const adjustedX = x - rulerSize;
282
- const adjustedY = y - rulerSize;
283
- return operation(adjustedX, adjustedY);
284
- });
285
- }
286
279
  function withRulerOffsetObject(canvas, rulerSize, coords, operation) {
287
280
  return withRulerSize(canvas, rulerSize, (rulerSize) => {
288
281
  const adjusted = {
@@ -776,42 +769,28 @@ function getAdaptiveZoomSpeed(canvas, baseSpeed) {
776
769
  }
777
770
 
778
771
  function setupKeyboardEvents(canvas, config) {
779
- // Track mouse position
780
- let lastMouseX = 0;
781
- let lastMouseY = 0;
782
- function handleMouseMove(event) {
783
- const rect = canvas.container.getBoundingClientRect();
784
- const rawMouseX = event.clientX - rect.left;
785
- const rawMouseY = event.clientY - rect.top;
786
- withRulerOffsets(canvas, config.rulerSize, rawMouseX, rawMouseY, (adjustedX, adjustedY) => {
787
- lastMouseX = adjustedX;
788
- lastMouseY = adjustedY;
789
- });
790
- }
791
772
  function handleKeyDown(event) {
792
773
  if (!(event instanceof KeyboardEvent))
793
774
  return;
794
775
  if (config.bindKeyboardEventsTo === "canvas" && document.activeElement !== canvas.container)
795
776
  return;
796
- const isFastPan = event.shiftKey;
797
- const panDistance = config.keyboardPanStep * (isFastPan ? config.keyboardFastMultiplier : 1);
798
777
  let handled = false;
799
778
  const newTransform = {};
800
779
  switch (event.key) {
801
780
  case "ArrowLeft":
802
- newTransform.translateX = canvas.transform.translateX + panDistance;
781
+ newTransform.translateX = canvas.transform.translateX + config.keyboardPanStep;
803
782
  handled = true;
804
783
  break;
805
784
  case "ArrowRight":
806
- newTransform.translateX = canvas.transform.translateX - panDistance;
785
+ newTransform.translateX = canvas.transform.translateX - config.keyboardPanStep;
807
786
  handled = true;
808
787
  break;
809
788
  case "ArrowUp":
810
- newTransform.translateY = canvas.transform.translateY + panDistance;
789
+ newTransform.translateY = canvas.transform.translateY + config.keyboardPanStep;
811
790
  handled = true;
812
791
  break;
813
792
  case "ArrowDown":
814
- newTransform.translateY = canvas.transform.translateY - panDistance;
793
+ newTransform.translateY = canvas.transform.translateY - config.keyboardPanStep;
815
794
  handled = true;
816
795
  break;
817
796
  case "=":
@@ -820,7 +799,7 @@ function setupKeyboardEvents(canvas, config) {
820
799
  const adaptiveZoomStep = config.enableAdaptiveSpeed
821
800
  ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
822
801
  : config.keyboardZoomStep;
823
- newTransform.scale = clampZoom(canvas.transform.scale * (1 + adaptiveZoomStep), config);
802
+ canvas.zoomIn(adaptiveZoomStep);
824
803
  handled = true;
825
804
  }
826
805
  break;
@@ -829,16 +808,21 @@ function setupKeyboardEvents(canvas, config) {
829
808
  const adaptiveZoomStep = config.enableAdaptiveSpeed
830
809
  ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
831
810
  : config.keyboardZoomStep;
832
- newTransform.scale = clampZoom(canvas.transform.scale * (1 - adaptiveZoomStep), config);
811
+ canvas.zoomOut(adaptiveZoomStep);
833
812
  handled = true;
834
813
  }
835
814
  break;
836
815
  case "0":
837
- if (event.metaKey || event.ctrlKey) {
838
- const targetScale = 1.0;
839
- const zoomFactor = targetScale / canvas.transform.scale;
840
- const zoomTransform = getZoomToMouseTransform(lastMouseX, lastMouseY, canvas.transform, zoomFactor, config);
841
- Object.assign(newTransform, zoomTransform);
816
+ if (event.ctrlKey) {
817
+ if (canvas.resetView) {
818
+ canvas.resetView();
819
+ }
820
+ handled = true;
821
+ }
822
+ else if (event.metaKey || event.ctrlKey) {
823
+ if (canvas.resetViewToCenter) {
824
+ canvas.resetViewToCenter();
825
+ }
842
826
  handled = true;
843
827
  }
844
828
  break;
@@ -866,10 +850,8 @@ function setupKeyboardEvents(canvas, config) {
866
850
  }
867
851
  const keyboardTarget = config.bindKeyboardEventsTo === "canvas" ? canvas.container : document;
868
852
  keyboardTarget.addEventListener("keydown", handleKeyDown);
869
- canvas.container.addEventListener("mousemove", handleMouseMove);
870
853
  return () => {
871
854
  keyboardTarget.removeEventListener("keydown", handleKeyDown);
872
- canvas.container.removeEventListener("mousemove", handleMouseMove);
873
855
  };
874
856
  }
875
857
 
@@ -1490,6 +1472,20 @@ function setupWheelEvents(canvas, config) {
1490
1472
  };
1491
1473
  }
1492
1474
 
1475
+ function getViewportCenter(canvas) {
1476
+ try {
1477
+ const bounds = canvas.getBounds();
1478
+ return {
1479
+ x: bounds.width / 2,
1480
+ y: bounds.height / 2,
1481
+ };
1482
+ }
1483
+ catch (error) {
1484
+ console.warn("Failed to calculate viewport center:", error);
1485
+ return { x: 0, y: 0 };
1486
+ }
1487
+ }
1488
+
1493
1489
  // Rulers
1494
1490
  const RULER_Z_INDEX = {
1495
1491
  GRID: 100,
@@ -2088,12 +2084,23 @@ class MarkupCanvas {
2088
2084
  });
2089
2085
  }
2090
2086
  resetView() {
2087
+ const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
2088
+ if (result) {
2089
+ this.emitTransformEvents();
2090
+ }
2091
+ return result;
2092
+ }
2093
+ resetViewToCenter() {
2091
2094
  return withTransition(this.transformLayer, this.config, () => {
2092
- const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
2093
- if (result) {
2094
- this.emitTransformEvents();
2095
- }
2096
- return result;
2095
+ return withClampedZoom(this.config, (clamp) => {
2096
+ const newScale = clamp(1.0);
2097
+ const center = getViewportCenter(this);
2098
+ const result = this.zoomToPoint(center.x, center.y, newScale);
2099
+ if (result) {
2100
+ this.emitTransformEvents();
2101
+ }
2102
+ return result;
2103
+ });
2097
2104
  });
2098
2105
  }
2099
2106
  zoomToFitContent() {
@@ -2135,30 +2142,28 @@ class MarkupCanvas {
2135
2142
  return this.updateTransform(newTransform);
2136
2143
  }
2137
2144
  // Zoom methods
2138
- zoomIn(factor = 0.1) {
2145
+ zoomIn(factor = 0.5) {
2139
2146
  return withTransition(this.transformLayer, this.config, () => {
2140
2147
  return withClampedZoom(this.config, (clamp) => {
2141
2148
  const newScale = clamp(this.baseCanvas.transform.scale * (1 + factor));
2142
- const newTransform = {
2143
- scale: newScale,
2144
- };
2145
- return this.updateTransform(newTransform);
2149
+ // Get the center of the viewport
2150
+ const center = getViewportCenter(this);
2151
+ return this.zoomToPoint(center.x, center.y, newScale);
2146
2152
  });
2147
2153
  });
2148
2154
  }
2149
- zoomOut(factor = 0.1) {
2155
+ zoomOut(factor = 0.5) {
2150
2156
  return withTransition(this.transformLayer, this.config, () => {
2151
2157
  return withClampedZoom(this.config, (clamp) => {
2152
2158
  const newScale = clamp(this.baseCanvas.transform.scale * (1 - factor));
2153
- const newTransform = {
2154
- scale: newScale,
2155
- };
2156
- return this.updateTransform(newTransform);
2159
+ // Get the center of the viewport
2160
+ const center = getViewportCenter(this);
2161
+ return this.zoomToPoint(center.x, center.y, newScale);
2157
2162
  });
2158
2163
  });
2159
2164
  }
2160
2165
  resetZoom() {
2161
- return this.resetView();
2166
+ return this.resetViewToCenter();
2162
2167
  }
2163
2168
  // Mouse drag control methods
2164
2169
  enableMouseDrag() {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 1.1.6
4
+ * @version 1.1.7
5
5
  */
6
6
  const EDITOR_PRESET = {
7
7
  // Canvas dimensions
@@ -16,8 +16,8 @@ const EDITOR_PRESET = {
16
16
  enableZoom: true,
17
17
  enablePan: true,
18
18
  enableTouch: true,
19
- enableKeyboard: false,
20
- bindKeyboardEventsTo: "canvas",
19
+ enableKeyboard: true,
20
+ bindKeyboardEventsTo: "document",
21
21
  // Zoom behavior
22
22
  zoomSpeed: 1.5,
23
23
  minZoom: 0.05,
@@ -272,13 +272,6 @@ function withRulerSize(canvas, rulerSize, operation) {
272
272
  const finalRulerSize = hasRulers ? rulerSize : 0;
273
273
  return operation(finalRulerSize);
274
274
  }
275
- function withRulerOffsets(canvas, rulerSize, x, y, operation) {
276
- return withRulerSize(canvas, rulerSize, (rulerSize) => {
277
- const adjustedX = x - rulerSize;
278
- const adjustedY = y - rulerSize;
279
- return operation(adjustedX, adjustedY);
280
- });
281
- }
282
275
  function withRulerOffsetObject(canvas, rulerSize, coords, operation) {
283
276
  return withRulerSize(canvas, rulerSize, (rulerSize) => {
284
277
  const adjusted = {
@@ -772,42 +765,28 @@ function getAdaptiveZoomSpeed(canvas, baseSpeed) {
772
765
  }
773
766
 
774
767
  function setupKeyboardEvents(canvas, config) {
775
- // Track mouse position
776
- let lastMouseX = 0;
777
- let lastMouseY = 0;
778
- function handleMouseMove(event) {
779
- const rect = canvas.container.getBoundingClientRect();
780
- const rawMouseX = event.clientX - rect.left;
781
- const rawMouseY = event.clientY - rect.top;
782
- withRulerOffsets(canvas, config.rulerSize, rawMouseX, rawMouseY, (adjustedX, adjustedY) => {
783
- lastMouseX = adjustedX;
784
- lastMouseY = adjustedY;
785
- });
786
- }
787
768
  function handleKeyDown(event) {
788
769
  if (!(event instanceof KeyboardEvent))
789
770
  return;
790
771
  if (config.bindKeyboardEventsTo === "canvas" && document.activeElement !== canvas.container)
791
772
  return;
792
- const isFastPan = event.shiftKey;
793
- const panDistance = config.keyboardPanStep * (isFastPan ? config.keyboardFastMultiplier : 1);
794
773
  let handled = false;
795
774
  const newTransform = {};
796
775
  switch (event.key) {
797
776
  case "ArrowLeft":
798
- newTransform.translateX = canvas.transform.translateX + panDistance;
777
+ newTransform.translateX = canvas.transform.translateX + config.keyboardPanStep;
799
778
  handled = true;
800
779
  break;
801
780
  case "ArrowRight":
802
- newTransform.translateX = canvas.transform.translateX - panDistance;
781
+ newTransform.translateX = canvas.transform.translateX - config.keyboardPanStep;
803
782
  handled = true;
804
783
  break;
805
784
  case "ArrowUp":
806
- newTransform.translateY = canvas.transform.translateY + panDistance;
785
+ newTransform.translateY = canvas.transform.translateY + config.keyboardPanStep;
807
786
  handled = true;
808
787
  break;
809
788
  case "ArrowDown":
810
- newTransform.translateY = canvas.transform.translateY - panDistance;
789
+ newTransform.translateY = canvas.transform.translateY - config.keyboardPanStep;
811
790
  handled = true;
812
791
  break;
813
792
  case "=":
@@ -816,7 +795,7 @@ function setupKeyboardEvents(canvas, config) {
816
795
  const adaptiveZoomStep = config.enableAdaptiveSpeed
817
796
  ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
818
797
  : config.keyboardZoomStep;
819
- newTransform.scale = clampZoom(canvas.transform.scale * (1 + adaptiveZoomStep), config);
798
+ canvas.zoomIn(adaptiveZoomStep);
820
799
  handled = true;
821
800
  }
822
801
  break;
@@ -825,16 +804,21 @@ function setupKeyboardEvents(canvas, config) {
825
804
  const adaptiveZoomStep = config.enableAdaptiveSpeed
826
805
  ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
827
806
  : config.keyboardZoomStep;
828
- newTransform.scale = clampZoom(canvas.transform.scale * (1 - adaptiveZoomStep), config);
807
+ canvas.zoomOut(adaptiveZoomStep);
829
808
  handled = true;
830
809
  }
831
810
  break;
832
811
  case "0":
833
- if (event.metaKey || event.ctrlKey) {
834
- const targetScale = 1.0;
835
- const zoomFactor = targetScale / canvas.transform.scale;
836
- const zoomTransform = getZoomToMouseTransform(lastMouseX, lastMouseY, canvas.transform, zoomFactor, config);
837
- Object.assign(newTransform, zoomTransform);
812
+ if (event.ctrlKey) {
813
+ if (canvas.resetView) {
814
+ canvas.resetView();
815
+ }
816
+ handled = true;
817
+ }
818
+ else if (event.metaKey || event.ctrlKey) {
819
+ if (canvas.resetViewToCenter) {
820
+ canvas.resetViewToCenter();
821
+ }
838
822
  handled = true;
839
823
  }
840
824
  break;
@@ -862,10 +846,8 @@ function setupKeyboardEvents(canvas, config) {
862
846
  }
863
847
  const keyboardTarget = config.bindKeyboardEventsTo === "canvas" ? canvas.container : document;
864
848
  keyboardTarget.addEventListener("keydown", handleKeyDown);
865
- canvas.container.addEventListener("mousemove", handleMouseMove);
866
849
  return () => {
867
850
  keyboardTarget.removeEventListener("keydown", handleKeyDown);
868
- canvas.container.removeEventListener("mousemove", handleMouseMove);
869
851
  };
870
852
  }
871
853
 
@@ -1486,6 +1468,20 @@ function setupWheelEvents(canvas, config) {
1486
1468
  };
1487
1469
  }
1488
1470
 
1471
+ function getViewportCenter(canvas) {
1472
+ try {
1473
+ const bounds = canvas.getBounds();
1474
+ return {
1475
+ x: bounds.width / 2,
1476
+ y: bounds.height / 2,
1477
+ };
1478
+ }
1479
+ catch (error) {
1480
+ console.warn("Failed to calculate viewport center:", error);
1481
+ return { x: 0, y: 0 };
1482
+ }
1483
+ }
1484
+
1489
1485
  // Rulers
1490
1486
  const RULER_Z_INDEX = {
1491
1487
  GRID: 100,
@@ -2084,12 +2080,23 @@ class MarkupCanvas {
2084
2080
  });
2085
2081
  }
2086
2082
  resetView() {
2083
+ const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
2084
+ if (result) {
2085
+ this.emitTransformEvents();
2086
+ }
2087
+ return result;
2088
+ }
2089
+ resetViewToCenter() {
2087
2090
  return withTransition(this.transformLayer, this.config, () => {
2088
- const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
2089
- if (result) {
2090
- this.emitTransformEvents();
2091
- }
2092
- return result;
2091
+ return withClampedZoom(this.config, (clamp) => {
2092
+ const newScale = clamp(1.0);
2093
+ const center = getViewportCenter(this);
2094
+ const result = this.zoomToPoint(center.x, center.y, newScale);
2095
+ if (result) {
2096
+ this.emitTransformEvents();
2097
+ }
2098
+ return result;
2099
+ });
2093
2100
  });
2094
2101
  }
2095
2102
  zoomToFitContent() {
@@ -2131,30 +2138,28 @@ class MarkupCanvas {
2131
2138
  return this.updateTransform(newTransform);
2132
2139
  }
2133
2140
  // Zoom methods
2134
- zoomIn(factor = 0.1) {
2141
+ zoomIn(factor = 0.5) {
2135
2142
  return withTransition(this.transformLayer, this.config, () => {
2136
2143
  return withClampedZoom(this.config, (clamp) => {
2137
2144
  const newScale = clamp(this.baseCanvas.transform.scale * (1 + factor));
2138
- const newTransform = {
2139
- scale: newScale,
2140
- };
2141
- return this.updateTransform(newTransform);
2145
+ // Get the center of the viewport
2146
+ const center = getViewportCenter(this);
2147
+ return this.zoomToPoint(center.x, center.y, newScale);
2142
2148
  });
2143
2149
  });
2144
2150
  }
2145
- zoomOut(factor = 0.1) {
2151
+ zoomOut(factor = 0.5) {
2146
2152
  return withTransition(this.transformLayer, this.config, () => {
2147
2153
  return withClampedZoom(this.config, (clamp) => {
2148
2154
  const newScale = clamp(this.baseCanvas.transform.scale * (1 - factor));
2149
- const newTransform = {
2150
- scale: newScale,
2151
- };
2152
- return this.updateTransform(newTransform);
2155
+ // Get the center of the viewport
2156
+ const center = getViewportCenter(this);
2157
+ return this.zoomToPoint(center.x, center.y, newScale);
2153
2158
  });
2154
2159
  });
2155
2160
  }
2156
2161
  resetZoom() {
2157
- return this.resetView();
2162
+ return this.resetViewToCenter();
2158
2163
  }
2159
2164
  // Mouse drag control methods
2160
2165
  enableMouseDrag() {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 1.1.6
4
+ * @version 1.1.7
5
5
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -214,13 +214,6 @@
214
214
  const finalRulerSize = hasRulers ? rulerSize : 0;
215
215
  return operation(finalRulerSize);
216
216
  }
217
- function withRulerOffsets(canvas, rulerSize, x, y, operation) {
218
- return withRulerSize(canvas, rulerSize, (rulerSize) => {
219
- const adjustedX = x - rulerSize;
220
- const adjustedY = y - rulerSize;
221
- return operation(adjustedX, adjustedY);
222
- });
223
- }
224
217
  function withRulerOffsetObject(canvas, rulerSize, coords, operation) {
225
218
  return withRulerSize(canvas, rulerSize, (rulerSize) => {
226
219
  const adjusted = {
@@ -714,42 +707,28 @@
714
707
  }
715
708
 
716
709
  function setupKeyboardEvents(canvas, config) {
717
- // Track mouse position
718
- let lastMouseX = 0;
719
- let lastMouseY = 0;
720
- function handleMouseMove(event) {
721
- const rect = canvas.container.getBoundingClientRect();
722
- const rawMouseX = event.clientX - rect.left;
723
- const rawMouseY = event.clientY - rect.top;
724
- withRulerOffsets(canvas, config.rulerSize, rawMouseX, rawMouseY, (adjustedX, adjustedY) => {
725
- lastMouseX = adjustedX;
726
- lastMouseY = adjustedY;
727
- });
728
- }
729
710
  function handleKeyDown(event) {
730
711
  if (!(event instanceof KeyboardEvent))
731
712
  return;
732
713
  if (config.bindKeyboardEventsTo === "canvas" && document.activeElement !== canvas.container)
733
714
  return;
734
- const isFastPan = event.shiftKey;
735
- const panDistance = config.keyboardPanStep * (isFastPan ? config.keyboardFastMultiplier : 1);
736
715
  let handled = false;
737
716
  const newTransform = {};
738
717
  switch (event.key) {
739
718
  case "ArrowLeft":
740
- newTransform.translateX = canvas.transform.translateX + panDistance;
719
+ newTransform.translateX = canvas.transform.translateX + config.keyboardPanStep;
741
720
  handled = true;
742
721
  break;
743
722
  case "ArrowRight":
744
- newTransform.translateX = canvas.transform.translateX - panDistance;
723
+ newTransform.translateX = canvas.transform.translateX - config.keyboardPanStep;
745
724
  handled = true;
746
725
  break;
747
726
  case "ArrowUp":
748
- newTransform.translateY = canvas.transform.translateY + panDistance;
727
+ newTransform.translateY = canvas.transform.translateY + config.keyboardPanStep;
749
728
  handled = true;
750
729
  break;
751
730
  case "ArrowDown":
752
- newTransform.translateY = canvas.transform.translateY - panDistance;
731
+ newTransform.translateY = canvas.transform.translateY - config.keyboardPanStep;
753
732
  handled = true;
754
733
  break;
755
734
  case "=":
@@ -758,7 +737,7 @@
758
737
  const adaptiveZoomStep = config.enableAdaptiveSpeed
759
738
  ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
760
739
  : config.keyboardZoomStep;
761
- newTransform.scale = clampZoom(canvas.transform.scale * (1 + adaptiveZoomStep), config);
740
+ canvas.zoomIn(adaptiveZoomStep);
762
741
  handled = true;
763
742
  }
764
743
  break;
@@ -767,16 +746,21 @@
767
746
  const adaptiveZoomStep = config.enableAdaptiveSpeed
768
747
  ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
769
748
  : config.keyboardZoomStep;
770
- newTransform.scale = clampZoom(canvas.transform.scale * (1 - adaptiveZoomStep), config);
749
+ canvas.zoomOut(adaptiveZoomStep);
771
750
  handled = true;
772
751
  }
773
752
  break;
774
753
  case "0":
775
- if (event.metaKey || event.ctrlKey) {
776
- const targetScale = 1.0;
777
- const zoomFactor = targetScale / canvas.transform.scale;
778
- const zoomTransform = getZoomToMouseTransform(lastMouseX, lastMouseY, canvas.transform, zoomFactor, config);
779
- Object.assign(newTransform, zoomTransform);
754
+ if (event.ctrlKey) {
755
+ if (canvas.resetView) {
756
+ canvas.resetView();
757
+ }
758
+ handled = true;
759
+ }
760
+ else if (event.metaKey || event.ctrlKey) {
761
+ if (canvas.resetViewToCenter) {
762
+ canvas.resetViewToCenter();
763
+ }
780
764
  handled = true;
781
765
  }
782
766
  break;
@@ -804,10 +788,8 @@
804
788
  }
805
789
  const keyboardTarget = config.bindKeyboardEventsTo === "canvas" ? canvas.container : document;
806
790
  keyboardTarget.addEventListener("keydown", handleKeyDown);
807
- canvas.container.addEventListener("mousemove", handleMouseMove);
808
791
  return () => {
809
792
  keyboardTarget.removeEventListener("keydown", handleKeyDown);
810
- canvas.container.removeEventListener("mousemove", handleMouseMove);
811
793
  };
812
794
  }
813
795
 
@@ -1428,6 +1410,20 @@
1428
1410
  };
1429
1411
  }
1430
1412
 
1413
+ function getViewportCenter(canvas) {
1414
+ try {
1415
+ const bounds = canvas.getBounds();
1416
+ return {
1417
+ x: bounds.width / 2,
1418
+ y: bounds.height / 2,
1419
+ };
1420
+ }
1421
+ catch (error) {
1422
+ console.warn("Failed to calculate viewport center:", error);
1423
+ return { x: 0, y: 0 };
1424
+ }
1425
+ }
1426
+
1431
1427
  // Rulers
1432
1428
  const RULER_Z_INDEX = {
1433
1429
  GRID: 100,
@@ -2026,12 +2022,23 @@
2026
2022
  });
2027
2023
  }
2028
2024
  resetView() {
2025
+ const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
2026
+ if (result) {
2027
+ this.emitTransformEvents();
2028
+ }
2029
+ return result;
2030
+ }
2031
+ resetViewToCenter() {
2029
2032
  return withTransition(this.transformLayer, this.config, () => {
2030
- const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
2031
- if (result) {
2032
- this.emitTransformEvents();
2033
- }
2034
- return result;
2033
+ return withClampedZoom(this.config, (clamp) => {
2034
+ const newScale = clamp(1.0);
2035
+ const center = getViewportCenter(this);
2036
+ const result = this.zoomToPoint(center.x, center.y, newScale);
2037
+ if (result) {
2038
+ this.emitTransformEvents();
2039
+ }
2040
+ return result;
2041
+ });
2035
2042
  });
2036
2043
  }
2037
2044
  zoomToFitContent() {
@@ -2073,30 +2080,28 @@
2073
2080
  return this.updateTransform(newTransform);
2074
2081
  }
2075
2082
  // Zoom methods
2076
- zoomIn(factor = 0.1) {
2083
+ zoomIn(factor = 0.5) {
2077
2084
  return withTransition(this.transformLayer, this.config, () => {
2078
2085
  return withClampedZoom(this.config, (clamp) => {
2079
2086
  const newScale = clamp(this.baseCanvas.transform.scale * (1 + factor));
2080
- const newTransform = {
2081
- scale: newScale,
2082
- };
2083
- return this.updateTransform(newTransform);
2087
+ // Get the center of the viewport
2088
+ const center = getViewportCenter(this);
2089
+ return this.zoomToPoint(center.x, center.y, newScale);
2084
2090
  });
2085
2091
  });
2086
2092
  }
2087
- zoomOut(factor = 0.1) {
2093
+ zoomOut(factor = 0.5) {
2088
2094
  return withTransition(this.transformLayer, this.config, () => {
2089
2095
  return withClampedZoom(this.config, (clamp) => {
2090
2096
  const newScale = clamp(this.baseCanvas.transform.scale * (1 - factor));
2091
- const newTransform = {
2092
- scale: newScale,
2093
- };
2094
- return this.updateTransform(newTransform);
2097
+ // Get the center of the viewport
2098
+ const center = getViewportCenter(this);
2099
+ return this.zoomToPoint(center.x, center.y, newScale);
2095
2100
  });
2096
2101
  });
2097
2102
  }
2098
2103
  resetZoom() {
2099
- return this.resetView();
2104
+ return this.resetViewToCenter();
2100
2105
  }
2101
2106
  // Mouse drag control methods
2102
2107
  enableMouseDrag() {
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).MarkupCanvas=t()}(this,function(){"use strict";const e="canvas-container",t="transform-layer",n="content-layer";function r(e,r){const o=Array.from(e.children);let a=e.querySelector(`.${t}`);a||(a=document.createElement("div"),a.className=t,e.appendChild(a)),function(e,t){e.style.position="absolute";const n=t.rulerSize;e.style.top=`${n}px`,e.style.left=`${n}px`,e.style.width=`${t.width}px`,e.style.height=`${t.height}px`,e.style.transformOrigin="0 0"}(a,r);let s=a.querySelector(`.${n}`);return s||(s=document.createElement("div"),s.className=n,a.appendChild(s),function(e,n,r){e.forEach(e=>{e===r||e.classList.contains(t)||n.appendChild(e)})}(o,s,a)),function(e){e.style.position="relative",e.style.width="100%",e.style.height="100%",e.style.pointerEvents="auto"}(s),{transformLayer:a,contentLayer:s}}function o(e,t,n){if(!n?.inverse)return{x:e,y:t};try{const r=n.inverse(),o=new DOMPoint(e,t).matrixTransform(r);return{x:o.x,y:o.y}}catch(n){return console.warn("Canvas to content conversion failed:",n),{x:e,y:t}}}function a(e,t){return Math.max(t.minZoom,Math.min(t.maxZoom,e))}function s(e,t,n){return new DOMMatrix([e,0,0,e,t,n])}function i(e,t,n,r,o){const s=o.enableRulers?-o.rulerSize:0,i=n||{scale:1,translateX:s,translateY:s},{scale:l,translateX:c,translateY:u}=i,d=a(l*r,o);if(Math.abs(d-l)<.001)return{scale:l,translateX:c,translateY:u};return{scale:d,translateX:e-(e-c)/l*d,translateY:t-(t-u)/l*d}}function l(e,t){return t(t=>a(t,e))}const c=new Map;function u(e,t,n){return e[t]?n():null}function d(e){let t=null,n=null;const r=(...r)=>{n=r,null===t&&(t=requestAnimationFrame(()=>{n&&e(...n),t=null,n=null}))};return r.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null,n=null)},r}function h(e,t,n){return n(null!==e.container.querySelector(".canvas-ruler")?t:0)}function m(e,t,n,r,o){const a=null!==e.container.querySelector(".canvas-ruler");return o(a?t-r:t,a?n-r:n)}function f(e,t){if("dark"===e.themeMode){return e[`${t}Dark`]}return e[t]}const g={width:8e3,height:8e3,enableAcceleration:!0,bindToWindow:!1,name:"markupCanvas",enablePostMessageAPI:!1,enableZoom:!0,enablePan:!0,enableTouch:!0,enableKeyboard:!0,bindKeyboardEventsTo:"canvas",zoomSpeed:1.5,minZoom:.05,maxZoom:80,enableTransition:!0,transitionDuration:.2,enableAdaptiveSpeed:!0,enableLeftDrag:!0,enableMiddleDrag:!0,requireSpaceForMouseDrag:!1,keyboardPanStep:50,keyboardFastMultiplier:20,keyboardZoomStep:.2,enableClickToZoom:!0,clickZoomLevel:1,requireOptionForClickZoom:!1,enableRulers:!0,enableGrid:!1,showRulers:!0,showGrid:!1,rulerFontSize:9,rulerFontFamily:"Monaco, Menlo, monospace",rulerUnits:"px",rulerSize:20,canvasBackgroundColor:"rgba(250, 250, 250, 1)",canvasBackgroundColorDark:"rgba(40, 40, 40, 1)",rulerBackgroundColor:"rgba(255, 255, 255, 0.95)",rulerBorderColor:"rgba(240, 240, 240, 1)",rulerTextColor:"rgba(102, 102, 102, 1)",rulerTickColor:"rgba(204, 204, 204, 1)",gridColor:"rgba(232, 86, 193, 0.5)",rulerBackgroundColorDark:"rgba(30, 30, 30, 0.95)",rulerBorderColorDark:"rgba(68, 68, 68, 1)",rulerTextColorDark:"rgba(170, 170, 170, 1)",rulerTickColorDark:"rgba(104, 104, 104, 1)",gridColorDark:"rgba(232, 86, 193, 0.5)",themeMode:"light",onTransformUpdate:()=>{}};function p(e){try{const t=e.container,n=e.config,r=e.transform||{scale:1,translateX:0,translateY:0},a=t.getBoundingClientRect(),i=a.width||t.clientWidth||0,l=a.height||t.clientHeight||0,c=h({container:t},n.rulerSize,e=>Math.max(0,i-e)),u=h({container:t},n.rulerSize,e=>Math.max(0,l-e)),d=n.width||g.width,m=n.height||g.height,f=function(e,t,n,r,a){const i=o(0,0,s(a.scale,a.translateX,a.translateY)),l=o(e,t,s(a.scale,a.translateX,a.translateY));return{x:Math.max(0,Math.min(n,i.x)),y:Math.max(0,Math.min(r,i.y)),width:Math.max(0,Math.min(n-i.x,l.x-i.x)),height:Math.max(0,Math.min(r-i.y,l.y-i.y))}}(c,u,d,m,r);return{width:c,height:u,contentWidth:d,contentHeight:m,scale:r.scale,translateX:r.translateX,translateY:r.translateY,visibleArea:f,scaledContentWidth:d*r.scale,scaledContentHeight:m*r.scale,canPanLeft:r.translateX<0,canPanRight:r.translateX+d*r.scale>c,canPanUp:r.translateY<0,canPanDown:r.translateY+m*r.scale>u,canZoomIn:r.scale<3.5,canZoomOut:r.scale>.1}}catch(e){return console.error("Failed to calculate canvas bounds:",e),{width:0,height:0,contentWidth:0,contentHeight:0,scale:1,translateX:0,translateY:0,visibleArea:{x:0,y:0,width:0,height:0},scaledContentWidth:0,scaledContentHeight:0,canPanLeft:!1,canPanRight:!1,canPanUp:!1,canPanDown:!1,canZoomIn:!1,canZoomOut:!1}}}function v(e,t){if(!e?.style||!t)return!1;try{return e.style.transform=function(e){return`matrix3d(${e.m11}, ${e.m12}, ${e.m13}, ${e.m14}, ${e.m21}, ${e.m22}, ${e.m23}, ${e.m24}, ${e.m31}, ${e.m32}, ${e.m33}, ${e.m34}, ${e.m41}, ${e.m42}, ${e.m43}, ${e.m44})`}(t),!0}catch(e){return console.warn("Transform application failed:",e),!1}}function y(e,t){try{if(t.enableTransition){window.__markupCanvasTransitionTimeout&&(clearTimeout(window.__markupCanvasTransitionTimeout),window.__markupCanvasTransitionTimeout=void 0);return function(e,t,n){const r=c.get(e);r&&clearTimeout(r);const o=window.setTimeout(()=>{n(),c.delete(e)},t);c.set(e,o)}("disableTransition",1e3*(t.transitionDuration??.2),()=>{e.style.transition="none",window.__markupCanvasTransitionTimeout=void 0}),!0}return!1}catch(e){return console.error("Failed to disable transitions:",e),!0}}function b(e,t,n){!function(e,t){try{return!!t.enableTransition&&(window.__markupCanvasTransitionTimeout&&(clearTimeout(window.__markupCanvasTransitionTimeout),window.__markupCanvasTransitionTimeout=void 0),e.style.transition=`transform ${t.transitionDuration}s linear`,!0)}catch(e){return console.error("Failed to enable transitions:",e),!1}}(e,t);try{return n()}finally{y(e,t)}}function w(t,n){if("static"===getComputedStyle(t).position&&(t.style.position="relative"),t.style.overflow="hidden",t.style.cursor="grab",t.style.overscrollBehavior="none",n){const e=f(n,"canvasBackgroundColor");t.style.backgroundColor=e}t.hasAttribute("tabindex")||t.setAttribute("tabindex","0"),function(e){const t=e.getBoundingClientRect(),n=getComputedStyle(e);0===t.height&&"auto"===n.height&&console.error("MarkupCanvas: Container height is 0. Please set a height on your container element using CSS.","Examples: height: 100vh, height: 500px, or use flexbox/grid layout.",e),0===t.width&&"auto"===n.width&&console.error("MarkupCanvas: Container width is 0. Please set a width on your container element using CSS.","Examples: width: 100vw, width: 800px, or use flexbox/grid layout.",e)}(t),t.classList.contains(e)||t.classList.add(e)}function C(e,t){if(!e?.appendChild)return console.error("Invalid container element provided to createCanvas"),null;try{w(e,t);const{transformLayer:n,contentLayer:a}=r(e,t);t.enableAcceleration&&function(e){try{return e.style.transform=e.style.transform||"translateZ(0)",e.style.backfaceVisibility="hidden",!0}catch(e){return console.error("Failed to enable hardware acceleration:",e),!1}}(n);const c=t.enableRulers?-t.rulerSize:0,d={scale:1,translateX:c,translateY:c};v(n,s(d.scale,d.translateX,d.translateY));return{container:e,transformLayer:n,contentLayer:a,config:t,transform:d,getBounds:function(){return p(this)},updateTransform:function(e){this.transform={...this.transform,...e};const t=s(this.transform.scale,this.transform.translateX,this.transform.translateY),n=v(this.transformLayer,t);return u(this.config,"onTransformUpdate",()=>{this.config.onTransformUpdate(this.transform)}),n},reset:function(){return this.updateTransform({scale:1,translateX:0,translateY:0})},handleResize:function(){return!0},setZoom:function(e){const t=l(this.config,t=>t(e));return this.updateTransform({scale:t})},canvasToContent:function(e,t){return o(e,t,s(this.transform.scale,this.transform.translateX,this.transform.translateY))},zoomToPoint:function(e,t,n){return b(this.transformLayer,this.config,()=>{const r=i(e,t,this.transform,n/this.transform.scale,this.config);return this.updateTransform(r)})},resetView:function(){return b(this.transformLayer,this.config,()=>h(this,this.config.rulerSize,e=>{const t={scale:1,translateX:-1*e,translateY:-1*e};return this.updateTransform(t)}))},zoomToFitContent:function(){return b(this.transformLayer,this.config,()=>{const e=this.getBounds(),t=e.width/this.config.width,n=e.height/this.config.height,r=l(this.config,e=>e(.9*Math.min(t,n))),o=this.config.width*r,a=this.config.height*r,s=(e.width-o)/2,i=(e.height-a)/2;return this.updateTransform({scale:r,translateX:s,translateY:i})})}}}catch(e){return console.error("Failed to create canvas:",e),null}}function k(e={}){const t={...g,...e};return("number"!=typeof t.width||t.width<=0)&&(console.warn("Invalid width, using default"),t.width=g.width),("number"!=typeof t.height||t.height<=0)&&(console.warn("Invalid height, using default"),t.height=g.height),("number"!=typeof t.zoomSpeed||t.zoomSpeed<=0)&&(console.warn("Invalid zoomSpeed, using default"),t.zoomSpeed=g.zoomSpeed),("number"!=typeof t.minZoom||t.minZoom<=0)&&(console.warn("Invalid minZoom, using default"),t.minZoom=g.minZoom),("number"!=typeof t.maxZoom||t.maxZoom<=t.minZoom)&&(console.warn("Invalid maxZoom, using default"),t.maxZoom=g.maxZoom),("number"!=typeof t.keyboardPanStep||t.keyboardPanStep<=0)&&(console.warn("Invalid keyboardPanStep, using default"),t.keyboardPanStep=g.keyboardPanStep),("number"!=typeof t.keyboardFastMultiplier||t.keyboardFastMultiplier<=0)&&(console.warn("Invalid keyboardFastMultiplier, using default"),t.keyboardFastMultiplier=g.keyboardFastMultiplier),("number"!=typeof t.clickZoomLevel||t.clickZoomLevel<=0)&&(console.warn("Invalid clickZoomLevel, using default"),t.clickZoomLevel=g.clickZoomLevel),("number"!=typeof t.rulerFontSize||t.rulerFontSize<=0)&&(console.warn("Invalid rulerFontSize, using default"),t.rulerFontSize=g.rulerFontSize),("number"!=typeof t.rulerSize||t.rulerSize<=0)&&(console.warn("Invalid rulerSize, using default"),t.rulerSize=g.rulerSize),t}class x{constructor(){this.listeners=new Map}setEmitInterceptor(e){this.emitInterceptor=e}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){const n=this.listeners.get(e);n&&n.delete(t)}emit(e,t){this.emitInterceptor?.(e,t);const n=this.listeners.get(e);n&&n.forEach(n=>{try{n(t)}catch(t){console.error(`Error in event handler for "${String(e)}":`,t)}})}removeAllListeners(){this.listeners.clear()}}const T=300,S=5;function D(e,t){if(!e?.getBounds)return t;try{const n=e.getBounds(),r=n.width*n.height;return t*(r/2073600)**1}catch(e){return console.warn("Failed to calculate adaptive zoom speed, using base speed:",e),t}}function M(e,t){let n=0,r=0;function o(o){const a=e.container.getBoundingClientRect(),s=o.clientX-a.left,i=o.clientY-a.top;!function(e,t,n,r,o){h(e,t,e=>o(n-e,r-e))}(e,t.rulerSize,s,i,(e,t)=>{n=e,r=t})}function s(o){if(!(o instanceof KeyboardEvent))return;if("canvas"===t.bindKeyboardEventsTo&&document.activeElement!==e.container)return;const s=o.shiftKey,l=t.keyboardPanStep*(s?t.keyboardFastMultiplier:1);let c=!1;const u={};switch(o.key){case"ArrowLeft":u.translateX=e.transform.translateX+l,c=!0;break;case"ArrowRight":u.translateX=e.transform.translateX-l,c=!0;break;case"ArrowUp":u.translateY=e.transform.translateY+l,c=!0;break;case"ArrowDown":u.translateY=e.transform.translateY-l,c=!0;break;case"=":case"+":{const n=t.enableAdaptiveSpeed?D(e,t.keyboardZoomStep):t.keyboardZoomStep;u.scale=a(e.transform.scale*(1+n),t),c=!0}break;case"-":{const n=t.enableAdaptiveSpeed?D(e,t.keyboardZoomStep):t.keyboardZoomStep;u.scale=a(e.transform.scale*(1-n),t),c=!0}break;case"0":if(o.metaKey||o.ctrlKey){const o=1/e.transform.scale,a=i(n,r,e.transform,o,t);Object.assign(u,a),c=!0}break;case"g":case"G":o.shiftKey&&e.toggleGrid&&e.toggleGrid(),c=o.shiftKey;break;case"r":case"R":!o.shiftKey||o.metaKey||o.ctrlKey||o.altKey||!e.toggleRulers||(e.toggleRulers(),c=!0)}c&&(o.preventDefault(),Object.keys(u).length>0&&e.updateTransform(u))}const l="canvas"===t.bindKeyboardEventsTo?e.container:document;return l.addEventListener("keydown",s),e.container.addEventListener("mousemove",o),()=>{l.removeEventListener("keydown",s),e.container.removeEventListener("mousemove",o)}}function z(e,t,n,r,o){n?t.requireSpaceForMouseDrag?e.container.style.cursor=r?"grab":"default":e.container.style.cursor=o?"grabbing":"grab":e.container.style.cursor="default"}function L(e,t,n,r,o){o.setIsDragging(!1),o.setDragButton(-1),z(e,t,n,r,!1)}function R(e,t,n,r,o,a,s,i,l,c){a&&e.button===s&&L(t,n,r,o,{setIsDragging:c.setIsDragging,setDragButton:c.setDragButton}),r&&0===e.button&&n.enableClickToZoom&&i>0&&function(e,t,n,r,o,a){const s=Date.now()-r,i=e.altKey,l=!n.requireOptionForClickZoom||i;if(s<T&&!o&&!a&&l){e.preventDefault();const r=t.container.getBoundingClientRect(),o=e.clientX-r.left,a=e.clientY-r.top,{clickX:s,clickY:i}=m(t,o,a,n.rulerSize,(e,t)=>({clickX:e,clickY:t})),l=t.canvasToContent(s,i),c=r.width/2,u=r.height/2,d=n.clickZoomLevel,h={scale:d,translateX:c-l.x*d,translateY:u-l.y*d};b(t.transformLayer,t.config,()=>{t.updateTransform(h)})}}(e,t,n,i,l,a),0===e.button&&function(e){e.setMouseDownTime(0),e.setHasDragged(!1)}({setMouseDownTime:c.setMouseDownTime,setHasDragged:c.setHasDragged})}function E(e,t,n=!0){let r=!0,o=!1,a=0,s=0,i=-1,l=!1,c=0,u=0,h=0,m=!1;const f={setIsDragging:e=>{o=e},setDragButton:e=>{i=e},setIsSpacePressed:e=>{l=e},setMouseDownTime:e=>{c=e},setMouseDownX:e=>{u=e},setMouseDownY:e=>{h=e},setHasDragged:e=>{m=e},setLastMouseX:e=>{a=e},setLastMouseY:e=>{s=e}},g=n=>{!function(e,t,n,r,o,a){n.requireSpaceForMouseDrag&&" "===e.key&&(a.setIsSpacePressed(!0),z(t,n,r,!0,o))}(n,e,t,r,o,{setIsSpacePressed:f.setIsSpacePressed})},p=n=>{!function(e,t,n,r,o,a){n.requireSpaceForMouseDrag&&" "===e.key&&(a.setIsSpacePressed(!1),z(t,n,r,!1,o),o&&L(t,n,r,!1,{setIsDragging:a.setIsDragging,setDragButton:a.setDragButton}))}(n,e,t,r,o,{setIsSpacePressed:f.setIsSpacePressed,setIsDragging:f.setIsDragging,setDragButton:f.setDragButton})},v=n=>{!function(e,t,n,r,o,a){const s=0===e.button,i=1===e.button;if(s&&(a.setMouseDownTime(Date.now()),a.setMouseDownX(e.clientX),a.setMouseDownY(e.clientY),a.setHasDragged(!1)),!r)return;(!n.requireSpaceForMouseDrag||o)&&(s&&n.enableLeftDrag||i&&n.enableMiddleDrag)&&(e.preventDefault(),a.setDragButton(e.button),a.setLastMouseX(e.clientX),a.setLastMouseY(e.clientY),z(t,n,r,o,!1))}(n,e,t,r,l,f)},y=n=>{!function(e,t,n,r,o,a,s,i,l,c,u,h){if(s>0){const t=Math.abs(e.clientX-i),s=Math.abs(e.clientY-l);if(t>S||s>S){h.setHasDragged(!0);const e=!n.requireSpaceForMouseDrag||a;!o&&r&&e&&h.setIsDragging(!0)}}if(!o||!r)return;e.preventDefault(),d((...e)=>{const n=e[0];if(!o||!r)return;const a=n.clientX-c,s=n.clientY-u,i={translateX:t.transform.translateX+a,translateY:t.transform.translateY+s};t.updateTransform(i),h.setLastMouseX(n.clientX),h.setLastMouseY(n.clientY)})(e)}(n,e,t,r,o,l,c,u,h,a,s,{setHasDragged:f.setHasDragged,setIsDragging:f.setIsDragging,setLastMouseX:f.setLastMouseX,setLastMouseY:f.setLastMouseY})},b=n=>{R(n,e,t,r,l,o,i,c,m,{setIsDragging:f.setIsDragging,setDragButton:f.setDragButton,setMouseDownTime:f.setMouseDownTime,setHasDragged:f.setHasDragged})},w=()=>{!function(e,t,n,r,o,a){o&&L(e,t,n,r,a)}(e,t,r,l,o,{setIsDragging:f.setIsDragging,setDragButton:f.setDragButton})};e.container.addEventListener("mousedown",v),document.addEventListener("mousemove",y),document.addEventListener("mouseup",b),e.container.addEventListener("mouseleave",w),t.requireSpaceForMouseDrag&&(document.addEventListener("keydown",g),document.addEventListener("keyup",p)),z(e,t,r,l,o);const C=()=>{e.container.removeEventListener("mousedown",v),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",b),e.container.removeEventListener("mouseleave",w),t.requireSpaceForMouseDrag&&(document.removeEventListener("keydown",g),document.removeEventListener("keyup",p))};return n?{cleanup:C,enable:()=>(r=!0,z(e,t,r,l,o),!0),disable:()=>(r=!1,o&&L(e,t,r,l,{setIsDragging:f.setIsDragging,setDragButton:f.setDragButton}),z(e,t,r,l,o),!0),isEnabled:()=>r}:C}function Y(e){const t=t=>{const n=t.data;if("markup-canvas"!==n.source)return;const r=e.config.name||"markupCanvas";if(n.canvasName!==r)return;const o=n.action,a=n.args||[];try{if("zoomIn"===o)e.zoomIn(a[0]);else if("zoomOut"===o)e.zoomOut(a[0]);else if("setZoom"===o){const t=a[0];if("number"!=typeof t||t<=0)throw new Error(`Invalid zoom level: ${t}. Must be a positive number.`);e.setZoom(t)}else if("resetZoom"===o)e.resetZoom();else if("panLeft"===o)e.panLeft(a[0]);else if("panRight"===o)e.panRight(a[0]);else if("panUp"===o)e.panUp(a[0]);else if("panDown"===o)e.panDown(a[0]);else if("fitToScreen"===o)e.fitToScreen();else if("centerContent"===o)e.centerContent();else if("scrollToPoint"===o)e.scrollToPoint(a[0],a[1]);else if("resetView"===o)e.resetView();else if("toggleRulers"===o)e.toggleRulers();else if("showRulers"===o)e.showRulers();else if("hideRulers"===o)e.hideRulers();else if("toggleGrid"===o)e.toggleGrid();else if("showGrid"===o)e.showGrid();else if("hideGrid"===o)e.hideGrid();else if("updateThemeMode"===o){const t=a[0];if("light"!==t&&"dark"!==t)throw new Error(`Invalid theme mode: ${t}`);e.updateThemeMode(t)}else{if("toggleThemeMode"!==o)throw new Error(`Unknown action: ${o}`);{const t="light"===e.getConfig().themeMode?"dark":"light";e.updateThemeMode(t)}}}catch(e){!function(e,t,n){window.postMessage({source:"markup-canvas-error",canvasName:e,action:t,error:n,timestamp:Date.now()},"*")}(r,o,e instanceof Error?e.message:String(e))}};return"undefined"!=typeof window&&window.addEventListener("message",t),()=>{"undefined"!=typeof window&&window.removeEventListener("message",t)}}function X(e,t){return{x:(e.clientX+t.clientX)/2,y:(e.clientY+t.clientY)/2}}function B(e,t){const n=e.clientX-t.clientX,r=e.clientY-t.clientY;return Math.sqrt(n*n+r*r)}function F(e,t,n,r){const o=i(n,r,e.transform,t,e.config);return e.updateTransform(o)}function $(e,t,n){e.preventDefault();const r=Array.from(e.touches);d((...e)=>{const r=e[0];if(1===r.length){if(1===n.touches.length){const e=r[0].clientX-n.touches[0].clientX,o=r[0].clientY-n.touches[0].clientY,a={translateX:t.transform.translateX+e,translateY:t.transform.translateY+o};t.updateTransform(a)}}else if(2===r.length){const e=B(r[0],r[1]),o=X(r[0],r[1]);if(n.lastDistance>0){const r=e/n.lastDistance,a=t.container.getBoundingClientRect();let s=o.x-a.left,i=o.y-a.top;const l=function(e,t,n,r){return h(e,t,e=>{const t={...n,x:n.x-e,y:n.y-e};return r(t)})}(t,t.config.rulerSize,{x:s,y:i},e=>e);s=l.x,i=l.y,F(t,r,s,i)}n.lastDistance=e,n.lastCenter=o}n.touches=r})(r)}function P(e){const t={touches:[],lastDistance:0,lastCenter:{}},n=e=>{!function(e,t){e.preventDefault(),t.touches=Array.from(e.touches),2===t.touches.length&&(t.lastDistance=B(t.touches[0],t.touches[1]),t.lastCenter=X(t.touches[0],t.touches[1]))}(e,t)},r=n=>{$(n,e,t)},o=e=>{!function(e,t){t.touches=Array.from(e.touches),t.touches.length<2&&(t.lastDistance=0)}(e,t)};return e.container.addEventListener("touchstart",n,{passive:!1}),e.container.addEventListener("touchmove",r,{passive:!1}),e.container.addEventListener("touchend",o,{passive:!1}),()=>{e.container.removeEventListener("touchstart",n),e.container.removeEventListener("touchmove",r),e.container.removeEventListener("touchend",o)}}function I(e){const t=e.ctrlKey||e.metaKey,n=[0===e.deltaMode,Math.abs(e.deltaY)<50,e.deltaY%1!=0,Math.abs(e.deltaX)>0&&Math.abs(e.deltaY)>0].filter(Boolean).length>=2;return{isTrackpad:n,isMouseWheel:!n,isTrackpadScroll:n&&!t,isTrackpadPinch:n&&t,isZoomGesture:t}}function Z(e,t){const n=(e=>d((...t)=>{const n=t[0];if(!n||!e?.updateTransform)return!1;try{const t=e.transform,r=1,o=n.deltaX*r,a=n.deltaY*r,s={scale:t.scale,translateX:t.translateX-o,translateY:t.translateY-a};return y(e.transformLayer,e.config),e.updateTransform(s)}catch(e){return console.error("Error handling trackpad pan:",e),!1}}))(e),r=r=>I(r).isTrackpadScroll?n(r):function(e,t,n){if(!e||"number"!=typeof e.deltaY)return console.warn("Invalid wheel event provided"),!1;if(!t?.updateTransform)return console.warn("Invalid canvas provided to handleWheelEvent"),!1;try{e.preventDefault();const r=t.container.getBoundingClientRect(),o=e.clientX-r.left,a=e.clientY-r.top,{mouseX:s,mouseY:i}=m(t,o,a,n.rulerSize,(e,t)=>({mouseX:e,mouseY:t})),l=n.zoomSpeed,c=I(e);if(!c.isZoomGesture)return!1;let u=n.enableAdaptiveSpeed?D(t,l):l;if(c.isTrackpadPinch){const e=.05*n.zoomSpeed;u=n.enableAdaptiveSpeed?D(t,e):e}return F(t,(e.deltaY<0?1:-1)>0?1+u:1/(1+u),s,i)}catch(e){return console.error("Error handling wheel event:",e),!1}}(r,e,t);return e.container.addEventListener("wheel",r,{passive:!1}),()=>{e.container.removeEventListener("wheel",r)}}const O=100,A=1e3,_=1001,G=4,H=4,N=100,K=100,q=20,V=200;function U(e,t){const n=function(e){const t=document.createElement("div");return t.className="canvas-ruler horizontal-ruler",t.style.cssText=`\n\tposition: absolute;\n\ttop: 0;\n\tleft: ${e.rulerSize}px;\n\tright: 0;\n\theight: ${e.rulerSize}px;\n\tbackground: var(--ruler-background-color);\n\tborder-bottom: 1px solid var(--ruler-border-color);\n\tborder-right: 1px solid var(--ruler-border-color);\n\tz-index: ${A};\n\tpointer-events: none;\n\tfont-family: ${e.rulerFontFamily};\n\tfont-size: ${e.rulerFontSize}px;\n\tcolor: var(--ruler-text-color);\n\toverflow: hidden;\n `,t}(t),r=function(e){const t=document.createElement("div");return t.className="canvas-ruler vertical-ruler",t.style.cssText=`\n\tposition: absolute;\n\ttop: ${e.rulerSize}px;\n\tleft: 0;\n\tbottom: 0;\n\twidth: ${e.rulerSize}px;\n\tbackground: var(--ruler-background-color);\n\tborder-right: 1px solid var(--ruler-border-color);\n\tborder-bottom: 1px solid var(--ruler-border-color);\n\tz-index: ${A};\n\tpointer-events: none;\n\tfont-family: ${e.rulerFontFamily};\n\tfont-size: ${e.rulerFontSize}px;\n\tcolor: var(--ruler-text-color);\n\toverflow: hidden;\n `,t}(t),o=function(e){const t=document.createElement("div");return t.className="canvas-ruler corner-box",t.style.cssText=`\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\twidth: ${e.rulerSize}px;\n\t\theight: ${e.rulerSize}px;\n\t\tbackground: var(--ruler-background-color);\n\t\tborder-right: 1px solid var(--ruler-border-color);\n\t\tborder-bottom: 1px solid var(--ruler-border-color);\n\t\tz-index: ${_};\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-family: ${e.rulerFontFamily};\n\t\tfont-size: ${e.rulerFontSize-2}px;\n\t\tcolor: var(--ruler-text-color);\n\t\tpointer-events: none;\n\t`,t.textContent=e.rulerUnits,t}(t),a=t.enableGrid?function(e){const t=document.createElement("div");return t.className="canvas-ruler grid-overlay",t.style.cssText=`\n\t\tposition: absolute;\n\t\ttop: ${e.rulerSize}px;\n\t\tleft: ${e.rulerSize}px;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: ${O};\n\t\tbackground-image: \n\t\t\tlinear-gradient(var(--grid-color) 1px, transparent 1px),\n\t\t\tlinear-gradient(90deg, var(--grid-color) 1px, transparent 1px);\n\t\tbackground-size: 100px 100px;\n\t\topacity: 0.5;\n\t`,t}(t):void 0;return e.appendChild(n),e.appendChild(r),e.appendChild(o),a&&e.appendChild(a),{horizontalRuler:n,verticalRuler:r,cornerBox:o,gridOverlay:a}}function W(e,t){const n=e/Math.max(5,Math.min(20,t/50)),r=10**Math.floor(Math.log10(n)),o=n/r;let a;return a=o<=1?1:o<=2?2:o<=5?5:10,a*r}function j(e,t,n,r,o){const a=document.createElement("div");a.className="tick",a.style.cssText=`\n\t\tposition: absolute;\n\t\tleft: ${n}px;\n\t\tbottom: 0;\n\t\twidth: 1px;\n\t\theight: ${G}px;\n\t\tbackground: var(--ruler-tick-color);\n\t`,e.appendChild(a);if(t%N===0){const r=document.createElement("div");r.style.cssText=`\n\t\t\tposition: absolute;\n\t\t\tleft: ${n}px;\n\t\t\tbottom: ${G+2}px;\n\t\t\tfont-size: ${o.rulerFontSize}px;\n\t\t\tline-height: 1;\n\t\t\tcolor: var(--ruler-text-color);\n\t\t\twhite-space: nowrap;\n\t\t\tpointer-events: none;\n\t\t`,r.textContent=Math.round(t).toString(),e.appendChild(r)}}function J(e,t,n,r,o){const a=document.createElement("div");a.className="tick",a.style.cssText=`\n\t\tposition: absolute;\n\t\ttop: ${n}px;\n\t\tright: 0;\n\t\twidth: ${H}px;\n\t\theight: 1px;\n\t\tbackground: var(--ruler-tick-color);\n\t`,e.appendChild(a);if(t%N===0){const r=document.createElement("div");r.style.cssText=`\n\t\t\tposition: absolute;\n\t\t\ttop: ${n-6}px;\n\t\t\tright: ${H+6}px;\n\t\t\tfont-size: ${o.rulerFontSize}px;\n\t\t\tline-height: 1;\n\t\t\tcolor: var(--ruler-text-color);\n\t\t\twhite-space: nowrap;\n\t\t\tpointer-events: none;\n\t\t\ttransform: rotate(-90deg);\n\t\t\ttransform-origin: right center;\n\t\t`,r.textContent=Math.round(t).toString(),e.appendChild(r)}}function Q(e,t,n,r,o){const a=e.getBounds(),s=a.scale||1,i=a.translateX||0,l=a.translateY||0,c=a.width-o.rulerSize,u=a.height-o.rulerSize,d=-i/s,h=-l/s,m=h+u/s;!function(e,t,n,r,o,a){const s=r,i=W(n-t,s),l=document.createDocumentFragment(),c=Math.floor(t/i)*i,u=Math.ceil(n/i)*i;for(let e=c;e<=u;e+=i){const n=(e-t)*o;n>=-50&&n<=s+50&&j(l,e,n,0,a)}e.innerHTML="",e.appendChild(l)}(t,d,d+c/s,c,s,o),function(e,t,n,r,o,a){const s=r,i=W(n-t,s),l=document.createDocumentFragment(),c=Math.floor(t/i)*i,u=Math.ceil(n/i)*i;for(let e=c;e<=u;e+=i){const n=(e-t)*o;n>=-50&&n<=s+50&&J(l,e,n,0,a)}e.innerHTML="",e.appendChild(l)}(n,h,m,u,s,o),r&&function(e,t,n,r){let o=K*t;for(;o<q;)o*=2;for(;o>V;)o/=2;e.style.backgroundSize=`${o}px ${o}px`,e.style.backgroundPosition=`${n%o}px ${r%o}px`}(r,s,i,l)}function ee(e,t){const n=f(t,"rulerBackgroundColor"),r=f(t,"rulerBorderColor"),o=f(t,"rulerTextColor"),a=f(t,"rulerTickColor"),s=f(t,"gridColor");e.horizontalRuler&&(e.horizontalRuler.style.setProperty("--ruler-background-color",n),e.horizontalRuler.style.setProperty("--ruler-border-color",r),e.horizontalRuler.style.setProperty("--ruler-text-color",o),e.horizontalRuler.style.setProperty("--ruler-tick-color",a)),e.verticalRuler&&(e.verticalRuler.style.setProperty("--ruler-background-color",n),e.verticalRuler.style.setProperty("--ruler-border-color",r),e.verticalRuler.style.setProperty("--ruler-text-color",o),e.verticalRuler.style.setProperty("--ruler-tick-color",a)),e.cornerBox&&(e.cornerBox.style.setProperty("--ruler-background-color",n),e.cornerBox.style.setProperty("--ruler-border-color",r),e.cornerBox.style.setProperty("--ruler-text-color",o)),e.gridOverlay&&e.gridOverlay.style.setProperty("--grid-color",s)}function te(e,t){if(!e?.container)return console.error("Invalid canvas provided to createRulers"),null;let n,r=null,o=!1;const a=()=>{!o&&n.horizontalRuler&&n.verticalRuler&&Q(e,n.horizontalRuler,n.verticalRuler,n.gridOverlay,t)};try{return n=U(e.container,t),r=function(e,t){const n=d(t),r=e.updateTransform;e.updateTransform=function(e){const t=r.call(this,e);return n(),t};const o=d(t);return window.addEventListener("resize",o),()=>{window.removeEventListener("resize",o),e.updateTransform=r,n.cleanup(),o.cleanup()}}(e,a),ee(n,t),a(),t.showRulers||(n.horizontalRuler.style.display="none",n.verticalRuler.style.display="none",n.cornerBox.style.display="none"),!t.showGrid&&n.gridOverlay&&(n.gridOverlay.style.display="none"),{horizontalRuler:n.horizontalRuler,verticalRuler:n.verticalRuler,cornerBox:n.cornerBox,gridOverlay:n.gridOverlay,update:a,updateTheme:e=>{o||ee(n,e)},show:()=>{n.horizontalRuler&&(n.horizontalRuler.style.display="block"),n.verticalRuler&&(n.verticalRuler.style.display="block"),n.cornerBox&&(n.cornerBox.style.display="flex"),n.gridOverlay&&(n.gridOverlay.style.display="block")},hide:()=>{n.horizontalRuler&&(n.horizontalRuler.style.display="none"),n.verticalRuler&&(n.verticalRuler.style.display="none"),n.cornerBox&&(n.cornerBox.style.display="none"),n.gridOverlay&&(n.gridOverlay.style.display="none")},toggleGrid:()=>{if(n.gridOverlay){const e="none"!==n.gridOverlay.style.display;n.gridOverlay.style.display=e?"none":"block"}},destroy:()=>{o=!0,r&&r(),n.horizontalRuler?.parentNode&&n.horizontalRuler.parentNode.removeChild(n.horizontalRuler),n.verticalRuler?.parentNode&&n.verticalRuler.parentNode.removeChild(n.verticalRuler),n.cornerBox?.parentNode&&n.cornerBox.parentNode.removeChild(n.cornerBox),n.gridOverlay?.parentNode&&n.gridOverlay.parentNode.removeChild(n.gridOverlay)}}}catch(e){return console.error("Failed to create rulers:",e),null}}return class{constructor(e,t={}){if(this.cleanupFunctions=[],this.rulers=null,this.dragSetup=null,this._isReady=!1,this.listen=new x,this.postMessageCleanup=null,!e)throw new Error("Container element is required");this.config=k(t);const n=C(e,this.config);if(!n)throw new Error("Failed to create canvas");this.baseCanvas=n,this.config.bindToWindow&&(this.listen.setEmitInterceptor((e,t)=>{this.broadcastEvent(e,t)}),this.setupGlobalBinding(),this.config.enablePostMessageAPI&&(this.postMessageCleanup=Y(this))),this.setupEventHandlers(),this._isReady=!0,this.listen.emit("ready",this)}setupGlobalBinding(){if("undefined"==typeof window)return;const e=this.config.name||"markupCanvas",t=window;t[e]=this,t.__markupCanvasInstances||(t.__markupCanvasInstances=new Map),t.__markupCanvasInstances.set(e,this)}cleanupGlobalBinding(){if("undefined"==typeof window)return;const e=this.config.name||"markupCanvas",t=window;delete t[e],t.__markupCanvasInstances&&t.__markupCanvasInstances.delete(e)}broadcastEvent(e,t){if("undefined"==typeof window)return;let n=t;"ready"===e&&(n={ready:!0}),window.postMessage({source:"markup-canvas",event:e,data:n,timestamp:Date.now(),canvasName:this.config.name},"*"),window.parent&&window.parent.postMessage({source:"markup-canvas",event:e,data:n,timestamp:Date.now(),canvasName:this.config.name},"*")}setupEventHandlers(){try{u(this.config,"enableZoom",()=>{const e=Z(this,this.config);this.cleanupFunctions.push(e)}),(this.config.enablePan||this.config.enableClickToZoom)&&(this.dragSetup=E(this,this.config,!0),this.cleanupFunctions.push(this.dragSetup.cleanup)),u(this.config,"enableKeyboard",()=>{const e=M(this,this.config);this.cleanupFunctions.push(e)}),u(this.config,"enableTouch",()=>{const e=P(this);this.cleanupFunctions.push(e)}),u(this.config,"enableRulers",()=>{this.rulers=te(this.baseCanvas,this.config),this.cleanupFunctions.push(()=>{this.rulers&&this.rulers.destroy()})})}catch(e){throw console.error("Failed to set up event handlers:",e),this.cleanup(),e}}get container(){return this.baseCanvas.container}get transformLayer(){return this.baseCanvas.transformLayer}get contentLayer(){return this.baseCanvas.contentLayer}get transform(){return this.baseCanvas.transform}get isReady(){return this._isReady}get isTransforming(){return this.dragSetup?.isEnabled()||!1}get visibleBounds(){return this.getVisibleArea()}getBounds(){return this.baseCanvas.getBounds()}updateTransform(e){const t=this.baseCanvas.updateTransform(e);return t&&this.emitTransformEvents(),t}emitTransformEvents(){const e=this.baseCanvas.transform;this.listen.emit("transform",e),this.listen.emit("zoom",e.scale),this.listen.emit("pan",{x:e.translateX,y:e.translateY})}reset(){return this.baseCanvas.reset()}handleResize(){return this.baseCanvas.handleResize()}setZoom(e){return b(this.transformLayer,this.config,()=>l(this.config,t=>{const n={scale:t(e)};return this.updateTransform(n)}))}canvasToContent(e,t){return this.baseCanvas.canvasToContent(e,t)}zoomToPoint(e,t,n){return b(this.transformLayer,this.config,()=>{const r=this.baseCanvas.zoomToPoint(e,t,n);return r&&this.emitTransformEvents(),r})}resetView(){return b(this.transformLayer,this.config,()=>{const e=!!this.baseCanvas.resetView&&this.baseCanvas.resetView();return e&&this.emitTransformEvents(),e})}zoomToFitContent(){return b(this.transformLayer,this.config,()=>{const e=this.baseCanvas.zoomToFitContent();return e&&this.emitTransformEvents(),e})}panLeft(e){const t=e??this.config.keyboardPanStep,n={translateX:this.baseCanvas.transform.translateX+t};return this.updateTransform(n)}panRight(e){const t=e??this.config.keyboardPanStep,n={translateX:this.baseCanvas.transform.translateX-t};return this.updateTransform(n)}panUp(e){const t=e??this.config.keyboardPanStep,n={translateY:this.baseCanvas.transform.translateY+t};return this.updateTransform(n)}panDown(e){const t=e??this.config.keyboardPanStep,n={translateY:this.baseCanvas.transform.translateY-t};return this.updateTransform(n)}zoomIn(e=.1){return b(this.transformLayer,this.config,()=>l(this.config,t=>{const n={scale:t(this.baseCanvas.transform.scale*(1+e))};return this.updateTransform(n)}))}zoomOut(e=.1){return b(this.transformLayer,this.config,()=>l(this.config,t=>{const n={scale:t(this.baseCanvas.transform.scale*(1-e))};return this.updateTransform(n)}))}resetZoom(){return this.resetView()}enableMouseDrag(){return this.dragSetup?.enable()??!1}disableMouseDrag(){return this.dragSetup?.disable()??!1}isMouseDragEnabled(){return this.dragSetup?.isEnabled()??!1}toggleGrid(){return!!this.rulers?.toggleGrid&&(this.rulers.toggleGrid(),!0)}showGrid(){return!!this.rulers?.gridOverlay&&(this.rulers.gridOverlay.style.display="block",!0)}hideGrid(){return!!this.rulers?.gridOverlay&&(this.rulers.gridOverlay.style.display="none",!0)}isGridVisible(){return!!this.rulers?.gridOverlay&&"none"!==this.rulers.gridOverlay.style.display}toggleRulers(){if(this.rulers){return this.areRulersVisible()?this.rulers.hide():this.rulers.show(),!0}return!1}showRulers(){return!!this.rulers&&(this.rulers.show(),!0)}hideRulers(){return!!this.rulers&&(this.rulers.hide(),!0)}areRulersVisible(){return!!this.rulers?.horizontalRuler&&"none"!==this.rulers.horizontalRuler.style.display}centerContent(){return b(this.transformLayer,this.config,()=>{const e=this.baseCanvas.getBounds(),t=(e.width-e.contentWidth*this.baseCanvas.transform.scale)/2,n=(e.height-e.contentHeight*this.baseCanvas.transform.scale)/2;return this.updateTransform({translateX:t,translateY:n})})}fitToScreen(){return b(this.transformLayer,this.config,()=>{const e=this.baseCanvas.zoomToFitContent();return e&&this.emitTransformEvents(),e})}getVisibleArea(){return this.baseCanvas.getBounds().visibleArea}isPointVisible(e,t){const n=this.getVisibleArea();return e>=n.x&&e<=n.x+n.width&&t>=n.y&&t<=n.y+n.height}scrollToPoint(e,t){return b(this.transformLayer,this.config,()=>{const n=this.baseCanvas.getBounds(),r=n.width/2,o=n.height/2,a=r-e*this.baseCanvas.transform.scale,s=o-t*this.baseCanvas.transform.scale;return this.updateTransform({translateX:a,translateY:s})})}getConfig(){return{...this.config}}updateConfig(e){this.config=k({...this.config,...e})}updateThemeMode(e){const t={...this.config,themeMode:e};this.config=k(t);const n=f(this.config,"canvasBackgroundColor");this.baseCanvas.container.style.backgroundColor=n,this.rulers&&this.rulers.updateTheme(this.config)}cleanup(){this.cleanupGlobalBinding(),this.postMessageCleanup&&(this.postMessageCleanup(),this.postMessageCleanup=null),this.cleanupFunctions.forEach(e=>{try{e()}catch(e){console.warn("Error during cleanup:",e)}}),this.cleanupFunctions=[],this.removeAllListeners()}on(e,t){this.listen.on(e,t)}off(e,t){this.listen.off(e,t)}emit(e,t){this.listen.emit(e,t)}removeAllListeners(){this.listen.removeAllListeners()}destroy(){this.cleanup(),window.__markupCanvasTransitionTimeout&&clearTimeout(window.__markupCanvasTransitionTimeout)}}});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).MarkupCanvas=t()}(this,function(){"use strict";const e="canvas-container",t="transform-layer",n="content-layer";function r(e,r){const o=Array.from(e.children);let a=e.querySelector(`.${t}`);a||(a=document.createElement("div"),a.className=t,e.appendChild(a)),function(e,t){e.style.position="absolute";const n=t.rulerSize;e.style.top=`${n}px`,e.style.left=`${n}px`,e.style.width=`${t.width}px`,e.style.height=`${t.height}px`,e.style.transformOrigin="0 0"}(a,r);let s=a.querySelector(`.${n}`);return s||(s=document.createElement("div"),s.className=n,a.appendChild(s),function(e,n,r){e.forEach(e=>{e===r||e.classList.contains(t)||n.appendChild(e)})}(o,s,a)),function(e){e.style.position="relative",e.style.width="100%",e.style.height="100%",e.style.pointerEvents="auto"}(s),{transformLayer:a,contentLayer:s}}function o(e,t,n){if(!n?.inverse)return{x:e,y:t};try{const r=n.inverse(),o=new DOMPoint(e,t).matrixTransform(r);return{x:o.x,y:o.y}}catch(n){return console.warn("Canvas to content conversion failed:",n),{x:e,y:t}}}function a(e,t){return Math.max(t.minZoom,Math.min(t.maxZoom,e))}function s(e,t,n){return new DOMMatrix([e,0,0,e,t,n])}function i(e,t,n,r,o){const s=o.enableRulers?-o.rulerSize:0,i=n||{scale:1,translateX:s,translateY:s},{scale:l,translateX:c,translateY:u}=i,d=a(l*r,o);if(Math.abs(d-l)<.001)return{scale:l,translateX:c,translateY:u};return{scale:d,translateX:e-(e-c)/l*d,translateY:t-(t-u)/l*d}}function l(e,t){return t(t=>a(t,e))}const c=new Map;function u(e,t,n){return e[t]?n():null}function d(e){let t=null,n=null;const r=(...r)=>{n=r,null===t&&(t=requestAnimationFrame(()=>{n&&e(...n),t=null,n=null}))};return r.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null,n=null)},r}function h(e,t,n){return n(null!==e.container.querySelector(".canvas-ruler")?t:0)}function m(e,t,n,r,o){const a=null!==e.container.querySelector(".canvas-ruler");return o(a?t-r:t,a?n-r:n)}function g(e,t){if("dark"===e.themeMode){return e[`${t}Dark`]}return e[t]}const f={width:8e3,height:8e3,enableAcceleration:!0,bindToWindow:!1,name:"markupCanvas",enablePostMessageAPI:!1,enableZoom:!0,enablePan:!0,enableTouch:!0,enableKeyboard:!0,bindKeyboardEventsTo:"canvas",zoomSpeed:1.5,minZoom:.05,maxZoom:80,enableTransition:!0,transitionDuration:.2,enableAdaptiveSpeed:!0,enableLeftDrag:!0,enableMiddleDrag:!0,requireSpaceForMouseDrag:!1,keyboardPanStep:50,keyboardFastMultiplier:20,keyboardZoomStep:.2,enableClickToZoom:!0,clickZoomLevel:1,requireOptionForClickZoom:!1,enableRulers:!0,enableGrid:!1,showRulers:!0,showGrid:!1,rulerFontSize:9,rulerFontFamily:"Monaco, Menlo, monospace",rulerUnits:"px",rulerSize:20,canvasBackgroundColor:"rgba(250, 250, 250, 1)",canvasBackgroundColorDark:"rgba(40, 40, 40, 1)",rulerBackgroundColor:"rgba(255, 255, 255, 0.95)",rulerBorderColor:"rgba(240, 240, 240, 1)",rulerTextColor:"rgba(102, 102, 102, 1)",rulerTickColor:"rgba(204, 204, 204, 1)",gridColor:"rgba(232, 86, 193, 0.5)",rulerBackgroundColorDark:"rgba(30, 30, 30, 0.95)",rulerBorderColorDark:"rgba(68, 68, 68, 1)",rulerTextColorDark:"rgba(170, 170, 170, 1)",rulerTickColorDark:"rgba(104, 104, 104, 1)",gridColorDark:"rgba(232, 86, 193, 0.5)",themeMode:"light",onTransformUpdate:()=>{}};function p(e){try{const t=e.container,n=e.config,r=e.transform||{scale:1,translateX:0,translateY:0},a=t.getBoundingClientRect(),i=a.width||t.clientWidth||0,l=a.height||t.clientHeight||0,c=h({container:t},n.rulerSize,e=>Math.max(0,i-e)),u=h({container:t},n.rulerSize,e=>Math.max(0,l-e)),d=n.width||f.width,m=n.height||f.height,g=function(e,t,n,r,a){const i=o(0,0,s(a.scale,a.translateX,a.translateY)),l=o(e,t,s(a.scale,a.translateX,a.translateY));return{x:Math.max(0,Math.min(n,i.x)),y:Math.max(0,Math.min(r,i.y)),width:Math.max(0,Math.min(n-i.x,l.x-i.x)),height:Math.max(0,Math.min(r-i.y,l.y-i.y))}}(c,u,d,m,r);return{width:c,height:u,contentWidth:d,contentHeight:m,scale:r.scale,translateX:r.translateX,translateY:r.translateY,visibleArea:g,scaledContentWidth:d*r.scale,scaledContentHeight:m*r.scale,canPanLeft:r.translateX<0,canPanRight:r.translateX+d*r.scale>c,canPanUp:r.translateY<0,canPanDown:r.translateY+m*r.scale>u,canZoomIn:r.scale<3.5,canZoomOut:r.scale>.1}}catch(e){return console.error("Failed to calculate canvas bounds:",e),{width:0,height:0,contentWidth:0,contentHeight:0,scale:1,translateX:0,translateY:0,visibleArea:{x:0,y:0,width:0,height:0},scaledContentWidth:0,scaledContentHeight:0,canPanLeft:!1,canPanRight:!1,canPanUp:!1,canPanDown:!1,canZoomIn:!1,canZoomOut:!1}}}function v(e,t){if(!e?.style||!t)return!1;try{return e.style.transform=function(e){return`matrix3d(${e.m11}, ${e.m12}, ${e.m13}, ${e.m14}, ${e.m21}, ${e.m22}, ${e.m23}, ${e.m24}, ${e.m31}, ${e.m32}, ${e.m33}, ${e.m34}, ${e.m41}, ${e.m42}, ${e.m43}, ${e.m44})`}(t),!0}catch(e){return console.warn("Transform application failed:",e),!1}}function y(e,t){try{if(t.enableTransition){window.__markupCanvasTransitionTimeout&&(clearTimeout(window.__markupCanvasTransitionTimeout),window.__markupCanvasTransitionTimeout=void 0);return function(e,t,n){const r=c.get(e);r&&clearTimeout(r);const o=window.setTimeout(()=>{n(),c.delete(e)},t);c.set(e,o)}("disableTransition",1e3*(t.transitionDuration??.2),()=>{e.style.transition="none",window.__markupCanvasTransitionTimeout=void 0}),!0}return!1}catch(e){return console.error("Failed to disable transitions:",e),!0}}function b(e,t,n){!function(e,t){try{return!!t.enableTransition&&(window.__markupCanvasTransitionTimeout&&(clearTimeout(window.__markupCanvasTransitionTimeout),window.__markupCanvasTransitionTimeout=void 0),e.style.transition=`transform ${t.transitionDuration}s linear`,!0)}catch(e){return console.error("Failed to enable transitions:",e),!1}}(e,t);try{return n()}finally{y(e,t)}}function w(t,n){if("static"===getComputedStyle(t).position&&(t.style.position="relative"),t.style.overflow="hidden",t.style.cursor="grab",t.style.overscrollBehavior="none",n){const e=g(n,"canvasBackgroundColor");t.style.backgroundColor=e}t.hasAttribute("tabindex")||t.setAttribute("tabindex","0"),function(e){const t=e.getBoundingClientRect(),n=getComputedStyle(e);0===t.height&&"auto"===n.height&&console.error("MarkupCanvas: Container height is 0. Please set a height on your container element using CSS.","Examples: height: 100vh, height: 500px, or use flexbox/grid layout.",e),0===t.width&&"auto"===n.width&&console.error("MarkupCanvas: Container width is 0. Please set a width on your container element using CSS.","Examples: width: 100vw, width: 800px, or use flexbox/grid layout.",e)}(t),t.classList.contains(e)||t.classList.add(e)}function x(e,t){if(!e?.appendChild)return console.error("Invalid container element provided to createCanvas"),null;try{w(e,t);const{transformLayer:n,contentLayer:a}=r(e,t);t.enableAcceleration&&function(e){try{return e.style.transform=e.style.transform||"translateZ(0)",e.style.backfaceVisibility="hidden",!0}catch(e){return console.error("Failed to enable hardware acceleration:",e),!1}}(n);const c=t.enableRulers?-t.rulerSize:0,d={scale:1,translateX:c,translateY:c};v(n,s(d.scale,d.translateX,d.translateY));return{container:e,transformLayer:n,contentLayer:a,config:t,transform:d,getBounds:function(){return p(this)},updateTransform:function(e){this.transform={...this.transform,...e};const t=s(this.transform.scale,this.transform.translateX,this.transform.translateY),n=v(this.transformLayer,t);return u(this.config,"onTransformUpdate",()=>{this.config.onTransformUpdate(this.transform)}),n},reset:function(){return this.updateTransform({scale:1,translateX:0,translateY:0})},handleResize:function(){return!0},setZoom:function(e){const t=l(this.config,t=>t(e));return this.updateTransform({scale:t})},canvasToContent:function(e,t){return o(e,t,s(this.transform.scale,this.transform.translateX,this.transform.translateY))},zoomToPoint:function(e,t,n){return b(this.transformLayer,this.config,()=>{const r=i(e,t,this.transform,n/this.transform.scale,this.config);return this.updateTransform(r)})},resetView:function(){return b(this.transformLayer,this.config,()=>h(this,this.config.rulerSize,e=>{const t={scale:1,translateX:-1*e,translateY:-1*e};return this.updateTransform(t)}))},zoomToFitContent:function(){return b(this.transformLayer,this.config,()=>{const e=this.getBounds(),t=e.width/this.config.width,n=e.height/this.config.height,r=l(this.config,e=>e(.9*Math.min(t,n))),o=this.config.width*r,a=this.config.height*r,s=(e.width-o)/2,i=(e.height-a)/2;return this.updateTransform({scale:r,translateX:s,translateY:i})})}}}catch(e){return console.error("Failed to create canvas:",e),null}}function C(e={}){const t={...f,...e};return("number"!=typeof t.width||t.width<=0)&&(console.warn("Invalid width, using default"),t.width=f.width),("number"!=typeof t.height||t.height<=0)&&(console.warn("Invalid height, using default"),t.height=f.height),("number"!=typeof t.zoomSpeed||t.zoomSpeed<=0)&&(console.warn("Invalid zoomSpeed, using default"),t.zoomSpeed=f.zoomSpeed),("number"!=typeof t.minZoom||t.minZoom<=0)&&(console.warn("Invalid minZoom, using default"),t.minZoom=f.minZoom),("number"!=typeof t.maxZoom||t.maxZoom<=t.minZoom)&&(console.warn("Invalid maxZoom, using default"),t.maxZoom=f.maxZoom),("number"!=typeof t.keyboardPanStep||t.keyboardPanStep<=0)&&(console.warn("Invalid keyboardPanStep, using default"),t.keyboardPanStep=f.keyboardPanStep),("number"!=typeof t.keyboardFastMultiplier||t.keyboardFastMultiplier<=0)&&(console.warn("Invalid keyboardFastMultiplier, using default"),t.keyboardFastMultiplier=f.keyboardFastMultiplier),("number"!=typeof t.clickZoomLevel||t.clickZoomLevel<=0)&&(console.warn("Invalid clickZoomLevel, using default"),t.clickZoomLevel=f.clickZoomLevel),("number"!=typeof t.rulerFontSize||t.rulerFontSize<=0)&&(console.warn("Invalid rulerFontSize, using default"),t.rulerFontSize=f.rulerFontSize),("number"!=typeof t.rulerSize||t.rulerSize<=0)&&(console.warn("Invalid rulerSize, using default"),t.rulerSize=f.rulerSize),t}class k{constructor(){this.listeners=new Map}setEmitInterceptor(e){this.emitInterceptor=e}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){const n=this.listeners.get(e);n&&n.delete(t)}emit(e,t){this.emitInterceptor?.(e,t);const n=this.listeners.get(e);n&&n.forEach(n=>{try{n(t)}catch(t){console.error(`Error in event handler for "${String(e)}":`,t)}})}removeAllListeners(){this.listeners.clear()}}const T=300,S=5;function D(e,t){if(!e?.getBounds)return t;try{const n=e.getBounds(),r=n.width*n.height;return t*(r/2073600)**1}catch(e){return console.warn("Failed to calculate adaptive zoom speed, using base speed:",e),t}}function M(e,t,n,r,o){n?t.requireSpaceForMouseDrag?e.container.style.cursor=r?"grab":"default":e.container.style.cursor=o?"grabbing":"grab":e.container.style.cursor="default"}function z(e,t,n,r,o){o.setIsDragging(!1),o.setDragButton(-1),M(e,t,n,r,!1)}function L(e,t,n,r,o,a,s,i,l,c){a&&e.button===s&&z(t,n,r,o,{setIsDragging:c.setIsDragging,setDragButton:c.setDragButton}),r&&0===e.button&&n.enableClickToZoom&&i>0&&function(e,t,n,r,o,a){const s=Date.now()-r,i=e.altKey,l=!n.requireOptionForClickZoom||i;if(s<T&&!o&&!a&&l){e.preventDefault();const r=t.container.getBoundingClientRect(),o=e.clientX-r.left,a=e.clientY-r.top,{clickX:s,clickY:i}=m(t,o,a,n.rulerSize,(e,t)=>({clickX:e,clickY:t})),l=t.canvasToContent(s,i),c=r.width/2,u=r.height/2,d=n.clickZoomLevel,h={scale:d,translateX:c-l.x*d,translateY:u-l.y*d};b(t.transformLayer,t.config,()=>{t.updateTransform(h)})}}(e,t,n,i,l,a),0===e.button&&function(e){e.setMouseDownTime(0),e.setHasDragged(!1)}({setMouseDownTime:c.setMouseDownTime,setHasDragged:c.setHasDragged})}function R(e,t,n=!0){let r=!0,o=!1,a=0,s=0,i=-1,l=!1,c=0,u=0,h=0,m=!1;const g={setIsDragging:e=>{o=e},setDragButton:e=>{i=e},setIsSpacePressed:e=>{l=e},setMouseDownTime:e=>{c=e},setMouseDownX:e=>{u=e},setMouseDownY:e=>{h=e},setHasDragged:e=>{m=e},setLastMouseX:e=>{a=e},setLastMouseY:e=>{s=e}},f=n=>{!function(e,t,n,r,o,a){n.requireSpaceForMouseDrag&&" "===e.key&&(a.setIsSpacePressed(!0),M(t,n,r,!0,o))}(n,e,t,r,o,{setIsSpacePressed:g.setIsSpacePressed})},p=n=>{!function(e,t,n,r,o,a){n.requireSpaceForMouseDrag&&" "===e.key&&(a.setIsSpacePressed(!1),M(t,n,r,!1,o),o&&z(t,n,r,!1,{setIsDragging:a.setIsDragging,setDragButton:a.setDragButton}))}(n,e,t,r,o,{setIsSpacePressed:g.setIsSpacePressed,setIsDragging:g.setIsDragging,setDragButton:g.setDragButton})},v=n=>{!function(e,t,n,r,o,a){const s=0===e.button,i=1===e.button;if(s&&(a.setMouseDownTime(Date.now()),a.setMouseDownX(e.clientX),a.setMouseDownY(e.clientY),a.setHasDragged(!1)),!r)return;(!n.requireSpaceForMouseDrag||o)&&(s&&n.enableLeftDrag||i&&n.enableMiddleDrag)&&(e.preventDefault(),a.setDragButton(e.button),a.setLastMouseX(e.clientX),a.setLastMouseY(e.clientY),M(t,n,r,o,!1))}(n,e,t,r,l,g)},y=n=>{!function(e,t,n,r,o,a,s,i,l,c,u,h){if(s>0){const t=Math.abs(e.clientX-i),s=Math.abs(e.clientY-l);if(t>S||s>S){h.setHasDragged(!0);const e=!n.requireSpaceForMouseDrag||a;!o&&r&&e&&h.setIsDragging(!0)}}if(!o||!r)return;e.preventDefault(),d((...e)=>{const n=e[0];if(!o||!r)return;const a=n.clientX-c,s=n.clientY-u,i={translateX:t.transform.translateX+a,translateY:t.transform.translateY+s};t.updateTransform(i),h.setLastMouseX(n.clientX),h.setLastMouseY(n.clientY)})(e)}(n,e,t,r,o,l,c,u,h,a,s,{setHasDragged:g.setHasDragged,setIsDragging:g.setIsDragging,setLastMouseX:g.setLastMouseX,setLastMouseY:g.setLastMouseY})},b=n=>{L(n,e,t,r,l,o,i,c,m,{setIsDragging:g.setIsDragging,setDragButton:g.setDragButton,setMouseDownTime:g.setMouseDownTime,setHasDragged:g.setHasDragged})},w=()=>{!function(e,t,n,r,o,a){o&&z(e,t,n,r,a)}(e,t,r,l,o,{setIsDragging:g.setIsDragging,setDragButton:g.setDragButton})};e.container.addEventListener("mousedown",v),document.addEventListener("mousemove",y),document.addEventListener("mouseup",b),e.container.addEventListener("mouseleave",w),t.requireSpaceForMouseDrag&&(document.addEventListener("keydown",f),document.addEventListener("keyup",p)),M(e,t,r,l,o);const x=()=>{e.container.removeEventListener("mousedown",v),document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",b),e.container.removeEventListener("mouseleave",w),t.requireSpaceForMouseDrag&&(document.removeEventListener("keydown",f),document.removeEventListener("keyup",p))};return n?{cleanup:x,enable:()=>(r=!0,M(e,t,r,l,o),!0),disable:()=>(r=!1,o&&z(e,t,r,l,{setIsDragging:g.setIsDragging,setDragButton:g.setDragButton}),M(e,t,r,l,o),!0),isEnabled:()=>r}:x}function E(e){const t=t=>{const n=t.data;if("markup-canvas"!==n.source)return;const r=e.config.name||"markupCanvas";if(n.canvasName!==r)return;const o=n.action,a=n.args||[];try{if("zoomIn"===o)e.zoomIn(a[0]);else if("zoomOut"===o)e.zoomOut(a[0]);else if("setZoom"===o){const t=a[0];if("number"!=typeof t||t<=0)throw new Error(`Invalid zoom level: ${t}. Must be a positive number.`);e.setZoom(t)}else if("resetZoom"===o)e.resetZoom();else if("panLeft"===o)e.panLeft(a[0]);else if("panRight"===o)e.panRight(a[0]);else if("panUp"===o)e.panUp(a[0]);else if("panDown"===o)e.panDown(a[0]);else if("fitToScreen"===o)e.fitToScreen();else if("centerContent"===o)e.centerContent();else if("scrollToPoint"===o)e.scrollToPoint(a[0],a[1]);else if("resetView"===o)e.resetView();else if("toggleRulers"===o)e.toggleRulers();else if("showRulers"===o)e.showRulers();else if("hideRulers"===o)e.hideRulers();else if("toggleGrid"===o)e.toggleGrid();else if("showGrid"===o)e.showGrid();else if("hideGrid"===o)e.hideGrid();else if("updateThemeMode"===o){const t=a[0];if("light"!==t&&"dark"!==t)throw new Error(`Invalid theme mode: ${t}`);e.updateThemeMode(t)}else{if("toggleThemeMode"!==o)throw new Error(`Unknown action: ${o}`);{const t="light"===e.getConfig().themeMode?"dark":"light";e.updateThemeMode(t)}}}catch(e){!function(e,t,n){window.postMessage({source:"markup-canvas-error",canvasName:e,action:t,error:n,timestamp:Date.now()},"*")}(r,o,e instanceof Error?e.message:String(e))}};return"undefined"!=typeof window&&window.addEventListener("message",t),()=>{"undefined"!=typeof window&&window.removeEventListener("message",t)}}function Y(e,t){return{x:(e.clientX+t.clientX)/2,y:(e.clientY+t.clientY)/2}}function X(e,t){const n=e.clientX-t.clientX,r=e.clientY-t.clientY;return Math.sqrt(n*n+r*r)}function B(e,t,n,r){const o=i(n,r,e.transform,t,e.config);return e.updateTransform(o)}function P(e,t,n){e.preventDefault();const r=Array.from(e.touches);d((...e)=>{const r=e[0];if(1===r.length){if(1===n.touches.length){const e=r[0].clientX-n.touches[0].clientX,o=r[0].clientY-n.touches[0].clientY,a={translateX:t.transform.translateX+e,translateY:t.transform.translateY+o};t.updateTransform(a)}}else if(2===r.length){const e=X(r[0],r[1]),o=Y(r[0],r[1]);if(n.lastDistance>0){const r=e/n.lastDistance,a=t.container.getBoundingClientRect();let s=o.x-a.left,i=o.y-a.top;const l=function(e,t,n,r){return h(e,t,e=>{const t={...n,x:n.x-e,y:n.y-e};return r(t)})}(t,t.config.rulerSize,{x:s,y:i},e=>e);s=l.x,i=l.y,B(t,r,s,i)}n.lastDistance=e,n.lastCenter=o}n.touches=r})(r)}function F(e){const t={touches:[],lastDistance:0,lastCenter:{}},n=e=>{!function(e,t){e.preventDefault(),t.touches=Array.from(e.touches),2===t.touches.length&&(t.lastDistance=X(t.touches[0],t.touches[1]),t.lastCenter=Y(t.touches[0],t.touches[1]))}(e,t)},r=n=>{P(n,e,t)},o=e=>{!function(e,t){t.touches=Array.from(e.touches),t.touches.length<2&&(t.lastDistance=0)}(e,t)};return e.container.addEventListener("touchstart",n,{passive:!1}),e.container.addEventListener("touchmove",r,{passive:!1}),e.container.addEventListener("touchend",o,{passive:!1}),()=>{e.container.removeEventListener("touchstart",n),e.container.removeEventListener("touchmove",r),e.container.removeEventListener("touchend",o)}}function $(e){const t=e.ctrlKey||e.metaKey,n=[0===e.deltaMode,Math.abs(e.deltaY)<50,e.deltaY%1!=0,Math.abs(e.deltaX)>0&&Math.abs(e.deltaY)>0].filter(Boolean).length>=2;return{isTrackpad:n,isMouseWheel:!n,isTrackpadScroll:n&&!t,isTrackpadPinch:n&&t,isZoomGesture:t}}function I(e,t){const n=(e=>d((...t)=>{const n=t[0];if(!n||!e?.updateTransform)return!1;try{const t=e.transform,r=1,o=n.deltaX*r,a=n.deltaY*r,s={scale:t.scale,translateX:t.translateX-o,translateY:t.translateY-a};return y(e.transformLayer,e.config),e.updateTransform(s)}catch(e){return console.error("Error handling trackpad pan:",e),!1}}))(e),r=r=>$(r).isTrackpadScroll?n(r):function(e,t,n){if(!e||"number"!=typeof e.deltaY)return console.warn("Invalid wheel event provided"),!1;if(!t?.updateTransform)return console.warn("Invalid canvas provided to handleWheelEvent"),!1;try{e.preventDefault();const r=t.container.getBoundingClientRect(),o=e.clientX-r.left,a=e.clientY-r.top,{mouseX:s,mouseY:i}=m(t,o,a,n.rulerSize,(e,t)=>({mouseX:e,mouseY:t})),l=n.zoomSpeed,c=$(e);if(!c.isZoomGesture)return!1;let u=n.enableAdaptiveSpeed?D(t,l):l;if(c.isTrackpadPinch){const e=.05*n.zoomSpeed;u=n.enableAdaptiveSpeed?D(t,e):e}return B(t,(e.deltaY<0?1:-1)>0?1+u:1/(1+u),s,i)}catch(e){return console.error("Error handling wheel event:",e),!1}}(r,e,t);return e.container.addEventListener("wheel",r,{passive:!1}),()=>{e.container.removeEventListener("wheel",r)}}function Z(e){try{const t=e.getBounds();return{x:t.width/2,y:t.height/2}}catch(e){return console.warn("Failed to calculate viewport center:",e),{x:0,y:0}}}const O=100,A=1e3,_=1001,G=4,H=4,N=100,V=100,K=20,q=200;function U(e,t){const n=function(e){const t=document.createElement("div");return t.className="canvas-ruler horizontal-ruler",t.style.cssText=`\n\tposition: absolute;\n\ttop: 0;\n\tleft: ${e.rulerSize}px;\n\tright: 0;\n\theight: ${e.rulerSize}px;\n\tbackground: var(--ruler-background-color);\n\tborder-bottom: 1px solid var(--ruler-border-color);\n\tborder-right: 1px solid var(--ruler-border-color);\n\tz-index: ${A};\n\tpointer-events: none;\n\tfont-family: ${e.rulerFontFamily};\n\tfont-size: ${e.rulerFontSize}px;\n\tcolor: var(--ruler-text-color);\n\toverflow: hidden;\n `,t}(t),r=function(e){const t=document.createElement("div");return t.className="canvas-ruler vertical-ruler",t.style.cssText=`\n\tposition: absolute;\n\ttop: ${e.rulerSize}px;\n\tleft: 0;\n\tbottom: 0;\n\twidth: ${e.rulerSize}px;\n\tbackground: var(--ruler-background-color);\n\tborder-right: 1px solid var(--ruler-border-color);\n\tborder-bottom: 1px solid var(--ruler-border-color);\n\tz-index: ${A};\n\tpointer-events: none;\n\tfont-family: ${e.rulerFontFamily};\n\tfont-size: ${e.rulerFontSize}px;\n\tcolor: var(--ruler-text-color);\n\toverflow: hidden;\n `,t}(t),o=function(e){const t=document.createElement("div");return t.className="canvas-ruler corner-box",t.style.cssText=`\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\twidth: ${e.rulerSize}px;\n\t\theight: ${e.rulerSize}px;\n\t\tbackground: var(--ruler-background-color);\n\t\tborder-right: 1px solid var(--ruler-border-color);\n\t\tborder-bottom: 1px solid var(--ruler-border-color);\n\t\tz-index: ${_};\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-family: ${e.rulerFontFamily};\n\t\tfont-size: ${e.rulerFontSize-2}px;\n\t\tcolor: var(--ruler-text-color);\n\t\tpointer-events: none;\n\t`,t.textContent=e.rulerUnits,t}(t),a=t.enableGrid?function(e){const t=document.createElement("div");return t.className="canvas-ruler grid-overlay",t.style.cssText=`\n\t\tposition: absolute;\n\t\ttop: ${e.rulerSize}px;\n\t\tleft: ${e.rulerSize}px;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: ${O};\n\t\tbackground-image: \n\t\t\tlinear-gradient(var(--grid-color) 1px, transparent 1px),\n\t\t\tlinear-gradient(90deg, var(--grid-color) 1px, transparent 1px);\n\t\tbackground-size: 100px 100px;\n\t\topacity: 0.5;\n\t`,t}(t):void 0;return e.appendChild(n),e.appendChild(r),e.appendChild(o),a&&e.appendChild(a),{horizontalRuler:n,verticalRuler:r,cornerBox:o,gridOverlay:a}}function W(e,t){const n=e/Math.max(5,Math.min(20,t/50)),r=10**Math.floor(Math.log10(n)),o=n/r;let a;return a=o<=1?1:o<=2?2:o<=5?5:10,a*r}function j(e,t,n,r,o){const a=document.createElement("div");a.className="tick",a.style.cssText=`\n\t\tposition: absolute;\n\t\tleft: ${n}px;\n\t\tbottom: 0;\n\t\twidth: 1px;\n\t\theight: ${G}px;\n\t\tbackground: var(--ruler-tick-color);\n\t`,e.appendChild(a);if(t%N===0){const r=document.createElement("div");r.style.cssText=`\n\t\t\tposition: absolute;\n\t\t\tleft: ${n}px;\n\t\t\tbottom: ${G+2}px;\n\t\t\tfont-size: ${o.rulerFontSize}px;\n\t\t\tline-height: 1;\n\t\t\tcolor: var(--ruler-text-color);\n\t\t\twhite-space: nowrap;\n\t\t\tpointer-events: none;\n\t\t`,r.textContent=Math.round(t).toString(),e.appendChild(r)}}function J(e,t,n,r,o){const a=document.createElement("div");a.className="tick",a.style.cssText=`\n\t\tposition: absolute;\n\t\ttop: ${n}px;\n\t\tright: 0;\n\t\twidth: ${H}px;\n\t\theight: 1px;\n\t\tbackground: var(--ruler-tick-color);\n\t`,e.appendChild(a);if(t%N===0){const r=document.createElement("div");r.style.cssText=`\n\t\t\tposition: absolute;\n\t\t\ttop: ${n-6}px;\n\t\t\tright: ${H+6}px;\n\t\t\tfont-size: ${o.rulerFontSize}px;\n\t\t\tline-height: 1;\n\t\t\tcolor: var(--ruler-text-color);\n\t\t\twhite-space: nowrap;\n\t\t\tpointer-events: none;\n\t\t\ttransform: rotate(-90deg);\n\t\t\ttransform-origin: right center;\n\t\t`,r.textContent=Math.round(t).toString(),e.appendChild(r)}}function Q(e,t,n,r,o){const a=e.getBounds(),s=a.scale||1,i=a.translateX||0,l=a.translateY||0,c=a.width-o.rulerSize,u=a.height-o.rulerSize,d=-i/s,h=-l/s,m=h+u/s;!function(e,t,n,r,o,a){const s=r,i=W(n-t,s),l=document.createDocumentFragment(),c=Math.floor(t/i)*i,u=Math.ceil(n/i)*i;for(let e=c;e<=u;e+=i){const n=(e-t)*o;n>=-50&&n<=s+50&&j(l,e,n,0,a)}e.innerHTML="",e.appendChild(l)}(t,d,d+c/s,c,s,o),function(e,t,n,r,o,a){const s=r,i=W(n-t,s),l=document.createDocumentFragment(),c=Math.floor(t/i)*i,u=Math.ceil(n/i)*i;for(let e=c;e<=u;e+=i){const n=(e-t)*o;n>=-50&&n<=s+50&&J(l,e,n,0,a)}e.innerHTML="",e.appendChild(l)}(n,h,m,u,s,o),r&&function(e,t,n,r){let o=V*t;for(;o<K;)o*=2;for(;o>q;)o/=2;e.style.backgroundSize=`${o}px ${o}px`,e.style.backgroundPosition=`${n%o}px ${r%o}px`}(r,s,i,l)}function ee(e,t){const n=g(t,"rulerBackgroundColor"),r=g(t,"rulerBorderColor"),o=g(t,"rulerTextColor"),a=g(t,"rulerTickColor"),s=g(t,"gridColor");e.horizontalRuler&&(e.horizontalRuler.style.setProperty("--ruler-background-color",n),e.horizontalRuler.style.setProperty("--ruler-border-color",r),e.horizontalRuler.style.setProperty("--ruler-text-color",o),e.horizontalRuler.style.setProperty("--ruler-tick-color",a)),e.verticalRuler&&(e.verticalRuler.style.setProperty("--ruler-background-color",n),e.verticalRuler.style.setProperty("--ruler-border-color",r),e.verticalRuler.style.setProperty("--ruler-text-color",o),e.verticalRuler.style.setProperty("--ruler-tick-color",a)),e.cornerBox&&(e.cornerBox.style.setProperty("--ruler-background-color",n),e.cornerBox.style.setProperty("--ruler-border-color",r),e.cornerBox.style.setProperty("--ruler-text-color",o)),e.gridOverlay&&e.gridOverlay.style.setProperty("--grid-color",s)}function te(e,t){if(!e?.container)return console.error("Invalid canvas provided to createRulers"),null;let n,r=null,o=!1;const a=()=>{!o&&n.horizontalRuler&&n.verticalRuler&&Q(e,n.horizontalRuler,n.verticalRuler,n.gridOverlay,t)};try{return n=U(e.container,t),r=function(e,t){const n=d(t),r=e.updateTransform;e.updateTransform=function(e){const t=r.call(this,e);return n(),t};const o=d(t);return window.addEventListener("resize",o),()=>{window.removeEventListener("resize",o),e.updateTransform=r,n.cleanup(),o.cleanup()}}(e,a),ee(n,t),a(),t.showRulers||(n.horizontalRuler.style.display="none",n.verticalRuler.style.display="none",n.cornerBox.style.display="none"),!t.showGrid&&n.gridOverlay&&(n.gridOverlay.style.display="none"),{horizontalRuler:n.horizontalRuler,verticalRuler:n.verticalRuler,cornerBox:n.cornerBox,gridOverlay:n.gridOverlay,update:a,updateTheme:e=>{o||ee(n,e)},show:()=>{n.horizontalRuler&&(n.horizontalRuler.style.display="block"),n.verticalRuler&&(n.verticalRuler.style.display="block"),n.cornerBox&&(n.cornerBox.style.display="flex"),n.gridOverlay&&(n.gridOverlay.style.display="block")},hide:()=>{n.horizontalRuler&&(n.horizontalRuler.style.display="none"),n.verticalRuler&&(n.verticalRuler.style.display="none"),n.cornerBox&&(n.cornerBox.style.display="none"),n.gridOverlay&&(n.gridOverlay.style.display="none")},toggleGrid:()=>{if(n.gridOverlay){const e="none"!==n.gridOverlay.style.display;n.gridOverlay.style.display=e?"none":"block"}},destroy:()=>{o=!0,r&&r(),n.horizontalRuler?.parentNode&&n.horizontalRuler.parentNode.removeChild(n.horizontalRuler),n.verticalRuler?.parentNode&&n.verticalRuler.parentNode.removeChild(n.verticalRuler),n.cornerBox?.parentNode&&n.cornerBox.parentNode.removeChild(n.cornerBox),n.gridOverlay?.parentNode&&n.gridOverlay.parentNode.removeChild(n.gridOverlay)}}}catch(e){return console.error("Failed to create rulers:",e),null}}return class{constructor(e,t={}){if(this.cleanupFunctions=[],this.rulers=null,this.dragSetup=null,this._isReady=!1,this.listen=new k,this.postMessageCleanup=null,!e)throw new Error("Container element is required");this.config=C(t);const n=x(e,this.config);if(!n)throw new Error("Failed to create canvas");this.baseCanvas=n,this.config.bindToWindow&&(this.listen.setEmitInterceptor((e,t)=>{this.broadcastEvent(e,t)}),this.setupGlobalBinding(),this.config.enablePostMessageAPI&&(this.postMessageCleanup=E(this))),this.setupEventHandlers(),this._isReady=!0,this.listen.emit("ready",this)}setupGlobalBinding(){if("undefined"==typeof window)return;const e=this.config.name||"markupCanvas",t=window;t[e]=this,t.__markupCanvasInstances||(t.__markupCanvasInstances=new Map),t.__markupCanvasInstances.set(e,this)}cleanupGlobalBinding(){if("undefined"==typeof window)return;const e=this.config.name||"markupCanvas",t=window;delete t[e],t.__markupCanvasInstances&&t.__markupCanvasInstances.delete(e)}broadcastEvent(e,t){if("undefined"==typeof window)return;let n=t;"ready"===e&&(n={ready:!0}),window.postMessage({source:"markup-canvas",event:e,data:n,timestamp:Date.now(),canvasName:this.config.name},"*"),window.parent&&window.parent.postMessage({source:"markup-canvas",event:e,data:n,timestamp:Date.now(),canvasName:this.config.name},"*")}setupEventHandlers(){try{u(this.config,"enableZoom",()=>{const e=I(this,this.config);this.cleanupFunctions.push(e)}),(this.config.enablePan||this.config.enableClickToZoom)&&(this.dragSetup=R(this,this.config,!0),this.cleanupFunctions.push(this.dragSetup.cleanup)),u(this.config,"enableKeyboard",()=>{const e=function(e,t){function n(n){if(!(n instanceof KeyboardEvent))return;if("canvas"===t.bindKeyboardEventsTo&&document.activeElement!==e.container)return;let r=!1;const o={};switch(n.key){case"ArrowLeft":o.translateX=e.transform.translateX+t.keyboardPanStep,r=!0;break;case"ArrowRight":o.translateX=e.transform.translateX-t.keyboardPanStep,r=!0;break;case"ArrowUp":o.translateY=e.transform.translateY+t.keyboardPanStep,r=!0;break;case"ArrowDown":o.translateY=e.transform.translateY-t.keyboardPanStep,r=!0;break;case"=":case"+":{const n=t.enableAdaptiveSpeed?D(e,t.keyboardZoomStep):t.keyboardZoomStep;e.zoomIn(n),r=!0}break;case"-":{const n=t.enableAdaptiveSpeed?D(e,t.keyboardZoomStep):t.keyboardZoomStep;e.zoomOut(n),r=!0}break;case"0":n.ctrlKey?(e.resetView&&e.resetView(),r=!0):(n.metaKey||n.ctrlKey)&&(e.resetViewToCenter&&e.resetViewToCenter(),r=!0);break;case"g":case"G":n.shiftKey&&e.toggleGrid&&e.toggleGrid(),r=n.shiftKey;break;case"r":case"R":!n.shiftKey||n.metaKey||n.ctrlKey||n.altKey||!e.toggleRulers||(e.toggleRulers(),r=!0)}r&&(n.preventDefault(),Object.keys(o).length>0&&e.updateTransform(o))}const r="canvas"===t.bindKeyboardEventsTo?e.container:document;return r.addEventListener("keydown",n),()=>{r.removeEventListener("keydown",n)}}(this,this.config);this.cleanupFunctions.push(e)}),u(this.config,"enableTouch",()=>{const e=F(this);this.cleanupFunctions.push(e)}),u(this.config,"enableRulers",()=>{this.rulers=te(this.baseCanvas,this.config),this.cleanupFunctions.push(()=>{this.rulers&&this.rulers.destroy()})})}catch(e){throw console.error("Failed to set up event handlers:",e),this.cleanup(),e}}get container(){return this.baseCanvas.container}get transformLayer(){return this.baseCanvas.transformLayer}get contentLayer(){return this.baseCanvas.contentLayer}get transform(){return this.baseCanvas.transform}get isReady(){return this._isReady}get isTransforming(){return this.dragSetup?.isEnabled()||!1}get visibleBounds(){return this.getVisibleArea()}getBounds(){return this.baseCanvas.getBounds()}updateTransform(e){const t=this.baseCanvas.updateTransform(e);return t&&this.emitTransformEvents(),t}emitTransformEvents(){const e=this.baseCanvas.transform;this.listen.emit("transform",e),this.listen.emit("zoom",e.scale),this.listen.emit("pan",{x:e.translateX,y:e.translateY})}reset(){return this.baseCanvas.reset()}handleResize(){return this.baseCanvas.handleResize()}setZoom(e){return b(this.transformLayer,this.config,()=>l(this.config,t=>{const n={scale:t(e)};return this.updateTransform(n)}))}canvasToContent(e,t){return this.baseCanvas.canvasToContent(e,t)}zoomToPoint(e,t,n){return b(this.transformLayer,this.config,()=>{const r=this.baseCanvas.zoomToPoint(e,t,n);return r&&this.emitTransformEvents(),r})}resetView(){const e=!!this.baseCanvas.resetView&&this.baseCanvas.resetView();return e&&this.emitTransformEvents(),e}resetViewToCenter(){return b(this.transformLayer,this.config,()=>l(this.config,e=>{const t=e(1),n=Z(this),r=this.zoomToPoint(n.x,n.y,t);return r&&this.emitTransformEvents(),r}))}zoomToFitContent(){return b(this.transformLayer,this.config,()=>{const e=this.baseCanvas.zoomToFitContent();return e&&this.emitTransformEvents(),e})}panLeft(e){const t=e??this.config.keyboardPanStep,n={translateX:this.baseCanvas.transform.translateX+t};return this.updateTransform(n)}panRight(e){const t=e??this.config.keyboardPanStep,n={translateX:this.baseCanvas.transform.translateX-t};return this.updateTransform(n)}panUp(e){const t=e??this.config.keyboardPanStep,n={translateY:this.baseCanvas.transform.translateY+t};return this.updateTransform(n)}panDown(e){const t=e??this.config.keyboardPanStep,n={translateY:this.baseCanvas.transform.translateY-t};return this.updateTransform(n)}zoomIn(e=.5){return b(this.transformLayer,this.config,()=>l(this.config,t=>{const n=t(this.baseCanvas.transform.scale*(1+e)),r=Z(this);return this.zoomToPoint(r.x,r.y,n)}))}zoomOut(e=.5){return b(this.transformLayer,this.config,()=>l(this.config,t=>{const n=t(this.baseCanvas.transform.scale*(1-e)),r=Z(this);return this.zoomToPoint(r.x,r.y,n)}))}resetZoom(){return this.resetViewToCenter()}enableMouseDrag(){return this.dragSetup?.enable()??!1}disableMouseDrag(){return this.dragSetup?.disable()??!1}isMouseDragEnabled(){return this.dragSetup?.isEnabled()??!1}toggleGrid(){return!!this.rulers?.toggleGrid&&(this.rulers.toggleGrid(),!0)}showGrid(){return!!this.rulers?.gridOverlay&&(this.rulers.gridOverlay.style.display="block",!0)}hideGrid(){return!!this.rulers?.gridOverlay&&(this.rulers.gridOverlay.style.display="none",!0)}isGridVisible(){return!!this.rulers?.gridOverlay&&"none"!==this.rulers.gridOverlay.style.display}toggleRulers(){if(this.rulers){return this.areRulersVisible()?this.rulers.hide():this.rulers.show(),!0}return!1}showRulers(){return!!this.rulers&&(this.rulers.show(),!0)}hideRulers(){return!!this.rulers&&(this.rulers.hide(),!0)}areRulersVisible(){return!!this.rulers?.horizontalRuler&&"none"!==this.rulers.horizontalRuler.style.display}centerContent(){return b(this.transformLayer,this.config,()=>{const e=this.baseCanvas.getBounds(),t=(e.width-e.contentWidth*this.baseCanvas.transform.scale)/2,n=(e.height-e.contentHeight*this.baseCanvas.transform.scale)/2;return this.updateTransform({translateX:t,translateY:n})})}fitToScreen(){return b(this.transformLayer,this.config,()=>{const e=this.baseCanvas.zoomToFitContent();return e&&this.emitTransformEvents(),e})}getVisibleArea(){return this.baseCanvas.getBounds().visibleArea}isPointVisible(e,t){const n=this.getVisibleArea();return e>=n.x&&e<=n.x+n.width&&t>=n.y&&t<=n.y+n.height}scrollToPoint(e,t){return b(this.transformLayer,this.config,()=>{const n=this.baseCanvas.getBounds(),r=n.width/2,o=n.height/2,a=r-e*this.baseCanvas.transform.scale,s=o-t*this.baseCanvas.transform.scale;return this.updateTransform({translateX:a,translateY:s})})}getConfig(){return{...this.config}}updateConfig(e){this.config=C({...this.config,...e})}updateThemeMode(e){const t={...this.config,themeMode:e};this.config=C(t);const n=g(this.config,"canvasBackgroundColor");this.baseCanvas.container.style.backgroundColor=n,this.rulers&&this.rulers.updateTheme(this.config)}cleanup(){this.cleanupGlobalBinding(),this.postMessageCleanup&&(this.postMessageCleanup(),this.postMessageCleanup=null),this.cleanupFunctions.forEach(e=>{try{e()}catch(e){console.warn("Error during cleanup:",e)}}),this.cleanupFunctions=[],this.removeAllListeners()}on(e,t){this.listen.on(e,t)}off(e,t){this.listen.off(e,t)}emit(e,t){this.listen.emit(e,t)}removeAllListeners(){this.listen.removeAllListeners()}destroy(){this.cleanup(),window.__markupCanvasTransitionTimeout&&clearTimeout(window.__markupCanvasTransitionTimeout)}}});
@@ -62,6 +62,7 @@ export interface Canvas extends BaseCanvas {
62
62
  zoomIn: (factor?: number) => boolean;
63
63
  zoomOut: (factor?: number) => boolean;
64
64
  resetZoom: (duration?: number) => boolean;
65
+ resetViewToCenter: () => boolean;
65
66
  enableMouseDrag: () => boolean;
66
67
  disableMouseDrag: () => boolean;
67
68
  isMouseDragEnabled: () => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markup-canvas/core",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "High-performance canvas-like container with pan and zoom capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/markup-canvas.cjs.js",
@@ -2,6 +2,7 @@ import { createCanvas } from "@/lib/canvas/index.js";
2
2
  import { createMarkupCanvasConfig } from "@/lib/config/createMarkupCanvasConfig.js";
3
3
  import { EventEmitter } from "@/lib/events/EventEmitter.js";
4
4
  import { setupKeyboardEvents, setupMouseEvents, setupPostMessageEvents, setupTouchEvents, setupWheelEvents } from "@/lib/events/index.js";
5
+ import { getViewportCenter } from "@/lib/events/utils/getViewportCenter.js";
5
6
  import { getThemeValue, withClampedZoom, withFeatureEnabled } from "@/lib/helpers/index.js";
6
7
  import { createRulers } from "@/lib/rulers/index.js";
7
8
  import { withTransition } from "@/lib/transition/withTransition.js";
@@ -260,12 +261,25 @@ export class MarkupCanvas implements Canvas {
260
261
  }
261
262
 
262
263
  resetView(): boolean {
264
+ const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
265
+ if (result) {
266
+ this.emitTransformEvents();
267
+ }
268
+ return result;
269
+ }
270
+
271
+ resetViewToCenter(): boolean {
263
272
  return withTransition(this.transformLayer, this.config, () => {
264
- const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
265
- if (result) {
266
- this.emitTransformEvents();
267
- }
268
- return result;
273
+ return withClampedZoom(this.config, (clamp) => {
274
+ const newScale = clamp(1.0);
275
+
276
+ const center = getViewportCenter(this);
277
+ const result = this.zoomToPoint(center.x, center.y, newScale);
278
+ if (result) {
279
+ this.emitTransformEvents();
280
+ }
281
+ return result;
282
+ });
269
283
  });
270
284
  }
271
285
 
@@ -313,32 +327,32 @@ export class MarkupCanvas implements Canvas {
313
327
  }
314
328
 
315
329
  // Zoom methods
316
- zoomIn(factor: number = 0.1): boolean {
330
+ zoomIn(factor: number = 0.5): boolean {
317
331
  return withTransition(this.transformLayer, this.config, () => {
318
332
  return withClampedZoom(this.config, (clamp) => {
319
333
  const newScale = clamp(this.baseCanvas.transform.scale * (1 + factor));
320
- const newTransform: Partial<Transform> = {
321
- scale: newScale,
322
- };
323
- return this.updateTransform(newTransform);
334
+
335
+ // Get the center of the viewport
336
+ const center = getViewportCenter(this);
337
+ return this.zoomToPoint(center.x, center.y, newScale);
324
338
  });
325
339
  });
326
340
  }
327
341
 
328
- zoomOut(factor: number = 0.1): boolean {
342
+ zoomOut(factor: number = 0.5): boolean {
329
343
  return withTransition(this.transformLayer, this.config, () => {
330
344
  return withClampedZoom(this.config, (clamp) => {
331
345
  const newScale = clamp(this.baseCanvas.transform.scale * (1 - factor));
332
- const newTransform: Partial<Transform> = {
333
- scale: newScale,
334
- };
335
- return this.updateTransform(newTransform);
346
+
347
+ // Get the center of the viewport
348
+ const center = getViewportCenter(this);
349
+ return this.zoomToPoint(center.x, center.y, newScale);
336
350
  });
337
351
  });
338
352
  }
339
353
 
340
354
  resetZoom(): boolean {
341
- return this.resetView();
355
+ return this.resetViewToCenter();
342
356
  }
343
357
 
344
358
  // Mouse drag control methods
@@ -15,8 +15,8 @@ export const EDITOR_PRESET: Required<MarkupCanvasConfig> = {
15
15
  enableZoom: true,
16
16
  enablePan: true,
17
17
  enableTouch: true,
18
- enableKeyboard: false,
19
- bindKeyboardEventsTo: "canvas",
18
+ enableKeyboard: true,
19
+ bindKeyboardEventsTo: "document",
20
20
 
21
21
  // Zoom behavior
22
22
  zoomSpeed: 1.5,
@@ -1,51 +1,30 @@
1
1
  import { getAdaptiveZoomSpeed } from "@/lib/events/utils/getAdaptiveZoomSpeed.js";
2
- import { withRulerOffsets } from "@/lib/helpers/index.js";
3
- import { clampZoom } from "@/lib/matrix/clampZoom.js";
4
- import { getZoomToMouseTransform } from "@/lib/matrix/getZoomToMouseTransform.js";
5
2
  import type { Canvas, MarkupCanvasConfig, Transform } from "@/types/index.js";
6
3
 
7
4
  export function setupKeyboardEvents(canvas: Canvas, config: Required<MarkupCanvasConfig>): () => void {
8
- // Track mouse position
9
- let lastMouseX = 0;
10
- let lastMouseY = 0;
11
-
12
- function handleMouseMove(event: MouseEvent): void {
13
- const rect = canvas.container.getBoundingClientRect();
14
- const rawMouseX = event.clientX - rect.left;
15
- const rawMouseY = event.clientY - rect.top;
16
-
17
- withRulerOffsets(canvas, config.rulerSize, rawMouseX, rawMouseY, (adjustedX, adjustedY) => {
18
- lastMouseX = adjustedX;
19
- lastMouseY = adjustedY;
20
- });
21
- }
22
-
23
5
  function handleKeyDown(event: Event): void {
24
6
  if (!(event instanceof KeyboardEvent)) return;
25
7
 
26
8
  if (config.bindKeyboardEventsTo === "canvas" && document.activeElement !== canvas.container) return;
27
9
 
28
- const isFastPan = event.shiftKey;
29
- const panDistance = config.keyboardPanStep * (isFastPan ? config.keyboardFastMultiplier : 1);
30
-
31
10
  let handled = false;
32
11
  const newTransform: Partial<Transform> = {};
33
12
 
34
13
  switch (event.key) {
35
14
  case "ArrowLeft":
36
- newTransform.translateX = canvas.transform.translateX + panDistance;
15
+ newTransform.translateX = canvas.transform.translateX + config.keyboardPanStep;
37
16
  handled = true;
38
17
  break;
39
18
  case "ArrowRight":
40
- newTransform.translateX = canvas.transform.translateX - panDistance;
19
+ newTransform.translateX = canvas.transform.translateX - config.keyboardPanStep;
41
20
  handled = true;
42
21
  break;
43
22
  case "ArrowUp":
44
- newTransform.translateY = canvas.transform.translateY + panDistance;
23
+ newTransform.translateY = canvas.transform.translateY + config.keyboardPanStep;
45
24
  handled = true;
46
25
  break;
47
26
  case "ArrowDown":
48
- newTransform.translateY = canvas.transform.translateY - panDistance;
27
+ newTransform.translateY = canvas.transform.translateY - config.keyboardPanStep;
49
28
  handled = true;
50
29
  break;
51
30
  case "=":
@@ -54,7 +33,7 @@ export function setupKeyboardEvents(canvas: Canvas, config: Required<MarkupCanva
54
33
  const adaptiveZoomStep = config.enableAdaptiveSpeed
55
34
  ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
56
35
  : config.keyboardZoomStep;
57
- newTransform.scale = clampZoom(canvas.transform.scale * (1 + adaptiveZoomStep), config);
36
+ canvas.zoomIn(adaptiveZoomStep);
58
37
  handled = true;
59
38
  }
60
39
  break;
@@ -63,18 +42,20 @@ export function setupKeyboardEvents(canvas: Canvas, config: Required<MarkupCanva
63
42
  const adaptiveZoomStep = config.enableAdaptiveSpeed
64
43
  ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
65
44
  : config.keyboardZoomStep;
66
- newTransform.scale = clampZoom(canvas.transform.scale * (1 - adaptiveZoomStep), config);
45
+ canvas.zoomOut(adaptiveZoomStep);
67
46
  handled = true;
68
47
  }
69
48
  break;
70
49
  case "0":
71
- if (event.metaKey || event.ctrlKey) {
72
- const targetScale = 1.0;
73
- const zoomFactor = targetScale / canvas.transform.scale;
74
-
75
- const zoomTransform = getZoomToMouseTransform(lastMouseX, lastMouseY, canvas.transform, zoomFactor, config);
76
-
77
- Object.assign(newTransform, zoomTransform);
50
+ if (event.ctrlKey) {
51
+ if (canvas.resetView) {
52
+ canvas.resetView();
53
+ }
54
+ handled = true;
55
+ } else if (event.metaKey || event.ctrlKey) {
56
+ if (canvas.resetViewToCenter) {
57
+ canvas.resetViewToCenter();
58
+ }
78
59
  handled = true;
79
60
  }
80
61
  break;
@@ -105,10 +86,8 @@ export function setupKeyboardEvents(canvas: Canvas, config: Required<MarkupCanva
105
86
  const keyboardTarget = config.bindKeyboardEventsTo === "canvas" ? canvas.container : document;
106
87
 
107
88
  keyboardTarget.addEventListener("keydown", handleKeyDown);
108
- canvas.container.addEventListener("mousemove", handleMouseMove);
109
89
 
110
90
  return () => {
111
91
  keyboardTarget.removeEventListener("keydown", handleKeyDown);
112
- canvas.container.removeEventListener("mousemove", handleMouseMove);
113
92
  };
114
93
  }
@@ -0,0 +1,14 @@
1
+ import type { Canvas } from "@/types/index.js";
2
+
3
+ export function getViewportCenter(canvas: Canvas): { x: number; y: number } {
4
+ try {
5
+ const bounds = canvas.getBounds();
6
+ return {
7
+ x: bounds.width / 2,
8
+ y: bounds.height / 2,
9
+ };
10
+ } catch (error) {
11
+ console.warn("Failed to calculate viewport center:", error);
12
+ return { x: 0, y: 0 };
13
+ }
14
+ }
@@ -66,6 +66,7 @@ export interface Canvas extends BaseCanvas {
66
66
  zoomIn: (factor?: number) => boolean;
67
67
  zoomOut: (factor?: number) => boolean;
68
68
  resetZoom: (duration?: number) => boolean;
69
+ resetViewToCenter: () => boolean;
69
70
  // Mouse drag control functions
70
71
  enableMouseDrag: () => boolean;
71
72
  disableMouseDrag: () => boolean;
@@ -1,2 +0,0 @@
1
- import type { Canvas, MarkupCanvasConfig } from "@/types/index.js";
2
- export declare function setupKeyboardEvents(canvas: Canvas, config: Required<MarkupCanvasConfig>): () => void;
@@ -1,115 +0,0 @@
1
- import { getAdaptiveZoomSpeed } from "@/lib/events/utils/getAdaptiveZoomSpeed.js";
2
- import { withRulerOffsets } from "@/lib/helpers/index.js";
3
- import { clampZoom } from "@/lib/matrix/clampZoom.js";
4
- import { getZoomToMouseTransform } from "@/lib/matrix/getZoomToMouseTransform.js";
5
- import type { Canvas, MarkupCanvasConfig, Transform } from "@/types/index.js";
6
-
7
- export function setupKeyboardEvents(canvas: Canvas, config: Required<MarkupCanvasConfig>): () => void {
8
- // Track mouse position
9
- let lastMouseX = 0;
10
- let lastMouseY = 0;
11
-
12
- function handleMouseMove(event: MouseEvent): void {
13
- const rect = canvas.container.getBoundingClientRect();
14
- const rawMouseX = event.clientX - rect.left;
15
- const rawMouseY = event.clientY - rect.top;
16
-
17
- // Use the new helper to adjust for ruler offset
18
- withRulerOffsets(canvas, config.rulerSize, rawMouseX, rawMouseY, (adjustedX, adjustedY) => {
19
- lastMouseX = adjustedX;
20
- lastMouseY = adjustedY;
21
- });
22
- }
23
-
24
- function handleKeyDown(event: Event): void {
25
- if (!(event instanceof KeyboardEvent)) return;
26
-
27
- if (config.bindKeyboardEventsTo === "canvas" && document.activeElement !== canvas.container) return;
28
-
29
- const isFastPan = event.shiftKey;
30
- const panDistance = config.keyboardPanStep * (isFastPan ? config.keyboardFastMultiplier : 1);
31
-
32
- let handled = false;
33
- const newTransform: Partial<Transform> = {};
34
-
35
- switch (event.key) {
36
- case "ArrowLeft":
37
- newTransform.translateX = canvas.transform.translateX + panDistance;
38
- handled = true;
39
- break;
40
- case "ArrowRight":
41
- newTransform.translateX = canvas.transform.translateX - panDistance;
42
- handled = true;
43
- break;
44
- case "ArrowUp":
45
- newTransform.translateY = canvas.transform.translateY + panDistance;
46
- handled = true;
47
- break;
48
- case "ArrowDown":
49
- newTransform.translateY = canvas.transform.translateY - panDistance;
50
- handled = true;
51
- break;
52
- case "=":
53
- case "+":
54
- {
55
- const adaptiveZoomStep = config.enableAdaptiveSpeed
56
- ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
57
- : config.keyboardZoomStep;
58
- newTransform.scale = clampZoom(canvas.transform.scale * (1 + adaptiveZoomStep), config);
59
- handled = true;
60
- }
61
- break;
62
- case "-":
63
- {
64
- const adaptiveZoomStep = config.enableAdaptiveSpeed
65
- ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
66
- : config.keyboardZoomStep;
67
- newTransform.scale = clampZoom(canvas.transform.scale * (1 - adaptiveZoomStep), config);
68
- handled = true;
69
- }
70
- break;
71
- case "0":
72
- if (event.metaKey || event.ctrlKey) {
73
- const targetScale = 1.0;
74
- const zoomFactor = targetScale / canvas.transform.scale;
75
-
76
- const zoomTransform = getZoomToMouseTransform(lastMouseX, lastMouseY, canvas.transform, zoomFactor, config);
77
-
78
- Object.assign(newTransform, zoomTransform);
79
- handled = true;
80
- }
81
- break;
82
- case "g":
83
- case "G":
84
- if (event.shiftKey && canvas.toggleGrid) {
85
- canvas.toggleGrid();
86
- }
87
- handled = event.shiftKey;
88
- break;
89
- case "r":
90
- case "R":
91
- if (event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey && canvas.toggleRulers) {
92
- canvas.toggleRulers();
93
- handled = true;
94
- }
95
- break;
96
- }
97
-
98
- if (handled) {
99
- event.preventDefault();
100
- if (Object.keys(newTransform).length > 0) {
101
- canvas.updateTransform(newTransform);
102
- }
103
- }
104
- }
105
-
106
- const keyboardTarget = config.bindKeyboardEventsTo ? canvas.container : document;
107
-
108
- keyboardTarget.addEventListener("keydown", handleKeyDown);
109
- canvas.container.addEventListener("mousemove", handleMouseMove);
110
-
111
- return () => {
112
- keyboardTarget.removeEventListener("keydown", handleKeyDown);
113
- canvas.container.removeEventListener("mousemove", handleMouseMove);
114
- };
115
- }